# Tool call

> One step of an AI agent: what it did, its result, and Allow or Reject. A Pyzed component for Python desktop GUIs, with examples and its API.

Source: https://albakore.github.io/pyzed/library/tool-call/

:::tip[In plain words]
One thing an AI assistant did, like reading or changing a file. Click it to see the details; if it needs your permission, it asks with Allow and Reject buttons.
:::

`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](https://albakore.github.io/pyzed/library/#ai-agents) · [Full reference →](https://albakore.github.io/pyzed/reference/ui/#toolcall)

**Code:**

```python
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)
```

:::note[New]
This component was added recently. Tell us how it works for you in the [issue tracker](https://github.com/albakore/pyzed/issues).
:::

## 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](https://albakore.github.io/pyzed/library/diff-view/).

## Usage

```python
import pyzed as pz
from 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

:::note[About these captures]
Each capture is recorded by pyzed itself, off-screen at 2x, in One Dark or One Light to match this site's theme, and cropped to the component so it shows at its real size. The pointer hovers, presses or types into it; a component that does not react to the pointer cycles through a few themes. With reduced motion you see the first frame. Open **Code** under a capture to see the snippet that draws it; free names such as `on_save` stand for your own callbacks.
:::

### State

Pending and running show a spinner, waiting asks with Always allow, Allow and Reject, failed and rejected say so in red.

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

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

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

```python
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)
```

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

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

### Kind

The kind picks the icon of a finished call.

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

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

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

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

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

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

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

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

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

```python
pz.container(ui.ToolCall(title="Tool", detail="custom", kind="other"), width=300)
```

### Content

Unfolded ones are drawn as they look after a click on the header.

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

```python
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)
```

```python
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)
```

```python
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)
```

```python
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

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](https://albakore.github.io/pyzed/guides/events/) and [Events of every component](https://albakore.github.io/pyzed/library/events/).

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

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](https://albakore.github.io/pyzed/guides/code-styles/).

**Imperative:**

```python
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"))
```

**Declarative:**

```python
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 too
```

**Functional:**

```python
from 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"))
```

## Tips

:::tip
`status` is yours: set it from what the agent reports (`pending`, then `running`, then `done` or `failed`; `rejected` after a Reject). `kind` only picks the icon: `read`, `edit`, `delete`, `move`, `search`, `execute`, `think`, `fetch`, `switch_mode` or `other`. The header only unfolds when there is something to show (`input`, `output`, `images` or `diffs`).
:::

## Properties and API

### `ui.ToolCall`

```python
class ToolCall
```

A 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](https://albakore.github.io/pyzed/editor/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](#events)).

[Full reference →](https://albakore.github.io/pyzed/reference/ui/#toolcall)

## Related

- [Diff view](https://albakore.github.io/pyzed/library/diff-view/): An edit as colored lines: added in green, removed in red.
- [Plan](https://albakore.github.io/pyzed/library/plan/): An agent's plan as a checklist that folds.
- [Edits bar](https://albakore.github.io/pyzed/library/edits-bar/): The files an agent changed, with Keep all and Reject all.
- [Context meter](https://albakore.github.io/pyzed/library/context-meter/): How full a model's context window is: a ring and `48k / 200k`.
