From 88cb824b14c77101fc82e2a7a67229c065cb92dd Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sun, 19 Jul 2026 22:50:20 -0500 Subject: [PATCH] perf(desktop): stop eagerly JSON.stringify-ing every tool's args + result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit buildToolView ran prettyJson (JSON.stringify + clamp) on part.args AND part.result for EVERY tool row, on every rebuild: - rawArgs was dead — assigned + typed, never read anywhere. Removed. - rawResult is only rendered by the web_search raw-JSON drilldown, yet was serialized for read_file/terminal/every tool. Moved to a memoized, web_search- only computation in the consumer (fallback.tsx), so a 100KB read_file result is no longer stringified just to be discarded. No behavior change (web_search drilldown identical; clamp still applies via prettyJson). The oversized-result guard test retargets from view.rawResult to prettyJson (its real layer now). typecheck + eslint clean; fallback-model tests green (26). --- .../assistant-ui/tool/fallback-model.test.ts | 16 +++++++++------- .../assistant-ui/tool/fallback-model/index.ts | 3 --- .../assistant-ui/tool/fallback-model/types.ts | 2 -- .../components/assistant-ui/tool/fallback.tsx | 15 ++++++++++----- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/apps/desktop/src/components/assistant-ui/tool/fallback-model.test.ts b/apps/desktop/src/components/assistant-ui/tool/fallback-model.test.ts index ea253ecadcba..c3f5361191d1 100644 --- a/apps/desktop/src/components/assistant-ui/tool/fallback-model.test.ts +++ b/apps/desktop/src/components/assistant-ui/tool/fallback-model.test.ts @@ -8,6 +8,7 @@ import { countDiffLineStats, inlineDiffFromResult, MAX_TOOL_RENDER_CHARS, + prettyJson, type ToolPart } from './fallback-model' @@ -342,15 +343,16 @@ describe('clampForDisplay', () => { }) // A large tool result (e.g. a 100KB read_file during a `/learn` run) must not -// be serialized into the rendered rawResult at full size — that JSON.stringify -// payload is what floods the renderer when many rows stack up. -describe('buildToolView caps serialized result size', () => { - it('clamps rawResult for an oversized result', () => { +// be serialized at full size — that JSON.stringify payload is what floods the +// renderer. buildToolView no longer prettyJson's every result eagerly; the +// web_search drilldown serializes lazily via prettyJson, which clamps. +describe('prettyJson caps serialized result size', () => { + it('clamps an oversized result', () => { const huge = 'y'.repeat(MAX_TOOL_RENDER_CHARS * 3) - const view = buildToolView(part({ result: { content: huge }, toolName: 'read_file' }), '') + const out = prettyJson({ content: huge }) - expect(view.rawResult.length).toBeLessThanOrEqual(MAX_TOOL_RENDER_CHARS + 200) - expect(view.rawResult).toContain('truncated') + expect(out.length).toBeLessThanOrEqual(MAX_TOOL_RENDER_CHARS + 200) + expect(out).toContain('truncated') }) }) diff --git a/apps/desktop/src/components/assistant-ui/tool/fallback-model/index.ts b/apps/desktop/src/components/assistant-ui/tool/fallback-model/index.ts index e027e97ef685..a1e8720431cf 100644 --- a/apps/desktop/src/components/assistant-ui/tool/fallback-model/index.ts +++ b/apps/desktop/src/components/assistant-ui/tool/fallback-model/index.ts @@ -11,7 +11,6 @@ import { isRecord, numberValue, parseMaybeObject, - prettyJson, unwrapToolPayload } from './format' import { findFirstUrl, hostnameOf, looksLikePath, looksLikeUrl } from './targets' @@ -1409,8 +1408,6 @@ export function buildToolView(part: ToolPart, inlineDiff: string): ToolView { imageUrl: toolImageUrl(argsRecord, resultRecord), inlineDiff, previewTarget: toolPreviewTarget(part.toolName, argsRecord, resultRecord), - rawArgs: prettyJson(part.args), - rawResult: prettyJson(part.result), rendersAnsi: rendersAnsi || undefined, searchHits: searchHits?.length ? searchHits : undefined, stderr: hasSplitStreams ? stderrRaw || undefined : undefined, diff --git a/apps/desktop/src/components/assistant-ui/tool/fallback-model/types.ts b/apps/desktop/src/components/assistant-ui/tool/fallback-model/types.ts index b4225310f8c3..e61140a12a6e 100644 --- a/apps/desktop/src/components/assistant-ui/tool/fallback-model/types.ts +++ b/apps/desktop/src/components/assistant-ui/tool/fallback-model/types.ts @@ -36,8 +36,6 @@ export interface ToolView { imageUrl?: string inlineDiff: string previewTarget?: string - rawArgs: string - rawResult: string /** Set for tools whose output naturally contains ANSI escape codes * (terminal/execute_code) so the renderer knows to run them through * the ANSI parser instead of printing them as literals. */ diff --git a/apps/desktop/src/components/assistant-ui/tool/fallback.tsx b/apps/desktop/src/components/assistant-ui/tool/fallback.tsx index 5b14de5fead5..7abf94c648b7 100644 --- a/apps/desktop/src/components/assistant-ui/tool/fallback.tsx +++ b/apps/desktop/src/components/assistant-ui/tool/fallback.tsx @@ -62,6 +62,7 @@ import { type ToolStatus, type ToolTitleAction } from './fallback-model' +import { prettyJson } from './fallback-model/format' // `true` when a ToolEntry is rendered inside an embedding wrapper that owns // the per-row chrome (timer / preview). The flat ToolGroupSlot sets this @@ -367,11 +368,15 @@ function ToolEntry({ part }: ToolEntryProps) { const hasSearchHits = Boolean(view.searchHits?.length) const searchResultsLabel = part.toolName === 'web_search' ? 'Search results' : view.detailLabel + // Only web_search renders the raw JSON drilldown, so serialize the result + // lazily here instead of prettyJson-ing every tool's result in buildToolView. + const rawResult = useMemo( + () => (part.toolName === 'web_search' && toolViewMode !== 'technical' ? prettyJson(part.result) : ''), + [part.toolName, part.result, toolViewMode] + ) + const showRawSearchDrilldown = - part.toolName === 'web_search' && - part.result !== undefined && - toolViewMode !== 'technical' && - Boolean(view.rawResult.trim()) + part.toolName === 'web_search' && part.result !== undefined && toolViewMode !== 'technical' && Boolean(rawResult.trim()) const hasExpandableContent = Boolean( view.imageUrl || view.inlineDiff || showDetail || hasSearchHits || toolViewMode === 'technical' @@ -587,7 +592,7 @@ function ToolEntry({ part }: ToolEntryProps) {
{copy.rawResponse}
-                {view.rawResult}
+                {rawResult}
               
)}