dsh-connect-trae 3.4.1 → 3.4.2
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 +104 -0
- package/README.en.md +20 -1
- package/README.md +28 -1
- package/lib/index.d.ts +179 -4
- package/lib/index.js +343 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,109 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.4.2 (2026-10-13)
|
|
4
|
+
|
|
5
|
+
> **插件现在与它自己解析到的那份 `@earendil-works/pi-ai` 代数对齐了**——这是 [issue #27](https://github.com/dingminhua/dsh-connect-trae/issues/27)
|
|
6
|
+
> 的修复。起因是报告人用任意模型都拿到 `PI_AI_ERROR / Cannot read properties of undefined (reading 'length')`,
|
|
7
|
+
> 且 usage 恒为 0:崩溃发生在**请求发出之前**的本地 token 估算里,所以既与模型无关,也看不到任何 HTTP 痕迹。
|
|
8
|
+
> 取证见 `docs/ISSUE27_DIAGNOSIS.md`。
|
|
9
|
+
|
|
10
|
+
### 修复
|
|
11
|
+
|
|
12
|
+
- **插件现在会在两种上下文形状之间双向适配,而不是把宿主的形状原样交给自己的 api**(issue #27):
|
|
13
|
+
- **原行为**:`createTraeAdapter` 把 `openAICompletionsApi()` 直接交给 `createProvider`,没有任何适配层。
|
|
14
|
+
插件声明 pi-ai 为 peer(`>=0.85.1`),所以它解析到哪一份**由安装环境决定**,未必是宿主那一份。
|
|
15
|
+
- **为什么这是必崩**:pi-ai **0.86 起**在每个公开入口调一次 `normalizeContext()`,于是 provider 收到的是
|
|
16
|
+
*归一化 transcript*:`{ messages: [ { role:'system', content:<字符串>, toolsAdded }, … ] }`——系统提示与工具声明
|
|
17
|
+
**只**挂在这条 system 消息上,顶层 `systemPrompt` / `tools` 已被删除。而 ≤0.85 的 `estimateMessageTokens`
|
|
18
|
+
**没有 `role === 'system'` 分支**:它 `for (const block of message.content)` 迭代一个**字符串**,逐字符拿到
|
|
19
|
+
`block`,再读 `block.name.length` → `TypeError`。因为这一步位于 `buildBaseOptions` 内、早于任何 HTTP 构造,
|
|
20
|
+
所以现象就是「105ms 内本地崩溃 + usage 恒 0 + 换模型无效」。
|
|
21
|
+
- **两个方向都要修**,因为 **DSH 自己也横跨两代**(`dsh-llm-pi-ai` ≤0.2.0-rc.1 声明 `^0.85.1`,0.2.0-rc.2 起声明
|
|
22
|
+
`^0.87.1`,0.2.1-alpha.2 已到 `^1.0.2`),而本插件声明支持 `>=0.1.7-rc.1 <0.3.0-0`:
|
|
23
|
+
- **新宿主 + 旧库**(issue #27 本尊):必崩。
|
|
24
|
+
- **旧宿主 + 新库**:不崩,但现代 api 只从 system 消息读提示与工具,顶层字段被**静默忽略** → 系统提示与
|
|
25
|
+
**全部工具声明**丢失(模型永远不会产生 toolCall)。只修前者会把一个响亮的故障换成一个安静的故障。
|
|
26
|
+
- **改后**:新增 `withContextAdaptation(api, generation)`,按**本插件自己解析到的那一代**决定折返方向;
|
|
27
|
+
两个适配函数都以**形状**做幂等短路(普通路径返回同一个对象,零分配)。
|
|
28
|
+
- `adaptLegacyPiAiContext`(transcript → 旧形状):把 system 文本提升为 `systemPrompt`,并把
|
|
29
|
+
`toolsRemoved`/`toolsAdded` **按序回放**成顶层 `tools`——工具那一步不能省,`normalizeContext` 之后它是
|
|
30
|
+
工具声明的**唯一**载体。
|
|
31
|
+
- `adaptModernPiAiContext`(旧形状 → transcript):把 `systemPrompt`/`tools` 提到一条首部 system 消息上,
|
|
32
|
+
并移除顶层字段,避免后续的形状判据误判。
|
|
33
|
+
- **提示文本的三条载体都要搬,`sections` 不能漏**:新代 `SystemMessage` 的提示文本有 `content` 与
|
|
34
|
+
`sections` 两条载体(`Record<string, string | null>`),库自己的 `getSystemMessageText` 是
|
|
35
|
+
「`content` + 全部非 null section」按空行连接。只读 `content` 会让以 section 形态到达的提示整体折成
|
|
36
|
+
`undefined`——与该适配器要防的「静默丢失」完全同类。现已逐字对齐库的语义(`content` 在前、
|
|
37
|
+
按声明顺序接 section、跳过 `null`)。
|
|
38
|
+
- **折上的语义与 `normalizeContext` 逐字对齐,包括「混合形状」**:当上下文**同时**带顶层字段和
|
|
39
|
+
system 消息时(部分归一化的宿主),pi-ai 自己的做法是**前插**(于是有两条 system 消息,provider 再按
|
|
40
|
+
空行折叠),本插件因此同样前插。实测该输出经真实 0.87.1 的 `resolveTranscript` 折叠后,与
|
|
41
|
+
`normalizeContext` 的结果**逐字相同**。早先版本在混合形状下直接原样返回,会静默丢掉顶层提示与其
|
|
42
|
+
工具——这条是评审的变异测试暴露的(见「测试」)。
|
|
43
|
+
- **代际判据落在 0.86,不是 0.87**(issue #27):
|
|
44
|
+
- 报告与兄弟插件的既有实现都按 `>= 0.87 → modern` 划分。实测**真实边界是 0.86.0**——
|
|
45
|
+
`dist/utils/transcript.js`、`normalizeContext`、估算器的 system 分支、以及「不再读 `systemPrompt`/`tools`」
|
|
46
|
+
四件事**同时**出现在 0.86.0。
|
|
47
|
+
- **为什么这个偏差有害**:若插件解析到 **0.86.x**,`>= 0.87` 的判据会把它当旧代而执行折返,而 0.86+ 的 api
|
|
48
|
+
根本不读顶层字段 → 系统提示与全部工具被静默丢弃。即「照抄现成实现」反而会引入一个新的静默故障。
|
|
49
|
+
- **新增 `src/pi-ai-runtime.ts`:世代探测不依赖版本号字符串**(issue #27):
|
|
50
|
+
- 主判据是解析到的那份包 manifest 的版本,用**数值**比较(`major > 0` 或 `minor >= 86` 即现代),
|
|
51
|
+
因此 `0.9.0` 不会被误判为高于 `0.86.0`,已发布的 `1.x` 也无需改动即可正确归类。
|
|
52
|
+
- 版本读不到时退化为**包布局**判据(`dist/utils/transcript.js` 是否存在)。
|
|
53
|
+
- **刻意不使用 `import.meta.resolve()` 做能力探测**:bundler 风格的解析器(vite,以及在它之上的 vitest)
|
|
54
|
+
会把 exports map 里的 `"./utils/*"` 通配符展开成路径**并直接返回,不检查文件是否存在**,于是对 0.85.1
|
|
55
|
+
也回答「有」——实测该假阳性会把 0.85.1 判成 modern,从而**在声称按能力判断的同时放过 issue #27**。
|
|
56
|
+
读包目录则不会被这样骗过。
|
|
57
|
+
- 解析不到 pi-ai(宿主外探测的市场安装)时取 `modern` 透传:每个做归一化的宿主都同时提供现代代,
|
|
58
|
+
且透传不可能破坏一个本来就正确的上下文。
|
|
59
|
+
|
|
60
|
+
### 诊断
|
|
61
|
+
|
|
62
|
+
- **启动时打印解析到的 pi-ai 版本与代数**(issue #27):混代安装是「安静坏掉」的那一类,日志里一行
|
|
63
|
+
`dsh-connect-trae: pi-ai 0.85.1 (legacy generation)` 让它在下次出现时立刻可见,而不是只表现为一次崩溃。
|
|
64
|
+
|
|
65
|
+
### 测试
|
|
66
|
+
|
|
67
|
+
- **全仓 549 → 594(+45)**:
|
|
68
|
+
- `tests/pi-ai-context.spec.ts`(38 例):代际判定(含 0.86/0.87 边界、数值排序、`1.x`、不可读版本)、
|
|
69
|
+
`decidePiAiGeneration` 的三条分支(含本机**永不可达**的「什么都没探测到」——市场安装的默认答案,
|
|
70
|
+
必须显式钉住)、折返(提示提升、**工具提升**、`toolsRemoved`/`toolsAdded` 顺序回放、既有顶层 `tools`
|
|
71
|
+
权威、空文本 system 消息只丢文本不丢工具、文本块、**`sections` 三条载体**、混合形状、畸形输入)、
|
|
72
|
+
折上(提升、移除顶层字段、幂等、空提示、**混合形状前插**)、
|
|
73
|
+
`withContextAdaptation`(两个入口都适配、同形状透传为**同一对象**、保留 `fetchDeferred` 等其余字段),
|
|
74
|
+
以及**对真实解析到的那份 pi-ai 的集成对拍**:`estimateContextTokens` 不抛错、`convertMessages` 后
|
|
75
|
+
`messages[0].role === 'system'` 且 `tools` 仍在。
|
|
76
|
+
- `tests/pi-ai-adapter-wiring.spec.ts`(7 例):**接线层**用例。上面那批函数级用例在把适配层从工厂里删掉后
|
|
77
|
+
**依然全绿**(已用变异验证),正是「函数没问题、只是没人调用」这一原始缺陷的形状;本文件 mock 掉
|
|
78
|
+
`openai-completions.lazy`,经 `adapter.current() → piProvider → streamSimple` 走真实装配路径断言交付给
|
|
79
|
+
api 的形状。
|
|
80
|
+
- **变异验证 8 次,8 次全部被抓**(①–⑤ 为自查,⑥–⑧ 由独立评审提出):
|
|
81
|
+
① 工厂里移除适配层 → 5 例失败;② 折返只搬 prompt 不搬 tools → 6 例失败;③ 边界写成 `>= 87` →
|
|
82
|
+
1 例失败;④ 折上时不移除顶层字段 → 2 例失败;⑤ 折返不做幂等短路 → 5 例失败;
|
|
83
|
+
**⑥ 折返忽略 `sections` → 2 例失败**;**⑦ 折上跳过多 system 消息的混合形状 → 1 例失败**;
|
|
84
|
+
**⑧ 不可探测时默认 `legacy` → 1 例失败**。恢复后 57 文件 594 例全绿。
|
|
85
|
+
- 确认新增用例真的在跑:改动前基线 **549**、改动后 **594**,净增 45,与新增用例数一致。
|
|
86
|
+
|
|
87
|
+
### 独立评审
|
|
88
|
+
|
|
89
|
+
发布前由独立评审对 diff 做了对抗性复核(真实发布包 + 真实 api 模块),确认:双向四组合均不丢提示、
|
|
90
|
+
不丢工具、不重复 system 消息;**0.86 边界**在 0.85.1/0.86.0/0.86.1/0.87.1/1.0.2 五个真实 tarball 上
|
|
91
|
+
逐项复核成立;**`import.meta.resolve` 的假阳性被独立复现**(该模块在 0.85.1 磁盘上不存在,vite 仍返回
|
|
92
|
+
其 URL);接线层用例确实覆盖工厂路径。
|
|
93
|
+
|
|
94
|
+
评审同时提出 **3 条有效缺陷**,均已修复并补上用例(即上表 ⑥⑦⑧):`sections` 载体被漏读、
|
|
95
|
+
混合形状下折上会静默丢弃顶层提示与工具、以及不可探测分支的默认值无任何用例约束。
|
|
96
|
+
|
|
97
|
+
### 文档
|
|
98
|
+
|
|
99
|
+
- `docs/ISSUE27_DIAGNOSIS.md`:对外部报告的逐条核验——链路 6 跳的取证、双向错配四组合矩阵的实测结果、
|
|
100
|
+
DSH 各版本与 pi-ai 代数对照,并明确指出该报告的 **3 处硬错误**(代际边界实为 0.86 而非 0.87;
|
|
101
|
+
CASE B 实测为 `{"tokens":25}` 而非 `15`;`undefined maxTokens` 实为 `NaN` 而非「夹回正整数」)
|
|
102
|
+
与 **2 处需修正的修复建议**(「收紧 peer 即可在安装期被拒」不成立——DSH 内核预检
|
|
103
|
+
`evaluatePluginCompatibility` 只覆盖 `@deepseek-ai/dsh*`,pi-ai 不在其中,报告引用的 `install-compat`
|
|
104
|
+
告警来自 dshmarket 社区插件且属只告警不阻止的 `soft-incompatible`;以及「升级 DSH」解决不了问题,
|
|
105
|
+
报告人本就在最新的 0.2.0-rc.2 上,而升级只会把他更牢地留在「新宿主」一侧)。
|
|
106
|
+
|
|
3
107
|
## 3.4.1 (2026-10-12)
|
|
4
108
|
|
|
5
109
|
> **`4011` 的含义改正了:它是 Trae 对 SOLO 通道的限流,不是「该 function 不提供此模型」。**
|
package/README.en.md
CHANGED
|
@@ -138,6 +138,25 @@ Restart the DSH process after install/update/uninstall.
|
|
|
138
138
|
**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).
|
|
139
139
|
**The peer upper bound is `<0.3.0-0`, so both the 0.1.7 and the 0.2 line are supported.**
|
|
140
140
|
|
|
141
|
+
### Both pi-ai context generations are supported (since 3.4.2)
|
|
142
|
+
|
|
143
|
+
DSH itself spans two generations of `@earendil-works/pi-ai`, and **which one this plugin resolves to is decided by your install**, not by the host — declaring it a peer is correct, but the two generations disagree on what a context looks like:
|
|
144
|
+
|
|
145
|
+
| | legacy (≤0.85) | modern (0.86+) |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| shape | `{ systemPrompt, tools, messages }` | `{ messages: [ {role:'system', content, toolsAdded}, … ] }` |
|
|
148
|
+
| system prompt | top-level `systemPrompt` | **only** on that system message |
|
|
149
|
+
| tool declarations | top-level `tools` | **only** on that system message's `toolsAdded` |
|
|
150
|
+
|
|
151
|
+
A mismatch breaks **in both directions**, so since 3.4.2 the plugin adapts bidirectionally according to the generation it resolved (issue #27):
|
|
152
|
+
|
|
153
|
+
- **Modern host + legacy library**: a guaranteed crash — the legacy token estimator has no `role === 'system'` branch, so it iterates a string and throws `Cannot read properties of undefined (reading 'length')`. This happens before the request is built, which is why it looks like "every model crashes, within milliseconds, with all-zero usage".
|
|
154
|
+
- **Legacy host + modern library**: no crash, but the modern api reads only that system message and silently ignores the top-level fields — the system prompt and **every tool declaration** are lost, so the model can never emit a tool call. Quieter, and also fixed.
|
|
155
|
+
|
|
156
|
+
The generation gate is the copy **this plugin resolved** (its manifest version; package layout when that is unreadable), **not the host version** — a nested older copy shadows a newer host copy and vice versa. Startup logs one line, `dsh-connect-trae: pi-ai <version> (<generation>)`; `legacy` means your install resolved to the ≤0.85 line.
|
|
157
|
+
|
|
158
|
+
The boundary is **0.86**: `dist/utils/transcript.js`, `normalizeContext`, the estimator's system branch, and the switch away from the top-level fields all land in 0.86.0 together. Gating on `0.87` misclassifies 0.86.x as legacy and folds a context the modern api consumes natively, destroying the prompt and the tools.
|
|
159
|
+
|
|
141
160
|
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.
|
|
142
161
|
|
|
143
162
|
### Why the upper bound is not `<0.2.0`
|
|
@@ -232,7 +251,7 @@ pnpm run check # typecheck + test + build
|
|
|
232
251
|
Local dev via a `link:` install to the desktop profile (restart DSH Desktop after editing):
|
|
233
252
|
|
|
234
253
|
```sh
|
|
235
|
-
dsh plugin --profile desktop add /
|
|
254
|
+
dsh plugin --profile desktop add /path/to/dsh-connect-trae
|
|
236
255
|
```
|
|
237
256
|
|
|
238
257
|
## Acknowledgements
|
package/README.md
CHANGED
|
@@ -143,6 +143,33 @@ npm install dsh-connect-trae
|
|
|
143
143
|
**要求 DSH 0.1.7-rc.1 及以上**(2.3.0 起不再支持 0.1.7 之前的宿主;peer 依赖范围已按此收窄)。
|
|
144
144
|
**peer 上界为 `<0.3.0-0`:0.1.7 与 0.2 两条线均受支持。**
|
|
145
145
|
|
|
146
|
+
### 同时支持 pi-ai 的两代上下文(3.4.2 起)
|
|
147
|
+
|
|
148
|
+
DSH 自己横跨 `@earendil-works/pi-ai` 的两代,而**本插件解析到哪一代由你的安装环境决定**,
|
|
149
|
+
未必是宿主那一代——插件声明它是 peer,这是对的,但两代对「上下文长什么样」的定义不同:
|
|
150
|
+
|
|
151
|
+
| | 旧代(≤0.85) | 新代(0.86+) |
|
|
152
|
+
| --- | --- | --- |
|
|
153
|
+
| 形状 | `{ systemPrompt, tools, messages }` | `{ messages: [ {role:'system', content, toolsAdded}, … ] }` |
|
|
154
|
+
| 系统提示 | 顶层 `systemPrompt` | **只**挂在那条 system 消息上 |
|
|
155
|
+
| 工具声明 | 顶层 `tools` | **只**挂在那条 system 消息的 `toolsAdded` 上 |
|
|
156
|
+
|
|
157
|
+
错配**两个方向都会坏**,所以 3.4.2 起插件会按自己解析到的那一代双向适配(issue #27):
|
|
158
|
+
|
|
159
|
+
- **新宿主 + 旧库**:必崩——旧代的 token 估算没有 `role === 'system'` 分支,会去迭代一个字符串并抛
|
|
160
|
+
`Cannot read properties of undefined (reading 'length')`。这一步在请求构造之前,所以表现为
|
|
161
|
+
「任意模型都崩、耗时极短、usage 恒为 0」。
|
|
162
|
+
- **旧宿主 + 新库**:不崩,但现代 api 只读那条 system 消息,顶层字段被**静默忽略**——系统提示与
|
|
163
|
+
**全部工具声明**丢失(模型永远不会产生 toolCall)。这类故障更隐蔽,同样已修。
|
|
164
|
+
|
|
165
|
+
代际判据是**本插件自己解析到的那份包**(读它的 manifest 版本;读不到时看包布局),
|
|
166
|
+
**不是宿主版本**——因为嵌套的旧副本会盖过宿主的新副本,反之亦然。启动时日志里会有一行
|
|
167
|
+
`dsh-connect-trae: pi-ai <版本> (<代数>)`,`legacy` 表示你这份安装解析到了 ≤0.85 的旧代。
|
|
168
|
+
|
|
169
|
+
分界线是 **0.86**:`dist/utils/transcript.js`、`normalizeContext`、估算器的 system 分支、
|
|
170
|
+
以及「不再读顶层字段」这四件事同时出现在 0.86.0。按 `0.87` 划分会把 0.86.x 误判成旧代而错误折返,
|
|
171
|
+
反而丢掉提示与工具。
|
|
172
|
+
|
|
146
173
|
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 门禁**:
|
|
147
174
|
peer 范围不满足的 bundle 会被**整体跳过**,只往 stderr 写一行,界面上表现为「插件凭空消失」。
|
|
148
175
|
|
|
@@ -242,7 +269,7 @@ pnpm run check # typecheck + test + build
|
|
|
242
269
|
本地开发用 `link:` 安装到 desktop profile(改码后重启 DSH Desktop 生效):
|
|
243
270
|
|
|
244
271
|
```sh
|
|
245
|
-
dsh plugin --profile desktop add /
|
|
272
|
+
dsh plugin --profile desktop add /path/to/dsh-connect-trae
|
|
246
273
|
```
|
|
247
274
|
|
|
248
275
|
## 致谢
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { Context, ProviderStreams } from "@earendil-works/pi-ai";
|
|
2
3
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
3
|
-
import { Context } from "@deepseek-ai/cordis";
|
|
4
|
+
import { Context as Context$1 } from "@deepseek-ai/cordis";
|
|
4
5
|
import { AttachmentStore } from "@deepseek-ai/dsh-attachment";
|
|
5
6
|
//#region src/reasoning.d.ts
|
|
6
7
|
declare const TRAE_REASONING_EFFORTS: readonly ["minimal", "low", "medium", "high", "xhigh"];
|
|
@@ -295,6 +296,113 @@ declare class TraeCatalog {
|
|
|
295
296
|
set(models: readonly TraeModelInfo[]): void;
|
|
296
297
|
}
|
|
297
298
|
//#endregion
|
|
299
|
+
//#region src/pi-ai-runtime.d.ts
|
|
300
|
+
/**
|
|
301
|
+
* Which `@earendil-works/pi-ai` copy THIS plugin resolves at runtime, and which
|
|
302
|
+
* context shape that copy consumes (issue #27).
|
|
303
|
+
*
|
|
304
|
+
* WHY THIS EXISTS. The plugin declares pi-ai as a peer, so the module it
|
|
305
|
+
* imports is whatever the install provides — and the two generations consume
|
|
306
|
+
* DIFFERENT context shapes:
|
|
307
|
+
*
|
|
308
|
+
* - **modern** (0.86+): every public entry point calls `normalizeContext()`
|
|
309
|
+
* once, so what reaches a provider is a normalized *transcript*:
|
|
310
|
+
* `{ messages: [ { role: 'system', content, toolsAdded }, … ] }`. The system
|
|
311
|
+
* prompt and the tool declarations live ONLY on that leading system message;
|
|
312
|
+
* the top-level `systemPrompt` / `tools` fields are gone, and the api never
|
|
313
|
+
* reads them back.
|
|
314
|
+
* - **legacy** (≤0.85): expects `{ systemPrompt, tools, messages }` and has no
|
|
315
|
+
* branch for a `system` message inside `messages` at all. Its token estimator
|
|
316
|
+
* crashes on one — `estimateMessageTokens` iterates the string `content`
|
|
317
|
+
* character by character and reads `.name` off a character — and its wire
|
|
318
|
+
* converter silently drops it.
|
|
319
|
+
*
|
|
320
|
+
* The mismatch is reachable in BOTH directions, because DSH itself spans both
|
|
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`), while this plugin
|
|
323
|
+
* accepts the whole range (`>=0.1.7-rc.1 <0.3.0-0`). Whichever side is newer,
|
|
324
|
+
* the other one is wrong:
|
|
325
|
+
*
|
|
326
|
+
* | host | resolved api | outcome |
|
|
327
|
+
* |---|---|---|
|
|
328
|
+
* | legacy | legacy | fine |
|
|
329
|
+
* | legacy | modern | no crash, but prompt + tools silently LOST |
|
|
330
|
+
* | modern | legacy | CRASH: `Cannot read properties of undefined (reading 'length')` |
|
|
331
|
+
* | modern | modern | fine |
|
|
332
|
+
*
|
|
333
|
+
* THE BOUNDARY IS 0.86, NOT 0.87. Checked against the published tarballs:
|
|
334
|
+
* `dist/utils/transcript.js` — and with it `normalizeContext`, the
|
|
335
|
+
* `role === 'system'` estimator branch, and the switch away from reading
|
|
336
|
+
* `systemPrompt`/`tools` — first appears in **0.86.0**. A `>= 0.87` gate
|
|
337
|
+
* misclassifies 0.86.x as legacy and folds a context the modern api consumes
|
|
338
|
+
* natively, which deletes the only carrier of the prompt and the tools.
|
|
339
|
+
*
|
|
340
|
+
* HOW the generation is decided, in order:
|
|
341
|
+
*
|
|
342
|
+
* 1. **The manifest version** of the copy this plugin resolved. Numeric semver
|
|
343
|
+
* comparison (`minor >= 86`), so `0.86.0` and the already-shipped `1.x` line
|
|
344
|
+
* both land on modern, and `0.9.0` correctly sorts below `0.86.0`.
|
|
345
|
+
* 2. **The package's own layout**, when no version could be read: the transcript
|
|
346
|
+
* module sits at `dist/utils/transcript.js` (the exports map's
|
|
347
|
+
* `"./utils/*" → "./dist/utils/*.js"` convention is identical in every
|
|
348
|
+
* published generation, 0.82 through 1.x).
|
|
349
|
+
*
|
|
350
|
+
* Both checks are deliberately resolver-independent. `import.meta.resolve()` is
|
|
351
|
+
* NOT used to answer this question: bundler-style resolvers (vite, and equally
|
|
352
|
+
* `vitest` under it) expand the `"./utils/*"` wildcard to a path and return it
|
|
353
|
+
* WITHOUT checking that the file exists, so a probe built on it answers "modern"
|
|
354
|
+
* for 0.85.1 — the exact false positive that would leave issue #27 unfixed while
|
|
355
|
+
* claiming to be capability-based. Reading the package directory cannot be
|
|
356
|
+
* fooled that way.
|
|
357
|
+
*
|
|
358
|
+
* The version is therefore the primary signal, and it also keeps the gate
|
|
359
|
+
* forward-compatible for free: a future 2.x needs no change here, whereas a
|
|
360
|
+
* file-name check alone would have to know about it.
|
|
361
|
+
*
|
|
362
|
+
* @module dsh-connect-trae/pi-ai-runtime
|
|
363
|
+
*/
|
|
364
|
+
/** The context generation a resolved pi-ai api module speaks. */
|
|
365
|
+
type PiAiGeneration = 'legacy' | 'modern';
|
|
366
|
+
/** What {@link piAiRuntimeInfo} reports about the resolved pi-ai copy. */
|
|
367
|
+
interface PiAiRuntimeInfo {
|
|
368
|
+
/** `modern` (0.86+: consumes normalized transcripts) or `legacy` (≤0.85 shape). */
|
|
369
|
+
generation: PiAiGeneration;
|
|
370
|
+
/** Version of the resolved copy, when the manifest could be read. */
|
|
371
|
+
version: string | undefined;
|
|
372
|
+
/** Package directory of the resolved copy, when it could be located. */
|
|
373
|
+
resolvedFrom: string | undefined;
|
|
374
|
+
/**
|
|
375
|
+
* Whether the copy could be inspected at all. `false` means pi-ai does not
|
|
376
|
+
* resolve from this plugin — the normal state of a market install probed
|
|
377
|
+
* OUTSIDE the DSH host, where the host supplies the module at plugin runtime.
|
|
378
|
+
* Inside DSH the copy is always inspectable: a nested one shadows host
|
|
379
|
+
* injection, and without a nested one the host supplies its own.
|
|
380
|
+
*/
|
|
381
|
+
inspected: boolean;
|
|
382
|
+
}
|
|
383
|
+
/** Resolve (once) which pi-ai copy this plugin runs against. */
|
|
384
|
+
declare function piAiRuntimeInfo(): PiAiRuntimeInfo;
|
|
385
|
+
/**
|
|
386
|
+
* The generation decision table, exported so every branch is pinned by tests
|
|
387
|
+
* rather than by a comment here — including the two this development tree can
|
|
388
|
+
* never reach (no local copy, and a copy with no readable version), which are
|
|
389
|
+
* exactly the states a market install founds its gate on.
|
|
390
|
+
*
|
|
391
|
+
* @param version - manifest version of the resolved copy, if readable.
|
|
392
|
+
* @param transcriptPresent - whether that copy actually ships
|
|
393
|
+
* `dist/utils/transcript.js`; `undefined` when nothing was inspected.
|
|
394
|
+
*/
|
|
395
|
+
declare function decidePiAiGeneration(version: string | undefined, transcriptPresent: boolean | undefined): PiAiGeneration;
|
|
396
|
+
/**
|
|
397
|
+
* The generation a version string belongs to, or `undefined` when the string
|
|
398
|
+
* cannot be read as a version at all.
|
|
399
|
+
*
|
|
400
|
+
* Only the minor of the `0.x` line matters for the boundary, and `major >= 1`
|
|
401
|
+
* is modern by definition (1.0.x is already in use by `dsh-llm-pi-ai@0.2.1-alpha.2`
|
|
402
|
+
* and consumes transcripts natively).
|
|
403
|
+
*/
|
|
404
|
+
declare function generationOf(version: string | undefined): PiAiGeneration | undefined;
|
|
405
|
+
//#endregion
|
|
298
406
|
//#region src/upstream.d.ts
|
|
299
407
|
type TraeUpstreamErrorKind = 'authentication' | 'hard_credit' | 'soft_rate' | 'not_found' | 'server' | 'client' | 'unconfigured';
|
|
300
408
|
type TraeChatResult = {
|
|
@@ -351,6 +459,67 @@ declare const TRAE_PROVIDER_DISPLAY_NAMES: Readonly<Record<TraeRegion, string>>;
|
|
|
351
459
|
/** Region a provider route id belongs to. */
|
|
352
460
|
declare function regionOfTraeProvider(provider: string): TraeRegion | undefined;
|
|
353
461
|
declare const TRAE_STREAM_IDLE_TIMEOUT_MS = 300000;
|
|
462
|
+
/**
|
|
463
|
+
* Fold a NORMALIZED transcript back into the `{ systemPrompt, tools, messages }`
|
|
464
|
+
* shape a LEGACY (≤0.85) api consumes — issue #27's crash and its two silent
|
|
465
|
+
* siblings.
|
|
466
|
+
*
|
|
467
|
+
* WHY (issue #27). 0.86+ normalizes at every public entry point, and DSH's host
|
|
468
|
+
* calls `Models.streamSimple()`, so on a modern host the context arriving here
|
|
469
|
+
* ALWAYS carries its prompt and its tool declarations on a leading system
|
|
470
|
+
* message — the top-level fields are gone. A legacy api has no branch for that
|
|
471
|
+
* message: `estimateMessageTokens` iterates the string `content` character by
|
|
472
|
+
* character and reads `.name` off a character, throwing
|
|
473
|
+
* `Cannot read properties of undefined (reading 'length')` before the request
|
|
474
|
+
* is even built. That is what surfaced as `PI_AI_ERROR` with all-zero usage.
|
|
475
|
+
*
|
|
476
|
+
* The fold must carry MORE than the prompt. `toolsAdded` is the ONLY remaining
|
|
477
|
+
* carrier of the tool declarations once `normalizeContext` has run, and a
|
|
478
|
+
* legacy api reads tools exclusively from `context.tools` — so lifting the text
|
|
479
|
+
* alone would trade a loud crash for a silent one: a model that can never emit
|
|
480
|
+
* a `toolCall` because the request declared no tools.
|
|
481
|
+
*
|
|
482
|
+
* Shape-guarded, not version-guarded: it asks "is there a system message inside
|
|
483
|
+
* `messages`?" rather than "which pi-ai is loaded?". A version test would be
|
|
484
|
+
* wrong the moment either side moves, and this predicate is an identity for the
|
|
485
|
+
* legacy shape, so the ordinary path stays byte-for-byte untouched. WHICH
|
|
486
|
+
* branch runs at all IS generation-gated — see {@link withContextAdaptation}.
|
|
487
|
+
*
|
|
488
|
+
* Returns the SAME object when there is nothing to fold, so a legacy host
|
|
489
|
+
* (which never normalizes) pays one `Array.some` and nothing else.
|
|
490
|
+
*/
|
|
491
|
+
declare function adaptLegacyPiAiContext(context: Context): Context;
|
|
492
|
+
/**
|
|
493
|
+
* Lift the legacy `{ systemPrompt, tools, messages }` shape into the normalized
|
|
494
|
+
* transcript a MODERN (0.86+) api consumes — the reverse mismatch, and just as
|
|
495
|
+
* silent.
|
|
496
|
+
*
|
|
497
|
+
* WHY. DSH spans both generations (`dsh-llm-pi-ai` ≤0.2.0-rc.1 pins `^0.85.1`)
|
|
498
|
+
* while this plugin may resolve a newer copy than its host. A LEGACY host never
|
|
499
|
+
* normalizes, so it hands the api the legacy shape; a modern api reads the
|
|
500
|
+
* prompt from a leading system message and the tools from its `toolsAdded`, and
|
|
501
|
+
* never looks at the top-level fields — so both are dropped without a sound.
|
|
502
|
+
* Verified against the published tarballs: the request that reaches the wire
|
|
503
|
+
* carries no system turn and `tools: []`.
|
|
504
|
+
*
|
|
505
|
+
* Identity for anything already in transcript shape, and for a context with
|
|
506
|
+
* neither a prompt nor tools (pi-ai's own `createInitialSystemMessage` returns
|
|
507
|
+
* `undefined` there, keeping an empty transcript empty).
|
|
508
|
+
*/
|
|
509
|
+
declare function adaptModernPiAiContext(context: Context): Context;
|
|
510
|
+
/**
|
|
511
|
+
* Wrap one pi-ai API module so BOTH stream entry points receive a context its
|
|
512
|
+
* own generation can consume.
|
|
513
|
+
*
|
|
514
|
+
* The gate is the copy THIS plugin resolved (see {@link piAiRuntimeInfo}) —
|
|
515
|
+
* what the host runs says nothing about it, because a nested copy on disk
|
|
516
|
+
* shadows host injection and vice versa. The wrapping is cheap and lazy: only
|
|
517
|
+
* `stream`/`streamSimple` are intercepted, the deferred-fetch entry points take
|
|
518
|
+
* no context and are passed through untouched, and each adapter function is an
|
|
519
|
+
* identity unless the context is actually the other generation's shape. So a
|
|
520
|
+
* healthy install does one `Array.some` per request.
|
|
521
|
+
*/
|
|
522
|
+
declare function withContextAdaptation(api: ProviderStreams, generation: PiAiGeneration): ProviderStreams;
|
|
354
523
|
interface TraeAdapterOptions {
|
|
355
524
|
shim: TraeShim;
|
|
356
525
|
catalog: TraeCatalog;
|
|
@@ -359,6 +528,12 @@ interface TraeAdapterOptions {
|
|
|
359
528
|
/** pi-ai provider name and profile display name; defaults to the CN name. */
|
|
360
529
|
displayName?: string;
|
|
361
530
|
resolveAttachments?: () => AttachmentStore | undefined;
|
|
531
|
+
/**
|
|
532
|
+
* Override the resolved pi-ai generation (see {@link withContextAdaptation});
|
|
533
|
+
* tests use it to exercise the combination this machine does not have
|
|
534
|
+
* installed.
|
|
535
|
+
*/
|
|
536
|
+
piAiGeneration?: PiAiGeneration;
|
|
362
537
|
}
|
|
363
538
|
interface TraeAdapter {
|
|
364
539
|
adapter: PiAiAdapter;
|
|
@@ -2116,7 +2291,7 @@ interface TraeUsageRouteOptions {
|
|
|
2116
2291
|
*/
|
|
2117
2292
|
declare function traeWebUsage(deps: TraeUsageRouteOptions, region: TraeRegion): Promise<TraeWebUsage>;
|
|
2118
2293
|
/** Mount the GET usage route on an optional webServer context. */
|
|
2119
|
-
declare function registerTraeUsageRoute(ctx: Context, deps: TraeUsageRouteOptions): void;
|
|
2294
|
+
declare function registerTraeUsageRoute(ctx: Context$1, deps: TraeUsageRouteOptions): void;
|
|
2120
2295
|
//#endregion
|
|
2121
2296
|
//#region src/model-probe.d.ts
|
|
2122
2297
|
/** How one model answered. */
|
|
@@ -2333,6 +2508,6 @@ declare function regionEnabled(config: Config, region: TraeRegion): boolean;
|
|
|
2333
2508
|
*/
|
|
2334
2509
|
declare function asVolatile<S>(schema: S): S;
|
|
2335
2510
|
declare const Config: z<Config>;
|
|
2336
|
-
declare function apply(ctx: Context, config: Config): void;
|
|
2511
|
+
declare function apply(ctx: Context$1, config: Config): void;
|
|
2337
2512
|
//#endregion
|
|
2338
|
-
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, type RawChatDelta, type RawChatMessage, type RawChatTool, SseDecoder, type SseEvent, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, 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_STATE_DB_FILENAME, 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 TraeModelProbeVerdict, type TraeModelsTestResponse, type TraeObservedModelConfig, type TraeProbeResult, type TraeProbeVerdict, 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 TraeWebAccountState, type TraeWebActivity, type TraeWebAppLogin, type TraeWebCheckin, type TraeWebCheckinClaim, type TraeWebCredits, type TraeWebUsage, type TraeWireModel, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, followAppLoginEnabled, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, 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, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|
|
2513
|
+
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, type PiAiGeneration, type PiAiRuntimeInfo, REGION_GATEWAYS, type RawChatDelta, type RawChatMessage, type RawChatTool, SseDecoder, type SseEvent, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, 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_STATE_DB_FILENAME, 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 TraeModelProbeVerdict, type TraeModelsTestResponse, type TraeObservedModelConfig, type TraeProbeResult, type TraeProbeVerdict, 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 TraeWebAccountState, type TraeWebActivity, type TraeWebAppLogin, type TraeWebCheckin, type TraeWebCheckinClaim, type TraeWebCredits, type TraeWebUsage, type TraeWireModel, UnconfiguredTraeUpstreamClient, adaptLegacyPiAiContext, adaptModernPiAiContext, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, decidePiAiGeneration, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, followAppLoginEnabled, generationOf, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, piAiRuntimeInfo, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, 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, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withContextAdaptation, withTraeRegion };
|
package/lib/index.js
CHANGED
|
@@ -3,9 +3,11 @@ import { createProvider } from "@earendil-works/pi-ai";
|
|
|
3
3
|
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
4
4
|
import { resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
|
|
5
5
|
import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
6
9
|
import { readFile, rm, stat } from "node:fs/promises";
|
|
7
10
|
import { createDecipheriv, createHash, randomBytes, randomUUID, sign, timingSafeEqual } from "node:crypto";
|
|
8
|
-
import { dirname, join } from "node:path";
|
|
9
11
|
import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
10
12
|
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
11
13
|
import { cpus, homedir, hostname, release, userInfo } from "node:os";
|
|
@@ -13,7 +15,6 @@ import { createServer } from "node:http";
|
|
|
13
15
|
import { Readable } from "node:stream";
|
|
14
16
|
import { execFile } from "node:child_process";
|
|
15
17
|
import { promisify } from "node:util";
|
|
16
|
-
import { existsSync } from "node:fs";
|
|
17
18
|
//#region src/catalog.ts
|
|
18
19
|
/**
|
|
19
20
|
* Bootstrap catalog: identity only where current Trae metadata has not been
|
|
@@ -322,6 +323,179 @@ var TraeCatalog = class {
|
|
|
322
323
|
}
|
|
323
324
|
};
|
|
324
325
|
//#endregion
|
|
326
|
+
//#region src/pi-ai-runtime.ts
|
|
327
|
+
/**
|
|
328
|
+
* Which `@earendil-works/pi-ai` copy THIS plugin resolves at runtime, and which
|
|
329
|
+
* context shape that copy consumes (issue #27).
|
|
330
|
+
*
|
|
331
|
+
* WHY THIS EXISTS. The plugin declares pi-ai as a peer, so the module it
|
|
332
|
+
* imports is whatever the install provides — and the two generations consume
|
|
333
|
+
* DIFFERENT context shapes:
|
|
334
|
+
*
|
|
335
|
+
* - **modern** (0.86+): every public entry point calls `normalizeContext()`
|
|
336
|
+
* once, so what reaches a provider is a normalized *transcript*:
|
|
337
|
+
* `{ messages: [ { role: 'system', content, toolsAdded }, … ] }`. The system
|
|
338
|
+
* prompt and the tool declarations live ONLY on that leading system message;
|
|
339
|
+
* the top-level `systemPrompt` / `tools` fields are gone, and the api never
|
|
340
|
+
* reads them back.
|
|
341
|
+
* - **legacy** (≤0.85): expects `{ systemPrompt, tools, messages }` and has no
|
|
342
|
+
* branch for a `system` message inside `messages` at all. Its token estimator
|
|
343
|
+
* crashes on one — `estimateMessageTokens` iterates the string `content`
|
|
344
|
+
* character by character and reads `.name` off a character — and its wire
|
|
345
|
+
* converter silently drops it.
|
|
346
|
+
*
|
|
347
|
+
* The mismatch is reachable in BOTH directions, because DSH itself spans both
|
|
348
|
+
* generations (`dsh-llm-pi-ai` ≤0.2.0-rc.1 declares `^0.85.1`; 0.2.0-rc.2 and
|
|
349
|
+
* later declare `^0.87.1`, 0.2.1-alpha.2 declares `^1.0.2`), while this plugin
|
|
350
|
+
* accepts the whole range (`>=0.1.7-rc.1 <0.3.0-0`). Whichever side is newer,
|
|
351
|
+
* the other one is wrong:
|
|
352
|
+
*
|
|
353
|
+
* | host | resolved api | outcome |
|
|
354
|
+
* |---|---|---|
|
|
355
|
+
* | legacy | legacy | fine |
|
|
356
|
+
* | legacy | modern | no crash, but prompt + tools silently LOST |
|
|
357
|
+
* | modern | legacy | CRASH: `Cannot read properties of undefined (reading 'length')` |
|
|
358
|
+
* | modern | modern | fine |
|
|
359
|
+
*
|
|
360
|
+
* THE BOUNDARY IS 0.86, NOT 0.87. Checked against the published tarballs:
|
|
361
|
+
* `dist/utils/transcript.js` — and with it `normalizeContext`, the
|
|
362
|
+
* `role === 'system'` estimator branch, and the switch away from reading
|
|
363
|
+
* `systemPrompt`/`tools` — first appears in **0.86.0**. A `>= 0.87` gate
|
|
364
|
+
* misclassifies 0.86.x as legacy and folds a context the modern api consumes
|
|
365
|
+
* natively, which deletes the only carrier of the prompt and the tools.
|
|
366
|
+
*
|
|
367
|
+
* HOW the generation is decided, in order:
|
|
368
|
+
*
|
|
369
|
+
* 1. **The manifest version** of the copy this plugin resolved. Numeric semver
|
|
370
|
+
* comparison (`minor >= 86`), so `0.86.0` and the already-shipped `1.x` line
|
|
371
|
+
* both land on modern, and `0.9.0` correctly sorts below `0.86.0`.
|
|
372
|
+
* 2. **The package's own layout**, when no version could be read: the transcript
|
|
373
|
+
* module sits at `dist/utils/transcript.js` (the exports map's
|
|
374
|
+
* `"./utils/*" → "./dist/utils/*.js"` convention is identical in every
|
|
375
|
+
* published generation, 0.82 through 1.x).
|
|
376
|
+
*
|
|
377
|
+
* Both checks are deliberately resolver-independent. `import.meta.resolve()` is
|
|
378
|
+
* NOT used to answer this question: bundler-style resolvers (vite, and equally
|
|
379
|
+
* `vitest` under it) expand the `"./utils/*"` wildcard to a path and return it
|
|
380
|
+
* WITHOUT checking that the file exists, so a probe built on it answers "modern"
|
|
381
|
+
* for 0.85.1 — the exact false positive that would leave issue #27 unfixed while
|
|
382
|
+
* claiming to be capability-based. Reading the package directory cannot be
|
|
383
|
+
* fooled that way.
|
|
384
|
+
*
|
|
385
|
+
* The version is therefore the primary signal, and it also keeps the gate
|
|
386
|
+
* forward-compatible for free: a future 2.x needs no change here, whereas a
|
|
387
|
+
* file-name check alone would have to know about it.
|
|
388
|
+
*
|
|
389
|
+
* @module dsh-connect-trae/pi-ai-runtime
|
|
390
|
+
*/
|
|
391
|
+
let cached;
|
|
392
|
+
/** Resolve (once) which pi-ai copy this plugin runs against. */
|
|
393
|
+
function piAiRuntimeInfo() {
|
|
394
|
+
cached ??= inspectResolvedPiAi();
|
|
395
|
+
return cached;
|
|
396
|
+
}
|
|
397
|
+
function inspectResolvedPiAi() {
|
|
398
|
+
const entry = resolveModuleFile();
|
|
399
|
+
const root = entry === void 0 ? void 0 : findPackageRoot(entry);
|
|
400
|
+
const version = root === void 0 ? void 0 : readVersion(root);
|
|
401
|
+
return {
|
|
402
|
+
generation: decidePiAiGeneration(version, root === void 0 ? void 0 : hasTranscriptModule(root)),
|
|
403
|
+
version,
|
|
404
|
+
resolvedFrom: root,
|
|
405
|
+
inspected: root !== void 0
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* The generation decision table, exported so every branch is pinned by tests
|
|
410
|
+
* rather than by a comment here — including the two this development tree can
|
|
411
|
+
* never reach (no local copy, and a copy with no readable version), which are
|
|
412
|
+
* exactly the states a market install founds its gate on.
|
|
413
|
+
*
|
|
414
|
+
* @param version - manifest version of the resolved copy, if readable.
|
|
415
|
+
* @param transcriptPresent - whether that copy actually ships
|
|
416
|
+
* `dist/utils/transcript.js`; `undefined` when nothing was inspected.
|
|
417
|
+
*/
|
|
418
|
+
function decidePiAiGeneration(version, transcriptPresent) {
|
|
419
|
+
const byVersion = generationOf(version);
|
|
420
|
+
if (byVersion !== void 0) return byVersion;
|
|
421
|
+
if (transcriptPresent === true) return "modern";
|
|
422
|
+
if (transcriptPresent === false) return "legacy";
|
|
423
|
+
return "modern";
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* The generation a version string belongs to, or `undefined` when the string
|
|
427
|
+
* cannot be read as a version at all.
|
|
428
|
+
*
|
|
429
|
+
* Only the minor of the `0.x` line matters for the boundary, and `major >= 1`
|
|
430
|
+
* is modern by definition (1.0.x is already in use by `dsh-llm-pi-ai@0.2.1-alpha.2`
|
|
431
|
+
* and consumes transcripts natively).
|
|
432
|
+
*/
|
|
433
|
+
function generationOf(version) {
|
|
434
|
+
if (version === void 0) return void 0;
|
|
435
|
+
const match = /^(\d+)\.(\d+)\./u.exec(version);
|
|
436
|
+
if (match === null) return void 0;
|
|
437
|
+
const major = Number(match[1]);
|
|
438
|
+
const minor = Number(match[2]);
|
|
439
|
+
if (major > 0) return "modern";
|
|
440
|
+
return minor >= 86 ? "modern" : "legacy";
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Whether the resolved package actually contains the transcript module.
|
|
444
|
+
*
|
|
445
|
+
* `dist/utils/transcript.js` is the file every modern generation ships and no
|
|
446
|
+
* legacy one has (checked: absent in 0.82.1 and 0.85.1; present in 0.86.0,
|
|
447
|
+
* 0.86.1, 0.87.1 and 1.0.2). Landing on the file directly, rather than asking a
|
|
448
|
+
* resolver to find it, is what keeps this honest under bundler-style resolution
|
|
449
|
+
* — see the module docstring.
|
|
450
|
+
*/
|
|
451
|
+
function hasTranscriptModule(packageRoot) {
|
|
452
|
+
return existsSync(join(packageRoot, "dist", "utils", "transcript.js"));
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* The file the main entry resolved to; undefined when resolution itself fails.
|
|
456
|
+
*
|
|
457
|
+
* `import.meta.resolve` (not `require.resolve`): pi-ai's exports map exposes
|
|
458
|
+
* "." under the `import` condition only, so the CJS resolver refuses the bare
|
|
459
|
+
* specifier outright — the ESM resolver is the one this plugin actually imports
|
|
460
|
+
* with, so it is also the one whose answer is meaningful here. Only the package
|
|
461
|
+
* LOCATION is taken from it; the generation question is answered from disk.
|
|
462
|
+
*/
|
|
463
|
+
function resolveModuleFile() {
|
|
464
|
+
try {
|
|
465
|
+
return fileURLToPath(import.meta.resolve("@earendil-works/pi-ai"));
|
|
466
|
+
} catch {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* The package directory owning `moduleFile`: `dist/index.js` sits directly
|
|
472
|
+
* inside the package, so walking up from the entry finds its manifest. The
|
|
473
|
+
* manifest is claimed only when its `name` matches, so a same-named ancestor (a
|
|
474
|
+
* pnpm virtual-store parent, for instance) cannot be misread.
|
|
475
|
+
*/
|
|
476
|
+
function findPackageRoot(moduleFile) {
|
|
477
|
+
let directory = dirname(moduleFile);
|
|
478
|
+
for (let depth = 0; depth < 4; depth++) {
|
|
479
|
+
if (readManifest(directory)?.name === "@earendil-works/pi-ai") return directory;
|
|
480
|
+
const parent = dirname(directory);
|
|
481
|
+
if (parent === directory) return void 0;
|
|
482
|
+
directory = parent;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/** Version from the manifest already located by {@link findPackageRoot}. */
|
|
486
|
+
function readVersion(packageRoot) {
|
|
487
|
+
const version = readManifest(packageRoot)?.version;
|
|
488
|
+
return typeof version === "string" ? version : void 0;
|
|
489
|
+
}
|
|
490
|
+
function readManifest(directory) {
|
|
491
|
+
try {
|
|
492
|
+
const parsed = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
|
|
493
|
+
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
494
|
+
} catch {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
//#endregion
|
|
325
499
|
//#region src/adapter.ts
|
|
326
500
|
/** Provider route this bundle owns for the domestic (CN) gateway. */
|
|
327
501
|
const TRAE_PROVIDER = "trae";
|
|
@@ -405,9 +579,172 @@ function toPiModel(info, baseUrl, providerId) {
|
|
|
405
579
|
compat: { supportsReasoningEffort: info.reasoningEfforts !== void 0 }
|
|
406
580
|
};
|
|
407
581
|
}
|
|
582
|
+
function isSystemMessage(message) {
|
|
583
|
+
return typeof message === "object" && message !== null && message.role === "system";
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* The text of one system message: its `content` (a string, as
|
|
587
|
+
* `normalizeContext` writes, or text blocks, which a provider may send back)
|
|
588
|
+
* followed by its named `sections` — exactly the join pi-ai's own
|
|
589
|
+
* `getSystemMessageText` performs.
|
|
590
|
+
*
|
|
591
|
+
* `sections` is not decoration: it is the modern transcript's second carrier of
|
|
592
|
+
* prompt text ("named, ordered prompt sections rendered verbatim after
|
|
593
|
+
* content"). Folding `content` alone would silently drop a prompt that arrives
|
|
594
|
+
* section-shaped, which is the same asymmetric-loss failure this adapter exists
|
|
595
|
+
* to prevent.
|
|
596
|
+
*/
|
|
597
|
+
function systemTextOf(message) {
|
|
598
|
+
const parts = [contentTextOf(message.content)];
|
|
599
|
+
const sections = message.sections;
|
|
600
|
+
if (typeof sections === "object" && sections !== null && !Array.isArray(sections)) {
|
|
601
|
+
for (const text of Object.values(sections)) if (typeof text === "string") parts.push(text);
|
|
602
|
+
}
|
|
603
|
+
return parts.filter((part) => part.length > 0).join("\n\n");
|
|
604
|
+
}
|
|
605
|
+
function contentTextOf(content) {
|
|
606
|
+
if (typeof content === "string") return content;
|
|
607
|
+
if (!Array.isArray(content)) return "";
|
|
608
|
+
const texts = [];
|
|
609
|
+
for (const block of content) {
|
|
610
|
+
if (typeof block !== "object" || block === null) continue;
|
|
611
|
+
const candidate = block;
|
|
612
|
+
if (candidate.type === "text" && typeof candidate.text === "string") texts.push(candidate.text);
|
|
613
|
+
}
|
|
614
|
+
return texts.join("\n\n");
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Replay one transcript system message's tool deltas into `state`, the same
|
|
618
|
+
* merge pi-ai's own `getCurrentTools` performs: removals first, then additions,
|
|
619
|
+
* keyed by name so a mid-conversation redeclaration replaces the original.
|
|
620
|
+
*/
|
|
621
|
+
function replayToolState(message, state) {
|
|
622
|
+
const removed = message.toolsRemoved;
|
|
623
|
+
if (Array.isArray(removed)) for (const tool of removed) {
|
|
624
|
+
const name = tool?.name;
|
|
625
|
+
if (typeof name === "string") state.delete(name);
|
|
626
|
+
}
|
|
627
|
+
const added = message.toolsAdded;
|
|
628
|
+
if (Array.isArray(added)) for (const tool of added) {
|
|
629
|
+
const name = tool?.name;
|
|
630
|
+
if (typeof name === "string") state.set(name, tool);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Fold a NORMALIZED transcript back into the `{ systemPrompt, tools, messages }`
|
|
635
|
+
* shape a LEGACY (≤0.85) api consumes — issue #27's crash and its two silent
|
|
636
|
+
* siblings.
|
|
637
|
+
*
|
|
638
|
+
* WHY (issue #27). 0.86+ normalizes at every public entry point, and DSH's host
|
|
639
|
+
* calls `Models.streamSimple()`, so on a modern host the context arriving here
|
|
640
|
+
* ALWAYS carries its prompt and its tool declarations on a leading system
|
|
641
|
+
* message — the top-level fields are gone. A legacy api has no branch for that
|
|
642
|
+
* message: `estimateMessageTokens` iterates the string `content` character by
|
|
643
|
+
* character and reads `.name` off a character, throwing
|
|
644
|
+
* `Cannot read properties of undefined (reading 'length')` before the request
|
|
645
|
+
* is even built. That is what surfaced as `PI_AI_ERROR` with all-zero usage.
|
|
646
|
+
*
|
|
647
|
+
* The fold must carry MORE than the prompt. `toolsAdded` is the ONLY remaining
|
|
648
|
+
* carrier of the tool declarations once `normalizeContext` has run, and a
|
|
649
|
+
* legacy api reads tools exclusively from `context.tools` — so lifting the text
|
|
650
|
+
* alone would trade a loud crash for a silent one: a model that can never emit
|
|
651
|
+
* a `toolCall` because the request declared no tools.
|
|
652
|
+
*
|
|
653
|
+
* Shape-guarded, not version-guarded: it asks "is there a system message inside
|
|
654
|
+
* `messages`?" rather than "which pi-ai is loaded?". A version test would be
|
|
655
|
+
* wrong the moment either side moves, and this predicate is an identity for the
|
|
656
|
+
* legacy shape, so the ordinary path stays byte-for-byte untouched. WHICH
|
|
657
|
+
* branch runs at all IS generation-gated — see {@link withContextAdaptation}.
|
|
658
|
+
*
|
|
659
|
+
* Returns the SAME object when there is nothing to fold, so a legacy host
|
|
660
|
+
* (which never normalizes) pays one `Array.some` and nothing else.
|
|
661
|
+
*/
|
|
662
|
+
function adaptLegacyPiAiContext(context) {
|
|
663
|
+
if (context === null || typeof context !== "object") return context;
|
|
664
|
+
const messages = Array.isArray(context.messages) ? context.messages : void 0;
|
|
665
|
+
if (messages === void 0) return context;
|
|
666
|
+
if (!messages.some(isSystemMessage)) return context;
|
|
667
|
+
const systemTexts = [];
|
|
668
|
+
const rest = [];
|
|
669
|
+
const toolState = /* @__PURE__ */ new Map();
|
|
670
|
+
for (const message of messages) {
|
|
671
|
+
if (!isSystemMessage(message)) {
|
|
672
|
+
rest.push(message);
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
const text = systemTextOf(message);
|
|
676
|
+
if (text !== "") systemTexts.push(text);
|
|
677
|
+
replayToolState(message, toolState);
|
|
678
|
+
}
|
|
679
|
+
const texts = [...context.systemPrompt === void 0 || context.systemPrompt === "" ? [] : [context.systemPrompt], ...systemTexts];
|
|
680
|
+
const systemPrompt = texts.length > 0 ? texts.join("\n\n") : void 0;
|
|
681
|
+
const promoted = Array.isArray(context.tools) ? void 0 : [...toolState.values()];
|
|
682
|
+
return {
|
|
683
|
+
...context,
|
|
684
|
+
...systemPrompt === void 0 ? {} : { systemPrompt },
|
|
685
|
+
...promoted === void 0 || promoted.length === 0 ? {} : { tools: promoted },
|
|
686
|
+
messages: rest
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Lift the legacy `{ systemPrompt, tools, messages }` shape into the normalized
|
|
691
|
+
* transcript a MODERN (0.86+) api consumes — the reverse mismatch, and just as
|
|
692
|
+
* silent.
|
|
693
|
+
*
|
|
694
|
+
* WHY. DSH spans both generations (`dsh-llm-pi-ai` ≤0.2.0-rc.1 pins `^0.85.1`)
|
|
695
|
+
* while this plugin may resolve a newer copy than its host. A LEGACY host never
|
|
696
|
+
* normalizes, so it hands the api the legacy shape; a modern api reads the
|
|
697
|
+
* prompt from a leading system message and the tools from its `toolsAdded`, and
|
|
698
|
+
* never looks at the top-level fields — so both are dropped without a sound.
|
|
699
|
+
* Verified against the published tarballs: the request that reaches the wire
|
|
700
|
+
* carries no system turn and `tools: []`.
|
|
701
|
+
*
|
|
702
|
+
* Identity for anything already in transcript shape, and for a context with
|
|
703
|
+
* neither a prompt nor tools (pi-ai's own `createInitialSystemMessage` returns
|
|
704
|
+
* `undefined` there, keeping an empty transcript empty).
|
|
705
|
+
*/
|
|
706
|
+
function adaptModernPiAiContext(context) {
|
|
707
|
+
if (context === null || typeof context !== "object") return context;
|
|
708
|
+
const messages = Array.isArray(context.messages) ? context.messages : void 0;
|
|
709
|
+
if (messages === void 0) return context;
|
|
710
|
+
const prompt = context.systemPrompt;
|
|
711
|
+
const tools = Array.isArray(context.tools) && context.tools.length > 0 ? context.tools : void 0;
|
|
712
|
+
if (!(typeof prompt === "string" && prompt.length > 0) && tools === void 0) return context;
|
|
713
|
+
const head = {
|
|
714
|
+
role: "system",
|
|
715
|
+
content: prompt ?? "",
|
|
716
|
+
...tools === void 0 ? {} : { toolsAdded: tools }
|
|
717
|
+
};
|
|
718
|
+
const { systemPrompt: _lifted, tools: _liftedTools, ...rest } = context;
|
|
719
|
+
return {
|
|
720
|
+
...rest,
|
|
721
|
+
messages: [head, ...messages]
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Wrap one pi-ai API module so BOTH stream entry points receive a context its
|
|
726
|
+
* own generation can consume.
|
|
727
|
+
*
|
|
728
|
+
* The gate is the copy THIS plugin resolved (see {@link piAiRuntimeInfo}) —
|
|
729
|
+
* what the host runs says nothing about it, because a nested copy on disk
|
|
730
|
+
* shadows host injection and vice versa. The wrapping is cheap and lazy: only
|
|
731
|
+
* `stream`/`streamSimple` are intercepted, the deferred-fetch entry points take
|
|
732
|
+
* no context and are passed through untouched, and each adapter function is an
|
|
733
|
+
* identity unless the context is actually the other generation's shape. So a
|
|
734
|
+
* healthy install does one `Array.some` per request.
|
|
735
|
+
*/
|
|
736
|
+
function withContextAdaptation(api, generation) {
|
|
737
|
+
const adapt = generation === "modern" ? adaptModernPiAiContext : adaptLegacyPiAiContext;
|
|
738
|
+
return {
|
|
739
|
+
...api,
|
|
740
|
+
stream: (model, context, options) => api.stream(model, adapt(context), options),
|
|
741
|
+
streamSimple: (model, context, options) => api.streamSimple(model, adapt(context), options)
|
|
742
|
+
};
|
|
743
|
+
}
|
|
408
744
|
function createTraeAdapter(options) {
|
|
409
745
|
const providerId = options.provider ?? "trae";
|
|
410
746
|
const providerName = options.displayName ?? "Trae";
|
|
747
|
+
const generation = options.piAiGeneration ?? piAiRuntimeInfo().generation;
|
|
411
748
|
const buildModels = () => options.catalog.current().map((info) => toPiModel(info, `${options.shim.baseUrl()}/v1`, providerId));
|
|
412
749
|
const provider = {
|
|
413
750
|
...createProvider({
|
|
@@ -424,7 +761,7 @@ function createTraeAdapter(options) {
|
|
|
424
761
|
}
|
|
425
762
|
} },
|
|
426
763
|
models: buildModels(),
|
|
427
|
-
api: openAICompletionsApi()
|
|
764
|
+
api: withContextAdaptation(openAICompletionsApi(), generation)
|
|
428
765
|
}),
|
|
429
766
|
getModels: () => buildModels()
|
|
430
767
|
};
|
|
@@ -5804,6 +6141,8 @@ function apply(ctx, config) {
|
|
|
5804
6141
|
Promise.all(REGION_KEYS.map((region) => stacks[region].shim.ready)).then(async () => {
|
|
5805
6142
|
if (stopped) return;
|
|
5806
6143
|
try {
|
|
6144
|
+
const piAi = piAiRuntimeInfo();
|
|
6145
|
+
ctx.logger.info(`dsh-connect-trae: pi-ai ${piAi.version ?? "unknown"} (${piAi.generation} generation)${piAi.inspected ? "" : " — not resolved locally, the host supplies it"}`);
|
|
5807
6146
|
for (const region of REGION_KEYS) {
|
|
5808
6147
|
const stack = stacks[region];
|
|
5809
6148
|
const trae = createTraeAdapter({
|
|
@@ -5877,4 +6216,4 @@ function apply(ctx, config) {
|
|
|
5877
6216
|
});
|
|
5878
6217
|
}
|
|
5879
6218
|
//#endregion
|
|
5880
|
-
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, 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_STATE_DB_FILENAME, 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, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, followAppLoginEnabled, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, 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, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|
|
6219
|
+
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, 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_STATE_DB_FILENAME, 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, adaptLegacyPiAiContext, adaptModernPiAiContext, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, decidePiAiGeneration, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, followAppLoginEnabled, generationOf, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, piAiRuntimeInfo, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, 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, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withContextAdaptation, withTraeRegion };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-trae",
|
|
3
3
|
"displayName": "DSH Connect Trae",
|
|
4
4
|
"description": "把本机登录的 Trae 模型接入 DeepSeek Harness:国内版与国际版双供应商并行,提供用量/积分概览与每日签到领取。",
|
|
5
|
-
"version": "3.4.
|
|
5
|
+
"version": "3.4.2",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|