Skip to content

Registro ApiProvider: punto único de incorporación de providers

源码版本v0.73.1

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

  1. Almacenar providers: la constante de módulo apiProviderRegistry = new Map<string, RegisteredApiProvider>(), con key el string api. Ver packages/ai/src/api-registry.ts:35-40.
  2. Envolver con aserción: wrapStream / wrapStreamSimple afirman model.api === api antes de llamar al stream real; si no coincide, lanzan Mismatched api. Ver packages/ai/src/api-registry.ts:42-64.
  3. Registrar/desregistrar: registerApiProvider escribe en la tabla, unregisterApiProviders(sourceId) borra en lote por source, clearApiProviders la vacía. Ver packages/ai/src/api-registry.ts:66-98.
  4. Consultar la tabla: getApiProvider(api) devuelve ApiProviderInternal | undefined; stream.ts lo invoca. Ver packages/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

La aserción de wrapStream es una sola línea, 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);
	};
}

Al registrar se envuelven ambas funciones y se almacena el provider interno completo:

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,
});

Flujo de datos

El registro y la consulta son dos segmentos:

Límites y fallos

  • api no coincide: wrapStream afirma antes de llamar al stream real. Ver packages/ai/src/api-registry.ts:47-49. Suele ocurrir cuando el llamador tiene una instancia de Model equivocada (pasa un modelo OpenAI a un provider Anthropic).
  • Registro duplicado: apiProviderRegistry.set sobrescribe; gana el último. Las extensiones pueden sobrescribir providers integrados.
  • Sin sourceId: unregisterApiProviders(sourceId) no toca los providers integrados sin sourceId. Ver packages/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: clearApiProviders seguido de registerBuiltInApiProviders(). Ver packages/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.