dsh-connect-trae 3.6.0 → 3.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 +69 -0
- package/README.en.md +12 -1
- package/README.md +25 -1
- package/lib/client.js +53 -0
- package/lib/index.d.ts +57 -5
- package/lib/index.js +37 -7
- package/package.json +18 -18
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.7.0 (2026-10-17)
|
|
4
|
+
|
|
5
|
+
> **对齐 DSH 0.2.1-rc.1。** 宿主把 `@earendil-works/pi-ai` 从 `^0.85.1` 抬到 `^1.1.0`,
|
|
6
|
+
> 这是 0.2 线里唯一有行为影响的变更。本插件的请求体**没有**回归——但那是靠 3.4.2 的双向适配
|
|
7
|
+
> 兜住的,本版把这条结论从「推理」变成「被测住的对照」。
|
|
8
|
+
|
|
9
|
+
### 变更
|
|
10
|
+
|
|
11
|
+
- **开发树对齐宿主实际随包版本**:13 个 `@deepseek-ai/dsh-*` devDependency 固定为 `0.2.1-rc.1`,
|
|
12
|
+
`cordis` → `^4.0.5`,`schemastery` → `3.18.5`(`pnpm-workspace.yaml` 的 override 同步抬升),
|
|
13
|
+
`@earendil-works/pi-ai` → **`1.1.0`(精确固定,不用 `^`)**,使本地 typecheck 与测试
|
|
14
|
+
跑在与用户**字节一致**的契约上。
|
|
15
|
+
- **`@earendil-works/pi-ai` peer 上界收窄为 `<2.0.0`**:原先 `>=0.85.1` 无上界,会默默放行
|
|
16
|
+
未审阅的 2.x。两端均已实测(0.85.1 ✅、1.1.0 ✅、1.3.0 ✅、2.0.0 ❌)。
|
|
17
|
+
|
|
18
|
+
### 修复
|
|
19
|
+
|
|
20
|
+
- **pi-ai 1.1.0 的类型契约**:1.1.0 把 `ProviderStreams` 的第二参改成带 `unique symbol`
|
|
21
|
+
品牌标记的 `TranscriptContext`,只有 `normalizeContext()` 能产出,于是 `withContextAdaptation`
|
|
22
|
+
在两处调用点报 `TS2345`(升级后才暴露,0.85.1 下类型可通)。适配层本来就在归一化器
|
|
23
|
+
**下方**、且旧代分支**必须**产出无品牌形状,所以在这唯一一条边界上做显式转型,
|
|
24
|
+
并在 `src/adapter.ts` 与测试侧 `send()` helper 处写明理由。
|
|
25
|
+
|
|
26
|
+
### 测试
|
|
27
|
+
|
|
28
|
+
- 新增 **线上请求体矩阵**(`tests/pi-ai-context.spec.ts`):起一个真实 SSE 端点,走
|
|
29
|
+
`createProvider` + 真实 `openAICompletionsApi()`,断言线上 body 里 `roles[0] === 'system'`、
|
|
30
|
+
system 文本等于原提示、`tools` 长度为 1;并覆盖「已归一化输入不二次包裹」的幂等性。
|
|
31
|
+
- 新增 **反向对照**:**不**套适配器时,现代副本会把提示与工具全部丢光(`roles: ["user"]`、
|
|
32
|
+
`system: null`、`tools: 0`,且**不报错**)——这条钉住适配器为什么是承重的。
|
|
33
|
+
- 全仓 **663** 例测试通过(原 660 + 3),双 tsconfig 类型检查通过,构建通过。
|
|
34
|
+
|
|
35
|
+
### 已核对、无需改动
|
|
36
|
+
|
|
37
|
+
- **peer 范围**:DSH 用 `satisfies(runtime, range, { includePrerelease: true })` 求值且只校验
|
|
38
|
+
`@deepseek-ai/dsh*`,`0.2.1-rc.1` 对 `>=0.1.7-rc.1 <0.3.0-0` 已判通过(实测)。预发布悬崖
|
|
39
|
+
只在**默认选项**下成立,不适用于本插件的门禁。
|
|
40
|
+
- **宿主 API**:`dsh-llm` / `dsh-settings` / `dsh-host-webserver` / `dsh-llm-pi-ai` 消费点签名未变。
|
|
41
|
+
- **客户端外壳**:产物仍只 `require` react / react-dom / react/jsx-runtime,槽位与主题令牌未变。
|
|
42
|
+
|
|
43
|
+
## 3.6.1 (2026-10-16)
|
|
44
|
+
|
|
45
|
+
> **「自动续期」不再是一句没有期限的承诺。** 卡片现在会告诉你**刷新令牌**何时到期 ——
|
|
46
|
+
> 在那之前访问令牌会自动续期,在那之后自动续期就彻底停了,必须重新登录一次 Trae。
|
|
47
|
+
|
|
48
|
+
### 修复
|
|
49
|
+
|
|
50
|
+
- **刷新令牌的到期时间对用户完全不可见(issue #37 追问引出)**:
|
|
51
|
+
- **问题**:卡片一直只写「访问令牌 {时间} 过期(自动续期)」。这句话是对的,
|
|
52
|
+
而且一直对 —— 直到**刷新令牌**到期的那个瞬间,自动续期就永久停止了。
|
|
53
|
+
一个读了几个月「自动续期」的用户,没有任何理由预期这件事。
|
|
54
|
+
- **根因**:刷新令牌的到期时间**被解析了、也被存下来了,但从来没送到浏览器** ——
|
|
55
|
+
状态接口里根本没有这个字段,所以卡片无从显示。
|
|
56
|
+
- **现在**:状态接口暴露刷新令牌到期时间,卡片**按账号**在临近到期时给出一行提示:
|
|
57
|
+
- 剩余 **30 天**以内 → 提醒(警告色),写明**到期时间**与**还剩几天**;
|
|
58
|
+
- **已到期** → 明确说明「自动续期已停止」,并给出恢复方式(重新登录一次 Trae);
|
|
59
|
+
- **还很久 / 没有该字段**(CLI 凭据本身不带)→ **不显示任何东西**。
|
|
60
|
+
健康的令牌不该在每一行都占一行文字,伪造的期限比不显示更糟。
|
|
61
|
+
|
|
62
|
+
> 这条不是理论问题:我们在排查 `#38` 时**亲身遇到过** —— 某区域插件自存的凭据访问令牌已过期,
|
|
63
|
+
刷新也返回 401,而卡片上一直写着「自动续期」。当时只能靠人肉发现。
|
|
64
|
+
|
|
65
|
+
### 测试
|
|
66
|
+
|
|
67
|
+
- 新增 5 例覆盖三种状态与两个边界(健康不显示、临近显示截止日与剩余天数、已过期显示错误态、
|
|
68
|
+
无该字段不伪造、最后一天仍算「未过期」)。
|
|
69
|
+
- **变异验证**:去掉 30 天提醒窗口 → 2 例变红;在缺少字段时伪造一个期限 → 1 例变红。
|
|
70
|
+
- 全仓 **660** 例测试通过,类型检查通过。
|
|
71
|
+
|
|
3
72
|
## 3.6.0 (2026-10-16)
|
|
4
73
|
|
|
5
74
|
> **多个账号可以并存了。** 本版新增**账号导入 / 导出**:不再需要每次把某台机器的登录凭据
|
package/README.en.md
CHANGED
|
@@ -138,7 +138,7 @@ Restart the DSH process after install/update/uninstall.
|
|
|
138
138
|
## DSH version compatibility
|
|
139
139
|
|
|
140
140
|
**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).
|
|
141
|
-
**The peer upper bound is `<0.3.0-0`, so both the 0.1.7 and the 0.2 line are supported.**
|
|
141
|
+
**The peer upper bound is `<0.3.0-0`, so both the 0.1.7 and the 0.2 line are supported (including 0.2.1-rc.1, verified).**
|
|
142
142
|
|
|
143
143
|
### Both pi-ai context generations are supported (since 3.4.2)
|
|
144
144
|
|
|
@@ -161,6 +161,17 @@ The boundary is **0.86**: `dist/utils/transcript.js`, `normalizeContext`, the es
|
|
|
161
161
|
|
|
162
162
|
The 0.2.0 line breaks **no** contract this plugin consumes — a symbol-by-symbol check confirmed every API it imports is unchanged ([full 0.2.0-rc.1 impact check](https://github.com/dingminhua/dsh-connect-trae/blob/main/docs/DSH_0.2.0_RC1_IMPACT_CHECK.md)). But since 0.1.7 DSH enforces a **bundle-level peer gate**: a bundle whose peers are unsatisfied is skipped **whole**, with one line on stderr and nothing in the UI — the plugin simply disappears.
|
|
163
163
|
|
|
164
|
+
### DSH 0.2.1-rc.1 (pi-ai 1.1.0) sync
|
|
165
|
+
|
|
166
|
+
The `0.2.1-rc.1` host moved `@earendil-works/pi-ai` from `^0.85.1` to **`^1.1.0`** — the only change on the 0.2 line with real behavioural impact. Three conclusions:
|
|
167
|
+
|
|
168
|
+
1. **The request body did not regress (measured, not inferred).** The 1.1.0 api reads the prompt **only** from a transcript system message and never touches top-level `systemPrompt` / `tools`, while `dsh-llm-pi-ai`'s `piContext()` still emits the legacy envelope. The bidirectional adapter this plugin has shipped since 3.4.2 covers exactly that: feeding the real host envelope through real `1.1.0` with a local SSE capture yields `roles: ["system","user"]`, an intact system text and `tools: 1`; **without the adapter it degenerates to `roles: ["user"]`, system `null`, `tools: 0`** — with no error. That contrast is pinned as a wire-level matrix in `tests/pi-ai-context.spec.ts`.
|
|
169
|
+
2. **Types were fixed.** 1.1.0 changed `ProviderStreams`' second parameter to a **branded** `TranscriptContext` (a `unique symbol` only `normalizeContext()` can produce). The adapter casts once at that single boundary (`withContextAdaptation` in `src/adapter.ts`) and documents why the brand cannot be obtained honestly there.
|
|
170
|
+
3. **The peer range needed no change.** DSH evaluates with `semver.satisfies(runtime, range, { includePrerelease: true })` and checks only the `@deepseek-ai/dsh*` namespace, so `>=0.1.7-rc.1 <0.3.0-0` already admits `0.2.1-rc.1` (verified). Note the prerelease cliff only exists under **default** options — it is not this plugin's situation.
|
|
171
|
+
|
|
172
|
+
The dev tree pins `@earendil-works/pi-ai` at exactly **1.1.0** (the version the host actually bundles) rather than `^1.1.0`, so local typecheck and tests run against the same contract users do; the `schemastery` override moved to `3.18.5` (`dsh-llm-pi-ai@0.2.1-rc.1` declares `~3.18.5`).
|
|
173
|
+
|
|
174
|
+
|
|
164
175
|
### Why the upper bound is not `<0.2.0`
|
|
165
176
|
|
|
166
177
|
2.3.1 wrote `<0.2.0-0`, meaning "not the 0.2.0 release yet". That spelling is **wrong**: in SemVer `0.2.0-rc.1 < 0.2.0-0`, so it excluded the entire 0.2.0 **prerelease** line, and the plugin went dead the moment a user installed the first 0.2.0 prerelease.
|
package/README.md
CHANGED
|
@@ -150,7 +150,7 @@ npm install dsh-connect-trae
|
|
|
150
150
|
## DSH 版本兼容
|
|
151
151
|
|
|
152
152
|
**要求 DSH 0.1.7-rc.1 及以上**(2.3.0 起不再支持 0.1.7 之前的宿主;peer 依赖范围已按此收窄)。
|
|
153
|
-
**peer 上界为 `<0.3.0-0`:0.1.7 与 0.2
|
|
153
|
+
**peer 上界为 `<0.3.0-0`:0.1.7 与 0.2 两条线均受支持(含 0.2.1-rc.1,已实测)。**
|
|
154
154
|
|
|
155
155
|
### 同时支持 pi-ai 的两代上下文(3.4.2 起)
|
|
156
156
|
|
|
@@ -182,6 +182,30 @@ DSH 自己横跨 `@earendil-works/pi-ai` 的两代,而**本插件解析到哪
|
|
|
182
182
|
0.2.0 线对本插件**没有契约破坏**——逐符号核对确认插件消费的 API 全部未变([完整的 0.2.0-rc.1 影响面核对](https://github.com/dingminhua/dsh-connect-trae/blob/main/docs/DSH_0.2.0_RC1_IMPACT_CHECK.md))。但 0.1.7 起 DSH 有一道**bundle 级 peer 门禁**:
|
|
183
183
|
peer 范围不满足的 bundle 会被**整体跳过**,只往 stderr 写一行,界面上表现为「插件凭空消失」。
|
|
184
184
|
|
|
185
|
+
### DSH 0.2.1-rc.1(pi-ai 1.1.0)同步结论
|
|
186
|
+
|
|
187
|
+
宿主 `0.2.1-rc.1` 把 `@earendil-works/pi-ai` 从 `^0.85.1` 抬到了 **`^1.1.0`**,这是 0.2 线里
|
|
188
|
+
唯一有实际行为影响的变化。三项结论:
|
|
189
|
+
|
|
190
|
+
1. **请求体没有丢东西(已实测)**。1.1.0 的 api **只**读 transcript 里的 system 消息,
|
|
191
|
+
顶层 `systemPrompt` / `tools` 完全不再读取;而 `dsh-llm-pi-ai` 的 `piContext()` 仍然产出
|
|
192
|
+
旧版信封。本插件 3.4.2 起就有的双向适配正好覆盖这一点——把真实宿主信封喂进真实
|
|
193
|
+
`1.1.0` + 本地 SSE 捕获线上请求体,得到 `roles: ["system","user"]`、system 文本完好、
|
|
194
|
+
`tools: 1`;**去掉适配则变成 `roles: ["user"]`、system `null`、`tools: 0`**(不报错)。
|
|
195
|
+
这条对照已固化为 `tests/pi-ai-context.spec.ts` 的线上请求体矩阵。
|
|
196
|
+
2. **类型面补齐**。1.1.0 把 `ProviderStreams` 的第二参改成带 `unique symbol` 品牌标记的
|
|
197
|
+
`TranscriptContext`,只有 `normalizeContext()` 能产出。适配层在这条边界上做一次显式转型
|
|
198
|
+
(`src/adapter.ts` 的 `withContextAdaptation`),并在两处调用点写明为何品牌在此处不可诚实获得。
|
|
199
|
+
3. **peer 范围无需改动**。DSH 用 `semver.satisfies(runtime, range, { includePrerelease: true })`
|
|
200
|
+
求值,且只校验 `@deepseek-ai/dsh*` 命名空间,因此 `>=0.1.7-rc.1 <0.3.0-0` 对 `0.2.1-rc.1` 已判通过
|
|
201
|
+
(已实测)。**注意**:预发布悬崖只在**默认选项**下成立——那是 `dsh-connect-workbuddy` 侧
|
|
202
|
+
调研文档所描述的困境,本插件不适用。
|
|
203
|
+
|
|
204
|
+
开发树的 `@earendil-works/pi-ai` 钉在 **1.1.0**(宿主实际随包的版本)而非 `^1.1.0`,
|
|
205
|
+
使本地 typecheck 与测试跑在与用户完全一致的契约上;`schemastery` 的 override 同步抬到
|
|
206
|
+
`3.18.5`(`dsh-llm-pi-ai@0.2.1-rc.1` 声明 `~3.18.5`)。
|
|
207
|
+
|
|
208
|
+
|
|
185
209
|
### peer 上界为什么不写 `<0.2.0`
|
|
186
210
|
|
|
187
211
|
2.3.1 曾写 `<0.2.0-0`,意图是「先不支持 0.2.0 正式版」。这个写法**是错的**:
|
package/lib/client.js
CHANGED
|
@@ -335,6 +335,13 @@ window.__ModuleLoader__.load({
|
|
|
335
335
|
.dsm-trae-usage-account{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:14px;background:var(--dsw-alias-bg-layer-2,#24262c)}
|
|
336
336
|
.dsm-trae-usage-account-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
|
|
337
337
|
.dsm-trae-usage-expiry{padding-left:19px;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
|
|
338
|
+
/* Refresh-token countdown (issue #37 follow-up). Sits under the access-token
|
|
339
|
+
line and aligns with it, so the two read as one stack: "token renews" then
|
|
340
|
+
"but this is when renewal itself ends". Only rendered when it is close or
|
|
341
|
+
already past, so a healthy account is unchanged. */
|
|
342
|
+
.dsm-trae-refresh-warn{padding-left:19px;font-size:12px;line-height:18px}
|
|
343
|
+
.dsm-trae-refresh-warn-soon{color:var(--dsw-alias-state-warn-primary,#b45309)}
|
|
344
|
+
.dsm-trae-refresh-warn-lapsed{color:var(--dsw-alias-state-error-primary,#dc2626)}
|
|
338
345
|
.dsm-trae-account-picker{display:block}
|
|
339
346
|
/* The 璁剧疆 block: label + hint on the left, control on the right, one save for
|
|
340
347
|
the whole block. It replaces the bare checkbox rows that used to write on
|
|
@@ -1306,6 +1313,40 @@ window.__ModuleLoader__.load({
|
|
|
1306
1313
|
minute: "2-digit"
|
|
1307
1314
|
}).format(new Date(value));
|
|
1308
1315
|
}
|
|
1316
|
+
/** How close a refresh token must be to lapsing before the card warns, in days. */
|
|
1317
|
+
const REFRESH_WARN_DAYS = 30;
|
|
1318
|
+
/**
|
|
1319
|
+
* The refresh-token warning for one account, or undefined when there is nothing
|
|
1320
|
+
* worth saying.
|
|
1321
|
+
*
|
|
1322
|
+
* The access token renews itself every few minutes, so its own expiry answers
|
|
1323
|
+
* "when does this need attention" with "never, until the refresh token lapses".
|
|
1324
|
+
* The refresh token is therefore the only honest countdown on this card, and it
|
|
1325
|
+
* has to be shown BEFORE it runs out: once it lapses the plugin cannot renew at
|
|
1326
|
+
* all and the only recovery is signing in to Trae again, which a user who has
|
|
1327
|
+
* been reading "auto-renewal" for months has no reason to expect.
|
|
1328
|
+
*
|
|
1329
|
+
* Three tones, matching the credit-expiry notice: `lapsed` (renewal already
|
|
1330
|
+
* dead), `soon` (inside the warning window), and nothing otherwise — a healthy
|
|
1331
|
+
* token must not add a line to every account row forever.
|
|
1332
|
+
*/
|
|
1333
|
+
function refreshTokenWarning(expiresAtMs, nowMs, t, formatTime) {
|
|
1334
|
+
if (expiresAtMs === void 0) return void 0;
|
|
1335
|
+
const text = formatTime(expiresAtMs);
|
|
1336
|
+
if (expiresAtMs <= nowMs) return {
|
|
1337
|
+
tone: "lapsed",
|
|
1338
|
+
text: t("row.refreshTokenLapsed", { expiresAt: text })
|
|
1339
|
+
};
|
|
1340
|
+
const days = Math.ceil((expiresAtMs - nowMs) / 864e5);
|
|
1341
|
+
if (days > REFRESH_WARN_DAYS) return void 0;
|
|
1342
|
+
return {
|
|
1343
|
+
tone: "soon",
|
|
1344
|
+
text: t("row.refreshTokenExpiring", {
|
|
1345
|
+
expiresAt: text,
|
|
1346
|
+
days
|
|
1347
|
+
})
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1309
1350
|
function formatCapacity(value, unknown) {
|
|
1310
1351
|
if (value === void 0) return unknown;
|
|
1311
1352
|
if (value >= 1e6 && value % 1e6 === 0) return `${value / 1e6}M`;
|
|
@@ -2575,6 +2616,14 @@ window.__ModuleLoader__.load({
|
|
|
2575
2616
|
sourceLabel(account.source)
|
|
2576
2617
|
]
|
|
2577
2618
|
}),
|
|
2619
|
+
(() => {
|
|
2620
|
+
const warning = refreshTokenWarning(account.refreshExpiresAtMs, Date.now(), t, formatDateTime);
|
|
2621
|
+
return warning === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2622
|
+
className: `dsm-trae-refresh-warn dsm-trae-refresh-warn-${warning.tone}`,
|
|
2623
|
+
role: "status",
|
|
2624
|
+
children: warning.text
|
|
2625
|
+
});
|
|
2626
|
+
})(),
|
|
2578
2627
|
error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2579
2628
|
className: "dsm-trae-account-note",
|
|
2580
2629
|
role: "alert",
|
|
@@ -3617,6 +3666,8 @@ window.__ModuleLoader__.load({
|
|
|
3617
3666
|
"row.sourceCli": "Trae CLI",
|
|
3618
3667
|
"row.signedIn": "Signed in: {accountName}",
|
|
3619
3668
|
"row.tokenExpiry": "Access token expires {expiresAt} (auto-renewal)",
|
|
3669
|
+
"row.refreshTokenExpiring": "Sign in to Trae again by {expiresAt} ({days} day(s) left) — automatic renewal stops after that.",
|
|
3670
|
+
"row.refreshTokenLapsed": "Automatic renewal has stopped: the Trae sign-in expired {expiresAt}. Sign in to Trae again to restore it.",
|
|
3620
3671
|
"row.requestFailed": "Request failed",
|
|
3621
3672
|
"row.creditsAvailableLabel": "Available",
|
|
3622
3673
|
"row.workCreditsLabel": "Work credits",
|
|
@@ -3783,6 +3834,8 @@ window.__ModuleLoader__.load({
|
|
|
3783
3834
|
"row.sourceCli": "Trae CLI",
|
|
3784
3835
|
"row.signedIn": "已登录:{accountName}",
|
|
3785
3836
|
"row.tokenExpiry": "访问令牌 {expiresAt} 过期(自动续期)",
|
|
3837
|
+
"row.refreshTokenExpiring": "请在 {expiresAt} 前重新登录一次 Trae(还剩 {days} 天),此后将无法自动续期。",
|
|
3838
|
+
"row.refreshTokenLapsed": "自动续期已停止:Trae 登录凭据已于 {expiresAt} 过期。请重新登录一次 Trae 以恢复。",
|
|
3786
3839
|
"row.requestFailed": "请求失败",
|
|
3787
3840
|
"row.creditsAvailableLabel": "可用",
|
|
3788
3841
|
"row.workCreditsLabel": "Work 积分",
|
package/lib/index.d.ts
CHANGED
|
@@ -319,9 +319,10 @@ declare class TraeCatalog {
|
|
|
319
319
|
*
|
|
320
320
|
* The mismatch is reachable in BOTH directions, because DSH itself spans both
|
|
321
321
|
* generations (`dsh-llm-pi-ai` ≤0.2.0-rc.1 declares `^0.85.1`; 0.2.0-rc.2 and
|
|
322
|
-
* later declare `^0.87.1`, 0.2.1-alpha.2 declares `^1.0.2
|
|
323
|
-
*
|
|
324
|
-
* the
|
|
322
|
+
* later declare `^0.87.1`, 0.2.1-alpha.2 declares `^1.0.2`, and 0.2.1-rc.1 —
|
|
323
|
+
* the current desktop host — declares **`^1.1.0`**, which also stopped reading
|
|
324
|
+
* the top-level fields entirely), while this plugin accepts the whole range
|
|
325
|
+
* (`>=0.1.7-rc.1 <0.3.0-0`). Whichever side is newer, the other one is wrong:
|
|
325
326
|
*
|
|
326
327
|
* | host | resolved api | outcome |
|
|
327
328
|
* |---|---|---|
|
|
@@ -494,13 +495,17 @@ declare function adaptLegacyPiAiContext(context: Context): Context;
|
|
|
494
495
|
* transcript a MODERN (0.86+) api consumes — the reverse mismatch, and just as
|
|
495
496
|
* silent.
|
|
496
497
|
*
|
|
497
|
-
* WHY. DSH spans both generations (`dsh-llm-pi-ai` ≤0.2.0-rc.1 pins `^0.85.1
|
|
498
|
+
* WHY. DSH spans both generations (`dsh-llm-pi-ai` ≤0.2.0-rc.1 pins `^0.85.1`,
|
|
499
|
+
* while the 0.2.1-rc.1 desktop host pins `^1.1.0` and still emits the legacy
|
|
500
|
+
* envelope from its own `piContext()`)
|
|
498
501
|
* while this plugin may resolve a newer copy than its host. A LEGACY host never
|
|
499
502
|
* normalizes, so it hands the api the legacy shape; a modern api reads the
|
|
500
503
|
* prompt from a leading system message and the tools from its `toolsAdded`, and
|
|
501
504
|
* never looks at the top-level fields — so both are dropped without a sound.
|
|
502
505
|
* Verified against the published tarballs: the request that reaches the wire
|
|
503
|
-
* carries no system turn and `tools: []`.
|
|
506
|
+
* carries no system turn and `tools: []`. This is exactly the shape that
|
|
507
|
+
* `dsh-llm-pi-ai@0.2.1-rc.1` + `pi-ai@1.1.0` would hit unaided, which is why
|
|
508
|
+
* the wire-level matrix in `tests/pi-ai-context.spec.ts` pins both directions.
|
|
504
509
|
*
|
|
505
510
|
* Identity for anything already in transcript shape, and for a context with
|
|
506
511
|
* neither a prompt nor tools (pi-ai's own `createInitialSystemMessage` returns
|
|
@@ -518,6 +523,27 @@ declare function adaptModernPiAiContext(context: Context): Context;
|
|
|
518
523
|
* no context and are passed through untouched, and each adapter function is an
|
|
519
524
|
* identity unless the context is actually the other generation's shape. So a
|
|
520
525
|
* healthy install does one `Array.some` per request.
|
|
526
|
+
*
|
|
527
|
+
* THE CAST IS THE CONTRACT (pi-ai 1.1.0). From 1.1.0 `ProviderStreams` takes
|
|
528
|
+
* `TranscriptContext`, a BRANDED type whose `unique symbol` member only
|
|
529
|
+
* `normalizeContext()` can produce — so a `Context` is no longer assignable and
|
|
530
|
+
* `tsc` reports `TS2345` at both call sites below. The brand cannot be acquired
|
|
531
|
+
* honestly here, for two reasons that are both deliberate:
|
|
532
|
+
*
|
|
533
|
+
* 1. This wrapper sits BELOW the normalizer. `Models.stream()`/`streamSimple()`
|
|
534
|
+
* call `normalizeContext()` and hand a real transcript to the provider;
|
|
535
|
+
* `createProvider()` passes it straight through to `api.stream`. So on the
|
|
536
|
+
* modern path `adapt()` is an identity and the value already IS a branded
|
|
537
|
+
* transcript at runtime — the type just cannot see it across that boundary.
|
|
538
|
+
* 2. The legacy path REQUIRES an unbranded shape. pi-ai ≤0.85 has no
|
|
539
|
+
* `normalizeContext` and no transcript type at all, so its api expects
|
|
540
|
+
* exactly the `{ systemPrompt, tools, messages }` object `adapt()` returns.
|
|
541
|
+
*
|
|
542
|
+
* So the value handed to `api` is "a context of the generation that api
|
|
543
|
+
* speaks", which no single edition's declared type names. Casting at this one
|
|
544
|
+
* boundary is what makes both generations expressible; deriving the type from
|
|
545
|
+
* the resolved copy instead would make the wrapper uncompilable against the
|
|
546
|
+
* other generation, which is the whole point of having it.
|
|
521
547
|
*/
|
|
522
548
|
declare function withContextAdaptation(api: ProviderStreams, generation: PiAiGeneration): ProviderStreams;
|
|
523
549
|
interface TraeAdapterOptions {
|
|
@@ -590,6 +616,13 @@ interface TraeAccountChoice {
|
|
|
590
616
|
region: TraeRegion;
|
|
591
617
|
source: 'desktop' | 'dsh' | 'cli';
|
|
592
618
|
tokenExpiresAtMs: number;
|
|
619
|
+
/**
|
|
620
|
+
* When this account's refresh token lapses, when the credential carries one.
|
|
621
|
+
* The access token above renews itself automatically until this expires; see
|
|
622
|
+
* the mirror field on `TraeWebAccount` in status-paths.ts for why the card
|
|
623
|
+
* needs it.
|
|
624
|
+
*/
|
|
625
|
+
refreshExpiresAtMs?: number;
|
|
593
626
|
selected: boolean;
|
|
594
627
|
}
|
|
595
628
|
/**
|
|
@@ -2002,6 +2035,18 @@ interface TraeWebAccount {
|
|
|
2002
2035
|
region: TraeRegion;
|
|
2003
2036
|
source: 'desktop' | 'dsh' | 'cli';
|
|
2004
2037
|
tokenExpiresAtMs: number;
|
|
2038
|
+
/**
|
|
2039
|
+
* When this account's REFRESH token expires, when the credential carries one.
|
|
2040
|
+
*
|
|
2041
|
+
* The access token above is renewed automatically — the plugin refreshes it
|
|
2042
|
+
* five minutes before it lapses — so by itself it answers "when does this
|
|
2043
|
+
* need attention", which is "never, while the refresh token lives". The
|
|
2044
|
+
* refresh token is the real horizon: once IT expires the automatic renewal
|
|
2045
|
+
* stops working and the user must sign in to Trae again. Before this field the
|
|
2046
|
+
* card could only say "auto-renewal", which stays true right up to the moment
|
|
2047
|
+
* it silently stops being true.
|
|
2048
|
+
*/
|
|
2049
|
+
refreshExpiresAtMs?: number;
|
|
2005
2050
|
selected: boolean;
|
|
2006
2051
|
}
|
|
2007
2052
|
/**
|
|
@@ -2228,6 +2273,13 @@ type TraeWebUsage = {
|
|
|
2228
2273
|
accountId: string;
|
|
2229
2274
|
accountName: string;
|
|
2230
2275
|
tokenExpiresAtMs: number;
|
|
2276
|
+
/**
|
|
2277
|
+
* When the BOUND account's refresh token expires (absent when the credential
|
|
2278
|
+
* carries none). See {@link TraeWebAccount.refreshExpiresAtMs} — this is the
|
|
2279
|
+
* horizon that actually matters, because the access token above renews
|
|
2280
|
+
* itself until this one lapses.
|
|
2281
|
+
*/
|
|
2282
|
+
refreshExpiresAtMs?: number;
|
|
2231
2283
|
/** Which per-region model directory and selection this account owns. */
|
|
2232
2284
|
region: TraeRegion;
|
|
2233
2285
|
/** Whether this region's provider is currently offered to DSH. */
|
package/lib/index.js
CHANGED
|
@@ -344,9 +344,10 @@ var TraeCatalog = class {
|
|
|
344
344
|
*
|
|
345
345
|
* The mismatch is reachable in BOTH directions, because DSH itself spans both
|
|
346
346
|
* generations (`dsh-llm-pi-ai` ≤0.2.0-rc.1 declares `^0.85.1`; 0.2.0-rc.2 and
|
|
347
|
-
* later declare `^0.87.1`, 0.2.1-alpha.2 declares `^1.0.2
|
|
348
|
-
*
|
|
349
|
-
* the
|
|
347
|
+
* later declare `^0.87.1`, 0.2.1-alpha.2 declares `^1.0.2`, and 0.2.1-rc.1 —
|
|
348
|
+
* the current desktop host — declares **`^1.1.0`**, which also stopped reading
|
|
349
|
+
* the top-level fields entirely), while this plugin accepts the whole range
|
|
350
|
+
* (`>=0.1.7-rc.1 <0.3.0-0`). Whichever side is newer, the other one is wrong:
|
|
350
351
|
*
|
|
351
352
|
* | host | resolved api | outcome |
|
|
352
353
|
* |---|---|---|
|
|
@@ -689,13 +690,17 @@ function adaptLegacyPiAiContext(context) {
|
|
|
689
690
|
* transcript a MODERN (0.86+) api consumes — the reverse mismatch, and just as
|
|
690
691
|
* silent.
|
|
691
692
|
*
|
|
692
|
-
* WHY. DSH spans both generations (`dsh-llm-pi-ai` ≤0.2.0-rc.1 pins `^0.85.1
|
|
693
|
+
* WHY. DSH spans both generations (`dsh-llm-pi-ai` ≤0.2.0-rc.1 pins `^0.85.1`,
|
|
694
|
+
* while the 0.2.1-rc.1 desktop host pins `^1.1.0` and still emits the legacy
|
|
695
|
+
* envelope from its own `piContext()`)
|
|
693
696
|
* while this plugin may resolve a newer copy than its host. A LEGACY host never
|
|
694
697
|
* normalizes, so it hands the api the legacy shape; a modern api reads the
|
|
695
698
|
* prompt from a leading system message and the tools from its `toolsAdded`, and
|
|
696
699
|
* never looks at the top-level fields — so both are dropped without a sound.
|
|
697
700
|
* Verified against the published tarballs: the request that reaches the wire
|
|
698
|
-
* carries no system turn and `tools: []`.
|
|
701
|
+
* carries no system turn and `tools: []`. This is exactly the shape that
|
|
702
|
+
* `dsh-llm-pi-ai@0.2.1-rc.1` + `pi-ai@1.1.0` would hit unaided, which is why
|
|
703
|
+
* the wire-level matrix in `tests/pi-ai-context.spec.ts` pins both directions.
|
|
699
704
|
*
|
|
700
705
|
* Identity for anything already in transcript shape, and for a context with
|
|
701
706
|
* neither a prompt nor tools (pi-ai's own `createInitialSystemMessage` returns
|
|
@@ -730,13 +735,35 @@ function adaptModernPiAiContext(context) {
|
|
|
730
735
|
* no context and are passed through untouched, and each adapter function is an
|
|
731
736
|
* identity unless the context is actually the other generation's shape. So a
|
|
732
737
|
* healthy install does one `Array.some` per request.
|
|
738
|
+
*
|
|
739
|
+
* THE CAST IS THE CONTRACT (pi-ai 1.1.0). From 1.1.0 `ProviderStreams` takes
|
|
740
|
+
* `TranscriptContext`, a BRANDED type whose `unique symbol` member only
|
|
741
|
+
* `normalizeContext()` can produce — so a `Context` is no longer assignable and
|
|
742
|
+
* `tsc` reports `TS2345` at both call sites below. The brand cannot be acquired
|
|
743
|
+
* honestly here, for two reasons that are both deliberate:
|
|
744
|
+
*
|
|
745
|
+
* 1. This wrapper sits BELOW the normalizer. `Models.stream()`/`streamSimple()`
|
|
746
|
+
* call `normalizeContext()` and hand a real transcript to the provider;
|
|
747
|
+
* `createProvider()` passes it straight through to `api.stream`. So on the
|
|
748
|
+
* modern path `adapt()` is an identity and the value already IS a branded
|
|
749
|
+
* transcript at runtime — the type just cannot see it across that boundary.
|
|
750
|
+
* 2. The legacy path REQUIRES an unbranded shape. pi-ai ≤0.85 has no
|
|
751
|
+
* `normalizeContext` and no transcript type at all, so its api expects
|
|
752
|
+
* exactly the `{ systemPrompt, tools, messages }` object `adapt()` returns.
|
|
753
|
+
*
|
|
754
|
+
* So the value handed to `api` is "a context of the generation that api
|
|
755
|
+
* speaks", which no single edition's declared type names. Casting at this one
|
|
756
|
+
* boundary is what makes both generations expressible; deriving the type from
|
|
757
|
+
* the resolved copy instead would make the wrapper uncompilable against the
|
|
758
|
+
* other generation, which is the whole point of having it.
|
|
733
759
|
*/
|
|
734
760
|
function withContextAdaptation(api, generation) {
|
|
735
761
|
const adapt = generation === "modern" ? adaptModernPiAiContext : adaptLegacyPiAiContext;
|
|
762
|
+
const forApi = (context) => adapt(context);
|
|
736
763
|
return {
|
|
737
764
|
...api,
|
|
738
|
-
stream: (model, context, options) => api.stream(model,
|
|
739
|
-
streamSimple: (model, context, options) => api.streamSimple(model,
|
|
765
|
+
stream: (model, context, options) => api.stream(model, forApi(context), options),
|
|
766
|
+
streamSimple: (model, context, options) => api.streamSimple(model, forApi(context), options)
|
|
740
767
|
};
|
|
741
768
|
}
|
|
742
769
|
function createTraeAdapter(options) {
|
|
@@ -1867,6 +1894,7 @@ var TraeCredentialStore = class {
|
|
|
1867
1894
|
region: regionOfCredential(live),
|
|
1868
1895
|
source: live.source,
|
|
1869
1896
|
tokenExpiresAtMs: live.expiresAtMs,
|
|
1897
|
+
...live.refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs: live.refreshExpiresAtMs },
|
|
1870
1898
|
selected: false
|
|
1871
1899
|
};
|
|
1872
1900
|
}
|
|
@@ -1881,6 +1909,7 @@ var TraeCredentialStore = class {
|
|
|
1881
1909
|
region: regionOfCredential(credential),
|
|
1882
1910
|
source: credential.source,
|
|
1883
1911
|
tokenExpiresAtMs: credential.expiresAtMs,
|
|
1912
|
+
...credential.refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs: credential.refreshExpiresAtMs },
|
|
1884
1913
|
selected: selectedExists ? traeAccountId(credential) === this.accountId : credential === defaultSelected
|
|
1885
1914
|
}));
|
|
1886
1915
|
}
|
|
@@ -5484,6 +5513,7 @@ async function traeWebUsage(deps, region) {
|
|
|
5484
5513
|
accountId: accounts.find((item) => item.selected)?.id ?? "",
|
|
5485
5514
|
accountName: credential.accountName ?? credential.userId,
|
|
5486
5515
|
tokenExpiresAtMs: credential.expiresAtMs,
|
|
5516
|
+
...credential.refreshExpiresAtMs === void 0 ? {} : { refreshExpiresAtMs: credential.refreshExpiresAtMs },
|
|
5487
5517
|
region,
|
|
5488
5518
|
enabled,
|
|
5489
5519
|
accounts,
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-trae",
|
|
3
3
|
"displayName": "DSH Connect Trae",
|
|
4
4
|
"description": "把本机登录的 Trae 模型接入 DeepSeek Harness:国内版与国际版双供应商并行,提供用量/积分概览与每日签到领取。",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.7.0",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|
|
@@ -85,26 +85,26 @@
|
|
|
85
85
|
"@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1 <0.3.0-0",
|
|
86
86
|
"@deepseek-ai/dsh-settings": ">=0.1.7-rc.1 <0.3.0-0",
|
|
87
87
|
"@deepseek-ai/schemastery": ">=3.18.4 <4.0.0",
|
|
88
|
-
"@earendil-works/pi-ai": ">=0.85.1",
|
|
88
|
+
"@earendil-works/pi-ai": ">=0.85.1 <2.0.0",
|
|
89
89
|
"react": "^18.2.0"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
92
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
93
|
-
"@deepseek-ai/dsh-atomic-write": "0.2.
|
|
94
|
-
"@deepseek-ai/dsh-attachment": "0.2.
|
|
95
|
-
"@deepseek-ai/dsh-client-locale": "0.2.
|
|
96
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.2.
|
|
97
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.
|
|
98
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.2.
|
|
99
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.2.
|
|
100
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.
|
|
101
|
-
"@deepseek-ai/dsh-home-paths": "0.2.
|
|
102
|
-
"@deepseek-ai/dsh-host-webserver": "0.2.
|
|
103
|
-
"@deepseek-ai/dsh-llm": "0.2.
|
|
104
|
-
"@deepseek-ai/dsh-llm-pi-ai": "0.2.
|
|
105
|
-
"@deepseek-ai/dsh-settings": "0.2.
|
|
106
|
-
"@deepseek-ai/schemastery": "3.18.
|
|
107
|
-
"@earendil-works/pi-ai": "
|
|
92
|
+
"@deepseek-ai/cordis": "^4.0.5",
|
|
93
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.1-rc.1",
|
|
94
|
+
"@deepseek-ai/dsh-attachment": "0.2.1-rc.1",
|
|
95
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-rc.1",
|
|
96
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.1",
|
|
97
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.1",
|
|
98
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.1",
|
|
99
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.2.1-rc.1",
|
|
100
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.1",
|
|
101
|
+
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.1",
|
|
102
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.1-rc.1",
|
|
103
|
+
"@deepseek-ai/dsh-llm": "0.2.1-rc.1",
|
|
104
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.2.1-rc.1",
|
|
105
|
+
"@deepseek-ai/dsh-settings": "0.2.1-rc.1",
|
|
106
|
+
"@deepseek-ai/schemastery": "3.18.5",
|
|
107
|
+
"@earendil-works/pi-ai": "1.1.0",
|
|
108
108
|
"@testing-library/dom": "^10.4.2",
|
|
109
109
|
"@testing-library/react": "^16.3.3",
|
|
110
110
|
"@types/node": "^22.20.0",
|