Skip to content

Contrato de tipos

源码版本v0.73.1

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

  1. Contrato de la función de stream: StreamFn reutiliza la firma de streamSimple y exige que la implementación no lance ante fallos de petición/modelo; los fallos se codifican en el stream como evento error y stopReason: "error". Ver packages/agent/src/types.ts:15-26.
  2. Modo de ejecución de herramientas: ToolExecutionMode = "sequential" | "parallel", aparece tanto en AgentLoopConfig.toolExecution como en AgentTool.executionMode (override por herramienta). Ver packages/agent/src/types.ts:28-36.
  3. Configuración del bucle: AgentLoopConfig extends SimpleStreamOptions, incluye model/convertToLlm/transformContext/getApiKey/shouldStopAfterTurn/getSteeringMessages/getFollowUpMessages/toolExecution/beforeToolCall/afterToolCall. Ver packages/agent/src/types.ts:115-248.
  4. Estado en runtime: AgentState declara tools y messages con setter/getter, permitiendo a la implementación copiar el array top-level; isStreaming/streamingMessage/pendingToolCalls/errorMessage son todos readonly. Ver packages/agent/src/types.ts:288-313.
  5. Protocolo de herramientas: AgentTool<TParameters, TDetails> extiende el Tool de pi-ai y añade label/prepareArguments/execute/executionMode; execute recibe toolCallId/params/signal/onUpdate. Ver packages/agent/src/types.ts:332-355.
  6. Unión de eventos: AgentEvent es una unión discriminada de 9 eventos, organizada por los cuatro ciclos de vida agent/turn/message/tool execution. Ver packages/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

Campos clave de AgentLoopConfig; convertToLlm es obligatorio, el resto opcional:

typescript
// 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:

typescript
// 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:

typescript
// 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 await de runLoop se interrumpe sin agent_end. Ver packages/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. Ver packages/agent/src/types.ts:119-127.
  • Copia en setter de AgentState: tools/messages son setter/getter; la implementación puede copiar el array top-level (la implementación por defecto createMutableAgentState lo hace), evitando mutación externa directa. Ver packages/agent/src/types.ts:295-300.
  • afterToolCall sin merge profundo: la sobrescritura por campo de content/details es "reemplazo total", sin merge profundo; isError/terminate se reemplazan por separado. La documentación lo dice claro: "No deep merge is performed". Ver packages/agent/src/types.ts:52-73.
  • AgentContext es una instantánea: Agent en createContextSnapshot hace messages.slice()/tools.slice() antes de pasar al bucle; las mutaciones internas (push partial, push toolResult) no afectan a Agent._state. Ver packages/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.