Files

4.4 KiB

JSON Event Stream Mode

pi --mode json "Your prompt"

Outputs all session events as JSON lines to stdout. Useful for integrating pi into other tools or custom UIs.

Event Types

Wire events use JsonAgentSessionEvent. It matches AgentSessionEvent except that streaming message updates omit cumulative snapshots:

type WithoutPartial<T> = T extends { partial: unknown } ? Omit<T, "partial"> : T;

type JsonAssistantMessageEvent<T> = T extends { type: "toolcall_start"; partial: unknown }
  ? WithoutPartial<T> & { id: string; toolName: string }
  : WithoutPartial<T>;

type JsonAgentSessionEvent =
  | Exclude<AgentSessionEvent, { type: "message_update" }>
  | {
      type: "message_update";
      usage: Usage;
      assistantMessageEvent: JsonAssistantMessageEvent<AssistantMessageEvent>;
    };

queue_update emits the full pending steering and follow-up queues whenever they change. compaction_start and compaction_end cover both manual and automatic compaction.

Other base events come from AgentEvent:

type AgentEvent =
  // Agent lifecycle
  | { type: "agent_start" }
  | { type: "agent_end"; messages: AgentMessage[] }
  // Turn lifecycle
  | { type: "turn_start" }
  | { type: "turn_end"; message: AgentMessage; toolResults: ToolResultMessage[] }
  // Message lifecycle
  | { type: "message_start"; message: AgentMessage }
  | { type: "message_update"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }
  | { type: "message_end"; message: AgentMessage }
  // Tool execution
  | { type: "tool_execution_start"; toolCallId: string; toolName: string; args: any }
  | { type: "tool_execution_update"; toolCallId: string; toolName: string; args: any; partialResult: any }
  | { type: "tool_execution_end"; toolCallId: string; toolName: string; result: any; isError: boolean };

Message Types

Base messages from packages/ai/src/types.ts:

  • UserMessage (line 134)
  • AssistantMessage (line 140)
  • ToolResultMessage (line 152)

Extended messages from packages/coding-agent/src/core/messages.ts:

  • BashExecutionMessage (line 29)
  • CustomMessage (line 46)
  • BranchSummaryMessage (line 55)
  • CompactionSummaryMessage (line 62)

Output Format

JSON mode uses strict JSONL framing. Split records only on LF (\n) and strip an optional preceding carriage return. Unicode line and paragraph separators are valid inside JSON strings and are not record boundaries. Node.js readline recognizes those separators, so it is not suitable for parsing this stream.

Read stdout continuously. A reader that stops consuming events can stall Pi when the pipe buffer fills.

Each record is a JSON object. The first record is the session header:

{"type":"session","version":3,"id":"uuid","timestamp":"...","cwd":"/path"}

Subsequent records contain events as they occur:

{"type":"agent_start"}
{"type":"turn_start"}
{"type":"message_start","message":{"role":"user","content":"Review this repository",...}}
{"type":"message_end","message":{"role":"user","content":"Review this repository",...}}
{"type":"message_start","message":{"role":"assistant","content":[],...}}
{"type":"message_update","usage":{...},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello"}}
{"type":"message_end","message":{...}}
{"type":"turn_end","message":{...},"toolResults":[]}
{"type":"agent_end","messages":[...],"willRetry":false}
{"type":"agent_settled"}

message_update records are delta-only. They omit both the cumulative message field and assistantMessageEvent.partial to keep stream size linear. The top-level usage field contains the latest cumulative provider-reported usage and may remain zero when a provider only reports usage at completion. Use contentIndex and delta to assemble live text, thinking, or tool-call arguments if needed. A toolcall_start event also includes the constant-sized id and toolName fields. message_end contains the final authoritative message.

Example

pi --mode json "List files" 2>/dev/null | jq -c 'select(.type == "message_end")'