Tool Calls
Visualize AI tool calls in your chat UI.
New in 0.4.0
A tool call that the turn paused on (an open approval or question) shows a clock instead of the tick of a finished call, also after a reload. Approving or answering is covered in Approvals and Questions.
Overview
When an agent uses tools, Chat UI shows what it is doing, step by step:
- the latest step prominently, with a spinner while the tool runs, a tick when it finished and a clock when it waits for you;
- the earlier steps of the turn folded behind "N previous steps" (with the turn's duration), which expands to a compact list;
- the label of a step is the model's
reasoningif the server sends one, else the tool'sdisplayName, else the tool's name prettified (get_weatherbecomes "Get Weather").
Arguments and results are not drawn in the transcript; they reach your code through the callbacks below, and approval cards show the arguments of a call that needs a decision (under "Details").
Default Tool Call Display
Tool calls are displayed automatically, live while the turn runs and from the stored message afterwards:
<Chat
baseUrl="/api/agents"
agentId="assistant"
/>A finished message carries its calls in metadata.toolCallDetails ({ id, name, args, result?, reasoning?, displayName? }[]) and, optionally, metadata.toolCallDurationSeconds:
<ChatMessage
message={{
...message,
metadata: {
toolCallDetails: [{ id: "call_1", name: "get_weather", args: { location: "New York" }, result: { temperature: 72 } }],
toolCallDurationSeconds: 2,
},
}}
/>Tool Call Callbacks
Track tool calls programmatically:
<Chat
baseUrl="/api/agents"
agentId="assistant"
onToolCall={(toolName, args) => {
console.log("Tool called:", toolName);
console.log("Arguments:", args);
// Analytics, logging, etc.
analytics.track("tool_call", { tool: toolName });
}}
onToolResult={(toolName, result) => {
console.log("Tool result:", toolName);
console.log("Result:", result);
}}
/>ToolCall Component
Use the built-in components directly:
import { ToolCall, ToolCalls } from "@cognipeer/chat-ui";
// A single call
<ToolCall
toolName="get_weather"
toolCallId="call_123"
args={{ location: "NYC" }}
result={{ temperature: 72 }}
/>
// Several calls: the last is the latest step, the rest fold
<ToolCalls
toolCalls={[
{ id: "1", name: "search", args: { query: "..." }, result: { results: [] } },
{ id: "2", name: "calculate", args: { expression: "..." } },
]}
isExecuting={false}
durationSeconds={3}
/>See ToolCall for every prop.
Tools that wait for you
When a call is paused on an approval or a question, its row shows a clock labelled "Waiting for your answer". The row is marked as long as the card is open; once the card has an outcome (answered, cancelled, expired) it goes back to a normal step. A conversation opened again after a reload keeps the cards but not the calls, so a row is made for each open card (callsWithOpenActions) and marked the same way. In your own layout: ToolCalls takes waitingIds (waitingToolCallIds(message, calls) computes it).
Active Tool Calls
Track in-progress tool calls in a custom layout:
import { useChat } from "@cognipeer/chat-ui";
function CustomChat() {
const { activeToolCalls } = useChat({
baseUrl: "/api/agents",
agentId: "assistant",
});
// activeToolCalls is a Map<string, { name, args, result?, reasoning?, displayName? }>
return (
<div>
{activeToolCalls.size > 0 && (
<div className="active-tools">
Running: {Array.from(activeToolCalls.values()).map((t) => t.name).join(", ")}
</div>
)}
</div>
);
}ChatMessageList draws activeToolCalls for you while the turn runs and no text has streamed yet; once text arrives the steps move to the message when it is stored.
Styling Tool Calls
Tool rows use the theme variables (--chat-accent-primary for the spinner and tick, --chat-warning for the waiting clock, --chat-text-secondary / --chat-text-tertiary for labels), so changing colours through themeColors restyles them. See Theming.

