/** * Centralized tool configuration registry * Defines display behavior for all tool types */ export interface ToolDisplayConfig { input: { type: 'one-line' | 'collapsible' | 'hidden'; // One-line config icon?: string; label?: string; getValue?: (input: any) => string; getSecondary?: (input: any) => string | undefined; action?: 'copy' | 'open-file' | 'jump-to-results' | 'none'; style?: string; wrapText?: boolean; colorScheme?: { primary?: string; secondary?: string; background?: string; border?: string; icon?: string; }; // Collapsible config title?: string | ((input: any) => string); defaultOpen?: boolean; contentType?: 'diff' | 'markdown' | 'file-list' | 'todo-list' | 'text' | 'task' | 'question-answer'; getContentProps?: (input: any, helpers?: any) => any; actionButton?: 'file-button' | 'none'; }; result?: { hidden?: boolean; hideOnSuccess?: boolean; type?: 'one-line' | 'collapsible' | 'special'; title?: string | ((result: any) => string); defaultOpen?: boolean; // Special result handlers contentType?: 'markdown' | 'file-list' | 'todo-list' | 'text' | 'success-message' | 'task' | 'question-answer'; getMessage?: (result: any) => string; getContentProps?: (result: any) => any; }; } export const TOOL_CONFIGS: Record = { // ============================================================================ // COMMAND TOOLS // ============================================================================ Bash: { input: { type: 'one-line', icon: 'terminal', getValue: (input) => input.command, getSecondary: (input) => input.description, action: 'copy', style: 'terminal', wrapText: true, colorScheme: { primary: 'text-green-400 font-mono', secondary: 'text-gray-400', background: '', border: 'border-green-500 dark:border-green-400', icon: 'text-green-500 dark:text-green-400' } }, result: { hideOnSuccess: true, type: 'special' } }, // ============================================================================ // FILE OPERATION TOOLS // ============================================================================ Read: { input: { type: 'one-line', label: 'Read', getValue: (input) => input.file_path || '', action: 'open-file', colorScheme: { primary: 'text-gray-700 dark:text-gray-300', background: '', border: 'border-gray-300 dark:border-gray-600', icon: 'text-gray-500 dark:text-gray-400' } }, result: { hidden: true } }, Edit: { input: { type: 'collapsible', title: (input) => { const filename = input.file_path?.split('/').pop() || input.file_path || 'file'; return `${filename}`; }, defaultOpen: false, contentType: 'diff', actionButton: 'none', getContentProps: (input) => ({ oldContent: input.old_string, newContent: input.new_string, filePath: input.file_path, badge: 'Edit', badgeColor: 'gray' }) }, result: { hideOnSuccess: true } }, Write: { input: { type: 'collapsible', title: (input) => { const filename = input.file_path?.split('/').pop() || input.file_path || 'file'; return `${filename}`; }, defaultOpen: false, contentType: 'diff', actionButton: 'none', getContentProps: (input) => ({ oldContent: '', newContent: input.content, filePath: input.file_path, badge: 'New', badgeColor: 'green' }) }, result: { hideOnSuccess: true } }, ApplyPatch: { input: { type: 'collapsible', title: (input) => { const filename = input.file_path?.split('/').pop() || input.file_path || 'file'; return `${filename}`; }, defaultOpen: false, contentType: 'diff', actionButton: 'none', getContentProps: (input) => ({ oldContent: input.old_string, newContent: input.new_string, filePath: input.file_path, badge: 'Patch', badgeColor: 'gray' }) }, result: { hideOnSuccess: true } }, // ============================================================================ // SEARCH TOOLS // ============================================================================ Grep: { input: { type: 'one-line', label: 'Grep', getValue: (input) => input.pattern, getSecondary: (input) => input.path ? `in ${input.path}` : undefined, action: 'jump-to-results', colorScheme: { primary: 'text-gray-700 dark:text-gray-300', secondary: 'text-gray-500 dark:text-gray-400', background: '', border: 'border-gray-400 dark:border-gray-500', icon: 'text-gray-500 dark:text-gray-400' } }, result: { type: 'collapsible', defaultOpen: false, title: (result) => { const toolData = result.toolUseResult || {}; const count = toolData.numFiles || toolData.filenames?.length || 0; return `Found ${count} ${count === 1 ? 'file' : 'files'}`; }, contentType: 'file-list', getContentProps: (result) => { const toolData = result.toolUseResult || {}; return { files: toolData.filenames || [] }; } } }, Glob: { input: { type: 'one-line', label: 'Glob', getValue: (input) => input.pattern, getSecondary: (input) => input.path ? `in ${input.path}` : undefined, action: 'jump-to-results', colorScheme: { primary: 'text-gray-700 dark:text-gray-300', secondary: 'text-gray-500 dark:text-gray-400', background: '', border: 'border-gray-400 dark:border-gray-500', icon: 'text-gray-500 dark:text-gray-400' } }, result: { type: 'collapsible', defaultOpen: false, title: (result) => { const toolData = result.toolUseResult || {}; const count = toolData.numFiles || toolData.filenames?.length || 0; return `Found ${count} ${count === 1 ? 'file' : 'files'}`; }, contentType: 'file-list', getContentProps: (result) => { const toolData = result.toolUseResult || {}; return { files: toolData.filenames || [] }; } } }, // ============================================================================ // TODO TOOLS // ============================================================================ TodoWrite: { input: { type: 'collapsible', title: 'Updating todo list', defaultOpen: false, contentType: 'todo-list', getContentProps: (input) => ({ todos: input.todos }) }, result: { type: 'collapsible', contentType: 'success-message', getMessage: () => 'Todo list updated' } }, TodoRead: { input: { type: 'one-line', label: 'TodoRead', getValue: () => 'reading list', action: 'none', colorScheme: { primary: 'text-gray-500 dark:text-gray-400', border: 'border-violet-400 dark:border-violet-500' } }, result: { type: 'collapsible', contentType: 'todo-list', getContentProps: (result) => { try { const content = String(result.content || ''); let todos = null; if (content.startsWith('[')) { todos = JSON.parse(content); } return { todos, isResult: true }; } catch (e) { return { todos: [], isResult: true }; } } } }, // ============================================================================ // TASK TOOLS (TaskCreate, TaskUpdate, TaskList, TaskGet) // ============================================================================ TaskCreate: { input: { type: 'one-line', label: 'Task', getValue: (input) => input.subject || 'Creating task', getSecondary: (input) => input.status || undefined, action: 'none', colorScheme: { primary: 'text-gray-700 dark:text-gray-300', border: 'border-violet-400 dark:border-violet-500', icon: 'text-violet-500 dark:text-violet-400' } }, result: { hideOnSuccess: true } }, TaskUpdate: { input: { type: 'one-line', label: 'Task', getValue: (input) => { const parts = []; if (input.taskId) parts.push(`#${input.taskId}`); if (input.status) parts.push(input.status); if (input.subject) parts.push(`"${input.subject}"`); return parts.join(' → ') || 'updating'; }, action: 'none', colorScheme: { primary: 'text-gray-700 dark:text-gray-300', border: 'border-violet-400 dark:border-violet-500', icon: 'text-violet-500 dark:text-violet-400' } }, result: { hideOnSuccess: true } }, TaskList: { input: { type: 'one-line', label: 'Tasks', getValue: () => 'listing tasks', action: 'none', colorScheme: { primary: 'text-gray-500 dark:text-gray-400', border: 'border-violet-400 dark:border-violet-500', icon: 'text-violet-500 dark:text-violet-400' } }, result: { type: 'collapsible', defaultOpen: true, title: 'Task list', contentType: 'task', getContentProps: (result) => ({ content: String(result?.content || '') }) } }, TaskGet: { input: { type: 'one-line', label: 'Task', getValue: (input) => input.taskId ? `#${input.taskId}` : 'fetching', action: 'none', colorScheme: { primary: 'text-gray-700 dark:text-gray-300', border: 'border-violet-400 dark:border-violet-500', icon: 'text-violet-500 dark:text-violet-400' } }, result: { type: 'collapsible', defaultOpen: true, title: 'Task details', contentType: 'task', getContentProps: (result) => ({ content: String(result?.content || '') }) } }, // ============================================================================ // SUBAGENT TASK TOOL // ============================================================================ Task: { input: { type: 'collapsible', title: (input) => { const subagentType = input.subagent_type || 'Agent'; const description = input.description || 'Running task'; return `Subagent / ${subagentType}: ${description}`; }, defaultOpen: false, contentType: 'markdown', getContentProps: (input) => { // If only prompt exists (and required fields), show just the prompt // Otherwise show all available fields const hasOnlyPrompt = input.prompt && !input.model && !input.resume; if (hasOnlyPrompt) { return { content: input.prompt || '' }; } // Format multiple fields const parts = []; if (input.model) { parts.push(`**Model:** ${input.model}`); } if (input.prompt) { parts.push(`**Prompt:**\n${input.prompt}`); } if (input.resume) { parts.push(`**Resuming from:** ${input.resume}`); } return { content: parts.join('\n\n') }; }, colorScheme: { border: 'border-purple-500 dark:border-purple-400', icon: 'text-purple-500 dark:text-purple-400' } }, result: { type: 'collapsible', title: 'Subagent result', defaultOpen: false, contentType: 'markdown', getContentProps: (result) => { // Handle agent results which may have complex structure if (result && result.content) { let content = result.content; // If content is a JSON string, try to parse it (agent results may arrive serialized) if (typeof content === 'string') { try { const parsed = JSON.parse(content); if (Array.isArray(parsed)) { content = parsed; } } catch { // Not JSON — use as-is return { content }; } } // If content is an array (typical for agent responses with multiple text blocks) if (Array.isArray(content)) { const textContent = content .filter((item: any) => item.type === 'text') .map((item: any) => item.text) .join('\n\n'); return { content: textContent || 'No response text' }; } return { content: String(content) }; } // Fallback to string representation return { content: String(result || 'No response') }; } } }, // ============================================================================ // INTERACTIVE TOOLS // ============================================================================ AskUserQuestion: { input: { type: 'collapsible', title: (input: any) => { const count = input.questions?.length || 0; const hasAnswers = input.answers && Object.keys(input.answers).length > 0; if (count === 1) { const header = input.questions[0]?.header || 'Question'; return hasAnswers ? `${header} — answered` : header; } return hasAnswers ? `${count} questions — answered` : `${count} questions`; }, defaultOpen: true, contentType: 'question-answer', getContentProps: (input: any) => ({ questions: input.questions || [], answers: input.answers || {} }), }, result: { hideOnSuccess: true } }, // ============================================================================ // PLAN TOOLS // ============================================================================ exit_plan_mode: { input: { type: 'collapsible', title: 'Implementation plan', defaultOpen: true, contentType: 'markdown', getContentProps: (input) => ({ content: input.plan?.replace(/\\n/g, '\n') || input.plan }) }, result: { type: 'collapsible', contentType: 'markdown', getContentProps: (result) => { try { let parsed = result.content; if (typeof parsed === 'string') { parsed = JSON.parse(parsed); } return { content: parsed.plan?.replace(/\\n/g, '\n') || parsed.plan }; } catch (e) { return { content: '' }; } } } }, // Also register as ExitPlanMode (the actual tool name used by Claude) ExitPlanMode: { input: { type: 'collapsible', title: 'Implementation plan', defaultOpen: true, contentType: 'markdown', getContentProps: (input) => ({ content: input.plan?.replace(/\\n/g, '\n') || input.plan }) }, result: { type: 'collapsible', contentType: 'markdown', getContentProps: (result) => { try { let parsed = result.content; if (typeof parsed === 'string') { parsed = JSON.parse(parsed); } return { content: parsed.plan?.replace(/\\n/g, '\n') || parsed.plan }; } catch (e) { return { content: '' }; } } } }, // ============================================================================ // DEFAULT FALLBACK // ============================================================================ Default: { input: { type: 'collapsible', title: 'Parameters', defaultOpen: false, contentType: 'text', getContentProps: (input) => ({ content: typeof input === 'string' ? input : JSON.stringify(input, null, 2), format: 'code' }) }, result: { type: 'collapsible', contentType: 'text', getContentProps: (result) => ({ content: String(result?.content || ''), format: 'plain' }) } } }; /** * Get configuration for a tool, with fallback to default */ export function getToolConfig(toolName: string): ToolDisplayConfig { return TOOL_CONFIGS[toolName] || TOOL_CONFIGS.Default; } /** * Check if a tool result should be hidden */ export function shouldHideToolResult(toolName: string, toolResult: any): boolean { const config = getToolConfig(toolName); if (!config.result) return false; // Always hidden if (config.result.hidden) return true; // Hide on success only if (config.result.hideOnSuccess && toolResult && !toolResult.isError) { return true; } return false; }