dsh-connect-trae 2.4.0 → 2.7.0
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 +131 -0
- package/README.en.md +12 -2
- package/README.md +12 -2
- package/lib/client.js +240 -16
- package/lib/index.d.ts +139 -7
- package/lib/index.js +601 -26
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,136 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.7.0 (2026-10-02)
|
|
4
|
+
|
|
5
|
+
> **新增「一键测试启用模型」**:给每个已启用的模型发一条最小消息,看哪些**真的能应答**。
|
|
6
|
+
>
|
|
7
|
+
> 起因是 issue #19 留下的那个问题——目录里有一些模型拿不到调用 id,而**能不能调用取决于账号档位**,本地任何检查都判断不了(本机只有免费账号,测不出付费账号的真实现状)。既然无法代替用户判断,就把判断交给上游,让用户一键问一次。
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **一键测试启用模型**(卡片模型区新增按钮):
|
|
12
|
+
- 对**已启用**的每个模型发一条最小的聊天请求(一句 `ping`、不带工具,花费降到最低),逐个给出结论:
|
|
13
|
+
| 结论 | 含义 |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| **可用** | 上游正常应答 |
|
|
16
|
+
| **需要更高档位** | `1005`:当前套餐不覆盖该模型(报告人 Pro 账号能用的模型,免费账号会看到这一条) |
|
|
17
|
+
| **上游不提供** | `4001` / `4011`:没有任何 SOLO function 服务这个 config_name |
|
|
18
|
+
| **被限流** | 上游限流,本次未测出 |
|
|
19
|
+
| **登录凭据被拒** | 凭据失效;整轮测试立即停止(否则后续每个模型都只会重复同一句) |
|
|
20
|
+
| **本次未测出** | 超时、连接中断、代理、5xx——**一律不算「不可用」** |
|
|
21
|
+
- **`本次未测出` 是一等公民**:把限流或断连显示成「不可用」,用户会去删掉一个其实能用的模型。只有上游明确的拒绝才算负面结论。
|
|
22
|
+
- **这是插件里唯一消耗额度的操作**(另一个是签到,改变的是账号状态)。因此:只由用户点击触发、没有定时也没有启动时自动跑;按钮带说明;一次测试发送的提示词固定为一句「ping」且不带工具。
|
|
23
|
+
- **逐个请求、串行执行**:Trae 对突发请求限流明显,并发探测会自己制造出「被限流/未测出」;每测完一个立刻更新该行徽标,所以不是空等一分钟。
|
|
24
|
+
- **结果只在会话内存里**:不写进设置——某个模型「上游不提供」是某一刻、某个账号的事实,持久化下来只会在上游改主意后继续误报。
|
|
25
|
+
- **路由守卫**:POST + 回环来源 + 区域参数;待测 id 必须存在于该区域的实时目录(不能拿任意的上游 config 名去消耗额度),数量上限 64。
|
|
26
|
+
|
|
27
|
+
### Tests
|
|
28
|
+
|
|
29
|
+
- **全仓 395 → 413(+18)**:
|
|
30
|
+
- `tests/model-probe.spec.ts`(新增 16 例):分类规则的每一个分支,重点是「不许把不确定判成不可用」——超时、断连、5xx、无输出的 200 流全部保持 `unknown`;`1005` → `gated`、`4001/4011` → `unsupported`、401 → `auth`、429 → `rate_limited`;流中途抛出的拒绝**带错误码时仍要分类**(不能退回 unknown);串行顺序与「认证失败即停止」;探测请求体是最小的 `ping` 且不含 `tools`。
|
|
31
|
+
- `tests/web-status.spec.ts`(+2 例):新路由把 id 交给**该区域**的探测实现并回传结论;方法/来源/请求体不合法时**一次调用都不发出**(用探测调用次数断言,而不是只看状态码),以及超过上限的列表被拒。
|
|
32
|
+
- **变异验证 3 次全部被抓**:把「不确定」改成负面结论 → 1 例失败;去掉认证失败即停止 → 1 例失败;不识别 `1005` 门禁码 → 2 例失败。恢复后 46 文件 413 例全绿。
|
|
33
|
+
- **真机验证(本机免费账号,国际版)**:`gpt-5.4` / `kimi-k3` → 可用;`gpt-6-luna` → 需要更高档位(`1005`);`gpt-6-astra` / `gpt-5.6-sol` / `glm-5.2` → 上游不提供。三种结论都在真实链路上得到验证。
|
|
34
|
+
|
|
35
|
+
## 2.6.0 (2026-10-02)
|
|
36
|
+
|
|
37
|
+
> **包含仍未发布的 2.5.1。** 2.5.1(补问 `chat_v3`,国际版目录 11 → 17)只打了 tag、未推 npm,因此没有用户拿到过它;2.6.0 在同一棵树上带上它。
|
|
38
|
+
>
|
|
39
|
+
> 本版做了一件**行为上的反转**:目录里拿不到调用 id 的模型**不再被隐藏,而是照常显示**,调用失败时给出可读的上游错误。起因是 issue #19 的讨论——可调用名单是**按账号档位**下发的,用免费账号测不到,不能推出付费账号用不了;**隐藏用户自己 IDE 里有的模型,比让他看到一条明确的错误更糟**。
|
|
40
|
+
|
|
41
|
+
### Features
|
|
42
|
+
|
|
43
|
+
- **模型列表改为「全部呈现」:不再隐藏拿不到调用 id 的模型**(issue #19):
|
|
44
|
+
- **原行为**:`mergeTraeModelSources` 会剔除「目录里有、但任何 function 的可调用名单里都没有」的行(2026-08-30 的结论:这类模型发出去必然 `4001 param is invalid`,所以不该暴露)。
|
|
45
|
+
- **为什么反转**:那个结论把**账号范围的事实**当成了**普遍事实**。可调用名单按账号档位下发,本机免费账号问不到不等于付费账号没有——报告人是 Pro,他的账号能正常调用本机免费账号被门禁(`1005`)挡住的模型。插件隐藏了他 Trae IDE 里明明有的模型,这才是更糟的结果。
|
|
46
|
+
- **现在的行为**:这些行照常出现在列表与勾选界面里;因为没有 wire 目标,调用会走显示 id + 默认 function,上游拒绝时由 2.5.0 引入的错误翻译如实转述,用户看到的是:
|
|
47
|
+
```
|
|
48
|
+
Trae does not serve this model under the SOLO function the request used
|
|
49
|
+
(config_name rejected) · Trae code 4001 · upstream: We're sorry, the param is invalid…
|
|
50
|
+
```
|
|
51
|
+
而不是「模型不见了」或一句像网络故障的提示。
|
|
52
|
+
- **实测(本机实时凭据 + 插件自身代码路径)**:国际版目录 **17 → 22**(新增 `gpt-6-astra`、`gpt-5.6-sol/terra/luna`、`glm-5.2` 这 5 个无调用目标的行);国内版目录 **19 → 30**。无调用目标的行会随上游开放 SOLO 通道而自动变得可调用——判据就是能不能 join 到调用 id,无需再改插件。
|
|
53
|
+
|
|
54
|
+
### Bug Fixes
|
|
55
|
+
|
|
56
|
+
- **国内版目录补齐:读全 7 个货架,新增 11 个模型**(issue #19 跟进):
|
|
57
|
+
- 国内版的发现名单此前只有 `solo_agent_remote` + `solo_work_remote`,把 `solo_coder`(12 个)、`chat_v3`(18 个)、`solo_agent`(18 个)、`solo_work_lite`(15 个)等组全部漏读——与国际版此前那个缺口形状相同。现在两个区域使用同一份组名单。
|
|
58
|
+
- 调用名单同步补上 `solo_agent_remote` / `chat_v3` / `solo_coder`(都在最后,只补缺口):实测国内版新增可见 `Doubao-Seed-Code`、`Doubao-Seed-2.0-Code`、`glm-5.1`、`glm-5`、`glm-5v-turbo`、`DeepSeek-V4-Pro`、`DeepSeek-V4-Flash`、`kimi-k2.5`、`qwen-3.5`、`minimax-m2.7`、`qwen-3.6-plus` 等。
|
|
59
|
+
- **纠正一处我自己的测量错误**:2.5.1 曾记「CN 的 `solo_coder` 组里 3 个模型任何 function 都 4001,所以不能扩大 CN」——那是**探测脚本的 bug**:调用时用了展示名而不是该 function 返回的 `config_name`。用正确的 config_name 重测,`doubao-seed-2.0-code`、`deepseek-v4-pro`、`deepseek-v4-flash` 在 `solo_coder` / `chat_v3` 等下**全部正常出字**。(同一个坑在同一轮里踩了两次,见 `docs/RELEASE_EXPERIENCE.md` 的教训类型。)
|
|
60
|
+
|
|
61
|
+
### Tests
|
|
62
|
+
|
|
63
|
+
- **全仓 395 → 395**(改写 4 条既有断言为新的行为契约):merge 保留无 wire 目标的行且不伪造 `wireConfigName` / `wireFunction`;国内版发现名单必须含 `solo_coder` / `chat_v3` / `solo_work_lite`;国内版调用名单的顺序与内容。
|
|
64
|
+
- **变异验证 3 次全部被抓**:把 merge 改回「丢弃无线行」→ 2 例失败;把国内版调用名单改回两项 → 1 例失败;把国内版发现名单改回两项 → 2 例失败。恢复后 45 文件 395 例全绿。
|
|
65
|
+
|
|
66
|
+
### Docs
|
|
67
|
+
|
|
68
|
+
- README(中英)「暂不支持」一节改写:这类模型**会显示**,选中后得到明确的上游拒绝错误,并说明「按账号档位下发、免费账号测不到不等于付费不可用」的新策略。
|
|
69
|
+
|
|
70
|
+
## 2.5.1 (2026-10-02)
|
|
71
|
+
|
|
72
|
+
> **补上一个 2.5.0 没修完的缺口**:国际版模型目录仍比应有的少。报告人升级 2.5.0 后反馈「pro 状态 PASS、调用 PASS,但模型数量还是没取全」——复测确认,并定位到插件**从未请求过的那个 function**。
|
|
73
|
+
|
|
74
|
+
### Bug Fixes
|
|
75
|
+
|
|
76
|
+
- **国际版模型目录 11 → 17:`chat_v3` 是一个从未被请求的宽名单**(issue #19 跟进):
|
|
77
|
+
- **现象**:2.5.0 之后报告人仍看不到 `Dola-Seed-2.0-Code`、`kimi-k2.7-code`、`deepseek-v4-flash-0731` 等模型。
|
|
78
|
+
- **根因**:2.5.0 只扩大了**发现侧**(remote 目录读全部组),而**最终目录 = 发现 ∩ wire**(`mergeTraeModelSources` 会剔除 join 不到 `config_name` 的模型——这是防止「显示了但一点就 4001」的保护,本身正确)。问题是插件的 wire 名单只问过 `solo_work_remote` / `solo_work_lite` / `solo_agent_remote`,而 **`chat_v3` 从未被请求**——它恰恰是名单最宽的一个:ai 区域 40 个 `config_name`(仅次于不可用于聊天的 `solo_agent` 的 43 个),并且列着一批别的 function 都没有的模型。
|
|
79
|
+
- **实测(本机实时凭据,AI 区域)**:把 `chat_v3` 加入 wire 名单(**放在最后**,因此只会补缺口、不改变任何既有模型的 function 归属)后,用插件自身代码路径(`TraeSoloRemoteCatalogClient` + `TraeSoloUpstreamClient` + `mergeTraeModelSources`)跑实时上游:发现 22 → wire 51 → **最终目录 11 → 17**。新增的 6 个全部挂在 `chat_v3`:`Dola-Seed-2.0-Code`、`deepseek-v4-flash-0731`、`kimi-k2.7-code`、`deepseek-v3.2`、`gemini-3-flash-premium`、`gemini_2.5_flash_premium`;每个都用插件自己的 `prepareSoloBody` 信封逐个验证**可正常出字**(同一模型在 `solo_agent_remote` 下回 `4001 param is invalid`)。
|
|
80
|
+
- **发现侧同步**:`deepseek-v3.2`、`gemini-3-flash-premium`、`gemini_2.5_flash_premium` 只出现在 `chat_v3` 组,因此 remote 目录也补问该组(否则它们根本不会被发现)。
|
|
81
|
+
- **仍然不显示的**:`gpt-6-astra`、`gpt-5.6-Sol/Terra/Luna`、`glm-5.2` —— 它们在**任何** function 的 wire 名单里都不存在,对 `llm_utils_chat` 每个通道都返回 `4011`/`4001`。它们属于 Trae IDE 的内置通道,不是插件能走的 SOLO 通道;merge 剔除它们是**正确行为**(宁可没有,也不放一个点了就报错的模型)。另外 `gpt-5.5` 现在可以显示了(它在 `solo_work_remote` 的 wire 名单里,此前只是被发现侧漏读)。
|
|
82
|
+
- **CN 区域故意未动**:CN 的 `solo_coder` 组确实还藏着模型(`glm-5`、`glm-5.1`、`qwen-3.5` 可调用),但它与另外三个「任何 function 都 4001」的模型(`doubao-seed-2.0-code`、`deepseek-v4-pro`、`deepseek-v4-flash`)**混在同一组**,而 merge 无法按 function 区分它们。扩大 CN 发现等于把它们一起暴露出来——正是这条 join 规则要防的事,因此留待逐个模型定出可用 function 后再做。
|
|
83
|
+
|
|
84
|
+
### Tests
|
|
85
|
+
|
|
86
|
+
- **全仓 392 → 395**:新增「ai wire 名单把 `chat_v3` 排在最后(且断言它必须是最后一位,防止将来被挪到前面抢走既有模型的 config)」、「ai 发现名单请求 `chat_v3`」、以及一条**守护 CN 不被顺手扩大**的断言(CN 名单既不含 `chat_v3` 也不含 `solo_coder`)。
|
|
87
|
+
- **变异验证 3 次全部被抓**:把 `chat_v3` 挪到名单最前(优先级被夺)→ 3 例失败;从 ai 发现名单去掉 `chat_v3` → 2 例失败;顺手扩大 CN 发现名单 → 2 例失败。恢复后 45 文件 395 例全绿。
|
|
88
|
+
|
|
89
|
+
### Docs
|
|
90
|
+
|
|
91
|
+
- `docs/MODEL_MANAGEMENT_DESIGN.md` 更正一处结论:2026-08-30 记的「`Doubao-Seed-Code` 不是任何 `config_name`、必须剔除」只对**当时问过的 function** 成立——它的 `config_name` 在 `chat_v3` 里且可正常调用。join 规则不变,变的是「有没有 wire」必须建立在**完整的 function 名单**上。
|
|
92
|
+
|
|
93
|
+
## 2.5.0 (2026-10-01)
|
|
94
|
+
|
|
95
|
+
> **国际版(AI 区域)三条用户可见故障的修复**,全部来自 [issue #19](https://github.com/dingminhua/dsh-connect-trae/issues/19) 的报告与
|
|
96
|
+
> 在真实 SG 凭据上的逐条实测(取证过程见 `docs/ISSUE19_DIAGNOSIS.md`,档位接口探索见 `docs/INTL_PAID_TIER_PROBE.md`)。
|
|
97
|
+
|
|
98
|
+
### Features
|
|
99
|
+
|
|
100
|
+
- **国际版模型目录改为读取全部组并集——此前只读 `solo_agent_remote` 一个组,另外 9 个模型从未出现**(issue #19 症状一):
|
|
101
|
+
- Trae 的 remote `/models` 答复**按 function 分组**,且组间不是互替关系:实测 `solo_agent` 组 19 个模型、`solo_agent_remote` 组 10 个。旧代码请求 `solo_agent_remote,solo_work_remote` 后**只遍历 preferred 一个组**,报告人 Trae IDE 里能看到、插件里却没有的 `GPT-6-Astra`、`GPT-5.6-Sol/Terra/Luna`、`GPT-5.5`、`GLM-5.2` 等**恰好全部在没读的 `solo_agent` 组里**。
|
|
102
|
+
- 现在 `TRAE_REMOTE_DIRECTORY_FUNCTIONS` 按区域声明要请求的组(AI 区域三个全取),**每个返回的组都并入目录**,同一模型先出现者胜、只发一次。请求一个网关不认识的组是无害的(未知组名被忽略而非拒绝)。
|
|
103
|
+
- **发现面变宽,调用面反而收窄**:目录 union 让模型「可见」,但模型**只能通过列出它的那个 function 调用**。实测 `get_detail_param` 对 `solo_agent` 有求必应地返回 43 条,而 `llm_utils_chat` 对**所有**以 `function: "solo_agent"` 发出的模型一律回 `code 4011`——所以 wire 函数表 `TRAE_DIRECTORY_FUNCTIONS.ai` 从 `['solo_agent','solo_work_remote','solo_work_lite']` 改为 `['solo_work_remote','solo_work_lite','solo_agent_remote']`:让目录可见的新模型仍通过真正可调用的 function 去调,`solo_agent_remote` 放最后,使两个 function 都列出的模型保持无 Remote 门禁的 IDE function。CN 区域未重新测量,维持原列表不变(union 会保留网关返回的任何组)。
|
|
104
|
+
|
|
105
|
+
### Bug Fixes
|
|
106
|
+
|
|
107
|
+
- **国际版订阅状态显示错误——Pro 会员被显示为「暂无有效套餐」**(issue #19 症状二):
|
|
108
|
+
- 卡片的判据只有 `has_package`,但那个字段回答的是「是否存在套餐权益对象」,不是「账号处于什么档位」——付费会员可以持有档位而没有套餐行,报告人的账号正是如此。
|
|
109
|
+
- Trae 同时返回的**权威档位字段** `user_pay_identity` / `user_pay_identity_str`(实测免费账号为 `0` / `"Free"`)此前在插件源码里**零引用**,被直接丢弃。
|
|
110
|
+
- 现在 `payStatus()` 完整保留两个档位字段(缺失时省略,不伪造),卡片判定改为 `has_package ∪ 档位非 Free`,并把档位名渲染进套餐行:「已有有效套餐 · Pro」/「暂无有效套餐 · Free」——免费用户看到**为什么**没有套餐,而不是只看到一个布尔。
|
|
111
|
+
- **只有精确的 `Free`(大小写不敏感)算免费**:未知或未来的档位名按付费处理。把付费会员错标成免费,比把免费账号显示成未知档位,是伤害更大的那个错误。
|
|
112
|
+
- **订阅门禁错误被吞成「Stream ended without finish_reason」**(issue #19 症状三):
|
|
113
|
+
- **现象**:免费/低档位账号选中需订阅的模型(如 `GPT-6-Sol`),得到的是一句像网络故障的报错,且会被重试多次。Trae 实际返回的是 **HTTP 200** + `event:error {"code":1005,"extra":"{\"plan\":N}"}`(`extra` 是 wire 上的 JSON **字符串**,订阅门禁只体现在 `extra.plan`)。
|
|
114
|
+
- **根因**:旧实现把上游答复原样以 200 转发,中途 `controller.error()` 截断流——HTTP 头已按 200 发出,`code 1005` 与 `plan` 在 HTTP 边界处丢失,pi-ai 只看到「流没给 finish_reason 就断了」,按传输故障处理。
|
|
115
|
+
- **修法**:`bridgeTraeSoloStreamOrFail` 在转发前**窥探流头**——任何输出开始之前的拒绝被翻译成真实的错误结果:`1005` → `402 hard_credit`(携带 plan 与「需要付费档」的文案,`hard_credit` 是让客户端停止而非重试的语义,402 是如实的状态码),`4011` / `4001` / 未知码 → `400 client`。code、plan、以及 Trae 那句与 code 相矛盾的原文(如 4011 自称"rate limit")**全部保留在 message 里**。
|
|
116
|
+
- 输出已开始后到来的拒绝(拒绝打断正在进行的回答):流以翻译后的可读错误终止,而不是裸截断。
|
|
117
|
+
- **窥探不伤健康流**:被窥探消费的头部字节逐字节重放;队列/进度/输出等任何 Trae 自有事件即触发直通,缓冲不超过首个事件。排队事件、分片工具调用等既有行为不变。
|
|
118
|
+
- **顺带登记两个此前不认识的错误码**(`grep` 证实源码与测试均零命中):`1005` = 订阅门禁(档位不够,`extra.plan` 指明所需档位);`4011` = 该 function 不提供此模型(其自报文案声称 rate limit,具有误导性)。已知的 `4001` = `config_name` 不被该 function 接受,语义不变。
|
|
119
|
+
|
|
120
|
+
### Tests
|
|
121
|
+
|
|
122
|
+
- **全仓 373 → 392(+19),含 5 次变异验证全部被抓**:
|
|
123
|
+
- `tests/solo-bridge.spec.ts` **+11**:`describeTraeFailure` 文案 5 例(1005 带 plan、4011 用 hint 而把误导原文降级为 detail、未知码回退、无 code 事件不虚构后缀、已解包的 extra 对象也能读 plan);`bridgeTraeSoloStreamOrFail` 策略 5 例(1005→402 `hard_credit`、4011→400 `client`、无名事件经错误码带识别、健康流含排队事件直通且头部重放逐字节不丢、输出先行后拒绝的流中场景以可读 message 终止);`TraeSoloBridge` 端到端 1 例(完整链路上订阅门禁成为错误结果而非被截断的 200 流)。其中「流中拒绝」用分块流构造:单块 body 会被窥探一起解码,无法测到该路径。
|
|
124
|
+
- `tests/card-subscription.spec.tsx` **新文件 5 例**:渲染真实卡片断言档位行——Pro 从档位单独判定、无档位时套餐行仍工作、Free 命名而非只报布尔、试用窗口优先于档位文案,及 `traePlanIsPaid` 判据表(只有精确 Free 算免费,未知档位按付费)。一例说明:无档位字符串时 badge 与 value 渲染同一裸标签,`getByText` 会命中多个元素,须用 `getAllByText`——这例测试在上一会话中断前正以多元素匹配失败,本次接续修复。
|
|
125
|
+
- `tests/usage.spec.ts` **+2**:档位字段与 `has_package` 分开保留;上游不带档位时省略而非伪造。
|
|
126
|
+
- `tests/solo-remote.spec.ts` **+1**、`tests/solo.spec.ts` 改写既有断言:目录 union 行为、AI 区域 function 顺序(`solo_agent` 必须绝迹)。
|
|
127
|
+
- **变异验证**(确认测试有约束力,5 次全部被抓):① 分类忽略 1005 → 2 例失败;② plan 提取禁用 → 4 例失败;③ 窥探失效(打回旧行为,即本次要修的 bug)→ 4 例失败;④ 档位恒判免费 → 2 例失败;⑤ union 退回单组 → 3 例失败。恢复后 45 文件 392 例全绿。
|
|
128
|
+
|
|
129
|
+
### Docs
|
|
130
|
+
|
|
131
|
+
- `docs/ISSUE19_DIAGNOSIS.md`(随 2.4.0 后的 `009d9a7` 提交):三个症状的完整取证——remote 目录两组的差集与报告人 IDE 的模型逐一吻合、`user_pay_identity_str` 零引用的 grep 证据、以真实上游字节喂给 bridge 复现「截断的 200」。
|
|
132
|
+
- `docs/INTL_PAID_TIER_PROBE.md`(`d76800d`):付费账号是否有专属接口的探索结论——**没有**,免费/付费同端点同目录,差别在调用时授权;含 `1005` / `4011` 的对照实验记录。
|
|
133
|
+
|
|
3
134
|
## 2.4.0 (2026-09-29)
|
|
4
135
|
|
|
5
136
|
> **包含仍未发布的 2.3.2。** 2.3.2 的 peer 范围修复(`>=0.1.7-rc.1 <0.2.0-0` → `<0.3.0-0`,见下一节)
|
package/README.en.md
CHANGED
|
@@ -42,6 +42,7 @@ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bund
|
|
|
42
42
|
- **Vision follows Trae's own declaration** — when Trae marks a model `multimodal`, the plugin advertises it as such with no manual tick per model (issue #16). The rule is "**models you tick ∪ models Trae marks multimodal**": models Trae marks `false`, or does not mark at all, stay text-only and can still be turned on by hand. A row enabled automatically is shown ticked and read-only, with the reason. See "Image input" below.
|
|
43
43
|
- **Account switching** — refreshes the token list and lets users select an account without storing tokens in DSH settings.
|
|
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
|
+
- **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`/`4011`; no SOLO function serves it) / **throttled** / **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.
|
|
45
46
|
- **Daily check-in claiming** — a one-click claim on the CN card (the button shows the daily reward, and reads "Claimed today" and disables itself once that account has been paid). This is the plugin's **only** operation that changes account state, and it runs only when you click. Trae allows **one check-in per device per day**: after switching accounts, if this machine's slot is already spent (by any account), the selected account cannot claim here that day — the card explains that rather than pretending the account was paid. The international side has no check-in activity, so no button is shown there.
|
|
46
47
|
- **Secure loopback shim** — one random port + in-process random secret per region; the real Trae token is never handed to pi-ai.
|
|
47
48
|
|
|
@@ -86,7 +87,7 @@ Notes:
|
|
|
86
87
|
|
|
87
88
|
This plugin serves models from Trae's **SOLO channel** (`DeepSeek-V4-Flash-Official`, `DeepSeek-V4-Pro-Official`, `GLM-5.3`, `GLM-5.2`, `Kimi-K3`, `MiniMax-M3`, `Qwen3.8-Max`, `Doubao-Seed-*`, …), for both the domestic and international sides.
|
|
88
89
|
|
|
89
|
-
The following three models currently come from the **TraeCode (Trae IDE) channel** and have **not** been opened up to the SOLO channel
|
|
90
|
+
The following three models currently come from the **TraeCode (Trae IDE) channel** and have **not** been opened up to the SOLO channel:
|
|
90
91
|
|
|
91
92
|
| Model |
|
|
92
93
|
| --- |
|
|
@@ -94,7 +95,16 @@ The following three models currently come from the **TraeCode (Trae IDE) channel
|
|
|
94
95
|
| `kimi-k2.8-preview` |
|
|
95
96
|
| `qwen3.8-flash` |
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
They **do appear in the model list** (Trae's directory advertises them), but the plugin has no channel that can call them yet. Sending a message with one selected produces an **explicit error**, for example:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Trae does not serve this model under the SOLO function the request used
|
|
102
|
+
(config_name rejected) · Trae code 4001 · upstream: We're sorry, the param is invalid…
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
That is the upstream refusal stated plainly, rather than a silent failure or something that reads like a network fault. **Once Trae opens them up to the SOLO channel the plugin picks them up automatically** (the test is simply whether a callable id can be resolved) — no plugin upgrade needed.
|
|
106
|
+
|
|
107
|
+
> **Why they are listed now (policy from 2026-10-02)**: such rows — present in the directory but with no resolvable callable id — used to be **hidden outright**, on the grounds that showing one only produces an error. But the callable rosters are issued **per account tier**: not seeing a model on a free account does not prove a paying account cannot call it (the reporter in issue #19 is on Pro, and their account calls models this machine's free account gets gated on). **Hiding models the user's own Trae IDE lists is worse than showing an explicit error**, so the behaviour is now: list everything, report refusals honestly.
|
|
98
108
|
|
|
99
109
|
> `deepseek-v4.1-flash` used to be listed here — `docs/DS41_CALLABILITY.md` (evidence gathered 2026-09-15) measured `4001 param is invalid` from all eight SOLO functions. **Trae has since opened it up to the SOLO channel**: it now ships in the live directory with `wireFunction: solo_work_remote` and calls succeed (verified against a live refresh). This page had not caught up, so it is removed from the table.
|
|
100
110
|
|
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
- **识图能力跟随 Trae 自身的标注** —— Trae 把某个模型标为 `multimodal` 时,插件直接按多模态对外声明,无需你在卡片上逐一手勾(issue #16)。判据是「**你手动勾选的 ∪ Trae 标注多模态的**」:Trae 明确标 `false` 或未标注的模型保持纯文本,你可以照旧手动为它们打开图片输入。卡片上被自动开启的那一行显示为已勾选且只读,并说明原因。详见下方「图片输入(识图)」。
|
|
43
43
|
- **多账号切换** —— 支持重新读取 Token 列表并选择账号;Token 不写入 DSH 设置。
|
|
44
44
|
- **只读用量与模型管理** —— 国内版查看 Work 积分与通用积分,国际版查看订阅/试用状态;刷新/启用 Trae 模型;只读查询不消耗额度。
|
|
45
|
+
- **一键测试启用模型** —— 给**已启用**的每个模型发一条最小消息(一句 `ping`、不带工具),逐个告诉你能不能真的应答:**可用** / **需要更高档位**(Trae `1005`,你的套餐不覆盖)/ **上游不提供**(`4001`/`4011`,SOLO 通道没有这个模型)/ **被限流** / **登录凭据被拒** / **本次未测出**。最后一项是一等公民:超时、断连、代理、5xx **一律不算「不可用」**——只有上游明确的拒绝才算负面结论。**这是插件里唯一消耗额度的操作**(签到改变的是账号状态,不消耗),只在你点击时运行,结果只留在当前会话里不写入设置。
|
|
45
46
|
- **每日签到领取** —— 国内版卡片上一键领取当日签到积分(按钮显示每日奖励,该账号已领取则显示「今日已领取」并禁用);这是插件里**唯一会改变账号状态**的操作,由你主动点击触发。Trae 的签到按「**每台设备每天一次**」限制:切换账号后如果本机今天的名额已经用掉(无论是哪个账号用的),当前账号在本机当天无法再签,卡片会说明原因而不是谎报「已领取」。国际版没有签到活动,因此不显示该按钮。
|
|
46
47
|
- **安全 loopback shim** —— 每区域一个随机端口 + 进程内随机 secret,真实 Trae token 不交给 pi-ai。
|
|
47
48
|
|
|
@@ -86,7 +87,7 @@ DSH PiAiAdapter(每个 provider 一套)
|
|
|
86
87
|
|
|
87
88
|
插件提供的是 Trae **SOLO 通道**的模型(`DeepSeek-V4-Flash-Official`、`DeepSeek-V4-Pro-Official`、`GLM-5.3`、`GLM-5.2`、`Kimi-K3`、`MiniMax-M3`、`Qwen3.8-Max`、`Doubao-Seed-*` 等),国内版与国际版均走这条通道。
|
|
88
89
|
|
|
89
|
-
以下 3 个模型目前来自 **TraeCode(Trae IDE)通道**,尚未开放到 SOLO
|
|
90
|
+
以下 3 个模型目前来自 **TraeCode(Trae IDE)通道**,尚未开放到 SOLO 通道:
|
|
90
91
|
|
|
91
92
|
| 模型 |
|
|
92
93
|
| --- |
|
|
@@ -94,7 +95,16 @@ DSH PiAiAdapter(每个 provider 一套)
|
|
|
94
95
|
| `kimi-k2.8-preview` |
|
|
95
96
|
| `qwen3.8-flash` |
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
它们**会出现在模型列表里**(Trae 的目录列出了它们),但插件侧暂时没有可调用它们的通道。选中后发送消息会得到一条**明确的错误**,例如:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Trae does not serve this model under the SOLO function the request used
|
|
102
|
+
(config_name rejected) · Trae code 4001 · upstream: We're sorry, the param is invalid…
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
即「上游不提供该模型」被如实说明,而不是静默失败或像网络故障。**官方把它们开放到 SOLO 通道后,插件会自动可用**(判据就是能不能拿到调用 id),无需升级插件。
|
|
106
|
+
|
|
107
|
+
> **为什么现在会显示(2026-10-02 起的策略)**:此前这类「目录里有、但拿不到调用 id」的模型会被**直接隐藏**,理由是「放出来只会报错」。但可调用名单是**按账号档位**下发的——在免费账号上测不到,不等于付费账号用不了(issue #19 的报告人就是 Pro:他的账号能调用本机免费账号被门禁挡住的模型)。**隐藏用户自己 Trae IDE 里有的模型,比让他看到一条明确的错误更糟**,所以改为:全部呈现 + 如实报错。
|
|
98
108
|
|
|
99
109
|
> `deepseek-v4.1-flash` 曾在此列——`docs/DS41_CALLABILITY.md`(2026-09-15 取证)实测 8 个 SOLO function
|
|
100
110
|
> 全部返回 `4001 param is invalid`。**上游后来把它开放到了 SOLO 通道**:当前实时目录里它带
|
package/lib/client.js
CHANGED
|
@@ -11,6 +11,13 @@ window.__ModuleLoader__.load({
|
|
|
11
11
|
const TRAE_USAGE_PATH = "/plugins/dsh-connect-trae/usage";
|
|
12
12
|
/** Plugin-owned live model refresh endpoint. */
|
|
13
13
|
const TRAE_MODELS_REFRESH_PATH = "/plugins/dsh-connect-trae/models/refresh";
|
|
14
|
+
/**
|
|
15
|
+
* Plugin-owned availability probe: POST the model ids to test, get one verdict
|
|
16
|
+
* per model. Unlike every read-only sibling this route SPENDS quota — a real
|
|
17
|
+
* (minimal) chat call per model — so it is user-initiated only, and the card
|
|
18
|
+
* labels the button as consuming a small amount.
|
|
19
|
+
*/
|
|
20
|
+
const TRAE_MODELS_TEST_PATH = "/plugins/dsh-connect-trae/models/test";
|
|
14
21
|
/** Plugin-owned local account rescan endpoint. */
|
|
15
22
|
const TRAE_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-trae/accounts/refresh";
|
|
16
23
|
/**
|
|
@@ -140,6 +147,21 @@ window.__ModuleLoader__.load({
|
|
|
140
147
|
enabled
|
|
141
148
|
});
|
|
142
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Whether Trae's plan identity says this account is on a paying plan.
|
|
152
|
+
*
|
|
153
|
+
* `has_package` alone is NOT that question — it reports whether a package
|
|
154
|
+
* entitlement row exists, and a member can be on a plan without one (issue
|
|
155
|
+
* #19: a Pro account rendered as "No active package"). Trae spells the plan in
|
|
156
|
+
* `user_pay_identity_str`, so this reads that field, and only the exact word
|
|
157
|
+
* `Free` counts as free: an unknown or future tier name is treated as paid,
|
|
158
|
+
* because claiming "Free" for a tier we simply do not recognise is the more
|
|
159
|
+
* damaging of the two errors.
|
|
160
|
+
*/
|
|
161
|
+
function traePlanIsPaid(payIdentityStr) {
|
|
162
|
+
const tier = payIdentityStr?.trim();
|
|
163
|
+
return tier !== void 0 && tier !== "" && !/^free$/i.test(tier);
|
|
164
|
+
}
|
|
143
165
|
//#endregion
|
|
144
166
|
//#region src/client/icon.ts
|
|
145
167
|
/**
|
|
@@ -239,6 +261,18 @@ window.__ModuleLoader__.load({
|
|
|
239
261
|
.dsm-trae-model-copy{display:flex;align-items:baseline;gap:8px;min-width:0}
|
|
240
262
|
.dsm-trae-model-name{display:inline-flex;align-items:baseline;gap:7px;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:13px;font-weight:500;line-height:19px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
241
263
|
.dsm-trae-model-name-rate{color:var(--dsw-alias-label-tertiary,#999);font-size:11px;font-weight:400;line-height:16px;flex:none}
|
|
264
|
+
/* The two head actions sit together; the probe one SPENDS quota, so it carries
|
|
265
|
+
the hint tooltip rather than looking like its read-only sibling. */
|
|
266
|
+
.dsm-trae-models-actions{display:flex;align-items:center;gap:8px;flex:none}
|
|
267
|
+
.dsm-trae-models-hint{margin:4px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
|
|
268
|
+
/* Verdict badge. Only a real refusal is coloured as a warning; an unknown
|
|
269
|
+
verdict stays neutral because an inconclusive probe is not evidence against
|
|
270
|
+
the model. */
|
|
271
|
+
.dsm-trae-model-probe{flex:none;border-radius:999px;padding:1px 7px;font-size:10px;font-weight:500;line-height:15px;white-space:nowrap}
|
|
272
|
+
.dsm-trae-model-probe-available{color:var(--dsw-alias-state-success-primary,#22a06b);border:1px solid color-mix(in srgb,var(--dsw-alias-state-success-primary,#22a06b) 40%,transparent)}
|
|
273
|
+
.dsm-trae-model-probe-gated,.dsm-trae-model-probe-unsupported{color:var(--dsw-alias-state-warning-primary,#d98f20);border:1px solid color-mix(in srgb,var(--dsw-alias-state-warning-primary,#d98f20) 40%,transparent)}
|
|
274
|
+
.dsm-trae-model-probe-auth{color:var(--dsw-alias-state-error-primary,#d92d20);border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary,#d92d20) 40%,transparent)}
|
|
275
|
+
.dsm-trae-model-probe-rate-limited,.dsm-trae-model-probe-unknown{color:var(--dsw-alias-label-tertiary,#999);border:1px solid color-mix(in srgb,var(--dsw-alias-label-tertiary,#999) 35%,transparent)}
|
|
242
276
|
.dsm-trae-model-meta{display:flex;align-items:center;gap:7px 12px;flex-wrap:wrap;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
|
|
243
277
|
.dsm-trae-model-options{display:flex;align-items:center;justify-content:flex-end;gap:12px;flex:none}
|
|
244
278
|
.dsm-trae-model-image{display:inline-flex;align-items:center;gap:4px;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:11px;line-height:16px;cursor:pointer}
|
|
@@ -355,6 +389,47 @@ window.__ModuleLoader__.load({
|
|
|
355
389
|
if (value >= 1e3 && value % 1e3 === 0) return `${value / 1e3}K`;
|
|
356
390
|
return formatNumber(value);
|
|
357
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* The subscription row's verdict.
|
|
394
|
+
*
|
|
395
|
+
* Two independent things can entitle an international account: a package row
|
|
396
|
+
* (`has_package`) and a plan identity (`user_pay_identity_str`). Reading only
|
|
397
|
+
* the first is what rendered a Pro member as "No active package" in issue #19,
|
|
398
|
+
* so either one counts. The tier string is carried separately so the row can
|
|
399
|
+
* name the plan instead of just asserting a boolean.
|
|
400
|
+
*/
|
|
401
|
+
function payVerdict(payStatus) {
|
|
402
|
+
const tier = payStatus.payIdentityStr?.trim();
|
|
403
|
+
const paid = payStatus.hasPackage || traePlanIsPaid(payStatus.payIdentityStr);
|
|
404
|
+
return {
|
|
405
|
+
paid,
|
|
406
|
+
...tier === void 0 || tier === "" ? {} : { tier },
|
|
407
|
+
label: paid ? "row.subscribed" : "row.noPackage"
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* The verdict badge for one model, or null when it has not been tested.
|
|
412
|
+
*
|
|
413
|
+
* `unknown` renders as inconclusive, never as a failure: a throttle or a dropped
|
|
414
|
+
* connection says nothing about the model, and painting it the same red as a
|
|
415
|
+
* real refusal is what makes users delete working models.
|
|
416
|
+
*/
|
|
417
|
+
const PROBE_LABELS = {
|
|
418
|
+
available: "row.probeAvailable",
|
|
419
|
+
gated: "row.probeGated",
|
|
420
|
+
unsupported: "row.probeUnsupported",
|
|
421
|
+
rate_limited: "row.probeRateLimited",
|
|
422
|
+
auth: "row.probeAuth",
|
|
423
|
+
unknown: "row.probeUnknown"
|
|
424
|
+
};
|
|
425
|
+
function probeBadge(probe, t) {
|
|
426
|
+
if (probe === void 0) return null;
|
|
427
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
428
|
+
className: `dsm-trae-model-probe dsm-trae-model-probe-${probe.verdict}`,
|
|
429
|
+
title: probe.detail,
|
|
430
|
+
children: t(PROBE_LABELS[probe.verdict])
|
|
431
|
+
});
|
|
432
|
+
}
|
|
358
433
|
function dotStyle(status) {
|
|
359
434
|
return { background: status === "signed-in" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" ? "var(--dsw-alias-state-error-primary, #d92d20)" : "var(--dsw-alias-label-dimmed, #9aa0a6)" };
|
|
360
435
|
}
|
|
@@ -376,6 +451,19 @@ window.__ModuleLoader__.load({
|
|
|
376
451
|
}
|
|
377
452
|
});
|
|
378
453
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
454
|
+
/**
|
|
455
|
+
* Availability verdicts from the one-click test, per region, keyed by model
|
|
456
|
+
* id. Session-only on purpose: a verdict is one moment on one account, and a
|
|
457
|
+
* persisted "not served" would keep claiming that long after upstream changed
|
|
458
|
+
* its mind.
|
|
459
|
+
*/
|
|
460
|
+
const [probeByRegion, setProbeByRegion] = (0, react.useState)({
|
|
461
|
+
cn: {},
|
|
462
|
+
ai: {}
|
|
463
|
+
});
|
|
464
|
+
const [testing, setTesting] = (0, react.useState)(void 0);
|
|
465
|
+
const [testError, setTestError] = (0, react.useState)(void 0);
|
|
466
|
+
const [testSummary, setTestSummary] = (0, react.useState)(void 0);
|
|
379
467
|
const [settingsRevision, setSettingsRevision] = (0, react.useState)(0);
|
|
380
468
|
/** Per-region unsaved model edits; a draft on one tab is never dropped by
|
|
381
469
|
* switching to the other tab, only by that tab's discard/save. */
|
|
@@ -604,6 +692,84 @@ window.__ModuleLoader__.load({
|
|
|
604
692
|
if (mounted.current) setTogglingRegion(void 0);
|
|
605
693
|
}
|
|
606
694
|
};
|
|
695
|
+
/**
|
|
696
|
+
* One-click availability test for the ENABLED models of the active region.
|
|
697
|
+
*
|
|
698
|
+
* One model per request, deliberately: the host probe is sequential anyway
|
|
699
|
+
* (Trae throttles bursts), and a per-model round trip lets each row show its
|
|
700
|
+
* verdict as soon as it is known instead of the card sitting blank for a
|
|
701
|
+
* minute. User-started, and it stops on the first authentication failure —
|
|
702
|
+
* every later probe would only repeat that answer.
|
|
703
|
+
*/
|
|
704
|
+
const testEnabledModels = async () => {
|
|
705
|
+
const ids = [...activeEnabledIds];
|
|
706
|
+
setTestError(void 0);
|
|
707
|
+
setTestSummary(void 0);
|
|
708
|
+
if (ids.length === 0) {
|
|
709
|
+
setTestError(t("row.modelsTestEmpty"));
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
setTesting({
|
|
713
|
+
done: 0,
|
|
714
|
+
total: ids.length
|
|
715
|
+
});
|
|
716
|
+
const collected = {};
|
|
717
|
+
let available = 0;
|
|
718
|
+
let denied = false;
|
|
719
|
+
try {
|
|
720
|
+
for (const [index, id] of ids.entries()) {
|
|
721
|
+
if (!mounted.current) return;
|
|
722
|
+
const response = await fetch(withTraeRegion(TRAE_MODELS_TEST_PATH, activeRegion), {
|
|
723
|
+
method: "POST",
|
|
724
|
+
headers: {
|
|
725
|
+
accept: "application/json",
|
|
726
|
+
"content-type": "application/json"
|
|
727
|
+
},
|
|
728
|
+
credentials: "same-origin",
|
|
729
|
+
body: JSON.stringify({ ids: [id] })
|
|
730
|
+
});
|
|
731
|
+
const body = await response.json().catch(() => ({}));
|
|
732
|
+
if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`);
|
|
733
|
+
const verdict = body.results?.[0];
|
|
734
|
+
if (verdict !== void 0) {
|
|
735
|
+
collected[id] = {
|
|
736
|
+
verdict: verdict.verdict,
|
|
737
|
+
detail: verdict.detail
|
|
738
|
+
};
|
|
739
|
+
if (verdict.verdict === "available") available += 1;
|
|
740
|
+
setProbeByRegion((prev) => ({
|
|
741
|
+
...prev,
|
|
742
|
+
[activeRegion]: {
|
|
743
|
+
...prev[activeRegion],
|
|
744
|
+
[id]: {
|
|
745
|
+
verdict: verdict.verdict,
|
|
746
|
+
detail: verdict.detail
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}));
|
|
750
|
+
if (verdict.verdict === "auth") {
|
|
751
|
+
denied = true;
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
setTesting({
|
|
756
|
+
done: index + 1,
|
|
757
|
+
total: ids.length
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
if (mounted.current) {
|
|
761
|
+
setTestSummary(t("row.modelsTestDone", {
|
|
762
|
+
available,
|
|
763
|
+
total: Object.keys(collected).length
|
|
764
|
+
}));
|
|
765
|
+
if (denied) setTestError(t("row.probeAuth"));
|
|
766
|
+
}
|
|
767
|
+
} catch (error) {
|
|
768
|
+
if (mounted.current) setTestError(t("row.modelsTestError", { message: error instanceof Error ? error.message : t("row.requestFailed") }));
|
|
769
|
+
} finally {
|
|
770
|
+
if (mounted.current) setTesting(void 0);
|
|
771
|
+
}
|
|
772
|
+
};
|
|
607
773
|
const refreshModels = async () => {
|
|
608
774
|
setBusy(true);
|
|
609
775
|
try {
|
|
@@ -680,6 +846,8 @@ window.__ModuleLoader__.load({
|
|
|
680
846
|
* message — it never sends one by itself.
|
|
681
847
|
*/
|
|
682
848
|
const rowImageIsAutomatic = (model) => model.multimodal === true;
|
|
849
|
+
/** The subscription row's verdict for the current account, when it has one. */
|
|
850
|
+
const payPlan = status.status === "signed-in" && status.payStatus !== void 0 ? payVerdict(status.payStatus) : void 0;
|
|
683
851
|
const activeContextBudgets = draft?.contextBudgets ?? savedContextBudgets;
|
|
684
852
|
const dirty = draft !== void 0;
|
|
685
853
|
/** Apply an edit to the ACTIVE tab's draft, seeding it from the current view. */
|
|
@@ -905,7 +1073,7 @@ window.__ModuleLoader__.load({
|
|
|
905
1073
|
})
|
|
906
1074
|
}) : null,
|
|
907
1075
|
status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
908
|
-
status.payStatus === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1076
|
+
status.payStatus === void 0 || payPlan === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
909
1077
|
className: "dsm-trae-usage-list",
|
|
910
1078
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
911
1079
|
className: "dsm-trae-usage-stats dsm-trae-usage-stats-two",
|
|
@@ -918,13 +1086,13 @@ window.__ModuleLoader__.load({
|
|
|
918
1086
|
className: "dsm-trae-usage-stat-label",
|
|
919
1087
|
children: t("row.subscriptionLabel")
|
|
920
1088
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
921
|
-
className: `dsm-trae-usage-stat-badge ${
|
|
922
|
-
children: t(
|
|
1089
|
+
className: `dsm-trae-usage-stat-badge ${payPlan.paid ? "dsm-trae-usage-stat-badge-on" : "dsm-trae-usage-stat-badge-off"}`,
|
|
1090
|
+
children: t(payPlan.label)
|
|
923
1091
|
})]
|
|
924
1092
|
}),
|
|
925
1093
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
926
1094
|
className: "dsm-trae-usage-stat-value dsm-trae-usage-stat-value-general",
|
|
927
|
-
children: status.payStatus.inTrial && status.payStatus.trialEndTimeMs > 0 ? t("row.trialUntil", { date: formatDateTime(status.payStatus.trialEndTimeMs) }) : t(
|
|
1095
|
+
children: status.payStatus.inTrial && status.payStatus.trialEndTimeMs > 0 ? t("row.trialUntil", { date: formatDateTime(status.payStatus.trialEndTimeMs) }) : [t(payPlan.label), payPlan.tier].filter(Boolean).join(" · ")
|
|
928
1096
|
}),
|
|
929
1097
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
930
1098
|
className: "dsm-trae-usage-stat-hint",
|
|
@@ -1054,16 +1222,44 @@ window.__ModuleLoader__.load({
|
|
|
1054
1222
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1055
1223
|
className: "dsm-trae-models-summary",
|
|
1056
1224
|
children: t("row.modelsSummary", { count: activeEnabledIds.size })
|
|
1057
|
-
})] }), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1225
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1226
|
+
className: "dsm-trae-models-actions",
|
|
1227
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1228
|
+
type: "button",
|
|
1229
|
+
className: "dsm-btn dsm-btn-outline",
|
|
1230
|
+
disabled: busy,
|
|
1231
|
+
onClick: () => {
|
|
1232
|
+
refreshModels();
|
|
1233
|
+
},
|
|
1234
|
+
children: busy ? t("row.modelsRefreshing") : t("row.modelsRefresh")
|
|
1235
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1236
|
+
type: "button",
|
|
1237
|
+
className: "dsm-btn dsm-btn-outline",
|
|
1238
|
+
disabled: busy || testing !== void 0 || activeEnabledIds.size === 0,
|
|
1239
|
+
title: t("row.modelsTestHint"),
|
|
1240
|
+
onClick: () => {
|
|
1241
|
+
testEnabledModels();
|
|
1242
|
+
},
|
|
1243
|
+
children: testing === void 0 ? t("row.modelsTestEnabled") : t("row.modelsTesting", {
|
|
1244
|
+
done: testing.done,
|
|
1245
|
+
total: testing.total
|
|
1246
|
+
})
|
|
1247
|
+
})]
|
|
1065
1248
|
})]
|
|
1066
1249
|
}),
|
|
1250
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1251
|
+
className: "dsm-trae-models-hint",
|
|
1252
|
+
children: t("row.modelsTestHint")
|
|
1253
|
+
}),
|
|
1254
|
+
testSummary === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1255
|
+
className: "dsm-trae-models-summary",
|
|
1256
|
+
children: testSummary
|
|
1257
|
+
}),
|
|
1258
|
+
testError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1259
|
+
className: "dsm-trae-usage-error",
|
|
1260
|
+
role: "alert",
|
|
1261
|
+
children: testError
|
|
1262
|
+
}),
|
|
1067
1263
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1068
1264
|
className: "dsm-trae-model-list",
|
|
1069
1265
|
children: visibleModels.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1083,10 +1279,14 @@ window.__ModuleLoader__.load({
|
|
|
1083
1279
|
className: "dsm-trae-model-copy",
|
|
1084
1280
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1085
1281
|
className: "dsm-trae-model-name",
|
|
1086
|
-
children: [
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1282
|
+
children: [
|
|
1283
|
+
model.name,
|
|
1284
|
+
model.creditMultiplier === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1285
|
+
className: "dsm-trae-model-name-rate",
|
|
1286
|
+
children: ["· x", model.creditMultiplier.toFixed(2)]
|
|
1287
|
+
}),
|
|
1288
|
+
probeBadge(probeByRegion[activeRegion][model.id], t)
|
|
1289
|
+
]
|
|
1090
1290
|
})
|
|
1091
1291
|
})]
|
|
1092
1292
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1281,6 +1481,18 @@ window.__ModuleLoader__.load({
|
|
|
1281
1481
|
"row.modelsSummary": "{count} enabled",
|
|
1282
1482
|
"row.modelsRefresh": "Refresh from Trae",
|
|
1283
1483
|
"row.modelsRefreshing": "Refreshing models…",
|
|
1484
|
+
"row.modelsTestEnabled": "Test enabled models",
|
|
1485
|
+
"row.modelsTesting": "Testing {done}/{total}…",
|
|
1486
|
+
"row.modelsTestHint": "Sends one minimal message per enabled model to see which ones actually answer. Costs a small amount of quota.",
|
|
1487
|
+
"row.modelsTestEmpty": "Enable at least one model first.",
|
|
1488
|
+
"row.modelsTestError": "Test failed: {message}",
|
|
1489
|
+
"row.modelsTestDone": "{available} of {total} answered.",
|
|
1490
|
+
"row.probeAvailable": "works",
|
|
1491
|
+
"row.probeGated": "needs a higher plan",
|
|
1492
|
+
"row.probeUnsupported": "not served",
|
|
1493
|
+
"row.probeRateLimited": "throttled",
|
|
1494
|
+
"row.probeAuth": "sign-in rejected",
|
|
1495
|
+
"row.probeUnknown": "not determined",
|
|
1284
1496
|
"row.modelEnabled": "Enabled",
|
|
1285
1497
|
"row.discard": "Discard changes",
|
|
1286
1498
|
"row.save": "Save",
|
|
@@ -1361,6 +1573,18 @@ window.__ModuleLoader__.load({
|
|
|
1361
1573
|
"row.modelsSummary": "已启用 {count} 个",
|
|
1362
1574
|
"row.modelsRefresh": "从 Trae 刷新",
|
|
1363
1575
|
"row.modelsRefreshing": "正在刷新模型…",
|
|
1576
|
+
"row.modelsTestEnabled": "一键测试启用模型",
|
|
1577
|
+
"row.modelsTesting": "正在测试 {done}/{total}…",
|
|
1578
|
+
"row.modelsTestHint": "给每个已启用模型发一条最小消息,看哪些真的能应答。会消耗少量额度。",
|
|
1579
|
+
"row.modelsTestEmpty": "请先启用至少一个模型。",
|
|
1580
|
+
"row.modelsTestError": "测试失败:{message}",
|
|
1581
|
+
"row.modelsTestDone": "{total} 个里有 {available} 个正常应答。",
|
|
1582
|
+
"row.probeAvailable": "可用",
|
|
1583
|
+
"row.probeGated": "需要更高档位",
|
|
1584
|
+
"row.probeUnsupported": "上游不提供",
|
|
1585
|
+
"row.probeRateLimited": "被限流",
|
|
1586
|
+
"row.probeAuth": "登录凭据被拒",
|
|
1587
|
+
"row.probeUnknown": "本次未测出",
|
|
1364
1588
|
"row.modelEnabled": "启用模型",
|
|
1365
1589
|
"row.discard": "放弃修改",
|
|
1366
1590
|
"row.save": "保存",
|