File size: 2,631 Bytes
0ed8124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21ad36a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ed8124
 
 
 
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
import { describe, expect, it } from 'vitest';
import { parseThoughtStream } from './think-parser';
import { parseToolCalls, renderToolResponse } from './tool-protocol';

describe('parseThoughtStream', () => {
  it('separates complete reasoning from visible content', () => {
    expect(parseThoughtStream('<think>check evidence</think>final answer')).toEqual({
      content: 'final answer',
      reasoning: 'check evidence',
      isThinking: false,
      hasReasoning: true,
    });
  });

  it('handles a closing-only marker emitted after a template-opened thought', () => {
    expect(parseThoughtStream('private chain\n</think>\npublic answer').content).toBe('public answer');
  });

  it('buffers a partial opening marker', () => {
    expect(parseThoughtStream('answer<thi').content).toBe('answer');
  });
});

describe('parseToolCalls', () => {
  it('extracts multiple strict tool envelopes', () => {
    const result = parseToolCalls(
      'note<tool_call>{"name":"memory","arguments":{"key":"x"}}</tool_call>' +
      '<tool_call>{"name":"js_eval","arguments":{"source":"return 2"}}</tool_call>',
    );
    expect(result.content).toBe('note');
    expect(result.calls.map((call) => call.name)).toEqual(['memory', 'js_eval']);
    expect(result.errors).toEqual([]);
  });

  it('reports incomplete streams without executing them', () => {
    expect(parseToolCalls('<tool_call>{"name":"memory"').incomplete).toBe(true);
  });

  it('parses the Qwen3.5 function and parameter format from the 27B chat template', () => {
    const result = parseToolCalls(`before
<tool_call>
<function=html_artifact>
<parameter=html>
<!doctype html><html><body><h1>Tool proof</h1></body></html>
</parameter>
<parameter=title>
Tool proof
</parameter>
</function>
</tool_call>`);
    expect(result.content).toBe('before');
    expect(result.errors).toEqual([]);
    expect(result.calls).toEqual([expect.objectContaining({
      name: 'html_artifact',
      arguments: {
        html: '<!doctype html><html><body><h1>Tool proof</h1></body></html>',
        title: 'Tool proof',
      },
    })]);
  });

  it('rejects malformed or duplicate Qwen3.5 parameters', () => {
    const malformed = parseToolCalls(
      '<tool_call><function=memory><parameter=action>get</parameter><parameter=action>list</parameter></function></tool_call>',
    );
    expect(malformed.calls).toEqual([]);
    expect(malformed.errors).toEqual(['function tool call repeats parameter action']);
  });

  it('wraps deterministic tool responses', () => {
    expect(renderToolResponse({ ok: true })).toBe('<tool_response>\n{"ok":true}\n</tool_response>');
  });
});