Skip to main content
One instruction in, one result out. run() resolves one of four results and never throws for anything that fails remotely; those failures arrive as the error result. Throwing is reserved for your own configuration or store faults. Call forUser() inside the request handler, once per call. Never hoist it to module scope.

The four results

What each result carries

On the blocking route the SDK retries a retryable failure once before resolving error; streamed turns (runStream(), or run() with onEvent) resolve without a retry. Treat retryable as permission for one more attempt, not a loop. Retrying never silently re-sends money: anything that moves it stops for the customer’s approval first.

What no result carries

Diagnostics such as internal ids, token usage, and raw payloads sit on a non-enumerable symbol, not on the result’s fields. JSON.stringify(result) never includes them, so a result is safe to send to the browser as is.

Sessions

Actions for the same user share one conversation. The SDK mints a session when the connection is stored and continues it on every call; there is nothing to thread through your code.
newConversation() starts a fresh thread. It keeps the connection, clears any stashed instruction, and retires stored approval cards. A fresh chat is not a fresh consent.

Streaming

onEvent implies streaming: the blocking path emits no events, so setting a listener switches the call over. An explicit stream: false wins, and the listener then hears nothing. The listener receives every event while run() still resolves the ordinary result, and a listener that throws never breaks the action. handleToolUse() accepts the same option. A progress frame is a phase and a label, nothing more. started and completed bracket one step, so replace the current line instead of stacking; waiting is a heartbeat. The label is finished customer-facing copy; render it unchanged. A stream is consumed once; iterating it twice throws. result() and the done event carry the identical result.