Tool call
Shows one tool call an agent made as a step of a checklist that opens onto what came back.
Examples
Refund $48.00?
Preparing replyPreparing
Running evalrefund-policyRunning
Looked upord_4812OK840ms
The orders service timed out after 3s.
Refund $48.00?
Search policyrefund windowStopped
Running evalrefund-policyRunning
PASS case 01 refund-policy (12ms)
PASS case 02 refund-policy (15ms)
PASS case 03 refund-policy (18ms)
PASS case 04 refund-policy (21ms)
PASS case 05 refund-policy (24ms)
PASS case 06 refund-policy (27ms)
PASS case 07 refund-policy (30ms)
PASS case 08 refund-policy (33ms)Sizes
defaultdefault
sm
Tool
eval
- Looked upord_4812OK90ms
lookup
- Fetchedhelp.example.com/policies/refundsOK640ms
web fetch
- ticketId
- tkt_4812
- priority
- high
any other tool
States
rest
hover
focus-visible
pressed
Hi Maya, sorry the headphones arrived damaged. I have refunded $48.00 to your original payment method.open
Usage
When to use it
- Use
- An agent calls a tool and the person should see what it asked for and what came back.
- Use
- A turn lists the calls of a step, in order, with how each ended.
- Use
- A call needs the person: it is paused for permission or was refused.
- Not for
- The output is a long shell session the person may type into. Use Code block.
Rules
11 rules
- Must
- Give every event a per-call seq and dispatch all of them through toolCallsReducer. Because a plain setState applies a duplicate or late running event over completed, so a finished call spins again.
- Must
- Send status: 'completed' or 'error' explicitly when a tool ends. Because the reducer reads a result with no status as an error, so a call that worked shows Failed.
- Must
- Dispatch {type: 'stop', gen} before the network cancel returns, and open the next turn with a newer gen. Because events that arrive while the cancel is awaited keep rows running, and reusing the stopped gen drops the next turn.
- Must
- Hide finished calls with ToolCallList hideCompleted, not a filter of your own. Because a hand filter for running drops failed and awaiting-permission rows, so the run stalls on an approval no one sees.
- Should
- Call registerTool(name, renderer, aliases) once at module scope for a product tool; never pass label per call site.
- Should
- Write active as an -ing verb, done in past tense and ask in the imperative, one or two words, with the object in detail.
- Must
- Use status: 'denied' with the person's feedback as reason for a refused call; never status: 'error'. Because a refusal is the person's choice; as an error it opens on a failure message and counts as a failed call in the turn's line.
- Must
- Keep the state in the glyph cell and the end of the line to the duration; add a word only for Failed, Stopped and Denied. Because a status word on every row is a column of noise, and the word is what survives grayscale and forced colors when the red is gone.
- Must
- Pass onReply to a waiting call, and put what the call does in permission.message. Because the ask card shows the detail in full under the question and the cost, and the person decides on those; a call with no message asks the question with nothing to weigh, and with no onReply the card has no way to answer.
- Never
- Stack ToolCalls in a div. Put them in a ToolCallList. Because the list folds finished calls into one line and keeps the newest visible; a hand-made stack shows every row of a long turn.
- Never
- Dump the input JSON as the body of a tool call. Because the registry renders the command, patch or capped output, and raw JSON shows escaped quotes with no cap.
Accessibility
7 checks
- A row that opens is a button with aria-expanded; its name is label, detail, the status word and the time.
- Every status has a word, read aloud when it is not shown: Done, Running, Failed, Denied, Stopped. The glyph is aria-hidden. The root sets aria-busy while pending or running.
- Enter or Space opens the body; Tab moves into the body controls; plain rows are not tab stops.
- The arc is aria-hidden beside its read-aloud word; a failure message is the first thing in the opened body.
- Deny comes before Allow in the tab order, so the first stop in the ask card never allows by accident.
- A result before its call shows after 2s as "Orphaned result", never Done.
- The ask card is a group named by its question. When it goes, the row takes focus, so a keyboard user is not dropped to the top of the page.
Edge cases
11 edge cases
- 120-character path
- Label and detail truncate to one line; the body wraps anywhere.
- No input or output
- One plain row, not a button, with no hover wash and no chevron. The glyph cell stays, so its verb lines up with the rows that open.
- Narrow screen
- The target truncates first, down to 6 characters; on a phone it wraps to a second line instead. The end of the line never wraps.
- Command in an approval
- Shown in full and wrapped in a code block, never truncated.
- Right to left
- Spacing and caret mirror; code stays left to right.
- Duplicate, out-of-order or replayed events
- Dropped and counted; a late stop echo leaves the next turn running.
- Finished turn
- ToolCallList folds every call into "Worked for 14s" (or "Ran 5 tools"), with a red Failed at its end when a call failed.
- Live turn
- Completed calls before the newest fold into one line like "Read 2 files, searched once"; the newest stays a row.
- Output longer than the cap
- Shell shows 24 lines, other tools 8, and 20,000 characters in all, with the cut said.
- Forced colors
- The words carry every state; the arc keeps its edge.
- Reduced motion
- The arc holds still; the sr-only word still says running.
API
21 props
callToolCallDatadefaultrequiredcall.tool and the input it readsbuilt-in rendererscall.startedAt, call.endedAtnumbercall.permission{patterns: string[]; message?: string}size'default' | 'sm'default'default'onReply(reply: 'once' | 'reject') => voidactionsReactNodedetailMonobooleandefaultthe renderer'slabelstringdefaultfrom the registrydoneLabelstringdefaultpast tense of labeldetailstringdefaultfrom the registrydurationnumberchildrenReactNodedefaultOpenbooleandefaulttrue when the call failedToolCallList callsreadonly ToolCallData[]defaultrequiredToolCallList livebooleandefaultany call not endedToolCallList hideCompletedbooleandefaultfalseToolCallList orphanTtlnumberdefault2000ToolCallList onReply(call, reply) => voiduseToolCalls(initial)[{calls, gen, dropped}, dispatch]defaultinitial: []labelsToolCallLabelsdefaultEnglish words
Tokens
10 tokens
--line-frame- oklch(var(--ink-c) / var(--line-frame-alpha))
--surface-hover- oklch(var(--ink-c) / 0.05)
--surface-card- oklch(var(--grey-c))
--fail-text- oklch(51% 0.180 26)
--text-2- oklch(var(--ink-c) / 0.80)
--type-tag- var(--type-code-sm)
--type-body-sm- 380 13px/18px var(--font-sans)
--tool-detail-min- 6ch
--code-prompt- oklch(var(--ink-c) / 0.40)
--ease-step- cubic-bezier(0.2, 0, 0, 1)