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,226 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Route-allocation probe (topic 3 of docs/rules/): what dimensions does the
4
+ * gateway allocate routes by, i.e. what exactly does 14407 "route config not
5
+ * found" key on?
6
+ *
7
+ * Spell under test (AGENTS.md:24): "`/v2/videos/generations`、`/v2/3d/generations`
8
+ * 路由存在但当前账号一律 14407 route config not found(无可用模型)"。
9
+ *
10
+ * Anchor evidence (docs/probes/media-2026-08-17.json): 14407 messages embed
11
+ * the MODEL name — "Video model [hunyuan-video-t2v] route config not found" —
12
+ * so 14407 is a per-(endpoint-family, model) lookup failure, not a 404.
13
+ *
14
+ * /v3/config (fetched live in this script) lists 24 models for this account
15
+ * incl. hunyuan-image-v3.0-art and NO video/3d models — but deepseek-v3,
16
+ * which routes fine on chat, is NOT among them, so "catalog membership" is
17
+ * not the route table. The route table is its own per-(family, model) map.
18
+ *
19
+ * Pre-registered predictions (2026-08-19):
20
+ * P1 chat + bogus model → 14407, message echoes the model name verbatim
21
+ * P2 images + bogus model → 14407 with "image model" wording
22
+ * P3 images + glm-5.2 (a chat-family model that IS in the catalog)
23
+ * → 14407 (route configs are family-scoped, catalog membership does
24
+ * not cross families)
25
+ * P4 chat + hunyuan-image-v3.0-art (image model) → 14407
26
+ * P5 chat + glm-5.0 (catalog-listed, NOT in the cli agents list)
27
+ * → 200 (route config exists; the cli list gates CLI display, not
28
+ * routing) [costs one max_tokens=1 completion if it passes]
29
+ * P6 unknown family /v2/foo/generations → 404 "Route Not Found"
30
+ * (path routing is a layer separate from model routing)
31
+ * Exploratory (no firm prediction): videos + omitted model field.
32
+ *
33
+ * Low rate: 2s spacing, single account; all arms are designed to fail at
34
+ * the route layer (zero generation cost) except P5's tiny completion.
35
+ *
36
+ * Usage: node scripts/probe-routing.mjs [--out docs/probes/routing-YYYY-MM-DD.jsonl]
37
+ */
38
+
39
+ import { readFileSync, existsSync, appendFileSync, mkdirSync } from 'node:fs'
40
+ import { homedir } from 'node:os'
41
+ import { join, dirname } from 'node:path'
42
+
43
+ const DSH_HOME = process.env.DSH_HOME ?? join(homedir(), '.dsh')
44
+ const GATEWAY = 'https://copilot.tencent.com'
45
+ const OUT = process.argv[process.argv.indexOf('--out') + 1] && process.argv.includes('--out')
46
+ ? process.argv[process.argv.indexOf('--out') + 1]
47
+ : join('docs', 'probes', `routing-${new Date().toISOString().slice(0, 10)}.jsonl`)
48
+ const SPACING_MS = 2000
49
+
50
+ function resolveKey() {
51
+ try {
52
+ const cfg = JSON.parse(readFileSync(join(DSH_HOME, 'codebuddy-plugin.json'), 'utf8'))
53
+ const active = (cfg.apiKeys ?? []).find((k) => k.name === cfg.activeApiKey)
54
+ if (active?.key) return active.key
55
+ } catch { /* fall through */ }
56
+ if (process.env.CODEBUDDY_API_KEY) return process.env.CODEBUDDY_API_KEY
57
+ const credFile = join(DSH_HOME, '.credentials.yaml')
58
+ if (existsSync(credFile)) {
59
+ const m = readFileSync(credFile, 'utf8').match(/^\s*CODEBUDDY_API_KEY:\s*["']?([^"'\s]+)["']?\s*$/m)
60
+ if (m) return m[1]
61
+ }
62
+ throw new Error('no CodeBuddy credential found')
63
+ }
64
+
65
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
66
+ const redact = (v) => typeof v === 'string'
67
+ ? v.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<redacted>').replace(/\b\d{6,}\b/g, '<redacted>')
68
+ : v
69
+
70
+ const HEADERS = (key) => ({
71
+ 'Content-Type': 'application/json',
72
+ Accept: 'application/json',
73
+ Authorization: `Bearer ${key}`,
74
+ 'User-Agent': 'CLI/unknown CodeBuddy/2.136.0',
75
+ })
76
+
77
+ const ARMS = [
78
+ { name: 'P1-chat-bogus-model', method: 'POST', path: '/v2/chat/completions',
79
+ body: { model: 'no-such-model-xyz', stream: true, max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] },
80
+ expect: { code: 14407 }, note: 'message echoes model name verbatim' },
81
+ { name: 'P2-images-bogus-model', method: 'POST', path: '/v2/images/generations',
82
+ body: { model: 'no-such-image-model', prompt: 'x', size: '1024x1024', n: 1 },
83
+ expect: { code: 14407 }, note: '"image model" wording' },
84
+ { name: 'P3-images-chat-model', method: 'POST', path: '/v2/images/generations',
85
+ body: { model: 'glm-5.2', prompt: 'x', size: '1024x1024', n: 1 },
86
+ expect: { code: 14407 }, note: 'catalog chat model does not cross into the image family' },
87
+ { name: 'P4-chat-image-model', method: 'POST', path: '/v2/chat/completions',
88
+ body: { model: 'hunyuan-image-v3.0-art', stream: true, max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] },
89
+ expect: { code: 14407 }, note: 'image model has no chat route' },
90
+ { name: 'P5-chat-catalog-noncli', method: 'POST', path: '/v2/chat/completions',
91
+ body: { model: 'glm-5.0', stream: true, stream_options: { include_usage: true }, max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] },
92
+ expect: { status: 200 }, note: 'catalog-listed but not cli-enabled still routes' },
93
+ { name: 'P6-unknown-family', method: 'POST', path: '/v2/foo/generations',
94
+ body: { model: 'x' },
95
+ expect: { status: 404 }, note: 'path routing layer is separate from model routing' },
96
+ { name: 'X1-videos-no-model', method: 'POST', path: '/v2/videos/generations',
97
+ body: { prompt: 'x' },
98
+ expect: null, note: 'exploratory: error shape when model field omitted' },
99
+ ]
100
+
101
+ // Round 2 (2026-08-19): the matrix refuted the naive "14407 = (family,model)"
102
+ // picture and revealed a THREE-LAYER structure. Revised rules under test:
103
+ // R-R2 media-family registry miss → family-specific code + verbatim echo:
104
+ // image → 14401, video/3d → 14407 ("<Family> model [<echo>] route
105
+ // config not found"); the miss means "model unknown in that family",
106
+ // a GLOBALLY known model passes lookup and fails later at dispatch
107
+ // (P3: images+glm-5.2 → bare 500; P4: chat+image-model → 500/11103)
108
+ // R-R3 chat-family registry miss → 11102 "model [echo] service info not
109
+ // found"; the chat registry is independent of /v3/config AND of the
110
+ // cli-enabled list (kimi-k2.5 routes fine though not cli-listed;
111
+ // glm-5.0 is catalog-listed yet 11102)
112
+ // R-R4 model known but backend unsupported in context → HTTP 500, either
113
+ // bare (image family) or 11103 "Backend [x] is not supported" (chat)
114
+ // Pre-registered confirmations (unobserved cases):
115
+ const ARMS_R2 = [
116
+ { name: 'C1-images-no-model', method: 'POST', path: '/v2/images/generations',
117
+ body: { prompt: 'x', size: '1024x1024', n: 1 },
118
+ expect: { code: 14401 }, note: 'image family echoes empty model like video X1' },
119
+ { name: 'C2-video-fresh-bogus', method: 'POST', path: '/v2/videos/generations',
120
+ body: { model: 'no-such-video-model', prompt: 'x' },
121
+ expect: { code: 14407 }, note: 'verbatim echo of a fresh unobserved name' },
122
+ { name: 'C3-3d-no-model', method: 'POST', path: '/v2/3d/generations',
123
+ body: { prompt: 'x' },
124
+ expect: { code: 14407 }, note: '3d family echoes empty model' },
125
+ { name: 'C4-chat-fresh-bogus', method: 'POST', path: '/v2/chat/completions',
126
+ body: { model: 'no-such-model-abc', stream: true, max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] },
127
+ expect: { code: 11102 }, note: 'chat registry miss wording is stable' },
128
+ { name: 'C5-chat-notcli-k25', method: 'POST', path: '/v2/chat/completions',
129
+ body: { model: 'kimi-k2.5', stream: true, stream_options: { include_usage: true }, max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] },
130
+ expect: { status: 200 }, note: 'cli list does not gate routing (k2.5 not cli-listed)' },
131
+ { name: 'C6-chat-volc-alias', method: 'POST', path: '/v2/chat/completions',
132
+ body: { model: 'deepseek-v3-2-volc', stream: true, max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] },
133
+ expect: { code: 11102 }, note: 'registry keyed by public ids; catalog-internal volc id unknown' },
134
+ { name: 'C7-chat-hunyuan-chat', method: 'POST', path: '/v2/chat/completions',
135
+ body: { model: 'hunyuan-chat', stream: true, max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] },
136
+ expect: { code: 11103 }, note: 'catalog chat-branded model resolves but its backend unsupported' },
137
+ { name: 'C8-3d-known-image-model', method: 'POST', path: '/v2/3d/generations',
138
+ body: { model: 'hunyuan-image-v3.0-art', prompt: 'x' },
139
+ expect: { status: 500 }, note: 'globally-known model passes 3d lookup, dies at dispatch — NOT 14407' },
140
+ ]
141
+
142
+ // Round 3 (2026-08-19): C1–C5 HIT confirmed R-R2/R-R3; C6/C7 MISS showed the
143
+ // chat registry also resolves catalog-internal aliases (deepseek-v3-2-volc
144
+ // → 200) and more backends (hunyuan-chat → 200). C8 refined R-R4: known
145
+ // model + unsupported family → 11103 "Backend [aiart] is not supported for
146
+ // 3d generation" — but with HTTP 400 (3d), not the 500 seen on chat (P4)
147
+ // and bare 500 on images (P3). R-R4 final form under test: the dispatch
148
+ // failure is 11103 with a per-family envelope: 3d/video → HTTP 400 with
149
+ // "...for <family> generation", chat → HTTP 500 bare wording, images →
150
+ // HTTP 500 with no JSON envelope at all.
151
+ const ARMS_R3 = [
152
+ { name: 'D1-video-known-chat-model', method: 'POST', path: '/v2/videos/generations',
153
+ body: { model: 'glm-5.2', prompt: 'x' },
154
+ expect: { status: 400, code: 11103 }, note: '11103 with "for video generation" wording, mirroring C8' },
155
+ { name: 'D2-images-known-chat-model', method: 'POST', path: '/v2/images/generations',
156
+ body: { model: 'kimi-k2.6', prompt: 'x', size: '1024x1024', n: 1 },
157
+ expect: { status: 500, code: null }, note: 'images family envelope is a bare 500 (P3 replication with a fresh model)' },
158
+ { name: 'D3-chat-image-model-retest', method: 'POST', path: '/v2/chat/completions',
159
+ body: { model: 'hunyuan-image-v3.0-art', stream: true, max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] },
160
+ expect: { status: 500, code: 11103 }, note: 'P4 envelope stability retest' },
161
+ ]
162
+
163
+ // Round 4 (2026-08-19): D1 MISS refined the video family — 14407 is its
164
+ // CATCH-ALL code, not only "route config not found": a globally known chat
165
+ // model on /v2/videos got 400/14407 "unsupported video params". D2/D3 HIT
166
+ // confirmed the images bare-500 and chat 500/11103 envelopes. Final R-R4
167
+ // prediction: a SECOND unobserved chat model on video repeats the catch-all.
168
+ const ARMS_R4 = [
169
+ { name: 'E1-video-chat-model-2', method: 'POST', path: '/v2/videos/generations',
170
+ body: { model: 'kimi-k2.6', prompt: 'x' },
171
+ expect: { status: 400, code: 14407 }, note: 'video catch-all: 14407 "unsupported video params" for any known non-video model' },
172
+ ]
173
+
174
+ async function probe(key, arm) {
175
+ const t0 = Date.now()
176
+ try {
177
+ const res = await fetch(`${GATEWAY}${arm.path}`, {
178
+ method: arm.method,
179
+ headers: HEADERS(key),
180
+ body: JSON.stringify(arm.body),
181
+ })
182
+ const text = await res.text()
183
+ let json = null
184
+ try { json = JSON.parse(text) } catch { /* SSE stream or HTML */ }
185
+ return {
186
+ name: arm.name, path: arm.path, model: arm.body?.model ?? null, expect: arm.expect, note: arm.note,
187
+ status: res.status, code: json?.code ?? null,
188
+ msg: redact(json?.msg ?? json?.error_msg ?? (json ? JSON.stringify(json).slice(0, 160) : text.slice(0, 160))),
189
+ ms: Date.now() - t0,
190
+ }
191
+ } catch (err) {
192
+ return { name: arm.name, path: arm.path, model: arm.body?.model ?? null, expect: arm.expect, note: arm.note, error: String(err?.message ?? err), ms: Date.now() - t0 }
193
+ }
194
+ }
195
+
196
+ function judge(rec) {
197
+ if (!rec.expect) return ''
198
+ const okStatus = rec.expect.status == null || rec.status === rec.expect.status
199
+ const okCode = rec.expect.code == null || rec.code === rec.expect.code
200
+ return okStatus && okCode ? ' HIT' : ' MISS'
201
+ }
202
+
203
+ const key = resolveKey()
204
+ mkdirSync(dirname(OUT), { recursive: true })
205
+ appendFileSync(OUT, JSON.stringify({ type: 'run', at: new Date().toISOString(), gateway: GATEWAY }) + '\n')
206
+ console.log(`evidence → ${OUT}`)
207
+
208
+ const SET = process.argv.includes('--set') ? process.argv[process.argv.indexOf('--set') + 1] : 'matrix'
209
+ const arms = SET === 'r2' ? ARMS_R2 : SET === 'r3' ? ARMS_R3 : SET === 'r4' ? ARMS_R4 : ARMS
210
+
211
+ // anchor: the account's catalog (proves which families are entitled)
212
+ const catRes = await fetch(`${GATEWAY}/v3/config`, { headers: { Accept: 'application/json', Authorization: `Bearer ${key}`, 'x-api-key': key, 'User-Agent': 'CLI/unknown CodeBuddy/2.136.0', 'X-Product': 'SaaS' } })
213
+ const cat = await catRes.json().catch(() => null)
214
+ const models = (cat?.data?.models ?? []).map((m) => m.id)
215
+ const cliModels = (cat?.data?.agents ?? []).find((a) => a.name === 'cli')?.models ?? []
216
+ appendFileSync(OUT, JSON.stringify({ type: 'catalog', at: new Date().toISOString(), models, cliModels }) + '\n')
217
+ console.log(`catalog: ${models.length} models, cli-enabled: ${cliModels.length}`)
218
+ await sleep(SPACING_MS)
219
+
220
+ for (const arm of arms) {
221
+ const rec = { type: 'probe', ...await probe(key, arm) }
222
+ appendFileSync(OUT, JSON.stringify(rec) + '\n')
223
+ console.log(`${arm.name.padEnd(24)} status=${rec.status ?? '-'} code=${rec.code ?? '-'} msg=${(rec.msg ?? rec.error ?? '').slice(0, 110)}${judge(rec)}`)
224
+ await sleep(SPACING_MS)
225
+ }
226
+ console.log('done.')
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/node
2
+ // 诊断探测:Trae inline 面 "3003 all models failed" 故障取证(2026-08-24)。
3
+ //
4
+ // 背景:用户经 dsh-codebuddy-plugin 使用 TraeWork CN 模型报
5
+ // "trae 3003 all models failed" + PI_AI_ERROR。
6
+ // 本脚本做最小对照实验并落证据盘(docs/probes/trae-3003-diagnosis-<ts>.json):
7
+ // [0] 双额度池余额(只读 ide_user_ent_usage,req_source 0/1)
8
+ // [A] inline_chat + 非默认模型(glm-5.3)
9
+ // [B] inline_chat + 账户默认模型(kimi-k2.6)
10
+ // [C] function=chat_v3 + glm-5.3(同信封同鉴权对照——健康则证明故障只在
11
+ // inline_chat 的模型解析层,非凭据/非信封/非配额)
12
+ // [R] remote 会话创建(含边缘节点漂移重试观测;成功即停,避免占并发额度)
13
+ // 判读要点:错误事件名在 `event:` 行(data JSON 内无 event 字段);业务级拒绝
14
+ // 恒为 JSON 信封,**裸文本 404/403 是 TLB/WAF 层行为**(docs/diagnosis-trae-3003.md)。
15
+ // 纪律:raw 面请求间隔 ≥20s(4011 限流很紧);本脚本单轮共 4 个 chat 请求。
16
+ import fs from 'node:fs'
17
+ import { homedir } from 'node:os'
18
+ import { join } from 'node:path'
19
+
20
+ const DSH_HOME = process.env.DSH_HOME ?? join(homedir(), '.dsh')
21
+ const AUTH_PATH = join(DSH_HOME, 'trae-plugin-auth.json')
22
+ const MCHOST = 'https://trae-api-cn.mchost.guru'
23
+
24
+ const { readJson } = await import('../core/json-store.js')
25
+ const { createTraeOAuth } = await import('../providers/trae/oauth.js')
26
+ const { buildChatRequest, traeOutboundHeaders } = await import('../providers/trae/gateway.js')
27
+ const { createRemoteSession } = await import('../providers/trae/remote.js')
28
+
29
+ const SETTINGS = { traeAuthBaseURL: 'https://api.trae.cn', traeChatBaseURL: MCHOST, traeLoginHost: 'https://www.trae.cn' }
30
+ const oauth = createTraeOAuth({ readAuth: () => readJson(AUTH_PATH), writeAuth: (v) => fs.writeFileSync(AUTH_PATH, JSON.stringify(v)) })
31
+ const cred = await oauth.resolveTraeCredential(SETTINGS)
32
+ if (!cred) { console.error('未登录(先在设置卡 TraeWork CN 分区登录或跑 probe-trae-live.mjs --login)'); process.exit(1) }
33
+ const authStore = readJson(AUTH_PATH)
34
+ const token = String(cred.authorization).replace(/^Cloud-IDE-JWT\s+/, '')
35
+ const device = authStore.device ?? {}
36
+ const uid = String(authStore.account?.uid ?? '')
37
+ const ideHeaders = (requestId) => ({
38
+ ...traeOutboundHeaders(device, uid ? Number(uid) : undefined, requestId),
39
+ Authorization: `Cloud-IDE-JWT ${token}`,
40
+ 'X-Cloudide-Token': token,
41
+ 'x-ide-token': token,
42
+ })
43
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
44
+
45
+ /** 解析 raw 面 SSE:event 名在 event: 行;裸 {code} 数据也算业务错误。 */
46
+ function parseRawSse(raw) {
47
+ let lastEvt = null
48
+ const events = []
49
+ for (const line of raw.split('\n')) {
50
+ const l = line.trim()
51
+ if (!l) { lastEvt = null; continue }
52
+ if (l.startsWith('event:')) { lastEvt = l.slice(6).trim(); continue }
53
+ if (!l.startsWith('data:')) continue
54
+ try {
55
+ const j = JSON.parse(l.slice(5).trim())
56
+ const evName = (typeof j.event === 'string' && j.event) ? j.event : lastEvt
57
+ const isErr = evName === 'error' || (typeof j.code === 'number' && j.response === undefined && j.usage === undefined)
58
+ events.push({ ev: evName ?? null, code: isErr ? j.code : undefined, message: isErr ? j.message : undefined, providerModel: j.provider_model_name })
59
+ } catch { /* 非 JSON data 忽略 */ }
60
+ }
61
+ return events
62
+ }
63
+
64
+ async function rawChatProbe(label, fn, model /* undefined=不带 model 字段 */) {
65
+ const payload = { stream: true, messages: [{ role: 'user', content: '只回复两个字:成功' }] }
66
+ if (model !== undefined) payload.model = model
67
+ const { body, requestId } = buildChatRequest(payload, `diag-${label}`)
68
+ body.function = fn
69
+ const t0 = Date.now()
70
+ const resp = await fetch(`${MCHOST}/api/agent/v3/llm_utils_chat`, {
71
+ method: 'POST', headers: ideHeaders(requestId), body: JSON.stringify(body), signal: AbortSignal.timeout(60_000),
72
+ })
73
+ const raw = await resp.text()
74
+ const events = parseRawSse(raw)
75
+ return {
76
+ label, fn, model: model ?? '(omitted)', status: resp.status,
77
+ errors: events.filter((e) => e.code != null).slice(0, 2),
78
+ providerModels: [...new Set(events.map((e) => e.providerModel).filter(Boolean))],
79
+ hasText: events.some((e) => e.ev === 'output'),
80
+ ms: Date.now() - t0, rawBytes: raw.length,
81
+ }
82
+ }
83
+
84
+ const evidence = {
85
+ at: new Date().toISOString(), note: '诊断脚本见 scripts/probe-trae-3003-diagnosis.mjs;结论见 docs/diagnosis-trae-3003.md',
86
+ accountUidMasked: uid.slice(0, 3) + '***', steps: {},
87
+ }
88
+
89
+ console.log('[0] 双额度池余额(只读)')
90
+ for (const src of [0, 1]) {
91
+ try {
92
+ const r = await fetch('https://api.trae.cn/trae/api/v2/pay/ide_user_ent_usage', {
93
+ method: 'POST', headers: ideHeaders(crypto.randomUUID()),
94
+ body: JSON.stringify({ require_usage: true, req_source: src }), signal: AbortSignal.timeout(15_000),
95
+ })
96
+ const j = await r.json().catch(() => null)
97
+ evidence.steps[`pool_src${src}`] = (j?.user_entitlement_pack_list ?? []).map((p) => ({
98
+ endpoint: p?.entitlement_base_info?.available_endpoint, limit: p?.entitlement_base_info?.quota?.credits_limit, used: p?.usage?.credits_amount,
99
+ }))
100
+ console.log(` req_source=${src}:`, JSON.stringify(evidence.steps[`pool_src${src}`]))
101
+ } catch (e) { evidence.steps[`pool_src${src}`] = { error: String(e?.message ?? e).slice(0, 120) } }
102
+ }
103
+
104
+ await sleep(21_000)
105
+ console.log('[A] inline_chat + glm-5.3(非默认模型)')
106
+ evidence.steps.A_inline_nonDefault = await rawChatProbe('A', 'inline_chat', 'glm-5.3')
107
+ console.log(' ', JSON.stringify(evidence.steps.A_inline_nonDefault))
108
+ await sleep(21_000)
109
+ console.log('[B] inline_chat + kimi-k2.6(账户默认模型)')
110
+ evidence.steps.B_inline_default = await rawChatProbe('B', 'inline_chat', 'kimi-k2.6')
111
+ console.log(' ', JSON.stringify(evidence.steps.B_inline_default))
112
+ await sleep(21_000)
113
+ console.log('[C] chat_v3 + glm-5.3(对照组:信封/鉴权健康性)')
114
+ evidence.steps.C_chatv3 = await rawChatProbe('C', 'chat_v3', 'glm-5.3')
115
+ console.log(' ', JSON.stringify(evidence.steps.C_chatv3))
116
+ await sleep(21_000)
117
+ console.log('[R] remote 会话创建(观测边缘节点漂移;成功即停善后)')
118
+ try {
119
+ const s = await createRemoteSession(MCHOST + '/api/remote/v1', token, 'glm-5.3', [{ role: 'user', content: '只回复两个字:成功' }])
120
+ evidence.steps.R_remote_create = { ok: true, sessionIdMasked: s.sessionId.slice(0, 10) + '…' }
121
+ console.log(' 创建成功 sid=' + s.sessionId.slice(0, 10) + '…')
122
+ } catch (e) {
123
+ evidence.steps.R_remote_create = { ok: false, error: String(e?.message ?? e).slice(0, 240) }
124
+ console.log(' ', evidence.steps.R_remote_create.error)
125
+ }
126
+
127
+ const a = evidence.steps.A_inline_nonDefault, b = evidence.steps.B_inline_default, c = evidence.steps.C_chatv3
128
+ evidence.verdict =
129
+ a.errors.length && b.errors.length && !c.errors.length
130
+ ? `INLINE-FACE-BROKEN: inline_chat 对默认与非默认模型一律 ${a.errors[0].code},同信封 chat_v3 正常 → 服务端 inline 面模型解析故障(非凭据/配额/信封);解法=切 remote 传输`
131
+ : a.errors.length && !b.errors.length
132
+ ? 'DEFAULT-ONLY: 仅非默认模型失败 → 与 trae-cloud-api.md §5.1 一致(function 位钉死到账户默认模型)'
133
+ : '见证据详情'
134
+ console.log('==>', evidence.verdict)
135
+
136
+ const outPath = join('docs/probes', `trae-3003-diagnosis-${Date.now()}.json`)
137
+ fs.mkdirSync('docs/probes', { recursive: true })
138
+ fs.writeFileSync(outPath, JSON.stringify(evidence, null, 2))
139
+ console.log('证据落盘 →', outPath)