Registre ApiProvider : point d'entrée unifié des provider
api-registry.ts est le cœur du registre des provider (registry)—une Map<string, RegisteredApiProvider>, dont la clé est la chaîne api (par ex. "anthropic-messages", "openai-responses") et la valeur un provider enveloppé de wrapStream / wrapStreamSimple. Tous les provider—les 9 intégrés, comme ceux enregistrés dynamiquement par des extensions—passent par le même registre. Le resolveApiProvider de stream.ts ne fait que consulter cette table.
Responsabilités
- Stocker les provider : constante de module
apiProviderRegistry = new Map<string, RegisteredApiProvider>(), avec la chaîneapicomme clé. Voirpackages/ai/src/api-registry.ts:35-40. - Envelopper d'une assertion :
wrapStream/wrapStreamSimpleassertentmodel.api === apiavant d'appeler le vrai stream, sinon jettentMismatched api. Voirpackages/ai/src/api-registry.ts:42-64. - Enregistrer/désenregistrer :
registerApiProviderécrit dans la table,unregisterApiProviders(sourceId)supprime par batch selon la source,clearApiProvidersvide tout. Voirpackages/ai/src/api-registry.ts:66-98. - Consulter la table :
getApiProvider(api)renvoieApiProviderInternal | undefined, appelé parstream.ts. Voirpackages/ai/src/api-registry.ts:80-82.
Motivation de design
Pourquoi envelopper le provider d'un wrapStream ? Parce que le système de types ne peut pas attraper « l'appelant a un Model<"openai-responses"> mais le passe à streamAnthropic »—le Model<TApi> de TS est générique, à l'exécution model.api seule détient la vérité. wrapStream ferme par closure l'api du provider au moment du registerApiProvider, au point d'appel il n'y a qu'une vérification model.api !== api, qui transforme l'erreur de type en erreur runtime, et la pile pointe directement vers l'appel discordant.
Pourquoi utiliser un sourceId plutôt qu'un delete direct ? Les provider enregistrés par une extension peuvent vivre au-delà du processus et des redémarrages; on ne permet à une extension de nettoyer que ce qu'elle a enregistré. sourceId donne à unregisterApiProviders une clé de filtrage stable; les provider intégrés ne portent pas de sourceId et ne sont pas supprimés par erreur.
Fichiers clés
packages/ai/src/api-registry.ts:23-27— interfaceApiProvider:api+stream+streamSimple, le provider doit fournir les trois.packages/ai/src/api-registry.ts:29-38—ApiProviderInternaletRegisteredApiProvider, portentsourceIden interne.packages/ai/src/api-registry.ts:40-40— instance MapapiProviderRegistry.packages/ai/src/api-registry.ts:42-52—wrapStream: assertionmodel.api !== api.packages/ai/src/api-registry.ts:54-64—wrapStreamSimple, même assertion.packages/ai/src/api-registry.ts:66-78—registerApiProvider: écrit dans la table + wrap.packages/ai/src/api-registry.ts:80-82—getApiProvider: consultation.packages/ai/src/api-registry.ts:88-98—unregisterApiProviders/clearApiProviders, pour reset de test et d'extension.
L'assertion de wrapStream se résume à une ligne 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);
};
}À l'enregistrement, on wrap les deux fonctions et on stocke le internal provider complet :
// 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,
});Flux de données
Enregistrement de provider et consultation, en deux temps :
Frontières et échecs
- api discordant :
wrapStreamasserte avant d'appeler le vrai stream, voirpackages/ai/src/api-registry.ts:47-49. Arrive typiquement quand l'appelant a une mauvaise instanceModel(par ex. un modèle OpenAI passé à un provider Anthropic). - Enregistrement en doublon :
apiProviderRegistry.setécrase, le dernier à s'enregistrer gagne. Une extension peut écraser un provider intégré. - Sans sourceId :
unregisterApiProviders(sourceId)ne touche pas aux provider intégrés sans sourceId, voirpackages/ai/src/api-registry.ts:89-92. getApiProviders: renvoie le tableau de tous les provider, utilisé par l'UI pour lister les « provider disponibles ».- Reset de test :
clearApiProviderspuisregisterBuiltInApiProviders(), voirpackages/ai/src/providers/register-builtins.ts:398-401.
Récapitulatif
api-registry.ts est une Map + deux fonctions wrap, qui transforment une non-correspondance de type d'énigme runtime en erreur explicite. Le triplet api + stream + streamSimple du provider est défini dans Abstraction provider et intégrations; l'implémentation concrète du stream d'un provider donné se lit dans Implémentation Anthropic SSE. La logique de dispatch de la façade de streaming se lit dans Façade stream/complete.