Client-side tools

A client-side tool runs in the client, usually the browser, not on your worker. You declare it with handler: "client". When the model calls it, the engine hands the call to the client, waits for the result, and folds it back into the conversation.

Example

The worker declares the tool and marks it handler: "client". It runs nothing itself.

server.mjs
function decide({ trigger, proposed }) {
    if (trigger.type === "session.start") {
        return {
            agent: {
                model: "claude-haiku-4-5-20251001",
                stream: true,
                system: "For location questions, call get_location instead of guessing.",
                tools: [
                    {
                        name: "get_location",
                        description: "Get the user's current city. Only the client can answer this.",
                        handler: "client"
                    }
                ]
            }
        };
    }

    // Nothing to run here: the client executes get_location.
    return proposed;
}

No tool.execute reaches the worker for get_location. When the model calls it, the run yields with the call pending, and the client settles it by id:

{ "type": "tool.result", "id": "<toolCallId>", "result": "Lisbon" }

The round trip

  1. The worker declares the tool handler: "client".
  2. The model calls it. The engine dispatches the call to the client, not the worker, and the run yields with the call pending.
  3. The client runs the tool and settles the call by id with a tool.result or tool.error.
  4. The engine records the result, fires tool.finished, and re-prompts the model, exactly as for a worker tool. The worker returns proposed.

Input and output schemas apply as they do for worker tools (see Tool calls).

Settling

The client answers a pending call with one of these inputs, addressing it by the call id:

type ClientInput =
    | { type: "tool.result"; id: string; attempt?: number; result?: unknown }
    | { type: "tool.error"; id: string; error: string; retryable: boolean; attempt?: number }

Only the session's owner may settle, and only a call whose handler is client. A tool message carried in a client.messages submit settles a pending client call the same way, which is how a browser transcript answers without a separate input.

Client-contributed tools

A client can also add tools at submit time, layered onto the config through the submit's client context:

type ClientContext = {
    tools?: AgentTool[]   // client-executed tools, added to the config
    // …
}

Each tool is declared handler: "client" and dispatched to the client like any other. Layering is additive: a name already taken by a worker tool or sub-agent is ignored, so the worker always wins its own names. Use it for browser-native capabilities the worker doesn't know up front. See AG-UI.

Next

  • Tool calls: worker tools, schemas, and errors.
  • AG-UI: the browser protocol that carries these.
  • Protocol: the full ClientInput and ClientContext.