Skip to content

Latest commit

 

History

History
2124 lines (1690 loc) · 54.7 KB

File metadata and controls

2124 lines (1690 loc) · 54.7 KB

Existing Pi Structure and Architecture

After reading this document, you should NOT need to consult the legacy TypeScript code.

This document is the authoritative specification for the Rust port of pi-mono.


Table of Contents

  1. Project Overview
  2. Message Types and Content Blocks
  3. Streaming Events
  4. Provider Interface
  5. Tool System
  6. Session File Format
  7. Configuration
  8. Authentication Storage
  9. CLI Commands and Flags
  10. Execution Flow
  11. RPC Mode Protocol
  12. Extensions API
  13. Resources System

1. Project Overview

Pi is an AI coding agent platform with these core components:

Component TypeScript Package Rust Equivalent
LLM Provider Abstraction @mariozechner/pi-ai pi::provider module
Agent Runtime @mariozechner/pi-agent pi::agent module
CLI Application @mariozechner/pi-coding-agent pi binary
Terminal UI @mariozechner/pi-tui pi::tui module

Key Statistics (TypeScript)

  • Core legacy tool set: read, bash, edit, write, grep, find, ls
  • 20+ LLM providers: Anthropic, OpenAI, Google, Bedrock, etc.
  • Session format version: 3
  • Default tools enabled: read, bash, edit, write

2. Message Types and Content Blocks

2.1 Message Union Type

pub enum Message {
    User(UserMessage),
    Assistant(AssistantMessage),
    ToolResult(ToolResultMessage),
}

2.2 User Message

pub struct UserMessage {
    pub content: UserContent,  // String or Vec<ContentBlock>
    pub timestamp: i64,        // Unix milliseconds
}

pub enum UserContent {
    Text(String),
    Blocks(Vec<ContentBlock>),  // TextContent | ImageContent only
}

2.3 Assistant Message

pub struct AssistantMessage {
    pub content: Vec<AssistantContentBlock>,  // Text | Thinking | ToolCall
    pub api: String,                          // e.g., "anthropic-messages"
    pub provider: String,                     // e.g., "anthropic"
    pub model: String,                        // Model ID
    pub usage: Usage,
    pub stop_reason: StopReason,
    pub error_message: Option<String>,
    pub timestamp: i64,
}

2.4 Tool Result Message

pub struct ToolResultMessage {
    pub tool_call_id: String,
    pub tool_name: String,
    pub content: Vec<ContentBlock>,  // TextContent | ImageContent
    pub details: Option<serde_json::Value>,
    pub is_error: bool,
    pub timestamp: i64,
}

2.5 Stop Reason

pub enum StopReason {
    Stop,      // Normal completion
    Length,    // Max tokens reached
    ToolUse,   // Tool call pending
    Error,     // Error occurred
    Aborted,   // User cancelled
}

2.6 Content Blocks

pub enum ContentBlock {
    Text(TextContent),
    Thinking(ThinkingContent),
    Image(ImageContent),
    ToolCall(ToolCall),
}

pub struct TextContent {
    pub text: String,
    pub text_signature: Option<String>,  // Provider-specific
}

pub struct ThinkingContent {
    pub thinking: String,
    pub thinking_signature: Option<String>,  // For replay
}

pub struct ImageContent {
    pub data: String,       // Base64 encoded
    pub mime_type: String,  // "image/jpeg", "image/png", etc.
}

pub struct ToolCall {
    pub id: String,
    pub name: String,
    pub arguments: serde_json::Value,
    pub thought_signature: Option<String>,  // Google-specific
}

2.7 Usage Tracking

pub struct Usage {
    pub input: u64,        // Input tokens (excluding cache read)
    pub output: u64,       // Output tokens
    pub cache_read: u64,   // Tokens read from cache
    pub cache_write: u64,  // Tokens written to cache
    pub total_tokens: u64,
    pub cost: Cost,
}

pub struct Cost {
    pub input: f64,       // Dollars
    pub output: f64,
    pub cache_read: f64,
    pub cache_write: f64,
    pub total: f64,
}

3. Streaming Events

3.1 Event Types

pub enum StreamEvent {
    Start { partial: AssistantMessage },

    TextStart { content_index: usize, partial: AssistantMessage },
    TextDelta { content_index: usize, delta: String, partial: AssistantMessage },
    TextEnd { content_index: usize, content: String, partial: AssistantMessage },

    ThinkingStart { content_index: usize, partial: AssistantMessage },
    ThinkingDelta { content_index: usize, delta: String, partial: AssistantMessage },
    ThinkingEnd { content_index: usize, content: String, partial: AssistantMessage },

    ToolCallStart { content_index: usize, partial: AssistantMessage },
    ToolCallDelta { content_index: usize, delta: String, partial: AssistantMessage },
    ToolCallEnd { content_index: usize, tool_call: ToolCall, partial: AssistantMessage },

    Done { reason: StopReason, message: AssistantMessage },
    Error { reason: StopReason, error: AssistantMessage },
}

3.2 Event Sequences

Text response:

Start → TextStart → TextDelta* → TextEnd → Done(Stop)

Tool call:

Start → ToolCallStart → ToolCallDelta* → ToolCallEnd → Done(ToolUse)

With thinking:

Start → ThinkingStart → ThinkingDelta* → ThinkingEnd → TextStart → ... → Done

4. Provider Interface

4.1 Provider Trait

#[async_trait]
pub trait Provider: Send + Sync {
    fn name(&self) -> &str;
    fn api(&self) -> &str;

    async fn stream(
        &self,
        context: &Context,
        options: &StreamOptions,
    ) -> Result<Pin<Box<dyn Stream<Item = Result<StreamEvent>> + Send>>>;
}

4.2 Context

pub struct Context {
    pub system_prompt: Option<String>,
    pub messages: Vec<Message>,
    pub tools: Vec<ToolDef>,
}

4.3 Stream Options

pub struct StreamOptions {
    pub temperature: Option<f32>,
    pub max_tokens: Option<u32>,
    pub api_key: Option<String>,
    pub cache_retention: CacheRetention,
    pub session_id: Option<String>,
    pub headers: HashMap<String, String>,
    pub thinking_level: Option<ThinkingLevel>,
    pub thinking_budgets: Option<ThinkingBudgets>,
}

pub enum CacheRetention {
    None,
    Short,
    Long,  // 1 hour TTL on Anthropic
}

pub enum ThinkingLevel {
    Off,
    Minimal,  // 1024 tokens
    Low,      // 2048 tokens
    Medium,   // 8192 tokens
    High,     // 16384 tokens
    XHigh,    // Model max
}

pub struct ThinkingBudgets {
    pub minimal: u32,  // Default: 1024
    pub low: u32,      // Default: 2048
    pub medium: u32,   // Default: 8192
    pub high: u32,     // Default: 16384
}

4.4 Model Definition

pub struct Model {
    pub id: String,
    pub name: String,
    pub api: String,              // "anthropic-messages", "openai-completions", etc.
    pub provider: String,         // "anthropic", "openai", etc.
    pub base_url: String,
    pub reasoning: bool,          // Supports thinking/reasoning
    pub input: Vec<InputType>,    // ["text", "image"]
    pub cost: ModelCost,
    pub context_window: u32,
    pub max_tokens: u32,
    pub headers: HashMap<String, String>,
}

pub struct ModelCost {
    pub input: f64,       // $/million tokens
    pub output: f64,
    pub cache_read: f64,
    pub cache_write: f64,
}

pub enum InputType {
    Text,
    Image,
}

4.5 Known APIs

pub enum Api {
    AnthropicMessages,
    OpenAICompletions,
    OpenAIResponses,
    AzureOpenAIResponses,
    BedrockConverseStream,
    GoogleGenerativeAI,
    GoogleGeminiCli,
    GoogleVertex,
    Custom(String),
}

4.6 Known Providers

pub enum KnownProvider {
    Anthropic,
    OpenAI,
    Google,
    GoogleVertex,
    AmazonBedrock,
    AzureOpenAI,
    GithubCopilot,
    XAI,
    Groq,
    Cerebras,
    OpenRouter,
    Mistral,
    // ... more
}

5. Tool System

5.1 Tool Trait

#[async_trait]
pub trait Tool: Send + Sync {
    fn name(&self) -> &str;
    fn label(&self) -> &str;
    fn description(&self) -> &str;
    fn parameters(&self) -> serde_json::Value;  // JSON Schema

    async fn execute(
        &self,
        tool_call_id: &str,
        input: serde_json::Value,
        on_update: Option<Box<dyn Fn(ToolUpdate) + Send>>,
    ) -> Result<ToolOutput>;
}

pub struct ToolOutput {
    pub content: Vec<ContentBlock>,
    pub details: Option<serde_json::Value>,
}

pub struct ToolUpdate {
    pub content: Vec<ContentBlock>,
    pub details: Option<serde_json::Value>,
}

5.2 Tool Definition for API

pub struct ToolDef {
    pub name: String,
    pub description: String,
    pub parameters: serde_json::Value,  // JSON Schema
}

5.3 Built-in Tools

READ Tool

Purpose: Read file contents (text or images)

Parameters:

{
  "type": "object",
  "properties": {
    "path": { "type": "string", "description": "File path (relative or absolute)" },
    "offset": { "type": "integer", "description": "1-indexed line to start from" },
    "limit": { "type": "integer", "description": "Max lines to read" }
  },
  "required": ["path"]
}

Behavior:

  • Truncation: 2000 lines OR 50KB (whichever first)
  • Truncation method: truncate_head (keeps beginning)
  • Image support: jpg, png, gif, webp → base64 with optional resize to 2000x2000
  • Supports ~ expansion in paths

Error conditions:

  • Path not found
  • Permission denied
  • Offset beyond EOF

BASH Tool

Purpose: Execute shell commands

Parameters:

{
  "type": "object",
  "properties": {
    "command": { "type": "string", "description": "Bash command to execute" },
    "timeout": { "type": "integer", "description": "Timeout in seconds" }
  },
  "required": ["command"]
}

Behavior:

  • Streams output via on_update callback (last 100KB rolling buffer)
  • Truncation: 2000 lines OR 50KB (whichever first)
  • Truncation method: truncate_tail (keeps end, shows errors)
  • Creates temp file if output > 50KB (path in details.full_output_path)
  • Inherits shell environment
  • Optional command prefix (e.g., "shopt -s expand_aliases")

Error conditions:

  • Non-zero exit code → Error with output + "Command exited with code X"
  • Timeout → Error with output + "Command timed out after X seconds"
  • Abort → Error "Command aborted"
  • Kills entire process tree on timeout/abort

WRITE Tool

Purpose: Create or overwrite files

Parameters:

{
  "type": "object",
  "properties": {
    "path": { "type": "string", "description": "File path" },
    "content": { "type": "string", "description": "Content to write" }
  },
  "required": ["path", "content"]
}

Behavior:

  • Creates parent directories recursively
  • UTF-8 encoding
  • Output: "Successfully wrote X bytes to path"

EDIT Tool

Purpose: Replace exact text in files

Parameters:

{
  "type": "object",
  "properties": {
    "path": { "type": "string", "description": "File path" },
    "oldText": { "type": "string", "description": "Exact text to find" },
    "newText": { "type": "string", "description": "Replacement text" }
  },
  "required": ["path", "oldText", "newText"]
}

Matching algorithm:

  1. Try exact match
  2. Fall back to fuzzy match:
    • Strip trailing whitespace per line
    • Normalize smart quotes (U+2018-U+201F → ' or ")
    • Normalize dashes (U+2010-U+2015, U+2212 → -)
    • Normalize special spaces (U+00A0, U+2002-U+200A, etc. → space)

Validation:

  • Must find oldText exactly once
  • Must actually change content (old != new)

Line ending handling:

  • Detects original line ending (CRLF vs LF)
  • Normalizes to LF internally
  • Restores original ending in output

BOM handling:

  • Strips UTF-8 BOM before matching
  • Restores BOM in output

Output includes:

  • Success message
  • Unified diff with line numbers
  • First changed line number

GREP Tool

Purpose: Search file contents (via ripgrep)

Parameters:

{
  "type": "object",
  "properties": {
    "pattern": { "type": "string", "description": "Regex or literal pattern" },
    "path": { "type": "string", "description": "Directory or file", "default": "." },
    "glob": { "type": "string", "description": "Glob filter (e.g., *.ts)" },
    "ignoreCase": { "type": "boolean", "default": false },
    "literal": { "type": "boolean", "default": false },
    "context": { "type": "integer", "default": 0 },
    "limit": { "type": "integer", "default": 100 }
  },
  "required": ["pattern"]
}

Behavior:

  • Uses ripgrep (rg --json)
  • Respects .gitignore
  • Searches dotfiles (--hidden)
  • Individual lines truncated to 500 chars

Limits:

  • Match limit: 100 (default)
  • Byte limit: 50KB
  • Line length: 500 chars

Output format:

path/file.ts:42: matching line content
path/file.ts-41- context line

FIND Tool

Purpose: Find files by glob pattern (via fd)

Parameters:

{
  "type": "object",
  "properties": {
    "pattern": { "type": "string", "description": "Glob pattern (e.g., *.ts)" },
    "path": { "type": "string", "description": "Directory", "default": "." },
    "limit": { "type": "integer", "default": 1000 }
  },
  "required": ["pattern"]
}

Behavior:

  • Uses fd (fd --glob)
  • Respects .gitignore
  • Searches dotfiles (--hidden)
  • Returns relative paths
  • Directories marked with trailing /

Limits:

  • Result limit: 1000 (default)
  • Byte limit: 50KB

LS Tool

Purpose: List directory contents

Parameters:

{
  "type": "object",
  "properties": {
    "path": { "type": "string", "description": "Directory", "default": "." },
    "limit": { "type": "integer", "default": 500 }
  }
}

Behavior:

  • Sorted alphabetically (case-insensitive)
  • Directories marked with trailing /
  • Includes dotfiles
  • Skips entries that fail stat

Limits:

  • Entry limit: 500 (default)
  • Byte limit: 50KB

5.4 Truncation Constants

pub const DEFAULT_MAX_LINES: usize = 2000;
pub const DEFAULT_MAX_BYTES: usize = 50 * 1024;  // 50KB
pub const GREP_MAX_LINE_LENGTH: usize = 500;

6. Session File Format

6.1 File Organization

~/.pi/agent/sessions/
└── --{encoded-cwd}--/
    └── {timestamp}_{session-id}.jsonl

CWD encoding: --${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--

  • Example: /home/user/project--home-user-project--

Timestamp format: YYYY-MM-DDTHH-mm-ss.sssZ (colons replaced with hyphens)

6.2 Session Version

Current version: 3

6.3 Header Structure

pub struct SessionHeader {
    pub r#type: String,              // "session"
    pub version: Option<u8>,         // Usually 3
    pub id: String,                  // UUID
    pub timestamp: String,           // ISO-8601
    pub cwd: String,                 // Absolute path
    pub provider: Option<String>,    // Provider name (optional)
    pub model_id: Option<String>,    // Model ID (optional)
    pub thinking_level: Option<String>,  // "off"|"minimal"|... (optional)
    pub parent_session: Option<String>,  // Parent session path (serialized as "branchedFrom"; accepts legacy "parentSession")
}

Serialization:

{
  "type": "session",
  "version": 3,
  "id": "uuid-string",
  "timestamp": "2024-01-15T10:30:45.123Z",
  "cwd": "/absolute/path/to/dir",
  "provider": "anthropic",
  "modelId": "claude-sonnet-4-20250514",
  "thinkingLevel": "medium",
  "branchedFrom": "/path/to/parent.jsonl"
}

6.4 Entry Types

All entries have base fields:

pub struct EntryBase {
    pub id: Option<String>,   // 8-char hex or UUID (may be missing on disk)
    pub parent_id: Option<String>,
    pub timestamp: String,    // ISO-8601
}

Message Entry

pub struct MessageEntry {
    #[serde(flatten)]
    pub base: EntryBase,      // type: "message"
    pub message: SessionMessage,
}

pub enum SessionMessage {
    User { content: UserContent, timestamp: Option<i64> },
    Assistant { /* full AssistantMessage fields */ },
    ToolResult { tool_use_id: String, content: Vec<ContentBlock>, timestamp: Option<i64> },
    Custom { custom_type: String, content: String, display: bool, details: Option<Value> },
    BashExecution { command: String, output: String, exit_code: i32, /* ... */ },
    BranchSummary { summary: String, from_id: String },
    CompactionSummary { summary: String, tokens_before: u64 },
}

Model Change Entry

pub struct ModelChangeEntry {
    #[serde(flatten)]
    pub base: EntryBase,      // type: "model_change"
    pub provider: String,
    pub model_id: String,
}

Thinking Level Change Entry

pub struct ThinkingLevelChangeEntry {
    #[serde(flatten)]
    pub base: EntryBase,      // type: "thinking_level_change"
    pub thinking_level: String,  // "off"|"minimal"|"low"|"medium"|"high"|"xhigh"
}

Compaction Entry

pub struct CompactionEntry {
    #[serde(flatten)]
    pub base: EntryBase,      // type: "compaction"
    pub summary: String,
    pub first_kept_entry_id: String,
    pub tokens_before: u64,
    pub details: Option<Value>,
    pub from_hook: Option<bool>,
}

Branch Summary Entry

pub struct BranchSummaryEntry {
    #[serde(flatten)]
    pub base: EntryBase,      // type: "branch_summary"
    pub from_id: String,      // "root" or entry ID
    pub summary: String,
    pub details: Option<Value>,
    pub from_hook: Option<bool>,
}

Label Entry

pub struct LabelEntry {
    #[serde(flatten)]
    pub base: EntryBase,      // type: "label"
    pub target_id: String,
    pub label: Option<String>,  // None = delete label
}

Session Info Entry

pub struct SessionInfoEntry {
    #[serde(flatten)]
    pub base: EntryBase,      // type: "session_info"
    pub name: Option<String>,
}

6.5 Tree Structure

  • Each entry has id and parent_id
  • Forms a linked tree enabling branching
  • Leaf pointer tracks current position
  • Moving leaf pointer enables branching without modifying history

6.6 ID Generation

  • Format: 8-character hexadecimal (from UUID slice)
  • Collision checking: 100 retries before full UUID
  • Uniqueness scope: Per-session only

7. Configuration

7.1 File Locations

Type Path
Global settings ~/.pi/agent/settings.json
Project settings ./.pi/settings.json
Auth ~/.pi/agent/auth.json
Models ~/.pi/agent/models.json
Sessions ~/.pi/agent/sessions/

7.2 Settings Structure

pub struct Settings {
    // Appearance
    pub theme: Option<String>,
    pub hide_thinking_block: Option<bool>,  // Default: false
    pub show_hardware_cursor: Option<bool>, // Default: false

    // Model Configuration
    pub default_provider: Option<String>,
    pub default_model: Option<String>,
    pub default_thinking_level: Option<String>,
    pub enabled_models: Option<Vec<String>>,  // Patterns for Ctrl+P cycling

    // Message Handling
    pub steering_mode: Option<String>,        // "all" | "one-at-a-time"
    pub follow_up_mode: Option<String>,       // "all" | "one-at-a-time"

    // Terminal Behavior
    pub quiet_startup: Option<bool>,          // Default: false
    pub collapse_changelog: Option<bool>,     // Default: false
    pub double_escape_action: Option<String>, // "fork" | "tree" | "none"
    pub editor_padding_x: Option<u32>,        // Default: 0
    pub autocomplete_max_visible: Option<u32>,// Default: 5

    // Compaction
    pub compaction: Option<CompactionSettings>,

    // Branch Summarization
    pub branch_summary: Option<BranchSummarySettings>,

    // Retry Configuration
    pub retry: Option<RetrySettings>,

    // Shell
    pub shell_path: Option<String>,
    pub shell_command_prefix: Option<String>,

    // Images
    pub images: Option<ImageSettings>,

    // Terminal Display
    pub terminal: Option<TerminalSettings>,

    // Thinking Budgets
    pub thinking_budgets: Option<ThinkingBudgets>,

    // Extensions/Skills/etc.
    pub packages: Option<Vec<PackageSource>>,
    pub extensions: Option<Vec<String>>,
    pub skills: Option<Vec<String>>,
    pub prompts: Option<Vec<String>>,
    pub themes: Option<Vec<String>>,
    pub enable_skill_commands: Option<bool>,  // Default: true
}

pub struct CompactionSettings {
    pub enabled: Option<bool>,         // Default: true
    pub reserve_tokens: Option<u32>,   // Default: 16384
    pub keep_recent_tokens: Option<u32>, // Default: 20000
}

pub struct RetrySettings {
    pub enabled: Option<bool>,         // Default: true
    pub max_retries: Option<u32>,      // Default: 3
    pub base_delay_ms: Option<u32>,    // Default: 2000
    pub max_delay_ms: Option<u32>,     // Default: 60000
}

pub struct ImageSettings {
    pub auto_resize: Option<bool>,     // Default: true (2000x2000 max)
    pub block_images: Option<bool>,    // Default: false
}

pub struct TerminalSettings {
    pub show_images: Option<bool>,     // Default: true (if supported)
    pub clear_on_shrink: Option<bool>, // Default: false
}

7.3 Settings Precedence

  1. CLI flags (highest)
  2. Environment variables
  3. Project settings (./.pi/settings.json)
  4. Global settings (~/.pi/agent/settings.json)
  5. Built-in defaults (lowest)

7.4 Environment Variables

// Config paths
PI_CODING_AGENT_DIR     // Override ~/.pi/agent
PI_PACKAGE_DIR          // Override package assets

// API Keys (per provider)
ANTHROPIC_API_KEY
OPENAI_API_KEY
GOOGLE_API_KEY
GOOGLE_CLOUD_API_KEY
AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY
XAI_API_KEY
GROQ_API_KEY
CEREBRAS_API_KEY
OPENROUTER_API_KEY
MISTRAL_API_KEY
// ... etc.

8. Authentication Storage

8.1 Auth File

  • Path: ~/.pi/agent/auth.json
  • Permissions: 0o600 (read-write owner only)
  • Locking: File lock with 30-second stale timeout

8.2 Credential Types

pub enum AuthCredential {
    ApiKey { key: String },
    OAuth {
        access_token: String,
        refresh_token: String,
        expires: i64,  // Unix milliseconds
    },
}

8.3 Auth File Structure

{
  "anthropic": { "type": "api_key", "key": "sk-ant-..." },
  "github-copilot": {
    "type": "oauth",
    "access_token": "...",
    "refresh_token": "...",
    "expires": 1234567890000
  }
}

8.4 API Key Resolution Priority

  1. Runtime override (--api-key flag)
  2. API key from auth.json (type: "api_key")
  3. OAuth token from auth.json (auto-refresh if expired)
  4. Environment variable (provider-specific)
  5. Fallback resolver (custom providers)

8.5 OAuth: Anthropic (Claude Pro/Max)

This port supports Anthropic OAuth as an alternative to API keys. Credentials are stored in auth.json under the provider key "anthropic" with type: "oauth".

8.5.1 PKCE

The login flow uses PKCE (RFC 7636):

  • verifier: 32 random bytes, Base64URL (no padding)
  • challenge: Base64URL(SHA256(verifier))

8.5.2 Authorization URL

  • client_id: 9d1c250a-e61b-44d9-88ed-5944d1962f5e
  • authorize_url: https://claude.ai/oauth/authorize
  • redirect_uri: https://console.anthropic.com/oauth/code/callback
  • scopes: org:create_api_key user:profile user:inference

Query params:

  • code=true
  • client_id=<client_id>
  • response_type=code
  • redirect_uri=<redirect_uri>
  • scope=<scopes>
  • code_challenge=<challenge>
  • code_challenge_method=S256
  • state=<verifier>

The user completes login in the browser and then pastes either:

  • the full callback URL, or
  • code#state, or
  • just code (in which case state defaults to the original verifier)

8.5.3 Token Exchange

POST https://console.anthropic.com/v1/oauth/token with JSON:

{
  "grant_type": "authorization_code",
  "client_id": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
  "code": "<code>",
  "state": "<state>",
  "redirect_uri": "https://console.anthropic.com/oauth/code/callback",
  "code_verifier": "<verifier>"
}

Response JSON:

{ "access_token": "...", "refresh_token": "...", "expires_in": 1234 }

Expiry is stored in milliseconds as:

expires = now_ms + (expires_in * 1000) - (5 * 60 * 1000)

8.5.4 Refresh

If an OAuth credential is expired, it must be refreshed automatically.

POST https://console.anthropic.com/v1/oauth/token with JSON:

{
  "grant_type": "refresh_token",
  "client_id": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
  "refresh_token": "<refresh_token>"
}

The refreshed credentials overwrite the stored entry in auth.json and are persisted immediately.


9. CLI Commands and Flags

9.1 Package Commands

Command Syntax Description
install pi install <source> [-l|--local] Install extension/skill/prompt/theme
remove pi remove <source> [-l|--local] Remove from settings
update pi update [source] Update all or specific source
list pi list List global + project packages
config pi config Open TUI config selector

9.2 Flags (Complete List)

Help & Version

Flag Aliases Type Default
--help -h bool false
--version -v bool false

Model Configuration

Flag Type Default Description
--provider string "google" Provider name
--model string "gemini-2.5-flash" Model ID
--api-key string None API key override
--models string None Comma-separated model patterns for cycling

Thinking/Reasoning

Flag Type Values Default
--thinking enum off, minimal, low, medium, high, xhigh None

System Prompt

Flag Type Description
--system-prompt string Override system prompt
--append-system-prompt string Append to system prompt

Session Management

Flag Aliases Type Description
--continue -c bool Continue previous session
--resume -r bool Select session from picker
--session string Specific session file path
--session-dir string Session storage directory
--no-session bool Ephemeral session

Mode & Output

Flag Aliases Type Values
--mode enum text, json, rpc
--print -p bool Non-interactive mode
--verbose bool Verbose startup

Tools

Flag Type Default
--no-tools bool Disable all tools
--tools string "read,bash,edit,write"

Extensions

Flag Aliases Type
--extension -e string (repeatable)
--no-extensions bool

Skills

Flag Type
--skill string (repeatable)
--no-skills bool

Prompt Templates

Flag Type
--prompt-template string (repeatable)
--no-prompt-templates bool

Themes

Flag Type
--theme string (repeatable)
--no-themes bool

Export & Listing

Flag Type Description
--export string Export session to HTML
--list-models bool/string List available models

9.3 Positional Arguments

  • File arguments: Prefixed with @ (e.g., @file.md)
  • Message arguments: Any non-flag positional argument

9.4 Usage

pi [options] [@files...] [messages...]

10. Execution Flow

1. Check for package commands (install/remove/update/list/config)
   └─ If matched: execute & exit(0)

2. Run migrations

3. First pass: parse extension/skill/prompt/theme flags
   └─ Load resources

4. Second pass: parse with extension-registered flags

5. Early exits:
   ├─ --version → print version & exit(0)
   ├─ --help → print help & exit(0)
   ├─ --list-models → list models & exit(0)
   └─ --export → export & exit(0/1)

6. Handle stdin (if not TTY and not RPC mode)
   └─ Prepend stdin to messages, force print=true

7. Prepare initial message from @files and message args

8. Determine mode:
   └─ isInteractive = !print && mode == undefined

9. Create session manager:
   ├─ --no-session → in-memory
   ├─ --session → open/fork specific file
   ├─ --continue → continue most recent
   ├─ --resume → show picker
   └─ default → create new

10. Resolve model scope from --models or settings

11. Build session options

12. Apply --api-key override

13. Create agent session
    └─ exit(1) if no model and not interactive

14. Clamp thinking level to model capabilities

15. Run mode:
    ├─ RPC → runRpcMode() [continues indefinitely]
    ├─ Interactive → InteractiveMode.run() [continues indefinitely]
    └─ Print → runPrintMode() → exit(0)

10.1 Exit Codes

Code Meaning
0 Success
1 Error (invalid args, file not found, API error, etc.)

11. RPC Mode Protocol (JSON over stdio)

11.1 Start RPC Mode

pi --mode rpc [options]

Common options:

  • --provider <name>
  • --model <id>
  • --no-session
  • --session-dir <path>

11.2 Protocol Overview

  • Commands: JSON objects sent to stdin, one per line.
  • Responses: JSON objects with type: "response" indicating success/failure.
  • Events: JSON objects streamed to stdout as JSON lines.
  • All commands support optional id for correlation; responses echo id.

11.3 Commands

Prompting

prompt

  • Request: {"id":"req-1","type":"prompt","message":"Hello"}
  • Optional images: array of ImageContent objects (type:"image", source = {type:"base64", mediaType, data}).
  • Optional streamingBehavior: "steer" or "followUp".
    • If agent is streaming and streamingBehavior is absent → error.
    • "steer": interrupt after current tool execution; remaining tool calls skipped.
    • "followUp": queue after agent finishes.
  • Extension commands (/command) execute immediately even during streaming.
  • Skill commands (/skill:name) and prompt templates (/template) expand before queueing.
  • Response: {"type":"response","command":"prompt","success":true}

steer

  • Request: {"type":"steer","message":"Stop and do this instead"}
  • Same expansion rules; extension commands not allowed (use prompt).
  • Response: {"type":"response","command":"steer","success":true}

follow_up

  • Request: {"type":"follow_up","message":"After you're done, also do this"}
  • Same expansion rules; extension commands not allowed.
  • Response: {"type":"response","command":"follow_up","success":true}

abort

  • Request: {"type":"abort"}
  • Response: {"type":"response","command":"abort","success":true}

new_session

  • Request: {"type":"new_session"} or {"type":"new_session","parentSession":"/path/to/parent.jsonl"}
  • Can be cancelled by session_before_switch extension handler.
  • Response: {"type":"response","command":"new_session","success":true,"data":{"cancelled":false}}

State

get_state

  • Request: {"type":"get_state"}
  • Response data:
    • model: full Model or null
    • thinkingLevel: "off"|"minimal"|"low"|"medium"|"high"|"xhigh"
    • isStreaming: bool
    • isCompacting: bool
    • steeringMode: "all"|"one-at-a-time"
    • followUpMode: "all"|"one-at-a-time"
    • sessionFile: string (omitted for in-memory sessions)
    • sessionId: string
    • sessionName: string (omitted if not set)
    • autoCompactionEnabled: bool
    • messageCount: number
    • pendingMessageCount: number

get_messages

  • Request: {"type":"get_messages"}
  • Response: {"type":"response","command":"get_messages","success":true,"data":{"messages":[...]}}
  • Messages are AgentMessage objects (User/Assistant/ToolResult/BashExecution).

Model

set_model

  • Request: {"type":"set_model","provider":"anthropic","modelId":"claude-sonnet-4-20250514"}
  • Response: {"type":"response","command":"set_model","success":true,"data":<Model>}

cycle_model

  • Request: {"type":"cycle_model"}
  • Response: {"type":"response","command":"cycle_model","success":true,"data":{"model":<Model>,"thinkingLevel":"medium","isScoped":false}}
  • Returns data: null if only one model available.

get_available_models

  • Request: {"type":"get_available_models"}
  • Response: {"type":"response","command":"get_available_models","success":true,"data":{"models":[<Model>,...]}}

Thinking

set_thinking_level

  • Request: {"type":"set_thinking_level","level":"high"}
  • Levels: "off"|"minimal"|"low"|"medium"|"high"|"xhigh"
  • Response: success true

cycle_thinking_level

  • Request: {"type":"cycle_thinking_level"}
  • Response: {"type":"response","command":"cycle_thinking_level","success":true,"data":{"level":"high"}}
  • Returns data: null if model does not support thinking.

Queue Modes

set_steering_mode

  • Request: {"type":"set_steering_mode","mode":"one-at-a-time"}
  • Modes: "all" or "one-at-a-time" (default)
  • Response: success true

set_follow_up_mode

  • Request: {"type":"set_follow_up_mode","mode":"one-at-a-time"}
  • Modes: "all" or "one-at-a-time" (default)
  • Response: success true

Compaction

compact

  • Request: {"type":"compact"} or {"type":"compact","customInstructions":"Focus on code changes"}
  • Response data: {summary, firstKeptEntryId, tokensBefore, details}

set_auto_compaction

  • Request: {"type":"set_auto_compaction","enabled":true}
  • Response: success true

Retry

set_auto_retry

  • Request: {"type":"set_auto_retry","enabled":true}
  • Response: success true

abort_retry

  • Request: {"type":"abort_retry"}
  • Response: success true

Bash

bash

  • Request: {"type":"bash","command":"ls -la"}
  • Response data:
    • output: string
    • exitCode: number
    • cancelled: bool
    • truncated: bool
    • fullOutputPath: string | null (only when truncated)

abort_bash

  • Request: {"type":"abort_bash"}
  • Response: success true

Session

get_session_stats

  • Request: {"type":"get_session_stats"}
  • Response data:
    • sessionFile, sessionId
    • userMessages, assistantMessages, toolCalls, toolResults, totalMessages
    • tokens: {input, output, cacheRead, cacheWrite, total}
    • cost: number (total $)

export_html

  • Request: {"type":"export_html"} or {"type":"export_html","outputPath":"/tmp/session.html"}
  • Response data: {path: "<output path>"}

switch_session

  • Request: {"type":"switch_session","sessionPath":"/path/to/session.jsonl"}
  • Can be cancelled by session_before_switch extension handler.
  • Response data: {cancelled: false}

fork

  • Request: {"type":"fork","entryId":"abc123"}
  • entryId must be a user message entry.
  • Creates a new session (branched from parent of selected entry).
  • Response data: {text:"<user message text>", cancelled:false}
  • Can be cancelled by session_before_fork extension handler.

get_fork_messages

  • Request: {"type":"get_fork_messages"}
  • Response data: {messages:[{entryId, text}, ...]}

get_last_assistant_text

  • Request: {"type":"get_last_assistant_text"}
  • Response data: {text: "<assistant text>"} or {text: null}

set_session_name

  • Request: {"type":"set_session_name","name":"my-feature-work"}
  • Response: success true

Commands

get_commands

  • Request: {"type":"get_commands"}
  • Response data: {"commands":[{name, description?, source, location?, path?}, ...]}
    • source: "extension"|"template"|"skill"
    • location: "user"|"project"|"path" (not present for extensions)

11.4 Events

Event Fields
agent_start {type:"agent_start"}
agent_end {type:"agent_end", messages:[AgentMessage], error?}
turn_start {type:"turn_start"}
turn_end {type:"turn_end", message:AgentMessage, toolResults:[AgentMessage]}
message_start {type:"message_start", message:AgentMessage}
message_update {type:"message_update", message:AgentMessage, assistantMessageEvent:<delta>}
message_end {type:"message_end", message:AgentMessage}
tool_execution_start {type:"tool_execution_start", toolCallId, toolName, args}
tool_execution_update {type:"tool_execution_update", toolCallId, toolName, args, partialResult}
tool_execution_end {type:"tool_execution_end", toolCallId, toolName, result, isError}
auto_compaction_start `{type:"auto_compaction_start", reason:"threshold"
auto_compaction_end {type:"auto_compaction_end", result, aborted, willRetry, errorMessage?}
auto_retry_start {type:"auto_retry_start", attempt, maxAttempts, delayMs, errorMessage}
auto_retry_end {type:"auto_retry_end", success, attempt, finalError?}
extension_error {type:"extension_error", extensionPath, event, error}

assistantMessageEvent delta types (streaming):

  • start
  • text_start, text_delta, text_end
  • thinking_start, thinking_delta, thinking_end
  • toolcall_start, toolcall_delta, toolcall_end (includes full toolCall)
  • done (reason: "stop"|"length"|"toolUse")
  • error (reason: "aborted"|"error")

11.5 Extension UI Protocol (RPC)

extension_ui_request (stdout):

  • Base: {type:"extension_ui_request", id, method, ...}
  • Dialog methods block until response or timeout:
    • select: {title, options:[{label,value}], placeholder?, default?, timeout?}
    • confirm: {title, message, default?, timeout?}
    • input: {title, placeholder?, default?, password?, timeout?}
    • editor: {title, language?, default?, readOnly?, timeout?}
  • Fire-and-forget methods (no response expected):
    • notify: {title, message, level?}
    • setStatus: {text}
    • setWidget: {content}
    • setTitle: {title}
    • set_editor_text: {text}
  • RPC limitations: custom() returns undefined; setWorkingMessage, setFooter, setHeader, setEditorComponent are no-ops; getEditorText() returns "".

extension_ui_response (stdin):

  • Base: {type:"extension_ui_response", id, value?, cancelled?}
  • Dialog responses:
    • select/input/editor: {value: <selected/entered>}
    • confirm: {value: true|false}
    • cancellation: {cancelled: true}

11.6 RPC Types

Model

  • id, name, api, provider, baseUrl
  • reasoning (bool)
  • input: ["text","image"]
  • contextWindow, maxTokens
  • cost: {input, output, cacheRead, cacheWrite}

UserMessage

  • {role:"user", content, timestamp, attachments:[]}
  • content can be string or array of TextContent/ImageContent.

AssistantMessage

  • {role:"assistant", content:[...], api, provider, model, usage, stopReason, timestamp}
  • usage: {input, output, cacheRead, cacheWrite, cost:{input, output, cacheRead, cacheWrite, total}}
  • stopReason: "stop"|"length"|"toolUse"|"error"|"aborted"

ToolResultMessage

  • {role:"toolResult", toolCallId, toolName, content, isError, timestamp}

BashExecutionMessage

  • {role:"bashExecution", command, output, exitCode, cancelled, truncated, fullOutputPath, timestamp}

Attachment

  • {id, type:"image", fileName, mimeType, size, content, extractedText, preview}


12. Extensions API

Extensions are Node.js modules that can register tools, commands, and event handlers. The Rust port implements a PiJS runtime (see EXTENSIONS.md for connector architecture).

12.1 Extension Entry Point

// Extension module exports activate function
export async function activate(ctx: ExtensionContext): Promise<void>;

12.2 ExtensionContext

pub struct ExtensionContext {
    // Registration
    fn register_tool(name: &str, config: ToolConfig, handler: ToolHandler);
    fn register_command(name: &str, config: CommandConfig, handler: CommandHandler);

    // Event handlers
    fn on_agent_start(handler: Fn(AgentStartEvent));
    fn on_agent_end(handler: Fn(AgentEndEvent));
    fn on_turn_start(handler: Fn(TurnStartEvent));
    fn on_turn_end(handler: Fn(TurnEndEvent));
    fn on_tool_execution_start(handler: Fn(ToolExecutionStartEvent));
    fn on_tool_execution_end(handler: Fn(ToolExecutionEndEvent));
    fn on_session_before_switch(handler: Fn(SessionEvent) -> bool);  // Return false to cancel
    fn on_session_before_fork(handler: Fn(SessionEvent) -> bool);    // Return false to cancel
    fn on_startup(handler: Fn(StartupEvent));

    // UI access
    ui: ExtensionUi,

    // Session access
    session: SessionAccess,

    // Logging
    log: Logger,
}

12.3 Tool Registration

interface ToolConfig {
    label: string;                // Display name
    description: string;          // For LLM context
    parameters: JsonSchema;       // JSON Schema for validation
}

type ToolHandler = (args: ToolArgs, update?: UpdateCallback) => Promise<ToolResult>;

interface ToolArgs {
    toolCallId: string;
    input: Record<string, unknown>;
}

interface ToolResult {
    content: ContentBlock[];
    details?: Record<string, unknown>;
    isError?: boolean;
}

type UpdateCallback = (partial: ToolResult) => void;

12.4 Command Registration

interface CommandConfig {
    description?: string;         // Help text
}

type CommandHandler = (args: string) => Promise<string | void>;

Commands are invoked via /command args syntax. If handler returns a string, it's used as the user message.

12.5 Extension UI Methods

interface ExtensionUi {
    // Dialogs (blocking, support cancellation)
    select(options: SelectOptions): Promise<string | undefined>;
    confirm(options: ConfirmOptions): Promise<boolean>;
    input(options: InputOptions): Promise<string | undefined>;
    editor(options: EditorOptions): Promise<string | undefined>;

    // Non-blocking updates
    notify(options: NotifyOptions): void;
    setStatus(text: string): void;
    setWidget(content: string): void;
    setTitle(title: string): void;

    // Editor interaction
    setEditorText(text: string): void;
    getEditorText(): string;  // Returns "" in RPC mode

    // Custom component (interactive TUI only)
    custom<T>(component: Component): Promise<T | undefined>;  // Returns undefined in RPC
}

interface SelectOptions {
    title: string;
    options: Array<{label: string; value: string}>;
    placeholder?: string;
    default?: string;
    timeout?: number;  // ms, undefined = no timeout
}

interface ConfirmOptions {
    title: string;
    message: string;
    default?: boolean;
    timeout?: number;
}

interface InputOptions {
    title: string;
    placeholder?: string;
    default?: string;
    password?: boolean;
    timeout?: number;
}

interface EditorOptions {
    title: string;
    language?: string;
    default?: string;
    readOnly?: boolean;
    timeout?: number;
}

interface NotifyOptions {
    title: string;
    message: string;
    level?: "info" | "warning" | "error";
}

Cancellation semantics:

  • Dialog methods return undefined when user cancels (Esc key)
  • Timeout triggers cancellation
  • In RPC mode, extension_ui_response with cancelled: true indicates cancellation
  • confirm() returns false on cancel (not undefined)

12.6 Session Access

interface SessionAccess {
    getMessages(): Message[];
    getState(): SessionState;
    getFile(): string | undefined;  // undefined for in-memory sessions
}

interface SessionState {
    sessionId: string;
    messageCount: number;
    isStreaming: boolean;
    model: Model | null;
    thinkingLevel: ThinkingLevel;
}

12.7 Extension Events

interface AgentStartEvent {
    sessionId: string;
}

interface AgentEndEvent {
    sessionId: string;
    messages: Message[];
    error?: string;
}

interface TurnStartEvent {
    sessionId: string;
    turnIndex: number;
}

interface TurnEndEvent {
    sessionId: string;
    turnIndex: number;
    message: AssistantMessage;
    toolResults: ToolResultMessage[];
}

interface ToolExecutionStartEvent {
    toolCallId: string;
    toolName: string;
    args: Record<string, unknown>;
}

interface ToolExecutionEndEvent {
    toolCallId: string;
    toolName: string;
    result: ToolResult;
    isError: boolean;
}

interface SessionEvent {
    currentSession: string | undefined;
    targetSession?: string;  // For switch
    forkEntryId?: string;    // For fork
}

interface StartupEvent {
    version: string;
    sessionFile?: string;
}

12.8 Extension Logging

interface Logger {
    debug(message: string, data?: Record<string, unknown>): void;
    info(message: string, data?: Record<string, unknown>): void;
    warn(message: string, data?: Record<string, unknown>): void;
    error(message: string, data?: Record<string, unknown>): void;
}

Logs are emitted as structured JSON (schema: pi.ext.log.v1).


13. Resources System

Resources are discoverable user content: skills, prompt templates, and themes.

13.1 Resource Discovery

Resources are loaded from multiple locations in priority order:

  1. Explicit CLI flags (--skill, --prompt-template, --theme)
  2. Settings arrays (skills, prompts, themes in settings.json)
  3. Installed packages (via packages setting)

13.2 Package Manifest

{
  "name": "@scope/package-name",
  "pi": {
    "extensions": ["./dist/extension.js"],
    "skills": ["./skills/"],
    "prompts": ["./prompts/"],
    "themes": ["./themes/"]
  }
}

If pi field is absent, defaults apply:

  • extensions: []
  • skills: ["./skills/"] if directory exists
  • prompts: ["./prompts/"] if directory exists
  • themes: ["./themes/"] if directory exists

13.3 Skills

Skills are markdown files with YAML frontmatter defining agent capabilities.

File locations:

  • Global: ~/.pi/agent/skills/*.md
  • Project: ./.pi/skills/*.md
  • Package: <package>/skills/*.md

Frontmatter schema:

---
name: skill-name           # Required, kebab-case
description: Brief desc    # Required, shown in skill list
allowed_tools:             # Optional, restrict to specific tools
  - read
  - bash
---

Skill content: Markdown body becomes the skill prompt.

Expansion: /skill:name args expands to:

<skill name="skill-name" arguments="args">
[skill markdown content]
</skill>

System prompt injection: When read tool is enabled, active skills are appended:

<available_skills>
<skill name="name1">description1</skill>
<skill name="name2">description2</skill>
</available_skills>

13.4 Prompt Templates

Prompt templates are markdown files for reusable user prompts.

File locations:

  • Global: ~/.pi/agent/prompts/*.md
  • Project: ./.pi/prompts/*.md
  • Package: <package>/prompts/*.md

Command format: /template-name arg1 arg2 ...

Variable substitution:

Variable Meaning
$1, $2, ... Positional arguments
$@ All arguments joined by space
$ARGUMENTS Same as $@
${@:N} Arguments from position N onward

Expansion result: Becomes the user message (after variable substitution).

13.5 Themes

Themes are JSON files defining terminal color schemes.

File locations:

  • Global: ~/.pi/agent/themes/*.json
  • Project: ./.pi/themes/*.json
  • Package: <package>/themes/*.json

Schema:

{
  "name": "theme-name",
  "colors": {
    "text": "#ffffff",
    "background": "#000000",
    "primary": "#007acc",
    "secondary": "#6c757d",
    "success": "#28a745",
    "warning": "#ffc107",
    "error": "#dc3545",
    "thinking": "#6c757d",
    "tool": "#17a2b8"
  }
}

13.6 Package Sources

Packages can be specified as:

Source Type Format Example
npm npm:<package>@<version> npm:@pi/tools@1.0.0
git git:<url>#<ref> git:github.com/user/repo#main
local path:<absolute-path> path:/home/user/my-extension

Settings field:

{
  "packages": [
    "npm:@pi/tools@1.0.0",
    "git:github.com/user/extension#v1.0"
  ]
}

Summary

This specification covers:

  • Message types: User, Assistant, ToolResult with all content block variants
  • Streaming: Full event type enumeration with sequences
  • Providers: Trait definition and model registry structure
  • Tools: Built-in tools with exact parameters and behaviors
  • Sessions: JSONL format with tree structure
  • Config: Settings structure with precedence rules
  • Auth: Credential storage with OAuth refresh
  • CLI: Complete flag list with execution flow
  • RPC: JSON command protocol with events, types, and extension UI
  • Extensions: Full API surface with registration, events, UI, and cancellation semantics
  • Resources: Skills, prompts, and themes discovery and expansion

After reading this document, you should NOT need to consult the legacy TypeScript code.


Appendix A: Complete Extension API Reference (Extracted 2026-02-04)

This appendix provides the complete, detailed Extension API extracted from the legacy pi-mono codebase.

A.1 Extension Factory Pattern

Extensions export a default factory function receiving the ExtensionAPI object:

export default function (pi: ExtensionAPI) {
  // Register handlers, tools, commands
}

// Or async:
export default async function (pi: ExtensionAPI) {
  // Async initialization
}

A.2 Complete Event Types (20+)

Event Can Modify Can Cancel Payload
resources_discover Yes No {}
session_start No No {}
session_before_switch No Yes {}
session_switch No No {reason, previousSessionFile?}
session_before_fork No Yes {}
session_fork No No {previousSessionFile?}
session_before_compact Yes Yes {preparation, branchEntries, signal}
session_compact No No {compactionEntry, fromExtension}
session_before_tree Yes Yes {preparation, signal}
session_tree No No {newLeafId, oldLeafId, summaryEntry?}
session_shutdown No No {}
context Yes No {messages}
before_agent_start Yes No {prompt, images?, systemPrompt}
agent_start No No {}
agent_end No No {messages}
turn_start No No {turnIndex, timestamp}
turn_end No No {turnIndex, message, toolResults}
model_select No No {model, previousModel?, source}
tool_call No Yes (block) {toolName, input, toolCallId}
tool_result Yes No {toolName, input, content, isError}
user_bash Yes No {command, excludeFromContext, cwd}
input Yes Yes (handled) {text, images?, source}

A.3 Registration APIs

Tool Registration:

pi.registerTool({
  name: string,
  label: string,
  description: string,
  parameters: TypeBoxSchema,
  execute: (toolCallId, params, signal, onUpdate, ctx) => Promise<ToolOutput>,
  renderCall?: (args, theme) => Component,
  renderResult?: (result, options, theme) => Component,
});

Command Registration:

pi.registerCommand(name, {
  description: string,
  handler: (args, ctx) => Promise<void>,
  getArgumentCompletions?: (prefix) => CompletionItem[],
});

Shortcut Registration:

pi.registerShortcut(key, {
  description: string,
  handler: (ctx) => Promise<void>,
});

Flag Registration:

pi.registerFlag(name, {
  description: string,
  type: "boolean" | "string",
  default?: boolean | string,
});

Provider Registration:

pi.registerProvider(name, {
  baseUrl: string,
  apiKey: string,  // env var name
  api: "anthropic-messages" | "openai-responses",
  models: Model[],
  streamSimple?: StreamHandler,
  oauth?: OAuthConfig,
});

A.4 Message APIs

// Custom message (not for LLM)
pi.sendMessage({
  customType: string,
  content: string,
  display?: boolean,
  details?: unknown,
}, {
  triggerTurn?: boolean,
  deliverAs?: "steer" | "followUp" | "nextTurn",
});

// User message (triggers LLM turn)
pi.sendUserMessage(content, {
  deliverAs?: "steer" | "followUp",
});

// Session entry (not sent to LLM)
pi.appendEntry(customType, data);

A.5 Session & Model APIs

// Session metadata
pi.setSessionName(name);
pi.getSessionName();
pi.setLabel(entryId, label);

// Tool management
pi.getActiveTools();
pi.getAllTools();
pi.setActiveTools(toolNames);

// Model control
await pi.setModel(model);
pi.getThinkingLevel();
pi.setThinkingLevel(level);

// Execution
await pi.exec(command, args, options);

// Inter-extension events
pi.events.emit(eventName, data);
pi.events.on(eventName, handler);

A.6 UI Context Methods

Dialogs (blocking):

await ctx.ui.select(title, options, opts);
await ctx.ui.confirm(title, message, opts);
await ctx.ui.input(title, placeholder?, opts);
await ctx.ui.editor(title, prefill?);

Non-blocking:

ctx.ui.notify(message, type?);
ctx.ui.setStatus(key, text);
ctx.ui.setWorkingMessage(message?);
ctx.ui.setWidget(key, content, options?);
ctx.ui.setFooter(factory?);
ctx.ui.setHeader(factory?);
ctx.ui.setTitle(title);

Editor:

ctx.ui.setEditorText(text);
ctx.ui.getEditorText();
ctx.ui.setEditorComponent(factory?);

Theme:

ctx.ui.theme;
ctx.ui.getAllThemes();
ctx.ui.getTheme(name);
ctx.ui.setTheme(theme);

A.7 Extension Context Properties

interface ExtensionContext {
  ui: ExtensionUIContext;
  hasUI: boolean;
  cwd: string;
  sessionManager: ReadonlySessionManager;
  modelRegistry: ModelRegistry;
  model: Model | undefined;

  isIdle(): boolean;
  abort(): void;
  hasPendingMessages(): boolean;
  shutdown(): void;
  getContextUsage(): ContextUsage | undefined;
  compact(options?): void;
  getSystemPrompt(): string;
}

interface ExtensionCommandContext extends ExtensionContext {
  waitForIdle(): Promise<void>;
  newSession(options?): Promise<{cancelled: boolean}>;
  fork(entryId): Promise<{cancelled: boolean}>;
  navigateTree(targetId, options?): Promise<{cancelled: boolean}>;
  switchSession(sessionPath): Promise<{cancelled: boolean}>;
}

Appendix B: Library Integration Requirements

B.1 asupersync Requirements

Currently Used:

  • Cx capability context
  • mpsc / oneshot channels
  • Mutex / Notify synchronization
  • timeout / sleep time operations

Should Use:

  • Cx::region() for structured concurrency (extension lifecycle)
  • database::sqlite for session indexing (optional)
  • Two-phase channel semantics for atomic message delivery
  • Cancellation budgets for bounded cleanup
  • LabRuntime for deterministic testing

B.2 rich_rust Requirements

Currently Used:

  • Console with markup
  • Panel for boxed content
  • Table for data
  • Rule for dividers

Should Enable:

  • markdown feature for assistant response rendering
  • syntax feature for code block highlighting
  • json feature for pretty-printed JSON output

B.3 charmed_rust Requirements

Currently Used:

  • bubbletea::Model for Elm Architecture
  • bubbles::TextArea for input
  • bubbles::Viewport for scrolling
  • bubbles::Spinner for loading
  • glamour::Renderer for markdown

Should Use:

  • lipgloss::Style for direct styling
  • bubbles::List for history navigation
  • bubbles::Table for structured output
  • Custom glamour theme matching Pi visual style