Pi 的配置、擴充、平台設定和 API 參考。

RPC模式

RPC 模式可透過stdin/stdout 上的JSON 協定實現編碼代理的無頭操作。這對於將代理嵌入其他應用程式、IDE 或自訂 UI 中非常有用。

Node.js/TypeScript 用戶注意:如果您正在構建 Node.js 應用程序,請考慮直接從 @earendil-works/pi-coding-agent 使用AgentSession,而不是生成子進程。請參閱src/core/agent-session.ts了解API。基於子進程的TypeScript客戶端,請參閱src/modes/rpc/rpc-client.ts

啟動RPC模式

pi --mode rpc [options]

常用選項:

  • --provider <name>:設定LLM提供者(anthropic、openai、google等)
  • --model <pattern>:模型模式或ID(支援provider/id和可選的:<thinking>
  • --name <name> / -n <name>:設定啟動時的會話顯示名稱
  • --no-session:禁用會話持久化
  • --session-dir <path>:自訂會話儲存目錄

協議概述

  • 指令:JSON物件傳送到stdin,每行一個
  • 回應:JSON 對象,其中 type: "response" 指示命令成功/失敗
  • 事件:代理事件以 JSON 行串流傳輸到 stdout

所有命令都支援用於請求/回應關聯的可選 id 欄位。如果提供,相應的響應將包含相同的idbash_execution_update 事件也包括其原始 bash 命令的 id

取景

RPC模式使用嚴格的JSONL語意,以LF(\n)作為唯一的記錄分隔符號。

這對客戶很重要:

  • 僅在 \n 上拆分記錄
  • 透過剝離尾隨 \r 接受可選的 \r\n 輸入
  • 不要使用將 Unicode 分隔符號視為換行符號的通用行讀取器

特別是,節點 readline 不符合 RPC 模式的協議,因為它也會在 U+2028U+2029 上進行拆分,而這兩個JSON 字串在 JSON 字串中有效。

命令

提示

迅速的

向代理程式發送使用者提示。在接受、排隊或處理提示後,將發出命令回應。事件在接受後繼續異步傳輸。

{"id": "req-1", "type": "prompt", "message": "Hello, world!"}

有圖像:

{"type": "prompt", "message": "What's in this image?", "images": [{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}]}

串流期間:如果代理程式已經在串流傳輸,則必須指定 streamingBehavior 來對訊息進行排隊:

{"type": "prompt", "message": "New instruction", "streamingBehavior": "steer"}
  • "steer":代理運行時對訊息進行排隊。它在當前助理輪次完成執行其工具呼叫之後、下一個 LLM 呼叫之前交付。
  • "followUp":等待代理商完成。僅當代理停止時才會傳遞訊息。

如果代理正在串流傳輸且未指定 streamingBehavior,則該命令將傳回錯誤。

擴展命令:如果訊息是擴展命令(例如,/mycommand),則即使在串流傳輸期間也會立即執行。擴充指令透過 pi.sendMessage() 管理自己的 LLM 互動。

輸入擴充:技能指令(/skill:name)和prompt templates(/template)在發送/排隊之前擴充。

回覆:

{"id": "req-1", "type": "response", "command": "prompt", "success": true}

success: true 表示提示已被接受、排隊或立即處理。 success: false表示提示在接受前被拒絕。接受後的失敗透過正常事件和訊息流報告,而不是作為相同請求 ID 的第二個 response

images 字段是可選的。每個圖像使用ImageContent格式:{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}

駕駛

在代理運行時對轉向訊息進行排隊。它在當前助理輪次完成執行其工具呼叫之後、下一個 LLM 呼叫之前交付。擴展了技能命令和prompt templates。不允許擴充指令(使用 prompt 代替)。

{"type": "steer", "message": "Stop and do this instead"}

有圖像:

{"type": "steer", "message": "Look at this instead", "images": [{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}]}

images 字段是可選的。每個圖像都使用ImageContent格式(與prompt相同)。

回覆:

{"type": "response", "command": "steer", "success": true}

請參閱set_steering_mode來控制如何處理轉向訊息。

跟進

將後續訊息放入佇列,以便在代理完成後進行處理。僅當代理不再有工具呼叫或轉向訊息時傳送。擴展了技能命令和prompt templates。不允許擴充指令(使用 prompt 代替)。

{"type": "follow_up", "message": "After you're done, also do this"}

有圖像:

{"type": "follow_up", "message": "Also check this image", "images": [{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}]}

images 字段是可選的。每個圖像都使用ImageContent格式(與prompt相同)。

回覆:

{"type": "response", "command": "follow_up", "success": true}

請參閱set_follow_up_mode以控制如何處理後續訊息。

中止

中止目前代理操作。

{"type": "abort"}

回覆:

{"type": "response", "command": "abort", "success": true}

新會話

開始新的會話。可透過 session_before_switch 擴充事件處理程序取消。

{"type": "new_session"}

使用可選的父會話追蹤:

{"type": "new_session", "parentSession": "/path/to/parent-session.jsonl"}

回覆:

{"type": "response", "command": "new_session", "success": true, "data": {"cancelled": false}}

如果延期取消:

{"type": "response", "command": "new_session", "success": true, "data": {"cancelled": true}}

狀態

獲取狀態

取得目前會話狀態。

{"type": "get_state"}

回覆:

{
  "type": "response",
  "command": "get_state",
  "success": true,
  "data": {
    "model": {...},
    "thinkingLevel": "medium",
    "isStreaming": false,
    "isCompacting": false,
    "steeringMode": "all",
    "followUpMode": "one-at-a-time",
    "sessionFile": "/path/to/session.jsonl",
    "sessionId": "abc123",
    "sessionName": "my-feature-work",
    "autoCompactionEnabled": true,
    "messageCount": 5,
    "pendingMessageCount": 0
  }
}

model字段是完整的Model物件或nullsessionName欄位是透過set_session_name設定的顯示名稱,如果未設定則省略。

獲取訊息

取得對話中的所有訊息。

{"type": "get_messages"}

回覆:

{
  "type": "response",
  "command": "get_messages",
  "success": true,
  "data": {"messages": [...]}
}

訊息是 AgentMessage 物件(參見 Message Types)。

模型

設定模型

切換到特定型號。

{"type": "set_model", "provider": "anthropic", "modelId": "claude-sonnet-4-20250514"}

回應包含完整的 Model 物件:

{
  "type": "response",
  "command": "set_model",
  "success": true,
  "data": {...}
}

循環模型

循環到下一個可用模型。如果只有一種模型可用,則傳回 null 資料。

{"type": "cycle_model"}

回覆:

{
  "type": "response",
  "command": "cycle_model",
  "success": true,
  "data": {
    "model": {...},
    "thinkingLevel": "medium",
    "isScoped": false
  }
}

model字段是一個完整的Model物件。

取得可用模型

列出所有已配置的型號。

{"type": "get_available_models"}

回應包含完整的 Model 物件陣列:

{
  "type": "response",
  "command": "get_available_models",
  "success": true,
  "data": {
    "models": [...]
  }
}

思維

設定思維級別

為支持它的模型設定推理/思考層次。

{"type": "set_thinking_level", "level": "high"}

等級:"off""minimal""low""medium""high""xhigh""max"

僅當所選模型支援時,"xhigh""max" 才會暴露。包括 GPT-5.6 在內的某些模型同時暴露了兩者。

回覆:

{"type": "response", "command": "set_thinking_level", "success": true}

循環思維水平

循環瀏覽可用的思維層次。如果模型不支持思考,則回傳null資料。

{"type": "cycle_thinking_level"}

回覆:

{
  "type": "response",
  "command": "cycle_thinking_level",
  "success": true,
  "data": {"level": "high"}
}

取得可用思考級別

列出目前模型支援的思維層次。對於沒有推理支持的模型,傳回 ["off"]

{"type": "get_available_thinking_levels"}

回覆:

{
  "type": "response",
  "command": "get_available_thinking_levels",
  "success": true,
  "data": {
    "levels": ["off", "minimal", "low", "medium", "high"]
  }
}

隊列模式

設定轉向模式

控制如何傳遞轉向訊息(來自steer)。

{"type": "set_steering_mode", "mode": "one-at-a-time"}

模式:

  • "all":在目前助手輪完成執行其工具呼叫後傳遞所有轉向訊息
  • "one-at-a-time":助手每次完成轉彎時傳遞一條轉向訊息(預設)

回覆:

{"type": "response", "command": "set_steering_mode", "success": true}

設定跟隨模式

控制後續訊息(來自follow_up)的傳遞方式。

{"type": "set_follow_up_mode", "mode": "one-at-a-time"}

模式:

  • "all":代理完成後傳遞所有後續訊息
  • "one-at-a-time":每個代理完成後傳遞一條後續訊息(預設)

回覆:

{"type": "response", "command": "set_follow_up_mode", "success": true}

壓實

袖珍的

手動壓縮對話上下文以減少令牌使用。

{"type": "compact"}

帶有自訂說明:

{"type": "compact", "customInstructions": "Focus on code changes"}

回覆:

{
  "type": "response",
  "command": "compact",
  "success": true,
  "data": {
    "summary": "Summary of conversation...",
    "firstKeptEntryId": "abc123",
    "tokensBefore": 150000,
    "estimatedTokensAfter": 32000,
    "usage": {
      "input": 32000,
      "output": 1200,
      "cacheRead": 0,
      "cacheWrite": 0,
      "totalTokens": 33200,
      "cost": {"input": 0.01, "output": 0.02, "cacheRead": 0, "cacheWrite": 0, "total": 0.03}
    },
    "details": {}
  }
}

estimatedTokensAfter 是對壓縮後立即重建的訊息上下文的啟發式估計,而不是提供者精確的令牌計數。 usage 報告產生摘要的 LLM 調用,並且可能會被自訂壓縮處理程序省略。

設定自動壓縮

當上下文快滿時啟用或停用自動壓縮。

{"type": "set_auto_compaction", "enabled": true}

回覆:

{"type": "response", "command": "set_auto_compaction", "success": true}

重試

設定自動重試

啟用或停用瞬態錯誤(過載、速率限制、5xx)時的自動重試。

{"type": "set_auto_retry", "enabled": true}

回覆:

{"type": "response", "command": "set_auto_retry", "success": true}

中止重試

中止正在進行的重試(取消延遲並停止重試)。

{"type": "abort_retry"}

回覆:

{"type": "response", "command": "abort_retry", "success": true}

重擊

bash

執行 shell 命令並將輸出新增至對話上下文。命令運行時將輸出流作為 bash_execution_update 事件;響應包含最終結果。

{"id": "req-1", "type": "bash", "command": "ls -la"}

包含一個 id 將串流傳輸的 bash_execution_update 事件與此指令相關聯。

回覆:

{
  "id": "req-1",
  "type": "response",
  "command": "bash",
  "success": true,
  "data": {
    "output": "total 48\ndrwxr-xr-x ...",
    "exitCode": 0,
    "cancelled": false,
    "truncated": false
  }
}

如果輸出被截斷,則包括 fullOutputPath

{
  "type": "response",
  "command": "bash",
  "success": true,
  "data": {
    "output": "truncated output...",
    "exitCode": 0,
    "cancelled": false,
    "truncated": true,
    "fullOutputPath": "/tmp/pi-bash-abc123.log"
  }
}

bash結果如何達到法學碩士:

bash 指令立即執行並回傳BashResult。在內部,會建立一個 BashExecutionMessage 並將其儲存在代理程式的訊息狀態中。

當發送下一個prompt指令時,所有訊息(包括BashExecutionMessage)在傳送到LLM之前都會進行轉換。 BashExecutionMessage 轉換為 UserMessage,格式如下:

Ran `ls -la`
```
總計 48
drwxr-xr-x...
```

這意味著:

  1. Bash 輸出包含在下一個提示的 LLM 上下文中,而不是立即包含在內
  2. 在提示之前可以執行多個bash命令;所有輸出都將包括在內

中止_bash

中止正在運行的 bash 指令。

{"type": "abort_bash"}

回覆:

{"type": "response", "command": "abort_bash", "success": true}

會議

獲取會話統計信息

取得令牌使用情況、成本統計資訊和目前上下文視窗使用情況。

{"type": "get_session_stats"}

回覆:

{
  "type": "response",
  "command": "get_session_stats",
  "success": true,
  "data": {
    "sessionFile": "/path/to/session.jsonl",
    "sessionId": "abc123",
    "userMessages": 5,
    "assistantMessages": 5,
    "toolCalls": 12,
    "toolResults": 12,
    "totalMessages": 22,
    "tokens": {
      "input": 50000,
      "output": 10000,
      "cacheRead": 40000,
      "cacheWrite": 5000,
      "total": 105000
    },
    "cost": 0.45,
    "contextUsage": {
      "tokens": 60000,
      "contextWindow": 200000,
      "percent": 30
    }
  }
}

tokenscost包括輔助訊息、工具報告的使用情況以及整個會話中的壓縮/分支摘要產生。 contextUsage 包含用於壓縮和頁尾顯示的實際目前上下文視窗估計。

當沒有模型或上下文視窗可用時,contextUsage 被省略。壓縮後,contextUsage.tokenscontextUsage.percent 立即變為 null,直到新的壓縮後助理響應提供有效的使用資料。

匯出_html

將會話匯出到 HTML 文件。

{"type": "export_html"}

使用自訂路徑:

{"type": "export_html", "outputPath": "/tmp/session.html"}

回覆:

{
  "type": "response",
  "command": "export_html",
  "success": true,
  "data": {"path": "/tmp/session.html"}
}

開關會話

載入不同的會話文件。可透過 session_before_switch 擴充事件處理程序取消。

{"type": "switch_session", "sessionPath": "/path/to/session.jsonl"}

回覆:

{"type": "response", "command": "switch_session", "success": true, "data": {"cancelled": false}}

如果分機取消了切換:

{"type": "response", "command": "switch_session", "success": true, "data": {"cancelled": true}}

根據活動分支上的先前用戶訊息建立新分叉。可透過 session_before_fork 擴充事件處理程序取消。返回分叉訊息的文字。

{"type": "fork", "entryId": "abc123"}

回覆:

{
  "type": "response",
  "command": "fork",
  "success": true,
  "data": {"text": "The original prompt text...", "cancelled": false}
}

如果擴充取消了分叉:

{
  "type": "response",
  "command": "fork",
  "success": true,
  "data": {"text": "The original prompt text...", "cancelled": true}
}

複製

將目前活動分支複製到目前位置的新會話中。可透過 session_before_fork 擴充事件處理程序取消。

{"type": "clone"}

回覆:

{
  "type": "response",
  "command": "clone",
  "success": true,
  "data": {"cancelled": false}
}

如果擴充功能取消了克隆:

{
  "type": "response",
  "command": "clone",
  "success": true,
  "data": {"cancelled": true}
}

獲取分叉訊息

取得可用於分叉的用戶訊息。

{"type": "get_fork_messages"}

回覆:

{
  "type": "response",
  "command": "get_fork_messages",
  "success": true,
  "data": {
    "messages": [
      {"entryId": "abc123", "text": "First prompt..."},
      {"entryId": "def456", "text": "Second prompt..."}
    ]
  }
}

取得條目

按附加順序取得所有會話條目(不包括會話標頭)。會話是具有穩定 id 的僅追加條目樹,因此條目 id 可以用作持久遊標:傳遞您看到的最後一個條目 id 作為 since 來嚴格僅獲取其後的條目,即使在客戶端重新啟動時也是如此。與get_messages不同,這包括預壓縮歷史和廢棄的分支。

{"type": "get_entries"}

使用遊標:

{"type": "get_entries", "since": "abc123"}

回覆:

{
  "type": "response",
  "command": "get_entries",
  "success": true,
  "data": {
    "entries": [
      {"type": "message", "id": "def456", "parentId": "abc123", "timestamp": "...", "message": {"role": "user", "...": "..."}}
    ],
    "leafId": "def456"
  }
}

leafId 是目前葉條目的 id(null 表示空會話),因此客戶端可以在一次往返中判斷活動分支是否移動。如果since與任何條目id都不匹配,則回應為success: false

取得樹

以條目樹的形式取得會話。每個節點都是{entry, children, label?, labelTimestamp?}。一個格式良好的會話有一個根;孤立條目(斷開的父鏈)也顯示為根。

{"type": "get_tree"}

回覆:

{
  "type": "response",
  "command": "get_tree",
  "success": true,
  "data": {
    "tree": [
      {
        "entry": {"type": "message", "id": "abc123", "parentId": null, "...": "..."},
        "children": [
          {"entry": {"type": "message", "id": "def456", "parentId": "abc123", "...": "..."}, "children": []}
        ]
      }
    ],
    "leafId": "def456"
  }
}

獲取最後一個助手文本

取得最後一封助理訊息的文字內容。

{"type": "get_last_assistant_text"}

回覆:

{
  "type": "response",
  "command": "get_last_assistant_text",
  "success": true,
  "data": {"text": "The assistant's response..."}
}

如果不存在輔助訊息,則回傳{"text": null}

設定會話名稱

設定目前會話的顯示名稱。該名稱出現在會話清單中並有助於識別會話。

{"type": "set_session_name", "name": "my-feature-work"}

回覆:

{
  "type": "response",
  "command": "set_session_name",
  "success": true
}

當前會話名稱可透過 sessionName 欄位中的 get_state 取得。若要在啟動RPC模式時設定初始名稱,請將--name <name>-n <name>傳遞給pi --mode rpc進程。

命令

獲取命令

取得可用指令(擴充指令、prompt templates和技能)。這些可以透過 prompt 指令透過前綴 / 來呼叫。

{"type": "get_commands"}

回覆:

{
  "type": "response",
  "command": "get_commands",
  "success": true,
  "data": {
    "commands": [
      {"name": "session-name", "description": "Set or clear session name", "source": "extension", "path": "/home/user/.pi/agent/extensions/session.ts"},
      {"name": "fix-tests", "description": "Fix failing tests", "source": "prompt", "location": "project", "path": "/home/user/myproject/.pi/agent/prompts/fix-tests.md"},
      {"name": "skill:brave-search", "description": "Web search via Brave API", "source": "skill", "location": "user", "path": "/home/user/.pi/agent/skills/brave-search/SKILL.md"}
    ]
  }
}

每個指令都有:

  • name:指令名稱(使用/name呼叫)
  • description:人類可讀的描述(擴充指令可選)
  • source:什麼樣的指令:
    • "extension":透過擴充中的pi.registerCommand()註冊
    • "prompt":從提示範本.md檔案載入
    • "skill":從技能目錄載入(名稱以skill:為前綴)
  • location:從哪裡加載(可選,對於擴展不存在):
    • "user":使用者等級(~/.pi/agent/
    • "project":專案等級 (./.pi/agent/)
    • "path":經由CLI或設定的顯式路徑
  • path:命令來源的絕對檔案路徑(可選)

注意:不含內建 TUI 指令(/settings/hotkeys 等)。它們僅在互動模式下處理,如果透過 prompt 發送,則不會執行。

活動

在代理操作期間,事件將作為 JSON 行流式傳輸到 stdout。事件通常不包含 id 欄位;當提供一個指令時,bash_execution_update 包括其原始 bash 指令的 id

事件類型

事件 描述
agent_start 代理開始處理
agent_end 一次低階代理運行完成(可能仍會重試、壓縮或排隊繼續)
agent_settled 代理運行已全部解決;不保留自動重試、壓縮重試或排隊延續
turn_start 新的轉折開始了
turn_end 轉彎完成(包括輔助訊息和工具結果)
message_start 訊息開始
message_update 串流更新(文字/思考/工具呼叫增量)
message_end 訊息完成
bash_execution_update 直接RPCbash指令輸出chunk
tool_execution_start 工具開始執行
tool_execution_update 工具執行進度(串流輸出)
tool_execution_end 工具完成
queue_update 待處理轉向/後續隊列已更改
compaction_start 壓實開始
compaction_end 壓實完成
auto_retry_start 自動重試開始(瞬時錯誤後)
auto_retry_end 自動重試完成(成功或最終失敗)
summarization_retry_scheduled 為瞬時壓縮或分支摘要錯誤安排重試
summarization_retry_attempt_start 重試匯總請求開始
summarization_retry_finished 總結重試循環完成
extension_error 擴展引發錯誤

代理啟動

當代理開始處理提示時發出。

{"type": "agent_start"}

代理結束

當一個低階代理運行完成時發出。包含本次運行期間產生的所有訊息。如果willRetry為真,則會自動重試。

{
  "type": "agent_end",
  "messages": [...],
  "willRetry": false
}

代理已解決

完整會話級運轉穩定後發出。此時Pi將不會透過重試、壓縮重試或排隊後續訊息自動繼續。

{"type": "agent_settled"}

轉彎開始/轉彎結束

一輪由一個助理回應以及任何由此產生的工具呼叫和結果組成。

{"type": "turn_start"}
{
  "type": "turn_end",
  "message": {...},
  "toolResults": [...]
}

訊息開始/訊息結束

當訊息開始和完成時發出。 message字段包含AgentMessage

{"type": "message_start", "message": {...}}
{"type": "message_end", "message": {...}}

message_update(串流)

在傳輸助理訊息期間發出。包含增量事件,但沒有累積訊息快照。

{
  "type": "message_update",
  "assistantMessageEvent": {
    "type": "text_delta",
    "contentIndex": 0,
    "delta": "Hello "
  }
}

assistantMessageEvent 欄位包含以下增量類型之一:

類型 描述
text_start 文字內容區塊開始
text_delta 文字內容區塊
text_end 文字內容區塊結束
thinking_start 思維塊開始了
thinking_delta 思考內容區塊
thinking_end 思維障礙結束
toolcall_start 工具呼叫開始
toolcall_delta 工具呼叫參數區塊
toolcall_end 工具呼叫結束(包括完整的toolCall物件)

串流傳輸文字回應的範例:

{"type":"message_update","assistantMessageEvent":{"type":"text_start","contentIndex":0}}
{"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello"}}
{"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":" world"}}
{"type":"message_update","assistantMessageEvent":{"type":"text_end","contentIndex":0,"content":"Hello world"}}

message_update故意省略了前面的累計message字段並且 assistantMessageEvent.partial。需要即時部分訊息的客戶端必須組裝它 來自 message_start 以及使用 contentIndex 的後續事件。對待message_end.message 作為權威。對於工具調用,緩衝區toolcall_delta.deltatoolcall_end.toolCall 包含已完成的呼叫。

bash_execution_update

從直接 bash 指令中為每個輸出區塊發出一次。 id 與命令的 id 匹配,允許客戶端將輸出與正確的命令相關聯。

命令運行時事件會傳送所有輸出,即使最終 bash 回應的 output 被截斷。

{
  "type": "bash_execution_update",
  "id": "req-1",
  "delta": "total 48\n"
}

工具執行開始/工具執行更新/工具執行結束

當工具啟動、傳輸進度並完成執行時發出。

{
  "type": "tool_execution_start",
  "toolCallId": "call_abc123",
  "toolName": "bash",
  "args": {"command": "ls -la"}
}

在執行期間,tool_execution_update事件會傳送部分結果(例如,bash到達時輸出):

{
  "type": "tool_execution_update",
  "toolCallId": "call_abc123",
  "toolName": "bash",
  "args": {"command": "ls -la"},
  "partialResult": {
    "content": [{"type": "text", "text": "partial output so far..."}],
    "details": {"truncation": null, "fullOutputPath": null}
  }
}

完成後:

{
  "type": "tool_execution_end",
  "toolCallId": "call_abc123",
  "toolName": "bash",
  "result": {
    "content": [{"type": "text", "text": "total 48\n..."}],
    "details": {...}
  },
  "isError": false
}

使用 toolCallId 關聯事件。 tool_execution_update中的partialResult包含迄今為止累積的輸出(不僅僅是增量),允許客戶端在每次更​​新時簡單地替換其顯示。

隊列更新

每當待處理的轉向或後續佇列發生變更時發出。

{
  "type": "queue_update",
  "steering": ["Focus on error handling"],
  "followUp": ["After that, summarize the result"]
}

壓縮開始/壓縮結束

壓縮運轉時發出,無論是手動或自動。

{"type": "compaction_start", "reason": "threshold"}

reason字段為"manual""threshold""overflow"

{
  "type": "compaction_end",
  "reason": "threshold",
  "result": {
    "summary": "Summary of conversation...",
    "firstKeptEntryId": "abc123",
    "tokensBefore": 150000,
    "estimatedTokensAfter": 32000,
    "usage": {
      "input": 32000,
      "output": 1200,
      "cacheRead": 0,
      "cacheWrite": 0,
      "totalTokens": 33200,
      "cost": {"input": 0.01, "output": 0.02, "cacheRead": 0, "cacheWrite": 0, "total": 0.03}
    },
    "details": {}
  },
  "aborted": false,
  "willRetry": false
}

如果 reason"overflow" 且壓縮成功,則 willRetrytrue,代理將自動重試提示。

若壓縮被中止,resultnullabortedtrue

若壓縮失敗(例如超過API配額),則resultnullabortedfalseerrorMessage包含錯誤描述。

自動重試開始/自動重試結束

當發生瞬時錯誤(過載、速率限制、5xx)後觸發自動重試時發出。

{
  "type": "auto_retry_start",
  "attempt": 1,
  "maxAttempts": 3,
  "delayMs": 2000,
  "errorMessage": "529 {\"type\":\"error\",\"error\":{\"type\":\"overloaded_error\",\"message\":\"Overloaded\"}}"
}
{
  "type": "auto_retry_end",
  "success": true,
  "attempt": 2
}

最終失敗時(超出最大重試次數):

{
  "type": "auto_retry_end",
  "success": false,
  "attempt": 3,
  "finalError": "529 overloaded_error: Overloaded"
}

summarization_retry_scheduled / summarization_retry_attempt_start / summarization_retry_finished

當壓縮或分支摘要匯總在暫時提供者錯誤後重試時發出。這些事件使用與自動助理輪次重試相同的重試設定。

{
  "type": "summarization_retry_scheduled",
  "attempt": 1,
  "maxAttempts": 3,
  "delayMs": 2000,
  "errorMessage": "terminated"
}
{
  "type": "summarization_retry_attempt_start",
  "source": "compaction",
  "reason": "threshold"
}

對於分支摘要,source"branchSummary",且不存在 reason

{
  "type": "summarization_retry_finished"
}

擴充錯誤

當擴展拋出錯誤時發出。

{
  "type": "extension_error",
  "extensionPath": "/path/to/extension.ts",
  "event": "tool_call",
  "error": "Error message..."
}

擴展 UI 協議

Extensions可以透過ctx.ui.select()ctx.ui.confirm()等請求用戶互動。在RPC模式下,這些被轉換為基本命令/事件流之上的請求/回應子協定。

擴充 UI 方法有兩類:

  • 對話框方法 (selectconfirminputeditor):在 stdout 上發出 extension_ui_request,並阻塞直到客戶端在 stdin 上傳回具有相同 idextension_ui_response
  • 即發即忘法notifysetStatussetWidgetsetTitleset_editor_text):在stdout上發出extension_ui_request,但不期望得到回應。客戶端可以顯示該訊息或忽略它。

如果對話方法包含 timeout 字段,則代理端將在逾時到期時使用預設值自動解析。客戶端不需要追蹤超時。

某些 ExtensionUIContext 方法在 RPC 模式下不受支援或降級,因為它們需要直接 TUI 存取:

  • custom() 回傳 undefined
  • setWorkingMessage()setWorkingIndicator()setFooter()setHeader()setEditorComponent()setToolsExpanded() 為空操作
  • getEditorText() 回傳 ""
  • getToolsExpanded() 回傳 false
  • pasteToEditor() 委託setEditorText()(無貼上/折疊處理)
  • getAllThemes() 回傳 []
  • getTheme() 回傳 undefined
  • setTheme() 回傳 { success: false, error: "..." }

注意:在 RPC 模式下,ctx.mode"rpc"ctx.hasUItrue,因為對話框和即發即棄方法透過擴展 UI 子協定發揮作用。使用ctx.mode === "tui"來保護TUI特定功能,例如需要真實終端的custom()

擴展 UI 請求 (stdout)

所有請求都有 type: "extension_ui_request"、唯一的 idmethod 欄位。

選擇

提示使用者從清單中進行選擇。帶有 timeout 欄位的對話框方法包括以毫秒為單位的超時;如果客戶端沒有及時回應,代理會自動解決undefined

{
  "type": "extension_ui_request",
  "id": "uuid-1",
  "method": "select",
  "title": "Allow dangerous command?",
  "options": ["Allow", "Block"],
  "timeout": 10000
}

預期回應:extension_ui_responsevalue(所選選項字串)或 cancelled: true

確認

提示使用者進行是/否確認。

{
  "type": "extension_ui_request",
  "id": "uuid-2",
  "method": "confirm",
  "title": "Clear session?",
  "message": "All messages will be lost.",
  "timeout": 5000
}

預期響應:extension_ui_responseconfirmed: true/falsecancelled: true

輸入

提示使用者輸入自由格式的文字。

{
  "type": "extension_ui_request",
  "id": "uuid-3",
  "method": "input",
  "title": "Enter a value",
  "placeholder": "type something..."
}

預期回應:extension_ui_responsevalue(輸入的文字)或cancelled: true

編輯

開啟具有可選預填充內容的多行文字編輯器。

{
  "type": "extension_ui_request",
  "id": "uuid-4",
  "method": "editor",
  "title": "Edit some text",
  "prefill": "Line 1\nLine 2\nLine 3"
}

預期回應:extension_ui_responsevalue(編輯後的文字)或cancelled: true

通知

顯示通知。即發即棄,預計不會有任何回應。

{
  "type": "extension_ui_request",
  "id": "uuid-5",
  "method": "notify",
  "message": "Command blocked by user",
  "notifyType": "warning"
}

notifyType字段為"info""warning""error"。如果省略,則預設為 "info"

設定狀態

設定或清除頁尾/狀態列中的狀態條目。一勞永逸。

{
  "type": "extension_ui_request",
  "id": "uuid-6",
  "method": "setStatus",
  "statusKey": "my-ext",
  "statusText": "Turn 3 running..."
}

發送 statusText: undefined (或省略)以清除該鍵的狀態條目。

設定小部件

設定或清除編輯器上方或下方顯示的小工具(文字行塊)。一勞永逸。

{
  "type": "extension_ui_request",
  "id": "uuid-7",
  "method": "setWidget",
  "widgetKey": "my-ext",
  "widgetLines": ["--- My Widget ---", "Line 1", "Line 2"],
  "widgetPlacement": "aboveEditor"
}

發送 widgetLines: undefined (或省略)以清除小部件。 widgetPlacement字段為"aboveEditor"(預設)或"belowEditor"。 RPC模式僅支援字串陣列;組件工廠被忽略。

設定標題

設定終端機視窗/選項卡標題。一勞永逸。

{
  "type": "extension_ui_request",
  "id": "uuid-8",
  "method": "setTitle",
  "title": "pi - my project"
}

設定編輯器文字

在輸入編輯器中設定文字。一勞永逸。

{
  "type": "extension_ui_request",
  "id": "uuid-9",
  "method": "set_editor_text",
  "text": "prefilled text for the user"
}

擴展 UI 響應 (stdin)

僅針對對話方法發送回應(selectconfirminputeditor)。 id 必須符合請求。

值響應(選擇、輸入、編輯)

{"type": "extension_ui_response", "id": "uuid-1", "value": "Allow"}

確認回覆(確認)

{"type": "extension_ui_response", "id": "uuid-2", "confirmed": true}

取消回應(任何對話框)

關閉任何對話框方法。擴充接收undefined(用於選擇/輸入/編輯器)或false(用於確認)。

{"type": "extension_ui_response", "id": "uuid-3", "cancelled": true}

錯誤處理

失敗的命令返回帶有 success: false 的回應:

{
  "type": "response",
  "command": "set_model",
  "success": false,
  "error": "Model not found: invalid/model"
}

解析錯誤:

{
  "type": "response",
  "command": "parse",
  "success": false,
  "error": "Failed to parse command: Unexpected token..."
}

類型

原始檔:

模型

{
  "id": "claude-sonnet-4-20250514",
  "name": "Claude Sonnet 4",
  "api": "anthropic-messages",
  "provider": "anthropic",
  "baseUrl": "https://api.anthropic.com",
  "reasoning": true,
  "input": ["text", "image"],
  "contextWindow": 200000,
  "maxTokens": 16384,
  "cost": {
    "input": 3.0,
    "output": 15.0,
    "cacheRead": 0.3,
    "cacheWrite": 3.75
  }
}

用戶留言

{
  "role": "user",
  "content": "Hello!",
  "timestamp": 1733234567890,
  "attachments": []
}

content字段可以是字串或TextContent/ImageContent塊的陣列。

助理留言

{
  "role": "assistant",
  "content": [
    {"type": "text", "text": "Hello! How can I help?"},
    {"type": "thinking", "thinking": "User is greeting me..."},
    {"type": "toolCall", "id": "call_123", "name": "bash", "arguments": {"command": "ls"}}
  ],
  "api": "anthropic-messages",
  "provider": "anthropic",
  "model": "claude-sonnet-4-20250514",
  "usage": {
    "input": 100,
    "output": 50,
    "cacheRead": 0,
    "cacheWrite": 0,
    "cost": {"input": 0.0003, "output": 0.00075, "cacheRead": 0, "cacheWrite": 0, "total": 0.00105}
  },
  "stopReason": "stop",
  "timestamp": 1733234567890
}

停止原因:"stop""length""toolUse""error""aborted"

工具結果訊息

{
  "role": "toolResult",
  "toolCallId": "call_123",
  "toolName": "bash",
  "content": [{"type": "text", "text": "total 48\ndrwxr-xr-x ..."}],
  "usage": {
    "input": 100,
    "output": 50,
    "cacheRead": 0,
    "cacheWrite": 0,
    "totalTokens": 150,
    "cost": {"input": 0.0003, "output": 0.00075, "cacheRead": 0, "cacheWrite": 0, "total": 0.00105}
  },
  "isError": false,
  "timestamp": 1733234567890
}

usage 是可選的,報告該工具執行的嵌套 LLM 工作。如果存在,它會影響會話令牌和成本總計。

Bash執行訊息

bash RPC 指令建立(不是由 LLM 工具呼叫):

{
  "role": "bashExecution",
  "command": "ls -la",
  "output": "total 48\ndrwxr-xr-x ...",
  "exitCode": 0,
  "cancelled": false,
  "truncated": false,
  "fullOutputPath": null,
  "timestamp": 1733234567890
}

依戀

{
  "id": "img1",
  "type": "image",
  "fileName": "photo.jpg",
  "mimeType": "image/jpeg",
  "size": 102400,
  "content": "base64-encoded-data...",
  "extractedText": null,
  "preview": null
}

範例:基本客戶端 (Python)

import subprocess
import json

proc = subprocess.Popen(
    ["pi", "--mode", "rpc", "--no-session"],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE,
    text=True
)

def send(cmd):
    proc.stdin.write(json.dumps(cmd) + "\n")
    proc.stdin.flush()

def read_events():
    for line in proc.stdout:
        yield json.loads(line)

# Send prompt
send({"type": "prompt", "message": "Hello!"})

# Process events
for event in read_events():
    if event.get("type") == "message_update":
        delta = event.get("assistantMessageEvent", {})
        if delta.get("type") == "text_delta":
            print(delta["delta"], end="", flush=True)
    
    if event.get("type") == "agent_end":
        print()
        break

範例:互動式客戶端 (Node.js)

有關完整的互動式範例,請參閱 test/rpc-example.ts,或有關類型化客戶端實作的 src/modes/rpc/rpc-client.ts

有關處理擴充 UI 協定的完整範例,請參閱與 examples/extensions/rpc-demo.ts 擴充配對的 examples/rpc-extension-ui.ts

const { spawn } = require("child_process");
const { StringDecoder } = require("string_decoder");

const agent = spawn("pi", ["--mode", "rpc", "--no-session"]);

function attachJsonlReader(stream, onLine) {
    const decoder = new StringDecoder("utf8");
    let buffer = "";

    stream.on("data", (chunk) => {
        buffer += typeof chunk === "string" ? chunk : decoder.write(chunk);

        while (true) {
            const newlineIndex = buffer.indexOf("\n");
            if (newlineIndex === -1) break;

            let line = buffer.slice(0, newlineIndex);
            buffer = buffer.slice(newlineIndex + 1);
            if (line.endsWith("\r")) line = line.slice(0, -1);
            onLine(line);
        }
    });

    stream.on("end", () => {
        buffer += decoder.end();
        if (buffer.length > 0) {
            onLine(buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer);
        }
    });
}

attachJsonlReader(agent.stdout, (line) => {
    const event = JSON.parse(line);

    if (event.type === "message_update") {
        const { assistantMessageEvent } = event;
        if (assistantMessageEvent.type === "text_delta") {
            process.stdout.write(assistantMessageEvent.delta);
        }
    }
});

// Send prompt
agent.stdin.write(JSON.stringify({ type: "prompt", message: "Hello" }) + "\n");

// Abort on Ctrl+C
process.on("SIGINT", () => {
    agent.stdin.write(JSON.stringify({ type: "abort" }) + "\n");
});