From 619fcc495e69f3466b07161a7e7261dca2fbefa6 Mon Sep 17 00:00:00 2001 From: Christian Gunderman Date: Thu, 2 Apr 2026 15:27:42 -0700 Subject: [PATCH] Fix crash. --- packages/cli/src/ui/components/AnsiOutput.tsx | 5 ++- .../messages/ToolResultDisplay.test.tsx | 31 +++++++++++++ .../components/messages/ToolResultDisplay.tsx | 45 +++++++++---------- .../ToolResultDisplay.test.tsx.snap | 10 +++++ 4 files changed, 64 insertions(+), 27 deletions(-) diff --git a/packages/cli/src/ui/components/AnsiOutput.tsx b/packages/cli/src/ui/components/AnsiOutput.tsx index a1b30b0856..b0ea1f0424 100644 --- a/packages/cli/src/ui/components/AnsiOutput.tsx +++ b/packages/cli/src/ui/components/AnsiOutput.tsx @@ -35,11 +35,12 @@ export const AnsiOutputText: React.FC = ({ ? Math.min(availableHeightLimit, maxLines) : (availableHeightLimit ?? maxLines ?? DEFAULT_HEIGHT); + const arrayData = Array.isArray(data) ? data : []; const lastLines = disableTruncation - ? data + ? arrayData : numLinesRetained === 0 ? [] - : data.slice(-numLinesRetained); + : arrayData.slice(-numLinesRetained); return ( {lastLines.map((line: AnsiLine, lineIndex: number) => ( diff --git a/packages/cli/src/ui/components/messages/ToolResultDisplay.test.tsx b/packages/cli/src/ui/components/messages/ToolResultDisplay.test.tsx index 31cf75e63c..1cb84db1c4 100644 --- a/packages/cli/src/ui/components/messages/ToolResultDisplay.test.tsx +++ b/packages/cli/src/ui/components/messages/ToolResultDisplay.test.tsx @@ -148,6 +148,9 @@ describe('ToolResultDisplay', () => { const diffResult = { fileDiff: 'diff content', fileName: 'test.ts', + filePath: 'test.ts', + originalContent: null, + newContent: 'new', }; const { lastFrame, waitUntilReady, unmount } = await renderWithProviders( { unmount(); }); + it('renders unknown objects as stringified JSON', async () => { + const unknownObject = { + hello: 'world', + nested: { + value: 42, + }, + }; + const { lastFrame, waitUntilReady, unmount } = await renderWithProviders( + , + { + config: makeFakeConfig({ useAlternateBuffer: false }), + settings: createMockSettings({ ui: { useAlternateBuffer: false } }), + }, + ); + await waitUntilReady(); + const output = lastFrame(); + + expect(output).toContain('"hello": "world"'); + expect(output).toContain('"value": 42'); + expect(output).toMatchSnapshot(); + unmount(); + }); + it('does not fall back to plain text if availableHeight is set and not in alternate buffer', async () => { // availableHeight calculation: 20 - 1 - 5 = 14 > 3 const { lastFrame, waitUntilReady, unmount } = await renderWithProviders( diff --git a/packages/cli/src/ui/components/messages/ToolResultDisplay.tsx b/packages/cli/src/ui/components/messages/ToolResultDisplay.tsx index 3b7cfaa8da..09c0d5e14f 100644 --- a/packages/cli/src/ui/components/messages/ToolResultDisplay.tsx +++ b/packages/cli/src/ui/components/messages/ToolResultDisplay.tsx @@ -12,9 +12,9 @@ import { AnsiOutputText, AnsiLineText } from '../AnsiOutput.js'; import { SlicingMaxSizedBox } from '../shared/SlicingMaxSizedBox.js'; import { theme } from '../../semantic-colors.js'; import { - type AnsiOutput, type AnsiLine, isSubagentProgress, + type ToolResultDisplay as CoreToolResultDisplay, } from '@google/gemini-cli-core'; import { useUIState } from '../../contexts/UIStateContext.js'; import { tryParseJSON } from '../../../utils/jsonoutput.js'; @@ -27,7 +27,7 @@ import { calculateToolContentMaxLines } from '../../utils/toolLayoutUtils.js'; import { SubagentProgressDisplay } from './SubagentProgressDisplay.js'; export interface ToolResultDisplayProps { - resultDisplay: string | object | undefined; + resultDisplay: CoreToolResultDisplay | undefined; availableTerminalHeight?: number; terminalWidth: number; renderOutputAsMarkdown?: boolean; @@ -36,11 +36,6 @@ export interface ToolResultDisplayProps { overflowDirection?: 'top' | 'bottom'; } -interface FileDiffResult { - fileDiff: string; - fileName: string; -} - export const ToolResultDisplay: React.FC = ({ resultDisplay, availableTerminalHeight, @@ -84,7 +79,7 @@ export const ToolResultDisplay: React.FC = ({ return null; } - const renderContent = (contentData: string | object | undefined) => { + const renderContent = (contentData: CoreToolResultDisplay | undefined) => { // Check if string content is valid JSON and pretty-print it const prettyJSON = typeof contentData === 'string' ? tryParseJSON(contentData) : null; @@ -123,28 +118,27 @@ export const ToolResultDisplay: React.FC = ({ {contentData} ); - } else if (typeof contentData === 'object' && 'fileDiff' in contentData) { + } else if ( + contentData && + typeof contentData === 'object' && + 'fileDiff' in contentData + ) { content = ( ); - } else { + } else if (Array.isArray(contentData)) { const shouldDisableTruncation = isAlternateBuffer || (availableTerminalHeight === undefined && maxLines === undefined); content = ( = ({ disableTruncation={shouldDisableTruncation} /> ); + } else { + content = ( + + {JSON.stringify(contentData, null, 2)} + + ); } // Final render based on session mode @@ -178,18 +178,13 @@ export const ToolResultDisplay: React.FC = ({ // Virtualized path for large ANSI arrays if (Array.isArray(resultDisplay)) { const limit = maxLines ?? availableHeight ?? ACTIVE_SHELL_MAX_LINES; - const listHeight = Math.min( - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - (resultDisplay as AnsiOutput).length, - limit, - ); + const listHeight = Math.min(resultDisplay.length, limit); return ( 1} keyExtractor={keyExtractor} diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ToolResultDisplay.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/ToolResultDisplay.test.tsx.snap index e34e66cc48..67e3f4161d 100644 --- a/packages/cli/src/ui/components/messages/__snapshots__/ToolResultDisplay.test.tsx.snap +++ b/packages/cli/src/ui/components/messages/__snapshots__/ToolResultDisplay.test.tsx.snap @@ -36,6 +36,16 @@ exports[`ToolResultDisplay > renders string result as plain text when renderOutp " `; +exports[`ToolResultDisplay > renders unknown objects as stringified JSON 1`] = ` +"{ + "hello": "world", + "nested": { + "value": 42 + } +} +" +`; + exports[`ToolResultDisplay > truncates very long string results 1`] = ` "... 249 hidden (Ctrl+O) ... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa