Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions changelog/unreleased/prompt-cache-and-stream-salvage.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
### English

- Preserve and normalize prompt cache token metrics (`cache_read_tokens`, `prompt_cache_hit_tokens`, and `prompt_tokens_details`) across Trae, Qoder, and WorkBuddy providers in both streaming and non-streaming responses. Originally submitted by [@longlixiao-ong](https://github.com/longlixiao-ong) in #293.
- Gracefully salvage upstream streams that finish with a terminal `finish_reason` before EOF without emitting literal `[DONE]`, preventing artificial 502 incomplete stream errors.
- Report prompt-cache token usage for Trae, Qoder, and WorkBuddy, streaming and non-streaming. Thanks [@longlixiao-ong](https://github.com/longlixiao-ong) (#293).
- Streams that end with a `finish_reason` but no `[DONE]` no longer return a 502.

### 中文

- 修复并标准化 Trae、Qoder 与 WorkBuddy 提供商在流式与非流式响应中的 Prompt 缓存 Token 指标(统一解析并透传 `cache_read_tokens`、`prompt_cache_hit_tokens` 与 `prompt_tokens_details`)。原稿由 [@longlixiao-ong](https://github.com/longlixiao-ong) 在 #293 提交。
- 容错处理上游流式响应在带有终态 `finish_reason` 正常结束但未显式发送 `[DONE]` 的边界情况,自动补齐结束帧,避免下游误判为 502 流异常中断。
- Trae、Qoder、WorkBuddy 流式与非流式均返回 Prompt 缓存 token 用量。感谢 [@longlixiao-ong](https://github.com/longlixiao-ong)(#293)。
- 上游带 `finish_reason` 结束但未发 `[DONE]` 的流不再报 502。
18 changes: 6 additions & 12 deletions changelog/unreleased/qoder-in-process-bridge.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
### English

- Qoder accounts can now run fully in-process from a PAT, with no per-account Node+WASM worker. A new `qoder-pat-v1` credential format stores a Personal Access Token; the new in-process `Bridge` (`internal/providers/qoder/bridge.go`) performs the PAT→jobToken exchange, proactive and forced token refresh, `/user/status` + OpenAPI `/api/v2/quota/usage` quota resolution, and `/model/list` catalog fetch — all via the `qauth` package ported from EchoPing07/Qoder-2API-Go (MIT, see `internal/providers/qoder/qauth/NOTICE`). Chat signing continues to use the existing in-process COSY client. The runtime no longer spawns a Node worker for PAT-backed accounts, eliminating the per-account WASM process, the liveness watchdog, and the restart ladder for them.
- Check-in (签到) now runs entirely in Go. The OpenAPI `/sash/api/v1/me/campaigns` list + `/claim` calls are made directly with the bridge's `securityOauthToken` and a machine identity derived deterministically from the PAT (no `runtime-info` child process). Token refresh before the call and force-refresh on a 401 are handled by the bridge. The worker `/admin/checkin` endpoint is now a legacy fallback for accounts still on the `qoder-native-v1` blob.
- Existing Qoder accounts that still carry the legacy `qoder-native-v1` CLI blob (no PAT) keep working via the worker path until the operator re-logs them with a PAT. The console's PAT login path (`LoginPAT`) now accepts a Qoder PAT and stores it in the `qoder-pat-v1` format, after which the account stops spawning a worker.
- Global-region check-in now authenticates the campaign list/claim calls with the OpenAPI bearer (`/api/v1/jobToken/exchange`), matching the region's token requirement; CN keeps using the COSY `securityOauthToken`. A 401 now also invalidates the cached OpenAPI bearer before the single force-refresh retry.
- Fixed Qoder in-process chat requests losing the model's thinking/reasoning stream. `BuildPlainChatBody` now fills `chat_context` the way the reference Qoder client does: `chat_context.text`, `chat_context.extra.modelConfig` (`key` + `is_reasoning`) and `chat_context.extra.originalContent` carry the latest user prompt and the routed model identity. The gateway resolves the thinking channel from that block, so an empty `extra` made it answer with content only. `source`/`version`/`session_type` were also aligned to the official client (`1` / `"3"` / `"qodercli"`). Reasoning now streams back as `reasoning_content` (and as Anthropic `thinking_delta` on `/v1/messages`).
- Fixed Qoder in-process catalog parsing and model discovery. `ModelInfos` previously expected legacy worker fields (`id`, `mapped_key`) and dropped every entry from raw gateway `/model/list` responses (which only supply `key` and `display_name`). Models are now derived from `key` and `display_name` via `CanonicalModelID`, restoring Qoder model routing for in-process accounts. `catalog_source` now also merges in-process Qoder catalogs, and `manager_probe` triggers background model discovery for prober-less in-process accounts on startup.
- Qoder PAT accounts now run fully in-process (no per-account Node worker), including token refresh, model catalog, quota, and check-in. (#313)
- Quota headline sums plan + add-on + org packs; reasoning streams again for in-process chat.
- Upgrade: existing Qoder accounts keep the worker path until you re-login them with a PAT.

### 中文

- Qoder 账号现在可以基于 PAT 完全进程内运行,不再为每个账号 spawn 一个 Node+WASM worker。新增 `qoder-pat-v1` 凭证格式存储 Personal Access Token;新增进程内 `Bridge`(`internal/providers/qoder/bridge.go`)负责 PAT→jobToken 交换、主动与强制 token 刷新、`/user/status` + OpenAPI `/api/v2/quota/usage` 配额解析、`/model/list` 目录拉取——全部经由从 EchoPing07/Qoder-2API-Go 移植的(MIT,见 `internal/providers/qoder/qauth/NOTICE`) `qauth` 包完成。chat 签名仍走既有进程内 COSY 客户端。运行时不再为 PAT 账号 spawn Node worker,从而消除每账号 WASM 进程、liveness 看门狗和重启阶梯。
- 签到(check-in)现在完全在 Go 里跑。OpenAPI 的 `/sash/api/v1/me/campaigns` 列表 + `/claim` 调用直接用 bridge 的 `securityOauthToken` 和从 PAT 确定性派生的机器身份发起(不再 spawn `runtime-info` 子进程)。调用前的 token 刷新和 401 时的强制刷新由 bridge 处理。worker 的 `/admin/checkin` 端点降级为仍是 `qoder-native-v1` blob 的老账号的 legacy fallback。
- 仍带着 legacy `qoder-native-v1` CLI blob(没有 PAT)的 Qoder 老账号在操作员用 PAT 重新登录前继续走 worker 路径。控制台的 PAT 登录路径(`LoginPAT`)现在接受 Qoder PAT 并以 `qoder-pat-v1` 格式存储,之后该账号就不再 spawn worker。
- 国际版签到改用 OpenAPI bearer(`/api/v1/jobToken/exchange`)来发起 campaign 列表/领取请求,符合该区的 token 要求;国内版继续使用 COSY 的 `securityOauthToken`。401 时现在会先清掉缓存的 OpenAPI bearer 再做一次强制刷新重试。
- 修复 Qoder 进程内 chat 请求丢失模型思考(reasoning)流的问题。`BuildPlainChatBody` 现在按官方 Qoder 客户端的方式填充 `chat_context`:`chat_context.text`、`chat_context.extra.modelConfig`(`key` + `is_reasoning`)与 `chat_context.extra.originalContent` 带上最新用户输入和路由到的模型标识。网关依据该块决定是否走思考通道,之前 `extra` 为空导致上游只回内容、不回思考。同时把 `source`/`version`/`session_type` 对齐官方客户端(`1` / `"3"` / `"qodercli"`)。现在思考过程会以 `reasoning_content` 流式返回(`/v1/messages` 入口则编码为 Anthropic `thinking_delta`)。
- 修复 Qoder 进程内模型目录解析与路由丢失问题。`ModelInfos` 之前仅兼容旧版 worker 的 `id` / `mapped_key` 字段,导致直连网关 `/model/list` 的原始数据(仅有 `key` 和 `display_name`)被当成空字段全部跳过,池中 Qoder 账号模型列表为空并报 `model_not_available`。现已自动兼容网关原生 `key` 与 `display_name` 派生 `CanonicalModelID`;`catalog_source` 也同步支持将无 worker 的进程内 Qoder 账号合并入展示目录,且 `manager_probe` 在探针刷新时对这类账号自动触发目录加载。
- Qoder PAT 账号改为完全进程内运行(不再每账号起 Node worker),token 刷新、模型目录、额度、签到均在进程内完成。(#313)
- 额度总览合计套餐 + 资源包 + 组织资源包;进程内对话恢复思考流输出。
- 升级提示:老 Qoder 账号需用 PAT 重新登录才会切到进程内模式,否则继续走 worker。
7 changes: 0 additions & 7 deletions changelog/unreleased/qoder-inprocess-quota-aggregate.md

This file was deleted.

10 changes: 4 additions & 6 deletions changelog/unreleased/qwenwork-provider.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
### English

- Add the QwenWork provider (`qwenwork`), an in-process adapter for the QwenWork desktop product. Two regions under one provider: `global` (qwenwork.ai, default) and `cn` (千问办公, qwenwork.cn). Browser login uses the single-use device authorization link; poll and token refresh run in-process, with refresh-token rotation stored automatically. Chat goes through the COSY-signed `agent_chat_generation` endpoint and the wrapped upstream SSE is relayed as OpenAI chunks, so `/v1/chat/completions`, `/v1/messages`, and `/v1/responses` all work, streaming and non-streaming, with multi-turn history and tool calls. The model catalog (`qwork-advanced`, `qwork-auto`, …) is fetched live from the upstream `qwork` list, and quota comes from the account-context API. Model-level errors ("Model is not available for this user") cool only that model; quota code 14018 maps to account quota. Credentials can also be imported from a qwenwork2api account export (`accessToken`/`refreshToken`). The CN region follows the desktop bundle configuration but has not yet been verified with a real CN account; international accounts are verified end-to-end.
- Console: new QwenWork International / 千问办公 国内版 account types in the add-account wizard. The authorization link is single-use: open it directly in a browser already signed in to the target account, and do not send it through chat tools. Models are addressable with the `qwenwork/` prefix (for example `qwenwork/qwork-advanced`).
- Protocol behavior was cross-checked against nostalgia296/qwenwork2api-makers (no license declared); the Go implementation is independent and copies no code.
- New QwenWork provider (`qwenwork/` prefix): browser login, chat on all three APIs with tools, live model list, and quota.
- CN region (千问办公) is not yet verified with a real account; Global is verified.

### 中文

- 新增 QwenWork 供应商(`qwenwork`):QwenWork(千问办公)桌面产品的进程内适配器。同一供应商下两个 region:`global`(qwenwork.ai,默认)与 `cn`(qwenwork.cn)。浏览器登录使用一次性的设备授权链接;轮询与令牌刷新都在进程内完成,refresh_token 轮换后自动保存。对话走 COSY 签名的 `agent_chat_generation` 端点,上游包装 SSE 转换为 OpenAI 分块转发,因此 `/v1/chat/completions`、`/v1/messages`、`/v1/responses` 流式与非流式均可用,支持多轮历史与工具调用。模型目录(`qwork-advanced`、`qwork-auto` 等)实时取自上游 `qwork` 列表,额度来自 account-context 接口。模型级错误("Model is not available for this user")只冷却该模型;额度错误码 14018 映射为账号额度。也支持从 qwenwork2api 的账号导出(`accessToken`/`refreshToken`)导入凭证。国内版按桌面端配置实现,但尚未用真实国内版账号验收;国际版已端到端验证。
- 控制台:添加账号向导新增 "QwenWork 国际版" 与 "千问办公 国内版" 两种类型。授权链接只能使用一次:请在已登录目标账号的浏览器窗口里直接打开,不要经聊天工具发送。模型可用 `qwenwork/` 前缀指定(例如 `qwenwork/qwork-advanced`)。
- 协议行为参考并核对了 nostalgia296/qwenwork2api-makers(未声明许可证);Go 实现为独立编写,未复制其代码。
- 新增 QwenWork(千问办公)供应商(`qwenwork/` 前缀):浏览器登录,三种 API 均可对话并支持工具调用,模型列表与额度实时获取。
- 国内版尚未用真实账号验证;国际版已验证。
10 changes: 4 additions & 6 deletions changelog/unreleased/region-qualified-model-prefix.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
### English

- Region-qualified model IDs: use `workbuddy-cn/glm-5.3-flash` or `workbuddy-gl/glm-5.3-flash` to route a request to CN or Global accounts respectively. Applies to all multi-region providers (WorkBuddy, Qoder).
- A bare provider prefix (`qoder/…`, `workbuddy/…`) keeps its existing behavior: no region is pinned and the pool auto-picks across regions, so international-only setups keep working unchanged. Bare model names with no prefix still auto-route across regions as before.
- New optional `-auto/` qualifier (`qoder-auto/…`, `workbuddy-auto/…`): an explicit spelling of the bare-prefix behavior — it pins the provider family while letting the pool pick the region, useful for cross-region load spreading within one provider. It reuses the pool's session-affinity latch so a route sticks to one region until it thins out.
- Pin a region with `workbuddy-cn/…` or `workbuddy-gl/…` (also Qoder); `-auto/` picks the region automatically.
- Bare `qoder/…`, `workbuddy/…`, and unprefixed models behave as before.

### 中文

- 区域限定模型 ID:使用 `workbuddy-cn/glm-5.3-flash` 或 `workbuddy-gl/glm-5.3-flash` 可将请求分别路由到 CN 或 Global 账户。适用于所有多区域 provider(WorkBuddy、Qoder)。
- 不带区域限定的供应商前缀(`qoder/…`、`workbuddy/…`)行为不变:不锁定区域,由池跨区域自动选择,只有国际账号的部署不受影响。不带任何前缀的裸模型名也仍按原逻辑跨区域自动路由。
- 新增可选的 `-auto/` 限定形式(`qoder-auto/…`、`workbuddy-auto/…`):与裸前缀行为一致的显式写法——固定供应商但不限定区域,由池自动选择区域,适合在单个 provider 内跨区域分摊负载。复用池的会话粘性机制,路由会粘在一个区域直到该区域候选不足才切换。
- 可用 `workbuddy-cn/…`、`workbuddy-gl/…` 指定区域(Qoder 同理);`-auto/` 由池自动选区域。
- 裸前缀 `qoder/…`、`workbuddy/…` 及无前缀模型行为不变。
10 changes: 4 additions & 6 deletions changelog/unreleased/runtime-quota-recovery.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
### English

- Auto-refresh stale exhausted quota: the maintenance loop now re-fetches quota every 5 minutes, but only for accounts whose cached snapshot still reports `Exceeded`. A snapshot can go stale when upstream credits are replenished out-of-band (billing reset, add-on purchase) or when a flaky quota endpoint leaves an old `Exceeded=true` in place, so the account stayed out of routing until an operator manually refreshed it. Healthy accounts are never touched by this sweep.
- Quota error instead of "no accounts available": when every account that can serve a model is out of quota, the request now returns `429 insufficient_quota` rather than a generic "no accounts available", so the client gets the real cause.
- Back off quota probing for genuinely exhausted accounts: the maintenance sweep still starts at 5 minutes, but each account that keeps reporting `Exceeded` now steps through 5m → 5m → 5m → 15m → 30m → 1h → 3h → 6h → 12h → 24h instead of being re-fetched every five minutes forever. Provider balances reset on a daily cadence, so a permanently empty account no longer costs ~288 pointless quota calls a day, settling at roughly one probe per day. The ladder resets to the fast end as soon as a snapshot reports a non-zero balance, and a failed probe keeps its level instead of escalating a transient outage into an hours-long wait.
- Accounts marked out of quota are re-checked automatically, with backoff from 5 minutes up to 24 hours.
- When every eligible account is out of quota, requests return `429 insufficient_quota` instead of "no accounts available".

### 中文

- 自动刷新 stale 的已耗尽配额:维护循环现在每 5 分钟重新拉取一次配额,但仅针对缓存快照仍为 `Exceeded` 的账号。快照可能因上游额度被外部恢复(计费重置、加购附加包)或配额接口抖动留下旧的 `Exceeded=true` 而变 stale,导致账号一直被路由排除,直到运维手动刷新才恢复。健康账号不会被此扫描触碰。
- 配额耗尽返回配额错误而非「无可用账户」:当能服务某模型的所有账号都配额耗尽时,请求现在返回 `429 insufficient_quota`,而不是笼统的「无可用账户」,让客户端拿到真实原因。
- 为真正耗尽的账号增加配额探测退避:维护扫描仍从 5 分钟起步,但连续报告 `Exceeded` 的账号会依次退避到 5m → 5m → 5m → 15m → 30m → 1h → 3h → 6h → 12h → 24h,而不是永远每 5 分钟重拉一次。供应商额度一般按天重置,长期为空的账号不会再每天产生约 288 次无效配额调用,最终稳定在每天约 1 次探测。一旦快照报告非零余额,退避立即回到最快档;探测失败则保持当前档位,不会把瞬时故障升级成数小时的等待。
- 额度耗尽的账号会自动复查,间隔从 5 分钟逐步退避到 24 小时。
- 所有可用账号都耗尽额度时,返回 `429 insufficient_quota`,而非「无可用账户」。
4 changes: 2 additions & 2 deletions changelog/unreleased/trae-login-checkin-fixes.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
### English

- Trae browser login now completes on its own. The loopback listener only parked the authorization code and waited for a console poll to redeem it, but the console's poll window is far shorter than a real browser login (an SMS login can take minutes), so the poll expired first and the code was never exchanged. The callback now exchanges the code and stores the credential as soon as the browser reaches the loopback listener, so closing the browser or missing the poll window no longer costs the login. A pasted callback URL still works as a fallback and now actually redeems the parked code; a callback URL carrying only `loginTraceID` is accepted for that path; and the single-use code is exchanged exactly once even when the callback, a poll, and a paste overlap.
- Trae browser login now completes on its own, even for slow SMS logins; pasting the callback URL still works as a fallback.

### 中文

- Trae 浏览器登录现在会自行完成。此前 loopback 监听只把授权码暂存下来,等待控制台轮询去换取令牌,但控制台的轮询窗口远短于真实浏览器登录(短信登录可能耗时数分钟),轮询先超时退出,授权码因此从未被换取。现在浏览器一到达 loopback 监听,回调就会立即换取授权码并写入凭据,关闭浏览器或错过轮询窗口都不再导致登录失败。粘贴回调 URL 仍可作为兜底,并且现在会真正换取已暂存的授权码;仅携带 `loginTraceID` 的回调 URL 也被接受用于该路径;即便回调、轮询与粘贴同时发生,单次使用的授权码也只会被换取一次。
- Trae 浏览器登录现在可自动完成,短信登录较慢也不会失败;粘贴回调 URL 仍可兜底。
Loading
Loading