Streaming
Stream a chat completion token-by-token with ai.stream() — textStream(), the normalized chunk union, final(), the single-consumption rule, and the no-fallback caveat.
ai.stream() returns a ChatStream — an async-iterable of normalized events
you can consume as the model generates. Providers frame their server-sent events
completely differently; Radon reassembles all of them into one event stream, so
your consumer code is identical on every provider.
const stream = ai.stream({
messages: [{ role: "user", content: "Write a haiku about the sea." }],
});
for await (const text of stream.textStream()) {
process.stdout.write(text);
}stream() takes the exact same ChatOptions
as chat() — messages, model, tools, sampling params, and so on.
Just the text
textStream() yields only the text deltas — the common "print tokens as they
arrive" case.
for await (const delta of stream.textStream()) {
process.stdout.write(delta);
}The full event stream
Iterate the stream directly to get the full normalized StreamChunk union — text
deltas, tool-call deltas, and a terminal finish chunk carrying the fully
assembled result.
for await (const chunk of stream) {
if (chunk.type === "text") {
process.stdout.write(chunk.delta);
} else if (chunk.type === "tool_call") {
// an incremental fragment of a tool call (see below)
} else if (chunk.type === "finish") {
console.log(chunk.message); // the assembled ChatResult
}
}text{ delta: string }A chunk of assistant text to append.
tool_call{ index, id?, name?, argumentsDelta? }A fragment of a tool call. id and name arrive on the first fragment;
argumentsDelta is a piece of the JSON arguments string, to be concatenated.
index identifies which call (0-based) when several stream in parallel.
finish{ finishReason, usage?, message }The terminal event. message is the complete ChatResult, with tool-call
arguments reassembled and parsed into objects.
You rarely assemble tool calls by hand
Radon reassembles streamed tool-call fragments for you: the finish chunk's
message.toolCalls (and final() below) already contains
complete, parsed calls. Handle raw tool_call deltas only if you want to show
tool activity live.
The final result
To stream and get the assembled object at the end, call final(). It drains
the stream (if you haven't already) and returns the same ChatResult that
chat() would have produced.
const stream = ai.stream({ messages });
for await (const text of stream.textStream()) {
process.stdout.write(text);
}
const result = await stream.final(); // safe to call after iterating
console.log(result.usage, result.finishReason, result.toolCalls);final() returns the same result object whether you call it after iterating or
without iterating at all (in which case it drains the stream for you).
Single consumption
A ChatStream can only be consumed once
Iterating a stream a second time throws — streams are single-use. textStream(),
a direct for await, and final() all consume the same underlying stream, so
pick one traversal (calling final() after a single textStream()/for await
pass is fine — it reuses the already-assembled result). For a fresh stream, call
ai.stream() again.
const stream = ai.stream({ messages });
for await (const _ of stream.textStream()) { /* ... */ }
for await (const _ of stream) { /* ❌ throws: already consumed */ }Fallback is not honored while streaming
stream() commits to a single provider
Fallback chains are honored by chat() only. A stream commits
to one provider — its provider (or the default) — because output has already
begun flowing to the caller by the time a mid-stream failure could occur. If you
pass fallback to stream() it is ignored (though, as a Pro feature, its
presence is still license-gated). Wrap stream() in your own try/catch to retry a
different provider.
Cancellation
Pass an AbortSignal to stop a stream early; it's forwarded to the underlying
fetch.
const controller = new AbortController();
const stream = ai.stream({ messages, signal: controller.signal });
// controller.abort() ends the streamAn aborted stream surfaces the abort verbatim; a genuine transport break surfaces
as StreamError.
Next steps
Chat
Send messages and read normalized results with ai.chat() — roles, sampling parameters, the max-tokens and pass-through caveats, and the ChatResult shape.
Tool calling
Define one tool schema and it works on every provider. The request-run-respond loop, toolChoice, parsed toolCalls, parallel calls, and InvalidToolArgumentsError.