dsh-connect-trae 2.0.1 → 2.0.3
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 +26 -0
- package/README.en.md +20 -0
- package/README.md +20 -0
- package/lib/index.d.ts +57 -7
- package/lib/index.js +137 -50
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.3 (2026-09-16)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
|
|
7
|
+
- **修复内置兜底目录里存在「不可调用模型」的问题**——插件在首次实时刷新落地前会先提供一份内置兜底模型列表,且这份列表**故意不经过「死 config_name」过滤**(过滤依赖实时目录,会把兜底本身误删)。因此它的 id 必须在源头就是正确的 `llm_utils_chat` config_name,而它此前不是:
|
|
8
|
+
- `auto` 不是任何 Trae 的 config_name,选中后会以 `4001` 失败——已移除。该条目会在「尚未刷新目录」时(未登录 / 网络抖动 / 卡片未刷新)被真正提供给用户,因此并非理论问题。
|
|
9
|
+
- `DeepSeek-V4-Flash` / `DeepSeek-V4-Pro` 缺少 `-Official` 后缀,不是真实 wire id——已更正为 `DeepSeek-V4-Flash-Official` / `DeepSeek-V4-Pro-Official`(依据 2026-09-15 实测目录,`docs/DS41_CALLABILITY.md`)。
|
|
10
|
+
- 新增回归测试锁定三条不变量:兜底 id 必须是真实 config_name(显式禁 `auto` 与裸名)、两个区域的兜底都不得出现 TraeCode 独占模型、`mergeTraeModelSources` 确实丢弃无 wire 匹配的目录行。
|
|
11
|
+
|
|
12
|
+
### Docs
|
|
13
|
+
|
|
14
|
+
- **README 新增「模型覆盖范围」一节(中英同步)**——说明插件提供的是 Trae SOLO 通道的模型,而 TraeCode 通道的 4 个模型(`deepseek-v4.1-flash`、`glm-5.3-flash`、`kimi-k2.8-preview`、`qwen3.8-flash`)尚未开放到 SOLO 通道,因此暂不支持,**需要等官方开放到 SOLO 后才能支持**;并区分了 `GLM-5.3`(支持)与 `glm-5.3-flash`(不支持)。
|
|
15
|
+
- 新增 TraeCode / agent-task 通道与 TraeCLI 的可行性取证(`docs/CODEC_CHANNEL_FEASIBILITY.md`、`docs/TRAECLI_FEASIBILITY.md`、`docs/TRAE_ECOSYSTEM_RESEARCH.md`):四条路径逐一实测均不通,且阻塞原因互相独立(协议 / 配额 / 同机 / 登录态与 agent 归属),据此确认这批模型属上游授权边界而非插件可绕过的技术问题。
|
|
16
|
+
|
|
17
|
+
## 2.0.2 (2026-09-15)
|
|
18
|
+
|
|
19
|
+
### Fixes
|
|
20
|
+
|
|
21
|
+
- **修复「Trae 目录里有、插件列表里没有」的整类问题**(issue #7:GLM-5.3 缺失)——根因是**模型调用的 `function` 选错了**,不是目录读取有误:
|
|
22
|
+
- Trae 把可调用目录**分散在多个 SOLO 模式 function 下**,且一个模型**只能通过列出它的那个 function 调用**。插件此前固定用 `solo_work_lite` 取目录与发请求,而 `glm-5.3` 只存在于 **`solo_work_remote`**:用前者调用会被上游拒绝(`4001 param is invalid`),用后者则正常流式返回(2026-09-15 受控实测,同一份请求体,仅 function 不同)。
|
|
23
|
+
- **目录发现改为多 function 并集**:CN 依序请求 `solo_work_remote` → `solo_work_lite`,国际请求 `solo_agent` → `solo_work_remote` → `solo_work_lite`;同一个 config 由**先列出的 function 拥有**(顺序即优先级)。某个 function 失败不再影响其它 function 的目录。
|
|
24
|
+
- **聊天请求按模型回放其来源 function**:每个模型随目录记下 `wireFunction`(并纳入设置 schema 持久化,重启后仍生效),调用时写回请求体——`glm-5.3` 因而会走 `solo_work_remote`。
|
|
25
|
+
- 顺带确认:CN 的两个登录(Trae CN IDE 与 TRAE SOLO CN)在同一 function 下拿到**完全相同**的列表,因此该修复对两者一致生效;国际版覆盖最全的仍是 `solo_agent`。
|
|
26
|
+
- 新增 4 个回归测试(多 function 并集、来源 function 标记、优先级归属、显式 function 覆盖默认值),230/230 全绿。
|
|
27
|
+
- **目录拉取失败时重试一次**——Trae 的目录端点存在间歇性 401/限流(实测同一凭证数秒前 200、随后 401)。启动时若恰好撞上,live 目录会拉取失败并**静默回退到上次保存的快照**,从而丢掉自那次保存以来的所有新模型(本版要放出的 glm-5.3 就在其中,实测复现)。现在合并骨架(remote 目录)失败会先重试一次(800ms 间隔)再降级。
|
|
28
|
+
|
|
3
29
|
## 2.0.1 (2026-09-15)
|
|
4
30
|
|
|
5
31
|
### Fixes
|
package/README.en.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<a href="README.md">中文</a> ·
|
|
11
11
|
<a href="#install">Install</a> ·
|
|
12
12
|
<a href="#how-it-works">How it works</a> ·
|
|
13
|
+
<a href="#model-coverage">Model coverage</a> ·
|
|
13
14
|
<a href="CHANGELOG.md">Changelog</a> ·
|
|
14
15
|
<a href="https://github.com/dingminhua/dsh-connect-trae/issues">Issues</a>
|
|
15
16
|
</p>
|
|
@@ -56,6 +57,25 @@ Usage overview hits the read-only `https://api.trae.cn/trae/api/v2/pay/*` and `/
|
|
|
56
57
|
|
|
57
58
|
> See `docs/IMPLEMENTATION_PLAN.md`, `docs/SOLO_ROUTE_DECISION.md`, `docs/USAGE_API_RESEARCH.md`.
|
|
58
59
|
|
|
60
|
+
## Model coverage
|
|
61
|
+
|
|
62
|
+
This plugin serves models from Trae's **SOLO channel** (`DeepSeek-V4-Flash-Official`, `DeepSeek-V4-Pro-Official`, `GLM-5.3`, `GLM-5.2`, `Kimi-K3`, `MiniMax-M3`, `Qwen3.8-Max`, `Doubao-Seed-*`, …), for both the domestic and international sides.
|
|
63
|
+
|
|
64
|
+
The following four models currently come from the **TraeCode (Trae IDE) channel** and have **not** been opened up to the SOLO channel, so this plugin does **not** support them yet:
|
|
65
|
+
|
|
66
|
+
| Model |
|
|
67
|
+
| --- |
|
|
68
|
+
| `deepseek-v4.1-flash` |
|
|
69
|
+
| `glm-5.3-flash` |
|
|
70
|
+
| `kimi-k2.8-preview` |
|
|
71
|
+
| `qwen3.8-flash` |
|
|
72
|
+
|
|
73
|
+
These four are reachable only inside the Trae IDE client; there is no callable channel for them on the plugin side. **They can only be supported once Trae officially opens them up to the SOLO channel.**
|
|
74
|
+
|
|
75
|
+
> Note the distinction: **`GLM-5.3` is supported** (it goes through the SOLO channel), but **`glm-5.3-flash` is not** — they are different models.
|
|
76
|
+
|
|
77
|
+
To use these four models today, use the **Trae IDE itself**.
|
|
78
|
+
|
|
59
79
|
## Install
|
|
60
80
|
|
|
61
81
|
```sh
|
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<a href="README.en.md">English</a> ·
|
|
11
11
|
<a href="#安装">安装</a> ·
|
|
12
12
|
<a href="#工作原理">工作原理</a> ·
|
|
13
|
+
<a href="#模型覆盖范围">模型覆盖范围</a> ·
|
|
13
14
|
<a href="CHANGELOG.md">更新日志</a> ·
|
|
14
15
|
<a href="https://github.com/dingminhua/dsh-connect-trae/issues">问题反馈</a>
|
|
15
16
|
</p>
|
|
@@ -56,6 +57,25 @@ DSH PiAiAdapter(每个 provider 一套)
|
|
|
56
57
|
|
|
57
58
|
> 详见 `docs/IMPLEMENTATION_PLAN.md`、`docs/SOLO_ROUTE_DECISION.md`、`docs/USAGE_API_RESEARCH.md`。
|
|
58
59
|
|
|
60
|
+
## 模型覆盖范围
|
|
61
|
+
|
|
62
|
+
插件提供的是 Trae **SOLO 通道**的模型(`DeepSeek-V4-Flash-Official`、`DeepSeek-V4-Pro-Official`、`GLM-5.3`、`GLM-5.2`、`Kimi-K3`、`MiniMax-M3`、`Qwen3.8-Max`、`Doubao-Seed-*` 等),国内版与国际版均走这条通道。
|
|
63
|
+
|
|
64
|
+
以下 4 个模型目前来自 **TraeCode(Trae IDE)通道**,尚未开放到 SOLO 通道,因此本插件**暂不支持**:
|
|
65
|
+
|
|
66
|
+
| 模型 |
|
|
67
|
+
| --- |
|
|
68
|
+
| `deepseek-v4.1-flash` |
|
|
69
|
+
| `glm-5.3-flash` |
|
|
70
|
+
| `kimi-k2.8-preview` |
|
|
71
|
+
| `qwen3.8-flash` |
|
|
72
|
+
|
|
73
|
+
这 4 个模型只在 Trae IDE 客户端内可用,插件侧没有可调用的通道。**需要等官方把它们开放到 SOLO 通道后,插件才能支持。**
|
|
74
|
+
|
|
75
|
+
> 注意区分:**`GLM-5.3` 是支持的**(走 SOLO 通道),但 **`glm-5.3-flash` 不支持**——两者是不同的模型。
|
|
76
|
+
|
|
77
|
+
如果你现在就要用这 4 个模型,请直接在 **Trae IDE 本体**中使用。
|
|
78
|
+
|
|
59
79
|
## 安装
|
|
60
80
|
|
|
61
81
|
推荐使用 DSH 插件命令安装 npm 已发布版本:
|
package/lib/index.d.ts
CHANGED
|
@@ -109,8 +109,24 @@ interface TraeModelInfo {
|
|
|
109
109
|
maxContextWindow?: number;
|
|
110
110
|
/** The `config_name` `llm_utils_chat` accepts; absent means `id` is already the wire id. */
|
|
111
111
|
wireConfigName?: string;
|
|
112
|
+
/** The directory function this model must be called through (see {@link TraeWireModel.function}). */
|
|
113
|
+
wireFunction?: string;
|
|
112
114
|
}
|
|
113
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Bootstrap catalog: identity only where current Trae metadata has not been
|
|
117
|
+
* fetched yet.
|
|
118
|
+
*
|
|
119
|
+
* Every id here must be a `config_name` that `llm_utils_chat` actually accepts,
|
|
120
|
+
* because this list is served verbatim before the first live refresh lands and
|
|
121
|
+
* it is NOT filtered against the wire map (the wire map can only confirm ids it
|
|
122
|
+
* happens to know, so filtering would delete the safety net exactly when it is
|
|
123
|
+
* needed — see `fallbackModels` in index.ts). The current CN roster was
|
|
124
|
+
* verified against the live remote directory on 2026-09-15
|
|
125
|
+
* (docs/DS41_CALLABILITY.md): `DeepSeek-V4-Flash-Official` and
|
|
126
|
+
* `DeepSeek-V4-Pro-Official` carry the `-Official` suffix, and there is no
|
|
127
|
+
* `auto` config_name — an `auto` row was previously served here and would have
|
|
128
|
+
* failed on selection.
|
|
129
|
+
*/
|
|
114
130
|
declare const FALLBACK_TRAE_MODELS: readonly TraeModelInfo[];
|
|
115
131
|
/**
|
|
116
132
|
* Bootstrap catalog for the international (ai) region, captured from the
|
|
@@ -147,6 +163,14 @@ interface TraeWireModel {
|
|
|
147
163
|
contextWindow?: number;
|
|
148
164
|
maxTokens?: number;
|
|
149
165
|
reasoning?: TraeReasoningCapability;
|
|
166
|
+
/**
|
|
167
|
+
* The `get_detail_param` function this config_name came from. Trae splits its
|
|
168
|
+
* callable roster across several functions (SOLO modes), and a model is only
|
|
169
|
+
* callable through the function that actually lists it — glm-5.3 answers
|
|
170
|
+
* `solo_work_remote` but rejects `solo_work_lite` with 4001. The chat call
|
|
171
|
+
* therefore has to replay the directory's own function.
|
|
172
|
+
*/
|
|
173
|
+
function?: string;
|
|
150
174
|
}
|
|
151
175
|
/**
|
|
152
176
|
* Merge the two Trae model sources into one authoritative catalog.
|
|
@@ -164,8 +188,8 @@ interface TraeWireModel {
|
|
|
164
188
|
*
|
|
165
189
|
* Joining is two-tier, in priority order:
|
|
166
190
|
* 1. `wire.id` (the `config_name`) equals the remote id — the model's display
|
|
167
|
-
* id is already its wire id (the common case: glm-5.2,
|
|
168
|
-
* kimi-k3, …).
|
|
191
|
+
* id is already its wire id (the common case: glm-5.2,
|
|
192
|
+
* DeepSeek-V4-Flash-Official, kimi-k3, …).
|
|
169
193
|
* 2. `wire.name` (the `display_name`) equals the remote display name — for
|
|
170
194
|
* models whose display id differs from the wire id across Trae versions.
|
|
171
195
|
* When the matched wire `config_name` differs from the remote id it is recorded
|
|
@@ -996,13 +1020,15 @@ declare function decodeTraeEvent(event: SseEvent): TraeStreamEvent;
|
|
|
996
1020
|
declare const TRAE_SOLO_FUNCTION = "solo_work_lite";
|
|
997
1021
|
declare const TRAE_SOLO_CHAT_PATH = "/api/agent/v3/llm_utils_chat";
|
|
998
1022
|
declare const TRAE_SOLO_MODELS_PATH = "/api/ide/v1/get_detail_param";
|
|
999
|
-
declare function prepareSoloBody(source: string, defaultModel?: string): string;
|
|
1023
|
+
declare function prepareSoloBody(source: string, defaultModel?: string, functionName?: string): string;
|
|
1000
1024
|
interface TraeSoloModel {
|
|
1001
1025
|
id: string;
|
|
1002
1026
|
name: string;
|
|
1003
1027
|
contextWindow?: number;
|
|
1004
1028
|
maxTokens?: number;
|
|
1005
1029
|
reasoning?: TraeReasoningCapability;
|
|
1030
|
+
/** The directory function that listed this config (replayed when calling it). */
|
|
1031
|
+
function?: string;
|
|
1006
1032
|
}
|
|
1007
1033
|
interface TraeSoloClientOptions {
|
|
1008
1034
|
credential(): Promise<TraeCredential>;
|
|
@@ -1015,15 +1041,39 @@ declare class TraeSoloUpstreamClient {
|
|
|
1015
1041
|
private readonly options;
|
|
1016
1042
|
private readonly fetchImpl;
|
|
1017
1043
|
constructor(options: TraeSoloClientOptions);
|
|
1044
|
+
/**
|
|
1045
|
+
* Read the callable roster for this credential's region.
|
|
1046
|
+
*
|
|
1047
|
+
* Every function in {@link TRAE_DIRECTORY_FUNCTIONS} is asked, in order, and
|
|
1048
|
+
* their answers are unioned: the first function to list a `config_name` owns
|
|
1049
|
+
* it. Trae splits its roster across SOLO modes, and a model is only callable
|
|
1050
|
+
* through the function that lists it (glm-5.3 exists solely under
|
|
1051
|
+
* `solo_work_remote` on the CN gateway). Asking one function therefore
|
|
1052
|
+
* silently hides models that the other one serves. The remote directory
|
|
1053
|
+
* remains the merge skeleton, so agent-internal entries (search_agent_*,
|
|
1054
|
+
* paygo variants) never surface even though they appear here.
|
|
1055
|
+
*/
|
|
1018
1056
|
fetchModels(signal?: AbortSignal): Promise<TraeSoloModel[]>;
|
|
1019
|
-
|
|
1057
|
+
/** Merge one function's config list into the shared catalogue (first wins). */
|
|
1058
|
+
private collectModels;
|
|
1059
|
+
chatStream(bodyJson: string, signal?: AbortSignal, functionName?: string): Promise<TraeChatResult>;
|
|
1020
1060
|
}
|
|
1021
1061
|
//#endregion
|
|
1022
1062
|
//#region src/solo-bridge.d.ts
|
|
1023
1063
|
/** Convert Trae's named SSE events into OpenAI chat-completion SSE chunks. */
|
|
1024
1064
|
declare function bridgeTraeSoloStream(response: Response, model: string): Response;
|
|
1025
|
-
/**
|
|
1026
|
-
|
|
1065
|
+
/**
|
|
1066
|
+
* One resolved wire target: the `config_name` `llm_utils_chat` accepts, plus
|
|
1067
|
+
* the directory function that listed it. Trae's roster is split across several
|
|
1068
|
+
* SOLO-mode functions and a model is only callable through the one listing it
|
|
1069
|
+
* (glm-5.3 answers only under `solo_work_remote`), so the chat call replays it.
|
|
1070
|
+
*/
|
|
1071
|
+
interface TraeWireTarget {
|
|
1072
|
+
configName: string;
|
|
1073
|
+
function?: string;
|
|
1074
|
+
}
|
|
1075
|
+
/** Resolves a display model id to its wire target (config_name + function). */
|
|
1076
|
+
type TraeWireResolver = (displayId: string) => TraeWireTarget | undefined;
|
|
1027
1077
|
/** Native SOLO client wrapper used by the loopback OpenAI adapter. */
|
|
1028
1078
|
declare class TraeSoloBridge implements TraeUpstreamClient {
|
|
1029
1079
|
private readonly upstream;
|
package/lib/index.js
CHANGED
|
@@ -15,18 +15,28 @@ import { Readable } from "node:stream";
|
|
|
15
15
|
import { execFile } from "node:child_process";
|
|
16
16
|
import { promisify } from "node:util";
|
|
17
17
|
//#region src/catalog.ts
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Bootstrap catalog: identity only where current Trae metadata has not been
|
|
20
|
+
* fetched yet.
|
|
21
|
+
*
|
|
22
|
+
* Every id here must be a `config_name` that `llm_utils_chat` actually accepts,
|
|
23
|
+
* because this list is served verbatim before the first live refresh lands and
|
|
24
|
+
* it is NOT filtered against the wire map (the wire map can only confirm ids it
|
|
25
|
+
* happens to know, so filtering would delete the safety net exactly when it is
|
|
26
|
+
* needed — see `fallbackModels` in index.ts). The current CN roster was
|
|
27
|
+
* verified against the live remote directory on 2026-09-15
|
|
28
|
+
* (docs/DS41_CALLABILITY.md): `DeepSeek-V4-Flash-Official` and
|
|
29
|
+
* `DeepSeek-V4-Pro-Official` carry the `-Official` suffix, and there is no
|
|
30
|
+
* `auto` config_name — an `auto` row was previously served here and would have
|
|
31
|
+
* failed on selection.
|
|
32
|
+
*/
|
|
19
33
|
const FALLBACK_TRAE_MODELS = [
|
|
20
34
|
{
|
|
21
|
-
id: "
|
|
22
|
-
name: "Auto"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: "DeepSeek-V4-Flash",
|
|
35
|
+
id: "DeepSeek-V4-Flash-Official",
|
|
26
36
|
name: "DeepSeek-V4-Flash"
|
|
27
37
|
},
|
|
28
38
|
{
|
|
29
|
-
id: "DeepSeek-V4-Pro",
|
|
39
|
+
id: "DeepSeek-V4-Pro-Official",
|
|
30
40
|
name: "DeepSeek-V4-Pro"
|
|
31
41
|
},
|
|
32
42
|
{
|
|
@@ -126,8 +136,8 @@ function displayKey(name) {
|
|
|
126
136
|
*
|
|
127
137
|
* Joining is two-tier, in priority order:
|
|
128
138
|
* 1. `wire.id` (the `config_name`) equals the remote id — the model's display
|
|
129
|
-
* id is already its wire id (the common case: glm-5.2,
|
|
130
|
-
* kimi-k3, …).
|
|
139
|
+
* id is already its wire id (the common case: glm-5.2,
|
|
140
|
+
* DeepSeek-V4-Flash-Official, kimi-k3, …).
|
|
131
141
|
* 2. `wire.name` (the `display_name`) equals the remote display name — for
|
|
132
142
|
* models whose display id differs from the wire id across Trae versions.
|
|
133
143
|
* When the matched wire `config_name` differs from the remote id it is recorded
|
|
@@ -156,7 +166,8 @@ function mergeTraeModelSources(remote, wire) {
|
|
|
156
166
|
reasoning: model.reasoning,
|
|
157
167
|
reasoningEfforts: Object.fromEntries(model.reasoning.supported.map((effort) => [effort, effort === "low" ? "light" : effort === "xhigh" ? "extra_high" : "high"]))
|
|
158
168
|
},
|
|
159
|
-
...wireModel.id !== "" && wireModel.id !== model.id ? { wireConfigName: wireModel.id } : {}
|
|
169
|
+
...wireModel.id !== "" && wireModel.id !== model.id ? { wireConfigName: wireModel.id } : {},
|
|
170
|
+
...wireModel.function === void 0 ? {} : { wireFunction: wireModel.function }
|
|
160
171
|
});
|
|
161
172
|
}
|
|
162
173
|
return result;
|
|
@@ -1818,6 +1829,25 @@ function applyReasoningEffort(body, effort, capability) {
|
|
|
1818
1829
|
//#endregion
|
|
1819
1830
|
//#region src/solo.ts
|
|
1820
1831
|
const TRAE_SOLO_FUNCTION = "solo_work_lite";
|
|
1832
|
+
/**
|
|
1833
|
+
* Directory functions to union per region, in priority order.
|
|
1834
|
+
*
|
|
1835
|
+
* Trae spreads its callable roster across several SOLO-mode functions, and a
|
|
1836
|
+
* model is only usable through the one that lists it: `glm-5.3` is absent from
|
|
1837
|
+
* `solo_work_lite` but present in `solo_work_remote` (verified 2026-09-15 —
|
|
1838
|
+
* calling it through the former answers `4001 param is invalid`, through the
|
|
1839
|
+
* latter streams normally). Rather than betting on a single function, the
|
|
1840
|
+
* directory unions them; the first function to provide a config wins, so the
|
|
1841
|
+
* order below decides which wire name a model is called with.
|
|
1842
|
+
*/
|
|
1843
|
+
const TRAE_DIRECTORY_FUNCTIONS = {
|
|
1844
|
+
cn: ["solo_work_remote", TRAE_SOLO_FUNCTION],
|
|
1845
|
+
ai: [
|
|
1846
|
+
"solo_agent",
|
|
1847
|
+
"solo_work_remote",
|
|
1848
|
+
TRAE_SOLO_FUNCTION
|
|
1849
|
+
]
|
|
1850
|
+
};
|
|
1821
1851
|
const TRAE_SOLO_CHAT_PATH = "/api/agent/v3/llm_utils_chat";
|
|
1822
1852
|
const TRAE_SOLO_MODELS_PATH = "/api/ide/v1/get_detail_param";
|
|
1823
1853
|
function classify(status) {
|
|
@@ -1831,14 +1861,14 @@ function classify(status) {
|
|
|
1831
1861
|
function finitePositive$1(value) {
|
|
1832
1862
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
1833
1863
|
}
|
|
1834
|
-
function prepareSoloBody(source, defaultModel = "glm-5.2") {
|
|
1864
|
+
function prepareSoloBody(source, defaultModel = "glm-5.2", functionName) {
|
|
1835
1865
|
const input = JSON.parse(source);
|
|
1836
1866
|
const model = typeof input["model"] === "string" && input["model"].trim() !== "" ? input["model"].trim() : defaultModel;
|
|
1837
1867
|
const body = {
|
|
1838
1868
|
...Array.isArray(input["messages"]) ? { messages: input["messages"] } : {},
|
|
1839
1869
|
model,
|
|
1840
1870
|
config_name: model,
|
|
1841
|
-
function:
|
|
1871
|
+
function: typeof input["function"] === "string" && input["function"] !== "" ? input["function"] : functionName ?? "solo_work_lite",
|
|
1842
1872
|
stream: true,
|
|
1843
1873
|
...Array.isArray(input["tools"]) ? { tools: input["tools"] } : {},
|
|
1844
1874
|
...typeof input["reasoning_effort"] === "string" ? { reasoning_effort: input["reasoning_effort"] } : {}
|
|
@@ -1880,32 +1910,60 @@ var TraeSoloUpstreamClient = class {
|
|
|
1880
1910
|
this.options = options;
|
|
1881
1911
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
1882
1912
|
}
|
|
1913
|
+
/**
|
|
1914
|
+
* Read the callable roster for this credential's region.
|
|
1915
|
+
*
|
|
1916
|
+
* Every function in {@link TRAE_DIRECTORY_FUNCTIONS} is asked, in order, and
|
|
1917
|
+
* their answers are unioned: the first function to list a `config_name` owns
|
|
1918
|
+
* it. Trae splits its roster across SOLO modes, and a model is only callable
|
|
1919
|
+
* through the function that lists it (glm-5.3 exists solely under
|
|
1920
|
+
* `solo_work_remote` on the CN gateway). Asking one function therefore
|
|
1921
|
+
* silently hides models that the other one serves. The remote directory
|
|
1922
|
+
* remains the merge skeleton, so agent-internal entries (search_agent_*,
|
|
1923
|
+
* paygo variants) never surface even though they appear here.
|
|
1924
|
+
*/
|
|
1883
1925
|
async fetchModels(signal) {
|
|
1884
1926
|
const [credential, identity] = await Promise.all([this.options.credential(), this.options.identity()]);
|
|
1885
1927
|
const region = regionOfCredential(credential);
|
|
1886
1928
|
const base = this.options.baseUrl ?? REGION_GATEWAYS[region].chat;
|
|
1887
|
-
const
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1929
|
+
const headers = {
|
|
1930
|
+
...buildTraeCnHeaders(credential, identity),
|
|
1931
|
+
Accept: "application/json"
|
|
1932
|
+
};
|
|
1933
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1934
|
+
const failures = [];
|
|
1935
|
+
for (const directoryFunction of TRAE_DIRECTORY_FUNCTIONS[region]) {
|
|
1936
|
+
let list;
|
|
1937
|
+
try {
|
|
1938
|
+
const response = await this.fetchImpl(traeEndpoint(base, TRAE_SOLO_MODELS_PATH), {
|
|
1939
|
+
method: "POST",
|
|
1940
|
+
headers,
|
|
1941
|
+
body: JSON.stringify({
|
|
1942
|
+
function: directoryFunction,
|
|
1943
|
+
config_names: null,
|
|
1944
|
+
need_prompt: false,
|
|
1945
|
+
current_config_info: null,
|
|
1946
|
+
poly_prompt: true,
|
|
1947
|
+
mode_type: null,
|
|
1948
|
+
agent_type: null
|
|
1949
|
+
}),
|
|
1950
|
+
signal: signal ?? AbortSignal.timeout(3e4)
|
|
1951
|
+
});
|
|
1952
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1953
|
+
const document = await response.json();
|
|
1954
|
+
list = Array.isArray(document["config_info_list"]) ? document["config_info_list"] : [];
|
|
1955
|
+
} catch (error) {
|
|
1956
|
+
failures.push(`${directoryFunction}: ${String(error).slice(0, 80)}`);
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1959
|
+
this.collectModels(list, directoryFunction, byId);
|
|
1960
|
+
}
|
|
1961
|
+
const models = [...byId.values()];
|
|
1962
|
+
if (models.length === 0) throw new Error(`Trae SOLO models response contained no models (${failures.join("; ") || "empty directory"})`);
|
|
1963
|
+
return models;
|
|
1964
|
+
}
|
|
1965
|
+
/** Merge one function's config list into the shared catalogue (first wins). */
|
|
1966
|
+
collectModels(list, directoryFunction, byId) {
|
|
1909
1967
|
for (const raw of list) {
|
|
1910
1968
|
if (typeof raw !== "object" || raw === null) continue;
|
|
1911
1969
|
const config = raw;
|
|
@@ -1923,21 +1981,21 @@ var TraeSoloUpstreamClient = class {
|
|
|
1923
1981
|
...config,
|
|
1924
1982
|
...detail
|
|
1925
1983
|
});
|
|
1926
|
-
|
|
1984
|
+
if (byId.has(id)) continue;
|
|
1985
|
+
byId.set(id, {
|
|
1927
1986
|
id,
|
|
1928
1987
|
name: typeof display["display_name"] === "string" && display["display_name"] !== "" ? display["display_name"] : id,
|
|
1929
1988
|
...contextWindow === void 0 ? {} : { contextWindow },
|
|
1930
1989
|
...maxTokens === void 0 ? {} : { maxTokens },
|
|
1931
|
-
...reasoning === void 0 ? {} : { reasoning }
|
|
1990
|
+
...reasoning === void 0 ? {} : { reasoning },
|
|
1991
|
+
function: directoryFunction
|
|
1932
1992
|
});
|
|
1933
1993
|
}
|
|
1934
|
-
if (models.length === 0) throw new Error("Trae SOLO models response contained no models");
|
|
1935
|
-
return models;
|
|
1936
1994
|
}
|
|
1937
|
-
async chatStream(bodyJson, signal) {
|
|
1995
|
+
async chatStream(bodyJson, signal, functionName) {
|
|
1938
1996
|
let prepared;
|
|
1939
1997
|
try {
|
|
1940
|
-
prepared = prepareSoloBody(bodyJson);
|
|
1998
|
+
prepared = prepareSoloBody(bodyJson, void 0, functionName);
|
|
1941
1999
|
} catch {
|
|
1942
2000
|
return {
|
|
1943
2001
|
ok: false,
|
|
@@ -2230,11 +2288,17 @@ var TraeSoloBridge = class {
|
|
|
2230
2288
|
const input = JSON.parse(bodyJson);
|
|
2231
2289
|
if (typeof input["model"] === "string" && input["model"] !== "") model = input["model"];
|
|
2232
2290
|
const entry = this.catalog?.current().find((item) => item.id === model);
|
|
2233
|
-
const
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2291
|
+
const fromCatalog = entry?.wireConfigName === void 0 ? void 0 : {
|
|
2292
|
+
configName: entry.wireConfigName,
|
|
2293
|
+
...entry.wireFunction === void 0 ? {} : { function: entry.wireFunction }
|
|
2294
|
+
};
|
|
2295
|
+
const fromResolver = this.wireResolver?.(model) ?? this.wireResolver?.(entry?.name ?? "");
|
|
2296
|
+
const target = fromCatalog ?? fromResolver;
|
|
2297
|
+
const wireModel = target?.configName ?? model;
|
|
2298
|
+
const wireFunction = target?.function ?? entry?.wireFunction;
|
|
2299
|
+
if (wireModel !== input["model"]) input["model"] = wireModel;
|
|
2300
|
+
if (wireFunction !== void 0 && input["function"] !== wireFunction) input["function"] = wireFunction;
|
|
2301
|
+
if (wireModel !== JSON.parse(bodyJson)["model"] || wireFunction !== void 0) prepared = JSON.stringify(input);
|
|
2238
2302
|
if (typeof input["reasoning_effort"] === "string") {
|
|
2239
2303
|
const efforts = entry?.reasoningEfforts;
|
|
2240
2304
|
const requested = input["reasoning_effort"];
|
|
@@ -3467,7 +3531,8 @@ const modelConfig = z.object({
|
|
|
3467
3531
|
contextWindow: z.number().step(1).min(1),
|
|
3468
3532
|
maxTokens: z.number().step(1).min(1),
|
|
3469
3533
|
input: z.array(z.union(["text", "image"])),
|
|
3470
|
-
creditMultiplier: z.number()
|
|
3534
|
+
creditMultiplier: z.number(),
|
|
3535
|
+
wireFunction: z.string()
|
|
3471
3536
|
});
|
|
3472
3537
|
const regionStateConfig = z.object({
|
|
3473
3538
|
lastCatalog: z.array(modelConfig).default([]),
|
|
@@ -3553,6 +3618,25 @@ function apply(ctx, config) {
|
|
|
3553
3618
|
if (explicit !== void 0) return explicit;
|
|
3554
3619
|
return legacyAccountRegion === region ? value.accountId : void 0;
|
|
3555
3620
|
};
|
|
3621
|
+
/**
|
|
3622
|
+
* Retry a directory read once. The gateways answer an intermittent 401 for a
|
|
3623
|
+
* credential that works moments later (observed 2026-09-15), and a failed
|
|
3624
|
+
* skeleton read would otherwise drop the runtime catalog back to the saved
|
|
3625
|
+
* snapshot — hiding every model added since that save (issue #7's glm-5.3
|
|
3626
|
+
* among them).
|
|
3627
|
+
*/
|
|
3628
|
+
async function withDirectoryRetry(read) {
|
|
3629
|
+
try {
|
|
3630
|
+
return await read();
|
|
3631
|
+
} catch (first) {
|
|
3632
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
3633
|
+
try {
|
|
3634
|
+
return await read();
|
|
3635
|
+
} catch {
|
|
3636
|
+
throw first;
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3556
3640
|
const stacks = {};
|
|
3557
3641
|
for (const region of REGION_KEYS) {
|
|
3558
3642
|
const catalog = new TraeCatalog(region);
|
|
@@ -3654,8 +3738,7 @@ function apply(ctx, config) {
|
|
|
3654
3738
|
delegating,
|
|
3655
3739
|
usageClient: new TraeUsageClient({ credential: () => store.resolve() }),
|
|
3656
3740
|
discoverModels: async (signal) => {
|
|
3657
|
-
const
|
|
3658
|
-
const merged = mergeTraeModelSources(remote, wireModels);
|
|
3741
|
+
const merged = mergeTraeModelSources(await withDirectoryRetry(() => remoteCatalog.fetchModels(signal)), await solo.fetchModels(signal));
|
|
3659
3742
|
wire.callableKeys.clear();
|
|
3660
3743
|
for (const model of merged) {
|
|
3661
3744
|
wire.callableKeys.add(model.id.trim().toLowerCase());
|
|
@@ -3665,8 +3748,12 @@ function apply(ctx, config) {
|
|
|
3665
3748
|
wire.byId.clear();
|
|
3666
3749
|
wire.byName.clear();
|
|
3667
3750
|
for (const model of merged) if (model.wireConfigName !== void 0) {
|
|
3668
|
-
|
|
3669
|
-
|
|
3751
|
+
const target = {
|
|
3752
|
+
configName: model.wireConfigName,
|
|
3753
|
+
...model.wireFunction === void 0 ? {} : { function: model.wireFunction }
|
|
3754
|
+
};
|
|
3755
|
+
wire.byId.set(model.id, target);
|
|
3756
|
+
wire.byName.set(model.name.trim().toLowerCase(), target);
|
|
3670
3757
|
}
|
|
3671
3758
|
return merged;
|
|
3672
3759
|
},
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-trae",
|
|
3
3
|
"displayName": "DSH Connect Trae",
|
|
4
4
|
"description": "Connect locally signed-in Trae models to DeepSeek Harness, plus a read-only usage/credits summary.",
|
|
5
|
-
"version": "2.0.
|
|
5
|
+
"version": "2.0.3",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|