---
title: Tool Call
description: One tool invocation an assistant made — name, status, arguments as JSON, and the result behind a disclosure
visibility: guest
draft: false
---

# Tool Call

An assistant that calls tools does most of its work where the reader cannot see it. `tool-call`
is the window into one of those calls: what was called, where it stands, what it was given, and
what came back.

It composes primitives you already have — [`code`](/components/code) for the name,
[`badge`](/components/badge) and [`shimmer`](/components/shimmer) for the state,
[`code-block`](/components/code-block) for the arguments and
[`collapsible`](/components/collapsible) for the result. Nothing here is a new visual language.

## Basic Usage

:::preview{title="A finished tool call"}
<x-wirekit::tool-call name="search_docs" status="done" :seconds="2" :arguments="['query' => 'rate limits', 'limit' => 4]">
    Four passages matched. The closest is <em>Rate limits</em>, which gives sixty requests a minute per token.
</x-wirekit::tool-call>
:::

:::source{language="blade"}
<x-wirekit::tool-call
    name="search_docs"
    status="done"
    :seconds="2"
    :arguments="['query' => 'rate limits', 'limit' => 4]"
>
    Four passages matched. The closest is <em>Rate limits</em>, which gives sixty requests a minute per token.
</x-wirekit::tool-call>
:::

## The Four States

A call is queued, in flight, finished or failed. The state is in the **words** as well as the
color, because a reader who cannot tell the intents apart still has to be able to tell a finished
call from a failed one.

:::preview{title="Every state a call can be in"}
<x-wirekit::stack gap="sm">
    <x-wirekit::tool-call name="fetch_invoice" status="pending" />
    <x-wirekit::tool-call name="get_weather" status="running" />
    <x-wirekit::tool-call name="search_docs" status="done" :seconds="2" />
    <x-wirekit::tool-call name="charge_card" status="failed" :seconds="9">Card declined — insufficient funds.</x-wirekit::tool-call>
</x-wirekit::stack>
:::

:::source{language="blade"}
<x-wirekit::tool-call name="fetch_invoice" status="pending" />
<x-wirekit::tool-call name="get_weather" status="running" />
<x-wirekit::tool-call name="search_docs" status="done" :seconds="2" />
<x-wirekit::tool-call name="charge_card" status="failed" :seconds="9">
    Card declined — insufficient funds.
</x-wirekit::tool-call>
:::

Two behaviors follow from the state on their own:

- A **running** call marks its block `aria-busy` and shimmers its label, so assistive technology
  knows the region is still changing.
- A **failed** call opens its disclosure. An error nobody sees is not reported; a result is
  reference material and stays closed.

## Duration

Pass `seconds` and the state word gives way to how long the call took. The **application** owns
that number — it knows when the call started, and a stopwatch in the browser would disagree with
it the moment the page re-renders from the server.

A value that is not a number is no label at all rather than a cast: `seconds="soon"` would
otherwise read as "Took 0 s", which states something you never said.

## Arguments

`arguments` takes an array and encodes it, or a string and prints it as given — so an application
that already holds the provider's raw JSON does not pay a decode and re-encode round trip to
display it.

The encoding is the readable one: slashes are left alone, so a URL argument does not arrive as
`https:\/\/example.test`.

## Inside a Conversation

A sequence of calls reads as a sequence. Put them in a [`timeline`](/components/timeline) when the
order carries meaning, or in a [`stack`](/components/stack) when it does not.

:::preview{title="Three calls in the order they happened"}
<x-wirekit::timeline variant="compact">
    <x-wirekit::timeline.item time="0.0s" intent="success">
        <x-wirekit::tool-call name="find_customer" status="done" :seconds="1" :arguments="['email' => 'ada@example.test']" />
    </x-wirekit::timeline.item>
    <x-wirekit::timeline.item time="1.2s" intent="success">
        <x-wirekit::tool-call name="list_invoices" status="done" :seconds="3" :arguments="['customer' => 4711, 'unpaid' => true]" />
    </x-wirekit::timeline.item>
    <x-wirekit::timeline.item time="4.4s">
        <x-wirekit::tool-call name="send_reminder" status="running" :arguments="['invoice' => 8814]" />
    </x-wirekit::timeline.item>
</x-wirekit::timeline>
:::

:::source{language="blade"}
<x-wirekit::timeline variant="compact">
    <x-wirekit::timeline.item time="0.0s" intent="success">
        <x-wirekit::tool-call name="find_customer" status="done" :seconds="1" :arguments="['email' => 'ada@example.test']" />
    </x-wirekit::timeline.item>
    <x-wirekit::timeline.item time="1.2s" intent="success">
        <x-wirekit::tool-call name="list_invoices" status="done" :seconds="3" :arguments="['customer' => 4711, 'unpaid' => true]" />
    </x-wirekit::timeline.item>
    <x-wirekit::timeline.item time="4.4s">
        <x-wirekit::tool-call name="send_reminder" status="running" :arguments="['invoice' => 8814]" />
    </x-wirekit::timeline.item>
</x-wirekit::timeline>
:::

## Streaming It From Livewire

The component is a render of server state, so a Livewire property drives it directly. Bind the
status and the result; the disclosure is keyed on the status, so a call that settles while the
reader has it open comes back in the state the new status implies rather than keeping the old one.

Keep the calls in a public property of the component:

```php
<?php

namespace App\Livewire;

use Livewire\Component;

class AssistantTurn extends Component
{
    /**
     * One entry per tool call the turn made, in the order they happened. Update an entry
     * while the call runs: its status moves from pending to running, then to done or failed.
     *
     * @var list<array{id: string, name: string, status: string, seconds?: int, arguments: array<string, mixed>|string, result?: string}>
     */
    public array $toolCalls = [];
}
```

Then render one block per call:

```blade
{{-- 1. One block per call the turn made, in the order they happened. --}}
@foreach($this->toolCalls as $call)
    {{-- 2. `wire:key` on the loop, so a re-render moves blocks instead of rebuilding them. --}}
    <x-wirekit::tool-call
        wire:key="tool-{{ $call['id'] }}"
        :name="$call['name']"
        :status="$call['status']"
        :seconds="$call['seconds'] ?? null"
        :arguments="$call['arguments']"
    >
        {{-- 3. The result is whatever you want a reader to see — text, a table, a link. --}}
        {{ $call['result'] ?? '' }}
    </x-wirekit::tool-call>
@endforeach
```

## Give It a Column That Can Shrink

A tool call carries an arguments block, and the longest line in one is usually a URL that does
not wrap. The component takes `min-w-0` so it shrinks inside a flex or grid parent instead of
insisting on its content width — but that caps the **item**, never the **track**.

A grid column written as the implicit `auto` track sizes itself to its widest child, and no
property on the child can change that. Measured at 390 px: four calls in a bare `display: grid`
came out 404 px wide, so the whole page scrolled sideways and the reader had to drag the column
to read one argument.

[`stack`](/components/stack) and [`grid`](/components/grid) already emit a track that can shrink.
Hand-written CSS needs to say so:

```css
/* 1. `minmax(0, 1fr)`, not `1fr` — a bare `1fr` still has an `auto` minimum. */
.tool-calls { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.5rem; }
```

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `name` | `string` | `null` | What was called. Shown as code and used in the arguments block's accessible name. |
| `status` | `string` | `pending` | `pending`, `running`, `done` or `failed`. Anything else is refused. |
| `arguments` | `array\|string` | `null` | The arguments the model passed. An array is encoded as readable JSON; a string is printed as given. |
| `seconds` | `int` | `null` | How long the call took. Replaces the state word once it has settled. A non-numeric value is ignored. |
| `open` | `bool` | `null` | Whether the result disclosure opens. `null` follows the status: a failure opens, everything else stays closed. |
| `scope` | `string` | `null` | Personalization scope, as on every component. |

## Accessibility

- A **running** call carries `aria-busy="true"` on its block, so assistive technology knows the
  region is still changing. It is removed the moment the call settles.
- The state is in the badge's **text**, never in its color alone (WCAG 1.4.1).
- The arguments block is a named scroll region — the name carries the tool, so ten calls on one
  page are ten distinguishable entries in a screen reader's rotor rather than ten called
  "JSON code".
- The result disclosure is [`collapsible`](/components/collapsible) and inherits its keyboard
  model and its `aria-expanded` wiring unchanged.

## Keyboard Interaction

| Key | Action |
|---|---|
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Opens or closes the result, when the trigger has focus. |

## See Also

- [Assistant Message](/components/assistant-message) — the turn a tool call belongs to.
- [Timeline](/components/timeline) — for a sequence of calls where the order carries meaning.
- [Code Block](/components/code-block) — the arguments renderer.
