mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-21 16:18:55 +00:00
perf(desktop): stop eagerly JSON.stringify-ing every tool's args + result
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).
This commit is contained in:
parent
3e23c502f2
commit
88cb824b14
4 changed files with 19 additions and 17 deletions
|
|
@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue