dsh-connect-trae 2.1.0 → 2.3.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 +82 -0
- package/README.en.md +12 -0
- package/README.md +12 -0
- package/icons/dsh-connect-trae-128.png +0 -0
- package/icons/dsh-connect-trae-64.png +0 -0
- package/lib/client.js +232 -38
- package/lib/index.d.ts +85 -12
- package/lib/index.js +166 -46
- package/package.json +28 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,87 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.3.0 (2026-09-25)
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- **只支持 DSH 0.1.7-rc.1 及以上,不再支持 0.1.7 之前的宿主**。2.2.0 用「双线 + 运行期能力探测」同时服务 0.1.5 与 0.1.7;0.1.5 线的设置面(`installSection` / `settingsScope` / `settings.plugin.item` / 非 volatile 写入)与 0.1.7 的新模型**互不相交**,保留它意味着每条路径都要背两套契约。从本版起删除全部 0.1.5 分支,`peerDependencies` 与 `devDependencies` 一并收窄到 `>=0.1.7-rc.1` / `0.1.7-rc.1`(`cordis >=4.0.4`、`schemastery >=3.18.4`)。做法对齐同级 `dsh-connect-workbuddy`(其 2.1.0 起的 0.1.7-only 改造)。
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **0.1.7 上 provider 被判「未配置」——命名空间必须用宿主服务的那个**(对齐 workbuddy 2.0.16 的实测)。0.1.7 的 `SettingsForms.describe()` 以 **Loader 条目 id** 为键,harness 对 provider 的命名空间做**精确匹配**查表(`namespaces.get(entry.settingsNs)`)。此前硬编码 `settingsNs: 'trae'`,而桌面宿主实际以 `dsh-connect-trae`(或 `include:dsh-connect-trae`)挂载条目,于是查表落空、provider 读作「未配置」,**配置入口与模型发现静默失效**、不报错。
|
|
12
|
+
- **修法**:新增 `settingsNamespaceOf(ctx)`,采用一线插件(`dsh-llm-pi-ai`)的权威范式 `const settingsNs = ctx.fiber.entry?.options.id ?? TRAE_SETTINGS_NS`,`registerConfigurableProviders` 与 `registerModelDiscovery` 全部改用解析值;`ctx.fiber.entry` 由 Loader 注入(非 Cordis 公共类型),保留探测 + 回落到 `'trae'`。
|
|
13
|
+
- **回归测试**(`tests/settings-integration.spec.ts`,+4 例):采纳 Loader 条目 id、无条目/空 id/非字符串时回落、以及**目录实际宣告的就是解析值**(断言目录条目而非常量,否则恒过)。
|
|
14
|
+
- **移除宿主端 `installSection` 双路径,`configure({auto}, owner)` 成为唯一路径**(对齐 workbuddy 的 0.1.7-only 写法)。0.1.7 的 `SettingsForms` 删除了 `installSection`;2.2.0 按能力探测两条路径,本版起无条件调用 `configure`,且把返回的 disposer 挂进 `ctx.effect()`——否则展示策略泄漏到插件销毁之后(一线插件同款:`child.effect(() => child.settings.configure({ auto: false }, …))`)。随 `installSection` 一起删除 `legacyInstallSettingsSection` / `sectionHooks` 及其对 `@deepseek-ai/dsh-settings` 命名空间的依赖。
|
|
15
|
+
- **客户端设置面只走 `configForms`**(对齐 workbuddy 的 0.1.7-only 写法):删除 `settingsScope.bind` 回退分支与 `settings.plugin.item` 槽位注册,只保留 `configForms.get(entryId)` + `plugins.bundle.config` / `plugins.row.config` 两个槽位。`inject` 维持 `['slots', 'locale']`,设置面仍经 `ctx.get()` 软探测。
|
|
16
|
+
- **卡片的 `view` 行为不再按双线区分**:`page` 视图默认展开、无 `view` 默认折叠;`set()` 契约收窄为 `Promise<boolean>`(0.1.7 表单返回显式布尔,`false` 即拒绝)。
|
|
17
|
+
- **区域开关等设置保存被宿主拒绝(`No configurable plugin entry "trae"`)——客户端命名空间绑定必须跟随 describe 镜像**。卡片此前在 `apply()` 时**一次性**解析命名空间:describe 镜像异步加载,且在运行中的 profile 里新增插件条目(本插件正是这种场景)时,镜像首次 describe 可能早于条目出现;`/trae/i` 匹配落空就**永久**回落到声明的 `'trae'`。`configForms.get('trae')` 把表单控制器永久绑到该 ns——宿主 `configEditor.entries()` 里没有 `options.id === 'trae'` 的条目,每次写入都抛 `No configurable plugin entry "trae"` 被拒;而控件仍可点(controller 对「ns 不在镜像」的派生沿用全局 `writable=true`),表现为「开关翻过去又弹回」。
|
|
18
|
+
- **修法**:设置面改为**可重绑代理**——`getSnapshot` / `set` 转发到「当前」控制器;别名解析(`served.ns`,`/trae/i` 匹配)在 apply 时尝试一次,并订阅镜像(`describe().subscribe`)在每次变化后重绑;镜像已就绪但缺条目时**一次性**触发 `describe().load()` 补拉(此后宿主 `settings/document-updated` 事件保持新鲜)。绑定前代理返回 `writable: false`,卡片只读而非提供必定失败的控件。
|
|
19
|
+
- **回归测试**(+3 例):激活测试的镜像桩改为真实形状(`ns: 'dsh-connect-trae'`,即 patch id,非声明的 fallback),断言绑定到 `dsh-connect-trae`;新增「镜像在 apply 后才出现条目 → 重绑到正确 ns」与「一次性 nudge 补拉镜像」两例。测试桩此前的 `ns: 'trae'` 恰好把 fallback 当成了正解,是这条缺陷没被 CI 拦住的原因之一。
|
|
20
|
+
- **插件管理页 / 市场页的 logo 显示不对**(默认占位/空白,对齐 workbuddy 2.0.17 的实测)。0.1.7 的插件管理页从插件包 `package.json` 的 `icon` 字段读图标(`dsh-client-ui-plugin-manager` 渲染 `row.meta?.icon`),此前没有声明该字段 → 宿主无图可用。
|
|
21
|
+
- **修法**(照同级 `dsh-connect-workbuddy` 的做法):新增 `icons/` 目录(64px + 128px 真实 PNG,其中 64px 与既有卡片内嵌 base64 逐字节一致——同族 LD logo),`package.json` 声明 `"icon": "icons/dsh-connect-trae-128.png"`,并把 `icons` 加入 `files` 白名单随包分发。
|
|
22
|
+
- **验证**:`npm pack --dry-run` 确认两个 PNG 进包(12 → 14 文件)。
|
|
23
|
+
- **模型倍率改回「wire 权威、Remote 兜底」,恢复折后价显示**(回归:b8f09bc 重构时误删 1.4.2 的 `display_contact_config` 解析)。`get_detail_param` 每行的 `display_contact_config`(第二层 JSON)里的 `consumption_rate.data.rate` 才是 Trae IDE 渲染的**折后价**;Remote 目录的 `consumption_rate` 在限时折扣下会报**未折价**(Seed-2.1-Pro:IDE `x0.08` vs Remote `0.80`,差 10 倍)。b8f09bc 改成多 function 并集时把这个解析连带删掉,倍率从此退化成 Remote 值。
|
|
24
|
+
- **修法**:`solo.ts` 恢复 `wireCreditMultiplier()`(读 `display_contact_config.consumption_rate.data.rate`,`enable` 为真且为正有限数时取值,缺失/损坏留空不虚构),`TraeSoloModel` / `TraeWireModel` 增列 `creditMultiplier`;`mergeTraeModelSources` 合并时 **wire 优先、Remote 兜底**(wire 未提供才用 Remote)。
|
|
25
|
+
- **国际版(ai)区域**:实测(2026-09-25 只读探测)国际版上游任何目录都不含 `consumption_rate`(订阅制,只有 `cost` 标签与 `manual_usage: 0|1`,官方 App 也只渲染「Lite-friendly」这类标签而非数值倍率),因此 ai 模型行按「解析不出则留空」保持不显示倍率——不虚构数值。
|
|
26
|
+
- **回归测试**(+2 例):`solo.spec.ts` 断言 wire 解析出折后价、缺失时不虚构;`catalog.spec.ts` 断言合并时 wire 优先、Remote 兜底、双缺留空。变异验证:把合并改回只读 Remote → 新用例立即失败。
|
|
27
|
+
|
|
28
|
+
### Dependencies
|
|
29
|
+
|
|
30
|
+
- `peerDependencies`:全部 `@deepseek-ai/dsh-*` 收窄为 `>=0.1.7-rc.1 <0.2.0-0`;`@deepseek-ai/cordis` 收窄为 `>=4.0.4 <5.0.0`;`@deepseek-ai/schemastery` 收窄为 `>=3.18.4 <4.0.0`(`volatile()` 自 3.18.3 起才有)。
|
|
31
|
+
- `devDependencies`:全部 `@deepseek-ai/dsh-*` 与 `cordis` / `schemastery` 升到 0.1.7-rc.1 线,开发树与用户解析一致。
|
|
32
|
+
- `tests/settings-integration.spec.ts` 的 settings 测试替身改为 **0.1.7 形状**(`SettingsForms` 语义的内存服务:`configure` + `describe` + 只写 volatile 字段的 `update` + `loader/volatile-update`),插件以**活引用配置**(volatile 字段 = `{get(): T}` 指向同一份文档)挂载,模拟真实宿主的写入→重放路径;原先写非 volatile 扁平字段(`lastCatalog` / `imageModelIds` / `enabledModelIds`)的用例改为写 `regions.cn` 槽位——正是 0.1.7 写入门接受且卡片实际采用的形状。
|
|
33
|
+
|
|
34
|
+
### Tests
|
|
35
|
+
|
|
36
|
+
- 310 → 316。移除 0.1.5 形态用例(client-activation 的 `settingsScope` 宿主、client-fallback 的 `settingsScope.bind` 回退、card-region-switch 的 void 返回契约),新增 4 例 `settingsNamespaceOf`、3 例命名空间重绑/补拉、2 例 wire 倍率解析与合并优先级。`pnpm run check`(typecheck + test + build)全绿。
|
|
37
|
+
|
|
38
|
+
## 2.2.0 (2026-09-24)
|
|
39
|
+
|
|
40
|
+
### Bug Fixes
|
|
41
|
+
|
|
42
|
+
- **支持 DSH 0.1.7 线,且同一个构建同时服务 0.1.5 与 0.1.7**(修复 [issue #13](https://github.com/dingminhua/dsh-connect-trae/issues/13))。这不是「多支持一个版本」的可选增强——**在 0.1.7 上插件的浏览器半侧此前根本不激活**:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
web boot: 1 entry did not activate
|
|
46
|
+
dsh-connect-trae: pending (waiting for service: settingsScope)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- **根因:`settingsScope` 被整个移除,而 Cordis 的依赖闸门是硬闸。** DSH **0.1.7-alpha.1** 起客户端设置服务换成 `configForms`,`settingsScope` **不再存在**(不是弃用、没有兼容层;0.1.6-alpha.2 还有,0.1.7-alpha.1 起包内已无此符号)。插件 `inject` 里仍声明它,于是 fiber 永远停在 `PENDING`——**不是某个功能坏掉,是整个客户端插件不激活**,因此「重新登录」「重装插件」一概无效。修法与 workbuddy 一致:`inject` 收窄为两条线都有的 `['slots', 'locale']`,设置面改由 `ctx.get()` **按能力软探测**(属性访问会抛 `cannot get property X without inject`,`?.` 挡不住;`ctx.get` 对缺失服务返回 `undefined`)。
|
|
50
|
+
- **0.1.7 上另有三处断裂,issue 只报了第一处。** 逐项对照 workbuddy 的实测(该仓库已用同一套适配跑通 0.1.7):
|
|
51
|
+
|
|
52
|
+
| # | 位置 | 0.1.5 | 0.1.7 |
|
|
53
|
+
|---|---|---|---|
|
|
54
|
+
| 1 | 客户端设置服务 | `settingsScope` | `configForms` |
|
|
55
|
+
| 2 | 客户端槽位名 | `settings.plugin.item` | `plugins.bundle.config` / `plugins.row.config` |
|
|
56
|
+
| 3 | 宿主端注册 | `installSection()` | `configure({auto}, owner)` |
|
|
57
|
+
| 4 | schema 可写声明 | 无需 | 必须标 `volatile()` |
|
|
58
|
+
| 5 | primitives 图标名 | `…Outline14` | `…OutlineRegular` |
|
|
59
|
+
|
|
60
|
+
- **第 2 条(槽位名)最容易被漏掉**:两条线声明的槽位集合**互不相交**(0.1.5 只有 `settings.plugin.item`;0.1.7 只有 `plugins.*`)。就算设置服务改对了,槽位名不改卡片依然渲染不出来。现在三个槽位**各自独立 try/catch** 逐个注册——一条线上不存在的槽位不能把另一条线的注册一起带走。
|
|
61
|
+
- **第 3 条(宿主端)在 0.1.7 上是静默失败**:`installSection` 被 `SettingsForms.configure()` 取代。由于该调用位于嵌套的 `ctx.inject` 回调内,抛出的 `installSection is not a function` 落在**那个子 fiber** 上——外层插件照常加载、两个 provider 照常注册,**但 `trae` 设置命名空间从未注册**,表现为卡片设置区无声消失。现在按能力探测:有 `configure` 走 `configure`,否则回落 `installSection`。
|
|
62
|
+
- **第 4 条(易失声明)**:0.1.7 的 settings 写入门要求插件 schema 把可写字段标为 volatile,否则**写入被直接拒绝**(`Plugin entry "trae" has no volatile fields`),而 `scope.set()` 却正常 resolve——用户会看到开关翻过去又静默复原。新增 `asVolatile()`:运行时探测 `schema.volatile()`,**有则调用、无则退化为 identity no-op**(`volatile()` 自 schemastery 3.18.3 起才有,0.1.5 线锁在 3.18.2)。**故意不手写 `meta.volatile = true`**——那会绕过 schemastery 自身的 `validateVolatileSchema` 校验,产出一个它自己都不认的 schema。
|
|
63
|
+
- **0.1.7 以「活引用」交付配置值。** 该线把 volatile 字段以 `{get(): T}` 形式交给消费方;不解包则 `config.regions`、`value.accounts[region]`、`config.authFile` 静默变成对象或 `undefined`,表现为「设置明明写了却读不到」。更隐蔽的是**展开一个活引用得到的是 `{get: <function>}` 而不是值**——卡片「写一个区域、保留另一个」的合并会因此**丢掉兄弟区域并把函数泄进设置文档**。新增 `unwrapVolatile()` / `unwrapVolatileDeep()` 并在所有读取与合并路径上解包,同时监听 `loader/volatile-update` 在每次写入后重读选择。
|
|
64
|
+
- **第 5 条(图标)会让整个客户端包挂掉**:两条线 primitives 的图标名不重叠(`IconChevronDownOutline14` vs `IconChevronDownOutlineRegular`),**没有任何一个静态 import 能同时服务两边**——静态导入 0.1.5 的名字会让 0.1.7 上的客户端包解析失败,整个卡片随之消失。折叠箭头改为**纯 CSS caret**(`border` + `rotate`),与版本无关。
|
|
65
|
+
- **设置写入改为「写入后回读校验」**:`set()` 的 promise resolve **不代表值已落盘**(0.1.7 上被拒绝的写入会重新加载 Host 状态然后正常返回,0.1.5 上则可能被文件占用丢弃)。四处写入(账号选择、账号重扫、区域开关、模型目录)统一走 `writeSettingsField()`:写入后回读,未落盘即抛错并在卡片上明确显示,而不是「点了没反应」。0.1.7 的 `set()` 还会返回显式布尔值,`false` 同样按拒绝处理。
|
|
66
|
+
- **卡片的 `view` 属性**:0.1.7 的插件管理器通过 owner props 向每个配置条目索取两种视图(`summary` 一句话简介 / `page` 带保存控件的完整表单)。卡片接受该属性并在 `page` 下默认展开——否则在 0.1.7 的插件页里卡片会是折叠的一行。
|
|
67
|
+
- **开发环境的 schemastery 从 3.18.2 提到 3.18.4**(`pnpm-workspace.yaml` 的 override,与 `devDependencies` 对齐)。此前 dev 树锁在 3.18.2,那里没有 `volatile()`,`asVolatile()` 退化为 no-op——**于是 volatile 真正生效的那条路径(每个 0.1.7 用户都会走的那条)在本地从未被执行过**,这类缺陷可以同时躲过评审与 CI。这与 workbuddy 记录的教训同源。
|
|
68
|
+
|
|
69
|
+
### Tests
|
|
70
|
+
|
|
71
|
+
测试总数 282 → 310。
|
|
72
|
+
|
|
73
|
+
- **新增 `tests/client-activation.spec.tsx`(3 例,本次最关键的一组)**:用真实的 cordis `Context` **启动真实的客户端入口模块**,分别对 0.1.7 形态(有 `configForms`、**无** `settingsScope`)、0.1.5 形态(有 `settingsScope`、无 `configForms`)、以及**两者都没有**的形态断言 fiber 到达 `ACTIVE`、槽位注册齐全、scope 取自正确的服务。之所以能直接导入真实入口,是因为它运行期只依赖 React 与本地文件,所有 DSH 包都是**类型导入**、构建时即被擦除。
|
|
74
|
+
- 此前 `client-fallback.spec.ts` 抓不到这个缺陷:它**手工镜像**入口函数体,因此验证的是「思路」而**从不执行真实的 `inject` 数组**——而恰恰是那个数组让 fiber 卡在 PENDING。
|
|
75
|
+
- **变异验证**:把 `inject` 改回 `['slots','locale','settingsScope']`,0.1.7 与「无设置面」两例立即变红(fiber 停在 0/1,永远到不了 `ACTIVE`),0.1.5 那例仍绿——精确复现了 issue 报的现象。
|
|
76
|
+
- **新增 `tests/dsh-017-compat.spec.ts`(13 例)**:活引用的单层/深层解包、数组与嵌套、**不就地改写调用方对象**;`regionEnabledOf` / `regionStateOf` / `regionEnabled` 穿透活引用读值;合并时**保留兄弟区域且不泄露函数**;`asVolatile()` 的两条分支按运行时能力确定性断言;以及**宿主端 `configure()` 优先于 `installSection()`**(用一个只有 `configure` 的真 Service 装配,断言走的是 `configure({auto:true})` 且没有 `installSection` 报错)。
|
|
77
|
+
- **变异验证**:把 `configure` 探测改回无保护裸调 `installSection` → 1 条立即变红;把 `nextRegionSlots` 的深解包去掉 → 1 条立即变红。
|
|
78
|
+
- `client-fallback.spec.ts` 的镜像同步更新,并补 2 例:**两条线都提供时 `configForms` 优先**(0.1.7 的写入闸门只认它),以及 **`configForms` 缺席时回落 `settingsScope.bind()`**。
|
|
79
|
+
- **`card-region-switch.spec.tsx` 新增 8 例**(渲染真组件、点真按钮):穿透活引用读区域开关;合并时**保留兄弟区域且不泄露函数**;Host 明确拒绝(`set()` 返回 `false`)与**「接受了但没落盘」**(`set()` 返回 `true` 而文档未变)两种失败都必须显式报错;0.1.5 的 `void` 返回视为成功;`view: 'page'` 时默认展开、无 `view` 时默认折叠;无设置面时渲染为只读。
|
|
80
|
+
- **变异验证**:去掉 `regionsMapOf` 的解包 → 1 条失败;去掉 `view` 判断 → 1 条失败;去掉 `false` 判断 → 0 条失败(回读校验兜住了,说明两层防护确实互补);去掉回读校验 → 1 条失败;**两层都去掉 → 1 条失败**。
|
|
81
|
+
- 顺带移除了该文件对 `dsh-client-ui-primitives` 的整包 `vi.mock`:图标已改纯 CSS,不再需要这个桩。
|
|
82
|
+
|
|
83
|
+
**双版本验证**:同一份代码在 schemastery **3.18.4**(volatile 生效)与 **3.18.2**(no-op 分支)下**均为 310 通过**。
|
|
84
|
+
|
|
3
85
|
## 2.1.0 (2026-09-24)
|
|
4
86
|
|
|
5
87
|
### Features
|
package/README.en.md
CHANGED
|
@@ -94,6 +94,18 @@ npm install dsh-connect-trae
|
|
|
94
94
|
|
|
95
95
|
Restart the DSH process after install/update/uninstall.
|
|
96
96
|
|
|
97
|
+
## DSH version compatibility
|
|
98
|
+
|
|
99
|
+
**Requires DSH 0.1.7-rc.1 and up** (since 2.3.0 the plugin no longer targets hosts older than 0.1.7; the peer dependency ranges are narrowed accordingly).
|
|
100
|
+
|
|
101
|
+
0.1.7 rebuilt the settings machinery, and the plugin follows the 0.1.7 contract:
|
|
102
|
+
|
|
103
|
+
- **Host registration**: `SettingsForms.configure({auto}, owner)` (`installSection` was removed in 0.1.7), and the namespace is the **Loader entry id the host actually serves** (`ctx.fiber.entry?.options.id`, falling back to `trae`) — the harness looks a provider's namespace up by exact match, so hard-coding `trae` made the provider read as "not configured".
|
|
104
|
+
- **Client settings surface**: `configForms` (`settingsScope` was removed in 0.1.7). `inject` declares only `slots` / `locale`; the settings surface is probed with `ctx.get()`. Cordis' dependency gate is hard — any `inject` entry the running line does not provide keeps `apply()` from ever running (this is why 2.2.0 and earlier sat at `pending (waiting for service: settingsScope)` on 0.1.7).
|
|
105
|
+
- **Config card slots**: `plugins.bundle.config` / `plugins.row.config` (`settings.plugin.item` was removed).
|
|
106
|
+
- **Schema writable marker**: writable fields must be marked `volatile()` (`asVolatile`), or 0.1.7's write gate rejects every write; 0.1.7 delivers config values as `{get(): T}` live references, so every read and merge path unwraps them first (`unwrapVolatile` / `unwrapVolatileDeep`).
|
|
107
|
+
- **The collapse caret is pure CSS**, with no static primitives icon import — icon naming changes across releases, so a static import is not a stable contract.
|
|
108
|
+
|
|
97
109
|
## Windows notes
|
|
98
110
|
|
|
99
111
|
- **Account data directory**: the plugin reads `%APPDATA%\Trae CN` / `%APPDATA%\TRAE SOLO CN` → `User\globalStorage\storage.json` (unrelated to the install directory). The folder names match macOS, so no extra config is needed; if the name does not match, point the `authFile` + `edition` plugin options at the exact path.
|
package/README.md
CHANGED
|
@@ -96,6 +96,18 @@ npm install dsh-connect-trae
|
|
|
96
96
|
|
|
97
97
|
安装、更新或卸载 bundle 后,需要重启对应的 DSH 进程。
|
|
98
98
|
|
|
99
|
+
## DSH 版本兼容
|
|
100
|
+
|
|
101
|
+
**要求 DSH 0.1.7-rc.1 及以上**(2.3.0 起不再支持 0.1.7 之前的宿主;peer 依赖范围已按此收窄)。
|
|
102
|
+
|
|
103
|
+
0.1.7 把设置体系整体重建,插件按 0.1.7 的契约工作:
|
|
104
|
+
|
|
105
|
+
- **宿主端注册**:`SettingsForms.configure({auto}, owner)`(0.1.7 起 `installSection` 已删除),命名空间取**宿主实际服务的 Loader 条目 id**(`ctx.fiber.entry?.options.id`,回落到 `trae`)——harness 按精确匹配查找 provider 的命名空间,写死 `trae` 会让 provider 被判「未配置」。
|
|
106
|
+
- **客户端设置面**:`configForms`(0.1.7 起 `settingsScope` 已移除)。`inject` 只声明 `slots` / `locale`,设置面用 `ctx.get()` 软探测——Cordis 的依赖闸门是硬闸,`inject` 里任一服务在运行线上不存在,`apply()` 就永远不会执行(这正是 2.2.0 及更早版本在 0.1.7 上卡在 `pending (waiting for service: settingsScope)` 的原因)。
|
|
107
|
+
- **配置卡片槽位**:`plugins.bundle.config` / `plugins.row.config`(`settings.plugin.item` 已删除)。
|
|
108
|
+
- **schema 可写声明**:可写字段必须标 `volatile()`(`asVolatile`),否则写入被 0.1.7 的写入门直接拒绝;0.1.7 以 `{get(): T}` 活引用交付配置值,所有读取与合并路径都先解包(`unwrapVolatile` / `unwrapVolatileDeep`)。
|
|
109
|
+
- **折叠箭头用纯 CSS**,不静态导入任何 primitives 图标——图标命名族随版本变动,静态导入不是稳定契约。
|
|
110
|
+
|
|
99
111
|
## Windows 说明
|
|
100
112
|
|
|
101
113
|
- **账号数据目录**:插件读取 `%APPDATA%\Trae CN` / `%APPDATA%\TRAE SOLO CN` 下的 `User\globalStorage\storage.json`(与安装目录无关)。目录名与 macOS 一致,无需额外配置;若目录名对不上,可用插件配置项 `authFile` + `edition` 直接指定完整路径。
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -5,7 +5,6 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
-
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
9
|
//#region src/status-paths.ts
|
|
11
10
|
/** Plugin-owned usage endpoint consumed by its browser half. */
|
|
@@ -31,6 +30,42 @@ window.__ModuleLoader__.load({
|
|
|
31
30
|
return `${path}?${TRAE_REGION_PARAM}=${region}`;
|
|
32
31
|
}
|
|
33
32
|
/**
|
|
33
|
+
* Peel ONE `{get(): T}` live reference, the shape DSH 0.1.7 delivers a
|
|
34
|
+
* volatile-marked settings field as (see {@link unwrapVolatileDeep}).
|
|
35
|
+
*/
|
|
36
|
+
function unwrapVolatile(value) {
|
|
37
|
+
if (value !== null && typeof value === "object" && typeof value.get === "function") return value.get();
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Deep copy of a value with every `{get(): T}` live reference replaced by the
|
|
42
|
+
* value it resolves to.
|
|
43
|
+
*
|
|
44
|
+
* DSH 0.1.7 hands volatile-marked fields back as live references, and a live
|
|
45
|
+
* reference is still `typeof === 'object'` — so it passes a naive object check
|
|
46
|
+
* and every lookup on it is `undefined`. Worse, spreading one (`{ ...ref }`)
|
|
47
|
+
* does NOT read the field: it produces `{ get: <function> }`. Any merge that
|
|
48
|
+
* spreads a resolved field to preserve its siblings — the card's "write one
|
|
49
|
+
* region, keep the other" write, or the account map's spread — would otherwise
|
|
50
|
+
* DROP every sibling and leak a function into the document.
|
|
51
|
+
*
|
|
52
|
+
* Both halves therefore unwrap before touching a resolved field. This lives in
|
|
53
|
+
* the node-free host↔client bridge because the browser half needs it too.
|
|
54
|
+
*
|
|
55
|
+
* Non-reference values are recursed into so a nested volatile field is caught
|
|
56
|
+
* as well; arrays and objects are rebuilt rather than mutated, so the caller's
|
|
57
|
+
* value is never touched.
|
|
58
|
+
*/
|
|
59
|
+
function unwrapVolatileDeep(value) {
|
|
60
|
+
if (value === null || typeof value !== "object") return value;
|
|
61
|
+
if (typeof value.get === "function") return unwrapVolatileDeep(value.get());
|
|
62
|
+
if (Array.isArray(value)) return value.map((entry) => unwrapVolatileDeep(entry));
|
|
63
|
+
const source = value;
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const key of Object.keys(source)) out[key] = unwrapVolatileDeep(source[key]);
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
34
69
|
* Build the next `regions` settings value for the card's save. The write
|
|
35
70
|
* targets ONLY the signed-in account's region slot; every other region's slot
|
|
36
71
|
* is carried over untouched, so switching accounts never clobbers the other
|
|
@@ -38,8 +73,9 @@ window.__ModuleLoader__.load({
|
|
|
38
73
|
* from an empty document.
|
|
39
74
|
*/
|
|
40
75
|
function nextRegionSlots(regions, region, slot) {
|
|
76
|
+
const unwrapped = unwrapVolatileDeep(regions);
|
|
41
77
|
return {
|
|
42
|
-
...typeof
|
|
78
|
+
...typeof unwrapped === "object" && unwrapped !== null && !Array.isArray(unwrapped) ? unwrapped : {},
|
|
43
79
|
[region]: slot
|
|
44
80
|
};
|
|
45
81
|
}
|
|
@@ -50,17 +86,23 @@ window.__ModuleLoader__.load({
|
|
|
50
86
|
* where the map was expected was a real shipped bug — the lookup then read
|
|
51
87
|
* `section['cn']` (absent), so the card's checkbox reported `true` forever and
|
|
52
88
|
* clicking it appeared to do nothing even though the write succeeded.
|
|
89
|
+
*
|
|
90
|
+
* Unwraps live references first: on DSH 0.1.7 the `regions` field arrives as a
|
|
91
|
+
* `{get(): T}` reference, which passes the `typeof === 'object'` check below
|
|
92
|
+
* and would otherwise be returned as if it were the map — making every region
|
|
93
|
+
* read back as absent (and the enabled flag read as "on" forever).
|
|
53
94
|
*/
|
|
54
95
|
function regionsMapOf(value) {
|
|
55
|
-
|
|
56
|
-
|
|
96
|
+
const unwrapped = unwrapVolatileDeep(value);
|
|
97
|
+
if (typeof unwrapped !== "object" || unwrapped === null || Array.isArray(unwrapped)) return {};
|
|
98
|
+
const record = unwrapped;
|
|
57
99
|
const nested = record["regions"];
|
|
58
100
|
if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) return nested;
|
|
59
101
|
return record;
|
|
60
102
|
}
|
|
61
103
|
/** One region's stored slot as a plain object; any other shape reads as empty. */
|
|
62
104
|
function regionSlotOf(value, region) {
|
|
63
|
-
const slot = regionsMapOf(value)[region];
|
|
105
|
+
const slot = unwrapVolatile(regionsMapOf(value)[region]);
|
|
64
106
|
return typeof slot === "object" && slot !== null && !Array.isArray(slot) ? slot : {};
|
|
65
107
|
}
|
|
66
108
|
/**
|
|
@@ -123,7 +165,11 @@ window.__ModuleLoader__.load({
|
|
|
123
165
|
.dsm-plugin-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
|
|
124
166
|
.dsm-plugin-card-title{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:15px;font-weight:600;line-height:1.4}
|
|
125
167
|
.dsm-plugin-card-description{color:var(--dsw-alias-label-tertiary,#999);font-size:13px;line-height:1.5}
|
|
126
|
-
|
|
168
|
+
/* Pure-CSS caret: the host primitives' chevron icon names are not part of any
|
|
169
|
+
stable contract across DSH releases, so no static import is used — a border
|
|
170
|
+
caret in the plugin's own CSS is version-proof. */
|
|
171
|
+
.dsm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary,#999);flex:none;width:16px;height:16px;position:relative;transition:transform .16s}
|
|
172
|
+
.dsm-plugin-card-chevron::before{content:"";display:block;position:absolute;left:4px;top:5px;width:7px;height:7px;border-right:1.6px solid currentColor;border-bottom:1.6px solid currentColor;transform:rotate(45deg)}
|
|
127
173
|
.dsm-plugin-card-chevron-open{transform:rotate(180deg)}
|
|
128
174
|
.dsm-plugin-card-body{border-top:1px solid var(--dsw-alias-border-l2,#36373b);margin:0 16px;padding:0 0 8px}
|
|
129
175
|
.dsm-plugin-card-icon{width:32px;height:32px;flex:none;border-radius:7px}
|
|
@@ -230,11 +276,49 @@ window.__ModuleLoader__.load({
|
|
|
230
276
|
*/
|
|
231
277
|
const POLL_INTERVAL_MS = 6e4;
|
|
232
278
|
const TRAE_GITHUB_URL = "https://github.com/dingminhua/dsh-connect-trae";
|
|
233
|
-
/**
|
|
279
|
+
/**
|
|
280
|
+
* Read the per-region account selections out of the settings snapshot.
|
|
281
|
+
*
|
|
282
|
+
* Deep-unwraps first: on DSH 0.1.7 a volatile field arrives as a `{get(): T}`
|
|
283
|
+
* live reference, which passes the `typeof === 'object'` check below and would
|
|
284
|
+
* be returned as if it were the map. Every lookup on it is then `undefined`,
|
|
285
|
+
* and the card's merge (`{ ...configured, [region]: id }`) would spread a
|
|
286
|
+
* reference into `{get: <function>}` — dropping the other region's selection
|
|
287
|
+
* and leaking a function into the settings document.
|
|
288
|
+
*/
|
|
234
289
|
function configuredAccountsOf(configured) {
|
|
235
|
-
const accounts = configured?.accounts;
|
|
290
|
+
const accounts = unwrapVolatileDeep(configured)?.accounts;
|
|
236
291
|
return typeof accounts === "object" && accounts !== null ? accounts : {};
|
|
237
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Write one settings field and confirm the value actually landed.
|
|
295
|
+
*
|
|
296
|
+
* `configForms.set()` resolving is NOT proof that anything was stored: a write
|
|
297
|
+
* can be accepted by the transport and still not materialize — a rejected
|
|
298
|
+
* write reloads Host state and merely RETURNS, so the caller's `await`
|
|
299
|
+
* succeeds while the document is unchanged. The form answers with an explicit
|
|
300
|
+
* boolean for exactly this reason.
|
|
301
|
+
*
|
|
302
|
+
* Either way the caller must not report success on an unpersisted write: the
|
|
303
|
+
* user would see the control flip and then silently revert.
|
|
304
|
+
*
|
|
305
|
+
* @throws {TraeSettingsWriteError} when the write was refused or did not land.
|
|
306
|
+
*/
|
|
307
|
+
var TraeSettingsWriteError = class extends Error {
|
|
308
|
+
constructor(field, detail) {
|
|
309
|
+
super(detail === void 0 || detail === "" ? `trae: settings field "${field}" was not persisted by the settings write` : `trae: settings field "${field}" was not persisted by the settings write (${detail})`);
|
|
310
|
+
this.name = "TraeSettingsWriteError";
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
async function writeSettingsField(scope, field, value, landed) {
|
|
314
|
+
if (await scope.set(field, value) === false) throw new TraeSettingsWriteError(field, "the Host refused the write");
|
|
315
|
+
if (!landed(unwrapVolatileDeep(scope.getSnapshot().value))) throw new TraeSettingsWriteError(field);
|
|
316
|
+
}
|
|
317
|
+
/** Read one region's stored `regions` slot out of an unwrapped settings value. */
|
|
318
|
+
function regionSlotsOf(value) {
|
|
319
|
+
const regions = value?.regions;
|
|
320
|
+
return typeof regions === "object" && regions !== null ? regions : {};
|
|
321
|
+
}
|
|
238
322
|
/** Inject the shared card CSS once. */
|
|
239
323
|
if (typeof document !== "undefined") {
|
|
240
324
|
const cssId = "dsh-connect-trae/client.css";
|
|
@@ -271,9 +355,9 @@ window.__ModuleLoader__.load({
|
|
|
271
355
|
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)" };
|
|
272
356
|
}
|
|
273
357
|
/** Render Trae sign-in state and the total usage summary as one expandable card. */
|
|
274
|
-
function TraeUsageCard({ t, settingsScope }) {
|
|
358
|
+
function TraeUsageCard({ t, settingsScope, view }) {
|
|
275
359
|
if (t === void 0) throw new Error("Trae usage card requires its translation function");
|
|
276
|
-
const [open, setOpen] = (0, react.useState)(
|
|
360
|
+
const [open, setOpen] = (0, react.useState)(view === "page");
|
|
277
361
|
/** The region whose tab is on screen; each tab is its own provider stack. */
|
|
278
362
|
const [activeRegion, setActiveRegion] = (0, react.useState)("cn");
|
|
279
363
|
/** Last-known usage per region, so tab dots survive tab switches. */
|
|
@@ -304,6 +388,12 @@ window.__ModuleLoader__.load({
|
|
|
304
388
|
const [claiming, setClaiming] = (0, react.useState)(false);
|
|
305
389
|
/** Last claim refusal, shown until the next successful refresh. */
|
|
306
390
|
const [claimError, setClaimError] = (0, react.useState)(void 0);
|
|
391
|
+
/**
|
|
392
|
+
* A refused or unpersisted settings write, surfaced instead of silently
|
|
393
|
+
* reverting. On 0.1.7 a rejected write resolves normally, so without this
|
|
394
|
+
* the control would flip and then quietly snap back on the next snapshot.
|
|
395
|
+
*/
|
|
396
|
+
const [writeError, setWriteError] = (0, react.useState)(void 0);
|
|
307
397
|
const mounted = (0, react.useRef)(true);
|
|
308
398
|
(0, react.useEffect)(() => {
|
|
309
399
|
mounted.current = true;
|
|
@@ -399,6 +489,7 @@ window.__ModuleLoader__.load({
|
|
|
399
489
|
]);
|
|
400
490
|
const rescanAccounts = async () => {
|
|
401
491
|
setBusy(true);
|
|
492
|
+
setWriteError(void 0);
|
|
402
493
|
try {
|
|
403
494
|
const response = await fetch(withTraeRegion(TRAE_ACCOUNTS_REFRESH_PATH, activeRegion), {
|
|
404
495
|
method: "POST",
|
|
@@ -409,11 +500,13 @@ window.__ModuleLoader__.load({
|
|
|
409
500
|
if (!response.ok || !Array.isArray(body.accounts)) throw new Error(`HTTP ${response.status}`);
|
|
410
501
|
const selected = body.accounts.find((account) => account.selected)?.id;
|
|
411
502
|
const configured = configuredAccountsOf(settingsScope?.getSnapshot().value);
|
|
412
|
-
if (selected !== void 0 && selected !== configured[activeRegion] && settingsScope
|
|
503
|
+
if (selected !== void 0 && selected !== configured[activeRegion] && settingsScope !== void 0 && settingsScope.getSnapshot().writable === true) await writeSettingsField(settingsScope, "accounts", {
|
|
413
504
|
...configured,
|
|
414
505
|
[activeRegion]: selected
|
|
415
|
-
});
|
|
506
|
+
}, (readBack) => configuredAccountsOf(readBack)[activeRegion] === selected);
|
|
416
507
|
await refreshUsage(activeRegion);
|
|
508
|
+
} catch (error) {
|
|
509
|
+
if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
417
510
|
} finally {
|
|
418
511
|
if (mounted.current) setBusy(false);
|
|
419
512
|
}
|
|
@@ -421,13 +514,15 @@ window.__ModuleLoader__.load({
|
|
|
421
514
|
const switchAccount = async (accountId) => {
|
|
422
515
|
if (settingsScope === void 0) return;
|
|
423
516
|
setSwitchingAccount(true);
|
|
517
|
+
setWriteError(void 0);
|
|
424
518
|
try {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
...configured,
|
|
519
|
+
await writeSettingsField(settingsScope, "accounts", {
|
|
520
|
+
...configuredAccountsOf(settingsScope.getSnapshot().value),
|
|
428
521
|
[activeRegion]: accountId
|
|
429
|
-
});
|
|
522
|
+
}, (readBack) => configuredAccountsOf(readBack)[activeRegion] === accountId);
|
|
430
523
|
await refreshUsage(activeRegion);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
431
526
|
} finally {
|
|
432
527
|
if (mounted.current) setSwitchingAccount(false);
|
|
433
528
|
}
|
|
@@ -474,8 +569,11 @@ window.__ModuleLoader__.load({
|
|
|
474
569
|
const toggleRegion = async (item, enabled) => {
|
|
475
570
|
if (settingsScope === void 0 || settingsScope.getSnapshot().writable !== true) return;
|
|
476
571
|
setTogglingRegion(item);
|
|
572
|
+
setWriteError(void 0);
|
|
477
573
|
try {
|
|
478
|
-
await settingsScope
|
|
574
|
+
await writeSettingsField(settingsScope, "regions", nextRegionEnabled(settingsScope.getSnapshot().value, item, enabled), (readBack) => regionSlotsOf(readBack)[item] !== void 0 && regionSlotsOf(readBack)[item].enabled === enabled);
|
|
575
|
+
} catch (error) {
|
|
576
|
+
if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
479
577
|
} finally {
|
|
480
578
|
if (mounted.current) setTogglingRegion(void 0);
|
|
481
579
|
}
|
|
@@ -523,7 +621,7 @@ window.__ModuleLoader__.load({
|
|
|
523
621
|
if (mounted.current) setBusy(false);
|
|
524
622
|
}
|
|
525
623
|
};
|
|
526
|
-
const settingsValue = settingsScope?.getSnapshot().value;
|
|
624
|
+
const settingsValue = unwrapVolatileDeep(settingsScope?.getSnapshot().value);
|
|
527
625
|
const region = activeRegion;
|
|
528
626
|
const configuredRegions = typeof settingsValue === "object" && settingsValue !== null && typeof settingsValue.regions === "object" && settingsValue.regions !== null ? settingsValue.regions : {};
|
|
529
627
|
const regionSlot = configuredRegions[region];
|
|
@@ -594,9 +692,10 @@ window.__ModuleLoader__.load({
|
|
|
594
692
|
const saveModels = async () => {
|
|
595
693
|
if (settingsScope === void 0) return;
|
|
596
694
|
setSaving(true);
|
|
695
|
+
setWriteError(void 0);
|
|
597
696
|
try {
|
|
598
697
|
if (status.status !== "signed-in") return;
|
|
599
|
-
|
|
698
|
+
const nextSlot = {
|
|
600
699
|
enabled: regionOn(activeRegion),
|
|
601
700
|
lastCatalog: visibleModels.map((model) => ({
|
|
602
701
|
...model,
|
|
@@ -605,9 +704,15 @@ window.__ModuleLoader__.load({
|
|
|
605
704
|
enabledModelIds: [...activeEnabledIds],
|
|
606
705
|
imageModelIds: [...activeImageIds].filter((id) => activeEnabledIds.has(id)),
|
|
607
706
|
contextBudgets: activeContextBudgets
|
|
608
|
-
}
|
|
707
|
+
};
|
|
708
|
+
await writeSettingsField(settingsScope, "regions", nextRegionSlots(configuredRegions, activeRegion, nextSlot), (readBack) => {
|
|
709
|
+
const ids = regionSlotsOf(readBack)[activeRegion]?.enabledModelIds;
|
|
710
|
+
return Array.isArray(ids) && ids.length === nextSlot.enabledModelIds.length && nextSlot.enabledModelIds.every((id) => ids.includes(id));
|
|
711
|
+
});
|
|
609
712
|
discardModels();
|
|
610
713
|
await refreshUsage(activeRegion);
|
|
714
|
+
} catch (error) {
|
|
715
|
+
if (mounted.current) setWriteError(error instanceof Error ? error.message : t("row.requestFailed"));
|
|
611
716
|
} finally {
|
|
612
717
|
if (mounted.current) setSaving(false);
|
|
613
718
|
}
|
|
@@ -642,8 +747,7 @@ window.__ModuleLoader__.load({
|
|
|
642
747
|
}),
|
|
643
748
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
644
749
|
"aria-hidden": "true",
|
|
645
|
-
className: `dsm-plugin-card-chevron${open ? " dsm-plugin-card-chevron-open" : ""}
|
|
646
|
-
children: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
|
|
750
|
+
className: `dsm-plugin-card-chevron${open ? " dsm-plugin-card-chevron-open" : ""}`
|
|
647
751
|
})
|
|
648
752
|
]
|
|
649
753
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -695,6 +799,11 @@ window.__ModuleLoader__.load({
|
|
|
695
799
|
className: "dsm-trae-tab-hint",
|
|
696
800
|
children: t("row.tabHint")
|
|
697
801
|
}),
|
|
802
|
+
writeError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
803
|
+
className: "dsm-trae-usage-error",
|
|
804
|
+
role: "alert",
|
|
805
|
+
children: t("row.settingsWriteFailed", { message: writeError })
|
|
806
|
+
}),
|
|
698
807
|
regionOn(activeRegion) ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
699
808
|
className: "dsm-trae-tab-off-notice",
|
|
700
809
|
role: "status",
|
|
@@ -1069,6 +1178,7 @@ window.__ModuleLoader__.load({
|
|
|
1069
1178
|
"row.tabSwitchHint": "Uncheck to switch this provider off. Its models disappear from the DSH model picker; its account, usage, and model settings are kept and return when you check it again.",
|
|
1070
1179
|
"row.tabSwitchAria": "Offer the {region} provider to DSH",
|
|
1071
1180
|
"row.tabOffNotice": "This provider is switched off: none of its models are offered to DSH. Your account, usage, and model settings are kept — check the box above the tab to bring it back.",
|
|
1181
|
+
"row.settingsWriteFailed": "The setting was not saved: {message}",
|
|
1072
1182
|
"row.signedOut": "Not signed in",
|
|
1073
1183
|
"row.signedOutHint": "Sign in once in the Trae desktop app; this plugin follows that sign-in automatically.",
|
|
1074
1184
|
"row.searchedTitle": "Paths checked",
|
|
@@ -1146,6 +1256,7 @@ window.__ModuleLoader__.load({
|
|
|
1146
1256
|
"row.tabSwitchHint": "取消勾选即关闭该供应商:它的模型不再出现在 DSH 模型选择器里;账号、用量与模型设置都会保留,重新勾选即恢复。",
|
|
1147
1257
|
"row.tabSwitchAria": "是否向 DSH 提供{region}供应商",
|
|
1148
1258
|
"row.tabOffNotice": "该供应商已关闭:它的模型不会提供给 DSH。账号、用量与模型设置均已保留——勾选上方复选框即可恢复。",
|
|
1259
|
+
"row.settingsWriteFailed": "设置未能保存:{message}",
|
|
1149
1260
|
"row.signedOut": "未登录",
|
|
1150
1261
|
"row.signedOutHint": "在 Trae 桌面 App 里登录一次即可,插件会自动跟随当前登录的账号。",
|
|
1151
1262
|
"row.searchedTitle": "已检查的路径",
|
|
@@ -1216,12 +1327,16 @@ window.__ModuleLoader__.load({
|
|
|
1216
1327
|
//#region src/client/index.tsx
|
|
1217
1328
|
/** Stable browser-plugin name. */
|
|
1218
1329
|
const name = "dsh-connect-trae-client";
|
|
1219
|
-
/**
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1330
|
+
/**
|
|
1331
|
+
* Client services required by the Plugin configuration contribution.
|
|
1332
|
+
*
|
|
1333
|
+
* Deliberately only the two services the 0.1.7 line provides unconditionally:
|
|
1334
|
+
* `slots` (card injection) and `locale` (copy). The settings surface is probed
|
|
1335
|
+
* through `ctx.get()` (which returns undefined, never throws, for an absent
|
|
1336
|
+
* service), so the card degrades to read-only rather than failing to load if
|
|
1337
|
+
* configForms is not served yet.
|
|
1338
|
+
*/
|
|
1339
|
+
const inject = ["slots", "locale"];
|
|
1225
1340
|
/**
|
|
1226
1341
|
* Register card copy and the Trae card under Plugin configuration.
|
|
1227
1342
|
*
|
|
@@ -1234,6 +1349,8 @@ window.__ModuleLoader__.load({
|
|
|
1234
1349
|
* NOTE: the try/catch boundary of this function is mirrored (duplicated) in
|
|
1235
1350
|
* `tests/client-fallback.spec.ts`, because the real client entry imports
|
|
1236
1351
|
* browser-only DSH packages that cannot load in the Node test environment.
|
|
1352
|
+
* If you change the guarded body, the soft probe, or the per-slot
|
|
1353
|
+
* registration here, update that mirror too.
|
|
1237
1354
|
*/
|
|
1238
1355
|
function apply(ctx) {
|
|
1239
1356
|
try {
|
|
@@ -1243,16 +1360,93 @@ window.__ModuleLoader__.load({
|
|
|
1243
1360
|
en
|
|
1244
1361
|
}), "dsh-connect-trae: settings copy");
|
|
1245
1362
|
const t = ctx.locale.bind(namespace);
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1363
|
+
const softGet = (name) => ctx.get(name);
|
|
1364
|
+
const forms = softGet("configForms");
|
|
1365
|
+
/**
|
|
1366
|
+
* The card's settings scope, bound to whichever namespace the Host actually
|
|
1367
|
+
* serves — resolved from the describe mirror, and RE-BOUND whenever the
|
|
1368
|
+
* mirror changes.
|
|
1369
|
+
*
|
|
1370
|
+
* The mirror answers asynchronously relative to this plugin's `apply()`
|
|
1371
|
+
* (and, on a profile reload, may gain namespaces that did not exist at
|
|
1372
|
+
* boot — this plugin's entry is exactly such a case). Resolving the
|
|
1373
|
+
* namespace ONCE at apply time and falling back to the declared constant
|
|
1374
|
+
* is a real failure: the form controller is bound to its namespace forever,
|
|
1375
|
+
* so a card whose entry appeared after the mirror's first load writes to
|
|
1376
|
+
* `trae` — a namespace the host does not serve — and every write is
|
|
1377
|
+
* refused (`No configurable plugin entry "trae"`), while the controls stay
|
|
1378
|
+
* enabled (the mirror reports global `writable` even when the namespace is
|
|
1379
|
+
* missing).
|
|
1380
|
+
*
|
|
1381
|
+
* The scope therefore forwards every read/write to the CURRENT controller:
|
|
1382
|
+
* once the mirror lists the trae namespace (any name matching `/trae/i`,
|
|
1383
|
+
* e.g. `dsh-connect-trae` or `include:dsh-connect-trae`), the card lands on
|
|
1384
|
+
* the right namespace and re-renders through its own subscription. Before
|
|
1385
|
+
* the mirror answers, the scope reports `writable: false` so the card is
|
|
1386
|
+
* read-only rather than offering controls that cannot save.
|
|
1387
|
+
*/
|
|
1388
|
+
const settingsScope = (() => {
|
|
1389
|
+
let current;
|
|
1390
|
+
let currentOff;
|
|
1391
|
+
let refreshedOnce = false;
|
|
1392
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1393
|
+
const notify = () => {
|
|
1394
|
+
for (const listener of [...listeners]) listener();
|
|
1395
|
+
};
|
|
1396
|
+
const scope = {
|
|
1397
|
+
getSnapshot: () => current?.getSnapshot() ?? {
|
|
1398
|
+
status: "unavailable",
|
|
1399
|
+
value: void 0,
|
|
1400
|
+
writable: false
|
|
1401
|
+
},
|
|
1402
|
+
subscribe: (listener) => {
|
|
1403
|
+
listeners.add(listener);
|
|
1404
|
+
return () => {
|
|
1405
|
+
listeners.delete(listener);
|
|
1406
|
+
};
|
|
1407
|
+
},
|
|
1408
|
+
set: (field, value) => current !== void 0 ? current.set(field, value) : Promise.resolve(false)
|
|
1409
|
+
};
|
|
1410
|
+
const rebind = () => {
|
|
1411
|
+
let served;
|
|
1412
|
+
try {
|
|
1413
|
+
served = (forms?.describe().getSnapshot().view?.namespaces ?? []).find((entry) => entry.ns === "trae" || /trae/i.test(entry.ns));
|
|
1414
|
+
} catch {}
|
|
1415
|
+
const next = served === void 0 || forms === void 0 ? void 0 : forms.get(served.ns);
|
|
1416
|
+
if (next !== current) {
|
|
1417
|
+
currentOff?.();
|
|
1418
|
+
current = next;
|
|
1419
|
+
currentOff = current?.subscribe(notify);
|
|
1420
|
+
notify();
|
|
1421
|
+
}
|
|
1422
|
+
if (next === void 0 && !refreshedOnce) {
|
|
1423
|
+
refreshedOnce = true;
|
|
1424
|
+
try {
|
|
1425
|
+
(forms?.describe()).load?.();
|
|
1426
|
+
} catch {}
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
rebind();
|
|
1430
|
+
forms?.describe().subscribe?.(rebind);
|
|
1431
|
+
return scope;
|
|
1432
|
+
})();
|
|
1433
|
+
const registerCard = (slotName, key) => {
|
|
1434
|
+
try {
|
|
1435
|
+
ctx.slots.inject(slotName, () => ctx.slots.register({
|
|
1436
|
+
name: slotName,
|
|
1437
|
+
key,
|
|
1438
|
+
priority: 30,
|
|
1439
|
+
inject: () => ({
|
|
1440
|
+
t,
|
|
1441
|
+
settingsScope
|
|
1442
|
+
})
|
|
1443
|
+
}, TraeUsageCard));
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
console.error(`[dsh-connect-trae] card slot "${slotName}" failed to register (host provider unaffected):`, error);
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
registerCard("plugins.bundle.config", "dsh-connect-trae");
|
|
1449
|
+
registerCard("plugins.row.config", "dsh-connect-trae#dsh-connect-trae");
|
|
1256
1450
|
} catch (error) {
|
|
1257
1451
|
console.error("[dsh-connect-trae] client card failed to load (host provider unaffected):", error);
|
|
1258
1452
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -175,6 +175,13 @@ interface TraeWireModel {
|
|
|
175
175
|
contextWindow?: number;
|
|
176
176
|
maxTokens?: number;
|
|
177
177
|
reasoning?: TraeReasoningCapability;
|
|
178
|
+
/**
|
|
179
|
+
* Effective (post-discount) credit multiplier, read from the row's
|
|
180
|
+
* `display_contact_config` — the figure the Trae IDE renders (see
|
|
181
|
+
* `wireCreditMultiplier` in solo.ts). Wins over the Remote directory's own
|
|
182
|
+
* `consumption_rate`, which reports the undiscounted value.
|
|
183
|
+
*/
|
|
184
|
+
creditMultiplier?: number;
|
|
178
185
|
/**
|
|
179
186
|
* The `get_detail_param` function this config_name came from. Trae splits its
|
|
180
187
|
* callable roster across several functions (SOLO modes), and a model is only
|
|
@@ -191,12 +198,19 @@ interface TraeWireModel {
|
|
|
191
198
|
* skeleton: it supplies the display id, display name, context windows, credit
|
|
192
199
|
* multiplier, reasoning and multimodal flags. `wire` (from `get_detail_param`)
|
|
193
200
|
* supplies the real `llm_utils_chat` `config_name` — the only id the chat
|
|
194
|
-
* endpoint actually accepts
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
201
|
+
* endpoint actually accepts — plus the authoritative post-discount credit
|
|
202
|
+
* multiplier when `display_contact_config` carries one. A remote row is only
|
|
203
|
+
* callable when it maps to a wire `config_name`, so a remote row with no wire
|
|
204
|
+
* match is DROPPED (it would otherwise be sent as an invalid `config_name` and
|
|
205
|
+
* rejected with 4001 "param is invalid"). Verified 2026-08-30: the Remote
|
|
206
|
+
* directory advertises `Doubao-Seed-Code` and `glm-5.3`, neither of which is a
|
|
207
|
+
* current `config_name`; both fail every request, so they must not be exposed.
|
|
208
|
+
*
|
|
209
|
+
* Credit multiplier precedence: the wire's `display_contact_config` rate wins
|
|
210
|
+
* whenever present (it is the post-discount figure the Trae IDE renders — the
|
|
211
|
+
* Remote directory can report up to 10x the undiscounted value, see
|
|
212
|
+
* `wireCreditMultiplier` in solo.ts); the Remote `consumption_rate` is the
|
|
213
|
+
* fallback when the wire row carries none.
|
|
200
214
|
*
|
|
201
215
|
* Joining is two-tier, in priority order:
|
|
202
216
|
* 1. `wire.id` (the `config_name`) equals the remote id — the model's display
|
|
@@ -1054,6 +1068,8 @@ interface TraeSoloModel {
|
|
|
1054
1068
|
contextWindow?: number;
|
|
1055
1069
|
maxTokens?: number;
|
|
1056
1070
|
reasoning?: TraeReasoningCapability;
|
|
1071
|
+
/** Effective (post-discount) credit multiplier, as the Trae IDE shows it. */
|
|
1072
|
+
creditMultiplier?: number;
|
|
1057
1073
|
/** The directory function that listed this config (replayed when calling it). */
|
|
1058
1074
|
function?: string;
|
|
1059
1075
|
}
|
|
@@ -1435,6 +1451,31 @@ interface TraeWebSearchPath {
|
|
|
1435
1451
|
reason: 'missing' | 'unreadable' | 'invalid';
|
|
1436
1452
|
message?: string;
|
|
1437
1453
|
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Peel ONE `{get(): T}` live reference, the shape DSH 0.1.7 delivers a
|
|
1456
|
+
* volatile-marked settings field as (see {@link unwrapVolatileDeep}).
|
|
1457
|
+
*/
|
|
1458
|
+
declare function unwrapVolatile<T>(value: T): T;
|
|
1459
|
+
/**
|
|
1460
|
+
* Deep copy of a value with every `{get(): T}` live reference replaced by the
|
|
1461
|
+
* value it resolves to.
|
|
1462
|
+
*
|
|
1463
|
+
* DSH 0.1.7 hands volatile-marked fields back as live references, and a live
|
|
1464
|
+
* reference is still `typeof === 'object'` — so it passes a naive object check
|
|
1465
|
+
* and every lookup on it is `undefined`. Worse, spreading one (`{ ...ref }`)
|
|
1466
|
+
* does NOT read the field: it produces `{ get: <function> }`. Any merge that
|
|
1467
|
+
* spreads a resolved field to preserve its siblings — the card's "write one
|
|
1468
|
+
* region, keep the other" write, or the account map's spread — would otherwise
|
|
1469
|
+
* DROP every sibling and leak a function into the document.
|
|
1470
|
+
*
|
|
1471
|
+
* Both halves therefore unwrap before touching a resolved field. This lives in
|
|
1472
|
+
* the node-free host↔client bridge because the browser half needs it too.
|
|
1473
|
+
*
|
|
1474
|
+
* Non-reference values are recursed into so a nested volatile field is caught
|
|
1475
|
+
* as well; arrays and objects are rebuilt rather than mutated, so the caller's
|
|
1476
|
+
* value is never touched.
|
|
1477
|
+
*/
|
|
1478
|
+
declare function unwrapVolatileDeep<T>(value: T): T;
|
|
1438
1479
|
/**
|
|
1439
1480
|
* Build the next `regions` settings value for the card's save. The write
|
|
1440
1481
|
* targets ONLY the signed-in account's region slot; every other region's slot
|
|
@@ -1573,13 +1614,30 @@ declare const name = "dsh-connect-trae";
|
|
|
1573
1614
|
declare const inject: string[];
|
|
1574
1615
|
/**
|
|
1575
1616
|
* Settings namespace, as a plain lowercase literal. DSH 0.1.2 removed the
|
|
1576
|
-
* `settingsNamespace` brand constructor from the npm package
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1617
|
+
* `settingsNamespace` brand constructor from the npm package, and every
|
|
1618
|
+
* consumer of this constant takes it as a kebab string — so no branding is
|
|
1619
|
+
* needed. It is the FALLBACK namespace; the namespace the host actually serves
|
|
1620
|
+
* comes from {@link settingsNamespaceOf} (0.1.7 keys settings by the Loader
|
|
1621
|
+
* entry id, which a profile patch may mount as `dsh-connect-trae` or
|
|
1622
|
+
* `include:dsh-connect-trae`).
|
|
1581
1623
|
*/
|
|
1582
1624
|
declare const TRAE_SETTINGS_NS = "trae";
|
|
1625
|
+
/**
|
|
1626
|
+
* The namespace the HOST actually serves this plugin under.
|
|
1627
|
+
*
|
|
1628
|
+
* On DSH 0.1.7 `SettingsForms.describe()` keys every namespace by the Loader
|
|
1629
|
+
* entry id, and the harness looks a provider's namespace up by EXACT match —
|
|
1630
|
+
* advertising `trae` while the host serves `include:dsh-connect-trae` made the
|
|
1631
|
+
* provider read as "not configured": its configure affordance and model
|
|
1632
|
+
* discovery both silently went dead.
|
|
1633
|
+
*
|
|
1634
|
+
* `ctx.fiber.entry` is added by the Loader, not by Cordis itself, so it is not
|
|
1635
|
+
* in Cordis's public types and is absent on hosts that mount a plugin without a
|
|
1636
|
+
* Loader entry (a test harness, or `ctx.plugin()` called directly). Hence the
|
|
1637
|
+
* probe plus the documented fallback, mirroring the first-party plugins:
|
|
1638
|
+
* `const settingsNs = ctx.fiber.entry?.options.id ?? NS`.
|
|
1639
|
+
*/
|
|
1640
|
+
declare function settingsNamespaceOf(ctx: unknown): string;
|
|
1583
1641
|
/** One region's saved model state: its own directory and the user's selection within it. */
|
|
1584
1642
|
interface TraeRegionState {
|
|
1585
1643
|
/**
|
|
@@ -1655,7 +1713,22 @@ declare function regionStateOf(config: Config, region: TraeRegion): TraeRegionSt
|
|
|
1655
1713
|
* disagree about a region's state.
|
|
1656
1714
|
*/
|
|
1657
1715
|
declare function regionEnabled(config: Config, region: TraeRegion): boolean;
|
|
1716
|
+
/**
|
|
1717
|
+
* Mark a schema's field as volatile.
|
|
1718
|
+
*
|
|
1719
|
+
* `volatile()` exists from schemastery 3.18.3; since 2.3.0 the peer range
|
|
1720
|
+
* requires `>=3.18.4`, so it is always present on a supported host. The probe
|
|
1721
|
+
* is kept as a safety net for a consumer that resolves an older schemastery
|
|
1722
|
+
* anyway (where the helper must degrade to identity — hand-writing
|
|
1723
|
+
* `meta.volatile = true` would bypass schemastery's own `validateVolatileSchema`
|
|
1724
|
+
* checks) and to keep both arms deterministically testable.
|
|
1725
|
+
*
|
|
1726
|
+
* Without the marker, 0.1.7's settings write gate REJECTS every write with
|
|
1727
|
+
* `Plugin entry "trae" has no volatile fields` while the scope's `set()` still
|
|
1728
|
+
* resolves — so the card would look like it saved and silently revert.
|
|
1729
|
+
*/
|
|
1730
|
+
declare function asVolatile<S>(schema: S): S;
|
|
1658
1731
|
declare const Config: z<Config>;
|
|
1659
1732
|
declare function apply(ctx: Context, config: Config): void;
|
|
1660
1733
|
//#endregion
|
|
1661
|
-
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, type RawChatDelta, type RawChatMessage, type RawChatTool, SseDecoder, type SseEvent, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, type TraeActivityRule, type TraeAdapter, type TraeCachedModelConfig, TraeCatalog, type TraeChatResult, type TraeCheckinClaim, type TraeCheckinStatus, type TraeContextBudget, type TraeCredential, TraeCredentialStore, TraeDelegatingUpstreamClient, type TraeDiscoveredModel, type TraeDiscoveredReasoning, type TraeEdition, TraeFallbackUpstreamClient, type TraeFallbackUpstreamOptions, type TraeFusionRawChatEnvelope, TraeGatedUpstreamClient, type TraeGatedUpstreamOptions, type TraeIdentity, type TraeInputModality, type TraeModelDetailRequest, type TraeModelInfo, type TraeObservedModelConfig, TraeRawCapabilityController, type TraeRawCapabilityControllerOptions, type TraeRawCapabilitySnapshot, TraeRawCapabilityState, type TraeRawChatBehaviorConfig, type TraeRawChatCapability, type TraeRawChatClientOptions, type TraeRawChatConfig, type TraeRawChatFailureReason, type TraeRawChatRuntimeConfig, TraeRawChatUpstreamClient, type TraeRawDiagnostic, type TraeRawDiagnosticState, type TraeRawGateway, type TraeRawGatewayOptions, type TraeRawResolverResult, type TraeReasoningCapability, type TraeReasoningEffort, type TraeRefreshDevice, type TraeRegion, type TraeRegionGateways, TraeRegionState, type TraeShim, TraeSoloBridge, type TraeSoloClientOptions, TraeSoloRemoteCatalogClient, type TraeSoloRemoteCatalogOptions, TraeSoloUpstreamClient, type TraeStorageCandidate, type TraeStreamEvent, type TraeUpstreamClient, TraeUsageClient, type TraeUsageOptions, type TraeUsagePack, type TraeUsageRouteOptions, type TraeUsageSnapshot, type TraeUsageSummary, type TraeUsageView, type TraeWebActivity, type TraeWebCheckin, type TraeWebCheckinClaim, type TraeWebCredits, type TraeWebUsage, type TraeWireModel, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, withTraeRegion };
|
|
1734
|
+
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, type RawChatDelta, type RawChatMessage, type RawChatTool, SseDecoder, type SseEvent, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, type TraeActivityRule, type TraeAdapter, type TraeCachedModelConfig, TraeCatalog, type TraeChatResult, type TraeCheckinClaim, type TraeCheckinStatus, type TraeContextBudget, type TraeCredential, TraeCredentialStore, TraeDelegatingUpstreamClient, type TraeDiscoveredModel, type TraeDiscoveredReasoning, type TraeEdition, TraeFallbackUpstreamClient, type TraeFallbackUpstreamOptions, type TraeFusionRawChatEnvelope, TraeGatedUpstreamClient, type TraeGatedUpstreamOptions, type TraeIdentity, type TraeInputModality, type TraeModelDetailRequest, type TraeModelInfo, type TraeObservedModelConfig, TraeRawCapabilityController, type TraeRawCapabilityControllerOptions, type TraeRawCapabilitySnapshot, TraeRawCapabilityState, type TraeRawChatBehaviorConfig, type TraeRawChatCapability, type TraeRawChatClientOptions, type TraeRawChatConfig, type TraeRawChatFailureReason, type TraeRawChatRuntimeConfig, TraeRawChatUpstreamClient, type TraeRawDiagnostic, type TraeRawDiagnosticState, type TraeRawGateway, type TraeRawGatewayOptions, type TraeRawResolverResult, type TraeReasoningCapability, type TraeReasoningEffort, type TraeRefreshDevice, type TraeRegion, type TraeRegionGateways, TraeRegionState, type TraeShim, TraeSoloBridge, type TraeSoloClientOptions, TraeSoloRemoteCatalogClient, type TraeSoloRemoteCatalogOptions, TraeSoloUpstreamClient, type TraeStorageCandidate, type TraeStreamEvent, type TraeUpstreamClient, TraeUsageClient, type TraeUsageOptions, type TraeUsagePack, type TraeUsageRouteOptions, type TraeUsageSnapshot, type TraeUsageSummary, type TraeUsageView, type TraeWebActivity, type TraeWebCheckin, type TraeWebCheckinClaim, type TraeWebCredits, type TraeWebUsage, type TraeWireModel, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
import * as dshSettings from "@deepseek-ai/dsh-settings";
|
|
3
2
|
import { createProvider } from "@earendil-works/pi-ai";
|
|
4
3
|
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
5
4
|
import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
@@ -150,12 +149,19 @@ function displayKey(name) {
|
|
|
150
149
|
* skeleton: it supplies the display id, display name, context windows, credit
|
|
151
150
|
* multiplier, reasoning and multimodal flags. `wire` (from `get_detail_param`)
|
|
152
151
|
* supplies the real `llm_utils_chat` `config_name` — the only id the chat
|
|
153
|
-
* endpoint actually accepts
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
152
|
+
* endpoint actually accepts — plus the authoritative post-discount credit
|
|
153
|
+
* multiplier when `display_contact_config` carries one. A remote row is only
|
|
154
|
+
* callable when it maps to a wire `config_name`, so a remote row with no wire
|
|
155
|
+
* match is DROPPED (it would otherwise be sent as an invalid `config_name` and
|
|
156
|
+
* rejected with 4001 "param is invalid"). Verified 2026-08-30: the Remote
|
|
157
|
+
* directory advertises `Doubao-Seed-Code` and `glm-5.3`, neither of which is a
|
|
158
|
+
* current `config_name`; both fail every request, so they must not be exposed.
|
|
159
|
+
*
|
|
160
|
+
* Credit multiplier precedence: the wire's `display_contact_config` rate wins
|
|
161
|
+
* whenever present (it is the post-discount figure the Trae IDE renders — the
|
|
162
|
+
* Remote directory can report up to 10x the undiscounted value, see
|
|
163
|
+
* `wireCreditMultiplier` in solo.ts); the Remote `consumption_rate` is the
|
|
164
|
+
* fallback when the wire row carries none.
|
|
159
165
|
*
|
|
160
166
|
* Joining is two-tier, in priority order:
|
|
161
167
|
* 1. `wire.id` (the `config_name`) equals the remote id — the model's display
|
|
@@ -177,12 +183,13 @@ function mergeTraeModelSources(remote, wire) {
|
|
|
177
183
|
for (const model of remote) {
|
|
178
184
|
const wireModel = wireById.get(displayKey(model.id)) ?? wireByName.get(displayKey(model.name));
|
|
179
185
|
if (wireModel === void 0) continue;
|
|
186
|
+
const creditMultiplier = wireModel.creditMultiplier ?? model.creditMultiplier;
|
|
180
187
|
result.push({
|
|
181
188
|
id: model.id,
|
|
182
189
|
name: model.name,
|
|
183
190
|
...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
|
|
184
191
|
...model.maxContextWindow === void 0 ? {} : { maxContextWindow: model.maxContextWindow },
|
|
185
|
-
...
|
|
192
|
+
...creditMultiplier === void 0 ? {} : { creditMultiplier },
|
|
186
193
|
input: ["text"],
|
|
187
194
|
reasoningSupported: model.reasoningSupported,
|
|
188
195
|
...model.reasoning === void 0 ? {} : {
|
|
@@ -1934,6 +1941,39 @@ function prepareSoloBody(source, defaultModel = "glm-5.2", functionName) {
|
|
|
1934
1941
|
}
|
|
1935
1942
|
return JSON.stringify(body);
|
|
1936
1943
|
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Read the effective credit multiplier from a `get_detail_param` row.
|
|
1946
|
+
*
|
|
1947
|
+
* The rate the Trae IDE renders lives in `display_contact_config` — a *string*
|
|
1948
|
+
* holding a second JSON document — and its `consumption_rate.data.rate` is
|
|
1949
|
+
* already the post-discount value (verified 2026-09-13, commit 1.4.2: the
|
|
1950
|
+
* Remote directory reports the undiscounted figure, e.g. `0.8`, while the IDE
|
|
1951
|
+
* and this field both say `0.08` under a 限时 1 折 promotion — up to a 10x
|
|
1952
|
+
* difference). The wire figure therefore wins over the Remote one during the
|
|
1953
|
+
* merge; this parser is what feeds it.
|
|
1954
|
+
*
|
|
1955
|
+
* The international (ai) gateway serves no `consumption_rate` anywhere (its
|
|
1956
|
+
* subscription models carry only `features.cost` tags), so rows without the
|
|
1957
|
+
* field keep their bare name — parsed-but-absent, never fabricated.
|
|
1958
|
+
*/
|
|
1959
|
+
function wireCreditMultiplier(config) {
|
|
1960
|
+
const raw = config["display_contact_config"];
|
|
1961
|
+
if (typeof raw !== "string" || raw === "") return void 0;
|
|
1962
|
+
let parsed;
|
|
1963
|
+
try {
|
|
1964
|
+
parsed = JSON.parse(raw);
|
|
1965
|
+
} catch {
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
if (typeof parsed !== "object" || parsed === null) return void 0;
|
|
1969
|
+
const consumption = parsed["consumption_rate"];
|
|
1970
|
+
if (typeof consumption !== "object" || consumption === null) return void 0;
|
|
1971
|
+
const entry = consumption;
|
|
1972
|
+
if (entry["enable"] !== true) return void 0;
|
|
1973
|
+
const data = entry["data"];
|
|
1974
|
+
if (typeof data !== "object" || data === null) return void 0;
|
|
1975
|
+
return finitePositive$1(data["rate"]);
|
|
1976
|
+
}
|
|
1937
1977
|
var TraeSoloUpstreamClient = class {
|
|
1938
1978
|
options;
|
|
1939
1979
|
fetchImpl;
|
|
@@ -2012,6 +2052,7 @@ var TraeSoloUpstreamClient = class {
|
|
|
2012
2052
|
...config,
|
|
2013
2053
|
...detail
|
|
2014
2054
|
});
|
|
2055
|
+
const creditMultiplier = wireCreditMultiplier(config);
|
|
2015
2056
|
if (byId.has(id)) continue;
|
|
2016
2057
|
byId.set(id, {
|
|
2017
2058
|
id,
|
|
@@ -2019,6 +2060,7 @@ var TraeSoloUpstreamClient = class {
|
|
|
2019
2060
|
...contextWindow === void 0 ? {} : { contextWindow },
|
|
2020
2061
|
...maxTokens === void 0 ? {} : { maxTokens },
|
|
2021
2062
|
...reasoning === void 0 ? {} : { reasoning },
|
|
2063
|
+
...creditMultiplier === void 0 ? {} : { creditMultiplier },
|
|
2022
2064
|
function: directoryFunction
|
|
2023
2065
|
});
|
|
2024
2066
|
}
|
|
@@ -3269,6 +3311,42 @@ function regionOfTraeStatusUrl(url) {
|
|
|
3269
3311
|
return TRAE_REGIONS.includes(value) ? value : void 0;
|
|
3270
3312
|
}
|
|
3271
3313
|
/**
|
|
3314
|
+
* Peel ONE `{get(): T}` live reference, the shape DSH 0.1.7 delivers a
|
|
3315
|
+
* volatile-marked settings field as (see {@link unwrapVolatileDeep}).
|
|
3316
|
+
*/
|
|
3317
|
+
function unwrapVolatile(value) {
|
|
3318
|
+
if (value !== null && typeof value === "object" && typeof value.get === "function") return value.get();
|
|
3319
|
+
return value;
|
|
3320
|
+
}
|
|
3321
|
+
/**
|
|
3322
|
+
* Deep copy of a value with every `{get(): T}` live reference replaced by the
|
|
3323
|
+
* value it resolves to.
|
|
3324
|
+
*
|
|
3325
|
+
* DSH 0.1.7 hands volatile-marked fields back as live references, and a live
|
|
3326
|
+
* reference is still `typeof === 'object'` — so it passes a naive object check
|
|
3327
|
+
* and every lookup on it is `undefined`. Worse, spreading one (`{ ...ref }`)
|
|
3328
|
+
* does NOT read the field: it produces `{ get: <function> }`. Any merge that
|
|
3329
|
+
* spreads a resolved field to preserve its siblings — the card's "write one
|
|
3330
|
+
* region, keep the other" write, or the account map's spread — would otherwise
|
|
3331
|
+
* DROP every sibling and leak a function into the document.
|
|
3332
|
+
*
|
|
3333
|
+
* Both halves therefore unwrap before touching a resolved field. This lives in
|
|
3334
|
+
* the node-free host↔client bridge because the browser half needs it too.
|
|
3335
|
+
*
|
|
3336
|
+
* Non-reference values are recursed into so a nested volatile field is caught
|
|
3337
|
+
* as well; arrays and objects are rebuilt rather than mutated, so the caller's
|
|
3338
|
+
* value is never touched.
|
|
3339
|
+
*/
|
|
3340
|
+
function unwrapVolatileDeep(value) {
|
|
3341
|
+
if (value === null || typeof value !== "object") return value;
|
|
3342
|
+
if (typeof value.get === "function") return unwrapVolatileDeep(value.get());
|
|
3343
|
+
if (Array.isArray(value)) return value.map((entry) => unwrapVolatileDeep(entry));
|
|
3344
|
+
const source = value;
|
|
3345
|
+
const out = {};
|
|
3346
|
+
for (const key of Object.keys(source)) out[key] = unwrapVolatileDeep(source[key]);
|
|
3347
|
+
return out;
|
|
3348
|
+
}
|
|
3349
|
+
/**
|
|
3272
3350
|
* Build the next `regions` settings value for the card's save. The write
|
|
3273
3351
|
* targets ONLY the signed-in account's region slot; every other region's slot
|
|
3274
3352
|
* is carried over untouched, so switching accounts never clobbers the other
|
|
@@ -3276,8 +3354,9 @@ function regionOfTraeStatusUrl(url) {
|
|
|
3276
3354
|
* from an empty document.
|
|
3277
3355
|
*/
|
|
3278
3356
|
function nextRegionSlots(regions, region, slot) {
|
|
3357
|
+
const unwrapped = unwrapVolatileDeep(regions);
|
|
3279
3358
|
return {
|
|
3280
|
-
...typeof
|
|
3359
|
+
...typeof unwrapped === "object" && unwrapped !== null && !Array.isArray(unwrapped) ? unwrapped : {},
|
|
3281
3360
|
[region]: slot
|
|
3282
3361
|
};
|
|
3283
3362
|
}
|
|
@@ -3288,17 +3367,23 @@ function nextRegionSlots(regions, region, slot) {
|
|
|
3288
3367
|
* where the map was expected was a real shipped bug — the lookup then read
|
|
3289
3368
|
* `section['cn']` (absent), so the card's checkbox reported `true` forever and
|
|
3290
3369
|
* clicking it appeared to do nothing even though the write succeeded.
|
|
3370
|
+
*
|
|
3371
|
+
* Unwraps live references first: on DSH 0.1.7 the `regions` field arrives as a
|
|
3372
|
+
* `{get(): T}` reference, which passes the `typeof === 'object'` check below
|
|
3373
|
+
* and would otherwise be returned as if it were the map — making every region
|
|
3374
|
+
* read back as absent (and the enabled flag read as "on" forever).
|
|
3291
3375
|
*/
|
|
3292
3376
|
function regionsMapOf(value) {
|
|
3293
|
-
|
|
3294
|
-
|
|
3377
|
+
const unwrapped = unwrapVolatileDeep(value);
|
|
3378
|
+
if (typeof unwrapped !== "object" || unwrapped === null || Array.isArray(unwrapped)) return {};
|
|
3379
|
+
const record = unwrapped;
|
|
3295
3380
|
const nested = record["regions"];
|
|
3296
3381
|
if (typeof nested === "object" && nested !== null && !Array.isArray(nested)) return nested;
|
|
3297
3382
|
return record;
|
|
3298
3383
|
}
|
|
3299
3384
|
/** One region's stored slot as a plain object; any other shape reads as empty. */
|
|
3300
3385
|
function regionSlotOf(value, region) {
|
|
3301
|
-
const slot = regionsMapOf(value)[region];
|
|
3386
|
+
const slot = unwrapVolatile(regionsMapOf(value)[region]);
|
|
3302
3387
|
return typeof slot === "object" && slot !== null && !Array.isArray(slot) ? slot : {};
|
|
3303
3388
|
}
|
|
3304
3389
|
/**
|
|
@@ -3718,14 +3803,34 @@ const name = "dsh-connect-trae";
|
|
|
3718
3803
|
const inject = ["llm"];
|
|
3719
3804
|
/**
|
|
3720
3805
|
* Settings namespace, as a plain lowercase literal. DSH 0.1.2 removed the
|
|
3721
|
-
* `settingsNamespace` brand constructor from the npm package
|
|
3722
|
-
*
|
|
3723
|
-
*
|
|
3724
|
-
*
|
|
3725
|
-
*
|
|
3806
|
+
* `settingsNamespace` brand constructor from the npm package, and every
|
|
3807
|
+
* consumer of this constant takes it as a kebab string — so no branding is
|
|
3808
|
+
* needed. It is the FALLBACK namespace; the namespace the host actually serves
|
|
3809
|
+
* comes from {@link settingsNamespaceOf} (0.1.7 keys settings by the Loader
|
|
3810
|
+
* entry id, which a profile patch may mount as `dsh-connect-trae` or
|
|
3811
|
+
* `include:dsh-connect-trae`).
|
|
3726
3812
|
*/
|
|
3727
3813
|
const TRAE_SETTINGS_NS = "trae";
|
|
3728
3814
|
/**
|
|
3815
|
+
* The namespace the HOST actually serves this plugin under.
|
|
3816
|
+
*
|
|
3817
|
+
* On DSH 0.1.7 `SettingsForms.describe()` keys every namespace by the Loader
|
|
3818
|
+
* entry id, and the harness looks a provider's namespace up by EXACT match —
|
|
3819
|
+
* advertising `trae` while the host serves `include:dsh-connect-trae` made the
|
|
3820
|
+
* provider read as "not configured": its configure affordance and model
|
|
3821
|
+
* discovery both silently went dead.
|
|
3822
|
+
*
|
|
3823
|
+
* `ctx.fiber.entry` is added by the Loader, not by Cordis itself, so it is not
|
|
3824
|
+
* in Cordis's public types and is absent on hosts that mount a plugin without a
|
|
3825
|
+
* Loader entry (a test harness, or `ctx.plugin()` called directly). Hence the
|
|
3826
|
+
* probe plus the documented fallback, mirroring the first-party plugins:
|
|
3827
|
+
* `const settingsNs = ctx.fiber.entry?.options.id ?? NS`.
|
|
3828
|
+
*/
|
|
3829
|
+
function settingsNamespaceOf(ctx) {
|
|
3830
|
+
const id = ctx?.fiber?.entry?.options?.id;
|
|
3831
|
+
return typeof id === "string" && id !== "" ? id : TRAE_SETTINGS_NS;
|
|
3832
|
+
}
|
|
3833
|
+
/**
|
|
3729
3834
|
* One region's saved state. A config written before the region split has only
|
|
3730
3835
|
* the flat fields: those were always captured from the CN endpoint (the
|
|
3731
3836
|
* plugin had no international support), so they are read as the CN state and
|
|
@@ -3735,7 +3840,7 @@ const TRAE_SETTINGS_NS = "trae";
|
|
|
3735
3840
|
* picks (the same failure workbuddy fixed with its region split).
|
|
3736
3841
|
*/
|
|
3737
3842
|
function regionStateOf(config, region) {
|
|
3738
|
-
const stored = config.regions?.[region];
|
|
3843
|
+
const stored = unwrapVolatile(unwrapVolatile(config.regions)?.[region]);
|
|
3739
3844
|
if (stored !== void 0) return stored;
|
|
3740
3845
|
if (region !== "cn") return {};
|
|
3741
3846
|
return {
|
|
@@ -3756,6 +3861,24 @@ function regionStateOf(config, region) {
|
|
|
3756
3861
|
function regionEnabled(config, region) {
|
|
3757
3862
|
return regionStateOf(config, region).enabled !== false;
|
|
3758
3863
|
}
|
|
3864
|
+
/**
|
|
3865
|
+
* Mark a schema's field as volatile.
|
|
3866
|
+
*
|
|
3867
|
+
* `volatile()` exists from schemastery 3.18.3; since 2.3.0 the peer range
|
|
3868
|
+
* requires `>=3.18.4`, so it is always present on a supported host. The probe
|
|
3869
|
+
* is kept as a safety net for a consumer that resolves an older schemastery
|
|
3870
|
+
* anyway (where the helper must degrade to identity — hand-writing
|
|
3871
|
+
* `meta.volatile = true` would bypass schemastery's own `validateVolatileSchema`
|
|
3872
|
+
* checks) and to keep both arms deterministically testable.
|
|
3873
|
+
*
|
|
3874
|
+
* Without the marker, 0.1.7's settings write gate REJECTS every write with
|
|
3875
|
+
* `Plugin entry "trae" has no volatile fields` while the scope's `set()` still
|
|
3876
|
+
* resolves — so the card would look like it saved and silently revert.
|
|
3877
|
+
*/
|
|
3878
|
+
function asVolatile(schema) {
|
|
3879
|
+
if (typeof schema.volatile === "function") return schema.volatile();
|
|
3880
|
+
return schema;
|
|
3881
|
+
}
|
|
3759
3882
|
const modelConfig = z.object({
|
|
3760
3883
|
id: z.string().required(),
|
|
3761
3884
|
name: z.string().required(),
|
|
@@ -3777,17 +3900,17 @@ const accountSelectionConfig = z.object({
|
|
|
3777
3900
|
ai: z.string().description("Selected international account id (never a token)")
|
|
3778
3901
|
});
|
|
3779
3902
|
const Config = z.object({
|
|
3780
|
-
authFile: z.string().description("Optional Trae storage.json path override"),
|
|
3781
|
-
edition: z.union([
|
|
3903
|
+
authFile: asVolatile(z.string().description("Optional Trae storage.json path override")),
|
|
3904
|
+
edition: asVolatile(z.union([
|
|
3782
3905
|
"auto",
|
|
3783
3906
|
"cn",
|
|
3784
3907
|
"sg",
|
|
3785
3908
|
"solo",
|
|
3786
3909
|
"solo-sg"
|
|
3787
|
-
]).default("auto").description("Trae edition hint"),
|
|
3910
|
+
]).default("auto").description("Trae edition hint")),
|
|
3788
3911
|
accountId: z.string().description("Deprecated: pre-split account selector, attributed to its own region"),
|
|
3789
|
-
accounts: accountSelectionConfig.description("Per-region account selections, keyed cn | ai"),
|
|
3790
|
-
regions: z.dict(regionStateConfig).default({}).description("Per-region model directory and selection, keyed cn | ai"),
|
|
3912
|
+
accounts: asVolatile(accountSelectionConfig.description("Per-region account selections, keyed cn | ai")),
|
|
3913
|
+
regions: asVolatile(z.dict(regionStateConfig).default({}).description("Per-region model directory and selection, keyed cn | ai")),
|
|
3791
3914
|
lastCatalog: z.array(modelConfig).description("Deprecated: pre-region-split CN model directory"),
|
|
3792
3915
|
enabledModelIds: z.array(z.string()).default([]).description("Deprecated: pre-region-split CN selection"),
|
|
3793
3916
|
contextBudgets: z.dict(z.number().step(1).min(1)).default({}).description("Deprecated: pre-region-split CN context budgets"),
|
|
@@ -3797,6 +3920,7 @@ const Config = z.object({
|
|
|
3797
3920
|
/** Every region, in card tab order. */
|
|
3798
3921
|
const REGION_KEYS = ["cn", "ai"];
|
|
3799
3922
|
function apply(ctx, config) {
|
|
3923
|
+
const settingsNs = settingsNamespaceOf(ctx);
|
|
3800
3924
|
let current = () => config;
|
|
3801
3925
|
const enabledSet = (value, region) => new Set(regionStateOf(value, region).enabledModelIds ?? []);
|
|
3802
3926
|
const imageSet = (value, region) => new Set(regionStateOf(value, region).imageModelIds ?? []);
|
|
@@ -3846,7 +3970,7 @@ function apply(ctx, config) {
|
|
|
3846
3970
|
*/
|
|
3847
3971
|
let legacyAccountRegion;
|
|
3848
3972
|
const effectiveAccountFor = (region, value) => {
|
|
3849
|
-
const explicit = value.accounts?.[region];
|
|
3973
|
+
const explicit = unwrapVolatile(value.accounts)?.[region];
|
|
3850
3974
|
if (explicit !== void 0) return explicit;
|
|
3851
3975
|
return legacyAccountRegion === region ? value.accountId : void 0;
|
|
3852
3976
|
};
|
|
@@ -3887,10 +4011,12 @@ function apply(ctx, config) {
|
|
|
3887
4011
|
try {
|
|
3888
4012
|
preferred = (await store.current())?.edition;
|
|
3889
4013
|
} catch {}
|
|
3890
|
-
const
|
|
3891
|
-
|
|
4014
|
+
const authFile = unwrapVolatile(config.authFile);
|
|
4015
|
+
const edition = unwrapVolatile(config.edition);
|
|
4016
|
+
const hint = (edition !== void 0 && edition !== "auto" ? edition : void 0) ?? preferred;
|
|
4017
|
+
return resolveTraeIdentity(authFile === void 0 ? traeStorageCandidates().filter((item) => item.source === "desktop" && (hint !== void 0 ? item.edition === hint : regionOfEdition(item.edition) === region)) : [{
|
|
3892
4018
|
edition: hint ?? (region === "ai" ? "sg" : "solo"),
|
|
3893
|
-
path:
|
|
4019
|
+
path: authFile,
|
|
3894
4020
|
source: "desktop"
|
|
3895
4021
|
}], hint ?? (region === "ai" ? "solo-sg" : "cn"));
|
|
3896
4022
|
};
|
|
@@ -3912,8 +4038,8 @@ function apply(ctx, config) {
|
|
|
3912
4038
|
};
|
|
3913
4039
|
const store = new TraeCredentialStore({
|
|
3914
4040
|
region,
|
|
3915
|
-
...config.authFile === void 0 ? {} : { storagePath: config.authFile },
|
|
3916
|
-
edition: config.edition ?? "auto",
|
|
4041
|
+
...unwrapVolatile(config.authFile) === void 0 ? {} : { storagePath: unwrapVolatile(config.authFile) },
|
|
4042
|
+
edition: unwrapVolatile(config.edition) ?? "auto",
|
|
3917
4043
|
refresh: async (credential) => refreshTraeCredential(credential, void 0, await refreshDeviceSafe())
|
|
3918
4044
|
});
|
|
3919
4045
|
const solo = new TraeSoloUpstreamClient({
|
|
@@ -4032,7 +4158,7 @@ function apply(ctx, config) {
|
|
|
4032
4158
|
registration.cn.directory?.replace(REGION_KEYS.filter((region) => regionEnabled(value, region)).map((region) => ({
|
|
4033
4159
|
provider: TRAE_PROVIDERS[region],
|
|
4034
4160
|
displayName: TRAE_PROVIDER_DISPLAY_NAMES[region],
|
|
4035
|
-
settingsNs
|
|
4161
|
+
settingsNs,
|
|
4036
4162
|
settingsPath: [],
|
|
4037
4163
|
declared: false
|
|
4038
4164
|
})));
|
|
@@ -4041,20 +4167,12 @@ function apply(ctx, config) {
|
|
|
4041
4167
|
const applySelection = (value) => {
|
|
4042
4168
|
for (const region of REGION_KEYS) {
|
|
4043
4169
|
const stack = stacks[region];
|
|
4044
|
-
stack.store.setSource(value.authFile, value.edition ?? "auto", effectiveAccountFor(region, value));
|
|
4170
|
+
stack.store.setSource(unwrapVolatile(value.authFile), unwrapVolatile(value.edition) ?? "auto", effectiveAccountFor(region, value));
|
|
4045
4171
|
stack.catalog.set(configuredModels(value, region));
|
|
4046
4172
|
stack.invalidateAdapter();
|
|
4047
4173
|
}
|
|
4048
4174
|
syncRegionRegistration(value);
|
|
4049
4175
|
};
|
|
4050
|
-
const sectionHooks = {
|
|
4051
|
-
setSource(source) {
|
|
4052
|
-
current = source;
|
|
4053
|
-
},
|
|
4054
|
-
onChange() {
|
|
4055
|
-
applySelection(current());
|
|
4056
|
-
}
|
|
4057
|
-
};
|
|
4058
4176
|
applySelection(config);
|
|
4059
4177
|
(async () => {
|
|
4060
4178
|
const id = current().accountId;
|
|
@@ -4067,10 +4185,12 @@ function apply(ctx, config) {
|
|
|
4067
4185
|
}
|
|
4068
4186
|
} catch {}
|
|
4069
4187
|
})();
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4188
|
+
ctx.inject(["settings"], (settingsCtx) => {
|
|
4189
|
+
const settings = settingsCtx.settings;
|
|
4190
|
+
ctx.effect(() => settings.configure({ auto: true }, ctx.fiber));
|
|
4191
|
+
});
|
|
4192
|
+
ctx.on("loader/volatile-update", () => {
|
|
4193
|
+
applySelection(current());
|
|
4074
4194
|
});
|
|
4075
4195
|
let stopped = false;
|
|
4076
4196
|
ctx.effect(() => () => {
|
|
@@ -4094,7 +4214,7 @@ function apply(ctx, config) {
|
|
|
4094
4214
|
};
|
|
4095
4215
|
registration[region].adapter = region === "cn" ? ctx.llm.registerAdapter([TRAE_PROVIDER], trae.adapter) : ctx.llm.registerAdapter([TRAE_AI_PROVIDER], trae.adapter);
|
|
4096
4216
|
}
|
|
4097
|
-
ctx.llm.registerModelDiscovery(
|
|
4217
|
+
ctx.llm.registerModelDiscovery(settingsNs, async (request, signal) => {
|
|
4098
4218
|
const region = regionOfTraeProvider(request.provider ?? "");
|
|
4099
4219
|
if (region === void 0) return [];
|
|
4100
4220
|
if (!regionEnabled(current(), region)) return [];
|
|
@@ -4110,7 +4230,7 @@ function apply(ctx, config) {
|
|
|
4110
4230
|
registration.cn.directory = ctx.llm.registerConfigurableProviders(REGION_KEYS.map((region) => ({
|
|
4111
4231
|
provider: TRAE_PROVIDERS[region],
|
|
4112
4232
|
displayName: TRAE_PROVIDER_DISPLAY_NAMES[region],
|
|
4113
|
-
settingsNs
|
|
4233
|
+
settingsNs,
|
|
4114
4234
|
settingsPath: [],
|
|
4115
4235
|
declared: false
|
|
4116
4236
|
})));
|
|
@@ -4153,4 +4273,4 @@ function apply(ctx, config) {
|
|
|
4153
4273
|
});
|
|
4154
4274
|
}
|
|
4155
4275
|
//#endregion
|
|
4156
|
-
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, withTraeRegion };
|
|
4276
|
+
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-trae",
|
|
3
3
|
"displayName": "DSH Connect Trae",
|
|
4
4
|
"description": "Connect locally signed-in Trae models to DeepSeek Harness, with a usage/credits summary and daily check-in claiming.",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.3.0",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"main": "lib/index.js",
|
|
22
22
|
"types": "lib/index.d.ts",
|
|
23
|
+
"icon": "icons/dsh-connect-trae-128.png",
|
|
23
24
|
"exports": {
|
|
24
25
|
".": {
|
|
25
26
|
"types": "./lib/index.d.ts",
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
},
|
|
32
33
|
"files": [
|
|
33
34
|
"lib",
|
|
35
|
+
"icons",
|
|
34
36
|
"docs/assets/dsh-connect-trae-usage-card.png",
|
|
35
37
|
"screenshots.json",
|
|
36
38
|
"cordis.patch.yml",
|
|
@@ -72,35 +74,35 @@
|
|
|
72
74
|
"url": "https://github.com/dingminhua/dsh-connect-trae/issues"
|
|
73
75
|
},
|
|
74
76
|
"peerDependencies": {
|
|
75
|
-
"@deepseek-ai/cordis": "
|
|
76
|
-
"@deepseek-ai/dsh-atomic-write": ">=0.1.
|
|
77
|
-
"@deepseek-ai/dsh-attachment": ">=0.1.
|
|
78
|
-
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.
|
|
79
|
-
"@deepseek-ai/dsh-home-paths": ">=0.1.
|
|
80
|
-
"@deepseek-ai/dsh-host-webserver": ">=0.1.
|
|
81
|
-
"@deepseek-ai/dsh-llm": ">=0.1.
|
|
82
|
-
"@deepseek-ai/dsh-llm-pi-ai": ">=0.1.
|
|
83
|
-
"@deepseek-ai/dsh-settings": ">=0.1.
|
|
84
|
-
"@deepseek-ai/schemastery": "
|
|
77
|
+
"@deepseek-ai/cordis": ">=4.0.4 <5.0.0",
|
|
78
|
+
"@deepseek-ai/dsh-atomic-write": ">=0.1.7-rc.1 <0.2.0-0",
|
|
79
|
+
"@deepseek-ai/dsh-attachment": ">=0.1.7-rc.1 <0.2.0-0",
|
|
80
|
+
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-rc.1 <0.2.0-0",
|
|
81
|
+
"@deepseek-ai/dsh-home-paths": ">=0.1.7-rc.1 <0.2.0-0",
|
|
82
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.1 <0.2.0-0",
|
|
83
|
+
"@deepseek-ai/dsh-llm": ">=0.1.7-rc.1 <0.2.0-0",
|
|
84
|
+
"@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1 <0.2.0-0",
|
|
85
|
+
"@deepseek-ai/dsh-settings": ">=0.1.7-rc.1 <0.2.0-0",
|
|
86
|
+
"@deepseek-ai/schemastery": ">=3.18.4 <4.0.0",
|
|
85
87
|
"@earendil-works/pi-ai": ">=0.85.1",
|
|
86
88
|
"react": "^18.2.0"
|
|
87
89
|
},
|
|
88
90
|
"devDependencies": {
|
|
89
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
90
|
-
"@deepseek-ai/dsh-atomic-write": "0.1.
|
|
91
|
-
"@deepseek-ai/dsh-attachment": "0.1.
|
|
92
|
-
"@deepseek-ai/dsh-client-locale": "0.1.
|
|
93
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.1.
|
|
94
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.1.
|
|
95
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.1.
|
|
96
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.
|
|
97
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.1.
|
|
98
|
-
"@deepseek-ai/dsh-home-paths": "0.1.
|
|
99
|
-
"@deepseek-ai/dsh-host-webserver": "0.1.
|
|
100
|
-
"@deepseek-ai/dsh-llm": "0.1.
|
|
101
|
-
"@deepseek-ai/dsh-llm-pi-ai": "0.1.
|
|
102
|
-
"@deepseek-ai/dsh-settings": "0.1.
|
|
103
|
-
"@deepseek-ai/schemastery": "3.18.
|
|
91
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
92
|
+
"@deepseek-ai/dsh-atomic-write": "0.1.7-rc.1",
|
|
93
|
+
"@deepseek-ai/dsh-attachment": "0.1.7-rc.1",
|
|
94
|
+
"@deepseek-ai/dsh-client-locale": "0.1.7-rc.1",
|
|
95
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.1",
|
|
96
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.1",
|
|
97
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.7-rc.1",
|
|
98
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.7-rc.1",
|
|
99
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.1",
|
|
100
|
+
"@deepseek-ai/dsh-home-paths": "0.1.7-rc.1",
|
|
101
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.7-rc.1",
|
|
102
|
+
"@deepseek-ai/dsh-llm": "0.1.7-rc.1",
|
|
103
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.1.7-rc.1",
|
|
104
|
+
"@deepseek-ai/dsh-settings": "0.1.7-rc.1",
|
|
105
|
+
"@deepseek-ai/schemastery": "3.18.4",
|
|
104
106
|
"@earendil-works/pi-ai": "^0.85.1",
|
|
105
107
|
"@testing-library/dom": "^10.4.2",
|
|
106
108
|
"@testing-library/react": "^16.3.3",
|