feat(pi): add the Pi coding agent (pi.dev) as a provider - #214
Open
davidliuk wants to merge 1 commit into
Open
Conversation
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>
| @@ -0,0 +1,350 @@ | |||
| import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; | |||
| import { mkdtemp, mkdir, rm, writeFile, readFile } from 'fs/promises'; | |||
| 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'; |
There was a problem hiding this comment.
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/sessionstranscript layout. - Updates UI provider picker/model selection and adds comprehensive tests using a fake
pibinary 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 on lines
+4771
to
+4773
| for (const filePath of files) { | ||
| if (path.basename(filePath).includes(sessionId)) return filePath; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds Pi — an MIT-licensed agent harness fronting 15+ model providers — alongside Claude Code, Codex, Cursor and Gemini.
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:
-rf pleaseError: Unknown option: -rf please@notes.md explainError: 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
Backend —
server/pi-cli.jsdrivespi --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 inserver/projects.js;server/index.jsroutespi-commandand abort;server/routes/cli-auth.jsreports status and can install the CLI.Session discovery reads
cwdfrom 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:
Frontend —
piadded toSessionProvider, the agent picker, model selection (PI_MODELSwithprovider/modelslugs 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
pibinary 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 typecheckandnpm run buildclean. Verified end to end against the real CLI: streaming works, a bad key producessession-created → pi-error → pi-completein under a second with no hang and a clean session map.Review: Codex reviewed
pi-cli.jsand found thaterrorandcloseboth firing on a failed spawn produced duplicate/contradictory terminal websocket events. Fixed, with a test asserting exactly onepi-errorand zeropi-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.