Streaming Model
stream and streamSimple return an AssistantMessageEventStream, which is an
async iterable of events. The stream is produced by provider adapters and
normalized to a single event vocabulary.
The stream/complete contract is implemented in src/facade/ml/stream.ts; the
event stream class lives in src/facade/ml/kit/event-stream.ts.
Dispatch
stream(model, context, options?, logger?) does the following before handing off
to a backend:
validateContext(context)checks message and tool shapes.- The model descriptor is shape-checked (
api,id,providermust be non-empty strings). getApiProvider(model.api)resolves the registered backend, throwing if none is wired in.- An optional
logger.debugtrace (ai.stream.start) is emitted. - The backend's
stream(orstreamSimple) method runs.
complete and completeSimple are thin wrappers that await result().
streamByApi(api, model, ...) throws on an api / model.api mismatch, then delegates to stream.
Event Types
AssistantMessageEvent (in src/facade/ml/types.ts) is a discriminated union:
startwith a partialAssistantMessage.text_start,text_delta,text_endfor text blocks (carrycontentIndex).thinking_start,thinking_delta,thinking_endfor reasoning blocks.toolcall_start,toolcall_delta,toolcall_endfor tool calls.donewithreason("stop" | "length" | "toolUse") and the finalmessage.errorwithreason("aborted" | "error") and anerrorassistant message.
Event Order
A typical streaming response:
start- zero or more block events, in any order
doneorerror
Tool-call events can interleave with text or thinking blocks depending on the
provider. The final message can contain multiple blocks in message.content.
Result Handling
AssistantMessageEventStream extends the generic EventStream<T, R>. It settles
its result on the first done (resolving to message) or error (resolving to
error). Useful methods:
result()resolves to the finalAssistantMessage.resultWithTimeout(timeoutMs)rejects if no terminal event arrives in time.filter(predicate)andmap(mapper)return derived async iterables.getHistory()returns buffered events (bounded byhistoryLimit).
Producers push events with push(event) or the typed helpers (pushStart,
pushTextDelta, pushToolCallEnd, pushDone, pushError, etc.).
createAssistantMessageEventStream(options?) constructs a fresh instance.
Tool Calls
Tool calls are represented as content blocks:
ToolCallcarriesid,name,arguments, and an optional GeminithoughtSignature.- Streamed argument fragments are parsed best-effort with
parseStreamingJson.
Adapters internally normalize messages through transformMessages (in
src/facade/ml/adapters/transform-messages.ts) before sending — normalizing
tool-call ids, stripping signatures other providers cannot replay, and inserting
synthetic results for orphaned tool calls. transformMessages is an internal
adapter helper and is not re-exported from indusagi/ai.
Aborts
All streaming functions accept an AbortSignal via StreamOptions.signal.
Providers check the signal and surface it as stopReason: "aborted" (an error
event with reason: "aborted").
Reasoning and Thinking
streamSimple/completeSimple take SimpleStreamOptions, which add reasoning
(a ThinkingLevel: "minimal" | "low" | "medium" | "high" | "xhigh") and an
optional thinkingBudgets override. Adapters map these through the helpers in
src/facade/ml/adapters/simple-options.ts:
buildBaseOptions(model, options?, apiKey?)derives the base request options, boundingmaxTokensby the model's max and a 32,000-token ceiling.clampReasoning(level)collapses"xhigh"to"high"for providers that do not support it.mapThinkingLevel(level, "supports-xhigh" | "clamp-xhigh")chooses whether to preserve or clamp"xhigh".adjustMaxTokensForThinking(...)reserves token headroom for the visible answer when a reasoning budget is applied.
