{"locale":"ja","source":{"rawBase":"https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/docs","githubBase":"https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs","editBase":"https://github.com/earendil-works/pi/edit/main/packages/coding-agent/docs"},"redirects":[{"from":"/docs/latest/session","to":"/docs/latest/session-format"},{"from":"/docs/latest/tree","to":"/docs/latest/sessions"}],"fileToSlug":{"compaction.md":"compaction","containerization.md":"containerization","custom-provider.md":"custom-provider","development.md":"development","environment-variables.md":"environment-variables","extensions.md":"extensions","index.md":"index","json.md":"json","keybindings.md":"keybindings","llama-cpp.md":"llama-cpp","models.md":"models","packages.md":"packages","prompt-templates.md":"prompt-templates","providers.md":"providers","quickstart.md":"quickstart","rpc.md":"rpc","sdk.md":"sdk","security.md":"security","session-format.md":"session-format","sessions.md":"sessions","settings.md":"settings","shell-aliases.md":"shell-aliases","skills.md":"skills","terminal-setup.md":"terminal-setup","termux.md":"termux","themes.md":"themes","tmux.md":"tmux","tui.md":"tui","usage.md":"usage","windows.md":"windows"},"pages":{"ja":{"compaction":{"title":"圧縮とブランチの要約","markdown":"LLM のコンテキスト ウィンドウは限られています。会話が長くなりすぎると、Pi は圧縮を使用して、最近の内容を保持しながら古いコンテンツを要約します。このページでは、自動圧縮と branch summarization の両方について説明します。\n\n**ソース ファイル** ([pi-mono](https://github.com/earendil-works/pi-mono)):\n- [`packages/coding-agent/src/core/compaction/compaction.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/compaction.ts) - 自動圧縮ロジック\n- [`packages/coding-agent/src/core/compaction/branch-summarization.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/branch-summarization.ts) - ブランチの要約\n- [`packages/coding-agent/src/core/compaction/utils.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/utils.ts) - 共有ユーティリティ (ファイル追跡、シリアル化)\n- [`packages/coding-agent/src/core/session-manager.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts) - エントリの種類 (`CompactionEntry`、`BranchSummaryEntry`)\n- [`packages/coding-agent/src/core/extensions/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/extensions/types.ts) - 拡張イベントのタイプ\n\nプロジェクト内の TypeScript 定義については、 `node_modules/@earendil-works/pi-coding-agent/dist/` を調べてください。\n\n## 概要\n\nPi には 2 つの要約メカニズムがあります。\n\n| 機構 | トリガー | 目的 |\n|-----------|---------|---------|\n| 圧縮 | コンテキストがしきい値を超えているか、または `/compact` | 古いメッセージを要約してコンテキストを解放する |\n| ブランチの要約 | `/tree` ナビゲーション | ブランチを切り替えるときにコンテキストを保持する |\n\nどちらも同じ構造化された要約形式を使用し、ファイル操作を累積的に追跡します。圧縮およびブランチサマリー要求では、新しいルーティング セッション ID が使用され、プロバイダーによってサポートされている場合は、これらの 1 回限りのプロンプトが再利用される可能性が低いため、プロンプト キャッシュの書き込みが無効になります。\n\n## 圧縮\n\n### トリガーされるとき\n\n自動圧縮は次の場合にトリガーされます。\n\n```\ncontextTokens > contextWindow - reserveTokens\n```\n\nデフォルトでは、`reserveTokens` は 16384 トークンです (`~/.pi/agent/settings.json` または `<project-dir>/.pi/settings.json` で構成可能)。これには LLM が対応する余地が残されています。\n\n`/compact [instructions]` を使用して手動でトリガーすることもできます。この場合、オプションの指示で概要が強調されます。\n\n### 仕組み\n\n1. **カットポイントの検索**: 最新のメッセージから逆方向に進み、`keepRecentTokens` (デフォルトは 20k、`~/.pi/agent/settings.json` または `<project-dir>/.pi/settings.json` で設定可能) に達するまでトークン推定値を蓄積します。\n2. **メッセージの抽出**: 以前に保持された境界 (またはセッションの開始) からカットポイントまでのメッセージを収集します。\n3. **概要の生成**: LLM を呼び出して構造化形式で要約し、以前の概要が存在する場合は反復コンテキストとして渡します。\n4. **エントリを追加**: `CompactionEntry` を概要とともに保存し、`firstKeptEntryId`\n5. **リロード**: `firstKeptEntryId` 以降の概要とメッセージを使用してセッションがリロードされます。\n\n```\nBefore compaction:\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬─────┬──────┬─────┬──── ─┬──────┬─────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\nAfter compaction (new entry appended):\n\n  entry:  0     1     2     3      4     5     6      7      8     9     10\n        ┌─────┬─────┬─────┬─────┬──────┬─────┬──── ─┬──────┬─────┬─────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 not sent to LLM                    sent to LLM\n                                                         ↑\n                                              starts from firstKeptEntryId\n\nWhat the LLM sees:\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   from cmp          messages from firstKeptEntryId\n```\n\n圧縮を繰り返すと、要約されたスパンは圧縮エントリ自体ではなく、前の圧縮の保持された境界 (`firstKeptEntryId`) から始まり、保持されたエントリがパス内で見つからない場合は、前の圧縮後のエントリにフォールバックします。これにより、以前の圧縮で生き残ったメッセージも次の要約パスに含めることで保存されます。 Pi は、新しい `CompactionEntry` を書き込む前に、再構築されたセッション コンテキストから `tokensBefore` を再計算するため、トークン数は、置き換えられる実際の圧縮前のコンテキストを反映します。\n\n### スプリットターン\n\n「ターン」はユーザー メッセージで始まり、次のユーザー メッセージまでのすべてのアシスタントの応答とツールの呼び出しが含まれます。通常、圧縮はターン境界でカットされます。\n\n1 つのターンが `keepRecentTokens` を超えると、カット ポイントはターンの途中でアシスタント メッセージに到達します。これが「スプリットターン」です。\n\n```\nSplit turn (one huge turn exceeds budget):\n\n  entry:  0     1     2      3     4      5      6     7      8\n        ┌─────┬─────┬─────┬──────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴──────┴──────┴─────┴──────┘\n                ↑                                     ↑\n         turnStartIndex = 1                  firstKeptEntryId = 7\n                │                                     │\n                └──── turnPrefixMessages (1-6) ───────┘\n                                                      └── kept (7-8)\n\n  isSplitTurn = true\n  messagesToSummarize = []  (no complete turns before)\n  turnPrefixMessages = [usr, ass, tool, ass, tool, tool]\n```\n\nスプリットターンの場合、Pi は 2 つのサマリーを生成し、それらをマージします。\n1. **履歴の概要**: 以前のコンテキスト (存在する場合)\n2. **ターン プレフィックスの概要**: スプリット ターンの前半部分\n\n### カットポイントのルール\n\n有効なカットポイントは次のとおりです。\n- ユーザーメッセージ\n- アシスタントのメッセージ\n- Bash実行メッセージ\n- カスタムメッセージ (custom_message、branch_summary)\n\nツールの結果では決してカットしないでください (ツールの呼び出しを維持する必要があります)。\n\n### CompactionEntry 構造体\n\n[`session-manager.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts) で定義:\n\n```typescript\ninterface CompactionEntry<T = unknown> {\n  type: \"compaction\";\n  id: string;\n  parentId: string;\n  timestamp: number;\n  summary: string;\n  firstKeptEntryId: string;\n  tokensBefore: number;\n  usage?: Usage;       // LLM usage that generated the summary\n  fromHook?: boolean;  // true if provided by extension (legacy field name)\n  details?: T;         // implementation-specific data\n}\n\n// Default compaction uses this for details (from compaction.ts):\ninterface CompactionDetails {\n  readFiles: string[];\n  modifiedFiles: string[];\n}\n```\n\nExtensions は、JSON でシリアル化可能なデータを `details` に保存できます。デフォルトの圧縮ではファイル操作が追跡されますが、カスタム拡張機能の実装では独自の構造を使用できます。生成され、拡張機能が提供する要約は、利用可能な場合は LLM `usage` を保存するため、セッションの合計には要約作業が含まれます。\n\n実装については、[`prepareCompaction()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/compaction.ts) と [`compact()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/compaction.ts) を参照してください。直接プログラムによる要約の場合、`generateSummary()` は要約テキストを返し、`generateSummaryWithUsage()` は`{ text, usage }` を返します。\n\n## ブランチの要約\n\n### トリガーされるとき\n\n`/tree` を使用して別のブランチに移動すると、Pi が終了する作業の要約を提案します。これにより、左のブランチから新しいブランチにコンテキストが挿入されます。\n\n### 仕組み\n\n1. **共通の祖先を見つける**: 古い位置と新しい位置が共有する最も深いノード\n2. **エントリの収集**: 古い葉から共通の祖先に戻る\n3. **予算に合わせて準備する**: トークンの予算までのメッセージを含めます (新しいものから順)\n4. **概要の生成**: 構造化フォーマットで LLM を呼び出す\n5. **エントリを追加**: ナビゲーション ポイントで `BranchSummaryEntry` を保存します\n\n```\nTree before navigation:\n\n         ┌─ B ─ C ─ D (old leaf, being abandoned)\n    A ───┤\n         └─ E ─ F (target)\n\nCommon ancestor: A\nEntries to summarize: B, C, D\n\nAfter navigation with summary:\n\n         ┌─ B ─ C ─ D\n    A ───┤\n         └─ E ─ F ─ [summary of B,C,D] (new leaf)\n```\n\n### 累積ファイル追跡\n\n圧縮と branch summarization の両方でファイルを累積的に追跡します。概要を生成するとき、pi は以下からファイル操作を抽出します。\n- 要約されるメッセージ内のツール呼び出し\n- 以前の圧縮またはブランチの概要 `details` (存在する場合)\n\nこれは、複数の圧縮またはネストされたブランチ サマリーにわたってファイル追跡が蓄積され、読み取りおよび変更されたファイルの完全な履歴が保存されることを意味します。\n\n### Branch SummaryEntry 構造体\n\n[`session-manager.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts) で定義:\n\n```typescript\ninterface BranchSummaryEntry<T = unknown> {\n  type: \"branch_summary\";\n  id: string;\n  parentId: string;\n  timestamp: number;\n  summary: string;\n  fromId: string;      // Entry we navigated from\n  usage?: Usage;       // LLM usage that generated the summary\n  fromHook?: boolean;  // true if provided by extension (legacy field name)\n  details?: T;         // implementation-specific data\n}\n\n// Default branch summarization uses this for details (from branch-summarization.ts):\ninterface BranchSummaryDetails {\n  readFiles: string[];\n  modifiedFiles: string[];\n}\n```\n\n圧縮と同様に、拡張機能はカスタム データを `details` に保存できます。\n\n実装については、[`collectEntriesForBranchSummary()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/branch-summarization.ts)、[`prepareBranchEntries()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/branch-summarization.ts)、[`generateBranchSummary()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/branch-summarization.ts)を参照してください。\n\n## 要約フォーマット\n\n圧縮と branch summarization はどちらも同じ構造化フォーマットを使用します。\n\n```markdown\n## Goal\n[What the user is trying to accomplish]\n\n## Constraints & Preferences\n- [Requirements mentioned by user]\n\n## Progress\n### Done\n- [x] [Completed tasks]\n\n### In Progress\n- [ ] [Current work]\n\n### Blocked\n- [Issues, if any]\n\n## Key Decisions\n- **[Decision]**: [Rationale]\n\n## Next Steps\n1. [What should happen next]\n\n## Critical Context\n- [Data needed to continue]\n\n<read-files>\npath/to/file1.ts\npath/to/file2.ts\n</read-files>\n\n<modified-files>\npath/to/changed.ts\n</modified-files>\n```\n\n### メッセージのシリアル化\n\n要約する前に、メッセージは [`serializeConversation()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/utils.ts) を介してテキストにシリアル化されます。\n\n```\n[User]: What they said\n[Assistant thinking]: Internal reasoning\n[Assistant]: Response text\n[Assistant tool calls]: read(path=\"foo.ts\"); edit(path=\"bar.ts\", ...)\n[Tool result]: Output from tool\n```\n\nこれにより、モデルはそれを継続する会話として扱うことができなくなります。\n\nツールの結果はシリアル化中に 2000 文字に切り詰められます。その制限を超えるコンテンツは、切り捨てられた文字数を示すマーカーに置き換えられます。通常、ツールの結果 (特に `read` と `bash` から) がコンテキスト サイズに最も大きく影響するため、これにより要約リクエストが妥当なトークン バジェット内に収まります。\n\n## Extensions によるカスタム要約\n\nExtensions は、コンパクションと branch summarization の両方をインターセプトしてカスタマイズできます。イベント タイプの定義については、[`extensions/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/extensions/types.ts) を参照してください。\n\n### セッション前_コンパクト\n\n自動圧縮または `/compact` の前に発生します。キャンセルしたり、カスタム概要を提供したりできます。タイプ ファイルの `SessionBeforeCompactEvent` と `CompactionPreparation` を参照してください。\n\n```typescript\npi.on(\"session_before_compact\", async (event, ctx) => {\n  const { preparation, branchEntries, customInstructions, reason, willRetry, signal } = event;\n\n  // preparation.messagesToSummarize - messages to summarize\n  // preparation.turnPrefixMessages - split turn prefix (if isSplitTurn)\n  // preparation.previousSummary - previous compaction summary\n  // preparation.fileOps - extracted file operations\n  // preparation.tokensBefore - context tokens before compaction\n  // preparation.firstKeptEntryId - where kept messages start\n  // preparation.settings - compaction settings\n\n  // branchEntries - all entries on current branch (for custom state)\n  // reason - \"manual\" (/compact), \"threshold\", or \"overflow\"\n  // willRetry - whether the aborted turn is retried after compaction (overflow recovery)\n  // signal - AbortSignal (pass to LLM calls)\n\n  // Cancel:\n  return { cancel: true };\n\n  // Custom summary:\n  return {\n    compaction: {\n      summary: \"Your summary...\",\n      firstKeptEntryId: preparation.firstKeptEntryId,\n      tokensBefore: preparation.tokensBefore,\n      // usage: summaryResponse.usage, // Optional; included in session totals\n      details: { /* custom data */ },\n    }\n  };\n});\n```\n\n#### メッセージをテキストに変換する\n\n独自のモデルで概要を生成するには、`serializeConversation` を使用してメッセージをテキストに変換します。\n\n```typescript\nimport { convertToLlm, serializeConversation } from \"@earendil-works/pi-coding-agent\";\n\npi.on(\"session_before_compact\", async (event, ctx) => {\n  const { preparation } = event;\n  \n  // Convert AgentMessage[] to Message[], then serialize to text\n  const conversationText = serializeConversation(\n    convertToLlm(preparation.messagesToSummarize)\n  );\n  // Returns:\n  // [User]: message text\n  // [Assistant thinking]: thinking content\n  // [Assistant]: response text\n  // [Assistant tool calls]: read(path=\"...\"); bash(command=\"...\")\n  // [Tool result]: output text\n\n  // Now send to your model for summarization\n  const { summary, usage } = await myModel.summarize(conversationText);\n  \n  return {\n    compaction: {\n      summary,\n      firstKeptEntryId: preparation.firstKeptEntryId,\n      tokensBefore: preparation.tokensBefore,\n      usage,\n    }\n  };\n});\n```\n\n別のモデルを使用した完全な例については、[custom-compaction.ts](../examples/extensions/custom-compaction.ts) を参照してください。\n\n### セッション前ツリー\n\n`/tree` ナビゲーションの前に発生します。ユーザーが要約を選択したかどうかに関係なく、常に起動されます。ナビゲーションをキャンセルしたり、カスタムの概要を提供したりできます。\n\n```typescript\npi.on(\"session_before_tree\", async (event, ctx) => {\n  const { preparation, signal } = event;\n\n  // preparation.targetId - where we're navigating to\n  // preparation.oldLeafId - current position (being abandoned)\n  // preparation.commonAncestorId - shared ancestor\n  // preparation.entriesToSummarize - entries that would be summarized\n  // preparation.userWantsSummary - whether user chose to summarize\n\n  // Cancel navigation entirely:\n  return { cancel: true };\n\n  // Provide custom summary (only used if userWantsSummary is true):\n  if (preparation.userWantsSummary) {\n    return {\n      summary: {\n        summary: \"Your summary...\",\n        // usage: summaryResponse.usage, // Optional; included in session totals\n        details: { /* custom data */ },\n      }\n    };\n  }\n});\n```\n\nタイプ ファイルの `SessionBeforeTreeEvent` と `TreePreparation` を参照してください。\n\n## 設定\n\n`~/.pi/agent/settings.json` または `<project-dir>/.pi/settings.json` で圧縮を構成します。\n\n```json\n{\n  \"compaction\": {\n    \"enabled\": true,\n    \"reserveTokens\": 16384,\n    \"keepRecentTokens\": 20000\n  }\n}\n```\n\n| 設定 | デフォルト | 説明 |\n|---------|---------|-------------|\n| `enabled` | `true` | 自動圧縮を有効にする |\n| `reserveTokens` | `16384` | LLM 応答用に予約するトークン |\n| `keepRecentTokens` | `20000` | 保持する最近のトークン (要約されていない) |\n\n`\"enabled\": false` で自動圧縮を無効にします。 `/compact` を使用して手動で圧縮することもできます。","sourceFile":"compaction.md"},"containerization":{"title":"コンテナ化","markdown":"Pi はデフォルトですべての権限で実行されますが、場合によっては、Pi が書き込むことができるディレクトリとアクセス権をより詳細に制御したい場合があります。\n\n一般的なオプションは 2 つあります。どちらでもできます\n1. `pi` プロセス全体を隔離された環境内で実行する、または\n2. ホスト上で `pi` を実行し、ツールの実行を隔離された環境にルーティングします。\n\n## パターンを選択してください\n\n| パターン | 何が孤立しているのか | こんな方に最適 | 注意事項 |\n| --- | --- | --- | --- |\n| Gondolin拡張子 | 組み込みツールと`!` コマンド | ホスト上で認証を維持しながらローカルのマイクロ VM を分離 | [`examples/extensions/gondolin/`](../examples/extensions/gondolin/)を参照してください。 |\n| プレーン Docker | ローカルコンテナ内の`pi`プロセス全体 | シンプルなローカル分離 | プロバイダー API key がコンテナに入ります。 |\n| OpenShell | ポリシー制御された sandbox 内の `pi` プロセス全体 | ローカルまたはリモート管理 sandbox | OpenShell ゲートウェイが必要です |\n\nExtensions は、`pi` プロセスが実行される場所であればどこでも実行されます。ツール ルーティング拡張機能を使用してホスト `pi` を実行する場合、他のカスタム拡張ツールも操作を委任しない限り、ホスト上で引き続き実行されます。\n\n## Gondolin\n\n[Gondolin](https://github.com/earendil-works/gondolin) はローカル Linux マイクロ VM です。\nホスト上で `pi` を使用するが、すべての組み込みツールを VM にルーティングする場合は、[example extension](../examples/extensions/gondolin) を使用します。\n\n設定：\n\n```bash\ncp -R packages/coding-agent/examples/extensions/gondolin ~/.pi/agent/extensions/gondolin\ncd ~/.pi/agent/extensions/gondolin\nnpm install --ignore-scripts\n```\n\nマウントしたいプロジェクトから実行します。\n\n```bash\ncd /path/to/project\npi -e ~/.pi/agent/extensions/gondolin\n```\n\nこの拡張機能は、VM の `/workspace` にホスト cwd をマウントし、`read`、`write`、`edit`、`bash`、`grep`、`find`、`ls` をオーバーライドします。\nユーザー `!` コマンドも VM にルーティングされます。\n`/workspace` でのファイル変更はホストにライトスルーされます。\n\n要件: Node.js >= 23.6.0 (`@earendil-works/gondolin`)、および QEMU (パッケージ マネージャーを介してインストールする必要があります)。\n\n## プレーン Docker\n\n最も単純なローカル コンテナ境界が必要な場合は、Docker で `pi` プロセス全体を実行します。\n\n`Dockerfile.pi`:\n\n```dockerfile\nFROM node:24-bookworm-slim\n\nRUN apt-get update \\\n  && apt-get install -y --no-install-recommends bash ca-certificates git ripgrep \\\n  && rm -rf /var/lib/apt/lists/*\nRUN npm install -g --ignore-scripts @earendil-works/pi-coding-agent\n\nWORKDIR /workspace\nENTRYPOINT [\"pi\"]\n```\n\nビルドして実行します。\n\n```bash\ndocker build -t pi-sandbox -f Dockerfile.pi .\n\ndocker run --rm -it \\\n  -e ANTHROPIC_API_KEY \\\n  -v \"$PWD:/workspace\" \\\n  -v pi-agent-home:/root/.pi/agent \\\n  pi-sandbox\n```\n\n`-v \"$PWD:/workspace\"` は、現在のディレクトリを /workspace のコンテナにマウントします。これにより、Gondolin の例のように、Docker 内の `/workspace` での読み取りと書き込みがホスト ファイルに直接影響します。\n\nコンテナローカルの設定とセッションが必要な場合は、`/root/.pi/agent` に名前付きボリュームを使用します。ホスト `~/.pi/agent` をマウントすると、ホスト認証ファイルとセッション ファイルがコンテナに公開されます。\n\n## OpenShell\n\nファイルシステム、プロセス、ネットワーク、資格情報、および推論制御を使用してポリシー制御された sandbox が必要な場合は、[NVIDIA OpenShell](https://docs.nvidia.com/openshell/about/overview) を使用します。\nOpenShell は、Docker、Podman、または VM ランタイムによってサポートされるローカル ゲートウェイ経由、またはリモート Kubernetes ゲートウェイ経由で sandboxes を実行できます。\n\nすべての sandbox にはアクティブなゲートウェイが必要です。\nsandbox を作成する前に、登録して選択してください:\n\n```bash\nopenshell gateway add <gateway-url> --name <name>\nopenshell gateway select <name>\n```\n\nOpenShell sandbox 内で `pi` を起動します。\n\n```bash\nopenshell sandbox create --name pi-sandbox --from pi -- pi\n```\n\nこのパターンでは、`pi` プロセス全体が sandbox 内で実行されます。\n組み込みツール、`!` コマンド、および拡張ツールは、OpenShell 境界内で実行されます。\n\nゲートウェイがリモートの場合、プロジェクト ファイルはホストからバインド マウントされません。つまり、sandbox への書き込みはマシンに反映されません。\nsandbox 内にリポジトリのクローンを作成するか、OpenShell ファイル転送コマンドを使用します。\n\n```bash\nopenshell sandbox upload pi-sandbox ./repo /workspace\nopenshell sandbox download pi-sandbox /workspace/repo ./repo-out\n```\n\nOpenShell プロバイダーは、生のモデル API key を sandbox の外側に保持できます。\n推論ルーティングが構成されている場合、sandbox 内のコードは `https://inference.local` を呼び出すことができ、ゲートウェイは構成されたプロバイダーの資格情報を上流に挿入します。\nモデルのトラフィックにこのルートを使用させる場合は、対応する OpenAI 互換または Anthropic 互換のエンドポイントを使用するように Pi を構成します。","sourceFile":"containerization.md"},"custom-provider":{"title":"カスタムProviders","markdown":"Extensions は、`pi.registerProvider()` を介してカスタム モデル プロバイダーを登録できます。これにより、次のことが可能になります。\n\n- **プロキシ** - 企業プロキシまたは API ゲートウェイ経由でリクエストをルーティングします。\n- **カスタム エンドポイント** - セルフホスト型またはプライベート モデルのデプロイメントを使用します。\n- **OAuth/SSO** - エンタープライズプロバイダーの認証フローを追加します\n- **カスタム APIs** - 非標準 LLM APIs のストリーミングを実装します。\n\n## 例 Extensions\n\nこれらの完全なプロバイダーの例を参照してください。\n\n- [`examples/extensions/custom-provider-anthropic/`](../examples/extensions/custom-provider-anthropic/)\n- [`examples/extensions/custom-provider-gitlab-duo/`](../examples/extensions/custom-provider-gitlab-duo/)\n\n## 目次\n\n- [Example Extensions](#example-extensions)\n- [Quick Reference](#quick-reference)\n- [Override Existing Provider](#override-existing-provider)\n- [Register New Provider](#register-new-provider)\n- [Unregister Provider](#unregister-provider)\n- [OAuth Support](#oauth-support)\n- [Custom Streaming API](#custom-streaming-api)\n- [Context Overflow Errors](#context-overflow-errors)\n- [Testing Your Implementation](#testing-your-implementation)\n- [Config Reference](#config-reference)\n- [Model Definition Reference](#model-definition-reference)\n\n## クイックリファレンス\n\nExtensions は、完全な pi-ai `Provider` を登録することも、従来のプロバイダー設定フォームを使用することもできます。カスタム認証、フィルタリング、更新、またはストリーミング動作が必要な場合は、完全なプロバイダーを推奨します。 Pi は、登録されたネイティブ プロバイダーの上にある `models.json` をオーバーライドします。\n\n```typescript\nimport { createProvider, openAICompletionsApi } from \"@earendil-works/pi-ai\";\nimport type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\n\nexport default function (pi: ExtensionAPI) {\n  pi.registerProvider(createProvider({\n    id: \"native-local\",\n    name: \"Native Local\",\n    baseUrl: \"http://localhost:8080/v1\",\n    auth: {\n      apiKey: {\n        name: \"Local server API key\",\n        async login(interaction) {\n          return {\n            type: \"api_key\",\n            key: await interaction.prompt({ type: \"secret\", message: \"API key\" })\n          };\n        },\n        async resolve({ credential }) {\n          return credential?.key\n            ? { auth: { apiKey: credential.key }, source: \"stored API key\" }\n            : undefined;\n        }\n      }\n    },\n    models: [],\n    api: openAICompletionsApi()\n  }));\n\n  // Legacy provider-config form:\n  // Override baseUrl for existing provider\n  pi.registerProvider(\"anthropic\", {\n    baseUrl: \"https://proxy.example.com\"\n  });\n\n  // Register new provider with models\n  pi.registerProvider(\"my-provider\", {\n    name: \"My Provider\",\n    baseUrl: \"https://api.example.com\",\n    apiKey: \"$MY_API_KEY\",\n    api: \"openai-completions\",\n    models: [\n      {\n        id: \"my-model\",\n        name: \"My Model\",\n        reasoning: false,\n        input: [\"text\", \"image\"],\n        cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n        contextWindow: 128000,\n        maxTokens: 4096\n      }\n    ]\n  });\n}\n```\n\n拡張ファクトリーは `async` にすることもできます。動的なモデル検出の場合、`session_start` ではなくファクトリでモデルをフェッチして登録します。 pi は起動を続行する前にファクトリーを待機するため、プロバイダーは対話型起動中および `pi --list-models` まで使用できます。\n\n## 既存のプロバイダーを上書きする\n\n最も単純な使用例: プロキシ経由で既存のプロバイダーをリダイレクトします。\n\n```typescript\n// All Anthropic requests now go through your proxy\npi.registerProvider(\"anthropic\", {\n  baseUrl: \"https://proxy.example.com\"\n});\n\n// Add custom headers to OpenAI requests\npi.registerProvider(\"openai\", {\n  headers: {\n    \"X-Custom-Header\": \"value\"\n  }\n});\n\n// Both baseUrl and headers\npi.registerProvider(\"google\", {\n  baseUrl: \"https://ai-gateway.corp.com/google\",\n  headers: {\n    \"X-Corp-Auth\": \"$CORP_AUTH_TOKEN\"  // env var or literal\n  }\n});\n```\n\n`baseUrl` および/または `headers` のみが提供される場合 (`models` は提供されない)、そのプロバイダーの既存のモデルはすべて新しいエンドポイントで保持されます。\n\n## 新しいプロバイダーを登録する\n\n完全に新しいプロバイダーを追加するには、必要な構成とともに `models` を指定します。\n\nモデル リストがリモート エンドポイントから取得される場合は、非同期拡張ファクトリを使用します。\n\n```typescript\nimport type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\n\nexport default async function (pi: ExtensionAPI) {\n  const response = await fetch(\"http://localhost:1234/v1/models\");\n  const payload = (await response.json()) as {\n    data: Array<{\n      id: string;\n      name?: string;\n      context_window?: number;\n      max_tokens?: number;\n    }>;\n  };\n\n  pi.registerProvider(\"local-openai\", {\n    baseUrl: \"http://localhost:1234/v1\",\n    apiKey: \"$LOCAL_OPENAI_API_KEY\",\n    api: \"openai-completions\",\n    models: payload.data.map((model) => ({\n      id: model.id,\n      name: model.name ?? model.id,\n      reasoning: false,\n      input: [\"text\"],\n      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n      contextWindow: model.context_window ?? 128000,\n      maxTokens: model.max_tokens ?? 4096,\n    })),\n  });\n}\n```\n\nこれにより、起動が完了する前に、取得したモデルが登録されます。\n\n```typescript\npi.registerProvider(\"my-llm\", {\n  baseUrl: \"https://api.my-llm.com/v1\",\n  apiKey: \"$MY_LLM_API_KEY\",  // env var reference\n  api: \"openai-completions\",  // which streaming API to use\n  models: [\n    {\n      id: \"my-llm-large\",\n      name: \"My LLM Large\",\n      reasoning: true,        // supports extended thinking\n      input: [\"text\", \"image\"],\n      cost: {\n        input: 3.0,           // $/million tokens\n        output: 15.0,\n        cacheRead: 0.3,\n        cacheWrite: 3.75\n      },\n      contextWindow: 200000,\n      maxTokens: 16384\n    }\n  ]\n});\n```\n\n`models` が提供されると、そのプロバイダーの既存のモデルがすべて**置き換え**されます。\n\n`apiKey` およびカスタム ヘッダー値は、`models.json` と同じ構成値構文を使用します。 `!command` は、最初に値全体のコマンドを実行し、`$ENV_VAR` と `${ENV_VAR}` は環境変数を補間し、`$` はリテラル ``apiKey` およびカスタム ヘッダー値は、`models.json` と同じ構成値構文を使用します。 `!command` は、最初に値全体のコマンドを実行し、`$ENV_VAR` と `${ENV_VAR}` は環境変数を補間し、`$` はリテラル  を出力し、`$!` はリテラルを出力します`!`。\n\n## プロバイダーの登録を解除する\n\n以前に `pi.registerProvider(name,...)` で登録されたプロバイダーを削除するには、`pi.unregisterProvider(name)` を使用します。\n\n```typescript\n// Register\npi.registerProvider(\"my-llm\", {\n  baseUrl: \"https://api.my-llm.com/v1\",\n  apiKey: \"$MY_LLM_API_KEY\",\n  api: \"openai-completions\",\n  models: [\n    {\n      id: \"my-llm-large\",\n      name: \"My LLM Large\",\n      reasoning: true,\n      input: [\"text\", \"image\"],\n      cost: { input: 3.0, output: 15.0, cacheRead: 0.3, cacheWrite: 3.75 },\n      contextWindow: 200000,\n      maxTokens: 16384\n    }\n  ]\n});\n\n// Later, remove it\npi.unregisterProvider(\"my-llm\");\n```\n\n登録を解除すると、そのプロバイダーの動的モデル、API key フォールバック、OAuth プロバイダー登録、およびカスタム ストリーム ハンドラー登録が削除されます。オーバーライドされた組み込みモデルまたはプロバイダーの動作は復元されます。\n\n最初の拡張ロード フェーズの後に行われた呼び出しはすぐに適用されるため、`/reload` は必要ありません。\n\n### API 種類\n\n`api` フィールドは、どのストリーミング実装が使用されるかを決定します。\n\n| API | 用途 |\n|-----|---------|\n| `anthropic-messages` | Anthropic Claude API とその互換品 |\n| `openai-completions` | OpenAI Chat Completions API と互換性 |\n| `openai-responses` | OpenAI の応答 API |\n| `azure-openai-responses` | Azure OpenAI の応答 API |\n| `openai-codex-responses` | OpenAI コーデックスの応答 API |\n| `mistral-conversations` | ネイティブ ミストラル チャット完了ストリーミング |\n| `google-generative-ai` | Google ジェネレーティブ AI API |\n| `google-vertex` | Google Vertex AI API |\n| `bedrock-converse-stream` | アマゾン ベッドロック コンバース API |\n\nほとんどの OpenAI 互換プロバイダーは `openai-completions` で動作します。モデル固有の思考レベルにはモデルレベルの `thinkingLevelMap` を使用し、プロバイダーの癖には `compat` を使用します。 `xhigh` および `max` レベルはオプトインであり、null 以外のマップ エントリが必要で、サポートされていないホールによって分離される場合があります。\n\n```typescript\nmodels: [{\n  id: \"custom-model\",\n  // ...\n  reasoning: true,\n  thinkingLevelMap: {              // map pi levels to provider values; null hides unsupported levels\n    minimal: null,\n    low: null,\n    medium: null,\n    high: \"default\",\n    xhigh: null,\n    max: \"max\"\n  },\n  compat: {\n    supportsDeveloperRole: false,   // use \"system\" instead of \"developer\"\n    supportsReasoningEffort: true,\n    maxTokensField: \"max_tokens\",   // instead of \"max_completion_tokens\"\n    requiresToolResultName: true,   // tool results need name field\n    thinkingFormat: \"qwen\",        // top-level enable_thinking: true\n    cacheControlFormat: \"anthropic\" // Anthropic-style cache_control markers\n  }\n}]\n```\n\nOpenRouter スタイルの `reasoning: { effort }` コントロールには `openrouter` を使用します。 Together スタイルの `reasoning: { enabled }` コントロールには `together` を使用します。 `supportsReasoningEffort` を使用すると、`reasoning_effort` も送信されます。 `chat_template_kwargs.enable_thinking` を読み取り、`preserve_thinking` を必要とするローカルの Qwen 互換サーバーには、`qwen-chat-template` を使用します。\nシステム プロンプト、最後のツール定義、および最後のユーザー、アシスタント、またはツール結果のテキスト コンテンツで、`cache_control` を介して Anthropic スタイルのプロンプト キャッシュを公開する OpenAI 互換プロバイダーには、`cacheControlFormat: \"anthropic\"` を使用します。\n\n`api: \"anthropic-messages\"` を使用する Anthropic 互換プロバイダーの場合、アップストリーム モデルが適応的思考 (`thinking.type: \"adaptive\"` プラス `output_config.effort`) を必要とするモデルまたはプロバイダーに `compat.forceAdaptiveThinking: true` を設定します。組み込みの適応クロード モデルは、これを自動的に設定します。空の思考シグネチャを発行し、リプレイ時に `signature: \"\"` を期待するプロバイダーにのみ `compat.allowEmptySignature: true` を設定します。\n\n> 移行メモ: ミストラルは `openai-completions` から `mistral-conversations` に移行しました。\n> ネイティブの Mistral モデルには `mistral-conversations` を使用します。\n> Mistral 互換/カスタム エンドポイントを意図的に `openai-completions` 経由でルーティングする場合は、必要に応じて `compat` フラグを明示的に設定します。\n\n### 認証ヘッダー\n\nプロバイダーが `Authorization: Bearer <key>` を予期しているが、標準の API を使用していない場合は、`authHeader: true` を設定します。\n\n```typescript\npi.registerProvider(\"custom-api\", {\n  baseUrl: \"https://api.example.com\",\n  apiKey: \"$MY_API_KEY\",\n  authHeader: true,  // adds Authorization: Bearer header\n  api: \"openai-completions\",\n  models: [...]\n});\n```\n\nキーはリクエストごとに解決されます。明示的なリクエスト `Authorization` ヘッダーは、生成された値よりも優先されます。\n\n## OAuth サポート\n\n`/login` と統合する OAuth/SSO 認証を追加します。\n\n```typescript\nimport type { OAuthCredentials, OAuthLoginCallbacks } from \"@earendil-works/pi-ai\";\n\npi.registerProvider(\"corporate-ai\", {\n  baseUrl: \"https://ai.corp.com/v1\",\n  api: \"openai-responses\",\n  models: [...],\n  oauth: {\n    name: \"Corporate AI (SSO)\",\n\n    async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n      const method = await callbacks.onSelect({\n        message: \"Select login method:\",\n        options: [\n          { id: \"browser\", label: \"Browser OAuth\" },\n          { id: \"device\", label: \"Device code\" }\n        ]\n      });\n      if (!method) throw new Error(\"Login cancelled\");\n\n      let code: string;\n      if (method === \"device\") {\n        callbacks.onDeviceCode({\n          userCode: \"ABCD-1234\",\n          verificationUri: \"https://sso.corp.com/device\",\n          intervalSeconds: 5,\n          expiresInSeconds: 900\n        });\n        code = await pollDeviceCodeUntilComplete();\n      } else {\n        callbacks.onAuth({ url: \"https://sso.corp.com/authorize?...\" });\n        code = await callbacks.onPrompt({ message: \"Enter SSO code:\" });\n      }\n\n      // Exchange for tokens (your implementation)\n      const tokens = await exchangeCodeForTokens(code);\n\n      return {\n        refresh: tokens.refreshToken,\n        access: tokens.accessToken,\n        expires: Date.now() + tokens.expiresIn * 1000\n      };\n    },\n\n    async refreshToken(credentials: OAuthCredentials, signal: AbortSignal): Promise<OAuthCredentials> {\n      const tokens = await refreshAccessToken(credentials.refresh, signal);\n      return {\n        refresh: tokens.refreshToken ?? credentials.refresh,\n        access: tokens.accessToken,\n        expires: Date.now() + tokens.expiresIn * 1000\n      };\n    },\n\n    getApiKey(credentials: OAuthCredentials): string {\n      return credentials.access;\n    }\n  }\n});\n```\n\n登録後、ユーザーは`/login corporate-ai` 経由で認証できます。\n\n### OAuthログインコールバック\n\n`callbacks` オブジェクトは、プロバイダー所有のフローに対して UI に依存しない対話を提供します。\n\n```typescript\ninterface OAuthLoginCallbacks {\n  // Open URL in browser (for OAuth redirects)\n  onAuth(params: { url: string }): void;\n\n  // Show device code (for device authorization flow)\n  onDeviceCode(params: {\n    userCode: string;\n    verificationUri: string;\n    intervalSeconds?: number;\n    expiresInSeconds?: number;\n  }): void;\n\n  // Show transient progress\n  onProgress?(message: string): void;\n\n  // Prompt user for input (for manual token entry)\n  onPrompt(params: { message: string }): Promise<string>;\n\n  // Show an interactive selector, e.g. to choose browser OAuth vs device code\n  onSelect(params: {\n    message: string;\n    options: { id: string; label: string }[];\n  }): Promise<string | undefined>;\n}\n```\n\n### OAuth認証情報\n\n資格情報は `~/.pi/agent/auth.json` に保持されます。\n\n```typescript\ninterface OAuthCredentials {\n  refresh: string;   // Refresh token (for refreshToken())\n  access: string;    // Access token (returned by getApiKey())\n  expires: number;   // Expiration timestamp in milliseconds\n}\n```\n\n## カスタムストリーミング API\n\n非標準の API を使用するプロバイダーの場合は、 `streamSimple` を実装します。独自のプロバイダー実装を作成する前に、既存のプロバイダー実装を調べてください。\n\n**参考実装:**\n- [anthropic.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/anthropic.ts) - 人間的なメッセージ API\n- [mistral.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/mistral.ts) - ミストラルの会話 API\n- [openai-completions.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/openai-completions.ts) - OpenAI チャットの完了\n- [openai-responses.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/openai-responses.ts) - OpenAI の応答 API\n- [google.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/google.ts) - Google ジェネレーティブ AI\n- [amazon-bedrock.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/amazon-bedrock.ts) - AWS ベッドロック\n\n### ストリームパターン\n\nすべてのプロバイダーは同じパターンに従います。\n\n```typescript\nimport {\n  type AssistantMessage,\n  type AssistantMessageEventStream,\n  type Context,\n  type Model,\n  type SimpleStreamOptions,\n  calculateCost,\n  createAssistantMessageEventStream,\n} from \"@earendil-works/pi-ai\";\n\nfunction streamMyProvider(\n  model: Model<any>,\n  context: Context,\n  options?: SimpleStreamOptions\n): AssistantMessageEventStream {\n  const stream = createAssistantMessageEventStream();\n\n  (async () => {\n    // Initialize output message\n    const output: AssistantMessage = {\n      role: \"assistant\",\n      content: [],\n      api: model.api,\n      provider: model.provider,\n      model: model.id,\n      usage: {\n        input: 0,\n        output: 0,\n        cacheRead: 0,\n        cacheWrite: 0,\n        totalTokens: 0,\n        cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n      },\n      stopReason: \"pending\",\n      timestamp: Date.now(),\n    };\n\n    try {\n      // Push start event\n      stream.push({ type: \"start\", partial: output });\n\n      // Make API request and process response...\n      // Push content events as they arrive and set stopReason from the terminal event.\n      if (output.stopReason === \"pending\") {\n        throw new Error(\"Provider stream ended without a stop reason\");\n      }\n      if (output.stopReason === \"error\" || output.stopReason === \"aborted\") {\n        throw new Error(output.errorMessage || \"An unknown error occurred\");\n      }\n\n      // Push done event\n      stream.push({\n        type: \"done\",\n        reason: output.stopReason,\n        message: output\n      });\n      stream.end();\n    } catch (error) {\n      output.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n      output.errorMessage = error instanceof Error ? error.message : String(error);\n      stream.push({ type: \"error\", reason: output.stopReason, error: output });\n      stream.end();\n    }\n  })();\n\n  return stream;\n}\n```\n\n### イベントの種類\n\n次の順序で `stream.push()` 経由でイベントをプッシュします。\n\n1. `{ type: \"start\", partial: output }` - ストリームが開始されました\n\n2. コンテンツ イベント (繰り返し可能、ブロックごとに `contentIndex` を追跡):\n   - `{ type: \"text_start\", contentIndex, partial }` - テキストブロックが開始されました\n   - `{ type: \"text_delta\", contentIndex, delta, partial }` - テキストチャンク\n   - `{ type: \"text_end\", contentIndex, content, partial }` - テキストブロックが終了しました\n   - `{ type: \"thinking_start\", contentIndex, partial }` - 思考が始まりました\n   - `{ type: \"thinking_delta\", contentIndex, delta, partial }` - 思考の塊\n   - `{ type: \"thinking_end\", contentIndex, content, partial }` - 思考は終了しました\n   - `{ type: \"toolcall_start\", contentIndex, partial }` - ツール呼び出しが開始されました\n   - `{ type: \"toolcall_delta\", contentIndex, delta, partial }` - ツール呼び出し JSON チャンク\n   - `{ type: \"toolcall_end\", contentIndex, toolCall, partial }` - ツール呼び出しが終了しました\n\n3. `{ type: \"done\", reason, message }` または `{ type: \"error\", reason, error }` - ストリームが終了しました\n\n各イベントの `partial` フィールドには、現在の `AssistantMessage` 状態が含まれます。データを受信したら `output.content` を更新し、`output` を `partial` として含めます。\n\n### コンテンツブロック\n\nコンテンツ ブロックが到着したら、`output.content` に追加します。\n\n```typescript\n// Text block\noutput.content.push({ type: \"text\", text: \"\" });\nstream.push({ type: \"text_start\", contentIndex: output.content.length - 1, partial: output });\n\n// As text arrives\nconst block = output.content[contentIndex];\nif (block.type === \"text\") {\n  block.text += delta;\n  stream.push({ type: \"text_delta\", contentIndex, delta, partial: output });\n}\n\n// When block completes\nstream.push({ type: \"text_end\", contentIndex, content: block.text, partial: output });\n```\n\n### ツール呼び出し\n\nツール呼び出しには、JSON の蓄積と解析が必要です。\n\n```typescript\n// Start tool call\noutput.content.push({\n  type: \"toolCall\",\n  id: toolCallId,\n  name: toolName,\n  arguments: {}\n});\nstream.push({ type: \"toolcall_start\", contentIndex: output.content.length - 1, partial: output });\n\n// Accumulate JSON\nlet partialJson = \"\";\npartialJson += jsonDelta;\ntry {\n  block.arguments = JSON.parse(partialJson);\n} catch {}\nstream.push({ type: \"toolcall_delta\", contentIndex, delta: jsonDelta, partial: output });\n\n// Complete\nstream.push({\n  type: \"toolcall_end\",\n  contentIndex,\n  toolCall: { type: \"toolCall\", id, name, arguments: block.arguments },\n  partial: output\n});\n```\n\n### 使用量とコスト\n\nAPI 応答から使用量を更新し、コストを計算します。\n\n```typescript\noutput.usage.input = response.usage.input_tokens;\noutput.usage.output = response.usage.output_tokens;\noutput.usage.cacheRead = response.usage.cache_read_tokens ?? 0;\noutput.usage.cacheWrite = response.usage.cache_write_tokens ?? 0;\noutput.usage.totalTokens = output.usage.input + output.usage.output +\n                           output.usage.cacheRead + output.usage.cacheWrite;\ncalculateCost(model, output.usage);\n```\n\n### コンテキストオーバーフローエラー\n\nリクエストがモデルのコンテキスト ウィンドウを超える場合、pi は会話を圧縮して再試行することで自動的に回復できます。この回復は、pi が障害をオーバーフローとして認識した場合にのみ開始されます。\n\n検出は、最終的なアシスタント メッセージに対して実行されます。\n\n- `stopReason === \"error\"`\n- `errorMessage` は、pi の既知のオーバーフロー パターンの 1 つに一致します ([`packages/ai/src/utils/overflow.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/utils/overflow.ts) を参照)\n\nプロバイダーが pi が認識できないメッセージを含むオーバーフロー エラーを返した場合は、プロバイダーを登録したのと同じ拡張機能からのエラーを正規化します。 `message_end` ハンドラーを使用してアシスタント メッセージを書き換え、その `errorMessage` が pi が認識するフレーズで始まるようにします。一般的なフォールバック `context_length_exceeded` が最も安全な選択です。\n\n```typescript\nconst MY_PROVIDER_OVERFLOW_PATTERN = /your provider's overflow phrase/i;\n\nexport default function (pi: ExtensionAPI) {\n  pi.registerProvider(\"my-provider\", { /* ... */ });\n\n  pi.on(\"message_end\", (event, ctx) => {\n    const message = event.message;\n    if (message.role !== \"assistant\") return;\n    if (message.stopReason !== \"error\") return;\n    if (\n      message.provider !== \"my-provider\" &&\n      ctx.model?.provider !== \"my-provider\"\n    )\n      return;\n\n    const errorMessage = message.errorMessage ?? \"\";\n    if (errorMessage.includes(\"context_length_exceeded\")) return;\n    if (!MY_PROVIDER_OVERFLOW_PATTERN.test(errorMessage)) return;\n\n    return {\n      message: {\n        ...message,\n        errorMessage: `context_length_exceeded: ${errorMessage}`,\n      },\n    };\n  });\n}\n```\n\n`message_end` は、pi が自動圧縮のためにアシスタント メッセージを追跡する前に実行されるため、書き換えられた `errorMessage` が pi のチェック対象になります。これを設定すると、pi は次のようになります。\n\n1. `errorMessage`からのオーバーフローを検出します。\n2. 失敗したアシスタント メッセージをライブ コンテキストから削除します。\n3. 圧縮を実行します。\n4. リクエストを一度再試行してください。\n\nリライトを慎重に保護してください。\n\n- スコープをプロバイダー (`message.provider` および `ctx.model?.provider`) に設定すると、他のプロバイダーからの無関係なエラーは影響を受けません。\n- pi の一般的なオーバーフロー パターンではなく、プロバイダー固有のパターンと一致します。レート制限またはスロットリング エラー (`rate limit`、`too many requests`) を書き換えると、pi の通常のバックオフ付き再試行パスの代わりに、誤って圧縮がトリガーされます。\n- `errorMessage` にすでに `context_length_exceeded` が含まれている場合はスキップします。そのため、ハンドラーは冪等です。\n\n### 登録\n\nストリーム関数を登録します。\n\n```typescript\npi.registerProvider(\"my-provider\", {\n  baseUrl: \"https://api.example.com\",\n  apiKey: \"$MY_API_KEY\",\n  api: \"my-custom-api\",\n  models: [...],\n  streamSimple: streamMyProvider\n});\n```\n\n## 実装のテスト\n\n組み込みプロバイダーで使用されるのと同じテスト スイートに対してプロバイダーをテストします。これらのテスト ファイルを [packages/ai/test/](https://github.com/earendil-works/pi-mono/tree/main/packages/ai/test) からコピーして調整します。\n\n| テスト | 目的 |\n|------|---------|\n| `stream.test.ts` | 基本的なストリーミング、テキスト出力 |\n| `tokens.test.ts` | トークンのカウントと使用法 |\n| `abort.test.ts` | アボート信号処理 |\n| `empty.test.ts` | 空の/最小限の応答 |\n| `context-overflow.test.ts` | コンテキストウィンドウの制限 |\n| `image-limits.test.ts` | 画像入力処理 |\n| `unicode-surrogate.test.ts` | Unicode のエッジケース |\n| `tool-call-without-result.test.ts` | ツール呼び出しのエッジケース |\n| `image-tool-result.test.ts` | ツール結果内の画像 |\n| `total-tokens.test.ts` | 合計トークンの計算 |\n| `cross-provider-handoff.test.ts` | プロバイダー間のコンテキストのハンドオフ |\n\nプロバイダーとモデルのペアでテストを実行し、互換性を確認します。\n\n## 構成リファレンス\n\n```typescript\ninterface ProviderConfig {\n  /** Display name for the provider in UI such as /login. */\n  name?: string;\n\n  /** API endpoint URL. Required when defining models. */\n  baseUrl?: string;\n\n  /** API key literal, env interpolation ($ENV_VAR or ${ENV_VAR}), or !command. Required when defining models (unless oauth). */\n  apiKey?: string;\n\n  /** API type for streaming. Required at provider or model level when defining models. */\n  api?: Api;\n\n  /** Custom streaming implementation for non-standard APIs. */\n  streamSimple?: (\n    model: Model<Api>,\n    context: Context,\n    options?: SimpleStreamOptions\n  ) => AssistantMessageEventStream;\n\n  /** Custom headers to include in requests. Values use the same resolution syntax as apiKey. */\n  headers?: Record<string, string>;\n\n  /** If true, adds Authorization: Bearer header with the resolved API key. */\n  authHeader?: boolean;\n\n  /** Models to register. If provided, replaces all existing models for this provider. */\n  models?: ProviderModelConfig[];\n\n  /** OAuth provider for /login support. */\n  oauth?: {\n    name: string;\n    login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;\n    refreshToken(credentials: OAuthCredentials, signal: AbortSignal): Promise<OAuthCredentials>;\n    getApiKey(credentials: OAuthCredentials): string;\n  };\n}\n```\n\n## モデル定義リファレンス\n\n```typescript\ninterface ProviderModelConfig {\n  /** Model ID (e.g., \"claude-sonnet-4-20250514\"). */\n  id: string;\n\n  /** Display name (e.g., \"Claude 4 Sonnet\"). */\n  name: string;\n\n  /** API type override for this specific model. */\n  api?: Api;\n\n  /** API endpoint URL override for this specific model. */\n  baseUrl?: string;\n\n  /** Whether the model supports extended thinking. */\n  reasoning: boolean;\n\n  /** Maps pi thinking levels to provider/model-specific values; null marks a level unsupported. */\n  thinkingLevelMap?: Partial<Record<\"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\", string | null>>;\n\n  /** Supported input types. */\n  input: (\"text\" | \"image\")[];\n\n  /** Cost per million tokens (for usage tracking). */\n  cost: {\n    input: number;\n    output: number;\n    cacheRead: number;\n    cacheWrite: number;\n  };\n\n  /** Maximum context window size in tokens. */\n  contextWindow: number;\n\n  /** Maximum output tokens. */\n  maxTokens: number;\n\n  /** Custom headers for this specific model. */\n  headers?: Record<string, string>;\n\n  /** Compatibility settings for the selected API. */\n  compat?: {\n    // openai-completions\n    supportsStore?: boolean;\n    supportsDeveloperRole?: boolean;\n    supportsReasoningEffort?: boolean;\n    supportsUsageInStreaming?: boolean;\n    supportsFinishReason?: boolean;\n    supportsStrictMode?: boolean;\n    supportsOpenAIGrammarTools?: boolean; // openai-completions/openai-responses; false falls back to normal function tools\n    maxTokensField?: \"max_completion_tokens\" | \"max_tokens\";\n    requiresToolResultName?: boolean;\n    requiresAssistantAfterToolResult?: boolean;\n    requiresThinkingAsText?: boolean;\n    requiresReasoningContentOnAssistantMessages?: boolean;\n    thinkingFormat?: \"openai\" | \"openrouter\" | \"deepseek\" | \"together\" | \"baseten\" | \"zai\" | \"qwen\" | \"chat-template\" | \"qwen-chat-template\" | \"string-thinking\" | \"ant-ling\";\n    chatTemplateKwargs?: Record<string, string | number | boolean | null | { \"$var\": \"thinking.enabled\" | \"thinking.effort\"; omitWhenOff?: boolean }>;\n    chatTemplateArgs?: Record<string, string | number | boolean | null | { \"$var\": \"thinking.enabled\" | \"thinking.effort\"; omitWhenOff?: boolean }>;\n    cacheControlFormat?: \"anthropic\";\n    sessionAffinityFormat?: \"openai\" | \"openai-nosession\" | \"openrouter\";\n    sendSessionAffinityHeaders?: boolean;\n\n    // anthropic-messages\n    supportsEagerToolInputStreaming?: boolean;\n    supportsLongCacheRetention?: boolean;\n    sendSessionAffinityHeaders?: boolean;\n    supportsCacheControlOnTools?: boolean;\n    forceAdaptiveThinking?: boolean;\n    allowEmptySignature?: boolean;\n    supportsStrictTools?: boolean;\n  };\n}\n```\n\n`openrouter` は `reasoning: { effort }` を送信します。 `deepseek` が有効になっている場合、`thinking: { type: \"enabled\" | \"disabled\" }` と `reasoning_effort` を送信します。 `together` は `reasoning: { enabled }` を送信し、`supportsReasoningEffort` が有効な場合は `reasoning_effort` も送信します。 `qwen` は、DashScope スタイルのトップレベル `enable_thinking` 用です。 `chat_template_kwargs.enable_thinking` を読み取り、`preserve_thinking` を必要とするローカルの Qwen 互換サーバーには、`qwen-chat-template` を使用します。構成可能な `chat_template_kwargs` には `chat-template` を使用します。たとえば、`chatTemplateKwargs: { \"thinking\": { \"$var\": \"thinking.enabled\" } }` の vLLM の背後にある DeepSeek V3.x です。プロバイダーが `chat_template_args` 未満のトグル値を予期し、オプションでトップレベルの `reasoning_effort` をサポートする場合は、`thinkingFormat: \"baseten\"` を `chatTemplateArgs` とともに使用します。\n`cacheControlFormat: \"anthropic\"` は、Anthropic スタイルの `cache_control` マーカーをシステム プロンプト、最後のツール定義、および最後のユーザー、アシスタント、またはツール結果のテキスト コンテンツに適用します。","sourceFile":"custom-provider.md"},"development":{"title":"発達","markdown":"追加のガイドラインについては、[AGENTS.md](https://github.com/earendil-works/pi-mono/blob/main/AGENTS.md) を参照してください。\n\n## 設定\n\n```bash\ngit clone https://github.com/earendil-works/pi-mono\ncd pi-mono\nnpm install\nnpm run build\n```\n\nソースから実行:\n\n```bash\n/path/to/pi-mono/pi-test.sh\n```\n\nスクリプトは任意のディレクトリから実行できます。 Pi は、呼び出し元の現在の作業ディレクトリを保持します。\n\n## フォーク/リブランディング\n\n`package.json` 経由で設定します:\n\n```json\n{\n  \"piConfig\": {\n    \"name\": \"pi\",\n    \"configDir\": \".pi\"\n  }\n}\n```\n\nフォークの `name`、`configDir`、および `bin` フィールドを変更します。 CLI バナー、設定パス、環境変数名に影響します。\n\n## パスの解決\n\n3 つの実行モード: npm インストール、スタンドアロン バイナリ、ソースからの tsx。\n\n**パッケージ アセットには常に `src/config.ts`** を使用します。\n\n```typescript\nimport { getPackageDir, getThemeDir } from \"./config.js\";\n```\n\n`__dirname` をパッケージ アセットに直接使用しないでください。\n\n## デバッグコマンド\n\n`/debug` (非表示) は `~/.pi/agent/pi-debug.log` に書き込みます:\n- ANSI コードを使用してレンダリングされた TUI 行\n- LLM に送信された最後のメッセージ\n\n## テスト\n\n```bash\n./test.sh                         # Run non-LLM tests (no API keys needed)\nnpm test                          # Run all tests\nnpm test -- test/specific.test.ts # Run specific test\n```\n\n## プロジェクトの構造\n\n```\npackages/\n  ai/           # LLM provider abstraction\n  agent/        # Agent loop and message types  \n  tui/          # Terminal UI components\n  coding-agent/ # CLI and interactive mode\n```","sourceFile":"development.md"},"environment-variables":{"title":"環境変数","markdown":"Pi は環境変数を 3 つの方法で使用します。\n\n- `PI_OFFLINE` などの変数は、Pi プロセスを構成します。\n- Pi は `PI_CODING_AGENT` を設定し、子プロセスが Pi 内で実行されていることを検出できるようにします。\n- LLM 呼び出し可能な bash ツールによって実行されるコマンドは、現在のセッションを記述する `PI_*` 変数を受け取ります。\n\nプロバイダー API キー変数は、[Providers](providers.md#environment-variables-or-auth-file) に個別に文書化されています。\n\n## プロセスマーカー\n\nCLI と RPC のエントリーポイントは `PI_CODING_AGENT=true` を設定します。子プロセスはそれを継承し、それを使用して、Pi 内で実行されていることを検出できます。これはセッション固有ではなく、Pi が SDK を通じて埋め込まれたときに自動的に設定されません。\n\n## Bash ツールのセッション環境\n\nbash ツールによって実行されるコマンドは、現在の Pi セッション状態を受け取ります。\n\n| 変数 | 説明 |\n|----------|-------------|\n| `PI_SESSION_ID` | 現在のセッションID |\n| `PI_SESSION_FILE` | 現在のセッション JSONL ファイルへの絶対パス。一時的なセッションの設定を解除する |\n| `PI_PROVIDER` | 現在選択されているモデルプロバイダー |\n| `PI_MODEL` | 現在選択されているモデルID |\n| `PI_REASONING_LEVEL` | 現在有効な推論レベル: `off`、`minimal`、`low`、`medium`、`high`、`xhigh`、または `max` |\n\n値は各コマンドの開始時に解決されます。したがって、モデルを切り替えたり、推論レベルを変更すると、Pi を再起動しなくても、次の bash コマンドに影響します。 `PI_PROVIDER` と `PI_MODEL` は、ルーターが内部で選択する可能性のある別のアップストリーム モデルではなく、選択された Pi モデルを識別します。\n\nどのモデルまたはプロバイダーが実行されているかを尋ねられた場合は、システム プロンプトから答えを推測するのではなく、次の変数を調べてください。\n\n```bash\nprintf '%s/%s\\n' \"$PI_PROVIDER\" \"$PI_MODEL\"\nprintf 'reasoning=%s session=%s\\n' \"$PI_REASONING_LEVEL\" \"$PI_SESSION_ID\"\n```\n\nセッションが永続的であれば、セッション ファイルを直接検査できます。\n\n```bash\nif [ -n \"$PI_SESSION_FILE\" ]; then\n  tail -n 1 \"$PI_SESSION_FILE\"\nfi\n```\n\nこれらの変数は、LLM 呼び出し可能な bash ツールに挿入されます。これらは、ユーザーが入力した `!` または `!!` コマンドには挿入されません。\n\n### カスタム Bash ツール\n\n`createBashTool()` で作成された Bash ツールは、Pi で登録されるとデフォルトでセッション環境を公開します。注入は `spawnHook` の前に行われるため、フックは `ctx.env` で変数を受け取ります。\n\n```typescript\nconst bashTool = createBashTool(cwd, {\n  spawnHook: (ctx) => ({\n    ...ctx,\n    env: { ...ctx.env, CI: \"1\" },\n  }),\n});\n```\n\nスポーンフックとは別にセッションメタデータを無効にします。\n\n```typescript\nconst bashTool = createBashTool(cwd, {\n  exposeSessionEnvironment: false,\n  spawnHook: (ctx) => ctx,\n});\n```\n\n無効にすると、Pi はこれらの変数の継承値を削除するため、ネストされた Pi プロセスは古い親セッションのメタデータを公開しません。\n\n## Pi プロセス構成\n\nこれらの変数は Pi 自体によって読み取られます。\n\n| 変数 | 説明 |\n|----------|-------------|\n| `PI_CODING_AGENT_DIR` | config ディレクトリをオーバーライドします。デフォルトは `~/.pi/agent` です |\n| `PI_CODING_AGENT_SESSION_DIR` | セッションストレージをオーバーライドします。 `--session-dir` によって上書きされます |\n| `PI_PACKAGE_DIR` | パッケージ ディレクトリをオーバーライドします。Nix/Guix ストア パスに便利です。 |\n| `PI_OFFLINE` | 更新チェック、パッケージ更新、テレメトリのインストール/更新などの起動ネットワーク操作を無効にします。 |\n| `PI_SKIP_VERSION_CHECK` | `pi.dev` 最新バージョンのリクエストを無効にする |\n| `PI_TELEMETRY` | インストール/更新テレメトリおよびプロバイダー属性ヘッダーをオーバーライドします: `1`/`true`/`yes` または `0`/`false`/`no` |\n| `PI_CACHE_RETENTION` | 拡張プロバイダー プロンプト キャッシュがサポートされている場合は、`long` に設定します。 |\n| `PI_SHARE_VIEWER_URL` | `/share` で使用されるベース URL をオーバーライドします |\n| `PI_HARDWARE_CURSOR` | ハードウェア カーソルを表示するには、`1` に設定します。 [Terminal setup](terminal-setup.md)を参照 |\n| `VISUAL`, `EDITOR` | `externalEditor` が設定されていない場合の外部エディターのフォールバック |\n| `HTTP_PROXY`, `HTTPS_PROXY` | アウトバウンド HTTP リクエストをプロキシする |\n\n`ANTHROPIC_API_KEY`、`OPENAI_API_KEY` などのプロバイダーの認証情報、およびクラウドプロバイダーの構成は、[Providers](providers.md#environment-variables-or-auth-file) にリストされます。","sourceFile":"environment-variables.md"},"extensions":{"title":"Extensions","markdown":"> pi は拡張機能を作成できます。あなたのユースケースに合わせて構築するよう依頼してください。\n\n\nExtensions は、pi の動作を拡張する TypeScript モジュールです。ライフサイクル イベントのサブスクライブ、LLM によって呼び出し可能なカスタム ツールの登録、コマンドの追加などを行うことができます。\n\n> **/reload の配置:** 自動検出のために拡張機能を `~/.pi/agent/extensions/` (グローバル) または `.pi/extensions/` (プロジェクトローカル) に配置します。 `pi -e./path.ts` は簡単なテストにのみ使用してください。自動検出された場所の Extensions は、`/reload` でホットリロードできます。\n\n**主な機能:**\n- **カスタム ツール** - LLM が `pi.registerTool()` 経由で呼び出すことができるツールを登録します。\n- **イベント インターセプト** - ツール呼び出しのブロックまたは変更、コンテキストの挿入、圧縮のカスタマイズ\n- **ユーザー インタラクション** - `ctx.ui` 経由でユーザーにプロンプ​​トを表示します (選択、確認、入力、通知)\n- **カスタム UI コンポーネント** - `ctx.ui.custom()` を介したキーボード入力を備えた完全な TUI コンポーネントにより、複雑な操作が可能\n- **カスタム コマンド** - `pi.registerCommand()` 経由で `/mycommand` のようなコマンドを登録します\n- **セッションの永続性** - 存続するストア状態は `pi.appendEntry()` 経由で再起動されます\n- **カスタム レンダリング** - ツールの呼び出し/結果およびメッセージが TUI にどのように表示されるかを制御します\n\n**使用例の例:**\n- 許可ゲート（`rm -rf`、`sudo`などの前に確認してください）\n- Git チェックポイント設定 (各ターンに隠し、分岐時に復元)\n- パス保護 (`.env`、`node_modules/` への書き込みをブロック)\n- カスタム圧縮 (会話を自分の方法で要約)\n- 会話の要約 (`summarize.ts` の例を参照)\n- インタラクティブなツール (質問、ウィザード、カスタム ダイアログ)\n- ステートフル ツール (ToDo リスト、接続プール)\n- 外部統合 (ファイル ウォッチャー、Webhook、CI トリガー)\n- 待っている間のゲーム (`snake.ts` の例を参照)\n\n実用的な実装については、[examples/extensions/](../examples/extensions/) を参照してください。\n\n## 目次\n\n- [Quick Start](#quick-start)\n- [Extension Locations](#extension-locations)\n- [Available Imports](#available-imports)\n- [Writing an Extension](#writing-an-extension)\n  - [Extension Styles](#extension-styles)\n- [Events](#events)\n  - [Lifecycle Overview](#lifecycle-overview)\n  - [Resource Events](#resource-events)\n  - [Session Events](#session-events)\n  - [Agent Events](#agent-events)\n  - [Model Events](#model-events)\n  - [Tool Events](#tool-events)\n- [ExtensionContext](#extensioncontext)\n- [ExtensionCommandContext](#extensioncommandcontext)\n- [ExtensionAPI Methods](#extensionapi-methods)\n- [State Management](#state-management)\n- [Custom Tools](#custom-tools)\n  - [Dynamic Tool Loading](#dynamic-tool-loading)\n- [Custom UI](#custom-ui)\n- [Error Handling](#error-handling)\n- [Mode Behavior](#mode-behavior)\n- [Examples Reference](#examples-reference)\n\n## クイックスタート\n\n`~/.pi/agent/extensions/my-extension.ts` を作成します:\n\n```typescript\nimport type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { Type } from \"typebox\";\n\nexport default function (pi: ExtensionAPI) {\n  // React to events\n  pi.on(\"session_start\", async (_event, ctx) => {\n    ctx.ui.notify(\"Extension loaded!\", \"info\");\n  });\n\n  pi.on(\"tool_call\", async (event, ctx) => {\n    if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n      const ok = await ctx.ui.confirm(\"Dangerous!\", \"Allow rm -rf?\");\n      if (!ok) return { block: true, reason: \"Blocked by user\" };\n    }\n  });\n\n  // Register a custom tool\n  pi.registerTool({\n    name: \"greet\",\n    label: \"Greet\",\n    description: \"Greet someone by name\",\n    parameters: Type.Object({\n      name: Type.String({ description: \"Name to greet\" }),\n    }),\n    async execute(toolCallId, params, signal, onUpdate, ctx) {\n      return {\n        content: [{ type: \"text\", text: `Hello, ${params.name}!` }],\n        details: {},\n      };\n    },\n  });\n\n  // Register a command\n  pi.registerCommand(\"hello\", {\n    description: \"Say hello\",\n    handler: async (args, ctx) => {\n      ctx.ui.notify(`Hello ${args || \"world\"}!`, \"info\");\n    },\n  });\n}\n```\n\n`--extension` (または `-e`) フラグを使用してテストします。\n\n```bash\npi -e ./my-extension.ts\n```\n\n## 拡張機能の場所\n\n> **セキュリティ:** Extensions は完全なシステム権限で実行され、任意のコードを実行できます。信頼できるソースからのみインストールしてください。\n\nExtensions は信頼できる場所から自動検出されます。プロジェクト ローカル `.pi/extensions` エントリは、プロジェクトが信頼された後にのみロードされます。\n\n| 位置 | 範囲 |\n|----------|-------|\n| `~/.pi/agent/extensions/*.ts` | グローバル (すべてのプロジェクト) |\n| `~/.pi/agent/extensions/*/index.ts` | グローバル (サブディレクトリ) |\n| `.pi/extensions/*.ts` | プロジェクトローカル |\n| `.pi/extensions/*/index.ts` | プロジェクトローカル (サブディレクトリ) |\n\n`settings.json` 経由の追加パス:\n\n```json\n{\n  \"packages\": [\n    \"npm:@foo/bar@1.0.0\",\n    \"git:github.com/user/repo@v1\"\n  ],\n  \"extensions\": [\n    \"/path/to/local/extension.ts\",\n    \"/path/to/local/extension/dir\"\n  ]\n}\n```\n\nnpm または git を介して拡張機能を pi パッケージとして共有するには、[packages.md](packages.md) を参照してください。\n\n## 利用可能なインポート\n\n| パッケージ | 目的 |\n|---------|---------|\n| `@earendil-works/pi-coding-agent` | 拡張タイプ (`ExtensionAPI`、`ExtensionContext`、イベント) |\n| `typebox` | ツールパラメータのスキーマ定義 |\n| `@earendil-works/pi-ai` | AI ユーティリティ (Google 互換の列挙型の場合は `StringEnum`) |\n| `@earendil-works/pi-tui` | カスタムレンダリング用のTUIコンポーネント |\n\nnpm 依存関係も機能します。拡張機能の横 (または親ディレクトリ) に `package.json` を追加し、`npm install` を実行すると、`node_modules/` からのインポートが自動的に解決されます。\n\n`pi install` (npm または git) でインストールされた分散 pi パッケージの場合、ランタイム deps は `dependencies` になければなりません。パッケージのインストールではデフォルトで実稼働インストール (`npm install --omit=dev`) が使用されるため、実行時には `devDependencies` は使用できません。 `npmCommand` が設定されている場合、git パッケージはラッパーとの互換性のためにプレーン `install` を使用します。\n\nNode.js ビルトイン (`node:fs`、`node:path` など) も利用できます。\n\n## 拡張機能の作成\n\n拡張機能は、`ExtensionAPI` を受け取るデフォルトのファクトリー関数をエクスポートします。ファクトリは同期または非同期にすることができます。\n\n```typescript\nimport type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\n\nexport default function (pi: ExtensionAPI) {\n  // Subscribe to events\n  pi.on(\"event_name\", async (event, ctx) => {\n    // ctx.ui for user interaction\n    const ok = await ctx.ui.confirm(\"Title\", \"Are you sure?\");\n    ctx.ui.notify(\"Done!\", \"info\");\n    ctx.ui.setStatus(\"my-ext\", \"Processing...\");  // Footer status\n    ctx.ui.setWidget(\"my-ext\", [\"Line 1\", \"Line 2\"]);  // Widget above editor (default)\n  });\n\n  // Register tools, commands, shortcuts, flags\n  pi.registerTool({ ... });\n  pi.registerCommand(\"name\", { ... });\n  pi.registerShortcut(\"ctrl+x\", { ... });\n  pi.registerFlag(\"my-flag\", { ... });\n}\n```\n\nExtensions は [jiti](https://github.com/unjs/jiti) 経由でロードされるため、TypeScript はコンパイルなしで動作します。\n\n工場出荷時に `Promise` が返された場合、pi はそれを待ってから起動を続行します。つまり、非同期初期化は、`session_start` より前、`resources_discover` より前、そして `pi.registerProvider()` 経由でキューに入れられたプロバイダー登録がフラッシュされる前に完了します。\n\n### 非同期ファクトリー関数\n\nリモート構成の取得や利用可能なモデルの動的検出などの 1 回限りの起動作業には、非同期ファクトリーを使用します。\n\n```typescript\nimport type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\n\nexport default async function (pi: ExtensionAPI) {\n  const response = await fetch(\"http://localhost:1234/v1/models\");\n  const payload = (await response.json()) as {\n    data: Array<{\n      id: string;\n      name?: string;\n      context_window?: number;\n      max_tokens?: number;\n    }>;\n  };\n\n  pi.registerProvider(\"local-openai\", {\n    baseUrl: \"http://localhost:1234/v1\",\n    apiKey: \"$LOCAL_OPENAI_API_KEY\",\n    api: \"openai-completions\",\n    models: payload.data.map((model) => ({\n      id: model.id,\n      name: model.name ?? model.id,\n      reasoning: false,\n      input: [\"text\"],\n      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n      contextWindow: model.context_window ?? 128000,\n      maxTokens: model.max_tokens ?? 4096,\n    })),\n  });\n}\n```\n\nこのパターンでは、フェッチされたモデルが通常の起動中および `pi --list-models` まで利用可能になります。\n\n### 有効期間の長いリソースとシャットダウン\n\n拡張機能ファクトリーは、セッションを開始しない呼び出しで実行される場合があります。プロセス、ソケット、ファイル ウォッチャー、タイマーなどのバックグラウンド リソースを工場出荷時に開始しないでください。\n\nバックグラウンド リソースの起動を `session_start` まで、またはリソースを必要とするコマンド/ツール/イベントまで延期します。冪等の `session_shutdown` ハンドラーを登録して、開始したセッション スコープのリソースを閉じます。\n\n### 拡張子スタイル\n\n**単一ファイル** - 最も単純で、小さな拡張子の場合:\n\n```\n~/.pi/agent/extensions/\n└── my-extension.ts\n```\n\n**index.ts を持つディレクトリ** - 複数のファイル拡張子の場合:\n\n```\n~/.pi/agent/extensions/\n└── my-extension/\n    ├── index.ts        # Entry point (exports default function)\n    ├── tools.ts        # Helper module\n    └── utils.ts        # Helper module\n```\n\n**依存関係のあるパッケージ** - npm パッケージを必要とする拡張機能の場合:\n\n```\n~/.pi/agent/extensions/\n└── my-extension/\n    ├── package.json    # Declares dependencies and entry points\n    ├── package-lock.json\n    ├── node_modules/   # After npm install\n    └── src/\n        └── index.ts\n```\n\n```json\n// package.json\n{\n  \"name\": \"my-extension\",\n  \"dependencies\": {\n    \"zod\": \"^3.0.0\",\n    \"chalk\": \"^5.0.0\"\n  },\n  \"pi\": {\n    \"extensions\": [\"./src/index.ts\"]\n  }\n}\n```\n\n拡張機能ディレクトリで `npm install` を実行すると、`node_modules/` からのインポートが自動的に機能します。\n\n## イベント\n\n### ライフサイクルの概要\n\n```\npi starts\n  │\n  ├─► project_trust (user/global and CLI extensions only, before project resources load)\n  ├─► session_start { reason: \"startup\" }\n  └─► resources_discover { reason: \"startup\" }\n      │\n      ▼\nuser sends prompt ─────────────────────────────────────────┐\n  │                                                        │\n  ├─► (extension commands checked first, bypass if found)  │\n  ├─► input (can intercept, transform, or handle)          │\n  ├─► (skill/template expansion if not handled)            │\n  ├─► before_agent_start (can inject message, modify system prompt)\n  ├─► agent_start                                          │\n  ├─► message_start / message_update / message_end         │\n  │                                                        │\n  │   ┌─── turn (repeats while LLM calls tools) ───┐       │\n  │   │                                            │       │\n  │   ├─► turn_start                               │       │\n  │   ├─► context (can modify messages)            │       │\n  │   ├─► before_provider_headers (can mutate headers)     |\n  │   ├─► before_provider_request (can inspect or replace payload)\n  │   ├─► after_provider_response (status + headers, before stream consume)\n  │   │                                            │       │\n  │   │   LLM responds, may call tools:            │       │\n  │   │     ├─► tool_execution_start               │       │\n  │   │     ├─► tool_call (can block)              │       │\n  │   │     ├─► tool_execution_update              │       │\n  │   │     ├─► tool_result (can modify)           │       │\n  │   │     └─► tool_execution_end                 │       │\n  │   │                                            │       │\n  │   └─► turn_end                                 │       │\n  │                                                        │\n  ├─► agent_end                                            │\n  └─► agent_settled (no retry/compaction/follow-up left)   │\n                                                           │\nuser sends another prompt ◄────────────────────────────────┘\n\n/new (new session) or /resume (switch session)\n  ├─► session_before_switch (can cancel)\n  ├─► session_shutdown\n  ├─► session_start { reason: \"new\" | \"resume\", previousSessionFile? }\n  └─► resources_discover { reason: \"startup\" }\n\n/fork or /clone\n  ├─► session_before_fork (can cancel)\n  ├─► session_shutdown\n  ├─► session_start { reason: \"fork\", previousSessionFile }\n  └─► resources_discover { reason: \"startup\" }\n\n/name or pi.setSessionName()\n  └─► session_info_changed\n\n/compact or auto-compaction\n  ├─► session_before_compact (can cancel or customize)\n  └─► session_compact\n\n/tree navigation\n  ├─► session_before_tree (can cancel or customize)\n  └─► session_tree\n\n/model or Ctrl+P (model selection/cycling)\n  ├─► thinking_level_select (if model change changes/clamps thinking level)\n  └─► model_select\n\nthinking level changes (settings, keybinding, pi.setThinkingLevel())\n  └─► thinking_level_select\n\nexit (Ctrl+C, Ctrl+D, SIGHUP, SIGTERM)\n  └─► session_shutdown\n```\n\n### スタートアップイベント\n\n#### プロジェクトトラスト\n\npi が動的構成 (`.pi` または `.agents/skills`) を持つプロジェクトを信頼するかどうかを決定する前に起動されます。これは起動時と、現在のプロセスで信頼が解決されていない cwd にセッション置換 (たとえば、`/resume`) が入ったときに実行されます。ユーザー/グローバル拡張機能および CLI `-e` 拡張機能のみが参加します。プロジェクトローカル拡張機能は、信頼が解決されるまでロードされません。\n\n```typescript\npi.on(\"project_trust\", async (event, ctx) => {\n  // event.cwd - current working directory\n  // ctx has a limited trust context: cwd, mode, hasUI, and select/confirm/input/notify UI helpers\n  if (await ctx.ui.confirm(\"Trust project?\", event.cwd)) {\n    return { trusted: \"yes\", remember: true };\n  }\n  return { trusted: \"undecided\" };\n});\n```\n\n`project_trust` ハンドラーは `{ trusted: \"yes\" | \"no\" | \"undecided\" }` を返さなければなりません。 `\"yes\"` または `\"no\"` を返すユーザー/グローバルまたは CLI 拡張機能が決定を所有します。最初の「はい/いいえ」の決定が優先され、組み込みの信頼プロンプトが抑制されます。 `remember: true` を使用して、はい/いいえの決定を維持します。それ以外の場合は、現在のプロセスにのみ適用されます。 `\"undecided\"` を返して、後のハンドラーまたは組み込みの信頼フローに決定させます。プロンプトを表示する前に、`ctx.hasUI` を確認してください。どのハンドラーも Yes/No を返さない場合、通常の信頼解決が続行されます。保存された `trust.json` 決定が最初に適用され、次に `defaultProjectTrust` がデフォルトで pi が尋ねるか、信頼するか、拒否するかを制御します。\n\n### リソースイベント\n\n#### リソース_発見\n\n`session_start` の後に起動されるため、拡張機能は追加のスキル、プロンプト、テーマのパスを提供できます。\n起動パスには `reason: \"startup\"` が使用されます。リロードには `reason: \"reload\"` が使用されます。\n\n```typescript\npi.on(\"resources_discover\", async (event, _ctx) => {\n  // event.cwd - current working directory\n  // event.reason - \"startup\" | \"reload\"\n  return {\n    skillPaths: [\"/path/to/skills\"],\n    promptPaths: [\"/path/to/prompts\"],\n    themePaths: [\"/path/to/themes\"],\n  };\n});\n```\n\n### セッションイベント\n\nセッションストレージの内部と SessionManager API については、[Session Format](session-format.md) を参照してください。\n\n#### セッション開始\n\nセッションの開始時、ロード時、またはリロード時に発生します。\n\n```typescript\npi.on(\"session_start\", async (event, ctx) => {\n  // event.reason - \"startup\" | \"reload\" | \"new\" | \"resume\" | \"fork\"\n  // event.previousSessionFile - present for \"new\", \"resume\", and \"fork\"\n  ctx.ui.notify(`Session: ${ctx.sessionManager.getSessionFile() ?? \"ephemeral\"}`, \"info\");\n});\n```\n\n#### session_info_changed\n\n現在のセッションの表示名が `/name`、RPC、または `pi.setSessionName()` で設定されている場合に発生します。\n\n```typescript\npi.on(\"session_info_changed\", async (event, ctx) => {\n  // event.name - current normalized name, or undefined if cleared\n  ctx.ui.notify(`Session renamed: ${event.name ?? \"(none)\"}`, \"info\");\n});\n```\n\n#### スイッチ前のセッション\n\n新しいセッションを開始する前 (`/new`) またはセッションを切り替える前 (`/resume`) に発生します。\n\n```typescript\npi.on(\"session_before_switch\", async (event, ctx) => {\n  // event.reason - \"new\" or \"resume\"\n  // event.targetSessionFile - session we're switching to (only for \"resume\")\n\n  if (event.reason === \"new\") {\n    const ok = await ctx.ui.confirm(\"Clear?\", \"Delete all messages?\");\n    if (!ok) return { cancel: true };\n  }\n});\n```\n\n切り替えまたは新しいセッションのアクションが成功した後、pi は古い拡張機能インスタンスに対して `session_shutdown` を発行し、新しいセッションに対して拡張機能をリロードおよび再バインドしてから、`reason: \"new\" | \"resume\"` および `previousSessionFile` とともに `session_start` を発行します。\n`session_shutdown` でクリーンアップ作業を実行し、`session_start` でメモリ内の状態を再確立します。\n\n#### session_before_fork\n\n`/fork` 経由でフォークするとき、または `/clone` 経由でクローン作成するときに発生します。\n\n```typescript\npi.on(\"session_before_fork\", async (event, ctx) => {\n  // event.entryId - ID of the selected entry\n  // event.position - \"before\" for /fork, \"at\" for /clone\n  return { cancel: true }; // Cancel fork/clone\n  // OR\n  return { skipConversationRestore: true }; // Reserved for future conversation restore control\n});\n```\n\nフォークまたはクローンが成功した後、pi は古い拡張機能インスタンスに対して `session_shutdown` を発行し、新しいセッションに対して拡張機能をリロードおよび再バインドしてから、`reason: \"fork\"` および `previousSessionFile` とともに `session_start` を発行します。\n`session_shutdown` でクリーンアップ作業を実行し、`session_start` でメモリ内の状態を再確立します。\n\n#### セッション前_コンパクト / セッション_コンパクト\n\n圧縮時に発火します。詳細は[compaction.md](compaction.md)をご覧ください。\n\n```typescript\npi.on(\"session_before_compact\", async (event, ctx) => {\n  const { preparation, branchEntries, customInstructions, reason, willRetry, signal } = event;\n\n  // reason - \"manual\" (/compact), \"threshold\", or \"overflow\"\n  // willRetry - whether the aborted turn is retried after compaction (overflow recovery)\n\n  // Cancel:\n  return { cancel: true };\n\n  // Custom summary:\n  return {\n    compaction: {\n      summary: \"...\",\n      firstKeptEntryId: preparation.firstKeptEntryId,\n      tokensBefore: preparation.tokensBefore,\n      // usage: summaryResponse.usage, // Optional; included in session totals\n    }\n  };\n});\n\npi.on(\"session_compact\", async (event, ctx) => {\n  // event.compactionEntry - the saved compaction\n  // event.fromExtension - whether extension provided it\n  // event.reason - \"manual\" (/compact), \"threshold\", or \"overflow\"\n  // event.willRetry - whether the aborted turn is retried after compaction (overflow recovery)\n});\n```\n\n#### セッション前ツリー / セッションツリー\n\n`/tree` ナビゲーションで発生します。ツリー ナビゲーションの概念については、[Sessions](sessions.md) を参照してください。\n\n```typescript\npi.on(\"session_before_tree\", async (event, ctx) => {\n  const { preparation, signal } = event;\n  return { cancel: true };\n  // OR provide custom summary:\n  return {\n    summary: {\n      summary: \"...\",\n      // usage: summaryResponse.usage, // Optional; included in session totals\n      details: {},\n    },\n  };\n});\n\npi.on(\"session_tree\", async (event, ctx) => {\n  // event.newLeafId, oldLeafId, summaryEntry, fromExtension\n});\n```\n\n#### セッションシャットダウン\n\n開始されたセッション ランタイムが破棄される前に発生します。これを使用して、`session_start` または他のセッション スコープのフックから開かれたリソースをクリーンアップします。\n\n```typescript\npi.on(\"session_shutdown\", async (event, ctx) => {\n  // event.reason - \"quit\" | \"reload\" | \"new\" | \"resume\" | \"fork\"\n  // event.targetSessionFile - destination session for session replacement flows\n  // Cleanup, save state, etc.\n});\n```\n\n### エージェントイベント\n\n#### before_agent_start\n\nユーザーがプロンプトを送信した後、エージェント ループの前に発生します。メッセージを挿入したり、システム プロンプトを変更したりできます。\n\n```typescript\npi.on(\"before_agent_start\", async (event, ctx) => {\n  // event.prompt - user's prompt text\n  // event.images - attached images (if any)\n  // event.systemPrompt - current chained system prompt for this handler\n  //   (includes changes from earlier before_agent_start handlers)\n  // event.systemPromptOptions - structured options used to build the system prompt\n  //   .customPrompt - any custom system prompt (from --system-prompt, SYSTEM.md, or custom templates)\n  //   .selectedTools - tools currently active in the prompt\n  //   .toolSnippets - one-line descriptions for each tool\n  //   .promptGuidelines - custom guideline bullets\n  //   .appendSystemPrompt - text from --append-system-prompt flags\n  //   .cwd - working directory\n  //   .contextFiles - AGENTS.md files and other loaded context files\n  //   .skills - loaded skills\n\n  return {\n    // Inject a persistent message (stored in session, sent to LLM)\n    message: {\n      customType: \"my-extension\",\n      content: \"Additional context for the LLM\",\n      display: true,\n    },\n    // Replace the system prompt for this turn (chained across extensions)\n    systemPrompt: event.systemPrompt + \"\\n\\nExtra instructions for this turn...\",\n  };\n});\n```\n\n`systemPromptOptions` フィールドにより、拡張機能は、システム プロンプトを構築するために Pi が使用するのと同じ構造化データにアクセスできるようになります。これにより、リソースを再検出したりフラグを再解析したりすることなく、Pi がロードしたもの (カスタム プロンプト、ガイドライン、ツール スニペット、context files、スキル) を検査できます。これは、拡張機能がユーザー指定の設定を尊重しながら、情報に基づいてシステム プロンプトに詳細な変更を加える必要がある場合に使用します。\n\n内部の `before_agent_start`、`event.systemPrompt`、および `ctx.getSystemPrompt()` は両方とも、現在のハンドラーの時点でのチェーンされたシステム プロンプトを反映しています。後の `before_agent_start` ハンドラーでも再度変更できます。\n\n#### エージェント_開始 / エージェント_終了 / エージェント_解決済み\n\n`agent_start` は、低レベルのエージェントの実行が開始されるときに起動されます。 `agent_end` は実行が終了すると起動されますが、Pi は引き続き自動再試行、自動圧縮と再試行、またはキューに入れられたフォローアップ メッセージの続行を行う可能性があります。 Pi が自動的に実行を継続しないことを認識する必要があるステータス統合には、`agent_settled` を使用します。\n\n```typescript\npi.on(\"agent_start\", async (_event, ctx) => {});\n\npi.on(\"agent_end\", async (event, ctx) => {\n  // event.messages - messages from this low-level run\n});\n\npi.on(\"agent_settled\", async (_event, ctx) => {\n  // ctx.isIdle() is true here unless another extension started a new run.\n});\n```\n\n#### ターンスタート / ターンエンド\n\n各ターン (LLM 応答 1 回 + ツール呼び出し) ごとに起動されます。\n\n```typescript\npi.on(\"turn_start\", async (event, ctx) => {\n  // event.turnIndex, event.timestamp\n});\n\npi.on(\"turn_end\", async (event, ctx) => {\n  // event.turnIndex, event.message, event.toolResults\n});\n```\n\n#### メッセージ開始 / メッセージ更新 / メッセージ終了\n\nメッセージのライフサイクル更新のために発生します。\n\n- `message_start` と `message_end` は、ユーザー、アシスタント、toolResult メッセージに対して発生します。\n- `message_update` はアシスタントのストリーミング更新のために起動されます。\n- `message_end` ハンドラーは、`{ message }` を返して、確定されたメッセージを置き換えることができます。交換品は同じ `role` を維持する必要があります。\n\n```typescript\npi.on(\"message_start\", async (event, ctx) => {\n  // event.message\n});\n\npi.on(\"message_update\", async (event, ctx) => {\n  // event.message\n  // event.assistantMessageEvent (token-by-token stream event)\n});\n\npi.on(\"message_end\", async (event, ctx) => {\n  if (event.message.role !== \"assistant\") return;\n\n  return {\n    message: {\n      ...event.message,\n      usage: {\n        ...event.message.usage,\n        cost: {\n          ...event.message.usage.cost,\n          total: 0.123,\n        },\n      },\n    },\n  };\n});\n```\n\n#### ツール実行開始 / ツール実行更新 / ツール実行終了\n\nツール実行ライフサイクルの更新のために起動されます。\n\nパラレルツールモード:\n- `tool_execution_start` はプリフライトフェーズ中にアシスタントソースオーダーで放出されます\n- `tool_execution_update` イベントはツール間でインターリーブする場合があります\n- `tool_execution_end` は各ツールが完了した後、ツール完了順に出力されます\n- 最終 `toolResult` メッセージ イベントは引き続きアシスタント ソースの順序で後から出力されます\n\n```typescript\npi.on(\"tool_execution_start\", async (event, ctx) => {\n  // event.toolCallId, event.toolName, event.args\n});\n\npi.on(\"tool_execution_update\", async (event, ctx) => {\n  // event.toolCallId, event.toolName, event.args, event.partialResult\n});\n\npi.on(\"tool_execution_end\", async (event, ctx) => {\n  // event.toolCallId, event.toolName, event.result, event.isError\n});\n```\n\n#### コンテクスト\n\n各 LLM 呼び出しの前に発生します。メッセージを非破壊的に変更します。メッセージの種類については、[Session Format](session-format.md) を参照してください。\n\n```typescript\npi.on(\"context\", async (event, ctx) => {\n  // event.messages - deep copy, safe to modify\n  const filtered = event.messages.filter(m => !shouldPrune(m));\n  return { messages: filtered };\n});\n```\n\n#### before_provider_headers\n\n送信 HTTP ヘッダーが組み立てられた後に起動されます。これを使用して、リクエスト ヘッダーを追加、上書き、または削除します。\n\nハンドラーはその場で `event.headers` を変異させます。キーを文字列に設定して追加または上書きするか、`null` に設定して削除します。\n\n```typescript\npi.on(\"before_provider_headers\", (event, ctx) => {\n  // Add or override — e.g. a session id for gateway tracing/attribution\n  event.headers[\"x-session-id\"] = ctx.sessionManager.getSessionId();\n\n  // Drop a tracking header pi adds for this call\n  event.headers[\"X-OpenRouter-Title\"] = null;\n});\n```\n\nプロバイダー要求ごとに 1 回実行されます。再試行では、フックを再起動するのではなく、同じヘッダーが再利用されます。\n\n#### before_provider_request\n\nプロバイダー固有のペイロードが構築された後、リクエストが送信される直前に起動されます。ハンドラーは拡張機能のロード順序で実行されます。 `undefined` を返すと、ペイロードは変更されません。他の値を返すと、後のハンドラーと実際のリクエストのペイロードが置き換えられます。\n\nこのフックは、プロバイダーレベルのシステム命令を書き換えたり、完全に削除したりできます。これらのペイロード レベルの変更は、最終的なシリアル化されたプロバイダー ペイロードではなく、Pi のシステム プロンプト文字列を報告する `ctx.getSystemPrompt()` には反映されません。\n\n```typescript\npi.on(\"before_provider_request\", (event, ctx) => {\n  console.log(JSON.stringify(event.payload, null, 2));\n\n  // Optional: replace payload\n  // return { ...event.payload, temperature: 0 };\n});\n```\n\nこれは主に、プロバイダーのシリアル化とキャッシュの動作をデバッグするのに役立ちます。\n\n#### after_provider_response\n\nHTTP 応答を受信した後、そのストリーム本体が消費される前に発生します。ハンドラーは拡張機能のロード順序で実行されます。\n\n```typescript\npi.on(\"after_provider_response\", (event, ctx) => {\n  // event.status - HTTP status code\n  // event.headers - normalized response headers\n  if (event.status === 429) {\n    console.log(\"rate limited\", event.headers[\"retry-after\"]);\n  }\n});\n```\n\nヘッダーが利用できるかどうかは、プロバイダーとトランスポートによって異なります。 Providers 抽象的な HTTP 応答はヘッダーを公開しない可能性があります。\n\n### モデルイベント\n\n#### モデル選択\n\n`/model` コマンド、モデルのサイクリング (`Ctrl+P`)、またはセッションの復元によってモデルが変更されたときに発生します。\n\n```typescript\npi.on(\"model_select\", async (event, ctx) => {\n  // event.model - newly selected model\n  // event.previousModel - previous model (undefined if first selection)\n  // event.source - \"set\" | \"cycle\" | \"restore\"\n\n  const prev = event.previousModel\n    ? `${event.previousModel.provider}/${event.previousModel.id}`\n    : \"none\";\n  const next = `${event.model.provider}/${event.model.id}`;\n\n  ctx.ui.notify(`Model changed (${event.source}): ${prev} -> ${next}`, \"info\");\n});\n```\n\nこれを使用して、UI 要素 (ステータス バー、フッター) を更新したり、アクティブなモデルが変更されたときにモデル固有の初期化を実行したりできます。\n\n#### 思考レベル選択\n\n思考レベルが変化すると発火します。これは通知のみです。ハンドラーの戻り値は無視されます。\n\n```typescript\npi.on(\"thinking_level_select\", async (event, ctx) => {\n  // event.level - newly selected thinking level\n  // event.previousLevel - previous thinking level\n\n  ctx.ui.setStatus(\"thinking\", `thinking: ${event.level}`);\n});\n```\n\n`pi.setThinkingLevel()`、モデルの変更、または組み込みの思考レベル コントロールによってアクティブな思考レベルが変更されたときに、これを使用して拡張機能 UI を更新します。\n\n### ツールイベント\n\n#### ツールコール\n\n`tool_execution_start` の後、ツールが実行される前に発生します。 **ブロックすることができます。** `isToolCallEventType` を使用して、入力された入力を絞り込んで取得します。\n\n`tool_call` が実行される前に、pi は、以前に発行されたエージェント イベントが `AgentSession` を介して排出し終わるのを待ちます。これは、現在のアシスタント ツール呼び出しメッセージを通じて `ctx.sessionManager` が最新であることを意味します。\n\nデフォルトの並列ツール実行モードでは、同じアシスタント メッセージからの兄弟ツール呼び出しが順番にプリフライトされ、同時に実行されます。 `tool_call` では、`ctx.sessionManager` の同じアシスタント メッセージからの兄弟ツールの結果が表示されるとは限りません。\n\n`event.input` は変更可能です。実行前にツール引数にパッチを適用するために、適切な場所で変更します。\n\n動作保証:\n- `event.input` への変更は実際のツールの実行に影響します\n- 後の `tool_call` ハンドラーは、以前のハンドラーによって行われた突然変異を認識します\n- 突然変異後に再検証は実行されません\n- `tool_call` からの戻り値は、`{ block: true, reason?: string, terminate?: boolean }` を介してブロックを制御します\n- `terminate` はブロックされた通話にのみ適用されます。エージェントは、バッチ内のすべての最終結果が終了するときにのみ早期に停止します。\n\n```typescript\nimport { isToolCallEventType } from \"@earendil-works/pi-coding-agent\";\n\npi.on(\"tool_call\", async (event, ctx) => {\n  // event.toolName - \"bash\", \"read\", \"write\", \"edit\", etc.\n  // event.toolCallId\n  // event.input - tool parameters (mutable)\n\n  // Built-in tools: no type params needed\n  if (isToolCallEventType(\"bash\", event)) {\n    // event.input is { command: string; timeout?: number }\n    event.input.command = `source ~/.profile\\n${event.input.command}`;\n\n    if (event.input.command.includes(\"rm -rf\")) {\n      return { block: true, reason: \"Dangerous command\", terminate: true };\n    }\n  }\n\n  if (isToolCallEventType(\"read\", event)) {\n    // event.input is { path: string; offset?: number; limit?: number }\n    console.log(`Reading: ${event.input.path}`);\n  }\n});\n```\n\n#### カスタムツール入力の入力\n\nカスタム ツールは入力タイプをエクスポートする必要があります。\n\n```typescript\n// my-extension.ts\nexport type MyToolInput = Static<typeof myToolSchema>;\n```\n\n明示的な型パラメータでは `isToolCallEventType` を使用します。\n\n```typescript\nimport { isToolCallEventType } from \"@earendil-works/pi-coding-agent\";\nimport type { MyToolInput } from \"my-extension\";\n\npi.on(\"tool_call\", (event) => {\n  if (isToolCallEventType<\"my_tool\", MyToolInput>(\"my_tool\", event)) {\n    event.input.action;  // typed\n  }\n});\n```\n\n#### ツール_結果\n\nツールの実行終了後、`tool_execution_end` に加えて最終的なツール結果メッセージ イベントが発行される前に発生します。 **結果を変更できます。**\n\n並列ツール モードでは、`tool_result` と `tool_execution_end` がツールの完了順序でインターリーブされる可能性がありますが、最終の `toolResult` メッセージ イベントは依然としてアシスタント ソースの順序で後で発行されます。\n\n`tool_result` ハンドラーはミドルウェアのように連鎖します。\n- ハンドラーは拡張機能のロード順序で実行されます\n- 各ハンドラーは、以前のハンドラー変更後の最新の結果を参照します。\n- ハンドラーは部分的なパッチ (`content`、`details`、`isError`、または `usage`) を返すことができます。省略されたフィールドは現在の値を保持します\n\nハンドラー内でネストされた非同期作業には `ctx.signal` を使用します。これにより、Esc でモデル呼び出し、`fetch()`、および拡張機能によって開始されたその他の中止を認識する操作をキャンセルできるようになります。\n\n```typescript\nimport { isBashToolResult } from \"@earendil-works/pi-coding-agent\";\n\npi.on(\"tool_result\", async (event, ctx) => {\n  // event.toolName, event.toolCallId, event.input\n  // event.content, event.details, event.isError, event.usage\n\n  if (isBashToolResult(event)) {\n    // event.details is typed as BashToolDetails\n  }\n\n  const response = await fetch(\"https://example.com/summarize\", {\n    method: \"POST\",\n    body: JSON.stringify({ content: event.content }),\n    signal: ctx.signal,\n  });\n\n  // Modify result:\n  return { content: [...], details: {...}, isError: false, usage: nestedModelUsage };\n});\n```\n\n### ユーザー Bash イベント\n\n#### ユーザー_bash\n\nユーザーが `!` または `!!` コマンドを実行すると発生します。 **迎撃可能**\n\n```typescript\nimport { createLocalBashOperations } from \"@earendil-works/pi-coding-agent\";\n\npi.on(\"user_bash\", (event, ctx) => {\n  // event.command - the bash command\n  // event.excludeFromContext - true if !! prefix\n  // event.cwd - working directory\n\n  // Option 1: Provide custom operations (e.g., SSH)\n  return { operations: remoteBashOps };\n\n  // Option 2: Wrap pi's built-in local bash backend\n  const local = createLocalBashOperations();\n  return {\n    operations: {\n      exec(command, cwd, options) {\n        return local.exec(`source ~/.profile\\n${command}`, cwd, options);\n      }\n    }\n  };\n\n  // Option 3: Full replacement - return result directly\n  return { result: { output: \"...\", exitCode: 0, cancelled: false, truncated: false } };\n});\n```\n\n### 入力イベント\n\n#### 入力\n\n拡張コマンドがチェックされた後、スキルとテンプレートの展開前に、ユーザー入力が受信されたときに発生します。イベントは生の入力テキストを参照するため、`/skill:foo` と `/template` はまだ展開されていません。\n\n**処理順序:**\n1. 拡張コマンド (`/cmd`) が最初にチェックされます - 見つかった場合、ハンドラーが実行され、入力イベントはスキップされます\n2. `input` イベントの発火 - 傍受、変換、または処理が可能\n3. 処理されない場合: スキルコマンド (`/skill:name`) はスキルコンテンツに展開されます\n4. 処理されない場合: prompt templates (`/template`) はテンプレートのコンテンツに展開されます\n5. エージェントの処理が開始されます (`before_agent_start` など)\n\n```typescript\npi.on(\"input\", async (event, ctx) => {\n  // event.text - raw input (before skill/template expansion)\n  // event.images - attached images, if any\n  // event.source - \"interactive\" (typed), \"rpc\" (API), or \"extension\" (via sendUserMessage)\n  // event.streamingBehavior - \"steer\" | \"followUp\" | undefined\n  //   undefined when idle, \"steer\" for mid-stream interrupts,\n  //   \"followUp\" for messages queued until the agent finishes\n\n  // Transform: rewrite input before expansion\n  if (event.text.startsWith(\"?quick \"))\n    return { action: \"transform\", text: `Respond briefly: ${event.text.slice(7)}` };\n\n  // Handle: respond without LLM (extension shows its own feedback)\n  if (event.text === \"ping\") {\n    ctx.ui.notify(\"pong\", \"info\");\n    return { action: \"handled\" };\n  }\n\n  // Route by source: skip processing for extension-injected messages\n  if (event.source === \"extension\") return { action: \"continue\" };\n\n  // Intercept skill commands before expansion\n  if (event.text.startsWith(\"/skill:\")) {\n    // Could transform, block, or let pass through\n  }\n\n  return { action: \"continue\" };  // Default: pass through to expansion\n});\n```\n\n**結果：**\n- `continue` - 変更せずに通過します (ハンドラーが何も返さない場合のデフォルト)\n- `transform` - テキスト/画像を変更し、拡張を続けます\n- `handled` - エージェントを完全にスキップします (これを返した最初のハンドラーが勝ちます)\n\nハンドラー間でチェーンを変換します。 `streamingBehavior` 対応ルーティングについては、[input-transform.ts](../examples/extensions/input-transform.ts) と [input-transform-streaming.ts](../examples/extensions/input-transform-streaming.ts) を参照してください。\n\n## 拡張コンテキスト\n\nすべてのハンドラーは `ctx: ExtensionContext` を受け取ります。\n\n### ctx.ui\n\nユーザー対話のための UI メソッド。詳細については、[Custom UI](#custom-ui) を参照してください。\n\n### ctx.モード\n\n現在の実行モード: `\"tui\"`、`\"rpc\"`、`\"json\"`、または `\"print\"`。 `ctx.mode === \"tui\"` を使用して、`custom()`、コンポーネント ファクトリ、ターミナル入力、ダイレクト TUI レンダリングなどのターミナルのみの機能を保護します。\n\n### ctx.hasUI\n\nTUI および RPC モードの `true`。 `false` 印刷モード (`-p`) および JSON モード。これを使用して、TUI と TUI 、`input`、`editor` の両方で機能するダイアログ メソッド (`select`、`confirm`、`input`、`editor`) およびファイア アンド フォーゲット メソッド (`notify`、`setStatus`、`setWidget`、`setTitle`、`setEditorText`) を保護します。 RPC モード。 RPC モードでは、一部の TUI 固有のメソッドは操作なし、またはデフォルトを返します ([rpc.md](rpc.md#extension-ui-protocol) を参照)。\n\n### ctx.cwd\n\n現在の作業ディレクトリ。\n\nプロジェクトローカル構成パスを構築するときは、ハードコーディング `.pi` の代わりに `CONFIG_DIR_NAME` を使用してください。ブランド変更されたディストリビューションでは、別の構成ディレクトリ名を使用できます。\n\n```typescript\nimport { CONFIG_DIR_NAME, type ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { join } from \"node:path\";\n\nexport default function (pi: ExtensionAPI) {\n  pi.on(\"session_start\", (_event, ctx) => {\n    const projectConfigPath = join(ctx.cwd, CONFIG_DIR_NAME, \"my-extension.json\");\n    // ...\n  });\n}\n```\n\n### ctx.isProjectTrusted()\n\n現在のセッション コンテキストに対してプロジェクト ローカルの信頼がアクティブかどうかを返します。これには、グローバル トラスト ストアに保存された決定だけでなく、一時的な信頼の決定と CLI の信頼のオーバーライドも含まれます。\n\nこれは、信頼できるプロジェクトに対してのみ適用されるプロジェクト ローカル拡張機能の構成を読み取る前に使用してください。\n\n### ctx.sessionManager\n\nセッション状態への読み取り専用アクセス。完全な SessionManager API とエントリ タイプについては、[Session Format](session-format.md) を参照してください。\n\n`tool_call` の場合、この状態はハンドラーが実行される前に現在のアシスタント メッセージを通じて同期されます。並列ツール実行モードでは、同じアシスタント メッセージからの兄弟ツールの結果が含まれることはまだ保証されていません。\n\n```typescript\nctx.sessionManager.getEntries()             // All entries\nctx.sessionManager.getBranch()              // Current branch\nctx.sessionManager.buildContextEntries()    // Active branch entries with compaction applied\nctx.sessionManager.getLeafId()              // Current leaf entry ID\n```\n\n### ctx.modelRegistry / ctx.model / ctx. ThinkingLevel / ctx.scopedModels\n\nモデル、プロバイダー、および解決された認証へのアクセス。 `ctx.modelRegistry.getProvider(id)` は有効な pi-ai プロバイダーを返しますが、`getProviderAuth(id)` は、ロードされたモデルを必要とせずに、現在の API key、ヘッダー、ベース URL、およびプロバイダー スコープの環境を解決します。 `ctx.model` はアクティブなモデル、`ctx.thinkingLevel` は現在の有効な思考レベルです。\n\n`ctx.scopedModels` は、現在のセッションをスコープとするモデルの読み取り専用リストです。`/scoped-models` コマンドで表示されるのと同じセットです。これは、セッション開始時に `--models` CLI フラグと `enabledModels` 設定 (`provider/modelId` のミニマッチまたはベア `modelId` で利用可能なカタログと照合) から解決されます。スコープが設定されていない場合は空です。これは、利用可能なすべてのモデルが使用できることを意味します。各エントリは `{ model, thinkingLevel? }` で、`thinkingLevel` はパターンによって固定された場合にのみ設定されます (例: `anthropic/*:high`)。これを使用して、`ctx.modelRegistry.getAvailable()` を介してカタログ全体を列挙する代わりに、組み込みのものをミラーリングするモデル ピッカーを設定します。\n\n### ctx.シグナル\n\n現在のエージェントの中止信号、またはアクティブなエージェントのターンがない場合は `undefined`。\n\nこれは、拡張ハンドラーによって開始される中止を認識するネストされた作業に使用します。次に例を示します。\n- `fetch(..., { signal: ctx.signal })`\n- `signal` を受け入れるモデル呼び出し\n- `AbortSignal` を受け入れるファイルまたはプロセス ヘルパー\n\n`ctx.signal` は通常、`tool_call`、`tool_result`、`message_update`、`turn_end` などのアクティブなターン イベント中に定義されます。\n通常、セッション イベント、拡張コマンド、pi がアイドル中に起動されるショートカットなどのアイドルまたは非ターン コンテキストでは、これは `undefined` です。\n\n```typescript\npi.on(\"tool_result\", async (event, ctx) => {\n  const response = await fetch(\"https://example.com/api\", {\n    method: \"POST\",\n    body: JSON.stringify(event),\n    signal: ctx.signal,\n  });\n\n  const data = await response.json();\n  return { details: data };\n});\n```\n\n### ctx.isIdle() / ctx.abort() / ctx.hasPendingMessages()\n\n制御フローヘルパー。 Pi がエージェントの実行、自動再試行、自動圧縮の再試行、またはキューに入れられた継続を処理している間、`ctx.isIdle()` は false です。\n\n### ctx.shutdown()\n\npi の正常なシャットダウンを要求します。\n\n- **対話型モード:** エージェントがアイドル状態になるまで延期されます (キューに入れられたステアリングおよびフォローアップ メッセージをすべて処理した後)。\n- **RPC モード:** 次のアイドル状態まで延期されます (現在のコマンド応答が完了した後、次のコマンドを待機しているとき)。\n- **印刷モード:** 何もしません。すべてのプロンプトが処理されると、プロセスは自動的に終了します。\n\n終了する前に、すべての拡張機能に `session_shutdown` イベントを発行します。すべてのコンテキスト (イベント ハンドラー、ツール、コマンド、ショートカット) で使用できます。\n\n```typescript\npi.on(\"tool_call\", (event, ctx) => {\n  if (isFatal(event.input)) {\n    ctx.shutdown();\n  }\n});\n```\n\n### ctx.getContextUsage()\n\nアクティブなモデルの現在のコンテキストの使用状況を返します。利用可能な場合は最後のアシスタントの使用状況を使用し、後続メッセージのトークンを推定します。\n\n```typescript\nconst usage = ctx.getContextUsage();\nif (usage && usage.tokens > 100_000) {\n  // ...\n}\n```\n\n### ctx.compact()\n\n完了を待たずに圧縮をトリガーします。フォローアップアクションには`onComplete`と`onError`を使用します。\n\n```typescript\nctx.compact({\n  customInstructions: \"Focus on recent changes\",\n  onComplete: (result) => {\n    ctx.ui.notify(\"Compaction completed\", \"info\");\n  },\n  onError: (error) => {\n    ctx.ui.notify(`Compaction failed: ${error.message}`, \"error\");\n  },\n});\n```\n\n### ctx.getSystemPrompt()\n\nPi の現在のシステム プロンプト文字列を返します。\n\n- `before_agent_start` の間、これは現在のターンでこれまでに行われた連鎖的なシステムプロンプトの変更を反映します。\n- 後の `context` メッセージの変異は含まれません。\n- `before_provider_request` ペイロードの書き換えは含まれません。\n- 後から読み込まれた拡張機能が拡張機能の後に実行された場合でも、最終的に送信される内容が変更される可能性があります。\n\n```typescript\npi.on(\"before_agent_start\", (event, ctx) => {\n  const prompt = ctx.getSystemPrompt();\n  console.log(`System prompt length: ${prompt.length}`);\n});\n```\n\n## 拡張コマンドコンテキスト\n\nコマンド ハンドラーは `ExtensionCommandContext` を受け取り、セッション制御メソッドで `ExtensionContext` を拡張します。これらはイベント ハンドラーから呼び出された場合にデッドロックの可能性があるため、コマンドでのみ使用できます。\n\n### ctx.getSystemPromptOptions()\n\nPi がシステム プロンプトを構築するために現在使用している基本入力を返します。\n\n```typescript\nconst options = ctx.getSystemPromptOptions();\nconst contextPaths = options.contextFiles?.map((file) => file.path) ?? [];\n```\n\nこれは、`before_agent_start` `event.systemPromptOptions` と同じ形状と変更可能性を持っています: カスタム プロンプト、アクティブなツール、ツール スニペット、プロンプト ガイドライン、追加されたシステム プロンプト テキスト、CWD、ロードされた context files、およびロードされたスキル。コンテキスト ファイルの完全な内容が含まれる場合があるため、機密性の高い拡張機能ローカル データとして扱い、コマンド リスト、ログ、またはオートコンプリート メタデータを通じて公開することは避けてください。\n\nこれは、現在の基本プロンプト入力を報告します。これには、ターンごとの `before_agent_start` 連鎖したシステム プロンプトの変更、その後の `context` イベント メッセージの変更、または `before_provider_request` ペイロードの書き換えは含まれません。\n\n### ctx.waitForIdle()\n\n自動再試行、自動圧縮再試行、キューに入れられた継続など、エージェントが完全に解決するまで待ちます。\n\n```typescript\npi.registerCommand(\"my-cmd\", {\n  handler: async (args, ctx) => {\n    await ctx.waitForIdle();\n    // Agent is now idle, safe to modify session\n  },\n});\n```\n\n### ctx.newSession(オプション?)\n\n新しいセッションを作成します。\n\n```typescript\nconst parentSession = ctx.sessionManager.getSessionFile();\nconst kickoff = \"Continue in the replacement session\";\n\nconst result = await ctx.newSession({\n  parentSession,\n  setup: async (sm) => {\n    sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Context from previous session...\" }],\n      timestamp: Date.now(),\n    });\n  },\n  withSession: async (ctx) => {\n    // Use only the replacement-session ctx here.\n    await ctx.sendUserMessage(kickoff);\n  },\n});\n\nif (result.cancelled) {\n  // An extension cancelled the new session\n}\n```\n\nオプション:\n- `parentSession`: 新しいセッションヘッダーに記録する親セッションファイル\n- `setup`: `withSession` が実行される前に、新しいセッションの `SessionManager` を変更します。\n- `withSession`: 新しい交換セッションのコンテキストに対して切り替え後の作業を実行します。キャプチャされた古い `pi` / コマンド `ctx` は使用しないでください。 [Session replacement lifecycle and footguns](#session-replacement-lifecycle-and-footguns)を参照してください。\n\n### ctx.fork(entryId、オプション?)\n\n特定のエントリからフォークして、新しいセッション ファイルを作成します。\n\n```typescript\nconst result = await ctx.fork(\"entry-id-123\", {\n  withSession: async (ctx) => {\n    // Use only the replacement-session ctx here.\n    ctx.ui.notify(\"Now in the forked session\", \"info\");\n  },\n});\nif (result.cancelled) {\n  // An extension cancelled the fork\n}\n\nconst cloneResult = await ctx.fork(\"entry-id-456\", { position: \"at\" });\nif (cloneResult.cancelled) {\n  // An extension cancelled the clone\n}\n```\n\nオプション:\n- `position`: `\"before\"` (デフォルト) 選択したユーザーメッセージの前にフォークし、そのプロンプトをエディターに復元します。\n- `position`: `\"at\"` は、エディターのテキストを復元せずに、選択したエントリを介してアクティブなパスを複製します。\n- `withSession`: 新しい交換セッションのコンテキストに対して切り替え後の作業を実行します。キャプチャされた古い `pi` / コマンド `ctx` は使用しないでください。 [Session replacement lifecycle and footguns](#session-replacement-lifecycle-and-footguns)を参照してください。\n\n### ctx.navigateTree(targetId、オプション?)\n\nsession tree 内の別のポイントに移動します。\n\n```typescript\nconst result = await ctx.navigateTree(\"entry-id-456\", {\n  summarize: true,\n  customInstructions: \"Focus on error handling changes\",\n  replaceInstructions: false, // true = replace default prompt entirely\n  label: \"review-checkpoint\",\n});\n```\n\nオプション:\n- `summarize`: 放棄されたブランチの概要を生成するかどうか\n- `customInstructions`: サマライザーのカスタム命令\n- `replaceInstructions`: true の場合、`customInstructions` は追加されるのではなく、デフォルトのプロンプトを置き換えます。\n- `label`: ブランチの要約エントリ (要約していない場合はターゲット エントリ) に付けるラベル\n\n### ctx.switchSession(セッションパス、オプション?)\n\n別のセッション ファイルに切り替えます。\n\n```typescript\nconst result = await ctx.switchSession(\"/path/to/session.jsonl\", {\n  withSession: async (ctx) => {\n    await ctx.sendUserMessage(\"Resume work in the replacement session\");\n  },\n});\nif (result.cancelled) {\n  // An extension cancelled the switch via session_before_switch\n}\n```\n\nオプション:\n- `withSession`: 新しい交換セッションのコンテキストに対して切り替え後の作業を実行します。キャプチャされた古い `pi` / コマンド `ctx` は使用しないでください。 [Session replacement lifecycle and footguns](#session-replacement-lifecycle-and-footguns)を参照してください。\n\n使用可能なセッションを検出するには、静的な `SessionManager.list()` または `SessionManager.listAll()` メソッドを使用します。\n\n```typescript\nimport { SessionManager } from \"@earendil-works/pi-coding-agent\";\n\npi.registerCommand(\"switch\", {\n  description: \"Switch to another session\",\n  handler: async (args, ctx) => {\n    const sessions = await SessionManager.list(ctx.cwd);\n    if (sessions.length === 0) return;\n    const choice = await ctx.ui.select(\n      \"Pick session:\",\n      sessions.map(s => s.file),\n    );\n    if (choice) {\n      await ctx.switchSession(choice, {\n        withSession: async (ctx) => {\n          ctx.ui.notify(\"Switched session\", \"info\");\n        },\n      });\n    }\n  },\n});\n```\n\n### セッション置換のライフサイクルとフットガン\n\n`withSession` は新しい `ReplacedSessionContext` を受け取り、置換セッションにバインドされた非同期ヘルパー `sendMessage()` および `sendUserMessage()` で `ExtensionCommandContext` を拡張します。\n\nライフサイクルとフットガン:\n- `withSession` は、古いセッションが `session_shutdown` を発行し、古いランタイムが破棄され、置換セッションがリバウンドされ、新しい拡張機能インスタンスがすでに `session_start` を受信した後にのみ実行されます。\n- コールバックは、新しい拡張機能インスタンス内ではなく、元のクロージャ内で引き続き実行されます。つまり、古い拡張機能インスタンスは、`withSession` が開始される前にすでにシャットダウン クリーンアップを実行している可能性があります。\n- キャプチャされた古い `pi` / 古いコマンド `ctx` セッションバインド オブジェクトは、置換後は古くなり、使用されるとスローされます。セッションにバインドされた作業には、`withSession` に渡された `ctx` のみを使用してください。\n- 以前に抽出された生のオブジェクトは引き続きユーザーの責任です。たとえば、置換前に `const sm = ctx.sessionManager` をキャプチャした場合、`sm` は古い `SessionManager` オブジェクトのままです。交換後の再使用はしないでください。\n- `withSession` のコードは、`session_shutdown` ハンドラーによって無効化された状態はすでになくなっていると想定する必要があります。文字列、ID、シリアル化された構成など、シャットダウンしても問題なく残るプレーン データのみをキャプチャします。\n\n安全なパターン:\n\n```typescript\npi.registerCommand(\"handoff\", {\n  handler: async (_args, ctx) => {\n    const kickoff = \"Continue from the replacement session\";\n    await ctx.newSession({\n      withSession: async (ctx) => {\n        await ctx.sendUserMessage(kickoff);\n      },\n    });\n  },\n});\n```\n\n安全でないパターン:\n\n```typescript\npi.registerCommand(\"handoff\", {\n  handler: async (_args, ctx) => {\n    const oldSessionManager = ctx.sessionManager;\n    await ctx.newSession({\n      withSession: async (_ctx) => {\n        // stale old objects: do not do this\n        oldSessionManager.getSessionFile();\n        pi.sendUserMessage(\"wrong\");\n      },\n    });\n  },\n});\n```\n\n### ctx.reload()\n\n`/reload` と同じリロード フローを実行します。\n\n```typescript\npi.registerCommand(\"reload-runtime\", {\n  description: \"Reload extensions, skills, prompts, themes, and context files\",\n  handler: async (_args, ctx) => {\n    await ctx.reload();\n    return;\n  },\n});\n```\n\n重要な行動:\n- `await ctx.reload()` は、現在の拡張機能ランタイムに対して `session_shutdown` を生成します\n- 次に、リソースをリロードし、`reason: \"reload\"` で `session_start` を、理由 `\"reload\"` で `resources_discover` を発行します。\n- 現在実行中のコマンド ハンドラーは引き続き古い呼び出しフレームを継続します。\n- `await ctx.reload()` 以降のコードはリロード前のバージョンから引き続き実行されます\n- `await ctx.reload()` 以降のコードは、古いメモリ内拡張機能の状態がまだ有効であると想定してはなりません\n- ハンドラーが戻った後、以降のコマンド/イベント/ツール呼び出しでは新しい拡張バージョンが使用されます。\n\n予測可能な動作を実現するには、リロードをそのハンドラーの端末として扱います (`await ctx.reload(); return;`)。\n\nツールは `ExtensionContext` で実行されるため、`ctx.reload()` を直接呼び出すことはできません。コマンドをリロード エントリポイントとして使用し、そのコマンドをフォローアップ ユーザー メッセージとしてキューに入れるツールを公開します。\n\nLLM がリロードをトリガーするために呼び出すことができるツールの例:\n\n```typescript\nimport type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { Type } from \"typebox\";\n\nexport default function (pi: ExtensionAPI) {\n  pi.registerCommand(\"reload-runtime\", {\n    description: \"Reload extensions, skills, prompts, themes, and context files\",\n    handler: async (_args, ctx) => {\n      await ctx.reload();\n      return;\n    },\n  });\n\n  pi.registerTool({\n    name: \"reload_runtime\",\n    label: \"Reload Runtime\",\n    description: \"Reload extensions, skills, prompts, themes, and context files\",\n    parameters: Type.Object({}),\n    async execute() {\n      pi.sendUserMessage(\"/reload-runtime\", { deliverAs: \"followUp\" });\n      return {\n        content: [{ type: \"text\", text: \"Queued /reload-runtime as a follow-up command.\" }],\n      };\n    },\n  });\n}\n```\n\n## 拡張API メソッド\n\n### pi.on(イベント、ハンドラー)\n\nイベントを購読します。イベントの種類と戻り値については、[Events](#events) を参照してください。\n\n### pi.registerTool(定義)\n\nLLM によって呼び出し可能なカスタム ツールを登録します。詳細については、[Custom Tools](#custom-tools) を参照してください。\n\n`pi.registerTool()` は、拡張機能のロード中と起動後の両方で動作します。 `session_start`、コマンド ハンドラー、またはその他のイベント ハンドラー内で呼び出すことができます。新しいツールは同じセッション内ですぐに更新されるため、`pi.getAllTools()` に表示され、`/reload` を使わずに LLM によって呼び出すことができます。\n\n実行時にツール (動的に追加されたツールを含む) を有効または無効にするには、`pi.setActiveTools()` を使用します。\n\n`Available tools` の 1 行エントリにカスタム ツールを選択するには `promptSnippet` を使用し、ツールがアクティブなときにデフォルトの `Guidelines` セクションにツール固有の箇条書きを追加するには `promptGuidelines` を使用します。\n\n**重要:** `promptGuidelines` の箇条書きは、ツール名のプレフィックスなしで `Guidelines` セクションにフラットに追加されます。各ガイドラインでは、参照するツールに名前を付ける必要があります。LLM は「これ」がどのツールを意味するかを判断できないため、「次の場合にこのツールを使用する」は避けてください。代わりに「次の場合に my_tool を使用する」と書きます。\n\n完全な例については、[dynamic-tools.ts](../examples/extensions/dynamic-tools.ts) を参照してください。\n\n```typescript\nimport { Type } from \"typebox\";\nimport { StringEnum } from \"@earendil-works/pi-ai\";\n\npi.registerTool({\n  name: \"my_tool\",\n  label: \"My Tool\",\n  description: \"What this tool does\",\n  promptSnippet: \"Summarize or transform text according to action\",\n  promptGuidelines: [\"Use my_tool when the user asks to summarize previously generated text.\"],\n  parameters: Type.Object({\n    action: StringEnum([\"list\", \"add\"] as const),\n    text: Type.Optional(Type.String()),\n  }),\n  prepareArguments(args) {\n    // Optional compatibility shim. Runs before schema validation.\n    // Return the current schema shape, for example to fold legacy fields\n    // into the modern parameter object.\n    return args;\n  },\n\n  async execute(toolCallId, params, signal, onUpdate, ctx) {\n    // Stream progress\n    onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n\n    return {\n      content: [{ type: \"text\", text: \"Done\" }],\n      details: { result: \"...\" },\n    };\n  },\n\n  // Optional: Custom rendering\n  renderCall(args, theme, context) { ... },\n  renderResult(result, options, theme, context) { ... },\n});\n```\n\n### pi.sendMessage(メッセージ、オプション?)\n\nカスタム メッセージをセッションに挿入します。カスタム メッセージは LLM コンテキストに参加します。 LLM に送信すべきでない永続的な TUI のみのコンテンツの場合は、[`pi.appendEntry()`](#piappendentrycustomtype-data) を [`pi.registerEntryRenderer()`](#piregisterentryrenderercustomtype-renderer) とともに使用します。\n\n```typescript\npi.sendMessage({\n  customType: \"my-extension\",\n  content: \"Message text\",\n  display: true,\n  details: { ... },\n}, {\n  triggerTurn: true,\n  deliverAs: \"steer\",\n});\n```\n\n**オプション:**\n- `deliverAs` - 配信モード:\n  - `\"steer\"` (デフォルト) - ストリーミング中にメッセージをキューに入れます。現在のアシスタント ターンがツール呼び出しの実行を終了した後、次の LLM 呼び出しの前に配信されます。\n  - `\"followUp\"` - エージェントが終了するのを待ちます。エージェントがツールを呼び出さなくなった場合にのみ配信されます。\n  - `\"nextTurn\"` - 次のユーザー プロンプトを待機中です。何も中断したりトリガーしたりしません。\n- `triggerTurn: true` - エージェントがアイドル状態の場合、直ちに LLM 応答をトリガーします。 `\"steer\"` および `\"followUp\"` モードにのみ適用されます (`\"nextTurn\"` では無視されます)。\n\n### pi.sendUserMessage(コンテンツ、オプション?)\n\nユーザーメッセージをエージェントに送信します。カスタム メッセージを送信する `sendMessage()` とは異なり、これはユーザーが入力したかのように表示される実際のユーザー メッセージを送信します。常にターンを誘発します。\n\n```typescript\n// Simple text message\npi.sendUserMessage(\"What is 2+2?\");\n\n// With content array (text + images)\npi.sendUserMessage([\n  { type: \"text\", text: \"Describe this image:\" },\n  { type: \"image\", source: { type: \"base64\", mediaType: \"image/png\", data: \"...\" } },\n]);\n\n// During streaming - must specify delivery mode\npi.sendUserMessage(\"Focus on error handling\", { deliverAs: \"steer\" });\npi.sendUserMessage(\"And then summarize\", { deliverAs: \"followUp\" });\n```\n\n**オプション:**\n- `deliverAs` - エージェントがストリーミングしている場合は必須:\n  - `\"steer\"` - 現在のアシスタント ターンがツール呼び出しの実行を終了した後、配信するメッセージをキューに入れます。\n  - `\"followUp\"` - エージェントがすべてのツールを完了するまで待機します\n\nストリーミングしていない場合、メッセージはすぐに送信され、新しいターンがトリガーされます。 `deliverAs` を使用せずにストリーミングすると、エラーがスローされます。\n\n完全な例については、[send-user-message.ts](../examples/extensions/send-user-message.ts) を参照してください。\n\n### pi.appendEntry(customType, データ?)\n\n拡張データを永続化します。カスタム エントリは LLM コンテキストに参加しません。インタラクティブ モードでは、`pi.registerEntryRenderer()` と組み合わせると、チャット トランスクリプト内でレンダリングすることもできます。\n\n```typescript\npi.appendEntry(\"my-state\", { count: 42 });\npi.appendEntry(\"status-card\", { title: \"Indexed files\", count: 17 });\n\n// Restore on reload\npi.on(\"session_start\", async (_event, ctx) => {\n  for (const entry of ctx.sessionManager.getEntries()) {\n    if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n      // Reconstruct from entry.data\n    }\n  }\n});\n```\n\n### pi.setSessionName(名前)\n\nセッションの表示名を設定します (最初のメッセージの代わりにセッション セレクターに表示されます)。\n\n```typescript\npi.setSessionName(\"Refactor auth module\");\n```\n\n### pi.getSessionName()\n\n現在のセッション名が設定されている場合は、それを取得します。\n\n```typescript\nconst name = pi.getSessionName();\nif (name) {\n  console.log(`Session: ${name}`);\n}\n```\n\n### pi.setLabel(entryId, ラベル)\n\nエントリのラベルを設定またはクリアします。ラベルは、ブックマークとナビゲーション用のユーザー定義のマーカーです (`/tree` セレクターに表示されます)。\n\n```typescript\n// Set a label\npi.setLabel(entryId, \"checkpoint-before-refactor\");\n\n// Clear a label\npi.setLabel(entryId, undefined);\n\n// Read labels via sessionManager\nconst label = ctx.sessionManager.getLabel(entryId);\n```\n\nラベルはセッション内に保持され、再起動しても存続します。これらを使用して、会話ツリー内の重要なポイント (ターン、チェックポイント) をマークします。\n\n### pi.registerCommand(名前, オプション)\n\nコマンドを登録します。\n\n複数の拡張機能が同じコマンド名を登録する場合、pi はそれらすべてを保持し、ロード順に数値呼び出しサフィックス (`/review:1` や `/review:2` など) を割り当てます。\n\n```typescript\npi.registerCommand(\"stats\", {\n  description: \"Show session statistics\",\n  handler: async (args, ctx) => {\n    const count = ctx.sessionManager.getEntries().length;\n    ctx.ui.notify(`${count} entries`, \"info\");\n  }\n});\n```\n\nオプション: `/command...` に引数の自動補完を追加します。\n\n```typescript\nimport type { AutocompleteItem } from \"@earendil-works/pi-tui\";\n\npi.registerCommand(\"deploy\", {\n  description: \"Deploy to an environment\",\n  getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {\n    const envs = [\"dev\", \"staging\", \"prod\"];\n    const items = envs.map((e) => ({ value: e, label: e }));\n    const filtered = items.filter((i) => i.value.startsWith(prefix));\n    return filtered.length > 0 ? filtered : null;\n  },\n  handler: async (args, ctx) => {\n    ctx.ui.notify(`Deploying: ${args}`, \"info\");\n  },\n});\n```\n\n### pi.getCommands()\n\n現在のセッションで `prompt` 経由で呼び出し可能な slash commands を取得します。拡張コマンド、prompt templates、スキルコマンドが含まれます。\nリストは、RPC `get_commands` の順序と一致します。最初に拡張機能、次にテンプレート、次にスキルです。\n\n```typescript\nconst commands = pi.getCommands();\nconst bySource = commands.filter((command) => command.source === \"extension\");\nconst userScoped = commands.filter((command) => command.sourceInfo.scope === \"user\");\n```\n\n各エントリは次のような形状になっています。\n\n```typescript\n{\n  name: string; // Invokable command name without the leading slash. May be suffixed like \"review:1\"\n  description?: string;\n  source: \"extension\" | \"prompt\" | \"skill\";\n  sourceInfo: {\n    path: string;\n    source: string;\n    scope: \"user\" | \"project\" | \"temporary\";\n    origin: \"package\" | \"top-level\";\n    baseDir?: string;\n  };\n}\n```\n\n`sourceInfo` を正規の出所フィールドとして使用します。コマンド名やアドホック パス解析から所有権を推測しないでください。\n\n組み込みの対話型コマンド (`/model` や `/settings` など) はここには含まれません。インタラクティブでのみ処理されます\nモードであり、`prompt` 経由で送信された場合は実行されません。\n\n### pi.registerMessageRenderer(customType, レンダラー)\n\nカスタム メッセージ用のカスタム TUI レンダラーを `customType` に登録します。カスタム メッセージは `pi.sendMessage()` で作成され、LLM コンテキストに参加します。 [Custom UI](#custom-ui)を参照してください。\n\n### pi.registerMarkdownTransformer(トランス)\n\n通常のユーザーテキスト、アシスタントテキスト、思考ブロックのMarkdownのトランスフォーマーを登録します。変圧器は拡張ロード順序で実行され、各変圧器は前の変圧器から返された Markdown を受け取ります。チェーンが終了すると、Pi は組み込みのレンダラーを使用して、変換されたコンテンツをレンダリングします。\n\nトランスフォーマーは、Markdown 文字列と以下のコンテキストを受け取ります。\n\n- `messageType` — `\"user\"`、`\"assistant\"`、または `\"assistant-thinking\"`\n- `isStreaming` — `true` アシスタントの部分的な更新。 `false` ユーザー、完了したアシスタント、および復元されたメッセージ用\n- `availableWidth` — 変換された Markdown コンテンツに使用できる正確な終端列\n\n変換された Markdown を返します。\n\n```typescript\npi.registerMarkdownTransformer((markdown, { messageType, isStreaming }) => {\n  if (isStreaming || messageType === \"assistant-thinking\") return markdown;\n  return markdown.replaceAll(\"-->\", \"→\");\n});\n```\n\n変圧器がスローされると、Pi はそれまでに生成された Markdown を保持し、次の変圧器に進みます。フックは表示専用です。元のメッセージはセッションおよびモデルのコンテキスト内で変更されません。これは、新しいユーザー メッセージ、アシスタントのストリーミング更新、復元されたセッション メッセージ、および端末幅の変更に対して実行されるため、トランスフォーマーは同期性と低コストを維持する必要があります。\n\n### pi.registerEntryRenderer(customType, レンダラー)\n\nカスタム エントリ用のカスタム TUI レンダラーを `customType` に登録します。カスタム エントリは `pi.appendEntry()` で作成され、LLM コンテキストには参加しません。\n\n```typescript\nimport { Box, Text } from \"@earendil-works/pi-tui\";\n\npi.registerEntryRenderer(\"status-card\", (entry, { expanded }, theme) => {\n  const data = entry.data as { title: string; count: number };\n  const box = new Box(1, 1, (text) => theme.bg(\"customMessageBg\", text));\n  box.addChild(new Text(`${theme.bold(data.title)}: ${data.count}`));\n  if (expanded) {\n    box.addChild(new Text(theme.fg(\"dim\", JSON.stringify(data, null, 2))));\n  }\n  return box;\n});\n\npi.appendEntry(\"status-card\", { title: \"Indexed files\", count: 17 });\n```\n\n### pi.registerShortcut(ショートカット、オプション)\n\nキーボードショートカットを登録します。ショートカットの形式と組み込みのキーバインドについては、[keybindings.md](keybindings.md) を参照してください。\n\n```typescript\npi.registerShortcut(\"ctrl+shift+p\", {\n  description: \"Toggle plan mode\",\n  handler: async (ctx) => {\n    ctx.ui.notify(\"Toggled!\");\n  },\n});\n```\n\n### pi.registerFlag(名前, オプション)\n\nCLIフラグを登録します。\n\n```typescript\npi.registerFlag(\"plan\", {\n  description: \"Start in plan mode\",\n  type: \"boolean\",\n  default: false,\n});\n\n// Check value\nif (pi.getFlag(\"plan\")) {\n  // Plan mode enabled\n}\n```\n\n### pi.exec(コマンド、引数、オプション?)\n\nシェルコマンドを実行します。\n\n```typescript\nconst result = await pi.exec(\"git\", [\"status\"], { signal, timeout: 5000 });\n// result.stdout, result.stderr, result.code, result.killed\n```\n\n### pi.getActiveTools() / pi.getAllTools() / pi.setActiveTools(names)\n\nアクティブなツールを管理します。これは、組み込みツールと動的に登録されたツールの両方で機能します。 `pi.getActiveTools()` はアクティブなツール名を `string[]` として返します。 `pi.getAllTools()` は、設定されているすべてのツールのメタデータを返します。\n\n```typescript\nconst active = pi.getActiveTools(); // [\"read\", \"bash\", ...]\nconst all = pi.getAllTools();\n// all = [{\n//   name: \"read\",\n//   description: \"Read file contents...\",\n//   parameters: ...,\n//   promptGuidelines: [\"Use read to examine files instead of cat or sed.\"],\n//   sourceInfo: { path: \"<builtin:read>\", source: \"builtin\", scope: \"temporary\", origin: \"top-level\" }\n// }, ...]\nconst builtinTools = all.filter((t) => t.sourceInfo.source === \"builtin\");\nconst extensionTools = all.filter((t) => t.sourceInfo.source !== \"builtin\" && t.sourceInfo.source !== \"sdk\");\npi.setActiveTools([...new Set([...active, \"my_custom_tool\"])]); // Keep current tools and enable my_custom_tool\npi.setActiveTools([\"read\", \"bash\"]); // Switch to read-only\n```\n\n`pi.getAllTools()` は、`name`、`description`、`parameters`、`promptGuidelines`、`sourceInfo` を返します。\n\n典型的な `sourceInfo.source` 値:\n- `builtin` 組み込みツールの場合\n- `sdk` `createAgentSession({ customTools })` 経由で渡されたツールの場合\n- 拡張機能によって登録されたツールの拡張機能ソース メタデータ\n\n### pi.setModel(モデル)\n\n現在のモデルを設定します。モデルに使用可能な API key がない場合は、`false` を返します。カスタム モデルの構成については、[models.md](models.md) を参照してください。\n\n```typescript\nconst model = ctx.modelRegistry.find(\"anthropic\", \"claude-sonnet-4-5\");\nif (model) {\n  const success = await pi.setModel(model);\n  if (!success) {\n    ctx.ui.notify(\"No API key for this model\", \"error\");\n  }\n}\n```\n\n### pi.getThinkingLevel() / pi.setThinkingLevel(レベル)\n\n思考レベルを取得または設定します。レベルはモデルの能力に固定されます (非推論モデルは常に「オフ」を使用します)。変更すると `thinking_level_select` が発生します。\n\n```typescript\nconst current = pi.getThinkingLevel();  // \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\"\npi.setThinkingLevel(\"high\");\n```\n\n### pi.イベント\n\n拡張機能間の通信用の共有イベント バス:\n\n```typescript\npi.events.on(\"my:event\", (data) => { ... });\npi.events.emit(\"my:event\", { ... });\n```\n\n### pi.registerProvider(名前, 構成)\n\nモデルプロバイダーを動的に登録またはオーバーライドします。プロキシ、カスタム エンドポイント、またはチーム全体のモデル構成に役立ちます。\n\n拡張ファクトリー関数中に行われた呼び出しはキューに入れられ、ランナーが初期化されると適用されます。それ以降に行われた呼び出し (たとえば、ユーザー セットアップ フローに続くコマンド ハンドラーからの呼び出し) は、`/reload` を必要とせずにすぐに有効になります。\n\n動的プロバイダーは `refreshModels` を実装できます。 Pi は、モデルの更新中にこれを呼び出し、返されたリストをプロバイダーを通じて同期的に公開し、正規の資格情報/保存されたカタログ/ネットワーク/信号コンテキストを渡します。拡張機能は、世代チェックされた `context.publish({ persist: entry })` を通じてカタログ メタデータを永続化するかどうかを決定します。 llama.cpp などのライブサーバーはモデルを永続化せずに返すことができます。\n\n`context.signal` は常に具体的な信号であり、プロバイダーのコールバックはそれをブロッキング I/O に渡す必要があります。パブリック `ModelRuntime.refresh()` および `ModelRegistry.refresh()` 呼び出しはオプションのシグナルを受け入れ、省略された場合は無制限になります。延長と申請は独自の期限を選択します。キャンセルにより、プロバイダーが信号を無視した場合でも呼び出し元は待機しなくなりますが、基礎となる作業を停止するには協力が必要です。\n\nネイティブプロバイダ認証、フィルタリング、リフレッシュ、またはストリーム動作を必要とする Extensions は、`@earendil-works/pi-ai` から完全な `Provider` を登録できます。プロバイダーが構成ベースとなり、`models.json` オーバーライドは引き続きその上に適用されます。\n\n```typescript\nimport { createProvider, openAICompletionsApi } from \"@earendil-works/pi-ai\";\n\nconst provider = createProvider({\n  id: \"local-server\",\n  name: \"Local Server\",\n  baseUrl: \"http://localhost:8080/v1\",\n  auth: {\n    apiKey: {\n      name: \"Local server setup\",\n      async login(interaction) {\n        return {\n          type: \"api_key\",\n          key: await interaction.prompt({ type: \"secret\", message: \"API key\" }),\n        };\n      },\n      async resolve({ credential }) {\n        return credential?.key\n          ? { auth: { apiKey: credential.key }, source: \"stored API key\" }\n          : undefined;\n      },\n    },\n  },\n  models: [],\n  api: openAICompletionsApi(),\n});\n\npi.registerProvider(provider);\n\n// Register a new provider with custom models\npi.registerProvider(\"my-proxy\", {\n  name: \"My Proxy\",\n  baseUrl: \"https://proxy.example.com\",\n  apiKey: \"$PROXY_API_KEY\",  // env var reference\n  api: \"anthropic-messages\",\n  models: [\n    {\n      id: \"claude-sonnet-4-20250514\",\n      name: \"Claude 4 Sonnet (proxy)\",\n      reasoning: false,\n      input: [\"text\", \"image\"],\n      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n      contextWindow: 200000,\n      maxTokens: 16384\n    }\n  ]\n});\n\n// Register a live llama.cpp catalog without persisting discovered models\npi.registerProvider(\"llama.cpp\", {\n  baseUrl: \"http://localhost:8080/v1\",\n  apiKey: \"local\",\n  api: \"openai-completions\",\n  async refreshModels({ signal }) {\n    const response = await fetch(\"http://localhost:8080/v1/models\", { signal });\n    const { data } = await response.json();\n    return data.map(({ id }) => ({\n      id,\n      name: id,\n      reasoning: false,\n      input: [\"text\"],\n      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n      contextWindow: 128000,\n      maxTokens: 16384\n    }));\n  }\n});\n\n// Override baseUrl for an existing provider (keeps all models)\npi.registerProvider(\"anthropic\", {\n  baseUrl: \"https://proxy.example.com\"\n});\n\n// Register provider with OAuth support for /login\npi.registerProvider(\"corporate-ai\", {\n  baseUrl: \"https://ai.corp.com\",\n  api: \"openai-responses\",\n  models: [...],\n  oauth: {\n    name: \"Corporate AI (SSO)\",\n    async login(callbacks) {\n      // Custom OAuth flow\n      callbacks.onAuth({ url: \"https://sso.corp.com/...\" });\n      const code = await callbacks.onPrompt({ message: \"Enter code:\" });\n      return { refresh: code, access: code, expires: Date.now() + 3600000 };\n    },\n    async refreshToken(credentials, signal) {\n      signal.throwIfAborted();\n      // Refresh logic\n      return credentials;\n    },\n    getApiKey(credentials) {\n      return credentials.access;\n    }\n  }\n});\n```\n\nオブジェクト フォームは、ネイティブ `auth`、`getModels`、`refreshModels`、`filterModels`、`stream`、`streamSimple` の動作を含む、完全な pi-ai `Provider` を受け入れます。\n\n**従来の構成オプション:**\n- `name` - UI でのプロバイダーの表示名 (`/login` など)。\n- `baseUrl` - API エンドポイント URL。モデルを定義するときに必要です。\n- `apiKey` - API key リテラル、環境補間 (`$ENV_VAR` または `${ENV_VAR}`)、または先行 `!command`。モデルを定義するときに必須です (`oauth` が指定されていない場合)。 `$` は ``apiKey` - API key リテラル、環境補間 (`$ENV_VAR` または `${ENV_VAR}`)、または先行 `!command`。モデルを定義するときに必須です (`oauth` が指定されていない場合)。 `$` は  をエスケープし、`$!` はコマンドの実行をトリガーせずにリテラル `!` をエスケープします。\n- `api` - API タイプ: `\"anthropic-messages\"`、`\"openai-completions\"`、`\"openai-responses\"` など。\n- `headers` - リクエストに含めるカスタムヘッダー。\n- `authHeader` - true の場合、`Authorization: Bearer` ヘッダーが自動的に追加されます。\n- `models` - モデル定義の配列。指定すると、このプロバイダーの既存のモデルがすべて置き換えられます。モデル定義では、`baseUrl` を設定して、そのモデルのプロバイダー エンドポイントをオーバーライドできます。\n- `refreshModels` - 非同期動的検出コールバック。返されたモデルは、拡張機能が提供するモデルを置き換えます。 `context.stored` には、永続化されたプロバイダーのスナップショットが含まれます。更新されたカタログ データが保持される必要がある場合にのみ、世代チェック済み `context.publish({ persist: entry })` を使用してください。 `persist: null` を使用してそのスナップショットを削除します。\n- `oauth` - `/login` サポートのための OAuth プロバイダー構成。プロバイダーを指定すると、ログイン メニューに表示されます。\n- `streamSimple` - 非標準 API のカスタム ストリーミング実装。\n\n高度なトピックについては、[custom-provider.md](custom-provider.md) を参照してください: カスタム ストリーミング API、OAuth の詳細、モデル定義のリファレンス。\n\n### pi.unregisterProvider(名前)\n\n以前に登録したプロバイダーとそのモデルを削除します。プロバイダーによってオーバーライドされた組み込みモデルが復元されます。プロバイダーが登録されていない場合は効果がありません。\n\n`registerProvider` と同様、これは初期ロードフェーズの後に呼び出すとすぐに有効になるため、`/reload` は必要ありません。\n\n```typescript\npi.registerCommand(\"my-setup-teardown\", {\n  description: \"Remove the custom proxy provider\",\n  handler: async (_args, _ctx) => {\n    pi.unregisterProvider(\"my-proxy\");\n  },\n});\n```\n\n## 状態管理\n\n適切な分岐をサポートするには、状態を含む Extensions をツール結果 `details` に保存する必要があります。\n\n```typescript\nexport default function (pi: ExtensionAPI) {\n  let items: string[] = [];\n\n  // Reconstruct state from session\n  pi.on(\"session_start\", async (_event, ctx) => {\n    items = [];\n    for (const entry of ctx.sessionManager.getBranch()) {\n      if (entry.type === \"message\" && entry.message.role === \"toolResult\") {\n        if (entry.message.toolName === \"my_tool\") {\n          items = entry.message.details?.items ?? [];\n        }\n      }\n    }\n  });\n\n  pi.registerTool({\n    name: \"my_tool\",\n    // ...\n    async execute(toolCallId, params, signal, onUpdate, ctx) {\n      items.push(\"new item\");\n      return {\n        content: [{ type: \"text\", text: \"Added\" }],\n        details: { items: [...items] },  // Store for reconstruction\n      };\n    },\n  });\n}\n```\n\n## カスタムツール\n\nLLM が `pi.registerTool()` 経由で呼び出すことができるツールを登録します。ツールはシステム プロンプトに表示され、カスタム レンダリングを行うことができます。\n\nデフォルトのシステム プロンプトの `Available tools` セクションで短い 1 行エントリを入力するには、`promptSnippet` を使用します。省略した場合、カスタム ツールはそのセクションから除外されます。\n\n`promptGuidelines` を使用して、ツール固有の箇条書きをデフォルトのシステム プロンプト `Guidelines` セクションに追加します。これらの箇条書きは、ツールがアクティブな間 (たとえば、`pi.setActiveTools([...])` の後) にのみ含まれます。\n\n**重要:** `promptGuidelines` の箇条書きは、ツール名のプレフィックスやグループ化なしで、`Guidelines` セクションにフラットに追加されます。各ガイドラインでは、参照するツールに名前を付ける必要があります。LLM は「これ」がどのツールを意味するかを判断できないため、「次の場合にこのツールを使用する」は避けてください。代わりに「次の場合に my_tool を使用する」と書きます。\n\n注: 一部のモデルは愚かで、ツール パスの引数に @ プレフィックスが含まれています。組み込みツールは、パスを解決する前に先頭の @ を削除します。カスタム ツールがパスを受け入れる場合は、先頭の @ も正規化してください。\n\nカスタム ツールがファイルを変更する場合は、`withFileMutationQueue()` を使用して、組み込みの `edit` および `write` と同じファイルごとのキューに参加させます。ツール呼び出しはデフォルトで並行して実行されるため、これは重要です。キューがなければ、2 つのツールが同じ古いファイルの内容を読み取り、異なる更新を計算し、最後に書き込まれた方が他方を上書きする可能性があります。\n\n失敗例: カスタム ツールは `foo.ts` を編集しますが、同じアシスタント ターンで組み込みの `edit` も `foo.ts` を変更します。ツールがキューに参加していない場合、両方が元の `foo.ts` を読み取り、別々の変更を適用することができ、それらの変更の 1 つが失われます。\n\n生のユーザー引数ではなく、実際のターゲット ファイル パスを `withFileMutationQueue()` に渡します。まず、`ctx.cwd` またはツールの作業ディレクトリを基準とした絶対パスに解決します。既存のファイルの場合、ヘルパーは `realpath()` を通じて正規化するため、同じファイルのシンボリックリンク エイリアスは 1 つのキューを共有します。新しいファイルの場合は、`realpath()` するものがまだないため、解決された絶対パスに戻ります。\n\n変更ウィンドウ全体をそのターゲット パス上でキューに入れます。これには、最終的な書き込みだけでなく、読み取り、変更、書き込みロジックも含まれます。\n\n```typescript\nimport { withFileMutationQueue } from \"@earendil-works/pi-coding-agent\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, resolve } from \"node:path\";\n\nasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n  const absolutePath = resolve(ctx.cwd, params.path);\n\n  return withFileMutationQueue(absolutePath, async () => {\n    await mkdir(dirname(absolutePath), { recursive: true });\n    const current = await readFile(absolutePath, \"utf8\");\n    const next = current.replace(params.oldText, params.newText);\n    await writeFile(absolutePath, next, \"utf8\");\n\n    return {\n      content: [{ type: \"text\", text: `Updated ${params.path}` }],\n      details: {},\n    };\n  });\n}\n```\n\n### ツールの定義\n\n```typescript\nimport { Type } from \"typebox\";\nimport { StringEnum } from \"@earendil-works/pi-ai\";\nimport { Text } from \"@earendil-works/pi-tui\";\n\npi.registerTool({\n  name: \"my_tool\",\n  label: \"My Tool\",\n  description: \"What this tool does (shown to LLM)\",\n  promptSnippet: \"List or add items in the project todo list\",\n  promptGuidelines: [\n    \"Use my_tool for todo planning instead of direct file edits when the user asks for a task list.\"\n  ],\n  parameters: Type.Object({\n    action: StringEnum([\"list\", \"add\"] as const),  // Use StringEnum for Google compatibility\n    text: Type.Optional(Type.String()),\n  }),\n  prepareArguments(args) {\n    if (!args || typeof args !== \"object\") return args;\n    const input = args as { action?: string; oldAction?: string };\n    if (typeof input.oldAction === \"string\" && input.action === undefined) {\n      return { ...input, action: input.oldAction };\n    }\n    return args;\n  },\n\n  async execute(toolCallId, params, signal, onUpdate, ctx) {\n    // Check for cancellation\n    if (signal?.aborted) {\n      return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n    }\n\n    // Stream progress updates\n    onUpdate?.({\n      content: [{ type: \"text\", text: \"Working...\" }],\n      details: { progress: 50 },\n    });\n\n    // Run commands via pi.exec (captured from extension closure)\n    const result = await pi.exec(\"some-command\", [], { signal });\n\n    // Return result\n    return {\n      content: [{ type: \"text\", text: \"Done\" }],  // Sent to LLM\n      details: { data: result },                   // For rendering & state\n      // usage: nestedModelResponse.usage,          // Optional nested LLM usage\n      // Optional: stop after this tool batch when every finalized tool result\n      // in the batch also returns terminate: true.\n      terminate: true,\n    };\n  },\n\n  // Optional: Custom rendering\n  renderCall(args, theme, context) { ... },\n  renderResult(result, options, theme, context) { ... },\n});\n```\n\n**使用量アカウンティング:** ツールがネストされた LLM 呼び出しを行う場合、それらを組み合わせた `Usage` を `usage` として返します。 Pi はツールの結果にそれを保持し、フッター、`/session`、および RPC セッション合計に含めます。 `tool_result` ハンドラーは、この値を検査または置き換えることができます。\n\n**エラーの通知:** ツールの実行を失敗としてマークするには (結果に `isError: true` を設定し、LLM に報告します)、`execute` からエラーをスローします。戻りオブジェクトにどのようなプロパティを含めるかに関係なく、値を返すとエラー フラグが設定されることはありません。\n\n**早期終了:** `execute()` から `terminate: true` を返し、現在のツール バッチの後に自動フォローアップ LLM 呼び出しをスキップする必要があることを示唆します。これは、すべての最終的なツールの結果としてそのバッチが終了する場合にのみ有効になります。エージェントが最後の構造化出力ツール呼び出しで終了する最小限の例については、[examples/extensions/structured-output.ts](../examples/extensions/structured-output.ts) を参照してください。\n\n```typescript\n// Correct: throw to signal an error\nasync execute(toolCallId, params) {\n  if (!isValid(params.input)) {\n    throw new Error(`Invalid input: ${params.input}`);\n  }\n  return { content: [{ type: \"text\", text: \"OK\" }], details: {} };\n}\n```\n\n**重要:** 文字列列挙には `@earendil-works/pi-ai` から `StringEnum` を使用します。 `Type.Union`/`Type.Literal` は Google の API では機能しません。\n\n**引数の準備:** `prepareArguments(args)` はオプションです。定義されている場合、スキーマ検証の前、および `execute()` の前に実行されます。これを使用して、保存されているツール呼び出し引数が現在のスキーマと一致しなくなった古いセッションを pi が再開するときに、受け入れられた古い入力形状を模倣します。 `parameters` に対して検証するオブジェクトを返します。パブリックスキーマを厳密に保ちます。再開された古いセッションを動作し続けるためだけに、非推奨の互換性フィールドを `parameters` に追加しないでください。\n\n例: 古いセッションには、トップレベルの `oldText` および `newText` を含む `edit` ツール呼び出しが含まれている可能性がありますが、現在のスキーマは `edits: [{ oldText, newText }]` のみを受け入れます。\n\n```typescript\npi.registerTool({\n  name: \"edit\",\n  label: \"Edit\",\n  description: \"Edit a single file using exact text replacement\",\n  parameters: Type.Object({\n    path: Type.String(),\n    edits: Type.Array(\n      Type.Object({\n        oldText: Type.String(),\n        newText: Type.String(),\n      }),\n    ),\n  }),\n  prepareArguments(args) {\n    if (!args || typeof args !== \"object\") return args;\n\n    const input = args as {\n      path?: string;\n      edits?: Array<{ oldText: string; newText: string }>;\n      oldText?: unknown;\n      newText?: unknown;\n    };\n\n    if (typeof input.oldText !== \"string\" || typeof input.newText !== \"string\") {\n      return args;\n    }\n\n    return {\n      ...input,\n      edits: [...(input.edits ?? []), { oldText: input.oldText, newText: input.newText }],\n    };\n  },\n  async execute(toolCallId, params, signal, onUpdate, ctx) {\n    // params now matches the current schema\n    return {\n      content: [{ type: \"text\", text: `Applying ${params.edits.length} edit block(s)` }],\n      details: {},\n    };\n  },\n});\n```\n\n### 組み込みツールのオーバーライド\n\nExtensions は、同じ名前のツールを登録することで、組み込みツール (`read`、`bash`、`edit`、`write`、`grep`、`find`、`ls`) をオーバーライドできます。対話型モードでは、これが発生した場合に警告が表示されます。\n\n```bash\n# Extension's read tool replaces built-in read\npi -e ./tool-override.ts\n```\n\nあるいは、`--no-builtin-tools` を使用して、拡張ツールを有効にしたまま、組み込みツールなしで開始します。\n```bash\n# No built-in tools, only extension tools\npi --no-builtin-tools -e ./my-extension.ts\n```\n\nロギングとアクセス制御で `read` をオーバーライドする完全な例については、[examples/extensions/tool-override.ts](../examples/extensions/tool-override.ts) を参照してください。\n\n**レンダリング:** 組み込みレンダラーの継承はスロットごとに解決されます。実行オーバーライドとレンダリング オーバーライドは独立しています。オーバーライドで `renderCall` が省略された場合は、組み込みの `renderCall` が使用されます。オーバーライドで `renderResult` が省略された場合は、組み込みの `renderResult` が使用されます。オーバーライドで両方を省略した場合、組み込みレンダラが自動的に使用されます (構文の強調表示、差分など)。これにより、UI を再実装することなく、ログ記録やアクセス制御用の組み込みツールをラップできます。\n\n**プロンプト メタデータ:** `promptSnippet` と `promptGuidelines` は組み込みツールから継承されません。オーバーライドでこれらのプロンプト指示を維持する必要がある場合は、それらをオーバーライドで明示的に定義します。\n\n**実装は、`details` 型を含む結果の形状と正確に一致する必要があります**。 UI とセッション ロジックは、レンダリングと状態追跡のためにこれらの形状に依存します。\n\n組み込みツールの実装:\n- [read.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/tools/read.ts) - `ReadToolDetails`\n- [bash.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/tools/bash.ts) - `BashToolDetails`\n- [edit.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/tools/edit.ts)\n- [write.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/tools/write.ts)\n- [grep.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/tools/grep.ts) - `GrepToolDetails`\n- [find.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/tools/find.ts) - `FindToolDetails`\n- [ls.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/tools/ls.ts) - `LsToolDetails`\n\n### リモート実行\n\n組み込みツールは、リモート システム (SSH、コンテナなど) に委任するためのプラグ可能な操作をサポートします。\n\n```typescript\nimport { createReadTool, createBashTool, type ReadOperations } from \"@earendil-works/pi-coding-agent\";\n\n// Create tool with custom operations\nconst remoteRead = createReadTool(cwd, {\n  operations: {\n    readFile: (path) => sshExec(remote, `cat ${path}`),\n    access: (path) => sshExec(remote, `test -r ${path}`).then(() => {}),\n  }\n});\n\n// Register, checking flag at execution time\npi.registerTool({\n  ...remoteRead,\n  async execute(id, params, signal, onUpdate, _ctx) {\n    const ssh = getSshConfig();\n    if (ssh) {\n      const tool = createReadTool(cwd, { operations: createRemoteOps(ssh) });\n      return tool.execute(id, params, signal, onUpdate);\n    }\n    return localRead.execute(id, params, signal, onUpdate);\n  },\n});\n```\n\n**操作インターフェイス:** `ReadOperations`、`WriteOperations`、`EditOperations`、`BashOperations`、`LsOperations`、`GrepOperations`、`FindOperations`\n\n`user_bash` の場合、拡張機能はローカル プロセスの生成、シェル解決、プロセス ツリーの終了を再実装する代わりに、`createLocalBashOperations()` 経由で pi のローカル シェル バックエンドを再利用できます。\n\nbash ツールは、実行前にコマンド、cwd、または env を調整するスポーンフックもサポートしています。\n\n```typescript\nimport { createBashTool } from \"@earendil-works/pi-coding-agent\";\n\nconst bashTool = createBashTool(cwd, {\n  spawnHook: ({ command, cwd, env }) => ({\n    command: `source ~/.profile\\n${command}`,\n    cwd: `/mnt/sandbox${cwd}`,\n    env: { ...env, CI: \"1\" },\n  }),\n});\n```\n\n`createBashTool()` は、現在のセッションを `PI_SESSION_ID`、`PI_SESSION_FILE`、`PI_PROVIDER`、`PI_MODEL`、`PI_REASONING_LEVEL` を介してコマンドに公開します。インジェクションは `spawnHook` より前に行われるため、フックはこれらの値を `env` で受け取り、上記のように既存の環境を展開するときにそれらの値を保持します。`exposeSessionEnvironment: false` を設定して無効にします。\n\n```typescript\nconst bashTool = createBashTool(cwd, {\n  exposeSessionEnvironment: false,\n});\n```\n\n変数のセマンティクスについては、「[Bash tool session environment](environment-variables.md#bash-tool-session-environment)」を参照してください。 `--ssh` フラグを使用した完全な SSH の例については、[examples/extensions/ssh.ts](../examples/extensions/ssh.ts) を参照してください。\n\n### 出力の切り捨て\n\n**ツールは、LLM コンテキストの負荷を避けるために、出力を切り詰める必要があります**。出力が大きいと、次のような問題が発生する可能性があります。\n- コンテキスト オーバーフロー エラー (プロンプトが長すぎます)\n- 圧縮の失敗\n- モデルのパフォーマンスの低下\n\n組み込みの制限は **50KB** (約 10,000 トークン) および **2000 行** のいずれか最初に到達した方です。エクスポートされた切り捨てユーティリティを使用します。\n\n```typescript\nimport {\n  truncateHead,      // Keep first N lines/bytes (good for file reads, search results)\n  truncateTail,      // Keep last N lines/bytes (good for logs, command output)\n  truncateLine,      // Truncate a single line to maxBytes with ellipsis\n  formatSize,        // Human-readable size (e.g., \"50KB\", \"1.5MB\")\n  DEFAULT_MAX_BYTES, // 50KB\n  DEFAULT_MAX_LINES, // 2000\n} from \"@earendil-works/pi-coding-agent\";\n\nasync execute(toolCallId, params, signal, onUpdate, ctx) {\n  const output = await runCommand();\n\n  // Apply truncation\n  const truncation = truncateHead(output, {\n    maxLines: DEFAULT_MAX_LINES,\n    maxBytes: DEFAULT_MAX_BYTES,\n  });\n\n  let result = truncation.content;\n\n  if (truncation.truncated) {\n    // Write full output to temp file\n    const tempFile = writeTempFile(output);\n\n    // Inform the LLM where to find complete output\n    result += `\\n\\n[Output truncated: ${truncation.outputLines} of ${truncation.totalLines} lines`;\n    result += ` (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}).`;\n    result += ` Full output saved to: ${tempFile}]`;\n  }\n\n  return { content: [{ type: \"text\", text: result }] };\n}\n```\n\n**重要なポイント:**\n- 始まりが重要なコンテンツ (検索結果、ファイルの読み取り) には `truncateHead` を使用します。\n- 終わりが重要なコンテンツ (ログ、コマンド出力) には `truncateTail` を使用します。\n- 出力が切り詰められた場合と、完全なバージョンの場所を常に LLM に通知します。\n- ツールの説明に切り捨て制限を文書化します。\n\n`rg` (ripgrep) を適切な切り捨てでラップする完全な例については、[examples/extensions/truncated-tool.ts](../examples/extensions/truncated-tool.ts) を参照してください。\n\n### 複数のツール\n\n1 つの拡張機能で複数のツールを共有状態に登録できます。\n\n```typescript\nexport default function (pi: ExtensionAPI) {\n  let connection = null;\n\n  pi.registerTool({ name: \"db_connect\", ... });\n  pi.registerTool({ name: \"db_query\", ... });\n  pi.registerTool({ name: \"db_close\", ... });\n\n  pi.on(\"session_shutdown\", async () => {\n    connection?.close();\n  });\n}\n```\n\n### カスタムレンダリング\n\nツールは、カスタム TUI 表示用に `renderCall` および `renderResult` を提供できます。完全なコンポーネント API については [tui.md](tui.md) を、ツール行の構成方法については [tool-execution.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/modes/interactive/components/tool-execution.ts) を参照してください。\n\nデフォルトでは、ツールの出力はパディングと背景を処理する `Box` でラップされます。定義された `renderCall` または `renderResult` は `Component` を返す必要があります。スロット レンダラーが定義されていない場合、`tool-execution.ts` はそのスロットに対してフォールバック レンダリングを使用します。\n\nツールがデフォルトの `Box` を使用する代わりに独自のシェルをレンダリングする必要がある場合は、 `renderShell: \"self\"` を設定します。これは、フレームや背景の動作を完全に制御する必要があるツール、たとえば、ツールが安定した後も視覚的に安定した状態を維持する必要がある大きなプレビューなどに便利です。\n\n```typescript\npi.registerTool({\n  name: \"my_tool\",\n  label: \"My Tool\",\n  description: \"Custom shell example\",\n  parameters: Type.Object({}),\n  renderShell: \"self\",\n  async execute() {\n    return { content: [{ type: \"text\", text: \"ok\" }], details: undefined };\n  },\n  renderCall(args, theme, context) {\n    return new Text(theme.fg(\"accent\", \"my custom shell\"), 0, 0);\n  },\n});\n```\n\n`renderCall` と `renderResult` はそれぞれ、次のような `context` オブジェクトを受け取ります。\n- `args` - 現在のツール呼び出しの引数\n- `state` - `renderCall` と `renderResult` にわたる共有行ローカル状態\n- `lastComponent` - そのスロットに対して以前に返されたコンポーネント (存在する場合)\n- `invalidate()` - このツール行の再レンダリングをリクエストします\n- `toolCallId`, `cwd`, `executionStarted`, `argsComplete`, `isPartial`, `expanded`, `showImages`, `isError`\n\nクロススロット共有状態には `context.state` を使用します。レンダリング間で同じコンポーネントを再利用および変更する場合は、返されたコンポーネント インスタンスにスロット ローカル キャッシュを保持します。\n\n#### renderCall\n\nツール呼び出しまたはヘッダーをレンダリングします。\n\n```typescript\nimport { Text } from \"@earendil-works/pi-tui\";\n\nrenderCall(args, theme, context) {\n  const text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n  let content = theme.fg(\"toolTitle\", theme.bold(\"my_tool \"));\n  content += theme.fg(\"muted\", args.action);\n  if (args.text) {\n    content += \" \" + theme.fg(\"dim\", `\"${args.text}\"`);\n  }\n  text.setText(content);\n  return text;\n}\n```\n\n#### renderResult\n\nツールの結果または出力をレンダリングします。\n\n```typescript\nrenderResult(result, { expanded, isPartial }, theme, context) {\n  if (isPartial) {\n    return new Text(theme.fg(\"warning\", \"Processing...\"), 0, 0);\n  }\n\n  if (result.details?.error) {\n    return new Text(theme.fg(\"error\", `Error: ${result.details.error}`), 0, 0);\n  }\n\n  let text = theme.fg(\"success\", \"✓ Done\");\n  if (expanded && result.details?.items) {\n    for (const item of result.details.items) {\n      text += \"\\n  \" + theme.fg(\"dim\", item);\n    }\n  }\n  return new Text(text, 0, 0);\n}\n```\n\nスロットに意図的に表示可能なコンテンツがない場合は、空の `Container` などの空の `Component` を返します。\n\n#### キーバインドのヒント\n\n`keyHint()` を使用して、アクティブなキーバインド設定を考慮したキーバインドのヒントを表示します。\n\n```typescript\nimport { keyHint } from \"@earendil-works/pi-coding-agent\";\n\nrenderResult(result, { expanded }, theme, context) {\n  let text = theme.fg(\"success\", \"✓ Done\");\n  if (!expanded) {\n    text += ` (${keyHint(\"app.tools.expand\", \"to expand\")})`;\n  }\n  return new Text(text, 0, 0);\n}\n```\n\n利用可能な機能:\n- `keyHint(keybinding, description)` - `\"app.tools.expand\"` や `\"tui.select.confirm\"` などの構成されたキーバインド ID をフォーマットします\n- `keyText(keybinding)` - キーバインド ID の未加工の設定済みキー テキストを返します。\n- `rawKeyHint(key, description)` - 生のキー文字列をフォーマットします\n\n名前空間付きキーバインド ID を使用します。\n- コーディング エージェント ID は、`app.tools.expand`、`app.editor.external`、`app.session.rename` など、`app.*` 名前空間を使用します。\n- 共有 TUI ID は、`tui.select.confirm`、`tui.select.cancel`、`tui.input.tab` など、`tui.*` 名前空間を使用します。\n\nキーバインド ID とデフォルトの完全なリストについては、[keybindings.md](keybindings.md) を参照してください。 `keybindings.json` は同じ名前空間 ID を使用します。\n\nカスタムエディターと `ctx.ui.custom()` コンポーネントは、挿入された引数として `keybindings: KeybindingsManager` を受け取ります。 `getKeybindings()` や `setKeybindings()` を呼び出すのではなく、挿入されたマネージャーを直接使用する必要があります。\n\n#### ベストプラクティス\n\n- `Text` とパディング `(0, 0)` を使用します。デフォルトのボックスはパディングを処理します。\n- 複数行のコンテンツには `\\n` を使用します。\n- ストリーミングの進行状況をハンドル `isPartial` で確認します。\n- 詳細については、オンデマンドで`expanded`をサポートしてください。\n- デフォルトのビューをコンパクトに保ちます。\n- 引数を `context.state` にコピーする代わりに、`renderResult` の `context.args` を読み取ります。\n- `context.state` は、呼び出しスロットと結果スロット間で共有する必要があるデータにのみ使用します。\n- 同じコンポーネント インスタンスを適切な場所で更新できる場合は、`context.lastComponent` を再利用します。\n- `renderShell: \"self\"` は、デフォルトのボックス化されたシェルが邪魔になる場合にのみ使用してください。セルフシェル モードでは、ツールは独自のフレーム、パディング、および背景を担当します。\n\n#### 後退する\n\nスロット レンダラーが定義されていない場合、またはスローされる場合:\n- `renderCall`: ツール名を表示します。\n- `renderResult`: `content` からの生のテキストを表示します\n\n### 動的なツールの読み込み\n\nExtensions は、少数の初期セットのみをアクティブにしたまま、多くのツールを登録できます。ツールは、実行中に `pi.setActiveTools()` を使用してさらにツールを追加できます。 Pi は純粋に追加的な変更を検出し、新しく利用可能なツール名をそのツールの結果に記録し、次のモデル要求の前に更新されたアクティブ セットを適用します。\n\nこれはどのモデルでも機能します。ネイティブ遅延読み込みサポートを備えた Models は、安定したプロンプト プレフィックスを保持し、ツール結果の位置に新しい定義を読み込みます。他のモデルでは、以下で説明するフォールバックが使用されます。\n\nライフサイクルは次のとおりです。\n\n1. すべてのツールを `pi.registerTool()` で登録すると、`pi.getAllTools()` に表示されます。\n2. `search_tools` などのローダー ツールをアクティブのままにして、検索可能なツールを非アクティブのままにします。\n3. ローダーの実行中に、`pi.setActiveTools([...currentTools,...matchingTools])` を呼び出します。変更は追加的である必要があります。同じ呼び出しで現在アクティブなツールを削除しないでください。\n4. Pi は、ローダーのツール結果にどのツールが追加されたかを記録します。\n5. 次のモデル応答の前に、Pi は、サポートされている場合はネイティブ遅延読み込みを使用して、サポートされていない場合は通常のアクティブ ツール リストを使用して、追加された定義を公開します。\n\nプロバイダー固有のツール参照を返したり、ローダーを特別な検索ツールとしてマークしたりする必要はありません。アクティブなツールの変更が合図です。 `pi.setActiveTools()` に渡される名前はすでに登録されている必要があります。不明な名前は無視されます。\n\n#### Models ネイティブ遅延読み込みあり\n\n- **人類学**\n  - **Models:** ソネット、オーパス、寓話バージョン 4.5 以降 (Haiku なし)\n  - **ネイティブ表現:** 遅延定義では `defer_loading` を使用します。ロード ポイントは `tool_reference` コンテンツを使用します。\n- **OpenAI**\n  - **Models:** `gpt-5.4` 以降のファミリー\n  - **ネイティブ表現:** Pi は、完了したクライアント `tool_search_call` および `tool_search_output` アイテムをロード ポイントに追加します。\n\n検証済みのカスタム モデルまたはプロキシの場合、`anthropic-messages` の場合は `compat.supportsToolReferences: true`、`openai-responses` および `openai-codex-responses` の場合は `compat.supportsToolSearch: true` を使用してネイティブ処理を有効にできます。エンドポイントとモデルが対応するネイティブ プロトコルを受け入れない限り、これらは無効のままにしておきます。\n\n#### フォールバック動作\n\n他のすべてのモデルとプロバイダーでは、動的アクティベーションは引き続き機能します。 Pi は、通常、次のリクエストで現在アクティブなツールの完全なリストを送信します。モデルは新しくアクティブ化されたツールを呼び出すことができますが、その定義を追加すると、プロバイダーのキャッシュされたプロンプト プレフィックスが無効になる可能性があります。\n\nPi は、ツールのグループを別のグループに置き換える場合など、アクティブ セットが純粋に追加的でない場合にも、この安全なフォールバックを使用します。したがって、ツールの削除は機能しますが、遅延ロードは使用されません。\n\nキャッシュの動作を最適にするには、セッション全体でローダー ツールをアクティブにし、アクティブ セットを置き換えるのではなくツールを追加します。また、`promptSnippet` または `promptGuidelines` でツールをアクティブにすると、システム プロンプトが再構築されることにも注意してください。プロバイダーが遅延スキーマをサポートしている場合でも、システム プロンプトの変更によりプレフィックスが無効になる可能性があります。遅延ロードされるツールは通常、ツール `description` に依存し、アクティブ専用プロンプト メタデータを省略する必要があります。\n\n#### 検索ツールの例\n\n次の拡張機能は 2 つの検索可能なツールを登録し、最初のアクティブ セットからそれらを削除し、ローダーとして `search_tools` のみを保持します。この例では単純なキーワード マッチングを使用していますが、検索の実装では BM25、埋め込み、リモート カタログ、またはプロジェクト固有のルーティングを使用することもできます。\n\n```typescript\nimport type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { Type } from \"typebox\";\n\nconst SEARCHABLE_TOOL_NAMES = new Set([\"lookup_weather\", \"search_issues\"]);\n\nexport default function (pi: ExtensionAPI) {\n  pi.registerTool({\n    name: \"lookup_weather\",\n    label: \"Lookup Weather\",\n    description: \"Look up the current weather for a city\",\n    parameters: Type.Object({ city: Type.String() }),\n    async execute(_toolCallId, params) {\n      return {\n        content: [{ type: \"text\", text: `Weather for ${params.city}: sunny` }],\n        details: {},\n      };\n    },\n  });\n\n  pi.registerTool({\n    name: \"search_issues\",\n    label: \"Search Issues\",\n    description: \"Search project issues by keyword\",\n    parameters: Type.Object({ query: Type.String() }),\n    async execute(_toolCallId, params) {\n      return {\n        content: [{ type: \"text\", text: `No open issues matching ${params.query}` }],\n        details: {},\n      };\n    },\n  });\n\n  pi.registerTool({\n    name: \"search_tools\",\n    label: \"Search Tools\",\n    description: \"Search for and enable tools relevant to a task\",\n    promptSnippet: \"Search for additional tools when the active tools cannot perform the task\",\n    promptGuidelines: [\n      \"Use search_tools when a task requires a capability that is not currently available.\",\n    ],\n    parameters: Type.Object({\n      query: Type.String({ description: \"Capability or task to search for\" }),\n      limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 10 })),\n    }),\n    async execute(_toolCallId, params) {\n      const terms = params.query.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);\n      const matches = pi.getAllTools()\n        .filter((tool) => SEARCHABLE_TOOL_NAMES.has(tool.name))\n        .map((tool) => ({\n          tool,\n          score: terms.reduce(\n            (score, term) =>\n              score + (`${tool.name} ${tool.description}`.toLowerCase().includes(term) ? 1 : 0),\n            0,\n          ),\n        }))\n        .filter((match) => match.score > 0)\n        .sort((a, b) => b.score - a.score)\n        .slice(0, params.limit ?? 3)\n        .map((match) => match.tool.name);\n\n      if (matches.length === 0) {\n        return {\n          content: [{ type: \"text\", text: `No tools found for: ${params.query}` }],\n          details: { matches: [] },\n        };\n      }\n\n      const active = pi.getActiveTools();\n      const added = matches.filter((name) => !active.includes(name));\n      pi.setActiveTools([...new Set([...active, ...added])]);\n\n      return {\n        content: [{\n          type: \"text\",\n          text: added.length > 0\n            ? `Loaded tools: ${added.join(\", \")}`\n            : `Matching tools already active: ${matches.join(\", \")}`,\n        }],\n        details: { matches, added },\n      };\n    },\n  });\n\n  pi.on(\"session_start\", () => {\n    // Keep searchable tools registered but initially inactive. Preserve built-ins\n    // and tools owned by other extensions, and keep the loader itself active.\n    const initialTools = pi.getActiveTools().filter(\n      (name) => !SEARCHABLE_TOOL_NAMES.has(name),\n    );\n    pi.setActiveTools([...new Set([...initialTools, \"search_tools\"])]);\n  });\n}\n```\n\n`search_tools` が一致を追加すると、モデルは直後のリクエストでその定義を受け取ります。ネイティブ対応モデルでは、最初のツール スキーマ プレフィックスを変更せずに、定義は検索結果の後にアンカーされます。他のモデルでは、同じ次のリクエストの通常のツール リストに表示されます。\n\n## カスタムUI\n\nExtensions は、`ctx.ui` メソッドを介してユーザーと対話し、メッセージ/ツールのレンダリング方法をカスタマイズできます。\n\n**カスタム コンポーネントについては、以下のコピー＆ペースト パターンが記載されている [tui.md](tui.md)** を参照してください。\n- 選択ダイアログ (SelectList)\n- キャンセルを伴う非同期操作 (BorderLoader)\n- 設定切り替え (SettingsList)\n- ステータスインジケーター (setStatus)\n- ストリーミング中の動作メッセージ、可視性、インジケーター (`setWorkingMessage`、`setWorkingVisible`、`setWorkingIndicator`)\n- エディターの上/下のウィジェット (setWidget)\n- 組み込みのスラッシュ/パス補完の上に重ねられたオートコンプリート プロバイダー (addAutocompleteProvider)\n- カスタムフッター (setFooter)\n\n### ダイアログ\n\n```typescript\n// Select from options\nconst choice = await ctx.ui.select(\"Pick one:\", [\"A\", \"B\", \"C\"]);\n\n// Confirm dialog\nconst ok = await ctx.ui.confirm(\"Delete?\", \"This cannot be undone\");\n\n// Text input\nconst name = await ctx.ui.input(\"Name:\", \"placeholder\");\n\n// Multi-line editor\nconst text = await ctx.ui.editor(\"Edit:\", \"prefilled text\");\n\n// Notification (non-blocking)\nctx.ui.notify(\"Done!\", \"info\");  // \"info\" | \"warning\" | \"error\"\n```\n\n#### カウントダウン付きの時間指定ダイアログ\n\nダイアログは、ライブカウントダウン表示で自動的に閉じる `timeout` オプションをサポートしています。\n\n```typescript\n// Dialog shows \"Title (5s)\" → \"Title (4s)\" → ... → auto-dismisses at 0\nconst confirmed = await ctx.ui.confirm(\n  \"Timed Confirmation\",\n  \"This dialog will auto-cancel in 5 seconds. Confirm?\",\n  { timeout: 5000 }\n);\n\nif (confirmed) {\n  // User confirmed\n} else {\n  // User cancelled or timed out\n}\n```\n\n**タイムアウト時の戻り値:**\n- `select()` は `undefined` を返します\n- `confirm()` は `false` を返します\n- `input()` は `undefined` を返します\n\n#### AbortSignal による手動終了\n\nさらに制御するには (例: タイムアウトとユーザーのキャンセルを区別するため)、`AbortSignal` を使用します。\n\n```typescript\nconst controller = new AbortController();\nconst timeoutId = setTimeout(() => controller.abort(), 5000);\n\nconst confirmed = await ctx.ui.confirm(\n  \"Timed Confirmation\",\n  \"This dialog will auto-cancel in 5 seconds. Confirm?\",\n  { signal: controller.signal }\n);\n\nclearTimeout(timeoutId);\n\nif (confirmed) {\n  // User confirmed\n} else if (controller.signal.aborted) {\n  // Dialog timed out\n} else {\n  // User cancelled (pressed Escape or selected \"No\")\n}\n```\n\n完全な例については、[examples/extensions/timed-confirm.ts](../examples/extensions/timed-confirm.ts) を参照してください。\n\n### ウィジェット、ステータス、フッター\n\n```typescript\n// Status in footer (persistent until cleared)\nctx.ui.setStatus(\"my-ext\", \"Processing...\");\nctx.ui.setStatus(\"my-ext\", undefined);  // Clear\n\n// Working loader (shown during streaming)\nctx.ui.setWorkingMessage(\"Thinking deeply...\");\nctx.ui.setWorkingMessage();  // Restore default\nctx.ui.setWorkingVisible(false);  // Hide the built-in working loader row entirely\nctx.ui.setWorkingVisible(true);   // Show the built-in working loader row\n\n// Working indicator (shown during streaming)\nctx.ui.setWorkingIndicator({ frames: [ctx.ui.theme.fg(\"accent\", \"●\")] });  // Static dot\nctx.ui.setWorkingIndicator({\n  frames: [\n    ctx.ui.theme.fg(\"dim\", \"·\"),\n    ctx.ui.theme.fg(\"muted\", \"•\"),\n    ctx.ui.theme.fg(\"accent\", \"●\"),\n    ctx.ui.theme.fg(\"muted\", \"•\"),\n  ],\n  intervalMs: 120,\n});\nctx.ui.setWorkingIndicator({ frames: [] });  // Hide indicator\nctx.ui.setWorkingIndicator();  // Restore default spinner\n\n// Widget above editor (default)\nctx.ui.setWidget(\"my-widget\", [\"Line 1\", \"Line 2\"]);\n// Widget below editor\nctx.ui.setWidget(\"my-widget\", [\"Line 1\", \"Line 2\"], { placement: \"belowEditor\" });\nctx.ui.setWidget(\"my-widget\", (tui, theme) => new Text(theme.fg(\"accent\", \"Custom\"), 0, 0));\nctx.ui.setWidget(\"my-widget\", undefined);  // Clear\n\n// Custom footer (replaces built-in footer entirely)\nctx.ui.setFooter((tui, theme) => ({\n  render(width) { return [theme.fg(\"dim\", \"Custom footer\")]; },\n  invalidate() {},\n}));\nctx.ui.setFooter(undefined);  // Restore built-in footer\n\n// Terminal title\nctx.ui.setTitle(\"pi - my-project\");\n\n// Editor text\nctx.ui.setEditorText(\"Prefill text\");\nconst current = ctx.ui.getEditorText();\n\n// Paste into editor (triggers paste handling, including collapse for large content)\nctx.ui.pasteToEditor(\"pasted content\");\n\n// Stack custom autocomplete behavior on top of the built-in provider\nctx.ui.addAutocompleteProvider((current) => ({\n  triggerCharacters: [\"#\"],\n  async getSuggestions(lines, line, col, options) {\n    const beforeCursor = (lines[line] ?? \"\").slice(0, col);\n    const match = beforeCursor.match(/(?:^|[ \\t])#([^\\s#]*)$/);\n    if (!match) {\n      return current.getSuggestions(lines, line, col, options);\n    }\n\n    return {\n      prefix: `#${match[1] ?? \"\"}`,\n      items: [{ value: \"#2983\", label: \"#2983\", description: \"Extension API for autocomplete\" }],\n    };\n  },\n  applyCompletion(lines, line, col, item, prefix) {\n    return current.applyCompletion(lines, line, col, item, prefix);\n  },\n  shouldTriggerFileCompletion(lines, line, col) {\n    return current.shouldTriggerFileCompletion?.(lines, line, col) ?? true;\n  },\n}));\n\n// Tool output expansion\nconst wasExpanded = ctx.ui.getToolsExpanded();\nctx.ui.setToolsExpanded(true);\nctx.ui.setToolsExpanded(wasExpanded);\n\n// Custom editor (vim mode, emacs mode, etc.)\nctx.ui.setEditorComponent((tui, theme, keybindings) => new VimEditor(tui, theme, keybindings));\nconst currentEditor = ctx.ui.getEditorComponent();\nctx.ui.setEditorComponent((tui, theme, keybindings) =>\n  new WrappedEditor(tui, theme, keybindings, currentEditor?.(tui, theme, keybindings))\n);\nctx.ui.setEditorComponent(undefined);  // Restore default editor\n\n// Theme management (see themes.md for creating themes)\nconst themes = ctx.ui.getAllThemes();  // [{ name: \"dark\", path: \"/...\" | undefined }, ...]\nconst lightTheme = ctx.ui.getTheme(\"light\");  // Load without switching\nconst result = ctx.ui.setTheme(\"light\");  // Switch by name\nif (!result.success) {\n  ctx.ui.notify(`Failed: ${result.error}`, \"error\");\n}\nctx.ui.setTheme(lightTheme!);  // Or switch by Theme object\nctx.ui.theme.fg(\"accent\", \"styled text\");  // Access current theme\n```\n\nカスタムの作業インジケーター フレームはそのままレンダリングされます。色が必要な場合は、`ctx.ui.theme.fg(...)` などを使用して、自分でフレーム文字列に色を追加します。\n\n### オートコンプリート Providers\n\n`ctx.ui.addAutocompleteProvider()` を使用して、組み込みのスラッシュ コマンドとパス プロバイダーの上にカスタム オートコンプリート ロジックをスタックします。 ``ctx.ui.addAutocompleteProvider()` を使用して、組み込みのスラッシュ コマンドとパス プロバイダーの上にカスタム オートコンプリート ロジックをスタックします。  などのカスタム ナチュラル トリガーには `triggerCharacters` を設定します。\n\n典型的なパターン:\n\n- カーソルの前のテキストを検査する\n- 拡張機能固有の構文が一致する場合に独自の提案を返す\n- それ以外の場合は `current.getSuggestions(...)` に委任します\n- カスタム挿入動作が必要な場合を除き、デリゲート `applyCompletion(...)`\n\n```typescript\npi.on(\"session_start\", (_event, ctx) => {\n  ctx.ui.addAutocompleteProvider((current) => ({\n    triggerCharacters: [\"#\"],\n    async getSuggestions(lines, cursorLine, cursorCol, options) {\n      const line = lines[cursorLine] ?? \"\";\n      const beforeCursor = line.slice(0, cursorCol);\n      const match = beforeCursor.match(/(?:^|[ \\t])#([^\\s#]*)$/);\n      if (!match) {\n        return current.getSuggestions(lines, cursorLine, cursorCol, options);\n      }\n\n      return {\n        prefix: `#${match[1] ?? \"\"}`,\n        items: [\n          { value: \"#2983\", label: \"#2983\", description: \"Extension API for registering custom @ autocomplete providers\" },\n          { value: \"#2753\", label: \"#2753\", description: \"Reload stale resource settings\" },\n        ],\n      };\n    },\n\n    applyCompletion(lines, cursorLine, cursorCol, item, prefix) {\n      return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);\n    },\n\n    shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {\n      return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;\n    },\n  }));\n});\n```\n\n`gh issue list` で最新のオープン GitHub 問題をプリロードし、`#...` を高速に完了するためにローカルでフィルタリングする完全な例については、[github-issue-autocomplete.ts](../examples/extensions/github-issue-autocomplete.ts) を参照してください。 GitHub CLI (`gh`) と GitHub リポジトリのチェックアウトが必要です。\n\n### カスタムコンポーネント\n\n複雑な UI の場合は、`ctx.ui.custom()` を使用します。これにより、`done()` が呼び出されるまで、エディターがコンポーネントに一時的に置き換えられます。\n\n```typescript\nimport { Text, Component } from \"@earendil-works/pi-tui\";\n\nconst result = await ctx.ui.custom<boolean>((tui, theme, keybindings, done) => {\n  const text = new Text(\"Press Enter to confirm, Escape to cancel\", 1, 1);\n\n  text.onKey = (key) => {\n    if (key === \"return\") done(true);\n    if (key === \"escape\") done(false);\n    return true;\n  };\n\n  return text;\n});\n\nif (result) {\n  // User pressed Enter\n}\n```\n\nコールバックは以下を受け取ります。\n- `tui` - TUI インスタンス (画面サイズ、フォーカス管理用)\n- `theme` - 現在のスタイリングのテーマ\n- `keybindings` - アプリのキーバインドマネージャー (ショートカットの確認用)\n- `done(value)` - コンポーネントを閉じて値を返す呼び出し\n\n完全なコンポーネント API については、[tui.md](tui.md) を参照してください。\n\n#### オーバーレイ モード (実験的)\n\n`{ overlay: true }` を渡すと、画面をクリアせずにコンポーネントを既存のコンテンツの上にフローティング モーダルとしてレンダリングします。\n\n```typescript\nconst result = await ctx.ui.custom<string | null>(\n  (tui, theme, keybindings, done) => new MyOverlayComponent({ onClose: done }),\n  { overlay: true }\n);\n```\n\n高度な配置 (アンカー、マージン、パーセンテージ、応答性の高い可視性) の場合は、`overlayOptions` を渡します。 `onHandle` を使用して、プログラムでフォーカスまたは可視性を制御します。\n\n```typescript\nconst result = await ctx.ui.custom<string | null>(\n  (tui, theme, keybindings, done) => new MyOverlayComponent({ onClose: done }),\n  {\n    overlay: true,\n    overlayOptions: { anchor: \"top-right\", width: \"50%\", margin: 2 },\n    onHandle: (handle) => {\n      handle.focus(); // focus this overlay and bring it to the visual front\n      // handle.unfocus({ target: editorComponent }); // release input to a specific component\n      // handle.setHidden(true/false); // toggle visibility\n      // handle.hide(); // permanently remove\n    }\n  }\n);\n```\n\nフォーカスされた表示オーバーレイは、一時的な非オーバーレイ カスタム UI が閉じた後に入力を再利用できます。オーバーレイが表示されている間、別のコンポーネントが入力を維持するように意図的にしたい場合は、`handle.unfocus({ target })` を呼び出します。 `{ target: null }` を渡すと、別のコンポーネントにフォーカスすることなくオーバーレイが解放されます。\n\n完全な `OverlayOptions` および `OverlayHandle` API および [overlay-qa-tests.ts](../examples/extensions/overlay-qa-tests.ts) の例については、[tui.md](tui.md) を参照してください。\n\n### カスタムエディター\n\nメインの入力エディタをカスタム実装 (vim モード、emacs モードなど) に置き換えます。\n\n```typescript\nimport { CustomEditor, type ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { matchesKey } from \"@earendil-works/pi-tui\";\n\nclass VimEditor extends CustomEditor {\n  private mode: \"normal\" | \"insert\" = \"insert\";\n\n  handleInput(data: string): void {\n    if (matchesKey(data, \"escape\") && this.mode === \"insert\") {\n      this.mode = \"normal\";\n      return;\n    }\n    if (this.mode === \"normal\" && data === \"i\") {\n      this.mode = \"insert\";\n      return;\n    }\n    super.handleInput(data);  // App keybindings + text editing\n  }\n}\n\nexport default function (pi: ExtensionAPI) {\n  pi.on(\"session_start\", (_event, ctx) => {\n    ctx.ui.setEditorComponent((tui, theme, keybindings) =>\n      new VimEditor(tui, theme, keybindings)\n    );\n  });\n}\n```\n\n**重要なポイント:**\n- `CustomEditor` (ベース `Editor` ではない) を拡張してアプリのキーバインドを取得します (エスケープして中止、Ctrl+D、モデル切り替え)\n- 扱っていない鍵については `super.handleInput(data)` までお電話ください。\n- Factory はアプリから `tui`、`theme`、`keybindings` を受け取ります\n- `setEditorComponent()` の前に `ctx.ui.getEditorComponent()` を使用して、以前に設定したカスタム エディターをラップします。\n- `undefined` を渡してデフォルトに戻します: `ctx.ui.setEditorComponent(undefined)`\n\nすでにエディターを置き換えている別の拡張機能を使用して作成するには、自分のファクトリーを設定する前に、以前のファクトリーをキャプチャーします。\n\n```typescript\nconst previous = ctx.ui.getEditorComponent();\nctx.ui.setEditorComponent((tui, theme, keybindings) =>\n  new MyEditor(tui, theme, keybindings, { base: previous?.(tui, theme, keybindings) })\n);\n```\n\nモードインジケーターを使用した完全な例については、[tui.md](tui.md) パターン 7 を参照してください。\n\n### メッセージとエントリのレンダリング\n\n`customType` でメッセージのカスタム レンダラーを登録します。 LLM コンテキストに参加する必要があるコンテンツにはメッセージ レンダラーを使用します。\n\n```typescript\nimport { Text } from \"@earendil-works/pi-tui\";\n\npi.registerMessageRenderer(\"my-extension\", (message, options, theme) => {\n  const { expanded, outputPad } = options;\n  let text = theme.fg(\"accent\", `[${message.customType}] `);\n  text += message.content;\n\n  if (expanded && message.details) {\n    text += \"\\n\" + theme.fg(\"dim\", JSON.stringify(message.details, null, 2));\n  }\n\n  return new Text(text, outputPad, 0);\n});\n```\n\nメッセージは `pi.sendMessage()` 経由で送信されます:\n\n```typescript\npi.sendMessage({\n  customType: \"my-extension\",  // Matches registerMessageRenderer\n  content: \"Status update\",\n  display: true,               // Show in TUI\n  details: { ... },            // Available in renderer\n});\n```\n\nLLM に送信すべきではない TUI のみのコンテンツの場合は、代わりにカスタム エントリをレンダリングします。\n\n```typescript\npi.registerEntryRenderer(\"my-card\", (entry, options, theme) => {\n  return new Text(theme.fg(\"accent\", JSON.stringify(entry.data)));\n});\n\npi.appendEntry(\"my-card\", { status: \"done\" });\n```\n\n### テーマカラー\n\nすべてのレンダリング関数は `theme` オブジェクトを受け取ります。カスタム テーマとフルカラー パレットの作成については、[themes.md](themes.md) を参照してください。\n\n```typescript\n// Foreground colors\ntheme.fg(\"toolTitle\", text)   // Tool names\ntheme.fg(\"accent\", text)      // Highlights\ntheme.fg(\"success\", text)     // Success (green)\ntheme.fg(\"error\", text)       // Errors (red)\ntheme.fg(\"warning\", text)     // Warnings (yellow)\ntheme.fg(\"muted\", text)       // Secondary text\ntheme.fg(\"dim\", text)         // Tertiary text\n\n// Text styles\ntheme.bold(text)\ntheme.italic(text)\ntheme.strikethrough(text)\n```\n\nカスタム ツール レンダラーでの構文強調表示の場合:\n\n```typescript\nimport { highlightCode, getLanguageFromPath } from \"@earendil-works/pi-coding-agent\";\n\n// Highlight code with explicit language\nconst highlighted = highlightCode(\"const x = 1;\", \"typescript\", theme);\n\n// Auto-detect language from file path\nconst lang = getLanguageFromPath(\"/path/to/file.rs\");  // \"rust\"\nconst highlighted = highlightCode(code, lang, theme);\n```\n\n## エラー処理\n\n- 拡張エラーがログに記録され、エージェントは続行します\n- `tool_call` エラーによりツールがブロックされる (フェールセーフ)\n- ツール `execute` エラーはスローによって通知する必要があります。スローされたエラーが捕捉され、`isError: true` で LLM に報告され、実行が続行されます。\n\n## モードの動作\n\n| モード | `ctx.mode` | `ctx.hasUI` | 注意事項 |\n|------|------------|-------------|-------|\n| 相互の作用 | `\"tui\"` | `true` | 端末レンダリングを備えた完全な TUI |\n| RPC (`--mode rpc`) | `\"rpc\"` | `true` | JSON プロトコルによるダイアログと通知。 `custom()` は `undefined` を返します。 [rpc.md](rpc.md) を参照 |\n| JSON (`--mode json`) | `\"json\"` | `false` | stdout へのイベント ストリーム。 UI メソッドは何も操作しません |\n| 印刷 (`-p`) | `\"print\"` | `false` | Extensions 実行してもプロンプトが表示されない |\n\nTUI 固有の機能 (`custom()`、コンポーネント ファクトリ、端末入力) の前に `ctx.mode === \"tui\"` を使用します。 TUI と RPC の両方のモードで機能するダイアログと通知メソッドの前に `ctx.hasUI` を使用します。\n\n## 例 参考資料\n\nすべての例は [examples/extensions/](../examples/extensions/) にあります。\n\n| 例 | 説明 | キーAPI |\n|---------|-------------|----------|\n| **ツール** |  |  |\n| `hello.ts` | 最小限のツール登録 | `registerTool` |\n| `question.ts` | ユーザーインタラクションを備えたツール | `registerTool`, `ui.select` |\n| `questionnaire.ts` | マルチステップウィザードツール | `registerTool`, `ui.custom` |\n| `todo.ts` | 永続性のあるステートフル ツール | `registerTool`、`appendEntry`、`renderResult`、セッションイベント |\n| `dynamic-tools.ts` | 起動後およびコマンド中にツールを登録します | `registerTool`, `session_start`, `registerCommand` |\n| `structured-output.ts` | `terminate: true` による最終的な構造化出力ツール | `registerTool`、ツールの結果を終了します |\n| `truncated-tool.ts` | 出力の切り捨ての例 | `registerTool`, `truncateHead` |\n| `tool-override.ts` | 組み込み読み取りツールをオーバーライドする | `registerTool` (内蔵と同じ名前) |\n| **コマンド** |  |  |\n| `pirate.ts` | ターンごとにシステムプロンプトを変更する | `registerCommand`, `before_agent_start` |\n| `summarize.ts` | 会話要約コマンド | `registerCommand`, `ui.custom` |\n| `handoff.ts` | クロスプロバイダーモデルのハンドオフ | `registerCommand`, `ui.editor`, `ui.custom` |\n| `qna.ts` | カスタム UI による Q&A | `registerCommand`, `ui.custom`, `setEditorText` |\n| `send-user-message.ts` | ユーザーメッセージを挿入する | `registerCommand`, `sendUserMessage` |\n| `reload-runtime.ts` | リロード コマンドと LLM ツールのハンドオフ | `registerCommand`, `ctx.reload()`, `sendUserMessage` |\n| `shutdown-command.ts` | 正常なシャットダウン コマンド | `registerCommand`, `shutdown()` |\n| **イベントとゲート** |  |  |\n| `permission-gate.ts` | 危険なコマンドをブロックする | `on(\"tool_call\")`, `ui.confirm` |\n| `project-trust.ts` | ユーザー/グローバルまたは CLI 拡張機能からのプロジェクトの信頼を決定または延期する | `on(\"project_trust\")`、信頼 UI、必要な信頼結果 |\n| `protected-paths.ts` | 特定のパスへの書き込みをブロックする | `on(\"tool_call\")` |\n| `confirm-destructive.ts` | セッションの変更を確認する | `on(\"session_before_switch\")`, `on(\"session_before_fork\")` |\n| `dirty-repo-guard.ts` | ダーティな git リポジトリについて警告する | `on(\"session_before_*\")`, `exec` |\n| `input-transform.ts` | ユーザー入力を変換する | `on(\"input\")` |\n| `input-transform-streaming.ts` | ストリーミング対応の入力変換 | `on(\"input\")`, `streamingBehavior` |\n| `model-status.ts` | 機種変更へのReact | `on(\"model_select\")`, `setStatus` |\n| `provider-payload.ts` | ペイロードとプロバイダー応答ヘッダーを検査する | `on(\"before_provider_request\")`, `on(\"after_provider_response\")` |\n| `system-prompt-header.ts` | システムプロンプト情報を表示する | `on(\"agent_start\")`, `getSystemPrompt` |\n| `claude-rules.ts` | ファイルからルールをロードする | `on(\"session_start\")`, `on(\"before_agent_start\")` |\n| `prompt-customizer.ts` | `systemPromptOptions` を使用してコンテキスト認識ツール ガイダンスを追加します | `on(\"before_agent_start\")`, `BuildSystemPromptOptions` |\n| `file-trigger.ts` | ファイルウォッチャーがメッセージをトリガーする | `sendMessage` |\n| **コンパクションとセッション** |  |  |\n| `custom-compaction.ts` | カスタム圧縮の概要 | `on(\"session_before_compact\")` |\n| `trigger-compact.ts` | 圧縮を手動でトリガーする | `compact()` |\n| `git-checkpoint.ts` | Git ターン時に隠します | `on(\"turn_start\")`, `on(\"session_before_fork\")`, `exec` |\n| `git-merge-and-resolve.ts` | 競合をフェッチ、マージ、解決する | `on(\"agent_end\")`, `exec`, `sendUserMessage` |\n| `auto-commit-on-exit.ts` | シャットダウン時にコミットする | `on(\"session_shutdown\")`, `exec` |\n| **UI コンポーネント** |  |  |\n| `status-line.ts` | フッターステータスインジケーター | `setStatus`、セッションイベント |\n| `working-indicator.ts` | ストリーミング動作インジケーターをカスタマイズする | `setWorkingIndicator`, `registerCommand` |\n| `github-issue-autocomplete.ts` | `gh issue list` から最近の未解決の問題をプリロードすることで、組み込みのオートコンプリートに加えて `#1234` の問題の完了を追加します | `addAutocompleteProvider`, `on(\"session_start\")`, `exec` |\n| `custom-footer.ts` | フッターを完全に置き換える | `registerCommand`, `setFooter` |\n| `custom-header.ts` | 起動ヘッダーを置き換える | `on(\"session_start\")`, `setHeader` |\n| `modal-editor.ts` | Vim スタイルのモーダルエディター | `setEditorComponent`, `CustomEditor` |\n| `rainbow-editor.ts` | カスタムエディターのスタイル設定 | `setEditorComponent` |\n| `widget-placement.ts` | エディターの上/下のウィジェット | `setWidget` |\n| `overlay-test.ts` | オーバーレイコンポーネント | `ui.custom` オーバーレイ オプションあり |\n| `overlay-qa-tests.ts` | 包括的なオーバーレイ テスト | `ui.custom`、すべてのオーバーレイ オプション |\n| `notify.ts` | 簡単な通知 | `ui.notify` |\n| `timed-confirm.ts` | タイムアウトのあるダイアログ | `ui.confirm` タイムアウト/シグナルあり |\n| `mac-system-theme.ts` | テーマの自動切り替え | `setTheme`, `exec` |\n| **複雑 Extensions** |  |  |\n| `plan-mode/` | フルプランモードの実装 | すべてのイベント タイプ、`registerCommand`、`registerShortcut`、`registerFlag`、`setStatus`、`setWidget`、`sendMessage`、`setActiveTools` |\n| `preset.ts` | 保存可能なプリセット (モデル、ツール、思考) | `registerCommand`, `registerShortcut`, `registerFlag`, `setModel`, `setActiveTools`, `setThinkingLevel`, `appendEntry` |\n| `tools.ts` | ツールのオン/オフを切り替える UI | `registerCommand`、`setActiveTools`、`SettingsList`、セッションイベント |\n| **リモートとサンドボックス** |  |  |\n| `ssh.ts` | SSH リモート実行 | `registerFlag`、`on(\"user_bash\")`、`on(\"before_agent_start\")`、ツール操作 |\n| `interactive-shell.ts` | 永続的なシェルセッション | `on(\"user_bash\")` |\n| `sandbox/` | サンドボックスツールの実行 | ツールの操作 |\n| `gondolin/` | 組み込みツールと `!` コマンドを Gondolin マイクロ VM にルーティングします | ツール操作、組み込みツールオーバーライド、`on(\"user_bash\")` |\n| `subagent/` | サブエージェントを生成する | `registerTool`, `exec` |\n| **ゲーム** |  |  |\n| `snake.ts` | ヘビゲーム | `registerCommand`、`ui.custom`、キーボードの処理 |\n| `space-invaders.ts` | スペースインベーダーゲーム | `registerCommand`, `ui.custom` |\n| `doom-overlay/` | オーバーレイのドゥーム | `ui.custom` オーバーレイ付き |\n| **Providers** |  |  |\n| `custom-provider-anthropic/` | カスタム人類プロキシ | `registerProvider` |\n| `custom-provider-gitlab-duo/` | GitLab Duo の統合 | `registerProvider` と OAuth |\n| **メッセージとコミュニケーション** |  |  |\n| `message-renderer.ts` | カスタムメッセージのレンダリング | `registerMessageRenderer`, `sendMessage` |\n| `entry-renderer.ts` | TUI のみのカスタム エントリ レンダリング | `registerEntryRenderer`, `appendEntry` |\n| `event-bus.ts` | 拡張間イベント | `pi.events` |\n| **セッションメタデータ** |  |  |\n| `session-name.ts` | セレクターのセッションに名前を付ける | `setSessionName`, `getSessionName` |\n| `bookmark.ts` | /tree のブックマーク エントリ | `setLabel` |\n| **その他** |  |  |\n| `inline-bash.ts` | ツール呼び出しのインライン bash | `on(\"tool_call\")` |\n| `bash-spawn-hook.ts` | 実行前にbash command、cwd、env を調整してください | `createBashTool`, `spawnHook` |\n| `with-deps/` | npm 依存関係のある拡張機能 | `package.json`のパッケージ構造 |","sourceFile":"extensions.md"},"index":{"title":"Pi ドキュメント","markdown":"Piは最小限のターミナルコーディングハーネスです。これは、TypeScript 拡張機能、スキル、prompt templates、テーマ、および pi パッケージを通じて拡張されながら、コアは小規模に留まるように設計されています。\n\n## クイックスタート\n\nPi を npm とともにインストールします。\n\n```bash\nnpm install -g --ignore-scripts @earendil-works/pi-coding-agent\n```\n\n`--ignore-scripts` は、インストール中に依存関係のライフサイクル スクリプトを無効にします。 Pi では、通常の npm インストールにインストール スクリプトは必要ありません。\n\nLinux または macOS では、インストーラーを使用することもできます。\n\n```bash\ncurl -fsSL https://pi.dev/install.sh | sh\n```\n\npi 自体をアンインストールするには、curl に npm を使用し、npm をインストールします。\n\n```bash\nnpm uninstall -g @earendil-works/pi-coding-agent\n```\n\npnpm、Yarn、または Bun のインストールの場合は、対応するグローバル削除コマンド: `pnpm remove -g @earendil-works/pi-coding-agent`、`yarn global remove @earendil-works/pi-coding-agent`、または `bun uninstall -g @earendil-works/pi-coding-agent` を使用します。\n\n次に、プロジェクト ディレクトリで実行します。\n\n```bash\npi\n```\n\npi を開始する前に、subscription providers に対して `/login` で認証するか、`ANTHROPIC_API_KEY` などの API key を設定します。\n\n完全な初回実行フローについては、[Quickstart](quickstart.md) を参照してください。\n\n## ここから始めましょう\n\n- [Quickstart](quickstart.md) - 最初のセッションをインストール、認証、実行します。\n- [Using Pi](usage.md) - インタラクティブ モード、slash commands、context files、および CLI リファレンス。\n- [Providers](providers.md) - 組み込みプロバイダーのサブスクリプションと API キーのセットアップ。\n- [llama.cpp](llama-cpp.md) - ローカルルーターを実行し、`/llama` でモデルを管理します。\n- [Security](security.md) - プロジェクトの信頼、sandbox 境界、および脆弱性レポート。\n- [Containerization](containerization.md) - sandbox pi と Gondolin、Docker、または OpenShell。\n- [Settings](settings.md) - グローバル設定とプロジェクト設定。\n- [Keybindings](keybindings.md) - デフォルトのショートカットとカスタムキーバインド。\n- [Sessions](sessions.md) - セッション管理、分岐、ツリー ナビゲーション。\n- [Compaction](compaction.md) - context compaction および branch summarization。\n\n## カスタマイズ\n\n- [Extensions](extensions.md) - TypeScript ツール、コマンド、イベント、カスタム UI のモジュール。\n- [Skills](skills.md) - 再利用可能なオンデマンド機能のエージェント Skills。\n- [Prompt templates](prompt-templates.md) - slash commands から拡張される再利用可能なプロンプト。\n- [Themes](themes.md) - 組み込みおよびカスタム terminal themes。\n- [Pi packages](packages.md) - 拡張機能、スキル、プロンプト、テーマをバンドルして共有します。\n- [Custom models](models.md) - サポートされているプロバイダー API のモデル エントリを追加します。\n- [Custom providers](custom-provider.md) - カスタム API および OAuth フローを実装します。\n\n## プログラムによる使用\n\n- [SDK](sdk.md) - Node.js アプリケーションに pi を埋め込みます。\n- [RPC mode](rpc.md) - stdin/stdout JSONL を積分します。\n- [JSON event stream mode](json.md) - 構造化されたイベントを含む印刷モード。\n- [TUI components](tui.md) - 拡張機能用のカスタム ターミナル UI を構築します。\n\n## 参照\n\n- [Environment variables](environment-variables.md) - Pi プロセス構成とセッション メタデータは bash ツールで使用できます。\n- [Session format](session-format.md) - JSONL セッション ファイル形式、エントリ タイプ、および SessionManager API。\n\n## プラットフォームのセットアップ\n\n- [Windows](windows.md)\n- [Termux on Android](termux.md)\n- [tmux](tmux.md)\n- [Terminal setup](terminal-setup.md)\n- [Shell aliases](shell-aliases.md)\n\n## 発達\n\n- [Development](development.md) - ローカルセットアップ、プロジェクト構造、およびデバッグ。","sourceFile":"index.md"},"json":{"title":"JSON イベントストリームモード","markdown":"```bash\npi --mode json \"Your prompt\"\n```\n\nすべてのセッション イベントを JSON 行から stdout として出力します。 pi を他のツールまたはカスタム UI に統合するのに役立ちます。\n\n## イベントの種類\n\nワイヤーイベントは`JsonAgentSessionEvent`を使用します。一致します\n[`AgentSessionEvent`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/agent-session.ts)\nただし、ストリーミング メッセージの更新では累積スナップショットが省略されます。\n\n```typescript\ntype WithoutPartial<T> = T extends { partial: unknown } ? Omit<T, \"partial\"> : T;\n\ntype JsonAgentSessionEvent =\n  | Exclude<AgentSessionEvent, { type: \"message_update\" }>\n  | {\n      type: \"message_update\";\n      assistantMessageEvent: WithoutPartial<AssistantMessageEvent>;\n    };\n```\n\n`queue_update` は、変更されるたびに完全な保留中のステアリングとフォローアップ キューを発行します。 `compaction_start` と `compaction_end` は、手動と自動の両方の圧縮をカバーします。\n\n他の基本イベントは以下から来ます\n[`AgentEvent`](https://github.com/earendil-works/pi-mono/blob/main/packages/agent/src/types.ts):\n\n```typescript\ntype AgentEvent =\n  // Agent lifecycle\n  | { type: \"agent_start\" }\n  | { type: \"agent_end\"; messages: AgentMessage[] }\n  // Turn lifecycle\n  | { type: \"turn_start\" }\n  | { type: \"turn_end\"; message: AgentMessage; toolResults: ToolResultMessage[] }\n  // Message lifecycle\n  | { type: \"message_start\"; message: AgentMessage }\n  | { type: \"message_update\"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }\n  | { type: \"message_end\"; message: AgentMessage }\n  // Tool execution\n  | { type: \"tool_execution_start\"; toolCallId: string; toolName: string; args: any }\n  | { type: \"tool_execution_update\"; toolCallId: string; toolName: string; args: any; partialResult: any }\n  | { type: \"tool_execution_end\"; toolCallId: string; toolName: string; result: any; isError: boolean };\n```\n\n## メッセージの種類\n\n[`packages/ai/src/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/types.ts#L134) からの基本メッセージ:\n- `UserMessage` (134 行目)\n- `AssistantMessage` (140行目)\n- `ToolResultMessage` (152行目)\n\n[`packages/coding-agent/src/core/messages.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/messages.ts#L29) からの延長メッセージ:\n- `BashExecutionMessage` (29 行目)\n- `CustomMessage` (46 行目)\n- `BranchSummaryMessage` (55 行目)\n- `CompactionSummaryMessage` (62行目)\n\n## 出力フォーマット\n\n各行は JSON オブジェクトです。最初の行はセッションヘッダーです。\n\n```json\n{\"type\":\"session\",\"version\":3,\"id\":\"uuid\",\"timestamp\":\"...\",\"cwd\":\"/path\"}\n```\n\n発生したイベントの後には次のようなものがあります。\n\n```json\n{\"type\":\"agent_start\"}\n{\"type\":\"turn_start\"}\n{\"type\":\"message_start\",\"message\":{\"role\":\"assistant\",\"content\":[],...}}\n{\"type\":\"message_update\",\"assistantMessageEvent\":{\"type\":\"text_delta\",\"contentIndex\":0,\"delta\":\"Hello\"}}\n{\"type\":\"message_end\",\"message\":{...}}\n{\"type\":\"turn_end\",\"message\":{...},\"toolResults\":[]}\n{\"type\":\"agent_end\",\"messages\":[...]}\n```\n\n`message_update` レコードはデルタのみです。累積 `message` フィールドと\n`assistantMessageEvent.partial` はストリーム サイズを線形に保ちます。 `contentIndex` と `delta` を使用します\n必要に応じて、ライブ テキスト、思考、またはツール呼び出しの引数を組み立てます。 `message_end` が含まれています\n最後の権威あるメッセージ。\n\n## 例\n\n```bash\npi --mode json \"List files\" 2>/dev/null | jq -c 'select(.type == \"message_end\")'\n```","sourceFile":"json.md"},"keybindings":{"title":"キーバインド","markdown":"すべてのキーボード ショートカットは `~/.pi/agent/keybindings.json` でカスタマイズできます。各アクションは 1 つ以上のキーにバインドできます。\n\n設定ファイルは、pi が内部で使用するものと、拡張機能の作成者が `keyHint()` および挿入された `keybindings` マネージャーで使用するのと同じ名前空間キーバインド ID を使用します。\n\n`cursorUp` や `expandTools` などの事前に名前空間が設定された ID を使用する古い構成は、起動時に自動的に名前空間が設定された ID に移行されます。\n\n`keybindings.json` を編集した後、pi で `/reload` を実行して、セッションを再起動せずに変更を適用します。\n\n## キーの形式\n\n`modifier+key` ここで、修飾子は `ctrl`、`shift`、`alt`、`super` (組み合わせ可能)、キーは次のとおりです。\n\n- **文字:** `a-z`\n- **数字:** `0-9`\n- **特殊キー:** `escape`, `esc`, `enter`, `return`, `tab`, `space`, `backspace`, `delete`, `insert`, `clear`, `home`, `end`, `pageUp`, `pageDown`, `up`, `down`, `left`, `right`\n- **ファンクションキー:** `f1`-`f12`\n- **記号:** `` ` ``, `-`, `=`, `[`, `]`, `\\`, `;`, `'`, `,`, `.`, `/`, `!`, `@`, `#`, `$`, `%`, `^`, `&`, `*`, `(`, `)`, `_`, `+`, `|`, `~`, `{`, `}`, `:`, `<`, `>`, `?`\n\n修飾子の組み合わせ: `ctrl+shift+x`、`alt+ctrl+x`、`ctrl+shift+alt+x`、`super+k`、`ctrl+super+k`、`ctrl+1` など。\n\n`super` バインディングには、通常は Kitty キーボード プロトコルを通じて、修飾子を個別に報告する端末が必要です。そのサポートのない端末では動作しない可能性があります。\n\n## すべてのアクション\n\n### TUI エディターのカーソル移動\n\n| キーバインドID | デフォルト | 説明 |\n|--------|---------|-------------|\n| `tui.editor.cursorUp` | `up` | カーソルを上に移動し、上部で古い履歴を参照します |\n| `tui.editor.cursorDown` | `down` | カーソルを下に移動し、下部で新しい履歴を参照します |\n| `tui.editor.historyPrevious` | *（なし）* | 前のプロンプト履歴エントリを選択します |\n| `tui.editor.historyNext` | *（なし）* | 次のプロンプト履歴エントリを選択します |\n| `tui.editor.cursorLeft` | `left`, `ctrl+b` | カーソルを左に移動 |\n| `tui.editor.cursorRight` | `right`, `ctrl+f` | カーソルを右に移動 |\n| `tui.editor.cursorWordLeft` | `alt+left`, `ctrl+left`, `alt+b` | カーソルを単語を左に移動 |\n| `tui.editor.cursorWordRight` | `alt+right`, `ctrl+right`, `alt+f` | カーソルを単語を右に移動 |\n| `tui.editor.cursorLineStart` | `home`, `ctrl+home`, `ctrl+a` | 行頭へ移動 |\n| `tui.editor.cursorLineEnd` | `end`, `ctrl+end`, `ctrl+e` | 行末に移動 |\n| `tui.editor.jumpForward` | `ctrl+]` | 文字まで前にジャンプ |\n| `tui.editor.jumpBackward` | `ctrl+alt+]` | キャラクターまで後方にジャンプ |\n| `tui.editor.pageUp` | `pageUp`, `ctrl+pageUp` | ページごとに上にスクロール |\n| `tui.editor.pageDown` | `pageDown`, `ctrl+pageDown` | ページごとに下にスクロールします |\n\n専用の履歴アクションは、複数行プロンプト内のカーソル位置に関係なく、常に履歴エントリを変更します。明示的な履歴バインディングは、メイン エディターがフォーカスされている間はアプリケーションのアクションよりも優先されるため、`tui.editor.historyPrevious` を `ctrl+p` にバインドすると、セレクターの `Ctrl+P` を変更せずに、そのコンテキストでのモデルの循環がオーバーライドされます。\n\n### TUI 編集者の削除\n\n| キーバインドID | デフォルト | 説明 |\n|--------|---------|-------------|\n| `tui.editor.deleteCharBackward` | `backspace` | 文字を後方に削除 |\n| `tui.editor.deleteCharForward` | `delete`, `ctrl+d` | 文字を前方に削除 |\n| `tui.editor.deleteWordBackward` | `ctrl+w`, `alt+backspace` | 単語を後方に削除 |\n| `tui.editor.deleteWordForward` | `alt+d`, `alt+delete` | 単語送りを削除 |\n| `tui.editor.deleteToLineStart` | `ctrl+u` | 行頭まで削除 |\n| `tui.editor.deleteToLineEnd` | `ctrl+k` | 行末まで削除 |\n\n### TUI 入力\n\n| キーバインドID | デフォルト | 説明 |\n|--------|---------|-------------|\n| `tui.input.newLine` | `shift+enter`, `ctrl+j` | 新しい行を挿入 |\n| `tui.input.submit` | `enter` | 入力内容を送信する |\n| `tui.input.tab` | `tab` | タブ/オートコンプリート |\n\n### TUI キルリング\n\n| キーバインドID | デフォルト | 説明 |\n|--------|---------|-------------|\n| `tui.editor.yank` | `ctrl+y` | 最近削除したテキストを貼り付けます |\n| `tui.editor.yankPop` | `alt+y` | ヤンク後に削除されたテキストを循環する |\n| `tui.editor.undo` | `ctrl+-` | 最後の編集を元に戻す |\n\n### TUI クリップボードと選択範囲\n\n| キーバインドID | デフォルト | 説明 |\n|--------|---------|-------------|\n| `tui.input.copy` | `ctrl+c` | 選択範囲をコピー |\n| `tui.select.up` | `up` | 選択範囲を上に移動 |\n| `tui.select.down` | `down` | 選択範囲を下に移動します |\n| `tui.select.pageUp` | `pageUp` | リストの 1 ページ上へ |\n| `tui.select.pageDown` | `pageDown` | リストの次のページへ |\n| `tui.select.confirm` | `enter` | 選択を確認する |\n| `tui.select.cancel` | `escape`, `ctrl+c` | 選択をキャンセルする |\n\n### TUI 全画面ビューポート\n\nこれらのアクションは、インタラクティブ モードで `--tui-mode fullscreen` を使用し、プライマリ トランスクリプト スクロール領域をターゲットとする場合に適用されます。 2 本指のトラックパッドとマウス ホイール入力により、ポインターの下の領域がスクロールされ、固定エディター/ステータス/フッター ドック上のトランスクリプトに戻ります。 OSC 8 ハイパーリンクをクリックすると、デフォルトのハンドラーで開きます。マウスの主ボタンでドラッグするとテキストが選択され、クリップボードにコピーされます。トランスクリプトの上端または下端を押し続けると、画面外のコンテンツが自動スクロールされます。\n\nフルスクリーンのトランスクリプト バインディングは、エディター バインディングよりも優先されます。したがって、変更されていないデフォルトのナビゲーション キーは全画面モードでトランスクリプトを制御しますが、その `ctrl` バリアントは引き続きエディターを制御します。フルスクリーン モード以外では、両方のバリアントがエディタを制御します。\n\n| 鍵 | デフォルトモード | 全画面モード |\n|-----|--------------|-----------------|\n| `home`, `end` | エディタ | 成績証明書 |\n| `ctrl+home`, `ctrl+end` | エディタ | エディタ |\n| `pageUp`, `pageDown` | エディタ | 成績証明書 |\n| `ctrl+pageUp`, `ctrl+pageDown` | エディタ | エディタ |\n\nこのルーティングは、通常のアクション バインディングを通じて引き続き構成可能です。たとえば、`\"tui.altScreen.pageUp\": \"ctrl+pageUp\"` を指定すると、全画面モードで `pageUp` がエディタを制御し、`ctrl+pageUp` がトランスクリプトを制御します。ページ全体のバインドを維持しながら、トランスクリプトのステップを小さくするには、`tui.altScreen.halfPageUp` と `tui.altScreen.halfPageDown` をバインドします。 `\"tui.altScreen.pageUp\": []` を設定すると、トランスクリプトのショートカットが完全に無効になります。ユーザー バインディングは、そのアクションのデフォルトを置き換えます。\n\n| キーバインドID | デフォルト | 説明 |\n|--------|---------|-------------|\n| `tui.altScreen.pageUp` | `pageUp` | トランスクリプトを 1 ページ上にスクロールします |\n| `tui.altScreen.pageDown` | `pageDown` | トランスクリプトを 1 ページ下にスクロールします |\n| `tui.altScreen.halfPageUp` | *（なし）* | トランスクリプトを半ページ上にスクロールします |\n| `tui.altScreen.halfPageDown` | *（なし）* | トランスクリプトを半ページ下にスクロールします |\n| `tui.altScreen.previousPrompt` | `ctrl+shift+up` | 前のマークされたメッセージにジャンプします |\n| `tui.altScreen.nextPrompt` | `ctrl+shift+down` | 次のマークされたメッセージにジャンプします |\n| `tui.altScreen.top` | `home` | トランスクリプトの先頭までスクロールします |\n| `tui.altScreen.bottom` | `end` | トランスクリプトの最後までスクロールして、新しい出力に従います |\n\n### 応用\n\n| キーバインドID | デフォルト | 説明 |\n|--------|---------|-------------|\n| `app.interrupt` | `escape` | キャンセル/中止 |\n| `app.clear` | `ctrl+c` | エディターをクリア (1 回目) / 終了 (2 回目) |\n| `app.exit` | `ctrl+d` | 終了 (エディターが空の場合) |\n| `app.suspend` | `ctrl+z` (Windows ではなし) | バックグラウンドで一時停止 |\n| `app.editor.external` | `ctrl+g` | 外部エディタで開きます (`externalEditor`、`$VISUAL`、`$EDITOR`、Windows のメモ帳、またはその他の場所の `nano`) |\n| `app.clipboard.pasteImage` | `ctrl+v` (Windows では `alt+v`) | クリップボードから画像またはテキストを貼り付けます |\n\n### セッション\n\n| キーバインドID | デフォルト | 説明 |\n|--------|---------|-------------|\n| `app.session.new` | *（なし）* | 新しいセッションを開始します (`/new`) |\n| `app.session.tree` | *（なし）* | session tree ナビゲーター (`/tree`) を開く |\n| `app.session.fork` | *（なし）* | 現在のセッションをフォークします (`/fork`) |\n| `app.session.resume` | *（なし）* | セッション再開ピッカーを開く (`/resume`) |\n| `app.session.togglePath` | `ctrl+p` | パス表示の切り替え |\n| `app.session.toggleSort` | `ctrl+s` | 並べ替えモードの切り替え |\n| `app.session.toggleNamedFilter` | `ctrl+n` | 名前付きのみのフィルターを切り替えます |\n| `app.session.rename` | `ctrl+r` | セッション名の変更 |\n| `app.session.delete` | `ctrl+d` | セッションの削除 |\n| `app.session.deleteNoninvasive` | `ctrl+backspace` | クエリが空の場合はセッションを削除する |\n\n### Modelsと思考\n\n| キーバインドID | デフォルト | 説明 |\n|--------|---------|-------------|\n| `app.model.select` | `ctrl+l` | モデルセレクターを開く |\n| `app.model.cycleForward` | `ctrl+p` | 次のモデルへのサイクル |\n| `app.model.cycleBackward` | `shift+ctrl+p` | 以前のモデルへのサイクル |\n| `app.thinking.cycle` | `shift+tab` | サイクル思考レベル |\n| `app.thinking.toggle` | `ctrl+t` | 思考ブロックを折りたたんだり拡張したりする |\n\n### ディスプレイとメッセージキュー\n\n| キーバインドID | デフォルト | 説明 |\n|--------|---------|-------------|\n| `app.tools.expand` | `ctrl+o` | ツール出力を折りたたむまたは展開する |\n| `app.message.copy` | `ctrl+x` | 最後のアシスタント メッセージ、または `/tree` で選択したメッセージをコピーします |\n| `app.message.followUp` | `alt+enter` | フォローアップメッセージをキューに入れる |\n| `app.message.dequeue` | `alt+up` | キューに入れられたメッセージをエディタに復元する |\n\n### ツリーナビゲーション\n\n| キーバインドID | デフォルト | 説明 |\n|--------|---------|-------------|\n| `app.tree.foldOrUp` | `ctrl+left`, `alt+left` | 現在の分岐セグメントを折りたたむか、前のセグメントの開始位置にジャンプします |\n| `app.tree.unfoldOrDown` | `ctrl+right`, `alt+right` | 現在の分岐セグメントを展開するか、次のセグメントの開始点または分岐の終了点にジャンプします |\n| `app.tree.editLabel` | `shift+l` | 選択したツリー ノードのラベルを編集します |\n| `app.tree.toggleLabelTimestamp` | `shift+t` | ツリー内のラベルのタイムスタンプを切り替えます |\n| `app.tree.filter.default` | `ctrl+d` | ツリーフィルターをデフォルトビューに設定する |\n| `app.tree.filter.noTools` | `ctrl+t` | ツールの結果を非表示にするツリー フィルターを切り替えます |\n| `app.tree.filter.userOnly` | `ctrl+u` | ユーザーメッセージのみを表示するツリーフィルターを切り替えます |\n| `app.tree.filter.labeledOnly` | `ctrl+l` | ラベル付きエントリのみを表示するツリー フィルターを切り替えます |\n| `app.tree.filter.all` | `ctrl+a` | すべてのエントリを表示するツリー フィルターを切り替えます |\n| `app.tree.filter.cycleForward` | `ctrl+o` | サイクルツリーフィルターを前方に移動 |\n| `app.tree.filter.cycleBackward` | `shift+ctrl+o` | サイクルツリーフィルターを逆方向に移動 |\n\n### スコープ付き Models セレクター\n\nスコープ付きモデル セレクター内で使用されます (`/scoped-models` 経由で開きます)。\n\n| キーバインドID | デフォルト | 説明 |\n|--------|---------|-------------|\n| `app.models.save` | `ctrl+s` | 現在のモデル選択を設定に保存する |\n| `app.models.enableAll` | `ctrl+a` | すべてのモデル (または現在の検索に一致するすべて) を有効にする |\n| `app.models.clearAll` | `ctrl+x` | すべてのモデルをクリアします（または現在の検索に一致するすべてのモデルをクリアします） |\n| `app.models.toggleProvider` | `ctrl+p` | 現在のプロバイダーのすべてのモデルを切り替えます |\n| `app.models.reorderUp` | `alt+up` | 選択したモデルをサイクル順序で上に移動します |\n| `app.models.reorderDown` | `alt+down` | 選択したモデルをサイクル順序で下に移動します |\n\n## カスタム構成\n\n`~/.pi/agent/keybindings.json` を作成します:\n\n```json\n{\n  \"tui.editor.historyPrevious\": \"ctrl+p\",\n  \"tui.editor.historyNext\": \"ctrl+n\",\n  \"tui.editor.deleteWordBackward\": [\"ctrl+w\", \"alt+backspace\"]\n}\n```\n\n各アクションには、単一のキーまたはキーの配列を含めることができます。ユーザー設定はデフォルトをオーバーライドします。\n\nWindows 端末は Unix ジョブ制御をサポートしていないため、ネイティブ Windows では、`app.suspend` にはデフォルトのバインドがありません。手動でバインドすると、pi は一時停止する代わりにステータス メッセージを表示します。 WSL では、通常の Linux `ctrl+z`/`fg` の動作が引き続き適用されます。\n\n### Emacs の例\n\n```json\n{\n  \"tui.editor.historyPrevious\": \"ctrl+p\",\n  \"tui.editor.historyNext\": \"ctrl+n\",\n  \"tui.editor.cursorLeft\": [\"left\", \"ctrl+b\"],\n  \"tui.editor.cursorRight\": [\"right\", \"ctrl+f\"],\n  \"tui.editor.cursorWordLeft\": [\"alt+left\", \"alt+b\"],\n  \"tui.editor.cursorWordRight\": [\"alt+right\", \"alt+f\"],\n  \"tui.editor.deleteCharForward\": [\"delete\", \"ctrl+d\"],\n  \"tui.editor.deleteCharBackward\": [\"backspace\", \"ctrl+h\"],\n  \"tui.input.newLine\": [\"shift+enter\", \"ctrl+j\"]\n}\n```\n\n### Vim の例\n\n```json\n{\n  \"tui.editor.cursorUp\": [\"up\", \"alt+k\"],\n  \"tui.editor.cursorDown\": [\"down\", \"alt+j\"],\n  \"tui.editor.cursorLeft\": [\"left\", \"alt+h\"],\n  \"tui.editor.cursorRight\": [\"right\", \"alt+l\"],\n  \"tui.editor.cursorWordLeft\": [\"alt+left\", \"alt+b\"],\n  \"tui.editor.cursorWordRight\": [\"alt+right\", \"alt+w\"]\n}\n```","sourceFile":"keybindings.md"},"llama-cpp":{"title":"llama.cpp","markdown":"Pi は、[llama.cpp](https://github.com/ggml-org/llama.cpp) ルーターサーバーをサポートします。ルーターは複数の GGUF モデルを検出し、それらをオンデマンドでロードまたはアンロードします。\n\nルーターをサポートする現在の llama.cpp ビルドを使用してください。 [build instructions](https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md) に従うか、プラットフォームに応じて [prebuilt release](https://github.com/ggml-org/llama.cpp/releases) をインストールしてください。\n\n## ルーターを起動する\n\n`--model` や `-m` を使用せずに `llama-server` を開始します。モデルを渡すと、ルーター モードではなく単一モデル モードが開始されます。\n\n```bash\nllama-server \\\n  --models-dir ~/models \\\n  --no-models-autoload \\\n  --jinja \\\n  --host 127.0.0.1 \\\n  --port 8080 \\\n  -ngl 999 \\\n  -c 32768\n```\n\n重要なオプション:\n\n- `--models-dir ~/models` はローカルの GGUF ファイルを検出します。\n- `--no-models-autoload` は、`/llama` を通じて明示的にロードし続けます。\n- `--jinja` は、互換性のあるチャット テンプレートとツール呼び出しを有効にします。\n- `-ngl 999` は、できるだけ多くのレイヤーを GPU にオフロードします。\n- `-c 32768` は、ロードされた各モデルのコンテキスト ウィンドウを設定します。モデルのネイティブ コンテキストを使用するにはこれを省略します。これには大幅に多くのメモリが必要になる可能性があります。\n\n単一ファイルのモデルは、モデル ディレクトリに直接置くことができます。マルチモーダル モデルとマルチシャード モデルを別のサブディレクトリに配置します。\n\n```text\n~/models/\n├── llama-3.2-1b-Q4_K_M.gguf\n├── gemma-3-4b-it-Q4_K_M/\n│   ├── gemma-3-4b-it-Q4_K_M.gguf\n│   └── mmproj-F16.gguf\n└── large-model-Q4_K_M/\n    ├── large-model-Q4_K_M-00001-of-00003.gguf\n    ├── large-model-Q4_K_M-00002-of-00003.gguf\n    └── large-model-Q4_K_M-00003-of-00003.gguf\n```\n\nファイルを手動で追加した後、ルーターを再起動します。モデルごとのコンテキスト サイズおよびその他のオプションについては、[llama.cpp model presets](https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md#model-presets) を使用します。\n\n## Piの設定\n\nPi を起動し、プロバイダーを構成します。\n\n```text\n/login llama.cpp\n```\n\nルーターの URL とオプションの API key を入力します。デフォルトの URL は `http://127.0.0.1:8080` です。\n\n環境変数は、`/login` なしで同じ値を設定できます。\n\n```bash\nexport LLAMA_BASE_URL=http://127.0.0.1:8080\nexport LLAMA_API_KEY=optional-secret\npi\n```\n\nサーバーが API key を使用する場合は、一致する `--api-key` 値で `llama-server` を開始します。ローカルのみのアクセスの場合は、`--host 127.0.0.1` を保持してください。\n\n## モデルの管理\n\n走る：\n\n```text\n/llama\n```\n\n- アンロードされたモデルを選択してロードします。\n- ロードされたモデルを選択してアンロードします。\n- **モデルのダウンロード…** を選択し、Hugging Face を検索して、リポジトリと量子化を選択します。正確な `owner/repository[:quant]` 値も機能します。\n- ロードまたはダウンロード中に Esc キーを押してキャンセルを確認します。\n\nHugging Face 検索は、設定されている場合は `HF_TOKEN` を使用し、次に `$HF_TOKEN_PATH`、`$HF_HOME/token`、`$XDG_CACHE_HOME/huggingface/token`、`~/.cache/huggingface/token` をチェックします。検索は認証なしでも機能しますが、レート制限が低くなります。 Pi は、ゲートされたリポジトリとそのアクセス ページへのリンクをダウンロードする前に警告します。 llama.cpp サーバーはダウンロードを実行するため、選択したリポジトリにアクセスする必要がある場合、そのプロセスには `HF_TOKEN` も必要です。\n\n他のモデルがロードされている場合、Pi はそれらを最初にアンロードするか、ロードしたままにするかを尋ねます。 Pi はモデルをサイレントにアンロードしたり、モデル ファイルを削除したりしません。ルーターは他のクライアントと共有される場合があるため、`/llama` は常にルーターの現在の状態を表示します。\n\n`/model`にはロードされたモデルのみが表示されます。モデルをロードした後、`/model` を実行して、現在の Pi セッション用にモデルを選択します。\n\nルーターが切断された場合、`/llama` には **再試行** と **閉じる** が表示されます。 [再試行] は、中断された操作を再実行せずに、再接続してモデルの状態を更新します。\n\n## トラブルシューティング\n\nルーターが到達可能であることを確認します。\n\n```bash\ncurl http://127.0.0.1:8080/health\ncurl http://127.0.0.1:8080/models\n```\n\n- **`/llama` にはモデルがありません:** `--models-dir`、ディレクトリのレイアウトを確認し、ルーターを再起動します。\n- **`/model` にモデルがありません:** 最初に `/llama` でロードしてください。\n- **ロードが失敗するか、メモリの使用量が多すぎる:** `-c` を下げるか、別のモデルをアンロードします。\n- **サーバーはルーターモードではありません:** `--model`、`-m`、または `-hf` を指定せずに起動してください。","sourceFile":"llama-cpp.md"},"models":{"title":"カスタムModels","markdown":"`~/.pi/agent/models.json` 経由でカスタム プロバイダーとモデル (Ollama、vLLM、LM Studio、プロキシ) を追加します。\n\n## 目次\n\n- [Minimal Example](#minimal-example)\n- [Full Example](#full-example)\n- [Supported APIs](#supported-apis)\n- [Provider Configuration](#provider-configuration)\n- [Model Configuration](#model-configuration)\n- [Overriding Built-in Providers](#overriding-built-in-providers)\n- [Per-model Overrides](#per-model-overrides)\n- [Anthropic Messages Compatibility](#anthropic-messages-compatibility)\n- [OpenAI Compatibility](#openai-compatibility)\n\n## 最小限の例\n\nローカル モデル (Ollama、LM Studio、vLLM) の場合、モデルごとに `id` のみが必要です。\n\n```json\n{\n  \"providers\": {\n    \"ollama\": {\n      \"baseUrl\": \"http://localhost:11434/v1\",\n      \"api\": \"openai-completions\",\n      \"apiKey\": \"ollama\",\n      \"models\": [\n        { \"id\": \"llama3.1:8b\" },\n        { \"id\": \"qwen2.5-coder:7b\" }\n      ]\n    }\n  }\n}\n```\n\n`apiKey` 値は、Ollama が無視するため、プレースホルダーです。 pi still treats models as requiring auth before they appear in `/model`, so keyless local servers should keep a dummy value, save a key for that provider with `/login`, or pass `--api-key` when selecting the model.\n\n一部の OpenAI 互換サーバーは、推論可能なモデルに使用される `developer` 役割を理解できません。これらのプロバイダーの場合は、`compat.supportsDeveloperRole` を `false` に設定して、pi がシステム プロンプトを代わりに `system` メッセージとして送信するようにします。サーバーが`reasoning_effort` もサポートしていない場合は、`compat.supportsReasoningEffort` を `false` に設定します。\n\nプロバイダー レベルで `compat` を設定してすべてのモデルに適用することも、モデル レベルで設定して特定のモデルをオーバーライドすることもできます。これは一般に、Ollama、vLLM、SGLang、および同様の OpenAI 互換サーバーに当てはまります。\n\n```json\n{\n  \"providers\": {\n    \"ollama\": {\n      \"baseUrl\": \"http://localhost:11434/v1\",\n      \"api\": \"openai-completions\",\n      \"apiKey\": \"ollama\",\n      \"compat\": {\n        \"supportsDeveloperRole\": false,\n        \"supportsReasoningEffort\": false\n      },\n      \"models\": [\n        {\n          \"id\": \"gpt-oss:20b\",\n          \"reasoning\": true\n        }\n      ]\n    }\n  }\n}\n```\n\n## 完全な例\n\n特定の値が必要な場合は、デフォルトをオーバーライドします。\n\n```json\n{\n  \"providers\": {\n    \"ollama\": {\n      \"baseUrl\": \"http://localhost:11434/v1\",\n      \"api\": \"openai-completions\",\n      \"apiKey\": \"ollama\",\n      \"models\": [\n        {\n          \"id\": \"llama3.1:8b\",\n          \"name\": \"Llama 3.1 8B (Local)\",\n          \"reasoning\": false,\n          \"input\": [\"text\"],\n          \"contextWindow\": 128000,\n          \"maxTokens\": 32000,\n          \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0 }\n        }\n      ]\n    }\n  }\n}\n```\n\n`/model` を開くたびにファイルがリロードされます。セッション中に編集します。再起動は必要ありません。\n\n## Google AI スタジオの例\n\n`google-generative-ai` と `baseUrl` を使用して、カスタム Gemma 4 エントリを含む Google AI Studio からモデルを追加します。\n\n```json\n{\n  \"providers\": {\n    \"my-google\": {\n      \"baseUrl\": \"https://generativelanguage.googleapis.com/v1beta\",\n      \"api\": \"google-generative-ai\",\n      \"apiKey\": \"$GEMINI_API_KEY\",\n      \"models\": [\n        {\n          \"id\": \"gemma-4-31b-it\",\n          \"name\": \"Gemma 4 31B\",\n          \"input\": [\"text\", \"image\"],\n          \"contextWindow\": 262144,\n          \"reasoning\": true\n        }\n      ]\n    }\n  }\n}\n```\n\n`baseUrl`は、`google-generative-ai` API タイプにカスタムモデルを追加する場合に必要です。\n\n## サポートされているAPI\n\n| API | 説明 |\n|-----|-------------|\n| `openai-completions` | OpenAI チャット補完 (最も互換性のある) |\n| `openai-responses` | OpenAI の応答 API |\n| `anthropic-messages` | 人間的なメッセージ API |\n| `google-generative-ai` | Google ジェネレーティブ AI |\n\nプロバイダー レベル (すべてのモデルのデフォルト) またはモデル レベル (モデルごとにオーバーライド) で `api` を設定します。\n\n## プロバイダーの構成\n\n| 分野 | 説明 |\n|-------|-------------|\n| `baseUrl` | API エンドポイント URL |\n| `api` | API タイプ (上記参照) |\n| `apiKey` | オプションの API key 構成 (以下の値の解決を参照)。認証が `/login`/`auth.json` または CLI `--api-key` によって提供される場合は省略します。 |\n| `oauth` | 動的 OAuth プロバイダー タイプ。現在、`\"radius\"` をサポートしています。ゲートウェイ `baseUrl` が必要です。 |\n| `headers` | カスタムヘッダー (以下の値の解決を参照) |\n| `authHeader` | `true` を設定すると、`Authorization: Bearer <apiKey>` が自動的に追加されます |\n| `models` | モデル構成の配列 |\n| `modelOverrides` | このプロバイダーの組み込みモデルまたは拡張機能に登録されたモデルのモデルごとのオーバーライド |\n\n`models` のプロバイダーの場合、非組み込みプロバイダー構成には、プロバイダー レベルまたはモデル レベルのいずれかで `baseUrl` および `api` の値が必要です。ファイルをロードするために `apiKey` は必要ありません。モデルは、`/login`/`auth.json`、CLI `--api-key`、またはプロバイダー `apiKey` を通じて認証が設定されている場合に使用可能になります。認証が構成されていない場合、モデルはロードされますが、`/model` と `--list-models` では利用できないままになります。\n\n### 値の解決\n\n`apiKey` フィールドと `headers` フィールドは、コマンドの実行、環境補間、およびリテラルをサポートします。\n\n- **シェルコマンド:** 先頭の `\"!command\"` は値全体をコマンドとして実行し、 stdout を使用します\n  ```json\n  \"apiKey\": \"!security find-generic-password -ws 'anthropic'\"\n  \"apiKey\": \"!op read 'op://vault/item/credential'\"\n  ```\n- **環境補間:** `\"$ENV_VAR\"` または `\"${ENV_VAR}\"` は、名前付き変数の値を使用します。補間はより大きなリテラル内で機能します。\n  ```json\n  \"apiKey\": \"$MY_API_KEY\"\n  \"apiKey\": \"${KEY_PREFIX}_${KEY_SUFFIX}\"\n  ```\n  `$FOO_BAR` は変数 `FOO_BAR` です。 `BAR` がリテラルテキストの場合は、`${FOO}_BAR` を使用します。環境変数が欠落していると、値が未解決になります。\n- **エスケープ:** `\"$\"` はリテラル `\"$\"` を出力します。 `\"$!\"` は、コマンドの実行をトリガーせずにリテラルの `\"!\"` を発行します。\n  ```json\n  \"apiKey\": \"$$literal-dollar-prefix\"\n  \"apiKey\": \"$!literal-bang-prefix\"\n  ```\n- **リテラル値:** 直接使用されます。 `MY_API_KEY` などの単純な大文字文字列はリテラルです。環境変数には `$MY_API_KEY` を使用します。\n  ```json\n  \"apiKey\": \"sk-...\"\n  ```\n\n`models.json` の場合、シェル コマンドはリクエスト時に解決されます。 pi は、任意のコマンドに組み込み TTL、古い再利用、または回復ロジックを意図的に適用しません。異なるコマンドには異なるキャッシュ戦略と失敗戦略が必要ですが、pi は正しい戦略を推測できません。\n\nコマンドが遅い、高価である、レート制限がある場合、または一時的な障害時に以前の値を使用し続ける必要がある場合は、必要なキャッシュまたは TTL 動作を実装する独自のスクリプトまたはコマンドでコマンドをラップします。\n\n`/model` 可用性チェックでは、構成された認証の存在が使用され、シェル コマンドは実行されません。\n\n### カスタムヘッダー\n\n```json\n{\n  \"providers\": {\n    \"custom-proxy\": {\n      \"baseUrl\": \"https://proxy.example.com/v1\",\n      \"apiKey\": \"$MY_API_KEY\",\n      \"api\": \"anthropic-messages\",\n      \"headers\": {\n        \"x-portkey-api-key\": \"$PORTKEY_API_KEY\",\n        \"x-secret\": \"!op read 'op://vault/item/secret'\"\n      },\n      \"models\": [...]\n    }\n  }\n}\n```\n\n## モデル構成\n\n| 分野 | 必須 | デフォルト | 説明 |\n|-------|----------|---------|-------------|\n| `id` | はい | — | モデル識別子 (API に渡されます) |\n| `name` | いいえ | `id` | 人間が判読できるモデルラベル。マッチング (`--model` パターン) に使用され、二次モデル詳細テキストとして表示されます。 |\n| `api` | いいえ | プロバイダーの `api` | このモデルのプロバイダーの API を上書きします |\n| `reasoning` | いいえ | `false` | 拡張された思考をサポートする |\n| `thinkingLevelMap` | いいえ | 省略 | pi の思考レベルをプロバイダーの値にマッピングし、サポートされていないレベルをマークします (以下を参照) |\n| `input` | いいえ | `[\"text\"]` | 入力タイプ: `[\"text\"]` または `[\"text\", \"image\"]` |\n| `contextWindow` | いいえ | `128000` | トークン単位のコンテキスト ウィンドウ サイズ |\n| `maxTokens` | いいえ | `16384` | 最大出力トークン |\n| `samplingParams` | いいえ | 省略 | サンプリングパラメータはすべてのリクエスト本文にそのままマージされます (以下を参照) |\n| `cost` | いいえ | すべてゼロ | オプションのリクエスト全体の入力価格レベルを備えた 100 万トークンあたりのレート |\n| `compat` | いいえ | プロバイダー `compat` | プロバイダーの互換性はオーバーライドされます。両方が設定されている場合は、プロバイダーレベルの `compat` とマージされます。 |\n\nコスト層は完全な代替レート セットを提供し、合計入力使用量 (`input + cacheRead + cacheWrite`) が `inputTokensAbove` を超えると、完全なリクエストに適用されます。複数の層が一致する場合、最も高いしきい値が優先されます。\n\n```json\n{\n  \"cost\": {\n    \"input\": 5,\n    \"output\": 30,\n    \"cacheRead\": 0.5,\n    \"cacheWrite\": 6.25,\n    \"tiers\": [\n      {\n        \"inputTokensAbove\": 272000,\n        \"input\": 10,\n        \"output\": 45,\n        \"cacheRead\": 1,\n        \"cacheWrite\": 12.5\n      }\n    ]\n  }\n}\n```\n\n現在の動作:\n- `/model`、`--list-models`、および対話型フッターには、モデル `id` ごとのエントリが表示されます。\n- 設定された `name` は、モデル マッチングと二次モデル詳細テキストに使用されます。フッター/ステータスバーのモデル ID は置き換えられません。\n\n### サンプリングパラメータ\n\n`samplingParams` は、フィールド pi がそれ自体を設定した後、モデルのすべてのリクエスト本文に逐語的にマージされる自由形式のオブジェクトであるため、そのキーが優先されます。これを使用して、pi がモデル化していないサンプリング パラメータ (llama.cpp の `min_p` や vLLM の `top_k` などのサーバー固有のパラメータを含む) を送信します。\n\n```json\n{\n  \"id\": \"deepseek-v4-flash\",\n  \"samplingParams\": {\n    \"temperature\": 1.0,\n    \"top_p\": 0.95,\n    \"top_k\": 0,\n    \"min_p\": 0.0\n  }\n}\n```\n\nOpenAI 互換の API のみが適用されます (`openai-completions`、`openai-responses`、`azure-openai-responses`)。他のAPIはそれを無視します。キーは pi の名前付きリクエスト フィールドをオーバーライドするため (たとえば、ここでの `temperature` キーはリクエスト レベルの温度を上回ります)、モデルのサンプリングの真実の単一ソースとして推奨されます。 `modelOverrides` では、`samplingParams` がキーごとに基本モデルの値とマージされます。\n\n### 思考レベルマップ\n\nモデル固有の思考制御を記述するには、モデル上で `thinkingLevelMap` を使用します。キーは円周率の思考レベルです: `off`、`minimal`、`low`、`medium`、`high`、`xhigh`、`max`。マップには穴がある場合があります。たとえば、モデルは、`xhigh` を公開せずに、`high` と `max` を公開できます。\n\n値はトライステートです。\n\n| 価値 | 意味 |\n|-------|---------|\n| 省略 | `high` までの標準レベルでは、プロバイダーのデフォルトのマッピングが使用されます。拡張 `xhigh` および `max` レベルはサポートされていません |\n| 弦 | レベルがサポートされており、この値がプロバイダーに送信されます |\n| `null` | レベルはサポートされておらず、非表示/スキップ/クランプされています |\n\noff、high、max 推論のみをサポートするモデルの例:\n\n```json\n{\n  \"id\": \"deepseek-v4-pro\",\n  \"reasoning\": true,\n  \"thinkingLevelMap\": {\n    \"minimal\": null,\n    \"low\": null,\n    \"medium\": null,\n    \"high\": \"high\",\n    \"xhigh\": null,\n    \"max\": \"max\"\n  }\n}\n```\n\n思考を無効にできないモデルの例:\n\n```json\n{\n  \"id\": \"always-thinking-model\",\n  \"reasoning\": true,\n  \"thinkingLevelMap\": {\n    \"off\": null\n  }\n}\n```\n\n移行: `compat.reasoningEffortMap` を使用していた古い構成は、そのマッピングをモデルレベルの `thinkingLevelMap` に移動する必要があります。 UI に表示すべきではないレベルには `null` を使用します。\n\n## 組み込み Providers をオーバーライドします\n\nモデルを再定義せずに、プロキシ経由で組み込みプロバイダーをルーティングします。\n\n```json\n{\n  \"providers\": {\n    \"anthropic\": {\n      \"baseUrl\": \"https://my-proxy.example.com/v1\"\n    }\n  }\n}\n```\n\nすべての組み込み Anthropic モデルは引き続き利用可能です。既存の OAuth または API key 認証は引き続き機能します。\n\nカスタム モデルを組み込みプロバイダーにマージするには、`models` 配列を含めます。\n\n```json\n{\n  \"providers\": {\n    \"anthropic\": {\n      \"baseUrl\": \"https://my-proxy.example.com/v1\",\n      \"apiKey\": \"$ANTHROPIC_API_KEY\",\n      \"api\": \"anthropic-messages\",\n      \"models\": [...]\n    }\n  }\n}\n```\n\nマージセマンティクス:\n- 組み込みモデルは保持されます。\n- カスタム モデルは、プロバイダー内で `id` によって更新/挿入されます。\n- カスタム モデル `id` が組み込みモデル `id` と一致する場合、カスタム モデルがその組み込みモデルを置き換えます。\n- カスタム モデル `id` が新しい場合は、組み込みモデルと一緒に追加されます。\n\n## モデルごとのオーバーライド\n\n`modelOverrides` を使用すると、プロバイダーの完全なモデル リストを置き換えることなく、組み込みモデルと一致する拡張登録モデルをカスタマイズできます。\n\n```json\n{\n  \"providers\": {\n    \"openrouter\": {\n      \"modelOverrides\": {\n        \"anthropic/claude-sonnet-4\": {\n          \"name\": \"Claude Sonnet 4 (Bedrock Route)\",\n          \"compat\": {\n            \"openRouterRouting\": {\n              \"only\": [\"amazon-bedrock\"]\n            }\n          }\n        }\n      }\n    }\n  }\n}\n```\n\n`modelOverrides` は、モデルごとに次のフィールドをサポートします: `name`、`reasoning`、`thinkingLevelMap`、`input`、`cost` (部分的)、`contextWindow`、`maxTokens`、`samplingParams` (キーごとにマージ)、`headers`、`compat`。\n\nDirect OpenAI GPT-5.6 Sol、Terra、および Luna はデフォルトで `272000` コンテキスト ウィンドウに設定されているため、リクエストは OpenAI のショート コンテキスト価格帯内に留まります。 OpenAI の 1.05M コンテキスト ウィンドウをオプトインするには、使用するモデルごとにウィンドウを増やします。\n\n```json\n{\n  \"providers\": {\n    \"openai\": {\n      \"modelOverrides\": {\n        \"gpt-5.6-sol\": {\n          \"contextWindow\": 1050000\n        }\n      }\n    }\n  }\n}\n```\n\nオーバーライドにより、組み込みの価格設定メタデータが保持されます。合計 272K を超える入力トークンを含むリクエストでは、リクエスト全体に対して GPT-5.6 のロングコンテキスト レートが使用されます。必要に応じて、同じオーバーライドを `gpt-5.6-terra` または `gpt-5.6-luna` に適用します。\n\n行動メモ:\n- `modelOverrides` は、組み込みプロバイダー モデルおよび一致する拡張登録済みプロバイダー モデルに適用されます。\n- 不明なモデル ID は無視されます。\n- プロバイダーレベルの `baseUrl`/`headers` と `modelOverrides` を組み合わせることができます。\n- `name` をオーバーライドすると、モデル マッチングと二次詳細テキストのみが変更されます。フッターとプライマリ モデル リストには引き続きモデル `id` が表示されます。\n- プロバイダーに対して `models` も定義されている場合、カスタム モデルは組み込みオーバーライドの後にマージされます。同じ `id` を持つカスタム モデルが、オーバーライドされた組み込みモデル エントリを置き換えます。\n\n## 人間的メッセージの互換性\n\n`api: \"anthropic-messages\"` を使用するプロバイダーまたはプロキシの場合、`compat` を使用して Anthropic 固有のリクエストの互換性を制御します。\n\nデフォルトでは、pi はツールごとに `eager_input_streaming: true` を送信します。プロキシまたは Anthropic 互換バックエンドがそのフィールドを拒否する場合は、`supportsEagerToolInputStreaming` を `false` に設定します。 Pi は `tools[].eager_input_streaming` を省略し、代わりにツール対応リクエストのレガシー `fine-grained-tool-streaming-2025-05-14` ベータ ヘッダーを送信します。\n\n一部の人間モデルでは、従来の予算ベースの思考ペイロードではなく、適応的思考 (`thinking.type: \"adaptive\"` プラス `output_config.effort`) が必要です。組み込みモデルではこれが自動的に設定されます。これらのモデルにルーティングするカスタム プロバイダーまたはエイリアスの場合は、`forceAdaptiveThinking` を `true` に設定します。\n\n一部の Anthropic 互換プロバイダーは、空の署名を持つ思考ブロックを発行し、再生時にそれらを期待します。これらのプロバイダーに対してのみ `allowEmptySignature` を `true` に設定します。本物の人間は空虚な思考のサインを拒否します。\n\n組み込みの Anthropic モデルでは、モデル メタデータで `supportsStrictTools` が有効になります。カスタム Anthropic 互換モデルは、エンドポイントが厳密な JSON スキーマ ツール定義を受け入れる場合、これを `true` に設定する必要があります。\n\n```json\n{\n  \"providers\": {\n    \"anthropic-proxy\": {\n      \"baseUrl\": \"https://proxy.example.com\",\n      \"api\": \"anthropic-messages\",\n      \"apiKey\": \"$ANTHROPIC_PROXY_KEY\",\n      \"compat\": {\n        \"supportsEagerToolInputStreaming\": false,\n        \"supportsLongCacheRetention\": true,\n        \"forceAdaptiveThinking\": true,\n        \"allowEmptySignature\": true\n      },\n      \"models\": [\n        {\n          \"id\": \"claude-opus-4-7\",\n          \"reasoning\": true,\n          \"input\": [\"text\", \"image\"]\n        }\n      ]\n    }\n  }\n}\n```\n\n| 分野 | 説明 |\n|-------|-------------|\n| `supportsEagerToolInputStreaming` | プロバイダーがツールごとの `eager_input_streaming` を受け入れるかどうか。デフォルト: `true`。 `false` に設定すると、そのフィールドが省略され、ツールが有効なリクエストで従来のきめ細かいツールのストリーミング ベータ ヘッダーが使用されます。 |\n| `supportsLongCacheRetention` | キャッシュ保持期間が `long` の場合に、プロバイダーが Anthropic の長期キャッシュ保持期間 (`cache_control.ttl: \"1h\"`) を受け入れるかどうか。デフォルト: `true`。 |\n| `sendSessionAffinityHeaders` | キャッシュが有効な場合にセッション ID から `x-session-affinity` を送信するかどうか。デフォルト: 既知のプロバイダーが自動検出されます。 |\n| `supportsCacheControlOnTools` | プロバイダーがツール定義で Anthropic スタイルの `cache_control` マーカーを受け入れるかどうか。デフォルト: `true`。 |\n| `forceAdaptiveThinking` | このモデルに適応的思考 (`thinking.type: \"adaptive\"` プラス `output_config.effort`) を送信するかどうか。組み込みの適応モデルはこれを自動的に設定します。デフォルト: `false`。 |\n| `allowEmptySignature` | 思考をテキストに変換する代わりに、空の思考署名を `signature: \"\"` として再生するかどうか。デフォルト: `false`。 |\n| `supportsStrictTools` | プロバイダーが厳密な JSON スキーマ ツール定義を受け入れるかどうか。デフォルト: `false`;組み込みの Anthropic モデルにより、生成されたメタデータでそれが可能になります。 |\n\n## OpenAI の互換性\n\n部分的な OpenAI 互換性を持つプロバイダーの場合は、`compat` フィールドを使用します。\n\n- プロバイダーレベル `compat` は、そのプロバイダーのすべてのモデルにデフォルトを適用します。\n- モデル レベルの `compat` は、そのモデルのプロバイダー レベルの値をオーバーライドします。\n\n```json\n{\n  \"providers\": {\n    \"local-llm\": {\n      \"baseUrl\": \"http://localhost:8080/v1\",\n      \"api\": \"openai-completions\",\n      \"compat\": {\n        \"supportsUsageInStreaming\": false,\n        \"maxTokensField\": \"max_tokens\"\n      },\n      \"models\": [...]\n    }\n  }\n}\n```\n\n| 分野 | 説明 |\n|-------|-------------|\n| `supportsStore` | プロバイダーは `store` フィールドをサポートします |\n| `supportsDeveloperRole` | `developer` と `system` の役割を使用する |\n| `supportsReasoningEffort` | `reasoning_effort` パラメータのサポート |\n| `supportsUsageInStreaming` | `stream_options: { include_usage: true }` をサポート (デフォルト: `true`) |\n| `supportsFinishReason` | ストリーミングされた応答に `finish_reason` が含まれるかどうか。 `false` の場合、ストリームが終了すると pi は `stop` または `toolUse` を推測します。デフォルト: `true`。 |\n| `maxTokensField` | `max_completion_tokens` または `max_tokens` を使用してください |\n| `requiresToolResultName` | ツールの結果メッセージに `name` を含める |\n| `requiresAssistantAfterToolResult` | ツールの結果後のユーザー メッセージの前にアシスタント メッセージを挿入する |\n| `requiresThinkingAsText` | 思考ブロックをプレーンテキストに変換する |\n| `requiresReasoningContentOnAssistantMessages` | 推論が有効な場合、再生されるすべてのアシスタント メッセージに空の `reasoning_content` を含めます |\n| `thinkingFormat` | `reasoning_effort`、`openrouter`、`deepseek`、`together`、`baseten`、`zai`、`qwen`、`chat-template`、`qwen-chat-template` の思考パラメーターを使用します |\n| `chatTemplateKwargs` | `chat_template_kwargs` の `thinkingFormat: \"chat-template\"` の値。 pi 制御の思考値には `{ \"$var\": \"thinking.enabled\" }` または `{ \"$var\": \"thinking.effort\" }` を使用します |\n| `chatTemplateArgs` | `chat_template_args` の `thinkingFormat: \"baseten\"` の値。 pi 制御の思考値には `{ \"$var\": \"thinking.enabled\" }` または `{ \"$var\": \"thinking.effort\" }` を使用します |\n| `cacheControlFormat` | システム プロンプト、最後のツール定義、最後のユーザー、アシスタント、またはツール結果のテキスト コンテンツで、Anthropic スタイルの `cache_control` マーカーを使用します。現在、`anthropic` のみがサポートされています。 |\n| `sendSessionAffinityHeaders` | `openai-completions` の場合、キャッシュが有効なときにセッション ID からセッション アフィニティ ヘッダーを送信します。デフォルト: `false`。 |\n| `sessionAffinityFormat` | `openai-completions` と `openai-responses` のセッション アフィニティ ヘッダー形式: `openai` は `session_id`/`x-client-request-id` (補完も `x-session-affinity`) を送信し、`openai-nosession` はアンダースコアを含む `session_id` ヘッダーを省略し、`openrouter` は `x-session-id` を送信します。 `prompt_cache_key` ボディパラメータには影響しません。デフォルト: 自動検出。 |\n| `supportsStrictMode` | プロバイダーが厳密な JSON スキーマ関数ツール定義を受け入れるかどうか。デフォルトは API によって異なります。組み込みの OpenAI モデルは、明示的な機能メタデータを保持します。 |\n| `supportsOpenAIGrammarTools` | OpenAI 互換の API がカスタム Lark/正規表現文法ツールを発行するかどうか。 `false` の場合、文法制限のあるツールは通常の機能ツールに戻ります。デフォルト: `false`;組み込みのモデル カタログにより、OpenAI、OpenAI Codex、Azure OpenAI、GitHub Copilot、opencode、Cloudflare AI Gateway 上の GPT-5+ モデルで有効になります。 |\n| `deferredToolsMode` | プロバイダー固有の遅延ツールのシリアル化を使用します。現在、Kimi の OpenAI 互換チャット補完形式では `\"kimi\"` のみがサポートされています。 |\n| `supportsLongCacheRetention` | キャッシュ保持期間が `long` の場合にプロバイダーが長期キャッシュ保持を受け入れるかどうか: OpenAI プロンプト キャッシュの場合は `prompt_cache_retention: \"24h\"`、または `cacheControlFormat` が `anthropic` の場合は `cache_control.ttl: \"1h\"` です。デフォルト: `true`。 |\n| `openRouterRouting` | OpenRouter プロバイダーのルーティング設定。このオブジェクトは、[OpenRouter API request](https://openrouter.ai/docs/guides/routing/provider-selection) の `provider` フィールドでそのまま送信されます。 |\n| `vercelGatewayRouting` | プロバイダー選択用の Vercel AI Gateway ルーティング構成 (`only`、`order`) |\n\n`openrouter` は `reasoning: { effort }` を使用します。 `together` は `reasoning: { enabled }` を使用し、`supportsReasoningEffort` が有効な場合は `reasoning_effort` も使用します。 `qwen` はトップレベルの `enable_thinking` を使用します。 `chat_template_kwargs.enable_thinking` と `preserve_thinking` を必要とするローカルの Qwen 互換サーバーには、`qwen-chat-template` を使用します。 DeepSeek V3.x テンプレートの場合は `chatTemplateKwargs: { \"thinking\": { \"$var\": \"thinking.enabled\" } }` など、構成可能な `chat_template_kwargs` が必要な vLLM/Hugging Face チャット テンプレートには `chat-template` を使用します。 `chat_template_args` を通じてトグルコントロールを公開し、オプションでトップレベルの `reasoning_effort` をサポートするプロバイダーには、`thinkingFormat: \"baseten\"` を `chatTemplateArgs` とともに使用します。\n\n`cacheControlFormat: \"anthropic\"` は、テキスト コンテンツおよびツール定義上の `cache_control` マーカーを介して Anthropic スタイルのプロンプト キャッシュを公開する OpenAI 互換プロバイダー用です。\n\n例：\n\n```json\n{\n  \"providers\": {\n    \"openrouter\": {\n      \"baseUrl\": \"https://openrouter.ai/api/v1\",\n      \"apiKey\": \"$OPENROUTER_API_KEY\",\n      \"api\": \"openai-completions\",\n      \"models\": [\n        {\n          \"id\": \"openrouter/anthropic/claude-3.5-sonnet\",\n          \"name\": \"OpenRouter Claude 3.5 Sonnet\",\n          \"compat\": {\n            \"openRouterRouting\": {\n              \"allow_fallbacks\": true,\n              \"require_parameters\": false,\n              \"data_collection\": \"deny\",\n              \"zdr\": true,\n              \"enforce_distillable_text\": false,\n              \"order\": [\"anthropic\", \"amazon-bedrock\", \"google-vertex\"],\n              \"only\": [\"anthropic\", \"amazon-bedrock\"],\n              \"ignore\": [\"gmicloud\", \"friendli\"],\n              \"quantizations\": [\"fp16\", \"bf16\"],\n              \"sort\": {\n                \"by\": \"price\",\n                \"partition\": \"model\"\n              },\n              \"max_price\": {\n                \"prompt\": 10,\n                \"completion\": 20\n              },\n              \"preferred_min_throughput\": {\n                \"p50\": 100,\n                \"p90\": 50\n              },\n              \"preferred_max_latency\": {\n                \"p50\": 1,\n                \"p90\": 3,\n                \"p99\": 5\n              }\n            }\n          }\n        }\n      ]\n    }\n  }\n}\n```\n\nVercel AI ゲートウェイの例:\n\n```json\n{\n  \"providers\": {\n    \"vercel-ai-gateway\": {\n      \"baseUrl\": \"https://ai-gateway.vercel.sh/v1\",\n      \"apiKey\": \"$AI_GATEWAY_API_KEY\",\n      \"api\": \"openai-completions\",\n      \"models\": [\n        {\n          \"id\": \"moonshotai/kimi-k2.5\",\n          \"name\": \"Kimi K2.5 (Fireworks via Vercel)\",\n          \"reasoning\": true,\n          \"input\": [\"text\", \"image\"],\n          \"cost\": { \"input\": 0.6, \"output\": 3, \"cacheRead\": 0, \"cacheWrite\": 0 },\n          \"contextWindow\": 262144,\n          \"maxTokens\": 262144,\n          \"compat\": {\n            \"vercelGatewayRouting\": {\n              \"only\": [\"fireworks\", \"novita\"],\n              \"order\": [\"fireworks\", \"novita\"]\n            }\n          }\n        }\n      ]\n    }\n  }\n}\n```","sourceFile":"models.md"},"packages":{"title":"Pi Packages","markdown":"> pi は pi パッケージの作成に役立ちます。拡張機能、スキル、prompt templates、またはテーマをバンドルするよう依頼してください。\n\n\nPi パッケージには拡張機能、スキル、prompt templates、テーマがバンドルされているため、npm または git を通じて共有できます。パッケージは、`pi` キーの下の `package.json` でリソースを宣言することも、従来のディレクトリを使用することもできます。\n\n## 目次\n\n- [Install and Manage](#install-and-manage)\n- [Package Sources](#package-sources)\n- [Creating a Pi Package](#creating-a-pi-package)\n- [Package Structure](#package-structure)\n- [Dependencies](#dependencies)\n- [Package Filtering](#package-filtering)\n- [Enable and Disable Resources](#enable-and-disable-resources)\n- [Scope and Deduplication](#scope-and-deduplication)\n\n## インストールと管理\n\n> **セキュリティ:** Pi パッケージは完全なシステム アクセスで実行されます。 Extensions 任意のコードを実行し、スキルは実行可能ファイルの実行を含む任意のアクションを実行するようにモデルに指示できます。サードパーティのパッケージをインストールする前に、ソース コードを確認してください。\n\n```bash\npi install npm:@foo/bar@1.0.0\npi install git:github.com/user/repo@v1\npi install https://github.com/user/repo  # raw URLs work too\npi install /absolute/path/to/package\npi install ./relative/path/to/package\n\npi remove npm:@foo/bar\npi list                     # show installed packages from settings\npi update                   # update pi only\npi update --all             # update pi, update packages, and reconcile pinned git refs\npi update --extensions      # update packages and reconcile pinned git refs only\npi update --models          # refresh model catalogs only\npi update --self            # update pi only\npi update --self --force    # reinstall pi even if current\npi update npm:@foo/bar      # update one package\npi update --extension npm:@foo/bar\n```\n\nこれらのコマンドは pi パッケージを管理し、`pi update` で pi CLI のインストールを更新できます。 pi 自体をアンインストールするには、[Quickstart](quickstart.md#uninstall) を参照してください。\n\nデフォルトでは、`install` と `remove` はユーザー設定 (`~/.pi/agent/settings.json`) に書き込みます。代わりに `-l` を使用してプロジェクト設定 (`.pi/settings.json`) に書き込みます。プロジェクト設定はチームと共有でき、プロジェクトが信頼された後、pi は起動時に不足しているパッケージを自動的にインストールします。\n\nパッケージをインストールせずに試すには、`--extension` または `-e` を使用します。これは、現在の実行のみのために一時ディレクトリにインストールされます。\n\n```bash\npi -e npm:@foo/bar\npi -e git:github.com/user/repo\n```\n\n## パッケージソース\n\nPi は設定で 3 つのソース タイプを受け入れ、`pi install` は受け入れます。\n\n### npm\n\n```\nnpm:@scope/pkg@1.2.3\nnpm:pkg\n```\n\n- バージョン管理された仕様は固定され、パッケージの更新によってスキップされます (`pi update --extensions`、`pi update --all`)。\n- ユーザーのインストールは `~/.pi/agent/npm/` 未満になります。\n- プロジェクトのインストールは `.pi/npm/` 未満になります。\n- `settings.json` に `npmCommand` を設定すると、npm パッケージ検索とインストール操作が `mise` や `asdf` などの特定のラッパー コマンドに固定されます。\n\n例：\n\n```json\n{\n  \"npmCommand\": [\"mise\", \"exec\", \"node@20\", \"--\", \"npm\"]\n}\n```\n\n### git\n\n```\ngit:github.com/user/repo@v1\ngit:git@github.com:user/repo@v1\nhttps://github.com/user/repo@v1\nssh://git@github.com/user/repo@v1\n```\n\n- `git:` プレフィックスがないと、プロトコル URL (`https://`、`http://`、`ssh://`、`git://`) のみが受け入れられます。\n- `git:` 接頭辞を使用すると、`github.com/user/repo` や `git@github.com:user/repo` などの短縮形式が受け入れられます。\n- HTTPS と SSH URL の両方がサポートされています。\n- SSH URL は、設定された SSH キーを自動的に使用します (`~/.ssh/config` を考慮)。\n- 非対話型実行 (例: CI) の場合、`GIT_TERMINAL_PROMPT=0` を設定して資格情報プロンプトを無効にし、`GIT_SSH_COMMAND` (例: `ssh -o BatchMode=yes -o ConnectTimeout=5`) を設定してフェイルファストにすることができます。\n- Ref は固定されたタグまたはコミットです。 `pi update --extensions` と `pi update --all` は、新しい参照に移動しませんが、既存のクローンを構成された参照に調整します。\n- `pi install git:host/user/repo@new-ref` を使用して設定を更新し、既存のパッケージを新しい固定参照に移動します。\n- `~/.pi/agent/git/<host>/<path>` (グローバル) または `.pi/git/<host>/<path>` (プロジェクト) にクローンが作成されます。\n- 調整によってチェックアウトが変更されると、pi はクローンをリセットしてクリーンアップし、`package.json` が存在する場合は `npm install` を実行します。\n\n**SSH 例:**\n```bash\n# git@host:path shorthand (requires git: prefix)\npi install git:git@github.com:user/repo\n\n# ssh:// protocol format\npi install ssh://git@github.com/user/repo\n\n# With version ref\npi install git:git@github.com:user/repo@v1.0.0\n```\n\n### ローカルパス\n\n```\n/absolute/path/to/package\n./relative/path/to/package\n```\n\nローカル パスはディスク上のファイルまたはディレクトリを指し、コピーせずに設定に追加されます。相対パスは、それらが含まれる設定ファイルに対して解決されます。パスがファイルの場合、単一の拡張子としてロードされます。ディレクトリの場合、pi はパッケージ ルールを使用してリソースを読み込みます。\n\n## Pi パッケージの作成\n\n`pi` マニフェストを `package.json` に追加するか、従来のディレクトリを使用します。見つけやすくするために、`pi-package` キーワードを含めます。\n\n```json\n{\n  \"name\": \"my-package\",\n  \"keywords\": [\"pi-package\"],\n  \"pi\": {\n    \"extensions\": [\"./extensions\"],\n    \"skills\": [\"./skills\"],\n    \"prompts\": [\"./prompts\"],\n    \"themes\": [\"./themes\"]\n  }\n}\n```\n\nパスはパッケージのルートからの相対パスです。配列はグロブ パターンと `!exclusions` をサポートします。\n\n### ギャラリーのメタデータ\n\n[package gallery](https://pi.dev/packages) には、`pi-package` でタグ付けされたパッケージが表示されます。 `video` または `image` フィールドを追加してプレビューを表示します。\n\n```json\n{\n  \"name\": \"my-package\",\n  \"keywords\": [\"pi-package\"],\n  \"pi\": {\n    \"extensions\": [\"./extensions\"],\n    \"video\": \"https://example.com/demo.mp4\",\n    \"image\": \"https://example.com/screenshot.png\"\n  }\n}\n```\n\n- **ビデオ**: MP4 のみ。デスクトップでは、ホバーすると自動再生されます。クリックすると全画面プレーヤーが開きます。\n- **画像**: PNG、JPEG、GIF、または WebP。静的プレビューとして表示されます。\n\n両方が設定されている場合は、ビデオが優先されます。\n\n## パッケージ構造\n\n### 大会ディレクトリ\n\n`pi` マニフェストが存在しない場合、pi は次のディレクトリからリソースを自動検出します。\n\n- `extensions/` は `.ts` および `.js` ファイルをロードします\n- `skills/` は再帰的に `SKILL.md` フォルダーを検索し、最上位の `.md` ファイルをスキルとしてロードします\n- `prompts/` は `.md` ファイルをロードします\n- `themes/` は `.json` ファイルをロードします\n\n## 依存関係\n\nサードパーティのランタイム依存関係は、`package.json` の `dependencies` に属します。拡張機能、スキル、prompt templates、またはテーマを登録しない依存関係も、`dependencies` に属します。 pi が npm または git からパッケージをインストールすると、`npm install` が実行されるため、これらの依存関係は自動的にインストールされます。\n\nPi には、拡張機能とスキルのコア パッケージがバンドルされています。これらのいずれかをインポートする場合は、`@earendil-works/pi-ai`、`@earendil-works/pi-agent-core`、`@earendil-works/pi-coding-agent`、`@earendil-works/pi-tui`、`typebox` を`\"*\"` の範囲で`peerDependencies` にリストし、バンドルしないでください。\n\n他の pi パッケージは tarball にバンドルする必要があります。これらを `dependencies` と `bundledDependencies` に追加し、`node_modules/` パスを通じてそれらのリソースを参照します。 Pi は個別のモジュール ルートを持つパッケージをロードするため、個別のインストールが衝突したりモジュールを共有したりすることはありません。\n\n例：\n\n```json\n{\n  \"dependencies\": {\n    \"shitty-extensions\": \"^1.0.1\"\n  },\n  \"bundledDependencies\": [\"shitty-extensions\"],\n  \"pi\": {\n    \"extensions\": [\"extensions\", \"node_modules/shitty-extensions/extensions\"],\n    \"skills\": [\"skills\", \"node_modules/shitty-extensions/skills\"]\n  }\n}\n```\n\n## パッケージのフィルタリング\n\n設定のオブジェクト フォームを使用して、パッケージが読み込むものをフィルターします。\n\n```json\n{\n  \"packages\": [\n    \"npm:simple-pkg\",\n    {\n      \"source\": \"npm:my-package\",\n      \"extensions\": [\"extensions/*.ts\", \"!extensions/legacy.ts\"],\n      \"skills\": [],\n      \"prompts\": [\"prompts/review.md\"],\n      \"themes\": [\"+themes/legacy.json\"]\n    }\n  ]\n}\n```\n\n`+path` と `-path` は、パッケージのルートを基準とした正確なパスです。\n\n- そのタイプをすべてロードするにはキーを省略します。\n- そのタイプをロードしない場合は、`[]` を使用します。\n- `!pattern` は一致を除外します。\n- `+path` 強制的に正確なパスを含めます。\n- `-path` 正確なパスを強制的に除外します。\n- マニフェストの上にあるフィルターレイヤー。彼らはすでに許可されているものを絞り込みます。\n\n## リソースの有効化と無効化\n\n`pi config` を使用して、インストールされたパッケージおよびローカル ディレクトリの拡張機能、スキル、prompt templates、およびテーマを有効または無効にします。 `pi config` はグローバル設定 (`~/.pi/agent/settings.json`) で開始します。 Tab キーを押して、グローバル モードとプロジェクト ローカル モードを切り替えます。 `pi config -l` を使用して、継承されたグローバル リソースが淡色表示された状態でプロジェクト オーバーライド (`.pi/settings.json`) を開始します。\n\n## スコープと重複排除\n\nパッケージは、グローバル設定とプロジェクト設定の両方に表示できます。同じパッケージが両方に表示される場合、プロジェクト エントリが `autoload: false` でない限り、プロジェクト エントリが優先されます。その場合、グローバル エントリに対するデルタとして適用されます。アイデンティティは次によって決定されます。\n\n- npm: パッケージ名\n- git: 参照のないリポジトリ URL\n- ローカル: 解決された絶対パス","sourceFile":"packages.md"},"prompt-templates":{"title":"プロンプトテンプレート","markdown":"> pi は prompt templates を作成できます。ワークフローに合わせて構築するよう依頼してください。\n\n\nプロンプト テンプレートは、完全なプロンプトに展開される Markdown スニペットです。エディターに「`/name`」と入力してテンプレートを呼び出します。`name` は`.md` を除いたファイル名です。\n\n## 所在地\n\nPi は以下から prompt templates をロードします:\n\n- グローバル: `~/.pi/agent/prompts/*.md`\n- プロジェクト: `.pi/prompts/*.md` (プロジェクトが信頼された後のみ)\n- パッケージ: `prompts/` ディレクトリまたは `package.json` の `pi.prompts` エントリ\n- 設定: `prompts` ファイルまたはディレクトリを含む配列\n- CLI: `--prompt-template <path>` (繰り返し可能)\n\n`--no-prompt-templates` で検出を無効にします。\n\n## 形式\n\n```markdown\n---\ndescription: Review staged git changes\n---\nReview the staged changes (`git diff --cached`). Focus on:\n- Bugs and logic errors\n- Security issues\n- Error handling gaps\n```\n\n- ファイル名がコマンド名になります。 `review.md` は `/review` になります。\n- `description` はオプションです。欠落している場合は、空ではない最初の行が使用されます。\n- `argument-hint` はオプションです。設定すると、オートコンプリート ドロップダウンの説明の前にヒントが表示されます。\n\n### 引数のヒント\n\nオートコンプリートで予期される引数を表示するには、frontmatter で `argument-hint` を使用します。必須の引数には `<angle brackets>` を使用し、オプションの引数には `[square brackets]` を使用します。\n\n```markdown\n---\ndescription: Review PRs from URLs with structured issue and code analysis\nargument-hint: \"<PR-URL>\"\n---\n```\n\nこれは、オートコンプリート ドロップダウンで次のように表示されます。\n\n```\n→ pr   <PR-URL>       — Review PRs from URLs with structured issue and code analysis\n  is   <issue>        — Analyze GitHub issues (bugs or feature requests)\n  wr   [instructions] — Finish the current task end-to-end\n  cl   — Audit changelog entries before release\n```\n\n## 使用法\n\nエディターで「`/`」と入力し、その後にテンプレート名を入力します。オートコンプリートには、使用可能なテンプレートが説明とともに表示されます。\n\n```\n/review                           # Expands review.md\n/component Button                 # Expands with argument\n/component Button \"click handler\" # Multiple arguments\n```\n\n## 引数\n\nテンプレートは、位置引数、デフォルト、および単純なスライスをサポートしています。\n\n- `$1`、`$2`、...位置引数\n- 結合されたすべての引数に対して `$@` または `$ARGUMENTS`\n- `${1:-default}` は、存在するか空でない場合は arg 1 を使用し、それ以外の場合は `default`\n- `${@:-default}` または `${ARGUMENTS:-default}` は、存在するか空でない場合はすべての引数を使用し、それ以外の場合は `default`\n- `${@:N}` N 番目の位置からの引数 (1 から始まるインデックス)\n- N で始まる `L` 引数の場合は `${@:N:L}`\n\n例：\n\n```markdown\n---\ndescription: Create a component\n---\nCreate a React component named $1 with features: $@\n```\n\nデフォルト値はオプションの引数に役立ちます。\n\n```markdown\nSummarize the current state in ${1:-7} bullet points.\n```\n\n使用法: `/component Button \"onClick handler\" \"disabled support\"`\n\n## ルールをロードしています\n\n- `prompts/` でのテンプレートの検出は非再帰的です。\n- サブディレクトリにテンプレートが必要な場合は、`prompts` 設定またはパッケージ マニフェストを介して明示的に追加します。","sourceFile":"prompt-templates.md"},"providers":{"title":"Providers","markdown":"Pi は、環境変数または認証ファイルを介して OAuth および API key プロバイダーを介してサブスクリプションベースのプロバイダーをサポートします。組み込みカタログは pi に同梱されています。構成されたプロバイダーは、新しいカタログを更新し、オフラインで使用するためにそれらを `~/.pi/agent/models-store.json` にキャッシュすることがあります。\n\n## 目次\n\n- [Subscriptions](#subscriptions)\n- [API Keys](#api-keys)\n- [Auth File](#auth-file)\n- [Cloud Providers](#cloud-providers)\n- [llama.cpp](#llamacpp)\n- [Custom Providers](#custom-providers)\n- [Resolution Order](#resolution-order)\n\n## 定期購入\n\n対話モードで `/login` を使用し、プロバイダーを選択します。\n\n- ChatGPT Plus/Pro (コーデックス)\n- クロード プロ/マックス\n- GitHub 副操縦士\n- xAI (Grok/X サブスクリプション)\n- OpenRouter (OAuth-minted API key OpenRouter クレジットから請求)\n- 半径\n\n認証情報をクリアするには、`/logout` を使用します。トークンは `~/.pi/agent/auth.json` に保存され、期限切れになると自動更新されます。 OpenRouter は代わりに、自動的に期限切れにならないユーザー制御の API key を作成します。\n\n### OpenAI コーデックス\n\n- ChatGPT Plus または Pro サブスクリプションが必要です\n- OpenAI による公式承認: [Codex for OSS](https://developers.openai.com/community/codex-for-oss)\n\n### クロード プロ/マックス\n\nAnthropic サブスクリプション認証は、Claude Pro/Max アカウントに対して有効です。サードパーティのハーネスの使用量は [extra usage](https://claude.ai/settings/usage) から引き出され、Claude プランの制限に対してではなく、トークンごとに請求されます。\n\n### GitHub 副操縦士\n\n- github.com の場合は Enter を押すか、GitHub Enterprise Server ドメインを入力してください\n- 「モデルがサポートされていません」というメッセージが表示された場合は、VS Code で有効にします。Copilot Chat → モデル セレクター → モデルの選択 → 「有効にする」\n\n### xAI (Grok/X サブスクリプション)\n\n- `/login xai` を実行し、**サブスクリプションを使用** を選択します\n- `XAI_API_KEY` は **API key** を使用して引き続き利用可能です\n\n### オープンルーター\n\n- `/login openrouter` を実行し、**OpenRouter でサインイン** を選択して OpenRouter PKCE 認証フローを開きます\n- 認証により、OpenRouter クレジットから請求されるユーザー制御の OpenRouter API key が作成されます\n- リモート/ヘッドレス マシン (例: SSH 上) では、ブラウザはループバック コールバックに到達できません。代わりに、最終的なリダイレクト URL (または認証コード) をログイン プロンプトに貼り付けます。\n- `OPENROUTER_API_KEY` は **API key** を使用して引き続き利用可能です\n\n### 半径\n\nRadius は動的な `pi-messages` ゲートウェイです。 `/login radius` は OAuth トークンを `auth.json` に保管します。ゲートウェイ カタログは個別に更新され、`models-store.json` にキャッシュされます。カスタム Radius ゲートウェイは、`\"oauth\": \"radius\"` およびゲートウェイ `baseUrl` を使用して `models.json` で宣言できます。\n\n## API キー\n\n### 環境変数または認証ファイル\n\n対話モードで `/login` を使用し、プロバイダーを選択して API key を `auth.json` に保存するか、環境変数を介して資格情報を設定します。\n\n```bash\nexport ANTHROPIC_API_KEY=sk-ant-...\npi\n```\n\n| プロバイダー | 環境変数 | `auth.json`キー |\n|----------|----------------------|------------------|\n| 人間的 | `ANTHROPIC_API_KEY` | `anthropic` |\n| アント・リン | `ANT_LING_API_KEY` | `ant-ling` |\n| Azure OpenAI の応答 | `AZURE_OPENAI_API_KEY` | `azure-openai-responses` |\n| OpenAI | `OPENAI_API_KEY` | `openai` |\n| ディープシーク | `DEEPSEEK_API_KEY` | `deepseek` |\n| NVIDIA NIM | `NVIDIA_API_KEY` | `nvidia` |\n| Google ジェミニ | `GEMINI_API_KEY` | `google` |\n| アマゾンの岩盤 | `AWS_BEARER_TOKEN_BEDROCK` | `amazon-bedrock` |\n| ミストラル | `MISTRAL_API_KEY` | `mistral` |\n| グロク | `GROQ_API_KEY` | `groq` |\n| 大脳 | `CEREBRAS_API_KEY` | `cerebras` |\n| Cloudflare AI ゲートウェイ | `CLOUDFLARE_API_KEY` (+ `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_GATEWAY_ID`) | `cloudflare-ai-gateway` |\n| Cloudflare ワーカー AI | `CLOUDFLARE_API_KEY` (+ `CLOUDFLARE_ACCOUNT_ID`) | `cloudflare-workers-ai` |\n| xAI | `XAI_API_KEY` | `xai` |\n| オープンルーター | `OPENROUTER_API_KEY` | `openrouter` |\n| Vercel AI ゲートウェイ | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway` |\n| ZAIコーディングプラン（グローバル） | `ZAI_API_KEY` | `zai` |\n| ZAIコーディングプラン（中国） | `ZAI_CODING_CN_API_KEY` | `zai-coding-cn` |\n| OpenCode Zen | `OPENCODE_API_KEY` | `opencode` |\n| OpenCode Go | `OPENCODE_API_KEY` | `opencode-go` |\n| 半径 | `RADIUS_API_KEY` | `radius` |\n| Hugging Face | `HF_TOKEN` | `huggingface` |\n| 花火 | `FIREWORKS_API_KEY` | `fireworks` |\n| 一緒にAI | `TOGETHER_API_KEY` | `together` |\n| ベーステン | `BASETEN_API_KEY` | `baseten` |\n| コーディングのためのキミ | `KIMI_API_KEY` | `kimi-coding` |\n| ミニマックス | `MINIMAX_API_KEY` | `minimax` |\n| ミニマックス（中国） | `MINIMAX_CN_API_KEY` | `minimax-cn` |\n| Qwen Token Plan（既存カタログ） | `QWEN_TOKEN_PLAN_API_KEY` | `qwen-token-plan` |\n| Qwenトークンプラン（個人） | `QWEN_TOKEN_PLAN_API_KEY` | `qwen-token-plan-individual` |\n| Qwen トークン プラン (中国) | `QWEN_TOKEN_PLAN_CN_API_KEY` | `qwen-token-plan-cn` |\n| シャオミ MiMo | `XIAOMI_API_KEY` | `xiaomi` |\n| Xiaomi MiMo トークン プラン (中国) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` | `xiaomi-token-plan-cn` |\n| Xiaomi MiMo トークン プラン (アムステルダム) | `XIAOMI_TOKEN_PLAN_AMS_API_KEY` | `xiaomi-token-plan-ams` |\n| Xiaomi MiMo トークン プラン (シンガポール) | `XIAOMI_TOKEN_PLAN_SGP_API_KEY` | `xiaomi-token-plan-sgp` |\n\n環境変数と `auth.json` キーのリファレンス: [`packages/ai/src/env-api-keys.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/env-api-keys.ts) の [`const envMap`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/env-api-keys.ts)。\n\n#### 認証ファイル\n\n認証情報を `~/.pi/agent/auth.json` に保存します:\n\n```json\n{\n  \"anthropic\": { \"type\": \"api_key\", \"key\": \"sk-ant-...\" },\n  \"ant-ling\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"openai\": { \"type\": \"api_key\", \"key\": \"sk-...\" },\n  \"deepseek\": { \"type\": \"api_key\", \"key\": \"sk-...\" },\n  \"nvidia\": { \"type\": \"api_key\", \"key\": \"nvapi-...\" },\n  \"google\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"opencode\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"opencode-go\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"together\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"qwen-token-plan\":  { \"type\": \"api_key\", \"key\": \"sk-sp-...\" },\n  \"qwen-token-plan-individual\": { \"type\": \"api_key\", \"key\": \"sk-sp-...\" },\n  \"qwen-token-plan-cn\": { \"type\": \"api_key\", \"key\": \"sk-sp-...\" },\n  \"xiaomi\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"xiaomi-token-plan-cn\":  { \"type\": \"api_key\", \"key\": \"...\" },\n  \"xiaomi-token-plan-ams\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"xiaomi-token-plan-sgp\": { \"type\": \"api_key\", \"key\": \"...\" }\n}\n```\n\n`qwen-token-plan-individual` は同じ国際エンドポイントを使用し、`QWEN_TOKEN_PLAN_API_KEY` は\n`qwen-token-plan` ただし、ピッカーは個人サブスクリプション用に文書化されたモデルに制限されます。既存の\nプロバイダーは、下位​​互換性を確保するために、より広範なカタログを保持しています。 `auth.json`を使用する場合は、\n選択したプロバイダーの資格情報。環境変数は両方の国際プロバイダーによって共有されます。\n\nファイルは `0600` 権限 (ユーザー読み取り/書き込みのみ) で作成されます。認証ファイルの資格情報は環境変数よりも優先されます。\n\nAPI key 認証情報には、プロバイダー スコープの環境値を含めることもできます。これらの値は、資格情報キー、プロバイダー/モデル ヘッダー、Cloudflare アカウント ID、Azure OpenAI 設定、Vertex プロジェクト/場所、Bedrock 設定、`PI_CACHE_RETENTION`、`HTTP_PROXY`/`HTTPS_PROXY` などのプロバイダー構成を解決するときに、環境変数を処理する前に使用されます。\n\n```json\n{\n  \"cloudflare-ai-gateway\": {\n    \"type\": \"api_key\",\n    \"key\": \"$CLOUDFLARE_API_KEY\",\n    \"env\": {\n      \"CLOUDFLARE_API_KEY\": \"...\",\n      \"CLOUDFLARE_ACCOUNT_ID\": \"account-id\",\n      \"CLOUDFLARE_GATEWAY_ID\": \"gateway-id\"\n    }\n  }\n}\n```\n\npi がプロジェクト シェル環境とは異なるプロバイダー設定を使用する必要がある場合にこれを使用します。\n\n### キーの解決\n\n`key` フィールドは、コマンドの実行、環境補間、およびリテラルをサポートします。\n\n- **シェル コマンド:** 開始時の `\"!command\"` は値全体をコマンドとして実行し、stdout (プロセス存続期間中キャッシュされる) を使用します。\n  ```json\n  { \"type\": \"api_key\", \"key\": \"!security find-generic-password -ws 'anthropic'\" }\n  { \"type\": \"api_key\", \"key\": \"!op read 'op://vault/item/credential'\" }\n  ```\n- **環境補間:** `\"$ENV_VAR\"` または `\"${ENV_VAR}\"` は、名前付き変数の値を使用します。補間はより大きなリテラル内で機能します。\n  ```json\n  { \"type\": \"api_key\", \"key\": \"$MY_ANTHROPIC_KEY\" }\n  { \"type\": \"api_key\", \"key\": \"${KEY_PREFIX}_${KEY_SUFFIX}\" }\n  ```\n  `$FOO_BAR` は変数 `FOO_BAR` です。 `BAR` がリテラルテキストの場合は、`${FOO}_BAR` を使用します。環境変数が欠落していると、値が未解決になります。\n- **エスケープ:** `\"$\"` はリテラル `\"$\"` を出力します。 `\"$!\"` は、コマンドの実行をトリガーせずにリテラルの `\"!\"` を発行します。\n  ```json\n  { \"type\": \"api_key\", \"key\": \"$$literal-dollar-prefix\" }\n  { \"type\": \"api_key\", \"key\": \"$!literal-bang-prefix\" }\n  ```\n- **リテラル値:** 直接使用されます。 `MY_API_KEY` などの単純な大文字文字列はリテラルです。環境変数には `$MY_API_KEY` を使用します。\n  ```json\n  { \"type\": \"api_key\", \"key\": \"sk-ant-...\" }\n  { \"type\": \"api_key\", \"key\": \"public\" }\n  ```\n\nOAuth 資格情報も、`/login` の後にここに保存され、自動的に管理されます。\n\n## クラウドProviders\n\n### Azure OpenAI\n\n```bash\nexport AZURE_OPENAI_API_KEY=...\nexport AZURE_OPENAI_BASE_URL=https://your-resource.ai.azure.com\n# also supported: https://your-resource.cognitiveservices.azure.com\n# also supported: https://your-resource.openai.azure.com\n# root endpoints are auto-normalized to /openai/v1\n# or use resource name instead of base URL\nexport AZURE_OPENAI_RESOURCE_NAME=your-resource\n\n# Optional\nexport AZURE_OPENAI_API_VERSION=2024-02-01\nexport AZURE_OPENAI_DEPLOYMENT_NAME_MAP=gpt-4=my-gpt4,gpt-4o=my-gpt4o\n```\n\n### アマゾンの岩盤\n\n`/login amazon-bedrock` を使用して Bedrock API key を保存するか、以下のアンビエント AWS 認証情報ソースのいずれかを構成します。\n\n```bash\n# Option 1: AWS Profile\nexport AWS_PROFILE=your-profile\n\n# Option 2: IAM Keys\nexport AWS_ACCESS_KEY_ID=AKIA...\nexport AWS_SECRET_ACCESS_KEY=...\n\n# Option 3: Bearer Token\nexport AWS_BEARER_TOKEN_BEDROCK=...\n\n# Optional region (defaults to us-east-1)\nexport AWS_REGION=us-west-2\n```\n\nECS タスク ロール (`AWS_CONTAINER_CREDENTIALS_*`) および IRSA (`AWS_WEB_IDENTITY_TOKEN_FILE`) もサポートします。\n\n```bash\npi --provider amazon-bedrock --model us.anthropic.claude-sonnet-4-20250514-v1:0\n```\n\nプロンプト キャッシュは、ID に認識可能なモデル名 (ベース モデルおよびシステム定義の推論プロファイル) が含まれるクロード モデルに対して自動的に有効になります。アプリケーション推論プロファイル (ARN にモデル名が含まれていない) の場合、`AWS_BEDROCK_FORCE_CACHE=1` を設定してキャッシュ ポイントを有効にします。\n\n```bash\nexport AWS_BEDROCK_FORCE_CACHE=1\npi --provider amazon-bedrock --model arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123\n```\n\nBedrock API プロキシに接続している場合は、次の環境変数を使用できます。\n\n```bash\n# Set the URL for the Bedrock proxy (standard AWS SDK env var)\nexport AWS_ENDPOINT_URL_BEDROCK_RUNTIME=https://my.corp.proxy/bedrock\n\n# Set if your proxy does not require authentication\nexport AWS_BEDROCK_SKIP_AUTH=1\n\n# Set if your proxy only supports HTTP/1.1\nexport AWS_BEDROCK_FORCE_HTTP1=1\n```\n\n### Cloudflare AI ゲートウェイ\n\n`CLOUDFLARE_API_KEY`は`/login`で設定できます。アカウント ID とゲートウェイ スラグは、環境変数として、または `auth.json` の API key 資格情報の `env` オブジェクトに設定できます。\n\n```bash\nexport CLOUDFLARE_API_KEY=...           # or use /login\nexport CLOUDFLARE_ACCOUNT_ID=...\nexport CLOUDFLARE_GATEWAY_ID=...        # create at dash.cloudflare.com → AI → AI Gateway\npi --provider cloudflare-ai-gateway --model \"claude-sonnet-4-5\"\n```\n\nCloudflare AIゲートウェイを介してOpenAI、Anthropic、Workers AIにルーティングします。 Workers AI は、統合された API (`/compat`) およびプレフィックス付きモデル ID (`workers-ai/@cf/...`) を使用します。 OpenAI は、`gpt-5.1` などのネイティブ OpenAI モデル ID を持つ OpenAI パススルー ルート (`/openai`) を使用します。 Anthropic は、`claude-sonnet-4-5` などのネイティブ Anthropic モデル ID を持つ Anthropic パススルー ルート (`/anthropic`) を使用します。\n\nAI ゲートウェイの認証では、`CLOUDFLARE_API_KEY` が `cf-aig-authorization` として使用されます。アップストリーム認証は次のいずれかになります。\n\n| モード | 認証のリクエスト | アップストリーム認証 |\n|------|--------------|---------------|\n| ワーカーAI | Cloudflareトークンのみ | Cloudflareネイティブ |\n| 統合請求 | Cloudflareトークンのみ | Cloudflareはアップストリーム認証を処理し、クレジットを差し引きます |\n| 保存されたBYOK | Cloudflareトークンのみ | Cloudflareは、AIゲートウェイダッシュボードに保存されているプロバイダーキーを挿入します |\n| インラインBYOK | Cloudflareトークンとアップストリームの`Authorization`ヘッダー | リクエストは上流プロバイダーキーを提供します |\n\n通常の pi の使用の場合は、統合請求または保存された BYOK をお勧めします。インライン BYOK では、たとえば `models.json` プロバイダー/モデルのオーバーライドを介して、Cloudflare AI Gateway プロバイダーの追加のアップストリーム `Authorization` ヘッダーを構成する必要があります。\n\n### Cloudflare ワーカー AI\n\n`CLOUDFLARE_API_KEY`は`/login`で設定できます。 `CLOUDFLARE_ACCOUNT_ID` は、環境変数として、または `auth.json` の API key 認証情報の `env` オブジェクトに設定できます。\n\n```bash\nexport CLOUDFLARE_API_KEY=...           # or use /login\nexport CLOUDFLARE_ACCOUNT_ID=...\npi --provider cloudflare-workers-ai --model \"@cf/moonshotai/kimi-k2.6\"\n```\n\nPi は、[prefix caching](https://developers.cloudflare.com/workers-ai/features/prompt-caching/) 割引に対して `x-session-affinity` を自動的に設定します。\n\n### Google バーテックス AI\n\nアプリケーションのデフォルト認証情報を使用します。\n\n```bash\ngcloud auth application-default login\nexport GOOGLE_CLOUD_PROJECT=your-project\nexport GOOGLE_CLOUD_LOCATION=us-central1\n```\n\nまたは、`GOOGLE_APPLICATION_CREDENTIALS` をサービス アカウント キー ファイルに設定します。\n\n## llama.cpp\n\nPi は、llama.cpp ルーターサーバーをサポートします。 `/login llama.cpp` で設定し、`/llama` でロードされたモデルを管理し、`/model` でロードされたモデルを選択します。\n\nサーバーのセットアップ、モデルのディレクトリのレイアウト、環境変数、およびコマンドの使用法については、[llama.cpp](llama-cpp.md) を参照してください。\n\n## カスタムProviders\n\n**models.json 経由:** Ollama、LM Studio、vLLM、またはサポートされている API (OpenAI Completions、OpenAI Responses、Anthropic Messages、Google Generative AI) を話すプロバイダーを追加します。 [models.md](models.md)を参照してください。\n\n**拡張機能経由:** カスタム API 実装または OAuth フローが必要なプロバイダーの場合は、拡張機能を作成します。 [custom-provider.md](custom-provider.md) と [examples/extensions/custom-provider-gitlab-duo](../examples/extensions/custom-provider-gitlab-duo/) を参照してください。\n\n## 解決順序\n\nプロバイダーの資格情報を解決する場合:\n\n1. CLI `--api-key` フラグ\n2. `auth.json` エントリ (API key または OAuth トークン)\n3. 環境変数\n4. `models.json` からのカスタムプロバイダーキー","sourceFile":"providers.md"},"quickstart":{"title":"クイックスタート","markdown":"このページでは、インストールから便利な最初の pi セッションまでを説明します。\n\n## インストール\n\nPi は npm パッケージとして配布されます。\n\n```bash\nnpm install -g --ignore-scripts @earendil-works/pi-coding-agent\n```\n\n`--ignore-scripts` は、インストール中に依存関係のライフサイクル スクリプトを無効にします。 Pi では、通常の npm インストールにインストール スクリプトは必要ありません。\n\n### アンインストール\n\npi をインストールしたパッケージ マネージャーを使用します。 curl インストーラーは npm をグローバルに使用するため、curl と npm のインストールは npm で削除されます。\n\n```bash\n# curl installer or npm install -g\nnpm uninstall -g @earendil-works/pi-coding-agent\n\n# pnpm\npnpm remove -g @earendil-works/pi-coding-agent\n\n# Yarn\nyarn global remove @earendil-works/pi-coding-agent\n\n# Bun\nbun uninstall -g @earendil-works/pi-coding-agent\n```\n\npi をアンインストールすると、設定、資格情報、セッション、およびインストールされている pi パッケージが `~/.pi/agent/` に残ります。\n\n次に、動作させたいプロジェクト ディレクトリで pi を起動します。\n\n```bash\ncd /path/to/project\npi\n```\n\n## 認証する\n\nPi は、環境変数または認証ファイルを通じて subscription providers から `/login`、または API キープロバイダーを使用できます。\n\n### オプション 1: サブスクリプション ログイン\n\npi を起動して実行します。\n\n```text\n/login\n```\n\n次にプロバイダーを選択します。組み込みのサブスクリプション ログインには、Claude Pro/Max、ChatGPT Plus/Pro (Codex)、および GitHub Copilot が含まれます。\n\n### オプション 2: API key\n\npi を起動する前に API key を設定します。\n\n```bash\nexport ANTHROPIC_API_KEY=sk-ant-...\npi\n```\n\n`/login` を実行し、API キープロバイダーを選択して、キーを `~/.pi/agent/auth.json` に保存することもできます。\n\nサポートされているすべてのプロバイダー、環境変数、クラウド プロバイダーの設定については、「[Providers](providers.md)」を参照してください。\n\n## 最初のセッション\n\npi が起動したら、リクエストを入力して Enter キーを押します。\n\n```text\nSummarize this repository and tell me how to run its checks.\n```\n\nデフォルトでは、pi はモデルに次の 4 つのツールを提供します。\n\n- `read` - ファイルの読み取り\n- `write` - ファイルを作成または上書きします\n- `edit` - パッチファイル\n- `bash` - シェルコマンドを実行します\n\n追加の組み込み読み取り専用ツール (`grep`、`find`、`ls`) は、ツール オプションを通じて使用できます。 Pi は現在の作業ディレクトリで実行され、そこでファイルを変更できます。ロールバックを簡単にしたい場合は、git または別のチェックポイント作成ワークフローを使用してください。\n\n## pi プロジェクトの指示を与える\n\nPi は起動時に context files をロードします。 `AGENTS.md` ファイルを追加して、プロジェクトでの作業方法を指示します。\n\n```markdown\n# Project Instructions\n\n- Run `npm run check` after code changes.\n- Do not run production migrations locally.\n- Keep responses concise.\n```\n\nPi ロード:\n\n- `~/.pi/agent/AGENTS.md` グローバル命令\n- 親ディレクトリと現在のディレクトリからの `AGENTS.md` または `CLAUDE.md`\n\nディレクトリに `AGENTS.override.md` が含まれている場合、Pi はそのディレクトリから `AGENTS.md` または `CLAUDE.md` の代わりにそれをロードします。\n\ncontext files を変更した後、pi を再起動するか、`/reload` を実行します。\n\n## よく試すこと\n\n### 参照ファイル\n\nエディターで「`@`」と入力してファイルをあいまい検索するか、コマンドラインでファイルを渡します。\n\n```bash\npi @README.md \"Summarize this\"\npi @src/app.ts @src/app.test.ts \"Review these together\"\n```\n\n画像またはテキストは Ctrl+V (Windows では Alt+V) で貼り付けることができます。画像をサポートされている端末にドラッグすることもできます。\n\n### シェルコマンドを実行する\n\nインタラクティブモードの場合:\n\n```text\n!npm run lint\n```\n\nコマンド出力はモデルに送信されます。出力をモデル コンテキストに追加せずにコマンドを実行するには、`!!command` を使用します。\n\n### モデルの切り替え\n\n`/model` または Ctrl+L を使用してモデルを選択します。 Shift+Tab を使用して思考レベルを切り替えます。 Ctrl+P / Shift+Ctrl+P を使用して、スコープ付きモデルを循環します。\n\n### 後で続ける\n\nセッションは自動的に保存されます。\n\n```bash\npi -c                  # Continue most recent session\npi -r                  # Browse previous sessions\npi --name \"my task\"    # Set session display name at startup\npi --session <path|id> # Open a specific session\n```\n\npi 内では、`/resume`、`/new`、`/tree`、`/fork`、`/clone` を使用してセッションを管理します。\n\n### 非対話型モード\n\nワンショット プロンプトの場合:\n\n```bash\npi -p \"Summarize this codebase\"\ncat README.md | pi -p \"Summarize this text\"\npi -p @screenshot.png \"What's in this image?\"\n```\n\nJSON イベント出力には `--mode json` を、プロセス統合には `--mode rpc` を使用します。\n\n## 次のステップ\n\n- [Using Pi](usage.md) - インタラクティブ モード、slash commands、セッション、context files、および CLI リファレンス。\n- [Providers](providers.md) - 認証とモデルのセットアップ。\n- [Settings](settings.md) - グローバル構成とプロジェクト構成。\n- [Keybindings](keybindings.md) - ショートカットとカスタマイズ。\n- [Pi Packages](packages.md) - 共有拡張機能、スキル、プロンプト、テーマをインストールします。\n\nプラットフォームのメモ: [Windows](windows.md)、[Termux](termux.md)、[tmux](tmux.md)、[Terminal setup](terminal-setup.md)、[Shell aliases](shell-aliases.md)。","sourceFile":"quickstart.md"},"rpc":{"title":"RPCモード","markdown":"RPC モードでは、stdin/stdout 上の JSON プロトコルを介したコーディング エージェントのヘッドレス操作が有効になります。これは、他のアプリケーション、IDE、またはカスタム UI にエージェントを埋め込む場合に便利です。\n\n**Node.js/TypeScript ユーザーへの注意**: Node.js アプリケーションを構築している場合は、サブプロセスを生成する代わりに、`@earendil-works/pi-coding-agent` から直接 `AgentSession` を使用することを検討してください。 APIについては、[`src/core/agent-session.ts`](../src/core/agent-session.ts)を参照してください。サブプロセスベースの TypeScript クライアントについては、[`src/modes/rpc/rpc-client.ts`](../src/modes/rpc/rpc-client.ts) を参照してください。\n\n## RPCモードの開始\n\n```bash\npi --mode rpc [options]\n```\n\n一般的なオプション:\n- `--provider <name>`: LLM プロバイダー (anthropic、openai、google など) を設定します。\n- `--model <pattern>`: モデルパターンまたは ID (`provider/id` およびオプションの `:<thinking>` をサポート)\n- `--name <name>` / `-n <name>`: 起動時のセッション表示名を設定します\n- `--no-session`: セッションの永続性を無効にする\n- `--session-dir <path>`: カスタムセッション保存ディレクトリ\n\n## プロトコルの概要\n\n- **コマンド**: JSON オブジェクトが stdin に送信されます (1 行に 1 つ)\n- **応答**: コマンドの成功/失敗を示す `type: \"response\"` を持つ JSON オブジェクト\n- **イベント**: エージェント イベントは JSON 行として stdout にストリーミングされます\n\nすべてのコマンドは、リクエスト/レスポンス相関のためのオプションの `id` フィールドをサポートしています。指定した場合、対応する応答には同じ `id` が含まれます。 `bash_execution_update` イベントには、元の `bash` コマンドの `id` も含ま​​れます。\n\n### フレーミング\n\nRPC モードは、LF (`\\n`) を唯一のレコード区切り文字とする厳密な JSONL セマンティクスを使用します。\n\nこれはクライアントにとって重要です。\n- `\\n` のみでレコードを分割します\n- 末尾の `\\r` を削除して、オプションの `\\r\\n` 入力を受け入れます\n- Unicode 区切り文字を改行として扱う汎用の行リーダーを使用しないでください。\n\n特に、ノード `readline` は、JSON 文字列内で有効な `U+2028` と `U+2029` でも分割されるため、RPC モードのプロトコルに準拠していません。\n\n## コマンド\n\n### プロンプト\n\n#### プロンプト\n\nユーザー プロンプトをエージェントに送信します。コマンド応答は、プロンプトが受け入れられるか、キューに入れられるか、処理された後に出力されます。イベントは受け入れられた後も非同期でストリーミングを続けます。\n\n```json\n{\"id\": \"req-1\", \"type\": \"prompt\", \"message\": \"Hello, world!\"}\n```\n\n画像付き:\n```json\n{\"type\": \"prompt\", \"message\": \"What's in this image?\", \"images\": [{\"type\": \"image\", \"data\": \"base64-encoded-data\", \"mimeType\": \"image/png\"}]}\n```\n\n**ストリーミング中**: エージェントがすでにストリーミングしている場合は、メッセージをキューに入れるには `streamingBehavior` を指定する必要があります。\n\n```json\n{\"type\": \"prompt\", \"message\": \"New instruction\", \"streamingBehavior\": \"steer\"}\n```\n\n- `\"steer\"`: エージェントの実行中にメッセージをキューに入れます。これは、現在のアシスタント ターンがツール呼び出しの実行を終了した後、次の LLM 呼び出しの前に配信されます。\n- `\"followUp\"`: エージェントが終了するまで待ちます。メッセージはエージェントが停止した場合にのみ配信されます。\n\nエージェントがストリーミング中であり、`streamingBehavior` が指定されていない場合、コマンドはエラーを返します。\n\n**拡張コマンド**: メッセージが拡張コマンド (例: `/mycommand`) の場合、ストリーミング中でもすぐに実行されます。拡張コマンドは、`pi.sendMessage()` を介して独自の LLM インタラクションを管理します。\n\n**入力拡張**: スキルコマンド (`/skill:name`) と prompt templates (`/template`) は送信/キューイング前に拡張されます。\n\n応答：\n```json\n{\"id\": \"req-1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true}\n```\n\n`success: true` は、プロンプトが受け入れられた、キューに入れられた、またはすぐに処理されたことを意味します。 `success: false` は、プロンプトが受け入れられる前に拒否されたことを意味します。受け入れ後の失敗は、同じリクエスト ID に対する 2 番目の `response` としてではなく、通常のイベントおよびメッセージ ストリームを通じて報告されます。\n\n`images` フィールドはオプションです。各画像は `ImageContent` 形式: `{\"type\": \"image\", \"data\": \"base64-encoded-data\", \"mimeType\": \"image/png\"}` を使用します。\n\n#### 操縦する\n\nエージェントの実行中にステアリング メッセージをキューに入れます。これは、現在のアシスタント ターンがツール呼び出しの実行を終了した後、次の LLM 呼び出しの前に配信されます。スキルコマンドとprompt templatesが拡張されます。拡張コマンドは使用できません (代わりに `prompt` を使用してください)。\n\n```json\n{\"type\": \"steer\", \"message\": \"Stop and do this instead\"}\n```\n\n画像付き:\n```json\n{\"type\": \"steer\", \"message\": \"Look at this instead\", \"images\": [{\"type\": \"image\", \"data\": \"base64-encoded-data\", \"mimeType\": \"image/png\"}]}\n```\n\n`images` フィールドはオプションです。各画像は `ImageContent` 形式 (`prompt` と同じ) を使用します。\n\n応答：\n```json\n{\"type\": \"response\", \"command\": \"steer\", \"success\": true}\n```\n\nステアリング メッセージの処理方法の制御については、[set_steering_mode](#set_steering_mode) を参照してください。\n\n#### フォローアップ\n\nエージェントの終了後に処理されるフォローアップ メッセージをキューに入れます。エージェントがツール呼び出しやステアリング メッセージをもう受けなくなった場合にのみ配信されます。スキルコマンドとprompt templatesが拡張されます。拡張コマンドは使用できません (代わりに `prompt` を使用してください)。\n\n```json\n{\"type\": \"follow_up\", \"message\": \"After you're done, also do this\"}\n```\n\n画像付き:\n```json\n{\"type\": \"follow_up\", \"message\": \"Also check this image\", \"images\": [{\"type\": \"image\", \"data\": \"base64-encoded-data\", \"mimeType\": \"image/png\"}]}\n```\n\n`images` フィールドはオプションです。各画像は `ImageContent` 形式 (`prompt` と同じ) を使用します。\n\n応答：\n```json\n{\"type\": \"response\", \"command\": \"follow_up\", \"success\": true}\n```\n\nフォローアップメッセージの処理方法の制御については、[set_follow_up_mode](#set_follow_up_mode) を参照してください。\n\n#### アボート\n\n現在のエージェントの操作を中止します。\n\n```json\n{\"type\": \"abort\"}\n```\n\n応答：\n```json\n{\"type\": \"response\", \"command\": \"abort\", \"success\": true}\n```\n\n#### 新しいセッション\n\n新しいセッションを開始します。 `session_before_switch` 拡張イベント ハンドラーによってキャンセルできます。\n\n```json\n{\"type\": \"new_session\"}\n```\n\nオプションの親セッション追跡を使用すると、次のようになります。\n```json\n{\"type\": \"new_session\", \"parentSession\": \"/path/to/parent-session.jsonl\"}\n```\n\n応答：\n```json\n{\"type\": \"response\", \"command\": \"new_session\", \"success\": true, \"data\": {\"cancelled\": false}}\n```\n\n延長がキャンセルされた場合:\n```json\n{\"type\": \"response\", \"command\": \"new_session\", \"success\": true, \"data\": {\"cancelled\": true}}\n```\n\n### 州\n\n#### 状態の取得\n\n現在のセッション状態を取得します。\n\n```json\n{\"type\": \"get_state\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_state\",\n  \"success\": true,\n  \"data\": {\n    \"model\": {...},\n    \"thinkingLevel\": \"medium\",\n    \"isStreaming\": false,\n    \"isCompacting\": false,\n    \"steeringMode\": \"all\",\n    \"followUpMode\": \"one-at-a-time\",\n    \"sessionFile\": \"/path/to/session.jsonl\",\n    \"sessionId\": \"abc123\",\n    \"sessionName\": \"my-feature-work\",\n    \"autoCompactionEnabled\": true,\n    \"messageCount\": 5,\n    \"pendingMessageCount\": 0\n  }\n}\n```\n\n`model` フィールドは完全な [Model](#model) オブジェクトまたは `null` です。 `sessionName` フィールドは、`set_session_name` で設定された表示名です。設定されていない場合は省略されます。\n\n#### get_messages\n\n会話内のすべてのメッセージを取得します。\n\n```json\n{\"type\": \"get_messages\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_messages\",\n  \"success\": true,\n  \"data\": {\"messages\": [...]}\n}\n```\n\nメッセージは `AgentMessage` オブジェクトです ([Message Types](#message-types) を参照)。\n\n### モデル\n\n#### セットモデル\n\n特定のモデルに切り替えます。\n\n```json\n{\"type\": \"set_model\", \"provider\": \"anthropic\", \"modelId\": \"claude-sonnet-4-20250514\"}\n```\n\n応答には完全な [Model](#model) オブジェクトが含まれます。\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"set_model\",\n  \"success\": true,\n  \"data\": {...}\n}\n```\n\n#### サイクルモデル\n\n次に利用可能なモデルに切り替えます。利用可能なモデルが 1 つだけの場合は、`null` データを返します。\n\n```json\n{\"type\": \"cycle_model\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"cycle_model\",\n  \"success\": true,\n  \"data\": {\n    \"model\": {...},\n    \"thinkingLevel\": \"medium\",\n    \"isScoped\": false\n  }\n}\n```\n\n`model` フィールドは完全な [Model](#model) オブジェクトです。\n\n#### get_available_models\n\n構成されているすべてのモデルをリストします。\n\n```json\n{\"type\": \"get_available_models\"}\n```\n\n応答には完全な [Model](#model) オブジェクトの配列が含まれます。\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_available_models\",\n  \"success\": true,\n  \"data\": {\n    \"models\": [...]\n  }\n}\n```\n\n### 考え\n\n#### set_ Thinking_level\n\nそれをサポートするモデルの推論/思考レベルを設定します。\n\n```json\n{\"type\": \"set_thinking_level\", \"level\": \"high\"}\n```\n\nレベル: `\"off\"`、`\"minimal\"`、`\"low\"`、`\"medium\"`、`\"high\"`、`\"xhigh\"`、`\"max\"`\n\n`\"xhigh\"` と `\"max\"` は、選択したモデルでサポートされている場合にのみ公開されます。 GPT-5.6 などの一部のモデルは両方を公開します。\n\n応答：\n```json\n{\"type\": \"response\", \"command\": \"set_thinking_level\", \"success\": true}\n```\n\n#### サイクル思考レベル\n\n利用可能な思考レベルを循環します。モデルが思考をサポートしていない場合は、`null` データを返します。\n\n```json\n{\"type\": \"cycle_thinking_level\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"cycle_thinking_level\",\n  \"success\": true,\n  \"data\": {\"level\": \"high\"}\n}\n```\n\n#### get_available_ Thinking_levels\n\n現在のモデルでサポートされている思考レベルをリストします。推論サポートのないモデルの場合は `[\"off\"]` を返します。\n\n```json\n{\"type\": \"get_available_thinking_levels\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_available_thinking_levels\",\n  \"success\": true,\n  \"data\": {\n    \"levels\": [\"off\", \"minimal\", \"low\", \"medium\", \"high\"]\n  }\n}\n```\n\n### キューモード\n\n#### セットステアリングモード\n\nステアリング メッセージ (`steer` から) の配信方法を制御します。\n\n```json\n{\"type\": \"set_steering_mode\", \"mode\": \"one-at-a-time\"}\n```\n\nモード:\n- `\"all\"`: 現在のアシスタント ターンがツール呼び出しの実行を終了した後に、すべてのステアリング メッセージを配信します。\n- `\"one-at-a-time\"`: 完了したアシスタントターンごとに 1 つのステアリングメッセージを配信します (デフォルト)\n\n応答：\n```json\n{\"type\": \"response\", \"command\": \"set_steering_mode\", \"success\": true}\n```\n\n#### set_follow_up_mode\n\nフォローアップ メッセージ (`follow_up` から) の配信方法を制御します。\n\n```json\n{\"type\": \"set_follow_up_mode\", \"mode\": \"one-at-a-time\"}\n```\n\nモード:\n- `\"all\"`: エージェントの終了時にすべてのフォローアップ メッセージを配信します\n- `\"one-at-a-time\"`: エージェントの完了ごとに 1 つのフォローアップ メッセージを配信します (デフォルト)\n\n応答：\n```json\n{\"type\": \"response\", \"command\": \"set_follow_up_mode\", \"success\": true}\n```\n\n### 圧縮\n\n#### コンパクト\n\n会話コンテキストを手動で圧縮して、トークンの使用量を削減します。\n\n```json\n{\"type\": \"compact\"}\n```\n\nカスタム命令を使用すると:\n```json\n{\"type\": \"compact\", \"customInstructions\": \"Focus on code changes\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"compact\",\n  \"success\": true,\n  \"data\": {\n    \"summary\": \"Summary of conversation...\",\n    \"firstKeptEntryId\": \"abc123\",\n    \"tokensBefore\": 150000,\n    \"estimatedTokensAfter\": 32000,\n    \"usage\": {\n      \"input\": 32000,\n      \"output\": 1200,\n      \"cacheRead\": 0,\n      \"cacheWrite\": 0,\n      \"totalTokens\": 33200,\n      \"cost\": {\"input\": 0.01, \"output\": 0.02, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0.03}\n    },\n    \"details\": {}\n  }\n}\n```\n\n`estimatedTokensAfter` は、プロバイダーが正確に定義するトークン数ではなく、圧縮直後に再構築されたメッセージ コンテキストに対するヒューリスティックな推定値です。 `usage` は、サマリーを生成した LLM 呼び出しをレポートします。カスタム圧縮ハンドラーによって省略される場合があります。\n\n#### set_auto_compaction\n\nコンテキストがほぼいっぱいになった場合の自動圧縮を有効または無効にします。\n\n```json\n{\"type\": \"set_auto_compaction\", \"enabled\": true}\n```\n\n応答：\n```json\n{\"type\": \"response\", \"command\": \"set_auto_compaction\", \"success\": true}\n```\n\n### リトライ\n\n#### set_auto_retry\n\n一時的なエラー (過負荷、レート制限、5xx) での自動再試行を有効または無効にします。\n\n```json\n{\"type\": \"set_auto_retry\", \"enabled\": true}\n```\n\n応答：\n```json\n{\"type\": \"response\", \"command\": \"set_auto_retry\", \"success\": true}\n```\n\n#### 中止_再試行\n\n進行中の再試行を中止します (遅延をキャンセルし、再試行を停止します)。\n\n```json\n{\"type\": \"abort_retry\"}\n```\n\n応答：\n```json\n{\"type\": \"response\", \"command\": \"abort_retry\", \"success\": true}\n```\n\n### バッシュ\n\n#### bash\n\nシェルコマンドを実行し、出力を会話コンテキストに追加します。コマンドの実行中にストリームを `bash_execution_update` イベントとして出力します。応答には最終結果が含まれます。\n\n```json\n{\"id\": \"req-1\", \"type\": \"bash\", \"command\": \"ls -la\"}\n```\n\nストリーミングされた `bash_execution_update` イベントをこのコマンドに関連付けるには、`id` を含めます。\n\n応答：\n```json\n{\n  \"id\": \"req-1\",\n  \"type\": \"response\",\n  \"command\": \"bash\",\n  \"success\": true,\n  \"data\": {\n    \"output\": \"total 48\\ndrwxr-xr-x ...\",\n    \"exitCode\": 0,\n    \"cancelled\": false,\n    \"truncated\": false\n  }\n}\n```\n\n出力が切り捨てられた場合、`fullOutputPath` が含まれます:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"bash\",\n  \"success\": true,\n  \"data\": {\n    \"output\": \"truncated output...\",\n    \"exitCode\": 0,\n    \"cancelled\": false,\n    \"truncated\": true,\n    \"fullOutputPath\": \"/tmp/pi-bash-abc123.log\"\n  }\n}\n```\n\n**bash の結果が LLM に届く仕組み:**\n\n`bash` コマンドはすぐに実行され、`BashResult` を返します。内部的には、`BashExecutionMessage` が作成され、エージェントのメッセージ状態に保存されます。\n\n次の `prompt` コマンドが送信されると、すべてのメッセージ (`BashExecutionMessage` を含む) が変換されてから LLM に送信されます。 `BashExecutionMessage` は、次の形式で `UserMessage` に変換されます。\n\n````\nRan `ls -la`\n```\n合計48\ndrwxr-xr-x...\n```\n````\n\nこれはつまり：\n1. Bash 出力は、すぐではなく **次のプロンプト** の LLM コンテキストに含まれます\n2. プロンプトの前に複数の bash コマンドを実行できます。すべての出力が含まれます\n\n#### 中止_bash\n\n実行中の bash コマンドを中止します。\n\n```json\n{\"type\": \"abort_bash\"}\n```\n\n応答：\n```json\n{\"type\": \"response\", \"command\": \"abort_bash\", \"success\": true}\n```\n\n### セッション\n\n#### get_session_stats\n\nトークンの使用状況、コスト統計、および現在のコンテキスト ウィンドウの使用状況を取得します。\n\n```json\n{\"type\": \"get_session_stats\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_session_stats\",\n  \"success\": true,\n  \"data\": {\n    \"sessionFile\": \"/path/to/session.jsonl\",\n    \"sessionId\": \"abc123\",\n    \"userMessages\": 5,\n    \"assistantMessages\": 5,\n    \"toolCalls\": 12,\n    \"toolResults\": 12,\n    \"totalMessages\": 22,\n    \"tokens\": {\n      \"input\": 50000,\n      \"output\": 10000,\n      \"cacheRead\": 40000,\n      \"cacheWrite\": 5000,\n      \"total\": 105000\n    },\n    \"cost\": 0.45,\n    \"contextUsage\": {\n      \"tokens\": 60000,\n      \"contextWindow\": 200000,\n      \"percent\": 30\n    }\n  }\n}\n```\n\n`tokens` と `cost` には、アシスタント メッセージ、ツールによって報告された使用状況、セッション全体にわたる圧縮/ブランチ サマリーの生成が含まれます。 `contextUsage` には、圧縮とフッター表示に使用される実際の現在のコンテキスト ウィンドウの推定値が含まれます。\n\n使用可能なモデルまたはコンテキスト ウィンドウがない場合、`contextUsage` は省略されます。 `contextUsage.tokens` と `contextUsage.percent` は、圧縮後の新しいアシスタント応答が有効な使用状況データを提供するまで、圧縮直後は `null` になります。\n\n#### エクスポート_html\n\nセッションを HTML ファイルにエクスポートします。\n\n```json\n{\"type\": \"export_html\"}\n```\n\nカスタムパスの場合:\n```json\n{\"type\": \"export_html\", \"outputPath\": \"/tmp/session.html\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"export_html\",\n  \"success\": true,\n  \"data\": {\"path\": \"/tmp/session.html\"}\n}\n```\n\n#### スイッチセッション\n\n別のセッション ファイルをロードします。 `session_before_switch` 拡張イベント ハンドラーによってキャンセルできます。\n\n```json\n{\"type\": \"switch_session\", \"sessionPath\": \"/path/to/session.jsonl\"}\n```\n\n応答：\n```json\n{\"type\": \"response\", \"command\": \"switch_session\", \"success\": true, \"data\": {\"cancelled\": false}}\n```\n\n内線番号が切り替えをキャンセルした場合:\n```json\n{\"type\": \"response\", \"command\": \"switch_session\", \"success\": true, \"data\": {\"cancelled\": true}}\n```\n\n#### フォーク\n\nアクティブなブランチ上の以前のユーザー メッセージから新しいフォークを作成します。 `session_before_fork` 拡張イベント ハンドラーによってキャンセルできます。フォーク元のメッセージのテキストを返します。\n\n```json\n{\"type\": \"fork\", \"entryId\": \"abc123\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"fork\",\n  \"success\": true,\n  \"data\": {\"text\": \"The original prompt text...\", \"cancelled\": false}\n}\n```\n\n拡張機能がフォークをキャンセルした場合:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"fork\",\n  \"success\": true,\n  \"data\": {\"text\": \"The original prompt text...\", \"cancelled\": true}\n}\n```\n\n#### クローン\n\n現在のアクティブなブランチを現在の位置で新しいセッションに複製します。 `session_before_fork` 拡張イベント ハンドラーによってキャンセルできます。\n\n```json\n{\"type\": \"clone\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"clone\",\n  \"success\": true,\n  \"data\": {\"cancelled\": false}\n}\n```\n\n拡張機能がクローンをキャンセルした場合:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"clone\",\n  \"success\": true,\n  \"data\": {\"cancelled\": true}\n}\n```\n\n#### get_fork_messages\n\nフォークに使用できるユーザー メッセージを取得します。\n\n```json\n{\"type\": \"get_fork_messages\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_fork_messages\",\n  \"success\": true,\n  \"data\": {\n    \"messages\": [\n      {\"entryId\": \"abc123\", \"text\": \"First prompt...\"},\n      {\"entryId\": \"def456\", \"text\": \"Second prompt...\"}\n    ]\n  }\n}\n```\n\n#### get_entries\n\nすべてのセッション エントリを追加順に取得します (セッション ヘッダーを除く)。セッションは、安定した ID を持つエントリの追加専用ツリーであるため、エントリ ID は永続カーソルとして機能します。クライアントの再起動後であっても、それ以降のエントリのみを厳密に取得するには、確認した最後のエントリ ID を `since` として渡します。 `get_messages` とは異なり、これには圧縮前の履歴と放棄されたブランチが含まれます。\n\n```json\n{\"type\": \"get_entries\"}\n```\n\nカーソルを使用する場合:\n```json\n{\"type\": \"get_entries\", \"since\": \"abc123\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_entries\",\n  \"success\": true,\n  \"data\": {\n    \"entries\": [\n      {\"type\": \"message\", \"id\": \"def456\", \"parentId\": \"abc123\", \"timestamp\": \"...\", \"message\": {\"role\": \"user\", \"...\": \"...\"}}\n    ],\n    \"leafId\": \"def456\"\n  }\n}\n```\n\n`leafId` は現在のリーフ エントリの ID (空のセッションの場合は `null`) であるため、クライアントはアクティブなブランチが移動したかどうかを 1 往復で知ることができます。 `since` がどのエントリ ID とも一致しない場合、応答は `success: false` になります。\n\n#### get_tree\n\nセッションをエントリのツリーとして取得します。各ノードは `{entry, children, label?, labelTimestamp?}` です。適切な形式のセッションにはルートが 1 つあります。孤立したエントリ (壊れた親チェーン) もルートとして表示されます。\n\n```json\n{\"type\": \"get_tree\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_tree\",\n  \"success\": true,\n  \"data\": {\n    \"tree\": [\n      {\n        \"entry\": {\"type\": \"message\", \"id\": \"abc123\", \"parentId\": null, \"...\": \"...\"},\n        \"children\": [\n          {\"entry\": {\"type\": \"message\", \"id\": \"def456\", \"parentId\": \"abc123\", \"...\": \"...\"}, \"children\": []}\n        ]\n      }\n    ],\n    \"leafId\": \"def456\"\n  }\n}\n```\n\n#### get_last_assistant_text\n\n最後のアシスタント メッセージのテキスト コンテンツを取得します。\n\n```json\n{\"type\": \"get_last_assistant_text\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_last_assistant_text\",\n  \"success\": true,\n  \"data\": {\"text\": \"The assistant's response...\"}\n}\n```\n\nアシスタント メッセージが存在しない場合は、`{\"text\": null}` を返します。\n\n#### セットセッション名\n\n現在のセッションの表示名を設定します。この名前はセッション リストに表示され、セッションを識別するのに役立ちます。\n\n```json\n{\"type\": \"set_session_name\", \"name\": \"my-feature-work\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"set_session_name\",\n  \"success\": true\n}\n```\n\n現在のセッション名は、`sessionName` フィールドの `get_state` で確認できます。 RPC モード開始時の初期名を設定するには、`--name <name>` または `-n <name>` を `pi --mode rpc` プロセスに渡します。\n\n### コマンド\n\n#### get_commands\n\n利用可能なコマンド（拡張コマンド、prompt templates、スキル）を取得します。これらは、`/` をプレフィックスとして付けて、`prompt` コマンド経由で呼び出すことができます。\n\n```json\n{\"type\": \"get_commands\"}\n```\n\n応答：\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_commands\",\n  \"success\": true,\n  \"data\": {\n    \"commands\": [\n      {\"name\": \"session-name\", \"description\": \"Set or clear session name\", \"source\": \"extension\", \"path\": \"/home/user/.pi/agent/extensions/session.ts\"},\n      {\"name\": \"fix-tests\", \"description\": \"Fix failing tests\", \"source\": \"prompt\", \"location\": \"project\", \"path\": \"/home/user/myproject/.pi/agent/prompts/fix-tests.md\"},\n      {\"name\": \"skill:brave-search\", \"description\": \"Web search via Brave API\", \"source\": \"skill\", \"location\": \"user\", \"path\": \"/home/user/.pi/agent/skills/brave-search/SKILL.md\"}\n    ]\n  }\n}\n```\n\n各コマンドには次のものがあります。\n- `name`: コマンド名(`/name`で起動)\n- `description`: 人間が読める説明 (拡張コマンドの場合はオプション)\n- `source`: どのようなコマンド:\n  - `\"extension\"`: 拡張機能の `pi.registerCommand()` 経由で登録されました\n  - `\"prompt\"`: プロンプト テンプレート `.md` ファイルからロード\n  - `\"skill\"`: スキルディレクトリからロード (名前の前に`skill:`が付きます)\n- `location`: ロード元 (オプション、拡張機能には存在しません):\n  - `\"user\"`: ユーザーレベル (`~/.pi/agent/`)\n  - `\"project\"`: プロジェクトレベル (`./.pi/agent/`)\n  - `\"path\"`: CLI または設定による明示的なパス\n- `path`: コマンドソースへの絶対ファイルパス (オプション)\n\n**注意**: 組み込みの TUI コマンド (`/settings`、`/hotkeys` など) は含まれていません。これらは対話モードでのみ処理され、`prompt` 経由で送信された場合は実行されません。\n\n## イベント\n\nエージェントの動作中、イベントは JSON 行として stdout にストリーミングされます。通常、イベントには `id` フィールドは含まれません。 `bash_execution_update` には、元の `bash` コマンドが提供された場合の `id` が含まれます。\n\n### イベントの種類\n\n| イベント | 説明 |\n|-------|-------------|\n| `agent_start` | エージェントが処理を開始します |\n| `agent_end` | 1 つの低レベル エージェントの実行が完了します (その後、再試行、圧縮、またはキューに入れられた継続が続く場合があります) |\n| `agent_settled` | エージェントの実行は完全に完了しました。自動再試行、圧縮再試行、またはキューに入れられた継続は残りません。 |\n| `turn_start` | 新しいターンが始まります |\n| `turn_end` | ターン完了 (アシスタント メッセージとツールの結果を含む) |\n| `message_start` | メッセージが始まります |\n| `message_update` | ストリーミング更新 (テキスト/思考/ツールコール デルタ) |\n| `message_end` | メッセージが完了しました |\n| `bash_execution_update` | 直接 RPC bash コマンド出力チャンク |\n| `tool_execution_start` | ツールの実行が開始されます |\n| `tool_execution_update` | ツール実行の進行状況 (ストリーミング出力) |\n| `tool_execution_end` | ツールが完了しました |\n| `queue_update` | 保留中のステアリング/フォローアップキューが変更されました |\n| `compaction_start` | 圧縮が始まります |\n| `compaction_end` | 圧縮が完了しました |\n| `auto_retry_start` | 自動再試行が開始されます (一時的なエラーの後) |\n| `auto_retry_end` | 自動再試行が完了しました (成功または最終失敗) |\n| `summarization_retry_scheduled` | 一時的な圧縮またはブランチサマリー要約エラーに対してスケジュールされた再試行 |\n| `summarization_retry_attempt_start` | 再試行された要約リクエストが開始されます |\n| `summarization_retry_finished` | 要約再試行ループが完了する |\n| `extension_error` | 拡張機能がエラーをスローしました |\n\n### エージェントの開始\n\nエージェントがプロンプトの処理を開始すると発行されます。\n\n```json\n{\"type\": \"agent_start\"}\n```\n\n### エージェントエンド\n\n1 つの低レベル エージェントの実行が完了すると発行されます。この実行中に生成されたすべてのメッセージが含まれます。 `willRetry` が true の場合、自動再試行が続きます。\n\n```json\n{\n  \"type\": \"agent_end\",\n  \"messages\": [...],\n  \"willRetry\": false\n}\n```\n\n### エージェント_解決済み\n\n完全なセッションレベルの実行が安定した後に発行されます。この時点では、Pi は、再試行、圧縮の再試行、またはキューに入れられたフォローアップ メッセージを通じて自動的に続行されません。\n\n```json\n{\"type\": \"agent_settled\"}\n```\n\n### ターンスタート / ターンエンド\n\nターンは、1 つのアシスタントの応答と、その結果として生じるツールの呼び出しと結果で構成されます。\n\n```json\n{\"type\": \"turn_start\"}\n```\n\n```json\n{\n  \"type\": \"turn_end\",\n  \"message\": {...},\n  \"toolResults\": [...]\n}\n```\n\n### メッセージ開始 / メッセージ終了\n\nメッセージの開始時と完了時に発行されます。 `message` フィールドには `AgentMessage` が含まれています。\n\n```json\n{\"type\": \"message_start\", \"message\": {...}}\n{\"type\": \"message_end\", \"message\": {...}}\n```\n\n### message_update (ストリーミング)\n\nアシスタント メッセージのストリーミング中に発行されます。累積メッセージ スナップショットのないデルタ イベントが含まれます。\n\n```json\n{\n  \"type\": \"message_update\",\n  \"assistantMessageEvent\": {\n    \"type\": \"text_delta\",\n    \"contentIndex\": 0,\n    \"delta\": \"Hello \"\n  }\n}\n```\n\n`assistantMessageEvent` フィールドには、次のデルタ タイプのいずれかが含まれます。\n\n| タイプ | 説明 |\n|------|-------------|\n| `text_start` | テキストコンテンツブロックが開始されました |\n| `text_delta` | テキストコンテンツチャンク |\n| `text_end` | テキストコンテンツブロックが終了しました |\n| `thinking_start` | 思考ブロックが始まりました |\n| `thinking_delta` | 思考コンテンツチャンク |\n| `thinking_end` | 思考ブロックが終わった |\n| `toolcall_start` | ツール呼び出しが開始されました |\n| `toolcall_delta` | ツール呼び出し引数チャンク |\n| `toolcall_end` | ツール呼び出しが終了しました (完全な `toolCall` オブジェクトを含む) |\n\nテキスト応答をストリーミングする例:\n```json\n{\"type\":\"message_update\",\"assistantMessageEvent\":{\"type\":\"text_start\",\"contentIndex\":0}}\n{\"type\":\"message_update\",\"assistantMessageEvent\":{\"type\":\"text_delta\",\"contentIndex\":0,\"delta\":\"Hello\"}}\n{\"type\":\"message_update\",\"assistantMessageEvent\":{\"type\":\"text_delta\",\"contentIndex\":0,\"delta\":\" world\"}}\n{\"type\":\"message_update\",\"assistantMessageEvent\":{\"type\":\"text_end\",\"contentIndex\":0,\"content\":\"Hello world\"}}\n```\n\n`message_update` は、前の累積 `message` フィールドを意図的に省略し、\n`assistantMessageEvent.partial`。ライブ部分メッセージが必要なクライアントはそれを組み立てる必要があります\n`message_start` 以降のイベントは `contentIndex` を使用します。ご褒美`message_end.message`\n権威あるものとして。ツール呼び出しの場合は、バッファ `toolcall_delta.delta`; `toolcall_end.toolCall`\n完了した通話が含まれます。\n\n### bash_実行_更新\n\n直接 `bash` コマンドから出力チャンクごとに 1 回発行されます。 `id` はコマンドの `id` と一致するため、クライアントは出力を正しいコマンドに関連付けることができます。\n\nイベントは、最後の `bash` 応答の `output` が切り詰められている場合でも、コマンドの実行中にすべての出力をストリーミングします。\n\n```json\n{\n  \"type\": \"bash_execution_update\",\n  \"id\": \"req-1\",\n  \"delta\": \"total 48\\n\"\n}\n```\n\n### ツール実行開始 / ツール実行更新 / ツール実行終了\n\nツールが開始され、ストリームが進行し、実行が完了したときに発行されます。\n\n```json\n{\n  \"type\": \"tool_execution_start\",\n  \"toolCallId\": \"call_abc123\",\n  \"toolName\": \"bash\",\n  \"args\": {\"command\": \"ls -la\"}\n}\n```\n\n実行中、`tool_execution_update` イベントは部分的な結果をストリーミングします (例: bash は到着時に出力されます)。\n\n```json\n{\n  \"type\": \"tool_execution_update\",\n  \"toolCallId\": \"call_abc123\",\n  \"toolName\": \"bash\",\n  \"args\": {\"command\": \"ls -la\"},\n  \"partialResult\": {\n    \"content\": [{\"type\": \"text\", \"text\": \"partial output so far...\"}],\n    \"details\": {\"truncation\": null, \"fullOutputPath\": null}\n  }\n}\n```\n\n完了したら:\n\n```json\n{\n  \"type\": \"tool_execution_end\",\n  \"toolCallId\": \"call_abc123\",\n  \"toolName\": \"bash\",\n  \"result\": {\n    \"content\": [{\"type\": \"text\", \"text\": \"total 48\\n...\"}],\n    \"details\": {...}\n  },\n  \"isError\": false\n}\n```\n\nイベントを関連付けるには、`toolCallId` を使用します。 `tool_execution_update` の `partialResult` には、これまでに蓄積された出力 (デルタだけではなく) が含まれているため、クライアントは更新のたびにディスプレイを簡単に置き換えることができます。\n\n### キュー_アップデート\n\n保留中のステアリングまたはフォローアップ キューが変更されるたびに発行されます。\n\n```json\n{\n  \"type\": \"queue_update\",\n  \"steering\": [\"Focus on error handling\"],\n  \"followUp\": [\"After that, summarize the result\"]\n}\n```\n\n### 圧縮開始 / 圧縮終了\n\n手動か自動かに関係なく、圧縮が実行されるときに発行されます。\n\n```json\n{\"type\": \"compaction_start\", \"reason\": \"threshold\"}\n```\n\n`reason` フィールドは、`\"manual\"`、`\"threshold\"`、または `\"overflow\"` です。\n\n```json\n{\n  \"type\": \"compaction_end\",\n  \"reason\": \"threshold\",\n  \"result\": {\n    \"summary\": \"Summary of conversation...\",\n    \"firstKeptEntryId\": \"abc123\",\n    \"tokensBefore\": 150000,\n    \"estimatedTokensAfter\": 32000,\n    \"usage\": {\n      \"input\": 32000,\n      \"output\": 1200,\n      \"cacheRead\": 0,\n      \"cacheWrite\": 0,\n      \"totalTokens\": 33200,\n      \"cost\": {\"input\": 0.01, \"output\": 0.02, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0.03}\n    },\n    \"details\": {}\n  },\n  \"aborted\": false,\n  \"willRetry\": false\n}\n```\n\n`reason` が `\"overflow\"` で圧縮が成功した場合、`willRetry` は `true` となり、エージェントは自動的にプロンプ​​トを再試行します。\n\n圧縮が中止された場合、`result` は `null`、`aborted` は `true` になります。\n\n圧縮が失敗した場合 (例: API クォータの超過)、`result` は `null`、`aborted` は `false`、`errorMessage` にはエラーの説明が含まれます。\n\n### auto_retry_start / auto_retry_end\n\n一時的なエラー (過負荷、レート制限、5xx) の後に自動再試行がトリガーされたときに発行されます。\n\n```json\n{\n  \"type\": \"auto_retry_start\",\n  \"attempt\": 1,\n  \"maxAttempts\": 3,\n  \"delayMs\": 2000,\n  \"errorMessage\": \"529 {\\\"type\\\":\\\"error\\\",\\\"error\\\":{\\\"type\\\":\\\"overloaded_error\\\",\\\"message\\\":\\\"Overloaded\\\"}}\"\n}\n```\n\n```json\n{\n  \"type\": \"auto_retry_end\",\n  \"success\": true,\n  \"attempt\": 2\n}\n```\n\n最終的な失敗時 (最大再試行回数を超えた場合):\n```json\n{\n  \"type\": \"auto_retry_end\",\n  \"success\": false,\n  \"attempt\": 3,\n  \"finalError\": \"529 overloaded_error: Overloaded\"\n}\n```\n\n### summarization_retry_scheduled / summarization_retry_attempt_start / summarization_retry_finished\n\n一時的なプロバイダー エラーの後に圧縮またはブランチ サマリーの要約が再試行されるときに生成されます。これらのイベントでは、アシスタント ターンの自動再試行と同じ再試行設定が使用されます。\n\n```json\n{\n  \"type\": \"summarization_retry_scheduled\",\n  \"attempt\": 1,\n  \"maxAttempts\": 3,\n  \"delayMs\": 2000,\n  \"errorMessage\": \"terminated\"\n}\n```\n\n```json\n{\n  \"type\": \"summarization_retry_attempt_start\",\n  \"source\": \"compaction\",\n  \"reason\": \"threshold\"\n}\n```\n\nブランチサマリーの場合、`source` は `\"branchSummary\"` であり、`reason` は存在しません。\n\n```json\n{\n  \"type\": \"summarization_retry_finished\"\n}\n```\n\n### 拡張エラー\n\n拡張機能がエラーをスローしたときに発行されます。\n\n```json\n{\n  \"type\": \"extension_error\",\n  \"extensionPath\": \"/path/to/extension.ts\",\n  \"event\": \"tool_call\",\n  \"error\": \"Error message...\"\n}\n```\n\n## 拡張UIプロトコル\n\nExtensions は、`ctx.ui.select()`、`ctx.ui.confirm()` などを介してユーザー対話を要求できます。RPC モードでは、これらは、基本コマンド/イベント フローの上にある要求/応答サブプロトコルに変換されます。\n\n拡張 UI メソッドには 2 つのカテゴリがあります。\n\n- **ダイアログ メソッド** (`select`、`confirm`、`input`、`editor`): stdout で `extension_ui_request` を発行し、クライアントが stdin で一致する `id` とともに `extension_ui_response` を返信するまでブロックします。\n- **ファイアアンドフォーゲットメソッド** (`notify`、`setStatus`、`setWidget`、`setTitle`、`set_editor_text`): stdout に `extension_ui_request` を発行しますが、応答は期待しません。クライアントは情報を表示することも無視することもできます。\n\nダイアログ メソッドに `timeout` フィールドが含まれている場合、タイムアウトが経過すると、エージェント側はデフォルト値で自動解決します。クライアントはタイムアウトを追跡する必要はありません。\n\n一部の `ExtensionUIContext` メソッドは、直接 TUI アクセスが必要なため、RPC モードではサポートされないか、機能が低下します。\n- `custom()` は `undefined` を返します\n- `setWorkingMessage()`、`setWorkingIndicator()`、`setFooter()`、`setHeader()`、`setEditorComponent()`、`setToolsExpanded()` は操作なしです\n- `getEditorText()` は `\"\"` を返します\n- `getToolsExpanded()` は `false` を返します\n- `pasteToEditor()` は `setEditorText()` に委譲します (貼り付け/折りたたみ処理なし)\n- `getAllThemes()` は `[]` を返します\n- `getTheme()` は `undefined` を返します\n- `setTheme()` は `{ success: false, error: \"...\" }` を返します\n\n注: ダイアログとファイア アンド フォーゲット メソッドは拡張 UI サブプロトコルを介して機能するため、RPC モードでは `ctx.mode` は `\"rpc\"` 、`ctx.hasUI` は `true` です。実際の端末を必要とする`custom()` などの TUI 固有の機能を保護するには、`ctx.mode === \"tui\"` を使用します。\n\n### 拡張 UI リクエスト (stdout)\n\nすべてのリクエストには、`type: \"extension_ui_request\"`、一意の `id`、および `method` フィールドがあります。\n\n#### 選択する\n\nユーザーにリストから選択するよう求めます。 `timeout` フィールドを持つダイアログ メソッドにはミリ秒単位のタイムアウトが含まれます。クライアントが時間内に応答しない場合、エージェントは `undefined` で自動解決します。\n\n```json\n{\n  \"type\": \"extension_ui_request\",\n  \"id\": \"uuid-1\",\n  \"method\": \"select\",\n  \"title\": \"Allow dangerous command?\",\n  \"options\": [\"Allow\", \"Block\"],\n  \"timeout\": 10000\n}\n```\n\n予期される応答: `extension_ui_response` と `value` (選択されたオプション文字列) または `cancelled: true`。\n\n#### 確認する\n\nユーザーに「はい/いいえ」の確認を求めます。\n\n```json\n{\n  \"type\": \"extension_ui_request\",\n  \"id\": \"uuid-2\",\n  \"method\": \"confirm\",\n  \"title\": \"Clear session?\",\n  \"message\": \"All messages will be lost.\",\n  \"timeout\": 5000\n}\n```\n\n予想される応答: `extension_ui_response` と `confirmed: true/false` または `cancelled: true`。\n\n#### 入力\n\nユーザーに自由形式のテキストの入力を求めます。\n\n```json\n{\n  \"type\": \"extension_ui_request\",\n  \"id\": \"uuid-3\",\n  \"method\": \"input\",\n  \"title\": \"Enter a value\",\n  \"placeholder\": \"type something...\"\n}\n```\n\n予期される応答: `extension_ui_response` と `value` (入力されたテキスト) または `cancelled: true`。\n\n#### エディタ\n\nオプションの事前入力コンテンツを含む複数行のテキスト エディターを開きます。\n\n```json\n{\n  \"type\": \"extension_ui_request\",\n  \"id\": \"uuid-4\",\n  \"method\": \"editor\",\n  \"title\": \"Edit some text\",\n  \"prefill\": \"Line 1\\nLine 2\\nLine 3\"\n}\n```\n\n予想される応答: `extension_ui_response` と `value` (編集されたテキスト) または `cancelled: true`。\n\n#### 通知する\n\n通知を表示します。ファイアアンドフォーゲット、応答は期待できません。\n\n```json\n{\n  \"type\": \"extension_ui_request\",\n  \"id\": \"uuid-5\",\n  \"method\": \"notify\",\n  \"message\": \"Command blocked by user\",\n  \"notifyType\": \"warning\"\n}\n```\n\n`notifyType` フィールドは、`\"info\"`、`\"warning\"`、または `\"error\"` です。省略した場合のデフォルトは `\"info\"` です。\n\n#### setStatus\n\nフッター/ステータス バーのステータス エントリを設定またはクリアします。火をつけて忘れる。\n\n```json\n{\n  \"type\": \"extension_ui_request\",\n  \"id\": \"uuid-6\",\n  \"method\": \"setStatus\",\n  \"statusKey\": \"my-ext\",\n  \"statusText\": \"Turn 3 running...\"\n}\n```\n\n`statusText: undefined` を送信 (または省略) して、そのキーのステータス エントリをクリアします。\n\n#### ウィジェットを設定する\n\nエディターの上または下に表示されるウィジェット (テキスト行のブロック) を設定またはクリアします。火をつけて忘れる。\n\n```json\n{\n  \"type\": \"extension_ui_request\",\n  \"id\": \"uuid-7\",\n  \"method\": \"setWidget\",\n  \"widgetKey\": \"my-ext\",\n  \"widgetLines\": [\"--- My Widget ---\", \"Line 1\", \"Line 2\"],\n  \"widgetPlacement\": \"aboveEditor\"\n}\n```\n\nウィジェットをクリアするには、`widgetLines: undefined` を送信 (または省略) します。 `widgetPlacement` フィールドは、`\"aboveEditor\"` (デフォルト) または `\"belowEditor\"` です。 RPC モードでは文字列配列のみがサポートされます。コンポーネント ファクトリは無視されます。\n\n#### タイトルを設定\n\nターミナルウィンドウ/タブのタイトルを設定します。火をつけて忘れる。\n\n```json\n{\n  \"type\": \"extension_ui_request\",\n  \"id\": \"uuid-8\",\n  \"method\": \"setTitle\",\n  \"title\": \"pi - my project\"\n}\n```\n\n#### set_editor_text\n\n入力エディターでテキストを設定します。火をつけて忘れる。\n\n```json\n{\n  \"type\": \"extension_ui_request\",\n  \"id\": \"uuid-9\",\n  \"method\": \"set_editor_text\",\n  \"text\": \"prefilled text for the user\"\n}\n```\n\n### 拡張機能 UI 応答 (stdin)\n\n応答はダイアログ メソッド (`select`、`confirm`、`input`、`editor`) に対してのみ送信されます。 `id` はリクエストと一致する必要があります。\n\n#### 値の応答 (選択、入力、エディター)\n\n```json\n{\"type\": \"extension_ui_response\", \"id\": \"uuid-1\", \"value\": \"Allow\"}\n```\n\n#### 確認応答（確認）\n\n```json\n{\"type\": \"extension_ui_response\", \"id\": \"uuid-2\", \"confirmed\": true}\n```\n\n#### キャンセル応答（任意のダイアログ）\n\nダイアログ メソッドをすべて閉じます。拡張機能は、`undefined` (選択/入力/エディター用) または `false` (確認用) を受け取ります。\n\n```json\n{\"type\": \"extension_ui_response\", \"id\": \"uuid-3\", \"cancelled\": true}\n```\n\n## エラー処理\n\n失敗したコマンドは、`success: false` の応答を返します。\n\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"set_model\",\n  \"success\": false,\n  \"error\": \"Model not found: invalid/model\"\n}\n```\n\n解析エラー:\n\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"parse\",\n  \"success\": false,\n  \"error\": \"Failed to parse command: Unexpected token...\"\n}\n```\n\n## 種類\n\nソースファイル:\n- [`packages/ai/src/types.ts`](../../ai/src/types.ts) - `Model`, `UserMessage`, `AssistantMessage`, `ToolResultMessage`\n- [`packages/agent/src/types.ts`](../../agent/src/types.ts) - `AgentMessage`, `AgentEvent`\n- [`src/core/messages.ts`](../src/core/messages.ts) - `BashExecutionMessage`\n- [`src/modes/json-event.ts`](../src/modes/json-event.ts) - `JsonAgentSessionEvent`\n- [`src/modes/rpc/rpc-types.ts`](../src/modes/rpc/rpc-types.ts) - RPC コマンド/応答タイプ、拡張 UI リクエスト/応答タイプ\n\n### モデル\n\n```json\n{\n  \"id\": \"claude-sonnet-4-20250514\",\n  \"name\": \"Claude Sonnet 4\",\n  \"api\": \"anthropic-messages\",\n  \"provider\": \"anthropic\",\n  \"baseUrl\": \"https://api.anthropic.com\",\n  \"reasoning\": true,\n  \"input\": [\"text\", \"image\"],\n  \"contextWindow\": 200000,\n  \"maxTokens\": 16384,\n  \"cost\": {\n    \"input\": 3.0,\n    \"output\": 15.0,\n    \"cacheRead\": 0.3,\n    \"cacheWrite\": 3.75\n  }\n}\n```\n\n### ユーザーメッセージ\n\n```json\n{\n  \"role\": \"user\",\n  \"content\": \"Hello!\",\n  \"timestamp\": 1733234567890,\n  \"attachments\": []\n}\n```\n\n`content` フィールドには、文字列または `TextContent`/`ImageContent` ブロックの配列を指定できます。\n\n### アシスタントメッセージ\n\n```json\n{\n  \"role\": \"assistant\",\n  \"content\": [\n    {\"type\": \"text\", \"text\": \"Hello! How can I help?\"},\n    {\"type\": \"thinking\", \"thinking\": \"User is greeting me...\"},\n    {\"type\": \"toolCall\", \"id\": \"call_123\", \"name\": \"bash\", \"arguments\": {\"command\": \"ls\"}}\n  ],\n  \"api\": \"anthropic-messages\",\n  \"provider\": \"anthropic\",\n  \"model\": \"claude-sonnet-4-20250514\",\n  \"usage\": {\n    \"input\": 100,\n    \"output\": 50,\n    \"cacheRead\": 0,\n    \"cacheWrite\": 0,\n    \"cost\": {\"input\": 0.0003, \"output\": 0.00075, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0.00105}\n  },\n  \"stopReason\": \"stop\",\n  \"timestamp\": 1733234567890\n}\n```\n\n停止理由: `\"stop\"`、`\"length\"`、`\"toolUse\"`、`\"error\"`、`\"aborted\"`\n\n### ツール結果メッセージ\n\n```json\n{\n  \"role\": \"toolResult\",\n  \"toolCallId\": \"call_123\",\n  \"toolName\": \"bash\",\n  \"content\": [{\"type\": \"text\", \"text\": \"total 48\\ndrwxr-xr-x ...\"}],\n  \"usage\": {\n    \"input\": 100,\n    \"output\": 50,\n    \"cacheRead\": 0,\n    \"cacheWrite\": 0,\n    \"totalTokens\": 150,\n    \"cost\": {\"input\": 0.0003, \"output\": 0.00075, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0.00105}\n  },\n  \"isError\": false,\n  \"timestamp\": 1733234567890\n}\n```\n\n`usage` はオプションで、ツールによって実行されたネストされた LLM 作業を報告します。存在すると、セッション トークンとコストの合計に影響します。\n\n### Bash実行メッセージ\n\n`bash` RPC コマンドによって作成されます (LLM ツール呼び出しによってではありません)。\n\n```json\n{\n  \"role\": \"bashExecution\",\n  \"command\": \"ls -la\",\n  \"output\": \"total 48\\ndrwxr-xr-x ...\",\n  \"exitCode\": 0,\n  \"cancelled\": false,\n  \"truncated\": false,\n  \"fullOutputPath\": null,\n  \"timestamp\": 1733234567890\n}\n```\n\n### アタッチメント\n\n```json\n{\n  \"id\": \"img1\",\n  \"type\": \"image\",\n  \"fileName\": \"photo.jpg\",\n  \"mimeType\": \"image/jpeg\",\n  \"size\": 102400,\n  \"content\": \"base64-encoded-data...\",\n  \"extractedText\": null,\n  \"preview\": null\n}\n```\n\n## 例: 基本クライアント (Python)\n\n```python\nimport subprocess\nimport json\n\nproc = subprocess.Popen(\n    [\"pi\", \"--mode\", \"rpc\", \"--no-session\"],\n    stdin=subprocess.PIPE,\n    stdout=subprocess.PIPE,\n    text=True\n)\n\ndef send(cmd):\n    proc.stdin.write(json.dumps(cmd) + \"\\n\")\n    proc.stdin.flush()\n\ndef read_events():\n    for line in proc.stdout:\n        yield json.loads(line)\n\n# Send prompt\nsend({\"type\": \"prompt\", \"message\": \"Hello!\"})\n\n# Process events\nfor event in read_events():\n    if event.get(\"type\") == \"message_update\":\n        delta = event.get(\"assistantMessageEvent\", {})\n        if delta.get(\"type\") == \"text_delta\":\n            print(delta[\"delta\"], end=\"\", flush=True)\n    \n    if event.get(\"type\") == \"agent_end\":\n        print()\n        break\n```\n\n## 例: インタラクティブクライアント (Node.js)\n\n完全な対話型の例については [`test/rpc-example.ts`](../test/rpc-example.ts) を、型指定されたクライアントの実装については [`src/modes/rpc/rpc-client.ts`](../src/modes/rpc/rpc-client.ts) を参照してください。\n\n拡張 UI プロトコルを処理する完全な例については、[`examples/extensions/rpc-demo.ts`](../examples/extensions/rpc-demo.ts) 拡張とペアになる [`examples/rpc-extension-ui.ts`](../examples/rpc-extension-ui.ts) を参照してください。\n\n```javascript\nconst { spawn } = require(\"child_process\");\nconst { StringDecoder } = require(\"string_decoder\");\n\nconst agent = spawn(\"pi\", [\"--mode\", \"rpc\", \"--no-session\"]);\n\nfunction attachJsonlReader(stream, onLine) {\n    const decoder = new StringDecoder(\"utf8\");\n    let buffer = \"\";\n\n    stream.on(\"data\", (chunk) => {\n        buffer += typeof chunk === \"string\" ? chunk : decoder.write(chunk);\n\n        while (true) {\n            const newlineIndex = buffer.indexOf(\"\\n\");\n            if (newlineIndex === -1) break;\n\n            let line = buffer.slice(0, newlineIndex);\n            buffer = buffer.slice(newlineIndex + 1);\n            if (line.endsWith(\"\\r\")) line = line.slice(0, -1);\n            onLine(line);\n        }\n    });\n\n    stream.on(\"end\", () => {\n        buffer += decoder.end();\n        if (buffer.length > 0) {\n            onLine(buffer.endsWith(\"\\r\") ? buffer.slice(0, -1) : buffer);\n        }\n    });\n}\n\nattachJsonlReader(agent.stdout, (line) => {\n    const event = JSON.parse(line);\n\n    if (event.type === \"message_update\") {\n        const { assistantMessageEvent } = event;\n        if (assistantMessageEvent.type === \"text_delta\") {\n            process.stdout.write(assistantMessageEvent.delta);\n        }\n    }\n});\n\n// Send prompt\nagent.stdin.write(JSON.stringify({ type: \"prompt\", message: \"Hello\" }) + \"\\n\");\n\n// Abort on Ctrl+C\nprocess.on(\"SIGINT\", () => {\n    agent.stdin.write(JSON.stringify({ type: \"abort\" }) + \"\\n\");\n});\n```","sourceFile":"rpc.md"},"sdk":{"title":"SDK","markdown":"> pi は、SDK の使用に役立ちます。ユースケースに合わせた統合を構築するよう依頼してください。\n\n\nSDK は、pi のエージェント機能へのプログラムによるアクセスを提供します。これを使用して、他のアプリケーションに pi を埋め込んだり、カスタム インターフェイスを構築したり、自動化されたワークフローと統合したりできます。\n\n**使用例の例:**\n- カスタム UI (Web、デスクトップ、モバイル) を構築する\n- エージェント機能を既存のアプリケーションに統合する\n- エージェント推論を使用して自動化されたパイプラインを作成する\n- サブエージェントを生成するカスタム ツールを構築する\n- エージェントの動作をプログラムでテストする\n\n最小限の制御から完全な制御までの実際の例については、[examples/sdk/](../examples/sdk/) を参照してください。\n\n## クイックスタート\n\n```typescript\nimport { createAgentSession, ModelRuntime, SessionManager } from \"@earendil-works/pi-coding-agent\";\n\nconst modelRuntime = await ModelRuntime.create();\nconst { session } = await createAgentSession({\n  sessionManager: SessionManager.inMemory(),\n  modelRuntime,\n});\n\nsession.subscribe((event) => {\n  if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n  }\n});\n\nawait session.prompt(\"What files are in the current directory?\");\n```\n\n## インストール\n\n```bash\nnpm install @earendil-works/pi-coding-agent\n```\n\nSDK はメインパッケージに含まれています。別途インストールする必要はありません。\n\n## 中心となる概念\n\n### createAgentSession()\n\n単一の `AgentSession` のメインのファクトリー関数。\n\n`createAgentSession()` は、`ResourceLoader` を使用して、拡張機能、スキル、prompt templates、テーマ、および context files を提供します。指定しない場合は、標準の検出で `DefaultResourceLoader` が使用されます。\n\n```typescript\nimport { createAgentSession, SessionManager } from \"@earendil-works/pi-coding-agent\";\n\n// Minimal: defaults with DefaultResourceLoader\nconst { session } = await createAgentSession();\n\n// Custom: override specific options\nconst { session } = await createAgentSession({\n  model: myModel,\n  tools: [\"read\", \"bash\"],\n  sessionManager: SessionManager.inMemory(),\n});\n```\n\n### エージェントセッション\n\nセッションは、エージェントのライフサイクル、メッセージ履歴、モデルの状態、圧縮、およびイベント ストリーミングを管理します。\n\n```typescript\ninterface AgentSession {\n  // Send a prompt and wait for completion\n  prompt(text: string, options?: PromptOptions): Promise<void>;\n\n  // Queue messages during streaming\n  steer(text: string): Promise<void>;\n  followUp(text: string): Promise<void>;\n\n  // Subscribe to events (returns unsubscribe function)\n  subscribe(listener: (event: AgentSessionEvent) => void): () => void;\n\n  // Session info\n  sessionFile: string | undefined;\n  sessionId: string;\n\n  // Model control\n  setModel(model: Model): Promise<void>;\n  setThinkingLevel(level: ThinkingLevel): void;\n  cycleModel(): Promise<ModelCycleResult | undefined>;\n  cycleThinkingLevel(): ThinkingLevel | undefined;\n\n  // State access\n  agent: Agent;\n  model: Model | undefined;\n  thinkingLevel: ThinkingLevel;\n  messages: AgentMessage[];\n  isStreaming: boolean;\n\n  // In-place tree navigation within the current session file\n  navigateTree(targetId: string, options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string }): Promise<{ editorText?: string; cancelled: boolean }>;\n\n  // Compaction\n  compact(customInstructions?: string): Promise<CompactionResult>;\n  abortCompaction(): void;\n\n  // Abort current operation\n  abort(): Promise<void>;\n\n  // Cleanup\n  dispose(): void;\n}\n```\n\n新しいセッション、再開、フォーク、インポートなどのセッション置換 API は、`AgentSession` ではなく `AgentSessionRuntime` にライブになります。\n\n### createAgentSessionRuntime() と AgentSessionRuntime\n\nアクティブなセッションを置き換えて、CWD バウンドのランタイム状態を再構築する必要がある場合は、ランタイム API を使用します。\nこれは、組み込みのインタラクティブ、印刷、および RPC モードで使用されるのと同じレイヤーです。\n\n`createAgentSessionRuntime()` は、ランタイム ファクトリと初期 cwd/セッション ターゲットを受け取ります。ファクトリは、プロセス グローバルの固定入力を閉じ、有効な cwd に対して cwd バインドされたサービスを再作成し、それらのサービスに対してセッション オプションを解決し、完全な実行時結果を返します。\n\n```typescript\nimport {\n  type CreateAgentSessionRuntimeFactory,\n  createAgentSessionFromServices,\n  createAgentSessionRuntime,\n  createAgentSessionServices,\n  getAgentDir,\n  SessionManager,\n} from \"@earendil-works/pi-coding-agent\";\n\nconst createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {\n  const services = await createAgentSessionServices({ cwd });\n  return {\n    ...(await createAgentSessionFromServices({\n      services,\n      sessionManager,\n      sessionStartEvent,\n    })),\n    services,\n    diagnostics: services.diagnostics,\n  };\n};\n\nconst runtime = await createAgentSessionRuntime(createRuntime, {\n  cwd: process.cwd(),\n  agentDir: getAgentDir(),\n  sessionManager: SessionManager.create(process.cwd()),\n});\n```\n\n`AgentSessionRuntime` は、以下のアクティブなランタイムの置き換えを所有しています。\n\n- `newSession()`\n- `switchSession()`\n- `fork()`\n- クローンは `fork(entryId, { position: \"at\" })` 経由でフローします\n- `importFromJsonl()`\n\n重要な行動:\n\n- これらの操作後の`runtime.session`の変化\n- イベントのサブスクリプションは特定の `AgentSession` に関連付けられているため、置換後に再サブスクライブしてください\n- 内線番号を使用している場合は、新しいセッションのためにもう一度 `runtime.session.bindExtensions(...)` に電話してください\n- 作成により `runtime.diagnostics` に診断が返されます\n- ランタイムの作成または置換が失敗した場合、メソッドがスローされ、呼び出し元がそれを処理する方法を決定します。\n\n```typescript\nlet session = runtime.session;\nlet unsubscribe = session.subscribe(() => {});\n\nawait runtime.newSession();\n\nunsubscribe();\nsession = runtime.session;\nunsubscribe = session.subscribe(() => {});\n```\n\n### プロンプトとメッセージキューイング\n\n`PromptOptions` は、プロンプトの拡張、ストリーミング中のキューイング動作、およびプロンプトのプリフライト通知を制御します。\n\n```typescript\ninterface PromptOptions {\n  expandPromptTemplates?: boolean;\n  images?: ImageContent[];\n  streamingBehavior?: \"steer\" | \"followUp\";\n  source?: InputSource;\n  preflightResult?: (success: boolean) => void;\n}\n```\n\n`preflightResult` は、`prompt()` の呼び出しごとに 1 回呼び出されます。\n\n- `true` プロンプトが受け入れられたか、キューに入れられたか、すぐに処理されたとき\n- `false` プロンプトプリフライトが受け入れ前に拒否された場合\n\n`prompt()` が解決される前に発動します。 `prompt()` は、再試行を含め、受け入れられたすべての実行が終了した後にのみ解決されます。受け入れ後の失敗は、`preflightResult(false)` ではなく、通常のイベントとメッセージ ストリームを通じて報告されます。\n\n`prompt()` メソッドは、prompt templates、拡張コマンド、およびメッセージ送信を処理します。\n\n```typescript\n// Basic prompt (when not streaming)\nawait session.prompt(\"What files are here?\");\n\n// With images\nawait session.prompt(\"What's in this image?\", {\n  images: [{ type: \"image\", source: { type: \"base64\", mediaType: \"image/png\", data: \"...\" } }]\n});\n\n// During streaming: must specify how to queue the message\nawait session.prompt(\"Stop and do this instead\", { streamingBehavior: \"steer\" });\nawait session.prompt(\"After you're done, also check X\", { streamingBehavior: \"followUp\" });\n```\n\n**行動：**\n- **拡張コマンド** (例: `/mycommand`): ストリーミング中でもすぐに実行されます。彼らは、`pi.sendMessage()` を介して独自の LLM インタラクションを管理します。\n- **ファイルベース prompt templates** (`.md` ファイルから): 送信またはキューに入れる前にコンテンツに展開されます。\n- **`streamingBehavior`** を使用しないストリーミング中: エラーがスローされます。 `steer()` または `followUp()` を直接使用するか、オプションを指定します。\n- **`preflightResult(true)`**: プロンプトが受け入れられたか、キューに入れられたか、すぐに処理されたことを意味します。\n- **`preflightResult(false)`**: 受け入れられる前にプリフライトが拒否されたことを意味します。\n\nストリーミング中の明示的なキューイングの場合:\n\n```typescript\n// Queue a steering message for delivery after the current assistant turn finishes its tool calls\nawait session.steer(\"New instruction\");\n\n// Wait for agent to finish (delivered only when agent stops)\nawait session.followUp(\"After you're done, also do this\");\n```\n\n`steer()` と `followUp()` はどちらもファイルベースの prompt templates を展開しますが、拡張コマンドでエラーが発生します (拡張コマンドをキューに入れることができません)。\n\n### エージェントとエージェント状態\n\n`Agent` クラス (`@earendil-works/pi-agent-core` から) は、コア LLM インタラクションを処理します。 `session.agent` からアクセスしてください。\n\n```typescript\n// Access current state\nconst state = session.agent.state;\n\n// state.messages: AgentMessage[] - conversation history\n// state.model: Model - current model\n// state.thinkingLevel: ThinkingLevel - current thinking level\n// state.systemPrompt: string - system prompt\n// state.tools: AgentTool[] - available tools\n// state.streamingMessage?: AgentMessage - current partial assistant message\n// state.errorMessage?: string - latest assistant error\n\n// Replace messages (useful for branching or restoration)\nsession.agent.state.messages = messages; // copies the top-level array\n\n// Replace tools\nsession.agent.state.tools = tools; // copies the top-level array\n\n// Wait for agent to finish processing\nawait session.agent.waitForIdle();\n```\n\n### イベント\n\nイベントをサブスクライブして、ストリーミング出力とライフサイクル通知を受け取ります。\n\n```typescript\nsession.subscribe((event) => {\n  switch (event.type) {\n    // Streaming text from assistant\n    case \"message_update\":\n      if (event.assistantMessageEvent.type === \"text_delta\") {\n        process.stdout.write(event.assistantMessageEvent.delta);\n      }\n      if (event.assistantMessageEvent.type === \"thinking_delta\") {\n        // Thinking output (if thinking enabled)\n      }\n      break;\n    \n    // Tool execution\n    case \"tool_execution_start\":\n      console.log(`Tool: ${event.toolName}`);\n      break;\n    case \"tool_execution_update\":\n      // Streaming tool output\n      break;\n    case \"tool_execution_end\":\n      console.log(`Result: ${event.isError ? \"error\" : \"success\"}`);\n      break;\n    \n    // Message lifecycle\n    case \"message_start\":\n      // New message starting\n      break;\n    case \"message_end\":\n      // Message complete\n      break;\n    \n    // Agent lifecycle\n    case \"agent_start\":\n      // Agent started processing prompt\n      break;\n    case \"agent_end\":\n      // Agent finished (event.messages contains new messages)\n      break;\n    \n    // Turn lifecycle (one LLM response + tool calls)\n    case \"turn_start\":\n      break;\n    case \"turn_end\":\n      // event.message: assistant response\n      // event.toolResults: tool results from this turn\n      break;\n    \n    // Session events (queue, compaction, retry)\n    case \"queue_update\":\n      console.log(event.steering, event.followUp);\n      break;\n    case \"compaction_start\":\n    case \"compaction_end\":\n    case \"auto_retry_start\":\n    case \"auto_retry_end\":\n    case \"summarization_retry_scheduled\":\n    case \"summarization_retry_attempt_start\":\n    case \"summarization_retry_finished\":\n      break;\n  }\n});\n```\n\n## オプションのリファレンス\n\n### ディレクトリ\n\n```typescript\nconst { session } = await createAgentSession({\n  // Working directory for DefaultResourceLoader discovery\n  cwd: process.cwd(), // default\n  \n  // Global config directory\n  agentDir: \"~/.pi/agent\", // default (expands ~)\n});\n```\n\n`cwd` は、`DefaultResourceLoader` によって次の目的で使用されます。\n- プロジェクトの拡張 (`.pi/extensions/`)\n- プロジェクトスキル:\n  - `.pi/skills/`\n  - `cwd` の `.agents/skills/` および祖先ディレクトリ (git リポジトリのルート、またはリポジトリにない場合はファイルシステムのルートまで)\n- プロジェクトのプロンプト (`.pi/prompts/`)\n- コンテキスト ファイル (cwd からウォーキングアップする `AGENTS.md`)\n- セッションディレクトリの命名\n\n`agentDir` は、`DefaultResourceLoader` によって次の目的で使用されます。\n- グローバル拡張機能 (`extensions/`)\n- グローバルスキル:\n  - `agentDir` の下の `skills/` (例: `~/.pi/agent/skills/`)\n  - `~/.agents/skills/`\n- グローバルプロンプト (`prompts/`)\n- グローバルコンテキストファイル (`AGENTS.md`)\n- 設定 (`settings.json`)\n- カスタムモデル (`models.json`)\n- 資格情報 (`auth.json`)\n- セッション (`sessions/`)\n\nカスタムの `ResourceLoader`、`cwd`、および `agentDir` はリソース検出を制御しなくなります。これらは依然としてセッションの名前付けとツール パスの解決に影響します。\n\n### モデル\n\n```typescript\nimport { getModel } from \"@earendil-works/pi-ai\";\nimport { ModelRuntime } from \"@earendil-works/pi-coding-agent\";\n\nconst modelRuntime = await ModelRuntime.create();\n\n// Find specific built-in model (doesn't check if API key exists)\nconst opus = getModel(\"anthropic\", \"claude-opus-4-5\");\nif (!opus) throw new Error(\"Model not found\");\n\n// Find any model by provider/id, including custom models from models.json\n// (doesn't check if API key exists)\nconst customModel = modelRuntime.getModel(\"my-provider\", \"my-model\");\n\n// Get only models that have valid authentication configured\nconst available = await modelRuntime.getAvailable();\n\nconst { session } = await createAgentSession({\n  model: opus,\n  thinkingLevel: \"medium\", // off, minimal, low, medium, high, xhigh, max\n  \n  // Models for cycling (Ctrl+P in interactive mode)\n  scopedModels: [\n    { model: opus, thinkingLevel: \"high\" },\n    { model: haiku, thinkingLevel: \"off\" },\n  ],\n  \n  modelRuntime,\n});\n```\n\nモデルが指定されていない場合:\n1. セッションからの復元を試行します (続行する場合)\n2. 設定のデフォルトを使用します\n3. 最初に利用可能なモデルにフォールバックします\n\nCLI モデル解析と一致させるには、エクスポートされたリゾルバー ヘルパーを使用します。\n\n```typescript\nimport {\n  resolveCliModel,\n  resolveModelScopeWithDiagnostics,\n} from \"@earendil-works/pi-coding-agent\";\n\nconst cliModel = resolveCliModel({\n  cliModel: \"anthropic/claude-opus-4-5:high\",\n  modelRuntime,\n});\nif (cliModel.error) throw new Error(cliModel.error);\nif (cliModel.warning) console.warn(cliModel.warning);\n\nconst { scopedModels, diagnostics } = await resolveModelScopeWithDiagnostics(\n  [\"anthropic/*:high\", \"gpt-5\"],\n  modelRuntime,\n);\nfor (const diagnostic of diagnostics) {\n  console.warn(diagnostic.message);\n}\n```\n\n`resolveCliModel()` は登録されているすべてのモデルを使用するため、`--api-key` スタイルの初回セットアップでは、保存された認証が存在する前にモデルを解決できます。 `resolveModelScopeWithDiagnostics()` は、警告を出力するのではなく返しながら、`--models` および `enabledModels` のセマンティクスと一致します。\n\n> [examples/sdk/02-custom-model.ts](../examples/sdk/02-custom-model.ts) を参照\n\n### API キーと OAuth\n\n認証解決の優先順位 (`ModelRuntime` によって処理):\n1. ランタイムオーバーライド (`setRuntimeApiKey` 経由、永続化されない)\n2. `auth.json` に保存された認証情報 (API key または OAuth トークン)\n3. 環境変数 (`ANTHROPIC_API_KEY`、`OPENAI_API_KEY` など)\n4. フォールバック リゾルバー (`models.json` からのカスタム プロバイダー キー用)\n\n```typescript\nimport { InMemoryCredentialStore } from \"@earendil-works/pi-ai\";\nimport { createAgentSession, ModelRuntime } from \"@earendil-works/pi-coding-agent\";\n\n// Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.json\nconst modelRuntime = await ModelRuntime.create();\n\n// Provider-owned auth methods and current status\nfor (const provider of modelRuntime.getProviders()) {\n  const status = await modelRuntime.checkAuth(provider.id);\n  console.log(provider.name, provider.auth, status);\n}\n\n// Runtime API key override (not persisted to disk)\nawait modelRuntime.setRuntimeApiKey(\"anthropic\", \"sk-my-temp-key\");\n\n// Custom credential and model locations\nconst customRuntime = await ModelRuntime.create({\n  authPath: \"/my/app/auth.json\",\n  modelsPath: \"/my/app/models.json\",\n});\n\n// Or inject any pi-ai CredentialStore\nconst credentials = new InMemoryCredentialStore();\nconst inMemoryRuntime = await ModelRuntime.create({ credentials });\n\nconst { session } = await createAgentSession({\n  modelRuntime: customRuntime,\n});\n```\n\n`login()`、`logout()`、`setRuntimeApiKey()`、および `removeRuntimeApiKey()` は、影響を受けるプロバイダーのキャッシュ/組み込みカタログ、構成、および可用性スナップショットがローカルで一貫した後に解決されます。リモート カタログの鮮度を待ちません。資格情報がコミットされたがローカル同期が失敗した場合、エクスポートされた `CredentialSynchronizationError` で拒否されます。資格情報の変更を盲目的に再試行するのではなく、その `providerId`、`operation`、`credential`、および `cause` フィールドを検査します。\n\nパブリック モデル/認証操作と `ModelRuntime.create({ signal })` はオプションの中止シグナルを受け入れ、省略された場合は制限がありません。 SDK アプリケーション独自のリモート カタログの鮮度に関する期限ポリシー:\n\n```typescript\nconst signal = AbortSignal.timeout(15_000);\nconst result = await modelRuntime.refresh({\n  providers: [\"anthropic\"],\n  signal,\n});\nif (result.aborted) console.warn(\"Catalog refresh timed out; using cached models\");\nfor (const [providerId, error] of result.errors) {\n  console.warn(`Could not refresh ${providerId}:`, error);\n}\n```\n\nネットワーク更新が失敗またはタイムアウトしても、成功した認証情報操作は取り消されません。 `refresh()` は新しいプロバイダーの世代を開始するため、古い停止した更新の後に待機せず、その後古い世代を公開することはできません。\n\n> [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts) を参照\n\n### システムプロンプト\n\n`ResourceLoader` を使用してシステム プロンプトをオーバーライドします。\n\n```typescript\nimport { createAgentSession, DefaultResourceLoader } from \"@earendil-works/pi-coding-agent\";\n\nconst loader = new DefaultResourceLoader({\n  systemPromptOverride: () => \"You are a helpful assistant.\",\n});\nawait loader.reload();\n\nconst { session } = await createAgentSession({ resourceLoader: loader });\n```\n\n> [examples/sdk/03-custom-prompt.ts](../examples/sdk/03-custom-prompt.ts) を参照\n\n### ツール\n\n有効にする組み込みツールを指定します。\n\n- 内蔵ツール名: `read`、`bash`、`edit`、`write`、`grep`、`find`、`ls`\n- デフォルトの組み込み: `read`、`bash`、`edit`、`write`\n- `noTools: \"all\"` はすべてのツールを無効にします\n- `noTools: \"builtin\"` は、拡張機能とカスタム ツールを有効にしたまま、デフォルトの組み込みを無効にします\n- `excludeTools` は、`tools` 許可リストが適用された後、特定の組み込み、拡張、またはカスタム ツール名を無効にします\n\n`edit` ツールは、Pi の TUI ディスプレイには `details.diff` を返し、SDK 消費者には標準統合パッチとして `details.patch` を返します。\n\n```typescript\nimport { createAgentSession } from \"@earendil-works/pi-coding-agent\";\n\n// Read-only mode\nconst { session } = await createAgentSession({\n  tools: [\"read\", \"grep\", \"find\", \"ls\"],\n});\n\n// Pick specific tools\nconst { session } = await createAgentSession({\n  tools: [\"read\", \"bash\", \"grep\"],\n});\n\n// Disable one tool while keeping the rest available\nconst { session } = await createAgentSession({\n  excludeTools: [\"ask_question\"],\n});\n```\n\n#### カスタム cwd を使用したツール\n\nカスタム `cwd` を渡すと、`createAgentSession()` はその cwd 用に選択された組み込みツールを構築します。\n\n```typescript\nimport { createAgentSession, SessionManager } from \"@earendil-works/pi-coding-agent\";\n\nconst cwd = \"/path/to/project\";\n\n// Use default tools for custom cwd\nconst { session } = await createAgentSession({\n  cwd,\n  sessionManager: SessionManager.inMemory(cwd),\n});\n\n// Or pick specific tools for custom cwd\nconst { session } = await createAgentSession({\n  cwd,\n  tools: [\"read\", \"bash\", \"grep\"],\n  sessionManager: SessionManager.inMemory(cwd),\n});\n```\n\n> [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts) を参照\n\n### カスタムツール\n\n```typescript\nimport { Type } from \"typebox\";\nimport { createAgentSession, defineTool } from \"@earendil-works/pi-coding-agent\";\n\n// Inline custom tool\nconst myTool = defineTool({\n  name: \"my_tool\",\n  label: \"My Tool\",\n  description: \"Does something useful\",\n  parameters: Type.Object({\n    input: Type.String({ description: \"Input value\" }),\n  }),\n  execute: async (_toolCallId, params) => ({\n    content: [{ type: \"text\", text: `Result: ${params.input}` }],\n    details: {},\n  }),\n});\n\n// Pass custom tools directly\nconst { session } = await createAgentSession({\n  customTools: [myTool],\n});\n```\n\nスタンドアロン定義および `customTools: [myTool]` のような配列には `defineTool()` を使用します。 Inline `pi.registerTool({... })` はすでにパラメーターの型を正しく推論しています。\n\n`customTools` 経由で渡されたカスタム ツールは、拡張機能に登録されたツールと結合されます。 ResourceLoader によってロードされた Extensions は、`pi.registerTool()` 経由でツールを登録することもできます。\n\n`tools` を渡す場合は、有効にする各カスタム ツールまたは拡張ツールの名前を含めます (例: `tools: [\"read\", \"bash\", \"my_tool\"]`)。\n\n> [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts) を参照\n\n### Extensions\n\nExtensions は `ResourceLoader` によってロードされます。 `DefaultResourceLoader` は、`~/.pi/agent/extensions/`、`.pi/extensions/`、settings.json 拡張ソースから拡張機能を検出します。\n\n```typescript\nimport { createAgentSession, DefaultResourceLoader } from \"@earendil-works/pi-coding-agent\";\n\nconst loader = new DefaultResourceLoader({\n  additionalExtensionPaths: [\"/path/to/my-extension.ts\"],\n  extensionFactories: [\n    (pi) => {\n      pi.on(\"agent_start\", () => {\n        console.log(\"[Inline Extension] Agent starting\");\n      });\n    },\n  ],\n});\nawait loader.reload();\n\nconst { session } = await createAgentSession({ resourceLoader: loader });\n```\n\nExtensions は、ツールの登録、イベントのサブスクライブ、コマンドの追加などを行うことができます。 API の詳細については、[extensions.md](extensions.md) を参照してください。\n\n**名前付きインライン拡張機能:** デフォルトでは、インライン ファクトリは起動時の Extensions リストに `<inline:1>`、`<inline:2>` などとして表示されます。代わりにわかりやすい名前を表示するには、ファクトリをラップします。\n\n```typescript\nimport type { InlineExtension } from \"@earendil-works/pi-coding-agent\";\n\nconst myProvider: InlineExtension = {\n  name: \"my-provider\",\n  factory: (pi) => {\n    pi.on(\"agent_start\", () => {\n      console.log(\"[my-provider] Agent starting\");\n    });\n  },\n};\n\nconst loader = new DefaultResourceLoader({\n  extensionFactories: [myProvider],\n});\n```\n\nこれは、`<inline:1>` ではなく `<inline:my-provider>` と表示されます。ベア ファクトリ関数は、下位互換性のために引き続き受け入れられます。\n\n**イベント バス:** Extensions は `pi.events` 経由で通信できます。外部から送信またはリッスンする必要がある場合は、共有の `eventBus` を `DefaultResourceLoader` に渡します。\n\n```typescript\nimport { createEventBus, DefaultResourceLoader } from \"@earendil-works/pi-coding-agent\";\n\nconst eventBus = createEventBus();\nconst loader = new DefaultResourceLoader({\n  eventBus,\n});\nawait loader.reload();\n\neventBus.on(\"my-extension:status\", (data) => console.log(data));\n```\n\n> [examples/sdk/06-extensions.ts](../examples/sdk/06-extensions.ts) と [docs/extensions.md](extensions.md) を参照\n\n### Skills\n\n```typescript\nimport {\n  createAgentSession,\n  DefaultResourceLoader,\n  type Skill,\n} from \"@earendil-works/pi-coding-agent\";\n\nconst customSkill: Skill = {\n  name: \"my-skill\",\n  description: \"Custom instructions\",\n  filePath: \"/path/to/SKILL.md\",\n  baseDir: \"/path/to\",\n  source: \"custom\",\n};\n\nconst loader = new DefaultResourceLoader({\n  skillsOverride: (current) => ({\n    skills: [...current.skills, customSkill],\n    diagnostics: current.diagnostics,\n  }),\n});\nawait loader.reload();\n\nconst { session } = await createAgentSession({ resourceLoader: loader });\n```\n\n> [examples/sdk/04-skills.ts](../examples/sdk/04-skills.ts) を参照\n\n### コンテキストファイル\n\n```typescript\nimport { createAgentSession, DefaultResourceLoader } from \"@earendil-works/pi-coding-agent\";\n\nconst loader = new DefaultResourceLoader({\n  agentsFilesOverride: (current) => ({\n    agentsFiles: [\n      ...current.agentsFiles,\n      { path: \"/virtual/AGENTS.md\", content: \"# Guidelines\\n\\n- Be concise\" },\n    ],\n  }),\n});\nawait loader.reload();\n\nconst { session } = await createAgentSession({ resourceLoader: loader });\n```\n\n> [examples/sdk/07-context-files.ts](../examples/sdk/07-context-files.ts) を参照\n\n### スラッシュコマンド\n\n```typescript\nimport {\n  createAgentSession,\n  DefaultResourceLoader,\n  type PromptTemplate,\n} from \"@earendil-works/pi-coding-agent\";\n\nconst customCommand: PromptTemplate = {\n  name: \"deploy\",\n  description: \"Deploy the application\",\n  source: \"(custom)\",\n  content: \"# Deploy\\n\\n1. Build\\n2. Test\\n3. Deploy\",\n};\n\nconst loader = new DefaultResourceLoader({\n  promptsOverride: (current) => ({\n    prompts: [...current.prompts, customCommand],\n    diagnostics: current.diagnostics,\n  }),\n});\nawait loader.reload();\n\nconst { session } = await createAgentSession({ resourceLoader: loader });\n```\n\n> [examples/sdk/08-prompt-templates.ts](../examples/sdk/08-prompt-templates.ts) を参照\n\n### セッション管理\n\nセッションは、`id`/`parentId` リンクを持つツリー構造を使用し、インプレース分岐を可能にします。\n\n```typescript\nimport {\n  type CreateAgentSessionRuntimeFactory,\n  createAgentSession,\n  createAgentSessionFromServices,\n  createAgentSessionRuntime,\n  createAgentSessionServices,\n  getAgentDir,\n  SessionManager,\n} from \"@earendil-works/pi-coding-agent\";\n\n// In-memory (no persistence)\nconst { session } = await createAgentSession({\n  sessionManager: SessionManager.inMemory(),\n});\n\n// New persistent session\nconst { session: persisted } = await createAgentSession({\n  sessionManager: SessionManager.create(process.cwd()),\n});\n\n// Continue most recent\nconst { session: continued, modelFallbackMessage } = await createAgentSession({\n  sessionManager: SessionManager.continueRecent(process.cwd()),\n});\nif (modelFallbackMessage) {\n  console.log(\"Note:\", modelFallbackMessage);\n}\n\n// Open specific file\nconst { session: opened } = await createAgentSession({\n  sessionManager: SessionManager.open(\"/path/to/session.jsonl\"),\n});\n\n// List sessions\nconst currentProjectSessions = await SessionManager.list(process.cwd());\nconst allSessions = await SessionManager.listAll(process.cwd());\n\n// Session replacement API for /new, /resume, /fork, /clone, and import flows.\nconst createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {\n  const services = await createAgentSessionServices({ cwd });\n  return {\n    ...(await createAgentSessionFromServices({\n      services,\n      sessionManager,\n      sessionStartEvent,\n    })),\n    services,\n    diagnostics: services.diagnostics,\n  };\n};\n\nconst runtime = await createAgentSessionRuntime(createRuntime, {\n  cwd: process.cwd(),\n  agentDir: getAgentDir(),\n  sessionManager: SessionManager.create(process.cwd()),\n});\n\n// Replace the active session with a fresh one\nawait runtime.newSession();\n\n// Replace the active session with another saved session\nawait runtime.switchSession(\"/path/to/session.jsonl\");\n\n// Replace the active session with a fork from a specific user entry\nawait runtime.fork(\"entry-id\");\n\n// Clone the active path through a specific entry\nawait runtime.fork(\"entry-id\", { position: \"at\" });\n```\n\n**SessionManager ツリー API:**\n\n```typescript\nconst sm = SessionManager.open(\"/path/to/session.jsonl\");\n\n// Session listing\nconst currentProjectSessions = await SessionManager.list(process.cwd());\nconst allSessions = await SessionManager.listAll(process.cwd());\n\n// Tree traversal\nconst entries = sm.getEntries();        // All entries (excludes header)\nconst tree = sm.getTree();              // Full tree structure\nconst path = sm.getPath();              // Path from root to current leaf\nconst leaf = sm.getLeafEntry();         // Current leaf entry\nconst entry = sm.getEntry(id);          // Get entry by ID\nconst children = sm.getChildren(id);    // Direct children of entry\n\n// Labels\nconst label = sm.getLabel(id);          // Get label for entry\nsm.appendLabelChange(id, \"checkpoint\"); // Set label\n\n// Branching\nsm.branch(entryId);                     // Move leaf to earlier entry\nsm.branchWithSummary(id, \"Summary...\");  // Branch with context summary\nsm.createBranchedSession(leafId);       // Extract path to new file\n```\n\n> [examples/sdk/11-sessions.ts](../examples/sdk/11-sessions.ts) と [Session Format](session-format.md) を参照\n\n### 設定管理\n\n```typescript\nimport { createAgentSession, SettingsManager, SessionManager } from \"@earendil-works/pi-coding-agent\";\n\n// Default: loads from files (global + project merged)\nconst { session } = await createAgentSession({\n  settingsManager: SettingsManager.create(),\n});\n\n// With overrides\nconst settingsManager = SettingsManager.create();\nsettingsManager.applyOverrides({\n  compaction: { enabled: false },\n  retry: { enabled: true, maxRetries: 5 },\n});\nconst { session } = await createAgentSession({ settingsManager });\n\n// In-memory (no file I/O, for testing)\nconst { session } = await createAgentSession({\n  settingsManager: SettingsManager.inMemory({ compaction: { enabled: false } }),\n  sessionManager: SessionManager.inMemory(),\n});\n\n// Custom directories\nconst { session } = await createAgentSession({\n  settingsManager: SettingsManager.create(\"/custom/cwd\", \"/custom/agent\"),\n});\n```\n\n**静的ファクトリー:**\n- `SettingsManager.create(cwd?, agentDir?)` - ファイルからロード\n- `SettingsManager.inMemory(settings?)` - ファイル I/O なし\n\n**プロジェクト固有の設定:**\n\n設定は 2 つの場所からロードされ、マージされます。\n1. グローバル: `~/.pi/agent/settings.json`\n2. プロジェクト: `<cwd>/.pi/settings.json`\n\nプロジェクトはグローバルをオーバーライドします。ネストされたオブジェクトはキーをマージします。設定者はデフォルトでグローバル設定を変更します。\n\n**永続性とエラー処理のセマンティクス:**\n\n- 設定のゲッター/セッターはメモリ内状態に対して同期されます。\n- Setter は永続書き込みを非同期でキューに入れます。\n- 耐久性境界が必要な場合 (たとえば、プロセスの終了前、またはテストでファイルの内容をアサートする前) に、`await settingsManager.flush()` を呼び出します。\n- `SettingsManager` は設定 I/O エラーを出力しません。 `settingsManager.drainErrors()` を使用してアプリ層で報告します。\n\n> [examples/sdk/10-settings.ts](../examples/sdk/10-settings.ts) を参照\n\n## リソースローダー\n\n`DefaultResourceLoader` を使用して、拡張機能、スキル、プロンプト、テーマ、および context files を見つけます。\n\n```typescript\nimport {\n  DefaultResourceLoader,\n  getAgentDir,\n} from \"@earendil-works/pi-coding-agent\";\n\nconst loader = new DefaultResourceLoader({\n  cwd,\n  agentDir: getAgentDir(),\n});\nawait loader.reload();\n\nconst extensions = loader.getExtensions();\nconst skills = loader.getSkills();\nconst prompts = loader.getPrompts();\nconst themes = loader.getThemes();\nconst contextFiles = loader.getAgentsFiles().agentsFiles;\n```\n\n## 戻り値\n\n`createAgentSession()` は次を返します:\n\n```typescript\ninterface CreateAgentSessionResult {\n  // The session\n  session: AgentSession;\n  \n  // Extensions result (for runner setup)\n  extensionsResult: LoadExtensionsResult;\n  \n  // Warning if session model couldn't be restored\n  modelFallbackMessage?: string;\n}\n\ninterface LoadExtensionsResult {\n  extensions: Extension[];\n  errors: Array<{ path: string; error: string }>;\n  runtime: ExtensionRuntime;\n}\n```\n\n## 完全な例\n\n```typescript\nimport { getModel } from \"@earendil-works/pi-ai\";\nimport { Type } from \"typebox\";\nimport {\n  createAgentSession,\n  DefaultResourceLoader,\n  defineTool,\n  ModelRuntime,\n  SessionManager,\n  SettingsManager,\n} from \"@earendil-works/pi-coding-agent\";\n\nconst modelRuntime = await ModelRuntime.create({\n  authPath: \"/custom/agent/auth.json\",\n  modelsPath: \"/custom/agent/models.json\",\n});\nif (process.env.MY_KEY) {\n  await modelRuntime.setRuntimeApiKey(\"anthropic\", process.env.MY_KEY);\n}\n\n// Inline tool\nconst statusTool = defineTool({\n  name: \"status\",\n  label: \"Status\",\n  description: \"Get system status\",\n  parameters: Type.Object({}),\n  execute: async () => ({\n    content: [{ type: \"text\", text: `Uptime: ${process.uptime()}s` }],\n    details: {},\n  }),\n});\n\nconst model = getModel(\"anthropic\", \"claude-opus-4-5\");\nif (!model) throw new Error(\"Model not found\");\n\n// In-memory settings with overrides\nconst settingsManager = SettingsManager.inMemory({\n  compaction: { enabled: false },\n  retry: { enabled: true, maxRetries: 2 },\n});\n\nconst loader = new DefaultResourceLoader({\n  cwd: process.cwd(),\n  agentDir: \"/custom/agent\",\n  settingsManager,\n  systemPromptOverride: () => \"You are a minimal assistant. Be concise.\",\n});\nawait loader.reload();\n\nconst { session } = await createAgentSession({\n  cwd: process.cwd(),\n  agentDir: \"/custom/agent\",\n\n  model,\n  thinkingLevel: \"off\",\n  modelRuntime,\n\n  tools: [\"read\", \"bash\", \"status\"],\n  customTools: [statusTool],\n  resourceLoader: loader,\n\n  sessionManager: SessionManager.inMemory(),\n  settingsManager,\n});\n\nsession.subscribe((event) => {\n  if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n  }\n});\n\nawait session.prompt(\"Get status and list files.\");\n```\n\n## 実行モード\n\nSDK は、`createAgentSession()` 上にカスタム インターフェイスを構築するための実行モード ユーティリティをエクスポートします。\n\n### インタラクティブモード\n\nエディター、チャット履歴、およびすべての組み込みコマンドを備えた完全なTUI インタラクティブ モード:\n\n```typescript\nimport {\n  type CreateAgentSessionRuntimeFactory,\n  createAgentSessionFromServices,\n  createAgentSessionRuntime,\n  createAgentSessionServices,\n  getAgentDir,\n  InteractiveMode,\n  SessionManager,\n} from \"@earendil-works/pi-coding-agent\";\n\nconst createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {\n  const services = await createAgentSessionServices({ cwd });\n  return {\n    ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),\n    services,\n    diagnostics: services.diagnostics,\n  };\n};\nconst runtime = await createAgentSessionRuntime(createRuntime, {\n  cwd: process.cwd(),\n  agentDir: getAgentDir(),\n  sessionManager: SessionManager.create(process.cwd()),\n});\n\nconst mode = new InteractiveMode(runtime, {\n  migratedProviders: [],\n  modelFallbackMessage: undefined,\n  initialMessage: \"Hello\",\n  initialImages: [],\n  initialMessages: [],\n});\n\nawait mode.run();\n```\n\n### runPrintMode\n\nシングルショット モード: プロンプトを送信し、結果を出力し、終了します。\n\n```typescript\nimport {\n  type CreateAgentSessionRuntimeFactory,\n  createAgentSessionFromServices,\n  createAgentSessionRuntime,\n  createAgentSessionServices,\n  getAgentDir,\n  runPrintMode,\n  SessionManager,\n} from \"@earendil-works/pi-coding-agent\";\n\nconst createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {\n  const services = await createAgentSessionServices({ cwd });\n  return {\n    ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),\n    services,\n    diagnostics: services.diagnostics,\n  };\n};\nconst runtime = await createAgentSessionRuntime(createRuntime, {\n  cwd: process.cwd(),\n  agentDir: getAgentDir(),\n  sessionManager: SessionManager.create(process.cwd()),\n});\n\nawait runPrintMode(runtime, {\n  mode: \"text\",\n  initialMessage: \"Hello\",\n  initialImages: [],\n  messages: [\"Follow up\"],\n});\n```\n\n### runRpcMode\n\nサブプロセス統合用のJSON-RPC モード:\n\n```typescript\nimport {\n  type CreateAgentSessionRuntimeFactory,\n  createAgentSessionFromServices,\n  createAgentSessionRuntime,\n  createAgentSessionServices,\n  getAgentDir,\n  runRpcMode,\n  SessionManager,\n} from \"@earendil-works/pi-coding-agent\";\n\nconst createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {\n  const services = await createAgentSessionServices({ cwd });\n  return {\n    ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),\n    services,\n    diagnostics: services.diagnostics,\n  };\n};\nconst runtime = await createAgentSessionRuntime(createRuntime, {\n  cwd: process.cwd(),\n  agentDir: getAgentDir(),\n  sessionManager: SessionManager.create(process.cwd()),\n});\n\nawait runRpcMode(runtime);\n```\n\nJSON プロトコルについては、[RPC documentation](rpc.md) を参照してください。\n\n## RPC 代替モード\n\nSDK でビルドせずにサブプロセスベースの統合を行う場合は、CLI を直接使用します。\n\n```bash\npi --mode rpc --no-session\n```\n\nJSON プロトコルについては、[RPC documentation](rpc.md) を参照してください。\n\n次の場合には SDK が優先されます。\n- タイプ セーフティが必要な場合\n- あなたも同じNode.jsプロセスにいます\n- エージェントの状態に直接アクセスする必要がある\n- ツール/拡張機能をプログラム的にカスタマイズしたい\n\nRPC モードは、次の場合に優先されます。\n- 別の言語から統合している\n- プロセスを分離したい\n- 言語に依存しないクライアントを構築している\n\n## 輸出\n\nメインのエントリ ポイントは以下をエクスポートします。\n\n```typescript\n// Factory\ncreateAgentSession\ncreateAgentSessionRuntime\nAgentSessionRuntime\n\n// Auth and Models\nModelRuntime // implements pi-ai Models and owns credential storage\nModelRegistry // synchronous extension compatibility facade\nCredentialSynchronizationError\nresolveCliModel\nresolveModelScopeWithDiagnostics\n\n// Resource loading\nDefaultResourceLoader\ntype ResourceLoader\ncreateEventBus\n\n// Constants and helpers\nCONFIG_DIR_NAME\ndefineTool\ngetAgentDir\ngetPackageDir\ngetReadmePath\ngetDocsPath\ngetExamplesPath\n\n// Session management\nSessionManager\nSettingsManager\n\n// Tool factories\ncreateCodingTools\ncreateReadOnlyTools\ncreateReadTool, createBashTool, createEditTool, createWriteTool\ncreateGrepTool, createFindTool, createLsTool\n\n// Types\ntype CreateAgentSessionOptions\ntype CreateAgentSessionResult\ntype ExtensionFactory\ntype InlineExtension\ntype ExtensionAPI\ntype ToolDefinition\ntype Skill\ntype PromptTemplate\ntype Tool\n```\n\n拡張子の種類については、完全な API については [extensions.md](extensions.md) を参照してください。","sourceFile":"sdk.md"},"security":{"title":"安全","markdown":"Pi はローカルコーディングエージェントです。これは、それを開始したユーザー アカウントのアクセス許可で実行され、そのユーザーが書き込み可能なファイルを同じローカル信頼境界内にあるものとして扱います。\n\n## プロジェクトトラスト\n\nプロジェクトの信頼は、pi がプロジェクトのローカル設定、リソース、パッケージ、および拡張機能を読み込むかどうかを制御します。これは sandbox ではなく、ディレクトリでの作業を開始した後にモデルがツールに実行を要求できる内容を制限しません。\n\nPi は、現在の作業ディレクトリから次のいずれかが見つかった場合、プロジェクトに信頼を必要とするリソースがあるとみなします。\n\n- `.pi/settings.json`\n- `.pi/extensions`、`.pi/skills`、`.pi/prompts`、または `.pi/themes`\n- `.pi/SYSTEM.md` または `.pi/APPEND_SYSTEM.md`\n- 現在のディレクトリまたは祖先ディレクトリ内のプロジェクト `.agents/skills`\n\n裸の `.pi` ディレクトリは、信頼を必要とするプロジェクト リソースとしてカウントされません。\n\n信頼を必要とし、現在のディレクトリまたは親ディレクトリに対する決定が保存されていないリソースを含むプロジェクトで対話型セッションが開始される場合、pi はグローバル設定の `defaultProjectTrust` に従います。デフォルト値は `\"ask\"` で、UI が使用可能な場合にプロジェクトを信頼するかどうかを尋ねます。保存された決定は、`~/.pi/agent/trust.json` の正規ディレクトリによって保存され、現在のパスまたは親パス上で最も近い保存された決定が、グローバル デフォルトの前に適用されます。\n\nプロジェクトを信頼すると、pi は以下を含む信頼を必要とするプロジェクト リソースを読み込むことができます。\n\n- `.pi/settings.json`\n- 拡張機能、スキル、prompt templates、テーマ、システム プロンプト ファイルなどの `.pi` リソース\n- プロジェクト設定で構成されたプロジェクト パッケージが見つからない\n- プロジェクトローカル拡張機能とプロジェクトパッケージ管理拡張機能\n\n信頼が低下すると、保護されたリソースがスキップされます。 `AGENTS.override.md`、`AGENTS.md`、`CLAUDE.md` などのコンテキスト ファイルは、コンテキストの読み込みが無効になっていない限り、プロジェクトの信頼性に関係なく読み込まれます。信頼が解決される前に、pi は context files、ユーザー/グローバル拡張、および CLI `-e` 拡張のみをロードします。ユーザー/グローバル拡張機能と CLI 拡張機能は、`project_trust` イベントを処理できます。はい/いいえの決定を返す最初の拡張機能がその決定を所有します。\n\n非対話型モード (`-p`、`--mode json`、および `--mode rpc`) では、信頼プロンプトは表示されません。該当する保存された信頼決定がなければ、`defaultProjectTrust: \"ask\"` と `\"never\"` はそのようなリソースを無視しますが、`\"always\"` はそれらを信頼します。 `--approve`/`-a` または `--no-approve`/`-na` を使用して、1 回の実行に対してプロジェクトの信頼をオーバーライドします。\n\n## 組み込みのサンドボックスなし\n\nPi には組み込みの sandbox が含まれません。組み込みツールは、pi プロセスの権限でファイルの読み取り、書き込み、編集、およびシェル コマンドの実行を行うことができます。 Extensions は、同じ権限で実行される TypeScript モジュールです。パッケージのインストール、シェル コマンド、言語サーバー、テスト コマンド、およびその他の開発者ツールは、通常のローカル プロセスとして動作します。\n\nこれは意図的なものです。 Pi は、ローカル ソース ツリーで動作し、プロジェクト ツールチェーンを呼び出し、ユーザーの既存の開発環境と統合するように設計されています。部分的なインプロセス sandbox は、依然としてホスト シェル、ファイル システム、パッケージ マネージャー、資格情報、および拡張コードに依存しているため、セキュリティ境界と誤解されやすくなります。実際の分離は、オペレーティング システムまたは仮想化/コンテナの境界から行う必要があります。\n\nプロジェクトの信頼は入力読み込みのガードにすぎません。これにより、承認する前にリポジトリが pi の設定や拡張機能をサイレントに変更することを防ぎます。信頼できないコード、信頼できないプロンプト、または信頼できないモデル出力を安全にするわけではありません。リポジトリ ファイル、コメント、ドキュメント、context files、またはビルド出力からのプロンプト インジェクションは、ローカル エージェントのリスクが予期されており、pi によって確実に防止することはできません。\n\n## 信頼できない作業または監視されていない作業の実行\n\n信頼できないリポジトリ、厳密に監視する予定のない生成コード、または無人オートメーションの場合は、包含された環境で pi を実行します。タスクに必要なファイルと資格情報のみを備えたコンテナー、VM、マイクロ VM、リモート sandbox、またはポリシー制御された sandbox を使用します。\n\n一般的なパターンは [Containerization](containerization.md) に記載されています。\n\n- コンテナ/sandbox 内で `pi` プロセス全体を実行します\n- 組み込みツールの実行を Gondolin マイクロ VM にルーティングしながらホスト pi を実行します\n- エージェントがアクセスする必要があるワークスペース パスのみをマウントします\n- コンテナがホストのセッション、設定、認証情報にアクセスする必要がない限り、ホスト `~/.pi/agent` のマウントを回避します。\n- 必要最小限の API key を渡すか、有効期間が短い認証情報を使用してください\n- タスクが必要のない場合はネットワーク アクセスを制限する\n- 結果を信頼できるシステムにコピーする前に、差分と出力を確認します。\n\nホスト ワークスペースの読み取り/書き込みをバインド マウントした場合でも、コンテナーまたは VM 内からの書き込みによってホスト ファイルが変更される可能性があります。意図しない書き込みからより強力な保護が必要な場合は、読み取り専用マウントを使用するか、sandbox にファイルをコピーしたり、sandbox からファイルをコピーしたりしてください。\n\n## セキュリティ問題の報告\n\nセキュリティ問題を報告するには、リポジトリ [Security Policy](https://github.com/earendil-works/pi-mono/blob/main/SECURITY.md) をフォローしてください。セキュリティに敏感なレポートについては公開問題を開かないでください。\n\n予想されるローカル エージェントの動作、組み込み sandbox の欠如、信頼できないコンテンツからのプロンプト インジェクション、およびユーザーがインストールした拡張機能やスキルの動作は、レポートが実際の権限境界バイパスを実証するか、ローカル ユーザーがまだ持っていないアクセスを pi がどのように許可するかを示す場合を除き、通常、セキュリティ境界の外側にあります。","sourceFile":"security.md"},"session-format":{"title":"セッションファイル形式","markdown":"セッションは JSONL (JSON 行) ファイルとして保存されます。各行は、`type` フィールドを持つ JSON オブジェクトです。セッション エントリは、`id`/`parentId` フィールドを介してツリー構造を形成し、新しいファイルを作成せずにインプレース分岐を可能にします。\n\n## ファイルの場所\n\n```\n~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl\n```\n\nここで、`<path>` は、`/` を `-` に置き換えた作業ディレクトリです。\n\n## セッションの削除\n\nセッションは、`~/.pi/agent/sessions/` にある `.jsonl` ファイルを削除することで削除できます。\n\nPi は、`/resume` から対話的にセッションを削除することもサポートしています (セッションを選択し、`Ctrl+D` を押して確認します)。利用可能な場合、pi は永久的な削除を避けるために `trash` CLI を使用します。\n\n## セッションバージョン\n\nセッションのヘッダーにはバージョン フィールドがあります。\n\n- **バージョン 1**: 線形エントリ シーケンス (レガシー、負荷時に自動移行)\n- **バージョン 2**: `id`/`parentId` リンクを備えたツリー構造\n- **バージョン 3**: `hookMessage` ロールの名前が `custom` に変更されました (拡張機能の統合)\n\n既存のセッションは、ロード時に現在のバージョン (v3) に自動的に移行されます。\n\n## ソースファイル\n\nGitHub ([pi-mono](https://github.com/earendil-works/pi-mono)) のソース:\n- [`packages/coding-agent/src/core/session-manager.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts) - セッション エントリ タイプと SessionManager\n- [`packages/coding-agent/src/core/messages.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/messages.ts) - 拡張メッセージ タイプ (BashExecutionMessage、CustomMessage など)\n- [`packages/ai/src/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/types.ts) - 基本メッセージ タイプ (UserMessage、AssistantMessage、ToolResultMessage)\n- [`packages/agent/src/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/agent/src/types.ts) - AgentMessage 共用体タイプ\n\nプロジェクト内の TypeScript 定義については、`node_modules/@earendil-works/pi-coding-agent/dist/` と `node_modules/@earendil-works/pi-ai/dist/` を調べてください。\n\n## メッセージの種類\n\nセッションエントリには `AgentMessage` オブジェクトが含まれています。これらの型を理解することは、セッションを解析したり拡張機能を作成したりするために不可欠です。\n\n### コンテンツブロック\n\nメッセージには、型指定されたコンテンツ ブロックの配列が含まれます。\n\n```typescript\ninterface TextContent {\n  type: \"text\";\n  text: string;\n}\n\ninterface ImageContent {\n  type: \"image\";\n  data: string;      // base64 encoded\n  mimeType: string;  // e.g., \"image/jpeg\", \"image/png\"\n}\n\ninterface ThinkingContent {\n  type: \"thinking\";\n  thinking: string;\n}\n\ninterface ToolCall {\n  type: \"toolCall\";\n  id: string;\n  name: string;\n  arguments: Record<string, any>;\n}\n```\n\n### 基本メッセージ タイプ (pi-ai より)\n\n```typescript\ninterface UserMessage {\n  role: \"user\";\n  content: string | (TextContent | ImageContent)[];\n  timestamp: number;  // Unix ms\n}\n\ninterface AssistantMessage {\n  role: \"assistant\";\n  content: (TextContent | ThinkingContent | ToolCall)[];\n  api: string;\n  provider: string;\n  model: string;\n  usage: Usage;\n  stopReason: \"stop\" | \"length\" | \"toolUse\" | \"error\" | \"aborted\";\n  errorMessage?: string;\n  timestamp: number;\n}\n\ninterface ToolResultMessage {\n  role: \"toolResult\";\n  toolCallId: string;\n  toolName: string;\n  content: (TextContent | ImageContent)[];\n  details?: any;      // Tool-specific metadata\n  usage?: Usage;      // Nested LLM work performed by the tool\n  isError: boolean;\n  timestamp: number;\n}\n\ninterface Usage {\n  input: number;\n  output: number;\n  cacheRead: number;\n  cacheWrite: number;\n  totalTokens: number;\n  cost: {\n    input: number;\n    output: number;\n    cacheRead: number;\n    cacheWrite: number;\n    total: number;\n  };\n}\n```\n\nエクスポートされた pi-ai `StopReason` タイプには `\"pending\"` も含ま​​れていますが、その値はストリーミング イベントの部分メッセージ用に予約されています。ターミナル `done`/`error` メッセージは、pi がアシスタント メッセージを永続化する前に完了理由に置き換えられるため、`\"pending\"` はセッション JSONL に表示されるべきではありません。\n\n### 拡張メッセージ タイプ (pi-coding-agent から)\n\n```typescript\ninterface BashExecutionMessage {\n  role: \"bashExecution\";\n  command: string;\n  output: string;\n  exitCode: number | undefined;\n  cancelled: boolean;\n  truncated: boolean;\n  fullOutputPath?: string;\n  excludeFromContext?: boolean;  // true for !! prefix commands\n  timestamp: number;\n}\n\ninterface CustomMessage {\n  role: \"custom\";\n  customType: string;            // Extension identifier\n  content: string | (TextContent | ImageContent)[];\n  display: boolean;              // Show in TUI\n  details?: any;                 // Extension-specific metadata\n  timestamp: number;\n}\n\ninterface BranchSummaryMessage {\n  role: \"branchSummary\";\n  summary: string;\n  fromId: string;                // Entry we branched from\n  timestamp: number;\n}\n\ninterface CompactionSummaryMessage {\n  role: \"compactionSummary\";\n  summary: string;\n  tokensBefore: number;\n  timestamp: number;\n}\n```\n\n### エージェントメッセージユニオン\n\n```typescript\ntype AgentMessage =\n  | UserMessage\n  | AssistantMessage\n  | ToolResultMessage\n  | BashExecutionMessage\n  | CustomMessage\n  | BranchSummaryMessage\n  | CompactionSummaryMessage;\n```\n\n## エントリーベース\n\nすべてのエントリ (`SessionHeader` を除く) は `SessionEntryBase` を拡張します。\n\n```typescript\ninterface SessionEntryBase {\n  type: string;\n  id: string;           // 8-char hex ID\n  parentId: string | null;  // Parent entry ID (null for first entry)\n  timestamp: string;    // ISO timestamp\n}\n```\n\n## エントリの種類\n\n### セッションヘッダー\n\nファイルの最初の行。メタデータのみ。ツリーの一部ではありません (`id`/`parentId` なし)。\n\n```json\n{\"type\":\"session\",\"version\":3,\"id\":\"uuid\",\"timestamp\":\"2024-12-03T14:00:00.000Z\",\"cwd\":\"/path/to/project\"}\n```\n\n親とのセッションの場合 (`/fork`、`/clone`、または `newSession({ parentSession })` で作成):\n\n```json\n{\"type\":\"session\",\"version\":3,\"id\":\"uuid\",\"timestamp\":\"2024-12-03T14:00:00.000Z\",\"cwd\":\"/path/to/project\",\"parentSession\":\"/path/to/original/session.jsonl\"}\n```\n\n### セッションメッセージエントリ\n\n会話中のメッセージ。 `message` フィールドには `AgentMessage` が含まれています。\n\n```json\n{\"type\":\"message\",\"id\":\"a1b2c3d4\",\"parentId\":\"prev1234\",\"timestamp\":\"2024-12-03T14:00:01.000Z\",\"message\":{\"role\":\"user\",\"content\":\"Hello\"}}\n{\"type\":\"message\",\"id\":\"b2c3d4e5\",\"parentId\":\"a1b2c3d4\",\"timestamp\":\"2024-12-03T14:00:02.000Z\",\"message\":{\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"Hi!\"}],\"provider\":\"anthropic\",\"model\":\"claude-sonnet-4-5\",\"usage\":{...},\"stopReason\":\"stop\"}}\n{\"type\":\"message\",\"id\":\"c3d4e5f6\",\"parentId\":\"b2c3d4e5\",\"timestamp\":\"2024-12-03T14:00:03.000Z\",\"message\":{\"role\":\"toolResult\",\"toolCallId\":\"call_123\",\"toolName\":\"bash\",\"content\":[{\"type\":\"text\",\"text\":\"output\"}],\"isError\":false}}\n```\n\n### ModelChangeEntry\n\nユーザーがセッション中にモデルを切り替えると発生します。\n\n```json\n{\"type\":\"model_change\",\"id\":\"d4e5f6g7\",\"parentId\":\"c3d4e5f6\",\"timestamp\":\"2024-12-03T14:05:00.000Z\",\"provider\":\"openai\",\"modelId\":\"gpt-4o\"}\n```\n\n### ThinkingLevelChangeEntry\n\nユーザーが思考/推論レベルを変更したときに発生します。\n\n```json\n{\"type\":\"thinking_level_change\",\"id\":\"e5f6g7h8\",\"parentId\":\"d4e5f6g7\",\"timestamp\":\"2024-12-03T14:06:00.000Z\",\"thinkingLevel\":\"high\"}\n```\n\n### 圧縮エントリ\n\nコンテキストが圧縮されたときに作成されます。以前のメッセージの概要を保存します。\n\n```json\n{\"type\":\"compaction\",\"id\":\"f6g7h8i9\",\"parentId\":\"e5f6g7h8\",\"timestamp\":\"2024-12-03T14:10:00.000Z\",\"summary\":\"User discussed X, Y, Z...\",\"firstKeptEntryId\":\"c3d4e5f6\",\"tokensBefore\":50000}\n```\n\n新しいハーネス生成の圧縮では、保持された圧縮後のコンテキストが、`firstKeptEntryId` ではなくエントリに直接埋め込まれます。\n\n```json\n{\"type\":\"compaction\",\"id\":\"f6g7h8i9\",\"parentId\":\"e5f6g7h8\",\"timestamp\":\"2024-12-03T14:10:00.000Z\",\"summary\":\"User discussed X, Y, Z...\",\"tokensBefore\":50000,\"retainedTail\":[{\"role\":\"user\",\"content\":\"latest request\"},{\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"latest reply\"}],\"provider\":\"anthropic\",\"model\":\"claude-sonnet-4-5\",\"usage\":{...},\"stopReason\":\"stop\"}]}\n```\n\nオプションのフィールド:\n- `usage`: 概要の生成による LLM の使用。セッショントークンとコストの合計に含まれます\n- `retainedTail`: 圧縮後に実体化された `AgentMessage[]` が保持されます。これは、古いセッションとの下位互換性のためのみのオプションです。新しいハーネスによって生成された圧縮にはこれが含まれているため、圧縮エントリの前に古いエントリをたどることなく、このチェックポイントからコンテキストを再構築できます。\n- `details`: 実装固有のデータ (例: デフォルトの場合は `{ readFiles: string[], modifiedFiles: string[] }`、拡張機能の場合はカスタム データ)\n- `fromHook`: 拡張機能によって生成された場合は `true`、pi で生成された場合は `false`/`undefined` (従来のフィールド名)\n- `firstKeptEntryId`: 古いエントリ形式との互換性のため。\n\n### ブランチ概要エントリ\n\n共通の祖先までの左ブランチの LLM 生成サマリーを使用して、`/tree` を介してブランチを切り替えるときに作成されます。放棄されたパスからコンテキストをキャプチャします。\n\n```json\n{\"type\":\"branch_summary\",\"id\":\"g7h8i9j0\",\"parentId\":\"a1b2c3d4\",\"timestamp\":\"2024-12-03T14:15:00.000Z\",\"fromId\":\"f6g7h8i9\",\"summary\":\"Branch explored approach A...\"}\n```\n\nオプションのフィールド:\n- `usage`: 概要の生成による LLM の使用。セッショントークンとコストの合計に含まれます\n- `details`: デフォルトのファイル追跡データ (`{ readFiles: string[], modifiedFiles: string[] }`)、または拡張子のカスタム データ\n- `fromHook`: 拡張機能によって生成された場合は `true`、pi で生成された場合は `false`/`undefined` (従来のフィールド名)\n\n### カスタムエントリー\n\n拡張機能の状態の永続性。 LLM コンテキストには参加しません。\n\n```json\n{\"type\":\"custom\",\"id\":\"h8i9j0k1\",\"parentId\":\"g7h8i9j0\",\"timestamp\":\"2024-12-03T14:20:00.000Z\",\"customType\":\"my-extension\",\"data\":{\"count\":42}}\n```\n\nリロード時に拡張機能のエントリを識別するには、`customType` を使用します。インタラクティブ モードでは、`pi.registerEntryRenderer(customType, renderer)` を介してカスタム エントリをレンダリングできますが、それでも LLM コンテキストには参加しません。\n\n### カスタムメッセージエントリ\n\nLLM コンテキストに参加する、拡張機能によって挿入されたメッセージ。\n\n```json\n{\"type\":\"custom_message\",\"id\":\"i9j0k1l2\",\"parentId\":\"h8i9j0k1\",\"timestamp\":\"2024-12-03T14:25:00.000Z\",\"customType\":\"my-extension\",\"content\":\"Injected context...\",\"display\":true}\n```\n\nフィールド:\n- `content`: 文字列または `(TextContent | ImageContent)[]` (UserMessage と同じ)\n- `display`: `true` = 個別のスタイルで TUI に表示、`false` = 非表示\n- `details`: オプションの拡張機能固有のメタデータ (LLM には送信されません)\n\n### ラベルエントリ\n\nエントリ上のユーザー定義のブックマーク/マーカー。\n\n```json\n{\"type\":\"label\",\"id\":\"j0k1l2m3\",\"parentId\":\"i9j0k1l2\",\"timestamp\":\"2024-12-03T14:30:00.000Z\",\"targetId\":\"a1b2c3d4\",\"label\":\"checkpoint-1\"}\n```\n\nラベルをクリアするには、`label` を `undefined` に設定します。\n\n### セッション情報エントリ\n\nセッションのメタデータ (ユーザー定義の表示名など)。拡張機能の`/name`、`--name` / `-n`、または `pi.setSessionName()` で設定します。\n\n```json\n{\"type\":\"session_info\",\"id\":\"k1l2m3n4\",\"parentId\":\"j0k1l2m3\",\"timestamp\":\"2024-12-03T14:35:00.000Z\",\"name\":\"Refactor auth module\"}\n```\n\nセッション名は、設定すると最初のメッセージの代わりにセッション セレクター (`/resume`) に表示されます。\n\n## ツリー構造\n\nエントリはツリーを形成します。\n- 最初のエントリには `parentId: null` が付いています\n- 後続の各エントリは、`parentId` を介してその親を指します。\n- 分岐すると、以前のエントリから新しい子が作成されます\n- 「リーフ」はツリー内の現在の位置です。\n\n```\n[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf\n                                                            │\n                                                            └─ [branch_summary] ─── [user msg] ← alternate branch\n```\n\n## コンテキストの構築\n\n`buildContextEntries()` は現在のリーフからルートまで歩き、圧縮を尊重しながらアクティブなエントリ リストを生成します。\n\n1. パス上のすべてのエントリを収集します\n2. `CompactionEntry` がパス上にある場合:\n   - 最初に圧縮エントリを含めます\n   - `retainedTail` が存在する場合、自己完結型チェックポイントとして機能し、圧縮後のエントリが含まれます。\n   - それ以外の場合は、`firstKeptEntryId` から圧縮までのエントリが含まれます\n   - 圧縮後のエントリが含まれます\n3. 選択した範囲内の非メッセージ エントリを保持して、対話モードでレンダリングできるようにします。\n\n`buildSessionContext()` は、そのエントリ リストに基づいて、LLM のメッセージ リストを生成します。\n\n1. フルパスから現在のモデルと思考レベルの設定を抽出します。\n2. 選択したエントリをメッセージに変換します。\n   - `message` -> 保存 `AgentMessage`\n   - `compaction` -> `compactionSummary` プラス `retainedTail` (存在する場合)\n   - `branch_summary` -> `branchSummary`\n   - `custom_message` -> `CustomMessage`\n   - `custom` -> コンテキストメッセージなし\n\nこれにより、新しい圧縮が自己完結型チェックポイントのように機能します。 `retainedTail` は、`firstKeptEntryId` のみを保存する古いセッションが引き続き正しく読み込まれるようにするためのオプションです。\n\n## 解析例\n\n```typescript\nimport { readFileSync } from \"fs\";\n\nconst lines = readFileSync(\"session.jsonl\", \"utf8\").trim().split(\"\\n\");\n\nfor (const line of lines) {\n  const entry = JSON.parse(line);\n\n  switch (entry.type) {\n    case \"session\":\n      console.log(`Session v${entry.version ?? 1}: ${entry.id}`);\n      break;\n    case \"message\":\n      console.log(`[${entry.id}] ${entry.message.role}: ${JSON.stringify(entry.message.content)}`);\n      break;\n    case \"compaction\":\n      console.log(`[${entry.id}] Compaction: ${entry.tokensBefore} tokens summarized`);\n      break;\n    case \"branch_summary\":\n      console.log(`[${entry.id}] Branch from ${entry.fromId}`);\n      break;\n    case \"custom\":\n      console.log(`[${entry.id}] Custom (${entry.customType}): ${JSON.stringify(entry.data)}`);\n      break;\n    case \"custom_message\":\n      console.log(`[${entry.id}] Extension message (${entry.customType}): ${entry.content}`);\n      break;\n    case \"label\":\n      console.log(`[${entry.id}] Label \"${entry.label}\" on ${entry.targetId}`);\n      break;\n    case \"model_change\":\n      console.log(`[${entry.id}] Model: ${entry.provider}/${entry.modelId}`);\n      break;\n    case \"thinking_level_change\":\n      console.log(`[${entry.id}] Thinking: ${entry.thinkingLevel}`);\n      break;\n  }\n}\n```\n\n## セッションマネージャー API\n\nプログラムでセッションを操作するための主要なメソッド。\n\n### 静的作成方法\n- `SessionManager.create(cwd, sessionDir?)` - 新しいセッション\n- `SessionManager.open(path, sessionDir?)` - 既存のセッション ファイルを開きます\n- `SessionManager.continueRecent(cwd, sessionDir?)` - 最新のものを継続するか、新しく作成します\n- `SessionManager.inMemory(cwd?)` - ファイルの永続性なし\n- `SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?)` - 別のプロジェクトからセッションをフォークする\n\n### 静的リスト方法\n- `SessionManager.list(cwd, sessionDir?, onProgress?)` - ディレクトリのセッションをリストします。\n- `SessionManager.listAll(onProgress?)` - すべてのプロジェクトにわたるすべてのセッションをリストします。\n\n### インスタンスメソッド - セッション管理\n- `newSession(options?)` - 新しいセッションを開始します (オプション: `{ parentSession?: string }`)\n- `setSessionFile(path)` - 別のセッション ファイルに切り替えます\n- `createBranchedSession(leafId)` - ブランチを新しいセッション ファイルに抽出します\n\n### インスタンス メソッド - 追加 (すべてエントリ ID を返します)\n- `appendMessage(message)` - メッセージを追加\n- `appendThinkingLevelChange(level)` - 思考の変化を記録する\n- `appendModelChange(provider, modelId)` - 機種変更の記録\n- `appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?)` - 圧縮を追加します\n- `appendCustomEntry(customType, data?)` - 拡張状態 (コンテキスト内ではない)\n- `appendSessionInfo(name)` - セッション表示名を設定します\n- `appendCustomMessageEntry(customType, content, display, details?)` - 拡張メッセージ (コンテキスト内)\n- `appendLabelChange(targetId, label)` - ラベルの設定/クリア\n\n### インスタンス メソッド - ツリー ナビゲーション\n- `getLeafId()` - 現在の位置\n- `getLeafEntry()` - 現在のリーフ エントリを取得します\n- `getEntry(id)` - ID によるエントリーの取得\n- `getBranch(fromId?)` - 入り口から根まで歩きます\n- `getTree()` - 完全なツリー構造を取得する\n- `getChildren(parentId)` - 直接の子を取得する\n- `getLabel(id)` - エントリ用のラベルを取得します\n- `branch(entryId)` - リーフを前のエントリに移動します\n- `resetLeaf()` - リーフを null にリセットします (エントリの前)\n- `branchWithSummary(entryId, summary, details?, fromHook?)` - コンテキストの概要を含む分岐\n\n### インスタンス メソッド - コンテキストと情報\n- `buildContextEntries()` - 圧縮が適用されたアクティブなブランチ エントリを取得します\n- `buildSessionContext()` - LLM のメッセージ、思考レベル、モデルを取得します\n- `getEntries()` - すべてのエントリ (ヘッダーを除く)\n- `getHeader()` - セッションヘッダーのメタデータ\n- `getSessionName()` - 最新の session_info エントリから表示名を取得します\n- `getCwd()` - 作業ディレクトリ\n- `getSessionDir()` - セッション保存ディレクトリ\n- `getSessionId()` - セッション UUID\n- `getSessionFile()` - セッション ファイル パス (メモリ内では未定義)\n- `isPersisted()` - セッションがディスクに保存されるかどうか","sourceFile":"session-format.md"},"sessions":{"title":"セッション","markdown":"Pi は会話をセッションとして保存するので、作業を続行したり、以前のターンから分岐したり、以前のパスを再訪したりすることができます。\n\n## セッションストレージ\n\nセッションは作業ディレクトリごとに整理されて `~/.pi/agent/sessions/` に自動保存されます。各セッションはツリー構造の JSONL ファイルです。\n\n```bash\npi -c                  # Continue most recent session\npi -r                  # Browse and select from past sessions\npi --no-session        # Ephemeral mode; do not save\npi --name \"my task\"    # Set session display name at startup\npi --session <path|id> # Use a specific session file or partial session ID\npi --fork <path|id>    # Fork a session file or partial session ID into a new session\n```\n\n対話モードで `/session` を使用すると、現在のセッション ファイル、セッション ID、メッセージ数、トークン、コストが表示されます。\n\nJSONL ファイル形式と SessionManager API については、[Session Format](session-format.md) を参照してください。\n\n## セッションコマンド\n\n| 指示 | 説明 |\n|---------|-------------|\n| `/resume` | 以前のセッションを参照して選択する |\n| `/new` | 新しいセッションを開始する |\n| `/name <name>` | 現在のセッションの表示名を設定します |\n| `/session` | セッション情報を表示 |\n| `/tree` | 現在の session tree をナビゲートします |\n| `/fork` | 以前のユーザー メッセージから新しいセッションを作成する |\n| `/clone` | 現在のアクティブなブランチを新しいセッションに複製します。 |\n| `/compact [prompt]` | 古いコンテキストを要約します。 [Compaction](compaction.md)を参照 |\n| `/export [file]` | セッションをHTMLにエクスポート |\n| `/share` | 共有可能な HTML リンクを使用してプライベート GitHub 要点としてアップロードします |\n\n## セッションの再開と削除\n\n`/resume` は、現在のプロジェクトの対話型セッション ピッカーを開きます。 `pi -r` は起動時に同じピッカーを開きます。\n\nピッカーでは次のことができます。\n\n- 入力して検索する\n- Ctrl+Pでパス表示を切り替える\n- Ctrl+S で並べ替えモードを切り替えます\n- Ctrl+N を使用して名前付きセッションにフィルターをかける\n- Ctrl+Rで名前を変更\n- Ctrl+D で削除し、確認します\n\n利用可能な場合、pi はファイルを完全に削除するのではなく、削除に `trash` CLI を使用します。\n\n## セッションの命名\n\n`/name <name>` を使用して、人間が読めるセッション名を設定します。\n\n```text\n/name Refactor auth module\n```\n\n起動時の名前を `--name` または `-n` で設定します。\n\n```bash\npi --name \"Refactor auth module\"\npi --name \"CI audit\" -p \"Review this build failure\"\n```\n\n名前付きセッションは、`/resume` と `pi -r` で見つけやすくなります。\n\n## `/tree`で分岐\n\nセッションはツリーとして保存されます。すべてのエントリには `id` と `parentId` があり、現在の位置がアクティブ リーフになります。 `/tree` を使用すると、新しいファイルを作成せずに、前のポイントにジャンプしてそこから続行できます。\n\n<p align=\"center\"><img src=\"images/tree-view.png\" alt=\"Tree View\" width=\"600\"></p>\n\n形状の例:\n\n```text\n├─ user: \"Hello, can you help...\"\n│  └─ assistant: \"Of course! I can...\"\n│     ├─ user: \"Let's try approach A...\"\n│     │  └─ assistant: \"For approach A...\"\n│     │     └─ user: \"That worked...\"  ← active\n│     └─ user: \"Actually, approach B...\"\n│        └─ assistant: \"For approach B...\"\n```\n\n### ツリーコントロール\n\n| 鍵 | アクション |\n|-----|--------|\n| ↑/↓ | 表示されているエントリを移動する |\n| ←/→ | ページアップ/ページダウン |\n| Ctrl+←/Ctrl+→ または Alt+←/Alt+→ | 分岐セグメント間を折りたたむ/展開する、またはジャンプする |\n| Shift+L | 選択したエントリのラベルを設定またはクリアします |\n| Shift+T | ラベルのタイムスタンプを切り替えます |\n| 入力 | エントリを選択 |\n| エスケープ/Ctrl+C | キャンセル |\n| Ctrl+O | サイクルフィルターモード |\n\nフィルター モードは、デフォルト、ツールなし、ユーザーのみ、ラベル付きのみ、およびすべてです。 [Settings](settings.md)の`treeFilterMode`でデフォルトを設定します。\n\n### 選択動作\n\nユーザーまたはカスタム メッセージの選択:\n\n1. リーフを選択したメッセージの親に移動します。\n2. 選択したメッセージ テキストをエディタに配置します。\n3. 新しいブランチを作成して、編集して再送信できます。\n\nアシスタント、ツール、圧縮、またはその他の非ユーザー エントリの選択:\n\n1. リーフをそのエントリに移動します。\n2. エディターを空のままにします。\n3. その時点から続行できます。\n\nroot ユーザーのメッセージを選択すると、リーフが空の会話にリセットされ、元のプロンプトがエディターに表示されます。\n\n## `/tree`、`/fork`、`/clone`\n\n| 特徴 | `/tree` | `/fork` | `/clone` |\n|---------|---------|---------|----------|\n| 出力 | 同じセッションファイル | 新しいセッションファイル | 新しいセッションファイル |\n| ビュー | 完全なツリー | ユーザーメッセージセレクター | 現在アクティブなブランチ |\n| 一般的な使用方法 | 代替案を検討する | 以前のプロンプトから新しいセッションを開始する | 続行する前に現在の作業を複製してください |\n| まとめ | オプションのブランチの概要 | なし | なし |\n\n代替案をまとめたい場合は、`/tree` を使用します。別のセッション ファイルが必要な場合は、`/fork` または `/clone` を使用します。\n\n## ブランチの概要\n\n`/tree` があるブランチから別のブランチに切り替えると、pi は放棄されたブランチを要約し、その要約を新しい位置に添付できます。これにより、ブランチ全体を再生することなく、離れたパスからの重要なコンテキストが保持されます。\n\nプロンプトが表示されたら、次のいずれかを選択します。\n\n1. 要約なし\n2. デフォルトのプロンプトで要約する\n3. カスタムフォーカス命令で要約する\n\nbranch summarization の内部構造と拡張フックについては、[Compaction](compaction.md) を参照してください。\n\n## セッション形式\n\nセッション ファイルは JSONL で、メッセージ エントリ、モデルの変更、思考レベルの変更、ラベル、圧縮、ブランチの要約、および拡張エントリが含まれます。\n\nパーサー、拡張機能、SDK の使用法、および完全な SessionManager API については、[Session Format](session-format.md) を参照してください。","sourceFile":"sessions.md"},"settings":{"title":"設定","markdown":"Pi は、グローバル設定をオーバーライドするプロジェクト設定を持つ JSON 設定ファイルを使用します。\n\n| 位置 | 範囲 |\n|----------|-------|\n| `~/.pi/agent/settings.json` | グローバル (すべてのプロジェクト) |\n| `.pi/settings.json` | プロジェクト(カレントディレクトリ) |\n\n直接編集するか、一般的なオプションに `/settings` を使用します。\n\n## プロジェクトトラスト\n\nインタラクティブな起動時に、pi は、プロジェクトのローカル設定、リソース、またはプロジェクト `.agents/skills` が含まれており、`~/.pi/agent/trust.json` のフォルダーまたは親フォルダーに対する決定が保存されていないプロジェクト フォルダーを信頼する前に質問します。プロジェクトを信頼すると、pi は `.pi/settings.json` および `.pi` リソースをロードし、不足しているプロジェクト パッケージをインストールし、プロジェクト拡張機能を実行できます。\n\n非対話型モード (`-p`、`--mode json`、および `--mode rpc`) では、信頼プロンプトは表示されません。該当する保存された信頼決定がなければ、グローバル設定の `defaultProjectTrust` を使用します: `ask` (デフォルト) と `never` はこれらのプロジェクト リソースを無視し、`always` はそれらを信頼します。 `--approve`/`-a` または `--no-approve`/`-na` を渡して、1 回の実行でプロジェクトの信頼をオーバーライドします。\n\n拡張機能または保存された決定が適用されない場合、`defaultProjectTrust` はフォールバック動作を制御します。 `~/.pi/agent/settings.json`で`\"ask\"`、`\"always\"`、`\"never\"`に設定するか、`/settings`で変更します。\n\n`pi config` とパッケージ コマンドは、`pi update` がプロンプトを表示しないことを除き、同じプロジェクト信頼フローを使用します。 1 つのコマンドに対してプロジェクトのローカル設定を信頼するには `--approve` を、無視するには `--no-approve` を渡します。\n\n対話モードで `/trust` を使用すると、直接の親フォルダーに対する信頼を含め、将来のセッションのためにプロジェクトの信頼決定を保存できます。 `~/.pi/agent/trust.json` のみを書き込みます。現在のセッションはリロードされないため、変更を有効にするために pi を再起動します。\n\n## すべての設定\n\n### モデルと思考\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `defaultProvider` | 弦 | - | デフォルトのプロバイダー (例: `\"anthropic\"`、`\"openai\"`) |\n| `defaultModel` | 弦 | - | デフォルトのモデルID |\n| `defaultThinkingLevel` | 弦 | - | `\"off\"`, `\"minimal\"`, `\"low\"`, `\"medium\"`, `\"high\"`, `\"xhigh\"`, `\"max\"` |\n| `hideThinkingBlock` | ブール値 | `false` | 出力内の思考ブロックを非表示にする |\n| `showCacheMissNotices` | ブール値 | `false` | 重大なプロンプト キャッシュ ミスのトランスクリプト通知を表示する |\n| `thinkingBudgets` | 物体 | - | 思考レベルごとのカスタムトークン予算 |\n\n#### 考えている予算\n\n```json\n{\n  \"thinkingBudgets\": {\n    \"minimal\": 1024,\n    \"low\": 4096,\n    \"medium\": 10240,\n    \"high\": 32768\n  }\n}\n```\n\n### UIとディスプレイ\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `theme` | 弦 | `\"dark\"` | テーマ名 (`\"dark\"`、`\"light\"`、またはカスタム) |\n| `externalEditor` | 弦 | `$VISUAL`、次に `$EDITOR`、Windows ではメモ帳、その他の場所では `nano` | Ctrl+G 外部エディターのコマンド。環境変数よりも優先されます |\n| `quietStartup` | ブール値 | `false` | 起動ヘッダーを非表示にする |\n| `defaultProjectTrust` | 弦 | `\"ask\"` | フォールバック プロジェクトの信頼動作: `\"ask\"`、`\"always\"`、または `\"never\"`。グローバル設定のみ |\n| `collapseChangelog` | ブール値 | `false` | 更新後に要約された変更ログを表示する |\n| `enableInstallTelemetry` | ブール値 | `true` | 最初のインストールまたは変更ログで更新が検出された後、匿名のインストール/更新バージョン ping を送信します。これは更新チェックを制御しません |\n| `enableAnalytics` | ブール値 | `false` | オプトイン分析データ共有。現在、実験的な初回セットアップ時にのみ要求されます (`PI_EXPERIMENTAL=1`) |\n| `trackingId` | 弦 | - | `enableAnalytics` がオンになったときに生成される分析追跡識別子 |\n| `doubleEscapeAction` | 弦 | `\"tree\"` | ダブルエスケープのアクション: `\"tree\"`、`\"fork\"`、または `\"none\"` |\n| `treeFilterMode` | 弦 | `\"default\"` | `/tree` のデフォルトフィルター: `\"default\"`、`\"no-tools\"`、`\"user-only\"`、`\"labeled-only\"`、`\"all\"` |\n| `editorPaddingX` | 番号 | `0` | 入力エディターの水平パディング (0 ～ 3) |\n| `outputPad` | 番号 | `1` | ユーザー メッセージ、アシスタント メッセージ、思考の水平パディング (0 または 1) |\n| `autocompleteMaxVisible` | 番号 | `5` | オートコンプリート ドロップダウンに表示される項目の最大数 (3 ～ 20) |\n| `showHardwareCursor` | ブール値 | `false` | TUI が IME サポート用に配置されている間、ターミナル カーソルを表示します |\n| `tuiMode` | 弦 | `\"regular\"` | インタラクティブ TUI モード: `\"regular\"` または実験的 `\"fullscreen\"`。 `/settings` からの変更はすぐに適用されます。 `--tui-mode` は起動時にこの設定を上書きします |\n| `fullscreenExitOutput` | 弦 | `\"transcript\"` | 全画面終了出力: `\"transcript\"` は最終的なトランスクリプトと再開ヒントを印刷しますが、`\"resume-hint\"` は前の画面を復元し、再開ヒントのみを印刷します。通常の TUI モードでは効果がありません |\n| `fullscreenScrollbar` | 弦 | `\"auto\"` | 全画面トランスクリプト スクロールバー: `\"auto\"` はスクロール中に一時的に表示し、`\"always\"` は右端の列を予約して表示したままにし、`\"hidden\"` は非表示にします。通常の TUI モードでは効果がありません |\n\nVS Code の場合、エディターが終了した後に pi が再開されるように、`--wait` を含めます。\n\n```json\n{\n  \"externalEditor\": \"code --wait\"\n}\n```\n\n### テレメトリとアップデートのチェック\n\n`enableInstallTelemetry` は、`https://pi.dev/api/report-install` への匿名インストール/更新 ping のみを制御します。テレメトリをオプトアウトしても、更新チェックは無効になりません。 Pi は引き続き `https://pi.dev/api/latest-version` をフェッチして最新バージョンを探すことができます。\n\nPi バージョン更新チェックを無効にするには、`PI_SKIP_VERSION_CHECK=1` を設定します。 `--offline` または `PI_OFFLINE=1` を使用して、更新チェック、パッケージ更新チェック、インストール/更新テレメトリなど、ここで説明するすべての起動ネットワーク操作を無効にします。\n\n### ネットワーク\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `httpProxy` | 弦 | - | HTTP プロキシ URL は `HTTP_PROXY` および `HTTPS_PROXY` として適用されます。グローバル設定のみ。 |\n\n```json\n{\n  \"httpProxy\": \"http://127.0.0.1:7890\"\n}\n```\n\n### 警告\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `warnings.anthropicExtraUsage` | ブール値 | `true` | Anthropic サブスクリプション認証で有料の追加使用量が使用される可能性がある場合に警告を表示します |\n\n```json\n{\n  \"warnings\": {\n    \"anthropicExtraUsage\": false\n  }\n}\n```\n\n### 圧縮\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `compaction.enabled` | ブール値 | `true` | 自動圧縮を有効にする |\n| `compaction.reserveTokens` | 番号 | `16384` | LLM 応答用に予約されたトークン |\n| `compaction.keepRecentTokens` | 番号 | `20000` | 保持する最近のトークン (要約されていない) |\n\n```json\n{\n  \"compaction\": {\n    \"enabled\": true,\n    \"reserveTokens\": 16384,\n    \"keepRecentTokens\": 20000\n  }\n}\n```\n\n### ブランチの概要\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `branchSummary.reserveTokens` | 番号 | `16384` | branch summarization 用に予約されたトークン |\n| `branchSummary.skipPrompt` | ブール値 | `false` | 「分岐を要約しますか?」をスキップします。 `/tree` ナビゲーションのプロンプト (デフォルトでは概要なし) |\n\n### リトライ\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `retry.enabled` | ブール値 | `true` | 一時的なエラーに対するエージェントレベルの自動再試行を有効にする |\n| `retry.maxRetries` | 番号 | `3` | エージェントレベルの最大再試行回数 |\n| `retry.baseDelayMs` | 番号 | `2000` | エージェントレベルの指数バックオフの基本遅延 (2 秒、4 秒、8 秒) |\n| `retry.provider.timeoutMs` | 番号 | SDK デフォルト | プロバイダー/SDK リクエストのタイムアウト (ミリ秒) |\n| `retry.provider.maxRetries` | 番号 | `0` | プロバイダー/SDK 再試行 |\n| `retry.provider.maxRetryDelayMs` | 番号 | `60000` | 失敗するまでのサーバー要求の最大遅延 (60 秒) |\n\nプロバイダーが `retry.provider.maxRetryDelayMs` より長い再試行遅延をリクエストすると、リクエストはサイレントに待機するのではなく、有益なエラーが表示されてただちに失敗します。制限を無効にするには、`0` に設定します。\n\nプロバイダーレベルの再試行が明示的に必要でない限り、`retry.provider.maxRetries` を `0` のままにしておきます。 `0` より上に設定すると、SDK/プロバイダーは、Pi がエラーを検出する前に使用量制限外エラーの処理を再試行するため、状況によってはプロバイダー クォータがリセットされるまでエージェントがブロックされる可能性があります。\n\n```json\n{\n  \"retry\": {\n    \"enabled\": true,\n    \"maxRetries\": 3,\n    \"baseDelayMs\": 2000,\n    \"provider\": {\n      \"timeoutMs\": 3600000,\n      \"maxRetries\": 0,\n      \"maxRetryDelayMs\": 60000\n    }\n  }\n}\n```\n\n### メッセージ配信\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `steeringMode` | 弦 | `\"one-at-a-time\"` | ステアリングメッセージの送信方法: `\"all\"` または `\"one-at-a-time\"` |\n| `followUpMode` | 弦 | `\"one-at-a-time\"` | フォローアップ メッセージの送信方法: `\"all\"` または `\"one-at-a-time\"` |\n| `transport` | 弦 | `\"auto\"` | 複数のトランスポートをサポートするプロバイダーの優先トランスポート: `\"sse\"`、`\"websocket\"`、`\"websocket-cached\"`、または `\"auto\"` |\n| `httpIdleTimeoutMs` | 番号 | `300000` | HTTP ヘッダー/本文のアイドル タイムアウト (ミリ秒単位)。明示的なストリーム アイドル タイムアウトを持つプロバイダーでも使用されます。無効にするには、`0` に設定します。 |\n| `websocketConnectTimeoutMs` | 番号 | `15000` | WebSocket トランスポートをサポートするプロバイダーの WebSocket 接続/オープン ハンドシェイク タイムアウト (ミリ秒単位)。無効にするには、`0` に設定します。 |\n\n### 端末とイメージ\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `terminal.showImages` | ブール値 | `true` | ターミナルに画像を表示する (サポートされている場合) |\n| `terminal.imageWidthCells` | 番号 | `60` | ターミナルセルの推奨インライン画像幅 |\n| `terminal.clearOnShrink` | ブール値 | `false` | コンテンツが縮小するときに空の行をクリアします (ちらつきが発生する可能性があります) |\n| `images.autoResize` | ブール値 | `true` | 画像のサイズを最大 2000x2000 に変更します。 `@file` 添付ファイル、`read`、ツールによって返された画像に適用されます |\n| `images.blockImages` | ブール値 | `false` | すべての画像が LLM に送信されるのをブロックする |\n\n### シェル\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `shellPath` | 弦 | - | カスタム シェル パス (Windows 上の Cygwin など)。ホームディレクトリの先頭の`~`をサポートします |\n| `shellCommandPrefix` | 弦 | - | すべての bash コマンドのプレフィックス (例: `\"shopt -s expand_aliases\"`) |\n| `npmCommand` | 弦[] | - | npm パッケージの検索/インストール操作に使用されるコマンド argv (例: `[\"mise\", \"exec\", \"node@20\", \"--\", \"npm\"]`) |\n\n```json\n{\n  \"npmCommand\": [\"mise\", \"exec\", \"node@20\", \"--\", \"npm\"]\n}\n```\n\n`npmCommand` は、git パッケージ内のインストール、アンインストール、依存関係のインストールを含む、すべての npm パッケージ マネージャー操作に使用されます。ユーザースコープの npm パッケージは `~/.pi/agent/npm/` にインストールされます。プロジェクト スコープの npm パッケージは `.pi/npm/` にインストールされます。プロセスを起動する必要があるとおりに、argv スタイルのエントリを正確に使用してください。 `npmCommand` が設定されている場合、git パッケージの依存関係インストールでは、ラッパーまたは代替パッケージ マネージャーの npm 固有のフラグを回避するために、プレーンな `install` が使用されます。\n\n### セッション\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `sessionDir` | 弦 | - | セッションファイルが保存されるディレクトリ。絶対パスまたは相対パスに `~` を加えたものを受け入れます。 |\n\n```json\n{ \"sessionDir\": \".pi/sessions\" }\n```\n\n複数のソースでセッション ディレクトリが指定されている場合、settings.json では優先順位は `--session-dir`、`PI_CODING_AGENT_SESSION_DIR`、次に `sessionDir` です。\n\n### モデルサイクリング\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `enabledModels` | 弦[] | - | Ctrl+P サイクリングのモデル パターン (`--models` CLI フラグと同じ形式) |\n\n```json\n{\n  \"enabledModels\": [\"claude-*\", \"gpt-4o\", \"gemini-2*\"]\n}\n```\n\n### Markdown\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `markdown.codeBlockIndent` | 弦 | `\"  \"` | コードブロックのインデント |\n| `markdown.mermaid` | 弦 | `\"streaming\"` | マーメイドレンダリングモード: `\"off\"`、`\"final\"`、または `\"streaming\"` |\n\n### リソース\n\nこれらの設定は、拡張機能、スキル、プロンプト、テーマの読み込み元を定義します。\n\n`~/.pi/agent/settings.json` のパスは `~/.pi/agent` を基準にして解決されます。 `.pi/settings.json` のパスは `.pi` を基準にして解決されます。絶対パスと `~` がサポートされています。\n\n| 設定 | タイプ | デフォルト | 説明 |\n|---------|------|---------|-------------|\n| `packages` | 配列 | `[]` | リソースをロードするための npm/git パッケージ |\n| `extensions` | 弦[] | `[]` | ローカル拡張ファイルのパスまたはディレクトリ |\n| `skills` | 弦[] | `[]` | ローカルスキルファイルのパスまたはディレクトリ |\n| `prompts` | 弦[] | `[]` | ローカル プロンプト テンプレートのパスまたはディレクトリ |\n| `themes` | 弦[] | `[]` | ローカルのテーマファイルのパスまたはディレクトリ |\n| `enableSkillCommands` | ブール値 | `true` | スキルを`/skill:name`コマンドとして登録 |\n\n配列は、グロブ パターンと除外をサポートします。除外するには `!pattern` を使用します。正確なパスを強制的に含めるには `+path` を使用し、正確なパスを強制的に除外するには `-path` を使用します。\n\n#### パッケージ\n\n文字列形式はパッケージからすべてのリソースを読み込みます。\n\n```json\n{\n  \"packages\": [\"pi-skills\", \"@org/my-extension\"]\n}\n```\n\nオブジェクト フォームは、ロードするリソースをフィルターします。\n\n```json\n{\n  \"packages\": [\n    {\n      \"source\": \"pi-skills\",\n      \"skills\": [\"brave-search\", \"transcribe\"],\n      \"extensions\": []\n    }\n  ]\n}\n```\n\nパッケージ管理の詳細については、[packages.md](packages.md) を参照してください。\n\n## 例\n\n```json\n{\n  \"defaultProvider\": \"anthropic\",\n  \"defaultModel\": \"claude-sonnet-4-20250514\",\n  \"defaultThinkingLevel\": \"medium\",\n  \"theme\": \"dark\",\n  \"compaction\": {\n    \"enabled\": true,\n    \"reserveTokens\": 16384,\n    \"keepRecentTokens\": 20000\n  },\n  \"retry\": {\n    \"enabled\": true,\n    \"maxRetries\": 3\n  },\n  \"enabledModels\": [\"claude-*\", \"gpt-4o\"],\n  \"warnings\": {\n    \"anthropicExtraUsage\": true\n  },\n  \"packages\": [\"pi-skills\"]\n}\n```\n\n## プロジェクトの上書き\n\nプロジェクト設定 (`.pi/settings.json`) はグローバル設定をオーバーライドします。ネストされたオブジェクトはマージされます。\n\n```json\n// ~/.pi/agent/settings.json (global)\n{\n  \"theme\": \"dark\",\n  \"compaction\": { \"enabled\": true, \"reserveTokens\": 16384 }\n}\n\n// .pi/settings.json (project)\n{\n  \"compaction\": { \"reserveTokens\": 8192 }\n}\n\n// Result\n{\n  \"theme\": \"dark\",\n  \"compaction\": { \"enabled\": true, \"reserveTokens\": 8192 }\n}\n```","sourceFile":"settings.md"},"shell-aliases":{"title":"シェルのエイリアス","markdown":"Pi は、bash を非対話モード (`bash -c`) で実行します。デフォルトでは、エイリアスは展開されません。\n\nシェル エイリアスを有効にするには、`~/.pi/agent/settings.json` に追加します。\n\n```json\n{\n  \"shellCommandPrefix\": \"shopt -s expand_aliases\\neval \\\"$(grep '^alias ' ~/.zshrc)\\\"\"\n}\n```\n\nシェル構成に一致するようにパス (`~/.zshrc`、`~/.bashrc` など) を調整します。","sourceFile":"shell-aliases.md"},"skills":{"title":"Skills","markdown":"> piはスキルを作成できます。あなたのユースケースに合わせて構築するよう依頼してください。\n\n\nSkills は、エージェントがオンデマンドでロードする自己完結型の機能パッケージです。スキルは、特定のタスクに関する特殊なワークフロー、セットアップ手順、ヘルパー スクリプト、リファレンス ドキュメントを提供します。\n\nPi は [Agent Skills standard](https://agentskills.io/specification) を実装し、ほとんどの違反について警告しますが、寛大なままです。 Pi では、標準では許可されていない場合でも、スキル名が親ディレクトリと異なることが許可されます。このルールは、複数のエージェント ハーネス間で使用される共有スキル ディレクトリには最適ではありません。\n\n## 目次\n\n- [Locations](#locations)\n- [How Skills Work](#how-skills-work)\n- [Skill Commands](#skill-commands)\n- [Skill Structure](#skill-structure)\n- [Frontmatter](#frontmatter)\n- [Validation](#validation)\n- [Example](#example)\n- [Skill Repositories](#skill-repositories)\n\n## 所在地\n\n> **セキュリティ:** Skills は、モデルに任意のアクションを実行するように指示でき、モデルが呼び出す実行可能コードが含まれる場合があります。使用する前にスキルの内容を確認してください。\n\nPi は以下からスキルをロードします:\n\n- グローバル：\n  - `~/.pi/agent/skills/`\n  - `~/.agents/skills/`\n- プロジェクト (プロジェクトが信頼された後のみ):\n  - `.pi/skills/`\n  - `cwd` の `.agents/skills/` および祖先ディレクトリ (git リポジトリのルート、またはリポジトリにない場合はファイルシステムのルートまで)\n- パッケージ: `skills/` ディレクトリまたは `package.json` の `pi.skills` エントリ\n- 設定: `skills` ファイルまたはディレクトリを含む配列\n- CLI: `--skill <path>` (繰り返し可能、`--no-skills` でも追加的)\n\n検出ルール:\n- `~/.pi/agent/skills/`と`.pi/skills/`では、直接ルート`.md`ファイルが個人スキルとして検出されます\n- すべてのスキルの場所で、`SKILL.md` を含むディレクトリが再帰的に検出されます\n- `~/.agents/skills/` とプロジェクト `.agents/skills/` では、ルート `.md` ファイルは無視されます\n\n`--no-skills` で検出を無効にします (明示的な `--skill` パスはまだロードされます)。\n\n### 他のハーネスからSkillsを使用する\n\nClaude Code または OpenAI Codex のスキルを使用するには、それらのディレクトリを設定に追加します。\n\n```json\n{\n  \"skills\": [\n    \"~/.claude/skills\",\n    \"~/.codex/skills\"\n  ]\n}\n```\n\nプロジェクト レベルのクロード コード スキルについては、`.pi/settings.json` に追加します。\n\n```json\n{\n  \"skills\": [\"../.claude/skills\"]\n}\n```\n\n## Skills の仕組み\n\n1. 起動時に、pi はスキルの場所をスキャンし、名前と説明を抽出します。\n2. システム プロンプトには、[specification](https://agentskills.io/integrate-skills) に従って XML 形式で利用可能なスキルが含まれています。\n3. タスクが一致すると、エージェントは `read` を使用して完全な SKILL.md をロードします (モデルは常にこれを行うわけではありません。強制するにはプロンプトまたは `/skill:name` を使用します)。\n4. エージェントは、相対パスを使用してスクリプトとアセットを参照し、指示に従います。\n\nこれは進歩的な開示です。説明のみが常にコンテキスト内にあり、完全な命令はオンデマンドでロードされます。\n\n## スキルコマンド\n\nSkills を `/skill:name` コマンドとして登録します。\n\n```bash\n/skill:brave-search           # Load and execute the skill\n/skill:pdf-tools extract      # Load skill with arguments\n```\n\nコマンド後の引数はスキル内容に`User: <args>`として追加されます。\n\nインタラクティブモードでは`/settings`、または`settings.json`でスキルコマンドを切り替えます。\n\n```json\n{\n  \"enableSkillCommands\": true\n}\n```\n\n## スキル構成\n\nスキルは、`SKILL.md` ファイルのあるディレクトリです。それ以外はすべて自由形式です。\n\n```\nmy-skill/\n├── SKILL.md              # Required: frontmatter + instructions\n├── scripts/              # Helper scripts\n│   └── process.sh\n├── references/           # Detailed docs loaded on-demand\n│   └── api-reference.md\n└── assets/\n    └── template.json\n```\n\n### SKILL.md形式\n\n````markdown\n---\nname: my-skill\ndescription: What this skill does and when to use it. Be specific.\n---\n\n# My Skill\n\n## Setup\n\nRun once before first use:\n```bash\ncd /path/to/skill && npm インストール\n```\n\n## Usage\n\n```bash\n./scripts/process.sh <input>\n```\n````\n\nスキル ディレクトリからの相対パスを使用します。\n\n```markdown\nSee [the reference guide](references/REFERENCE.md) for details.\n```\n\n## フロントマター\n\n[Agent Skills specification](https://agentskills.io/specification#frontmatter-required) ごとに:\n\n| 分野 | 必須 | 説明 |\n|-------|----------|-------------|\n| `name` | はい | 最大64文字。小文字の a ～ z、0 ～ 9、ハイフン。標準とは異なり、Pi ではこれが親ディレクトリと一致する必要はありません。これは、その標準要件が共有スキル ディレクトリにとって最適ではないためです。 |\n| `description` | はい | 最大 1024 文字。スキルが何をするのか、いつ使用するのか。 |\n| `license` | いいえ | ライセンス名またはバンドルされたファイルへの参照。 |\n| `compatibility` | いいえ | 最大 500 文字。環境要件。 |\n| `metadata` | いいえ | 任意のキーと値のマッピング。 |\n| `allowed-tools` | いいえ | 事前承認ツール (実験的) のスペース区切りのリスト。 |\n| `disable-model-invocation` | いいえ | `true` の場合、スキルはシステム プロンプトから非表示になります。ユーザーは `/skill:name` を使用する必要があります。 |\n\n### 名前のルール\n\n- 1～64文字\n- 小文字、数字、ハイフンのみ\n- 先頭/末尾のハイフンなし\n- 連続したハイフンは使用できません\nPi では、名前が親ディレクトリと一致する必要はありません。 Agent Skills 標準ではこの要件が満たされていますが、その要件は複数のツールで使用される共有スキル ディレクトリにとっては最適ではありません。\n\n有効: `pdf-processing`、`data-analysis`、`code-review`\n無効: `PDF-Processing`、`-pdf`、`pdf--processing`\n\n### 説明 ベストプラクティス\n\n説明によって、エージェントがいつスキルをロードするかが決まります。具体的にしてください。\n\n良い：\n```yaml\ndescription: Extracts text and tables from PDF files, fills PDF forms, and merges multiple PDFs. Use when working with PDF documents.\n```\n\n貧しい：\n```yaml\ndescription: Helps with PDFs.\n```\n\n## 検証\n\nPi は、エージェント Skills の標準に照らしてスキルを検証します。ほとんどの問題では警告が生成されますが、スキルは引き続きロードされます。\n\n- 名前が64文字を超えているか、無効な文字が含まれています\n- 名前の先頭/末尾がハイフンであるか、連続したハイフンが含まれています\n- 説明が 1024 文字を超えています\n\n不明なフロントマターフィールドは無視されます。\n\n**例外:** 説明が欠落している Skills はロードされません。\n\n名前の衝突（異なる場所からの同じ名前）は警告し、最初に見つかったスキルを保持します。\n\n## 例\n\n```\nbrave-search/\n├── SKILL.md\n├── search.js\n└── content.js\n```\n\n**スキル.md:**\n````markdown\n---\nname: brave-search\ndescription: Web search and content extraction via Brave Search API. Use for searching documentation, facts, or any web content.\n---\n\n# Brave Search\n\n## Setup\n\n```bash\ncd /path/to/brave-search && npm インストール\n```\n\n## Search\n\n```bash\n./search.js \"query\" # 基本的な検索\n./search.js \"query\" --content # ページのコンテンツを含めます\n```\n\n## Extract Page Content\n\n```bash\n./content.js https://example.com\n```\n````\n\n## スキルリポジトリ\n\n- [Anthropic Skills](https://github.com/anthropics/skills) - ドキュメント処理 (docx、pdf、pptx、xlsx)、Web 開発\n- [Pi Skills](https://github.com/badlogic/pi-skills) - Web 検索、ブラウザ自動化、Google APIs、文字起こし","sourceFile":"skills.md"},"terminal-setup":{"title":"端末のセットアップ","markdown":"Pi は、信頼性の高い修飾キー検出のために [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) を使用します。最新の端末のほとんどはこのプロトコルをサポートしていますが、一部の端末では設定が必要です。\n\n## キティ、iTerm2\n\n箱から出してすぐに使えます。\n\n## アップルターミナル\n\nPi により、利用可能な場合は拡張キー レポートが有効になります。 Terminal.app が引き続き `Shift+Enter` に対してプレーンな Return を送信する場合、pi はローカル macOS 修飾子のフォールバックを使用して、その Return を `Shift+Enter` として扱います。\n\nこのフォールバックは、pi が Terminal.app と同じ Mac 上で実行されている場合にのみ機能します。リモート SSH 経由でローカル キーボードを検出できません。\n\n## ゴーストティ\n\nGhostty 構成に追加します (macOS では `~/Library/Application Support/com.mitchellh.ghostty/config`、Linux では `~/.config/ghostty/config`):\n\n```\nkeybind = alt+backspace=text:\\x1b\\x7f\n```\n\n古いクロード コードのバージョンでは、次の Ghostty マッピングが追加されている可能性があります。\n\n```\nkeybind = shift+enter=text:\\n\n```\n\nそのマッピングは生の改行バイトを送信します。 pi の内部では、これは `Ctrl+J` と区別できないため、tmux と pi には実際の `shift+enter` キー イベントが表示されなくなります。\n\nクロード コード 2.x 以降がそのマッピングを追加した唯一の理由である場合は、その Ghostty マッピングが依然として必要な tmux でクロード コードを使用する場合を除き、それを削除できます。\n\nPi は、デフォルトの改行エイリアスとして `Ctrl+J` をバインドするため、追加の pi 設定を行わずに、その再マップを介して `Shift+Enter` が tmux で動作し続けます。\n\n## ウェズターム\n\nWezTerm は通常、xtermmodifyOtherKeys を介して `Shift+Enter` に対してそのまま使用できます。 Kitty キーボード プロトコルを明示的に使用するには、`~/.wezterm.lua` を作成します。\n\n```lua\nlocal wezterm = require 'wezterm'\nlocal config = wezterm.config_builder()\nconfig.enable_kitty_keyboard = true\nreturn config\n```\n\nmacOS では、WezTerm はデフォルトで `Option+Enter` を全画面にバインドします。 pi フォローアップ キューイングに `Option+Enter` を使用するには、次のキー オーバーライドを追加します。\n\n```lua\nlocal wezterm = require 'wezterm'\nlocal config = wezterm.config_builder()\nconfig.keys = {\n  {\n    key = 'Enter',\n    mods = 'ALT',\n    action = wezterm.action.SendString('\\x1b[13;3u'),\n  },\n}\nreturn config\n```\n\nすでに `config.keys` テーブルがある場合は、そこにエントリを追加します。\n\nWSL では、WezTerm は IME 候補ウィンドウの位置決めに可視ハードウェア カーソルを必要とする場合があります。 CJK IME 候補がテキスト カーソルに追従しない場合は、pi を実行する前に `PI_HARDWARE_CURSOR=1` を設定するか、設定で `showHardwareCursor` を `true` に設定してください。\n\n## アラクリティ\n\nAlacritty は通常、何も設定しなくても `Shift+Enter` で機能します。 macOS では、`Option+Enter` がプレーンな `Enter` として到着する場合があります。 PI フォローアップ キューイングに `Option+Enter` を使用するには、`~/.config/alacritty/alacritty.toml` に追加します。\n\n```toml\n[[keyboard.bindings]]\nkey = \"Enter\"\nmods = \"Alt\"\nchars = \"\\u001b[13;3u\"\n```\n\n設定を変更した後、Alacritty を再起動します。\n\n## VS コード (統合ターミナル)\n\nVS Code 1.109.5 以降では、統合ターミナルで Kitty キーボード プロトコルがデフォルトで有効になっているため、`Shift+Enter` はそのまま使用できるはずです。\n\n1.109.5 より古い VS Code バージョンでは、`Shift+Enter` に対する明示的なターミナル キーバインドが必要です。\n\n`keybindings.json` 場所:\n- macOS: `~/Library/Application Support/Code/User/keybindings.json`\n- Linux: `~/.config/Code/User/keybindings.json`\n- Windows: `%APPDATA%\\\\Code\\\\User\\\\keybindings.json`\n\n`keybindings.json` に追加:\n\n```json\n{\n  \"key\": \"shift+enter\",\n  \"command\": \"workbench.action.terminal.sendSequence\",\n  \"args\": { \"text\": \"\\u001b[13;2u\" },\n  \"when\": \"terminalFocus\"\n}\n```\n\n## Windowsターミナル\n\npi が使用する変更された Enter キーを転送するには、`settings.json` (Ctrl+Shift+、または [設定] → [JSON ファイルを開く]) に追加します。\n\n```json\n{\n  \"actions\": [\n    {\n      \"command\": { \"action\": \"sendInput\", \"input\": \"\\u001b[13;2u\" },\n      \"keys\": \"shift+enter\"\n    },\n    {\n      \"command\": { \"action\": \"sendInput\", \"input\": \"\\u001b[13;3u\" },\n      \"keys\": \"alt+enter\"\n    }\n  ]\n}\n```\n\n- `Shift+Enter` は新しい行を挿入します。\n- Windows ターミナルは、デフォルトで `Alt+Enter` を全画面にバインドします。これにより、pi はフォローアップ キューイング用の `Alt+Enter` を受信できなくなります。\n- `Alt+Enter` を `sendInput` に再マッピングすると、代わりに実際のキーコードが pi に転送されます。\n\nすでに `actions` 配列がある場合は、そこにオブジェクトを追加します。以前の全画面表示の動作が続く場合は、Windows ターミナルを完全に閉じてから再度開きます。\n\n## xfce4-ターミナル、ターミネータ\n\nこれらの端末では、エスケープ シーケンスのサポートが制限されています。 `Ctrl+Enter` や `Shift+Enter` などの変更された Enter キーは、通常の `Enter` と区別できず、`submit: [\"ctrl+enter\"]` などのカスタム キーバインドが機能しなくなります。\n\n最適なエクスペリエンスを得るには、Kitty キーボード プロトコルをサポートする端末を使用してください。\n- [Kitty](https://sw.kovidgoyal.net/kitty/)\n- [Ghostty](https://ghostty.org/)\n- [WezTerm](https://wezfurlong.org/wezterm/)\n- [iTerm2](https://iterm2.com/)\n- [Alacritty](https://github.com/alacritty/alacritty) (Kitty プロトコルをサポートするコンパイルが必要)\n\n## IntelliJ IDEA (統合ターミナル)\n\n組み込みターミナルでは、エスケープ シーケンスのサポートが制限されています。 IntelliJ のターミナルでは、Shift+Enter と Enter を区別できません。\n\nハードウェア カーソルを表示したい場合は、pi を実行する前に `PI_HARDWARE_CURSOR=1` を設定します (互換性のためにデフォルトでは無効になっています)。\n\n最高のエクスペリエンスを得るには、専用のターミナル エミュレーターの使用を検討してください。","sourceFile":"terminal-setup.md"},"termux":{"title":"Termux (Android) セットアップ","markdown":"Pi は、Android 用のターミナル エミュレーターおよび Linux 環境である [Termux](https://termux.dev/) を介して Android 上で実行されます。\n\n## 前提条件\n\n1. GitHub または F-Droid から [Termux](https://github.com/termux/termux-app#installation) をインストールします (Google Play ではなく、そのバージョンは非推奨です)\n2. クリップボードやその他のデバイスを統合するには、GitHub または F-Droid から [Termux:API](https://github.com/termux/termux-api#installation) をインストールします\n\n## インストール\n\n```bash\n# Update packages\npkg update && pkg upgrade\n\n# Install dependencies\npkg install nodejs termux-api git\n\n# Install pi\nnpm install -g --ignore-scripts @earendil-works/pi-coding-agent\n\n# Create config directory\nmkdir -p ~/.pi/agent\n\n# Run pi\npi\n```\n\n## クリップボードのサポート\n\nTermux で実行する場合、クリップボード操作は `termux-clipboard-set` と `termux-clipboard-get` を使用します。これらを機能させるには、Termux:API アプリをインストールする必要があります。\n\nTermux では画像クリップボードはサポートされていません (`ctrl+v` 画像貼り付け機能は機能しません)。\n\n## Termux の AGENTS.md の例\n\nエージェントが Termux 環境を理解できるように、`~/.pi/agent/AGENTS.md` を作成します。\n\n````markdown\n# Agent Environment: Termux on Android\n\n## Location\n- **OS**: Android (Termux terminal emulator)\n- **Home**: `/data/data/com.termux/files/home`\n- **Prefix**: `/data/data/com.termux/files/usr`\n- **Shared storage**: `/storage/emulated/0` (Downloads, Documents, etc.)\n\n## Opening URLs\n```bash\ntermux-open-url \"https://example.com\"\n```\n\n## Opening Files\n```bash\ntermux-open file.pdf # デフォルトのアプリで開きます\ntermux-open --chooser image.jpg # アプリを選択\n```\n\n## Clipboard\n```bash\ntermux-clipboard-set \"text\" # コピー\ntermux-clipboard-get # 貼り付け\n```\n\n## Notifications\n```bash\ntermux-notification -t \"タイトル\" -c \"内容\"\n```\n\n## Device Info\n```bash\ntermux-battery-status # バッテリー情報\ntermux-wifi-connectioninfo # WiFi 情報\ntermux-telephony-deviceinfo # デバイス情報\n```\n\n## Sharing\n```bash\ntermux-share -a send file.txt # ファイルを共有\n```\n\n## Other Useful Commands\n```bash\ntermux-toast \"message\" # クイックトーストポップアップ\ntermux-vibrate # デバイスを振動させる\ntermux-tts-speak \"hello\" # テキスト読み上げ\ntermux-camera-photo out.jpg # 写真を撮る\n```\n\n## Notes\n- Termux:API app must be installed for `termux-*` commands\n- Use `pkg install termux-api` for the command-line tools\n- Storage permission needed for `/storage/emulated/0` access\n````\n\n## 制限事項\n\n- **画像クリップボードなし**: Termux クリップボード API はテキストのみをサポートします\n- **ネイティブ バイナリなし**: 一部のオプションのネイティブ依存関係 (クリップボード モジュールなど) は Android ARM64 では利用できず、インストール中にスキップされます。\n- **ストレージ アクセス**: `/storage/emulated/0` のファイル (ダウンロードなど) にアクセスするには、`termux-setup-storage` を 1 回実行して権限を付与します\n\n## トラブルシューティング\n\n### クリップボードが機能しない\n\n両方のアプリがインストールされていることを確認します。\n1. Termux (GitHub または F-Droid から)\n2. Termux:API (GitHub または F-Droid から)\n\n次に、CLI ツールをインストールします。\n```bash\npkg install termux-api\n```\n\n### 共有ストレージに対するアクセス許可が拒否されました\n\n1 回実行してストレージ権限を付与します。\n```bash\ntermux-setup-storage\n```\n\n### Node.js インストールの問題\n\nnpm が失敗した場合は、キャッシュをクリアしてみてください。\n```bash\nnpm cache clean --force\n```","sourceFile":"termux.md"},"themes":{"title":"テーマ","markdown":"> pi はテーマを作成できます。セットアップ用にビルドするように依頼してください。\n\n\nテーマは、TUI の色を定義する JSON ファイルです。\n\n## 目次\n\n- [Locations](#locations)\n- [Selecting a Theme](#selecting-a-theme)\n- [Creating a Custom Theme](#creating-a-custom-theme)\n- [Theme Format](#theme-format)\n- [Color Tokens](#color-tokens)\n- [Color Values](#color-values)\n- [Tips](#tips)\n\n## 所在地\n\nPi は次からテーマを読み込みます:\n\n- 内蔵: `dark`、`light`\n- グローバル: `~/.pi/agent/themes/*.json`\n- プロジェクト: `.pi/themes/*.json` (プロジェクトが信頼された後のみ)\n- パッケージ: `themes/` ディレクトリまたは `package.json` の `pi.themes` エントリ\n- 設定: `themes` ファイルまたはディレクトリを含む配列\n- CLI: `--theme <path>` (繰り返し可能)\n\n`--no-themes` で検出を無効にします。\n\n## テーマの選択\n\n`/settings` または `settings.json` でテーマを選択します。\n\n```json\n{\n  \"theme\": \"my-theme\"\n}\n```\n\n最初の実行時に、pi は端末の背景を検出し、デフォルトで `dark` または `light` に設定されます。\n\n## カスタムテーマの作成\n\n1. テーマファイルを作成します。\n\n```bash\nmkdir -p ~/.pi/agent/themes\nvim ~/.pi/agent/themes/my-theme.json\n```\n\n2. 必要なすべての色を使用してテーマを定義します ([Color Tokens](#color-tokens) を参照)。\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json\",\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"primary\": \"#00aaff\",\n    \"secondary\": 242\n  },\n  \"colors\": {\n    \"accent\": \"primary\",\n    \"border\": \"primary\",\n    \"borderAccent\": \"#00ffff\",\n    \"borderMuted\": \"secondary\",\n    \"success\": \"#00ff00\",\n    \"error\": \"#ff0000\",\n    \"warning\": \"#ffff00\",\n    \"muted\": \"secondary\",\n    \"dim\": 240,\n    \"text\": \"\",\n    \"thinkingText\": \"secondary\",\n    \"selectedBg\": \"#2d2d30\",\n    \"scrollbarThumb\": \"#555566\",\n    \"userMessageBg\": \"#2d2d30\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#2d2d30\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"primary\",\n    \"toolPendingBg\": \"#1e1e2e\",\n    \"toolSuccessBg\": \"#1e2e1e\",\n    \"toolErrorBg\": \"#2e1e1e\",\n    \"toolTitle\": \"primary\",\n    \"toolOutput\": \"\",\n    \"mdHeading\": \"#ffaa00\",\n    \"mdLink\": \"primary\",\n    \"mdLinkUrl\": \"secondary\",\n    \"mdCode\": \"#00ffff\",\n    \"mdCodeBlock\": \"\",\n    \"mdCodeBlockBorder\": \"secondary\",\n    \"mdQuote\": \"secondary\",\n    \"mdQuoteBorder\": \"secondary\",\n    \"mdHr\": \"secondary\",\n    \"mdListBullet\": \"#00ffff\",\n    \"toolDiffAdded\": \"#00ff00\",\n    \"toolDiffRemoved\": \"#ff0000\",\n    \"toolDiffContext\": \"secondary\",\n    \"syntaxComment\": \"secondary\",\n    \"syntaxKeyword\": \"primary\",\n    \"syntaxFunction\": \"#00aaff\",\n    \"syntaxVariable\": \"#ffaa00\",\n    \"syntaxString\": \"#00ff00\",\n    \"syntaxNumber\": \"#ff00ff\",\n    \"syntaxType\": \"#00aaff\",\n    \"syntaxOperator\": \"primary\",\n    \"syntaxPunctuation\": \"secondary\",\n    \"thinkingOff\": \"secondary\",\n    \"thinkingMinimal\": \"primary\",\n    \"thinkingLow\": \"#00aaff\",\n    \"thinkingMedium\": \"#00ffff\",\n    \"thinkingHigh\": \"#ff00ff\",\n    \"thinkingXhigh\": \"#ff0000\",\n    \"thinkingMax\": \"#ff0088\",\n    \"bashMode\": \"#ffaa00\"\n  }\n}\n```\n\n3. `/settings` からテーマを選択します。\n\n**ホット リロード:** 現在アクティブなカスタム テーマ ファイルを編集すると、pi はそれを自動的にリロードし、即座に視覚的なフィードバックを提供します。\n\n## テーマ形式\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json\",\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"blue\": \"#0066cc\",\n    \"gray\": 242\n  },\n  \"colors\": {\n    \"accent\": \"blue\",\n    \"muted\": \"gray\",\n    \"text\": \"\",\n    ...\n  }\n}\n```\n\n- `name` は必須であり、一意である必要があり、`/` を含めることはできません。\n- `vars` はオプションです。再利用可能な色をここで定義し、`colors` で参照します。\n- `colors` は、51 個の必須トークンをすべて定義する必要があります。 `thinkingMax` はオプションであり、`thinkingXhigh` に戻ります。 `scrollbarThumb` はオプションであり、`selectedBg` に戻ります。\n\n`$schema` フィールドは、エディターの自動補完と検証を有効にします。\n\n## カラートークン\n\nすべてのテーマでは、必要な 51 個のカラー トークンをすべて定義する必要があります。 `thinkingMax` と `scrollbarThumb` は、既存のテーマとの互換性を保つためのオプションです。省略した場合は、それぞれ `thinkingXhigh` と `selectedBg` が使用されます。\n\n### コアUI（11色）\n\n| トークン | 目的 |\n|-------|---------|\n| `accent` | プライマリアクセント (ロゴ、選択されたアイテム、カーソル) |\n| `border` | 通常の境界線 |\n| `borderAccent` | ハイライトされた境界線 |\n| `borderMuted` | 微妙な境界線（編集者） |\n| `success` | 成功状態 |\n| `error` | エラー状態 |\n| `warning` | 警告状態 |\n| `muted` | 二次テキスト |\n| `dim` | 三次テキスト |\n| `text` | デフォルトのテキスト (通常は `\"\"`) |\n| `thinkingText` | 思考ブロックのテキスト |\n\n### 背景とコンテンツ (11 個は必須、1 個はオプション)\n\n| トークン | 目的 |\n|-------|---------|\n| `selectedBg` | 選択した線の背景 |\n| `scrollbarThumb` | 全画面スクロールバーのサムの背景。オプション、`selectedBg` に戻ります |\n| `userMessageBg` | ユーザーメッセージの背景 |\n| `userMessageText` | ユーザーメッセージテキスト |\n| `customMessageBg` | 拡張メッセージの背景 |\n| `customMessageText` | 拡張メッセージのテキスト |\n| `customMessageLabel` | 拡張メッセージラベル |\n| `toolPendingBg` | ツールボックス (保留中) |\n| `toolSuccessBg` | 工具箱（成功） |\n| `toolErrorBg` | ツールボックス（エラー） |\n| `toolTitle` | ツールのタイトル |\n| `toolOutput` | ツール出力テキスト |\n\n### Markdown (10色)\n\n| トークン | 目的 |\n|-------|---------|\n| `mdHeading` | 見出し |\n| `mdLink` | リンクテキスト |\n| `mdLinkUrl` | リンクURL |\n| `mdCode` | インラインコード |\n| `mdCodeBlock` | コードブロックの内容 |\n| `mdCodeBlockBorder` | コードブロックフェンス |\n| `mdQuote` | ブロック引用テキスト |\n| `mdQuoteBorder` | ブロッククォートの境界線 |\n| `mdHr` | 横罫 |\n| `mdListBullet` | 箇条書きのリスト |\n\n### ツール差分（3色）\n\n| トークン | 目的 |\n|-------|---------|\n| `toolDiffAdded` | 追加された行 |\n| `toolDiffRemoved` | 削除された行 |\n| `toolDiffContext` | コンテキスト行 |\n\n### 構文強調表示 (9 色)\n\n| トークン | 目的 |\n|-------|---------|\n| `syntaxComment` | コメント |\n| `syntaxKeyword` | キーワード |\n| `syntaxFunction` | 関数名 |\n| `syntaxVariable` | 変数 |\n| `syntaxString` | 文字列 |\n| `syntaxNumber` | 数字 |\n| `syntaxType` | 種類 |\n| `syntaxOperator` | オペレーター |\n| `syntaxPunctuation` | 句読点 |\n\n### 思考レベルの境界線 (6 つは必須、1 つはオプション)\n\n思考レベルを示すエディターの境界線の色 (微妙なものから目立つものまでの視覚的な階層):\n\n| トークン | 目的 |\n|-------|---------|\n| `thinkingOff` | 思考停止 |\n| `thinkingMinimal` | ミニマル思考 |\n| `thinkingLow` | 低思考 |\n| `thinkingMedium` | 中程度の思考 |\n| `thinkingHigh` | 高い思考力 |\n| `thinkingXhigh` | 非常に高度な思考 |\n| `thinkingMax` | 最大限の思考。オプション、`thinkingXhigh` に戻ります |\n\n### バッシュモード（1色）\n\n| トークン | 目的 |\n|-------|---------|\n| `bashMode` | bash モードのエディターの境界線 (`!` プレフィックス) |\n\n### HTML エクスポート (オプション)\n\n`export` セクションは、`/export` HTML 出力の色を制御します。省略した場合、色は`userMessageBg`から派生します。\n\n```json\n{\n  \"export\": {\n    \"pageBg\": \"#18181e\",\n    \"cardBg\": \"#1e1e24\",\n    \"infoBg\": \"#3c3728\"\n  }\n}\n```\n\n## 色の値\n\n次の 4 つの形式がサポートされています。\n\n| 形式 | 例 | 説明 |\n|--------|---------|-------------|\n| 16進数 | `\"#ff0000\"` | 6桁の16進RGB |\n| 256色 | `39` | xterm 256 色パレット インデックス (0 ～ 255) |\n| 変数 | `\"primary\"` | `vars` エントリへの参照 |\n| デフォルト | `\"\"` | 端末のデフォルトの色 |\n\n### 256 色のパレット\n\n- `0-15`: 基本的な ANSI カラー (端末に依存)\n- `16-231`: 6×6×6 RGB 立方体 (`16 + 36×R + 6×G + B` ここで R、G、B は 0 ～ 5)\n- `232-255`: グレースケールランプ\n\n### 端末の互換性\n\nPi は 24 ビット RGB カラーを使用します。最新の端末のほとんどはこれをサポートしています (iTerm2、Kitty、WezTerm、Windows Terminal、VS Code)。 256 色のみをサポートする古い端末の場合、pi は最も近い近似値に戻ります。\n\nトゥルーカラーのサポートを確認します。\n\n```bash\necho $COLORTERM  # Should output \"truecolor\" or \"24bit\"\n```\n\n## ヒント\n\n**暗い端末:** コントラストの高い、明るく飽和した色を使用します。\n\n**明るい端末:** コントラストが低く、暗く落ち着いた色を使用します。\n\n**色の調和:** 基本パレット (Nord、Gruvbox、Tokyo Night) から始めて、`vars` で定義し、一貫して参照します。\n\n**テスト:** さまざまなメッセージ タイプ、ツールの状態、マークダウン コンテンツ、および折り返された長いテキストを使用してテーマを確認します。\n\n**VS コード:** 正確な色を得るには、`terminal.integrated.minimumContrastRatio` から `1` に設定します。\n\n## 例\n\n組み込みテーマを参照してください。\n- [dark.json](../src/modes/interactive/theme/dark.json)\n- [light.json](../src/modes/interactive/theme/light.json)","sourceFile":"themes.md"},"tmux":{"title":"tmux セットアップ","markdown":"Pi は tmux 内で動作しますが、tmux はデフォルトで特定のキーから修飾子情報を削除します。設定がなければ、`Shift+Enter` と `Ctrl+Enter` は通常、プレーンの `Enter` と区別できません。\n\n## 推奨される構成\n\n`~/.tmux.conf` に追加:\n\n```tmux\nset -g extended-keys on\nset -g extended-keys-format csi-u\n```\n\n次に、tmux を完全に再起動します。\n\n```bash\ntmux kill-server\ntmux\n```\n\nPi は、キティ キーボード プロトコルが利用できない場合に、拡張キーのレポートを自動的に要求します。 `extended-keys-format csi-u` では、tmux は変更されたキーを CSI-u 形式で転送します。これは最も信頼性の高い構成です。 `extended-keys-format` オプションには tmux 3.5 以降が必要です。\n\n## `csi-u`がおすすめな理由\n\nのみ:\n\n```tmux\nset -g extended-keys on\n```\n\ntmux のデフォルトは `extended-keys-format xterm` です。アプリケーションが拡張キーのレポートを要求すると、変更されたキーは次のような xterm `modifyOtherKeys` 形式で転送されます。\n\n- `Ctrl+C` → `\\x1b[27;5;99~`\n- `Ctrl+D` → `\\x1b[27;5;100~`\n- `Ctrl+Enter` → `\\x1b[27;5;13~`\n\n`extended-keys-format csi-u` を使用すると、同じキーが次のように転送されます。\n\n- `Ctrl+C` → `\\x1b[99;5u`\n- `Ctrl+D` → `\\x1b[100;5u`\n- `Ctrl+Enter` → `\\x1b[13;5u`\n\nPi は両方の形式をサポートしますが、`csi-u` が推奨される tmux セットアップです。\n\n## これで修正される内容\n\ntmux 拡張キーがないと、変更された Enter キーは従来のシーケンスに折りたたまれます。\n\n| 鍵 | 拡張キーなし | `csi-u`付き |\n|-----|-----------------|--------------|\n| 入力 | `\\r` | `\\r` |\n| Shift+Enter | `\\r` | `\\x1b[13;2u` |\n| Ctrl+Enter | `\\r` | `\\x1b[13;5u` |\n| Alt/Option+Enter | `\\x1b\\r` | `\\x1b[13;3u` |\n\nこれは、デフォルトのキーバインド (送信するには `Enter`、改行には `Shift+Enter`) と、変更された Enter を使用するカスタム キーバインドに影響します。\n\n## 要件\n\n- `extended-keys-format csi-u` の場合は tmux 3.5 以降 (確認するには `tmux -V` を実行します)\n- 拡張キーをサポートするターミナル エミュレーター (Ghostty、Kitty、iTerm2、WezTerm、Windows Terminal)\n\ntmux 3.2 ～ 3.4 では、`extended-keys-format csi-u` を省略します。 Pi は、tmux のデフォルトの xterm `modifyOtherKeys` 形式を引き続きサポートします。","sourceFile":"tmux.md"},"tui":{"title":"TUI コンポーネント","markdown":"> pi は TUI コンポーネントを作成できます。あなたのユースケースに合わせて構築するよう依頼してください。\n\n\nExtensions およびカスタム ツールは、対話型ユーザー インターフェイス用のカスタム TUI コンポーネントをレンダリングできます。このページでは、コンポーネント システムと利用可能なビルディング ブロックについて説明します。\n\n**出典:** [`@earendil-works/pi-tui`](https://github.com/earendil-works/pi-mono/tree/main/packages/tui)\n\n## コンポーネントインターフェース\n\nすべてのコンポーネントは以下を実装します。\n\n```typescript\ninterface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n| 方法 | 説明 |\n|--------|-------------|\n| `render(width)` | 文字列の配列を返します (1 行に 1 つ)。各行は **`width`** を超えてはなりません。 |\n| `handleInput?(data)` | コンポーネントにフォーカスがあるときにキーボード入力を受け取ります。 |\n| `wantsKeyRelease?` | true の場合、コンポーネントはキーリリースイベント (Kitty プロトコル) を受信します。デフォルト: false。 |\n| `invalidate()` | キャッシュされたレンダリング状態をクリアします。テーマの変更時に呼び出されます。 |\n\nTUI は、レンダリングされた各行の最後に完全な SGR リセットと OSC 8 リセットを追加します。スタイルは複数の行を超えて継承されません。スタイルを設定して複数行のテキストを出力する場合は、行ごとにスタイルを再適用するか、`wrapTextWithAnsi()` を使用して折り返された各行のスタイルが保持されるようにします。\n\n## フォーカス可能なインターフェイス (IME サポート)\n\nテキスト カーソルを表示し、IME (インプット メソッド エディター) のサポートを必要とするコンポーネントは、`Focusable` インターフェイスを実装する必要があります。\n\n```typescript\nimport { CURSOR_MARKER, type Component, type Focusable } from \"@earendil-works/pi-tui\";\n\nclass MyInput implements Component, Focusable {\n  focused: boolean = false;  // Set by TUI when focus changes\n  \n  render(width: number): string[] {\n    const marker = this.focused ? CURSOR_MARKER : \"\";\n    // Emit marker right before the fake cursor\n    return [`> ${beforeCursor}${marker}\\x1b[7m${atCursor}\\x1b[27m${afterCursor}`];\n  }\n}\n```\n\n`Focusable` コンポーネントにフォーカスがある場合、TUI:\n1. コンポーネントに `focused = true` を設定します\n2. `CURSOR_MARKER` (ゼロ幅 APC エスケープ シーケンス) のレンダリングされた出力をスキャンします。\n3. ハードウェア端末カーソルをその位置に配置します\n4. `showHardwareCursor` が有効な場合にのみハードウェア カーソルを表示します\n\nカーソルはデフォルトでは非表示のままです。これにより、隠しカーソルで IME 候補ウィンドウを追跡する端末のハードウェア カーソルを配置しながら、偽のカーソルのレンダリングが維持されます。一部の端末では、IME の位置決めに目に見えるハードウェア カーソルが必要です。 `showHardwareCursor`、`setShowHardwareCursor(true)`、または `PI_HARDWARE_CURSOR=1` で有効にします。 `Editor` および `Input` 組み込みコンポーネントは、このインターフェイスをすでに実装しています。\n\n### 入力が埋め込まれたコンテナコンポーネント\n\nコンテナコンポーネント (ダイアログ、セレクタなど) に `Input` または `Editor` の子が含まれる場合、コンテナは `Focusable` を実装し、フォーカス状態を子に伝播する必要があります。そうしないと、ハードウェア カーソルが IME 入力に対して正しく配置されません。\n\n```typescript\nimport { Container, type Focusable, Input } from \"@earendil-works/pi-tui\";\n\nclass SearchDialog extends Container implements Focusable {\n  private searchInput: Input;\n\n  // Focusable implementation - propagate to child input for IME cursor positioning\n  private _focused = false;\n  get focused(): boolean {\n    return this._focused;\n  }\n  set focused(value: boolean) {\n    this._focused = value;\n    this.searchInput.focused = value;\n  }\n\n  constructor() {\n    super();\n    this.searchInput = new Input();\n    this.addChild(this.searchInput);\n  }\n}\n```\n\nこの伝播がないと、IME (中国語、日本語、韓国語など) で入力すると、画面上の間違った位置に候補ウィンドウが表示されます。\n\n## コンポーネントの使用\n\n**拡張機能内** `ctx.ui.custom()` 経由:\n\n```typescript\npi.on(\"session_start\", async (_event, ctx) => {\n  const result = await ctx.ui.custom<string | null>((tui, theme, keybindings, done) =>\n    new MyComponent({\n      theme,\n      keybindings,\n      onChange: () => tui.requestRender(),\n      onSelect: (value) => done(value),\n      onCancel: () => done(null),\n    })\n  );\n});\n```\n\n**カスタム ツール内** `ctx.ui.custom()` 経由:\n\n```typescript\nasync execute(toolCallId, params, signal, onUpdate, ctx) {\n  const result = await ctx.ui.custom<string | null>((tui, theme, keybindings, done) =>\n    new MyComponent({\n      theme,\n      keybindings,\n      onChange: () => tui.requestRender(),\n      onSelect: (value) => done(value),\n      onCancel: () => done(null),\n    })\n  );\n  // Use result...\n}\n```\n\n## オーバーレイ\n\nオーバーレイは、画面をクリアせずに、既存のコンテンツの上にコンポーネントをレンダリングします。 `{ overlay: true }` を `ctx.ui.custom()` に渡します:\n\n```typescript\nconst result = await ctx.ui.custom<string | null>(\n  (tui, theme, keybindings, done) => new MyDialog({ onClose: done }),\n  { overlay: true }\n);\n```\n\n位置決めとサイズ変更には、`overlayOptions` を使用します。\n\n```typescript\nconst result = await ctx.ui.custom<string | null>(\n  (tui, theme, keybindings, done) => new SidePanel({ onClose: done }),\n  {\n    overlay: true,\n    overlayOptions: {\n      // Size: number or percentage string\n      width: \"50%\",          // 50% of terminal width\n      minWidth: 40,          // minimum 40 columns\n      maxHeight: \"80%\",      // max 80% of terminal height\n\n      // Position: anchor-based (default: \"center\")\n      anchor: \"right-center\", // 9 positions: center, top-left, top-center, etc.\n      offsetX: -2,            // offset from anchor\n      offsetY: 0,\n\n      // Or percentage/absolute positioning\n      row: \"25%\",            // 25% from top\n      col: 10,               // column 10\n\n      // Margins\n      margin: 2,             // all sides, or { top, right, bottom, left }\n\n      // Responsive: hide on narrow terminals\n      visible: (termWidth, termHeight) => termWidth >= 80,\n    },\n    // Get handle for programmatic focus and visibility control\n    onHandle: (handle) => {\n      // handle.focus() - focus this overlay and bring it to the visual front\n      // handle.unfocus() - release input to normal fallback\n      // handle.unfocus({ target }) - release input to a specific component or null\n      // handle.setHidden(true/false) - toggle visibility\n      // handle.hide() - permanently remove\n    },\n  }\n);\n```\n\n### オーバーレイフォーカス\n\nフォーカスされた表示オーバーレイは、一時的な非オーバーレイ UI 全体で入力の所有権を保持します。オーバーレイが `{ overlay: true }` を持たずに別の `ctx.ui.custom()` コンポーネントを開いた場合、その置換 UI はアクティブな間入力を受け取ります。閉じると、フォーカスされたオーバーレイは入力を再利用できます。\n\n表示されているオーバーレイが入力の所有を停止し、TUI が別の表示されているキャプチャ オーバーレイまたは前のフォーカス ターゲットにフォールバックする必要がある場合は、`handle.unfocus()` を使用します。オーバーレイが表示されている間に特定のコンポーネントが入力を受信する必要がある場合は、`handle.unfocus({ target })` を使用します。 `{ target: null }` を渡すと、フォーカスが再度設定されるまで、フォーカスされたコンポーネントが意図的に残されません。\n\n### オーバーレイのライフサイクル\n\nオーバーレイ コンポーネントは閉じると破棄されます。参照を再利用しないでください - 新しいインスタンスを作成します。\n\n```typescript\n// Wrong - stale reference\nlet menu: MenuComponent;\nawait ctx.ui.custom((_, __, ___, done) => {\n  menu = new MenuComponent(done);\n  return menu;\n}, { overlay: true });\nsetActiveComponent(menu);  // Disposed\n\n// Correct - re-call to re-show\nconst showMenu = () => ctx.ui.custom((_, __, ___, done) => \n  new MenuComponent(done), { overlay: true });\n\nawait showMenu();  // First show\nawait showMenu();  // \"Back\" = just call again\n```\n\nアンカー、マージン、スタッキング、応答性の高い可視性、およびアニメーションをカバーする包括的な例については、[overlay-qa-tests.ts](../examples/extensions/overlay-qa-tests.ts) を参照してください。\n\n## 内蔵コンポーネント\n\n`@earendil-works/pi-tui` からインポート:\n\n```typescript\nimport { Text, Box, Container, Spacer, Markdown } from \"@earendil-works/pi-tui\";\n```\n\n### 文章\n\nワードラップされた複数行のテキスト。\n\n```typescript\nconst text = new Text(\n  \"Hello World\",    // content\n  1,                // paddingX (default: 1)\n  1,                // paddingY (default: 1)\n  (s) => bgGray(s)  // optional background function\n);\ntext.setText(\"Updated\");\n```\n\n### 箱\n\nパディングと背景色のコンテナー。\n\n```typescript\nconst box = new Box(\n  1,                // paddingX\n  1,                // paddingY\n  (s) => bgGray(s)  // background function\n);\nbox.addChild(new Text(\"Content\", 0, 0));\nbox.setBgFn((s) => bgBlue(s));\n```\n\n### 容器\n\n子コンポーネントを垂直方向にグループ化します。\n\n```typescript\nconst container = new Container();\ncontainer.addChild(component1);\ncontainer.addChild(component2);\ncontainer.removeChild(component1);\n```\n\n### スペーサー\n\n空の垂直スペース。\n\n```typescript\nconst spacer = new Spacer(2);  // 2 empty lines\n```\n\n### Markdown\n\n構文を強調表示してマークダウンをレンダリングします。\n\n```typescript\nconst md = new Markdown(\n  \"# Title\\n\\nSome **bold** text\",\n  1,        // paddingX\n  1,        // paddingY\n  theme     // MarkdownTheme (see below)\n);\nmd.setText(\"Updated markdown\");\n```\n\n### 画像\n\nサポートされている端末 (Kitty、iTerm2、Ghostty、WezTerm、Warp) でイメージをレンダリングします。\n\n```typescript\nconst image = new Image(\n  base64Data,   // base64-encoded image\n  \"image/png\",  // MIME type\n  theme,        // ImageTheme\n  { maxWidthCells: 80, maxHeightCells: 24 }\n);\n```\n\n## キーボード入力\n\nキー検出には `matchesKey()` を使用します。\n\n```typescript\nimport { matchesKey, Key } from \"@earendil-works/pi-tui\";\n\nhandleInput(data: string) {\n  if (matchesKey(data, Key.up)) {\n    this.selectedIndex--;\n  } else if (matchesKey(data, Key.enter)) {\n    this.onSelect?.(this.selectedIndex);\n  } else if (matchesKey(data, Key.escape)) {\n    this.onCancel?.();\n  } else if (matchesKey(data, Key.ctrl(\"c\"))) {\n    // Ctrl+C\n  }\n}\n```\n\n**キー識別子** (オートコンプリートまたは文字列リテラルには `Key.*` を使用します):\n- 基本キー: `Key.enter`、`Key.escape`、`Key.tab`、`Key.space`、`Key.backspace`、`Key.delete`、`Key.home`、`Key.end`\n- 矢印キー: `Key.up`、`Key.down`、`Key.left`、`Key.right`\n- 修飾子あり: `Key.ctrl(\"c\")`、`Key.shift(\"tab\")`、`Key.alt(\"left\")`、`Key.ctrlShift(\"p\")`\n- 文字列形式も機能します: `\"enter\"`、`\"ctrl+c\"`、`\"shift+tab\"`、`\"ctrl+shift+p\"`\n\n## 線幅\n\n**重要:** `render()` からの各行は、`width` パラメータを超えてはなりません。\n\n```typescript\nimport { visibleWidth, truncateToWidth } from \"@earendil-works/pi-tui\";\n\nrender(width: number): string[] {\n  // Truncate long lines\n  return [truncateToWidth(this.text, width)];\n}\n```\n\nユーティリティ:\n- `visibleWidth(str)` - 表示幅を取得します (ANSI コードを無視します)\n- `truncateToWidth(str, width, ellipsis?)` - オプションの省略記号を使用して切り詰めます\n- `wrapTextWithAnsi(str, width)` - ANSI コードを保持するワードラップ\n\n## カスタムコンポーネントの作成\n\n例: インタラクティブセレクター\n\n```typescript\nimport {\n  matchesKey, Key,\n  truncateToWidth, visibleWidth\n} from \"@earendil-works/pi-tui\";\n\nclass MySelector {\n  private items: string[];\n  private selected = 0;\n  private cachedWidth?: number;\n  private cachedLines?: string[];\n  \n  public onSelect?: (item: string) => void;\n  public onCancel?: () => void;\n\n  constructor(items: string[]) {\n    this.items = items;\n  }\n\n  handleInput(data: string): void {\n    if (matchesKey(data, Key.up) && this.selected > 0) {\n      this.selected--;\n      this.invalidate();\n    } else if (matchesKey(data, Key.down) && this.selected < this.items.length - 1) {\n      this.selected++;\n      this.invalidate();\n    } else if (matchesKey(data, Key.enter)) {\n      this.onSelect?.(this.items[this.selected]);\n    } else if (matchesKey(data, Key.escape)) {\n      this.onCancel?.();\n    }\n  }\n\n  render(width: number): string[] {\n    if (this.cachedLines && this.cachedWidth === width) {\n      return this.cachedLines;\n    }\n\n    this.cachedLines = this.items.map((item, i) => {\n      const prefix = i === this.selected ? \"> \" : \"  \";\n      return truncateToWidth(prefix + item, width);\n    });\n    this.cachedWidth = width;\n    return this.cachedLines;\n  }\n\n  invalidate(): void {\n    this.cachedWidth = undefined;\n    this.cachedLines = undefined;\n  }\n}\n```\n\n拡張機能での使用法:\n\n```typescript\npi.registerCommand(\"pick\", {\n  description: \"Pick an item\",\n  handler: async (_args, ctx) => {\n    const items = [\"Option A\", \"Option B\", \"Option C\"];\n    const selected = await ctx.ui.custom<string | null>((tui, _theme, _keybindings, done) => {\n      const selector = new MySelector(items);\n      selector.onSelect = done;\n      selector.onCancel = () => done(null);\n\n      return {\n        render: (width) => selector.render(width),\n        handleInput: (data) => {\n          selector.handleInput(data);\n          tui.requestRender();\n        },\n        invalidate: () => selector.invalidate(),\n      };\n    });\n\n    if (selected !== null) {\n      ctx.ui.notify(`Selected: ${selected}`, \"info\");\n    }\n  }\n});\n```\n\n## テーマ設定\n\nコンポーネントはスタイル設定用のテーマ オブジェクトを受け入れます。\n\n**`renderCall`/`renderResult`** では、`theme` パラメータを使用します。\n\n```typescript\nrenderResult(result, options, theme, context) {\n  // Use theme.fg() for foreground colors\n  return new Text(theme.fg(\"success\", \"Done!\"), 0, 0);\n  \n  // Use theme.bg() for background colors\n  const styled = theme.bg(\"toolPendingBg\", theme.fg(\"accent\", \"text\"));\n}\n```\n\n**前景色** (`theme.fg(color, text)`):\n\n| カテゴリ | 色 |\n|----------|--------|\n| 一般的な | `text`, `accent`, `muted`, `dim` |\n| 状態 | `success`, `error`, `warning` |\n| 国境 | `border`, `borderAccent`, `borderMuted` |\n| メッセージ | `userMessageText`, `customMessageText`, `customMessageLabel` |\n| ツール | `toolTitle`, `toolOutput` |\n| 差分 | `toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext` |\n| Markdown | `mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet` |\n| 構文 | `syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation` |\n| 考え | `thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `thinkingMax` |\n| モード | `bashMode` |\n\n**背景色** (`theme.bg(color, text)`):\n\n`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`\n\n**Markdown** には、`getMarkdownTheme()` を使用します:\n\n```typescript\nimport { getMarkdownTheme } from \"@earendil-works/pi-coding-agent\";\nimport { Markdown } from \"@earendil-works/pi-tui\";\n\nrenderResult(result, options, theme, context) {\n  const mdTheme = getMarkdownTheme();\n  return new Markdown(result.details.markdown, 0, 0, mdTheme);\n}\n```\n\n**カスタム コンポーネントの場合**、独自のテーマ インターフェイスを定義します。\n\n```typescript\ninterface MyTheme {\n  selected: (s: string) => string;\n  normal: (s: string) => string;\n}\n```\n\n## デバッグログ\n\nstdout に書き込まれた生の ANSI ストリームをキャプチャするには、`PI_TUI_WRITE_LOG` を設定します。\n\n```bash\nPI_TUI_WRITE_LOG=/tmp/tui-ansi.log npx tsx packages/tui/test/chat-simple.ts\n```\n\n## パフォーマンス\n\n可能であれば、レンダリングされた出力をキャッシュします。\n\n```typescript\nclass CachedComponent {\n  private cachedWidth?: number;\n  private cachedLines?: string[];\n\n  render(width: number): string[] {\n    if (this.cachedLines && this.cachedWidth === width) {\n      return this.cachedLines;\n    }\n    // ... compute lines ...\n    this.cachedWidth = width;\n    this.cachedLines = lines;\n    return lines;\n  }\n\n  invalidate(): void {\n    this.cachedWidth = undefined;\n    this.cachedLines = undefined;\n  }\n}\n```\n\n状態が変化したときに `invalidate()` を呼び出し、挿入された `tui.requestRender()` を使用して再レンダリングをトリガーします。\n\n## 無効化とテーマの変更\n\nテーマが変更されると、TUI はすべてのコンポーネントで `invalidate()` を呼び出してキャッシュをクリアします。テーマの変更を確実に有効にするために、コンポーネントは `invalidate()` を適切に実装する必要があります。\n\n### 問題\n\nコンポーネントがテーマの色を文字列にプリベイクし (`theme.fg()`、`theme.bg()` などを介して) キャッシュする場合、キャッシュされた文字列には古いテーマの ANSI エスケープ コードが含まれます。コンポーネントがテーマ別コンテンツを個別に保存している場合、レンダー キャッシュをクリアするだけでは十分ではありません。\n\n**間違ったアプローチ** (テーマの色は更新されません):\n\n```typescript\nclass BadComponent extends Container {\n  private content: Text;\n\n  constructor(message: string, theme: Theme) {\n    super();\n    // Pre-baked theme colors stored in Text component\n    this.content = new Text(theme.fg(\"accent\", message), 1, 0);\n    this.addChild(this.content);\n  }\n  // No invalidate override - parent's invalidate only clears\n  // child render caches, not the pre-baked content\n}\n```\n\n### 解決策\n\nテーマカラーを使用してコンテンツを構築するコンポーネントは、 `invalidate()` が呼び出されたときにそのコンテンツを再構築する必要があります。\n\n```typescript\nclass GoodComponent extends Container {\n  private message: string;\n  private content: Text;\n\n  constructor(message: string) {\n    super();\n    this.message = message;\n    this.content = new Text(\"\", 1, 0);\n    this.addChild(this.content);\n    this.updateDisplay();\n  }\n\n  private updateDisplay(): void {\n    // Rebuild content with current theme\n    this.content.setText(theme.fg(\"accent\", this.message));\n  }\n\n  override invalidate(): void {\n    super.invalidate();  // Clear child caches\n    this.updateDisplay(); // Rebuild with new theme\n  }\n}\n```\n\n### パターン: 無効化時に再構築\n\n複雑なコンテンツを含むコンポーネントの場合:\n\n```typescript\nclass ComplexComponent extends Container {\n  private data: SomeData;\n\n  constructor(data: SomeData) {\n    super();\n    this.data = data;\n    this.rebuild();\n  }\n\n  private rebuild(): void {\n    this.clear();  // Remove all children\n\n    // Build UI with current theme\n    this.addChild(new Text(theme.fg(\"accent\", theme.bold(\"Title\")), 1, 0));\n    this.addChild(new Spacer(1));\n\n    for (const item of this.data.items) {\n      const color = item.active ? \"success\" : \"muted\";\n      this.addChild(new Text(theme.fg(color, item.label), 1, 0));\n    }\n  }\n\n  override invalidate(): void {\n    super.invalidate();\n    this.rebuild();\n  }\n}\n```\n\n### これが重要な場合\n\nこのパターンは次の場合に必要です。\n\n1. **テーマカラーのプリベイク** - `theme.fg()` または `theme.bg()` を使用して、子コンポーネントに保存されるスタイル付き文字列を作成します\n2. **構文の強調表示** - テーマベースの構文の色を適用する `highlightCode()` の使用\n3. **複雑なレイアウト** - テーマカラーを埋め込む子コンポーネントツリーの構築\n\nこのパターンは、次の場合には必要ありません。\n\n1. **テーマ コールバックの使用** - レンダリング中に呼び出される `(text) => theme.fg(\"accent\", text)` などの関数を渡す\n2. **シンプルなコンテナ** - テーマ別コンテンツを追加せずに、他のコンポーネントをグループ化するだけです\n3. **ステートレス レンダリング** - `render()` 呼び出しごとにテーマに合わせた出力を新たに計算します (キャッシュなし)\n\n## よくあるパターン\n\nこれらのパターンは、拡張機能における最も一般的な UI ニーズをカバーします。 **最初から構築するのではなく、これらのパターンをコピーします。**\n\n### パターン1：選択ダイアログ（SelectList）\n\nユーザーがオプションのリストから選択できるようにするため。 `@earendil-works/pi-tui` の `SelectList` と `DynamicBorder` をフレームに使用します。\n\n```typescript\nimport type { ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { DynamicBorder } from \"@earendil-works/pi-coding-agent\";\nimport { Container, type SelectItem, SelectList, Text } from \"@earendil-works/pi-tui\";\n\npi.registerCommand(\"pick\", {\n  handler: async (_args, ctx) => {\n    const items: SelectItem[] = [\n      { value: \"opt1\", label: \"Option 1\", description: \"First option\" },\n      { value: \"opt2\", label: \"Option 2\", description: \"Second option\" },\n      { value: \"opt3\", label: \"Option 3\" },  // description is optional\n    ];\n\n    const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {\n      const container = new Container();\n\n      // Top border\n      container.addChild(new DynamicBorder((s: string) => theme.fg(\"accent\", s)));\n\n      // Title\n      container.addChild(new Text(theme.fg(\"accent\", theme.bold(\"Pick an Option\")), 1, 0));\n\n      // SelectList with theme\n      const selectList = new SelectList(items, Math.min(items.length, 10), {\n        selectedPrefix: (t) => theme.fg(\"accent\", t),\n        selectedText: (t) => theme.fg(\"accent\", t),\n        description: (t) => theme.fg(\"muted\", t),\n        scrollInfo: (t) => theme.fg(\"dim\", t),\n        noMatch: (t) => theme.fg(\"warning\", t),\n      });\n      selectList.onSelect = (item) => done(item.value);\n      selectList.onCancel = () => done(null);\n      container.addChild(selectList);\n\n      // Help text\n      container.addChild(new Text(theme.fg(\"dim\", \"↑↓ navigate • enter select • esc cancel\"), 1, 0));\n\n      // Bottom border\n      container.addChild(new DynamicBorder((s: string) => theme.fg(\"accent\", s)));\n\n      return {\n        render: (w) => container.render(w),\n        invalidate: () => container.invalidate(),\n        handleInput: (data) => { selectList.handleInput(data); tui.requestRender(); },\n      };\n    });\n\n    if (result) {\n      ctx.ui.notify(`Selected: ${result}`, \"info\");\n    }\n  },\n});\n```\n\n**例:** [preset.ts](../examples/extensions/preset.ts)、[tools.ts](../examples/extensions/tools.ts)\n\n### パターン 2: キャンセル付きの非同期操作 (BorderLoader)\n\n時間がかかり、キャンセルできる必要がある操作の場合。 `BorderedLoader` はスピナーを表示し、エスケープをハンドルしてキャンセルします。\n\n```typescript\nimport { BorderedLoader } from \"@earendil-works/pi-coding-agent\";\n\npi.registerCommand(\"fetch\", {\n  handler: async (_args, ctx) => {\n    const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {\n      const loader = new BorderedLoader(tui, theme, \"Fetching data...\");\n      loader.onAbort = () => done(null);\n\n      // Do async work\n      fetchData(loader.signal)\n        .then((data) => done(data))\n        .catch(() => done(null));\n\n      return loader;\n    });\n\n    if (result === null) {\n      ctx.ui.notify(\"Cancelled\", \"info\");\n    } else {\n      ctx.ui.setEditorText(result);\n    }\n  },\n});\n```\n\n**例:** [qna.ts](../examples/extensions/qna.ts)、[handoff.ts](../examples/extensions/handoff.ts)\n\n### パターン 3: 設定/切り替え (SettingsList)\n\n複数の設定を切り替えます。 `@earendil-works/pi-tui` から `getSettingsListTheme()` までの `SettingsList` を使用します。\n\n```typescript\nimport { getSettingsListTheme } from \"@earendil-works/pi-coding-agent\";\nimport { Container, type SettingItem, SettingsList, Text } from \"@earendil-works/pi-tui\";\n\npi.registerCommand(\"settings\", {\n  handler: async (_args, ctx) => {\n    const items: SettingItem[] = [\n      { id: \"verbose\", label: \"Verbose mode\", currentValue: \"off\", values: [\"on\", \"off\"] },\n      { id: \"color\", label: \"Color output\", currentValue: \"on\", values: [\"on\", \"off\"] },\n    ];\n\n    await ctx.ui.custom((_tui, theme, _kb, done) => {\n      const container = new Container();\n      container.addChild(new Text(theme.fg(\"accent\", theme.bold(\"Settings\")), 1, 1));\n\n      const settingsList = new SettingsList(\n        items,\n        Math.min(items.length + 2, 15),\n        getSettingsListTheme(),\n        (id, newValue) => {\n          // Handle value change\n          ctx.ui.notify(`${id} = ${newValue}`, \"info\");\n        },\n        () => done(undefined),  // On close\n        { enableSearch: true }, // Optional: enable fuzzy search by label\n      );\n      container.addChild(settingsList);\n\n      return {\n        render: (w) => container.render(w),\n        invalidate: () => container.invalidate(),\n        handleInput: (data) => settingsList.handleInput?.(data),\n      };\n    });\n  },\n});\n```\n\n**例:** [tools.ts](../examples/extensions/tools.ts)\n\n### パターン 4: 永続的なステータス インジケーター\n\nレンダリング間で維持されるステータスをフッターに表示します。モードインジケーターに適しています。\n\n```typescript\n// Set status (shown in footer)\nctx.ui.setStatus(\"my-ext\", ctx.ui.theme.fg(\"accent\", \"● active\"));\n\n// Clear status\nctx.ui.setStatus(\"my-ext\", undefined);\n```\n\n**例:** [status-line.ts](../examples/extensions/status-line.ts)、[plan-mode/index.ts](../examples/extensions/plan-mode/index.ts)、[preset.ts](../examples/extensions/preset.ts)\n\n### パターン 4b: 動作インジケーターのカスタマイズ\n\npi が応答をストリーミングしている間に表示されるインライン作業インジケーターをカスタマイズします。\n\n```typescript\n// Static indicator\nctx.ui.setWorkingIndicator({ frames: [ctx.ui.theme.fg(\"accent\", \"●\")] });\n\n// Custom animated indicator\nctx.ui.setWorkingIndicator({\n  frames: [\n    ctx.ui.theme.fg(\"dim\", \"·\"),\n    ctx.ui.theme.fg(\"muted\", \"•\"),\n    ctx.ui.theme.fg(\"accent\", \"●\"),\n    ctx.ui.theme.fg(\"muted\", \"•\"),\n  ],\n  intervalMs: 120,\n});\n\n// Hide the indicator entirely\nctx.ui.setWorkingIndicator({ frames: [] });\n\n// Restore pi's default spinner\nctx.ui.setWorkingIndicator();\n```\n\nこれは、通常のストリーミング動作インジケーターにのみ影響します。圧縮および再試行ローダーは、組み込みのスタイルを維持します。カスタム フレームはそのままレンダリングされるため、拡張機能は必要に応じて独自の色を追加する必要があります。\n\n**例:** [working-indicator.ts](../examples/extensions/working-indicator.ts)\n\n### パターン 5: エディターの上/下のウィジェット\n\n入力エディターの上または下に永続的なコンテンツを表示します。 ToDo リストや進捗状況に適しています。\n\n```typescript\n// Simple string array (above editor by default)\nctx.ui.setWidget(\"my-widget\", [\"Line 1\", \"Line 2\"]);\n\n// Render below the editor\nctx.ui.setWidget(\"my-widget\", [\"Line 1\", \"Line 2\"], { placement: \"belowEditor\" });\n\n// Or with theme\nctx.ui.setWidget(\"my-widget\", (_tui, theme) => {\n  const lines = items.map((item, i) =>\n    item.done\n      ? theme.fg(\"success\", \"✓ \") + theme.fg(\"muted\", item.text)\n      : theme.fg(\"dim\", \"○ \") + item.text\n  );\n  return {\n    render: () => lines,\n    invalidate: () => {},\n  };\n});\n\n// Clear\nctx.ui.setWidget(\"my-widget\", undefined);\n```\n\n**例:** [plan-mode/index.ts](../examples/extensions/plan-mode/index.ts)\n\n### パターン6: カスタムフッター\n\nフッターを置き換えます。 `footerData` は、他の方法では拡張機能にアクセスできないデータを公開します。\n\n```typescript\nctx.ui.setFooter((tui, theme, footerData) => ({\n  invalidate() {},\n  render(width: number): string[] {\n    // footerData.getGitBranch(): string | null\n    // footerData.getExtensionStatuses(): ReadonlyMap<string, string>\n    return [`${ctx.model?.id} (${footerData.getGitBranch() || \"no git\"})`];\n  },\n  dispose: footerData.onBranchChange(() => tui.requestRender()), // reactive\n}));\n\nctx.ui.setFooter(undefined); // restore default\n```\n\nトークンの統計は `ctx.sessionManager.getBranch()` および `ctx.model` から入手できます。\n\n**例:** [custom-footer.ts](../examples/extensions/custom-footer.ts)\n\n### パターン7：カスタムエディター（vimモードなど）\n\nメインの入力エディタをカスタム実装に置き換えます。モーダル編集 (vim)、さまざまなキーバインド (emacs)、または特殊な入力処理に役立ちます。\n\n```typescript\nimport { CustomEditor, type ExtensionAPI } from \"@earendil-works/pi-coding-agent\";\nimport { matchesKey, truncateToWidth } from \"@earendil-works/pi-tui\";\n\ntype Mode = \"normal\" | \"insert\";\n\nclass VimEditor extends CustomEditor {\n  private mode: Mode = \"insert\";\n\n  handleInput(data: string): void {\n    // Escape: switch to normal mode, or pass through for app handling\n    if (matchesKey(data, \"escape\")) {\n      if (this.mode === \"insert\") {\n        this.mode = \"normal\";\n        return;\n      }\n      // In normal mode, escape aborts agent (handled by CustomEditor)\n      super.handleInput(data);\n      return;\n    }\n\n    // Insert mode: pass everything to CustomEditor\n    if (this.mode === \"insert\") {\n      super.handleInput(data);\n      return;\n    }\n\n    // Normal mode: vim-style navigation\n    switch (data) {\n      case \"i\": this.mode = \"insert\"; return;\n      case \"h\": super.handleInput(\"\\x1b[D\"); return; // Left\n      case \"j\": super.handleInput(\"\\x1b[B\"); return; // Down\n      case \"k\": super.handleInput(\"\\x1b[A\"); return; // Up\n      case \"l\": super.handleInput(\"\\x1b[C\"); return; // Right\n    }\n    // Pass unhandled keys to super (ctrl+c, etc.), but filter printable chars\n    if (data.length === 1 && data.charCodeAt(0) >= 32) return;\n    super.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    const lines = super.render(width);\n    // Add mode indicator to bottom border (use truncateToWidth for ANSI-safe truncation)\n    if (lines.length > 0) {\n      const label = this.mode === \"normal\" ? \" NORMAL \" : \" INSERT \";\n      const lastLine = lines[lines.length - 1]!;\n      // Pass \"\" as ellipsis to avoid adding \"...\" when truncating\n      lines[lines.length - 1] = truncateToWidth(lastLine, width - label.length, \"\") + label;\n    }\n    return lines;\n  }\n}\n\nexport default function (pi: ExtensionAPI) {\n  pi.on(\"session_start\", (_event, ctx) => {\n    // Factory receives the TUI, theme, and keybindings from the app\n    ctx.ui.setEditorComponent((tui, theme, keybindings) =>\n      new VimEditor(tui, theme, keybindings)\n    );\n  });\n}\n```\n\n**重要なポイント:**\n\n- ** `CustomEditor`** (ベース `Editor` ではなく) を拡張して、アプリのキーバインドを取得します (中止するには Escape、終了するには Ctrl+D、モデルの切り替えなど)\n- **扱っていないキーについては、`super.handleInput(data)`** にお電話ください。\n- **ファクトリ パターン**: `setEditorComponent` は、`tui`、`theme`、および `keybindings` を取得するファクトリ関数を受け取ります\n- **デフォルトのエディターを復元するには、`undefined`** を渡します: `ctx.ui.setEditorComponent(undefined)`\n\n**例:** [modal-editor.ts](../examples/extensions/modal-editor.ts)\n\n## 重要なルール\n\n1. **常にコールバックからテーマを使用してください** - テーマを直接インポートしないでください。 `ctx.ui.custom((tui, theme, keybindings, done) =>...)` コールバックから `theme` を使用します。\n\n2. **常に DynamicBorder color パラメータを入力してください** - `(s) => theme.fg(\"accent\", s)` ではなく、`(s: string) => theme.fg(\"accent\", s)` と入力します。\n\n3. **状態変更後に tui.requestRender() を呼び出す** - `handleInput` では、状態を更新した後に `tui.requestRender()` を呼び出します。\n\n4. **3 つのメソッド オブジェクトを返します** - カスタム コンポーネントには `{ render, invalidate, handleInput }` が必要です。\n\n5. **既存のコンポーネントを使用** - `SelectList`、`SettingsList`、`BorderedLoader` がケースの 90% をカバーします。再構築しないでください。\n\n## 例\n\n- **選択 UI**: [examples/extensions/preset.ts](../examples/extensions/preset.ts) - DynamicBorder フレームを使用した SelectList\n- **キャンセル付き非同期**: [examples/extensions/qna.ts](../examples/extensions/qna.ts) - LLM 呼び出し用の BorderLoader\n- **設定切り替え**: [examples/extensions/tools.ts](../examples/extensions/tools.ts) - ツールの有効化/無効化の設定リスト\n- **ステータス インジケーター**: [examples/extensions/plan-mode/index.ts](../examples/extensions/plan-mode/index.ts) - setStatus および setWidget\n- **作業インジケーター**: [examples/extensions/working-indicator.ts](../examples/extensions/working-indicator.ts) - setWorkingIndicator\n- **カスタム フッター**: [examples/extensions/custom-footer.ts](../examples/extensions/custom-footer.ts) - 統計情報を含む setFooter\n- **カスタム エディタ**: [examples/extensions/modal-editor.ts](../examples/extensions/modal-editor.ts) - Vim のようなモーダル編集\n- **スネーク ゲーム**: [examples/extensions/snake.ts](../examples/extensions/snake.ts) - キーボード入力付きの完全なゲーム、ゲーム ループ\n- **カスタム ツール レンダリング**: [examples/extensions/todo.ts](../examples/extensions/todo.ts) - renderCall および renderResult","sourceFile":"tui.md"},"usage":{"title":"Piの使用","markdown":"このページには、クイックスタート ページには収まらない日常の使用状況の詳細が収集されます。\n\n## インタラクティブモード\n\n<p align=\"center\"><img src=\"images/interactive-mode.png\" alt=\"Interactive Mode\" width=\"600\"></p>\n\nインターフェイスには 4 つの主要な領域があります。\n\n- **スタートアップ ヘッダー** - ショートカット、ロードされた context files、prompt templates、スキル、および拡張機能\n- **メッセージ** - ユーザー メッセージ、アシスタントの応答、ツール呼び出し、ツールの結果、通知、エラー、拡張機能 UI\n- **エディタ** - 入力する場所。境界線の色は現在の思考レベルを示します\n- **フッター** - 作業ディレクトリ、セッション名、トークン/キャッシュの使用状況、コスト、コンテキストの使用状況、および現在のモデル。合計には、アシスタントの応答、ツールによって報告された使用状況、および概要の生成が含まれます。\n\nエディターは、`/settings` などの組み込み UI またはカスタム拡張 UI に一時的に置き換えることができます。\n\n### エディターの機能\n\n| 特徴 | どうやって |\n|---------|-----|\n| ファイルリファレンス | プロジェクト ファイルをあいまい検索するには、「`@`」と入力します。 |\n| パスの完成 | Tab キーを押してパスを完成させます |\n| 複数行入力 | Windows ターミナルの Shift+Enter、または Ctrl+Enter |\n| 応答をコピーする | Ctrl+X は最後のアシスタント メッセージをコピーします。 `/tree`では、選択したメッセージをコピーします |\n| 画像 | Windows では Ctrl+V、Alt+V を押して貼り付けるか、ターミナルにドラッグします |\n| シェルコマンド | `!command` が実行され、出力がモデルに送信されます |\n| 隠しシェルコマンド | `!!command` は出力をモデルに送信せずに実行されます |\n| 外部エディター | Ctrl+G で `externalEditor`、`$VISUAL`、`$EDITOR`、Windows のメモ帳、またはその他の場所で `nano` が開きます |\n\nすべてのショートカットとカスタマイズについては、[Keybindings](keybindings.md) を参照してください。\n\n## スラッシュコマンド\n\nエディターに「`/`」と入力してコマンド補完を開きます。 Extensionsはカスタムコマンドを登録でき、スキルは`/skill:name`として利用可能で、prompt templatesは`/templatename`を介して拡張されます。\n\n| 指示 | 説明 |\n|---------|-------------|\n| `/login`, `/logout` | OAuth または API キー認証情報を管理する |\n| [`/llama`](llama-cpp.md) | llama.cpp ルーター モデルのダウンロード、ロード、アンロード |\n| `/model` | モデルの切り替え |\n| `/scoped-models` | Ctrl+P サイクリングのモデルを有効/無効にする |\n| `/settings` | 思考レベル、テーマ、メッセージ配信、トランスポート |\n| `/resume` | 前のセッションからのPick |\n| `/new` | 新しいセッションを開始する |\n| `/name <name>` | セッション表示名の設定 |\n| `/session` | セッションファイル、ID、メッセージ、トークン、コストを表示します |\n| `/tree` | セッション内の任意の時点にジャンプし、そこから続行します |\n| `/trust` | プロジェクトの信頼決定を将来のセッションのために保存する |\n| `/fork` | 以前のユーザー メッセージから新しいセッションを作成する |\n| `/clone` | 現在のアクティブなブランチを新しいセッションに複製します。 |\n| `/compact [prompt]` | 手動でコンテキストを圧縮（オプションでカスタム命令を使用） |\n| `/copy` | 最後のアシスタント メッセージをクリップボードにコピーします |\n| `/export [file]` | セッションを HTML または JSONL にエクスポート |\n| `/import <file>` | JSONL ファイルからセッションをインポートして再開する |\n| `/share` | 共有可能な HTML リンクを使用してプライベート GitHub 要点としてアップロードします |\n| `/reload` | キーバインド、拡張機能、スキル、プロンプト、テーマ、および context files をリロードします |\n| `/hotkeys` | すべてのキーボード ショートカットを表示 |\n| `/changelog` | バージョン履歴を表示する |\n| `/quit` | 円周率をやめる |\n\n## メッセージキュー\n\nエージェントが動作している間にメッセージを送信できます。\n\n- **Enter** は、現在のアシスタント ターンがツール呼び出しの実行を終了した後に配信されるステアリング メッセージをキューに入れます。\n- **Alt+Enter** はフォローアップ メッセージをキューに入れ、エージェントがすべての作業を完了した後に配信します。\n- **Escape** は中止され、キューに入れられたメッセージをエディタに復元します。\n- **Alt+Up** は、キューに入れられたメッセージをエディタに取得します。\n\nWindows ターミナルでは、Alt+Enter はデフォルトで全画面表示になります。 pi がショートカットを受信できるようにする場合は、[Terminal setup](terminal-setup.md) で説明されているように再マッピングします。\n\n[Settings](settings.md) での配信を `steeringMode` と `followUpMode` で構成します。\n\n## セッション\n\nセッションは、作業ディレクトリごとに整理されて、`~/.pi/agent/sessions/` に自動的に保存されます。\n\n```bash\npi -c                  # Continue most recent session\npi -r                  # Browse and select a session\npi --no-session        # Ephemeral mode; do not save\npi --name \"my task\"    # Set session display name at startup\npi --session <path|id> # Use a specific session file or session ID\npi --fork <path|id>    # Fork a session into a new session file\n```\n\n便利なセッションコマンド:\n\n- `/session` は、現在のセッション ファイルと ID を示します。\n- `/tree` はファイル内 session tree をナビゲートし、放棄されたブランチを要約できます。\n- `/fork` は、以前のユーザー メッセージから新しいセッションを作成します。\n- `/clone` は、現在のアクティブなブランチを新しいセッション ファイルに複製します。\n- `/compact` は、古いメッセージを自由なコンテキストに要約します。\n\n詳細については、[Sessions](sessions.md) および [Compaction](compaction.md) を参照してください。\n\n## コンテキストファイル\n\nPi は、起動時に以下から `AGENTS.md` または `CLAUDE.md` をロードします。\n\n- `~/.pi/agent/AGENTS.md` グローバル命令\n- 親ディレクトリ、現在の作業ディレクトリから上に移動\n- 現在のディレクトリ\n\nディレクトリに `AGENTS.override.md` が含まれている場合、Pi はそのディレクトリから `AGENTS.md` または `CLAUDE.md` の代わりにそれをロードします。他のディレクトリのコンテキスト ファイルは引き続き通常どおり階層化されます。\n\nプロジェクトの規則、コマンド、安全ルール、および設定には context files を使用します。 `--no-context-files` または `-nc` でロードを無効にします。\n\n### システムプロンプトファイル\n\nデフォルトのシステム プロンプトを次のものに置き換えます。\n\n- プロジェクトの場合は `.pi/SYSTEM.md`\n- `~/.pi/agent/SYSTEM.md` 世界中で\n\nどちらの場所でも、デフォルトのプロンプトを `APPEND_SYSTEM.md` に置き換えずに追加します。\n\n### プロジェクトトラスト\n\nインタラクティブな起動時に、pi は、プロジェクトのローカル設定、リソース、またはプロジェクト `.agents/skills` が含まれており、`~/.pi/agent/trust.json` のフォルダーまたは親フォルダーに対する決定が保存されていないプロジェクト フォルダーを信頼する前に質問します。プロジェクトを信頼すると、pi は `.pi/settings.json` および `.pi` リソースをロードし、不足しているプロジェクト パッケージをインストールし、プロジェクト拡張機能を実行できます。\n\n信頼決定の前に、pi は、`project_trust` イベントを処理できるように、context files、ユーザー/グローバル拡張、および CLI `-e` 拡張のみをロードします。プロジェクト ローカル拡張機能、プロジェクト パッケージ管理拡張機能、およびプロジェクト設定は、プロジェクトが信頼された後にのみ読み込まれます。この分割は、現在のプロセスで信頼が解決されていない別の cwd からセッションに切り替える場合にも適用されます。\n\n非対話型モード (`-p`、`--mode json`、および `--mode rpc`) では、信頼プロンプトは表示されません。該当する保存された信頼決定がなければ、グローバル設定の `defaultProjectTrust` を使用します: `ask` (デフォルト) と `never` はこれらのプロジェクト リソースを無視し、`always` はそれらを信頼します。 `--approve`/`-a` または `--no-approve`/`-na` を渡して、1 回の実行でプロジェクトの信頼をオーバーライドします。\n\n拡張機能または保存された決定が適用されない場合、`defaultProjectTrust` はフォールバック動作を制御します。 `~/.pi/agent/settings.json`で`\"ask\"`、`\"always\"`、`\"never\"`に設定するか、`/settings`で変更します。\n\n`pi config` とパッケージ コマンドは、`pi update` がプロンプトを表示しないことを除き、同じプロジェクト信頼フローを使用します。 1 つのコマンドに対してプロジェクトのローカル設定を信頼するには `--approve` を、無視するには `--no-approve` を渡します。\n\n対話モードで `/trust` を使用すると、直接の親フォルダーに対する信頼を含め、将来のセッションのためにプロジェクトの信頼決定を保存できます。 `~/.pi/agent/trust.json` のみを書き込みます。現在のセッションはリロードされないため、変更を有効にするために pi を再起動します。\n\n\n## セッションのエクスポートと共有\n\n`/export [file]` を使用してセッションを HTML に書き込みます。\n\n`/share` を使用して、共有可能な HTML リンクを含むプライベート GitHub 要点をアップロードします。\n\nオープンソース作業に pi を使用し、モデル、プロンプト、ツール、評価研究のセッションを公開したい場合は、[`badlogic/pi-share-hf`](https://github.com/badlogic/pi-share-hf) を参照してください。セッションを Hugging Face データセットに公開します。\n\n## CLI 参考\n\n```bash\npi [options] [@files...] [messages...]\n```\n\n### パッケージコマンド\n\n```bash\npi install <source> [-l]     # Install package, -l for project-local\npi remove <source> [-l]      # Remove package\npi uninstall <source> [-l]   # Alias for remove\npi update [source|self|pi]   # Update pi only, or one package source\npi update --all              # Update pi and packages; reconcile pinned git refs\npi update --extensions       # Update packages only; reconcile pinned git refs\npi update --models           # Refresh model catalogs only\npi update --self             # Update pi only\npi update --extension <src>  # Update one package\npi list                      # List installed packages\npi config                    # Enable/disable package resources\n```\n\nこれらのコマンドは pi パッケージを管理し、`pi update` で pi CLI のインストールを更新できます。 pi 自体をアンインストールするには、[Quickstart](quickstart.md#uninstall) を参照してください。 `pi config` およびプロジェクト パッケージ コマンドは、`--approve`/`--no-approve` を受け入れて、1 つのコマンドのプロジェクト ローカル設定を信頼または無視します。 `pi update` はプロジェクトの信頼を求めるプロンプトを表示しません。\n\nパッケージのソースとセキュリティに関する注意事項については、[Pi Packages](packages.md) を参照してください。\n\n### モード\n\n| フラグ | 説明 |\n|------|-------------|\n| デフォルト | インタラクティブモード |\n| `-p`, `--print` | 応答を出力して終了する |\n| `--mode json` | すべてのイベントを JSON 行として出力します。 [JSON mode](json.md)を参照 |\n| `--mode rpc` | stdin/stdout を上回る RPC モード。 [RPC mode](rpc.md)を参照 |\n| `--export <in> [out]` | セッションを HTML にエクスポートする |\n\n印刷モードでは、pi はパイプされた stdin も読み取り、それを最初のプロンプトにマージします。\n\n```bash\ncat README.md | pi -p \"Summarize this text\"\n```\n\n### モデルオプション\n\n| オプション | 説明 |\n|--------|-------------|\n| `--provider <name>` | プロバイダー (`anthropic`、`openai`、`google` など) |\n| `--model <pattern>` | モデルのパターンまたは ID。 `provider/id` とオプションの `:<thinking>` をサポートします |\n| `--api-key <key>` | API key、環境変数を上書きする |\n| `--thinking <level>` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max` |\n| `--models <patterns>` | Ctrl+P サイクルのカンマ区切りパターン |\n| `--list-models [search]` | 利用可能なモデルをリストする |\n\n### セッションオプション\n\n| オプション | 説明 |\n|--------|-------------|\n| `-c`, `--continue` | 最新のセッションを継続する |\n| `-r`, `--resume` | セッションを参照して選択します |\n| `--セッション<パス\\ | ID>` | 特定のセッション ファイルまたは部分的な UUID を使用する |\n| `--fork <パス\\ | ID>` | セッション ファイルまたは部分的な UUID を新しいセッションにフォークする |\n| `--session-dir <dir>` | カスタムセッションストレージディレクトリ |\n| `--no-session` | 一時モード。保存しないでください |\n| `--name <name>`, `-n <name>` | 起動時にセッション表示名を設定する |\n\n### ツールオプション\n\n| オプション | 説明 |\n|--------|-------------|\n| `--tools <list>`, `-t <list>` | 特定の組み込みツール、拡張ツール、およびカスタム ツールを許可リストに登録する |\n| `--exclude-tools <list>`, `-xt <list>` | 特定の組み込みツール、拡張ツール、カスタム ツールを無効にする |\n| `--no-builtin-tools`, `-nbt` | 組み込みツールを無効にするが、拡張機能/カスタム ツールは有効のままにする |\n| `--no-tools`, `-nt` | すべてのツールを無効にする |\n\n内蔵ツール: `read`、`bash`、`edit`、`write`、`grep`、`find`、`ls`。\n\n### リソースオプション\n\n| オプション | 説明 |\n|--------|-------------|\n| `-e`, `--extension <source>` | パス、npm、または git から拡張機能をロードします。繰り返し可能な |\n| `--no-extensions` | 拡張機能の検出を無効にする |\n| `--skill <path>` | スキルをロードします。繰り返し可能な |\n| `--no-skills` | スキル検出を無効にする |\n| `--prompt-template <path>` | プロンプトテンプレートをロードします。繰り返し可能な |\n| `--no-prompt-templates` | プロンプトテンプレート検出を無効にする |\n| `--theme <path>` | テーマをロードします。繰り返し可能な |\n| `--no-themes` | テーマの検出を無効にする |\n| `--no-context-files`, `-nc` | `AGENTS.md` および `CLAUDE.md` 検出を無効にする |\n\n`--no-*` を明示的なフラグと組み合わせて、設定を無視して必要なものを正確にロードします。例：\n\n```bash\npi --no-extensions -e ./my-extension.ts\n```\n\n### その他のオプション\n\n| オプション | 説明 |\n|--------|-------------|\n| `--system-prompt <text>` | デフォルトのプロンプトを置き換えます。 context files とスキルはまだ追加されています |\n| `--append-system-prompt <text>` | システムプロンプトに追加 |\n| `--tui-mode <mode>` | TUI モード: `regular` (デフォルト) または実験的 `fullscreen` |\n| `--verbose` | 冗長起動を強制する |\n| `-a`, `--approve` | この実行に対してプロジェクト ローカル ファイルを信頼する |\n| `-na`, `--no-approve` | この実行ではプロジェクトのローカル ファイルを無視します |\n| `-h`, `--help` | ヘルプを表示する |\n| `-v`, `--version` | バージョンを表示 |\n\n`fullscreen` モードでは、トランスクリプトはターミナル ビューポート内でスクロールしますが、キューに入れられたメッセージ、作業ステータス、拡張機能ウィジェット、エディタ、およびフッターは下部に固定されたままになります。マウス/トラックパッド入力により、ポインタの下の領域がスクロールされます。キーボード ビューポート アクションは常に利用可能です。インライン イメージは、Kitty や Ghostty など、Kitty グラフィックス プロトコルをサポートする端末で動作します。 iTerm2 では、インライン画像プロトコルではアプリケーション所有のスクロール中にプレースメントを削除したりトリミングしたりできないため、テキスト プレースホルダーとしてレンダリングされます。 `regular` モードでは、pi はメイン画面と端末所有のスクロールバックを使用し、iTerm2 インライン イメージは通常どおりレンダリングを続けます。\n\n`/settings` で **TUI モード** を設定すると、`regular` と `fullscreen` がすぐに切り替わり、今後のセッションでデフォルトを選択できます。 **全画面終了出力** は、全画面を終了して最終的なトランスクリプトを印刷するか、前の画面に戻してセッション再開のヒントのみを印刷するかを制御します。\n\n### ファイル引数\n\nファイルをメッセージに含めるには、ファイルの前に `@` を付けます。\n\n```bash\npi @prompt.md \"Answer this\"\npi -p @screenshot.png \"What's in this image?\"\npi @code.ts @test.ts \"Review these files\"\n```\n\n### 例\n\n```bash\n# Interactive with initial prompt\npi \"List all .ts files in src/\"\n\n# Non-interactive\npi -p \"Summarize this codebase\"\n\n# Non-interactive with piped stdin\ncat README.md | pi -p \"Summarize this text\"\n\n# Named one-shot session\npi --name \"release audit\" -p \"Audit this repository\"\n\n# Different model\npi --provider openai --model gpt-4o \"Help me refactor\"\n\n# Model with provider prefix\npi --model openai/gpt-4o \"Help me refactor\"\n\n# Model with thinking level shorthand\npi --model sonnet:high \"Solve this complex problem\"\n\n# Limit model cycling\npi --models \"claude-*,gpt-4o\"\n\n# Read-only mode\npi --tools read,grep,find,ls -p \"Review the code\"\n\n# Disable one extension or built-in tool while keeping the rest available\npi --exclude-tools ask_question\n```\n\n## 設計原則\n\nPi はコアを小さく保ち、ワークフロー固有の動作を拡張機能、スキル、prompt templates、およびパッケージにプッシュします。\n\nこれには、組み込みの MCP、サブエージェント、権限ポップアップ、プラン モード、To-Do、またはバックグラウンド bash は意図的に含まれていません。これらのワークフローは、拡張機能またはパッケージとして構築またはインストールすることも、コンテナーや tmux などの外部ツールを使用することもできます。\n\n完全な理論的根拠については、[blog post](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/) をお読みください。","sourceFile":"usage.md"},"windows":{"title":"Windowsのセットアップ","markdown":"Pi には Windows 上で bash シェルが必要です。確認した場所（順番に）：\n\n1. `~/.pi/agent/settings.json` からのカスタム パス\n2. Git バッシュ (`C:\\Program Files\\Git\\bin\\bash.exe`)\n3. PATH 上の`bash.exe` (Cygwin、MSYS2、WSL)\n\nほとんどのユーザーにとって、[Git for Windows](https://git-scm.com/download/win) で十分です。\n\n## カスタムシェルパス\n\n```json\n{\n  \"shellPath\": \"C:\\\\cygwin64\\\\bin\\\\bash.exe\"\n}\n```","sourceFile":"windows.md"}}},"navigation":{"ja":[{"title":"ここから開始","items":[{"title":"Pi ドキュメント","path":"/docs/latest","slug":"index"},{"title":"クイックスタート","path":"/docs/latest/quickstart","slug":"quickstart"},{"title":"Piの使用","path":"/docs/latest/usage","slug":"usage"},{"title":"Providers","path":"/docs/latest/providers","slug":"providers"},{"title":"安全","path":"/docs/latest/security","slug":"security"},{"title":"コンテナ化","path":"/docs/latest/containerization","slug":"containerization"},{"title":"設定","path":"/docs/latest/settings","slug":"settings"},{"title":"キーバインド","path":"/docs/latest/keybindings","slug":"keybindings"},{"title":"セッション","path":"/docs/latest/sessions","slug":"sessions"},{"title":"圧縮とブランチの要約","path":"/docs/latest/compaction","slug":"compaction"}]},{"title":"カスタマイズ","items":[{"title":"Extensions","path":"/docs/latest/extensions","slug":"extensions"},{"title":"Skills","path":"/docs/latest/skills","slug":"skills"},{"title":"プロンプトテンプレート","path":"/docs/latest/prompt-templates","slug":"prompt-templates"},{"title":"テーマ","path":"/docs/latest/themes","slug":"themes"},{"title":"Pi Packages","path":"/docs/latest/packages","slug":"packages"},{"title":"カスタムModels","path":"/docs/latest/models","slug":"models"},{"title":"カスタムProviders","path":"/docs/latest/custom-provider","slug":"custom-provider"}]},{"title":"リファレンス","items":[{"title":"セッションファイル形式","path":"/docs/latest/session-format","slug":"session-format"}]},{"title":"プログラムからの使用","items":[{"title":"SDK","path":"/docs/latest/sdk","slug":"sdk"},{"title":"RPCモード","path":"/docs/latest/rpc","slug":"rpc"},{"title":"JSON イベントストリームモード","path":"/docs/latest/json","slug":"json"},{"title":"TUI コンポーネント","path":"/docs/latest/tui","slug":"tui"}]},{"title":"プラットフォーム設定","items":[{"title":"Windowsのセットアップ","path":"/docs/latest/windows","slug":"windows"},{"title":"Termux (Android) セットアップ","path":"/docs/latest/termux","slug":"termux"},{"title":"tmux セットアップ","path":"/docs/latest/tmux","slug":"tmux"},{"title":"端末のセットアップ","path":"/docs/latest/terminal-setup","slug":"terminal-setup"},{"title":"シェルのエイリアス","path":"/docs/latest/shell-aliases","slug":"shell-aliases"}]},{"title":"開発","items":[{"title":"発達","path":"/docs/latest/development","slug":"development"}]}]}}
