chatstreams its answer as an AI SDK UI message stream. Your app’s backend starts the run with?passthrough=trueand hands the response to the browser unchanged, souseChatreads it with no mapping.pay_suppliergives the model apaytool that needs approval. The run is held until a person approves, edits or rejects the payment, thengenerateTextcontinues where it stopped.
The bundle
vendor/agentos-sdk-0.1.0.tgz, the TypeScript SDK (see SDKs: it isn’t on npm yet and ships with AgentOS). Node 24 runs agent.ts directly. Set config.models.main to a model from your environment’s Models page, zip the folder with vendor/ in it and deploy it.
How the model is wired
agentosModel(inv) is an AI SDK model for the role main in config.models, served by the gateway. Its requests go through inv.fetch, which adds the run token only for the gateway and the platform and only while the invocation runs. Build it inside the handler, once per invocation: the token ends when the invocation answers. Pass another role for another model, for example agentosModel(inv, "judge").
Stream chat to a useChat frontend
The browser never talks to AgentOS and never sees your API key. Your backend starts a run ofchat with ?passthrough=true and returns the response as it arrives: AgentOS relays the agent’s status, Content-Type, Cache-Control, X-Accel-Buffering and X-Vercel-AI-* headers and its body byte for byte, plus X-AgentOS-Run-Id (see Passthrough).
useChat sends the whole conversation each turn, so the agent needs no history of its own. To tie a conversation to one of your customers, create a session with {"user": "<their id>"} and pass its session_id in the run body; the agent then sees inv.user and can keep memory per user.
From a terminal, the same call shows the stream:
completed with {"text": ...} as its output (see Streaming answers).
Approve a tool call
runWithApprovals is generateText with the SDK’s approval handling. When the model calls pay, the conversation so far is kept in run state and one hold asks a person about every pending call (approve, edit its input, or reject). On the resume the decisions become tool-approval responses: an approved call runs once, a rejected one tells the model why, and generateText continues.
1
Start the run
2
Decide
A reviewer decides in the panel’s Review queue, or through the API. Each item’s
id is the tool call’s id; read it from the hold:3
Read the result
The run continues with the decision and completes:
Good to know
- Errors in a stream.
streamTextonly logs errors by default, including a guardrail’s block. The example passesonErrorto bothstreamTextandtoUIMessageStreamResponse: the browser gets the guardrail’s message as anerrorpart, and the run fails withblocked_by_guardrailinstead of completing with a cut answer. See Guardrails. - Report before the stream ends. Call
inv.setOutput(...)before the last chunk. A stream that ends without it fails the run withmissing_output. - A held run’s resume isn’t streamed. A streaming capability that asks a person ends its stream; read the resumed run’s output with
GET …/runs/{run_id}. - Run state is at most 1 MiB.
runWithApprovalskeeps the conversation in run state while the run is held, and it grows with the transcript. - Code before the ask runs again on the resume.
runWithApprovalshandles its own calls; keep anything else you do before it idempotent (see Human approvals).
ai 7.0.138, @ai-sdk/openai-compatible 3.0.68 and Node 24, with the conformance kit. More frameworks: Framework examples.
