router.run({...}) returns an async iterator of AgentEvents. The event shape is identical across providers — your rendering code doesn’t care whether Claude or Gemini produced the turn.
The event union
Event type | Fields | Meaning |
|---|---|---|
text | text: string | Partial text delta. Stream these to your UI / stdout. |
tool_call | call_id: string, name: string, arguments: object | Model is calling a tool. The router dispatches it automatically — this event is observational. |
tool_result | call_id: string, name: string, result: object, error?: string | Tool returned. Log if you want; the model already has it. |
finish | stop_reason: string, session_id?: string, usage: object | Turn complete. Capture session_id to continue. |
error | message: string, errorType: string | Terminal failure. The iterator closes. |
Handling every event
Under the hood — SSE
The router hitsPOST /api/v1/storage/sandboxes/{sandbox_id}/agents/run with Accept: text/event-stream. The server streams Server-Sent Events; the client parses frames into the neutral AgentEvent union.
You don’t need to think about SSE. But if you want to bypass the router and talk to the endpoint yourself (for example, to proxy the stream through your own server to a browser client), the SSE parser helpers are exported:
Cancellation
Pass anAbortSignal on signal to cancel mid-stream:
Streaming to a browser
Three common patterns:- Proxy the SSE through your server — cleanest. Your browser client gets a normal
EventSource; the server keeps the API key. - Expose a WebSocket — your server subscribes to the router and forwards events.
- Bypass the router on the server and use
iterateSseFrames— if you need a different wire format than SSE.
COPASS_API_KEY server-side. The router SDK is designed to be imported in a server context, not shipped to the browser.
Tool calls, in detail
When the model calls a connected integration tool (e.g.,github.list_issues):
Custom tools
If you want tools beyond the integrations — domain-specific functions, local I/O, business logic — register them in-process viaAgentToolRegistry from copass-core-agents. The in-process backends pick them up; the hosted router path currently supports integration-provided tools only. See the Providers page for when to drop to an in-process backend.
Next steps
- Multi-turn — the
finish.session_id→ next-turn loop. - Integrations — how the tools the agent calls got there.

