'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const {
extractToolNames,
createToolSieveState,
processToolSieveChunk,
flushToolSieve,
parseToolCalls,
parseToolCallsDetailed,
parseStandaloneToolCalls,
formatOpenAIStreamToolCalls,
} = require('../../internal/js/helpers/stream-tool-sieve.js');
function runSieve(chunks, toolNames) {
const state = createToolSieveState();
const events = [];
for (const chunk of chunks) {
events.push(...processToolSieveChunk(state, chunk, toolNames));
}
events.push(...flushToolSieve(state, toolNames));
return events;
}
function collectText(events) {
return events
.filter((evt) => evt.type === 'text' && evt.text)
.map((evt) => evt.text)
.join('');
}
test('extractToolNames keeps only declared tool names (Go parity)', () => {
const names = extractToolNames([
{ function: { description: 'no name tool' } },
{ function: { name: ' read_file ' } },
{ function: { name: 'read_file' } },
{},
]);
assert.deepEqual(names, ['read_file']);
});
test('parseToolCalls parses XML markup tool call', () => {
const payload = 'README.MD';
const calls = parseToolCalls(payload, ['read_file']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'read_file');
assert.deepEqual(calls[0].input, { path: 'README.MD' });
});
test('parseToolCalls parses DSML shell as XML-compatible tool call', () => {
const payload = '<|DSML|tool_calls><|DSML|invoke name="read_file"><|DSML|parameter name="path">README.MD|DSML|parameter>|DSML|invoke>|DSML|tool_calls>';
const calls = parseToolCalls(payload, ['read_file']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'read_file');
assert.deepEqual(calls[0].input, { path: 'README.MD' });
});
test('parseToolCalls parses hyphenated DSML shell with here-doc CDATA', () => {
const payload = `
`;
const calls = parseToolCalls(payload, ['Bash']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'Bash');
assert.equal(calls[0].input.description, 'Create commit with architecture doc updates');
assert.equal(calls[0].input.command.includes('git commit -m "$(cat <<\'EOF\''), true);
assert.equal(calls[0].input.command.includes('Co-Authored-By: Claude Opus 4.7'), true);
});
test('parseToolCalls ignores bare hyphenated tool_calls lookalike', () => {
const payload = 'pwd';
const calls = parseToolCalls(payload, ['Bash']);
assert.equal(calls.length, 0);
});
test('parseToolCalls tolerates DSML trailing pipe tag terminator', () => {
const payload = [
'<|DSML|tool_calls| ',
' <|DSML|invoke name="terminal">',
' <|DSML|parameter name="command">|DSML|parameter>',
' <|DSML|parameter name="timeout">|DSML|parameter>',
' |DSML|invoke>',
'|DSML|tool_calls>',
].join('\n');
const calls = parseToolCalls(payload, ['terminal']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'terminal');
assert.deepEqual(calls[0].input, { command: 'find "/home" -type d', timeout: 10 });
});
test('parseToolCalls tolerates extra leading less-than before DSML tags', () => {
const payload = [
'<<|DSML|tool_calls>',
' <<|DSML|invoke name="Bash">',
' <<|DSML|parameter name="command">|DSML|parameter>',
' |DSML|invoke>',
'|DSML|tool_calls>',
].join('\n');
const calls = parseToolCalls(payload, ['Bash']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'Bash');
assert.deepEqual(calls[0].input, { command: 'pwd' });
});
test('parseToolCalls tolerates repeated DSML prefix noise', () => {
const payload = [
'<',
' <',
' <',
' ',
'',
].join('\n');
const calls = parseToolCalls(payload, ['Bash']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'Bash');
assert.deepEqual(calls[0].input, { command: 'git status' });
});
test('parseToolCalls tolerates DSML space-separator typo', () => {
const payload = '<|DSML tool_calls><|DSML invoke name="Read"><|DSML parameter name="file_path">|DSML parameter>|DSML invoke>|DSML tool_calls>';
const calls = parseToolCalls(payload, ['Read']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'Read');
assert.deepEqual(calls[0].input, { file_path: '/tmp/input.txt' });
});
test('parseToolCalls ignores DSML space lookalike tag names', () => {
const payload = '<|DSML tool_calls_extra><|DSML invoke name="Read"><|DSML parameter name="file_path">/tmp/input.txt|DSML parameter>|DSML invoke>|DSML tool_calls_extra>';
const calls = parseToolCalls(payload, ['Read']);
assert.equal(calls.length, 0);
});
test('parseToolCalls tolerates collapsed DSML tag names', () => {
const todos = [
'[x] 检查 toolcalls_format.go 格式化逻辑',
'[x] 检查 toolcalls_parse.go 解析逻辑',
'[x] 检查 toolcalls_xml.go 和 toolcalls_dsml.go',
'[x] 检查 toolcalls_markup.go 和 toolcalls_json_repair.go',
'[x] 检查 prompt/tool_calls.go 注入逻辑',
'[x] 检查 toolstream 流式解析',
'[x] 查看测试文件确认预期行为',
'[x] 给出调查结论',
].join('\n');
const payload = ``;
const calls = parseToolCalls(payload, ['update_todo_list']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'update_todo_list');
assert.equal(calls[0].input.todos, todos);
});
test('parseToolCalls ignores collapsed DSML lookalike tag names', () => {
const payload = 'x';
const calls = parseToolCalls(payload, ['update_todo_list']);
assert.equal(calls.length, 0);
});
test('parseToolCalls keeps canonical XML examples inside DSML CDATA', () => {
const content = 'x';
const payload = `<|DSML|tool_calls><|DSML|invoke name="write_file"><|DSML|parameter name="path">notes.md|DSML|parameter><|DSML|parameter name="content">|DSML|parameter>|DSML|invoke>|DSML|tool_calls>`;
const calls = parseToolCalls(payload, ['write_file']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'write_file');
assert.deepEqual(calls[0].input, { path: 'notes.md', content });
});
test('parseToolCalls preserves simple inline markup inside CDATA as text', () => {
const payload = 'urgent]]>';
const calls = parseToolCalls(payload, ['Write']);
assert.equal(calls.length, 1);
assert.equal(calls[0].input.description, 'urgent');
});
test('parseToolCalls recovers when CDATA never closes inside a valid wrapper', () => {
const payload = '';
const calls = parseToolCalls(payload, ['Write']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'Write');
assert.equal(calls[0].input.content, 'hello world');
});
test('parseToolCalls supports JSON scalar parameters', () => {
const payload = '123true';
const calls = parseToolCalls(payload, ['configure']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'configure');
assert.equal(calls[0].input.count, 123);
assert.equal(calls[0].input.max_tokens, 256);
assert.equal(calls[0].input.enabled, true);
});
test('parseToolCalls treats item-only parameter body as array', () => {
const payload = [
'<|DSML|tool_calls>',
'<|DSML|invoke name="AskUserQuestion">',
'<|DSML|parameter name="questions">',
'- ',
'',
'',
'',
'
',
' ',
'',
'false',
' ',
'|DSML|parameter>',
'|DSML|invoke>',
'|DSML|tool_calls>',
].join('\n');
const calls = parseToolCalls(payload, ['AskUserQuestion']);
assert.equal(calls.length, 1);
assert.deepEqual(calls[0].input.questions, [
{
question: 'What would you like to do next?',
header: 'Next step',
options: [
{ label: 'Run tests', description: 'Run the test suite' },
{ label: 'Other task', description: 'Something else entirely' },
],
multiSelect: false,
},
]);
});
test('parseToolCalls treats CDATA item-only body as array', () => {
const todos = '
Testing EnterWorktree tool
Test EnterWorktree tool
in_progress
Testing TodoWrite tool
Test TodoWrite tool
completed
';
const payload = `<|DSML|tool_calls><|DSML|invoke name="TodoWrite"><|DSML|parameter name="todos">|DSML|parameter>|DSML|invoke>|DSML|tool_calls>`;
const calls = parseToolCalls(payload, ['TodoWrite']);
assert.equal(calls.length, 1);
assert.deepEqual(calls[0].input.todos, [
{
activeForm: 'Testing EnterWorktree tool',
content: 'Test EnterWorktree tool',
status: 'in_progress',
},
{
activeForm: 'Testing TodoWrite tool',
content: 'Test TodoWrite tool',
status: 'completed',
},
]);
});
test('parseToolCalls treats single-item CDATA body as array', () => {
const payload = 'one]]>';
const calls = parseToolCalls(payload, ['TodoWrite']);
assert.equal(calls.length, 1);
assert.deepEqual(calls[0].input.todos, ['one']);
});
test('parseToolCalls treats loose JSON list as array', () => {
for (const [label, body] of [
['plain text', '{"content":"Test TodoWrite tool","status":"completed"}, {"content":"Another task","status":"pending"}'],
['cdata', ''],
]) {
const payload = `${body}`;
const calls = parseToolCalls(payload, ['TodoWrite']);
assert.equal(calls.length, 1, label);
assert.deepEqual(calls[0].input.todos, [
{ content: 'Test TodoWrite tool', status: 'completed' },
{ content: 'Another task', status: 'pending' },
]);
}
});
test('parseToolCalls keeps preserved text parameters as text', () => {
const payload = '';
const calls = parseToolCalls(payload, ['Write']);
assert.equal(calls.length, 1);
assert.equal(calls[0].input.content, '{"content":"Test TodoWrite tool","status":"completed"}, {"content":"Another task","status":"pending"}');
});
test('formatOpenAIStreamToolCalls normalizes camelCase inputSchema string fields', () => {
const formatted = formatOpenAIStreamToolCalls([
{ name: 'Write', input: { content: { message: 'hi' }, taskId: 1 } },
], new Map(), [
{ name: 'Write', inputSchema: { type: 'object', properties: { content: { type: 'string' }, taskId: { type: 'string' } } } },
]);
assert.equal(formatted.length, 1);
const args = JSON.parse(formatted[0].function.arguments);
assert.equal(args.content, '{"message":"hi"}');
assert.equal(args.taskId, '1');
});
test('formatOpenAIStreamToolCalls preserves arrays when schema says array', () => {
const todos = [{ content: 'x', status: 'pending', priority: 'high' }];
const formatted = formatOpenAIStreamToolCalls([
{ name: 'todowrite', input: { todos } },
], new Map(), [
{ name: 'todowrite', inputSchema: { type: 'object', properties: { todos: { type: 'array', items: { type: 'object' } } } } },
]);
assert.equal(formatted.length, 1);
const args = JSON.parse(formatted[0].function.arguments);
assert.deepEqual(args.todos, todos);
});
test('parseToolCalls treats CDATA object fragment as object', () => {
const fragment = ' ';
const payload = ``;
const calls = parseToolCalls(payload, ['AskUserQuestion']);
assert.equal(calls.length, 1);
assert.deepEqual(calls[0].input.questions, {
question: 'Pick one',
options: [
{ label: 'A' },
{ label: 'B' },
],
});
});
test('parseToolCalls normalizes mixed DSML and XML tool tags', () => {
// Models commonly mix DSML wrapper tags with canonical inner tags.
const payload = '<|DSML|tool_calls><|DSML|parameter name="path">README.MD|DSML|parameter>|DSML|tool_calls>';
const calls = parseToolCalls(payload, ['read_file']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'read_file');
assert.deepEqual(calls[0].input, { path: 'README.MD' });
});
test('parseToolCalls skips prose mention of same wrapper variant', () => {
const payload = [
'Summary: support canonical and DSML <|DSML|tool_calls> wrappers.',
'',
'<|DSML|tool_calls>',
'<|DSML|invoke name="Bash">',
'<|DSML|parameter name="command">|DSML|parameter>',
'|DSML|invoke>',
'|DSML|tool_calls>',
].join('\n');
const calls = parseToolCalls(payload, ['Bash']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'Bash');
assert.equal(calls[0].input.command, 'git status');
});
test('sieve emits tool_calls after prose mentions same wrapper variant', () => {
const events = runSieve([
'Summary: support canonical and DSML <|DSML|tool_calls> wrappers.\n\n',
'<|DSML|tool_calls>\n',
'<|DSML|invoke name="Bash">\n',
'<|DSML|parameter name="command">|DSML|parameter>\n',
'|DSML|invoke>\n',
'|DSML|tool_calls>',
], ['Bash']);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(finalCalls.length, 1);
assert.equal(finalCalls[0].name, 'Bash');
assert.equal(finalCalls[0].input.command, 'git status');
assert.equal(collectText(events).includes('Summary:'), true);
});
test('sieve emits tool_calls for DSML space-separator typo', () => {
const events = runSieve([
'准备读取文件。\n',
'<|DSML tool_calls>\n',
'<|DSML invoke name="Read">\n',
'<|DSML parameter name="file_path">|DSML parameter>\n',
'|DSML invoke>\n',
'|DSML tool_calls>',
], ['Read']);
const text = collectText(events);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(finalCalls.length, 1);
assert.equal(finalCalls[0].name, 'Read');
assert.equal(finalCalls[0].input.file_path, '/tmp/input.txt');
assert.equal(text.includes('准备读取文件'), true);
assert.equal(text.includes('<|DSML invoke'), false);
});
test('sieve emits tool_calls for DSML trailing pipe tag terminator', () => {
const events = runSieve([
'<|DSML|tool_calls| \n',
'<|DSML|invoke name="terminal">\n',
'<|DSML|parameter name="command">|DSML|parameter>\n',
'<|DSML|parameter name="timeout">|DSML|parameter>\n',
'|DSML|invoke>\n',
'|DSML|tool_calls>',
], ['terminal']);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
const text = collectText(events);
assert.equal(finalCalls.length, 1);
assert.equal(finalCalls[0].name, 'terminal');
assert.deepEqual(finalCalls[0].input, { command: 'find "/home" -type d', timeout: 10 });
assert.equal(text.toLowerCase().includes('dsml'), false);
});
test('sieve emits tool_calls for extra leading less-than DSML tags without leaking prefix', () => {
const events = runSieve([
'<<|DSML|tool_calls>\n',
'<<|DSML|invoke name="Bash">\n',
'<<|DSML|parameter name="command">|DSML|parameter>\n',
'|DSML|invoke>\n',
'|DSML|tool_calls>',
], ['Bash']);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
const text = collectText(events);
assert.equal(finalCalls.length, 1);
assert.equal(finalCalls[0].name, 'Bash');
assert.deepEqual(finalCalls[0].input, { command: 'pwd' });
assert.equal(text.includes('<'), false);
});
test('sieve keeps DSML space lookalike tag names as text', () => {
const input = '<|DSML tool_calls_extra><|DSML invoke name="Read"><|DSML parameter name="file_path">/tmp/input.txt|DSML parameter>|DSML invoke>|DSML tool_calls_extra>';
const events = runSieve([input], ['Read']);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(finalCalls.length, 0);
assert.equal(collectText(events), input);
});
test('sieve emits tool_calls for collapsed DSML tag names and preserves prefix text', () => {
const todos = [
'[x] 检查 toolcalls_format.go 格式化逻辑',
'[x] 检查 toolcalls_parse.go 解析逻辑',
'[x] 检查 toolcalls_xml.go 和 toolcalls_dsml.go',
'[x] 检查 toolcalls_markup.go 和 toolcalls_json_repair.go',
'[x] 检查 prompt/tool_calls.go 注入逻辑',
'[x] 检查 toolstream 流式解析',
'[x] 查看测试文件确认预期行为',
'[x] 给出调查结论',
].join('\n');
const events = runSieve([
'[]\n',
'\n',
'\n',
`\n`,
'\n',
'',
], ['update_todo_list']);
const text = collectText(events);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(finalCalls.length, 1);
assert.equal(finalCalls[0].name, 'update_todo_list');
assert.equal(finalCalls[0].input.todos, todos);
assert.equal(text, '[]\n');
});
test('sieve keeps collapsed DSML lookalike tag names as text', () => {
const input = 'x';
const events = runSieve([input], ['update_todo_list']);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(finalCalls.length, 0);
assert.equal(collectText(events), input);
});
test('sieve preserves review body with alias mentions before real DSML tool calls', () => {
const events = runSieve([
"Done reviewing the diff. Here's my analysis before we commit:\n\n",
'Summary of Changes\n',
'DSML wrapper variant support — recognize aliases (, <|tool_calls>, <|tool_calls>) alongside canonical and <|DSML|tool_calls> wrappers.\n\n',
'<|DSML|tool_calls>\n',
'<|DSML|invoke name="Bash">\n',
'<|DSML|parameter name="command">|DSML|parameter>\n',
'<|DSML|parameter name="description">|DSML|parameter>\n',
'|DSML|invoke>\n',
'<|DSML|invoke name="Bash">\n',
'<|DSML|parameter name="command">, <|tool_calls>, <|tool_calls> alongside existing canonical wrappers.\nEOF\n)"]]>|DSML|parameter>\n',
'<|DSML|parameter name="description">|DSML|parameter>\n',
'|DSML|invoke>\n',
'|DSML|tool_calls>',
], ['Bash']);
const text = collectText(events);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(finalCalls.length, 2);
assert.equal(text.includes('<|DSML|tool_calls> wrappers'), true);
assert.equal(text.includes('Summary of Changes'), true);
assert.equal(text.includes('git add docs/toolcall-semantics.md'), false);
});
test('sieve preserves Chinese review body with inline DSML mention before real tool call', () => {
const events = runSieve([
'# Context from my IDE setup:\n\n## My request for Codex:\n',
'基于我的审查,这是工作区更改的总结和提交。\n\n## 审查报告\n\n### 文档\n\nAPI.md 中的工具调用部分缺少针对新 DSML 别名的更新——它只提到了 `',
'<|DSML|tool_calls>` 和 canonical ``。由于这涉及 API 兼容性和文档准确性,需要在下游进行记录。\n\n',
'### 代码\n\n所有更改现在一致地处理四个 DSML wrapper 变体。\n\n现在提交已暂存的更改。\n\n',
'<|DSML|tool_calls>\n',
' <|DSML|invoke name="Bash">\n',
' <|DSML|parameter name="command">|DSML|parameter>\n',
' <|DSML|parameter name="description">|DSML|parameter>\n',
' |DSML|invoke>\n',
'|DSML|tool_calls>\n\n补充',
], ['Bash']);
const text = collectText(events);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(finalCalls.length, 1);
assert.equal(text.includes('它只提到了 `<|DSML|tool_calls>` 和 canonical ``。由于这涉及 API 兼容性'), true);
assert.equal(text.includes('补充'), true);
assert.equal(text.includes('<|DSML|invoke'), false);
});
test('sieve captures hyphenated DSML shell with here-doc CDATA', () => {
const events = runSieve([
'\n',
'\n',
'\n',
'\n',
'\n',
'',
], ['Bash']);
const text = collectText(events);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(finalCalls.length, 1);
assert.equal(finalCalls[0].input.command.includes('git commit -m "$(cat <<\'EOF\''), true);
assert.equal(finalCalls[0].input.command.includes('Co-Authored-By: Claude Opus 4.7'), true);
assert.equal(text.includes('dsml-tool-calls'), false);
assert.equal(text.includes('git commit -m'), false);
});
test('parseToolCalls ignores JSON tool_calls payload (XML-only)', () => {
const payload = JSON.stringify({
tool_calls: [{ name: 'read_file', input: { path: 'README.MD' } }],
});
const calls = parseToolCalls(payload, ['read_file']);
assert.equal(calls.length, 0);
});
test('parseToolCalls ignores tool_call payloads that exist only inside fenced code blocks', () => {
const text = [
'I will call a tool now.',
'```xml',
'README.md',
'```',
].join('\n');
const calls = parseToolCalls(text, ['read_file']);
assert.equal(calls.length, 0);
});
test('parseToolCalls keeps unknown schema names when toolNames is provided', () => {
const payload = 'go';
const calls = parseToolCalls(payload, ['search']);
assert.equal(calls.length, 1);
assert.equal(calls[0].name, 'not_in_schema');
});
test('sieve emits tool_calls for XML tool call payload', () => {
const events = runSieve(
['README.MD'],
['read_file'],
);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(finalCalls.length, 1);
assert.equal(finalCalls[0].name, 'read_file');
});
test('sieve emits tool_calls when XML tag spans multiple chunks', () => {
const events = runSieve(
[
'',
'README.MD',
],
['read_file'],
);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(finalCalls.length, 1);
assert.equal(finalCalls[0].name, 'read_file');
});
test('sieve emits tool_calls when DSML tag spans multiple chunks', () => {
const events = runSieve(
[
'<|DSML|tool',
'_calls><|DSML|invoke name="read_file">',
'<|DSML|parameter name="path">README.MD|DSML|parameter>|DSML|invoke>|DSML|tool_calls>',
],
['read_file'],
);
const leakedText = collectText(events);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(leakedText, '');
assert.equal(finalCalls.length, 1);
assert.equal(finalCalls[0].name, 'read_file');
});
test('sieve emits tool_calls when fullwidth DSML prefix variant spans multiple chunks', () => {
const events = runSieve(
[
'<|DSML|tool',
'_calls>\n',
'<|DSML|invoke name="Bash">\n',
'<|DSML|parameter name="command">|DSML|parameter>\n',
'<|DSML|parameter name="description">|DSML|parameter>\n',
'|DSML|invoke>\n',
'<|DSML|invoke name="Bash">\n',
'<|DSML|parameter name="command">/dev/null || echo "No package.json found"]]>|DSML|parameter>\n',
'<|DSML|parameter name="description">|DSML|parameter>\n',
'|DSML|invoke>\n',
'|DSML|tool_calls>',
],
['Bash'],
);
const leakedText = collectText(events);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(leakedText, '');
assert.equal(finalCalls.length, 2);
assert.equal(finalCalls[0].name, 'Bash');
assert.equal(finalCalls[1].name, 'Bash');
});
test('sieve keeps long XML tool calls buffered until the closing tag arrives', () => {
const longContent = 'x'.repeat(4096);
const splitAt = longContent.length / 2;
const events = runSieve(
[
'\n \n \n \n',
],
['write_to_file'],
);
const leakedText = collectText(events);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(leakedText, '');
assert.equal(finalCalls.length, 1);
assert.equal(finalCalls[0].name, 'write_to_file');
assert.equal(finalCalls[0].input.content, longContent);
});
test('sieve recovers when CDATA never closes inside a valid wrapper', () => {
const events = runSieve(
[
'\n \n \n \n',
],
['Write'],
);
const leakedText = collectText(events);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(finalCalls.length, 1);
assert.equal(finalCalls[0].name, 'Write');
assert.equal(finalCalls[0].input.content, 'hello world');
assert.equal(leakedText, '');
});
test('sieve keeps CDATA tool examples buffered until the outer closing tag arrives', () => {
const content = [
'# DS2API 4.0 更新内容',
'',
'x'.repeat(4096),
'```xml',
'',
' ',
' x',
' ',
'',
'```',
'tail',
].join('\n');
const innerClose = content.indexOf('') + ''.length;
const state = createToolSieveState();
const chunks = [
'\n \n \n DS2API-4.0-Release-Notes.md\n \n',
];
const events = [];
chunks.forEach((chunk, idx) => {
const next = processToolSieveChunk(state, chunk, ['Write']);
if (idx <= 1) {
assert.deepEqual(next, []);
}
events.push(...next);
});
events.push(...flushToolSieve(state, ['Write']));
const leakedText = collectText(events);
const finalCalls = events.filter((evt) => evt.type === 'tool_calls').flatMap((evt) => evt.calls || []);
assert.equal(leakedText, '');
assert.equal(finalCalls.length, 1);
assert.equal(finalCalls[0].name, 'Write');
assert.equal(finalCalls[0].input.content, content);
});
test('parseToolCalls keeps XML-looking CDATA content intact', () => {
const content = [
'# Release notes',
'```xml',
'x',
'```',
].join('\n');
const payload = `DS2API-4.0-Release-Notes.md`;
const calls = parseToolCalls(payload, ['Write']);
assert.equal(calls.length, 1);
assert.equal(calls[0].input.content, content);
assert.equal(calls[0].input.file_path, 'DS2API-4.0-Release-Notes.md');
});
test('sieve passes JSON tool_calls payload through as text (XML-only)', () => {
const events = runSieve(
['{"tool_calls":[{"name":"read_file","input":{"path":"README.MD"}}]}'],
['read_file'],
);
const leakedText = collectText(events);
const hasToolCall = events.some((evt) => evt.type === 'tool_calls' && evt.calls?.length > 0);
assert.equal(hasToolCall, false);
assert.equal(leakedText.includes('tool_calls'), true);
});
test('sieve keeps embedded invalid tool-like json as normal text to avoid stream stalls', () => {
const events = runSieve(
[
'前置正文D。',
"{'tool_calls':[{'name':'read_file','input':{'path':'README.MD'}}]}",
'后置正文E。',
],
['read_file'],
);
const leakedText = collectText(events);
const hasToolCall = events.some((evt) => evt.type === 'tool_calls');
assert.equal(hasToolCall, false);
assert.equal(leakedText.includes('前置正文D。'), true);
assert.equal(leakedText.includes('后置正文E。'), true);
assert.equal(leakedText.toLowerCase().includes('tool_calls'), true);
});
test('sieve passes malformed executable-looking XML through as text', () => {
const chunk = '{"path":"README.MD"}';
const events = runSieve([chunk], ['read_file']);
const leakedText = collectText(events);
const hasToolCalls = events.some((evt) => evt.type === 'tool_calls' && evt.calls?.length > 0);
assert.equal(hasToolCalls, false);
assert.equal(leakedText, chunk);
});
test('sieve keeps bare tool_call XML as plain text without wrapper', () => {
const chunk = 'README.MD';
const events = runSieve([chunk], ['read_file']);
const leakedText = collectText(events);
const hasToolCalls = events.some((evt) => evt.type === 'tool_calls' && evt.calls?.length > 0);
assert.equal(hasToolCalls, false);
assert.equal(leakedText, chunk);
});
test('sieve flushes incomplete captured XML tool blocks by falling back to raw text', () => {
const events = runSieve(
[
'前置正文G。',
'\n',
' \n',
],
['read_file'],
);
const leakedText = collectText(events);
const expected = ['前置正文G。', '\n', ' \n'].join('');
const hasToolCalls = events.some((evt) => evt.type === 'tool_calls' && evt.calls?.length > 0);
assert.equal(hasToolCalls, false);
assert.equal(leakedText, expected);
});
test('sieve captures XML wrapper tags with attributes without leaking wrapper text', () => {
const events = runSieve(
[
'前置正文H。',
'README.MD',
'后置正文I。',
],
['read_file'],
);
const leakedText = collectText(events);
const hasToolCall = events.some((evt) => evt.type === 'tool_calls' && evt.calls?.length > 0);
assert.equal(hasToolCall, true);
assert.equal(leakedText.includes('前置正文H。'), true);
assert.equal(leakedText.includes('后置正文I。'), true);
assert.equal(leakedText.includes(''), false);
assert.equal(leakedText.includes(''), false);
});
test('sieve keeps plain text intact in tool mode when no tool call appears', () => {
const events = runSieve(
['你好,', '这是普通文本回复。', '请继续。'],
['read_file'],
);
const leakedText = collectText(events);
const hasToolCall = events.some((evt) => evt.type === 'tool_calls');
assert.equal(hasToolCall, false);
assert.equal(leakedText, '你好,这是普通文本回复。请继续。');
});
test('sieve keeps plain "tool_calls" prose as text when no valid payload follows', () => {
const events = runSieve(
['前置。', '这里提到 tool_calls 只是解释,不是调用。', '后置。'],
['read_file'],
);
const leakedText = collectText(events);
const hasToolCall = events.some((evt) => evt.type === 'tool_calls' && evt.calls?.length > 0);
assert.equal(hasToolCall, false);
assert.equal(leakedText.includes('tool_calls'), true);
assert.equal(leakedText, '前置。这里提到 tool_calls 只是解释,不是调用。后置。');
});
test('sieve keeps numbered planning prose when no tool payload follows', () => {
const events = runSieve(
['好的,我会依次测试每个工具。\n\n1. 获取当前时间'],
['get_current_time'],
);
const leakedText = collectText(events);
const hasToolCall = events.some((evt) => evt.type === 'tool_calls' && evt.calls?.length > 0);
assert.equal(hasToolCall, false);
assert.equal(leakedText, '好的,我会依次测试每个工具。\n\n1. 获取当前时间');
});
test('sieve does not trigger tool calls for long fenced examples beyond legacy tail window', () => {
const longPadding = 'x'.repeat(700);
const events = runSieve(
[
`前置说明\n\`\`\`json\n${longPadding}\n`,
'{"tool_calls":[{"name":"read_file","input":{"path":"README.MD"}}]}\n',
'```',
'\n后置说明',
],
['read_file'],
);
const hasTool = events.some((evt) => evt.type === 'tool_calls' && evt.calls?.length > 0);
const leakedText = collectText(events);
assert.equal(hasTool, false);
assert.equal(leakedText.includes('后置说明'), true);
assert.equal(leakedText.toLowerCase().includes('tool_calls'), true);
});
test('sieve keeps fence state when triple-backticks are split across chunks', () => {
const events = runSieve(
[
'示例开始\n``',
'`json\n{"tool_calls":[{"name":"read_file","input":{"path":"README.MD"}}]}\n',
'```',
'\n示例结束',
],
['read_file'],
);
const hasTool = events.some((evt) => evt.type === 'tool_calls' && evt.calls?.length > 0);
const leakedText = collectText(events);
assert.equal(hasTool, false);
assert.equal(leakedText.includes('示例结束'), true);
assert.equal(leakedText.toLowerCase().includes('tool_calls'), true);
});
test('formatOpenAIStreamToolCalls reuses ids with the same idStore', () => {
const idStore = new Map();
const calls = [{ name: 'read_file', input: { path: 'README.MD' } }];
const first = formatOpenAIStreamToolCalls(calls, idStore);
const second = formatOpenAIStreamToolCalls(calls, idStore);
assert.equal(first.length, 1);
assert.equal(second.length, 1);
assert.equal(first[0].id, second[0].id);
});
test('parseToolCalls rejects mismatched markup tags', () => {
const payload = 'README.md';
const calls = parseToolCalls(payload, ['read_file']);
assert.equal(calls.length, 0);
});