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.
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
- The worker declares the tool
handler: "client". - The model calls it. The engine dispatches the call to the client, not the worker, and the run yields with the call pending.
- The client runs the tool and settles the call by
idwith atool.resultortool.error. - The engine records the result, fires
tool.finished, and re-prompts the model, exactly as for a worker tool. The worker returnsproposed.
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
ClientInputandClientContext.