import { useState, useMemo } from "react"; import { cn } from "@/lib/utils"; import { Terminal, FileText, FileEdit, Search, FolderSearch, Globe, Link, ListTodo, Bot, ChevronDown, ChevronRight, Loader2, Check, X, Clock, Copy, GitBranch, MessageCircle, Wrench, Settings, Send, Server, KeyRound, Timer, Zap, Code2, BookOpen, MonitorPlay, Moon, Play, Braces, ListChecks, CalendarClock, Trash2, List, Eye, StopCircle, RefreshCw, Languages, Map, MapPin, Users, UserMinus, Webhook, FlaskConical, GitFork, } from "lucide-react"; import type { ToolCallInfo } from "@/hooks/useChat"; /** * Tool icon map — EXACT parity with original claw-code tool names. * Supports both original names and legacy aliases. */ const TOOL_ICONS: Record = { // ── Core 19 tools (original names) ── bash: Terminal, PowerShell: MonitorPlay, read_file: FileText, write_file: FileEdit, edit_file: FileEdit, glob_search: FolderSearch, grep_search: Search, NotebookEdit: BookOpen, WebSearch: Globe, WebFetch: Link, TodoWrite: ListTodo, Agent: Bot, SendUserMessage: MessageCircle, Brief: MessageCircle, TestingPermission: FlaskConical, ToolSearch: Wrench, Config: Settings, Skill: Zap, Sleep: Moon, REPL: Play, StructuredOutput: Braces, // ── Extended tools (full parity) ── TaskCreate: ListChecks, TaskGet: Eye, TaskList: List, TaskOutput: Terminal, TaskStop: StopCircle, TaskUpdate: RefreshCw, CronCreate: CalendarClock, CronDelete: Trash2, CronList: CalendarClock, LSP: Languages, EnterPlanMode: Map, ExitPlanMode: MapPin, EnterWorktree: GitFork, ExitWorktree: GitBranch, TeamCreate: Users, TeamDelete: UserMinus, RemoteTrigger: Webhook, SyntheticOutput: FlaskConical, // ── MCP ── mcp_tool: Server, list_mcp_resources: Server, read_mcp_resource: Server, mcp_auth: KeyRound, // ── Legacy aliases ── powershell: MonitorPlay, grep: Search, glob: FolderSearch, web_search: Globe, web_fetch: Link, todo_read: ListTodo, todo_write: ListTodo, sub_agent: Bot, send_message: Send, ask_user: MessageCircle, tool_search: Wrench, config_read: Settings, config_write: Settings, notebook_edit: BookOpen, skill: Zap, }; /** * Tool color map — EXACT parity with original claw-code tool names. */ const TOOL_COLORS: Record = { // ── Core 19 tools ── bash: "text-green-400", PowerShell: "text-blue-500", read_file: "text-blue-400", write_file: "text-yellow-400", edit_file: "text-orange-400", glob_search: "text-cyan-400", grep_search: "text-purple-400", NotebookEdit: "text-emerald-400", WebSearch: "text-pink-400", WebFetch: "text-indigo-400", TodoWrite: "text-teal-400", Agent: "text-amber-400", SendUserMessage: "text-sky-400", Brief: "text-sky-400", TestingPermission: "text-gray-400", ToolSearch: "text-violet-400", Config: "text-slate-400", Skill: "text-yellow-300", Sleep: "text-indigo-300", REPL: "text-lime-400", StructuredOutput: "text-cyan-300", // ── Extended tools (full parity) ── TaskCreate: "text-emerald-500", TaskGet: "text-emerald-400", TaskList: "text-emerald-300", TaskOutput: "text-emerald-400", TaskStop: "text-red-400", TaskUpdate: "text-emerald-300", CronCreate: "text-amber-500", CronDelete: "text-red-400", CronList: "text-amber-400", LSP: "text-blue-500", EnterPlanMode: "text-violet-500", ExitPlanMode: "text-violet-400", EnterWorktree: "text-orange-500", ExitWorktree: "text-orange-400", TeamCreate: "text-cyan-500", TeamDelete: "text-red-400", RemoteTrigger: "text-pink-500", SyntheticOutput: "text-lime-500", // ── MCP ── mcp_tool: "text-rose-400", list_mcp_resources: "text-rose-300", read_mcp_resource: "text-rose-300", mcp_auth: "text-rose-500", // ── Legacy aliases ── powershell: "text-blue-500", grep: "text-purple-400", glob: "text-cyan-400", web_search: "text-pink-400", web_fetch: "text-indigo-400", todo_read: "text-teal-400", todo_write: "text-teal-400", sub_agent: "text-amber-400", send_message: "text-sky-400", ask_user: "text-sky-400", tool_search: "text-violet-400", config_read: "text-slate-400", config_write: "text-slate-400", notebook_edit: "text-emerald-400", skill: "text-yellow-300", }; /** Simple diff line renderer for edit_file results */ function DiffView({ content }: { content: string }) { const lines = content.split("\n"); return (
{lines.map((line, i) => { let cls = "text-foreground/70"; if (line.startsWith("+ ") || line.startsWith("+\t") || line === "+") { cls = "text-green-400 bg-green-400/10"; } else if ( line.startsWith("- ") || line.startsWith("-\t") || line === "-" ) { cls = "text-red-400 bg-red-400/10"; } else if (line.startsWith("@@ ")) { cls = "text-cyan-400/60"; } else if (line.startsWith("--- ") || line.startsWith("+++ ")) { cls = "text-muted-foreground"; } return (
{line}
); })}
); } /** Detect if output looks like a diff */ function isDiffLike(content: string): boolean { if (!content) return false; const lines = content.split("\n").slice(0, 20); let diffIndicators = 0; for (const l of lines) { if ( l.startsWith("+ ") || l.startsWith("- ") || l.startsWith("@@ ") || l.startsWith("--- ") || l.startsWith("+++ ") ) diffIndicators++; } return diffIndicators >= 2; } export function ToolCallCard({ tool }: { tool: ToolCallInfo }) { const [expanded, setExpanded] = useState(false); const [copied, setCopied] = useState(false); const Icon = TOOL_ICONS[tool.name] || Terminal; const colorClass = TOOL_COLORS[tool.name] || "text-muted-foreground"; let parsedArgs: Record = {}; try { parsedArgs = JSON.parse(tool.arguments || "{}"); } catch { parsedArgs = { raw: tool.arguments }; } const showDiff = useMemo( () => (tool.name === "edit_file" || tool.name === "bash") && tool.result && isDiffLike(tool.result), [tool.name, tool.result] ); // Format the tool call summary — supports both original and legacy names const getSummary = () => { switch (tool.name) { case "bash": case "PowerShell": case "powershell": return String(parsedArgs.command || "").substring(0, 80); case "read_file": case "write_file": case "edit_file": case "NotebookEdit": case "notebook_edit": return String(parsedArgs.path || parsedArgs.notebook_path || ""); case "grep_search": case "grep": return `"${parsedArgs.pattern}" in ${parsedArgs.path || "."}`; case "glob_search": case "glob": return String(parsedArgs.pattern || ""); case "WebSearch": case "web_search": return String(parsedArgs.query || ""); case "WebFetch": case "web_fetch": return String(parsedArgs.url || "").substring(0, 60); case "Agent": case "sub_agent": return String(parsedArgs.description || parsedArgs.task || "").substring(0, 60); case "SendUserMessage": case "Brief": case "ask_user": return String(parsedArgs.message || parsedArgs.question || "").substring(0, 60); case "TestingPermission": return `check: ${parsedArgs.tool || "unknown"}`; case "ToolSearch": case "tool_search": return String(parsedArgs.query || ""); case "Config": return parsedArgs.value !== undefined ? `${parsedArgs.setting} = ${parsedArgs.value}` : String(parsedArgs.setting || "all"); case "config_read": return String(parsedArgs.key || "all"); case "config_write": return `${parsedArgs.key} = ${parsedArgs.value}`; case "Skill": case "skill": return String(parsedArgs.skill || parsedArgs.name || ""); case "Sleep": return `${parsedArgs.duration_ms || 0}ms`; case "REPL": return `${parsedArgs.language || "python"}: ${String(parsedArgs.code || "").substring(0, 50)}`; case "TodoWrite": return "Update task list"; // Extended tools case "TaskCreate": return String(parsedArgs.description || "").substring(0, 60); case "TaskGet": case "TaskOutput": case "TaskStop": case "TaskUpdate": return String(parsedArgs.id || ""); case "TaskList": return "List background tasks"; case "CronCreate": return `${parsedArgs.schedule} — ${String(parsedArgs.command || "").substring(0, 40)}`; case "CronDelete": return String(parsedArgs.id || ""); case "CronList": return "List cron jobs"; case "LSP": return `${parsedArgs.action || ""} ${parsedArgs.path || ""}`; case "EnterPlanMode": return "Enter plan mode"; case "ExitPlanMode": return "Exit plan mode"; case "EnterWorktree": return String(parsedArgs.branch || ""); case "ExitWorktree": return "Exit worktree"; case "TeamCreate": return String(parsedArgs.name || ""); case "TeamDelete": return String(parsedArgs.id || ""); case "RemoteTrigger": return `${parsedArgs.method || "POST"} ${String(parsedArgs.url || "").substring(0, 50)}`; case "SyntheticOutput": return String(parsedArgs.format || "json"); default: return tool.name; } }; const copyResult = () => { if (tool.result) { navigator.clipboard.writeText(tool.result); setCopied(true); setTimeout(() => setCopied(false), 2000); } }; return (
{/* Header */} {/* Expanded content */} {expanded && (
{/* Arguments */}
Input
              {tool.name === "bash" || tool.name === "PowerShell" || tool.name === "powershell"
                ? String(parsedArgs.command || "")
                : JSON.stringify(parsedArgs, null, 2)}
            
{/* Result */} {tool.result !== undefined && (
Output {tool.isError && ( (error) )} {showDiff && ( (diff) )}
{showDiff ? (
) : (
                  {tool.result}
                
)}
)}
)}
); }