// v2.0.65 (#115) — Cascade native tool bridge. // // Validates that: // 1. canMapAllTools correctly admits supported tools and rejects mixed/ // unknown sets so emulation fallback fires. // 2. Forward + reverse argument translators round-trip per known tool. // 3. shouldUseNativeBridge is default-off (auto-on was removed). Explicit // WINDSURFAPI_NATIVE_TOOL_BRIDGE=1 / all_mapped still work; OFF wins. // 4. buildAdditionalStepsFromHistory produces decodable trajectory step // protos when prior assistant tool_calls + tool results are present. // 5. windsurf.parseTrajectorySteps surfaces native cascade step kinds // (view_file=14, run_command=28, grep_search_v2=105, find=34, // list_directory=15, write_to_file=23) as toolCalls with // cascade_native:true and the right name + args. // 6. buildSendCascadeMessageRequest writes additional_steps to field 9 // (repeated CortexTrajectoryStep). import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { TOOL_MAP, CASCADE_STEP, canMapAllTools, partitionTools, shouldUseNativeBridge, buildAdditionalStep, buildAdditionalStepsFromHistory, buildNativeToolHistory, buildReverseLookup, parseNativeFunctionCallsFromText, NativeFunctionCallStreamParser, } from '../src/cascade-native-bridge.js'; import { normalizeMessagesForCascade } from '../src/handlers/tool-emulation.js'; import { parseTrajectorySteps, buildSendCascadeMessageRequest, } from '../src/windsurf.js'; import { parseFields, getField, getAllFields, writeMessageField, writeVarintField, writeStringField, } from '../src/proto.js'; import { getSystemPrompts, resetSystemPrompt, setSystemPrompts } from '../src/runtime-config.js'; const fnTool = (name) => ({ type: 'function', function: { name, parameters: { type: 'object' } } }); describe('canMapAllTools', () => { it('admits only mature default-native tools without an explicit tool allowlist', () => { const prev = process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS; delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS; try { assert.equal(canMapAllTools([fnTool('Bash'), fnTool('shell_command'), fnTool('run_command')]), true); assert.equal(canMapAllTools([fnTool('Read'), fnTool('Bash'), fnTool('Glob')]), false); } finally { if (prev === undefined) delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS; else process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS = prev; } }); it('rejects when ANY tool is unmapped', () => { assert.equal(canMapAllTools([fnTool('Read'), fnTool('get_weather')]), false); }); it('rejects empty / non-array input', () => { assert.equal(canMapAllTools([]), false); assert.equal(canMapAllTools(null), false); assert.equal(canMapAllTools(undefined), false); }); it('admits Codex-style command names by default and other native names only when allowlisted', () => { const prev = process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS; delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS; try { assert.equal(canMapAllTools([fnTool('run_command')]), true); assert.equal(canMapAllTools([fnTool('view_file'), fnTool('run_command'), fnTool('find')]), false); process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS = 'view_file,run_command,find'; assert.equal(canMapAllTools([fnTool('view_file'), fnTool('run_command'), fnTool('find')]), true); } finally { if (prev === undefined) delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS; else process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS = prev; } }); it('admits mixed Claude Code + Codex names when explicitly allowlisted', () => { const prev = process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS; process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS = 'Read,run_command,Grep'; try { assert.equal(canMapAllTools([fnTool('Read'), fnTool('run_command'), fnTool('Grep')]), true); } finally { if (prev === undefined) delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS; else process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS = prev; } }); }); describe('shouldUseNativeBridge — default-off + explicit gates', () => { const tools = [fnTool('Read'), fnTool('Bash')]; it('GPT family on /v1/responses route → OFF (v2.0.70 reverts auto-on for GPT — cascade native grammar makes GPT fabricate)', () => { // v2.0.66 had GPT auto-on, v2.0.70 reverts after end-to-end probe // showed markers=none and PROBE fabricated. GPT family now goes // through emulation + gpt_native dialect. assert.equal( shouldUseNativeBridge(tools, { modelKey: 'gpt-5.5-medium', provider: 'openai', route: 'responses' }), false, ); assert.equal( shouldUseNativeBridge(tools, { modelKey: 'o4-mini', provider: 'openai', route: 'responses' }), false, ); }); it('GPT family on /v1/chat/completions → off (default emulation path)', () => { assert.equal( shouldUseNativeBridge(tools, { modelKey: 'gpt-5.5-medium', provider: 'openai', route: 'chat' }), false, ); }); it('Anthropic Claude → OFF by default (v2.0.75 #124 regression fix — bridge runs tools in REMOTE cascade sandbox, but client tools expect LOCAL execution; hang forever)', () => { assert.equal( shouldUseNativeBridge(tools, { modelKey: 'claude-sonnet-4.6', provider: 'anthropic', route: 'responses' }), false, ); assert.equal( shouldUseNativeBridge(tools, { modelKey: 'claude-sonnet-4.6', provider: 'anthropic', route: 'chat' }), false, ); }); it('Gemini → off (no auto-on — emulation works fine for it)', () => { assert.equal( shouldUseNativeBridge(tools, { modelKey: 'gemini-2.5-flash', provider: 'google', route: 'responses' }), false, ); }); it('partial-mapped tools on Claude → OFF (post-v2.0.75 — same #124 logic, no client wants remote execution by default)', () => { assert.equal( shouldUseNativeBridge([fnTool('Read'), fnTool('get_weather')], { modelKey: 'claude-sonnet-4.6', provider: 'anthropic', route: 'responses', }), false, ); }); it('zero mapped tools → off (no point booting native path)', () => { assert.equal( shouldUseNativeBridge([fnTool('get_weather'), fnTool('update_plan')], { modelKey: 'gpt-5.5-medium', provider: 'openai', route: 'responses', }), false, ); }); it('explicit env override forces on for any mapped tool set (deployer opting into remote execution)', () => { const orig = process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE; const toolsOrig = process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS; process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE = '1'; process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS = 'Read,Bash'; try { assert.equal( shouldUseNativeBridge(tools, { modelKey: 'claude-sonnet-4-6', provider: 'anthropic', route: 'chat' }), true, ); // GPT too, when explicitly enabled. assert.equal( shouldUseNativeBridge(tools, { modelKey: 'gpt-5.5-medium', provider: 'openai', route: 'responses' }), true, ); } finally { if (orig === undefined) delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE; else process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE = orig; if (toolsOrig === undefined) delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS; else process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS = toolsOrig; } }); it('all_mapped mode enables only when every function tool maps', () => { const orig = process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE; const toolsOrig = process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS; process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE = 'all_mapped'; process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS = 'Read,Bash,Grep,Glob'; try { assert.equal( shouldUseNativeBridge([fnTool('Read'), fnTool('Bash'), fnTool('Grep'), fnTool('Glob')], { modelKey: 'claude-sonnet-4.6', provider: 'anthropic', route: 'chat', }), true, ); assert.equal( shouldUseNativeBridge([fnTool('Read'), fnTool('Bash'), fnTool('update_plan')], { modelKey: 'claude-sonnet-4.6', provider: 'anthropic', route: 'chat', }), false, ); } finally { if (orig === undefined) delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE; else process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE = orig; if (toolsOrig === undefined) delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS; else process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS = toolsOrig; } }); it('OFF override beats explicit on', () => { const onOrig = process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE; const offOrig = process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_OFF; process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE = '1'; process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_OFF = '1'; try { assert.equal( shouldUseNativeBridge(tools, { modelKey: 'gpt-5.5-medium', provider: 'openai', route: 'responses' }), false, ); } finally { if (onOrig === undefined) delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE; else process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE = onOrig; if (offOrig === undefined) delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_OFF; else process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_OFF = offOrig; } }); }); describe('TOOL_MAP — forward / reverse round-trip per kind', () => { it('Read ↔ view_file preserves file_path / offset / limit', () => { const original = { file_path: '/abs/path/foo.ts', offset: 10, limit: 200 }; const cascade = TOOL_MAP.Read.forward(original); assert.equal(cascade.absolute_path_uri, 'file:///abs/path/foo.ts'); assert.equal(cascade.offset, 10); assert.equal(cascade.limit, 200); const back = TOOL_MAP.Read.reverse(cascade); assert.deepEqual(back, original); }); it('Bash ↔ run_command preserves command (and cwd if present)', () => { const original = { command: 'npm test --silent' }; const cascade = TOOL_MAP.Bash.forward(original); assert.equal(cascade.command_line, 'npm test --silent'); const back = TOOL_MAP.Bash.reverse(cascade); assert.equal(back.command, 'npm test --silent'); // cwd round-trip const withCwd = TOOL_MAP.Bash.forward({ command: 'ls', cwd: '/tmp' }); assert.equal(TOOL_MAP.Bash.reverse(withCwd).cwd, '/tmp'); }); it('Grep ↔ grep_search_v2 preserves pattern + flags', () => { const original = { pattern: 'foo', '-i': true, head_limit: 50, glob: '*.js' }; const cascade = TOOL_MAP.Grep.forward(original); assert.equal(cascade.pattern, 'foo'); assert.equal(cascade.case_insensitive, true); assert.equal(cascade.head_limit, 50); assert.equal(cascade.glob, '*.js'); const back = TOOL_MAP.Grep.reverse(cascade); assert.equal(back.pattern, 'foo'); assert.equal(back['-i'], true); assert.equal(back.head_limit, 50); assert.equal(back.glob, '*.js'); }); it('Glob ↔ find preserves pattern + path', () => { const cascade = TOOL_MAP.Glob.forward({ pattern: '**/*.ts', path: 'src' }); assert.equal(cascade.pattern, '**/*.ts'); assert.equal(cascade.search_directory, 'src'); const back = TOOL_MAP.Glob.reverse(cascade); assert.equal(back.pattern, '**/*.ts'); assert.equal(back.path, 'src'); }); it('Write ↔ write_to_file preserves file_path + content', () => { const cascade = TOOL_MAP.Write.forward({ file_path: '/tmp/x.txt', content: 'hello\n' }); assert.equal(cascade.target_file_uri, 'file:///tmp/x.txt'); assert.deepEqual(cascade.code_content, ['hello\n']); const back = TOOL_MAP.Write.reverse(cascade); assert.equal(back.file_path, '/tmp/x.txt'); assert.equal(back.content, 'hello\n'); }); }); describe('buildReverseLookup', () => { it('inverts caller tools by cascade kind', () => { const lookup = buildReverseLookup([fnTool('Read'), fnTool('Bash'), fnTool('Grep')]); assert.deepEqual(lookup.get('view_file'), ['Read']); assert.deepEqual(lookup.get('run_command'), ['Bash']); assert.deepEqual(lookup.get('grep_search_v2'), ['Grep']); }); it('returns empty map for empty input', () => { const lookup = buildReverseLookup([]); assert.equal(lookup.size, 0); }); it('handles caller declaring multiple tools that map to same kind', () => { const lookup = buildReverseLookup([fnTool('Read'), fnTool('view_file'), fnTool('read_file')]); const list = lookup.get('view_file'); assert.ok(list.includes('Read')); assert.ok(list.includes('view_file')); assert.ok(list.includes('read_file')); }); }); describe('parseNativeFunctionCallsFromText', () => { it('maps Claude provider-native read_file invoke back to caller Read', () => { const lookup = buildReverseLookup([fnTool('Read')]); const out = parseNativeFunctionCallsFromText( 'I will inspect it.\n\n\n/tmp/a.txt\n\n', lookup, ); assert.equal(out.toolCalls.length, 1); assert.equal(out.toolCalls[0].name, 'Read'); assert.deepEqual(JSON.parse(out.toolCalls[0].argumentsJson), { file_path: '/tmp/a.txt' }); assert.ok(!out.text.includes('')); assert.ok(!out.text.includes(' { const lookup = buildReverseLookup([fnTool('Read')]); const text = '1'; const out = parseNativeFunctionCallsFromText(text, lookup); assert.equal(out.toolCalls.length, 0); assert.equal(out.text, ''); }); it('drops known invokes when the caller did not declare a matching tool', () => { const lookup = buildReverseLookup([fnTool('Bash')]); const text = 'before README.md after'; const out = parseNativeFunctionCallsFromText(text, lookup); assert.equal(out.toolCalls.length, 0); assert.equal(out.text, 'before after'); }); it('stream parser withholds provider-native XML until it can emit a tool_call', () => { const lookup = buildReverseLookup([fnTool('Read')]); const parser = new NativeFunctionCallStreamParser(lookup); const a = parser.feed('\n\n'); assert.equal(a.text, ''); assert.equal(a.toolCalls.length, 0); const b = parser.feed('README.md\n'); assert.equal(b.text, ''); assert.equal(b.toolCalls.length, 0); const c = parser.feed('\n'); assert.equal(c.text, ''); assert.equal(c.toolCalls.length, 1); assert.equal(c.toolCalls[0].name, 'Read'); assert.deepEqual(JSON.parse(c.toolCalls[0].argumentsJson), { file_path: 'README.md' }); const tail = parser.flush(); assert.equal(tail.text, ''); assert.equal(tail.toolCalls.length, 0); }); it('stream parser holds partial opening tags across chunk boundaries', () => { const lookup = buildReverseLookup([fnTool('Read')]); const parser = new NativeFunctionCallStreamParser(lookup); const a = parser.feed('prefix a.txt suffix'); assert.equal(b.text, ' suffix'); assert.equal(b.toolCalls.length, 1); assert.deepEqual(JSON.parse(b.toolCalls[0].argumentsJson), { file_path: 'a.txt' }); }); it('stream parser drops an incomplete XML block once it exceeds 64KiB', () => { const lookup = buildReverseLookup([fnTool('Read')]); const parser = new NativeFunctionCallStreamParser(lookup); parser.feed(''); const overflow = parser.feed('x'.repeat(70_000)); assert.deepEqual(overflow, { text: '', toolCalls: [] }); assert.equal(parser.buffer, ''); const later = parser.feed('after-cap text'); assert.equal(later.text, 'after-cap text'); }); it('stream parser does not drop a large content delta that is not an XML block', () => { const lookup = buildReverseLookup([fnTool('Read')]); const parser = new NativeFunctionCallStreamParser(lookup); const body = 'plain '.repeat(20_000); const out = parser.feed(body); assert.equal(out.text, body); assert.equal(out.toolCalls.length, 0); }); it('stream parser drops incomplete XML on flush', () => { const lookup = buildReverseLookup([fnTool('Read')]); const parser = new NativeFunctionCallStreamParser(lookup); assert.deepEqual(parser.feed('start '), { text: 'start ', toolCalls: [] }); const tail = parser.flush(); assert.equal(tail.text, ''); assert.equal(tail.toolCalls.length, 0); }); it('non-stream parser drops dangling function_calls blocks', () => { const lookup = buildReverseLookup([fnTool('Read')]); const out = parseNativeFunctionCallsFromText('prefix ', lookup); assert.equal(out.text, 'prefix'); assert.equal(out.toolCalls.length, 0); }); }); describe('buildAdditionalStep / buildAdditionalStepsFromHistory', () => { it('view_file step encodes CortexStepType=8 with the body on oneof field 14', () => { const buf = buildAdditionalStep('view_file', { absolute_path_uri: 'file:///foo.ts', content: 'console.log("hi")', }, { observed: true }); assert.ok(Buffer.isBuffer(buf)); const fields = parseFields(buf); const typeField = getField(fields, 1, 0); assert.equal(typeField.value, 8, 'CortexStepType.VIEW_FILE is 8, not the oneof field 14'); const oneof = getField(fields, 14, 2); assert.ok(oneof, 'view_file body should be on field 14'); const body = parseFields(oneof.value); assert.equal(getField(body, 1, 2).value.toString('utf8'), 'file:///foo.ts'); assert.equal(getField(body, 4, 2).value.toString('utf8'), 'console.log("hi")'); }); it('run_command step puts command_line on field 23 + combined_output on 21', () => { const buf = buildAdditionalStep('run_command', { command_line: 'echo hi', full_output: 'hi\n', exit_code: 0, }, { observed: true }); const fields = parseFields(buf); assert.equal(getField(fields, 1, 0).value, 21, 'CortexStepType.RUN_COMMAND is 21'); const body = parseFields(getField(fields, 28, 2).value); assert.equal(getField(body, 23, 2).value.toString('utf8'), 'echo hi'); const combined = parseFields(getField(body, 21, 2).value); assert.equal(getField(combined, 1, 2).value.toString('utf8'), 'hi\n'); }); it('full assistant→tool history → trajectory step buffers', () => { const messages = [ { role: 'user', content: 'find me a file' }, { role: 'assistant', content: null, tool_calls: [{ id: 'call_1', type: 'function', function: { name: 'Read', arguments: JSON.stringify({ file_path: '/etc/hosts' }) }, }], }, { role: 'tool', tool_call_id: 'call_1', content: '127.0.0.1 localhost\n' }, ]; const steps = buildAdditionalStepsFromHistory(messages); assert.equal(steps.length, 1); const fields = parseFields(steps[0]); assert.equal(getField(fields, 1, 0).value, 8, 'CortexStepType.VIEW_FILE is 8'); const body = parseFields(getField(fields, 14, 2).value); assert.equal(getField(body, 4, 2).value.toString('utf8'), '127.0.0.1 localhost\n'); }); it('WebFetch history encodes tool result as read_url_content web_document', () => { const messages = [ { role: 'assistant', content: null, tool_calls: [{ id: 'call_fetch', type: 'function', function: { name: 'WebFetch', arguments: JSON.stringify({ url: 'https://example.com/docs' }) }, }], }, { role: 'tool', tool_call_id: 'call_fetch', content: 'fetched document body' }, ]; const steps = buildAdditionalStepsFromHistory(messages); assert.equal(steps.length, 1); const fields = parseFields(steps[0]); assert.equal(getField(fields, 1, 0).value, 31, 'CortexStepType.READ_URL_CONTENT is 31'); const body = parseFields(getField(fields, 40, 2).value); assert.equal(getField(body, 1, 2).value.toString('utf8'), 'https://example.com/docs'); assert.equal(getField(body, 5, 2), null, 'read_url_content must not encode legacy top-level summary field 5'); const doc = parseFields(getField(body, 2, 2).value); assert.equal(getField(doc, 2, 2).value.toString('utf8'), 'fetched document body'); assert.equal(getField(doc, 3, 2).value.toString('utf8'), 'https://example.com/docs'); }); it('skips unmapped tool_calls (fall back to emulation path)', () => { const messages = [ { role: 'assistant', tool_calls: [{ id: 'call_x', function: { name: 'get_weather', arguments: '{}' }, }], }, { role: 'tool', tool_call_id: 'call_x', content: 'sunny' }, ]; const steps = buildAdditionalStepsFromHistory(messages); assert.equal(steps.length, 0); }); }); // V-13 (KH-034) — history truth: a step is only complete when the caller // actually reported a result; a failed shell command keeps its non-zero // status; unmapped history stays visible to the emulation path; and a // reused cascade receives only the rounds it has not already seen. describe('native history truth (V-13)', () => { const callMsg = (id, name, args, extra = {}) => ({ role: 'assistant', content: null, tool_calls: [{ id, type: 'function', function: { name, arguments: JSON.stringify(args) }, }], ...extra, }); const resultMsg = (id, content, extra = {}) => ({ role: 'tool', tool_call_id: id, content, ...extra }); function decodeRunCommand(buf) { const fields = parseFields(buf); assert.equal(getField(fields, 1, 0).value, 21, 'CortexStepType.RUN_COMMAND is 21'); return { status: getField(fields, 4, 0).value, body: parseFields(getField(fields, 28, 2).value), }; } it('an unobserved call never becomes DONE and a failed Bash keeps its non-zero exit_code', () => { // Layer 1: the encoder itself refuses to stamp DONE without an observation. assert.equal( buildAdditionalStep('run_command', { command_line: 'ls' }), null, 'no observation means no completed step', ); // Layer 2: history conversion drops the unobserved call instead of // telling the planner it ran with empty output. const unobserved = buildAdditionalStepsFromHistory([ { role: 'user', content: 'go' }, callMsg('call_unobserved', 'Bash', { command: 'ls' }), ]); assert.equal(unobserved.length, 0, 'an assistant tool_call with no result is not a DONE step'); // A failed Bash result carries its exit status. The step is DONE because // the command completed — not because it succeeded. const failedText = 'Exit code: 2\nOutput:\nls: cannot access /nope'; const failed = buildAdditionalStepsFromHistory([ { role: 'user', content: 'go' }, callMsg('call_failed', 'Bash', { command: 'ls /nope' }), resultMsg('call_failed', failedText), ]); assert.equal(failed.length, 1); const decoded = decodeRunCommand(failed[0]); assert.equal(decoded.status, 3, 'status DONE means the step completed, not that it succeeded'); assert.equal(getField(decoded.body, 6, 0).value, 2, 'a failed Bash keeps its non-zero exit_code'); const combined = parseFields(getField(decoded.body, 21, 2).value); assert.equal(getField(combined, 1, 2).value.toString('utf8'), failedText); assert.equal( getField(decoded.body, 4, 2), null, 'one authoritative output field: combined_output only, no duplicated stdout', ); // Success keeps 0 — the fix must not turn every command into a failure. const ok = buildAdditionalStepsFromHistory([ { role: 'user', content: 'go' }, callMsg('call_ok', 'Bash', { command: 'true' }), resultMsg('call_ok', 'Exit code: 0\n'), ]); assert.equal(getField(decodeRunCommand(ok[0]).body, 6, 0).value, 0); // Anthropic-style failures carry no numeric code; they must still not be // replayed as success (proto3 reads an absent field as 0). const toolError = buildAdditionalStepsFromHistory([ { role: 'user', content: 'go' }, callMsg('call_err', 'Bash', { command: 'nope' }), resultMsg('call_err', '[tool error] bash: nope: command not found'), ]); assert.equal( getField(decodeRunCommand(toolError[0]).body, 6, 0).value, 1, 'a signalled failure without a numeric code is encoded non-zero, never 0', ); const flagged = buildAdditionalStepsFromHistory([ { role: 'user', content: 'go' }, callMsg('call_flag', 'Bash', { command: 'nope' }), resultMsg('call_flag', 'bash: nope: command not found', { is_error: true }), ]); assert.equal(getField(decodeRunCommand(flagged[0]).body, 6, 0).value, 1); // codex-style structured shell results keep their exit status too. const json = buildAdditionalStepsFromHistory([ { role: 'user', content: 'go' }, callMsg('call_json', 'Bash', { command: 'nope' }), resultMsg('call_json', JSON.stringify({ output: 'boom', metadata: { exit_code: 127 } })), ]); assert.equal(getField(decodeRunCommand(json[0]).body, 6, 0).value, 127); }); it('a reused cascade receives only the new tool round, not the already-injected prefix', () => { const messages = [ { role: 'user', content: 'u1' }, callMsg('call_1', 'Read', { file_path: '/a' }), resultMsg('call_1', 'A'), { role: 'assistant', content: 'read a' }, callMsg('call_2', 'Read', { file_path: '/b' }), resultMsg('call_2', 'B'), ]; // A fresh cascade never saw the earlier round, so it gets the full history. const fresh = buildNativeToolHistory(messages); assert.equal(fresh.steps.length, 2); // The full build exposes where the new round starts, so the wire can // slice instead of encoding the history a second time. assert.equal(fresh.deltaStart, 1); assert.deepEqual(fresh.steps.slice(fresh.deltaStart), buildAdditionalStepsFromHistory(messages, { reuse: true })); // The reused cascade already holds call_1's step; re-injecting it would // duplicate the observation in its trajectory. const delta = buildAdditionalStepsFromHistory(messages, { reuse: true }); assert.equal(delta.length, 1, 'only the newest pending round is new'); const body = parseFields(getField(parseFields(delta[0]), 14, 2).value); assert.equal(getField(body, 4, 2).value.toString('utf8'), 'B'); // A reused cascade whose newest turn is a user message has nothing new. assert.equal( buildAdditionalStepsFromHistory([...messages, { role: 'user', content: 'next' }], { reuse: true }).length, 0, ); // The trailing round is only a delta when it has observations; an // unanswered proposal is not a completed step. assert.equal( buildAdditionalStepsFromHistory(messages.slice(0, 5), { reuse: true }).length, 0, ); }); it('unmapped and orphan tool history survives in the emulation message list', () => { const messages = [ { role: 'user', content: 'u1' }, { role: 'assistant', content: null, tool_calls: [ { id: 'call_mapped', type: 'function', function: { name: 'Bash', arguments: '{"command":"ls"}' } }, { id: 'call_unmapped', type: 'function', function: { name: 'get_weather', arguments: '{"city":"x"}' } }, ], }, resultMsg('call_mapped', 'Exit code: 0\nok'), resultMsg('call_unmapped', 'sunny'), resultMsg('call_orphan', 'orphan output'), ]; const history = buildNativeToolHistory(messages); assert.equal(history.steps.length, 1, 'only the mapped+observed call becomes a step'); // The mapped pair belongs to the bridge and must not be duplicated into // the text history; the unmapped pair and the orphan result are retained. const retained = history.emulationMessages; assert.equal(retained.length, 4, 'user + assistant(unmapped call) + unmapped result + orphan result'); assert.deepEqual(retained[1].tool_calls.map(tc => tc.id), ['call_unmapped']); assert.equal(retained[2].tool_call_id, 'call_unmapped'); assert.equal(retained[3].tool_call_id, 'call_orphan'); const folded = normalizeMessagesForCascade(retained, [fnTool('get_weather')], { injectUserPreamble: false }); const text = folded.map(m => (typeof m.content === 'string' ? m.content : '')).join('\n'); assert.match(text, /sunny/, 'the unmapped result reaches the model as emulated history'); assert.match(text, /orphan output/, 'an orphan result is not silently dropped'); assert.doesNotMatch(text, /Exit code: 0/, 'the bridge-owned result is not duplicated as text'); }); }); describe('parseTrajectorySteps — native step recognition', () => { // Helpers to build a CortexTrajectoryStep envelope by hand. We avoid // calling buildAdditionalStep here so tests double-cover encoder / decoder // independently. const wrapStep = (typeEnum, oneofField, bodyBuf) => Buffer.concat([ writeVarintField(1, typeEnum), writeVarintField(4, 3), // status DONE writeMessageField(oneofField, bodyBuf), ]); // GetCascadeTrajectoryStepsResponse is `repeated CortexTrajectoryStep steps = 1` const wrapResponse = (...stepBufs) => Buffer.concat(stepBufs.map(b => writeMessageField(1, b))); it('view_file step → toolCall with name=view_file + arguments + result', () => { const body = Buffer.concat([ writeStringField(1, 'file:///abs/foo.ts'), writeVarintField(11, 0), writeVarintField(12, 100), writeStringField(4, 'console.log("hi")\n'), ]); const resp = wrapResponse(wrapStep(8, 14, body)); const steps = parseTrajectorySteps(resp); assert.equal(steps.length, 1); const calls = steps[0].toolCalls.filter(tc => tc.cascade_native); assert.equal(calls.length, 1); assert.equal(calls[0].name, 'view_file'); const args = JSON.parse(calls[0].argumentsJson); assert.equal(args.absolute_path_uri, 'file:///abs/foo.ts'); assert.equal(args.limit, 100); assert.equal(calls[0].result, 'console.log("hi")\n'); }); it('run_command step → toolCall with combined_output observation', () => { const combinedOutput = writeStringField(1, 'hi\n'); const body = Buffer.concat([ writeStringField(23, 'echo hi'), writeMessageField(21, combinedOutput), ]); const resp = wrapResponse(wrapStep(21, 28, body)); const steps = parseTrajectorySteps(resp); const calls = steps[0].toolCalls.filter(tc => tc.cascade_native); assert.equal(calls.length, 1); assert.equal(calls[0].name, 'run_command'); assert.equal(calls[0].result, 'hi\n'); const args = JSON.parse(calls[0].argumentsJson); assert.equal(args.command_line, 'echo hi'); }); it('grep_search_v2 step (field 105) → toolCall name grep_search_v2', () => { const body = Buffer.concat([ writeStringField(2, 'todo'), writeStringField(3, 'src'), writeStringField(15, 'src/foo.ts:10:// todo\n'), ]); const resp = wrapResponse(wrapStep(91, 105, body)); const steps = parseTrajectorySteps(resp); const calls = steps[0].toolCalls.filter(tc => tc.cascade_native); assert.equal(calls.length, 1); assert.equal(calls[0].name, 'grep_search_v2'); assert.equal(calls[0].result, 'src/foo.ts:10:// todo\n'); }); it('list_directory step → children joined as result', () => { const body = Buffer.concat([ writeStringField(1, 'file:///src'), writeStringField(2, 'foo.ts'), writeStringField(2, 'bar.ts'), ]); const resp = wrapResponse(wrapStep(9, 15, body)); const steps = parseTrajectorySteps(resp); const calls = steps[0].toolCalls.filter(tc => tc.cascade_native); assert.equal(calls.length, 1); assert.equal(calls[0].name, 'list_directory'); assert.equal(calls[0].result, 'foo.ts\nbar.ts'); }); it('multiple native steps in same trajectory all surface', () => { const viewBody = Buffer.concat([writeStringField(1, 'file:///a.ts'), writeStringField(4, 'A')]); const cmdBody = Buffer.concat([writeStringField(23, 'ls'), writeMessageField(21, writeStringField(1, 'a.ts\n'))]); const resp = wrapResponse(wrapStep(8, 14, viewBody), wrapStep(21, 28, cmdBody)); const steps = parseTrajectorySteps(resp); assert.equal(steps.length, 2); assert.equal(steps[0].toolCalls.filter(tc => tc.cascade_native).length, 1); assert.equal(steps[1].toolCalls.filter(tc => tc.cascade_native).length, 1); }); }); describe('buildSendCascadeMessageRequest — additional_steps on field 9', () => { it('omits XML reinforcement from a Kimi preamble', () => { setSystemPrompts({ toolReinforcement: 'Use this exact format: {"name":"...","arguments":{...}}' }); try { const proto = buildSendCascadeMessageRequest( 'k', 'cid', 'hi', 12345, 'MODEL_TEST', 'sess', { toolPreamble: '<|tool_calls_section_begin|>\nAvailable tools:\n<|tool_calls_section_end|>' }, ); const top = parseFields(proto); const cfg = parseFields(getField(top, 5, 2).value); const planner = parseFields(getField(cfg, 1, 2).value); const conversational = parseFields(getField(planner, 2, 2).value); const section = parseFields(getField(conversational, 12, 2).value); const text = getField(section, 2, 2).value.toString('utf8'); assert.match(text, /<\|tool_calls_section_begin\|>/); assert.doesNotMatch(text, /Use this exact format:\s*/i); } finally { resetSystemPrompt('toolReinforcement'); } }); // The test above INJECTS a reinforcement containing ``, so it proves the // suppression works — but not that it ever runs. Detection keys off that marker being // present, so if the shipped default stops carrying it the suppression silently becomes a // no-op and the test above still passes. #252 did exactly that: it added the dialect-scoped // suppression AND replaced the default with a generic sentence, and the two cancelled out. // Measured after the merge: conflictsWithKimiPreamble never fired on the default config. // // Net effect was the opposite of the PR's intent — the collision went away because the // marker was gone everywhere, which also stripped the format example from every non-Kimi // dialect. Pin the precondition so the mechanism cannot be hollowed out again. it('the shipped default carries the marker the Kimi suppression keys off', () => { resetSystemPrompt('toolReinforcement'); const { toolReinforcement } = getSystemPrompts(); assert.match( toolReinforcement, /<\s*tool_call\s*>/i, 'default toolReinforcement must contain : it is both the format example for ' + 'non-Kimi dialects and the trigger conflictsWithKimiPreamble() detects', ); // And end-to-end: with the DEFAULT (not an injected string), a Kimi preamble must still // suppress it. This is the assertion #252 needed and did not have. const proto = buildSendCascadeMessageRequest( 'k', 'cid', 'hi', 12345, 'MODEL_TEST', 'sess', { toolPreamble: '<|tool_calls_section_begin|>\nAvailable tools:\n<|tool_calls_section_end|>' }, ); const top = parseFields(proto); const cfg = parseFields(getField(top, 5, 2).value); const planner = parseFields(getField(cfg, 1, 2).value); const conversational = parseFields(getField(planner, 2, 2).value); const text = getField(parseFields(getField(conversational, 12, 2).value), 2, 2).value.toString('utf8'); assert.match(text, /<\|tool_calls_section_begin\|>/, 'the Kimi preamble itself must survive'); assert.doesNotMatch( text, /<\s*tool_call\s*>/i, 'the XML reinforcement must be suppressed under a Kimi preamble, using the real default', ); }); it('includes each step as repeated field 9', () => { const stepA = buildAdditionalStep('view_file', { absolute_path_uri: 'file:///a', content: 'A' }, { observed: true }); const stepB = buildAdditionalStep('run_command', { command_line: 'ls', full_output: 'a\n' }, { observed: true }); const proto = buildSendCascadeMessageRequest( 'k', 'cid', 'hi', 12345, 'MODEL_TEST', 'sess', { additionalSteps: [stepA, stepB] }, ); const fields = parseFields(proto); const additional = getAllFields(fields, 9).filter(f => f.wireType === 2); assert.equal(additional.length, 2, 'two repeated additional_steps expected on field 9'); }); it('omits field 9 when no additionalSteps provided', () => { const proto = buildSendCascadeMessageRequest('k', 'cid', 'hi', 12345, 'MODEL_TEST', 'sess', {}); const fields = parseFields(proto); assert.equal(getAllFields(fields, 9).length, 0); }); it('nativeMode=true switches planner_mode to DEFAULT (1) and adds tool_config', () => { const proto = buildSendCascadeMessageRequest('k', 'cid', 'hi', 12345, 'MODEL_TEST', 'sess', { nativeMode: true, nativeAllowlist: ['view_file', 'run_command'], }); const top = parseFields(proto); const cfgField = getField(top, 5, 2); assert.ok(cfgField, 'cascade_config required on field 5'); const cfg = parseFields(cfgField.value); const planner = parseFields(getField(cfg, 1, 2).value); // CascadePlannerConfig.tool_config = field 13 (CascadeToolConfig) const toolCfg = getField(planner, 13, 2); assert.ok(toolCfg, 'CascadePlannerConfig.tool_config (field 13) should be set in nativeMode'); const tc = parseFields(toolCfg.value); const allow = getAllFields(tc, 32).map(f => f.value.toString('utf8')); assert.ok(allow.includes('view_file')); assert.ok(allow.includes('run_command')); // conversational planner sub-config (field 2) → planner_mode (field 4) = DEFAULT (1) const conv = parseFields(getField(planner, 2, 2).value); const mode = getField(conv, 4, 0); assert.equal(mode.value, 1, 'planner_mode in nativeMode should be DEFAULT (1)'); assert.equal(getField(conv, 10, 2), null, 'nativeMode must not write a no-tool tool_calling_section'); assert.equal(getField(conv, 12, 2), null, 'nativeMode must not write a no-tool additional_instructions_section'); }); it('native allowlist experiment aliases still enable the matching tool sub-configs', () => { const proto = buildSendCascadeMessageRequest('k', 'cid', 'hi', 12345, 'MODEL_TEST', 'sess', { nativeMode: true, nativeAllowlist: ['read_file', 'grep_v2', 'list_dir'], }); const top = parseFields(proto); const cfg = parseFields(getField(top, 5, 2).value); const planner = parseFields(getField(cfg, 1, 2).value); const tc = parseFields(getField(planner, 13, 2).value); const allow = getAllFields(tc, 32).map(f => f.value.toString('utf8')); assert.deepEqual(allow, ['read_file', 'grep_v2', 'list_dir']); assert.ok(getField(tc, 10, 2), 'read_file should still enable ViewFileToolConfig field 10'); assert.ok(getField(tc, 33, 2), 'grep_v2 should still enable GrepV2ToolConfig field 33'); assert.ok(getField(tc, 19, 2), 'list_dir should enable ListDirToolConfig field 19'); assert.equal(getField(tc, 5, 2), null, 'list_dir experiment should not also enable FindToolConfig'); }); it('native tool config raw overrides inject exact sub-config bytes for protocol experiments', () => { process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_CONFIG_RAW = 'read_file:0a03616263;grep_v2:base64:EgJwYQ=='; try { const proto = buildSendCascadeMessageRequest('k', 'cid', 'hi', 12345, 'MODEL_TEST', 'sess', { nativeMode: true, nativeAllowlist: ['read_file', 'grep_v2'], }); const top = parseFields(proto); const cfg = parseFields(getField(top, 5, 2).value); const planner = parseFields(getField(cfg, 1, 2).value); const tc = parseFields(getField(planner, 13, 2).value); assert.equal(getField(tc, 10, 2).value.toString('hex'), '0a03616263'); assert.equal(getField(tc, 33, 2).value.toString('hex'), '12027061'); assert.deepEqual(getAllFields(tc, 32).map(f => f.value.toString('utf8')), ['read_file', 'grep_v2']); } finally { delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_CONFIG_RAW; } }); it('native tool config raw overrides can inject unknown top-level fields for matrix experiments', () => { process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_CONFIG_RAW = 'field42:0a03776562;field40:'; try { const proto = buildSendCascadeMessageRequest('k', 'cid', 'hi', 12345, 'MODEL_TEST', 'sess', { nativeMode: true, nativeAllowlist: ['search_web', 'read_url_content'], }); const top = parseFields(proto); const cfg = parseFields(getField(top, 5, 2).value); const planner = parseFields(getField(cfg, 1, 2).value); const tc = parseFields(getField(planner, 13, 2).value); assert.equal(getField(tc, 42, 2).value.toString('hex'), '0a03776562'); assert.equal(getField(tc, 40, 2).value.length, 0); assert.deepEqual(getAllFields(tc, 32).map(f => f.value.toString('utf8')), ['search_web', 'read_url_content']); } finally { delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_CONFIG_RAW; } }); it('native allowlist emits confirmed WebSearch/WebFetch tool-config fields', () => { const proto = buildSendCascadeMessageRequest('k', 'cid', 'hi', 12345, 'MODEL_TEST', 'sess', { nativeMode: true, nativeAllowlist: ['search_web', 'read_url_content'], }); const top = parseFields(proto); const cfg = parseFields(getField(top, 5, 2).value); const planner = parseFields(getField(cfg, 1, 2).value); const tc = parseFields(getField(planner, 13, 2).value); assert.equal(getField(tc, 13, 2).value.length, 0, 'search_web should use SearchWebToolConfig field 13'); assert.equal(getField(tc, 37, 2).value.length, 0, 'read_url_content should use ReadUrlContentToolConfig field 37'); assert.deepEqual(getAllFields(tc, 32).map(f => f.value.toString('utf8')), ['search_web', 'read_url_content']); }); it('native tool config raw overrides support web tool aliases', () => { process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_CONFIG_RAW = 'search_web:0800;read_url_content:0a00'; try { const proto = buildSendCascadeMessageRequest('k', 'cid', 'hi', 12345, 'MODEL_TEST', 'sess', { nativeMode: true, nativeAllowlist: ['search_web', 'read_url_content'], }); const top = parseFields(proto); const cfg = parseFields(getField(top, 5, 2).value); const planner = parseFields(getField(cfg, 1, 2).value); const tc = parseFields(getField(planner, 13, 2).value); assert.equal(getField(tc, 13, 2).value.toString('hex'), '0800'); assert.equal(getField(tc, 37, 2).value.toString('hex'), '0a00'); } finally { delete process.env.WINDSURFAPI_NATIVE_TOOL_BRIDGE_CONFIG_RAW; } }); it('nativeMode=true can carry environment facts without tool emulation schema', () => { const proto = buildSendCascadeMessageRequest('k', 'cid', 'hi', 12345, 'MODEL_TEST', 'sess', { nativeMode: true, nativeAllowlist: ['view_file'], nativeEnvironment: '- Working directory: /repo\n- Platform: linux', }); const top = parseFields(proto); const cfg = parseFields(getField(top, 5, 2).value); const planner = parseFields(getField(cfg, 1, 2).value); const conv = parseFields(getField(planner, 2, 2).value); assert.equal(getField(conv, 4, 0).value, 1, 'nativeMode still uses DEFAULT planner'); assert.equal(getField(conv, 10, 2), null, 'native env must not inject tool_calling_section schemas'); const additional = getField(conv, 12, 2); assert.ok(additional, 'native env should be written to additional_instructions_section'); const section = parseFields(additional.value); const text = getField(section, 2, 2)?.value?.toString('utf8') || ''; assert.match(text, /Working directory: \/repo/); assert.match(text, /built-in IDE tools/); assert.doesNotMatch(text, /You have access to the following functions/); assert.doesNotMatch(text, //); }); it('nativeMode=false (default) keeps planner_mode = NO_TOOL (3) and skips tool_config', () => { const proto = buildSendCascadeMessageRequest('k', 'cid', 'hi', 12345, 'MODEL_TEST', 'sess', {}); const top = parseFields(proto); const cfg = parseFields(getField(top, 5, 2).value); const planner = parseFields(getField(cfg, 1, 2).value); assert.equal(getField(planner, 13, 2), null, 'tool_config should NOT be set when nativeMode is off'); const conv = parseFields(getField(planner, 2, 2).value); assert.equal(getField(conv, 4, 0).value, 3); }); }); describe('CASCADE_STEP type constants — sanity', () => { it('matches proto field numbers used in oneof', () => { assert.equal(CASCADE_STEP.view_file.typeEnum, 8); assert.equal(CASCADE_STEP.run_command.typeEnum, 21); assert.equal(CASCADE_STEP.grep_search_v2.typeEnum, 91); assert.equal(CASCADE_STEP.find.typeEnum, 25); assert.equal(CASCADE_STEP.list_directory.typeEnum, 9); assert.equal(CASCADE_STEP.write_to_file.typeEnum, 16); }); }); // ─── v2.0.66 (#115) — partition mode + codex CLI mapping ────────────── describe('partitionTools — v2.0.66 mixed-mapping splitter', () => { it('splits mapped vs unmapped on a real codex CLI 0.128 toolset (web_search default-off)', () => { // Captured live from `dump-codex-tools.mjs`: codex CLI 0.128 declares // these 11 tools by default. Native web search/fetch remain opt-in in // the bridge allowlist, so the default mapped set stays shell-only. const codexTools = [ 'shell_command', 'update_plan', 'request_user_input', 'apply_patch', 'web_search', 'view_image', 'spawn_agent', 'send_input', 'resume_agent', 'wait_agent', 'close_agent', ].map(fnTool); const part = partitionTools(codexTools); assert.equal(part.hasAny, true); const mappedNames = part.mapped.map(t => t.function.name).sort(); assert.deepEqual(mappedNames, ['shell_command']); assert.equal(part.unmapped.length, 10); assert.ok(part.unmapped.find(t => t.function.name === 'apply_patch')); assert.ok(part.unmapped.find(t => t.function.name === 'update_plan')); assert.ok(part.unmapped.find(t => t.function.name === 'web_search')); }); it('returns hasAny=false when no tool maps', () => { const part = partitionTools([fnTool('get_weather'), fnTool('rng')]); assert.equal(part.hasAny, false); assert.equal(part.mapped.length, 0); assert.equal(part.unmapped.length, 2); }); it('Claude Code-style tools keep Read/Glob out of the default native scope', () => { const part = partitionTools([fnTool('Read'), fnTool('Bash'), fnTool('Glob')]); assert.equal(part.hasAny, true); assert.deepEqual(part.mapped.map(t => t.function.name), ['Bash']); assert.deepEqual(part.unmapped.map(t => t.function.name), ['Read', 'Glob']); }); it('skips non-function entries gracefully', () => { const part = partitionTools([ fnTool('Read'), { type: 'web_search' }, { type: 'function' }, // missing function.name { type: 'function', function: { name: '' } }, ]); assert.equal(part.mapped.length, 0); assert.equal(part.unmapped.length, 1); }); }); describe('TOOL_MAP — codex CLI 0.128 shell_command mapping (v2.0.66)', () => { it('shell_command ↔ run_command preserves command + workdir', () => { const fwd = TOOL_MAP.shell_command.forward({ command: 'pytest -x', workdir: 'D:/Project/foo', timeout_ms: 30000, }); assert.equal(fwd.command_line, 'pytest -x'); assert.equal(fwd.cwd, 'D:/Project/foo'); assert.equal(fwd.blocking, true); const back = TOOL_MAP.shell_command.reverse(fwd); assert.equal(back.command, 'pytest -x'); assert.equal(back.workdir, 'D:/Project/foo'); }); it('shell_command reverse maps cascade combined_output → codex shell schema', () => { // Simulates the trajectory step → tool_call reverse direction the // proxy uses when surfacing a cascade-side run_command back to the // codex CLI client. const back = TOOL_MAP.shell_command.reverse({ command_line: 'echo hi', cwd: '/tmp', stdout: 'hi\n', }); assert.equal(back.command, 'echo hi'); assert.equal(back.workdir, '/tmp'); }); }); describe('canMapAllTools (legacy strict gate, kept for compat)', () => { it('still returns false when ANY tool is unmapped', () => { assert.equal(canMapAllTools([fnTool('Read'), fnTool('get_weather')]), false); assert.equal(canMapAllTools([fnTool('Read'), fnTool('Bash'), fnTool('Glob')]), false); }); }); describe('mergeReasoningEffortIntoModel — v2.0.66 reasoning_effort fold-in', () => { it('codex CLI shape (reasoning.effort) folds into bare gpt-5.5', async () => { const { mergeReasoningEffortIntoModel } = await import('../src/handlers/chat.js'); assert.equal( mergeReasoningEffortIntoModel('gpt-5.5', { reasoning: { effort: 'xhigh' } }), 'gpt-5.5-xhigh', ); assert.equal( mergeReasoningEffortIntoModel('gpt-5.5', { reasoning: { effort: 'high' } }), 'gpt-5.5-high', ); }); it('OpenAI-style (top-level reasoning_effort) also works', async () => { const { mergeReasoningEffortIntoModel } = await import('../src/handlers/chat.js'); assert.equal( mergeReasoningEffortIntoModel('gpt-5.5', { reasoning_effort: 'low' }), 'gpt-5.5-low', ); }); it('an explicit effort suffix is authoritative over the effort field (CompatClients F1)', async () => { const { mergeReasoningEffortIntoModel } = await import('../src/handlers/chat.js'); // This case used to pin the OPPOSITE (suffix stripped and replaced), which // is the defect Codex hit: it sends reasoning.effort=medium on every turn, // so gpt-6-luna-max was served as gpt-6-luna-medium. An explicit tier in // the model id is the caller's selection and must win in both directions — // a -medium request must not be promoted either. assert.equal( mergeReasoningEffortIntoModel('gpt-6-luna-max', { reasoning: { effort: 'medium' } }), 'gpt-6-luna-max', ); assert.equal( mergeReasoningEffortIntoModel('gpt-5.5-xhigh', { reasoning: { effort: 'medium' } }), 'gpt-5.5-xhigh', ); assert.equal( mergeReasoningEffortIntoModel('claude-opus-4-8-medium', { reasoning: { effort: 'xhigh' } }), 'claude-opus-4-8-medium', ); // No suffix in the request → the effort field still supplies the tier. assert.equal( mergeReasoningEffortIntoModel('claude-opus-4.8', { reasoning_effort: 'max' }), 'claude-opus-4.8-max', ); }); it('returns input unchanged when effort missing', async () => { const { mergeReasoningEffortIntoModel } = await import('../src/handlers/chat.js'); assert.equal(mergeReasoningEffortIntoModel('gpt-5.5', {}), 'gpt-5.5'); assert.equal(mergeReasoningEffortIntoModel('gpt-5.5', { reasoning: {} }), 'gpt-5.5'); assert.equal(mergeReasoningEffortIntoModel('gpt-5.5', { reasoning: { effort: '' } }), 'gpt-5.5'); }); it('returns input unchanged for unknown effort strings (catalog has no -bogus variant)', async () => { const { mergeReasoningEffortIntoModel } = await import('../src/handlers/chat.js'); assert.equal(mergeReasoningEffortIntoModel('gpt-5.5', { reasoning_effort: 'bogus' }), 'gpt-5.5'); }); it('returns input unchanged when merged model is not in catalog', async () => { const { mergeReasoningEffortIntoModel } = await import('../src/handlers/chat.js'); // claude models don't have effort suffixes — the merged form // `claude-sonnet-4.6-xhigh` doesn't exist in models.js, so the helper // refuses to swap. assert.equal( mergeReasoningEffortIntoModel('claude-sonnet-4.6', { reasoning: { effort: 'xhigh' } }), 'claude-sonnet-4.6', ); }); it('handles `minimal` by mapping to `none` (windsurf catalog naming)', async () => { const { mergeReasoningEffortIntoModel } = await import('../src/handlers/chat.js'); // gpt-5.5-none is in the catalog (the lowest tier alias) assert.equal( mergeReasoningEffortIntoModel('gpt-5.5', { reasoning_effort: 'minimal' }), 'gpt-5.5-none', ); }); });