RPC モード
RPC モードは、stdin/stdout 上の JSON プロトコルでコーディング agent をヘッドレス動作させます。他のアプリケーション、IDE、カスタム UI への組み込みに便利です。
Node.js/TypeScript ユーザー向け:Node.js アプリケーションを構築する場合は、サブプロセスを spawn する代わりに @earendil-works/pi-coding-agent の AgentSession を直接使用することを検討してください。API は src/core/agent-session.ts を参照。サブプロセスベースの TypeScript クライアントは src/modes/rpc/rpc-client.ts を参照。
RPC モードの起動
Section titled “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>:カスタムセッション保存ディレクトリ
プロトコル概要
Section titled “プロトコル概要”- Commands(コマンド):stdin に送信する JSON オブジェクト、1 行 1 件
- Responses(レスポンス):
type: "response"の JSON オブジェクトでコマンドの成功/失敗を示す - Events(イベント):agent イベントが JSON 行として stdout にストリーミング
すべてのコマンドは、リクエスト/レスポンスの相関用にオプションの id フィールドをサポートします。指定した場合、対応するレスポンスに同じ id が含まれます。
フレーミング
Section titled “フレーミング”RPC モードは厳密な JSONL セマンティクスを使用し、LF(\n)のみをレコード区切りとします。
クライアントにとって重要な点:
- レコードは
\nのみで分割 - オプションの
\r\n入力は末尾の\rを除去して受け入れ - Unicode 区切り文字を改行として扱う汎用行リーダーは使用しない
特に、Node の readline は U+2028 と U+2029 でも分割するため RPC モードのプロトコルに準拠しません。これらは JSON 文字列内で有効です。
prompt
Section titled “prompt”ユーザー プロンプトを 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 を参照。
follow_up
Section titled “follow_up”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}new_session
Section titled “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
Section titled “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
Section titled “get_messages”会話内のすべてのメッセージを取得します。
{"type": "get_messages"}レスポンス:
{ "type": "response", "command": "get_messages", "success": true, "data": {"messages": [...]}}メッセージは AgentMessage オブジェクトです(Message Types を参照)。
set_model
Section titled “set_model”特定のモデルに切り替えます。
{"type": "set_model", "provider": "anthropic", "modelId": "claude-sonnet-4-20250514"}レスポンスには完全な Model オブジェクトが含まれます:
{ "type": "response", "command": "set_model", "success": true, "data": {...}}cycle_model
Section titled “cycle_model”次の利用可能なモデルに切り替えます。利用可能なモデルが 1 つだけの場合、null データを返します。
{"type": "cycle_model"}レスポンス:
{ "type": "response", "command": "cycle_model", "success": true, "data": { "model": {...}, "thinkingLevel": "medium", "isScoped": false }}model フィールドは完全な Model オブジェクトです。
get_available_models
Section titled “get_available_models”設定済みのすべてのモデルを一覧表示します。
{"type": "get_available_models"}レスポンスには完全な Model オブジェクトの配列が含まれます:
{ "type": "response", "command": "get_available_models", "success": true, "data": { "models": [...] }}set_thinking_level
Section titled “set_thinking_level”対応するモデルの推論/思考レベルを設定します。
{"type": "set_thinking_level", "level": "high"}レベル:"off"、"minimal"、"low"、"medium"、"high"、"xhigh"
注意:"xhigh" は OpenAI codex-max モデルのみサポートされます。
レスポンス:
{"type": "response", "command": "set_thinking_level", "success": true}cycle_thinking_level
Section titled “cycle_thinking_level”利用可能な思考レベルを循環します。モデルが思考をサポートしない場合、null データを返します。
{"type": "cycle_thinking_level"}レスポンス:
{ "type": "response", "command": "cycle_thinking_level", "success": true, "data": {"level": "high"}}キュー モード
Section titled “キュー モード”set_steering_mode
Section titled “set_steering_mode”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}set_follow_up_mode
Section titled “set_follow_up_mode”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}コンパクション
Section titled “コンパクション”compact
Section titled “compact”会話コンテキストを手動でコンパクト化してトークン使用量を削減します。
{"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 はコンパクション直後に再構築されたメッセージ コンテキストに対するヒューリスティック推定であり、プロバイダー正確なトークン数ではありません。
set_auto_compaction
Section titled “set_auto_compaction”コンテキストがほぼ満杯のときの自動コンパクションを有効/無効にします。
{"type": "set_auto_compaction", "enabled": true}レスポンス:
{"type": "response", "command": "set_auto_compaction", "success": true}set_auto_retry
Section titled “set_auto_retry”一時的なエラー(過負荷、レート制限、5xx)に対する自動リトライを有効/無効にします。
{"type": "set_auto_retry", "enabled": true}レスポンス:
{"type": "response", "command": "set_auto_retry", "success": true}abort_retry
Section titled “abort_retry”進行中のリトライを中止します(遅延をキャンセルしてリトライを停止)。
{"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 48drwxr-xr-x ...```これは次を意味します:
- Bash 出力は次の prompt で LLM コンテキストに含まれ、即座には含まれません
- prompt の前に複数の bash コマンドを実行できます。すべての出力が含まれます
BashExecutionMessage自体にイベントは出力されません
abort_bash
Section titled “abort_bash”実行中の bash コマンドを中止します。
{"type": "abort_bash"}レスポンス:
{"type": "response", "command": "abort_bash", "success": true}get_session_stats
Section titled “get_session_stats”トークン使用量、コスト統計、現在のコンテキスト ウィンドウ使用量を取得します。
{"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.tokens と contextUsage.percent は null です。
export_html
Section titled “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
Section titled “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 拡張イベント ハンドラーでキャンセル可能です。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}}get_fork_messages
Section titled “get_fork_messages”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..."} ] }}get_last_assistant_text
Section titled “get_last_assistant_text”最後の assistant メッセージのテキスト内容を取得します。
{"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
Section titled “set_session_name”現在のセッションの表示名を設定します。名前はセッション一覧に表示され、セッションの識別に役立ちます。
{"type": "set_session_name", "name": "my-feature-work"}レスポンス:
{ "type": "response", "command": "set_session_name", "success": true}現在のセッション名は get_state の sessionName フィールドで取得できます。RPC モード起動時に初期名を設定するには、pi --mode rpc プロセスに --name <name> または -n <name> を渡します。
get_commands
Section titled “get_commands”利用可能なコマンド(拡張コマンド、プロンプト テンプレート、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 フィールドは含まれません(レスポンスのみ)。
イベント タイプ
Section titled “イベント タイプ”| Event | Description |
|---|---|
agent_start | Agent が処理を開始 |
agent_end | Agent が完了(生成されたすべてのメッセージを含む) |
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_start
Section titled “agent_start”agent がプロンプトの処理を開始するときに出力されます。
{"type": "agent_start"}agent_end
Section titled “agent_end”agent が完了するときに出力されます。この実行中に生成されたすべてのメッセージを含みます。
{ "type": "agent_end", "messages": [...]}turn_start / turn_end
Section titled “turn_start / turn_end”1 ターンは 1 つの assistant レスポンスと、それに続くツール呼び出しと結果で構成されます。
{"type": "turn_start"}{ "type": "turn_end", "message": {...}, "toolResults": [...]}message_start / message_end
Section titled “message_start / message_end”メッセージの開始と完了時に出力されます。message フィールドには AgentMessage が含まれます。
{"type": "message_start", "message": {...}}{"type": "message_end", "message": {...}}message_update(ストリーミング)
Section titled “message_update(ストリーミング)”assistant メッセージのストリーミング中に出力されます。部分メッセージとストリーミング デルタ イベントの両方を含みます。
{ "type": "message_update", "message": {...}, "assistantMessageEvent": { "type": "text_delta", "contentIndex": 0, "delta": "Hello ", "partial": {...} }}assistantMessageEvent フィールドには次のデルタ タイプのいずれかが含まれます:
| Type | Description |
|---|---|
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_update の partialResult にはこれまでの累積出力(デルタだけでなく)が含まれ、クライアントは各更新で表示を置き換えるだけで済みます。
queue_update
Section titled “queue_update”保留中の steering または follow-up キューが変更されるたびに出力されます。
{ "type": "queue_update", "steering": ["Focus on error handling"], "followUp": ["After that, summarize the result"]}compaction_start / compaction_end
Section titled “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, "details": {} }, "aborted": false, "willRetry": false}reason が "overflow" でコンパクションが成功した場合、willRetry は true で agent が自動的にプロンプトをリトライします。
コンパクションが中止された場合、result は null、aborted は true です。
コンパクションが失敗した場合(例:API クォータ超過)、result は null、aborted は false、errorMessage にエラー説明が含まれます。
auto_retry_start / auto_retry_end
Section titled “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"}extension_error
Section titled “extension_error”拡張がエラーをスローしたときに出力されます。
{ "type": "extension_error", "extensionPath": "/path/to/extension.ts", "event": "tool_call", "error": "Error message..."}拡張 UI プロトコル
Section titled “拡張 UI プロトコル”拡張は ctx.ui.select()、ctx.ui.confirm() などでユーザー インタラクションを要求できます。RPC モードでは、これらは基本コマンド/イベント フローの上に構築されたリクエスト/レスポンス サブプロトコルに変換されます。
拡張 UI メソッドには 2 つのカテゴリがあります:
- Dialog メソッド(
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を出力しますが、レスポンスは期待しません。クライアントは情報を表示するか無視できます。
dialog メソッドに timeout フィールドがある場合、タイムアウト後に agent 側がデフォルト値で自動解決します。クライアントはタイムアウトを追跡する必要はありません。
一部の ExtensionUIContext メソッドは、直接 TUI アクセスが必要なため RPC モードではサポートされないか劣化します:
custom()はundefinedを返すsetWorkingMessage()、setWorkingIndicator()、setFooter()、setHeader()、setEditorComponent()、setToolsExpanded()は no-opgetEditorText()は""を返すgetToolsExpanded()はfalseを返すpasteToEditor()はsetEditorText()に委譲(ペースト/折りたたみ処理なし)getAllThemes()は[]を返すgetTheme()はundefinedを返すsetTheme()は{ success: false, error: "..." }を返す
注意:RPC モードでは dialog と fire-and-forget メソッドが拡張 UI サブプロトコル経由で機能するため、ctx.mode は "rpc"、ctx.hasUI は true です。実際のターミナルを必要とする TUI 固有機能(custom() など)を保護するには ctx.mode === "tui" を使用してください。
拡張 UI リクエスト(stdout)
Section titled “拡張 UI リクエスト(stdout)”すべてのリクエストには type: "extension_ui_request"、一意の id、method フィールドがあります。
select
Section titled “select”ユーザーにリストから選択を促します。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。
confirm
Section titled “confirm”ユーザーに 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。
editor
Section titled “editor”オプションの事前入力付きで複数行テキスト エディターを開きます。
{ "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。
notify
Section titled “notify”通知を表示します。Fire-and-forget、レスポンス不要。
{ "type": "extension_ui_request", "id": "uuid-5", "method": "notify", "message": "Command blocked by user", "notifyType": "warning"}notifyType フィールドは "info"、"warning"、または "error" です。省略時は "info" がデフォルトです。
setStatus
Section titled “setStatus”フッター/ステータス バーのステータス項目を設定またはクリアします。Fire-and-forget。
{ "type": "extension_ui_request", "id": "uuid-6", "method": "setStatus", "statusKey": "my-ext", "statusText": "Turn 3 running..."}statusText: undefined(または省略)を送信して、そのキーのステータス項目をクリアします。
setWidget
Section titled “setWidget”エディターの上または下に表示されるウィジェット(テキスト行ブロック)を設定またはクリアします。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 モードでは文字列配列のみサポートされ、コンポーネント ファクトリは無視されます。
setTitle
Section titled “setTitle”ターミナル ウィンドウ/タブのタイトルを設定します。Fire-and-forget。
{ "type": "extension_ui_request", "id": "uuid-8", "method": "setTitle", "title": "pi - my project"}set_editor_text
Section titled “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)
Section titled “拡張 UI レスポンス(stdin)”dialog メソッド(select、confirm、input、editor)のみレスポンスを送信します。id はリクエストと一致する必要があります。
値レスポンス(select、input、editor)
Section titled “値レスポンス(select、input、editor)”{"type": "extension_ui_response", "id": "uuid-1", "value": "Allow"}確認レスポンス(confirm)
Section titled “確認レスポンス(confirm)”{"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..."}ソース ファイル:
packages/ai/src/types.ts-Model、UserMessage、AssistantMessage、ToolResultMessagepackages/agent/src/types.ts-AgentMessage、AgentEventsrc/core/messages.ts-BashExecutionMessagesrc/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
Section titled “UserMessage”{ "role": "user", "content": "Hello!", "timestamp": 1733234567890, "attachments": []}content フィールドは文字列または TextContent/ImageContent ブロックの配列にできます。
AssistantMessage
Section titled “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"
ToolResultMessage
Section titled “ToolResultMessage”{ "role": "toolResult", "toolCallId": "call_123", "toolName": "bash", "content": [{"type": "text", "text": "total 48\ndrwxr-xr-x ..."}], "isError": false, "timestamp": 1733234567890}BashExecutionMessage
Section titled “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
Section titled “Attachment”{ "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 subprocessimport 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 promptsend({"type": "prompt", "message": "Hello!"})
# Process eventsfor 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 promptagent.stdin.write(JSON.stringify({ type: "prompt", message: "Hello" }) + "\n");
// Abort on Ctrl+Cprocess.on("SIGINT", () => { agent.stdin.write(JSON.stringify({ type: "abort" }) + "\n");});