/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import type React from 'react'; import { Box, Text } from 'ink'; import { theme } from '../../semantic-colors.js'; import Spinner from 'ink-spinner'; import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js'; import type { SubagentProgress, SubagentActivityItem, } from '@google/gemini-cli-core'; import { TOOL_STATUS } from '../../constants.js'; import { STATUS_INDICATOR_WIDTH } from './ToolShared.js'; import { safeJsonToMarkdown } from '@google/gemini-cli-core'; export interface SubagentProgressDisplayProps { progress: SubagentProgress; terminalWidth: number; historyOverrides?: SubagentActivityItem[]; } export const formatToolArgs = (args?: string): string => { if (!args) return ''; try { const parsed: unknown = JSON.parse(args); if (typeof parsed !== 'object' || parsed === null) { return args; } if ( 'description' in parsed && typeof parsed.description === 'string' && parsed.description ) { return parsed.description; } if ('command' in parsed && typeof parsed.command === 'string') return parsed.command; if ('file_path' in parsed && typeof parsed.file_path === 'string') return parsed.file_path; if ('dir_path' in parsed && typeof parsed.dir_path === 'string') return parsed.dir_path; if ('query' in parsed && typeof parsed.query === 'string') return parsed.query; if ('url' in parsed && typeof parsed.url === 'string') return parsed.url; if ('target' in parsed && typeof parsed.target === 'string') return parsed.target; return args; } catch { return args; } }; export const SubagentProgressDisplay: React.FC< SubagentProgressDisplayProps > = ({ progress, terminalWidth, historyOverrides }) => { let headerText: string | undefined; let headerColor = theme.text.secondary; const isCloud = progress.agentName === 'cloud-subagent' || progress.agentName === 'cloud_subagent'; const prefix = isCloud ? '☁ Cloud' : `Subagent ${progress.agentName}`; if (progress.state === 'cancelled') { headerText = `${prefix} was cancelled.`; headerColor = theme.status.warning; } else if (progress.state === 'error') { headerText = `${prefix} failed.`; headerColor = theme.status.error; } else if (progress.state === 'completed') { headerText = `${prefix} completed.`; headerColor = theme.status.success; } else { headerText = isCloud ? `☁ Running cloud subagent...` : `Running subagent ${progress.agentName}...`; headerColor = isCloud ? theme.status.warning : theme.text.primary; } return ( {headerText && ( {headerText} )} {(historyOverrides ?? progress.recentActivity).map( (item: SubagentActivityItem) => { if (item.type === 'thought') { const isCancellation = item.content === 'Request cancelled.'; const icon = isCancellation ? 'ℹ ' : '💭'; const color = isCancellation ? theme.status.warning : theme.text.secondary; return ( {icon} {item.content} ); } else if (item.type === 'tool_call') { const statusSymbol = item.status === 'running' ? ( ) : item.status === 'completed' ? ( {TOOL_STATUS.SUCCESS} ) : item.status === 'cancelled' ? ( {TOOL_STATUS.CANCELED} ) : ( {TOOL_STATUS.ERROR} ); const formattedArgs = item.description || formatToolArgs(item.args); const displayArgs = formattedArgs.length > 60 ? formattedArgs.slice(0, 60) + '...' : formattedArgs; return ( {statusSymbol} {item.displayName || item.content} {displayArgs && ( {displayArgs} )} ); } return null; }, )} {progress.result && ( {progress.terminateReason && progress.terminateReason !== 'GOAL' && ( Agent Finished Early ({progress.terminateReason}) )} )} ); };