pi-codes 1.0.0

Typed Rust SDK for the pi coding agent (earendil-works/pi): serde models of the `pi --mode json` event stream and the `pi --mode rpc` stdin/stdout command protocol, plus an async (Tokio) RPC client.
Documentation
# pi-codes

Typed Rust SDK for the [pi coding agent](https://github.com/earendil-works/pi)
(npm `@earendil-works/pi-coding-agent`): serde models of the
`pi --mode json` JSONL event stream and the `pi --mode rpc` stdin/stdout
command protocol, plus an async (Tokio) RPC client.

Tested against pi 1.0.0. The crate version may carry a patch offset
above the CLI release for crate-side additions.

## What's covered

- **`--mode rpc`** — the headless command protocol: typed
  [`RpcCommand`]s (prompt/steer/follow-up, state, model, thinking,
  compaction, bash, session), the `{"type":"response"}` envelope with id
  correlation, and typed views of the important payloads
  ([`AgentState`], [`BashResult`], messages, and the
  [`InputDisposition`] a `prompt`/`steer`/`follow_up` ack reports on
  pi ≥ 0.99.0). Unmodeled commands pass through via `RpcCommand::Raw`.
- **`--mode json`** — the one-shot event stream: `PiEvent` with typed
  lifecycle/tool events and an `Unknown` fallback that preserves the
  payload, so newer CLIs degrade soft.
- **Messages** — `system` / `user` / `assistant` / `toolResult` /
  `bashExecution` roles with content blocks (text, thinking, toolCall,
  image) and usage accounting. The `system` role (pi ≥ 0.86.0) carries
  the prompt `sections` and `toolsAdded` / `toolsRemoved` declarations.

## Live testing

The integration tier (`cargo test -p pi-codes --features
integration-tests`) drives a real `pi --mode rpc` process and is
**credential-free**: state, model catalog, id correlation, the `bash`
command (a real shell round trip with typed results landing in
`get_messages`), and clean failure envelopes for unknown commands.
Model-turn coverage additionally needs a configured provider
(`pi auth check --provider <p>`).

Note: pi requires **Node 22+** (`fs.globSync`).

The model tier (gated on `OPENAI_API_KEY`) adds a streamed model turn
plus a conformance trio mirroring wirecheck's cross-harness checks:
the model reads a planted nonce file, writes a requested nonce to a
requested path, and runs a shell command with a disk-visible side
effect — write/bash verified on disk, never from the transcript.

## Measured wire notes (pi 0.84.4 → 0.99.1)

- **Input acks report a disposition (0.99.0).** A successful `prompt`,
  `steer` or `follow_up` response carries `data.disposition`:
  `started` (a run began; `prompt` only), `queued` (held during a run)
  or `handled` (an extension consumed it — no run started, so don't
  wait for `agent_settled`). Read it with `RpcResponse::disposition()`.
  Earlier CLIs sent a bare success envelope. Pinned by the 0.99.1
  corpus.
- **Nested tool calls are events only (0.99.0).** A tool that runs
  other tools (`ctx.executeTool()`, e.g. a `codemode` script) emits
  `tool_execution_*` events carrying `parentToolCallId`, with a
  `<parent id>/<n>` `toolCallId`. They never appear as tool calls or
  tool results in the transcript.
- **Assistant messages carry `thinkingLevel` (0.99.0)**, and built-in
  `bash` results add a `structuredContent` twin (`output`, `exit_code`,
  `truncated`) beside the model-facing `content`. Pinned by the 0.99.1
  corpus.

- **Every turn opens with a `system` message (0.86.0).** It streams as
  `message_start` / `message_end` before the user message and leads
  `agent_end`'s `messages`, carrying the full prompt as named
  `sections` plus `toolsAdded` declarations; later prompt/tool changes
  arrive as further `system` messages that patch by name. Pinned by
  the 0.86.0 corpus; 0.84.4 never emitted one.
- **pi has no tool-approval gate.** read/bash/edit/write run without
  prompting; there is no `--yolo` equivalent because nothing needs
  bypassing. `--approve`/`--no-approve` only control trusting
  project-local config files (extensions, AGENTS.md).
- **`tool_execution_end` carries no `args` field** in RPC mode, though
  the documented `AgentEvent` type includes it. Pinned by the corpus;
  the decoder tolerates both.
- **`--mode json` hangs headless**: with no TTY it produced no output
  and never exited in our probes (with and without `--print`). Use
  `--mode rpc` for headless work — that is what `PiRpcClient` drives.
- The model may issue parallel tool calls in one assistant message; a
  racing read can fail with ENOENT and recover next turn. Tool errors
  arrive as `tool_execution_end { isError: true }` with the error text
  in `result.content`, not as stream errors.

## Framing contract

RPC mode is strict JSONL: split records on `\n` only and tolerate a
trailing `\r`. U+2028/U+2029 are valid inside JSON strings and must not
split records — the Tokio line reader used here complies.