Contrato de tipos
packages/agent/src/types.ts es el contrato completo de tipos que pi-agent-core expone. Define el StreamFn de entrada al bucle, el ToolExecutionMode para el scheduling de herramientas, el AgentLoopConfig que ensambla el bucle, el AgentState de estado en runtime, el protocolo de herramientas AgentTool, la instantánea de contexto AgentContext, el flujo de eventos AgentEvent y la unión de mensajes extensible AgentMessage. Esta capa es deliberadamente sólo interface/type sin código en runtime, para que AgentSession/Agent/runLoop dependan de abstracciones y no de concreciones.
Responsabilidades
- Contrato de la función de stream:
StreamFnreutiliza la firma destreamSimpley exige que la implementación no lance ante fallos de petición/modelo; los fallos se codifican en el stream como eventoerrorystopReason: "error". Verpackages/agent/src/types.ts:15-26. - Modo de ejecución de herramientas:
ToolExecutionMode = "sequential" | "parallel", aparece tanto enAgentLoopConfig.toolExecutioncomo enAgentTool.executionMode(override por herramienta). Verpackages/agent/src/types.ts:28-36. - Configuración del bucle:
AgentLoopConfig extends SimpleStreamOptions, incluyemodel/convertToLlm/transformContext/getApiKey/shouldStopAfterTurn/getSteeringMessages/getFollowUpMessages/toolExecution/beforeToolCall/afterToolCall. Verpackages/agent/src/types.ts:115-248. - Estado en runtime:
AgentStatedeclaratoolsymessagescon setter/getter, permitiendo a la implementación copiar el array top-level;isStreaming/streamingMessage/pendingToolCalls/errorMessageson todos readonly. Verpackages/agent/src/types.ts:288-313. - Protocolo de herramientas:
AgentTool<TParameters, TDetails>extiende elToolde pi-ai y añadelabel/prepareArguments/execute/executionMode;executerecibetoolCallId/params/signal/onUpdate. Verpackages/agent/src/types.ts:332-355. - Unión de eventos:
AgentEventes una unión discriminada de 9 eventos, organizada por los cuatro ciclos de vida agent/turn/message/tool execution. Verpackages/agent/src/types.ts:374-389.
Motivación de diseño
¿Por qué AgentMessage es Message | CustomAgentMessages[keyof CustomAgentMessages]? Porque el asistente de codificación quiere mezclar en el transcript "mensajes sólo para UI" (artifact, notification, resumen de thinking) que no deberían enviarse al LLM. CustomAgentMessages es por defecto una interfaz vacía; la capa de aplicación usa declaration merging para añadir sus propios roles, y convertToLlm los filtra o convierte a user/assistant/toolResult que el LLM pueda entender. Este diseño permite extender el tipo del transcript en tiempo de compilación, sin degenerar a any.
¿Por qué el contrato de los hooks insiste tanto en "no lanzar"? Porque runLoop es un flujo de eventos single-threaded: si un callback lanza, interrumpe el bucle sin producir la secuencia normal de eventos agent_end, y la UI se queda en isStreaming=true. Por eso la documentación de convertToLlm/transformContext/getApiKey/shouldStopAfterTurn/getSteeringMessages/getFollowUpMessages dice "must not throw or reject, devuelve un fallback". Las excepciones de los hooks en los puntos de invocación se capturan con try/catch y se convierten en resultados de error, pero los callbacks del bucle mismo no se pueden proteger: es una restricción dura del contrato de tipos.
¿Por qué BeforeToolCallResult sólo tiene dos campos opcionales block/reason y no un resultado completo? Porque la fase before sólo decide "dejarlo correr o no", no "cuál es el resultado". Si se bloquea, el bucle construye un resultado de error y el reason va al content. Así el hook before no tiene que conocer la forma concreta del resultado de la herramienta; while que el hook after (AfterToolCallResult) sí permite sobrescribir por campo content/details/isError/terminate, porque ahí el resultado ya existe.
¿Por qué execute de AgentTool usa genéricos TParameters/TDetails? TParameters se restringe a TSchema (typebox); Static<TParameters> es el tipo de parámetros deducido en compilación; TDetails es la forma de details definida por la herramienta. Así la implementación recibe params fuertemente tipado, mientras que el lado del bucle almacena con AgentTool<any> para listas heterogéneas, con seguridad de tipos y compatibilidad a la vez.
Archivos clave
packages/agent/src/types.ts:15-26—StreamFn: la firma reutilizastreamSimple; el contrato exige que los fallos se codifiquen en el stream, no se lancen.packages/agent/src/types.ts:28-36— Comentario deToolExecutionMode, explica las diferencias de orden de eventos entre sequential y parallel.packages/agent/src/types.ts:47-73—BeforeToolCallResult/AfterToolCallResult: block+reason vs sobrescritura por campo.packages/agent/src/types.ts:76-113—BeforeToolCallContext/AfterToolCallContext/ShouldStopAfterTurnContext, las formas de entrada de los hooks.packages/agent/src/types.ts:115-248— Todos los campos deAgentLoopConfig, cada uno con JSDoc de contrato.packages/agent/src/types.ts:257-280—CustomAgentMessages+ uniónAgentMessage, punto de extensión vía declaration merging.packages/agent/src/types.ts:288-313—AgentState: setter/getter permiten copiar campos; el estado en runtime es todo readonly.packages/agent/src/types.ts:316-355—AgentToolResult/AgentToolUpdateCallback/AgentTool, protocolo de herramientas.packages/agent/src/types.ts:358-365—AgentContext: instantánea de entrada al bucle, sólo systemPrompt/messages/tools.packages/agent/src/types.ts:367-389—AgentEvent: unión discriminada de 9 eventos.
Campos clave de AgentLoopConfig; convertToLlm es obligatorio, el resto opcional:
// packages/agent/src/types.ts:115-144
export interface AgentLoopConfig extends SimpleStreamOptions {
model: Model<any>;
/** Converts AgentMessage[] to LLM-compatible Message[] before each LLM call. */
convertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
// ...
}AgentMessage se extiende vía declaration merging; por defecto es la unión Message de pi-ai:
// packages/agent/src/types.ts:271-280
export interface CustomAgentMessages {
// Empty by default - apps extend via declaration merging
}
export type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessages];AgentTool extiende el Tool de pi-ai y añade el protocolo de ejecución:
// packages/agent/src/types.ts:332-346
export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {
label: string;
prepareArguments?: (args: unknown) => Static<TParameters>;
execute: (
toolCallId: string,
params: Static<TParameters>,
signal?: AbortSignal,
onUpdate?: AgentToolUpdateCallback<TDetails>,
) => Promise<AgentToolResult<TDetails>>;
// ...
}Flujo de datos
Cómo los tipos fluyen entre capas:
Límites y fallos
- Contrato de fallo de StreamFn: los fallos de petición/modelo deben codificarse en el stream, no lanzarse; si no, el
for awaitderunLoopse interrumpe sinagent_end. Verpackages/agent/src/types.ts:16-23. - Contrato de fallo de convertToLlm: no debe lanzar, debe devolver un fallback
Message[]; los comentarios del tipo advierten que lanzar interrumpe el bucle sin secuencia de eventos. Verpackages/agent/src/types.ts:119-127. - Copia en setter de AgentState:
tools/messagesson setter/getter; la implementación puede copiar el array top-level (la implementación por defectocreateMutableAgentStatelo hace), evitando mutación externa directa. Verpackages/agent/src/types.ts:295-300. - afterToolCall sin merge profundo: la sobrescritura por campo de
content/detailses "reemplazo total", sin merge profundo;isError/terminatese reemplazan por separado. La documentación lo dice claro: "No deep merge is performed". Verpackages/agent/src/types.ts:52-73. - AgentContext es una instantánea:
AgentencreateContextSnapshothacemessages.slice()/tools.slice()antes de pasar al bucle; las mutaciones internas (push partial, push toolResult) no afectan aAgent._state. Verpackages/agent/src/agent.ts:402-408.
Resumen
types.ts es la capa de contrato de pi-agent-core: StreamFn fija la forma de la función de stream, AgentLoopConfig ensambla el bucle, AgentState expone el estado readonly en runtime, AgentTool define el protocolo de herramientas, AgentMessage se extiende por declaration merging, y AgentEvent es la unión de 9 eventos. El contrato insiste en "no lanzar" para proteger la integridad de la secuencia de eventos de runLoop. Cómo se consumen estos tipos en clase Agent y ciclo de vida y bucle while doble; la invocación concreta de los hooks en la ejecución de herramientas en ejecución de herramientas sequential/parallel.