chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
266
third_party/pi/packages/coding-agent/docs/json.md
vendored
266
third_party/pi/packages/coding-agent/docs/json.md
vendored
@@ -1,16 +1,203 @@
|
||||
# JSON Event Stream Mode
|
||||
# JSON Event Stream
|
||||
|
||||
JSON mode emits structured progress for one invocation:
|
||||
|
||||
```bash
|
||||
pi --mode json "Your prompt"
|
||||
pi --mode json "Review this repository"
|
||||
```
|
||||
|
||||
Outputs all session events as JSON lines to stdout. Useful for integrating pi into other tools or custom UIs.
|
||||
Pi writes one session header followed by session events, then exits after the supplied prompts finish. RPC mode emits the same session-event shapes but has no session header because it is a bidirectional, long-lived protocol. See [RPC Mode](rpc.md).
|
||||
|
||||
## Event Types
|
||||
This page is the canonical reference for events shared by JSON and RPC mode. Message values use the [shared message types](message-types.md).
|
||||
|
||||
Wire events use `JsonAgentSessionEvent`. It matches
|
||||
[`AgentSessionEvent`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/agent-session.ts)
|
||||
except that streaming message updates omit cumulative snapshots:
|
||||
## Framing and process I/O
|
||||
|
||||
The stream uses strict JSONL framing. Each record is one JSON object terminated by LF (`\n`). Split records only on LF 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` is not suitable for this stream because it also recognizes those Unicode separators. Use a byte or UTF-8 stream decoder and split on LF.
|
||||
|
||||
Read stdout continuously. A reader that stops consuming records can stall Pi when the pipe buffer fills. Stdout is reserved for JSONL; diagnostics and application logging go to stderr.
|
||||
|
||||
## Session header
|
||||
|
||||
The first JSON-mode record is the current [session header](session-format.md#sessionheader):
|
||||
|
||||
```json
|
||||
{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path"}
|
||||
```
|
||||
|
||||
RPC mode does not emit this record. Use [`get_state`](rpc-commands.md#get_state) for its current session ID and file.
|
||||
|
||||
## Event sequence
|
||||
|
||||
A basic run produces records like these:
|
||||
|
||||
```json
|
||||
{"type":"agent_start"}
|
||||
{"type":"turn_start"}
|
||||
{"type":"message_start","message":{"role":"user","content":"Review this repository","timestamp":1733234401000}}
|
||||
{"type":"message_end","message":{"role":"user","content":"Review this repository","timestamp":1733234401000}}
|
||||
{"type":"message_start","message":{"role":"assistant","content":[],"stopReason":"pending","...":"..."}}
|
||||
{"type":"message_update","usage":{"...":"..."},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello"}}
|
||||
{"type":"message_end","message":{"role":"assistant","...":"..."}}
|
||||
{"type":"turn_end","message":{"role":"assistant","...":"..."},"toolResults":[]}
|
||||
{"type":"agent_end","messages":[{"...":"..."}],"willRetry":false}
|
||||
{"type":"agent_settled","aborted":false}
|
||||
```
|
||||
|
||||
`agent_end` closes one low-level agent run. Automatic retry, overflow recovery, compaction retry, steering, or follow-up work can still continue. `agent_settled` means Pi has no remaining automatic work for that session-level run.
|
||||
|
||||
## Agent and turn events
|
||||
|
||||
| Event | Fields | Meaning |
|
||||
|---|---|---|
|
||||
| `agent_start` | None | A low-level agent run started. |
|
||||
| `agent_end` | `messages`, `willRetry` | That low-level run ended. `messages` contains messages generated by the run. |
|
||||
| `agent_settled` | `aborted` | Pi will not continue automatically through retries, compaction recovery, or queued messages. `aborted` is `true` when the run ended because it was aborted. |
|
||||
| `turn_start` | None | One assistant turn started. |
|
||||
| `turn_end` | `message`, `toolResults` | One assistant response and its resulting tool calls finished. |
|
||||
|
||||
A turn is one assistant response plus any tool calls and tool results produced by that response.
|
||||
|
||||
## Message events
|
||||
|
||||
| Event | Fields | Meaning |
|
||||
|---|---|---|
|
||||
| `message_start` | `message` | A message started. |
|
||||
| `message_update` | `usage`, `assistantMessageEvent` | An assistant message emitted a content-block update. |
|
||||
| `message_end` | `message` | A message completed. This is the authoritative final message. |
|
||||
|
||||
### Reconstruct streaming messages
|
||||
|
||||
Wire `message_update` records are delta-only. They omit the SDK event's cumulative `message` field and every `assistantMessageEvent.partial` snapshot so stream size remains linear.
|
||||
|
||||
The nested event is one of:
|
||||
|
||||
| Type | Fields in addition to `type` | Meaning |
|
||||
|---|---|---|
|
||||
| `start` | None | The provider stream started; its cumulative `partial` field is removed on the wire. |
|
||||
| `text_start` | `contentIndex` | A text block started. |
|
||||
| `text_delta` | `contentIndex`, `delta` | Append text to the block. |
|
||||
| `text_end` | `contentIndex`, `content` | The text block ended with authoritative content. |
|
||||
| `thinking_start` | `contentIndex` | A thinking block started. |
|
||||
| `thinking_delta` | `contentIndex`, `delta` | Append thinking text to the block. |
|
||||
| `thinking_end` | `contentIndex`, `content` | The thinking block ended with authoritative content. |
|
||||
| `toolcall_start` | `contentIndex`, `id`, `toolName` | A tool-call block started. |
|
||||
| `toolcall_delta` | `contentIndex`, `delta` | Append serialized argument data. |
|
||||
| `toolcall_end` | `contentIndex`, `toolCall` | The tool call ended with the complete `ToolCall`. |
|
||||
| `done` | `reason`, `message` | The provider stream completed successfully. |
|
||||
| `error` | `reason`, `error` | The provider stream ended with an error or abort message. |
|
||||
|
||||
The normal agent loop translates provider-level `start`, `done`, and `error` into `message_start` and `message_end` session events rather than emitting them as `message_update`. They remain admitted by the exported `JsonAgentSessionEvent` transformation for callers that construct a matching session event.
|
||||
|
||||
Use `contentIndex` to identify the content block. Buffer `delta` fields for a live display, but replace reconstructed data with the completed content in `text_end`, `thinking_end`, or `toolcall_end`. Replace the whole partial message with `message_end.message` when it arrives.
|
||||
|
||||
The top-level `usage` is the latest cumulative provider-reported usage for the assistant response. It can remain zero until completion when a provider does not report usage while streaming.
|
||||
|
||||
```json
|
||||
{"type":"message_update","usage":{"input":100,"output":1,"cacheRead":0,"cacheWrite":0,"totalTokens":101,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello "}}
|
||||
```
|
||||
|
||||
## Tool execution events
|
||||
|
||||
| Event | Fields | Meaning |
|
||||
|---|---|---|
|
||||
| `tool_execution_start` | `toolCallId`, `toolName`, `args` | Tool execution started. |
|
||||
| `tool_execution_update` | `toolCallId`, `toolName`, `args`, `partialResult` | The tool reported a partial result. |
|
||||
| `tool_execution_end` | `toolCallId`, `toolName`, `result`, `isError`, `durationMs` | Tool execution finished. `durationMs` is how long the tool's `execute()` took, measured with a monotonic clock; absent when the tool did not run. |
|
||||
|
||||
Use `toolCallId` to correlate the lifecycle. `partialResult` is the latest partial result supplied by the tool. Whether it replaces or extends an earlier update depends on that tool's result contract.
|
||||
|
||||
```json
|
||||
{"type":"tool_execution_start","toolCallId":"call_abc123","toolName":"bash","args":{"command":"ls -la"}}
|
||||
{"type":"tool_execution_update","toolCallId":"call_abc123","toolName":"bash","args":{"command":"ls -la"},"partialResult":{"content":[{"type":"text","text":"partial output"}],"details":{}}}
|
||||
{"type":"tool_execution_end","toolCallId":"call_abc123","toolName":"bash","result":{"content":[{"type":"text","text":"complete output"}],"details":{}},"isError":false}
|
||||
```
|
||||
|
||||
## Queue and state events
|
||||
|
||||
| Event | Fields | Meaning |
|
||||
|---|---|---|
|
||||
| `queue_update` | `steering`, `followUp` | The pending steering or follow-up queue changed. Both fields contain the complete current queue. |
|
||||
| `entry_appended` | `entry` | An extension appended a custom session entry through `pi.appendEntry()`. |
|
||||
| `session_info_changed` | `name` | The session display name changed. An absent `name` means it was cleared. |
|
||||
| `thinking_level_changed` | `level` | The active thinking level changed. |
|
||||
|
||||
The `entry` value uses a persisted [session entry type](session-format.md#entry-types).
|
||||
|
||||
## Compaction events
|
||||
|
||||
`compaction_start` reports why compaction began:
|
||||
|
||||
```json
|
||||
{"type":"compaction_start","reason":"threshold"}
|
||||
```
|
||||
|
||||
`reason` is `"manual"`, `"threshold"`, or `"overflow"`.
|
||||
|
||||
`compaction_end` contains the result when compaction succeeds:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "compaction_end",
|
||||
"reason": "threshold",
|
||||
"result": {
|
||||
"summary": "Summary of conversation...",
|
||||
"firstKeptEntryId": "abc123",
|
||||
"tokensBefore": 150000,
|
||||
"estimatedTokensAfter": 32000,
|
||||
"usage": {"...": "..."},
|
||||
"details": {}
|
||||
},
|
||||
"aborted": false,
|
||||
"willRetry": false
|
||||
}
|
||||
```
|
||||
|
||||
If compaction was aborted, `result` is absent and `aborted` is true. If it failed, `result` is absent, `aborted` is false, and `errorMessage` describes the failure. Successful overflow recovery sets `willRetry` to true before Pi retries the prompt.
|
||||
|
||||
See [Compaction and Branch Summaries](compaction.md) for result semantics.
|
||||
|
||||
## Retry events
|
||||
|
||||
Assistant-turn retry emits:
|
||||
|
||||
```json
|
||||
{"type":"auto_retry_start","attempt":1,"maxAttempts":3,"delayMs":2000,"errorMessage":"529 overloaded"}
|
||||
{"type":"auto_retry_end","success":true,"attempt":2}
|
||||
```
|
||||
|
||||
On final failure, `auto_retry_end` has `success: false` and a `finalError` string.
|
||||
|
||||
Compaction and branch-summary retry emit:
|
||||
|
||||
```json
|
||||
{"type":"summarization_retry_scheduled","attempt":1,"maxAttempts":3,"delayMs":2000,"errorMessage":"terminated"}
|
||||
{"type":"summarization_retry_attempt_start","source":"compaction","reason":"threshold"}
|
||||
{"type":"summarization_retry_finished"}
|
||||
```
|
||||
|
||||
For a branch summary, `source` is `"branchSummary"` and `reason` is absent. The `reason` on a compaction retry is `"manual"`, `"threshold"`, or `"overflow"`.
|
||||
|
||||
## RPC-only events
|
||||
|
||||
A direct RPC [`bash`](rpc-commands.md#bash) command emits one `bash_execution_update` for each output chunk. Its optional `id` matches the command ID. The final command response can contain truncated output, but these events stream all output:
|
||||
|
||||
```json
|
||||
{"type":"bash_execution_update","id":"req-1","delta":"total 48\n"}
|
||||
```
|
||||
|
||||
RPC also adds `extension_error` when an extension handler throws:
|
||||
|
||||
```json
|
||||
{"type":"extension_error","extensionPath":"/path/to/extension.ts","event":"tool_call","error":"Error message"}
|
||||
```
|
||||
|
||||
Extension UI records are a separate RPC subprotocol, not `AgentSessionEvent` values. See [RPC Extension UI](rpc-extension-ui.md).
|
||||
|
||||
## TypeScript types
|
||||
|
||||
The SDK's `AgentSessionEvent` contains cumulative streaming snapshots for in-process consumers. JSON and RPC transform only `message_update`:
|
||||
|
||||
```typescript
|
||||
type WithoutPartial<T> = T extends { partial: unknown } ? Omit<T, "partial"> : T;
|
||||
@@ -28,71 +215,12 @@ type JsonAgentSessionEvent =
|
||||
};
|
||||
```
|
||||
|
||||
`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`](https://github.com/earendil-works/pi-mono/blob/main/packages/agent/src/types.ts):
|
||||
|
||||
```typescript
|
||||
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`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/types.ts#L134):
|
||||
- `UserMessage` (line 134)
|
||||
- `AssistantMessage` (line 140)
|
||||
- `ToolResultMessage` (line 152)
|
||||
|
||||
Extended messages from [`packages/coding-agent/src/core/messages.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/messages.ts#L29):
|
||||
- `BashExecutionMessage` (line 29)
|
||||
- `CustomMessage` (line 46)
|
||||
- `BranchSummaryMessage` (line 55)
|
||||
- `CompactionSummaryMessage` (line 62)
|
||||
|
||||
## Output Format
|
||||
|
||||
Each line is a JSON object. The first line is the session header:
|
||||
|
||||
```json
|
||||
{"type":"session","version":3,"id":"uuid","timestamp":"...","cwd":"/path"}
|
||||
```
|
||||
|
||||
Followed by events as they occur:
|
||||
|
||||
```json
|
||||
{"type":"agent_start"}
|
||||
{"type":"turn_start"}
|
||||
{"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":[...]}
|
||||
```
|
||||
|
||||
`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.
|
||||
Use the exported `JsonAgentSessionEvent` type from `@earendil-works/pi-coding-agent`. Its implementation is in [`json-event.ts`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/modes/json-event.ts).
|
||||
|
||||
## Example
|
||||
|
||||
Print completed messages from a one-shot run:
|
||||
|
||||
```bash
|
||||
pi --mode json "List files" 2>/dev/null | jq -c 'select(.type == "message_end")'
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user