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