Provider-Abstraktion und 9 eingebaute Registrierungen
@mariozechner/pi-ai definiert einen Provider über nur drei Felder: api, stream, streamSimple. register-builtins.ts registriert mit registerApiProvider die Implementierungen von 9 Anbietern in der Tabelle. Statt das konkrete Provider-Modul direkt zu importieren, wird ein dynamic import über createLazyStream / createLazySimpleStream gewickelt — erst beim ersten Aufruf wird das SDK geladen; Modulgröße und Startzeit bleiben verschont.
Verantwortung
- Provider-Abstraktion definieren: die
ApiProvider-Schnittstelle verlangt einenapi-Bezeichner plus die zwei Funktionenstream/streamSimple; siehepackages/ai/src/api-registry.ts:23-27. - Lazy-Loading-Wickel:
createLazyStream/createLazySimpleStreamgeben eine synchrone Stream-Funktion zurück, die intern perimport()das Modul lädt und dannforwardStreamaufruft; siehepackages/ai/src/providers/register-builtins.ts:159-178. - Registrierung von 9 Anbietern:
registerBuiltInApiProvidersruft pro AnbieterregisterApiProviderauf; siehepackages/ai/src/providers/register-builtins.ts:342-396. - Modul-Laden heißt Registrieren: am Dateiende wird auf oberster Ebene
registerBuiltInApiProviders()aufgerufen; sobaldstream.tses importiert, wird es getriggert; siehepackages/ai/src/providers/register-builtins.ts:403-403. - 9 Api-Typen: der
KnownApi-Union-Typ zählt 9 String-Literale auf; siehepackages/ai/src/types.ts:6-17.
Entwurfsmotivation
Warum alle Provider lazy laden? Weil manche SDKs schwer sind — Bedrock zieht das gesamte @aws-sdk/client-bedrock-runtime, OpenAI Responses zieht einen Haufen Abhängigkeiten. Bei statischem Top-Level-Import müsste auch geladen werden, wer nur Anthropic nutzt. createLazyStream macht den ersten Aufruf zu einem dynamic import; beim Start wird nur register-builtins.ts selbst mit seinen paar Dutzend Zeilen geparst; erst wer einen Anbieter nutzt, lädt ihn.
Warum wird registerBuiltInApiProviders am Dateiende auf oberster Ebene aufgerufen und nicht exportiert, damit der Aufrufer entscheidet? Weil stream.ts oben import "./providers/register-builtins.js" steht — ein Side-Effect-Import; sobald irgendwer stream aus @mariozechner/pi-ai importiert, ist die Registrierung passiert. Aufrufer haben Zero-Config. resetApiProviders dient dem Reset in Tests.
Wichtige Dateien
packages/ai/src/types.ts:6-17—KnownApimit 9 Literalen;Apierlaubt erweiternde Strings.packages/ai/src/api-registry.ts:23-27—ApiProvider-Schnittstelle, das Dreigestirn.packages/ai/src/providers/register-builtins.ts:159-178—createLazyStream: outer stream + dynamisches Laden + forwardStream + Fehler-push.packages/ai/src/providers/register-builtins.ts:180-220—createLazySimpleStream, Simple-Variante, gleiches Muster.packages/ai/src/providers/register-builtins.ts:323-340— 9 Lazy-Funktionen + private Bedrock-Lazy.packages/ai/src/providers/register-builtins.ts:342-396— Body vonregisterBuiltInApiProviders.packages/ai/src/providers/register-builtins.ts:398-403—resetApiProvidersund Top-Level-Registrierung.packages/ai/src/types.ts:155-159—StreamFunction-Signatur, gibtAssistantMessageEventStreamzurück.
Kern des Lazy-Wickels: der outer stream kehrt sofort zurück; bei Lade-Fehlern wird per push ein Error-Event gesendet:
// packages/ai/src/providers/register-builtins.ts:159-178
function createLazyStream<TApi extends Api, TOptions extends StreamOptions, TSimpleOptions extends SimpleStreamOptions>(
loadModule: () => Promise<LazyProviderModule<TApi, TOptions, TSimpleOptions>>,
): StreamFunction<TApi, TOptions> {
return (model, context, options) => {
const outer = new AssistantMessageEventStream();
loadModule()
.then((module) => {
const inner = module.stream(model, context, options);
forwardStream(outer, inner);
})
.catch((error) => {
const message = createLazyLoadErrorMessage(model, error);
outer.push({ type: "error", reason: "error", error: message });
outer.end(message);
});
return outer;
};
}Die Registrierung der 9 Anbieter ist jeweils ein registerApiProvider mit einem api-Literal:
// packages/ai/src/providers/register-builtins.ts:343-347
registerApiProvider({
api: "anthropic-messages",
stream: streamAnthropic,
streamSimple: streamSimpleAnthropic,
});Datenfluss
Provider-Registrierung und erster Aufruf in zwei Abschnitten:
Grenzen und Fehler
- Lazy-Laden schlägt fehl:
loadModule().catchverpackt den Fehler als{ type: "error" }-Event und pusht es in den outer stream, sodass der Aufrufer perfor awaites bekommt; siehepackages/ai/src/providers/register-builtins.ts:170-174. - Liste der 9:
anthropic-messages,openai-completions,mistral-conversations,openai-responses,azure-openai-responses,openai-codex-responses,google-generative-ai,google-vertex,bedrock-converse-stream; siehepackages/ai/src/providers/register-builtins.ts:343-395. - Bedrock separat: wegen des Gewichts des AWS SDK sind
streamBedrockLazy/streamSimpleBedrockLazynicht exportiert, sondern nur intern genutzt; siehepackages/ai/src/providers/register-builtins.ts:339-340. - Erweiterungs-Registrierung: Drittanbieter-Erweiterungen können
registerApiProvidermit einem eigenenapi-String aufrufen; derApi-Typ wird überstring & {}aufgefangen; siehepackages/ai/src/types.ts:17-17.
Zusammenfassung
Die Provider-Abstraktion ist das Dreigestirn aus api + stream + streamSimple; alle 9 Built-ins sind über createLazyStream lazy geladen. Die Verteillogik der Fassade steht in stream/complete-Fassade, die Datenstruktur der Registrierung in Provider-Registrierung, und wie eine konkrete Familie SSE parst in Anthropic SSE-Implementierung.