import test from 'node:test'; import assert from 'node:assert/strict'; import { calculate, executeBuiltin, prepareToolCall, searchRecipeByDish } from '../src/tools/tool-registry.js'; import { createToolStreamFilter, displayTextFromRaw, parseToolCalls } from '../src/tools/tool-protocol.js'; test('calculator evaluates arithmetic without JavaScript evaluation', () => { assert.equal(calculate('2 + 3 * (4 - 1)^2'), 29); assert.equal(calculate('1.5e2 / 3'), 50); assert.throws(() => calculate('globalThis.alert(1)'), /Unsupported character/); assert.throws(() => calculate('2 / 0'), /Division by zero/); }); test('calculator supports safe trigonometry for geometry problems', () => { assert.ok(Math.abs(calculate('sin(pi / 2)') - 1) < 1e-12); assert.ok(Math.abs(calculate('10 * tan_deg(30)') - 5.773502691896257) < 1e-12); assert.ok(Math.abs(calculate('sqrt(3^2 + 4^2)') - 5) < 1e-12); assert.ok(Math.abs(calculate('asin_deg(0.5)') - 30) < 1e-12); assert.throws(() => calculate('fetch(1)'), /Unsupported function/); assert.throws(() => calculate('sqrt(-1)'), /finite number/); }); test('native Python-style protocol parses single and multiple calls', () => { const calls = parseToolCalls("before<|tool_call_start|>[calculate(expression='2 + 2'), current_datetime(time_zone='UTC')]<|tool_call_end|>"); assert.deepEqual(calls.map(call => call.name), ['calculate', 'current_datetime']); assert.deepEqual(calls[0].arguments, { expression: '2 + 2' }); assert.deepEqual(calls[1].arguments, { time_zone: 'UTC' }); assert.equal(displayTextFromRaw("Answer<|tool_call_start|>[calculate(expression='1')]<|tool_call_end|><|im_end|>"), 'Answer'); }); test('protocol supports positional, primitive, and nested JSON values', () => { const [call] = parseToolCalls('<|tool_call_start|>random_integer(1, max=4)<|tool_call_end|>'); assert.deepEqual(call.positional, [1]); assert.deepEqual(call.arguments, { max: 4 }); const [nested] = parseToolCalls('<|tool_call_start|>custom(payload={"ok":true,"items":[1,2]}, optional=None)<|tool_call_end|>'); assert.deepEqual(nested.arguments, { payload: { ok: true, items: [1, 2] }, optional: null }); assert.throws(() => parseToolCalls('<|tool_call_start|>broken(a=1'), /incomplete/); }); test('stream filter suppresses tool syntax even across chunks', () => { let visible = ''; const states = []; const filter = createToolStreamFilter(chunk => { visible += chunk; }, { onToolCallStart: () => states.push('start'), onToolCallEnd: () => states.push('end'), }); for (const chunk of ['Hello ', '<|tool_', "call_start|>[calculate(expression='2')]<|tool_call_", 'end|>', ' world']) filter.push(chunk); filter.finish(); assert.equal(visible, 'Hello world'); assert.deepEqual(states, ['start', 'end']); }); test('tool schemas and arguments are validated', () => { const tool = { id: 'test:lookup_order', source: 'test', enabled: true, name: 'lookup_order', description: 'Look up an order.', parameters: { type: 'object', properties: { order_id: { type: 'string' } }, required: ['order_id'], additionalProperties: false } }; const prepared = prepareToolCall({ name: 'lookup_order', arguments: { order_id: 'A-1' }, positional: [] }, [tool]); assert.deepEqual(prepared.args, { order_id: 'A-1' }); assert.throws(() => prepareToolCall({ name: 'lookup_order', arguments: {}, positional: [] }, [tool]), /Missing required/); tool.enabled = false; assert.throws(() => prepareToolCall({ name: 'lookup_order', arguments: { order_id: 'A-1' }, positional: [] }, [tool]), /Unknown or disabled/); }); test('datetime and random built-ins reject invalid inputs', async () => { await assert.rejects(executeBuiltin('current_datetime', { time_zone: 'Definitely/Invalid' }), /Invalid IANA time zone/); await assert.rejects(executeBuiltin('random_integer', { min: 5, max: 4 }), /min ≤ max/); for (let index = 0; index < 20; index += 1) { const result = await executeBuiltin('random_integer', { min: -2, max: 2 }); assert.ok(result.value >= -2 && result.value <= 2); } }); test('dish recipe search prefers an exact completed-dish name', async () => { const fetcher = async url => { assert.equal(new URL(url).searchParams.get('s'), 'Pad Thai'); return { ok: true, status: 200, json: async () => ({ meals: [ { idMeal: '2', strMeal: 'Quick Pad Thai', strIngredient1: 'Noodles' }, { idMeal: '1', strMeal: 'Pad Thai', strCategory: 'Main', strArea: 'Thai', strIngredient1: 'Rice Noodles', strMeasure1: '200g', strInstructions: 'Cook it.', strSource: 'https://example.com/pad-thai' }, ] }), }; }; const result = await searchRecipeByDish('Pad Thai', undefined, fetcher); assert.equal(result.provider, 'TheMealDB'); assert.equal(result.recipes[0].name, 'Pad Thai'); assert.deepEqual(result.recipes[0].ingredients, ['200g Rice Noodles']); assert.equal(result.recipes[0].source_url, 'https://example.com/pad-thai'); }); test('dish recipe search accepts whole-word title matches and rejects partial false matches', async () => { const response = meals => ({ ok: true, status: 200, json: async () => ({ meals }) }); const friedRice = await searchRecipeByDish('fried rice', undefined, async () => response([ { idMeal: '1', strMeal: 'Chicken Fried Rice' }, ])); assert.equal(friedRice.recipes[0].name, 'Chicken Fried Rice'); const falsePartial = await searchRecipeByDish('chicken parm', undefined, async () => response([ { idMeal: '2', strMeal: 'Chicken Parmentier' }, ])); assert.deepEqual(falsePartial.recipes, []); }); test('dish recipe search validates names and handles no result', async () => { await assert.rejects(searchRecipeByDish('', undefined, async () => {}), /between 1 and 100/); await assert.rejects(searchRecipeByDish('x'.repeat(101), undefined, async () => {}), /between 1 and 100/); const result = await searchRecipeByDish('Caesar Salad', undefined, async () => ({ ok: true, status: 200, json: async () => ({ meals: null }) })); assert.deepEqual(result.recipes, []); });