Kanzo UI
AI

Tool

One call the model made — its name, its state, what went in and what came back. The payload is children, not a JSON prop, and that is the reason to use this one.

Parameters
Result
ContractRegionDueDays late
The quarry has opened onto somethingAshfall Reach1312-09-086
A basilisk, and it knows the routeAshfall Reach1312-09-086
Something is relighting the lampsDuskfen1312-09-122
Parameters
Result
A duty is filled from the posting hall first, and 0 of Amber Hall's cantors are ready. Free elsewhere: Kesi Adeyemi, Perpetua Lund.

Usage

import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from "@kanzo-tech/ai";
<Tool state="done">
  <ToolHeader>board.query</ToolHeader>
  <ToolContent>
    <ToolInput>
      <pre>{sql}</pre>
    </ToolInput>
    <ToolOutput>
      <Table>{/* the rows that came back */}</Table>
    </ToolOutput>
  </ToolContent>
</Tool>

The payload is children

This is the deliberate divergence from the source these shapes came from, and it is the reason to reach for ours.

AI Elements takes input and output as props and renders them as JSON. That is right for a general chatbot: it cannot know what the tool was, so a formatted blob is the only honest rendering of an unknown value.

We always know. Our input is a SQL statement and our output is a result table, and both already have a component in this house — Table, the CodeEditor, a DataTableRoot, a ChartRoot. Rendering a SQL statement as a JSON blob discards it: the reader loses the syntax, the column alignment, the sort, and every affordance the real component has.

So the payload is composition. ToolInput and ToolOutput take children, and a JSON rendering is what happens when the caller has nothing better:

{/* composed — a real table, a real editor, a real chart */}
<ToolOutput>
  <Table>…</Table>
</ToolOutput>

{/* the fallback — a JsonTreeView, only when there is nothing better */}
<ToolInput data={{ contract: "Q-1058", duty: "cantor" }} />

data is drawn with JsonTreeView only when there are no children. Children always win, so a call that starts life as a JSON dump can grow a real rendering one line at a time without changing its shape.

The trade is honest and worth stating: the props version is shorter for a chatbot that genuinely does not know its tools. If that is your case, pass data and you have written the same thing.

The four states

state is pending, running, done or failed — the same four Task uses, from the same type, because a tool call is a step with a name, and giving the two their own four-word vocabularies is how they end up disagreeing.

The header renders each as a Badge with a matching tone and icon, and a running call gets a Spinner instead of a glyph.

A finished call opens by default; the other three do not. A call still running has nothing to show, and a reader who opens a finished one gets the result under their eyes without a second click. defaultOpen overrides it in either direction.

The state sits on a wrapper, not on the collapsible, and that is not tidiness. Ark writes its own data-stateopen / closed — on a machine's root, so a tool state written there replaces it. The parts keep working and every [data-state=open] a consumer wrote silently stops matching, with nothing to see in a diff. Ours is a wrapping div carrying data-state="done", with Ark's own root untouched inside it.

That wrapper is also what ToolOutput selects on: a failed call tints its output through group-data-[state=failed], so an error message reads as one without the caller styling it.

Anatomy

Tool                    (div, carries data-state and the group)
└── Collapsible         (Ark's own root — its data-state is left alone)
    ├── ToolHeader      (the trigger: name, Badge, indicator)
    └── ToolContent
        ├── ToolInput   (children, or `data` as a JSON tree)
        └── ToolOutput  (children, or `data` as a JSON tree)

ToolHeader truncates its name rather than wrapping, because a tool name is an identifier and a wrapped identifier costs a line on every call in the transcript.

API Reference

Tool

Renders a div around a Collapsible.

PropTypeDefault
state"pending" | "running" | "done" | "failed""pending"
defaultOpenbooleanstate === "done"
openboolean
onOpenChangeCollapsible's handler

state is the RunState type, shared with Task.

ToolInput · ToolOutput

Both render a div and share ToolIoProps.

PropTypeDefault
dataunknown

Drawn as a JSON tree when — and only when — there are no children.

ToolHeader

Extends CollapsibleTrigger. Children are the tool's name; the badge and the indicator are supplied.

ToolProps is exported, so a wrapper can take the same props without restating them.

On this page