Registro ApiProvider: punto único de incorporación de providers
api-registry.ts es el centro del registro de providers: un Map<string, RegisteredApiProvider> con key el string api (como "anthropic-messages" o "openai-responses"), y como valor un provider envuelto con wrapStream / wrapStreamSimple. Todos los providers —los 9 integrados, los registrados dinámicamente por extensiones— pasan por el mismo registro. El resolveApiProvider de stream.ts simplemente consulta esta tabla.
Responsabilidades
- Almacenar providers: la constante de módulo
apiProviderRegistry = new Map<string, RegisteredApiProvider>(), con key el stringapi. Verpackages/ai/src/api-registry.ts:35-40. - Envolver con aserción:
wrapStream/wrapStreamSimpleafirmanmodel.api === apiantes de llamar al stream real; si no coincide, lanzanMismatched api. Verpackages/ai/src/api-registry.ts:42-64. - Registrar/desregistrar:
registerApiProviderescribe en la tabla,unregisterApiProviders(sourceId)borra en lote por source,clearApiProvidersla vacía. Verpackages/ai/src/api-registry.ts:66-98. - Consultar la tabla:
getApiProvider(api)devuelveApiProviderInternal | undefined;stream.tslo invoca. Verpackages/ai/src/api-registry.ts:80-82.
Motivación de diseño
¿Por qué envolver al provider con un wrapStream? Porque el sistema de tipos no puede garantizar "el llamador tiene un Model<"openai-responses"> pero lo pasa a streamAnthropic": el Model<TApi> de TS es genérico, y en runtime model.api es la verdad. wrapStream captura el api del provider al registrarlo y lo cierra en la aserción; el punto de llamada hace un único chequeo model.api !== api, convirtiendo un error de tipos en un error de runtime con stack apuntando a la invocación equivocada.
¿Por qué usar sourceId en vez de un delete directo? Los providers registrados por extensiones pueden vivir entre procesos y reinicios; una extensión sólo debería poder limpiar los que ella registró. sourceId da a unregisterApiProviders una clave estable de filtrado. Los providers integrados no llevan sourceId al registrarse, así no se borran por accidente.
Archivos clave
packages/ai/src/api-registry.ts:23-27— InterfazApiProvider:api+stream+streamSimple; el provider debe dar los tres.packages/ai/src/api-registry.ts:29-38—ApiProviderInternalyRegisteredApiProvider, consourceIdinterno.packages/ai/src/api-registry.ts:40-40— Instancia del MapapiProviderRegistry.packages/ai/src/api-registry.ts:42-52—wrapStream: aserciónmodel.api !== api.packages/ai/src/api-registry.ts:54-64—wrapStreamSimple, misma aserción.packages/ai/src/api-registry.ts:66-78—registerApiProvider: escribe en la tabla y aplica wrap.packages/ai/src/api-registry.ts:80-82—getApiProvider: consulta.packages/ai/src/api-registry.ts:88-98—unregisterApiProviders/clearApiProviders, para reset de tests y extensiones.
La aserción de wrapStream es una sola línea, 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);
};
}Al registrar se envuelven ambas funciones y se almacena el provider interno completo:
// 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,
});Flujo de datos
El registro y la consulta son dos segmentos:
Límites y fallos
- api no coincide:
wrapStreamafirma antes de llamar al stream real. Verpackages/ai/src/api-registry.ts:47-49. Suele ocurrir cuando el llamador tiene una instancia deModelequivocada (pasa un modelo OpenAI a un provider Anthropic). - Registro duplicado:
apiProviderRegistry.setsobrescribe; gana el último. Las extensiones pueden sobrescribir providers integrados. - Sin sourceId:
unregisterApiProviders(sourceId)no toca los providers integrados sin sourceId. Verpackages/ai/src/api-registry.ts:89-92. getApiProviders: devuelve el array de todos los providers, útil cuando la UI lista "providers disponibles".- Reset de tests:
clearApiProvidersseguido deregisterBuiltInApiProviders(). Verpackages/ai/src/providers/register-builtins.ts:398-401.
Resumen
api-registry.ts es un Map + dos funciones de wrap, que convierte un desajuste de tipos de misterio en runtime a un error explícito. El tríptico api + stream + streamSimple del provider se define en abstracción de provider y built-ins; cómo implementa stream un provider concreto en implementación SSE de Anthropic. La lógica de despacho de la fachada en fachada stream/complete.