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")'