Skip to content

feat(pi): add the Pi coding agent (pi.dev) as a provider - #214

Open
davidliuk wants to merge 1 commit into
mainfrom
feat/pi-agent-provider
Open

feat(pi): add the Pi coding agent (pi.dev) as a provider#214
davidliuk wants to merge 1 commit into
mainfrom
feat/pi-agent-provider

Conversation

@davidliuk

Copy link
Copy Markdown
Collaborator

Adds Pi — an MIT-licensed agent harness fronting 15+ model providers — alongside Claude Code, Codex, Cursor and Gemini.

npm install -g --ignore-scripts @earendil-works/pi-coding-agent

Two bugs the real CLI caught that the docs didn't

I built this against the installed binary rather than from documentation, and both of these would otherwise have shipped:

1. The prompt cannot go in argv. Pi parses positional arguments. Verified against pi 0.83.0:

prompt result as argv
-rf please Error: Unknown option: -rf please
@notes.md explain Error: File not found: .../notes.md explain
-- (as terminator) Error: Unknown option: --

Both are ordinary user input — and @ mentions are a Dr. Claw feature — while Pi has no end-of-options terminator. So the prompt travels over Pi's documented stdin piping, which accepts arbitrary bytes. A test asserts @notes.md -rf 请解释这段代码 🦞 arrives byte-identical.

2. stdin must be explicitly closed. Pi blocks until stdin reaches EOF. Leaving the pipe open hangs the turn forever — no output, timer running — which is precisely the symptom this project has been fixing elsewhere. I hit this live during development; it now has a regression test using an empty prompt.

What's wired

Backendserver/pi-cli.js drives pi --mode json -p --session-id <uuid> --model <provider/id> and maps the JSONL event stream onto the existing chat contract (pi-response / pi-complete / pi-error / token-budget), with abort (SIGTERM escalating to SIGKILL). Session discovery, message reading, and delete live in server/projects.js; server/index.js routes pi-command and abort; server/routes/cli-auth.js reports status and can install the CLI.

Session discovery reads cwd from each transcript's header line rather than decoding the directory name. The directory encoding (/private/tmp/a/b--private-tmp-a-b--, confirmed on disk) is lossy: a path segment containing - is indistinguishable from a separator. Transcripts are memoized on (size, mtimeMs), so this doesn't reintroduce the repeated-full-rescan problem.

Status detection distinguishes not installed from installed but logged out — Pi has no credentials of its own, so the latter is a normal state users will hit:

authenticated: false | cliAvailable: true |
error: Pi is installed but no provider is logged in. Run "pi" and use /login.

Frontendpi added to SessionProvider, the agent picker, model selection (PI_MODELS with provider/model slugs and free-text entry), session lists, tag/delete/navigation paths, and streaming handlers reusing the existing delta buffer.

Testing

36 new tests drive the real provider code against a fake pi binary speaking the event stream captured from 0.83.0 — so CI needs neither Pi nor model credentials. Covered: argv construction, stdin delivery of adversarial prompts, empty prompts, error turns, stderr-only crashes, missing binary, non-JSON banner output, multi-byte UTF-8 split across stdout chunks, abort mid-stream, resume vs. new session identity, and session discovery / messages / delete.

Full suite 143 passed; npm run typecheck and npm run build clean. Verified end to end against the real CLI: streaming works, a bad key produces session-created → pi-error → pi-complete in under a second with no hang and a clean session map.

Review: Codex reviewed pi-cli.js and found that error and close both firing on a failed spawn produced duplicate/contradictory terminal websocket events. Fixed, with a test asserting exactly one pi-error and zero pi-complete.

Note

This is independent of #212 and #213 and can merge in any order. Once #213 lands, Pi is a natural next discoverer — it ships pi --list-models, which reports exactly the models the user's authenticated providers expose.

Pi is an MIT-licensed agent harness that fronts 15+ model providers. This wires
it in alongside Claude Code, Codex, Cursor and Gemini.

Backend
- server/pi-cli.js drives `pi --mode json -p --session-id <uuid>` and maps its
  JSONL event stream onto the existing chat contract (pi-response /
  pi-complete / pi-error / token-budget), with abort support.
- server/projects.js discovers sessions under ~/.pi/agent/sessions and reads
  their messages; server/index.js routes pi-command and abort.
- server/routes/cli-auth.js reports install/login state and can install the CLI.

Two behaviours were found by running the real CLI rather than reading its docs,
and both would have shipped as bugs:

- **The prompt must go over stdin, not argv.** Pi parses positional arguments,
  so against pi 0.83.0 a prompt of "-rf please" fails with "Unknown option" and
  "@notes.md explain" fails with "File not found" — and Pi has no `--`
  end-of-options terminator ("Unknown option: --"). Both are ordinary user
  input, and `@` mentions are a Dr. Claw feature. Pi's documented stdin piping
  accepts arbitrary bytes, so the prompt travels there.
- **stdin must be explicitly closed.** Pi waits for EOF before running, so an
  open stdin pipe hangs the turn forever with no output and a running timer —
  the exact symptom this project has been fixing elsewhere. This was observed
  live before being fixed.

Session discovery reads the `cwd` from each transcript's header line rather
than decoding the directory name, which is lossy (a path segment containing '-'
is indistinguishable from a separator). Transcripts are memoized on
(size, mtimeMs) so repeated project refreshes do not re-read the tree.

Frontend
- 'pi' added to SessionProvider, the agent picker, model selection (PI_MODELS,
  provider/model slugs, free-text entry), session lists, tag/delete/navigation
  paths, and streaming handlers that reuse the existing delta buffer.

Testing
- 36 new tests drive the real provider code against a fake `pi` binary speaking
  the captured 0.83.0 event stream: argv construction, stdin delivery of
  adversarial prompts, empty prompts, error turns, stderr-only crashes, missing
  binary, non-JSON banner output, multi-byte UTF-8 split across stdout chunks,
  abort mid-stream, session identity, and session discovery/messages/delete.
- Full suite 143 passed; typecheck and build clean. Verified end to end against
  the real CLI: streaming, clean error reporting, no hangs, session cleanup.

Reviewed with Codex, which found duplicate terminal websocket events when
'error' and 'close' both fire on a failed spawn; fixed and covered by a test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 5, 2026 19:53
@@ -0,0 +1,350 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { mkdtemp, mkdir, rm, writeFile, readFile } from 'fs/promises';
Comment thread server/index.js
import { queryOpenRouter, abortOpenRouterSession, isOpenRouterSessionActive, getOpenRouterSessionStartTime, getActiveOpenRouterSessions } from './openrouter.js';
import { queryLocalGPU, abortLocalGPUSession, isLocalGPUSessionActive, getLocalGPUSessionStartTime, getActiveLocalGPUSessions } from './local-gpu.js';
import { spawnNanoClaudeCode, abortNanoClaudeCodeSession, isNanoClaudeCodeSessionActive, getNanoClaudeCodeSessionStartTime, getActiveNanoClaudeCodeSessions } from './nano-claude-code.js';
import { spawnPi, abortPiSession, isPiSessionActive, getPiSessionStartTime, getActivePiSessions } from './pi-cli.js';

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Pi (pi.dev) as a new session provider across the server + UI, including CLI status detection, session discovery from Pi’s on-disk JSONL transcripts, and end-to-end streaming over Pi’s JSON event mode.

Changes:

  • Adds a Pi provider implementation (server/pi-cli.js) that pipes prompts over stdin, parses JSONL events, and streams them to the existing chat contract (pi-response / pi-complete / pi-error / token-budget) with abort support.
  • Extends project/session plumbing to discover, list, load, and delete Pi sessions based on Pi’s ~/.pi/agent/sessions transcript layout.
  • Updates UI provider picker/model selection and adds comprehensive tests using a fake pi binary and real transcript-shaped fixtures.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/types/app.ts Adds pi provider type and piSessions to Project.
src/hooks/useProjectsState.ts Includes piSessions in change detection, session aggregation, tagging, navigation, and deletion filtering.
src/components/sidebar/utils/utils.ts Includes Pi sessions in sidebar session aggregation/sorting.
src/components/project-dashboard/view/ProjectDashboard.tsx Includes Pi sessions in dashboard session list aggregation.
src/components/chat/view/subcomponents/ChatComposer.tsx Adds Pi to provider picker and model selection plumbing.
src/components/chat/view/ChatInterface.tsx Adds Pi availability defaults, model config routing, and status polling endpoint wiring.
src/components/chat/hooks/useChatSessionState.ts Resolves provider as pi when loading a session found under project.piSessions.
src/components/chat/hooks/useChatRealtimeHandlers.ts Adds streaming + lifecycle handling for pi-* websocket events.
src/components/chat/hooks/useChatProviderState.ts Stores/loads Pi model preference and exposes it to the chat UI.
src/components/chat/hooks/useChatComposerState.ts Sends pi-command websocket messages with Pi-specific options.
shared/modelConstants.js Introduces PI_MODELS with provider/model slugs and custom entry support.
server/utils/piCli.js Adds Pi CLI command resolution + session directory encoding helpers.
server/routes/cli-auth.js Adds Pi installer metadata and /api/cli/pi/status credential detection via pi --list-models.
server/projects.js Implements Pi transcript indexing, message reading, delete, and DB reconciliation hooks.
server/pi-cli.js Core Pi CLI driver: argv construction, stdin prompt piping, JSONL parsing, error handling, and abort.
server/index.js Routes pi-command and abort handling; adds filesystem watcher root for Pi sessions.
server/tests/pi-session-index.test.mjs Tests Pi transcript discovery/indexing/message reading/deletion against real-shaped JSONL fixtures.
server/tests/pi-cli.test.mjs Tests argv/stdin/stream parsing/abort behavior against a fake pi binary emitting captured-like JSONL.
docs/configuration.md Documents PI_CLI_PATH, PI_MODEL, Pi install/login flow, and operational details.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +158 to +162
const piSessions = (project.piSessions || []).map((session) => ({
...session,
__provider: 'pi' as const,
}));

Comment on lines +695 to +698
const piSession = project.piSessions?.find((session) => session.id === targetSessionId);
if (piSession) {
matchedSession = { ...piSession, __provider: 'pi' };
}
Comment thread server/projects.js
Comment on lines +4771 to +4773
for (const filePath of files) {
if (path.basename(filePath).includes(sessionId)) return filePath;
}
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.

2 participants