mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-15 14:23:02 -07:00
Fix crash.
This commit is contained in:
@@ -35,11 +35,12 @@ export const AnsiOutputText: React.FC<AnsiOutputProps> = ({
|
||||
? 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 (
|
||||
<Box flexDirection="column" width={width} flexShrink={0} overflow="hidden">
|
||||
{lastLines.map((line: AnsiLine, lineIndex: number) => (
|
||||
|
||||
@@ -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(
|
||||
<ToolResultDisplay
|
||||
@@ -222,6 +225,34 @@ describe('ToolResultDisplay', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders unknown objects as stringified JSON', async () => {
|
||||
const unknownObject = {
|
||||
hello: 'world',
|
||||
nested: {
|
||||
value: 42,
|
||||
},
|
||||
};
|
||||
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
|
||||
<ToolResultDisplay
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
resultDisplay={unknownObject as any}
|
||||
terminalWidth={80}
|
||||
availableTerminalHeight={20}
|
||||
/>,
|
||||
{
|
||||
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(
|
||||
|
||||
@@ -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<ToolResultDisplayProps> = ({
|
||||
resultDisplay,
|
||||
availableTerminalHeight,
|
||||
@@ -84,7 +79,7 @@ export const ToolResultDisplay: React.FC<ToolResultDisplayProps> = ({
|
||||
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<ToolResultDisplayProps> = ({
|
||||
{contentData}
|
||||
</Text>
|
||||
);
|
||||
} else if (typeof contentData === 'object' && 'fileDiff' in contentData) {
|
||||
} else if (
|
||||
contentData &&
|
||||
typeof contentData === 'object' &&
|
||||
'fileDiff' in contentData
|
||||
) {
|
||||
content = (
|
||||
<DiffRenderer
|
||||
diffContent={
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
(contentData as FileDiffResult).fileDiff
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
filename={(contentData as FileDiffResult).fileName}
|
||||
diffContent={contentData.fileDiff}
|
||||
filename={contentData.fileName}
|
||||
availableTerminalHeight={availableHeight}
|
||||
terminalWidth={childWidth}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
} else if (Array.isArray(contentData)) {
|
||||
const shouldDisableTruncation =
|
||||
isAlternateBuffer ||
|
||||
(availableTerminalHeight === undefined && maxLines === undefined);
|
||||
|
||||
content = (
|
||||
<AnsiOutputText
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
data={contentData as AnsiOutput}
|
||||
data={contentData}
|
||||
availableTerminalHeight={
|
||||
isAlternateBuffer ? undefined : availableHeight
|
||||
}
|
||||
@@ -153,6 +147,12 @@ export const ToolResultDisplay: React.FC<ToolResultDisplayProps> = ({
|
||||
disableTruncation={shouldDisableTruncation}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<Text wrap="wrap" color={theme.text.primary}>
|
||||
{JSON.stringify(contentData, null, 2)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
// Final render based on session mode
|
||||
@@ -178,18 +178,13 @@ export const ToolResultDisplay: React.FC<ToolResultDisplayProps> = ({
|
||||
// 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 (
|
||||
<Box width={childWidth} flexDirection="column" maxHeight={listHeight}>
|
||||
<ScrollableList
|
||||
width={childWidth}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
data={resultDisplay as AnsiOutput}
|
||||
data={resultDisplay}
|
||||
renderItem={renderVirtualizedAnsiLine}
|
||||
estimatedItemHeight={() => 1}
|
||||
keyExtractor={keyExtractor}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user