import { Chip, Disclosure, Spinner } from "@heroui/react"; import type { ToolCallContent, ToolCallPayload, ToolCallStatus } from "../lib/acp"; const STATUS_COLOR: Record = { pending: "default", in_progress: "accent", completed: "success", failed: "danger", }; /** * Tool kinds get a glyph rather than a colour: status already owns colour in * this row, and two colour dimensions on one line stop reading as either. */ const KIND_GLYPH: Record = { read: "▤", edit: "✎", delete: "␡", move: "→", search: "⌕", execute: "❯", think: "◇", fetch: "↓", switch_mode: "⇄", other: "•", }; function DiffBlock({ item }: { item: ToolCallContent }) { const oldText = item.oldText ?? ""; const newText = item.newText ?? ""; return (
{item.path ?? "(unnamed file)"}
{oldText ? (
            {oldText}
          
) : (
new file
)}
          {newText}
        
); } function ContentItem({ item }: { item: ToolCallContent }) { if (item.type === "diff") return ; if (item.type === "terminal") { // Terminal content is a live handle, not data: the bridge does not mirror // the pty, so claiming to show output would be a lie. return (
live terminal {item.terminalId ?? ""} — output stays in the session
); } const block = item.content; const text = block?.text ?? block?.resource?.text ?? (block?.type === "image" ? "[image]" : block?.type === "resource_link" ? `[${block.name ?? block.uri ?? "resource"}]` : ""); if (!text) return null; return (
      {text}
    
); } export function ToolCall({ call }: { call: ToolCallPayload }) { const status: ToolCallStatus = call.status ?? "pending"; const running = status === "pending" || status === "in_progress"; const content = call.content ?? []; const glyph = KIND_GLYPH[call.kind ?? "other"] ?? KIND_GLYPH.other; const header = (
{glyph} {call.title ?? call.kind ?? "tool call"} {running ? ( ) : ( {status.replace("_", " ")} )}
); const hasDetail = content.length > 0 || (call.locations?.length ?? 0) > 0; // A tool call with nothing to show should not pretend to be expandable: an // empty disclosure opening onto blank space reads as a loading bug. if (!hasDetail) { return (
{header}
); } return ( {header} {call.locations?.length ? (
{call.locations.map((l) => (l.line ? `${l.path}:${l.line}` : l.path)).join(" · ")}
) : null} {content.map((item, i) => ( ))}
); }