/** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import type React from 'react'; import { Box, Text } from 'ink'; import { CoreToolCallStatus } from '@google/gemini-cli-core'; import type { HistoryItem, HistoryItemWithoutId, HistoryItemToolDisplayGroup, ToolDisplayItem, } from '../../types.js'; import { theme } from '../../semantic-colors.js'; import { ToolStatusIndicator } from './ToolShared.js'; import { useSettings } from '../../contexts/SettingsContext.js'; interface ToolGroupDisplayProps { item: HistoryItem | HistoryItemWithoutId; isToolGroupBoundary?: boolean; } export const ToolGroupDisplay: React.FC = ({ item, isToolGroupBoundary, }) => { const settings = useSettings(); const isCompactModeEnabled = settings.merged.ui?.compactToolOutput === true; if (item.type !== 'tool_display_group') { return null; } const { tools, borderColor, borderDimColor, borderTop, borderBottom } = item as HistoryItemToolDisplayGroup; const visibleTools = tools.filter( (t) => t.status !== CoreToolCallStatus.AwaitingApproval, ); const noticeTools = visibleTools.filter((t) => t.format === 'notice'); const otherTools = visibleTools.filter( (t) => t.format !== 'notice' && t.format !== 'hidden', ); const hasOtherTools = otherTools.length > 0; const isClosingSlice = tools.length === 0 && borderBottom; // If no tools are visible and it's not an explicit closing slice, hide the group if (visibleTools.length === 0 && !isClosingSlice) { return null; } // Standard view behavior: If compact mode is enabled, non-notice tools // are typically rendered without an outer box. const shouldShowBox = (hasOtherTools || isClosingSlice) && !isCompactModeEnabled; const boxBorderTop = borderTop || noticeTools.length > 0; return ( {noticeTools.map((tool, index) => { const isFirstInGroup = index === 0 && borderTop; const isLastElementInGroup = index === noticeTools.length - 1 && !shouldShowBox && borderBottom; return ( 0 ? 1 : 0} marginBottom={isLastElementInGroup ? 1 : 0} > ); })} {shouldShowBox ? ( {otherTools.map((tool, index) => ( ))} ) : otherTools.length > 0 ? ( // Compact mode or no tools to box 0 ? 1 : 0} marginBottom={borderBottom ? 1 : 0} > {otherTools.map((tool, index) => ( ))} ) : null} ); }; interface ToolDisplayMessageProps { tool: ToolDisplayItem; } const ToolDisplayMessage: React.FC = ({ tool }) => { const settings = useSettings(); const isCompactModeEnabled = settings.merged.ui?.compactToolOutput === true; // Since ToolDisplayItem is ToolDisplay & { status, ... }, we check for identifying properties // of ToolDisplay. If name or description is missing and there's no result, it might be "empty". if (!tool.name && !tool.description && !tool.result && !tool.resultSummary) { return ( Error: Tool display missing ); } const { status, format: preferredFormat, name, description, resultSummary, result, } = tool; const format = preferredFormat || 'auto'; if (format === 'hidden') { return null; } if (format === 'notice') { // If the name is part of the description (typical for topic updates), // suppress the bold name to avoid redundancy and match legacy UI. const isRedundant = !!(name && description?.includes(`"${name}"`)); return ( {name && !isRedundant && ( {name}: )} {description && ( {description} )} ); } const isCompact = format === 'compact' || (format === 'auto' && isCompactModeEnabled); if (isCompact) { return ( {' '} {name || tool.originalRequestName}{' '} {description && {description}} {resultSummary && ( {' '} → {resultSummary.replace(/\n/g, ' ')} )} ); } // Box format (full) return ( {' '} {name || tool.originalRequestName}{' '} {description && {description}} {resultSummary && !result && ( {resultSummary} )} {result && ( )} ); }; interface ToolResultDisplayContentProps { content: ToolDisplayItem['result']; summary?: string | null; } const ToolResultDisplayContent: React.FC = ({ content, summary, }) => { if (!content) return null; switch (content.type) { case 'text': return ( {content.text} {summary && ( {summary} )} ); case 'diff': // Simplified diff display for now return ( {summary && {summary}} {`[Diff Display: ${content.beforeText.length} -> ${content.afterText.length} chars]`} ); case 'terminal': return [Terminal Output]; case 'agent': return ( [Subagent: {content.threadId}] ); default: return null; } };