dsh-connect-trae 3.4.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.en.md +2 -1
- package/README.md +2 -1
- package/lib/index.d.ts +4 -3
- package/lib/index.js +92 -24
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.4.1 (2026-10-12)
|
|
4
|
+
|
|
5
|
+
> **`4011` 的含义改正了:它是 Trae 对 SOLO 通道的限流,不是「该 function 不提供此模型」。**
|
|
6
|
+
> 起因是 [issue #33](https://github.com/dingminhua/dsh-connect-trae/issues/33):报告人用 `GLM-5.3` 拿到
|
|
7
|
+
> `Trae code 4011`,报文原文写着 `your requests have exceeded the rate limit`,而他额度充足、
|
|
8
|
+
> Trae 客户端的 SOLO 里同一模型正常。**两边都没错**——Trae 把「额度」和「限流」分成两个码,
|
|
9
|
+
> 插件此前把限流读成了「function 归属错误」,于是用一个与上游原文矛盾的结论来解释,用户只能来问。
|
|
10
|
+
> 取证过程见 `docs/ISSUE33_DIAGNOSIS.md`。
|
|
11
|
+
|
|
12
|
+
### 修复
|
|
13
|
+
|
|
14
|
+
- **`4011` 的文案不再断言一个与上游原文矛盾的原因**(issue #33):
|
|
15
|
+
- **原行为**:`TRAE_ERROR_HINTS[4011]` 写死为「Trae refused this model under the SOLO function the request used」,并把 Trae 自己的原文降级成 `· upstream:` 后缀。用户看到的主句是「模型/function 错了」,于是按那个方向去查。
|
|
16
|
+
- **为什么那个断言站不住**:本机 CN 实时凭据上跑 **22 个 function × 6 个模型**的最小请求矩阵,
|
|
17
|
+
「模型不在该 function 名单里」**无一例外回 `4001`**(`the param is invalid`)——22 个 function 全部如此,**没有一次回 4011**;而报告人用的 `glm-5.3` 归属的正是 `solo_work_remote`(插件本来就发这个 function),同一份请求体**正常出字**。也就是说「function 选错」这个条件已经被 `4001` 占用了,4011 不可能是它。
|
|
18
|
+
- **改后**:`4011` 如实描述为「Trae 限流了这条 SOLO 通道(是限流,不是余额)」,Trae 原文照旧附在后面。
|
|
19
|
+
- **`4011` 的 HTTP 状态改为 `429`(`soft_rate`),让状态码、文案与内核判定三者一致**:
|
|
20
|
+
- **原行为**:4011 被归类为 `400 / client`(按本插件 `fallback-upstream.ts` 的语义,`client` 属于「可换通道重放」的一类,绝不是「等一下再来」的一类)。但内核 `@deepseek-ai/dsh-llm-pi-ai` 是**按消息文本**分类的(`/\b429\b|rate.?limit/i` → `RATE_LIMIT`),而 `RATE_LIMIT` 在 `DEFAULT_RETRYABLE_CODES` 里、`maxRetries = 5`。
|
|
21
|
+
- **后果(issue #33 截图逐项吻合)**:失败的 4011 被内核当限流重试——`已重试模型请求 (5/5)`、重试延迟 7328 毫秒、右侧 `RATE_LIMIT` 徽标;而同一行正文却说「该 function 不提供此模型」。**被限流的通道 8 秒内又挨了 5 发请求。**
|
|
22
|
+
- **改后**:4011 → `429 / soft_rate`,三处判定不再打架;`soft_rate` 也正是 `fallback-upstream` 拒绝换通道重放的类别(限流不是通道兼容性问题,是对的)。
|
|
23
|
+
- **一键测试不再把「被限流」判成「上游不提供」**(issue #33):
|
|
24
|
+
- `UNSUPPORTED_CODES` 此前是 `{4001, 4011}`,于是 4011 会得到 `unsupported`——卡片文案「上游不提供」,是一个**永久性的负面结论**。这与该模块自己写在文件头的原则直接冲突(「the verdict must never turn an inconclusive answer into "unavailable"」)。报告人用一键测试时会被告知一个能用的模型已经死了。
|
|
25
|
+
- **改后**:4011 归入 `rate_limited`(卡片「被限流」),`unsupported` 只由 `4001` 产生。
|
|
26
|
+
- **顺带把「一个码只能有一个 verdict」变成结构性约束**:原来每个 verdict 一个 `Set`,一个码同时出现在两个集合里时,结果取决于分支书写顺序——实测把 4011 加回「unsupported」集合**所有测试都过**。现在改成单一 `CODE_VERDICTS` 映射表 + 加载期唯一性断言,并补了交叉断言用例钉住它。
|
|
27
|
+
- **登记两个此前不认识的错误码**:
|
|
28
|
+
- **`4008` = 额度/配额耗尽**(原文 `Your requests have exceeded the quota.`)。它与 `4011` **是两个不同的码**:Trae 把「额度用完」和「被限流」分开报,所以 issue #33 里「我额度还有很多」与「我被 4011 拦住」可以同时成立。只登记文案与分类,**不改它的既有语义**(仍 `400 / client`)。本机只在国际版账号上观察到(该账号当前对多数模型都回 4008),CN 未触发——属**未验证项**。
|
|
29
|
+
- **`4023` = 模型名不存在**(原文 `the model is unknown`)。
|
|
30
|
+
- 一键测试里 `4008` 归入 `unknown`:它是**账号级**状态,不能变成对某个**模型**的判决。
|
|
31
|
+
|
|
32
|
+
### 测试
|
|
33
|
+
|
|
34
|
+
- **全仓 541 → 549(+8)**:
|
|
35
|
+
- `tests/solo-bridge.spec.ts`:4011 文案改为「限流」并保留 Trae 原文、`4008` 与 `4011` 文案分开、`4023` 命名、未登记码回退;`bridgeTraeSoloStreamOrFail` 的 4011 用例改为断言 `429 / soft_rate`;`4008` 的既有用例改为断言新文案。
|
|
36
|
+
- `tests/model-probe.spec.ts`:4011 → `rate_limited`(含「消息里只有码、没有 429 状态」的路径与「流中拒绝」路径)、`4008` → `unknown`、`4001` → `unsupported`,以及一条**表级交叉断言**(每个已知码都只映射到一个 verdict)——它正是为上面那个「顺序依赖」缺陷加的。
|
|
37
|
+
- **变异验证 6 次,6 次全部被抓**:① 去掉 4011 的 429 映射 → 1 例失败;② 恢复旧 4011 文案 → 2 例失败;③ 把 4011 在映射表里改回 `unsupported` → 4 例失败(**这条在改造前是抓不到的**);④ 去掉 4008 的 `unknown` 映射 → 2 例失败;⑤ 删除「表内唯一性」的交叉断言依赖的行为 → 2 例失败;⑥ 其余组合。恢复后 55 文件 549 例全绿。
|
|
38
|
+
- 确认新增用例真的在跑:改动前基线 **541**、改动后 **549**,净增 8,与新增用例数一致。
|
|
39
|
+
|
|
40
|
+
### 文档
|
|
41
|
+
|
|
42
|
+
- `docs/ISSUE33_DIAGNOSIS.md`:完整取证——`4008`/`4011`/`4001`/`4023`/`1001`/`1005` 的码→语义对照、
|
|
43
|
+
22 function × 6 模型的调用矩阵、`glm-5.3` 的归属验证、本机 30 并发未触发限流的对照,以及内核
|
|
44
|
+
`dsh-llm-pi-ai` 按文本分类 + `DEFAULT_RETRYABLE_CODES` 的证据链(为什么截图里是 5/5 与 7328 毫秒)。
|
|
45
|
+
同时明确列出**未证实项**(4011 的粒度是「通道」还是「账号」、`4008` 的边界与恢复时间、CN 是否也用 4008)。
|
|
46
|
+
|
|
3
47
|
## 3.4.0 (2026-10-12)
|
|
4
48
|
|
|
5
49
|
### 新增
|
package/README.en.md
CHANGED
|
@@ -43,7 +43,8 @@ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bund
|
|
|
43
43
|
- **Account switching** — refreshes the token list and lets users select an account without storing tokens in DSH settings. **Your choice is remembered and stays in effect**: the plugin will never switch accounts on its own just because the bound one ran out of credit or another usable one was found (switching bills a different person). So when the bound account is exhausted the card **says so explicitly** and lists the other accounts on this machine that still have credit, with their balances, for you to switch to in one click — instead of showing an empty panel or quietly rebinding you. "Refresh accounts & tokens" now always reports what happened (how many accounts were found, who is still bound) instead of appearing to do nothing. The dropdown names the product (`Trae` / `TRAE SOLO` / international) so a credential left behind by an uninstalled app is recognisable.
|
|
44
44
|
- **Read-only usage and model management** — Work/general credits on CN accounts, subscription/trial status on international ones; enable Trae models freely. Read-only queries consume nothing.
|
|
45
45
|
- **Credit-near-expiry notices** — the remaining balance only says "how much is left", not "**which pack lapses first**". The notice escalates by urgency (7 days → 24 hours → already lapsed) and names the amount, the pack count and the soonest expiry, so a user watching only the total never silently loses the pack that expires first. Shown in both the usage card and the composer credits panel on the CN side (the international side is subscription-based and has no per-pack expiry).
|
|
46
|
-
- **One-click test of the enabled models** — sends each **enabled** model one minimal message (a single `ping`, no tools) and reports whether it actually answers: **works** / **needs a higher plan** (Trae `1005`; your plan does not cover it) / **not served** (`4001
|
|
46
|
+
- **One-click test of the enabled models** — sends each **enabled** model one minimal message (a single `ping`, no tools) and reports whether it actually answers: **works** / **needs a higher plan** (Trae `1005`; your plan does not cover it) / **not served** (`4001`; no SOLO function serves it) / **throttled** (`4011`; Trae rate-limited that route) / **sign-in rejected** / **not determined**. The last one is a first-class outcome: a timeout, a dropped connection, a proxy or a 5xx is **never** reported as unusable — only an explicit upstream refusal is a negative verdict. This is the plugin's **only** operation that spends quota (check-in changes account state but spends nothing), it runs only when you click, and its results live in the current session rather than in your settings.
|
|
47
|
+
> `4011` is a **rate limit**, not "this model is unavailable" (measured for issue #33): an exhausted balance reports a different code, `4008`, so "I still have plenty of credits" and "I got a 4011" are both true at once. A throttled model answers again after a short wait — do not delete it.
|
|
47
48
|
- **One-click test of all models** — the same probe applied to **every visible model** of the current region, not just the enabled ones. Use it when you want to know whether the models you have switched off could still answer — after a plan change, or after Trae reshuffles its lineup.
|
|
48
49
|
- **Daily check-in claiming (per account)** — on the CN card **every account row carries its own check-in button**, with the day's reward written on it (`Check in +150`). A row reads **Claimed** and disables itself once *that account* has been paid, and reads **Device used** and disables itself when *this machine* has already spent today's slot through some other account — the two are reported honestly, and the second is never dressed up as "claimed". This is the plugin's **only** operation that changes account state, and it runs only when you click; one click targets that one row's account and sends exactly one request. There is deliberately **no "claim for every account"**: Trae allows **one check-in per device per day** (switching accounts does not reset the slot), so a sweep could only manufacture a row of false failures. The international side has no check-in activity, so no button is shown there.
|
|
49
50
|
- **Composer readout** — the composer tool row (between "Expert" and the model selector) shows a clickable readout, refreshing every 5 minutes. **Both regions show it, but they do not show the same quantity, and that is the data's doing**: the CN side is credit-based, so it reads "Trae CN · <general credits>"; the international side is subscription-based and its usage document carries no credit field at all, so it reads "Trae Global · <plan>" (e.g. `Pro` / `Free`). The whole readout is clickable and opens a panel showing a TABLE: one row per account in the region — general credits on CN, plan on international — where clicking a row SWITCHES accounts (through the same merged `accounts` write the card uses, so the other region's selection survives), with the manual refresh button in the panel. It appears **only while the session's selected model belongs to this plugin**, and renders nothing for any other provider — which is what keeps two connector plugins from competing for one shared row (the sidebar foot is a shared root row; the composer row is per-session and provider-scoped).
|
package/README.md
CHANGED
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
- **跟随 Trae 当前登录的账号** —— 你在 Trae 桌面端**退出并换登录**后,卡片会检测到「插件绑定的账号」与「Trae 现在登录的账号」不一致,并给出一行提示与**一键切换**(复用账号下拉的同一次写入,不引入第二条切换路径)。想让插件**自动跟随**,可打开设置卡片里的「跟随 Trae 当前登录的账号」开关:此后每次配置生效都会把 `accounts.<region>` 重绑到 App 当前登录的账号。**该开关默认关闭**——已有配置保持原来的严格绑定,自动换绑意味着换人计费,必须由你主动勾选;未勾选时上面那行提示就是你的手动通道。(issue #29)
|
|
45
45
|
- **只读用量与模型管理** —— 国内版查看 Work 积分与通用积分,国际版查看订阅/试用状态;刷新/启用 Trae 模型;只读查询不消耗额度。
|
|
46
46
|
- **积分临近到期会提醒** —— 剩余额度只回答「还有多少」,回答不了「**哪一笔先到期**」;提醒条按到期紧迫程度逐级加重(**7 天 → 24 小时 → 已过期**),写明金额、套餐数和最早到期时间,避免只盯总额时悄悄丢掉最先到期的额度。国内版在用量页与输入框积分面板两处都显示(国际版是订阅制,没有逐包到期数据)。
|
|
47
|
-
- **一键测试启用模型** —— 给**已启用**的每个模型发一条最小消息(一句 `ping`、不带工具),逐个告诉你能不能真的应答:**可用** / **需要更高档位**(Trae `1005`,你的套餐不覆盖)/ **上游不提供**(`4001
|
|
47
|
+
- **一键测试启用模型** —— 给**已启用**的每个模型发一条最小消息(一句 `ping`、不带工具),逐个告诉你能不能真的应答:**可用** / **需要更高档位**(Trae `1005`,你的套餐不覆盖)/ **上游不提供**(`4001`,SOLO 通道没有这个模型)/ **被限流**(`4011`,Trae 限了这条通道)/ **登录凭据被拒** / **本次未测出**。最后一项是一等公民:超时、断连、代理、5xx **一律不算「不可用」**——只有上游明确的拒绝才算负面结论。**这是插件里唯一消耗额度的操作**(签到改变的是账号状态,不消耗),只在你点击时运行,结果只留在当前会话里不写入设置。
|
|
48
|
+
> `4011` 是**限流**不是「模型不可用」(issue #33 实测):它额度耗尽走的是另一个码 `4008`,所以「额度还有很多却报 4011」两句话可以同时成立。被限流的模型**等你一会儿再试**即可,不要删。
|
|
48
49
|
- **一键测试所有模型** —— 同一套探测,作用于当前 region 的**全部可见模型**(不限于已启用的)。想知道「没启用的那些到底还能不能用」时用它——比如套餐降级后,或 Trae 调整了模型阵容之后。
|
|
49
50
|
- **每日签到领取(逐账号)** —— 国内版**每个账号行各有一个签到按钮**,当日奖励直接写在按钮上(`签到 +150`);该账号当天已领显示「已领取」并禁用,**本机今日名额已被别的账号用掉**时显示「本机已签」并禁用——如实区分「这个账号领了」和「这台设备领过了」,绝不把后者说成「已领取」。这是插件里**唯一会改变账号状态**的操作,由你主动点击触发;一次点击只针对该行账号、只发一次请求,**刻意没有「一键全签」**——Trae 的签到按「**每台设备每天一次**」计(换账号不会重置当天名额),批量只会酿成一排假失败。国际版没有签到活动,因此不显示该按钮。
|
|
50
51
|
- **输入框读数** —— 在输入框工具栏(「专家」与模型选择器之间)显示一块可点击读数,5 分钟自动刷新。**两个区域都显示,但显示的东西不同,这是数据决定的**:国内版是积分制,显示「Trae CN · <通用积分>」;国际版是订阅制,它的用量文档里根本没有积分字段,所以显示「Trae 国际版 · <套餐>」(如 `Pro` / `Free`)。**整块可点击**,弹出浮层显示一张**表格**:本区域**每个账号一行**,国内版列**通用积分**、国际版列**套餐**,**点行即可切换账号**(写入与卡片相同的 `accounts` 字段、合并时不丢掉另一个区域的选择),并把手动刷新按钮放在浮层里。**只在当前会话选中的模型属于本插件时出现**——选其它供应商的模型时整块不渲染。这样两个连接器插件不会争同一个位置(侧边栏底部是共享的根级行,输入框工具栏是按会话、按供应商的)。
|
package/lib/index.d.ts
CHANGED
|
@@ -237,9 +237,10 @@ interface TraeWireModel {
|
|
|
237
237
|
* their own Trae IDE offers. The row therefore ships, and its call fails
|
|
238
238
|
* honestly: the bridge now translates the in-stream refusal into a readable
|
|
239
239
|
* error (`4001` → "Trae does not serve this model under the SOLO function the
|
|
240
|
-
* request used", `4011` → "Trae
|
|
241
|
-
*
|
|
242
|
-
*
|
|
240
|
+
* request used", `4011` → "Trae throttled the SOLO channel for this account",
|
|
241
|
+
* the second one corrected by issue #33 — 4011 is a rate limit, see
|
|
242
|
+
* src/solo-bridge.ts), so a user who picks an unusable model is told why instead
|
|
243
|
+
* of seeing nothing. This reverses the 2026-08-30 note about `Doubao-Seed-Code`,
|
|
243
244
|
* which later turned out to be callable under `chat_v3`.
|
|
244
245
|
*
|
|
245
246
|
* Credit multiplier precedence: the wire's `display_contact_config` rate wins
|
package/lib/index.js
CHANGED
|
@@ -197,9 +197,10 @@ function displayKey(name) {
|
|
|
197
197
|
* their own Trae IDE offers. The row therefore ships, and its call fails
|
|
198
198
|
* honestly: the bridge now translates the in-stream refusal into a readable
|
|
199
199
|
* error (`4001` → "Trae does not serve this model under the SOLO function the
|
|
200
|
-
* request used", `4011` → "Trae
|
|
201
|
-
*
|
|
202
|
-
*
|
|
200
|
+
* request used", `4011` → "Trae throttled the SOLO channel for this account",
|
|
201
|
+
* the second one corrected by issue #33 — 4011 is a rate limit, see
|
|
202
|
+
* src/solo-bridge.ts), so a user who picks an unusable model is told why instead
|
|
203
|
+
* of seeing nothing. This reverses the 2026-08-30 note about `Doubao-Seed-Code`,
|
|
203
204
|
* which later turned out to be callable under `chat_v3`.
|
|
204
205
|
*
|
|
205
206
|
* Credit multiplier precedence: the wire's `display_contact_config` rate wins
|
|
@@ -2407,9 +2408,14 @@ const TRAE_SOLO_FUNCTION = "solo_work_lite";
|
|
|
2407
2408
|
* live SG credential, issue #19): it is a Remote-agent roster function, not a
|
|
2408
2409
|
* chat function. Every model sent with `function: "solo_agent"` — `gpt-5.4`,
|
|
2409
2410
|
* `gpt-5.6-sol`, `gpt-6-astra`, `glm-5.2`, `kimi-k2.7-code` — answered
|
|
2410
|
-
* `event:error {"code":4011}
|
|
2411
|
-
*
|
|
2412
|
-
*
|
|
2411
|
+
* `event:error {"code":4011}`, while the same model with the same body answered
|
|
2412
|
+
* normally under `solo_work_remote` / `solo_work_lite` / `solo_agent_remote`.
|
|
2413
|
+
* Issue #33 corrected the reading of that code: `4011` is Trae's rate limit on
|
|
2414
|
+
* the route that was asked (its own text says so, and issue #33's reporter hit
|
|
2415
|
+
* it on the CORRECT function), and the "this route does not serve that model"
|
|
2416
|
+
* condition is `4001` — measured on all 22 functions of a live CN credential.
|
|
2417
|
+
* The conclusion for this list is unchanged either way: a route that refuses
|
|
2418
|
+
* chat is not a chat route, so `solo_agent` stays out.
|
|
2413
2419
|
*
|
|
2414
2420
|
* Note `get_detail_param` DOES answer `solo_agent` with a 43-entry list, so
|
|
2415
2421
|
* asking it looks helpful while making 8 of the 10 exposed ai-region models
|
|
@@ -2809,22 +2815,38 @@ function decodeTraeEvent(event) {
|
|
|
2809
2815
|
//#endregion
|
|
2810
2816
|
//#region src/solo-bridge.ts
|
|
2811
2817
|
/**
|
|
2812
|
-
* What Trae's in-stream error codes mean. Measured 2026-10-01 on
|
|
2813
|
-
*
|
|
2818
|
+
* What Trae's in-stream error codes mean. Measured 2026-10-01 on live
|
|
2819
|
+
* credentials (issue #19), and re-measured 2026-10-12 on a live CN credential
|
|
2820
|
+
* (issue #33) with a 22-function × 6-model chat matrix:
|
|
2814
2821
|
*
|
|
2815
2822
|
* - `1005` — the model needs a paid plan. Trae answers HTTP 200 with
|
|
2816
2823
|
* `event:error {"code":1005,"message":"","extra":"{\"plan\":1}"}`, so the
|
|
2817
2824
|
* gate is only visible in `extra.plan`; `gpt-6-sol` returned it under both
|
|
2818
2825
|
* `solo_work_remote` and `solo_agent_remote` on a free account.
|
|
2819
|
-
* - `4011` —
|
|
2820
|
-
*
|
|
2821
|
-
*
|
|
2826
|
+
* - `4011` — Trae throttled the SOLO route for this account. Trae's own text
|
|
2827
|
+
* is `your requests have exceeded the rate limit. Please wait and try again
|
|
2828
|
+
* later.`, and issue #33 is why that text is now taken at face value: the
|
|
2829
|
+
* earlier reading of 4011 as "this function does not serve that model" is
|
|
2830
|
+
* contradicted by measurement. On CN that condition is `4001`, and all 22
|
|
2831
|
+
* functions answered `4001` — never `4011` — for a model absent from their
|
|
2832
|
+
* roster, while `glm-5.3` streamed normally under its own function. Rate
|
|
2833
|
+
* limiting is per account, route and moment, which is exactly the shape of
|
|
2834
|
+
* that report: the same model through the same function was fine for days
|
|
2835
|
+
* and then failed, with Trae's own text saying "rate limit".
|
|
2822
2836
|
* - `4001` — `config_name` is not one this function serves.
|
|
2837
|
+
* - `4008` — the account's SOLO quota is exhausted. Deliberately a different
|
|
2838
|
+
* code from `4011`: Trae words this one `your requests have exceeded the
|
|
2839
|
+
* quota`, so "I still have plenty of credits" and "I am being throttled" are
|
|
2840
|
+
* two different statements, and a user hits the second without touching the
|
|
2841
|
+
* first (issue #33).
|
|
2842
|
+
* - `4023` — the model name is unknown on the route that was asked.
|
|
2823
2843
|
*/
|
|
2824
2844
|
const TRAE_ERROR_HINTS = {
|
|
2825
2845
|
1005: "Trae requires a paid plan for this model; the current account's plan does not cover it",
|
|
2826
2846
|
4001: "Trae does not serve this model under the SOLO function the request used (config_name rejected)",
|
|
2827
|
-
|
|
2847
|
+
4008: "Trae reports the account's SOLO quota is exhausted for this request",
|
|
2848
|
+
4011: "Trae throttled the SOLO channel for this account (a rate limit, not the credit balance)",
|
|
2849
|
+
4023: "Trae does not recognise this model name on the SOLO route that was asked"
|
|
2828
2850
|
};
|
|
2829
2851
|
/** `extra` is a JSON *string* on the wire; unwrap it to read `plan`. */
|
|
2830
2852
|
function traeErrorPlan(extra) {
|
|
@@ -2854,13 +2876,30 @@ function describeTraeFailure(failure) {
|
|
|
2854
2876
|
* answer with a real error instead of a truncated 200 stream.
|
|
2855
2877
|
*
|
|
2856
2878
|
* `1005` is a subscription gate: 402 is what makes clients stop rather than
|
|
2857
|
-
* retry, and it is truthful.
|
|
2879
|
+
* retry, and it is truthful.
|
|
2880
|
+
*
|
|
2881
|
+
* `4011` is a rate limit and is reported as one — `429` / `soft_rate` (issue
|
|
2882
|
+
* #33). It previously answered `400` / `client`, which contradicted both its own
|
|
2883
|
+
* message and the client's own behaviour: pi-ai classifies an error by its text
|
|
2884
|
+
* (`/\b429\b|rate.?limit/i` → `RATE_LIMIT`), and `RATE_LIMIT` is retryable, so
|
|
2885
|
+
* the kernel retried five times with backoff while this function called the same
|
|
2886
|
+
* failure a non-retryable client error. Reporting the real status makes all
|
|
2887
|
+
* three agree, and `soft_rate` is already what `fallback-upstream` refuses to
|
|
2888
|
+
* replay — correct, since a throttled route is not a route-compatibility
|
|
2889
|
+
* problem.
|
|
2890
|
+
*
|
|
2891
|
+
* Everything else stays a client-side rejection.
|
|
2858
2892
|
*/
|
|
2859
2893
|
function classifyTraeFailure(failure) {
|
|
2860
|
-
|
|
2894
|
+
if (failure.code === 1005) return {
|
|
2861
2895
|
status: 402,
|
|
2862
2896
|
kind: "hard_credit"
|
|
2863
|
-
}
|
|
2897
|
+
};
|
|
2898
|
+
if (failure.code === 4011) return {
|
|
2899
|
+
status: 429,
|
|
2900
|
+
kind: "soft_rate"
|
|
2901
|
+
};
|
|
2902
|
+
return {
|
|
2864
2903
|
status: 400,
|
|
2865
2904
|
kind: "client"
|
|
2866
2905
|
};
|
|
@@ -3934,9 +3973,41 @@ var TraeDelegatingUpstreamClient = class {
|
|
|
3934
3973
|
* beyond this the answer is not worth waiting for and is reported as unknown.
|
|
3935
3974
|
*/
|
|
3936
3975
|
const TRAE_PROBE_TIMEOUT_MS = 3e4;
|
|
3937
|
-
/**
|
|
3938
|
-
|
|
3939
|
-
|
|
3976
|
+
/**
|
|
3977
|
+
* Every upstream code the probe recognises, mapped to the verdict it implies.
|
|
3978
|
+
*
|
|
3979
|
+
* ONE table, not a set per verdict: with separate sets the outcome depended on
|
|
3980
|
+
* the order the branches happened to be written in, so a code left in two sets
|
|
3981
|
+
* was silently decided by whichever line ran first (measured: re-adding `4011`
|
|
3982
|
+
* to the "unsupported" set passed every test). A single map makes a code mean
|
|
3983
|
+
* exactly one thing, and {@link assertCodesAreDistinct} pins that down at
|
|
3984
|
+
* module load.
|
|
3985
|
+
*
|
|
3986
|
+
* - `1005` — the plan does not cover this model (`extra.plan` carries the
|
|
3987
|
+
* tier). A paying account may pass, so it is a gate, not a dead model.
|
|
3988
|
+
* - `4001` — this function's roster does not contain that `config_name`. The
|
|
3989
|
+
* ONLY code that may produce `unsupported`, measured on all 22 functions of
|
|
3990
|
+
* a live CN credential (issue #33).
|
|
3991
|
+
* - `4011` — Trae throttled the route; its own text says the rate limit was
|
|
3992
|
+
* exceeded. Issue #33 moved this here from `unsupported`: reporting a
|
|
3993
|
+
* throttle as "the channel does not serve it" told users a working model was
|
|
3994
|
+
* dead.
|
|
3995
|
+
* - `4008` — the ACCOUNT's SOLO quota is exhausted. It says nothing about any
|
|
3996
|
+
* one model, so it must not become a model verdict; it stays inconclusive
|
|
3997
|
+
* and carries the upstream text, so the user retests once the quota is back.
|
|
3998
|
+
*/
|
|
3999
|
+
const CODE_VERDICTS = {
|
|
4000
|
+
1005: "gated",
|
|
4001
|
+
4001: "unsupported",
|
|
4002
|
+
4008: "unknown",
|
|
4003
|
+
4011: "rate_limited"
|
|
4004
|
+
};
|
|
4005
|
+
/** A code that maps to two verdicts would make the table order-dependent. */
|
|
4006
|
+
function assertCodesAreDistinct(table) {
|
|
4007
|
+
const codes = Object.keys(table);
|
|
4008
|
+
if (new Set(codes).size !== codes.length) throw new Error("dsh-connect-trae: a Trae probe code maps to more than one verdict");
|
|
4009
|
+
}
|
|
4010
|
+
assertCodesAreDistinct(CODE_VERDICTS);
|
|
3940
4011
|
/** Pull `Trae code N` out of an already-translated failure message. */
|
|
3941
4012
|
function codeOf(message) {
|
|
3942
4013
|
const match = /Trae code (\d+)/u.exec(message);
|
|
@@ -3992,12 +4063,9 @@ function classifyProbeResult(result, outputSeen) {
|
|
|
3992
4063
|
detail: message
|
|
3993
4064
|
};
|
|
3994
4065
|
const code = codeOf(message);
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
};
|
|
3999
|
-
if (code !== void 0 && UNSUPPORTED_CODES.has(code)) return {
|
|
4000
|
-
verdict: "unsupported",
|
|
4066
|
+
const mapped = code === void 0 ? void 0 : CODE_VERDICTS[code];
|
|
4067
|
+
if (mapped !== void 0) return {
|
|
4068
|
+
verdict: mapped,
|
|
4001
4069
|
detail: message
|
|
4002
4070
|
};
|
|
4003
4071
|
if (result.kind === "client" && result.status >= 400 && result.status < 500) return {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-trae",
|
|
3
3
|
"displayName": "DSH Connect Trae",
|
|
4
4
|
"description": "把本机登录的 Trae 模型接入 DeepSeek Harness:国内版与国际版双供应商并行,提供用量/积分概览与每日签到领取。",
|
|
5
|
-
"version": "3.4.
|
|
5
|
+
"version": "3.4.1",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|