Skip to main content
WireKit
Copy for LLM

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 for the name, badge and shimmer for the state, code-block for the arguments and collapsible for the result. Nothing here is a new visual language.

Basic Usage

A finished tool call
search_docs Took 2 s
{
    "query": "rate limits",
    "limit": 4
}

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.

Every state a call can be in
fetch_invoice Queued
get_weather Running…
search_docs Took 2 s
charge_card Failed after 9 s
Card declined — insufficient funds.

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 when the order carries meaning, or in a stack when it does not.

Three calls in the order they happened
  1. Status: Success 0.0s
    find_customer Took 1 s
    {
        "email": "[email protected]"
    }
  2. Status: Success 1.2s
    list_invoices Took 3 s
    {
        "customer": 4711,
        "unpaid": true
    }
  3. 4.4s
    send_reminder Running…
    {
        "invoice": 8814
    }

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

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:

{{-- 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 and grid already emit a track that can shrink. Hand-written CSS needs to say so:

/* 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 and inherits its keyboard model and its aria-expanded wiring unchanged.

Keyboard Interaction

Key Action
Enter / Space Opens or closes the result, when the trigger has focus.

See Also

Updated in WireKit v2.51.0 (2026-09-14)

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store