Skip to content

stream/complete: the facade entry point for LLM calls

源码版本v0.73.1

stream.ts is the thinnest layer that @mariozechner/pi-ai exposes — four functions stream / complete / streamSimple / completeSimple, plus an internal resolveApiProvider. It does not parse SSE, build request bodies, or handle retries. It just routes the (model, context, options) triple to the right provider based on model.api. Think of it as a dispatch office between callers and provider implementations: callers only see the unified return type AssistantMessageEventStream, and never see whether underneath is Anthropic SSE, OpenAI Responses, or Bedrock Converse.

Responsibilities

This layer does four things:

  1. Dispatch: stream looks up the provider in the registry by model.api and hands off (model, context, options). See packages/ai/src/stream.ts:25-32.
  2. Synchronous stream return: regardless of whether the underlying provider initializes asynchronously (lazy loading), stream synchronously returns an AssistantMessageEventStream and pushes events as they arrive. See packages/ai/src/stream.ts:30-31.
  3. Simplified entry points: streamSimple / completeSimple take SimpleStreamOptions (exposing only the UI-common fields like reasoning, thinkingBudgets), and the provider translates simple options into full options itself. See packages/ai/src/stream.ts:43-50.
  4. Missing provider error: resolveApiProvider throws No API provider registered for api: <api> when nothing is registered, instead of failing silently at the call site. See packages/ai/src/stream.ts:17-23.

Design motivation

Why have such a thin layer? Because the two things callers care about — "which model to call" and "streaming vs. final result" — should be decoupled from the specific provider. stream.ts collapses these two dimensions into four functions. Downstream consumers like pi-coding-agent's AgentSession, the CLI tool, and extensions all use the same entry points without importing concrete provider modules directly. That way provider implementations can be lazily loaded, swapped, or extended with new api registrations, without changing caller code.

complete does not implement a separate non-streaming path; it reuses stream and then calls s.result() — streaming infrastructure and one-shot returns share one pipeline, and providers only need to implement the streaming version.

Key files

The entire implementation of stream is resolve + hand-off, with no business logic:

typescript
// packages/ai/src/stream.ts:25-32
export function stream<TApi extends Api>(
	model: Model<TApi>,
	context: Context,
	options?: ProviderStreamOptions,
): AssistantMessageEventStream {
	const provider = resolveApiProvider(model.api);
	return provider.stream(model, context, options as StreamOptions);
}

complete reuses stream and then takes result(), without duplicating dispatch logic:

typescript
// packages/ai/src/stream.ts:34-41
export async function complete<TApi extends Api>(
	model: Model<TApi>,
	context: Context,
	options?: ProviderStreamOptions,
): Promise<AssistantMessage> {
	const s = stream(model, context, options);
	return s.result();
}

Data flow

Dispatch chain of stream(model, ctx, opts):

Boundaries and failure modes

  • Missing provider: resolveApiProvider throws immediately rather than returning an empty stream, see packages/ai/src/stream.ts:19-21. When an extension's dynamically registered provider is missing, the caller finds out right away.
  • Lazy-loaded providers: concrete provider modules are wrapped with createLazyStream. stream synchronously returns an outer stream, and once the module has loaded it forwardStreams to the inner one, see packages/ai/src/providers/register-builtins.ts:159-178. stream itself is unaware of the lazy loading.
  • Type assertion: options as StreamOptions casts ProviderStreamOptions (which may carry extension-specific fields) to the type the provider expects; the provider reads fields as needed.
  • Optional options: all three functions allow options? to be omitted; providers fall back to defaults.

Summary

stream.ts is a 60-line dispatch facade that routes (model, context, options) to the provider in the registry. stream / complete are the full-options versions, and streamSimple / completeSimple are UI-friendly subset versions. For the registry itself, see ApiProvider registry; for how the 9 built-in providers register, see Provider abstraction and built-ins; for the AssistantMessageEventStream that gets returned, see EventStream async iterator.