/* trae-shim v2 标记状态机单元测试(复制自 trae-shim.mjs 纯函数段,验证后即弃) * 覆盖 2026-10-08 故障报告全部失败形态 + 跨帧边界 + 内嵌标记炸弹 */ const M_BEGIN = '<|FunctionCallBegin|>'; const M_END = '<|FunctionCallEnd|>'; const BEGIN_ALT = ''; const BEGIN_ALL = [M_BEGIN, BEGIN_ALT]; function logEvent(kind, detail) { console.log(' [evt]', kind, String(detail || '').slice(0, 120)); } function markerGenId() { return 'call_' + Math.random().toString(36).slice(2, 12); } function repairJsonText(s) { const stack = []; let inStr = false, esc = false; let out = ''; for (const ch of s) { if (inStr) { if (esc) { esc = false; out += ch; continue; } if (ch === '\\') { esc = true; out += ch; continue; } if (ch === '"') { inStr = false; out += ch; continue; } const code = ch.charCodeAt(0); if (code < 0x20) { out += code === 0x0a ? '\\n' : code === 0x0d ? '\\r' : code === 0x09 ? '\\t' : '\\u' + code.toString(16).padStart(4, '0'); continue; } out += ch; continue; } if (ch === '"') { inStr = true; out += ch; continue; } if (ch === '[' || ch === '{') stack.push(ch); else if (ch === ']' || ch === '}') stack.pop(); out += ch; } if (inStr) out += '"'; while (stack.length) out += stack.pop() === '[' ? ']' : '}'; return out; } function extractCallItems(parsed) { let arr = parsed; if (!Array.isArray(arr)) arr = [arr]; const items = []; for (const entry of arr) { if (!entry || typeof entry !== 'object' || !entry.name) continue; let params; if (entry.parameters !== undefined && entry.parameters !== null) { params = typeof entry.parameters === 'string' ? entry.parameters : JSON.stringify(entry.parameters); } else if (entry.arguments !== undefined && entry.arguments !== null) { params = typeof entry.arguments === 'string' ? entry.arguments : JSON.stringify(entry.arguments); } else { const rest = { ...entry }; delete rest.name; params = Object.keys(rest).length ? JSON.stringify(rest) : '{}'; } items.push({ name: String(entry.name), args: params }); } return items.length ? items : null; } function extractRawJsonValue(s, start) { let i = start; while (i < s.length && /\s/.test(s[i])) i++; if (i >= s.length) return null; if (s[i] === '"') { let j = i + 1; while (j < s.length) { if (s[j] === '\\') { j += 2; continue; } if (s[j] === '"') return s.slice(i, j + 1); j++; } return null; } if (s[i] === '{' || s[i] === '[') { const stack = []; let inStr = false, esc = false; for (let j = i; j < s.length; j++) { const c = s[j]; if (inStr) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue; } if (c === '"') { inStr = true; continue; } if (c === '[' || c === '{') stack.push(c); else if (c === ']' || c === '}') stack.pop(); if (!stack.length) return s.slice(i, j + 1); } return null; } return null; } function salvageCallItems(payload) { const p = String(payload ?? ''); try { const r = extractCallItems(JSON.parse(p)); if (r) return r; } catch { /* try repairs */ } const t = p.trim(); if (!t.startsWith('[') && !t.startsWith('{')) return null; try { const r = extractCallItems(JSON.parse(repairJsonText(t))); if (r) return r; } catch { /* last resort */ } const m = t.match(/"name"\s*:\s*"((?:[^"\\]|\\.)*)"/); if (m) { let args = '{}'; const pi = t.indexOf('"parameters"'); const ai = t.indexOf('"arguments"'); const keyIdx = [pi, ai].filter((x) => x >= 0).sort((a, b) => a - b)[0]; if (keyIdx != null) { const colon = t.indexOf(':', keyIdx); const rawVal = colon >= 0 ? extractRawJsonValue(t, colon + 1) : null; if (rawVal != null) args = rawVal; } else { const rest = {}; try { const obj = JSON.parse(repairJsonText(t)); const e = Array.isArray(obj) ? obj[0] : obj; if (e && typeof e === 'object') { for (const k of Object.keys(e)) if (k !== 'name') rest[k] = e[k]; } } catch { /* keep {} */ } if (Object.keys(rest).length) args = JSON.stringify(rest); } logEvent('marker_salvage', 'regex name=' + m[1]); return [{ name: m[1], args }]; } return null; } function newMarkerState() { return { mode: 'text', buf: '', idx: 0 }; } function markerEmitParsed(st, payload) { const salvaged = salvageCallItems(payload); if (!salvaged) return null; return salvaged.map((it) => ({ type: 'tool_call', marker: true, raw: payload, value: { index: st.idx++, id: markerGenId(), name: it.name, args: it.args }, })); } function findEndOutsideStrings(s) { let inStr = false, esc = false; for (let i = 0; i < s.length; i++) { const c = s[i]; if (inStr) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue; } if (c === '"') { inStr = true; continue; } if (c === '<' && s.startsWith(M_END, i)) return i; } return -1; } function feedText(st, text) { const out = []; st.buf += text; while (true) { if (st.mode === 'text') { let hit = -1, hitLen = 0; for (const b of BEGIN_ALL) { const i = st.buf.indexOf(b); if (i >= 0 && (hit < 0 || i < hit)) { hit = i; hitLen = b.length; } } if (hit >= 0) { if (hit > 0) out.push({ type: 'text', value: st.buf.slice(0, hit) }); st.buf = st.buf.slice(hit + hitLen); st.mode = 'marker'; continue; } const maxKeep = Math.min(st.buf.length, M_BEGIN.length - 1); let keepLen = 0; for (let l = maxKeep; l > 0; l--) { const suf = st.buf.slice(st.buf.length - l); if (BEGIN_ALL.some((b) => b.startsWith(suf))) { keepLen = l; break; } } const emitLen = st.buf.length - keepLen; if (emitLen > 0) { out.push({ type: 'text', value: st.buf.slice(0, emitLen) }); st.buf = st.buf.slice(emitLen); } break; } else { const t = st.buf.trimStart(); const j = (t.startsWith('{') || t.startsWith('[')) ? findEndOutsideStrings(st.buf) : st.buf.indexOf(M_END); if (j >= 0) { const payload = st.buf.slice(0, j); st.buf = st.buf.slice(j + M_END.length); st.mode = 'text'; const items = markerEmitParsed(st, payload); if (items) out.push(...items); else out.push({ type: 'text', value: M_BEGIN + payload + M_END }); continue; } if (st.buf.length > 262144) { const items = markerEmitParsed(st, st.buf); if (items) out.push(...items); else out.push({ type: 'text', value: M_BEGIN + st.buf }); st.buf = ''; st.mode = 'text'; break; } break; } } return out; } function flushMarker(st) { if (!st.buf) return []; const rest = st.buf; st.buf = ''; if (st.mode === 'text') return [{ type: 'text', value: rest }]; const items = markerEmitParsed(st, rest); if (items) return items; return [{ type: 'text', value: M_BEGIN + rest }]; } /* ---------- 测试骨架 ---------- */ let pass = 0, fail = 0; function check(name, cond, extra) { if (cond) { pass++; console.log('PASS', name); } else { fail++; console.log('FAIL', name, extra !== undefined ? JSON.stringify(extra).slice(0, 300) : ''); } } function runFeed(chunks, allowed) { const st = newMarkerState(); const items = []; for (const c of chunks) items.push(...feedText(st, c)); items.push(...flushMarker(st)); /* 模拟 normalizeToolStream 的白名单过滤 */ const out = []; for (const it of items) { if (it.type === 'tool_call' && it.marker && allowed && allowed.size && !allowed.has(it.value.name)) { out.push({ type: 'text', value: M_BEGIN + (it.raw || '') + M_END }); continue; } out.push(it); } return out; } const calls = (items) => items.filter((x) => x.type === 'tool_call'); const texts = (items) => items.filter((x) => x.type === 'text').map((x) => x.value).join(''); /* 1. 纯文本 */ let r = runFeed(['hello world']); check('1 纯文本透传', r.length === 1 && r[0].type === 'text' && r[0].value === 'hello world', r); /* 2. 完整单调用 */ r = runFeed(['A' + M_BEGIN + '[{"name":"write","parameters":{"file_path":"x"}}]' + M_END + 'B']); check('2 完整调用抽取', r.length === 3 && calls(r).length === 1 && calls(r)[0].value.name === 'write' && calls(r)[0].value.args === '{"file_path":"x"}' && texts(r) === 'AB', r); /* 3. 逐字符跨帧(SSE 分块切断标记) */ const full = '前文' + M_BEGIN + '[{"name":"bash","parameters":{"cmd":"ls"}}]' + M_END + '后文'; const pieces = []; for (let i = 0; i < full.length; i += 3) pieces.push(full.slice(i, i + 3)); r = runFeed(pieces); check('3 跨帧边界不切断', calls(r).length === 1 && calls(r)[0].value.name === 'bash' && calls(r)[0].value.args === '{"cmd":"ls"}' && texts(r) === '前文后文', r); /* 4. 内嵌标记炸弹(写故障报告场景:参数内容本身含 <|FunctionCallEnd|>) */ const bomb = M_BEGIN + '[{"name":"write","parameters":{"file_path":"a.md","content":"report ' + M_BEGIN + '[x]' + M_END + ' end"}}]' + M_END; r = runFeed([bomb]); check('4 内嵌标记不腰斩', calls(r).length === 1 && calls(r)[0].value.name === 'write' && calls(r)[0].value.args.includes(M_END) && calls(r)[0].value.args.includes('report') && texts(r) === '', r); /* 5. 缺 parameters 包装层(故障报告实测畸形形态) */ r = runFeed([M_BEGIN + '[{"name":"write","file_path":"C:\\\\x.txt","content":"hi"}]' + M_END]); const c5 = calls(r)[0]; let a5 = {}; try { a5 = JSON.parse(c5.value.args); } catch { /* noop */ } check('5 缺层参数回包', calls(r).length === 1 && a5.file_path === 'C:\\x.txt' && a5.content === 'hi', r); /* 6. 截断标记(max_tokens 中断)flush 抢救 */ r = runFeed(['text ' + M_BEGIN + '[{"name":"write","parameters":{"file_path":"a","content":"partial with \\"quotes and {braces']); check('6 截断 flush 抢救', calls(r).length === 1 && calls(r)[0].value.name === 'write', r); let a6 = {}; try { a6 = JSON.parse(calls(r)[0].value.args); fail && a6; } catch { a6 = null; } check('6b 抢救出的 args 是合法 JSON', a6 !== null && typeof a6 === 'object', calls(r)[0] && calls(r)[0].value.args); /* 7. 字符串里裸换行(畸形 JSON 修复) */ r = runFeed([M_BEGIN + '[{"name":"write","parameters":{"content":"line1\nline2"}}]' + M_END]); let a7 = null; try { a7 = JSON.parse(calls(r)[0].value.args); } catch { /* noop */ } check('7 裸控制字符修复', calls(r).length === 1 && a7 && a7.content === 'line1\nline2', r); /* 8. DSH 宿主协议形态(竖线在右的起始标记 + arguments 键) */ r = runFeed([BEGIN_ALT + '{"name":"bash","arguments":"echo hi"}' + M_END]); check('8 DSH 协议形态解析', calls(r).length === 1 && calls(r)[0].value.name === 'bash' && calls(r)[0].value.args === 'echo hi', r); /* 9. 白名单过滤:非 tools 里的 name 降级为文本 */ r = runFeed([M_BEGIN + '[{"name":"fake_tool","parameters":{}}]' + M_END], new Set(['write'])); check('9 白名单外降级文本', calls(r).length === 0 && texts(r).includes('fake_tool'), r); /* 10. 纯散文示例(无法解析)透传为文本 */ r = runFeed(['协议形如 ' + M_BEGIN + 'not json at all' + M_END + ' 即可']); check('10 散文透传', calls(r).length === 0 && texts(r).includes('not json at all'), r); /* 11. 参数内含嵌套引号/反斜杠的 Windows 脚本 + 内容含完整标记炸弹(故障报告场景,合法 JSON) */ const bombContent = "$roots = @('C:\\x')\nSelect-String -Pattern '" + M_BEGIN + "[x]" + M_END + "'"; const inner = { name: 'write', parameters: { file_path: 'C:\\Users\\a\\find.ps1', content: bombContent } }; const win = M_BEGIN + JSON.stringify([inner]) + M_END; const st11 = newMarkerState(); const out11 = [...feedText(st11, win.slice(0, 40)), ...feedText(st11, win.slice(40)), ...flushMarker(st11)]; const c11 = calls(out11)[0]; let a11 = null; try { a11 = JSON.parse(c11.value.args); } catch { /* noop */ } check('11 Windows 深层转义 + 内容含标记炸弹', c11 && a11 && a11.file_path === 'C:\\Users\\a\\find.ps1' && a11.content === bombContent && texts(out11) === '', out11); check('11b 内容原样保留(含起止标记)', a11 && a11.content.includes(M_BEGIN) && a11.content.includes(M_END), a11); /* 12. 起始标记跨帧且与另一形态重叠 */ r = runFeed(['xx<', '|FunctionCall', 'Begin|>[{"name":"write","parameters":{}}]', M_END]); check('12 起始标记跨帧(|开头)', calls(r).length === 1 && calls(r)[0].value.name === 'write' && texts(r) === 'xx', r); console.log('\n===== ' + pass + ' passed, ' + fail + ' failed ====='); process.exit(fail ? 1 : 0);