{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tool",
  "title": "Tool",
  "author": "Jamie Thompson <jamie@jamiethompson.design> (https://jamiethompson.design)",
  "description": "Collapsible tool-call card for AI chat: header with wrench icon, tool name derived from the part type (or toolName for dynamic tools), and a status badge for every AI SDK tool state (input-streaming, input-available, approval-requested, approval-responded, output-available, output-denied, output-error); expandable content showing Parameters (input as JSON) and Result or Error (JSON and strings in Code Blocks, React nodes and primitives rendered as given). Use it to render ToolUIPart and DynamicToolUIPart parts from useChat messages.",
  "dependencies": [
    "ai@^7",
    "cn",
    "lucide-react"
  ],
  "registryDependencies": [
    "badge",
    "collapsible",
    "@uifiles/code-block"
  ],
  "files": [
    {
      "path": "registry/ai/tool.tsx",
      "content": "// Derived from Vercel AI Elements tool.tsx (Apache-2.0, Copyright 2023 Vercel, Inc.).\n// Modified for uifiles: ported from Radix UI to Base UI; dependencies point at @uifiles.\n\"use client\"\n\nimport type { DynamicToolUIPart, ToolUIPart } from \"ai\"\nimport { cn } from \"cn\"\nimport {\n  CheckCircleIcon,\n  ChevronDownIcon,\n  CircleIcon,\n  ClockIcon,\n  WrenchIcon,\n  XCircleIcon,\n} from \"lucide-react\"\nimport type { ComponentProps, ReactNode } from \"react\"\nimport { isValidElement } from \"react\"\nimport { Badge } from \"@/components/ui/badge\"\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\"\n\nimport { CodeBlock } from \"./code-block\"\n\nexport type ToolProps = ComponentProps<typeof Collapsible>\n\nexport const Tool = ({ className, ...props }: ToolProps) => (\n  <Collapsible\n    className={cn(\"group not-prose mb-4 w-full rounded-md border\", className)}\n    {...props}\n  />\n)\n\nexport type ToolPart = ToolUIPart | DynamicToolUIPart\n\nexport type ToolHeaderProps = {\n  title?: string | undefined\n  className?: string | undefined\n} & (\n  | { type: ToolUIPart[\"type\"]; state: ToolUIPart[\"state\"]; toolName?: never }\n  | {\n      type: DynamicToolUIPart[\"type\"]\n      state: DynamicToolUIPart[\"state\"]\n      toolName: string\n    }\n)\n\nconst statusLabels: Record<ToolPart[\"state\"], string> = {\n  \"approval-requested\": \"Awaiting Approval\",\n  \"approval-responded\": \"Responded\",\n  \"input-available\": \"Running\",\n  \"input-streaming\": \"Pending\",\n  \"output-available\": \"Completed\",\n  \"output-denied\": \"Denied\",\n  \"output-error\": \"Error\",\n}\n\nconst statusIcons: Record<ToolPart[\"state\"], ReactNode> = {\n  \"approval-requested\": <ClockIcon className=\"size-4 text-muted-foreground\" />,\n  \"approval-responded\": <CheckCircleIcon className=\"size-4 text-primary\" />,\n  \"input-available\": <ClockIcon className=\"size-4 animate-pulse\" />,\n  \"input-streaming\": <CircleIcon className=\"size-4\" />,\n  \"output-available\": <CheckCircleIcon className=\"size-4 text-primary\" />,\n  \"output-denied\": <XCircleIcon className=\"size-4 text-destructive\" />,\n  \"output-error\": <XCircleIcon className=\"size-4 text-destructive\" />,\n}\n\n// A newer `ai` can emit a state this map has not learned yet; show the raw\n// state with a neutral icon rather than an empty badge.\nexport const getStatusBadge = (status: ToolPart[\"state\"]) => (\n  <Badge className=\"gap-1.5 rounded-full text-xs\" variant=\"secondary\">\n    {statusIcons[status] ?? <CircleIcon className=\"size-4\" />}\n    {statusLabels[status] ?? status}\n  </Badge>\n)\n\nexport const ToolHeader = ({\n  className,\n  title,\n  type,\n  state,\n  toolName,\n  ...props\n}: ToolHeaderProps) => {\n  const derivedName =\n    type === \"dynamic-tool\" ? toolName : type.split(\"-\").slice(1).join(\"-\")\n\n  return (\n    <CollapsibleTrigger\n      className={cn(\n        \"flex w-full items-center justify-between gap-4 p-3\",\n        className\n      )}\n      {...props}\n    >\n      <div className=\"flex items-center gap-2\">\n        <WrenchIcon className=\"size-4 text-muted-foreground\" />\n        <span className=\"text-sm font-medium\">{title ?? derivedName}</span>\n        {getStatusBadge(state)}\n      </div>\n      <ChevronDownIcon className=\"size-4 text-muted-foreground transition-transform group-data-open:rotate-180\" />\n    </CollapsibleTrigger>\n  )\n}\n\nexport type ToolContentProps = ComponentProps<typeof CollapsibleContent>\n\nexport const ToolContent = ({ className, ...props }: ToolContentProps) => (\n  <CollapsibleContent\n    className={cn(\n      \"space-y-4 p-4 text-popover-foreground outline-none data-open:animate-in data-open:slide-in-from-top-2 data-closed:animate-out data-closed:fade-out-0 data-closed:slide-out-to-top-2\",\n      className\n    )}\n    {...props}\n  />\n)\n\n// Tool inputs and outputs are JSON on the wire, but a consumer can hand us\n// anything; BigInt is stringified and whatever else JSON rejects (circular\n// references) falls back to String() so the card never throws mid-stream.\nconst toJson = (value: unknown): string => {\n  try {\n    return (\n      JSON.stringify(\n        value,\n        (_key, item: unknown) =>\n          typeof item === \"bigint\" ? item.toString() : item,\n        2\n      ) ?? String(value)\n    )\n  } catch {\n    return String(value)\n  }\n}\n\nexport type ToolInputProps = ComponentProps<\"div\"> & {\n  input: ToolPart[\"input\"]\n}\n\nexport const ToolInput = ({ className, input, ...props }: ToolInputProps) => (\n  <div className={cn(\"space-y-2 overflow-hidden\", className)} {...props}>\n    <div className=\"text-xs font-medium tracking-wide text-muted-foreground uppercase\">\n      Parameters\n    </div>\n    <div className=\"rounded-md bg-muted/50\">\n      {input === undefined ? (\n        <p className=\"px-4 py-3 text-xs text-muted-foreground\">No input yet</p>\n      ) : (\n        <CodeBlock code={toJson(input)} language=\"json\" />\n      )}\n    </div>\n  </div>\n)\n\nexport type ToolOutputProps = ComponentProps<\"div\"> & {\n  output: ToolPart[\"output\"]\n  errorText: ToolPart[\"errorText\"]\n}\n\nconst renderOutput = (output: ToolPart[\"output\"]): ReactNode => {\n  if (output === undefined) {\n    return null\n  }\n  if (typeof output === \"string\") {\n    return <CodeBlock code={output} language=\"json\" />\n  }\n  if (typeof output === \"object\") {\n    return isValidElement(output) ? (\n      <div>{output}</div>\n    ) : (\n      <CodeBlock code={toJson(output)} language=\"json\" />\n    )\n  }\n  return <div>{String(output)}</div>\n}\n\nexport const ToolOutput = ({\n  className,\n  output,\n  errorText,\n  ...props\n}: ToolOutputProps) => {\n  if (output === undefined && !errorText) {\n    return null\n  }\n\n  return (\n    <div className={cn(\"space-y-2\", className)} {...props}>\n      <div className=\"text-xs font-medium tracking-wide text-muted-foreground uppercase\">\n        {errorText ? \"Error\" : \"Result\"}\n      </div>\n      <div\n        className={cn(\n          \"overflow-x-auto rounded-md text-xs [&_table]:w-full\",\n          errorText ? \"bg-card text-destructive\" : \"bg-muted/50 text-foreground\"\n        )}\n      >\n        {errorText && <div>{errorText}</div>}\n        {renderOutput(output)}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ai/tool.tsx"
    }
  ],
  "docs": "Derived from Vercel AI Elements (Apache-2.0, Copyright 2023 Vercel, Inc.); ported to Base UI and maintained by uifiles. Built for the Base UI styles (base-nova): it composes the Base UI wrappers from components/ui and relies on their render prop and event signatures, so it does not work on Radix or React Aria styles. Ported to Base UI. Tool is Base UI Collapsible.Root: onOpenChange is (open, eventDetails); ToolContent is Collapsible.Panel (forceMount → keepMounted). Open-state styling uses data-open/data-closed and group-data-open (root carries data-open) instead of data-[state=open]. Status icon colors use semantic tokens (text-primary for completed/responded, text-destructive for error/denied, text-muted-foreground for awaiting approval) because the theme has no success/warning tokens; the error result container is bg-card text-destructive (upstream bg-destructive/10) to meet WCAG AA contrast with the light-mode destructive token. The \"Parameters\" and \"Result\"/\"Error\" labels are <div>s (upstream <h4>) so a tool card never breaks the page's heading order. ToolInput renders a muted \"No input yet\" placeholder while input is undefined (the shape the AI SDK emits on tool-input-start) instead of crashing, and stringifies BigInt values; anything else JSON cannot serialize falls back to String(). ToolOutput shows falsy results (0, false, \"\") and renders booleans and numbers as text. An unknown tool state renders its raw state text with a neutral icon.",
  "categories": [
    "ai",
    "chat",
    "agent"
  ],
  "type": "registry:component"
}