Skip to content

ApiProvider-Registrierung: einheitlicher Einstiegspunkt für Provider

源码版本v0.73.1

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

  1. Provider speichern: modulinterne Konstante apiProviderRegistry = new Map<string, RegisteredApiProvider>(), mit api-String als Key. Siehe packages/ai/src/api-registry.ts:35-40.
  2. Assertion-Wickel: wrapStream / wrapStreamSimple prüfen vor dem echten Stream-Aufruf, dass model.api === api, und werfen sonst Mismatched api. Siehe packages/ai/src/api-registry.ts:42-64.
  3. Registrieren/Deregistrieren: registerApiProvider schreibt in die Tabelle, unregisterApiProviders(sourceId) löscht pro source gebündelt, clearApiProviders leert alles. Siehe packages/ai/src/api-registry.ts:66-98.
  4. Lookup: getApiProvider(api) gibt ApiProviderInternal | undefined zurück, aufgerufen von stream.ts. Siehe packages/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

Die Assertion in wrapStream ist eine Zeile model.api !== api:

typescript
// 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:

typescript
// 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: wrapStream prüft vor dem echten Stream-Aufruf; siehe packages/ai/src/api-registry.ts:47-49. Tritt auf, wenn der Aufrufer eine falsche Model-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; siehe packages/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: clearApiProviders gefolgt von registerBuiltInApiProviders(); siehe packages/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.