mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-24 22:55:13 +00:00
feat(cli): truncate shell command output in history
Refines the display of shell command outputs in the history to prevent excessive vertical scrolling and improve readability. Key changes: - Implemented pruneShellOutput utility in textUtils to safely truncate strings and AnsiOutput arrays. - Updated useShellCommandProcessor to prune shell execution results to a maximum of 10 lines before adding them to the history. - Updated toolMapping to apply truncation to run_shell_command results when mapping core status to display status. - Added maxLines prop to AnsiOutputText and ToolResultDisplay components to enforce line limits during rendering. - Configured ShellToolMessage to pass a 10-line limit to the display component. This ensures that long shell outputs are truncated in the UI history while retaining the full output for the model context.
This commit is contained in:
committed by
Jarrod Whelan
parent
80e1fa198f
commit
f9573d6352
@@ -14,18 +14,25 @@ interface AnsiOutputProps {
|
||||
data: AnsiOutput;
|
||||
availableTerminalHeight?: number;
|
||||
width: number;
|
||||
maxLines?: number;
|
||||
}
|
||||
|
||||
export const AnsiOutputText: React.FC<AnsiOutputProps> = ({
|
||||
data,
|
||||
availableTerminalHeight,
|
||||
width,
|
||||
maxLines,
|
||||
}) => {
|
||||
const lastLines = data.slice(
|
||||
-(availableTerminalHeight && availableTerminalHeight > 0
|
||||
const effectiveHeight =
|
||||
availableTerminalHeight && availableTerminalHeight > 0
|
||||
? availableTerminalHeight
|
||||
: DEFAULT_HEIGHT),
|
||||
);
|
||||
: (maxLines ?? DEFAULT_HEIGHT);
|
||||
|
||||
const limit = maxLines
|
||||
? Math.min(effectiveHeight, maxLines)
|
||||
: effectiveHeight;
|
||||
|
||||
const lastLines = data.slice(-limit);
|
||||
return (
|
||||
<Box flexDirection="column" width={width} flexShrink={0}>
|
||||
{lastLines.map((line: AnsiLine, lineIndex: number) => (
|
||||
|
||||
@@ -153,6 +153,7 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
|
||||
availableTerminalHeight={availableTerminalHeight}
|
||||
terminalWidth={terminalWidth}
|
||||
renderOutputAsMarkdown={renderOutputAsMarkdown}
|
||||
maxLines={10}
|
||||
/>
|
||||
{isThisShellFocused && config && (
|
||||
<Box paddingLeft={STATUS_INDICATOR_WIDTH} marginTop={1}>
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface ToolResultDisplayProps {
|
||||
availableTerminalHeight?: number;
|
||||
terminalWidth: number;
|
||||
renderOutputAsMarkdown?: boolean;
|
||||
maxLines?: number;
|
||||
}
|
||||
|
||||
interface FileDiffResult {
|
||||
@@ -39,16 +40,21 @@ export const ToolResultDisplay: React.FC<ToolResultDisplayProps> = ({
|
||||
availableTerminalHeight,
|
||||
terminalWidth,
|
||||
renderOutputAsMarkdown = true,
|
||||
maxLines,
|
||||
}) => {
|
||||
const { renderMarkdown } = useUIState();
|
||||
|
||||
const availableHeight = availableTerminalHeight
|
||||
let availableHeight = availableTerminalHeight
|
||||
? Math.max(
|
||||
availableTerminalHeight - STATIC_HEIGHT - RESERVED_LINE_COUNT,
|
||||
MIN_LINES_SHOWN + 1, // enforce minimum lines shown
|
||||
)
|
||||
: undefined;
|
||||
|
||||
if (maxLines && availableHeight) {
|
||||
availableHeight = Math.min(availableHeight, maxLines);
|
||||
}
|
||||
|
||||
const combinedPaddingAndBorderWidth = 4;
|
||||
const childWidth = terminalWidth - combinedPaddingAndBorderWidth;
|
||||
|
||||
@@ -107,6 +113,7 @@ export const ToolResultDisplay: React.FC<ToolResultDisplayProps> = ({
|
||||
data={truncatedResultDisplay as AnsiOutput}
|
||||
availableTerminalHeight={availableHeight}
|
||||
width={childWidth}
|
||||
maxLines={maxLines}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,9 @@ vi.mock('node:os', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
vi.mock('node:crypto');
|
||||
vi.mock('../utils/textUtils.js');
|
||||
vi.mock('../utils/textUtils.js', () => ({
|
||||
pruneShellOutput: vi.fn((output) => output),
|
||||
}));
|
||||
|
||||
import {
|
||||
useShellCommandProcessor,
|
||||
|
||||
@@ -26,8 +26,10 @@ import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import fs from 'node:fs';
|
||||
import { themeManager } from '../../ui/themes/theme-manager.js';
|
||||
import { pruneShellOutput } from '../utils/textUtils.js';
|
||||
|
||||
export const OUTPUT_UPDATE_INTERVAL_MS = 1000;
|
||||
const MAX_OUTPUT_LINES_HISTORY = 10;
|
||||
const MAX_OUTPUT_LENGTH = 10000;
|
||||
|
||||
function addShellCommandToGeminiHistory(
|
||||
@@ -284,7 +286,10 @@ export const useShellCommandProcessor = (
|
||||
const finalToolDisplay: IndividualToolCallDisplay = {
|
||||
...initialToolDisplay,
|
||||
status: finalStatus,
|
||||
resultDisplay: finalOutput,
|
||||
resultDisplay: pruneShellOutput(
|
||||
finalOutput,
|
||||
MAX_OUTPUT_LINES_HISTORY,
|
||||
),
|
||||
};
|
||||
|
||||
// Add the complete, contextual result to the local UI history.
|
||||
|
||||
@@ -17,6 +17,9 @@ import {
|
||||
type HistoryItemToolGroup,
|
||||
type IndividualToolCallDisplay,
|
||||
} from '../types.js';
|
||||
import { pruneShellOutput } from '../utils/textUtils.js';
|
||||
|
||||
const MAX_OUTPUT_LINES_HISTORY = 10;
|
||||
|
||||
import { checkExhaustive } from '../../utils/checks.js';
|
||||
|
||||
@@ -117,7 +120,10 @@ export function mapToDisplay(
|
||||
return {
|
||||
...baseDisplayProperties,
|
||||
status: mapCoreStatusToDisplayStatus(call.status),
|
||||
resultDisplay,
|
||||
resultDisplay:
|
||||
call.request.name === 'run_shell_command'
|
||||
? pruneShellOutput(resultDisplay, MAX_OUTPUT_LINES_HISTORY)
|
||||
: resultDisplay,
|
||||
confirmationDetails,
|
||||
outputFile,
|
||||
ptyId,
|
||||
|
||||
@@ -10,6 +10,32 @@ import { stripVTControlCharacters } from 'node:util';
|
||||
import stringWidth from 'string-width';
|
||||
import { LRUCache } from 'mnemonist';
|
||||
import { LRU_BUFFER_PERF_CACHE_LIMIT } from '../constants.js';
|
||||
import type { AnsiOutput, ToolResultDisplay } from '@google/gemini-cli-core';
|
||||
|
||||
/**
|
||||
* Prunes shell output to the last N lines.
|
||||
* Handles both string output and AnsiOutput object.
|
||||
*/
|
||||
export function pruneShellOutput(
|
||||
output: ToolResultDisplay | undefined,
|
||||
maxLines: number,
|
||||
): ToolResultDisplay | undefined {
|
||||
if (output === undefined) return undefined;
|
||||
|
||||
if (typeof output === 'string') {
|
||||
const lines = output.split('\n');
|
||||
if (lines.length <= maxLines) return output;
|
||||
return lines.slice(-maxLines).join('\n');
|
||||
}
|
||||
|
||||
if (Array.isArray(output)) {
|
||||
// Assume it's AnsiOutput (AnsiLine[])
|
||||
if (output.length <= maxLines) return output;
|
||||
return output.slice(-maxLines) as AnsiOutput;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the maximum width of a multi-line ASCII art string.
|
||||
|
||||
Reference in New Issue
Block a user