/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import React from 'react'; import { Box, Text } from 'ink'; import { IndividualToolCallDisplay, ToolCallStatus } from '../../types.js'; import { DiffRenderer } from './DiffRenderer.js'; import { Colors } from '../../colors.js'; import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js'; import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js'; const STATIC_HEIGHT = 1; const RESERVED_LINE_COUNT = 5; // for tool name, status, padding etc. const STATUS_INDICATOR_WIDTH = 3; export type TextEmphasis = 'high' | 'medium' | 'low'; export interface ToolMessageProps extends IndividualToolCallDisplay { availableTerminalHeight: number; emphasis?: TextEmphasis; } export const ToolMessage: React.FC = ({ name, description, resultDisplay, status, availableTerminalHeight, emphasis = 'medium', }) => { const contentHeightEstimate = availableTerminalHeight - STATIC_HEIGHT - RESERVED_LINE_COUNT; const resultIsString = typeof resultDisplay === 'string' && resultDisplay.trim().length > 0; const lines = React.useMemo( () => (resultIsString ? resultDisplay.split('\n') : []), [resultIsString, resultDisplay], ); // Truncate the overall string content if it's too long. // MarkdownRenderer will handle specific truncation for code blocks within this content. // Estimate available height for this specific tool message content area // This is a rough estimate; ideally, we'd have a more precise measurement. const displayableResult = React.useMemo( () => resultIsString ? lines.slice(-contentHeightEstimate).join('\n') : resultDisplay, [lines, resultIsString, contentHeightEstimate, resultDisplay], ); const hiddenLines = lines.length - contentHeightEstimate; return ( {emphasis === 'high' && } {displayableResult && ( {hiddenLines > 0 && ( ... first {hiddenLines} line{hiddenLines === 1 ? '' : 's'}{' '} hidden ... )} {typeof displayableResult === 'string' && ( )} {typeof displayableResult !== 'string' && ( )} )} ); }; type ToolStatusIndicatorProps = { status: ToolCallStatus; }; const ToolStatusIndicator: React.FC = ({ status, }) => ( {status === ToolCallStatus.Pending && ( o )} {status === ToolCallStatus.Executing && ( )} {status === ToolCallStatus.Success && ( )} {status === ToolCallStatus.Confirming && ( ? )} {status === ToolCallStatus.Canceled && ( - )} {status === ToolCallStatus.Error && ( x )} ); type ToolInfo = { name: string; description: string; status: ToolCallStatus; emphasis: TextEmphasis; }; const ToolInfo: React.FC = ({ name, description, status, emphasis, }) => { const nameColor = React.useMemo(() => { switch (emphasis) { case 'high': return Colors.Foreground; case 'medium': return Colors.Foreground; case 'low': return Colors.SubtleComment; default: { const exhaustiveCheck: never = emphasis; return exhaustiveCheck; } } }, [emphasis]); return ( {name} {' '} {description} ); }; const TrailingIndicator: React.FC = () => ( );