Merge pull request #67842 from NousResearch/perf/desktop-tool-view-lazy-json

perf(desktop): stop eagerly JSON.stringify-ing every tool's args + result
This commit is contained in:
brooklyn! 2026-07-19 23:03:47 -05:00 committed by GitHub
commit 919eb30ca7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 19 additions and 17 deletions

View file

@ -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')
})
})

View file

@ -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,

View file

@ -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. */

View file

@ -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) {
<details className="max-w-full">
<summary className={cn(TOOL_SECTION_LABEL_CLASS, 'mb-0')}>{copy.rawResponse}</summary>
<pre className={cn(TOOL_SECTION_PRE_CLASS, 'mt-1 whitespace-pre-wrap wrap-anywhere')}>
{view.rawResult}
{rawResult}
</pre>
</details>
)}