stream/complete: Fassadeneingang des LLM-Aufrufs
stream.ts ist die dünnste Schicht von @mariozechner/pi-ai nach außen — vier Funktionen stream / complete / streamSimple / completeSimple, plus eine interne resolveApiProvider. Sie parst kein SSE, baut keinen Request-Body, macht keine Retries, sondern routet nur das Triple (model, context, options) anhand von model.api an den passenden Provider (provider). Man kann sie als „Verteilzentrale zwischen Aufrufer und Provider-Implementierung" begreifen: der Aufrufer sieht nur den einheitlichen Rückgabetyp AssistantMessageEventStream, nicht ob darunter Anthropic SSE, OpenAI Responses oder Bedrock Converse liegt.
Verantwortung
Diese Schicht macht vier Dinge:
- Verteilung:
streamsucht anhand vonmodel.apiden Provider in der Registrierung (registry) und reicht(model, context, options)weiter. Siehepackages/ai/src/stream.ts:25-32. - Synchrone Stream-Rückgabe: egal ob der darunterliegende Provider asynchron initialisiert wird (lazy loading),
streamgibt synchron einenAssistantMessageEventStreamzurück; Events werden später hineingepusht. Siehepackages/ai/src/stream.ts:30-31. - Simple-Eingänge:
streamSimple/completeSimplenehmenSimpleStreamOptions(nur die für UIs üblichen Felder wiereasoning,thinkingBudgets) und der Provider übersetzt die Simple-Options selbst zu vollen Options. Siehepackages/ai/src/stream.ts:43-50. - Fehler bei fehlendem Provider:
resolveApiProviderwirftNo API provider registered for api: <api>, wenn nichts registriert ist, anstatt an der Aufrufstelle stumm zu scheitern. Siehepackages/ai/src/stream.ts:17-23.
Entwurfsmotivation
Warum diese dünne Schicht? Weil die zwei Dinge, die den Aufrufer interessieren — „welches Modell rufe ich auf" und „streaming oder Endergebnis" — vom konkreten Provider entkoppelt sind. stream.ts zieht diese zwei Dimensionen auf vier Funktionen zusammen; AgentSession von pi-coding-agent, CLI-Werkzeuge und Erweiterungen nutzen alle denselben Eingang und müssen keine konkreten Provider-Module direkt importieren. So können Provider lazy geladen, ausgetauscht oder durch Erweiterungen mit neuem api registriert werden — der Aufrufercode bleibt unverändert.
complete implementiert keine eigene nicht-streaming-Logik, sondern wiederverwendet stream und ruft s.result() auf — die Streaming-Infrastruktur und die Einmal-Rückgabe teilen sich eine Pipeline; Provider brauchen nur die Streaming-Variante.
Wichtige Dateien
packages/ai/src/stream.ts:1-3— obenimport "./providers/register-builtins.js"löst die Selbstregistrierung der 9 eingebauten Provider aus;getApiProviderholt aus der Registrierung.packages/ai/src/stream.ts:17-23—resolveApiProvider, wirft bei fehlendem Provider.packages/ai/src/stream.ts:25-32—streamin der Voll-Optionen-Variante.packages/ai/src/stream.ts:34-41—completeentsprichtstream(...).result().packages/ai/src/stream.ts:43-50—streamSimplegeht überprovider.streamSimple.packages/ai/src/stream.ts:52-59—completeSimpleentsprichtstreamSimple(...).result().
Die gesamte Implementierung von stream ist resolve + weiterreichen, ohne Geschäftslogik:
// 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 wiederverwendet stream und holt dann result(), ohne die Verteillogik zu duplizieren:
// 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();
}Datenfluss
Die Verteilungskette von stream(model, ctx, opts):
Grenzen und Fehler
- Fehlender Provider:
resolveApiProviderwirft direkt, statt einen leeren Stream zu liefern; siehepackages/ai/src/stream.ts:19-21. Wenn ein per Erweiterung dynamisch registrierter Provider nicht registriert ist, erfährt der Aufrufer es sofort. - Lazy geladene Provider: das konkrete Provider-Modul ist mit
createLazyStreamumwickelt;streamgibt synchron einen outer stream zurück; sobald das Modul geladen ist, wird perforwardStreaman den inner weitergeleitet, siehepackages/ai/src/providers/register-builtins.ts:159-178.streamselbst weiß nichts vom Lazy Loading. - Type Assertion:
options as StreamOptionscastetProviderStreamOptions(mit erweiterungsspezifischen Feldern) auf den vom Provider erwarteten Typ; der Provider liest Felder nach Bedarf. optionsoptional: alle drei Funktionen erlaubenoptions?zu fehlen; der Provider fängt mit Default-Werten ab.
Zusammenfassung
stream.ts ist eine 60-Zeilen-Fassade, die (model, context, options) an den Provider in der Registrierung routet. stream / complete sind die Voll-Optionen-Variante, streamSimple / completeSimple die UI-freundliche Teilmenge. Die Struktur der Registrierung selbst steht in Provider-Registrierung, die Registrierung der 9 eingebauten Provider in Provider-Abstraktion und Built-ins, und der zurückgegebene AssistantMessageEventStream in EventStream Async-Iterator.