Skip to content

feat: add Pi CLI adapter for multi-provider LLM support#58

Closed
charles-cooper wants to merge 1 commit intoevmts:mainfrom
charles-cooper:pi-backend
Closed

feat: add Pi CLI adapter for multi-provider LLM support#58
charles-cooper wants to merge 1 commit intoevmts:mainfrom
charles-cooper:pi-backend

Conversation

@charles-cooper
Copy link

@charles-cooper charles-cooper commented Jan 28, 2026

Summary

Adds <Pi> component that spawns pi --mode json to execute agents, giving Smithers multi-provider LLM support via the pi CLI.

Changes

  • New component: <Pi provider="anthropic" model="sonnet">prompt</Pi>
  • Stream parser: Handles pi's JSON event stream with proper block lifecycle
  • Error handling: Auth failures, missing CLI, rate limits
  • Unit tests: 63 test cases for arg-builder, errors, stream parser

Usage

<Pi provider="anthropic" model="claude-sonnet-4-5" thinking="medium">
  Fix the failing tests
</Pi>

<Pi provider="openai" model="gpt-4o">
  Review this code
</Pi>

Design

  • CLI spawn (not direct API) for loose coupling and process isolation
  • Reuses existing executeCLI infrastructure
  • Extends BaseStreamParser for consistent patterns

Testing

  • bun test src/components/agents/pi-cli/ src/streaming/pi-parser.test.ts

PROMPT

PR #58: Pi CLI Adapter - Session Transcript

Overview

This document captures the user prompts, design discussions, and decisions that led to the Pi CLI adapter implementation.


User Prompts (Chronological)

1. Initial Request

User: explore this codebase and figure out how to adapt it to pi sdk (~/pi-mono)

2. Understanding Current Architecture

User: how do agent backends work in smithers rn

3. Challenging the Proposed Approach

User: you are suggesting using direct api calls via pi sdk instead of spawning pi cli? explain why this might be a good idea

4. Critical Design Constraint

User: like you should also consider how this is going to fit into smithers, given that it's owned by a 3rd party

5. Validation Request

User: is the startup overhead the only difference? validate these assumptions with a trio of review subagents

6. Implementation Kickoff

User: spawn another trio of subagents to design the code, then (combined with your own idea about how the code should look) write a task file. then, spawn an opus subagent to implement it. lastly, spawn a review trio to check adherence with the plan, then report back to me.

7. Iteration Style

User: yes, next time don't ask, just spawn review agents / fix agent until it's done.

8. Specific Agent Assignment

User: have chatgpt pro fix the bugs (give it read and edit tool only), then have an opus subagent clean up any syntax errors after. then spawn the review trio again

9. Final Tasks

User: do both in subagents. for the unit tests, follow the same patterns as in the codebase.


Design Discussion

Initial Proposal (Model-Generated)

Two integration options were proposed:

  1. Direct API - Import @mariozechner/pi-agent and make direct LLM calls
  2. CLI Spawn - Spawn pi --mode json as subprocess

Initial model recommendation leaned toward Direct API for:

  • No spawn overhead (~50-100ms per agent)
  • Native streaming events
  • In-process tool execution
  • Type safety

User Challenge

The user questioned whether startup overhead was the only meaningful difference, prompting a deeper analysis via three review subagents (Opus, Gemini, GPT).

Review Findings (Model-Generated via Subagents)

Opus identified:

  • Process isolation (crash containment)
  • Resource cleanup handled by OS
  • Session/context management differs
  • MCP server lifecycle management
  • Tool sandboxing differences
  • Stop conditions: proc.kill() vs cooperative abort()

Gemini identified:

  • Direct API would duplicate ~300 LOC that pi-agent already provides
  • But CLI approach maintains independence

GPT identified:

  • Security implications of shared vs isolated memory
  • Tool execution context exposure
  • Cancellation semantics differ materially

Pivotal User Insight

User: like you should also consider how this is going to fit into smithers, given that it's owned by a 3rd party

This reframed the decision from purely technical to architectural/ownership:

CLI Spawn Direct API
Loose coupling via process boundary Tight coupling to pi SDK internals
Can swap pi for any CLI TypeScript types must match
Upgrade independently Version conflicts possible
No dependency in package.json pi becomes core dependency

Final Decision

CLI spawning is the right answer for a third-party integration:

  • Smithers stays independent of pi's release cycle
  • Process isolation is a feature, not overhead
  • The "startup overhead" concern is noise (50ms vs 10s+ LLM calls)
  • Can always switch to direct API later if same team owns both

Implementation Approach

Architecture

<Pi model="sonnet">prompt</Pi>
       │
       ▼
PiAdapter.execute()
       │
       ▼
Bun.spawn(['pi', '--mode', 'json', '-p', '--no-session', ...])
       │
       ▼
PiStreamParser (JSON events → SmithersStreamPart)

Key Design Decisions

  1. Reuse existing infrastructure

    • executeCLI from shared/cli-executor.ts
    • BaseStreamParser for consistent patterns
    • Adapter interface matches Claude/Amp/Codex adapters
  2. Event mapping

    • Pi JSON events → SmithersStreamPart types
    • Block lifecycle tracking (text, reasoning, tools)
    • Message boundaries handled correctly
  3. Error handling

    • PiNotInstalledError with install instructions
    • PiAuthError with provider context
    • Rate limit detection
  4. Limitations accepted for v1

    • No custom tools (pi uses extensions, not MCP)
    • No session resume
    • Token usage returns zeros (pi doesn't stream this yet)

Review Iterations

The implementation went through 6 review rounds with fix iterations:

Round Issues Found Fixed
1 4 critical (empty delta, static IDs, guards, contentIndex)
2 Event type mismatch (messages vs message)
3 isError handling, usage summing
4 Message boundaries, tool lifecycle
5 toolName lookup (pi omits from end event)
6 flush() buffer parsing, message_end cleanup

Final Deliverable

  • 12 files, +1206 lines
  • 63 unit test cases
  • Production-ready stream parser with proper block lifecycle
  • Commit: a44827c

- Add <Pi> component that spawns pi --mode json
- PiStreamParser handles JSON events with block lifecycle
- Error handling for auth, missing CLI, rate limits
- Unit tests for arg-builder, errors, stream parser
@charles-cooper charles-cooper marked this pull request as ready for review January 28, 2026 22:29
@roninjin10
Copy link
Contributor

Pi support added #72

@roninjin10 roninjin10 closed this Feb 13, 2026
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

Comments