ui : parse write_file and edit_file titles without the content blob

Both block headers parsed the full args JSON at mount, even collapsed, and
write_file and edit_file args embed the whole file content or edit
strings, so every block paid a full-blob JSON parse just to read the path.
Split the meta into a title tier that extracts the path with a targeted
key match (full parse only as fallback) and a body tier that keeps the
full parse; Svelte deriveds are lazy, and the body snippet renders only
while the block is expanded, so collapsed blocks no longer parse args.

Assisted-by: pi:zai-org/GLM-5.3
This commit is contained in:
Aleksander Grygier
2026-09-06 01:47:55 +02:00
parent fb308031a2
commit bb338bb5c8
6 changed files with 275 additions and 11 deletions
@@ -1,5 +1,5 @@
<script lang="ts">
import { parseEditFileMeta } from './parsers/edit-file';
import { parseEditFileMeta, parseEditFileTitleMeta } from './parsers/edit-file';
import ToolCallBlock from './ToolCallBlock.svelte';
import { XCircle } from '@lucide/svelte';
import { MAX_HEIGHT_CODE_BLOCK, RESULT_STAT_SEPARATOR } from '$lib/constants';
@@ -16,10 +16,14 @@
let { isStreaming, onToggle, open, section }: Props = $props();
const editFileMeta = $derived(parseEditFileMeta(section));
const editFileMeta = $derived(parseEditFileTitleMeta(section));
// body-only: the full meta parses the embedded edit strings, and these
// deriveds are read solely from the children snippet, which renders only
// while the block is expanded
const editFileBody = $derived(parseEditFileMeta(section));
const home = $derived(toolsStore.serverHome);
const editDiffs = $derived(
(editFileMeta?.edits ?? []).map((edit) => computeLineDiff(edit.oldText, edit.newText))
(editFileBody?.edits ?? []).map((edit) => computeLineDiff(edit.oldText, edit.newText))
);
</script>
@@ -45,11 +49,11 @@
<span>{meta.errorMessage}</span>
</div>
{:else if meta && meta.edits.length > 0}
{:else if meta && editFileBody && editFileBody.edits.length > 0}
{#each editDiffs as diffLines, ei (ei)}
<div class={ei === 0 ? '' : 'mt-3'}>
<div class="mb-1.5 text-xs text-muted-foreground/70 italic">
Edit {ei + 1}&nbsp;of&nbsp;{meta.edits.length}
Edit {ei + 1}&nbsp;of&nbsp;{editFileBody.edits.length}
</div>
<div style:max-height={MAX_HEIGHT_CODE_BLOCK} class="diff-block">
@@ -1,5 +1,5 @@
<script lang="ts">
import { parseWriteFileMeta } from './parsers/write-file';
import { parseWriteFileMeta, parseWriteFileTitleMeta } from './parsers/write-file';
import ToolCallBlock from './ToolCallBlock.svelte';
import { XCircle } from '@lucide/svelte';
import { SyntaxHighlightedCode } from '$lib/components/app';
@@ -17,7 +17,11 @@
let { isStreaming, onToggle, open, section }: Props = $props();
const writeFileMeta = $derived(parseWriteFileMeta(section));
const writeFileMeta = $derived(parseWriteFileTitleMeta(section));
// body-only: the full meta parses the embedded file content, and this
// derived is read solely from the children snippet, which renders only
// while the block is expanded
const writeFileBody = $derived(parseWriteFileMeta(section));
const home = $derived(toolsStore.serverHome);
</script>
@@ -45,7 +49,7 @@
</div>
{:else if meta}
<SyntaxHighlightedCode
code={meta.content}
code={writeFileBody?.content ?? ''}
language={meta.language}
maxHeight={MAX_HEIGHT_CODE_BLOCK}
streaming={ctx.isCodeStreaming}
@@ -28,6 +28,42 @@ function parseFinalToolArgs(blob: string): Record<string, unknown> | null {
}
}
// Compiled per key on first use; the key set is tiny and fixed.
const toolArgStringRegexes = new Map<string, RegExp>();
/**
* Extract a string field from a JSON tool-args blob without parsing the
* whole document. write_file and edit_file args embed full file contents,
* yet the block title needs only the path; a targeted key match plus a
* JSON.parse of the captured string literal alone keeps title rendering
* O(path) instead of O(blob). Returns undefined when the key is missing
* or its value is not a string; callers fall back to the full parse.
*/
export function extractToolArgString(toolArgs: string, keys: string[]): string | undefined {
for (const key of keys) {
let pattern = toolArgStringRegexes.get(key);
if (!pattern) {
pattern = new RegExp(`"${key}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)"`);
toolArgStringRegexes.set(key, pattern);
}
const match = pattern.exec(toolArgs);
if (!match) continue;
try {
const value: unknown = JSON.parse(`"${match[1]}"`);
if (typeof value === 'string') return value;
} catch {
// fall through to the next key; the full parse is the fallback
}
}
return undefined;
}
/**
* Parse a section's toolArgs against an expected tool name. Returns
* `null` when:
@@ -3,7 +3,7 @@
// rendering), plus the result blob for `result` / `edits_applied` /
// `error` fields.
import { parseToolArgs } from './_shared';
import { extractToolArgString, parseToolArgs } from './_shared';
import { FILE_PATH_SEPARATOR_REGEX } from '$lib/constants';
import { BuiltInTool } from '$lib/enums';
import type { AgenticSection } from '$lib/types';
@@ -23,6 +23,19 @@ export type EditFileMeta = {
errorMessage?: string;
};
/** Everything the block title and status pill show; the full meta (with
* the embedded edit strings) stays body-only so collapsed blocks never
* parse the args blob. */
export type EditFileTitleMeta = {
fileName: string;
filePath: string;
resultMessage?: string;
editsApplied?: number;
errorMessage?: string;
};
const PATH_KEYS = ['path', 'file_path', 'filePath'];
export function parseEditFileMeta(section: AgenticSection): EditFileMeta | null {
const args = parseToolArgs(BuiltInTool.SERVER_EDIT_FILE, section, { partial: true });
@@ -79,3 +92,45 @@ export function parseEditFileMeta(section: AgenticSection): EditFileMeta | null
resultMessage
};
}
/**
* Title-tier meta for edit_file blocks: everything the header and status
* pill render, obtained without parsing the embedded edit strings. The path
* comes from a targeted key extraction; the full parse runs only as a
* fallback for arg shapes the extraction can't see.
*/
export function parseEditFileTitleMeta(section: AgenticSection): EditFileTitleMeta | null {
if (section.toolName !== BuiltInTool.SERVER_EDIT_FILE || !section.toolArgs) return null;
let rawPath: string | undefined = extractToolArgString(section.toolArgs, PATH_KEYS);
if (!rawPath) {
const args = parseToolArgs(BuiltInTool.SERVER_EDIT_FILE, section, { partial: true });
const fallbackPath = args?.path ?? args?.file_path ?? args?.filePath;
if (typeof fallbackPath === 'string' && fallbackPath) rawPath = fallbackPath;
}
if (!rawPath) return null;
const fileName = rawPath.split(FILE_PATH_SEPARATOR_REGEX).pop() || rawPath;
const resultObj = tryParseToolResultObject(section.toolResult);
let resultMessage: string | undefined;
let editsApplied: number | undefined;
let errorMessage: string | undefined;
if (typeof resultObj?.error === 'string') {
errorMessage = resultObj.error;
} else if (resultObj) {
if (typeof resultObj.result === 'string') {
resultMessage = resultObj.result;
}
if (Number.isFinite(Number(resultObj.edits_applied))) {
editsApplied = Number(resultObj.edits_applied);
}
}
return { editsApplied, errorMessage, fileName, filePath: rawPath, resultMessage };
}
@@ -3,7 +3,7 @@
// finishes) and surfaces `bytes`, `result`, and `error` from the
// result blob.
import { parseToolArgs } from './_shared';
import { extractToolArgString, parseToolArgs } from './_shared';
import { CODE_BLOCK, FILE_PATH_SEPARATOR_REGEX } from '$lib/constants';
import { BuiltInTool } from '$lib/enums';
import type { AgenticSection } from '$lib/types';
@@ -19,6 +19,20 @@ export type WriteFileMeta = {
errorMessage?: string;
};
/** Everything the block title and status pill show; the full meta (with
* the embedded file content) stays body-only so collapsed blocks never
* parse the content blob. */
export type WriteFileTitleMeta = {
fileName: string;
filePath: string;
language: string;
bytesWritten?: number;
resultMessage?: string;
errorMessage?: string;
};
const PATH_KEYS = ['path', 'file_path', 'filePath'];
export function parseWriteFileMeta(section: AgenticSection): WriteFileMeta | null {
const args = parseToolArgs(BuiltInTool.SERVER_WRITE_FILE, section, { partial: true });
@@ -51,3 +65,43 @@ export function parseWriteFileMeta(section: AgenticSection): WriteFileMeta | nul
resultMessage
};
}
/**
* Title-tier meta for write_file blocks: everything the header and status
* pill render, obtained without parsing the embedded file content. The path
* comes from a targeted key extraction; the full parse runs only as a
* fallback for arg shapes the extraction can't see.
*/
export function parseWriteFileTitleMeta(section: AgenticSection): WriteFileTitleMeta | null {
if (section.toolName !== BuiltInTool.SERVER_WRITE_FILE || !section.toolArgs) return null;
let rawPath: string | undefined = extractToolArgString(section.toolArgs, PATH_KEYS);
if (!rawPath) {
const args = parseToolArgs(BuiltInTool.SERVER_WRITE_FILE, section, { partial: true });
const fallbackPath = args?.path ?? args?.file_path ?? args?.filePath;
if (typeof fallbackPath === 'string' && fallbackPath) rawPath = fallbackPath;
}
if (!rawPath) return null;
const fileName = rawPath.split(FILE_PATH_SEPARATOR_REGEX).pop() || rawPath;
const language =
getFileTypeByExtension(rawPath)?.replace(CODE_BLOCK.TEXT_LANGUAGE_PREFIX_REGEX, '') ??
CODE_BLOCK.DEFAULT_LANGUAGE;
const resultObj = tryParseToolResultObject(section.toolResult);
const bytesWritten =
resultObj && Number.isFinite(Number(resultObj.bytes)) ? Number(resultObj.bytes) : undefined;
const resultMessage = typeof resultObj?.result === 'string' ? resultObj.result : undefined;
const errorMessage = typeof resultObj?.error === 'string' ? resultObj.error : undefined;
return {
bytesWritten,
errorMessage,
fileName,
filePath: rawPath,
language,
resultMessage
};
}
+112 -1
View File
@@ -1,5 +1,8 @@
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 {
parseEditFileMeta,
parseEditFileTitleMeta
} 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';
@@ -7,6 +10,7 @@ import { parseReadFileMeta } from '$lib/components/app/chat/ChatMessages/ChatMes
import { parseRunJavascriptMeta } from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/run-javascript';
import {
parseWriteFileMeta,
parseWriteFileTitleMeta,
type WriteFileMeta
} from '$lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/write-file';
import { AgenticSectionType, BuiltInTool } from '$lib/enums';
@@ -223,6 +227,113 @@ describe('parseWriteFileMeta', () => {
});
});
describe('parseWriteFileTitleMeta', () => {
it('matches the full meta for path, language and result fields', () => {
const args = JSON.stringify({ content: 'x'.repeat(50_000), path: '/foo.ts' });
const toolResult = '{"result":"wrote","bytes":42}';
const section = makeSection(
{ toolArgs: args, toolName: BuiltInTool.SERVER_WRITE_FILE, toolResult },
BuiltInTool.SERVER_WRITE_FILE
);
const full = parseWriteFileMeta(section);
const title = parseWriteFileTitleMeta(section);
expect(title?.filePath).toBe(full?.filePath);
expect(title?.fileName).toBe(full?.fileName);
expect(title?.language).toBe(full?.language);
expect(title?.bytesWritten).toBe(full?.bytesWritten);
expect(title?.resultMessage).toBe(full?.resultMessage);
expect(title?.errorMessage).toBe(full?.errorMessage);
});
it('extracts a path with escaped characters without parsing the content blob', () => {
const section = makeSection(
{
toolArgs: '{"path":"/a\\nb\\"c/d.ts","content":"x"}',
toolName: BuiltInTool.SERVER_WRITE_FILE
},
BuiltInTool.SERVER_WRITE_FILE
);
expect(parseWriteFileTitleMeta(section)?.filePath).toBe('/a\nb"c/d.ts');
});
it('falls back to the full parse for args the extractor can not see', () => {
const section = makeSection(
{
// key written with an escaped unicode escape sequence in the name
toolArgs: '{"\\u0070ath":"/foo.ts","content":"x"}',
toolName: BuiltInTool.SERVER_WRITE_FILE
},
BuiltInTool.SERVER_WRITE_FILE
);
expect(parseWriteFileTitleMeta(section)?.filePath).toBe('/foo.ts');
});
it('accepts partial args like the full parser', () => {
const section = makeSection(
{ toolArgs: '{"path":"/foo.t', toolName: BuiltInTool.SERVER_WRITE_FILE },
BuiltInTool.SERVER_WRITE_FILE
);
expect(parseWriteFileTitleMeta(section)?.filePath).toBe('/foo.t');
});
it('returns null for sections with a different tool name', () => {
expect(
parseWriteFileTitleMeta(
makeSection({
toolArgs: '{"path":"/x","content":"y"}',
toolName: BuiltInTool.SERVER_READ_FILE
})
)
).toBeNull();
});
});
describe('parseEditFileTitleMeta', () => {
it('matches the full meta for path and result fields', () => {
const section = makeSection(
{
toolArgs: '{"path":"/foo.ts","edits":[{"old_text":"a","new_text":"b"}]}' + ' '.repeat(0),
toolName: BuiltInTool.SERVER_EDIT_FILE,
toolResult: '{"result":"ok","edits_applied":1}'
},
BuiltInTool.SERVER_EDIT_FILE
);
const full = parseEditFileMeta(section);
const title = parseEditFileTitleMeta(section);
expect(title?.filePath).toBe(full?.filePath);
expect(title?.fileName).toBe(full?.fileName);
expect(title?.editsApplied).toBe(full?.editsApplied);
expect(title?.resultMessage).toBe(full?.resultMessage);
expect(title?.errorMessage).toBe(full?.errorMessage);
});
it('surfaces errorMessage from the result blob without parsing args', () => {
const section = makeSection(
{
toolArgs: '{"path":"/foo.ts","edits":[]}',
toolName: BuiltInTool.SERVER_EDIT_FILE,
toolResult: '{"error":"permission denied"}'
},
BuiltInTool.SERVER_EDIT_FILE
);
expect(parseEditFileTitleMeta(section)?.errorMessage).toBe('permission denied');
});
it('returns null when args have no path-like field', () => {
expect(
parseEditFileTitleMeta(
makeSection({ toolArgs: '{"edits":[]}', toolName: BuiltInTool.SERVER_EDIT_FILE })
)
).toBeNull();
});
});
describe('parseEditFileMeta', () => {
it('parses edits array and applies editsApplied from the result', () => {
const section = makeSection(