Spaces:
Running
Running
| 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, []); | |
| }); | |