Mischief

23 / Agent UI

Tool Call

A compact record of one tool invocation: name, status, duration, and the input and output behind a disclosure.

search_filessearch_files queued

Input

{
  "pattern": "nullable email",
  "path": "migrations/",
  "limit": 5
}

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/tool-call
import { ToolCall } from "mischief-ui/tool-call"
Also installs
  • lucide-react

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/tool-call/tool-call.tsx
"use client" import * as React from "react"import {  Check,  ChevronRight,  Loader,  Circle,  TriangleAlert,  Wrench,} from "lucide-react"import { cn } from "@/lib/utils" export type ToolCallStatus = "pending" | "running" | "success" | "error"

Usage

export function Search() {
  return (
    <ToolCall
      name="web_search"
      status="success"
      input={{ query: "agent ui", limit: 5 }}
      output={<p>Three matches.</p>}
      durationMs={340}
    />
  )
}

The four states

A call moves through as many of these as it needs. Each one changes what is shown and is announced politely, naming the tool, so a reader who is not watching still learns what happened.

StatusShows
pendingQueued. The input, and nothing that has happened yet.
runningIn flight, with a live duration if startedAt is set.
successThe output, and the final duration.
errorThe failure message in place of the output.

Pass startedAt while running and the duration counts up on its own; pass durationMs once it settles and that fixed figure is shown instead. Setting neither is fine -- the call simply reports no timing.

Input and output

Input is rendered for you: an object is formatted as JSON, a string is shown as it is. Output is not, because only you know whether the result is a table, a paragraph, or three files. Render it and pass it in.

<ToolCall
  name="search_files"
  status="success"
  input={{ pattern: "nullable email", path: "migrations/" }}
  output={<FileTree nodes={matches} />}
  durationMs={340}
/>

There is no syntax highlighting on the input, and no dependency that would provide it. Keep what you pass small enough to read: the arguments that decide what the call did, not everything that was in scope.

API

namestringThe tool name shown in the header.
status"pending" | "running" | "success" | "error"The current phase. Defaults to "pending".
inputunknownRendered as formatted JSON, or as-is when it is a string.
outputReactNodeWhatever the tool returned, rendered by you.
errorstringA failure message shown inside the panel.
startedAtnumberEpoch milliseconds. Drives a live duration while running.
durationMsnumberThe final duration once the call settles.
iconReactNodeReplaces the default tool icon.
open, defaultOpen, onOpenChangeboolean, boolean, (open: boolean) => voidControls the detail disclosure.

Accessibility

Status changes are announced through a polite status region naming the tool. The disclosure is a native button with aria-expanded and aria-controls, and its accessible name says which tool it belongs to. Input is rendered as plain preformatted text in a horizontally scrollable region, with no syntax highlighting and no extra dependency.