File size: 1,609 Bytes
b8a03fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { buildSystemPrompt } from '../desktop/backend/model-client.js';
import {
  formatToolResultForModel,
  parseToolCall,
  removeToolCallJson,
  SOVEREIGN_TOOLS,
} from '../desktop/backend/tools.js';

test('model system prompt exposes only the allowlisted sovereign tools', () => {
  const prompt = buildSystemPrompt(SOVEREIGN_TOOLS);

  expect(prompt).toContain('Never ask the user for secret keys');
  expect(prompt).toContain('workspace.search');
  expect(prompt).toContain('repo.verify');
  expect(prompt).not.toContain('git push');
  expect(prompt).not.toContain('shell');
});

test('model tool call parser accepts raw and fenced JSON requests', () => {
  expect(parseToolCall('{"name":"repo.status","input":{}}')).toEqual({
    name: 'repo.status',
    input: {},
  });

  expect(parseToolCall([
    '```json',
    '{"tool_call":{"name":"workspace.read_file","input":{"path":"README.md"}}}',
    '```',
  ].join('\n'))).toEqual({
    name: 'workspace.read_file',
    input: { path: 'README.md' },
  });
});

test('tool call cleanup removes JSON control payloads from chat-visible text', () => {
  const fenced = [
    '```json',
    '{"tool_call":{"name":"repo.status","input":{}}}',
    '```',
  ].join('\n');

  expect(removeToolCallJson(fenced)).toBe('');
  expect(removeToolCallJson(`Need context.\n\n${fenced}`)).toBe('Need context.');
});

test('tool result formatter returns a structured model followup payload', () => {
  expect(formatToolResultForModel(
    { name: 'repo.status', input: {} },
    { name: 'repo.status', ok: true, output: '## main' },
  )).toContain('"tool_result"');
});