ai.stream.TurnStream

The raw text-delta stream of ONE model turn. Pull-based: nothing is read from the wire until `next()` or `final_turn()` is called.

Reference version

Signature

class ai.stream.TurnStream

The raw text-delta stream of ONE model turn. Pull-based: nothing is read from the wire until next() or final_turn() is called.

next() yields DELTA chunks (each item is only the newly arrived text, never the accumulated prefix) and returns Done when the turn is over. final_turn() drains the rest of the stream and returns the completed turn: the accumulated text as one Text block, the folded stop_reason, and usage where the wire provided token counts.

Source:<builtin>/ai/ns_stream/stream.bamlbytes 615121029

Fields

_sse

baml.http.SseStream | null

_chunks

string[]

_cursor

int

_pcursor

int

_text

string

_input_tokens

int | null

_output_tokens

int | null

_done

bool

_require_terminal

string | null

_saw_terminal

bool

_call_started

baml.time.Instant | null

_ttft_from

baml.time.Instant | null

_capture_sse

bool

Static methods

function

from_chunks

(chunks: string[]) -> ai.stream.TurnStream

A turn stream fed from literal delta chunks — the scripted counterpart of a live stream, for tests. There is no wire to be cut, so a scripted stream is never strict: the turn completes as StopReason.Complete after the last chunk.

function

from_event_source

(next_event: ai.stream.EventSource) -> ai.stream.TurnStream

A stream backed by one normalized-event callback. Reliability clients use this to relay a chosen member's deltas and final metadata while retaining TurnStream's ordinary accumulation and termination behavior. The source itself owns any retry/fallback policy; once it returns a TextDelta, that delta is immediately visible to the caller.

function

from_sse

(
sse: baml.http.SseStream,
decode: (string) -> ai.stream.Event[] throws ai.errors.InvokeError,
require_terminal: string | null = …
) -> ai.stream.TurnStream

A turn stream over a live SSE connection. decode receives the raw batch string from sse.next() (a JSON array of SseEvent objects; see decode_sse_batch) and yields the events it contains.

require_terminal opts into the strict termination contract: pass the provider id ("anthropic", "openai", …) when the protocol has an explicit end-of-turn event that the decoder yields as TurnDone. The stream then throws ai.errors.NetworkFailure if the connection ends before that event arrives, rather than reporting the truncated text as a completed turn. Leave it null (the default) only for a protocol whose sole end-of-turn signal IS the socket closing.

Instance methods

function

_capture_batch

(self, batch: string) -> void

(internal) Append a raw SSE batch to the wire record when capture is on. Telemetry must never fail the stream, so a malformed batch is skipped.

function

_check_terminal

(self) -> void throws ai.errors.NetworkFailure

(internal) The strict-termination check: a no-op unless this stream required an explicit terminal event and the stream ended without one, in which case the truncated turn is reported as the transport failure it is instead of being passed off as complete. Called on every end-of-stream path.

function

_finish

(self) -> void

(internal) Marks the turn over and releases the SSE connection behind it, if this stream has one.

function

_stamp_first_delta

(self) -> void

(internal) Stamp time-to-first-token on this stream's wire record, once.

function

final_turn

(
self
) -> ai.ModelTurn throws baml.errors.Io | baml.errors.Timeout | baml.errors.UnknownError | reflect.errors.CompilationError | ai.errors.Failure

Drain the stream and return the completed turn. Safe to call at any point (including after Done); calling twice returns an equal turn.

On a strict stream (from_sse's require_terminal) that ended without the provider's terminal event this throws ai.errors.NetworkFailure instead of returning a turn — the stop_reason default below must never turn a truncated stream into a StopReason.Complete answer.

function

next

(
self
) -> string[] | ai.stream.Done throws baml.errors.Io | baml.errors.Timeout | baml.errors.UnknownError | reflect.errors.CompilationError | ai.errors.Failure

All READY text deltas, or Done when the turn is over. Metadata events are folded silently; calling next() after Done keeps returning Done.

Drain-ready-then-deliver: every event already decoded (the wire layer returns whole batches — everything that queued while the caller was busy) is returned as ONE batch, and the wire is only pulled when no deliverable text is in hand. Delta boundaries are preserved — every element is one provider text delta, so consumers that want each SSE delta individually iterate the batch. A consumer that keeps up sees one-element batches; a consumer that falls behind (e.g. a partial parse slower than the token interval) gets the whole backlog in one return instead of replaying it delta by delta — the pull-model equivalent of the legacy engine's latest-snapshot watch channel.