コンテンツにスキップ

RPC モード

RPC モードは、stdin/stdout 上の JSON プロトコルでコーディング agent をヘッドレス動作させます。他のアプリケーション、IDE、カスタム UI への組み込みに便利です。

Node.js/TypeScript ユーザー向け:Node.js アプリケーションを構築する場合は、サブプロセスを spawn する代わりに @earendil-works/pi-coding-agentAgentSession を直接使用することを検討してください。API は src/core/agent-session.ts を参照。サブプロセスベースの TypeScript クライアントは src/modes/rpc/rpc-client.ts を参照。

Terminal window
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>:カスタムセッション保存ディレクトリ
  • Commands(コマンド):stdin に送信する JSON オブジェクト、1 行 1 件
  • Responses(レスポンス)type: "response" の JSON オブジェクトでコマンドの成功/失敗を示す
  • Events(イベント):agent イベントが JSON 行として stdout にストリーミング

すべてのコマンドは、リクエスト/レスポンスの相関用にオプションの id フィールドをサポートします。指定した場合、対応するレスポンスに同じ id が含まれます。

RPC モードは厳密な JSONL セマンティクスを使用し、LF(\n)のみをレコード区切りとします。

クライアントにとって重要な点:

  • レコードは \n のみで分割
  • オプションの \r\n 入力は末尾の \r を除去して受け入れ
  • Unicode 区切り文字を改行として扱う汎用行リーダーは使用しない

特に、Node の readlineU+2028U+2029 でも分割するため RPC モードのプロトコルに準拠しません。これらは JSON 文字列内で有効です。

ユーザー プロンプトを agent に送信します。コマンド レスポンスは、プロンプトが受け入れ、キューに入れ、または処理された後に出力されます。受け入れ後もイベントは非同期でストリーミングを続けます。

{"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 実行中にメッセージをキューに入れます。現在の assistant ターンがツール呼び出しの実行を完了した後、次の LLM 呼び出しの前に配信されます。
  • "followUp":agent の完了を待ちます。agent が停止したときのみメッセージが配信されます。

agent がストリーミング中で streamingBehavior が指定されていない場合、コマンドはエラーを返します。

拡張コマンド:メッセージが拡張コマンド(例:/mycommand)の場合、ストリーミング中でも即座に実行されます。拡張コマンドは pi.sendMessage() 経由で独自の LLM インタラクションを管理します。

入力展開:Skill コマンド(/skill:name)とプロンプト テンプレート(/template)は、送信/キューイング前に展開されます。

レスポンス:

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

success: true はプロンプトが受け入れ、キューに入れ、または即座に処理されたことを意味します。success: false は受け入れ前に拒否されたことを意味します。受け入れ後の失敗は通常のイベントとメッセージ ストリームで報告され、同じリクエスト id に対する 2 つ目の response にはなりません。

images フィールドはオプションです。各画像は ImageContent 形式を使用します:{"type": "image", "data": "base64-encoded-data", "mimeType": "image/png"}

agent 実行中に steering メッセージをキューに入れます。現在の assistant ターンがツール呼び出しの実行を完了した後、次の LLM 呼び出しの前に配信されます。Skill コマンドとプロンプト テンプレートは展開されます。拡張コマンドは許可されません(代わりに 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}

steering メッセージの処理方法の制御は set_steering_mode を参照。

agent 完了後に処理する follow-up メッセージをキューに入れます。agent にツール呼び出しや steering メッセージが残っていない場合のみ配信されます。Skill コマンドとプロンプト テンプレートは展開されます。拡張コマンドは許可されません(代わりに 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}

follow-up メッセージの処理方法の制御は set_follow_up_mode を参照。

現在の agent 操作を中止します。

{"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 オブジェクトまたは null です。sessionName フィールドは 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": {...}
}

次の利用可能なモデルに切り替えます。利用可能なモデルが 1 つだけの場合、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"

注意:"xhigh" は OpenAI codex-max モデルのみサポートされます。

レスポンス:

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

利用可能な思考レベルを循環します。モデルが思考をサポートしない場合、null データを返します。

{"type": "cycle_thinking_level"}

レスポンス:

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

steering メッセージ(steer から)の配信方法を制御します。

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

モード:

  • "all":現在の assistant ターンがツール呼び出しの実行を完了した後、すべての steering メッセージを配信
  • "one-at-a-time":完了した assistant ターンごとに 1 つの steering メッセージを配信(デフォルト)

レスポンス:

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

follow-up メッセージ(follow_up から)の配信方法を制御します。

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

モード:

  • "all":agent 完了時にすべての follow-up メッセージを配信
  • "one-at-a-time":agent 完了ごとに 1 つの follow-up メッセージを配信(デフォルト)

レスポンス:

{"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,
"details": {}
}
}

estimatedTokensAfter はコンパクション直後に再構築されたメッセージ コンテキストに対するヒューリスティック推定であり、プロバイダー正確なトークン数ではありません。

コンテキストがほぼ満杯のときの自動コンパクションを有効/無効にします。

{"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}

シェル コマンドを実行し、出力を会話コンテキストに追加します。

{"type": "bash", "command": "ls -la"}

レスポンス:

{
"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 ...
```

これは次を意味します:

  1. Bash 出力は次の prompt で LLM コンテキストに含まれ、即座には含まれません
  2. prompt の前に複数の bash コマンドを実行できます。すべての出力が含まれます
  3. BashExecutionMessage 自体にイベントは出力されません

実行中の 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
}
}
}

tokens には現在のセッション状態の assistant 使用量合計が含まれます。contextUsage にはコンパクションとフッター表示に使用される実際の現在のコンテキスト ウィンドウ推定が含まれます。

モデルまたはコンテキスト ウィンドウがない場合、contextUsage は省略されます。コンパクション直後は、コンパクション後の新しい assistant レスポンスが有効な使用量データを提供するまで、contextUsage.tokenscontextUsage.percentnull です。

セッションを 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}}

アクティブ ブランチ上の以前のユーザー メッセージから新しい fork を作成します。session_before_fork 拡張イベント ハンドラーでキャンセル可能です。fork 元メッセージのテキストを返します。

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

レスポンス:

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

拡張が fork をキャンセルした場合:

{
"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}
}

拡張が clone をキャンセルした場合:

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

fork に使用可能なユーザー メッセージを取得します。

{"type": "get_fork_messages"}

レスポンス:

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

最後の assistant メッセージのテキスト内容を取得します。

{"type": "get_last_assistant_text"}

レスポンス:

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

assistant メッセージが存在しない場合、{"text": null} を返します。

現在のセッションの表示名を設定します。名前はセッション一覧に表示され、セッションの識別に役立ちます。

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

レスポンス:

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

現在のセッション名は get_statesessionName フィールドで取得できます。RPC モード起動時に初期名を設定するには、pi --mode rpc プロセスに --name <name> または -n <name> を渡します。

利用可能なコマンド(拡張コマンド、プロンプト テンプレート、skill)を取得します。/ プレフィックス付きで 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 ディレクトリから読み込み(名前は skill: プレフィックス付き)
  • location:読み込み元(オプション、拡張には存在しない):
    • "user":ユーザーレベル(~/.pi/agent/
    • "project":プロジェクトレベル(./.pi/agent/
    • "path":CLI または設定経由の明示的パス
  • path:コマンドソースの絶対ファイル パス(オプション)

注意:組み込み TUI コマンド(/settings/hotkeys など)は含まれません。これらは対話モードでのみ処理され、prompt 経由で送信しても実行されません。

agent 操作中、イベントは JSON 行として stdout にストリーミングされます。イベントには id フィールドは含まれません(レスポンスのみ)。

EventDescription
agent_startAgent が処理を開始
agent_endAgent が完了(生成されたすべてのメッセージを含む)
turn_start新しいターンが開始
turn_endターンが完了(assistant メッセージとツール結果を含む)
message_startメッセージが開始
message_updateストリーミング更新(text/thinking/toolcall デルタ)
message_endメッセージが完了
tool_execution_startツールが実行を開始
tool_execution_updateツール実行の進捗(ストリーミング出力)
tool_execution_endツールが完了
queue_update保留中の steering/follow-up キューが変更
compaction_startコンパクションが開始
compaction_endコンパクションが完了
auto_retry_start自動リトライが開始(一時的エラー後)
auto_retry_end自動リトライが完了(成功または最終失敗)
extension_error拡張がエラーをスロー

agent がプロンプトの処理を開始するときに出力されます。

{"type": "agent_start"}

agent が完了するときに出力されます。この実行中に生成されたすべてのメッセージを含みます。

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

1 ターンは 1 つの assistant レスポンスと、それに続くツール呼び出しと結果で構成されます。

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

メッセージの開始と完了時に出力されます。message フィールドには AgentMessage が含まれます。

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

assistant メッセージのストリーミング中に出力されます。部分メッセージとストリーミング デルタ イベントの両方を含みます。

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

assistantMessageEvent フィールドには次のデルタ タイプのいずれかが含まれます:

TypeDescription
startメッセージ生成が開始
text_startテキスト コンテンツ ブロックが開始
text_deltaテキスト コンテンツ チャンク
text_endテキスト コンテンツ ブロックが終了
thinking_start思考ブロックが開始
thinking_delta思考コンテンツ チャンク
thinking_end思考ブロックが終了
toolcall_startツール呼び出しが開始
toolcall_deltaツール呼び出し引数チャンク
toolcall_endツール呼び出しが終了(完全な toolCall オブジェクトを含む)
doneメッセージ完了(理由:"stop""length""toolUse"
errorエラー発生(理由:"aborted""error"

テキスト レスポンスのストリーミング例:

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

tool_execution_start / tool_execution_update / tool_execution_end

Section titled “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_updatepartialResult にはこれまでの累積出力(デルタだけでなく)が含まれ、クライアントは各更新で表示を置き換えるだけで済みます。

保留中の steering または follow-up キューが変更されるたびに出力されます。

{
"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,
"details": {}
},
"aborted": false,
"willRetry": false
}

reason"overflow" でコンパクションが成功した場合、willRetrytrue で agent が自動的にプロンプトをリトライします。

コンパクションが中止された場合、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"
}

拡張がエラーをスローしたときに出力されます。

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

拡張は ctx.ui.select()ctx.ui.confirm() などでユーザー インタラクションを要求できます。RPC モードでは、これらは基本コマンド/イベント フローの上に構築されたリクエスト/レスポンス サブプロトコルに変換されます。

拡張 UI メソッドには 2 つのカテゴリがあります:

  • Dialog メソッドselectconfirminputeditor):stdout に extension_ui_request を出力し、クライアントが stdin で一致する idextension_ui_response を送るまでブロックします。
  • Fire-and-forget メソッドnotifysetStatussetWidgetsetTitleset_editor_text):stdout に extension_ui_request を出力しますが、レスポンスは期待しません。クライアントは情報を表示するか無視できます。

dialog メソッドに timeout フィールドがある場合、タイムアウト後に agent 側がデフォルト値で自動解決します。クライアントはタイムアウトを追跡する必要はありません。

一部の ExtensionUIContext メソッドは、直接 TUI アクセスが必要なため RPC モードではサポートされないか劣化します:

  • custom()undefined を返す
  • setWorkingMessage()setWorkingIndicator()setFooter()setHeader()setEditorComponent()setToolsExpanded() は no-op
  • getEditorText()"" を返す
  • getToolsExpanded()false を返す
  • pasteToEditor()setEditorText() に委譲(ペースト/折りたたみ処理なし)
  • getAllThemes()[] を返す
  • getTheme()undefined を返す
  • setTheme(){ success: false, error: "..." } を返す

注意:RPC モードでは dialog と fire-and-forget メソッドが拡張 UI サブプロトコル経由で機能するため、ctx.mode"rpc"ctx.hasUItrue です。実際のターミナルを必要とする TUI 固有機能(custom() など)を保護するには ctx.mode === "tui" を使用してください。

すべてのリクエストには type: "extension_ui_request"、一意の idmethod フィールドがあります。

ユーザーにリストから選択を促します。timeout フィールド付き dialog メソッドにはミリ秒単位のタイムアウトが含まれます。クライアントが時間内に応答しない場合、agent は undefined で自動解決します。

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

期待されるレスポンス:value(選択されたオプション文字列)または cancelled: true 付きの extension_ui_response

ユーザーに yes/no 確認を促します。

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

期待されるレスポンス:confirmed: true/false または cancelled: true 付きの extension_ui_response

ユーザーに自由形式テキストの入力を促します。

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

期待されるレスポンス:value(入力テキスト)または cancelled: true 付きの extension_ui_response

オプションの事前入力付きで複数行テキスト エディターを開きます。

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

期待されるレスポンス:value(編集後テキスト)または cancelled: true 付きの extension_ui_response

通知を表示します。Fire-and-forget、レスポンス不要。

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

notifyType フィールドは "info""warning"、または "error" です。省略時は "info" がデフォルトです。

フッター/ステータス バーのステータス項目を設定またはクリアします。Fire-and-forget。

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

statusText: undefined(または省略)を送信して、そのキーのステータス項目をクリアします。

エディターの上または下に表示されるウィジェット(テキスト行ブロック)を設定またはクリアします。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(または省略)を送信してウィジェットをクリアします。widgetPlacement フィールドは "aboveEditor"(デフォルト)または "belowEditor" です。RPC モードでは文字列配列のみサポートされ、コンポーネント ファクトリは無視されます。

ターミナル ウィンドウ/タブのタイトルを設定します。Fire-and-forget。

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

入力エディターのテキストを設定します。Fire-and-forget。

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

dialog メソッド(selectconfirminputeditor)のみレスポンスを送信します。id はリクエストと一致する必要があります。

値レスポンス(select、input、editor)

Section titled “値レスポンス(select、input、editor)”
{"type": "extension_ui_response", "id": "uuid-1", "value": "Allow"}
{"type": "extension_ui_response", "id": "uuid-2", "confirmed": true}

キャンセル レスポンス(任意の dialog)

Section titled “キャンセル レスポンス(任意の dialog)”

任意の dialog メソッドを閉じます。拡張は undefined(select/input/editor)または false(confirm)を受け取ります。

{"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 ..."}],
"isError": false,
"timestamp": 1733234567890
}

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)

Section titled “例:基本クライアント(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)

Section titled “例:対話型クライアント(Node.js)”

完全な対話例は test/rpc-example.ts、型付きクライアント実装は src/modes/rpc/rpc-client.ts を参照。

拡張 UI プロトコルの完全な処理例は examples/rpc-extension-ui.ts を参照。examples/extensions/rpc-demo.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");
});