Radondocs

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.

stream.ts
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 stream

An aborted stream surfaces the abort verbatim; a genuine transport break surfaces as StreamError.

Next steps

On this page