import { parseToolArgs } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/_shared'; import { parseEditFileMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/edit-file'; import { parseExecShellCommandMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/exec-shell-command'; import { parseFileGlobSearchMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/file-glob-search'; import { parseGrepSearchMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/grep-search'; import { parseReadFileMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/read-file'; import { parseRunJavascriptMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/run-javascript'; import { parseWriteFileMeta, type WriteFileMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/write-file'; import { AgenticSectionType, BuiltInTool } from '$lib/enums'; import type { AgenticSection } from '$lib/types'; import { abbreviateHome, formatCwdMessage, lastPathSegment, parseCwdMessage } from '$lib/utils'; import { describe, expect, it } from 'vitest'; function makeSection( overrides: Partial = {}, toolName = BuiltInTool.SERVER_READ_FILE ): AgenticSection { return { content: '', toolArgs: JSON.stringify({ path: '/foo.txt' }), toolName, toolResult: undefined, type: AgenticSectionType.TOOL_CALL, ...overrides }; } describe('lastPathSegment', () => { it('returns the last segment of an absolute path', () => { expect(lastPathSegment('/Users/me/code/my-project')).toBe('my-project'); }); it('returns the last segment of a tilde-relative path', () => { expect(lastPathSegment('~/git/llama.brand')).toBe('llama.brand'); }); it('strips trailing slashes', () => { expect(lastPathSegment('/foo/bar/')).toBe('bar'); }); it('strips multiple trailing slashes', () => { expect(lastPathSegment('/foo/bar///')).toBe('bar'); }); it('returns the input unchanged when there is no slash', () => { expect(lastPathSegment('project')).toBe('project'); }); it('returns tilde when only tilde is given', () => { expect(lastPathSegment('~/')).toBe('~'); }); }); describe('abbreviateHome', () => { it('abbreviates paths under home with a tilde', () => { expect(abbreviateHome('/Users/al/Documents/x.txt', '/Users/al')).toBe('~/Documents/x.txt'); }); it('abbreviates home itself to a bare tilde', () => { expect(abbreviateHome('/Users/al', '/Users/al')).toBe('~'); }); it('returns paths outside home unchanged', () => { expect(abbreviateHome('/opt/project', '/Users/al')).toBe('/opt/project'); }); it('does not abbreviate a mere prefix match', () => { expect(abbreviateHome('/Users/alice/x', '/Users/al')).toBe('/Users/alice/x'); }); it('returns the path unchanged when home is unknown', () => { expect(abbreviateHome('/Users/al/Documents', null)).toBe('/Users/al/Documents'); }); }); describe('formatCwdMessage / parseCwdMessage', () => { it('formats a cwd change matching the UI text, with a file link', () => { expect(formatCwdMessage('/Users/al/Documents', '/Users/al')).toBe( 'Set working directory to [file:///Users/al/Documents](~/Documents).' ); }); it('falls back to the basename display when home is unknown', () => { expect(formatCwdMessage('/opt/project', null)).toBe( 'Set working directory to [file:///opt/project](project).' ); }); it('round-trips through the parser', () => { const info = parseCwdMessage(formatCwdMessage('/Users/al/Documents', '/Users/al')); expect(info?.path).toBe('/Users/al/Documents'); expect(info?.display).toBe('~/Documents'); }); it('parses a cwd message even when guidance follows the link', () => { expect( parseCwdMessage( 'Set working directory to [file:///a/b](~/b). Tool calls run with this as their working directory.' ) ).toEqual({ display: '~/b', path: '/a/b' }); }); it('parses the cleared marker', () => { expect(parseCwdMessage('Working directory cleared')).toEqual({ display: '', path: null }); }); it('returns null for non-cwd content', () => { expect(parseCwdMessage('hello there')).toBeNull(); }); }); describe('parseToolArgs (shared)', () => { it('returns null when the section has no toolArgs', () => { const result = parseToolArgs( BuiltInTool.SERVER_READ_FILE, makeSection({ toolArgs: undefined }) ); expect(result).toBeNull(); }); it('returns null when the tool name does not match', () => { const result = parseToolArgs( BuiltInTool.SERVER_READ_FILE, makeSection({ toolArgs: '{"path":"/x"}' }, BuiltInTool.SERVER_WRITE_FILE) ); expect(result).toBeNull(); }); it('returns null when args are not valid final JSON (partial: false)', () => { const result = parseToolArgs( BuiltInTool.SERVER_READ_FILE, makeSection({ toolArgs: '{"path": "/foo.tx' }) ); expect(result).toBeNull(); }); it('returns parsed args when valid final JSON', () => { const result = parseToolArgs( BuiltInTool.SERVER_READ_FILE, makeSection({ toolArgs: '{"path":"/foo.txt"}' }) ); expect(result).toEqual({ path: '/foo.txt' }); }); it('accepts partial JSON when partial: true', () => { const result = parseToolArgs( BuiltInTool.SERVER_READ_FILE, makeSection({ toolArgs: '{"path": "/foo.tx' }), { partial: true } ); expect(result).toEqual({ path: '/foo.tx' }); }); }); describe('parseWriteFileMeta', () => { it('returns null for sections with a different tool name', () => { expect( parseWriteFileMeta( makeSection({ toolArgs: '{"path":"/x","content":"y"}', toolName: BuiltInTool.SERVER_READ_FILE }) ) ).toBeNull(); }); it('returns null when args have no path-like field', () => { expect( parseWriteFileMeta( makeSection({ toolArgs: '{"content":"x"}', toolName: BuiltInTool.SERVER_WRITE_FILE }) ) ).toBeNull(); }); it('accepts partial args (renders incrementally as content streams in)', () => { const meta = parseWriteFileMeta( makeSection({ toolArgs: '{"path":"/foo.t', toolName: BuiltInTool.SERVER_WRITE_FILE }) ); expect(meta?.filePath).toBe('/foo.t'); }); it('returns file path, language, content, bytes, resultMessage', () => { const meta = parseWriteFileMeta( makeSection( { toolArgs: '{"path":"/foo.ts","content":"x"}', toolName: BuiltInTool.SERVER_WRITE_FILE, toolResult: '{"result":"wrote","bytes":42}' }, BuiltInTool.SERVER_WRITE_FILE ) ); expect(meta).toMatchObject>({ bytesWritten: 42, content: 'x', filePath: '/foo.ts', language: expect.any(String), resultMessage: 'wrote' }); }); it('surfaces errorMessage from the result blob', () => { const meta = parseWriteFileMeta( makeSection({ toolArgs: '{"path":"/foo","content":"x"}', toolName: BuiltInTool.SERVER_WRITE_FILE, toolResult: '{"error":"permission denied"}' }) ); expect(meta?.errorMessage).toBe('permission denied'); }); }); describe('parseEditFileMeta', () => { it('parses edits array and applies editsApplied from the result', () => { const section = makeSection( { toolArgs: '{"path":"/foo.ts","edits":[{"old_text":"a","new_text":"b"},{"old_text":"c","new_text":"d"}]}', toolName: BuiltInTool.SERVER_EDIT_FILE, toolResult: '{"result":"ok","edits_applied":2}' }, BuiltInTool.SERVER_EDIT_FILE ); const meta = parseEditFileMeta(section); expect(meta?.edits).toEqual([ { newText: 'b', oldText: 'a' }, { newText: 'd', oldText: 'c' } ]); expect(meta?.editsApplied).toBe(2); expect(meta?.resultMessage).toBe('ok'); }); it('drops edits with empty old_text', () => { const section = makeSection( { toolArgs: '{"path":"/foo","edits":[{"old_text":""},{"old_text":"a","new_text":""}]}', toolName: BuiltInTool.SERVER_EDIT_FILE }, BuiltInTool.SERVER_EDIT_FILE ); const meta = parseEditFileMeta(section); // First entry is dropped (empty old_text). Second is kept // (empty new_text is fine - it's the "delete" case). expect(meta?.edits).toEqual([{ newText: '', oldText: 'a' }]); }); it('errorMessage wins over result message', () => { const section = makeSection( { toolArgs: '{"path":"/foo"}', toolName: BuiltInTool.SERVER_EDIT_FILE, toolResult: '{"error":"bad path","result":"ok"}' }, BuiltInTool.SERVER_EDIT_FILE ); const meta = parseEditFileMeta(section); expect(meta?.errorMessage).toBe('bad path'); expect(meta?.resultMessage).toBeUndefined(); }); }); describe('parseReadFileMeta', () => { it('parses file name alone (no range)', () => { const meta = parseReadFileMeta( makeSection({ toolArgs: '{"path":"/foo.txt"}' }, BuiltInTool.SERVER_READ_FILE) ); expect(meta?.fileName).toBe('foo.txt'); expect(meta?.lineRange).toBeNull(); }); it('parses start_line + end_line into a range', () => { const meta = parseReadFileMeta( makeSection( { toolArgs: '{"path":"/foo.ts","start_line":10,"end_line":20}' }, BuiltInTool.SERVER_READ_FILE ) ); expect(meta?.lineRange).toEqual({ end: 20, start: 10 }); }); it('parses start_line + line_count into a range', () => { const meta = parseReadFileMeta( makeSection( { toolArgs: '{"path":"/foo.ts","start_line":10,"line_count":5}' }, BuiltInTool.SERVER_READ_FILE ) ); expect(meta?.lineRange).toEqual({ end: 14, start: 10 }); }); it('returns null when args cannot be parsed', () => { expect( parseReadFileMeta(makeSection({ toolArgs: '{bad' }, BuiltInTool.SERVER_READ_FILE)) ).toBeNull(); }); }); describe('parseGrepSearchMeta', () => { it('returns null when path or pattern is missing', () => { expect( parseGrepSearchMeta( makeSection({ toolArgs: '{"pattern":"foo"}', toolName: BuiltInTool.SERVER_GREP_SEARCH }) ) ).toBeNull(); expect( parseGrepSearchMeta( makeSection({ toolArgs: '{"path":"/x"}', toolName: BuiltInTool.SERVER_GREP_SEARCH }) ) ).toBeNull(); }); it('parses structured plain_text_response into matches', () => { const meta = parseGrepSearchMeta( makeSection( { toolArgs: '{"path":"/x","pattern":"foo"}', toolName: BuiltInTool.SERVER_GREP_SEARCH, toolResult: JSON.stringify({ plain_text_response: 'a.ts:hello\nb.ts:world' }) }, BuiltInTool.SERVER_GREP_SEARCH ) ); expect(meta?.matches).toHaveLength(2); expect(meta?.matches[0]).toEqual({ content: 'hello', file: 'a.ts' }); }); it('falls back to raw-text parsing when result is not JSON', () => { const meta = parseGrepSearchMeta( makeSection( { toolArgs: '{"path":"/x","pattern":"foo"}', toolName: BuiltInTool.SERVER_GREP_SEARCH, toolResult: 'a.ts:hello\nb.ts:world' }, BuiltInTool.SERVER_GREP_SEARCH ) ); expect(meta?.matches).toHaveLength(2); }); it('parses line numbers when return_line_numbers is true', () => { const meta = parseGrepSearchMeta( makeSection( { toolArgs: '{"path":"/x","pattern":"foo","return_line_numbers":true}', toolName: BuiltInTool.SERVER_GREP_SEARCH, toolResult: 'a.ts:12:hello' }, BuiltInTool.SERVER_GREP_SEARCH ) ); expect(meta?.matches[0]).toEqual({ content: 'hello', file: 'a.ts', line: 12 }); expect(meta?.showLineNumbers).toBe(true); }); }); describe('parseFileGlobSearchMeta', () => { it('falls back to raw-text parsing when result is not JSON', () => { const meta = parseFileGlobSearchMeta( makeSection( { toolArgs: '{"path":"/x"}', toolName: BuiltInTool.SERVER_FILE_GLOB_SEARCH, toolResult: 'a.ts\nb.ts' }, BuiltInTool.SERVER_FILE_GLOB_SEARCH ) ); expect(meta?.matches).toEqual(['a.ts', 'b.ts']); }); it('parses plain_text_response from a JSON object', () => { const meta = parseFileGlobSearchMeta( makeSection( { toolArgs: '{"path":"/x"}', toolName: BuiltInTool.SERVER_FILE_GLOB_SEARCH, toolResult: JSON.stringify({ plain_text_response: 'a.ts\nb.ts' }) }, BuiltInTool.SERVER_FILE_GLOB_SEARCH ) ); expect(meta?.matches).toEqual(['a.ts', 'b.ts']); }); it('surfaces errorMessage from the result blob', () => { const meta = parseFileGlobSearchMeta( makeSection( { toolArgs: '{"path":"/x"}', toolName: BuiltInTool.SERVER_FILE_GLOB_SEARCH, toolResult: JSON.stringify({ error: 'permission denied' }) }, BuiltInTool.SERVER_FILE_GLOB_SEARCH ) ); expect(meta?.errorMessage).toBe('permission denied'); }); }); describe('parseRunJavascriptMeta', () => { it('returns null when code is missing', () => { expect( parseRunJavascriptMeta( makeSection({ toolArgs: '{}', toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT }) ) ).toBeNull(); }); it('reads code and timeout', () => { const meta = parseRunJavascriptMeta( makeSection( { toolArgs: '{"code":"Math.PI","timeout_ms":5000}', toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT }, BuiltInTool.BROWSER_RUN_JAVASCRIPT ) ); expect(meta?.code).toBe('Math.PI'); expect(meta?.timeoutMs).toBe(5000); }); it('reads error field from a JSON-object result', () => { const meta = parseRunJavascriptMeta( makeSection( { toolArgs: '{"code":"throw new Error()"}', toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT, toolResult: JSON.stringify({ error: 'undefined is not a function' }) }, BuiltInTool.BROWSER_RUN_JAVASCRIPT ) ); expect(meta?.errorMessage).toBe('undefined is not a function'); }); it('does NOT treat a JSON-array result as an error', () => { // SandboxService returns successful output as a JSON array; // only JSON objects carry `error`. Raw arrays must round-trip // through unchanged. const meta = parseRunJavascriptMeta( makeSection( { toolArgs: '{"code":"[1,2,3]"}', toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT, toolResult: '[1,2,3]' }, BuiltInTool.BROWSER_RUN_JAVASCRIPT ) ); expect(meta?.errorMessage).toBeUndefined(); }); it('scans a non-JSON string result for an `Error:` line', () => { const meta = parseRunJavascriptMeta( makeSection( { toolArgs: '{"code":"foo"}', toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT, toolResult: 'Error: undefined is not a function\n at :1:1' }, BuiltInTool.BROWSER_RUN_JAVASCRIPT ) ); expect(meta?.errorMessage).toBe('undefined is not a function'); }); }); describe('parseExecShellCommandMeta', () => { it('reads command from the args', () => { const meta = parseExecShellCommandMeta( makeSection( { toolArgs: '{"command":"ls -la"}', toolName: BuiltInTool.SERVER_EXEC_SHELL_COMMAND }, BuiltInTool.SERVER_EXEC_SHELL_COMMAND ) ); expect(meta?.command).toBe('ls -la'); }); it('accepts cmd / shell_command aliases', () => { expect( parseExecShellCommandMeta( makeSection( { toolArgs: '{"cmd":"ls"}', toolName: BuiltInTool.SERVER_EXEC_SHELL_COMMAND }, BuiltInTool.SERVER_EXEC_SHELL_COMMAND ) )?.command ).toBe('ls'); expect( parseExecShellCommandMeta( makeSection( { toolArgs: '{"shell_command":"ls"}', toolName: BuiltInTool.SERVER_EXEC_SHELL_COMMAND }, BuiltInTool.SERVER_EXEC_SHELL_COMMAND ) )?.command ).toBe('ls'); }); it('returns null when no command alias is present', () => { expect( parseExecShellCommandMeta( makeSection( { toolArgs: '{"cwd":"/x"}', toolName: BuiltInTool.SERVER_EXEC_SHELL_COMMAND }, BuiltInTool.SERVER_EXEC_SHELL_COMMAND ) ) ).toBeNull(); }); });