Tool call
ui.ToolCall is a component for one action of an AI agent (reading a file, editing it, running a command), drawn like in Zed: a line with its status, the title and what it works on (detail), and a click unfolds the input and the output (plus any images). Edits, commands (kind edit, delete, move or execute) and calls that wait are cards instead. diffs show the edits it made as colored diffs, and while it runs progress shows the last lines a command printed. While status is "waiting" it asks right on the card: Always allow, Allow or Reject, reported through on_allow, on_allow_always and on_deny. The component remembers whether it is unfolded; your app only keeps the data.
ui.ToolCall · Category: AI agents · Full reference →


pz.column([ ui.ToolCall(title="Read file", detail="app/storage.py", kind="read", output="def save(path): ..."), ui.ToolCall(title="Search", detail="def save", kind="search", output="app/storage.py:12"), ui.ToolCall(title="Edit file", detail="app/storage.py", kind="edit", status="waiting", diffs=[{"path": "app/storage.py", "old": "def save(path):\n write(path)\n", "new": "def save(path, data):\n write(path, data)\n"}], on_allow=on_allow, on_allow_always=on_allow_always, on_deny=on_deny), ui.ToolCall(title="Run", detail="pytest -q", kind="execute", status="running", progress="collected 24 items\n........................"),], spacing=4, width=560)When to use
Section titled “When to use”- In an agent’s chat, one per step the agent takes.
- With
status="waiting", to ask before a risky step such as writing files or running a command. - For an edit on its own, outside a tool call, use Diff view.
import pyzed as pzfrom pyzed import ui
class Chat(pz.App): def __init__(self): self.status = "waiting"
def answer(self, allowed): self.status = "done" if allowed else "rejected"
def view(self): return ui.ToolCall(title="Run", detail="pytest -q", kind="execute", status=self.status, input={"command": "pytest -q"}, output="24 passed", on_allow=lambda e: self.answer(True), on_deny=lambda e: self.answer(False))Variants
Section titled “Variants”Pending and running show a spinner, waiting asks with Always allow, Allow and Reject, failed and rejected say so in red.


status="pending"Code
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="pending"), width=360)

status="running"Code
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="running"), width=360)

status="done"Code
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="done"), width=360)

status="waiting"Code
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="waiting", on_allow=on_allow, on_allow_always=on_allow_always, on_deny=on_deny), width=360)

status="failed"Code
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="failed"), width=360)

status="rejected"Code
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="rejected"), width=360)The kind picks the icon of a finished call.


kind="read"Code
pz.container(ui.ToolCall(title="Read file", detail="app.py", kind="read"), width=300)

kind="edit"Code
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit"), width=300)

kind="delete"Code
pz.container(ui.ToolCall(title="Delete file", detail="old.py", kind="delete"), width=300)

kind="move"Code
pz.container(ui.ToolCall(title="Move file", detail="a.py → b.py", kind="move"), width=300)

kind="search"Code
pz.container(ui.ToolCall(title="Search", detail="def save", kind="search"), width=300)

kind="execute"Code
pz.container(ui.ToolCall(title="Run", detail="pytest -q", kind="execute"), width=300)

kind="think"Code
pz.container(ui.ToolCall(title="Think", kind="think"), width=300)

kind="fetch"Code
pz.container(ui.ToolCall(title="Fetch", detail="https://example.com", kind="fetch"), width=300)

kind="switch_mode"Code
pz.container(ui.ToolCall(title="Switch mode", detail="plan", kind="switch_mode"), width=300)

kind="other"Code
pz.container(ui.ToolCall(title="Tool", detail="custom", kind="other"), width=300)Content
Section titled “Content”Unfolded ones are drawn as they look after a click on the header.


progress=Code
pz.container(ui.ToolCall(title="Run", detail="pytest -q", kind="execute", status="running", progress="collected 24 items\n............\n............"), width=360)

diffs=Code
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", status="running", diffs=[{"path": "app.py", "old": "x = 1\ny = 2\n", "new": "x = 10\ny = 2\n"}]), width=360)

allow_always=FalseCode
pz.container(ui.ToolCall(title="Run", detail="rm -rf build", kind="execute", status="waiting", allow_always=False, on_allow=on_allow, on_deny=on_deny), width=360)

input=, output=Code
pz.container(ui.ToolCall(title="Read file", detail="app.py", kind="read", input={"path": "app.py"}, output="import pyzed as pz\n\npz.run(App)"), width=360)

Code
pz.container(ui.ToolCall(title="Edit file", detail="app.py", kind="edit", diffs=[{"path": "app.py", "old": "x = 1\ny = 2\n", "new": "x = 10\ny = 2\n"}]), width=360)Events
Section titled “Events”Tool call has 3 events of its own, listed below. Like every widget it also has the 18 common events (click, double_click, right_click, mouse_enter, drag, key_down, resize…), attached with .on(...): see Events and Events of every component.
It is a pz.Component, so it also raises on_mount and on_unmount, and its handlers get a pz.Event whose value holds the data.
| Event | Handler gets | When |
|---|---|---|
on_allow |
(event) |
Allow was pressed. |
on_allow_always |
(event) |
Always allow was pressed. |
on_deny |
(event) |
Reject was pressed. |
Handler gets is what Pyzed passes to your callable: () means nothing. Any handler can also be a message (a value that is not callable), which goes to update().
Handling the events
Section titled “Handling the events”The same events written three ways. Imperative: methods of the app change self. Declarative: the handlers produce messages and one update() decides. Functional: a pure function draws an immutable state and asks for changes with set_state(...). See Imperative, declarative and functional.
class Chat(pz.App): def __init__(self): self.status = "waiting"
def answer(self, status): self.status = status
def view(self): # a pz.Component: handlers get a pz.Event return ui.ToolCall(title="Run", detail="pytest -q", kind="execute", status=self.status, input={"command": "pytest -q"}, on_allow=lambda e: self.answer("done"), on_allow_always=lambda e: self.answer("done"), on_deny=lambda e: self.answer("rejected"))class Chat(pz.App): status = "waiting"
def update(self, message): match message: case "allow": self.status = "done" case "deny": self.status = "rejected"
def view(self): return ui.ToolCall(title="Run", detail="pytest -q", kind="execute", status=self.status, on_allow="allow", on_deny="deny") # messages work for components toofrom dataclasses import dataclass
@dataclass(frozen=True)class State: status: str = "waiting"
def view(state, set_state): return ui.ToolCall(title="Run", detail="pytest -q", kind="execute", status=state.status, on_allow=lambda e: set_state(status="done"), on_deny=lambda e: set_state(status="rejected"))Properties and API
Section titled “Properties and API”ui.ToolCall
Section titled “ui.ToolCall”class ToolCallA component for one action of an AI agent, drawn like in Zed’s Agent panel: a line with its icon, what it does and where (it unfolds to show the input and the output), or a card for edits and commands, with the diff or the output. While status is "waiting" it asks right on the card: Always allow, Allow or Reject. See AI agents.
| Property | Default | Description |
|---|---|---|
title |
"" |
What it does (“Edit file”). |
detail |
"" |
What it works on, in a monospace font. |
kind |
"" |
ACP’s kind: read, edit, delete, move, search, execute, think, fetch or other (its icon). |
status |
"done" |
pending, running, waiting, done, failed or rejected. |
input |
None |
The arguments (a dict or text). |
output |
None |
The answer (text or a dict). |
images |
None |
PNG bytes shown under the output. |
diffs |
None |
The edits it made: {"path", "old", "new"} dicts, shown as colored diffs (see ui.diff_view). |
progress |
"" |
What it printed so far (a command’s output); its last lines show under the header while it runs. |
allow_always |
True |
Shows Always allow. |
theme |
None |
Theme. |
Events: on_allow, on_allow_always, on_deny (events).