RPC 模式
RPC 模式透過 stdin/stdout 上的 JSON 協議,讓程式開發 Agent 可以以 headless 方式執行。這適合將 Agent 嵌入其他應用、IDE 或自訂 UI。
Node.js/TypeScript 使用者注意:如果正在建置 Node.js 應用程式,優先考慮直接從 @earendil-works/pi-coding-agent 使用 AgentSession,而不是啟動子程序。API 見 src/core/agent-session.ts。基於子程序的 TypeScript 客戶端見 src/modes/rpc/rpc-client.ts。
啟動 RPC 模式
pi --mode rpc [options]常用選項:
--provider <name>:設定 LLM Provider(anthropic、openai、google 等)--model <pattern>:模型比對模式或 ID(支援provider/id和選用的:<thinking>)--name <name>/-n <name>:設定啟動時的工作階段顯示名稱--no-session:停用工作階段持久化--session-dir <path>:自訂工作階段儲存目錄
協議概述
- 指令:傳送到 stdin 的 JSON 物件,每行一個
- 回應:JSON 物件,其中
type: "response"指示指令成功/失敗 - 事件:Agent 事件以 JSON 行形式串流輸出到 stdout
所有指令都支援選用 id 欄位,用於關聯請求和回應。如果提供,回應會包含相同的 id。bash_execution_update 事件還會包含其來源 bash 指令的 id。
分幀
RPC 模式使用嚴格的 JSONL 語義,以 LF (\n) 作為唯一記錄分隔符。
這對客戶端很重要:
- 僅在
\n上拆分記錄 - 透過剝離尾隨
\r接受選用的\r\n輸入 - 不要使用將 Unicode 分隔符視為換行符的通用行讀取器
特別是,Node readline 不符合 RPC 模式協議,因為它也會在 U+2028 和 U+2029 上拆分,而這兩個字元在 JSON 字串內部是合法的。
指令
傳送 Prompt
prompt
向 Agent 傳送使用者 Prompt。Prompt 被接受、排隊或處理後會發出指令回應;接受後事件會繼續async串流輸出。
{"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"}]}串流傳輸期間:如果 Agent 已經在串流傳輸,必須指定 streamingBehavior 來將訊息加入佇列:
{"type": "prompt", "message": "New instruction", "streamingBehavior": "steer"}"steer":Agent 執行階段將訊息加入佇列。它會在目前助理輪次次執行完工具呼叫後、下一次 LLM 呼叫前投遞。"followUp":等待 Agent 完成。只有在 Agent 停止後才投遞訊息。
如果 Agent 正在串流傳輸且未指定 streamingBehavior,該指令會傳回錯誤。
擴充指令:如果訊息是擴充指令(例如,/mycommand),則即使在串流傳輸期間也會立即執行。擴充指令透過 pi.sendMessage() 管理自己的 LLM 互動。
輸入展開:Skill 指令(/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"}。
steer
在 Agent 執行階段排隊一條 steering message。它會在目前助理輪次次執行完工具呼叫後、下一次 LLM 呼叫前投遞。Skill 指令和 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來控制如何處理轉向訊息。
follow_up
將 follow-up message 加入佇列,在 Agent 完成後處理。只有當 Agent 不再有工具呼叫或 steering message 時才會投遞。Skill 指令和 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以控制如何處理後續訊息。
abort
中止目前代理操作。
{"type": "abort"}回復:
{"type": "response", "command": "abort", "success": true}new_session
開始新的工作階段。可以透過 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}}狀態
get_state
取得目前工作階段狀態。
{"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物件或 null。 sessionName 欄位是透過 set_session_name 設定的顯示名稱,如果未設定則省略。
get_messages
取得對話中的所有訊息。
{"type": "get_messages"}回復:
{
"type": "response",
"command": "get_messages",
"success": true,
"data": {"messages": [...]}
}訊息是 AgentMessage 物件(參見 Message Types)。
模型
set_model
切換到指定模型。
{"type": "set_model", "provider": "anthropic", "modelId": "claude-sonnet-4-20250514"}回應包含完整的 Model 物件:
{
"type": "response",
"command": "set_model",
"success": true,
"data": {...}
}cycle_model
切換到下一個可用模型。如果只有一種模型可用,則傳回 null 資料。
{"type": "cycle_model"}回復:
{
"type": "response",
"command": "cycle_model",
"success": true,
"data": {
"model": {...},
"thinkingLevel": "medium",
"isScoped": false
}
}model 欄位是一個完整的 Model 物件。
get_available_models
列出所有已設定模型。
{"type": "get_available_models"}回應包含完整的 Model 物件陣列:
{
"type": "response",
"command": "get_available_models",
"success": true,
"data": {
"models": [...]
}
}思考等級
set_thinking_level
為支援的模型設定 reasoning/thinking level。
{"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}cycle_thinking_level
在可用 thinking level 之間循環。如果模型不支援 thinking,則傳回 null 資料。
{"type": "cycle_thinking_level"}回復:
{
"type": "response",
"command": "cycle_thinking_level",
"success": true,
"data": {"level": "high"}
}get_available_thinking_levels
列出目前模型支援的 thinking level。對於不支援 reasoning 的模型,傳回 ["off"]。
{"type": "get_available_thinking_levels"}回復:
{
"type": "response",
"command": "get_available_thinking_levels",
"success": true,
"data": {
"levels": ["off", "minimal", "low", "medium", "high"]
}
}佇列模式
set_steering_mode
控制如何投遞 steering message(來自 steer)。
{"type": "set_steering_mode", "mode": "one-at-a-time"}模式:
"all":在目前助理輪次次執行完工具呼叫後投遞所有 steering message"one-at-a-time":每完成一個助理輪次次投遞一條 steering message(預設)
回復:
{"type": "response", "command": "set_steering_mode", "success": true}set_follow_up_mode
控制 follow-up message(來自 follow_up)的投遞方式。
{"type": "set_follow_up_mode", "mode": "one-at-a-time"}模式:
"all":Agent 完成後投遞所有 follow-up message"one-at-a-time":每次 Agent 完成後投遞一條 follow-up message(預設)
回復:
{"type": "response", "command": "set_follow_up_mode", "success": true}壓縮
compact
手動壓縮對話上下文以減少 token 使用量。
{"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 是對壓縮後立即重建的訊息上下文的啟發式估算,不是 Provider 精確 token 計數。usage 報告產生摘要的 LLM 呼叫;自訂壓縮處理常式可能會省略它。
set_auto_compaction
當上下文快滿時啟用或停用自動壓縮。
{"type": "set_auto_compaction", "enabled": true}回復:
{"type": "response", "command": "set_auto_compaction", "success": true}重試
set_auto_retry
啟用或停用瞬態錯誤(過載、速率限制、5xx)時的自動重試。
{"type": "set_auto_retry", "enabled": true}回復:
{"type": "response", "command": "set_auto_retry", "success": true}abort_retry
中止正在進行的重試(取消延遲並停止重試)。
{"type": "abort_retry"}回復:
{"type": "response", "command": "abort_retry", "success": true}Bash
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 結果如何進入 LLM:
bash 指令會立即執行並傳回 BashResult。內部會建立一個 BashExecutionMessage,並將其儲存到 Agent 的訊息狀態中。
傳送下一個 prompt 指令時,所有訊息(包括 BashExecutionMessage)都會在傳送給 LLM 前轉換。BashExecutionMessage 會轉換為如下格式的 UserMessage:
Ran `ls -la`
```
total 48
drwxr-xr-x ...
```這表示:
- Bash 輸出會在 下一個 prompt 時才包含在 LLM 上下文中,而非立即包含
- 可在一個 prompt 前執行多個 bash 指令;所有輸出都會被包含
abort_bash
中止正在執行的 bash 指令。
{"type": "abort_bash"}回復:
{"type": "response", "command": "abort_bash", "success": true}工作階段
get_session_stats
取得 token 使用情況、成本統計資訊和目前上下文視窗使用情況。
{"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
}
}
}tokens 和 cost 包含整個工作階段中的助理訊息、工具報告的 usage,以及壓縮/分支摘要產生。contextUsage 包含用於壓縮和頁尾顯示的實際目前上下文視窗估算。
當沒有模型或上下文視窗可用時,contextUsage 被省略。壓縮後,contextUsage.tokens 和 contextUsage.percent 立即變為 null,直到新的壓縮後助理回應提供有效的使用資料。
export_html
將工作階段匯出到 HTML 檔案。
{"type": "export_html"}使用自訂路徑:
{"type": "export_html", "outputPath": "/tmp/session.html"}回復:
{
"type": "response",
"command": "export_html",
"success": true,
"data": {"path": "/tmp/session.html"}
}switch_session
載入不同的工作階段檔案。可以透過 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}}fork
基於目前分支上較早的使用者訊息建立新分叉。可以透過 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}
}clone
將目前分支複製到目前位置的新工作階段中。可以透過 session_before_fork 擴充事件處理常式取消。
{"type": "clone"}回復:
{
"type": "response",
"command": "clone",
"success": true,
"data": {"cancelled": false}
}如果擴充取消了克隆:
{
"type": "response",
"command": "clone",
"success": true,
"data": {"cancelled": true}
}get_fork_messages
取得可用於分叉的使用者訊息。
{"type": "get_fork_messages"}回復:
{
"type": "response",
"command": "get_fork_messages",
"success": true,
"data": {
"messages": [
{"entryId": "abc123", "text": "First prompt..."},
{"entryId": "def456", "text": "Second prompt..."}
]
}
}get_entries
按追加順序取得所有工作階段條目(不包括工作階段標頭資訊)。工作階段是具有穩定 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。
get_tree
以條目樹的形式取得工作階段。每個節點都是 {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"
}
}get_last_assistant_text
取得最後一條助理訊息的文字內容。
{"type": "get_last_assistant_text"}回復:
{
"type": "response",
"command": "get_last_assistant_text",
"success": true,
"data": {"text": "The assistant's response..."}
}如果不存在 assistant 訊息,則傳回 {"text": null}。
set_session_name
設定目前工作階段的顯示名稱。該名稱出現在工作階段清單中並有助於識別工作階段。
{"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 程序。
指令
get_commands
取得可用指令(擴充指令、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 傳送,則不會執行。
事件
Agent 執行期間,事件會以 JSON 行形式串流輸出到 stdout。事件通常不包含 id 欄位;如果來源 bash 指令提供了 id,bash_execution_update 會包含該 id。
事件類型
| 事件 | 描述 |
|---|---|
agent_start |
Agent 開始處理 |
agent_end |
一次底層 Agent 執行完成(之後可能仍有重試、壓縮或排隊續寫) |
agent_settled |
Agent 執行已完全穩定;沒有剩餘的自動重試、壓縮重試或排隊續寫 |
turn_start |
新輪次開始 |
turn_end |
輪次完成(包含助理訊息和工具結果) |
message_start |
訊息開始 |
message_update |
串流更新(文字/思考/工具呼叫增量) |
message_end |
訊息完成 |
bash_execution_update |
直接 RPC Bash 指令輸出 chunk |
tool_execution_start |
工具開始執行 |
tool_execution_update |
工具執行進度(串流輸出) |
tool_execution_end |
工具完成 |
queue_update |
待處理 steering/follow-up 佇列已變化 |
compaction_start |
壓縮開始 |
compaction_end |
壓縮完成 |
auto_retry_start |
自動重試開始(瞬時錯誤後) |
auto_retry_end |
自動重試完成(成功或最終失敗) |
summarization_retry_scheduled |
為瞬時壓縮或分支摘要錯誤安排重試 |
summarization_retry_attempt_start |
重試摘要請求開始 |
summarization_retry_finished |
摘要重試循環完成 |
extension_error |
擴充引發錯誤 |
agent_start
當 Agent 開始處理 Prompt 時發出。
{"type": "agent_start"}agent_end
當一次底層 Agent 執行完成時發出。包含本次執行期間產生的所有訊息。如果 willRetry 為 true,隨後會進行自動重試。
{
"type": "agent_end",
"messages": [...],
"willRetry": false
}agent_settled
完整的工作階段級執行穩定後發出。此時 Pi 不會再透過重試、壓縮重試或排隊 follow-up message 自動繼續。
{"type": "agent_settled"}turn_start / turn_end
一個輪次由一條助理回應及其產生的工具呼叫和結果組成。
{"type": "turn_start"}{
"type": "turn_end",
"message": {...},
"toolResults": [...]
}message_start / message_end
當訊息開始和完成時發出。 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 block 開始 |
thinking_delta |
thinking 內容塊 |
thinking_end |
thinking block 結束 |
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.delta; toolcall_end.toolCall
包含已完成的呼叫。
bash_execution_update
從直接 bash 指令中為每個輸出塊發出一次。 id 與指令的 id 比對,允許客戶端將輸出與正確的指令相關聯。
指令執行階段事件會傳輸所有輸出,即使最終 bash 回應的 output 被截斷。
{
"type": "bash_execution_update",
"id": "req-1",
"delta": "total 48\n"
}tool_execution_start / tool_execution_update / tool_execution_end
當工具啟動、傳輸進度並完成執行時發出。
{
"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 包含迄今為止累積的輸出(不僅僅是增量),允許客戶端在每次更新時簡單地替換其顯示。
queue_update
每當待處理的轉向或後續佇列發生更改時發出。
{
"type": "queue_update",
"steering": ["Focus on error handling"],
"followUp": ["After that, summarize the result"]
}compaction_start / compaction_end
壓縮執行階段發出,無論是手動還是自動。
{"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" 並且壓縮成功,則 willRetry 為 true,代理將自動重試提示。
如果壓縮被中止,result 是 null,aborted 是 true。
如果壓縮失敗(例如超過 API 配額),則 result 為 null,aborted 為 false,errorMessage 包含錯誤描述。
auto_retry_start / auto_retry_end
當發生瞬時錯誤(過載、速率限制、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
當壓縮或分支摘要匯總在暫時 Provider 錯誤後重試時發出。這些事件使用與自動助理輪次次重試相同的重試設定。
{
"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"
}extension_error
當擴充拋出錯誤時發出。
{
"type": "extension_error",
"extensionPath": "/path/to/extension.ts",
"event": "tool_call",
"error": "Error message..."
}擴充 UI 協議
Extensions 可以透過 ctx.ui.select()、ctx.ui.confirm() 等請求使用者互動。在 RPC 模式下,這些被轉換為基本指令/事件流之上的請求/回應子協議。
擴充 UI 方法有兩類:
- 對話框方法 (
select、confirm、input、editor):在 stdout 上發出extension_ui_request並阻塞,直到客戶端在 stdin 上發回與比對的id相比對的extension_ui_response。 - fire-and-forget方法(
notify、setStatus、setWidget、setTitle、set_editor_text):在 stdout 上發出extension_ui_request,但不期望得到回應。客戶端可以顯示該資訊或忽略它。
如果對話方法包含 timeout 欄位,則Agent 端將在超時到期時使用預設值自動解析。客戶端不需要追蹤超時。
某些 ExtensionUIContext 方法在 RPC 模式下不受支援或降級,因為它們需要直接 TUI 存取:
custom()傳回undefinedsetWorkingMessage()、setWorkingIndicator()、setFooter()、setHeader()、setEditorComponent()、setToolsExpanded()為空操作getEditorText()傳回""getToolsExpanded()傳回falsepasteToEditor()委託給setEditorText()(無貼上/折疊處理)getAllThemes()傳回[]getTheme()傳回undefinedsetTheme()傳回{ success: false, error: "..." }
注意:在 RPC 模式下,ctx.mode 為 "rpc",ctx.hasUI 為 true,因為對話框和即發即棄方法透過擴充 UI 子協議發揮作用。使用 ctx.mode === "tui" 來保護 TUI 特定功能,例如需要真實終端機的 custom()。
擴充 UI 請求 (stdout)
所有請求都有 type: "extension_ui_request"、唯一的 id 和 method 欄位。
select
提示使用者從清單中進行選擇。帶有 timeout 欄位的對話框方法包括以毫秒為單位的超時;如果客戶端沒有及時回應,代理會自動解析 undefined。
{
"type": "extension_ui_request",
"id": "uuid-1",
"method": "select",
"title": "Allow dangerous command?",
"options": ["Allow", "Block"],
"timeout": 10000
}預期回應:extension_ui_response 與 value(所選選項字串)或 cancelled: true。
confirm
提示使用者進行是/否確認。
{
"type": "extension_ui_request",
"id": "uuid-2",
"method": "confirm",
"title": "Clear session?",
"message": "All messages will be lost.",
"timeout": 5000
}預期回應:extension_ui_response 與 confirmed: true/false 或 cancelled: true。
input
提示使用者輸入自由格式的文字。
{
"type": "extension_ui_request",
"id": "uuid-3",
"method": "input",
"title": "Enter a value",
"placeholder": "type something..."
}預期回應:extension_ui_response 和 value(輸入的文字)或 cancelled: true。
editor
開啟帶有選用預填充內容的多行文字編輯器。
{
"type": "extension_ui_request",
"id": "uuid-4",
"method": "editor",
"title": "Edit some text",
"prefill": "Line 1\nLine 2\nLine 3"
}預期回應:extension_ui_response 與 value(編輯後的文字)或 cancelled: true。
notify
顯示通知。即發即棄,預計不會有任何回應。
{
"type": "extension_ui_request",
"id": "uuid-5",
"method": "notify",
"message": "Command blocked by user",
"notifyType": "warning"
}notifyType 欄位為 "info"、"warning" 或 "error"。如果省略,則預設為 "info"。
setStatus
設定或清除頁尾/狀態欄中的狀態條目。該請求是 fire-and-forget,不會等待回應。
{
"type": "extension_ui_request",
"id": "uuid-6",
"method": "setStatus",
"statusKey": "my-ext",
"statusText": "Turn 3 running..."
}傳送 statusText: undefined(或省略)以清除該鍵的狀態條目。
setWidget
設定或清除顯示在編輯器上方或下方的 widget(文字行塊)。該請求是 fire-and-forget,不會等待回應。
{
"type": "extension_ui_request",
"id": "uuid-7",
"method": "setWidget",
"widgetKey": "my-ext",
"widgetLines": ["--- My Widget ---", "Line 1", "Line 2"],
"widgetPlacement": "aboveEditor"
}傳送 widgetLines: undefined(或省略)以清除 widget。widgetPlacement 欄位為 "aboveEditor"(預設)或 "belowEditor"。RPC 模式僅支援字串陣列;元件工廠會被忽略。
setTitle
設定終端機視窗/分頁標題。該請求是 fire-and-forget,不會等待回應。
{
"type": "extension_ui_request",
"id": "uuid-8",
"method": "setTitle",
"title": "pi - my project"
}set_editor_text
在輸入編輯器中設定文字。該請求是 fire-and-forget,不會等待回應。
{
"type": "extension_ui_request",
"id": "uuid-9",
"method": "set_editor_text",
"text": "prefilled text for the user"
}擴充 UI 回應 (stdin)
僅針對對話方法傳送回應(select、confirm、input、editor)。 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..."
}類型
原始檔:
packages/ai/src/types.ts-Model,UserMessage,AssistantMessage,ToolResultMessagepackages/agent/src/types.ts-AgentMessage,AgentEventsrc/core/messages.ts-BashExecutionMessagesrc/modes/json-event.ts-JsonAgentSessionEventsrc/modes/rpc/rpc-types.ts- RPC 指令/回應類型,擴充 UI 請求/回應類型
模型
{
"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
}
}UserMessage
{
"role": "user",
"content": "Hello!",
"timestamp": 1733234567890,
"attachments": []
}content 欄位可以是字串或 TextContent/ImageContent 塊的陣列。
AssistantMessage
{
"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 工作。如果存在,它會計入工作階段 token 和成本總計。
BashExecutionMessage
由 bash RPC 指令建立(不是由 LLM 工具呼叫):
{
"role": "bashExecution",
"command": "ls -la",
"output": "total 48\ndrwxr-xr-x ...",
"exitCode": 0,
"cancelled": false,
"truncated": false,
"fullOutputPath": null,
"timestamp": 1733234567890
}Attachment
{
"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");
});