Tool Call Flow
Application-side tool
- Client → Server: PUT /session or POST /session/:id
- Client ← Server: SSE: tool_call (toolCallId, name, input)
- Client ← Server: SSE: turn_stop (stopReason: "tool_use")
- Client executes tool
- Client → Server: POST /session/:id (messages: [tool result])
- Client ← Server: SSE: text_delta, turn_stop (stopReason: "end_turn")
Server-side tool (trusted, inline)
- Client → Server: PUT /session or POST /session/:id
- Client ← Server: SSE: tool_call (toolCallId, name, input)
- Server executes tool inline
- Client ← Server: SSE: tool_result (toolCallId, content)
- Client ← Server: SSE: text_delta, turn_stop (stopReason: "end_turn")
Server-side tool (permission required)
- Client → Server: PUT /session or POST /session/:id
- Client ← Server: SSE: tool_call (toolCallId, name, input)
- Client ← Server: SSE: turn_stop (stopReason: "tool_use")
- Client grants or denies permission
- Client → Server: POST /session/:id (messages: [tool_permission])
- Server executes tool (or informs LLM of denial), continues streaming
- Client ← Server: SSE: tool_result (toolCallId, content)
- Client ← Server: SSE: text_delta, turn_stop (stopReason: "end_turn")
Parallel tool calls
The server may emit multiple tool_call events before turn_stop. The client should handle all of them — execute application-side tools and respond to untrusted server tool permissions — then re-submit all results and permissions together in a single POST /session/:id. Trusted server-side tools are handled inline by the server and do not require client action.
Example with two application-side tools, one trusted server tool, and one untrusted server tool — all called in parallel:
- Client → Server: POST /session/:id (messages: [user message])
- Client ← Server: SSE: tool_call (toolCallId: "call_001", name: "client_tool_1", input: {...})
- Client ← Server: SSE: tool_call (toolCallId: "call_002", name: "client_tool_2", input: {...})
- Client ← Server: SSE: tool_call (toolCallId: "call_003", name: "server_tool_trusted", input: {...})
- Client ← Server: SSE: tool_call (toolCallId: "call_004", name: "server_tool_untrusted", input: {...})
- Server executes server_tool_trusted inline
- Client ← Server: SSE: tool_result (toolCallId: "call_003", content: "...")
- Client ← Server: SSE: turn_stop (stopReason: "tool_use")
- Client executes client_tool_1 and client_tool_2; grants or denies permission for server_tool_untrusted
- Client → Server: POST /session/:id (messages: [tool result call_001, tool result call_002, tool_permission call_004])
- Server executes server_tool_untrusted (or informs LLM of denial)
- Client ← Server: SSE: tool_result (toolCallId: "call_004", content: "...")
- Client ← Server: SSE: text_delta, turn_stop (stopReason: "end_turn")
Tool call resolving
Server
After the LLM emits tool calls, the server resolves each one:
- For each
tool_call, check if it is a trusted server-side tool — if so, execute it inline immediately and emit atool_resultevent. - If any tool calls remain unexecuted, emit
turn_stopwithstopReason: "tool_use". - When the client re-submits, append the client-provided tool result messages to history.
- For each
tool_permissionin the submission, find the matchingtool_callbytoolCallId— execute the tool if granted, or record a denial to inform the LLM. - Append all
tool_resultevents to history and continue the agent loop.
Client
When the client receives turn_stop with stopReason: "tool_use":
- Collect all
tool_callevents from the current turn. - Ignore any whose
toolCallIdalready has a matchingtool_result— those were handled inline by the server. - For each remaining tool call, determine whether it is an application-side tool (by matching the name against tools declared in the request) or a server-side tool:
- Application-side tool: execute it and collect the result.
- Server-side tool: prompt the user or apply policy to grant or deny permission.
- Submit all results and permissions together in a single
POST /session/:id.
Tool call resumption
If a client has no in-memory state (e.g. after a restart or recovery), it can call GET /session/:id to retrieve the session history and resume from where it left off:
- Fetch session history via
GET /session/:id. - Inspect the last assistant message in history — if it has unresolved tool calls (no matching
toolmessage in history), the last turn ended withstopReason: "tool_use"and requires client action. - Apply the same client-side resolving logic: identify application-side tools to execute and server-side tools requiring permission.
- Submit results and permissions via
POST /session/:idto continue.