Spaces:
Runtime error
Runtime error
File size: 6,853 Bytes
077865a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | import { describe, it, expect } from 'vitest';
import {
rescueInlineToolCalls,
startsWithDialectMarker,
couldBecomeDialectMarker,
containsDialectMarker,
} from '../../lib/tool-call-rescue.js';
const TOOLS = new Set(['Read', 'Write', 'Bash', 'Grep', 'list_dir']);
describe('inline tool-call dialect rescue', () => {
describe('Kimi/DeepSeek token dialect', () => {
it('parses a well-formed functions.NAME:IDX call', () => {
const text = '<|tool_calls_section_begin|><|tool_call_begin|>functions.Read:0<|tool_call_argument_begin|>{"file_path":"/tmp/x.txt"}<|tool_call_end|><|tool_calls_section_end|>';
const r = rescueInlineToolCalls(text, TOOLS);
expect(r.detected).toBe(true);
expect(r.calls).toEqual([{ name: 'Read', arguments: '{"file_path":"/tmp/x.txt"}' }]);
expect(r.cleanText).toBe('');
});
it('parses multiple calls and keeps surrounding prose as cleanText', () => {
const text = 'Let me check.\n<|tool_call_begin|>functions.Read:0<|tool_call_argument_begin|>{"file_path":"/a"}<|tool_call_end|><|tool_call_begin|>functions.Grep:1<|tool_call_argument_begin|>{"pattern":"TODO"}<|tool_call_end|>';
const r = rescueInlineToolCalls(text, TOOLS);
expect(r.calls).toHaveLength(2);
expect(r.calls![1]).toEqual({ name: 'Grep', arguments: '{"pattern":"TODO"}' });
expect(r.cleanText).toBe('Let me check.');
});
it('reports detected-but-unparseable when the id token is degraded (no function name)', () => {
// The live capture that motivated this module: an opaque id where
// functions.NAME:IDX should be — there is no way to know the tool.
const text = '<|tool_calls_section_begin|> <|tool_call_begin|> chatcmpl-tool-bde5fae954a11b1b <|tool_call_argument_begin|> {"file_path": "/private/tmp/demo-project/src"} <|tool_call_end|>';
const r = rescueInlineToolCalls(text, TOOLS);
expect(r.detected).toBe(true);
expect(r.calls).toBeNull();
});
it('rejects a call naming a tool the request never declared', () => {
const text = '<|tool_call_begin|>functions.DropTables:0<|tool_call_argument_begin|>{}<|tool_call_end|>';
const r = rescueInlineToolCalls(text, TOOLS);
expect(r.detected).toBe(true);
expect(r.calls).toBeNull();
});
});
describe('llama/groq <function=> dialect', () => {
it('parses <function=NAME{json}</function> (the failed_generation shape)', () => {
const text = '<function=Bash{"command": "npm run build"}</function>';
const r = rescueInlineToolCalls(text, TOOLS);
expect(r.detected).toBe(true);
expect(r.calls).toEqual([{ name: 'Bash', arguments: '{"command": "npm run build"}' }]);
});
it('parses the variant with a closing > after the name', () => {
const text = '<function=Read>{"file_path": "/tmp/a"}</function>';
const r = rescueInlineToolCalls(text, TOOLS);
expect(r.calls).toEqual([{ name: 'Read', arguments: '{"file_path": "/tmp/a"}' }]);
});
it('treats array-shaped arguments as unparseable (live Groq failure shape)', () => {
const text = '<function=Bash [{"a": "npm run build"}, {"b": "/tmp"}]</function>';
const r = rescueInlineToolCalls(text, TOOLS);
expect(r.detected).toBe(true);
expect(r.calls).toBeNull();
});
});
describe('Qwen/Hermes <tool_call> XML dialect', () => {
it('parses a single block with object arguments', () => {
const text = '<tool_call>\n{"name": "Write", "arguments": {"file_path": "/tmp/n.txt", "content": "hi"}}\n</tool_call>';
const r = rescueInlineToolCalls(text, TOOLS);
expect(r.calls).toHaveLength(1);
expect(r.calls![0].name).toBe('Write');
expect(JSON.parse(r.calls![0].arguments)).toEqual({ file_path: '/tmp/n.txt', content: 'hi' });
});
it('accepts the "parameters" key and multiple blocks', () => {
const text = '<tool_call>{"name": "Read", "parameters": {"file_path": "/a"}}</tool_call><tool_call>{"name": "Grep", "arguments": {"pattern": "x"}}</tool_call>';
const r = rescueInlineToolCalls(text, TOOLS);
expect(r.calls).toHaveLength(2);
});
it('treats a truncated unclosed block as unparseable', () => {
const text = '<tool_call>{"name": "Read", "argum';
const r = rescueInlineToolCalls(text, TOOLS);
expect(r.detected).toBe(true);
expect(r.calls).toBeNull();
});
});
describe('bare / fenced JSON dialect (schema-gated)', () => {
it('rescues a bare JSON object naming a known tool', () => {
const r = rescueInlineToolCalls('{"name": "list_dir", "arguments": {"path": "/tmp"}}', TOOLS);
expect(r.detected).toBe(true);
expect(r.calls).toEqual([{ name: 'list_dir', arguments: '{"path":"/tmp"}' }]);
});
it('rescues a ```json fenced call', () => {
const r = rescueInlineToolCalls('```json\n{"name": "Bash", "arguments": {"command": "ls"}}\n```', TOOLS);
expect(r.calls).toHaveLength(1);
});
it('does NOT touch ordinary JSON answers that do not name a requested tool', () => {
const r = rescueInlineToolCalls('{"name": "Tashfeen", "age": 30}', TOOLS);
expect(r.detected).toBe(false);
expect(r.cleanText).toBe('{"name": "Tashfeen", "age": 30}');
});
it('does NOT touch plain prose', () => {
const r = rescueInlineToolCalls('The port is 49152, configured in config.json.', TOOLS);
expect(r.detected).toBe(false);
});
});
describe('stream hold-window helpers', () => {
it('startsWithDialectMarker matches all dialects, with leading whitespace', () => {
expect(startsWithDialectMarker('<|tool_calls_section_begin|>x')).toBe(true);
expect(startsWithDialectMarker(' <|tool_call_begin|>')).toBe(true);
expect(startsWithDialectMarker('\n<tool_call>{}')).toBe(true);
expect(startsWithDialectMarker('<function=Bash{')).toBe(true);
expect(startsWithDialectMarker('Hello!')).toBe(false);
expect(startsWithDialectMarker('<html>')).toBe(false);
});
it('couldBecomeDialectMarker holds strict prefixes and releases divergent text', () => {
expect(couldBecomeDialectMarker('<')).toBe(true);
expect(couldBecomeDialectMarker('<|to')).toBe(true);
expect(couldBecomeDialectMarker('<fun')).toBe(true);
expect(couldBecomeDialectMarker('<tool_ca')).toBe(true);
expect(couldBecomeDialectMarker('<div')).toBe(false);
expect(couldBecomeDialectMarker('Hi')).toBe(false);
// Full marker present is no longer a *prefix* — startsWith takes over.
expect(couldBecomeDialectMarker('<tool_call>')).toBe(false);
});
it('containsDialectMarker finds markers mid-text', () => {
expect(containsDialectMarker('prose then <function=Read{"a":1}</function>')).toBe(true);
expect(containsDialectMarker('plain text')).toBe(false);
});
});
});
|