stream/complete: fachada de entrada a invocaciones LLM
stream.ts es la capa más fina de @mariozechner/pi-ai hacia el exterior: cuatro funciones stream / complete / streamSimple / completeSimple, más un resolveApiProvider interno. No parsea SSE, no construye el cuerpo de la petición, no gestiona reintentos; sólo enruta la terna (model, context, options) al provider correspondiente según model.api. Se puede entender como una "centralita de despacho" entre quien llama y la implementación del provider: el llamador sólo ve el tipo de retorno unificado AssistantMessageEventStream y no sabe si debajo hay SSE de Anthropic, Responses de OpenAI o Converse de Bedrock.
Responsabilidades
Esta capa hace cuatro cosas:
- Despachar:
streambusca en el registro el provider segúnmodel.apiy le entrega(model, context, options). Verpackages/ai/src/stream.ts:25-32. - Retorno síncrono del stream: aunque el provider tarde en inicializarse (lazy loading),
streamdevuelve síncronamente unAssistantMessageEventStream; los eventos se emiten cuando lleguen. Verpackages/ai/src/stream.ts:30-31. - Entrada simplificada:
streamSimple/completeSimpleaceptanSimpleStreamOptions(sólo exponenreasoning,thinkingBudgetsy otros campos típicos de UI), y el provider traduce las opciones simples a opciones completas. Verpackages/ai/src/stream.ts:43-50. - Error si falta provider:
resolveApiProviderlanzaNo API provider registered for api: <api>cuando no hay registro, sin fallar silenciosamente en el punto de invocación. Verpackages/ai/src/stream.ts:17-23.
Motivación de diseño
¿Por qué tener una capa tan fina? Porque las dos cosas que le importan al llamador —"qué modelo usar" y "streaming o resultado final"— están desacopladas del provider concreto. stream.ts condensa esas dos dimensiones en cuatro funciones; el AgentSession de pi-coding-agent, las herramientas del CLI y las extensiones usan la misma entrada, sin necesidad de importar directamente el módulo de un provider concreto. Así la implementación del provider puede ser lazy-loaded, reemplazable, o registrar nuevas api desde extensiones, sin tocar el código del llamador.
complete no duplica la lógica no-streaming, sino que reutiliza stream y luego s.result(): la infraestructura de streaming y la de resultado único comparten una sola tubería; cada provider sólo implementa la versión streaming.
Archivos clave
packages/ai/src/stream.ts:1-3— Arriba,import "./providers/register-builtins.js"dispara el auto-registro de los 9 providers integrados;getApiProviderlos lee del registro.packages/ai/src/stream.ts:17-23—resolveApiProvider, lanza si falta el provider.packages/ai/src/stream.ts:25-32—streamversión con todas las opciones.packages/ai/src/stream.ts:34-41—completeequivale astream(...).result().packages/ai/src/stream.ts:43-50—streamSimpledelega aprovider.streamSimple.packages/ai/src/stream.ts:52-59—completeSimpleequivale astreamSimple(...).result().
La implementación entera de stream es resolve + delegar, sin lógica de negocio:
// packages/ai/src/stream.ts:25-32
export function stream<TApi extends Api>(
model: Model<TApi>,
context: Context,
options?: ProviderStreamOptions,
): AssistantMessageEventStream {
const provider = resolveApiProvider(model.api);
return provider.stream(model, context, options as StreamOptions);
}complete reutiliza stream y luego toma result(), sin duplicar la lógica de despacho:
// packages/ai/src/stream.ts:34-41
export async function complete<TApi extends Api>(
model: Model<TApi>,
context: Context,
options?: ProviderStreamOptions,
): Promise<AssistantMessage> {
const s = stream(model, context, options);
return s.result();
}Flujo de datos
Cadena de despacho de stream(model, ctx, opts):
Límites y fallos
- Provider ausente:
resolveApiProviderlanza directamente, no devuelve un stream vacío. Verpackages/ai/src/stream.ts:19-21. Si una extensión registra un provider dinámicamente y aún no está registrado, el llamador se entera al instante. - Provider lazy-loaded: los módulos provider concretos se envuelven con
createLazyStream;streamdevuelve síncronamente el outer stream, y cuando el módulo termina de cargar haceforwardStreamal inner. Verpackages/ai/src/providers/register-builtins.ts:159-178.streamno se entera del lazy loading. - Type assertion:
options as StreamOptionshace un cast fuerte deProviderStreamOptions(con campos personalizados de extensión) al tipo que espera el provider; el provider lee los campos que necesita. optionsopcional: las tres funciones permiten omitiroptions?; el provider usa valores por defecto.
Resumen
stream.ts es una fachada de despacho de 60 líneas que enruta (model, context, options) al provider del registro. stream / complete son las versiones con todas las opciones, streamSimple / completeSimple son las versiones friendly para UI. La estructura del registro se ve en registro de provider; el registro de los 9 providers integrados en abstracción de provider y built-ins; y el AssistantMessageEventStream final en EventStream async iterator.