dsh-tap 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/AGENTS.md +99 -0
  2. package/CHANGELOG.md +751 -0
  3. package/LICENSE +21 -0
  4. package/README.en.md +59 -0
  5. package/README.md +256 -0
  6. package/cordis.patch.yml +385 -0
  7. package/core/bridge.js +698 -0
  8. package/core/json-store.js +91 -0
  9. package/core/rotation.js +108 -0
  10. package/core/usage-meter.js +176 -0
  11. package/docs/diagnosis-cache-quota.md +181 -0
  12. package/docs/diagnosis-qoder-flash.md +67 -0
  13. package/docs/diagnosis-trae-3003.md +302 -0
  14. package/docs/goals/bridge-port-host-split.md +91 -0
  15. package/docs/goals/desktop-adaptation.md +106 -0
  16. package/docs/goals/qoder-cn-provider-design.md +308 -0
  17. package/docs/goals/settings-card-ux-redesign-plan.md +1680 -0
  18. package/docs/goals/settings-card-ux-redesign.md +162 -0
  19. package/docs/goals/trae-agent-v3.md +41 -0
  20. package/docs/goals/trae-work-cn-repair.md +44 -0
  21. package/docs/goals/v0.8-/351/242/235/345/272/246/345/217/257/350/247/201-/346/250/241/345/236/213/345/212/250/346/200/201/345/214/226-/345/244/232/346/234/215/345/212/241/345/225/206.md +85 -0
  22. package/docs/pitfalls.md +105 -0
  23. package/docs/reverse/trae-cloud-api.md +218 -0
  24. package/docs/reverse/trae-model-catalog.md +129 -0
  25. package/docs/reverse/traework-cn.md +520 -0
  26. package/docs/rules/STATE.md +198 -0
  27. package/docs/rules/content-moderation.md +54 -0
  28. package/docs/rules/dev-role-boundary.md +94 -0
  29. package/docs/rules/extra-providers.md +42 -0
  30. package/docs/rules/gateway-facts.md +91 -0
  31. package/docs/rules/oauth-handshake.md +76 -0
  32. package/docs/rules/prompt-cache.md +93 -0
  33. package/docs/rules/quota-signals.md +125 -0
  34. package/docs/rules/routing.md +84 -0
  35. package/docs/rules/templates/oauth-reverse-checklist.md +42 -0
  36. package/docs/rules/trae-surface.md +242 -0
  37. package/docs/rules/ua-validation.md +81 -0
  38. package/host-config.js +282 -0
  39. package/index.js +2306 -0
  40. package/lib/client.js +2893 -0
  41. package/local-scan.js +104 -0
  42. package/package.json +82 -0
  43. package/providers/ark/index.js +11 -0
  44. package/providers/bailian/index.js +10 -0
  45. package/providers/bigmodel/index.js +11 -0
  46. package/providers/codebuddy/agenttool.js +122 -0
  47. package/providers/codebuddy/catalog.js +227 -0
  48. package/providers/codebuddy/errors.js +44 -0
  49. package/providers/codebuddy/headers.js +36 -0
  50. package/providers/codebuddy/images.js +125 -0
  51. package/providers/codebuddy/index.js +123 -0
  52. package/providers/codebuddy/oauth.js +279 -0
  53. package/providers/deepseek/index.js +11 -0
  54. package/providers/moonshot/index.js +11 -0
  55. package/providers/openai-compat.js +177 -0
  56. package/providers/openrouter/index.js +27 -0
  57. package/providers/qoder/catalog.js +145 -0
  58. package/providers/qoder/cosy.js +419 -0
  59. package/providers/qoder/gateway.js +563 -0
  60. package/providers/qoder/index.js +116 -0
  61. package/providers/qoder/oauth.js +364 -0
  62. package/providers/qoder/qoder_auth.wasm +0 -0
  63. package/providers/qoder/quota.js +56 -0
  64. package/providers/qwen/index.js +15 -0
  65. package/providers/tool-pairing.js +129 -0
  66. package/providers/trae/catalog.js +103 -0
  67. package/providers/trae/errors.js +85 -0
  68. package/providers/trae/gateway.js +853 -0
  69. package/providers/trae/index.js +126 -0
  70. package/providers/trae/oauth.js +443 -0
  71. package/providers/trae/quota.js +75 -0
  72. package/providers/trae/remote.js +365 -0
  73. package/scripts/capture-cache.mjs +65 -0
  74. package/scripts/capture-traffic.mjs +83 -0
  75. package/scripts/hermes-probe-dev-role.mjs +263 -0
  76. package/scripts/measure-latency.mjs +253 -0
  77. package/scripts/probe-ark-thinking.mjs +298 -0
  78. package/scripts/probe-cache-decline.mjs +292 -0
  79. package/scripts/probe-cache-ttl.mjs +221 -0
  80. package/scripts/probe-cache.mjs +156 -0
  81. package/scripts/probe-codebuddy-efforts.mjs +355 -0
  82. package/scripts/probe-codebuddy-tier-wiring.mjs +244 -0
  83. package/scripts/probe-effort-gaps.mjs +133 -0
  84. package/scripts/probe-media.mjs +115 -0
  85. package/scripts/probe-moderation.mjs +159 -0
  86. package/scripts/probe-oauth.mjs +617 -0
  87. package/scripts/probe-qoder-attribution-arm8.mjs +92 -0
  88. package/scripts/probe-qoder-attribution-arm9.mjs +102 -0
  89. package/scripts/probe-qoder-attribution.mjs +266 -0
  90. package/scripts/probe-qoder-flash-confirm.mjs +94 -0
  91. package/scripts/probe-qoder-live.mjs +344 -0
  92. package/scripts/probe-qoder-matrix.mjs +274 -0
  93. package/scripts/probe-qoder-null-content.mjs +153 -0
  94. package/scripts/probe-qoder-pairing.mjs +308 -0
  95. package/scripts/probe-qoder-quota.mjs +162 -0
  96. package/scripts/probe-qoder-thinking-config.mjs +52 -0
  97. package/scripts/probe-qoder-thinking-efforts.mjs +269 -0
  98. package/scripts/probe-quota.mjs +136 -0
  99. package/scripts/probe-quota2.mjs +144 -0
  100. package/scripts/probe-routing.mjs +226 -0
  101. package/scripts/probe-trae-3003-diagnosis.mjs +139 -0
  102. package/scripts/probe-trae-agent-v3.mjs +399 -0
  103. package/scripts/probe-trae-efforts.mjs +149 -0
  104. package/scripts/probe-trae-live.mjs +147 -0
  105. package/scripts/probe-trae-max-effort.mjs +179 -0
  106. package/scripts/probe-trae-model-routing.mjs +381 -0
  107. package/scripts/probe-trae-thinking-scene.mjs +238 -0
  108. package/scripts/probe-trae-transport-outage.mjs +176 -0
  109. package/scripts/probe-ua.mjs +508 -0
  110. package/scripts/trae-model-catalog.mjs +632 -0
  111. package/scripts/verify-agents-md.mjs +45 -0
  112. package/scripts/verify-bridge.mjs +1041 -0
  113. package/scripts/verify-core-generic.mjs +302 -0
  114. package/scripts/verify-desktop-acceptance.mjs +184 -0
  115. package/scripts/verify-host-config.mjs +265 -0
  116. package/scripts/verify-models.mjs +390 -0
  117. package/scripts/verify-providers.mjs +255 -0
  118. package/scripts/verify-qoder-provider.mjs +1020 -0
  119. package/scripts/verify-rotation.mjs +308 -0
  120. package/scripts/verify-trae-model-catalog.mjs +284 -0
  121. package/scripts/verify-trae-provider.mjs +1451 -0
@@ -0,0 +1,381 @@
1
+ #!/usr/bin/env node
2
+ // 探测 Trae 上游模型路由生效条件 —— 四轮合并版(2026-08-29 合并)。
3
+ // Usage: node scripts/probe-trae-model-routing.mjs [--round 2|evidence|3|4](默认 2)
4
+ //
5
+ // 轮次考古(臂表/判据/证据落盘路径格式均保持各轮原样):
6
+ // --round 2 = 原 probe-trae-model-routing.mjs(2026-08-24,v2)
7
+ // A/B: raw llm_utils_chat 用网关同款完整头组;C2/C3: remote 9router 变体对照双模型。
8
+ // 判据:raw 看 timing_cost.provider_model_name;remote 看事件流内容+自报模型名对照。
9
+ // 证据落盘 docs/probes/trae-model-routing2-<ts>.json。
10
+ // --round evidence = 原 probe-trae-routing-evidence.mjs(2026-08-24)
11
+ // 补充证据:完整事件流落盘 + plan_item/model_config/timing_cost 形态打印。
12
+ // 落盘 docs/probes/trae-raw-stream-glm53.jsonl / docs/probes/trae-remote-stream-glm53.txt。
13
+ // --round 3 = 原 probe-trae-routing3.mjs(2026-08-24)
14
+ // raw 路径模型路由终局判定。F0: get_model_list 按 function 取模型清单(免费);
15
+ // F1: kimi-k3;F2: glm-5.3+custom_model;F3: DeepSeek-V4-Flash-Official;F4: function=chat_v3。
16
+ // 全部完整落盘 docs/probes/trae-model-routing3-<ts>.json,判据 = timing_cost.provider_model_name 或错误码。
17
+ // --round 4 = 原 probe-trae-routing4.mjs(2026-08-24)
18
+ // llm_utils_chat 其他 function 位的模型路由探测。solo_agent_lite / solo_work_lite
19
+ // 的模型清单含 glm-5.3/kimi-k3/DeepSeek-V4 全系(state.vscdb)。
20
+ // 落盘 docs/probes/trae-model-routing4-<ts>.json,判据 = timing_cost.provider_model_name。
21
+ import fs from "node:fs";
22
+ import path from "node:path";
23
+
24
+ function argValue(flag) {
25
+ const i = process.argv.indexOf(flag);
26
+ return i > 0 ? process.argv[i + 1] : null;
27
+ }
28
+ const ROUNDS = { 2: runRound2, evidence: runRoundEvidence, 3: runRound3, 4: runRound4 };
29
+ const ROUND = argValue("--round") ?? "2";
30
+ if (process.argv.includes("--help") || !ROUNDS[ROUND]) {
31
+ console.log("Usage: node scripts/probe-trae-model-routing.mjs [--round 2|evidence|3|4](默认 2;轮次对应见文件头注释)");
32
+ process.exit(process.argv.includes("--help") ? 0 : 1);
33
+ }
34
+
35
+ const AUTH_PATH = process.env.HOME + "/.dsh/trae-plugin-auth.json";
36
+ const RAW_URL = "https://trae-api-cn.mchost.guru/api/agent/v3/llm_utils_chat";
37
+ const REMOTE_BASE = "https://trae-api-cn.mchost.guru/api/remote/v1";
38
+
39
+ const auth = JSON.parse(fs.readFileSync(AUTH_PATH, "utf8"));
40
+ const token = auth.auth.accessToken;
41
+ const { device, account } = auth;
42
+ const uid = account.uid;
43
+
44
+ const rawHeaders = {
45
+ "Content-Type": "application/json",
46
+ Accept: "text/event-stream",
47
+ Connection: "keep-alive",
48
+ "x-app-id": "6eefa01c-1036-4c7e-9ca5-d891f63bfcd8",
49
+ "x-ide-version": "3.3.67",
50
+ "x-ide-version-code": "20260401",
51
+ "x-ide-version-type": "stable",
52
+ "x-device-cpu": "AMD",
53
+ "x-device-type": "windows",
54
+ "x-os-version": "Windows 10",
55
+ "x-system-type": "Windows",
56
+ "x-request-id": crypto.randomUUID(),
57
+ "User-Agent": "",
58
+ "x-device-id": device.deviceId,
59
+ "x-machine-id": device.machineId,
60
+ "x-device-brand": device.deviceBrand,
61
+ "x-uid": String(uid),
62
+ "Authorization": "Cloud-IDE-JWT " + token,
63
+ "X-Cloudide-Token": token,
64
+ "x-ide-token": token,
65
+ };
66
+ const webHeaders = {
67
+ "Authorization": "Cloud-IDE-JWT " + token,
68
+ "Content-Type": "application/json",
69
+ "X-Trae-Client-Type": "web",
70
+ "X-Preferenced-Language": "zh-CN",
71
+ "x-user-region": "CN",
72
+ "Origin": "https://solo.trae.cn",
73
+ "Referer": "https://solo.trae.cn/",
74
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
75
+ Accept: "text/event-stream",
76
+ };
77
+
78
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
79
+
80
+ // ─────────────────────────── ROUND 2(原 probe-trae-model-routing.mjs) ───────────────────────────
81
+
82
+ async function runRound2() {
83
+ function glm53CustomModel() {
84
+ return {
85
+ name: "glm-5.3", config_name: "glm-5.3", config_source: 1, provider: "",
86
+ multimodal: false, ak: "", sk: "", base_url: "", auth_type: 0, use_remote_service: true,
87
+ features: {
88
+ access: { data: { identity_list: [0, 5, 1, 2, 3, 100] } },
89
+ consumption_rate: { enable: true, data: { rate: 0.4 } },
90
+ discount: { enable: true, subKey: "exclusive_discount", data: {} },
91
+ reasoning: { enable: true },
92
+ },
93
+ };
94
+ }
95
+
96
+ async function rawChat(label, prompt, body) {
97
+ const full = {
98
+ messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
99
+ function: "inline_chat",
100
+ request_id: crypto.randomUUID(),
101
+ session_id: crypto.randomUUID(),
102
+ stream: true,
103
+ ...body,
104
+ };
105
+ const resp = await fetch(RAW_URL, { method: "POST", headers: rawHeaders, body: JSON.stringify(full) });
106
+ const status = resp.status;
107
+ if (status !== 200) return { label, status, error: (await resp.text()).slice(0, 500) };
108
+ const raw = await resp.text();
109
+ let timingModel = null, finalText = "";
110
+ for (const line of raw.split("\n")) {
111
+ const t = line.trim();
112
+ if (!t.startsWith("data:")) continue;
113
+ let obj; try { obj = JSON.parse(t.slice(5).trim()); } catch { continue; }
114
+ if (obj.timing_cost?.provider_model_name) timingModel = obj.timing_cost.provider_model_name;
115
+ if (typeof obj.response === "string") finalText = obj.response;
116
+ }
117
+ return { label, status, timingModel, finalText: finalText.slice(0, 300) };
118
+ }
119
+
120
+ async function remoteChat(label, modelName, prompt) {
121
+ const commonParams = JSON.stringify({
122
+ language: "zh-cn", app_language: "zh-CN", quality: "stable",
123
+ app_version: "1.0.0.1229", web_id: "", user_identity: "Free",
124
+ is_freshman: "0", biz_user_id: "", user_unique_id: "",
125
+ scope: "marscode-cn", tenant: "marscode", region: "cn", aiRegion: "cn",
126
+ is_privacy_mode: 0, privacy_mode: "off", solo_chat_mode: "code",
127
+ });
128
+ const body = {
129
+ mode: "code",
130
+ environment_id: "default",
131
+ initial_message: {
132
+ chat_session_id: "",
133
+ content: [],
134
+ query: JSON.stringify([{ type: "text", data: { content: prompt } }]),
135
+ model_name: modelName,
136
+ agent_type: "solo_agent_remote",
137
+ model_selection_strategy: "manual",
138
+ common_params: commonParams,
139
+ },
140
+ env: "remote",
141
+ auto_create_project: false,
142
+ origin: "web",
143
+ };
144
+ const resp = await fetch(REMOTE_BASE + "/chat_sessions", { method: "POST", headers: { ...webHeaders, Accept: "application/json" }, body: JSON.stringify(body) });
145
+ const status = resp.status;
146
+ const text = await resp.text();
147
+ if (status !== 200) return { label, status, error: text.slice(0, 500) };
148
+ const data = JSON.parse(text);
149
+ const payload = data.data ?? data;
150
+ const sid = payload.chat_session_id, mid = payload.message_id;
151
+ if (!sid || !mid) return { label, status, createResp: data, error: "missing ids" };
152
+ const ev = await fetch(`${REMOTE_BASE}/chat_sessions/${sid}/events?reply_to_message_id=${mid}`, { headers: webHeaders });
153
+ const evText = await ev.text();
154
+ // 提取 plan_item 累计文本与事件类型清单
155
+ const eventTypes = [];
156
+ const thoughts = {};
157
+ let usage = null, done = null, error = null;
158
+ for (const chunk of evText.split("\n\n")) {
159
+ let evName = null, dataLine = null;
160
+ for (const line of chunk.split("\n")) {
161
+ if (line.startsWith("event:")) evName = line.slice(6).trim();
162
+ if (line.startsWith("data:")) dataLine = line.slice(5).trim();
163
+ }
164
+ if (!dataLine) continue;
165
+ let obj; try { obj = JSON.parse(dataLine); } catch { continue; }
166
+ const name = evName || obj.event || "message";
167
+ eventTypes.push(name);
168
+ if (name === "plan_item" && obj.id) thoughts[obj.id] = obj.thought ?? obj.text ?? JSON.stringify(obj).slice(0, 200);
169
+ if (name === "token_usage") usage = obj;
170
+ if (name === "done") done = obj;
171
+ if (name === "error") error = obj;
172
+ }
173
+ return {
174
+ label, status, session_id: sid, eventTypes,
175
+ thoughtText: Object.values(thoughts).join("\n---\n").slice(0, 500),
176
+ usage, done: done ? JSON.stringify(done).slice(0, 300) : null, error,
177
+ rawTail: evText.slice(-1500),
178
+ };
179
+ }
180
+
181
+ const out = { ts: new Date().toISOString(), probes: [] };
182
+ const push = (p) => { out.probes.push(p); console.log(" ", p.label, "status:", p.status, "| timingModel:", p.timingModel ?? "-", "| events:", (p.eventTypes ?? []).join(",") || "-", "| err:", (p.error && JSON.stringify(p.error).slice(0, 150)) ?? "-"); };
183
+
184
+ console.log("[A] raw kimi-k3 ...");
185
+ push(await rawChat("A-raw-kimi-k3", "Reply with exactly: ok", { model: "kimi-k3" }));
186
+ await sleep(22000);
187
+ console.log("[B] raw glm-5.3 + custom_model ...");
188
+ push(await rawChat("B-raw-custom_model", "Reply with exactly: ok", { model: "glm-5.3", custom_model: glm53CustomModel() }));
189
+ await sleep(22000);
190
+ console.log("[C2] remote glm-5.3 ...");
191
+ push(await remoteChat("C2-remote-glm53", "glm-5.3", "你是哪个模型?请只回答你的模型名称,不要其他内容。"));
192
+ await sleep(8000);
193
+ console.log("[C3] remote kimi-k2.6 ...");
194
+ push(await remoteChat("C3-remote-kimi26", "kimi-k2.6", "你是哪个模型?请只回答你的模型名称,不要其他内容。"));
195
+
196
+ const fname = "docs/probes/trae-model-routing2-" + out.ts.replace(/[:.]/g, "-").slice(0, 19) + ".json";
197
+ fs.writeFileSync(path.resolve(fname), JSON.stringify(out, null, 2));
198
+ console.log("saved:", fname);
199
+ }
200
+
201
+ // ─────────────────────────── ROUND evidence(原 probe-trae-routing-evidence.mjs) ───────────────────────────
202
+
203
+ async function runRoundEvidence() {
204
+ // [D] raw glm-5.3 全事件落盘(形态校准)
205
+ console.log("[D] raw glm-5.3 full stream ...");
206
+ {
207
+ const body = {
208
+ messages: [{ role: "user", content: [{ type: "text", text: "Reply with exactly: ok" }] }],
209
+ model: "glm-5.3", function: "inline_chat",
210
+ request_id: crypto.randomUUID(), session_id: crypto.randomUUID(), stream: true,
211
+ };
212
+ const resp = await fetch(RAW_URL, { method: "POST", headers: rawHeaders, body: JSON.stringify(body) });
213
+ const raw = await resp.text();
214
+ fs.writeFileSync("docs/probes/trae-raw-stream-glm53.jsonl", raw);
215
+ for (const line of raw.split("\n")) {
216
+ const t = line.trim();
217
+ if (!t.startsWith("data:")) continue;
218
+ let obj; try { obj = JSON.parse(t.slice(5).trim()); } catch { continue; }
219
+ const keys = Object.keys(obj).join(",");
220
+ if (obj.event === "timing_cost" || obj.provider_model_name) console.log(" timing:", JSON.stringify(obj).slice(0, 250));
221
+ else if (obj.event === "output" || obj.response !== undefined) console.log(" output keys:", keys, "| resp:", JSON.stringify(obj.response ?? "").slice(0, 60));
222
+ else console.log(" event:", obj.event ?? "(none)", "| keys:", keys.slice(0, 120));
223
+ }
224
+ }
225
+
226
+ await sleep(22000);
227
+
228
+ // [E] remote glm-5.3 全事件落盘(plan_item 形态校准)
229
+ console.log("[E] remote glm-5.3 full stream ...");
230
+ {
231
+ const prompt = "你是哪个模型?请只回答你的模型名称。";
232
+ const commonParams = JSON.stringify({
233
+ language: "zh-cn", app_language: "zh-CN", quality: "stable", app_version: "1.0.0.1229",
234
+ web_id: "", user_identity: "Free", is_freshman: "0", biz_user_id: "", user_unique_id: "",
235
+ scope: "marscode-cn", tenant: "marscode", region: "cn", aiRegion: "cn",
236
+ is_privacy_mode: 0, privacy_mode: "off", solo_chat_mode: "code",
237
+ });
238
+ const body = {
239
+ mode: "code", environment_id: "default",
240
+ initial_message: {
241
+ chat_session_id: "", content: [],
242
+ query: JSON.stringify([{ type: "text", data: { content: prompt } }]),
243
+ model_name: "glm-5.3", agent_type: "solo_agent_remote",
244
+ model_selection_strategy: "manual", common_params: commonParams,
245
+ },
246
+ env: "remote", auto_create_project: false, origin: "web",
247
+ };
248
+ const resp = await fetch(REMOTE_BASE + "/chat_sessions", { method: "POST", headers: { ...webHeaders, Accept: "application/json" }, body: JSON.stringify(body) });
249
+ const data = JSON.parse(await resp.text());
250
+ const sid = data.data?.chat_session_id, mid = data.data?.message_id;
251
+ console.log(" session:", sid, "message:", mid);
252
+ const ev = await fetch(`${REMOTE_BASE}/chat_sessions/${sid}/events?reply_to_message_id=${mid}`, { headers: webHeaders });
253
+ const evText = await ev.text();
254
+ fs.writeFileSync("docs/probes/trae-remote-stream-glm53.txt", evText);
255
+ // 形态打印:每种事件的第一条
256
+ const seen = new Set();
257
+ for (const chunk of evText.split("\n\n")) {
258
+ let evName = null, dataLine = null;
259
+ for (const line of chunk.split("\n")) {
260
+ if (line.startsWith("event:")) evName = line.slice(6).trim();
261
+ if (line.startsWith("data:")) dataLine = line.slice(5).trim();
262
+ }
263
+ if (!dataLine || !evName || seen.has(evName)) continue;
264
+ seen.add(evName);
265
+ console.log(" [" + evName + "]", dataLine.slice(0, 280));
266
+ }
267
+ }
268
+ console.log("done");
269
+ }
270
+
271
+ // ─────────────────────────── ROUND 3(原 probe-trae-routing3.mjs) ───────────────────────────
272
+
273
+ async function runRound3() {
274
+ const out = { ts: new Date().toISOString(), probes: [] };
275
+
276
+ // F0: get_model_list(两域名各试一次)
277
+ console.log("[F0] get_model_list ...");
278
+ for (const host of ["https://api.trae.cn", "https://trae-api-cn.mchost.guru"]) {
279
+ try {
280
+ const r = await fetch(host + "/api/ide/v1/get_model_list", {
281
+ method: "POST", headers: rawHeaders,
282
+ body: JSON.stringify({ functions: ["assistant", "chat_v3", "inline_chat", "solo_agent_lite", "solo_work_lite"], force_refresh: false }),
283
+ });
284
+ const text = await r.text();
285
+ let d = null;
286
+ try { d = JSON.parse(text); } catch { /* HTML 错误页 */ }
287
+ out.probes.push({ label: "F0-get_model_list", host, status: r.status, body: d ?? text.slice(0, 300) });
288
+ console.log(" host:", host, "status:", r.status, d ? "(json)" : "(non-json)");
289
+ const data = d?.data ?? d;
290
+ for (const [fn, models] of Object.entries(data?.model_list_map ?? {})) {
291
+ if (!Array.isArray(models)) continue;
292
+ console.log(" [" + fn + "]", models.map((m) => m.name ?? m.config_name).join(", "));
293
+ }
294
+ } catch (e) { console.log(" host:", host, "ERR", String(e).slice(0, 120)); }
295
+ }
296
+
297
+ async function rawFull(label, extra) {
298
+ const body = {
299
+ messages: [{ role: "user", content: [{ type: "text", text: "Reply with exactly: ok" }] }],
300
+ function: "inline_chat", request_id: crypto.randomUUID(), session_id: crypto.randomUUID(), stream: true,
301
+ ...extra,
302
+ };
303
+ const resp = await fetch("https://trae-api-cn.mchost.guru/api/agent/v3/llm_utils_chat", { method: "POST", headers: rawHeaders, body: JSON.stringify(body) });
304
+ const raw = await resp.text();
305
+ let timing = null, err = null, text = "";
306
+ for (const line of raw.split("\n")) {
307
+ const t = line.trim();
308
+ if (!t.startsWith("data:")) continue;
309
+ let obj; try { obj = JSON.parse(t.slice(5).trim()); } catch { continue; }
310
+ if (obj.event === "error" || (obj.code != null && obj.code !== 0)) err = obj;
311
+ if (obj.event === "timing_cost" && obj.provider_model_name) timing = obj.provider_model_name;
312
+ if (typeof obj.response === "string") text = obj.response;
313
+ }
314
+ const rec = { label, status: resp.status, timing, err, text: text.slice(0, 80), raw: raw.slice(0, 2000) };
315
+ out.probes.push(rec);
316
+ console.log(" ", label, "status:", resp.status, "| timing:", timing ?? "-", "| err:", err ? JSON.stringify(err).slice(0, 120) : "-", "| text:", JSON.stringify(text.slice(0, 40)));
317
+ }
318
+
319
+ console.log("[F1] raw kimi-k3 ...");
320
+ await rawFull("F1-kimi-k3", { model: "kimi-k3" });
321
+ await sleep(22000);
322
+ console.log("[F2] raw glm-5.3 + custom_model ...");
323
+ await rawFull("F2-glm53-custom_model", { model: "glm-5.3", custom_model: { name: "glm-5.3", config_name: "glm-5.3", config_source: 1, provider: "", multimodal: false, ak: "", sk: "", base_url: "", auth_type: 0, use_remote_service: true } });
324
+ await sleep(22000);
325
+ console.log("[F3] raw DeepSeek-V4-Flash-Official ...");
326
+ await rawFull("F3-ds-v4-flash-official", { model: "DeepSeek-V4-Flash-Official" });
327
+ await sleep(22000);
328
+ console.log("[F4] raw function=chat_v3 + model=glm-5.3 ...");
329
+ await rawFull("F4-chat_v3-glm53", { model: "glm-5.3", function: "chat_v3" });
330
+
331
+ const fname = "docs/probes/trae-model-routing3-" + out.ts.replace(/[:.]/g, "-").slice(0, 19) + ".json";
332
+ fs.writeFileSync(fname, JSON.stringify(out, null, 2));
333
+ console.log("saved:", fname);
334
+ }
335
+
336
+ // ─────────────────────────── ROUND 4(原 probe-trae-routing4.mjs) ───────────────────────────
337
+
338
+ async function runRound4() {
339
+ const out = { ts: new Date().toISOString(), probes: [] };
340
+
341
+ async function rawFull(label, extra) {
342
+ const body = {
343
+ messages: [{ role: "user", content: [{ type: "text", text: "Reply with exactly: ok" }] }],
344
+ function: "inline_chat", request_id: crypto.randomUUID(), session_id: crypto.randomUUID(), stream: true,
345
+ ...extra,
346
+ };
347
+ const resp = await fetch("https://trae-api-cn.mchost.guru/api/agent/v3/llm_utils_chat", { method: "POST", headers: rawHeaders, body: JSON.stringify(body) });
348
+ const raw = await resp.text();
349
+ let timing = null, err = null, text = "";
350
+ for (const line of raw.split("\n")) {
351
+ const t = line.trim();
352
+ if (!t.startsWith("data:")) continue;
353
+ let obj; try { obj = JSON.parse(t.slice(5).trim()); } catch { continue; }
354
+ if (obj.event === "error" || (obj.code != null && obj.code !== 0)) err = obj;
355
+ if (obj.event === "timing_cost" && obj.provider_model_name) timing = obj.provider_model_name;
356
+ if (typeof obj.response === "string" && obj.response) text = obj.response;
357
+ }
358
+ out.probes.push({ label, status: resp.status, timing, err, text: text.slice(0, 80), raw: raw.slice(0, 3000) });
359
+ console.log(" ", label, "| status:", resp.status, "| timing:", timing ?? "-", "| err:", err ? JSON.stringify(err).slice(0, 130) : "-", "| text:", JSON.stringify(text.slice(0, 40)));
360
+ }
361
+
362
+ console.log("[G1] solo_agent_lite + glm-5.3 ...");
363
+ await rawFull("G1-lite-glm53", { model: "glm-5.3", function: "solo_agent_lite" });
364
+ await sleep(22000);
365
+ console.log("[G2] solo_agent_lite + kimi-k3 ...");
366
+ await rawFull("G2-lite-kimik3", { model: "kimi-k3", function: "solo_agent_lite" });
367
+ await sleep(22000);
368
+ console.log("[G3] solo_work_lite + DeepSeek-V4-Flash-Official ...");
369
+ await rawFull("G3-worklite-dsflash", { model: "DeepSeek-V4-Flash-Official", function: "solo_work_lite" });
370
+ await sleep(22000);
371
+ console.log("[G4] solo_agent_lite + 错误模型名(负对照) ...");
372
+ await rawFull("G4-lite-bogus", { model: "not-a-model", function: "solo_agent_lite" });
373
+
374
+ const fname = "docs/probes/trae-model-routing4-" + out.ts.replace(/[:.]/g, "-").slice(0, 19) + ".json";
375
+ fs.writeFileSync(fname, JSON.stringify(out, null, 2));
376
+ console.log("saved:", fname);
377
+ }
378
+
379
+ // ─────────────────────────── 轮次调度 ───────────────────────────
380
+
381
+ ROUNDS[ROUND]().catch((e) => { console.error(e); process.exit(1); });
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * probe-trae-thinking-scene.mjs — G3:找能把 thinking 置 enabled 的 scene/参数组合。
4
+ *
5
+ * 背景(docs/diagnosis-trae-3003.md 2026-10-04 补记 + gateway-facts Trae 节):
6
+ * inline_chat 面把 thinking 置 disabled、模型默认档 high ⇒ 组合非法恒 3003;
7
+ * chat_v3 请求体没有 effort/thinking 字段。而官方客户端 bundle
8
+ *(@byted-icube/solo-lite 551.*.mjs / ai-modules-chat index.mjs)证实:
9
+ * 官方在 remote 通道消息体的 `custom_model` 对象里携带 `reasoning_effort`
10
+ *(旧名 `reasoning_effort_level` 由版本门切换),取值来自逐模型档位存储
11
+ * `AI.agent.model.reasoning_effort_level_by_agent_model_v2`。
12
+ *
13
+ * 本探针分两段(真实上游,小额):
14
+ * P1 remote 通道(chat_sessions):baseline / custom_model.reasoning_effort ∈
15
+ * {light, high, extra_high} × --repeat 采样,看 reasoning_content 是否响应档位
16
+ * (踩坑 #42:多臂多采样,DIRECTION-CONSISTENT 才算数)。
17
+ * P2 raw 面(llm_utils_chat)场景/字段枚举(单发):thinking:{type:enabled} /
18
+ * thinking_enable:true / scene_params 内嵌 effort / function=solo_agent_lite
19
+ * 带 effort ——任一臂出 reasoning_content 即候选。
20
+ *
21
+ * 用法:node scripts/probe-trae-thinking-scene.mjs [--model glm-5.3] [--repeat 2]
22
+ * [--skip-p2]
23
+ * 证据 → docs/probes/trae-thinking-scene-<ts>.json(不含令牌)
24
+ */
25
+ import { writeFileSync, mkdirSync } from 'node:fs'
26
+ import { homedir } from 'node:os'
27
+ import { join } from 'node:path'
28
+
29
+ import { createTraeOAuth } from '../providers/trae/oauth.js'
30
+ import {
31
+ buildChatRequest, createTraeStreamParser,
32
+ TRAE_APP_ID, TRAE_IDE_VERSION, TRAE_IDE_VERSION_CODE,
33
+ } from '../providers/trae/gateway.js'
34
+ import {
35
+ createRemoteSession, openRemoteEvents,
36
+ stopRemoteSession, createRemoteEventParser,
37
+ } from '../providers/trae/remote.js'
38
+ import { readJson } from '../core/json-store.js'
39
+
40
+ const DSH_HOME = process.env.DSH_HOME ?? join(homedir(), '.dsh')
41
+ const AUTH_PATH = join(DSH_HOME, 'trae-plugin-auth.json')
42
+ const SETTINGS = {
43
+ traeAuthBaseURL: process.env.TRAE_AUTH_BASE ?? 'https://api.trae.cn',
44
+ traeChatBaseURL: process.env.TRAE_CHAT_BASE ?? 'https://trae-api-cn.mchost.guru',
45
+ traeLoginHost: process.env.TRAE_LOGIN_HOST ?? 'https://www.trae.cn',
46
+ }
47
+
48
+ const args = process.argv.slice(2)
49
+ const argOf = (name, dflt) => {
50
+ const i = args.indexOf(name)
51
+ return i >= 0 && args[i + 1] !== undefined ? args[i + 1] : dflt
52
+ }
53
+ const MODEL = argOf('--model', 'glm-5.3')
54
+ const REPEAT = Number(argOf('--repeat', '2')) || 2
55
+ const SKIP_P2 = args.includes('--skip-p2')
56
+ const PROMPT = '一个笼子里有鸡和兔共 10 只,脚共 28 只。鸡兔各几只?只给最终数字。'
57
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
58
+
59
+ const oauth = createTraeOAuth({ readAuth: () => readJson(AUTH_PATH), writeAuth: () => {} })
60
+ const cred = await oauth.resolveTraeCredential(SETTINGS)
61
+ if (!cred) {
62
+ console.error('未登录 Trae(先跑 node scripts/probe-trae-live.mjs --login)')
63
+ process.exit(2)
64
+ }
65
+ const token = String(cred.authorization).replace(/^Cloud-IDE-JWT\s+/, '')
66
+ const authStore = readJson(AUTH_PATH)
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // P1:remote 通道,custom_model.reasoning_effort(官方 bundle 证实的线缆字段)
70
+
71
+ async function remoteArm(label, level) {
72
+ let sessionId = null
73
+ let messageId = null
74
+ const out = { arm: label, level: level ?? null }
75
+ try {
76
+ // 走生产路径(createRemoteSession 内部 = buildRemoteCreateBody + custom_model
77
+ // 注入)——门控证据必须覆盖实际出站的代码路径,不手搭信封。
78
+ const created = await createRemoteSession(SETTINGS.traeChatBaseURL, token, MODEL, [{ role: 'user', content: PROMPT }], { reasoningEffort: level })
79
+ sessionId = created.sessionId
80
+ messageId = created.messageId
81
+ out.createHttp = 200
82
+ const evResp = await openRemoteEvents(SETTINGS.traeChatBaseURL, token, sessionId, messageId)
83
+ const raw = await evResp.text()
84
+ out.eventsHttp = evResp.status
85
+ const parser = createRemoteEventParser()
86
+ let reasoning = ''
87
+ let visible = ''
88
+ let lastEventName = null
89
+ for (const line of raw.split('\n')) {
90
+ const s = line.trim()
91
+ if (!s) { lastEventName = null; continue }
92
+ if (s.startsWith('event:')) { lastEventName = s.slice(6).trim(); continue }
93
+ if (s.startsWith('id:') || s.startsWith(':')) continue
94
+ if (!s.startsWith('data:')) continue
95
+ const dataStr = s.slice(5).trim()
96
+ if (!dataStr) continue
97
+ if (dataStr === '[DONE]') { parser.handle('done', {}); continue }
98
+ let chunk
99
+ try { chunk = JSON.parse(dataStr) } catch { continue }
100
+ const ev = parser.handle(lastEventName, chunk)
101
+ if (ev.error) out.error = JSON.stringify(ev.error).slice(0, 200)
102
+ if (typeof ev.reasoning === 'string') reasoning += ev.reasoning
103
+ if (typeof ev.text === 'string') visible += ev.text
104
+ }
105
+ out.reasoningChars = reasoning.length
106
+ out.textChars = visible.length
107
+ out.actualModel = parser.actualModel()
108
+ out.usage = parser.usage()
109
+ out.bytes = raw.length
110
+ } catch (err) {
111
+ out.error = String(err?.message ?? err).slice(0, 200)
112
+ } finally {
113
+ if (sessionId) await stopRemoteSession(SETTINGS.traeChatBaseURL, token, sessionId, messageId)
114
+ }
115
+ return out
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // P2:raw 面(llm_utils_chat)scene/字段枚举(单发)
120
+
121
+ async function rawArm(label, { fn, extraBody }) {
122
+ const payload = {
123
+ model: MODEL, stream: true,
124
+ messages: [{ role: 'user', content: PROMPT }],
125
+ }
126
+ const { body, requestId } = buildChatRequest(payload, 'probe-thinking-scene')
127
+ if (fn) body.function = fn
128
+ Object.assign(body, extraBody ?? {})
129
+ const out = { arm: label, function: body.function }
130
+ try {
131
+ const res = await fetch(`${SETTINGS.traeChatBaseURL}/api/agent/v3/llm_utils_chat`, {
132
+ method: 'POST',
133
+ headers: {
134
+ 'Content-Type': 'application/json',
135
+ Accept: 'text/event-stream',
136
+ Authorization: `Cloud-IDE-JWT ${token}`,
137
+ 'X-Cloudide-Token': token,
138
+ 'x-ide-token': token,
139
+ 'x-app-id': TRAE_APP_ID,
140
+ 'x-ide-version': TRAE_IDE_VERSION,
141
+ 'x-ide-version-code': TRAE_IDE_VERSION_CODE,
142
+ 'x-request-id': requestId,
143
+ ...(authStore.account?.uid ? { 'x-uid': String(authStore.account.uid) } : {}),
144
+ ...(authStore.device?.deviceId ? { 'x-device-id': authStore.device.deviceId } : {}),
145
+ ...(authStore.device?.machineId ? { 'x-machine-id': authStore.device.machineId } : {}),
146
+ },
147
+ body: JSON.stringify(body),
148
+ signal: AbortSignal.timeout(60_000),
149
+ })
150
+ const raw = await res.text()
151
+ out.http = res.status
152
+ const parser = createTraeStreamParser()
153
+ let reasoning = ''
154
+ let visible = ''
155
+ let lastEventName = null
156
+ for (const line of raw.split('\n')) {
157
+ const s = line.trim()
158
+ if (!s) { lastEventName = null; continue }
159
+ if (s.startsWith('event:')) { lastEventName = s.slice(6).trim(); continue }
160
+ if (!s.startsWith('data:')) continue
161
+ const dataStr = s.slice(5).trim()
162
+ if (!dataStr || dataStr === '[DONE]') continue
163
+ let chunk
164
+ try { chunk = JSON.parse(dataStr) } catch { continue }
165
+ const ev = parser.handle(lastEventName, chunk)
166
+ if (ev.error) out.inband = JSON.stringify(ev.error).slice(0, 200)
167
+ if (typeof ev.reasoning === 'string') reasoning += ev.reasoning
168
+ if (typeof ev.text === 'string') visible += ev.text
169
+ }
170
+ out.reasoningChars = reasoning.length
171
+ out.textChars = visible.length
172
+ out.providerModel = parser.providerModel()
173
+ out.rawHead = raw.slice(0, 400)
174
+ } catch (err) {
175
+ out.error = String(err?.message ?? err).slice(0, 200)
176
+ }
177
+ return out
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+
182
+ console.log(`model=${MODEL} repeat=${REPEAT} skipP2=${SKIP_P2}`)
183
+ const evidence = { at: new Date().toISOString(), model: MODEL, repeat: REPEAT, prompt: PROMPT, p1: [], p2: [] }
184
+
185
+ console.log('== P1 remote custom_model.reasoning_effort ==')
186
+ const LEVELS = ['light', 'high', 'extra_high']
187
+ for (let i = 0; i < REPEAT; i++) {
188
+ evidence.p1.push(await remoteArm(`baseline#${i + 1}`, undefined))
189
+ console.log(` baseline#${i + 1} done`)
190
+ for (const level of LEVELS) {
191
+ evidence.p1.push(await remoteArm(`${level}#${i + 1}`, level))
192
+ console.log(` ${level}#${i + 1} done`)
193
+ await sleep(3000) // remote 无 4011 但有排队/并发门,节奏克制
194
+ }
195
+ }
196
+
197
+ console.log('\narm create events reasoning text actualModel error')
198
+ for (const a of evidence.p1) {
199
+ console.log(`${a.arm.padEnd(18)} ${String(a.createHttp ?? '-').padStart(6)} ${String(a.eventsHttp ?? '-').padStart(7)} ${String(a.reasoningChars ?? '-').padStart(9)} ${String(a.textChars ?? '-').padStart(5)} ${String(a.actualModel ?? '-').padEnd(20)} ${(a.error ?? '').slice(0, 60)}`)
200
+ }
201
+ const mean = (xs) => (xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : 0)
202
+ const baseVals = evidence.p1.filter((a) => a.level === null && a.reasoningChars != null).map((a) => a.reasoningChars)
203
+ const baseMean = mean(baseVals)
204
+ console.log(`\nbaseline reasoning 均值 ${Math.round(baseMean)}(n=${baseVals.length})`)
205
+ evidence.verdicts = {}
206
+ for (const level of LEVELS) {
207
+ const vals = evidence.p1.filter((a) => a.level === level && a.reasoningChars != null).map((a) => a.reasoningChars)
208
+ const m = mean(vals)
209
+ const verdict = vals.length && (vals.every((v) => v > baseMean) || vals.every((v) => v < baseMean)) ? 'DIRECTION-CONSISTENT' : 'INCONCLUSIVE'
210
+ evidence.verdicts[level] = { vals, mean: Math.round(m), verdict }
211
+ console.log(` ${level.padEnd(10)} 均值 ${String(Math.round(m)).padStart(6)} 各次 ${JSON.stringify(vals)} → ${verdict}`)
212
+ }
213
+
214
+ if (!SKIP_P2) {
215
+ console.log('\n== P2 raw 面 scene/字段枚举(单发)==')
216
+ const p2Arms = [
217
+ ['inline+thinking.type=enabled', { fn: 'inline_chat', extraBody: { thinking: { type: 'enabled' } } }],
218
+ ['inline+thinking_enable', { fn: 'inline_chat', extraBody: { thinking_enable: true } }],
219
+ ['inline+scene_params.effort', { fn: 'inline_chat', extraBody: { scene_params: JSON.stringify({ reasoning_effort: 'light' }) } }],
220
+ ['solo_agent_lite+effort', { fn: 'solo_agent_lite', extraBody: { reasoning_effort: 'light' } }],
221
+ ['chat_v3+effort', { fn: 'chat_v3', extraBody: { reasoning_effort: 'light' } }],
222
+ ]
223
+ for (const [label, opts] of p2Arms) {
224
+ evidence.p2.push(await rawArm(label, opts))
225
+ console.log(` ${label} done`)
226
+ await sleep(20_000) // raw 面 4011 紧:联调间隔 ≥20s
227
+ }
228
+ console.log('\narm fn http reasoning text providerModel inband/error')
229
+ for (const a of evidence.p2) {
230
+ console.log(`${a.arm.padEnd(28)} ${String(a.function ?? '-').padEnd(16)} ${String(a.http ?? '-').padStart(5)} ${String(a.reasoningChars ?? '-').padStart(9)} ${String(a.textChars ?? '-').padStart(5)} ${String(a.providerModel ?? '-').padEnd(20)} ${((a.inband ?? a.error) ?? '').slice(0, 60)}`)
231
+ }
232
+ }
233
+
234
+ mkdirSync('docs/probes', { recursive: true })
235
+ const outPath = `docs/probes/trae-thinking-scene-${Date.now()}.json`
236
+ writeFileSync(outPath, JSON.stringify(evidence, null, 2))
237
+ console.log('\n证据 →', outPath)
238
+ console.log('判读纪律:P1 DIRECTION-CONSISTENT 才说明档位**可能**被线上承认;P2 任一臂出 reasoning_content 即候选 scene。INCONCLUSIVE 不改出站(#42)。')