{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tool-call",
  "title": "Tool Call",
  "description": "A compact record of one tool invocation with its input and output behind a disclosure.",
  "dependencies": [
    "lucide-react@^1.31.0"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/default/tool-call/tool-call.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  Check,\n  ChevronRight,\n  Loader,\n  Circle,\n  TriangleAlert,\n  Wrench,\n} from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\n\nexport type ToolCallStatus = \"pending\" | \"running\" | \"success\" | \"error\"\n\nexport type ToolCallProps = Omit<\n  React.HTMLAttributes<HTMLDivElement>,\n  \"children\" | \"input\"\n> & {\n  name: string\n  status?: ToolCallStatus\n  input?: unknown\n  output?: React.ReactNode\n  error?: string\n  startedAt?: number\n  durationMs?: number\n  icon?: React.ReactNode\n  inputLabel?: string\n  outputLabel?: string\n  open?: boolean\n  defaultOpen?: boolean\n  onOpenChange?: (open: boolean) => void\n}\n\nconst statusText: Record<ToolCallStatus, string> = {\n  pending: \"Queued\",\n  running: \"Running\",\n  success: \"Done\",\n  error: \"Failed\",\n}\n\nfunction formatDuration(ms: number) {\n  if (ms < 1000) return `${Math.round(ms)}ms`\n\n  const seconds = ms / 1000\n  if (seconds < 60)\n    return `${seconds < 10 ? seconds.toFixed(1) : Math.round(seconds)}s`\n\n  const minutes = Math.floor(seconds / 60)\n  return `${minutes}m ${Math.round(seconds - minutes * 60)}s`\n}\n\nfunction formatInput(value: unknown) {\n  if (typeof value === \"string\") return value\n\n  try {\n    return JSON.stringify(value, null, 2)\n  } catch {\n    return String(value)\n  }\n}\n\nfunction StatusIcon({ status }: { status: ToolCallStatus }) {\n  if (status === \"running\") {\n    return (\n      <Loader\n        aria-hidden=\"true\"\n        size={15}\n        className=\"text-muted-foreground animate-spin motion-reduce:animate-none\"\n      />\n    )\n  }\n\n  if (status === \"success\") {\n    return <Check aria-hidden=\"true\" size={15} className=\"text-accent\" />\n  }\n\n  if (status === \"error\") {\n    return (\n      <TriangleAlert\n        aria-hidden=\"true\"\n        size={15}\n        className=\"text-destructive\"\n      />\n    )\n  }\n\n  return (\n    <Circle aria-hidden=\"true\" size={15} className=\"text-muted-foreground\" />\n  )\n}\n\nexport function ToolCall({\n  name,\n  status = \"pending\",\n  input,\n  output,\n  error,\n  startedAt,\n  durationMs,\n  icon,\n  inputLabel = \"Input\",\n  outputLabel = \"Output\",\n  open,\n  defaultOpen = false,\n  onOpenChange,\n  className,\n  ...rootProps\n}: ToolCallProps) {\n  const reactId = React.useId()\n  const panelId = `${reactId}-panel`\n  const isRunning = status === \"running\"\n\n  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)\n  const isOpen = open ?? uncontrolledOpen\n\n  const setOpen = (next: boolean) => {\n    if (open === undefined) setUncontrolledOpen(next)\n    onOpenChange?.(next)\n  }\n\n  const [tick, setTick] = React.useState(() => Date.now())\n\n  React.useEffect(() => {\n    if (!isRunning || startedAt == null) return\n\n    const timer = window.setInterval(() => setTick(Date.now()), 100)\n\n    return () => window.clearInterval(timer)\n  }, [isRunning, startedAt])\n\n  const duration =\n    durationMs ??\n    (isRunning && startedAt != null ? Math.max(0, tick - startedAt) : undefined)\n\n  const hasPanel = input !== undefined || output !== undefined || error != null\n\n  return (\n    <div\n      data-slot=\"tool-call\"\n      data-status={status}\n      className={cn(\n        \"border-border bg-card text-card-foreground rounded-[var(--radius)] border\",\n        className\n      )}\n      {...rootProps}\n    >\n      <div\n        data-slot=\"tool-call-header\"\n        className=\"flex items-center gap-2.5 px-3.5 py-2.5\"\n      >\n        <span aria-hidden=\"true\" className=\"text-muted-foreground shrink-0\">\n          {icon ?? <Wrench size={15} />}\n        </span>\n\n        <span\n          data-slot=\"tool-call-name\"\n          className=\"font-[family-name:var(--font-mono),monospace] text-sm font-semibold\"\n        >\n          {name}\n        </span>\n\n        <span className=\"ml-auto flex items-center gap-2\">\n          {duration != null ? (\n            <span\n              data-slot=\"tool-call-duration\"\n              className=\"text-muted-foreground font-[family-name:var(--font-mono),monospace] text-xs tabular-nums\"\n            >\n              {formatDuration(duration)}\n            </span>\n          ) : null}\n\n          <StatusIcon status={status} />\n          <span className=\"sr-only\" role=\"status\">\n            {name} {statusText[status].toLowerCase()}\n          </span>\n\n          {hasPanel ? (\n            <button\n              type=\"button\"\n              data-slot=\"tool-call-trigger\"\n              aria-expanded={isOpen}\n              aria-controls={panelId}\n              className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring inline-flex min-h-11 items-center gap-1 rounded-full px-2 text-xs font-semibold focus-visible:ring-2 focus-visible:outline-none\"\n              onClick={() => setOpen(!isOpen)}\n            >\n              <span className=\"sr-only\">\n                {isOpen ? \"Hide\" : \"Show\"} details for {name}\n              </span>\n              <ChevronRight\n                aria-hidden=\"true\"\n                size={14}\n                className={cn(\n                  \"transition-transform duration-200 motion-reduce:transition-none\",\n                  isOpen && \"rotate-90\"\n                )}\n              />\n            </button>\n          ) : null}\n        </span>\n      </div>\n\n      {hasPanel ? (\n        <div\n          id={panelId}\n          data-slot=\"tool-call-panel\"\n          hidden={!isOpen}\n          className=\"border-border grid gap-3 border-t px-3.5 py-3\"\n        >\n          {input !== undefined ? (\n            <div data-slot=\"tool-call-input\">\n              <p className=\"text-muted-foreground text-xs font-bold tracking-[0.08em] uppercase\">\n                {inputLabel}\n              </p>\n              <pre className=\"bg-muted/60 mt-1.5 overflow-x-auto rounded-[calc(var(--radius)-0.25rem)] p-2.5 font-[family-name:var(--font-mono),monospace] text-xs leading-relaxed\">\n                {formatInput(input)}\n              </pre>\n            </div>\n          ) : null}\n\n          {output !== undefined ? (\n            <div data-slot=\"tool-call-output\">\n              <p className=\"text-muted-foreground text-xs font-bold tracking-[0.08em] uppercase\">\n                {outputLabel}\n              </p>\n              <div className=\"mt-1.5 text-sm leading-relaxed\">{output}</div>\n            </div>\n          ) : null}\n\n          {error != null ? (\n            <p data-slot=\"tool-call-error\" className=\"text-destructive text-sm\">\n              {error}\n            </p>\n          ) : null}\n        </div>\n      ) : null}\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "ai",
    "tool",
    "disclosure",
    "agent"
  ],
  "type": "registry:ui"
}