Client tools
Register your app as a client on a session, declare the tools it offers, and answer the calls the model routes to you.
Some tools cannot run anywhere but in your app. Reading the form the user is looking at, highlighting a row in their table, asking them to pick a date — the agent should be able to call these, and no server-side integration can provide them.
A client is a participant that registers on a session, declares tools, and answers calls. A browser tab is a client. So is a backend worker.
Registering
POST /v1/sessions/{session_id}/clients
{
"name": "web",
"lifetime": { "type": "ttl", "data": { "seconds": 60 } },
"tools": [
{
"name": "highlight_row",
"description": "Highlight a row in the table the user is looking at.",
"parameters": {
"type": "object",
"properties": { "row_id": { "type": "string" } },
"required": ["row_id"]
}
}
]
}
namestring
What the model sees this client's tools under. If the session has already spent the name, a counter is appended — the assigned name comes back in the `client_registered` event, and it is the one that matters.
stringlifetimenever | ttl
`never` ends only on an explicit leave. `ttl` ends `seconds` after the last ping, so a closed tab stops offering its tools without having to say so.
never | ttltoolsClientTool[]
Each tool has a name (1–64 chars, `[A-Za-z0-9_-]`), a description up to 4096 characters, and a JSON Schema object for its arguments.
ClientTool[]Either credential reaches these routes: the session’s own token, or a workspace API key holding session.manage.
Choosing a lifetime
A tab can close without warning, so give it a TTL and ping while it is open. When the pings stop, the registration lapses and the model stops being offered tools nothing can answer.
POST /v1/sessions/{session_id}/clients/{client_id}/pingA server-side client that stays up for the life of the session can use
never and skip the ping entirely. It leaves when it says it leaves.
DELETE /v1/sessions/{session_id}/clients/{client_id}Answering a call
Watch the session stream for client_tool_dispatched — it names your client_id, the call_id, the tool as you declared it (unnamespaced), and the arguments.
Acknowledge it
An ack marks the difference between a client that is working on a call and one that never saw it. The two carry different deadlines, so acking buys you the longer one.
POST /v1/sessions/{session_id}/clients/{client_id}/calls/{call_id}/ackDo the work, then post the result
POST /v1/sessions/{session_id}/clients/{client_id}/calls/{call_id}/resultIf your client goes away mid-call, the call settles as client_tool_failed with a reason, and the model receives an error tool result — so the turn recovers instead of stalling on a client that is not coming back.
Changing what you offer
A client declares the whole list, every time. There is no partial update: a tool you stop naming is a tool you stopped offering.
PUT /v1/sessions/{session_id}/clients/{client_id}/tools
This is how a client that navigates between screens keeps its tools honest — re-PUT the list on every screen change, and the model is only ever offered what is actually there.