/** * ToolCallCard shows one tool call the agent is running, in the spirit of * Zed's agent panel: a collapsible card with the tool kind, a live status, * touched locations, and the call's output (text, or a file diff). * * @example * */ import type { ToolCall } from "../reducer"; import { InlineText } from "./InlineText"; import type { AcpToolCallContent } from "../protocol"; import { Markdown } from "./Markdown"; import { CopyButton } from "./CopyButton"; const kindIcons: Record = { read: "📖", edit: "✏️", delete: "🗑️", move: "📦", search: "🔍", execute: "⚡", think: "💭", fetch: "🌐", switch_mode: "🔀", other: "🔧", }; const statusLabels: Record = { pending: "pending", in_progress: "running", completed: "done", failed: "failed", }; export function ToolCallCard({ call }: { call: ToolCall }) { // Extract all textual content for copy button const getToolContent = (): string => { return call.contents .map((content) => { if (content.type === "content" && content.content?.text) { return content.content.text; } if (content.type === "diff") { const parts = []; if (content.path) parts.push(`Path: ${content.path}`); if (content.oldText) parts.push(`Old:\n${content.oldText}`); if (content.newText) parts.push(`New:\n${content.newText}`); return parts.join("\n\n"); } return ""; }) .filter(Boolean) .join("\n\n"); }; return (
{kindIcons[call.toolKind] ?? kindIcons.other} {statusLabels[call.status] ?? call.status} {getToolContent() && ( )}
{call.locations.length > 0 && (
    {call.locations.map((location, index) => ( // biome-ignore lint/suspicious/noArrayIndexKey: tool-call locations carry no id and the list is replaced wholesale on every update.
  • {location.path} {location.line != null ? `:${location.line}` : ""}
  • ))}
)} {call.contents.map((content, index) => ( // biome-ignore lint/suspicious/noArrayIndexKey: tool-call contents carry no id and the list is replaced wholesale on every update. ))}
); } function ToolContent({ content, toolKind, }: { content: AcpToolCallContent; toolKind: string }) { switch (content.type) { case "content": return ( ); case "diff": return (
{content.path} {content.oldText != null && (
							{prefixLines(content.oldText, "- ")}
						
)}
						{prefixLines(content.newText ?? "", "+ ")}
					
); case "terminal": return

terminal: {content.terminalId}

; default: return null; } } /** * TextToolContent renders a tool call's textual output. System-command * output (kind "execute") is verbatim text: it goes into a monospace block, * never through the markdown pipeline. */ function TextToolContent({ text, toolKind, }: { text: string | undefined; toolKind: string }) { if (!text) { return null; } if (toolKind === "execute") { return
{text}
; } return ; } function prefixLines(text: string, prefix: string): string { return text .split("\n") .map((line) => prefix + line) .join("\n"); }