ApiProvider-Registrierung: einheitlicher Einstiegspunkt für Provider
api-registry.ts ist das Zentrum der Provider-Registrierung (provider registry) — eine Map<string, RegisteredApiProvider>, deren Key der api-String ist (z. B. "anthropic-messages", "openai-responses"), und deren Wert ein um wrapStream / wrapStreamSimple gewickelter Provider ist. Alle Provider — die 9 eingebauten wie die dynamisch registrierten aus Erweiterungen — laufen über dieselbe Registrierung. resolveApiProvider in stream.ts ist einfach ein Lookup in dieser Tabelle.
Verantwortung
- Provider speichern: modulinterne Konstante
apiProviderRegistry = new Map<string, RegisteredApiProvider>(), mitapi-String als Key. Siehepackages/ai/src/api-registry.ts:35-40. - Assertion-Wickel:
wrapStream/wrapStreamSimpleprüfen vor dem echten Stream-Aufruf, dassmodel.api === api, und werfen sonstMismatched api. Siehepackages/ai/src/api-registry.ts:42-64. - Registrieren/Deregistrieren:
registerApiProviderschreibt in die Tabelle,unregisterApiProviders(sourceId)löscht pro source gebündelt,clearApiProvidersleert alles. Siehepackages/ai/src/api-registry.ts:66-98. - Lookup:
getApiProvider(api)gibtApiProviderInternal | undefinedzurück, aufgerufen vonstream.ts. Siehepackages/ai/src/api-registry.ts:80-82.
Entwurfsmotivation
Warum wrapStream um den Provider wickeln? Weil das Typsystem Fehler der Form „Aufrufer hat ein Model<"openai-responses">, reicht es aber an streamAnthropic" nicht abfängt — Model<TApi> in TS ist generisch; zur Laufzeit ist model.api die Wahrheit. wrapStream schließt beim registerApiProvider-Aufruf den api-Wert in die Assertion ein, sodass am Aufrufort nur ein model.api !== api-Check nötig ist; der Typfehler wird zu einem Laufzeitfehler und der Stacktrace zeigt direkt auf den falschen Aufruf.
Warum ein sourceId statt direktem delete? Erweiterungs-Provider können prozess- und restart-übergreifend sein; Erweiterungen dürfen nur ihre eigenen Registrierungen löschen. sourceId gibt unregisterApiProviders einen stabilen Filterkey, und eingebaute Provider werden ohne sourceId registriert, sodass sie nicht versehentlich gelöscht werden.
Wichtige Dateien
packages/ai/src/api-registry.ts:23-27—ApiProvider-Schnittstelle:api+stream+streamSimple; ein Provider muss alle drei liefern.packages/ai/src/api-registry.ts:29-38—ApiProviderInternalundRegisteredApiProvider, intern mitsourceId.packages/ai/src/api-registry.ts:40-40—apiProviderRegistry-Map-Instanz.packages/ai/src/api-registry.ts:42-52—wrapStream: diemodel.api !== api-Assertion.packages/ai/src/api-registry.ts:54-64—wrapStreamSimple, analoge Assertion.packages/ai/src/api-registry.ts:66-78—registerApiProvider: Schreiben + Wrapping.packages/ai/src/api-registry.ts:80-82—getApiProvider: Tabellen-Lookup.packages/ai/src/api-registry.ts:88-98—unregisterApiProviders/clearApiProviders, für Tests und Erweiterungs-Resets.
Die Assertion in wrapStream ist eine Zeile model.api !== api:
// packages/ai/src/api-registry.ts:42-52
function wrapStream<TApi extends Api, TOptions extends StreamOptions>(
api: TApi,
stream: StreamFunction<TApi, TOptions>,
): ApiStreamFunction {
return (model, context, options) => {
if (model.api !== api) {
throw new Error(`Mismatched api: ${model.api} expected ${api}`);
}
return stream(model as Model<TApi>, context, options as TOptions);
};
}Bei der Registrierung werden beide Funktionen gewrappt und der vollständige interne Provider gespeichert:
// packages/ai/src/api-registry.ts:70-77
apiProviderRegistry.set(provider.api, {
provider: {
api: provider.api,
stream: wrapStream(provider.api, provider.stream),
streamSimple: wrapStreamSimple(provider.api, provider.streamSimple),
},
sourceId,
});Datenfluss
Provider-Registrierung und Lookup in zwei Abschnitten:
Grenzen und Fehler
- api passt nicht:
wrapStreamprüft vor dem echten Stream-Aufruf; siehepackages/ai/src/api-registry.ts:47-49. Tritt auf, wenn der Aufrufer eine falscheModel-Instanz hat (z. B. OpenAI-Modell an Anthropic-Provider). - Doppelte Registrierung:
apiProviderRegistry.setüberschreibt direkt; der zuletzt Registrierte gewinnt. Erweiterungen können eingebaute Provider überschreiben. - Fehlender sourceId:
unregisterApiProviders(sourceId)lässt eingebaute Provider ohne sourceId unangetastet; siehepackages/ai/src/api-registry.ts:89-92. getApiProviders: gibt ein Array aller Provider zurück; verwendet, wenn die UI eine Liste „verfügbarer Provider" anzeigt.- Test-Reset:
clearApiProvidersgefolgt vonregisterBuiltInApiProviders(); siehepackages/ai/src/providers/register-builtins.ts:398-401.
Zusammenfassung
api-registry.ts ist eine Map plus zwei Wrap-Funktionen; sie machen aus einem Typ-Mismatch zur Laufzeit einen klaren Fehler statt eines Rätsels. Das Dreigestirn aus api + stream + streamSimple eines Providers ist in Provider-Abstraktion und Built-ins definiert; wie eine konkrete Familie stream implementiert, steht in Anthropic SSE-Implementierung. Die Verteillogik der Fassade steht in stream/complete-Fassade.