# Tool Call

> One tool invocation: name and icon, queued/running/done/failed/awaiting-approval status, live elapsed time and collapsible JSON args and result.

- **Slug:** `tool-call` · **Category:** agent · **Status:** beta
- **Import:** `import { ToolCall, ToolCallStatus } from "@kura-ui/react/tool-call";`
- **Docs:** https://kuro.abstergo.fyi/components/tool-call
- **Registry item:** https://kuro.abstergo.fyi/r/tool-call.json
- **Tags:** tool, function-calling, agent, mcp, status, json, timer, collapsible

## Install

```bash
# shadcn registry (copies source into your project)
npx shadcn@latest add https://kuro.abstergo.fyi/r/tool-call.json
# Kura CLI (same, plus dependency resolution and --json output)
npx @kura-ui/cli add tool-call
# npm package
npm install @kura-ui/react
```

## When to use

- Show a function/tool call an LLM made (AI SDK tool parts, MCP calls, OpenAI/Anthropic tool_use blocks) inside an assistant message.
- Show a tool's progress with a live timer, then its result or error.
- A tool that needs the user's go-ahead: status="awaiting-approval" with Approve/Deny buttons in `actions`.
- Compact inline mentions of tool use within answer text (variant="inline").

## When not to use

- A multi-step plan with many steps and progress — use agent-timeline (put ToolCalls inside steps if needed).
- A dedicated, prominent permission request with risk level and keyboard shortcuts — use approval-prompt.
- Plain code output — use code-block.
- Model thinking — use reasoning.

## Props

| Prop | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `name` | `string` | — | yes | Tool identifier, e.g. "search_web". Rendered in mono when no `title` is given; also used in the status announcement. |
| `title` | `React.ReactNode` | — | no | Human label, e.g. "Searching the web" → "Searched the web". Replaces `name` visually. |
| `icon` | `React.ReactNode` | — | no | Tool icon (lucide), e.g. <Globe />. Default a wrench. |
| `status` | `"pending" \| "running" \| "success" \| "error" \| "awaiting-approval"` | `"running"` | no | Lifecycle. pending = hollow ring; running = jade pulse + shimmering title + ticking timer; success = check; error = cross (details open by default); awaiting-approval = jade hand. |
| `args` | `unknown` | — | no | Arguments. Objects are pretty-printed JSON with syntax tint (CodeBlock); strings render as-is. |
| `result` | `unknown` | — | no | Output. Objects are pretty-printed JSON; strings render as-is. Long values collapse at 220px with “Show N more lines”. |
| `error` | `React.ReactNode` | — | no | Error message for status="error", shown in a danger panel in the details. |
| `summary` | `React.ReactNode` | — | no | One-line summary next to the title (truncated). Default: the first primitive value in `args`. Pass null to hide. |
| `startedAt` | `Date \| number` | — | no | Start time for the live timer (e.g. from the server). Default: when status became running. |
| `duration` | `number` | — | no | Final duration in ms (persisted calls). Default: measured from running → success/error. |
| `variant` | `"card" \| "inline"` | `"card"` | no | card = bordered card; inline = compact borderless row for use inside message text. |
| `open` | `boolean` | — | no | Details expanded (controlled). |
| `defaultOpen` | `boolean` | — | no | Initial expanded state when uncontrolled. Default true for "error" and "awaiting-approval". |
| `onOpenChange` | `(open: boolean) => void` | — | no | Called when details are toggled. |
| `actions` | `React.ReactNode` | — | no | Always-visible footer row (right-aligned), e.g. Deny / Approve buttons. |

Native attributes of the root element, `className` and `ref` are passed through.

## Anatomy

- **ToolCall** (`data-slot="tool-call"`) — `data-status`, `data-variant`, `data-state="open" | "closed"` (when it has details).
- **Trigger** (`data-slot="tool-call-trigger"`) — Header row; a button with aria-expanded when there are details.
- **Status** (`data-slot="tool-call-status"`) — 20px status glyph (aria-hidden).
- **Icon** (`data-slot="tool-call-icon"`) — Tool icon (aria-hidden).
- **Title** (`data-slot="tool-call-title"`) — Title or mono name; shimmers while running.
- **Summary** (`data-slot="tool-call-summary"`) — Truncated one-line summary.
- **Elapsed** (`data-slot="tool-call-elapsed"`) — Mono tabular duration: 180ms, 1.5s, 1m 5s.
- **Details** (`data-slot="tool-call-details"`) — Collapsible args/error/result (grid-rows transition, inert when closed).
- **Section** (`data-slot="tool-call-section"`) — “Arguments” / “Result” label + headerless CodeBlock.
- **Error** (`data-slot="tool-call-error"`) — Danger panel with the error message.
- **Actions** (`data-slot="tool-call-actions"`) — Footer for `actions`.

## Accessibility

- The header is a button (aria-expanded/aria-controls) only when there is something to expand.
- Each call has a polite status region (“search_web Running” → “search_web Completed”); the ticking timer is not announced.
- Status is conveyed by text for screen readers, not only by colour/icon.
- Collapsed details are inert; JSON panes are keyboard-scrollable regions named “Arguments code” / “Result code”.
- Reduced motion: The running pulse and title shimmer stop; the chevron and details change without travel (fade only).

| Keys | Action |
| --- | --- |
| Enter / Space | Expand or collapse the details |
| Tab | Into the JSON panes and footer actions |

## Notes for agents

- Map AI SDK tool part states: input-streaming → pending, input-available → running, output-available → success, output-error → error.
- Keep the same ToolCall mounted across states so the measured timer works; for history, pass `duration` (ms).
- Swap `title` between present and past tense (“Searching…” → “Searched the web”) as the status changes.
- args/result accept any JSON-serializable value; circular objects fall back to String().

## Examples

### Finished call (minimal)

Name, args and a string result.

```tsx
import { ToolCall } from "@kura-ui/react/tool-call";

export function ToolCallMinimal() {
  return (
    <ToolCall
      name="read_file"
      status="success"
      duration={182}
      args={{ path: "src/app/page.tsx" }}
      result="export default function Page() {\n  return <Chat />;\n}"
      className="w-full max-w-lg"
    />
  );
}
```

### Live lifecycle (full)

pending → running (shimmer + timer) → success with a JSON result; controlled open state.

```tsx
"use client";

import { Globe, RotateCcw } from "lucide-react";
import * as React from "react";
import { Button } from "@kura-ui/react/button";
import { ToolCall, type ToolCallStatus } from "@kura-ui/react/tool-call";

const RESULT = {
  results: [
    { title: "useOptimistic – React", url: "https://react.dev/reference/react/useOptimistic" },
    { title: "Server Actions and Mutations", url: "https://nextjs.org/docs/app/guides/forms" },
  ],
  took_ms: 412,
};

export function ToolCallFull() {
  const [status, setStatus] = React.useState<ToolCallStatus>("pending");
  const [open, setOpen] = React.useState(false);
  const [run, setRun] = React.useState(0);

  // biome-ignore lint/correctness/useExhaustiveDependencies: `run` replays the lifecycle
  React.useEffect(() => {
    setStatus("pending");
    const a = setTimeout(() => setStatus("running"), 700);
    const b = setTimeout(() => setStatus("success"), 3100);
    return () => {
      clearTimeout(a);
      clearTimeout(b);
    };
  }, [run]);

  return (
    <div className="flex w-full max-w-lg flex-col items-start gap-3">
      <ToolCall
        name="search_web"
        title={status === "success" ? "Searched the web" : "Searching the web"}
        icon={<Globe />}
        status={status}
        args={{ query: "react useOptimistic rollback", max_results: 2 }}
        result={status === "success" ? RESULT : undefined}
        summary="react useOptimistic rollback"
        variant="card"
        open={open}
        onOpenChange={setOpen}
        className="w-full"
      />
      <Button variant="secondary" size="sm" onClick={() => setRun((r) => r + 1)}>
        <RotateCcw /> Replay
      </Button>
    </div>
  );
}
```

### All states (state)

Queued, running, done and failed.

```tsx
import { FileText, Terminal } from "lucide-react";
import { ToolCall } from "@kura-ui/react/tool-call";

export function ToolCallStates() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-2">
      <ToolCall name="list_files" status="pending" args={{ dir: "src/components" }} />
      <ToolCall
        name="run_tests"
        icon={<Terminal />}
        status="running"
        args={{ filter: "message" }}
      />
      <ToolCall
        name="read_file"
        icon={<FileText />}
        status="success"
        duration={96}
        args={{ path: "README.md" }}
        result="# Kura UI"
      />
      <ToolCall
        name="deploy_preview"
        status="error"
        duration={4210}
        args={{ branch: "feat/message" }}
        error="Error: build failed — Type error in message.tsx:295"
        defaultOpen={false}
      />
    </div>
  );
}
```

### Inline (variant)

A compact row inside answer text.

```tsx
import { Globe } from "lucide-react";
import { ToolCall } from "@kura-ui/react/tool-call";

export function ToolCallInline() {
  return (
    <div className="flex w-full max-w-lg flex-col gap-2 text-[15px] text-kura-fg leading-relaxed">
      <p>I checked the docs before answering:</p>
      <ToolCall
        variant="inline"
        name="fetch_url"
        title="Read react.dev"
        icon={<Globe />}
        status="success"
        duration={640}
        args={{ url: "https://react.dev/reference/react/useOptimistic" }}
        result={{ status: 200, bytes: 48213 }}
      />
      <p>The hook drops the optimistic value automatically when the action throws.</p>
    </div>
  );
}
```

### Awaiting approval (composition)

Approve/Deny Buttons in the actions footer resolve the call.

```tsx
"use client";

import { Terminal } from "lucide-react";
import * as React from "react";
import { Button } from "@kura-ui/react/button";
import { ToolCall, type ToolCallStatus } from "@kura-ui/react/tool-call";

export function ToolCallApproval() {
  const [status, setStatus] = React.useState<ToolCallStatus>("awaiting-approval");
  return (
    <ToolCall
      name="run_command"
      title="Run a shell command"
      icon={<Terminal />}
      status={status}
      args={{ command: "pnpm db:migrate --env production" }}
      result={status === "success" ? "Applied 3 migrations." : undefined}
      duration={status === "success" ? 1830 : undefined}
      className="w-full max-w-lg"
      actions={
        status === "awaiting-approval" ? (
          <>
            <Button variant="ghost" size="sm" onClick={() => setStatus("error")}>
              Deny
            </Button>
            <Button size="sm" onClick={() => setStatus("success")}>
              Approve
            </Button>
          </>
        ) : undefined
      }
      error={status === "error" ? "Denied by user." : undefined}
    />
  );
}
```

## Dependencies

- npm: `lucide-react`
- Kura registry items: `kura-theme`, `code-block`, `shimmer-text`, `use-controllable-state`, `utils`
- Related: `agent-timeline`, `approval-prompt`, `code-block`, `reasoning`, `message`
