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.
- package/AGENTS.md +99 -0
- package/CHANGELOG.md +751 -0
- package/LICENSE +21 -0
- package/README.en.md +59 -0
- package/README.md +256 -0
- package/cordis.patch.yml +385 -0
- package/core/bridge.js +698 -0
- package/core/json-store.js +91 -0
- package/core/rotation.js +108 -0
- package/core/usage-meter.js +176 -0
- package/docs/diagnosis-cache-quota.md +181 -0
- package/docs/diagnosis-qoder-flash.md +67 -0
- package/docs/diagnosis-trae-3003.md +302 -0
- package/docs/goals/bridge-port-host-split.md +91 -0
- package/docs/goals/desktop-adaptation.md +106 -0
- package/docs/goals/qoder-cn-provider-design.md +308 -0
- package/docs/goals/settings-card-ux-redesign-plan.md +1680 -0
- package/docs/goals/settings-card-ux-redesign.md +162 -0
- package/docs/goals/trae-agent-v3.md +41 -0
- package/docs/goals/trae-work-cn-repair.md +44 -0
- 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
- package/docs/pitfalls.md +105 -0
- package/docs/reverse/trae-cloud-api.md +218 -0
- package/docs/reverse/trae-model-catalog.md +129 -0
- package/docs/reverse/traework-cn.md +520 -0
- package/docs/rules/STATE.md +198 -0
- package/docs/rules/content-moderation.md +54 -0
- package/docs/rules/dev-role-boundary.md +94 -0
- package/docs/rules/extra-providers.md +42 -0
- package/docs/rules/gateway-facts.md +91 -0
- package/docs/rules/oauth-handshake.md +76 -0
- package/docs/rules/prompt-cache.md +93 -0
- package/docs/rules/quota-signals.md +125 -0
- package/docs/rules/routing.md +84 -0
- package/docs/rules/templates/oauth-reverse-checklist.md +42 -0
- package/docs/rules/trae-surface.md +242 -0
- package/docs/rules/ua-validation.md +81 -0
- package/host-config.js +282 -0
- package/index.js +2306 -0
- package/lib/client.js +2893 -0
- package/local-scan.js +104 -0
- package/package.json +82 -0
- package/providers/ark/index.js +11 -0
- package/providers/bailian/index.js +10 -0
- package/providers/bigmodel/index.js +11 -0
- package/providers/codebuddy/agenttool.js +122 -0
- package/providers/codebuddy/catalog.js +227 -0
- package/providers/codebuddy/errors.js +44 -0
- package/providers/codebuddy/headers.js +36 -0
- package/providers/codebuddy/images.js +125 -0
- package/providers/codebuddy/index.js +123 -0
- package/providers/codebuddy/oauth.js +279 -0
- package/providers/deepseek/index.js +11 -0
- package/providers/moonshot/index.js +11 -0
- package/providers/openai-compat.js +177 -0
- package/providers/openrouter/index.js +27 -0
- package/providers/qoder/catalog.js +145 -0
- package/providers/qoder/cosy.js +419 -0
- package/providers/qoder/gateway.js +563 -0
- package/providers/qoder/index.js +116 -0
- package/providers/qoder/oauth.js +364 -0
- package/providers/qoder/qoder_auth.wasm +0 -0
- package/providers/qoder/quota.js +56 -0
- package/providers/qwen/index.js +15 -0
- package/providers/tool-pairing.js +129 -0
- package/providers/trae/catalog.js +103 -0
- package/providers/trae/errors.js +85 -0
- package/providers/trae/gateway.js +853 -0
- package/providers/trae/index.js +126 -0
- package/providers/trae/oauth.js +443 -0
- package/providers/trae/quota.js +75 -0
- package/providers/trae/remote.js +365 -0
- package/scripts/capture-cache.mjs +65 -0
- package/scripts/capture-traffic.mjs +83 -0
- package/scripts/hermes-probe-dev-role.mjs +263 -0
- package/scripts/measure-latency.mjs +253 -0
- package/scripts/probe-ark-thinking.mjs +298 -0
- package/scripts/probe-cache-decline.mjs +292 -0
- package/scripts/probe-cache-ttl.mjs +221 -0
- package/scripts/probe-cache.mjs +156 -0
- package/scripts/probe-codebuddy-efforts.mjs +355 -0
- package/scripts/probe-codebuddy-tier-wiring.mjs +244 -0
- package/scripts/probe-effort-gaps.mjs +133 -0
- package/scripts/probe-media.mjs +115 -0
- package/scripts/probe-moderation.mjs +159 -0
- package/scripts/probe-oauth.mjs +617 -0
- package/scripts/probe-qoder-attribution-arm8.mjs +92 -0
- package/scripts/probe-qoder-attribution-arm9.mjs +102 -0
- package/scripts/probe-qoder-attribution.mjs +266 -0
- package/scripts/probe-qoder-flash-confirm.mjs +94 -0
- package/scripts/probe-qoder-live.mjs +344 -0
- package/scripts/probe-qoder-matrix.mjs +274 -0
- package/scripts/probe-qoder-null-content.mjs +153 -0
- package/scripts/probe-qoder-pairing.mjs +308 -0
- package/scripts/probe-qoder-quota.mjs +162 -0
- package/scripts/probe-qoder-thinking-config.mjs +52 -0
- package/scripts/probe-qoder-thinking-efforts.mjs +269 -0
- package/scripts/probe-quota.mjs +136 -0
- package/scripts/probe-quota2.mjs +144 -0
- package/scripts/probe-routing.mjs +226 -0
- package/scripts/probe-trae-3003-diagnosis.mjs +139 -0
- package/scripts/probe-trae-agent-v3.mjs +399 -0
- package/scripts/probe-trae-efforts.mjs +149 -0
- package/scripts/probe-trae-live.mjs +147 -0
- package/scripts/probe-trae-max-effort.mjs +179 -0
- package/scripts/probe-trae-model-routing.mjs +381 -0
- package/scripts/probe-trae-thinking-scene.mjs +238 -0
- package/scripts/probe-trae-transport-outage.mjs +176 -0
- package/scripts/probe-ua.mjs +508 -0
- package/scripts/trae-model-catalog.mjs +632 -0
- package/scripts/verify-agents-md.mjs +45 -0
- package/scripts/verify-bridge.mjs +1041 -0
- package/scripts/verify-core-generic.mjs +302 -0
- package/scripts/verify-desktop-acceptance.mjs +184 -0
- package/scripts/verify-host-config.mjs +265 -0
- package/scripts/verify-models.mjs +390 -0
- package/scripts/verify-providers.mjs +255 -0
- package/scripts/verify-qoder-provider.mjs +1020 -0
- package/scripts/verify-rotation.mjs +308 -0
- package/scripts/verify-trae-model-catalog.mjs +284 -0
- package/scripts/verify-trae-provider.mjs +1451 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Live integration probe for the Trae channel (REAL network, own account).
|
|
4
|
+
*
|
|
5
|
+
* The offline suite (verify:trae-provider) locks translation logic against
|
|
6
|
+
* mocks; this script is the one manual calibration step that needs a real
|
|
7
|
+
* login — the request envelope / SSE grammar of the Trae cloud gateway is
|
|
8
|
+
* extracted from binary strings and must be confirmed against the live wire.
|
|
9
|
+
*
|
|
10
|
+
* node scripts/probe-trae-live.mjs --login
|
|
11
|
+
* Real OAuth device flow against api.trae.cn: prints the authorization
|
|
12
|
+
* URL (open it, log in), waits for the 127.0.0.1 callback, then
|
|
13
|
+
* immediately exercises a DeviceProof refresh to prove the self-held
|
|
14
|
+
* keypair works online. Tokens never print (masked only).
|
|
15
|
+
*
|
|
16
|
+
* node scripts/probe-trae-live.mjs --chat "你好"
|
|
17
|
+
* One-shot real chat via the gateway's request builder; dumps the RAW
|
|
18
|
+
* response (status/headers/body) to docs/probes/trae-chat-live-*.json
|
|
19
|
+
* for schema correction (no credentials in the dump).
|
|
20
|
+
*
|
|
21
|
+
* node scripts/probe-trae-live.mjs --sig raw # flip DeviceProof encoding
|
|
22
|
+
*
|
|
23
|
+
* Uses the SAME auth store as the plugin (~/.dsh/trae-plugin-auth.json,
|
|
24
|
+
* DSH_HOME respected) — one login serves both.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { writeFileSync, mkdirSync } from 'node:fs'
|
|
28
|
+
import { homedir } from 'node:os'
|
|
29
|
+
import { join } from 'node:path'
|
|
30
|
+
|
|
31
|
+
import { createTraeOAuth } from '../providers/trae/oauth.js'
|
|
32
|
+
import { buildChatRequest, TRAE_APP_ID, TRAE_IDE_VERSION, TRAE_IDE_VERSION_CODE } from '../providers/trae/gateway.js'
|
|
33
|
+
|
|
34
|
+
const DSH_HOME = process.env.DSH_HOME ?? join(homedir(), '.dsh')
|
|
35
|
+
const AUTH_PATH = join(DSH_HOME, 'trae-plugin-auth.json')
|
|
36
|
+
|
|
37
|
+
const SETTINGS = {
|
|
38
|
+
traeAuthBaseURL: process.env.TRAE_AUTH_BASE ?? 'https://api.trae.cn',
|
|
39
|
+
traeChatBaseURL: process.env.TRAE_CHAT_BASE ?? 'https://trae-api-cn.mchost.guru',
|
|
40
|
+
traeLoginHost: process.env.TRAE_LOGIN_HOST ?? 'https://www.trae.cn',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function mask(t) {
|
|
44
|
+
return typeof t === 'string' && t.length > 12 ? `${t.slice(0, 6)}…${t.slice(-4)}(${t.length} 字符)` : '(none)'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { readJson, writeJson } = await import('../core/json-store.js')
|
|
48
|
+
const oauth = createTraeOAuth({ readAuth: () => readJson(AUTH_PATH), writeAuth: (v) => writeJson(AUTH_PATH, v) })
|
|
49
|
+
|
|
50
|
+
const args = process.argv.slice(2)
|
|
51
|
+
const mode = args.includes('--login') ? 'login' : args.includes('--chat') ? 'chat' : args.includes('--sig') ? 'sig' : null
|
|
52
|
+
if (!mode) {
|
|
53
|
+
console.log('用法:--login(真实登录+刷新自证)| --chat "文本"(真实对话,证据落盘)| --sig der|raw(切换签名编码)')
|
|
54
|
+
process.exit(1)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (mode === 'sig') {
|
|
58
|
+
const fmt = args[args.indexOf('--sig') + 1]
|
|
59
|
+
console.log('signatureFormat →', oauth.setSignatureFormat(fmt))
|
|
60
|
+
process.exit(0)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (mode === 'login') {
|
|
64
|
+
const status0 = oauth.oauthStatus()
|
|
65
|
+
if (status0.signedIn) {
|
|
66
|
+
console.log('已有登录态:', status0.account ?? '', '令牌至', new Date(status0.accessTokenExpiresAt ?? 0).toISOString())
|
|
67
|
+
}
|
|
68
|
+
const { authUrl } = await oauth.startOAuth(SETTINGS)
|
|
69
|
+
console.log('\n在浏览器打开并完成登录(10 分钟内有效):\n')
|
|
70
|
+
console.log(authUrl, '\n')
|
|
71
|
+
process.stdout.write('等待回调')
|
|
72
|
+
for (let i = 0; i < 600; i++) {
|
|
73
|
+
await new Promise((r) => setTimeout(r, 1000))
|
|
74
|
+
process.stdout.write('.')
|
|
75
|
+
const s = oauth.oauthStatus()
|
|
76
|
+
if (s.signedIn) {
|
|
77
|
+
console.log('\n\n登录成功 ✓ 账号:', s.account ?? '(GetUserInfo 未返回昵称)')
|
|
78
|
+
console.log('accessToken:', mask(readJson(AUTH_PATH).auth?.accessToken))
|
|
79
|
+
console.log('refreshToken:', mask(readJson(AUTH_PATH).auth?.refreshToken))
|
|
80
|
+
// 立即自证刷新链路(DeviceProof 线上验签)。
|
|
81
|
+
const store = readJson(AUTH_PATH)
|
|
82
|
+
store.auth.expiresAt = Date.now() - 1000 // 强制走刷新
|
|
83
|
+
writeJson(AUTH_PATH, store)
|
|
84
|
+
const cred = await oauth.resolveTraeCredential(SETTINGS)
|
|
85
|
+
if (cred && readJson(AUTH_PATH).auth?.expiresAt > Date.now() + 60_000) {
|
|
86
|
+
console.log('DeviceProof 刷新成功 ✓(自持设备密钥线上可用)新令牌:', mask(readJson(AUTH_PATH).auth.accessToken))
|
|
87
|
+
} else {
|
|
88
|
+
console.log('DeviceProof 刷新失败 ✗ —— 尝试 `node scripts/probe-trae-live.mjs --sig raw` 后重新 --login')
|
|
89
|
+
}
|
|
90
|
+
process.exit(0)
|
|
91
|
+
}
|
|
92
|
+
if (!s.pending && s.error) {
|
|
93
|
+
console.log('\n\n登录失败:', s.error)
|
|
94
|
+
process.exit(1)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
console.log('\n\n超时(10 分钟未完成回调)')
|
|
98
|
+
process.exit(1)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// mode === 'chat'
|
|
102
|
+
const text = args[args.indexOf('--chat') + 1] ?? '你好'
|
|
103
|
+
const cred = await oauth.resolveTraeCredential(SETTINGS)
|
|
104
|
+
if (!cred) {
|
|
105
|
+
console.error('未登录(先跑 --login)。')
|
|
106
|
+
process.exit(1)
|
|
107
|
+
}
|
|
108
|
+
const payload = { model: process.env.TRAE_MODEL ?? 'glm-5.3', stream: true, messages: [{ role: 'user', content: text }] }
|
|
109
|
+
const { body: reqBody, requestId } = buildChatRequest(payload, 'probe-live')
|
|
110
|
+
const token = String(cred.authorization).replace(/^Cloud-IDE-JWT\s+/, '')
|
|
111
|
+
const authStore = readJson(AUTH_PATH)
|
|
112
|
+
const res = await fetch(`${SETTINGS.traeChatBaseURL}/api/agent/v3/llm_utils_chat`, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: {
|
|
115
|
+
'Content-Type': 'application/json',
|
|
116
|
+
Accept: 'text/event-stream',
|
|
117
|
+
Authorization: `Cloud-IDE-JWT ${token}`,
|
|
118
|
+
'X-Cloudide-Token': token,
|
|
119
|
+
'x-ide-token': token,
|
|
120
|
+
'x-app-id': TRAE_APP_ID,
|
|
121
|
+
'x-ide-version': TRAE_IDE_VERSION,
|
|
122
|
+
'x-ide-version-code': TRAE_IDE_VERSION_CODE,
|
|
123
|
+
'x-request-id': requestId,
|
|
124
|
+
...(authStore.account?.uid ? { 'x-uid': String(authStore.account.uid) } : {}),
|
|
125
|
+
...(authStore.device?.deviceId ? { 'x-device-id': authStore.device.deviceId } : {}),
|
|
126
|
+
...(authStore.device?.machineId ? { 'x-machine-id': authStore.device.machineId } : {}),
|
|
127
|
+
},
|
|
128
|
+
body: JSON.stringify(reqBody),
|
|
129
|
+
})
|
|
130
|
+
const raw = await res.text()
|
|
131
|
+
const outPath = `docs/probes/trae-chat-live-${Date.now()}.json`
|
|
132
|
+
mkdirSync('docs/probes', { recursive: true })
|
|
133
|
+
writeFileSync(outPath, JSON.stringify({
|
|
134
|
+
at: new Date().toISOString(),
|
|
135
|
+
request: { url: `${SETTINGS.traeChatBaseURL}/api/agent/v3/llm_utils_chat`, body: reqBody },
|
|
136
|
+
response: {
|
|
137
|
+
status: res.status,
|
|
138
|
+
contentType: res.headers.get('content-type'),
|
|
139
|
+
headers: Object.fromEntries([...res.headers].filter(([k]) => !k.includes('token') && !k.includes('authorization'))),
|
|
140
|
+
bodyHead: raw.slice(0, 8000),
|
|
141
|
+
bodyBytes: raw.length,
|
|
142
|
+
},
|
|
143
|
+
}, null, 2))
|
|
144
|
+
console.log(`HTTP ${res.status}(${res.headers.get('content-type')}),body ${raw.length} 字节`)
|
|
145
|
+
console.log('证据落盘 →', outPath)
|
|
146
|
+
console.log('\n--- body 前 1200 字节 ---')
|
|
147
|
+
console.log(raw.slice(0, 1200))
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 探测 Trae remote 通道是否接受 reasoning_effort=max 拼写。
|
|
3
|
+
*
|
|
4
|
+
* 目录只声明了 light/high/extra_high,但上游(trae-api-cn.mchost.guru)可能
|
|
5
|
+
* 也接受 max(CodeBuddy 侧的拼写)。本脚本用本地存储的 Trae OAuth token,
|
|
6
|
+
* 向 remote 会话端点发 reasoning_effort=max 的创建请求,看是否 200 + 正常事件流。
|
|
7
|
+
*
|
|
8
|
+
* 用法:node scripts/probe-trae-max-effort.mjs
|
|
9
|
+
* --model deepseek-v4.1-flash (默认)
|
|
10
|
+
* --base https://trae-api-cn.mchost.guru
|
|
11
|
+
* --effort max (可重复,默认 max + extra_high 对照)
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync } from 'node:fs'
|
|
14
|
+
import { homedir } from 'node:os'
|
|
15
|
+
import { join } from 'node:path'
|
|
16
|
+
|
|
17
|
+
const args = Object.fromEntries(process.argv.slice(2).reduce((acc, a, i, arr) => {
|
|
18
|
+
if (a.startsWith('--')) {
|
|
19
|
+
const k = a.slice(2)
|
|
20
|
+
const v = arr[i + 1]
|
|
21
|
+
if (v && !v.startsWith('--')) { acc.push([k, v]); arr.splice(i + 1, 1) }
|
|
22
|
+
else acc.push([k, 'true'])
|
|
23
|
+
}
|
|
24
|
+
return acc
|
|
25
|
+
}, []))
|
|
26
|
+
|
|
27
|
+
const MODEL = args.model || 'deepseek-v4.1-flash'
|
|
28
|
+
const BASE = args.base || 'https://trae-api-cn.mchost.guru'
|
|
29
|
+
const EFFORTS = args.effort
|
|
30
|
+
? [args.effort].flat()
|
|
31
|
+
: ['max', 'extra_high'] // max = 待测;extra_high = 对照
|
|
32
|
+
|
|
33
|
+
const authPath = join(homedir(), '.dsh', 'trae-plugin-auth.json')
|
|
34
|
+
const authStore = JSON.parse(readFileSync(authPath, 'utf8'))
|
|
35
|
+
const token = authStore.auth?.accessToken
|
|
36
|
+
if (!token) { console.error('NO_TOKEN'); process.exit(1) }
|
|
37
|
+
|
|
38
|
+
const WEB_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36'
|
|
39
|
+
const headers = (stream) => ({
|
|
40
|
+
Authorization: `Cloud-IDE-JWT ${token}`,
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'X-Trae-Client-Type': 'web',
|
|
43
|
+
'X-Preferenced-Language': 'zh-CN',
|
|
44
|
+
'x-user-region': 'CN',
|
|
45
|
+
Origin: 'https://solo.trae.cn',
|
|
46
|
+
Referer: 'https://solo.trae.cn/',
|
|
47
|
+
'User-Agent': WEB_UA,
|
|
48
|
+
Accept: stream ? 'text/event-stream' : 'application/json',
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const commonParams = JSON.stringify({
|
|
52
|
+
language: 'zh-cn', app_language: 'zh-CN', quality: 'stable',
|
|
53
|
+
app_version: '1.0.0.1229', web_id: '', user_identity: 'Free',
|
|
54
|
+
is_freshman: '0', biz_user_id: '', user_unique_id: '',
|
|
55
|
+
scope: 'marscode-cn', tenant: 'marscode', region: 'cn', aiRegion: 'cn',
|
|
56
|
+
is_privacy_mode: 0, privacy_mode: 'off', solo_chat_mode: 'code',
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
function buildBody(model, effort) {
|
|
60
|
+
const im = {
|
|
61
|
+
chat_session_id: '', content: [],
|
|
62
|
+
query: JSON.stringify([{ type: 'text', data: { content: '一个笼子里有鸡和兔共 10 只,脚共 28 只。鸡兔各几只?只给最终数字。' } }]),
|
|
63
|
+
model_name: model, agent_type: 'solo_agent_remote',
|
|
64
|
+
model_selection_strategy: 'manual', common_params: commonParams,
|
|
65
|
+
}
|
|
66
|
+
if (effort) {
|
|
67
|
+
im.custom_model = {
|
|
68
|
+
model_name: model, config_name: model, config_source: 1,
|
|
69
|
+
is_preset: true, use_remote_service: true, multimodal: false,
|
|
70
|
+
reasoning_effort: effort,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { mode: 'code', environment_id: 'default', initial_message: im, env: 'remote', auto_create_project: false, origin: 'web' }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function probe(effort) {
|
|
77
|
+
const label = effort || 'none(omit)'
|
|
78
|
+
const t0 = Date.now()
|
|
79
|
+
try {
|
|
80
|
+
// 1. create session
|
|
81
|
+
const createResp = await fetch(`${BASE}/api/remote/v1/chat_sessions`, {
|
|
82
|
+
method: 'POST', headers: headers(false),
|
|
83
|
+
body: JSON.stringify(buildBody(MODEL, effort)),
|
|
84
|
+
signal: AbortSignal.timeout(20_000),
|
|
85
|
+
})
|
|
86
|
+
const createText = await createResp.text()
|
|
87
|
+
if (createResp.status >= 400) {
|
|
88
|
+
return { effort: label, createHttp: createResp.status, createBody: createText.slice(0, 300), ms: Date.now() - t0 }
|
|
89
|
+
}
|
|
90
|
+
let data
|
|
91
|
+
try { data = JSON.parse(createText) } catch {
|
|
92
|
+
return { effort: label, createHttp: createResp.status, error: 'non-json create', ms: Date.now() - t0 }
|
|
93
|
+
}
|
|
94
|
+
if (data.code != null && data.code !== 0 && !data.data) {
|
|
95
|
+
return { effort: label, createHttp: createResp.status, bizCode: data.code, bizMsg: data.message, ms: Date.now() - t0 }
|
|
96
|
+
}
|
|
97
|
+
const payload = data.data ?? data
|
|
98
|
+
const sessionId = String(payload.chat_session_id ?? '')
|
|
99
|
+
const messageId = String(payload.message_id ?? '')
|
|
100
|
+
if (!sessionId || !messageId) {
|
|
101
|
+
return { effort: label, createHttp: createResp.status, error: 'missing session/message id', ms: Date.now() - t0 }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 2. open events, read entire stream to EOF (like probe-trae-thinking-scene)
|
|
105
|
+
const eventsResp = await fetch(
|
|
106
|
+
`${BASE}/api/remote/v1/chat_sessions/${sessionId}/events?reply_to_message_id=${messageId}`,
|
|
107
|
+
{ headers: headers(true) },
|
|
108
|
+
)
|
|
109
|
+
if (eventsResp.status >= 400) {
|
|
110
|
+
const evText = await eventsResp.text().catch(() => '')
|
|
111
|
+
return { effort: label, createHttp: 200, eventsHttp: eventsResp.status, eventsBody: evText.slice(0, 300), ms: Date.now() - t0 }
|
|
112
|
+
}
|
|
113
|
+
const raw = await eventsResp.text()
|
|
114
|
+
let reasoningChars = 0, textChars = 0, actualModel = null, finish = null, bytes = raw.length
|
|
115
|
+
const eventTypes = {}
|
|
116
|
+
let eventSamples = null
|
|
117
|
+
let lastEventName = null
|
|
118
|
+
// also capture raw SSE lines for debugging
|
|
119
|
+
let rawLines = []
|
|
120
|
+
for (const line of raw.split('\n')) {
|
|
121
|
+
const s = line.trim()
|
|
122
|
+
if (!s) { lastEventName = null; continue }
|
|
123
|
+
if (s.startsWith('event:')) { lastEventName = s.slice(6).trim(); continue }
|
|
124
|
+
if (s.startsWith('id:') || s.startsWith(':')) continue
|
|
125
|
+
if (!s.startsWith('data:')) continue
|
|
126
|
+
const dataStr = s.slice(5).trim()
|
|
127
|
+
if (!dataStr) continue
|
|
128
|
+
if (dataStr === '[DONE]') { finish = finish || 'done'; continue }
|
|
129
|
+
let ev
|
|
130
|
+
try { ev = JSON.parse(dataStr) } catch { continue }
|
|
131
|
+
// use .event field (production parser way) or SSE event: line
|
|
132
|
+
const eventName = (typeof ev.event === 'string' && ev.event) ? ev.event : lastEventName
|
|
133
|
+
const et = eventName || (ev.model_config ? 'model_config'
|
|
134
|
+
: ev.plan_item ? 'plan_item'
|
|
135
|
+
: ev.done ? 'done'
|
|
136
|
+
: ev.error ? 'error'
|
|
137
|
+
: 'other')
|
|
138
|
+
eventTypes[et] = (eventTypes[et] || 0) + 1
|
|
139
|
+
if (!eventSamples) eventSamples = []
|
|
140
|
+
if (eventSamples.length < 8) eventSamples.push({ event: eventName || null, data: dataStr.slice(0, 250) })
|
|
141
|
+
if (rawLines.length < 30) rawLines.push(s.slice(0, 200))
|
|
142
|
+
if (eventName === 'model_config' || ev.model_config) {
|
|
143
|
+
if (typeof ev.config_name === 'string') actualModel = ev.config_name
|
|
144
|
+
}
|
|
145
|
+
if (eventName === 'plan_item' || ev.plan_item) {
|
|
146
|
+
const pi = ev.plan_item || ev
|
|
147
|
+
if (typeof pi.reasoning_content === 'string') reasoningChars += pi.reasoning_content.length
|
|
148
|
+
if (typeof pi.thought === 'string') textChars += pi.thought.length
|
|
149
|
+
if (pi.tool_call_info?.name === 'finish') {
|
|
150
|
+
finish = 'finish'
|
|
151
|
+
const sp = pi.tool_call_info?.params?.summary
|
|
152
|
+
if (typeof sp === 'string') textChars += sp.length
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (eventName === 'done' || ev.done) finish = finish || 'done'
|
|
156
|
+
if (eventName === 'error' || ev.error) { return { effort: label, createHttp: 200, eventsHttp: 200, streamError: ev.error || ev, eventTypes, eventSamples, rawLines, ms: Date.now() - t0 } }
|
|
157
|
+
}
|
|
158
|
+
return { effort: label, createHttp: 200, eventsHttp: 200, reasoningChars, textChars, actualModel, finish, bytes, eventTypes, eventSamples, rawLines, ms: Date.now() - t0 }
|
|
159
|
+
} catch (e) {
|
|
160
|
+
return { effort: label, error: e?.message ?? String(e), ms: Date.now() - t0 }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log(`Trae max-effort probe: model=${MODEL} base=${BASE} efforts=${EFFORTS.join(',')}`)
|
|
165
|
+
const results = []
|
|
166
|
+
for (let i = 0; i < EFFORTS.length; i++) {
|
|
167
|
+
if (i > 0) { console.log(' (waiting 10s for session slot...)'); await new Promise(r => setTimeout(r, 10_000)) }
|
|
168
|
+
const r = await probe(EFFORTS[i])
|
|
169
|
+
results.push(r)
|
|
170
|
+
console.log(JSON.stringify(r))
|
|
171
|
+
}
|
|
172
|
+
console.log('\n--- summary ---')
|
|
173
|
+
for (const r of results) {
|
|
174
|
+
const ok = r.createHttp === 200 && r.eventsHttp === 200 && !r.streamError && !r.error
|
|
175
|
+
const verdict = ok
|
|
176
|
+
? (r.reasoningChars > 0 ? `ACCEPTED (reasoning ${r.reasoningChars} chars)` : 'ACCEPTED (no reasoning)')
|
|
177
|
+
: `REJECTED (${r.error || r.bizCode || r.createHttp})`
|
|
178
|
+
console.log(` ${r.effort}: ${verdict}`)
|
|
179
|
+
}
|