Abstracción de provider y registro de 9 built-ins
@mariozechner/pi-ai define al provider con sólo tres campos: api, stream, streamSimple. register-builtins.ts registra las implementaciones de 9 proveedores usando registerApiProvider. Al registrar, no se importa directamente el módulo del provider concreto, sino que se envuelve con createLazyStream / createLazySimpleStream para hacer un dynamic import: la primera invocación carga el SDK, así ni el tamaño del módulo ni el tiempo de arranque se ven afectados.
Responsabilidades
- Definir la abstracción Provider: la interfaz
ApiProviderexige el identificadorapi+ dos funcionesstream/streamSimple. Verpackages/ai/src/api-registry.ts:23-27. - Envoltorio lazy loading:
createLazyStream/createLazySimpleStreamdevuelven una función de stream síncrona que internamente haceimport()del módulo concreto y luegoforwardStream. Verpackages/ai/src/providers/register-builtins.ts:159-178. - Registrar 9 providers:
registerBuiltInApiProvidersinvocaregisterApiProviderpara cada uno. Verpackages/ai/src/providers/register-builtins.ts:342-396. - Cargar el módulo es registrar: al final del archivo se llama a
registerBuiltInApiProviders()al nivel superior, de modo que basta con questream.tslo importe para dispararlo. Verpackages/ai/src/providers/register-builtins.ts:403-403. - 9 tipos Api: el tipo unión
KnownApienumera 9 literales. Verpackages/ai/src/types.ts:6-17.
Motivación de diseño
¿Por qué todos los providers son lazy-loaded? Porque algunos SDK son pesados: Bedrock arrastra todo el @aws-sdk/client-bedrock-runtime de AWS, OpenAI Responses se trae un montón de dependencias. Si se importaran estáticamente al nivel superior, aunque el usuario sólo usara Anthropic, tendría que cargar todos los SDK. createLazyStream convierte la primera invocación en un dynamic import; al arrancar sólo se parsean las pocas líneas de register-builtins.ts, y cada provider se carga sólo cuando se usa.
¿Por qué registerBuiltInApiProviders se llama al final del archivo en lugar de exportarse para que el llamador decida? Porque stream.ts arriba hace import "./providers/register-builtins.js", un import de efectos secundarios: en cuanto alguien importa stream desde @mariozechner/pi-ai, el registro ocurre. El llamador no necesita configuración. resetApiProviders queda para resetear en tests.
Archivos clave
packages/ai/src/types.ts:6-17—KnownApicon 9 literales +Apipermite extender con strings.packages/ai/src/api-registry.ts:23-27— InterfazApiProvider, el tríptico.packages/ai/src/providers/register-builtins.ts:159-178—createLazyStream: outer stream + carga dinámica + forwardStream + push de error.packages/ai/src/providers/register-builtins.ts:180-220—createLazySimpleStream, mismo patrón en versión simple.packages/ai/src/providers/register-builtins.ts:323-340— 9 funciones lazy + lazy privado de Bedrock.packages/ai/src/providers/register-builtins.ts:342-396— Cuerpo deregisterBuiltInApiProviders.packages/ai/src/providers/register-builtins.ts:398-403—resetApiProvidersy registro al nivel superior.packages/ai/src/types.ts:155-159— Firma deStreamFunction, devuelveAssistantMessageEventStream.
El núcleo del envoltorio lazy: el outer stream se devuelve enseguida; los errores de carga también se traducen en eventos de error vía push:
// packages/ai/src/providers/register-builtins.ts:159-178
function createLazyStream<TApi extends Api, TOptions extends StreamOptions, TSimpleOptions extends SimpleStreamOptions>(
loadModule: () => Promise<LazyProviderModule<TApi, TOptions, TSimpleOptions>>,
): StreamFunction<TApi, TOptions> {
return (model, context, options) => {
const outer = new AssistantMessageEventStream();
loadModule()
.then((module) => {
const inner = module.stream(model, context, options);
forwardStream(outer, inner);
})
.catch((error) => {
const message = createLazyLoadErrorMessage(model, error);
outer.push({ type: "error", reason: "error", error: message });
outer.end(message);
});
return outer;
};
}El registro de los 9 providers es un registerApiProvider por cada uno, cada uno con su literal api:
// packages/ai/src/providers/register-builtins.ts:343-347
registerApiProvider({
api: "anthropic-messages",
stream: streamAnthropic,
streamSimple: streamSimpleAnthropic,
});Flujo de datos
El registro y la primera invocación, en dos tramos:
Límites y fallos
- Fallo de lazy loading:
loadModule().catchenvuelve el error en un evento{ type: "error" }que se empuja al outer stream, de modo que elfor awaitdel llamador lo recibe. Verpackages/ai/src/providers/register-builtins.ts:170-174. - Lista de 9:
anthropic-messages,openai-completions,mistral-conversations,openai-responses,azure-openai-responses,openai-codex-responses,google-generative-ai,google-vertex,bedrock-converse-stream. Verpackages/ai/src/providers/register-builtins.ts:343-395. - Bedrock aparte: dado el peso del SDK de AWS,
streamBedrockLazy/streamSimpleBedrockLazyno se exportan, son internos. Verpackages/ai/src/providers/register-builtins.ts:339-340. - Registro desde extensiones: una extensión de terceros puede llamar a
registerApiProviderpara registrar su propio stringapi; el tipoApiusastring & {}como fallback. Verpackages/ai/src/types.ts:17-17.
Resumen
La abstracción de provider = el tríptico api + stream + streamSimple; los 9 built-ins pasan todos por createLazyStream para lazy loading. La lógica de despacho de la fachada en fachada stream/complete, la estructura de datos del registro en registro de provider, y el parseo SSE de un provider concreto en implementación SSE de Anthropic.