Skip to content

feat(pi): add a pi --mode rpc adapter - #29

Merged
dviejokfs merged 3 commits into
mainfrom
feat/pi-adapter
Oct 3, 2026
Merged

dviejokfs merged 3 commits into
mainfrom
feat/pi-adapter

Conversation

@dviejokfs

@dviejokfs dviejokfs commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Outcome

Adds Provider::Pi and providers::Pi, behind a new default pi feature, which drive the pi coding agent through pi --mode rpc (JSONL over stdio). Embedders get pi as a fourth first-class harness next to Claude Code, Codex and OpenCode, with:

  • streamed text and thinking deltas, the tool lifecycle, usage with cost and context-window occupancy, compaction start/completion/failure, and auto-retry warnings;
  • session start and resume. A resume of a session pi does not have fails as pi::session_not_found, and one whose messageCount pi does not report as a valid count fails as pi::session_unconfirmed, both before the prompt is written, so a stale id never silently starts a new conversation;
  • cooperative cancellation through pi's abort command;
  • extension dialogs (confirm, select, input, editor) delivered to the interaction handler as approvals and questions;
  • discovery: the model catalog, read over RPC with --no-session and including per-model thinking levels, plus a credential check through pi --list-models;
  • skill discovery and management for pi's user and project skill directories.

pi's end-of-run events (agent_end, turn_end) repeat the whole conversation, so on long turns they outgrow the event-line limit. To keep those turns working without raising the limit:

  • as soon as a frame crosses the limit, the runtime asks the new AgentAdapter::accepts_oversized_frame with the frame's first 512 bytes. The default declines, which fails the turn at once, so other adapters behave as before;
  • an accepted frame is consumed without being buffered, and AgentAdapter::parse_oversized_frame receives an OversizedFrame with its first and last 512 bytes;
  • pi drops the redundant frames. An oversized tool result is reported with its output omitted, but with the outcome pi wrote in isError, which pi places after the result. If that outcome cannot be read, the call is reported as failed, never as succeeded.

Fix included: event lines are now held to max_event_line_bytes exactly. Previously a CRLF-terminated line at the limit was rejected, and an unterminated final line one byte over it was accepted.

Not included yet:

  • process retention, steering and live messages;
  • turn-scoped MCP servers. They are rejected before spawning, and strict MCP maps to --no-extensions;
  • pi MCP listing and management (reported as unsupported);
  • native image attachments.

Protocol and security impact

  • CLI version: tested against pi 1.0.0.
  • Permissions:
    • pi runs tools without asking, so the adapter accepts only FullAccess (--no-approve) and Plan, which passes pi's read-only tools (read, grep, find, ls) through --tools, intersected with allowed_tools.
    • Default, AcceptEdits and custom modes fail before spawning instead of silently running with full access.
    • Approvals only come from pi extensions' UI requests.
  • Argument injection: model, thinking level, session id and tool names are validated before they reach pi's argv. Values starting with -, empty values, and tool names containing , are rejected.
  • Environment: PI_CODING_AGENT_DIR and PI_CODING_AGENT_SESSION_DIR join the local, SSH and Temps sandbox environment allowlists, like CLAUDE_HOME. PI_SKIP_VERSION_CHECK=1 stops pi from contacting its update server on every turn.
  • Bounded output: tool output, interaction text, warnings, session titles and the set of running tools are all bounded. Retry warnings do not echo the provider's error text.
  • Compatibility: Provider is #[non_exhaustive], so Provider::Pi is additive. accepts_oversized_frame and parse_oversized_frame have default implementations. Default discovery now reports four harnesses instead of three; the fullstack example now enables only the three agents its UI implements.

Verification

  • cargo fmt --all -- --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --all-features: 382 lib tests plus every integration suite, including the new tests/pi_rpc.rs (12 tests against a scripted pi RPC fixture: tool turn, retained resume, missing session, rejected prompt, extension confirm, cancel/abort, oversized summaries, an oversized failing tool result, an oversized answer, rejected modes and turn-scoped MCP, discovery)
  • Fullstack example: cargo clippy and the full Playwright e2e suite (19 passed)
  • Feature combinations checked: --no-default-features, --no-default-features --features pi, and cargo +1.88 check --all-features --all-targets --locked (MSRV)
  • Documentation and changelog updated: README, API reference (new pi section and capability column), events, architecture, discovery, tailnets, quickstart, site
  • python3 scripts/check-package.py and cargo package --locked

Manual: examples/pi_smoke.rs drives the real pi 1.0.0 binary against a local mock of the Anthropic Messages API. All scenarios passed:

  • readiness and discovery;
  • a bash tool turn;
  • resume of the same session without a second SessionStarted;
  • the missing-session rejection;
  • plan mode blocking bash;
  • a turn under a 4 KiB event limit;
  • a failing bash call whose output exceeds that limit, reported as failed from isError;
  • cancellation in about 0.5 s.

To run it against a real model, set PI_SMOKE_ENVIRONMENT to the names of the provider key variables to forward:

cargo run --example pi_smoke --features pi -- [provider/model]

🤖 Generated with Claude Code

Provider::Pi drives the pi coding agent through `pi --mode rpc`, its JSONL
stdio protocol, behind a new default `pi` feature.

Each turn starts with get_state. A resumed session that pi reports as empty
fails with pi::session_not_found before the prompt is written, so a stale id
never silently starts a new conversation. Text and thinking deltas, tool
executions, usage with the context window, compaction and auto-retry are
mapped onto TurnEvent, and agent_settled ends the turn. Extension confirm,
select, input and editor dialogs reach the interaction handler as approvals
and questions; cancellation sends abort.

pi has no per-tool approval prompt, so only FullAccess (--no-approve) and
Plan (read-only --tools) are accepted, and other modes fail before spawning.
Strict MCP config maps to --no-extensions. Discovery reads the catalog over
RPC with --no-session and authentication from --list-models.

pi's end-of-run events repeat the whole conversation and outgrow the event
line limit on long turns. The runtime now consumes an oversized frame
without buffering it and passes its prefix to the new
AgentAdapter::parse_oversized_frame, which lets pi drop those redundant
frames and report an oversized tool result as omitted; every other adapter
keeps failing the turn. Lines are now held to the limit exactly, including
CRLF-terminated lines and a final line without a terminator.

PI_CODING_AGENT_DIR and PI_CODING_AGENT_SESSION_DIR join the forwarded
environment allowlists, and skill discovery covers pi's user and project
skill directories.

Verified with unit tests, a scripted RPC fixture (tests/pi_rpc.rs) and
examples/pi_smoke.rs against pi 1.0.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Adds a new pi coding agent adapter.

The PR appears safe to merge; no new actionable issue remains.

Summary

The PR adds pi as a default-enabled provider and extends bounded event reading to let adapters handle oversized frames. The changes since the previous review add early rejection of unrecognized oversized frames, preserve oversized tool outcomes, and reject resumes whose session count cannot be confirmed.

  • Adds pi RPC turns, discovery, permissions, interactions, and session handling.
  • Adds focused tests for oversized frames, failed tools, and unconfirmed resumes.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Pi RPC stdout] --> B{Frame within limit?}
    B -->|Yes| C[Parse complete event]
    B -->|No| D{Adapter accepts prefix?}
    D -->|No| E[Fail turn promptly]
    D -->|Yes| F[Consume frame; retain first and last 512 bytes]
    F --> G[Parse oversized summary or tool outcome]
Loading

Reviews (2) · Last reviewed commit: "fix(pi): keep oversized tool failures fa..."

Comment thread src/providers/pi.rs Outdated
Comment thread src/providers/pi.rs Outdated
Comment thread src/runtime.rs Outdated
dviejokfs and others added 2 commits October 3, 2026 10:36
…ments

Discovery reports every provider compiled into the runtime, and the example
took the SDK's default features, so adding pi made it discover a fourth
harness its UI has no label, icon or picker entry for, and its e2e test
failed. Enable only Claude Code, Codex and OpenCode, plus the transports it
uses, until the example supports pi.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ions

An oversized tool_execution_end was reported as Succeeded from its first
512 bytes, but pi writes isError after the result, so a failed tool with a
large result looked successful. The runtime now also keeps the last 512 bytes
of an oversized frame and passes both ends to the adapter as an
OversizedFrame. pi reads isError from the tail, and reports a call whose
outcome it cannot read as failed, never as succeeded.

Adapters now decide on an oversized frame as soon as it crosses the limit,
through the new AgentAdapter::accepts_oversized_frame. A declined frame fails
the turn at once instead of being read to its end, so a provider writing an
endless line no longer holds the turn until its deadline; only frames the
adapter accepts are consumed. Under a limit smaller than 512 bytes the
decision waits for the whole prefix.

A resume now requires get_state to report a positive messageCount. A missing
or malformed count fails as pi::session_unconfirmed before the prompt is
sent, instead of being treated as a session that exists.

The pi smoke example gains a failing bash call whose output exceeds a 4 KiB
event limit; against pi 1.0.0 it is reported as failed from isError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dviejokfs
dviejokfs merged commit 0bbf0bc into main Oct 3, 2026
7 checks passed
@dviejokfs
dviejokfs deleted the feat/pi-adapter branch October 3, 2026 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant