From ea02b0176833388cb8516886b499879bf9ac0bb5 Mon Sep 17 00:00:00 2001 From: Reflex Date: Wed, 29 Jul 2026 06:40:20 +0000 Subject: [PATCH] feat(examples): add Pi agent to blueprint, feature examples, and combined app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pin @earendil-works/pi-coding-agent@0.82.1 in the axon-agents blueprint and make Pi reachable from both example vehicles over the broker's pi_json protocol, running GLM-5.2 on Runloop's dedicated Nebius endpoint. feature-examples gains a `pi` agent (blueprint install — Pi has no catalog agent mount), a `ctx.pi` branch in single-prompt, and a session-resume-pi use case. combined-app gains PiConnectionManager, /api/pi routes, usePiAgent, and Pi wiring through the registry, timeline and setup UI. Pi has no handshake, so connect() is the whole setup: there is no initialize() step anywhere on the Pi path. Turns settle on agent_settled only — agent_end may be followed by an auto-retry. `--mode rpc` and `--session-dir` stay broker-owned and are never passed from here. Credentials come from the environment: NEBIUS_API_KEY and NEBIUS_BASE_URL travel as Runloop secrets (feature-examples) or devbox environment variables (combined-app), and the models.json written at launch stores only the literal "$NEBIUS_API_KEY" for Pi to interpolate. --- examples/blueprint/Dockerfile | 1 + examples/blueprint/README.md | 3 +- examples/combined-app/.env.example | 2 + examples/combined-app/README.md | 18 +- examples/combined-app/src/client/App.css | 1 + examples/combined-app/src/client/App.tsx | 32 +- .../src/client/components/AgentSidebar.tsx | 8 +- .../src/client/components/SetupCard.tsx | 29 +- .../client/components/TimelineEventItem.tsx | 60 ++- .../combined-app/src/client/hooks/useAgent.ts | 16 +- .../src/client/hooks/useAgentList.ts | 2 +- .../src/client/hooks/usePiAgent.ts | 495 ++++++++++++++++++ examples/combined-app/src/client/types.ts | 37 +- .../combined-app/src/server/agent-registry.ts | 7 +- examples/combined-app/src/server/index.ts | 5 + .../combined-app/src/server/pi-manager.ts | 223 ++++++++ .../src/server/routes/lifecycle.ts | 26 + examples/combined-app/src/server/routes/pi.ts | 52 ++ .../combined-app/src/server/routes/prompt.ts | 35 ++ examples/combined-app/src/shared/ws-events.ts | 6 +- examples/feature-examples/README.md | 2 +- examples/feature-examples/src/agents.ts | 30 +- examples/feature-examples/src/main.ts | 37 +- examples/feature-examples/src/scaffold.ts | 50 +- examples/feature-examples/src/types.ts | 25 +- .../src/use-cases/agent-via-blueprint.ts | 9 +- .../feature-examples/src/use-cases/index.ts | 2 + .../src/use-cases/session-resume-pi.ts | 86 +++ .../src/use-cases/single-prompt.ts | 27 +- .../templates/compatibility.md.template | 4 +- .../templates/llms.txt.template | 4 +- llms.txt | 9 +- 32 files changed, 1266 insertions(+), 77 deletions(-) create mode 100644 examples/combined-app/src/client/hooks/usePiAgent.ts create mode 100644 examples/combined-app/src/server/pi-manager.ts create mode 100644 examples/combined-app/src/server/routes/pi.ts create mode 100644 examples/feature-examples/src/use-cases/session-resume-pi.ts diff --git a/examples/blueprint/Dockerfile b/examples/blueprint/Dockerfile index cc4b1fa..4a0ad53 100644 --- a/examples/blueprint/Dockerfile +++ b/examples/blueprint/Dockerfile @@ -1,6 +1,7 @@ FROM runloop:runloop/starter-x86_64 RUN npm install -g @zed-industries/codex-acp +RUN npm install -g @earendil-works/pi-coding-agent@0.82.1 USER user WORKDIR /home/user diff --git a/examples/blueprint/README.md b/examples/blueprint/README.md index 6a508a8..8c5fe31 100644 --- a/examples/blueprint/README.md +++ b/examples/blueprint/README.md @@ -2,7 +2,7 @@ > **Alpha — subject to change.** This example uses an SDK in early development. APIs and behavior may change without notice between versions. -Builds the shared `axon-agents` Runloop [blueprint](https://docs.runloop.ai/guides/blueprints) used by examples that demonstrate pre-baked agent images. The blueprint bakes the agent binaries (Claude Code, OpenCode, Codex ACP) into a devbox image so subsequent devboxes start quickly and reproducibly. +Builds the shared `axon-agents` Runloop [blueprint](https://docs.runloop.ai/guides/blueprints) used by examples that demonstrate pre-baked agent images. The blueprint bakes the agent binaries (Claude Code, OpenCode, Codex ACP, Codex CLI, Pi) into a devbox image so subsequent devboxes start quickly and reproducibly. **You must run this once before any other example will work.** The other examples create devboxes with `blueprint_name: "axon-agents"` — if that blueprint does not exist on your Runloop account, devbox creation will fail. @@ -44,6 +44,7 @@ See [`Dockerfile`](Dockerfile) for the exact contents. At the time of writing it - [OpenCode](https://opencode.ai) — for ACP examples using OpenCode - [Codex ACP](https://www.npmjs.com/package/@zed-industries/codex-acp) — for ACP examples using Codex - [Codex CLI](https://developers.openai.com/codex/cli) — for native Codex module examples (pinned to 0.144.1, matching the SDK's vendored protocol types) +- [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) — for native Pi module examples (pinned to 0.82.1, matching the wire types hand-written in `sdk/src/pi/protocol/`) ## Alternatives diff --git a/examples/combined-app/.env.example b/examples/combined-app/.env.example index 8a0a79c..9cf0248 100644 --- a/examples/combined-app/.env.example +++ b/examples/combined-app/.env.example @@ -1,3 +1,5 @@ RUNLOOP_API_KEY=your_runloop_api_key ANTHROPIC_API_KEY=your_anthropic_api_key OPENAI_API_KEY=your_openai_api_key +NEBIUS_API_KEY=your_nebius_api_key +NEBIUS_BASE_URL=your_nebius_endpoint_url diff --git a/examples/combined-app/README.md b/examples/combined-app/README.md index 0c379e2..3470abd 100644 --- a/examples/combined-app/README.md +++ b/examples/combined-app/README.md @@ -2,7 +2,7 @@ > **Alpha — subject to change.** This example uses an SDK in early development. APIs and behavior may change without notice between versions. -A full-stack demo that supports ACP, Claude Code, and Codex agents running in Runloop devboxes. An Express backend manages agent connections (one per protocol) and fans out SDK timeline events to a React frontend over a single WebSocket. Multiple agents can run concurrently. +A full-stack demo that supports ACP, Claude Code, Codex, and Pi agents running in Runloop devboxes. An Express backend manages agent connections (one per protocol) and fans out SDK timeline events to a React frontend over a single WebSocket. Multiple agents can run concurrently. ## Prerequisites @@ -10,6 +10,7 @@ A full-stack demo that supports ACP, Claude Code, and Codex agents running in Ru - A [Runloop](https://runloop.ai) API key - An [Anthropic](https://anthropic.com) API key (required for Claude agents) - An [OpenAI](https://platform.openai.com) API key (required for Codex agents) +- A Nebius API key and base URL (required for Pi agents, which run GLM-5.2 on Runloop's dedicated Nebius endpoint) - The `@runloop/remote-agents-sdk` SDK built locally (`cd ../../sdk && bun run build`) ## Setup @@ -29,8 +30,15 @@ Add your keys to `.env`: RUNLOOP_API_KEY=your_runloop_api_key ANTHROPIC_API_KEY=your_anthropic_api_key OPENAI_API_KEY=your_openai_api_key +NEBIUS_API_KEY=your_nebius_api_key +NEBIUS_BASE_URL=your_nebius_endpoint_url ``` +Pi agents read `NEBIUS_API_KEY` and `NEBIUS_BASE_URL` from the devbox +environment. At launch the server writes `~/.pi/agent/models.json` declaring a +`nebius` provider whose `apiKey` is the literal `"$NEBIUS_API_KEY"`, which Pi +interpolates from the environment — the key itself is never written to disk. + Codex agents authenticate via a `~/.codex/auth.json` written into the devbox at launch. By default it's generated in api-key mode from `OPENAI_API_KEY`. To use ChatGPT-plan auth instead, set `CODEX_AUTH_JSON` to the full contents of your @@ -42,7 +50,7 @@ CODEX_AUTH_JSON="$(cat ~/.codex/auth.json)" ### Build the shared blueprint (one-time, required) -This example provisions devboxes with `blueprint_name: "axon-agents"` (see [`src/server/acp-manager.ts`](src/server/acp-manager.ts), [`src/server/claude-manager.ts`](src/server/claude-manager.ts), and [`src/server/codex-manager.ts`](src/server/codex-manager.ts)). That blueprint must exist on your Runloop account before starting an agent from the UI — otherwise `POST /api/start` will fail when creating the devbox. Codex agents require a blueprint built after the Codex CLI was added to the [`Dockerfile`](../blueprint/Dockerfile) — re-run the command below if your `axon-agents` image predates it. +This example provisions devboxes with `blueprint_name: "axon-agents"` (see [`src/server/acp-manager.ts`](src/server/acp-manager.ts), [`src/server/claude-manager.ts`](src/server/claude-manager.ts), and [`src/server/codex-manager.ts`](src/server/codex-manager.ts), and [`src/server/pi-manager.ts`](src/server/pi-manager.ts)). That blueprint must exist on your Runloop account before starting an agent from the UI — otherwise `POST /api/start` will fail when creating the devbox. Codex and Pi agents require a blueprint built after their CLIs were added to the [`Dockerfile`](../blueprint/Dockerfile) — re-run the command below if your `axon-agents` image predates them. From the monorepo root: @@ -66,8 +74,8 @@ Open http://localhost:5176. The Vite dev server proxies `/api/*` and `/ws` to th ## How It Works -1. **Start an agent** — the setup card lets you choose ACP, Claude, or Codex, configure the agent binary / blueprint, and optionally set a system prompt. `POST /api/start` provisions an Axon channel and devbox, then opens the appropriate SDK connection (`ACPAxonConnection`, `ClaudeAxonConnection`, or `CodexAxonConnection`). -2. **Send a prompt** — `POST /api/prompt` dispatches to the active connection's `prompt()` (ACP) or `send()` (Claude/Codex) and returns immediately. +1. **Start an agent** — the setup card lets you choose ACP, Claude, Codex, or Pi, configure the agent binary / blueprint, and optionally set a system prompt. `POST /api/start` provisions an Axon channel and devbox, then opens the appropriate SDK connection (`ACPAxonConnection`, `ClaudeAxonConnection`, `CodexAxonConnection`, or `PiAxonConnection`). +2. **Send a prompt** — `POST /api/prompt` dispatches to the active connection's `prompt()` (ACP) or `send()` (Claude/Codex/Pi) and returns immediately. For Pi, `send()` resolves once the prompt is *accepted*; the turn ends only when `agent_settled` arrives. 3. **Stream events** — the SDK's `onTimelineEvent` callback fires for every classified event (protocol messages, system turns, unknowns). The server broadcasts each event over WebSocket with an `agentId` tag. 4. **Render blocks** — the React client filters events by `agentId`, builds incremental turn blocks (`useBlockManager`), and renders them through `AssistantTurn` / `TurnBlocks`. @@ -84,6 +92,7 @@ src/ │ ├── acp-client.ts ACP Client implementation (permissions, elicitation) │ ├── claude-manager.ts Claude connection lifecycle │ ├── codex-manager.ts Codex connection lifecycle (threads, approvals) +│ ├── pi-manager.ts Pi connection lifecycle (sessions, steer/follow-up) │ └── agent-registry.ts Multi-agent bookkeeping └── client/ ├── main.tsx React entry point @@ -94,6 +103,7 @@ src/ │ ├── useACPAgent.ts ACP event handling and state │ ├── useClaudeAgent.ts Claude event handling and state │ ├── useCodexAgent.ts Codex event handling and state (items, approvals) +│ ├── usePiAgent.ts Pi event handling and state (streaming deltas, tools) │ ├── useBlockManager.ts Turn block accumulation │ ├── useAgentList.ts Agent list polling │ ├── useAttachments.ts File/image attachment handling diff --git a/examples/combined-app/src/client/App.css b/examples/combined-app/src/client/App.css index 0228541..d128aad 100644 --- a/examples/combined-app/src/client/App.css +++ b/examples/combined-app/src/client/App.css @@ -1875,6 +1875,7 @@ html, body, #root { .tl-kind-acp { background: rgba(63, 185, 80, 0.15); color: var(--success); } .tl-kind-claude { background: rgba(136, 87, 255, 0.15); color: #a78bfa; } .tl-kind-codex { background: rgba(240, 246, 252, 0.12); color: #e6edf3; } +.tl-kind-pi { background: rgba(249, 115, 22, 0.15); color: #fb923c; } .tl-kind-unknown { background: rgba(139, 148, 158, 0.15); color: var(--text-secondary); } .tl-kind-custom { background: rgba(56, 189, 248, 0.15); color: #38bdf8; } diff --git a/examples/combined-app/src/client/App.tsx b/examples/combined-app/src/client/App.tsx index 767c75c..b47a06d 100644 --- a/examples/combined-app/src/client/App.tsx +++ b/examples/combined-app/src/client/App.tsx @@ -243,12 +243,18 @@ export default function App() { autoApprovePermissions: startAutoApprove, ...sharedConfig, } - : { - blueprintName: blueprintName || undefined, - model: model || undefined, - dangerouslySkipPermissions: startAutoApprove, - ...sharedConfig, - }; + : selectedAgentType === "pi" + ? { + blueprintName: blueprintName || undefined, + model: model || undefined, + ...sharedConfig, + } + : { + blueprintName: blueprintName || undefined, + model: model || undefined, + dangerouslySkipPermissions: startAutoApprove, + ...sharedConfig, + }; try { const resp = await api<{ agentId: string; agentType: AgentType; [key: string]: unknown }>( @@ -266,7 +272,9 @@ export default function App() { ? (blueprintName || "Claude Agent") : selectedAgentType === "codex" ? (blueprintName || "Codex Agent") - : (agentBinary || "ACP Agent"), + : selectedAgentType === "pi" + ? (blueprintName || "Pi Agent") + : (agentBinary || "ACP Agent"), axonId: resp.axonId as string, devboxId: resp.devboxId as string, createdAt: Date.now(), @@ -430,7 +438,9 @@ export default function App() { ? "Claude Code" : agent.agentType === "codex" ? "Codex" - : "ACP Agent"; + : agent.agentType === "pi" + ? "Pi" + : "ACP Agent"; // Derive devbox status from the last devbox_lifecycle system event in messages const lastDevboxEvent = [...agent.messages].reverse().find( @@ -510,6 +520,12 @@ export default function App() { )} + {agent.agentType === "pi" && agent.sessionId && ( +
+ Session: {agent.sessionId} +
+ )} + {agent.agentType === "codex" && agent.threadId && (
Thread: {agent.threadId} diff --git a/examples/combined-app/src/client/components/AgentSidebar.tsx b/examples/combined-app/src/client/components/AgentSidebar.tsx index 8fdd794..dd8aa9c 100644 --- a/examples/combined-app/src/client/components/AgentSidebar.tsx +++ b/examples/combined-app/src/client/components/AgentSidebar.tsx @@ -42,7 +42,13 @@ export function AgentSidebar({ >
- {agent.agentType === "claude" ? "C" : agent.agentType === "codex" ? "X" : "A"} + {agent.agentType === "claude" + ? "C" + : agent.agentType === "codex" + ? "X" + : agent.agentType === "pi" + ? "P" + : "A"} {agent.name}
@@ -86,6 +86,13 @@ export function SetupCard({ > Codex +