char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aleksander Grygierui: Improve Chat Messages rendering performance (#28460)0afb805b1

char/aliceai-t5-moe
18.7 KiB650 linesraw
1import { parseToolArgs } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/_shared';
2import {
3	parseEditFileMeta,
4	parseEditFileTitleMeta
5} from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/edit-file';
6import { parseExecShellCommandMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/exec-shell-command';
7import { parseFileGlobSearchMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/file-glob-search';
8import { parseGrepSearchMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/grep-search';
9import { parseReadFileMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/read-file';
10import { parseRunJavascriptMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/run-javascript';
11import {
12	parseWriteFileMeta,
13	parseWriteFileTitleMeta
14} from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/write-file';
15import { AgenticSectionType, BuiltInTool } from '$lib/enums';
16import type { AgenticSection, WriteFileMeta } from '$lib/types';
17import { abbreviateHome, formatCwdMessage, lastPathSegment, parseCwdMessage } from '$lib/utils';
18import { describe, expect, it } from 'vitest';
19
20function makeSection(
21	overrides: Partial<AgenticSection> = {},
22	toolName = BuiltInTool.SERVER_READ_FILE
23): AgenticSection {
24	return {
25		content: '',
26		toolArgs: JSON.stringify({ path: '/foo.txt' }),
27		toolName,
28		toolResult: undefined,
29		type: AgenticSectionType.TOOL_CALL,
30		...overrides
31	};
32}
33
34describe('lastPathSegment', () => {
35	it('returns the last segment of an absolute path', () => {
36		expect(lastPathSegment('/Users/me/code/my-project')).toBe('my-project');
37	});
38
39	it('returns the last segment of a tilde-relative path', () => {
40		expect(lastPathSegment('~/git/llama.brand')).toBe('llama.brand');
41	});
42
43	it('strips trailing slashes', () => {
44		expect(lastPathSegment('/foo/bar/')).toBe('bar');
45	});
46
47	it('strips multiple trailing slashes', () => {
48		expect(lastPathSegment('/foo/bar///')).toBe('bar');
49	});
50
51	it('returns the input unchanged when there is no slash', () => {
52		expect(lastPathSegment('project')).toBe('project');
53	});
54
55	it('returns tilde when only tilde is given', () => {
56		expect(lastPathSegment('~/')).toBe('~');
57	});
58});
59
60describe('abbreviateHome', () => {
61	it('abbreviates paths under home with a tilde', () => {
62		expect(abbreviateHome('/Users/al/Documents/x.txt', '/Users/al')).toBe('~/Documents/x.txt');
63	});
64
65	it('abbreviates home itself to a bare tilde', () => {
66		expect(abbreviateHome('/Users/al', '/Users/al')).toBe('~');
67	});
68
69	it('returns paths outside home unchanged', () => {
70		expect(abbreviateHome('/opt/project', '/Users/al')).toBe('/opt/project');
71	});
72
73	it('does not abbreviate a mere prefix match', () => {
74		expect(abbreviateHome('/Users/alice/x', '/Users/al')).toBe('/Users/alice/x');
75	});
76
77	it('returns the path unchanged when home is unknown', () => {
78		expect(abbreviateHome('/Users/al/Documents', null)).toBe('/Users/al/Documents');
79	});
80});
81
82describe('formatCwdMessage / parseCwdMessage', () => {
83	it('formats a cwd change matching the UI text, with a file link', () => {
84		expect(formatCwdMessage('/Users/al/Documents', '/Users/al')).toBe(
85			'Set working directory to [file:///Users/al/Documents](~/Documents).'
86		);
87	});
88
89	it('falls back to the basename display when home is unknown', () => {
90		expect(formatCwdMessage('/opt/project', null)).toBe(
91			'Set working directory to [file:///opt/project](project).'
92		);
93	});
94
95	it('round-trips through the parser', () => {
96		const info = parseCwdMessage(formatCwdMessage('/Users/al/Documents', '/Users/al'));
97
98		expect(info?.path).toBe('/Users/al/Documents');
99		expect(info?.display).toBe('~/Documents');
100	});
101
102	it('parses a cwd message even when guidance follows the link', () => {
103		expect(
104			parseCwdMessage(
105				'Set working directory to [file:///a/b](~/b). Tool calls run with this as their working directory.'
106			)
107		).toEqual({ display: '~/b', path: '/a/b' });
108	});
109
110	it('parses the cleared marker', () => {
111		expect(parseCwdMessage('Working directory cleared')).toEqual({ display: '', path: null });
112	});
113
114	it('returns null for non-cwd content', () => {
115		expect(parseCwdMessage('hello there')).toBeNull();
116	});
117});
118
119describe('parseToolArgs (shared)', () => {
120	it('returns null when the section has no toolArgs', () => {
121		const result = parseToolArgs(
122			BuiltInTool.SERVER_READ_FILE,
123			makeSection({ toolArgs: undefined })
124		);
125
126		expect(result).toBeNull();
127	});
128
129	it('returns null when the tool name does not match', () => {
130		const result = parseToolArgs(
131			BuiltInTool.SERVER_READ_FILE,
132			makeSection({ toolArgs: '{"path":"/x"}' }, BuiltInTool.SERVER_WRITE_FILE)
133		);
134
135		expect(result).toBeNull();
136	});
137
138	it('returns null when args are not valid final JSON (partial: false)', () => {
139		const result = parseToolArgs(
140			BuiltInTool.SERVER_READ_FILE,
141			makeSection({ toolArgs: '{"path": "/foo.tx' })
142		);
143
144		expect(result).toBeNull();
145	});
146
147	it('returns parsed args when valid final JSON', () => {
148		const result = parseToolArgs(
149			BuiltInTool.SERVER_READ_FILE,
150			makeSection({ toolArgs: '{"path":"/foo.txt"}' })
151		);
152
153		expect(result).toEqual({ path: '/foo.txt' });
154	});
155
156	it('accepts partial JSON when partial: true', () => {
157		const result = parseToolArgs(
158			BuiltInTool.SERVER_READ_FILE,
159			makeSection({ toolArgs: '{"path": "/foo.tx' }),
160			{ partial: true }
161		);
162
163		expect(result).toEqual({ path: '/foo.tx' });
164	});
165});
166
167describe('parseWriteFileMeta', () => {
168	it('returns null for sections with a different tool name', () => {
169		expect(
170			parseWriteFileMeta(
171				makeSection({
172					toolArgs: '{"path":"/x","content":"y"}',
173					toolName: BuiltInTool.SERVER_READ_FILE
174				})
175			)
176		).toBeNull();
177	});
178
179	it('returns null when args have no path-like field', () => {
180		expect(
181			parseWriteFileMeta(
182				makeSection({ toolArgs: '{"content":"x"}', toolName: BuiltInTool.SERVER_WRITE_FILE })
183			)
184		).toBeNull();
185	});
186
187	it('accepts partial args (renders incrementally as content streams in)', () => {
188		const meta = parseWriteFileMeta(
189			makeSection({ toolArgs: '{"path":"/foo.t', toolName: BuiltInTool.SERVER_WRITE_FILE })
190		);
191
192		expect(meta?.filePath).toBe('/foo.t');
193	});
194
195	it('returns file path, language, content, bytes, resultMessage', () => {
196		const meta = parseWriteFileMeta(
197			makeSection(
198				{
199					toolArgs: '{"path":"/foo.ts","content":"x"}',
200					toolName: BuiltInTool.SERVER_WRITE_FILE,
201					toolResult: '{"result":"wrote","bytes":42}'
202				},
203				BuiltInTool.SERVER_WRITE_FILE
204			)
205		);
206
207		expect(meta).toMatchObject<Partial<WriteFileMeta>>({
208			bytesWritten: 42,
209			content: 'x',
210			filePath: '/foo.ts',
211			language: expect.any(String),
212			resultMessage: 'wrote'
213		});
214	});
215
216	it('surfaces errorMessage from the result blob', () => {
217		const meta = parseWriteFileMeta(
218			makeSection({
219				toolArgs: '{"path":"/foo","content":"x"}',
220				toolName: BuiltInTool.SERVER_WRITE_FILE,
221				toolResult: '{"error":"permission denied"}'
222			})
223		);
224
225		expect(meta?.errorMessage).toBe('permission denied');
226	});
227});
228
229describe('parseWriteFileTitleMeta', () => {
230	it('matches the full meta for path, language and result fields', () => {
231		const args = JSON.stringify({ content: 'x'.repeat(50_000), path: '/foo.ts' });
232		const toolResult = '{"result":"wrote","bytes":42}';
233		const section = makeSection(
234			{ toolArgs: args, toolName: BuiltInTool.SERVER_WRITE_FILE, toolResult },
235			BuiltInTool.SERVER_WRITE_FILE
236		);
237		const full = parseWriteFileMeta(section);
238		const title = parseWriteFileTitleMeta(section);
239
240		expect(title?.filePath).toBe(full?.filePath);
241		expect(title?.fileName).toBe(full?.fileName);
242		expect(title?.language).toBe(full?.language);
243		expect(title?.bytesWritten).toBe(full?.bytesWritten);
244		expect(title?.resultMessage).toBe(full?.resultMessage);
245		expect(title?.errorMessage).toBe(full?.errorMessage);
246	});
247
248	it('extracts a path with escaped characters without parsing the content blob', () => {
249		const section = makeSection(
250			{
251				toolArgs: '{"path":"/a\\nb\\"c/d.ts","content":"x"}',
252				toolName: BuiltInTool.SERVER_WRITE_FILE
253			},
254			BuiltInTool.SERVER_WRITE_FILE
255		);
256
257		expect(parseWriteFileTitleMeta(section)?.filePath).toBe('/a\nb"c/d.ts');
258	});
259
260	it('falls back to the full parse for args the extractor can not see', () => {
261		const section = makeSection(
262			{
263				// key written with an escaped unicode escape sequence in the name
264				toolArgs: '{"\\u0070ath":"/foo.ts","content":"x"}',
265				toolName: BuiltInTool.SERVER_WRITE_FILE
266			},
267			BuiltInTool.SERVER_WRITE_FILE
268		);
269
270		expect(parseWriteFileTitleMeta(section)?.filePath).toBe('/foo.ts');
271	});
272
273	it('accepts partial args like the full parser', () => {
274		const section = makeSection(
275			{ toolArgs: '{"path":"/foo.t', toolName: BuiltInTool.SERVER_WRITE_FILE },
276			BuiltInTool.SERVER_WRITE_FILE
277		);
278
279		expect(parseWriteFileTitleMeta(section)?.filePath).toBe('/foo.t');
280	});
281
282	it('returns null for sections with a different tool name', () => {
283		expect(
284			parseWriteFileTitleMeta(
285				makeSection({
286					toolArgs: '{"path":"/x","content":"y"}',
287					toolName: BuiltInTool.SERVER_READ_FILE
288				})
289			)
290		).toBeNull();
291	});
292});
293
294describe('parseEditFileTitleMeta', () => {
295	it('matches the full meta for path and result fields', () => {
296		const section = makeSection(
297			{
298				toolArgs: '{"path":"/foo.ts","edits":[{"old_text":"a","new_text":"b"}]}' + ' '.repeat(0),
299				toolName: BuiltInTool.SERVER_EDIT_FILE,
300				toolResult: '{"result":"ok","edits_applied":1}'
301			},
302			BuiltInTool.SERVER_EDIT_FILE
303		);
304		const full = parseEditFileMeta(section);
305		const title = parseEditFileTitleMeta(section);
306
307		expect(title?.filePath).toBe(full?.filePath);
308		expect(title?.fileName).toBe(full?.fileName);
309		expect(title?.editsApplied).toBe(full?.editsApplied);
310		expect(title?.resultMessage).toBe(full?.resultMessage);
311		expect(title?.errorMessage).toBe(full?.errorMessage);
312	});
313
314	it('surfaces errorMessage from the result blob without parsing args', () => {
315		const section = makeSection(
316			{
317				toolArgs: '{"path":"/foo.ts","edits":[]}',
318				toolName: BuiltInTool.SERVER_EDIT_FILE,
319				toolResult: '{"error":"permission denied"}'
320			},
321			BuiltInTool.SERVER_EDIT_FILE
322		);
323
324		expect(parseEditFileTitleMeta(section)?.errorMessage).toBe('permission denied');
325	});
326
327	it('returns null when args have no path-like field', () => {
328		expect(
329			parseEditFileTitleMeta(
330				makeSection({ toolArgs: '{"edits":[]}', toolName: BuiltInTool.SERVER_EDIT_FILE })
331			)
332		).toBeNull();
333	});
334});
335
336describe('parseEditFileMeta', () => {
337	it('parses edits array and applies editsApplied from the result', () => {
338		const section = makeSection(
339			{
340				toolArgs:
341					'{"path":"/foo.ts","edits":[{"old_text":"a","new_text":"b"},{"old_text":"c","new_text":"d"}]}',
342				toolName: BuiltInTool.SERVER_EDIT_FILE,
343				toolResult: '{"result":"ok","edits_applied":2}'
344			},
345			BuiltInTool.SERVER_EDIT_FILE
346		);
347		const meta = parseEditFileMeta(section);
348
349		expect(meta?.edits).toEqual([
350			{ newText: 'b', oldText: 'a' },
351			{ newText: 'd', oldText: 'c' }
352		]);
353		expect(meta?.editsApplied).toBe(2);
354		expect(meta?.resultMessage).toBe('ok');
355	});
356
357	it('drops edits with empty old_text', () => {
358		const section = makeSection(
359			{
360				toolArgs: '{"path":"/foo","edits":[{"old_text":""},{"old_text":"a","new_text":""}]}',
361				toolName: BuiltInTool.SERVER_EDIT_FILE
362			},
363			BuiltInTool.SERVER_EDIT_FILE
364		);
365		const meta = parseEditFileMeta(section);
366
367		// First entry is dropped (empty old_text). Second is kept
368		// (empty new_text is fine - it's the "delete" case).
369		expect(meta?.edits).toEqual([{ newText: '', oldText: 'a' }]);
370	});
371
372	it('errorMessage wins over result message', () => {
373		const section = makeSection(
374			{
375				toolArgs: '{"path":"/foo"}',
376				toolName: BuiltInTool.SERVER_EDIT_FILE,
377				toolResult: '{"error":"bad path","result":"ok"}'
378			},
379			BuiltInTool.SERVER_EDIT_FILE
380		);
381		const meta = parseEditFileMeta(section);
382
383		expect(meta?.errorMessage).toBe('bad path');
384		expect(meta?.resultMessage).toBeUndefined();
385	});
386});
387
388describe('parseReadFileMeta', () => {
389	it('parses file name alone (no range)', () => {
390		const meta = parseReadFileMeta(
391			makeSection({ toolArgs: '{"path":"/foo.txt"}' }, BuiltInTool.SERVER_READ_FILE)
392		);
393
394		expect(meta?.fileName).toBe('foo.txt');
395		expect(meta?.lineRange).toBeNull();
396	});
397
398	it('parses start_line + end_line into a range', () => {
399		const meta = parseReadFileMeta(
400			makeSection(
401				{ toolArgs: '{"path":"/foo.ts","start_line":10,"end_line":20}' },
402				BuiltInTool.SERVER_READ_FILE
403			)
404		);
405
406		expect(meta?.lineRange).toEqual({ end: 20, start: 10 });
407	});
408
409	it('parses start_line + line_count into a range', () => {
410		const meta = parseReadFileMeta(
411			makeSection(
412				{ toolArgs: '{"path":"/foo.ts","start_line":10,"line_count":5}' },
413				BuiltInTool.SERVER_READ_FILE
414			)
415		);
416
417		expect(meta?.lineRange).toEqual({ end: 14, start: 10 });
418	});
419
420	it('returns null when args cannot be parsed', () => {
421		expect(
422			parseReadFileMeta(makeSection({ toolArgs: '{bad' }, BuiltInTool.SERVER_READ_FILE))
423		).toBeNull();
424	});
425});
426
427describe('parseGrepSearchMeta', () => {
428	it('returns null when path or pattern is missing', () => {
429		expect(
430			parseGrepSearchMeta(
431				makeSection({ toolArgs: '{"pattern":"foo"}', toolName: BuiltInTool.SERVER_GREP_SEARCH })
432			)
433		).toBeNull();
434		expect(
435			parseGrepSearchMeta(
436				makeSection({ toolArgs: '{"path":"/x"}', toolName: BuiltInTool.SERVER_GREP_SEARCH })
437			)
438		).toBeNull();
439	});
440
441	it('parses structured plain_text_response into matches', () => {
442		const meta = parseGrepSearchMeta(
443			makeSection(
444				{
445					toolArgs: '{"path":"/x","pattern":"foo"}',
446					toolName: BuiltInTool.SERVER_GREP_SEARCH,
447					toolResult: JSON.stringify({ plain_text_response: 'a.ts:hello\nb.ts:world' })
448				},
449				BuiltInTool.SERVER_GREP_SEARCH
450			)
451		);
452
453		expect(meta?.matches).toHaveLength(2);
454		expect(meta?.matches[0]).toEqual({ content: 'hello', file: 'a.ts' });
455	});
456
457	it('falls back to raw-text parsing when result is not JSON', () => {
458		const meta = parseGrepSearchMeta(
459			makeSection(
460				{
461					toolArgs: '{"path":"/x","pattern":"foo"}',
462					toolName: BuiltInTool.SERVER_GREP_SEARCH,
463					toolResult: 'a.ts:hello\nb.ts:world'
464				},
465				BuiltInTool.SERVER_GREP_SEARCH
466			)
467		);
468
469		expect(meta?.matches).toHaveLength(2);
470	});
471
472	it('parses line numbers when return_line_numbers is true', () => {
473		const meta = parseGrepSearchMeta(
474			makeSection(
475				{
476					toolArgs: '{"path":"/x","pattern":"foo","return_line_numbers":true}',
477					toolName: BuiltInTool.SERVER_GREP_SEARCH,
478					toolResult: 'a.ts:12:hello'
479				},
480				BuiltInTool.SERVER_GREP_SEARCH
481			)
482		);
483
484		expect(meta?.matches[0]).toEqual({ content: 'hello', file: 'a.ts', line: 12 });
485		expect(meta?.showLineNumbers).toBe(true);
486	});
487});
488
489describe('parseFileGlobSearchMeta', () => {
490	it('falls back to raw-text parsing when result is not JSON', () => {
491		const meta = parseFileGlobSearchMeta(
492			makeSection(
493				{
494					toolArgs: '{"path":"/x"}',
495					toolName: BuiltInTool.SERVER_FILE_GLOB_SEARCH,
496					toolResult: 'a.ts\nb.ts'
497				},
498				BuiltInTool.SERVER_FILE_GLOB_SEARCH
499			)
500		);
501
502		expect(meta?.matches).toEqual(['a.ts', 'b.ts']);
503	});
504
505	it('parses plain_text_response from a JSON object', () => {
506		const meta = parseFileGlobSearchMeta(
507			makeSection(
508				{
509					toolArgs: '{"path":"/x"}',
510					toolName: BuiltInTool.SERVER_FILE_GLOB_SEARCH,
511					toolResult: JSON.stringify({ plain_text_response: 'a.ts\nb.ts' })
512				},
513				BuiltInTool.SERVER_FILE_GLOB_SEARCH
514			)
515		);
516
517		expect(meta?.matches).toEqual(['a.ts', 'b.ts']);
518	});
519
520	it('surfaces errorMessage from the result blob', () => {
521		const meta = parseFileGlobSearchMeta(
522			makeSection(
523				{
524					toolArgs: '{"path":"/x"}',
525					toolName: BuiltInTool.SERVER_FILE_GLOB_SEARCH,
526					toolResult: JSON.stringify({ error: 'permission denied' })
527				},
528				BuiltInTool.SERVER_FILE_GLOB_SEARCH
529			)
530		);
531
532		expect(meta?.errorMessage).toBe('permission denied');
533	});
534});
535
536describe('parseRunJavascriptMeta', () => {
537	it('returns null when code is missing', () => {
538		expect(
539			parseRunJavascriptMeta(
540				makeSection({ toolArgs: '{}', toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT })
541			)
542		).toBeNull();
543	});
544
545	it('reads code and timeout', () => {
546		const meta = parseRunJavascriptMeta(
547			makeSection(
548				{
549					toolArgs: '{"code":"Math.PI","timeout_ms":5000}',
550					toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT
551				},
552				BuiltInTool.BROWSER_RUN_JAVASCRIPT
553			)
554		);
555
556		expect(meta?.code).toBe('Math.PI');
557		expect(meta?.timeoutMs).toBe(5000);
558	});
559
560	it('reads error field from a JSON-object result', () => {
561		const meta = parseRunJavascriptMeta(
562			makeSection(
563				{
564					toolArgs: '{"code":"throw new Error()"}',
565					toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT,
566					toolResult: JSON.stringify({ error: 'undefined is not a function' })
567				},
568				BuiltInTool.BROWSER_RUN_JAVASCRIPT
569			)
570		);
571
572		expect(meta?.errorMessage).toBe('undefined is not a function');
573	});
574
575	it('does NOT treat a JSON-array result as an error', () => {
576		// SandboxService returns successful output as a JSON array;
577		// only JSON objects carry `error`. Raw arrays must round-trip
578		// through unchanged.
579		const meta = parseRunJavascriptMeta(
580			makeSection(
581				{
582					toolArgs: '{"code":"[1,2,3]"}',
583					toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT,
584					toolResult: '[1,2,3]'
585				},
586				BuiltInTool.BROWSER_RUN_JAVASCRIPT
587			)
588		);
589
590		expect(meta?.errorMessage).toBeUndefined();
591	});
592
593	it('scans a non-JSON string result for an `Error:` line', () => {
594		const meta = parseRunJavascriptMeta(
595			makeSection(
596				{
597					toolArgs: '{"code":"foo"}',
598					toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT,
599					toolResult: 'Error: undefined is not a function\n  at <anonymous>:1:1'
600				},
601				BuiltInTool.BROWSER_RUN_JAVASCRIPT
602			)
603		);
604
605		expect(meta?.errorMessage).toBe('undefined is not a function');
606	});
607});
608
609describe('parseExecShellCommandMeta', () => {
610	it('reads command from the args', () => {
611		const meta = parseExecShellCommandMeta(
612			makeSection(
613				{ toolArgs: '{"command":"ls -la"}', toolName: BuiltInTool.SERVER_EXEC_SHELL_COMMAND },
614				BuiltInTool.SERVER_EXEC_SHELL_COMMAND
615			)
616		);
617
618		expect(meta?.command).toBe('ls -la');
619	});
620
621	it('accepts cmd / shell_command aliases', () => {
622		expect(
623			parseExecShellCommandMeta(
624				makeSection(
625					{ toolArgs: '{"cmd":"ls"}', toolName: BuiltInTool.SERVER_EXEC_SHELL_COMMAND },
626					BuiltInTool.SERVER_EXEC_SHELL_COMMAND
627				)
628			)?.command
629		).toBe('ls');
630		expect(
631			parseExecShellCommandMeta(
632				makeSection(
633					{ toolArgs: '{"shell_command":"ls"}', toolName: BuiltInTool.SERVER_EXEC_SHELL_COMMAND },
634					BuiltInTool.SERVER_EXEC_SHELL_COMMAND
635				)
636			)?.command
637		).toBe('ls');
638	});
639
640	it('returns null when no command alias is present', () => {
641		expect(
642			parseExecShellCommandMeta(
643				makeSection(
644					{ toolArgs: '{"cwd":"/x"}', toolName: BuiltInTool.SERVER_EXEC_SHELL_COMMAND },
645					BuiltInTool.SERVER_EXEC_SHELL_COMMAND
646				)
647			)
648		).toBeNull();
649	});
650});