print 與 rpc 模式
pi 的非互動模式有兩種:print(單次,文字或 JSON 輸出)和 rpc(長連接,JSON-RPC over stdin/stdout)。print 模式用於腳本化呼叫 pi -p "fix this bug",rpc 模式用於把 pi 嵌到其他 GUI 應用(VS Code 擴充、網頁前端)裡。兩者共用 AgentSessionRuntime,但訂閱事件、輸出格式、擴充 UI 整合的方式完全不同。
職責
- print 模式:
runPrintMode接mode: "text" | "json",text 模式只輸出最終 assistant 訊息文字,json 模式串流輸出所有AgentSessionEvent。見packages/coding-agent/src/modes/print-mode.ts:32-66。 - rpc 模式:
runRpcMode用attachJsonlLineReader從 stdin 讀 JSON 命令,透過handleCommand分派 29 種命令類型,事件和回應寫到 stdout。見packages/coding-agent/src/modes/rpc/rpc-mode.ts:48-70、packages/coding-agent/src/modes/rpc/rpc-mode.ts:371-625。 - 訊號處理:兩種模式都註冊 SIGTERM / SIGHUP handler,先
killTrackedDetachedChildren再disposeRuntime,保證 bash 子行程不殘留。見packages/coding-agent/src/modes/print-mode.ts:47-63、packages/coding-agent/src/modes/rpc/rpc-mode.ts:351-365。 - 擴充 UI 橋接:rpc 模式實作
createExtensionUIContext,把擴充的 dialog / widget 請求透過extension_ui_request事件發到客戶端,客戶端回應extension_ui_response回來。見packages/coding-agent/src/modes/rpc/rpc-mode.ts:83-130。 - session 替換鉤子:兩種模式都透過
runtimeHost.setRebindSession註冊 rebind 回呼,session 切換後重新bindExtensions、重訂閱事件。見packages/coding-agent/src/modes/print-mode.ts:67-108、packages/coding-agent/src/modes/rpc/rpc-mode.ts:306-349。
設計動機
為什麼 print 和 rpc 不共用程式碼?因為輸出契約不同:print 模式 stdout 是單向的(要麼文字要麼 JSON 行),rpc 模式 stdin/stdout 是雙向的(命令進、事件+回應出)。rpc 還要處理擴充 UI 請求的 pending Promise map(等客戶端回應),這套機制 print 模式完全不需要。共用會引入不必要的複雜度,所以兩個檔案各自實作自己的 rebindSession 和 handleEvent。
rpc 模式的 29 個命令類型(prompt、steer、follow_up、abort、new_session、get_state、set_model、cycle_model、bash、fork、clone、export_html 等)是 AgentSession 公共 API 的完整映射。客戶端可以透過 RPC 呼叫 AgentSession 暴露的所有方法,這讓 pi 可以被嵌入到任何語言寫的 GUI 裡,只要能講 JSON-RPC。
print 模式的 mode: "text" 路徑只看最後一條 assistant 訊息:成功就寫 content.text 到 stdout,失敗(stopReason === "error" || "aborted")就寫 stderr 並回傳 exitCode 1。簡單粗暴,適合 shell 腳本 pi -p "..." | jq 這種用法。
關鍵檔案
packages/coding-agent/src/modes/print-mode.ts:14-26—PrintModeOptions:mode、messages、initialMessage、initialImages。packages/coding-agent/src/modes/print-mode.ts:32-70—runPrintMode函式簽名、disposeRuntime、registerSignalHandlers、rebindSession。packages/coding-agent/src/modes/print-mode.ts:110-145— 主流程:發 initialMessage、循環發 messages、text 模式輸出最後一條 assistant 訊息。packages/coding-agent/src/modes/rpc/rpc-mode.ts:1-13— 檔案頭註解,描述 rpc 協定(命令、回應、事件、擴充 UI)。packages/coding-agent/src/modes/rpc/rpc-mode.ts:48-70—runRpcMode函式簽名、output/success/errorhelper。packages/coding-agent/src/modes/rpc/rpc-mode.ts:83-130—createDialogPromise,擴充 UI 請求的 Promise + timeout + abort 管理。packages/coding-agent/src/modes/rpc/rpc-mode.ts:306-349—rebindSession:重綁擴充、session.subscribe把事件 output 出去。packages/coding-agent/src/modes/rpc/rpc-mode.ts:371-625—handleCommandswitch,29 個命令分支。packages/coding-agent/src/modes/rpc/rpc-types.ts— RPC 協定類型定義(RpcCommand、RpcResponse、RpcExtensionUIRequest等)。
print 模式 text 路徑,只輸出最後一條 assistant 訊息的 text content:
// packages/coding-agent/src/modes/print-mode.ts:128-145
if (mode === "text") {
const state = session.state;
const lastMessage = state.messages[state.messages.length - 1];
if (lastMessage?.role === "assistant") {
const assistantMsg = lastMessage as AssistantMessage;
if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
console.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);
exitCode = 1;
} else {
for (const content of assistantMsg.content) {
if (content.type === "text") {
writeRawStdout(`${content.text}\n`);
}
}
}
}
}rpc 模式 prompt 命令,用 preflightResult 在 prompt 接受後立刻回 response:
// packages/coding-agent/src/modes/rpc/rpc-mode.ts:379-401
case "prompt": {
let preflightSucceeded = false;
void session
.prompt(command.message, {
images: command.images,
streamingBehavior: command.streamingBehavior,
source: "rpc",
preflightResult: (didSucceed) => {
if (didSucceed) {
preflightSucceeded = true;
output(success(id, "prompt"));
}
},
})
.catch((e) => {
if (!preflightSucceeded) {
output(error(id, "prompt", e.message));
}
});資料流
兩種模式的輸入輸出對照:
邊界與失敗
- rpc 不支援 @file 參數:
main.ts在分派前檢查parsed.mode === "rpc" && parsed.fileArgs.length > 0直接報錯退出,見packages/coding-agent/src/main.ts:475-478。 - rpc 不支援主題切換:
setTheme回傳{ success: false, error: "Theme switching not supported in RPC mode" },見packages/coding-agent/src/modes/rpc/rpc-mode.ts:291-294。 - rpc 不支援工具展開:
getToolsExpanded永遠回傳 false,因為 TUI 概念在 rpc 不存在,見packages/coding-agent/src/modes/rpc/rpc-mode.ts:296-303。 - 擴充 UI timeout / abort:
createDialogPromise支援opts.timeout和opts.signal,逾時或 abort 時 resolve 預設值,不 reject,讓擴充邏輯能繼續,見packages/coding-agent/src/modes/rpc/rpc-mode.ts:90-130。 - print 模式 stdout 接管:
main.ts在非 interactive 模式呼叫takeOverStdout(),所有console.log重導到內部緩衝,最後flushRawStdout寫出,避免 TUI 跳脫序列污染,見packages/coding-agent/src/modes/print-mode.ts:155-157。 - shutdown 訊號:
rpc-mode的shutdownRequested標誌由擴充shutdownHandler觸發,主迴圈偵測到後清理退出,見packages/coding-agent/src/modes/rpc/rpc-mode.ts:79-81、packages/coding-agent/src/modes/rpc/rpc-mode.ts:337-339。
小結
print 和 rpc 是 pi 的非互動模式,共用 AgentSessionRuntime 但輸出契約不同。print 單向輸出文字或 JSON 事件串流,rpc 是雙向 JSON-RPC 協定,把 AgentSession 所有方法暴露給外部客戶端,並橋接擴充 UI 請求。組裝入口看 CLI 入口與分派,runtime 替換看 會話 switch/fork/import。