Spaces:
Running
Running
File size: 6,067 Bytes
6c30253 | 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 | 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, []);
});
|