{"locale":"de","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":{"de":{"compaction":{"title":"Komprimierung und Zweigzusammenfassung","markdown":"LLMs haben begrenzte Kontextfenster. Wenn Gespräche zu lang werden, verwendet Pi die Komprimierung, um ältere Inhalte zusammenzufassen und gleichzeitig aktuelle Arbeiten beizubehalten. Diese Seite behandelt sowohl die automatische Komprimierung als auch branch summarization.\n\n**Quelldateien** ([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) – Logik zur automatischen Komprimierung\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) – Zweigzusammenfassung\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) – Gemeinsame Dienstprogramme (Dateiverfolgung, Serialisierung)\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) – Eintragstypen (`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) – Erweiterungsereignistypen\n\nÜberprüfen Sie für TypeScript-Definitionen in Ihrem Projekt `node_modules/@earendil-works/pi-coding-agent/dist/`.\n\n## Überblick\n\nPi verfügt über zwei Zusammenfassungsmechanismen:\n\n| Mechanismus | Auslösen | Zweck |\n|-----------|---------|---------|\n| Verdichtung | Der Kontext überschreitet den Schwellenwert oder `/compact` | Fassen Sie alte Nachrichten zusammen, um den Kontext freizugeben |\n| Zweigzusammenfassung | `/tree` Navigation | Behalten Sie den Kontext beim Wechseln von Zweigen bei |\n\nBeide verwenden dasselbe strukturierte Zusammenfassungsformat und verfolgen Dateivorgänge kumulativ. Komprimierungs- und Zweigzusammenfassungsanforderungen verwenden neue Routing-Sitzungs-IDs und deaktivieren, sofern vom Anbieter unterstützt, Eingabeaufforderungs-Cache-Schreibvorgänge, da diese einmaligen Eingabeaufforderungen wahrscheinlich nicht wiederverwendet werden.\n\n## Verdichtung\n\n### Wenn es ausgelöst wird\n\nDie automatische Komprimierung wird ausgelöst, wenn:\n\n```\ncontextTokens > contextWindow - reserveTokens\n```\n\nStandardmäßig beträgt `reserveTokens` 16384 Token (konfigurierbar in `~/.pi/agent/settings.json` oder `<project-dir>/.pi/settings.json`). Dies lässt Raum für die Reaktion des LLM.\n\nSie können auch manuell mit `/compact [instructions]` auslösen, wobei optionale Anweisungen die Zusammenfassung fokussieren.\n\n### Wie es funktioniert\n\n1. **Schnittpunkt finden**: Gehen Sie von der neuesten Nachricht aus rückwärts und sammeln Sie Token-Schätzungen, bis `keepRecentTokens` (Standard 20.000, konfigurierbar in `~/.pi/agent/settings.json` oder `<project-dir>/.pi/settings.json`) erreicht ist\n2. **Nachrichten extrahieren**: Sammeln Sie Nachrichten von der zuvor beibehaltenen Grenze (oder dem Sitzungsstart) bis zum Schnittpunkt\n3. **Zusammenfassung generieren**: Rufen Sie LLM auf, um eine Zusammenfassung im strukturierten Format zu erstellen und die vorherige Zusammenfassung als iterativen Kontext zu übergeben, sofern vorhanden\n4. **Eintrag anhängen**: Speichern Sie `CompactionEntry` mit Zusammenfassung und `firstKeptEntryId`\n5. **Neu laden**: Sitzung wird neu geladen, wobei Zusammenfassung + Nachrichten ab `firstKeptEntryId` verwendet werden\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\nBei wiederholten Komprimierungen beginnt die zusammengefasste Spanne an der beibehaltenen Grenze der vorherigen Komprimierung (`firstKeptEntryId`), nicht am Komprimierungseintrag selbst, und fällt auf den Eintrag nach der vorherigen Komprimierung zurück, wenn dieser beibehaltene Eintrag nicht im Pfad gefunden werden kann. Dadurch bleiben Nachrichten erhalten, die die frühere Komprimierung überstanden haben, indem sie auch in den nächsten Zusammenfassungsdurchlauf einbezogen werden. Pi berechnet außerdem `tokensBefore` aus dem neu erstellten Sitzungskontext neu, bevor das neue `CompactionEntry` geschrieben wird, sodass die Tokenanzahl den tatsächlichen Kontext vor der Komprimierung widerspiegelt, der ersetzt wird.\n\n### Geteilte Kurven\n\nEin „Turn“ beginnt mit einer Benutzernachricht und umfasst alle Assistentenantworten und Werkzeugaufrufe bis zur nächsten Benutzernachricht. Normalerweise erfolgt der Verdichtungsschnitt an den Kurvengrenzen.\n\nWenn eine einzelne Umdrehung `keepRecentTokens` überschreitet, landet der Schnittpunkt mitten in der Umdrehung bei einer Hilfsmeldung. Dies ist ein „Split Turn“:\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\nFür geteilte Runden generiert Pi zwei Zusammenfassungen und führt sie zusammen:\n1. **Zusammenfassung des Verlaufs**: Vorheriger Kontext (falls vorhanden)\n2. **Zusammenfassung der Rundenpräfixe**: Der frühe Teil der geteilten Runde\n\n### Schnittpunktregeln\n\nGültige Schnittpunkte sind:\n- Benutzernachrichten\n- Assistentennachrichten\n- BashExecution-Nachrichten\n- Benutzerdefinierte Nachrichten (custom_message, branch_summary)\n\nSchneiden Sie niemals nach Werkzeugergebnissen (sie müssen bei ihrem Werkzeugaufruf bleiben).\n\n### CompactionEntry-Struktur\n\nDefiniert in [`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 kann alle JSON-serialisierbaren Daten in `details` speichern. Die Standardkomprimierung verfolgt Dateivorgänge, aber benutzerdefinierte Erweiterungsimplementierungen können ihre eigene Struktur verwenden. Generierte und von der Erweiterung bereitgestellte Zusammenfassungen speichern ihren LLM `usage`, sofern verfügbar, sodass die Sitzungssummen die Zusammenfassungsarbeit umfassen.\n\nSiehe [`prepareCompaction()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/compaction.ts) und [`compact()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/compaction.ts) für die Implementierung. Für eine direkte programmatische Zusammenfassung gibt `generateSummary()` den Zusammenfassungstext und `generateSummaryWithUsage()` `{ text, usage }` zurück.\n\n## Zweigzusammenfassung\n\n### Wenn es ausgelöst wird\n\nWenn Sie `/tree` verwenden, um zu einem anderen Zweig zu navigieren, bietet Pi an, die Arbeit, die Sie verlassen, zusammenzufassen. Dadurch wird Kontext vom linken Zweig in den neuen Zweig eingefügt.\n\n### Wie es funktioniert\n\n1. **Gemeinsamen Vorfahren finden**: Tiefster Knoten, den alte und neue Positionen gemeinsam haben\n2. **Einträge sammeln**: Gehen Sie vom alten Blatt zurück zum gemeinsamen Vorfahren\n3. **Mit Budget vorbereiten**: Nachrichten bis zum Token-Budget einbeziehen (neueste zuerst)\n4. **Zusammenfassung erstellen**: LLM mit strukturiertem Format aufrufen\n5. **Eintrag anhängen**: `BranchSummaryEntry` am Navigationspunkt speichern\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### Kumulative Dateiverfolgung\n\nSowohl Komprimierung als auch branch summarization verfolgen Dateien kumulativ. Beim Generieren einer Zusammenfassung extrahiert pi Dateioperationen aus:\n- Toolaufrufe in den Nachrichten werden zusammengefasst\n- Vorherige Komprimierung oder Zweigzusammenfassung `details` (falls vorhanden)\n\nDies bedeutet, dass die Dateiverfolgung über mehrere Komprimierungen oder verschachtelte Zweigzusammenfassungen hinweg akkumuliert wird und der vollständige Verlauf der gelesenen und geänderten Dateien erhalten bleibt.\n\n### BranchSummaryEntry-Struktur\n\nDefiniert in [`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\nGenau wie bei der Komprimierung können Erweiterungen benutzerdefinierte Daten in `details` speichern.\n\nSiehe [`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) und [`generateBranchSummary()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/branch-summarization.ts) für die Implementierung.\n\n## Zusammenfassungsformat\n\nSowohl die Komprimierung als auch branch summarization verwenden dasselbe strukturierte Format:\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### Nachrichtenserialisierung\n\nVor der Zusammenfassung werden Nachrichten über [`serializeConversation()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/utils.ts) in Text serialisiert:\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\nDadurch wird verhindert, dass das Modell das Gespräch als Fortsetzung betrachtet.\n\nTool-Ergebnisse werden während der Serialisierung auf 2000 Zeichen gekürzt. Inhalte, die über diese Grenze hinausgehen, werden durch eine Markierung ersetzt, die angibt, wie viele Zeichen abgeschnitten wurden. Dadurch bleiben Zusammenfassungsanfragen innerhalb angemessener Token-Budgets, da Tool-Ergebnisse (insbesondere von `read` und `bash`) normalerweise den größten Beitrag zur Kontextgröße leisten.\n\n## Benutzerdefinierte Zusammenfassung über Extensions\n\nExtensions kann sowohl die Komprimierung als auch branch summarization abfangen und anpassen. Siehe [`extensions/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/extensions/types.ts) für Ereignistypdefinitionen.\n\n### session_before_compact\n\nGefeuert vor der automatischen Komprimierung oder `/compact`. Kann abbrechen oder eine benutzerdefinierte Zusammenfassung bereitstellen. Siehe `SessionBeforeCompactEvent` und `CompactionPreparation` in der Typendatei.\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#### Konvertieren von Nachrichten in Text\n\nUm eine Zusammenfassung mit Ihrem eigenen Modell zu erstellen, konvertieren Sie Nachrichten mit `serializeConversation` in Text:\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\nUnter [custom-compaction.ts](../examples/extensions/custom-compaction.ts) finden Sie ein vollständiges Beispiel mit einem anderen Modell.\n\n### session_before_tree\n\nVor `/tree` Navigation abgefeuert. Wird immer ausgelöst, unabhängig davon, ob der Benutzer die Zusammenfassung ausgewählt hat. Kann die Navigation abbrechen oder eine benutzerdefinierte Zusammenfassung bereitstellen.\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\nSiehe `SessionBeforeTreeEvent` und `TreePreparation` in der Typendatei.\n\n## Einstellungen\n\nKomprimierung in `~/.pi/agent/settings.json` oder `<project-dir>/.pi/settings.json` konfigurieren:\n\n```json\n{\n  \"compaction\": {\n    \"enabled\": true,\n    \"reserveTokens\": 16384,\n    \"keepRecentTokens\": 20000\n  }\n}\n```\n\n| Einstellung | Standard | Beschreibung |\n|---------|---------|-------------|\n| `enabled` | `true` | Aktivieren Sie die automatische Komprimierung |\n| `reserveTokens` | `16384` | Für die LLM-Antwort zu reservierende Token |\n| `keepRecentTokens` | `20000` | Kürzlich zu behaltende Token (nicht zusammengefasst) |\n\nDeaktivieren Sie die automatische Komprimierung mit `\"enabled\": false`. Sie können weiterhin manuell mit `/compact` komprimieren.","sourceFile":"compaction.md"},"containerization":{"title":"Containerisierung","markdown":"Pi läuft standardmäßig mit allen Berechtigungen, aber in manchen Fällen möchten Sie mehr Kontrolle darüber haben, in welche Verzeichnisse Pi schreiben kann und welche Zugriffe es hat.\n\nEs gibt zwei allgemeine Optionen. Sie können entweder\n1. Führen Sie den gesamten `pi`-Prozess in einer isolierten Umgebung aus, oder\n2. Führen Sie `pi` auf dem Host aus und leiten Sie die Tool-Ausführung in eine isolierte Umgebung weiter.\n\n## Wählen Sie ein Muster\n\n| Muster | Was ist isoliert | Am besten für | Notizen |\n| --- | --- | --- | --- |\n| Gondolin Erweiterung | Integrierte Tools und `!` Befehle | Lokale Mikro-VM-Isolierung unter Beibehaltung der Authentifizierung auf dem Host | Siehe [`examples/extensions/gondolin/`](../examples/extensions/gondolin/). |\n| Einfach Docker | Gesamter `pi`-Prozess in einem lokalen Container | Einfache lokale Isolierung | Anbieter API keys betreten den Container. |\n| OpenShell | Gesamter `pi`-Prozess in einem richtliniengesteuerten sandbox | Lokal oder remote verwaltet sandbox | Erfordert ein OpenShell Gateway |\n\nExtensions wird überall dort ausgeführt, wo der `pi`-Prozess ausgeführt wird. Wenn Sie Host `pi` mit einer Tool-Routing-Erweiterung ausführen, werden andere benutzerdefinierte Erweiterungstools weiterhin auf dem Host ausgeführt, sofern sie ihre Vorgänge nicht ebenfalls delegieren.\n\n## Gondolin\n\n[Gondolin](https://github.com/earendil-works/gondolin) ist eine lokale Linux-Mikro-VM.\nVerwenden Sie [example extension](../examples/extensions/gondolin), wenn Sie `pi` auf dem Host möchten, aber alle integrierten Tools an die VM weitergeleitet werden sollen.\n\nAufstellen:\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\nFühren Sie das Projekt aus, das Sie bereitstellen möchten:\n\n```bash\ncd /path/to/project\npi -e ~/.pi/agent/extensions/gondolin\n```\n\nDie Erweiterung mountet den Host-CWD bei `/workspace` in der VM und überschreibt `read`, `write`, `edit`, `bash`, `grep`, `find` und `ls`.\nBenutzerbefehle `!` werden ebenfalls an die VM weitergeleitet.\nDateiänderungen unter `/workspace` werden auf den Host übertragen.\n\nAnforderungen: Node.js >= 23.6.0 für `@earendil-works/gondolin`, plus QEMU (erfordert die Installation über Ihren Paketmanager).\n\n## Einfach Docker\n\nFühren Sie den gesamten `pi`-Prozess in Docker aus, wenn Sie die einfachste lokale Containergrenze wünschen.\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\nErstellen und ausführen:\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\nMit `-v \"$PWD:/workspace\"` wird Ihr aktuelles Verzeichnis im Container unter /workspace bereitgestellt, sodass sich Lese- und Schreibvorgänge in `/workspace` in Docker direkt auf Ihre Hostdateien auswirken, wie im Beispiel Gondolin.\n\nVerwenden Sie ein benanntes Volume für `/root/.pi/agent`, wenn Sie Container-lokale Einstellungen und Sitzungen wünschen. Durch das Mounten Ihres Hosts `~/.pi/agent` werden Host-Authentifizierungs- und Sitzungsdateien für den Container verfügbar gemacht.\n\n## OpenShell\n\nVerwenden Sie [NVIDIA OpenShell](https://docs.nvidia.com/openshell/about/overview), wenn Sie ein richtliniengesteuertes sandbox mit Dateisystem-, Prozess-, Netzwerk-, Anmeldeinformations- und Rückschlusskontrollen wünschen.\nOpenShell kann sandboxes über ein lokales Gateway ausführen, das von Docker, Podman oder einer VM-Laufzeit unterstützt wird, oder über ein Remote-Kubernetes-Gateway.\n\nJeder sandbox erfordert ein aktives Gateway.\nRegistrieren Sie sich und wählen Sie eines aus, bevor Sie ein sandbox erstellen:\n\n```bash\nopenshell gateway add <gateway-url> --name <name>\nopenshell gateway select <name>\n```\n\nStarten Sie `pi` in einem OpenShell sandbox:\n\n```bash\nopenshell sandbox create --name pi-sandbox --from pi -- pi\n```\n\nIn diesem Muster läuft der gesamte `pi`-Prozess innerhalb von sandbox ab.\nIntegrierte Tools, `!`-Befehle und Erweiterungstools werden innerhalb der OpenShell-Grenze ausgeführt.\n\nWenn das Gateway entfernt ist, werden Projektdateien nicht vom Host gebunden, was bedeutet, dass Schreibvorgänge im sandbox nicht auf Ihrem Computer widergespiegelt werden.\nKlonen Sie das Repository in sandbox oder verwenden Sie OpenShell Dateiübertragungsbefehle:\n\n```bash\nopenshell sandbox upload pi-sandbox ./repo /workspace\nopenshell sandbox download pi-sandbox /workspace/repo ./repo-out\n```\n\nOpenShell-Anbieter können Rohmodell-API keys außerhalb von sandbox behalten.\nWenn das Inferenz-Routing konfiguriert ist, kann Code innerhalb von sandbox `https://inference.local` aufrufen, und das Gateway fügt die konfigurierten Anbieteranmeldeinformationen stromaufwärts ein.\nKonfigurieren Sie Pi, um den entsprechenden OpenAI-kompatiblen oder Anthropic-kompatiblen Endpunkt zu verwenden, wenn Sie möchten, dass der Modellverkehr diese Route verwenden soll.","sourceFile":"containerization.md"},"custom-provider":{"title":"Benutzerdefiniert Providers","markdown":"Extensions kann benutzerdefinierte Modellanbieter über `pi.registerProvider()` registrieren. Dies ermöglicht:\n\n- **Proxys** – Leiten Sie Anfragen über Unternehmens-Proxys oder API Gateways weiter\n- **Benutzerdefinierte Endpunkte** – Verwenden Sie selbstgehostete oder private Modellbereitstellungen\n- **OAuth/SSO** – Authentifizierungsflüsse für Unternehmensanbieter hinzufügen\n- **Benutzerdefinierte APIs** – Implementieren Sie Streaming für nicht standardmäßige LLM APIs\n\n## Beispiel Extensions\n\nSehen Sie sich diese vollständigen Anbieterbeispiele an:\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## Inhaltsverzeichnis\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## Kurzreferenz\n\nExtensions kann entweder ein vollständiges Pi-AI `Provider` registrieren oder das alte Provider-Config-Formular verwenden. Bevorzugen Sie einen Komplettanbieter, wenn benutzerdefiniertes Authentifizierungs-, Filter-, Aktualisierungs- oder Streaming-Verhalten erforderlich ist. Pi erstellt `models.json` Überschreibungen über registrierten nativen Anbietern.\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\nDie Erweiterungsfabrik kann auch `async` sein. Für die dynamische Modellerkennung holen und registrieren Sie Modelle in der Fabrik statt `session_start`. pi wartet auf die Factory, bevor der Startvorgang fortgesetzt wird, sodass der Anbieter während des interaktiven Startvorgangs und für `pi --list-models` verfügbar ist.\n\n## Vorhandenen Anbieter überschreiben\n\nDer einfachste Anwendungsfall: Einen bestehenden Anbieter über einen Proxy umleiten.\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\nWenn nur `baseUrl` und/oder `headers` bereitgestellt werden (kein `models`), bleiben alle vorhandenen Modelle für diesen Anbieter mit dem neuen Endpunkt erhalten.\n\n## Neuen Anbieter registrieren\n\nUm einen völlig neuen Anbieter hinzuzufügen, geben Sie `models` zusammen mit der erforderlichen Konfiguration an.\n\nWenn die Modellliste von einem Remote-Endpunkt stammt, verwenden Sie eine asynchrone Erweiterungsfactory:\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\nDadurch werden die abgerufenen Modelle registriert, bevor der Startvorgang abgeschlossen ist.\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\nWenn `models` bereitgestellt wird, **ersetzt** es alle vorhandenen Modelle für diesen Anbieter.\n\n`apiKey` und benutzerdefinierte Header-Werte verwenden dieselbe Konfigurationswertsyntax wie `models.json`: `!command` führt beim Start einen Befehl für den gesamten Wert aus, `$ENV_VAR` und `${ENV_VAR}` interpolieren Umgebungsvariablen, `$` gibt ein Literal ``apiKey` und benutzerdefinierte Header-Werte verwenden dieselbe Konfigurationswertsyntax wie `models.json`: `!command` führt beim Start einen Befehl für den gesamten Wert aus, `$ENV_VAR` und `${ENV_VAR}` interpolieren Umgebungsvariablen, `$` gibt ein Literal  aus und `$!` gibt ein Literal aus `!`.\n\n## Anbieter abmelden\n\nMit `pi.unregisterProvider(name)` können Sie einen Anbieter entfernen, der zuvor über `pi.registerProvider(name,...)` registriert wurde:\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\nDurch die Aufhebung der Registrierung werden die dynamischen Modelle, API key Fallback, OAuth Anbieterregistrierung und benutzerdefinierte Stream-Handler-Registrierungen dieses Anbieters entfernt. Alle integrierten Modelle oder Anbieterverhalten, die überschrieben wurden, werden wiederhergestellt.\n\nAnrufe, die nach der ersten Ladephase der Erweiterung getätigt werden, werden sofort angewendet, sodass kein `/reload` erforderlich ist.\n\n### API Typen\n\nDas Feld `api` bestimmt, welche Streaming-Implementierung verwendet wird:\n\n| API | Verwendung für |\n|-----|---------|\n| `anthropic-messages` | Anthropic Claude API und kompatible |\n| `openai-completions` | OpenAI-Chat-Abschlüsse API und kompatible |\n| `openai-responses` | OpenAI-Antworten API |\n| `azure-openai-responses` | Azure OpenAI-Antworten API |\n| `openai-codex-responses` | OpenAI-Codex-Antworten API |\n| `mistral-conversations` | Native Mistral Chat Completions-Streaming |\n| `google-generative-ai` | Generative KI von Google API |\n| `google-vertex` | Google Vertex AI API |\n| `bedrock-converse-stream` | Amazon Bedrock Converse API |\n\nDie meisten OpenAI-kompatiblen Anbieter arbeiten mit `openai-completions`. Verwenden Sie die Modellebene `thinkingLevelMap` für modellspezifische Denkebenen und `compat` für Anbieter-Eigenheiten. Die Ebenen `xhigh` und `max` sind optional, erfordern Nicht-Null-Karteneinträge und können durch nicht unterstützte Lücken getrennt sein:\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\nVerwenden Sie `openrouter` für `reasoning: { effort }`-Steuerelemente im OpenRouter-Stil. Verwenden Sie `together` für `reasoning: { enabled }`-Steuerelemente im Together-Stil. mit `supportsReasoningEffort` sendet es auch `reasoning_effort`. Verwenden Sie `qwen-chat-template` für lokale Qwen-kompatible Server, die `chat_template_kwargs.enable_thinking` lesen und `preserve_thinking` benötigen.\nVerwenden Sie `cacheControlFormat: \"anthropic\"` für OpenAI-kompatible Anbieter, die Eingabeaufforderungs-Caching im Anthropic-Stil über `cache_control` für die Systemeingabeaufforderung, die letzte Tooldefinition und den Textinhalt des letzten Benutzers, Assistenten oder Toolergebnisses verfügbar machen.\n\nFür Anthropic-kompatible Anbieter, die `api: \"anthropic-messages\"` verwenden, setzen Sie `compat.forceAdaptiveThinking: true` für Modelle oder Anbieter, deren Upstream-Modell adaptives Denken erfordert (`thinking.type: \"adaptive\"` plus `output_config.effort`). Integrierte adaptive Claude-Modelle stellen dies automatisch ein. Legen Sie `compat.allowEmptySignature: true` nur für Anbieter fest, die leere Denksignaturen aussenden und bei der Wiedergabe `signature: \"\"` erwarten.\n\n> Migrationshinweis: Mistral ist von `openai-completions` auf `mistral-conversations` umgezogen.\n> Verwenden Sie `mistral-conversations` für native Mistral-Modelle.\n> Wenn Sie Mistral-kompatible/benutzerdefinierte Endpunkte absichtlich über `openai-completions` weiterleiten, legen Sie die `compat`-Flags explizit nach Bedarf fest.\n\n### Auth-Header\n\nWenn Ihr Anbieter `Authorization: Bearer <key>` erwartet, aber keinen Standard API verwendet, legen Sie `authHeader: true` fest:\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\nDer Schlüssel wird für jede Anfrage aufgelöst. Ein expliziter Anforderungsheader `Authorization` hat Vorrang vor dem generierten Wert.\n\n## OAuth Unterstützung\n\nFügen Sie die OAuth/SSO-Authentifizierung hinzu, die in `/login` integriert ist:\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\nNach der Registrierung können sich Benutzer über `/login corporate-ai` authentifizieren.\n\n### OAuthLoginCallbacks\n\nDas `callbacks`-Objekt stellt UI-neutrale Interaktionen für den anbietereigenen Flow bereit:\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### OAuthAnmeldeinformationen\n\nAnmeldeinformationen bleiben in `~/.pi/agent/auth.json` erhalten:\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## Benutzerdefiniertes Streaming API\n\nImplementieren Sie für Anbieter mit nicht standardmäßigen APIs `streamSimple`. Studieren Sie die vorhandenen Anbieterimplementierungen, bevor Sie Ihre eigene schreiben:\n\n**Referenzimplementierungen:**\n- [anthropic.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/anthropic.ts) – Anthropische Botschaften API\n- [mistral.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/mistral.ts) – Mistral-Gespräche API\n- [openai-completions.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/openai-completions.ts) – OpenAI-Chat-Abschlüsse\n- [openai-responses.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/openai-responses.ts) – OpenAI-Antworten API\n- [google.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/google.ts) – Google Generative AI\n- [amazon-bedrock.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/amazon-bedrock.ts) – AWS-Grundgestein\n\n### Stream-Muster\n\nAlle Anbieter folgen dem gleichen Muster:\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### Ereignistypen\n\nPush-Ereignisse über `stream.push()` in dieser Reihenfolge:\n\n1. `{ type: \"start\", partial: output }` – Stream gestartet\n\n2. Inhaltsereignisse (wiederholbar, Spur `contentIndex` für jeden Block):\n   - `{ type: \"text_start\", contentIndex, partial }` – Textblock gestartet\n   - `{ type: \"text_delta\", contentIndex, delta, partial }` – Textblock\n   - `{ type: \"text_end\", contentIndex, content, partial }` – Textblock beendet\n   - `{ type: \"thinking_start\", contentIndex, partial }` - Das Nachdenken hat begonnen\n   - `{ type: \"thinking_delta\", contentIndex, delta, partial }` – Denkblock\n   - `{ type: \"thinking_end\", contentIndex, content, partial }` – Das Denken ist beendet\n   - `{ type: \"toolcall_start\", contentIndex, partial }` – Werkzeugaufruf gestartet\n   - `{ type: \"toolcall_delta\", contentIndex, delta, partial }` – Werkzeugaufruf JSON Chunk\n   - `{ type: \"toolcall_end\", contentIndex, toolCall, partial }` – Werkzeugaufruf beendet\n\n3. `{ type: \"done\", reason, message }` oder `{ type: \"error\", reason, error }` – Stream beendet\n\nDas `partial`-Feld in jedem Ereignis enthält den aktuellen `AssistantMessage`-Status. Aktualisieren Sie `output.content`, sobald Sie Daten erhalten, und fügen Sie dann `output` als `partial` hinzu.\n\n### Inhaltsblöcke\n\nFügen Sie Inhaltsblöcke zu `output.content` hinzu, sobald sie eintreffen:\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### Werkzeugaufrufe\n\nToolaufrufe erfordern das Sammeln von JSON und das Parsen:\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### Nutzung und Kosten\n\nAktualisieren Sie die Nutzung von API Antwort und berechnen Sie die Kosten:\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### Kontextüberlauffehler\n\nWenn eine Anfrage das Kontextfenster des Modells überschreitet, kann Pi automatisch wiederhergestellt werden, indem die Konversation komprimiert und erneut versucht wird. Diese Wiederherstellung setzt nur dann ein, wenn Pi den Fehler als Überlauf erkennt.\n\nDie Erkennung erfolgt anhand der finalisierten Assistentennachricht:\n\n- `stopReason === \"error\"`\n- `errorMessage` entspricht einem der bekannten Überlaufmuster von pi (siehe [`packages/ai/src/utils/overflow.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/utils/overflow.ts))\n\nWenn Ihr Anbieter Überlauffehler mit einer Meldung zurückgibt, die pi nicht erkennt, normalisieren Sie den Fehler über dieselbe Erweiterung, die den Anbieter registriert. Verwenden Sie einen `message_end`-Handler, um die Assistentennachricht so umzuschreiben, dass ihre `errorMessage` mit einer Phrase beginnt, die pi erkennt. Der generische Fallback `context_length_exceeded` ist die sicherste Wahl.\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` wird ausgeführt, bevor Pi die Assistentenmeldung für die automatische Komprimierung verfolgt, sodass Pi das umgeschriebene `errorMessage` prüft. Wenn dies eingerichtet ist, wird pi:\n\n1. Erkennen Sie den Überlauf von `errorMessage`.\n2. Löschen Sie die fehlgeschlagene Assistentennachricht aus dem Live-Kontext.\n3. Führen Sie die Komprimierung aus.\n4. Wiederholen Sie die Anfrage einmal.\n\nBewahren Sie die Umschreibung sorgfältig auf:\n\n- Ordnen Sie es Ihrem Provider zu (`message.provider` und `ctx.model?.provider`), sodass unabhängige Fehler von anderen Providern unberührt bleiben.\n- Entspricht einem anbieterspezifischen Muster, nicht den generischen Überlaufmustern von pi. Das Umschreiben von Ratenbegrenzungs- oder Drosselungsfehlern (`rate limit`, `too many requests`) würde fälschlicherweise eine Komprimierung anstelle des normalen Pi-Wiederholungspfads mit Backoff auslösen.\n- Überspringen, wenn `errorMessage` bereits `context_length_exceeded` enthält, sodass der Handler idempotent ist.\n\n### Anmeldung\n\nRegistrieren Sie Ihre Stream-Funktion:\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## Testen Sie Ihre Implementierung\n\nTesten Sie Ihren Anbieter anhand derselben Testsuiten, die auch von integrierten Anbietern verwendet werden. Kopieren Sie diese Testdateien von [packages/ai/test/](https://github.com/earendil-works/pi-mono/tree/main/packages/ai/test) und passen Sie sie an:\n\n| Prüfen | Zweck |\n|------|---------|\n| `stream.test.ts` | Grundlegendes Streaming, Textausgabe |\n| `tokens.test.ts` | Token-Zählung und -Nutzung |\n| `abort.test.ts` | AbortSignal-Behandlung |\n| `empty.test.ts` | Leere/minimale Antworten |\n| `context-overflow.test.ts` | Grenzen des Kontextfensters |\n| `image-limits.test.ts` | Handhabung der Bildeingabe |\n| `unicode-surrogate.test.ts` | Unicode-Randfälle |\n| `tool-call-without-result.test.ts` | Randfälle von Werkzeugaufrufen |\n| `image-tool-result.test.ts` | Bilder in Tool-Ergebnissen |\n| `total-tokens.test.ts` | Gesamt-Token-Berechnung |\n| `cross-provider-handoff.test.ts` | Kontextübergabe zwischen Anbietern |\n\nFühren Sie Tests mit Ihren Anbieter-/Modellpaaren durch, um die Kompatibilität zu überprüfen.\n\n## Konfigurationsreferenz\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## Modelldefinitionsreferenz\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` sendet `reasoning: { effort }`. `deepseek` sendet `thinking: { type: \"enabled\" | \"disabled\" }` und `reasoning_effort`, wenn aktiviert. `together` sendet `reasoning: { enabled }` und auch `reasoning_effort`, wenn `supportsReasoningEffort` aktiviert ist. `qwen` steht für die oberste Ebene im DashScope-Stil `enable_thinking`. Verwenden Sie `qwen-chat-template` für lokale Qwen-kompatible Server, die `chat_template_kwargs.enable_thinking` lesen und `preserve_thinking` benötigen. Verwenden Sie `chat-template` für konfigurierbare `chat_template_kwargs`, zum Beispiel DeepSeek V3.x hinter vLLM mit `chatTemplateKwargs: { \"thinking\": { \"$var\": \"thinking.enabled\" } }`. Verwenden Sie `thinkingFormat: \"baseten\"` mit `chatTemplateArgs`, wenn der Anbieter Umschaltwerte unter `chat_template_args` erwartet und optional `reasoning_effort` der obersten Ebene unterstützt.\n`cacheControlFormat: \"anthropic\"` wendet `cache_control`-Markierungen im Anthropic-Stil auf die Systemeingabeaufforderung, die letzte Werkzeugdefinition und den Textinhalt des letzten Benutzers, Assistenten oder Werkzeugergebnisses an.","sourceFile":"custom-provider.md"},"development":{"title":"Entwicklung","markdown":"Weitere Richtlinien finden Sie unter [AGENTS.md](https://github.com/earendil-works/pi-mono/blob/main/AGENTS.md).\n\n## Aufstellen\n\n```bash\ngit clone https://github.com/earendil-works/pi-mono\ncd pi-mono\nnpm install\nnpm run build\n```\n\nVon der Quelle ausführen:\n\n```bash\n/path/to/pi-mono/pi-test.sh\n```\n\nDas Skript kann von jedem Verzeichnis aus ausgeführt werden. Pi behält das aktuelle Arbeitsverzeichnis des Anrufers.\n\n## Forking / Rebranding\n\nKonfigurieren über `package.json`:\n\n```json\n{\n  \"piConfig\": {\n    \"name\": \"pi\",\n    \"configDir\": \".pi\"\n  }\n}\n```\n\nÄndern Sie die Felder `name`, `configDir` und `bin` für Ihren Fork. Betrifft CLI Banner, Konfigurationspfade und Umgebungsvariablennamen.\n\n## Pfadauflösung\n\nDrei Ausführungsmodi: npm Installation, eigenständige Binärdatei, tsx von der Quelle.\n\n**Verwenden Sie immer `src/config.ts`** für Paket-Assets:\n\n```typescript\nimport { getPackageDir, getThemeDir } from \"./config.js\";\n```\n\nVerwenden Sie `__dirname` niemals direkt für Paket-Assets.\n\n## Debug-Befehl\n\n`/debug` (versteckt) schreibt an `~/.pi/agent/pi-debug.log`:\n- Gerenderte TUI-Zeilen mit ANSI-Codes\n- Letzte an das LLM gesendete Nachrichten\n\n## Testen\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## Projektstruktur\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":"Umgebungsvariablen","markdown":"Pi verwendet Umgebungsvariablen auf drei Arten:\n\n- Variablen wie `PI_OFFLINE` konfigurieren den Pi-Prozess.\n- Pi setzt `PI_CODING_AGENT`, damit untergeordnete Prozesse erkennen können, dass sie in Pi ausgeführt werden.\n- Befehle, die vom LLM-aufrufbaren bash-Tool ausgeführt werden, empfangen `PI_*` Variablen, die die aktuelle Sitzung beschreiben.\n\nProvider-API-Schlüsselvariablen werden separat in [Providers](providers.md#environment-variables-or-auth-file) dokumentiert.\n\n## Prozessmarker\n\nDie Einstiegspunkte CLI und RPC setzen `PI_CODING_AGENT=true`. Untergeordnete Prozesse erben es und können damit erkennen, dass sie in Pi ausgeführt werden. Es ist nicht sitzungsspezifisch und wird nicht automatisch festgelegt, wenn Pi über SDK eingebettet wird.\n\n## Bash-Tool-Sitzungsumgebung\n\nBefehle, die vom bash-Tool ausgeführt werden, erhalten den aktuellen Pi-Sitzungsstatus:\n\n| Variable | Beschreibung |\n|----------|-------------|\n| `PI_SESSION_ID` | Aktuelle Sitzungs-ID |\n| `PI_SESSION_FILE` | Absoluter Pfad zur aktuellen Sitzungsdatei JSONL; Für kurzlebige Sitzungen deaktiviert |\n| `PI_PROVIDER` | Aktuell ausgewählter Modellanbieter |\n| `PI_MODEL` | Aktuell ausgewählte Modell-ID |\n| `PI_REASONING_LEVEL` | Aktuelles Niveau des effektiven Denkens: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` oder `max` |\n\nDie Werte werden beim Start jedes Befehls aufgelöst. Ein Modellwechsel oder eine Änderung der Argumentationsebene wirkt sich daher auf den nächsten bash-Befehl aus, ohne dass Pi neu gestartet werden muss. `PI_PROVIDER` und `PI_MODEL` identifizieren das ausgewählte Pi Modell, nicht ein anderes Upstream-Modell, das ein Router intern auswählen kann.\n\nWenn Sie gefragt werden, welches Modell oder welcher Anbieter ausgeführt wird, überprüfen Sie diese Variablen, anstatt die Antwort aus der Systemeingabeaufforderung abzuleiten:\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\nDie Sitzungsdatei kann direkt überprüft werden, wenn die Sitzung dauerhaft ist:\n\n```bash\nif [ -n \"$PI_SESSION_FILE\" ]; then\n  tail -n 1 \"$PI_SESSION_FILE\"\nfi\n```\n\nDiese Variablen werden in das LLM-aufrufbare bash-Tool eingefügt. Sie werden nicht in vom Benutzer eingegebene `!`- oder `!!`-Befehle eingefügt.\n\n### Benutzerdefinierte Bash-Tools\n\nMit `createBashTool()` erstellte Bash-Tools legen die Sitzungsumgebung standardmäßig offen, wenn sie mit Pi registriert werden. Die Injektion erfolgt vor `spawnHook`, sodass ein Hook die Variablen in `ctx.env` empfängt:\n\n```typescript\nconst bashTool = createBashTool(cwd, {\n  spawnHook: (ctx) => ({\n    ...ctx,\n    env: { ...ctx.env, CI: \"1\" },\n  }),\n});\n```\n\nSitzungsmetadaten unabhängig vom Spawn-Hook deaktivieren:\n\n```typescript\nconst bashTool = createBashTool(cwd, {\n  exposeSessionEnvironment: false,\n  spawnHook: (ctx) => ctx,\n});\n```\n\nWenn die Option deaktiviert ist, entfernt Pi geerbte Werte für diese Variablen, sodass verschachtelte Pi-Prozesse keine veralteten Metadaten der übergeordneten Sitzung offenlegen.\n\n## Pi Prozesskonfiguration\n\nDiese Variablen werden von Pi selbst gelesen:\n\n| Variable | Beschreibung |\n|----------|-------------|\n| `PI_CODING_AGENT_DIR` | Überschreiben Sie das Konfigurationsverzeichnis. Standard ist `~/.pi/agent` |\n| `PI_CODING_AGENT_SESSION_DIR` | Sitzungsspeicher überschreiben; überschrieben durch `--session-dir` |\n| `PI_PACKAGE_DIR` | Überschreiben Sie das Paketverzeichnis, nützlich für Nix/Guix-Speicherpfade |\n| `PI_OFFLINE` | Deaktivieren Sie Startnetzwerkvorgänge, einschließlich Updateprüfungen, Paketaktualisierungen und Installations-/Update-Telemetrie |\n| `PI_SKIP_VERSION_CHECK` | Deaktivieren Sie die Anforderung der neuesten Version `pi.dev` |\n| `PI_TELEMETRY` | Überschreiben Sie Installations-/Update-Telemetrie- und Anbieterzuordnungsheader: `1`/`true`/`yes` oder `0`/`false`/`no` |\n| `PI_CACHE_RETENTION` | Auf `long` für erweitertes Provider-Prompt-Caching setzen, sofern unterstützt |\n| `PI_SHARE_VIEWER_URL` | Überschreiben Sie die von `/share` verwendete Basis-URL |\n| `PI_HARDWARE_CURSOR` | Auf `1` einstellen, um den Hardware-Cursor anzuzeigen; siehe [Terminal setup](terminal-setup.md) |\n| `VISUAL`, `EDITOR` | Externer Editor-Fallback, wenn `externalEditor` nicht gesetzt ist |\n| `HTTP_PROXY`, `HTTPS_PROXY` | Proxy für ausgehende HTTP-Anfragen |\n\nAnbieteranmeldeinformationen wie `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` und die Cloud-Anbieterkonfiguration werden in [Providers](providers.md#environment-variables-or-auth-file) aufgeführt.","sourceFile":"environment-variables.md"},"extensions":{"title":"Extensions","markdown":"> pi kann Erweiterungen erstellen. Bitten Sie es, eines für Ihren Anwendungsfall zu erstellen.\n\n\nExtensions sind TypeScript Module, die das Verhalten von Pi erweitern. Sie können Lebenszyklusereignisse abonnieren, vom LLM aufrufbare benutzerdefinierte Tools registrieren, Befehle hinzufügen und vieles mehr.\n\n> **Platzierung für /reload:** Fügen Sie Erweiterungen für die automatische Erkennung in `~/.pi/agent/extensions/` (global) oder `.pi/extensions/` (projektlokal) ein. Verwenden Sie `pi -e./path.ts` nur für Schnelltests. Extensions an automatisch erkannten Orten kann mit `/reload` im laufenden Betrieb neu geladen werden.\n\n**Hauptfunktionen:**\n- **Benutzerdefinierte Tools** – Registrieren Sie Tools, die das LLM über `pi.registerTool()` aufrufen kann\n- **Ereignisabfang** – Toolaufrufe blockieren oder ändern, Kontext einfügen, Komprimierung anpassen\n- **Benutzerinteraktion** – Benutzer über `ctx.ui` auffordern (auswählen, bestätigen, eingeben, benachrichtigen)\n- **Benutzerdefinierte UI-Komponenten** – Vollständige TUI-Komponenten mit Tastatureingabe über `ctx.ui.custom()` für komplexe Interaktionen\n- **Benutzerdefinierte Befehle** – Registrieren Sie Befehle wie `/mycommand` über `pi.registerCommand()`\n- **Sitzungspersistenz** – Speicherstatus, der Neustarts über `pi.appendEntry()` übersteht\n- **Benutzerdefiniertes Rendering** – Steuern Sie, wie Toolaufrufe/Ergebnisse und Meldungen in TUI angezeigt werden.\n\n**Beispielhafte Anwendungsfälle:**\n- Berechtigungstore (Bestätigung vor `rm -rf`, `sudo` usw.)\n- Git Checkpointing (in jeder Runde verstauen, bei Zweig wiederherstellen)\n- Pfadschutz (Schreibvorgänge auf `.env`, `node_modules/` blockieren)\n- Benutzerdefinierte Komprimierung (Konversation nach Ihren Wünschen zusammenfassen)\n- Gesprächszusammenfassungen (siehe Beispiel `summarize.ts`)\n- Interaktive Tools (Fragen, Assistenten, benutzerdefinierte Dialoge)\n- Zustandsbehaftete Tools (Todo-Listen, Verbindungspools)\n- Externe Integrationen (File Watcher, Webhooks, CI-Trigger)\n- Spiele während du wartest (siehe Beispiel `snake.ts`)\n\nSiehe [examples/extensions/](../examples/extensions/) für funktionierende Implementierungen.\n\n## Inhaltsverzeichnis\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## Schnellstart\n\nErstellen Sie `~/.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\nTest mit `--extension` (oder `-e`) Flag:\n\n```bash\npi -e ./my-extension.ts\n```\n\n## Erweiterungsstandorte\n\n> **Sicherheit:** Extensions wird mit Ihren vollständigen Systemberechtigungen ausgeführt und kann beliebigen Code ausführen. Installieren Sie nur von Quellen, denen Sie vertrauen.\n\nExtensions werden von vertrauenswürdigen Standorten automatisch erkannt. Projektlokale `.pi/extensions`-Einträge werden erst geladen, nachdem das Projekt vertrauenswürdig ist.\n\n| Standort | Umfang |\n|----------|-------|\n| `~/.pi/agent/extensions/*.ts` | Global (alle Projekte) |\n| `~/.pi/agent/extensions/*/index.ts` | Global (Unterverzeichnis) |\n| `.pi/extensions/*.ts` | Projektlokal |\n| `.pi/extensions/*/index.ts` | Projektlokal (Unterverzeichnis) |\n\nZusätzliche Pfade über `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\nInformationen zum Teilen von Erweiterungen über npm oder Git als Pi-Pakete finden Sie unter [packages.md](packages.md).\n\n## Verfügbare Importe\n\n| Paket | Zweck |\n|---------|---------|\n| `@earendil-works/pi-coding-agent` | Erweiterungstypen (`ExtensionAPI`, `ExtensionContext`, Ereignisse) |\n| `typebox` | Schemadefinitionen für Werkzeugparameter |\n| `@earendil-works/pi-ai` | KI-Dienstprogramme (`StringEnum` für Google-kompatible Aufzählungen) |\n| `@earendil-works/pi-tui` | TUI Komponenten für benutzerdefiniertes Rendering |\n\nnpm Abhängigkeiten funktionieren auch. Fügen Sie ein `package.json` neben Ihrer Erweiterung (oder in einem übergeordneten Verzeichnis) hinzu, führen Sie `npm install` aus und Importe aus `node_modules/` werden automatisch aufgelöst.\n\nFür verteilte Pi-Pakete, die mit `pi install` (npm oder Git) installiert wurden, müssen sich die Laufzeitdeps in `dependencies` befinden. Bei der Paketinstallation werden standardmäßig Produktionsinstallationen (`npm install --omit=dev`) verwendet, sodass `devDependencies` zur Laufzeit nicht verfügbar sind. Wenn `npmCommand` konfiguriert ist, verwenden Git-Pakete einfaches `install` für die Kompatibilität mit Wrappern.\n\nNode.js integrierte Funktionen (`node:fs`, `node:path` usw.) sind ebenfalls verfügbar.\n\n## Eine Erweiterung schreiben\n\nEine Erweiterung exportiert eine Standard-Factory-Funktion, die `ExtensionAPI` empfängt. Die Factory kann synchron oder asynchron sein:\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 werden über [jiti](https://github.com/unjs/jiti) geladen, daher funktioniert TypeScript ohne Kompilierung.\n\nWenn die Fabrik eine `Promise` zurückgibt, wartet Pi darauf, bevor es mit dem Start fortfährt. Das bedeutet, dass die asynchrone Initialisierung vor `session_start`, vor `resources_discover` und bevor über `pi.registerProvider()` in die Warteschlange gestellte Anbieterregistrierungen gelöscht werden.\n\n### Asynchrone Factory-Funktionen\n\nVerwenden Sie eine asynchrone Factory für einmalige Startarbeiten wie das Abrufen der Remote-Konfiguration oder das dynamische Erkennen verfügbarer Modelle.\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\nDieses Muster stellt die abgerufenen Modelle während des normalen Startvorgangs und für `pi --list-models` zur Verfügung.\n\n### Langlebige Ressourcen und Herunterfahren\n\nErweiterungsfabriken können in Aufrufen ausgeführt werden, die nie eine Sitzung starten. Starten Sie keine Hintergrundressourcen wie Prozesse, Sockets, Dateibeobachter oder Timer ab Werk.\n\nVerschieben Sie den Start der Hintergrundressource bis `session_start` oder den Befehl/das Tool/das Ereignis, das die Ressource benötigt. Registrieren Sie einen idempotenten `session_shutdown`-Handler, um alle von Ihnen gestarteten sitzungsbezogenen Ressourcen zu schließen.\n\n### Erweiterungsstile\n\n**Einzelne Datei** – am einfachsten, für kleine Erweiterungen:\n\n```\n~/.pi/agent/extensions/\n└── my-extension.ts\n```\n\n**Verzeichnis mit index.ts** – für Erweiterungen mit mehreren Dateien:\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**Paket mit Abhängigkeiten** – für Erweiterungen, die npm Pakete benötigen:\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\nFühren Sie `npm install` im Erweiterungsverzeichnis aus, dann funktionieren Importe aus `node_modules/` automatisch.\n\n## Veranstaltungen\n\n### Lebenszyklusübersicht\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### Startup-Events\n\n#### project_trust\n\nWird ausgelöst, bevor pi entscheidet, ob einem Projekt dynamische Konfigurationen (`.pi` oder `.agents/skills`) vertraut werden sollen. Es wird während des Startvorgangs ausgeführt und wenn beim Sitzungsaustausch (z. B. `/resume`) ein CWD eingegeben wird, dessen Vertrauen im aktuellen Prozess nicht aufgelöst wurde. Es nehmen nur Benutzer-/globale Erweiterungen und CLI `-e` Erweiterungen teil; Projektlokale Erweiterungen werden erst geladen, nachdem die Vertrauensstellung aufgelöst wurde.\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\nEin `project_trust`-Handler muss `{ trusted: \"yes\" | \"no\" | \"undecided\" }` zurückgeben. Eine Benutzer-/Global- oder CLI-Erweiterung, die `\"yes\"` oder `\"no\"` zurückgibt, besitzt die Entscheidung; Die erste Ja/Nein-Entscheidung gewinnt und unterdrückt die integrierte Vertrauensaufforderung. Verwenden Sie `remember: true`, um eine Ja/Nein-Entscheidung beizubehalten; andernfalls gilt es nur für den aktuellen Prozess. Geben Sie `\"undecided\"` zurück, damit spätere Handler oder der integrierte Vertrauensfluss entscheiden können. Überprüfen Sie `ctx.hasUI`, bevor Sie dazu aufgefordert werden. Wenn kein Handler „Ja/Nein“ zurückgibt, wird die normale Vertrauensauflösung fortgesetzt: Gespeicherte `trust.json`-Entscheidungen gelten zuerst, dann `defaultProjectTrust` steuert, ob pi standardmäßig fragt, vertraut oder ablehnt.\n\n### Ressourcenereignisse\n\n#### resources_discover\n\nWird nach `session_start` ausgelöst, damit Erweiterungen zusätzliche Fähigkeiten, Eingabeaufforderungen und Themenpfade beitragen können.\nDer Startpfad verwendet `reason: \"startup\"`. Beim Neuladen wird `reason: \"reload\"` verwendet.\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### Sitzungsereignisse\n\nSiehe [Session Format](session-format.md) für interne Informationen zum Sitzungsspeicher und zum SessionManager API.\n\n#### session_start\n\nWird ausgelöst, wenn eine Sitzung gestartet, geladen oder neu geladen wird.\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\nWird ausgelöst, wenn der Anzeigename der aktuellen Sitzung über `/name`, RPC oder `pi.setSessionName()` festgelegt wird.\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#### session_before_switch\n\nWird vor dem Starten einer neuen Sitzung (`/new`) oder dem Wechseln der Sitzung (`/resume`) ausgelöst.\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\nNach einem erfolgreichen Wechsel oder einer neuen Sitzungsaktion gibt pi `session_shutdown` für die alte Erweiterungsinstanz aus, lädt Erweiterungen für die neue Sitzung neu und bindet sie neu und gibt dann `session_start` mit `reason: \"new\" | \"resume\"` und `previousSessionFile` aus.\nFühren Sie Bereinigungsarbeiten in `session_shutdown` durch und stellen Sie dann alle In-Memory-Zustände in `session_start` wieder her.\n\n#### session_before_fork\n\nWird beim Forken über `/fork` oder Klonen über `/clone` ausgelöst.\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\nNach einem erfolgreichen Fork oder Klon gibt Pi `session_shutdown` für die alte Erweiterungsinstanz aus, lädt und bindet Erweiterungen für die neue Sitzung neu und gibt dann `session_start` mit `reason: \"fork\"` und `previousSessionFile` aus.\nFühren Sie Bereinigungsarbeiten in `session_shutdown` durch und stellen Sie dann alle In-Memory-Zustände in `session_start` wieder her.\n\n#### session_before_compact / session_compact\n\nAuf Verdichtung abgefeuert. Weitere Informationen finden Sie unter [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#### session_before_tree / session_tree\n\nAusgelöst bei `/tree` Navigation. Siehe [Sessions](sessions.md) für Baumnavigationskonzepte.\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#### session_shutdown\n\nWird ausgelöst, bevor eine gestartete Sitzungslaufzeit abgebrochen wird. Verwenden Sie dies, um Ressourcen zu bereinigen, die von `session_start` oder anderen sitzungsbezogenen Hooks geöffnet wurden.\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### Agentenereignisse\n\n#### before_agent_start\n\nWird ausgelöst, nachdem der Benutzer eine Eingabeaufforderung übermittelt hat, vor der Agentenschleife. Kann eine Nachricht einfügen und/oder die Systemaufforderung ändern.\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\nDas Feld `systemPromptOptions` gibt Erweiterungen Zugriff auf dieselben strukturierten Daten, die Pi zum Erstellen der Systemeingabeaufforderung verwendet. Auf diese Weise können Sie überprüfen, was Pi geladen hat – benutzerdefinierte Eingabeaufforderungen, Richtlinien, Tool-Snippets, context files, Fertigkeiten –, ohne Ressourcen erneut zu entdecken oder Flags erneut zu analysieren. Verwenden Sie es, wenn Ihre Erweiterung tiefgreifende, fundierte Änderungen an der Systemeingabeaufforderung unter Berücksichtigung der vom Benutzer bereitgestellten Konfiguration vornehmen muss.\n\nInnerhalb von `before_agent_start` spiegeln `event.systemPrompt` und `ctx.getSystemPrompt()` beide die verkettete Systemaufforderung ab dem aktuellen Handler wider. Spätere `before_agent_start`-Handler können es immer noch erneut ändern.\n\n#### agent_start / agent_end / agent_settled\n\n`agent_start` wird ausgelöst, wenn die Ausführung eines Agenten auf niedriger Ebene beginnt. `agent_end` wird ausgelöst, wenn die Ausführung endet, aber Pi kann es dennoch automatisch wiederholen, automatisch komprimieren und erneut versuchen oder mit in der Warteschlange befindlichen Folgenachrichten fortfahren. Verwenden Sie `agent_settled` für Statusintegrationen, die wissen müssen, dass Pi nicht automatisch weiter ausgeführt wird.\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#### turn_start / turn_end\n\nWird für jede Runde abgefeuert (eine LLM-Antwort + Werkzeugaufrufe).\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#### message_start / message_update / message_end\n\nAusgelöst wegen Aktualisierungen des Nachrichtenlebenszyklus.\n\n- `message_start` und `message_end` werden für Benutzer-, Assistenten- und ToolResult-Nachrichten ausgelöst.\n- `message_update` wird für Assistenten-Streaming-Updates ausgelöst.\n- `message_end`-Handler können `{ message }` zurückgeben, um die endgültige Nachricht zu ersetzen. Der Ersatz muss gleich bleiben `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#### tool_execution_start / tool_execution_update / tool_execution_end\n\nAusgelöst wegen Aktualisierungen des Tool-Ausführungslebenszyklus.\n\nIm Parallelwerkzeugmodus:\n- `tool_execution_start` wird während der Preflight-Phase in der Reihenfolge der Hilfsquellen ausgegeben\n- `tool_execution_update` Ereignisse können sich über mehrere Tools hinweg verschachteln\n- `tool_execution_end` wird in der Reihenfolge der Werkzeugvervollständigung ausgegeben, nachdem jedes Werkzeug fertiggestellt wurde\n- Letzte `toolResult` Nachrichtenereignisse werden später weiterhin in der Reihenfolge der Assistentenquelle ausgegeben\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#### Kontext\n\nWird vor jedem LLM-Aufruf ausgelöst. Ändern Sie Nachrichten zerstörungsfrei. Informationen zu Nachrichtentypen finden Sie unter [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\nWird ausgelöst, nachdem die ausgehenden HTTP-Header zusammengestellt wurden. Verwenden Sie es, um Anforderungsheader hinzuzufügen, zu überschreiben oder zu entfernen.\n\nHandler mutieren `event.headers` an Ort und Stelle. Legen Sie einen Schlüssel auf eine Zeichenfolge fest, um sie hinzuzufügen oder zu überschreiben, oder auf `null`, um sie zu löschen.\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\nWird einmal pro Anbieteranforderung ausgeführt; Bei Wiederholungsversuchen werden dieselben Header erneut verwendet, anstatt den Hook erneut auszulösen.\n\n#### before_provider_request\n\nWird ausgelöst, nachdem die anbieterspezifische Nutzlast erstellt wurde, unmittelbar bevor die Anfrage gesendet wird. Handler werden in der Reihenfolge des Ladens der Erweiterungen ausgeführt. Durch die Rückgabe von `undefined` bleibt die Nutzlast unverändert. Die Rückgabe eines anderen Werts ersetzt die Nutzlast für spätere Handler und für die eigentliche Anfrage.\n\nDieser Hook kann Systemanweisungen auf Anbieterebene umschreiben oder vollständig entfernen. Diese Änderungen auf Nutzlastebene werden von `ctx.getSystemPrompt()` nicht widergespiegelt, das die Systemaufforderungszeichenfolge von Pi und nicht die endgültige serialisierte Anbieternutzlast meldet.\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\nDies ist hauptsächlich zum Debuggen der Provider-Serialisierung und des Cache-Verhaltens nützlich.\n\n#### after_provider_response\n\nWird ausgelöst, nachdem eine HTTP-Antwort empfangen wurde und bevor der Stream-Body verbraucht wird. Handler werden in der Reihenfolge des Ladens der Erweiterungen ausgeführt.\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\nDie Header-Verfügbarkeit hängt vom Anbieter und Transport ab. Providers dass abstrakte HTTP-Antworten möglicherweise keine Header offenlegen.\n\n### Modellveranstaltungen\n\n#### model_select\n\nWird ausgelöst, wenn sich das Modell über den Befehl `/model`, den Modellwechsel (`Ctrl+P`) oder die Sitzungswiederherstellung ändert.\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\nVerwenden Sie dies, um UI-Elemente (Statusleisten, Fußzeilen) zu aktualisieren oder eine modellspezifische Initialisierung durchzuführen, wenn sich das aktive Modell ändert.\n\n#### think_level_select\n\nWird ausgelöst, wenn sich die Denkebene ändert. Dies ist nur eine Benachrichtigung; Rückgabewerte des Handlers werden ignoriert.\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\nVerwenden Sie dies, um die Benutzeroberfläche der Erweiterung zu aktualisieren, wenn `pi.setThinkingLevel()`, Modelländerungen oder integrierte Steuerelemente auf der Denkebene die Ebene des aktiven Denkens ändern.\n\n### Tool-Ereignisse\n\n#### tool_call\n\nWird nach `tool_execution_start` ausgelöst, bevor das Tool ausgeführt wird. **Kann blockieren.** Verwenden Sie `isToolCallEventType`, um Eingaben einzugrenzen und getippte Eingaben zu erhalten.\n\nBevor `tool_call` ausgeführt wird, wartet pi darauf, dass zuvor ausgegebene Agent-Ereignisse den Ablauf durch `AgentSession` beenden. Dies bedeutet, dass `ctx.sessionManager` durch die aktuelle Assistenten-Tool-Aufrufnachricht auf dem neuesten Stand ist.\n\nIm standardmäßigen parallelen Tool-Ausführungsmodus werden Geschwistertool-Aufrufe aus derselben Assistentennachricht nacheinander einem Preflight unterzogen und dann gleichzeitig ausgeführt. Es ist nicht garantiert, dass `tool_call` die Ergebnisse des Geschwistertools aus derselben Assistentennachricht in `ctx.sessionManager` sieht.\n\n`event.input` ist veränderlich. Mutieren Sie es an Ort und Stelle, um Toolargumente vor der Ausführung zu patchen.\n\nVerhaltensgarantien:\n- Mutationen zu `event.input` wirken sich auf die tatsächliche Werkzeugausführung aus\n- Spätere `tool_call`-Handler sehen Mutationen, die von früheren Handlern vorgenommen wurden\n- Nach Ihrer Mutation wird keine erneute Validierung durchgeführt\n- Rückgabewerte von `tool_call` steuern Blockierung über `{ block: true, reason?: string, terminate?: boolean }`\n- `terminate` gilt nur für einen blockierten Anruf; Der Agent stoppt nur dann vorzeitig, wenn jedes endgültige Ergebnis im Stapel beendet wird\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#### Eingabe benutzerdefinierter Tools eingeben\n\nBenutzerdefinierte Tools sollten ihren Eingabetyp exportieren:\n\n```typescript\n// my-extension.ts\nexport type MyToolInput = Static<typeof myToolSchema>;\n```\n\nVerwenden Sie `isToolCallEventType` mit expliziten Typparametern:\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#### tool_result\n\nWird nach Abschluss der Tool-Ausführung und vor `tool_execution_end` ausgelöst, außerdem werden die endgültigen Tool-Ergebnismeldungsereignisse ausgegeben. **Kann das Ergebnis ändern.**\n\nIm parallelen Werkzeugmodus können `tool_result` und `tool_execution_end` in der Reihenfolge der Werkzeugvervollständigung verschachtelt sein, während die letzten `toolResult`-Nachrichtenereignisse noch später in der Reihenfolge der Assistentenquelle ausgegeben werden.\n\n`tool_result` Handler verketten wie Middleware:\n- Handler werden in der Reihenfolge des Ladens der Erweiterungen ausgeführt\n- Jeder Handler sieht das neueste Ergebnis nach vorherigen Handleränderungen\n- Handler können Teilpatches zurückgeben (`content`, `details`, `isError` oder `usage`); Ausgelassene Felder behalten ihre aktuellen Werte\n\nVerwenden Sie `ctx.signal` für verschachtelte asynchrone Arbeit innerhalb des Handlers. Dadurch können Esc Modellaufrufe, `fetch()` und andere von der Erweiterung gestartete Abbruchvorgänge abbrechen.\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### Benutzer-Bash-Ereignisse\n\n#### user_bash\n\nWird ausgelöst, wenn der Benutzer die Befehle `!` oder `!!` ausführt. **Kann abfangen.**\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### Eingabeereignisse\n\n#### Eingang\n\nWird ausgelöst, wenn Benutzereingaben empfangen werden, nachdem Erweiterungsbefehle überprüft wurden, aber vor der Skill- und Vorlagenerweiterung. Das Ereignis sieht den rohen Eingabetext, daher sind `/skill:foo` und `/template` noch nicht erweitert.\n\n**Bearbeitungsreihenfolge:**\n1. Erweiterungsbefehle (`/cmd`) werden zuerst überprüft. Wenn sie gefunden werden, wird der Handler ausgeführt und das Eingabeereignis wird übersprungen\n2. `input` Ereignisfeuer – können abgefangen, transformiert oder verarbeitet werden\n3. Wenn nicht behandelt: Fertigkeitsbefehle (`/skill:name`) werden auf Fertigkeitsinhalte erweitert\n4. Wenn nicht behandelt: prompt templates (`/template`) auf Vorlageninhalt erweitert\n5. Die Agentenverarbeitung beginnt (`before_agent_start` usw.)\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**Ergebnisse:**\n- `continue` – unverändert durchlaufen (Standard, wenn der Handler nichts zurückgibt)\n- `transform` – Text/Bilder ändern, dann mit der Erweiterung fortfahren\n- `handled` – Agent vollständig überspringen (der erste Handler, der dies zurückgibt, gewinnt)\n\nTransformiert die Kette über Handler hinweg. Siehe [input-transform.ts](../examples/extensions/input-transform.ts) und [input-transform-streaming.ts](../examples/extensions/input-transform-streaming.ts) für `streamingBehavior`-fähiges Routing.\n\n## ExtensionContext\n\nAlle Handler erhalten `ctx: ExtensionContext`.\n\n### ctx.ui\n\nUI-Methoden für die Benutzerinteraktion. Ausführliche Informationen finden Sie unter [Custom UI](#custom-ui).\n\n### ctx.mode\n\nAktueller Laufmodus: `\"tui\"`, `\"rpc\"`, `\"json\"` oder `\"print\"`. Verwenden Sie `ctx.mode === \"tui\"`, um reine Terminalfunktionen wie `custom()`, Komponentenfabriken, Terminaleingabe und direktes TUI-Rendering zu schützen.\n\n### ctx.hasUI\n\n`true` in den Modi TUI und RPC. `false` im Druckmodus (`-p`) und JSON Modus. Verwenden Sie dies, um Dialogmethoden (`select`, `confirm`, `input`, `editor`) und Fire-and-Forget-Methoden (`notify`, `setStatus`, `setWidget`, `setTitle`, `setEditorText`) zu schützen, die sowohl in TUI als auch funktionieren RPC Modi. Im RPC-Modus sind einige TUI-spezifische Methoden No-Ops oder geben Standardwerte zurück (siehe [rpc.md](rpc.md#extension-ui-protocol)).\n\n### ctx.cwd\n\nAktuelles Arbeitsverzeichnis.\n\nVerwenden Sie `CONFIG_DIR_NAME` anstelle der Hartcodierung von `.pi`, wenn Sie projektlokale Konfigurationspfade erstellen. Umbenannte Distributionen können einen anderen Konfigurationsverzeichnisnamen verwenden.\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\nGibt zurück, ob projektlokale Vertrauensstellung für den aktuellen Sitzungskontext aktiv ist. Dazu gehören temporäre Vertrauensentscheidungen und CLI Vertrauensüberschreibungen, nicht nur gespeicherte Entscheidungen im globalen Vertrauensspeicher.\n\nVerwenden Sie dies, bevor Sie die projektlokale Erweiterungskonfiguration lesen, die nur für vertrauenswürdige Projekte berücksichtigt werden sollte.\n\n### ctx.sessionManager\n\nLesezugriff auf den Sitzungsstatus. Siehe [Session Format](session-format.md) für den vollständigen SessionManager API und die Eintragstypen.\n\nFür `tool_call` wird dieser Status durch die aktuelle Assistentennachricht synchronisiert, bevor Handler ausgeführt werden. Im parallelen Tool-Ausführungsmodus ist es immer noch nicht garantiert, dass die Ergebnisse von Geschwistertools aus derselben Assistentenmeldung einbezogen werden.\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\nZugriff auf Modelle, Anbieter und aufgelöste Authentifizierung. `ctx.modelRegistry.getProvider(id)` gibt den effektiven Pi-AI-Anbieter zurück, während `getProviderAuth(id)` seine aktuellen API key, Header, Basis-URL und anbieterbezogene Umgebung auflöst, ohne dass ein geladenes Modell erforderlich ist. `ctx.model` ist das aktive Modell und `ctx.thinkingLevel` ist seine aktuelle effektive Denkebene.\n\n`ctx.scopedModels` ist die schreibgeschützte Liste der Modelle, die für die aktuelle Sitzung gelten – derselbe Satz, den der Befehl `/scoped-models` anzeigt. Es wird beim Sitzungsstart mit dem Flag `--models` CLI und der Einstellung `enabledModels` gelöst (abgeglichen mit dem verfügbaren Katalog mit Minimatch auf `provider/modelId` oder einem bloßen `modelId`). Es ist leer, wenn kein Scoping konfiguriert ist, was bedeutet, dass jedes verfügbare Modell verwendbar ist. Jeder Eintrag ist `{ model, thinkingLevel? }`, wobei `thinkingLevel` nur gesetzt wird, wenn ein Muster ihn fixiert (z. B. `anthropic/*:high`). Verwenden Sie es, um eine Modellauswahl zu füllen, die die integrierte Modellauswahl widerspiegelt, anstatt den gesamten Katalog über `ctx.modelRegistry.getAvailable()` aufzulisten.\n\n### ctx.signal\n\nDas aktuelle Agenten-Abbruchsignal oder `undefined`, wenn kein Agentenzug aktiv ist.\n\nVerwenden Sie dies für abbruchbewusste verschachtelte Arbeiten, die von Erweiterungshandlern gestartet werden, zum Beispiel:\n- `fetch(..., { signal: ctx.signal })`\n- Modellaufrufe, die `signal` akzeptieren\n- Datei- oder Prozesshilfsprogramme, die `AbortSignal` akzeptieren\n\n`ctx.signal` wird typischerweise bei aktiven Zugereignissen wie `tool_call`, `tool_result`, `message_update` und `turn_end` definiert.\nIn Leerlauf- oder Nicht-Turn-Kontexten wie Sitzungsereignissen, Erweiterungsbefehlen und ausgelösten Verknüpfungen, während Pi im Leerlauf ist, ist es normalerweise `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\nKontrollflusshelfer. `ctx.isIdle()` ist falsch, während Pi eine Agentenausführung, einen automatischen Wiederholungsversuch, einen automatischen Komprimierungswiederholungsversuch oder eine Fortsetzung in der Warteschlange verarbeitet.\n\n### ctx.shutdown()\n\nFordern Sie ein ordnungsgemäßes Herunterfahren von Pi an.\n\n- **Interaktiver Modus:** Wird verschoben, bis der Agent inaktiv wird (nachdem alle in der Warteschlange befindlichen Steuerungs- und Folgenachrichten verarbeitet wurden).\n- **RPC-Modus:** Aufgeschoben bis zum nächsten Ruhezustand (nach Abschluss der aktuellen Befehlsantwort, beim Warten auf den nächsten Befehl).\n- **Druckmodus:** Kein Betrieb. Der Prozess wird automatisch beendet, wenn alle Eingabeaufforderungen verarbeitet wurden.\n\nGibt vor dem Beenden das Ereignis `session_shutdown` an alle Erweiterungen aus. Verfügbar in allen Kontexten (Ereignishandler, Tools, Befehle, Verknüpfungen).\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\nGibt die aktuelle Kontextverwendung für das aktive Modell zurück. Verwendet die letzte Assistentennutzung, sofern verfügbar, und schätzt dann die Token für nachfolgende Nachrichten.\n\n```typescript\nconst usage = ctx.getContextUsage();\nif (usage && usage.tokens > 100_000) {\n  // ...\n}\n```\n\n### ctx.compact()\n\nLösen Sie die Komprimierung aus, ohne den Abschluss abzuwarten. Verwenden Sie `onComplete` und `onError` für Folgeaktionen.\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\nGibt die aktuelle Systemaufforderungszeichenfolge von Pi zurück.\n\n- Während `before_agent_start` spiegelt dies verkettete System-Prompt-Änderungen wider, die bisher für die aktuelle Runde vorgenommen wurden.\n- Spätere `context`-Nachrichtenmutationen sind nicht enthalten.\n- `before_provider_request` Payload-Rewrites sind nicht enthalten.\n- Wenn später geladene Erweiterungen nach Ihren ausgeführt werden, können sie dennoch ändern, was letztendlich gesendet wird.\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## ExtensionCommandContext\n\nBefehlshandler erhalten `ExtensionCommandContext`, was `ExtensionContext` um Sitzungssteuerungsmethoden erweitert. Diese sind nur in Befehlen verfügbar, da sie einen Deadlock verursachen können, wenn sie von Ereignishandlern aufgerufen werden.\n\n### ctx.getSystemPromptOptions()\n\nGibt die Basiseingaben zurück, die Pi derzeit zum Erstellen der Systemeingabeaufforderung verwendet.\n\n```typescript\nconst options = ctx.getSystemPromptOptions();\nconst contextPaths = options.contextFiles?.map((file) => file.path) ?? [];\n```\n\nDies hat die gleiche Form und Veränderlichkeit wie `before_agent_start` `event.systemPromptOptions`: benutzerdefinierte Eingabeaufforderung, aktive Tools, Tool-Snippets, Eingabeaufforderungsrichtlinien, angehängter Systemeingabeaufforderungstext, cwd, geladenes context files und geladene Fertigkeiten. Es kann vollständige Inhalte der Kontextdatei enthalten. Behandeln Sie es daher als vertrauliche erweiterungslokale Daten und vermeiden Sie die Offenlegung über Befehlslisten, Protokolle oder Metadaten zur automatischen Vervollständigung.\n\nHier werden die aktuellen Basiseingabeaufforderungseingaben gemeldet. Es umfasst keine `before_agent_start` verketteten System-Prompt-Änderungen pro Runde, spätere `context` Ereignismeldungsmutationen oder `before_provider_request` Nutzlastumschreibungen.\n\n### ctx.waitForIdle()\n\nWarten Sie, bis sich der Agent vollständig beruhigt hat, einschließlich automatischer Wiederholungsversuche, automatischer Komprimierungswiederholungsversuche und Fortsetzungen in der Warteschlange:\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(Optionen?)\n\nErstellen Sie eine neue Sitzung:\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\nOptionen:\n- `parentSession`: übergeordnete Sitzungsdatei zur Aufzeichnung im neuen Sitzungsheader\n- `setup`: mutiert `SessionManager` der neuen Sitzung, bevor `withSession` ausgeführt wird\n- `withSession`: Führen Sie die Arbeit nach dem Wechsel in einem neuen Ersetzungssitzungskontext aus. Verwenden Sie nicht den erfassten alten `pi` / Befehl `ctx`; siehe [Session replacement lifecycle and footguns](#session-replacement-lifecycle-and-footguns).\n\n### ctx.fork(entryId, Optionen?)\n\nVerzweigen Sie von einem bestimmten Eintrag und erstellen Sie eine neue Sitzungsdatei:\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\nOptionen:\n- `position`: `\"before\"` (Standard) verzweigt vor der ausgewählten Benutzernachricht und stellt diese Eingabeaufforderung im Editor wieder her\n- `position`: `\"at\"` dupliziert den aktiven Pfad durch den ausgewählten Eintrag, ohne den Editortext wiederherzustellen\n- `withSession`: Führen Sie die Arbeit nach dem Wechsel in einem neuen Ersetzungssitzungskontext aus. Verwenden Sie nicht den erfassten alten `pi` / Befehl `ctx`; siehe [Session replacement lifecycle and footguns](#session-replacement-lifecycle-and-footguns).\n\n### ctx.navigateTree(targetId, Optionen?)\n\nNavigieren Sie zu einem anderen Punkt im session 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\nOptionen:\n- `summarize`: Ob eine Zusammenfassung des verlassenen Zweigs erstellt werden soll\n- `customInstructions`: Benutzerdefinierte Anweisungen für die Zusammenfassung\n- `replaceInstructions`: Wenn wahr, ersetzt `customInstructions` die Standardaufforderung, anstatt angehängt zu werden\n- `label`: Beschriftung zum Anhängen an den Zweigzusammenfassungseintrag (oder Zieleintrag, wenn nicht zusammenfassend)\n\n### ctx.switchSession(sessionPath, Optionen?)\n\nWechseln Sie zu einer anderen Sitzungsdatei:\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\nOptionen:\n- `withSession`: Führen Sie die Arbeit nach dem Wechsel in einem neuen Ersetzungssitzungskontext aus. Verwenden Sie nicht den erfassten alten `pi` / Befehl `ctx`; siehe [Session replacement lifecycle and footguns](#session-replacement-lifecycle-and-footguns).\n\nUm verfügbare Sitzungen zu ermitteln, verwenden Sie die statischen Methoden `SessionManager.list()` oder `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### Lebenszyklus des Sitzungsaustauschs und Fußfeuerwaffen\n\n`withSession` erhält ein neues `ReplacedSessionContext`, das `ExtensionCommandContext` mit asynchronen `sendMessage()`- und `sendUserMessage()`-Helfern erweitert, die an die Ersatzsitzung gebunden sind.\n\nLebenszyklus und Fußfeuerwaffen:\n- `withSession` wird erst ausgeführt, nachdem die alte Sitzung `session_shutdown` ausgegeben hat, die alte Laufzeit abgebaut wurde, die Ersatzsitzung neu gebunden wurde und die neue Erweiterungsinstanz bereits `session_start` empfangen hat.\n- Der Rückruf wird weiterhin im ursprünglichen Abschluss ausgeführt, nicht innerhalb der neuen Erweiterungsinstanz. Das bedeutet, dass Ihre alte Erweiterungsinstanz möglicherweise bereits die Bereinigung beim Herunterfahren durchgeführt hat, bevor `withSession` startet.\n- Erfasste alte `pi` / alte Befehls-`ctx` sitzungsgebundene Objekte sind nach dem Ersetzen veraltet und werden bei Verwendung ausgelöst. Verwenden Sie für sitzungsgebundene Arbeit nur das an `withSession` übergebene `ctx`.\n- Zuvor extrahierte Rohobjekte liegen weiterhin in Ihrer Verantwortung. Wenn Sie beispielsweise `const sm = ctx.sessionManager` vor dem Ersetzen erfassen, ist `sm` immer noch das alte `SessionManager`-Objekt. Nach dem Austausch nicht wiederverwenden.\n- Der Code in `withSession` sollte davon ausgehen, dass jeder von Ihrem `session_shutdown`-Handler ungültig gemachte Status bereits verschwunden ist. Erfassen Sie nur einfache Daten, die das Herunterfahren sauber überstehen, wie z. B. Zeichenfolgen, IDs und serialisierte Konfigurationen.\n\nSicheres Muster:\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\nUnsicheres Muster:\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\nFühren Sie den gleichen Neuladeablauf wie `/reload` aus.\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\nWichtiges Verhalten:\n- `await ctx.reload()` gibt `session_shutdown` für die aktuelle Erweiterungslaufzeit aus\n- Anschließend werden die Ressourcen neu geladen und `session_start` mit `reason: \"reload\"` und `resources_discover` mit Grund `\"reload\"` ausgegeben.\n- Der aktuell laufende Befehlshandler läuft weiterhin im alten Aufrufrahmen weiter\n- Code nach `await ctx.reload()` läuft weiterhin ab der Pre-Reload-Version\n- Code nach `await ctx.reload()` darf nicht davon ausgehen, dass der alte In-Memory-Erweiterungsstatus noch gültig ist\n- Nachdem der Handler zurückgekehrt ist, verwenden zukünftige Befehle/Ereignisse/Toolaufrufe die neue Erweiterungsversion\n\nFür vorhersehbares Verhalten behandeln Sie reload als Terminal für diesen Handler (`await ctx.reload(); return;`).\n\nTools werden mit `ExtensionContext` ausgeführt, sodass sie `ctx.reload()` nicht direkt aufrufen können. Verwenden Sie einen Befehl als Neulade-Einstiegspunkt und stellen Sie dann ein Tool bereit, das diesen Befehl als Folge-Benutzernachricht in die Warteschlange stellt.\n\nBeispieltool, das das LLM aufrufen kann, um ein Neuladen auszulösen:\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## ErweiterungAPI Methoden\n\n### pi.on(Ereignis, Handler)\n\nAbonnieren Sie Veranstaltungen. Siehe [Events](#events) für Ereignistypen und Rückgabewerte.\n\n### pi.registerTool(definition)\n\nRegistrieren Sie ein vom LLM aufrufbares benutzerdefiniertes Tool. Ausführliche Informationen finden Sie unter [Custom Tools](#custom-tools).\n\n`pi.registerTool()` funktioniert sowohl während des Ladens der Erweiterung als auch nach dem Start. Sie können es innerhalb von `session_start`, Befehlshandlern oder anderen Ereignishandlern aufrufen. Neue Werkzeuge werden sofort in derselben Sitzung aktualisiert, erscheinen also in `pi.getAllTools()` und sind vom LLM ohne `/reload` aufrufbar.\n\nVerwenden Sie `pi.setActiveTools()`, um Tools (einschließlich dynamisch hinzugefügter Tools) zur Laufzeit zu aktivieren oder zu deaktivieren.\n\nVerwenden Sie `promptSnippet`, um ein benutzerdefiniertes Werkzeug für einen einzeiligen Eintrag in `Available tools` zu aktivieren, und `promptGuidelines`, um werkzeugspezifische Aufzählungszeichen an den Standardabschnitt `Guidelines` anzuhängen, wenn das Werkzeug aktiv ist.\n\n**Wichtig:** `promptGuidelines`-Aufzählungszeichen werden flach an den `Guidelines`-Abschnitt angehängt, ohne Werkzeugnamen-Präfix. Jede Richtlinie muss das Werkzeug benennen, auf das sie sich bezieht – vermeiden Sie „Verwenden Sie dieses Werkzeug, wenn …“, da das LLM nicht erkennen kann, welches Werkzeug „dies“ bedeutet. Schreiben Sie stattdessen „My_tool verwenden, wenn…“.\n\nEin vollständiges Beispiel finden Sie unter [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(Nachricht, Optionen?)\n\nFügen Sie eine benutzerdefinierte Nachricht in die Sitzung ein. Benutzerdefinierte Nachrichten nehmen am LLM-Kontext teil. Für dauerhafte TUI-Inhalte, die nicht an das LLM gesendet werden sollen, verwenden Sie [`pi.appendEntry()`](#piappendentrycustomtype-data) mit [`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**Optionen:**\n- `deliverAs` - Liefermodus:\n  - `\"steer\"` (Standard) – Stellt die Nachricht während des Streamings in die Warteschlange. Wird geliefert, nachdem der aktuelle Assistentenzug die Ausführung seiner Werkzeugaufrufe abgeschlossen hat, und zwar vor dem nächsten LLM-Aufruf.\n  - `\"followUp\"` – Wartet, bis der Agent fertig ist. Wird nur geliefert, wenn der Agent keine Tool-Aufrufe mehr hat.\n  - `\"nextTurn\"` – In der Warteschlange für die nächste Benutzeraufforderung. Unterbricht oder löst nichts aus.\n- `triggerTurn: true` – Wenn der Agent inaktiv ist, wird sofort eine LLM-Antwort ausgelöst. Gilt nur für die Modi `\"steer\"` und `\"followUp\"` (wird für `\"nextTurn\"` ignoriert).\n\n### pi.sendUserMessage(Inhalt, Optionen?)\n\nSenden Sie eine Benutzernachricht an den Agenten. Im Gegensatz zu `sendMessage()`, das benutzerdefinierte Nachrichten sendet, wird hiermit eine tatsächliche Benutzernachricht gesendet, die aussieht, als wäre sie vom Benutzer eingegeben worden. Löst immer eine Runde aus.\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**Optionen:**\n- `deliverAs` – Erforderlich, wenn der Agent streamt:\n  - `\"steer\"` – Stellt die Nachricht zur Zustellung in die Warteschlange, nachdem der aktuelle Assistentenzug die Ausführung seiner Werkzeugaufrufe abgeschlossen hat\n  - `\"followUp\"` – Wartet darauf, dass der Agent alle Tools beendet\n\nWenn nicht gestreamt wird, wird die Nachricht sofort gesendet und löst eine neue Runde aus. Beim Streamen ohne `deliverAs` wird ein Fehler ausgegeben.\n\nEin vollständiges Beispiel finden Sie unter [send-user-message.ts](../examples/extensions/send-user-message.ts).\n\n### pi.appendEntry(customType, Daten?)\n\nErweiterungsdaten beibehalten. Benutzerdefinierte Einträge nehmen NICHT am LLM-Kontext teil. Im interaktiven Modus können sie in Verbindung mit `pi.registerEntryRenderer()` auch innerhalb des Chat-Transkripts gerendert werden.\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(name)\n\nLegen Sie den Anzeigenamen der Sitzung fest (wird in der Sitzungsauswahl anstelle der ersten Nachricht angezeigt).\n\n```typescript\npi.setSessionName(\"Refactor auth module\");\n```\n\n### pi.getSessionName()\n\nRufen Sie den aktuellen Sitzungsnamen ab, falls festgelegt.\n\n```typescript\nconst name = pi.getSessionName();\nif (name) {\n  console.log(`Session: ${name}`);\n}\n```\n\n### pi.setLabel(entryId, label)\n\nLegen Sie eine Beschriftung für einen Eintrag fest oder löschen Sie sie. Beschriftungen sind benutzerdefinierte Markierungen für Lesezeichen und Navigation (angezeigt im `/tree`-Selektor).\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\nBeschriftungen bleiben in der Sitzung bestehen und überleben Neustarts. Markieren Sie damit wichtige Punkte (Abbiegungen, Kontrollpunkte) im Konversationsbaum.\n\n### pi.registerCommand(name, Optionen)\n\nRegistrieren Sie einen Befehl.\n\nWenn mehrere Erweiterungen denselben Befehlsnamen registrieren, behält pi sie alle und weist numerische Aufrufsuffixe in der Ladereihenfolge zu, zum Beispiel `/review:1` und `/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\nOptional: Argument-Autovervollständigung für `/command...` hinzufügen:\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\nHolen Sie sich die slash commands, die für den Aufruf über `prompt` in der aktuellen Sitzung verfügbar ist. Enthält Erweiterungsbefehle, prompt templates und Fertigkeitsbefehle.\nDie Liste entspricht der Reihenfolge RPC `get_commands`: zuerst Erweiterungen, dann Vorlagen, dann Fertigkeiten.\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\nJeder Eintrag hat diese Form:\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\nVerwenden Sie `sourceInfo` als kanonisches Herkunftsfeld. Leiten Sie den Besitz nicht aus Befehlsnamen oder aus der Ad-hoc-Pfadanalyse ab.\n\nIntegrierte interaktive Befehle (wie `/model` und `/settings`) sind hier nicht enthalten. Sie werden ausschließlich interaktiv bearbeitet\nModus und würde nicht ausgeführt, wenn es über `prompt` gesendet würde.\n\n### pi.registerMessageRenderer(customType, Renderer)\n\nRegistrieren Sie einen benutzerdefinierten TUI-Renderer für benutzerdefinierte Nachrichten bei Ihrem `customType`. Benutzerdefinierte Nachrichten werden mit `pi.sendMessage()` erstellt und nehmen am LLM-Kontext teil. Siehe [Custom UI](#custom-ui).\n\n### pi.registerMarkdownTransformer(Transformer)\n\nRegistrieren Sie einen Transformator für die Markdown in normalem Benutzertext, Assistententext und Denkblöcken. Transformatoren laufen in der Reihenfolge der Erweiterungslasten, und jeder Transformator empfängt die vom vorherigen Transformator zurückgegebene Markdown. Nachdem die Kette abgeschlossen ist, rendert Pi den transformierten Inhalt mit seinem integrierten Renderer.\n\nDer Transformator empfängt die Zeichenfolge Markdown und einen Kontext mit:\n\n- `messageType` – `\"user\"`, `\"assistant\"` oder `\"assistant-thinking\"`\n- `isStreaming` – `true` für teilweise Assistentenaktualisierungen; `false` für Benutzer, abgeschlossenen Assistenten und wiederhergestellte Nachrichten\n- `availableWidth` – genaue Terminalspalten, die für den transformierten Markdown-Inhalt verfügbar sind\n\nGib das transformierte Markdown zurück:\n\n```typescript\npi.registerMarkdownTransformer((markdown, { messageType, isStreaming }) => {\n  if (isStreaming || messageType === \"assistant-thinking\") return markdown;\n  return markdown.replaceAll(\"-->\", \"→\");\n});\n```\n\nWenn ein Transformator wirft, behält Pi das bisher produzierte Markdown und fährt mit dem nächsten Transformator fort. Der Hook kann nur angezeigt werden: Die ursprüngliche Nachricht bleibt im Sitzungs- und Modellkontext unverändert. Es wird für neue Benutzernachrichten, Assistenten-Streaming-Updates, wiederhergestellte Sitzungsnachrichten und Änderungen der Terminalbreite ausgeführt, sodass Transformer synchron und kostengünstig bleiben sollten.\n\n### pi.registerEntryRenderer(customType, Renderer)\n\nRegistrieren Sie einen benutzerdefinierten TUI-Renderer für benutzerdefinierte Einträge bei Ihrem `customType`. Benutzerdefinierte Einträge werden mit `pi.appendEntry()` erstellt und nehmen nicht am LLM-Kontext teil.\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(Verknüpfung, Optionen)\n\nRegistrieren Sie eine Tastenkombination. Siehe [keybindings.md](keybindings.md) für das Verknüpfungsformat und die integrierten Tastenkombinationen.\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(Name, Optionen)\n\nRegistrieren Sie ein CLI-Flag.\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(Befehl, Argumente, Optionen?)\n\nFühren Sie einen Shell-Befehl aus.\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\nAktive Werkzeuge verwalten. Dies funktioniert sowohl für integrierte Tools als auch für dynamisch registrierte Tools. `pi.getActiveTools()` gibt die aktiven Werkzeugnamen als `string[]` zurück; `pi.getAllTools()` gibt Metadaten für alle konfigurierten Tools zurück.\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()` gibt `name`, `description`, `parameters`, `promptGuidelines` und `sourceInfo` zurück.\n\nTypische `sourceInfo.source`-Werte:\n- `builtin` für integrierte Werkzeuge\n- `sdk` für über `createAgentSession({ customTools })` übergebene Werkzeuge\n- Metadaten der Erweiterungsquelle für Tools, die von Erweiterungen registriert werden\n\n### pi.setModel(Modell)\n\nStellen Sie das aktuelle Modell ein. Gibt `false` zurück, wenn für das Modell kein API key verfügbar ist. Informationen zum Konfigurieren benutzerdefinierter Modelle finden Sie unter [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(level)\n\nHolen Sie sich die Denkebene oder legen Sie sie fest. Die Ebene ist auf die Modellfähigkeiten beschränkt (nicht-begründende Modelle verwenden immer „aus“). Änderungen geben `thinking_level_select` aus.\n\n```typescript\nconst current = pi.getThinkingLevel();  // \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\"\npi.setThinkingLevel(\"high\");\n```\n\n### pi.events\n\nGemeinsamer Ereignisbus für die Kommunikation zwischen Nebenstellen:\n\n```typescript\npi.events.on(\"my:event\", (data) => { ... });\npi.events.emit(\"my:event\", { ... });\n```\n\n### pi.registerProvider(name, config)\n\nRegistrieren oder überschreiben Sie einen Modellanbieter dynamisch. Nützlich für Proxys, benutzerdefinierte Endpunkte oder teamweite Modellkonfigurationen.\n\nWährend der Extension-Factory-Funktion getätigte Anrufe werden in die Warteschlange gestellt und angewendet, sobald der Runner initialisiert wird. Danach vorgenommene Aufrufe – beispielsweise von einem Befehlshandler nach einem Benutzer-Setup-Ablauf – werden sofort wirksam, ohne dass eine `/reload` erforderlich ist.\n\nDynamische Anbieter können `refreshModels` implementieren. Pi ruft es während der Modellaktualisierung auf, veröffentlicht die zurückgegebene Liste synchron über den Anbieter und übergibt den kanonischen Anmeldeinformations-/gespeicherten Katalog-/Netzwerk-/Signalkontext. Die Erweiterung entscheidet, ob Katalogmetadaten durch generierungsgeprüfte `context.publish({ persist: entry })` beibehalten werden; Live-Server wie llama.cpp können Modelle zurückgeben, ohne sie beizubehalten.\n\n`context.signal` ist immer ein konkretes Signal und Provider-Rückrufe müssen es an blockierende E/A weitergeben. Öffentliche `ModelRuntime.refresh()`- und `ModelRegistry.refresh()`-Aufrufe akzeptieren ein optionales Signal und sind unbegrenzt, wenn es weggelassen wird; Verlängerungen und Bewerbungen wählen ihre eigenen Fristen. Durch die Stornierung muss der Anrufer nicht mehr warten, selbst wenn ein Anbieter das Signal ignoriert. Es ist jedoch dennoch eine Zusammenarbeit erforderlich, um die zugrunde liegende Arbeit zu stoppen.\n\nExtensions, die eine native Anbieterauthentifizierung, Filterung, Aktualisierung oder Stream-Verhalten benötigen, können eine vollständige `Provider` von `@earendil-works/pi-ai` registrieren. Der Anbieter wird zur Kompositionsbasis und es gelten weiterhin `models.json` Überschreibungen darüber.\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\nDie Objektform akzeptiert ein vollständiges pi-ai `Provider`, einschließlich nativem `auth`-, `getModels`-, `refreshModels`-, `filterModels`-, `stream`- und `streamSimple`-Verhalten.\n\n**Legacy-Konfigurationsoptionen:**\n- `name` – Anzeigename für den Anbieter in der Benutzeroberfläche, z. B. `/login`.\n- `baseUrl` - API Endpunkt-URL. Erforderlich beim Definieren von Modellen.\n- `apiKey` - API key Literal, Umgebungsinterpolation (`$ENV_VAR` oder `${ENV_VAR}`) oder führendes `!command`. Erforderlich beim Definieren von Modellen (sofern `oauth` nicht angegeben). `$` maskiert ``apiKey` - API key Literal, Umgebungsinterpolation (`$ENV_VAR` oder `${ENV_VAR}`) oder führendes `!command`. Erforderlich beim Definieren von Modellen (sofern `oauth` nicht angegeben). `$` maskiert  und `$!` maskiert ein Literal `!`, ohne die Befehlsausführung auszulösen.\n- `api` - API Typ: `\"anthropic-messages\"`, `\"openai-completions\"`, `\"openai-responses\"` usw.\n- `headers` – Benutzerdefinierte Header zur Einbindung in Anfragen.\n- `authHeader` – Wenn wahr, wird der Header `Authorization: Bearer` automatisch hinzugefügt.\n- `models` – Array von Modelldefinitionen. Falls bereitgestellt, ersetzt es alle vorhandenen Modelle für diesen Anbieter. Modelldefinitionen können `baseUrl` festlegen, um den Anbieterendpunkt für dieses Modell zu überschreiben.\n- `refreshModels` – Asynchroner dynamischer Erkennungsrückruf. Die zurückgegebenen Modelle ersetzen die von der Erweiterung bereitgestellten Modelle. `context.stored` enthält den persistenten Anbieter-Snapshot; Verwenden Sie generationsüberprüft `context.publish({ persist: entry })` nur, wenn aktualisierte Katalogdaten bestehen bleiben sollen. Verwenden Sie `persist: null`, um diesen Schnappschuss zu löschen.\n- `oauth` – OAuth Anbieterkonfiguration für `/login`-Unterstützung. Sofern angegeben, erscheint der Anbieter im Anmeldemenü.\n- `streamSimple` – Benutzerdefinierte Streaming-Implementierung für nicht standardmäßige APIs.\n\nWeitere Themen finden Sie unter [custom-provider.md](custom-provider.md): benutzerdefiniertes Streaming APIs, OAuth Details, Referenz zur Modelldefinition.\n\n### pi.unregisterProvider(name)\n\nEntfernen Sie einen zuvor registrierten Anbieter und seine Modelle. Integrierte Modelle, die vom Anbieter überschrieben wurden, werden wiederhergestellt. Hat keine Auswirkung, wenn der Anbieter nicht registriert wurde.\n\nWie `registerProvider` wird dies sofort wirksam, wenn es nach der anfänglichen Ladephase aufgerufen wird, sodass ein `/reload` nicht erforderlich ist.\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## Staatsverwaltung\n\nExtensions mit Status sollte es im Tool-Ergebnis `details` speichern, um eine ordnungsgemäße Verzweigungsunterstützung zu gewährleisten:\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## Benutzerdefinierte Werkzeuge\n\nRegistrieren Sie Tools, die das LLM über `pi.registerTool()` aufrufen kann. Werkzeuge werden in der Systemeingabeaufforderung angezeigt und können über ein benutzerdefiniertes Rendering verfügen.\n\nVerwenden Sie `promptSnippet` für einen kurzen einzeiligen Eintrag im Abschnitt `Available tools` in der Standard-Systemeingabeaufforderung. Wenn es weggelassen wird, werden benutzerdefinierte Tools in diesem Abschnitt nicht berücksichtigt.\n\nVerwenden Sie `promptGuidelines`, um werkzeugspezifische Aufzählungszeichen zum Standard-Systemaufforderungsabschnitt `Guidelines` hinzuzufügen. Diese Aufzählungszeichen werden nur eingefügt, während das Tool aktiv ist (z. B. nach `pi.setActiveTools([...])`).\n\n**Wichtig:** `promptGuidelines`-Aufzählungszeichen werden flach an den `Guidelines`-Abschnitt angehängt, ohne Präfix oder Gruppierung des Werkzeugnamens. Jede Richtlinie muss das Werkzeug benennen, auf das sie sich bezieht – vermeiden Sie „Verwenden Sie dieses Werkzeug, wenn …“, da das LLM nicht erkennen kann, welches Werkzeug „dies“ bedeutet. Schreiben Sie stattdessen „My_tool verwenden, wenn…“.\n\nHinweis: Einige Modelle sind Idioten und enthalten das @-Präfix in Werkzeugpfadargumenten. Integrierte Tools entfernen ein führendes @, bevor Pfade aufgelöst werden. Wenn Ihr benutzerdefiniertes Tool einen Pfad akzeptiert, normalisieren Sie auch ein führendes @.\n\nWenn Ihr benutzerdefiniertes Tool Dateien mutiert, verwenden Sie `withFileMutationQueue()`, damit es an derselben Datei-Warteschlange teilnimmt wie die integrierten `edit` und `write`. Dies ist wichtig, da Toolaufrufe standardmäßig parallel ausgeführt werden. Ohne die Warteschlange können zwei Tools denselben alten Dateiinhalt lesen, unterschiedliche Aktualisierungen berechnen und dann der letzte Schreibvorgang den anderen überschreiben.\n\nBeispiel für einen Fehlerfall: Ihr benutzerdefiniertes Werkzeug bearbeitet `foo.ts`, während das integrierte `edit` im selben Assistentenzug auch `foo.ts` ändert. Wenn Ihr Tool nicht an der Warteschlange teilnimmt, können beide das Original `foo.ts` lesen, separate Änderungen anwenden und eine dieser Änderungen geht verloren.\n\nÜbergeben Sie den tatsächlichen Zieldateipfad an `withFileMutationQueue()`, nicht das rohe Benutzerargument. Lösen Sie es zunächst in einen absoluten Pfad auf, relativ zu `ctx.cwd` oder dem Arbeitsverzeichnis Ihres Tools. Für vorhandene Dateien kanonisiert der Helfer durch `realpath()`, sodass Symlink-Aliase für dieselbe Datei eine Warteschlange gemeinsam nutzen. Bei neuen Dateien wird auf den aufgelösten absoluten Pfad zurückgegriffen, da noch nichts zu `realpath()` vorhanden ist.\n\nStellen Sie das gesamte Mutationsfenster auf diesem Zielpfad in die Warteschlange. Dazu gehört die Lese-, Änderungs- und Schreiblogik, nicht nur der endgültige Schreibvorgang.\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### Werkzeugdefinition\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**Nutzungsabrechnung:** Wenn ein Tool verschachtelte LLM-Aufrufe durchführt, geben Sie deren kombinierte `Usage` als `usage` zurück. Pi behält es im Tool-Ergebnis bei und fügt es in die Fußzeile ein, `/session` und RPC Sitzungssummen. `tool_result` Handler können diesen Wert überprüfen oder ersetzen.\n\n**Signalisierungsfehler:** Um eine Toolausführung als fehlgeschlagen zu markieren (setzt `isError: true` für das Ergebnis und meldet es an das LLM), werfen Sie einen Fehler von `execute` aus. Durch die Rückgabe eines Werts wird niemals das Fehlerflag gesetzt, unabhängig davon, welche Eigenschaften Sie in das Rückgabeobjekt aufnehmen.\n\n**Vorzeitige Beendigung:** Geben Sie `terminate: true` von `execute()` zurück, um darauf hinzuweisen, dass der automatische Folge-LLM-Aufruf nach der aktuellen Werkzeugcharge übersprungen werden sollte. Dies wird nur wirksam, wenn jedes finalisierte Werkzeugergebnis in diesem Stapel beendet wird. Unter [examples/extensions/structured-output.ts](../examples/extensions/structured-output.ts) finden Sie ein Minimalbeispiel, bei dem der Agent mit einem abschließenden Toolaufruf mit strukturierter Ausgabe endet.\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**Wichtig:** Verwenden Sie `StringEnum` von `@earendil-works/pi-ai` für String-Aufzählungen. `Type.Union`/`Type.Literal` funktioniert nicht mit Googles API.\n\n**Argumentvorbereitung:** `prepareArguments(args)` ist optional. Falls definiert, wird es vor der Schemavalidierung und vor `execute()` ausgeführt. Verwenden Sie es, um eine ältere akzeptierte Eingabeform nachzuahmen, wenn Pi eine ältere Sitzung fortsetzt, deren gespeicherte Tool-Aufrufargumente nicht mehr mit dem aktuellen Schema übereinstimmen. Geben Sie das Objekt zurück, das anhand von `parameters` validiert werden soll. Halten Sie das öffentliche Schema streng. Fügen Sie keine veralteten Kompatibilitätsfelder zu `parameters` hinzu, nur damit alte fortgesetzte Sitzungen weiterhin funktionieren.\n\nBeispiel: Eine ältere Sitzung kann einen `edit`-Tool-Aufruf mit `oldText` und `newText` der obersten Ebene enthalten, während das aktuelle Schema nur `edits: [{ oldText, newText }]` akzeptiert.\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### Überschreiben integrierter Tools\n\nExtensions kann integrierte Tools (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`) überschreiben, indem ein Tool mit demselben Namen registriert wird. Im interaktiven Modus wird in diesem Fall eine Warnung angezeigt.\n\n```bash\n# Extension's read tool replaces built-in read\npi -e ./tool-override.ts\n```\n\nAlternativ können Sie `--no-builtin-tools` verwenden, um ohne integrierte Tools zu starten und gleichzeitig die Erweiterungstools aktiviert zu lassen:\n```bash\n# No built-in tools, only extension tools\npi --no-builtin-tools -e ./my-extension.ts\n```\n\nUnter [examples/extensions/tool-override.ts](../examples/extensions/tool-override.ts) finden Sie ein vollständiges Beispiel, das `read` mit Protokollierung und Zugriffskontrolle überschreibt.\n\n**Rendering:** Die integrierte Renderer-Vererbung wird pro Slot aufgelöst. Ausführungsüberschreibung und Rendering-Überschreibung sind unabhängig voneinander. Wenn Ihre Außerkraftsetzung `renderCall` weglässt, wird das integrierte `renderCall` verwendet. Wenn Ihre Außerkraftsetzung `renderResult` weglässt, wird das integrierte `renderResult` verwendet. Wenn Ihre Überschreibung beides weglässt, wird automatisch der integrierte Renderer verwendet (Syntaxhervorhebung, Unterschiede usw.). Dadurch können Sie integrierte Tools für die Protokollierung oder Zugriffskontrolle umschließen, ohne die Benutzeroberfläche neu implementieren zu müssen.\n\n**Prompt-Metadaten:** `promptSnippet` und `promptGuidelines` werden nicht vom integrierten Tool geerbt. Wenn Ihre Außerkraftsetzung diese Eingabeaufforderungsanweisungen beibehalten soll, definieren Sie sie explizit in der Außerkraftsetzung.\n\n**Ihre Implementierung muss mit der genauen Ergebnisform** übereinstimmen, einschließlich des Typs `details`. Die Benutzeroberfläche und die Sitzungslogik hängen für das Rendering und die Statusverfolgung von diesen Formen ab.\n\nIntegrierte Tool-Implementierungen:\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### Remote-Ausführung\n\nIntegrierte Tools unterstützen steckbare Vorgänge zum Delegieren an Remote-Systeme (SSH, Container usw.):\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**Betriebsschnittstellen:** `ReadOperations`, `WriteOperations`, `EditOperations`, `BashOperations`, `LsOperations`, `GrepOperations`, `FindOperations`\n\nFür `user_bash` können Erweiterungen das lokale Shell-Backend von pi über `createLocalBashOperations()` wiederverwenden, anstatt das Spawnen lokaler Prozesse, die Shell-Auflösung und die Beendigung des Prozessbaums neu zu implementieren.\n\nDas bash-Tool unterstützt auch einen Spawn-Hook, um den Befehl, cwd oder env vor der Ausführung anzupassen:\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()` macht die aktuelle Sitzung Befehlen über `PI_SESSION_ID`, `PI_SESSION_FILE`, `PI_PROVIDER`, `PI_MODEL` und `PI_REASONING_LEVEL` zugänglich. Die Injektion erfolgt vor `spawnHook`, sodass Hooks diese Werte in `env` erhalten und sie beibehalten, wenn sie die vorhandene Umgebung wie oben verbreiten. Stellen Sie `exposeSessionEnvironment: false` ein, um sie zu deaktivieren:\n\n```typescript\nconst bashTool = createBashTool(cwd, {\n  exposeSessionEnvironment: false,\n});\n```\n\nSiehe [Bash tool session environment](environment-variables.md#bash-tool-session-environment) für Variablensemantik. Siehe [examples/extensions/ssh.ts](../examples/extensions/ssh.ts) für ein vollständiges SSH-Beispiel mit `--ssh`-Flag.\n\n### Ausgabekürzung\n\n**Tools MÜSSEN ihre Ausgabe abschneiden**, um eine Überlastung des LLM-Kontexts zu vermeiden. Große Ausgaben können Folgendes verursachen:\n- Kontextüberlauffehler (Eingabeaufforderung zu lang)\n- Verdichtungsfehler\n- Beeinträchtigte Modellleistung\n\nDas integrierte Limit beträgt **50 KB** (~10.000 Token) und **2000 Zeilen**, je nachdem, was zuerst erreicht wird. Verwenden Sie die exportierten Kürzungsdienstprogramme:\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**Wichtige Punkte:**\n- Verwenden Sie `truncateHead` für Inhalte, bei denen der Anfang wichtig ist (Suchergebnisse, Dateilesevorgänge).\n- Verwenden Sie `truncateTail` für Inhalte, bei denen es auf das Ende ankommt (Protokolle, Befehlsausgabe).\n- Informieren Sie das LLM immer, wenn die Ausgabe gekürzt wird und wo die Vollversion zu finden ist\n- Dokumentieren Sie die Kürzungsgrenzen in der Beschreibung Ihres Tools\n\nUnter [examples/extensions/truncated-tool.ts](../examples/extensions/truncated-tool.ts) finden Sie ein vollständiges Beispiel für das Umschließen von `rg` (ripgrep) mit korrekter Kürzung.\n\n### Mehrere Tools\n\nEine Erweiterung kann mehrere Tools mit gemeinsamem Status registrieren:\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### Benutzerdefiniertes Rendering\n\nTools können `renderCall` und `renderResult` für die benutzerdefinierte TUI-Anzeige bereitstellen. Siehe [tui.md](tui.md) für die vollständige Komponente API und [tool-execution.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/modes/interactive/components/tool-execution.ts) für die Zusammensetzung der Werkzeugreihen.\n\nStandardmäßig ist die Werkzeugausgabe in eine `Box` eingebunden, die den Abstand und den Hintergrund übernimmt. Ein definiertes `renderCall` oder `renderResult` muss ein `Component` zurückgeben. Wenn kein Slot-Renderer definiert ist, verwendet `tool-execution.ts` das Fallback-Rendering für diesen Slot.\n\nLegen Sie `renderShell: \"self\"` fest, wenn das Tool seine eigene Shell rendern soll, anstatt die Standardeinstellung `Box` zu verwenden. Dies ist nützlich für Tools, die eine vollständige Kontrolle über den Rahmen oder das Hintergrundverhalten benötigen, beispielsweise große Vorschauen, die nach dem Einschwingen des Tools visuell stabil bleiben müssen.\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` und `renderResult` erhalten jeweils ein `context`-Objekt mit:\n- `args` – die aktuellen Werkzeugaufrufargumente\n- `state` – gemeinsamer zeilenlokaler Zustand über `renderCall` und `renderResult`\n- `lastComponent` – die zuvor zurückgegebene Komponente für diesen Steckplatz, falls vorhanden\n- `invalidate()` – Erneutes Rendern dieser Werkzeugreihe anfordern\n- `toolCallId`, `cwd`, `executionStarted`, `argsComplete`, `isPartial`, `expanded`, `showImages`, `isError`\n\nVerwenden Sie `context.state` für den steckplatzübergreifenden Freigabestatus. Behalten Sie Slot-lokale Caches für die zurückgegebene Komponenteninstanz bei, wenn Sie dieselbe Komponente beim Rendern wiederverwenden und mutieren möchten.\n\n#### renderCall\n\nRendert den Toolaufruf oder Header:\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\nRendert das Werkzeugergebnis oder die Ausgabe:\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\nWenn ein Slot absichtlich keinen sichtbaren Inhalt hat, geben Sie eine leere `Component` zurück, beispielsweise eine leere `Container`.\n\n#### Hinweise zur Tastenkombination\n\nVerwenden Sie `keyHint()`, um Tastenkombinationshinweise anzuzeigen, die die aktive Tastenkombinationskonfiguration berücksichtigen:\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\nVerfügbare Funktionen:\n- `keyHint(keybinding, description)` – Formatiert eine konfigurierte Tastenkombinations-ID wie `\"app.tools.expand\"` oder `\"tui.select.confirm\"`\n- `keyText(keybinding)` – Gibt den roh konfigurierten Schlüsseltext für eine Tastenkombinations-ID zurück\n- `rawKeyHint(key, description)` – Formatieren Sie eine Rohschlüsselzeichenfolge\n\nVerwenden Sie namensraumbasierte Tastenkombinations-IDs:\n- Coding-Agent-IDs verwenden den Namespace `app.*`, zum Beispiel `app.tools.expand`, `app.editor.external`, `app.session.rename`\n- Geteilte TUI-IDs verwenden den Namespace `tui.*`, zum Beispiel `tui.select.confirm`, `tui.select.cancel`, `tui.input.tab`\n\nDie vollständige Liste der Tastenkombinations-IDs und Standardeinstellungen finden Sie unter [keybindings.md](keybindings.md). `keybindings.json` verwendet dieselben Namespace-IDs.\n\nBenutzerdefinierte Editoren und `ctx.ui.custom()`-Komponenten erhalten `keybindings: KeybindingsManager` als injiziertes Argument. Sie sollten diesen injizierten Manager direkt verwenden, anstatt `getKeybindings()` oder `setKeybindings()` aufzurufen.\n\n#### Best Practices\n\n- Verwenden Sie `Text` mit Polsterung `(0, 0)`. Die Standardbox übernimmt die Auffüllung.\n- Verwenden Sie `\\n` für mehrzeilige Inhalte.\n- Behandeln Sie `isPartial` für den Streaming-Fortschritt.\n- Support `expanded` für Details auf Anfrage.\n- Halten Sie die Standardansicht kompakt.\n- Lesen Sie `context.args` in `renderResult`, anstatt Argumente in `context.state` zu kopieren.\n- Verwenden Sie `context.state` nur für Daten, die über Anruf- und Ergebnisslots hinweg gemeinsam genutzt werden müssen.\n- `context.lastComponent` wiederverwenden, wenn dieselbe Komponenteninstanz direkt aktualisiert werden kann.\n- Verwenden Sie `renderShell: \"self\"` nur, wenn die standardmäßige Box-Shell im Weg ist. Im Self-Shell-Modus ist das Tool für den Rahmen, die Polsterung und den Hintergrund selbst verantwortlich.\n\n#### Zurückgreifen\n\nWenn ein Slot-Renderer nicht definiert ist oder Folgendes auslöst:\n- `renderCall`: Zeigt den Werkzeugnamen an\n- `renderResult`: Zeigt Rohtext von `content`\n\n### Dynamische Werkzeugbeladung\n\nExtensions kann viele Werkzeuge registrieren, während nur ein kleiner Anfangssatz aktiv bleibt. Ein Werkzeug kann dann während der Ausführung weitere Werkzeuge mit `pi.setActiveTools()` hinzufügen. Pi erkennt rein additive Änderungen, zeichnet die neu verfügbaren Werkzeugnamen für dieses Werkzeugergebnis auf und wendet den aktualisierten aktiven Satz vor der nächsten Modellanforderung an.\n\nDas funktioniert bei jedem Modell. Models mit nativer Unterstützung für verzögertes Laden behält das stabile Eingabeaufforderungspräfix bei und lädt die neuen Definitionen an der Tool-Ergebnisposition. Andere Modelle nutzen den unten beschriebenen Fallback.\n\nDer Lebenszyklus ist:\n\n1. Registrieren Sie jedes Werkzeug mit `pi.registerTool()`, damit es in `pi.getAllTools()` erscheint.\n2. Lassen Sie Ladetools wie `search_tools` aktiv und durchsuchbare Tools inaktiv.\n3. Rufen Sie während der Loader-Ausführung `pi.setActiveTools([...currentTools,...matchingTools])` auf. Die Änderung muss additiv sein: Derzeit aktive Werkzeuge dürfen nicht im selben Aufruf entfernt werden.\n4. Pi zeichnet auf, welche Werkzeuge zum Werkzeugergebnis des Laders hinzugefügt wurden.\n5. Vor der nächsten Modellantwort stellt Pi die hinzugefügten Definitionen mithilfe des nativen verzögerten Ladens bereit, sofern dies unterstützt wird, oder andernfalls der normalen Liste der aktiven Tools.\n\nSie müssen keine anbieterspezifischen Tool-Referenzen zurückgeben oder den Loader als spezielles Suchtool markieren. Der aktive Werkzeugwechsel ist das Signal. An `pi.setActiveTools()` übergebene Namen müssen bereits registriert sein; Unbekannte Namen werden ignoriert.\n\n#### Models mit nativem verzögertem Laden\n\n- **Anthropisch**\n  - **Models:** Sonett, Opus, Fable Version 4.5 oder neuer (ohne Haiku)\n  - **Native Darstellung:** Aufgeschobene Definitionen verwenden `defer_loading`; Der Ladepunkt verwendet `tool_reference` Inhalte.\n- **OpenAI**\n  - **Models:** `gpt-5.4` und neuere Familie\n  - **Native Darstellung:** Pi fügt abgeschlossene Client-Elemente `tool_search_call` und `tool_search_output` am Ladepunkt hinzu.\n\nFür ein verifiziertes benutzerdefiniertes Modell oder einen Proxy kann die native Handhabung mit `compat.supportsToolReferences: true` für `anthropic-messages` oder `compat.supportsToolSearch: true` für `openai-responses` und `openai-codex-responses` aktiviert werden. Lassen Sie diese deaktiviert, es sei denn, der Endpunkt und das Modell akzeptieren das entsprechende native Protokoll.\n\n#### Fallback-Verhalten\n\nBei allen anderen Modellen und Anbietern funktioniert die dynamische Aktivierung weiterhin: Pi sendet die komplette aktuell aktive Werkzeugliste normal bei der nächsten Anfrage. Das Modell kann die neu aktivierten Tools aufrufen, aber das Hinzufügen ihrer Definitionen kann dazu führen, dass das zwischengespeicherte Eingabeaufforderungspräfix des Anbieters ungültig wird.\n\nPi verwendet diesen sicheren Fallback auch, wenn der aktive Satz nicht rein additiv ist, beispielsweise beim Ersetzen einer Werkzeuggruppe durch eine andere. Daher funktionieren Werkzeugentfernungen, sie verwenden jedoch kein verzögertes Laden.\n\nUm das beste Cache-Verhalten zu erzielen, lassen Sie das Loader-Tool während der gesamten Sitzung aktiv und fügen Sie Tools hinzu, anstatt den aktiven Satz zu ersetzen. Beachten Sie außerdem, dass durch die Aktivierung eines Tools mit `promptSnippet` oder `promptGuidelines` die Systemeingabeaufforderung neu erstellt wird; Diese systembedingte Änderung kann das Präfix ungültig machen, selbst wenn der Anbieter verzögerte Schemata unterstützt. Langsam geladene Tools sollten sich normalerweise auf ihr Tool `description` verlassen und nur aktive Eingabeaufforderungsmetadaten weglassen.\n\n#### Beispiel für ein Suchtool\n\nDie folgende Erweiterung registriert zwei durchsuchbare Tools, entfernt sie aus dem anfänglichen aktiven Satz und behält nur `search_tools` als Ladeprogramm bei. Das Beispiel verwendet einen einfachen Schlüsselwortabgleich, aber die Suchimplementierung könnte BM25, Einbettungen, einen Remote-Katalog oder projektspezifisches Routing verwenden.\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\nWenn `search_tools` eine Übereinstimmung hinzufügt, erhält das Modell diese Definition bei der unmittelbar folgenden Anfrage. Bei einem nativfähigen Modell wird die Definition nach dem Suchergebnis verankert, ohne dass das anfängliche Tool-Schema-Präfix geändert wird. Bei anderen Modellen erscheint es auf derselben folgenden Anfrage in der normalen Werkzeugliste.\n\n## Benutzerdefinierte Benutzeroberfläche\n\nExtensions kann über `ctx.ui`-Methoden mit Benutzern interagieren und anpassen, wie Nachrichten/Tools gerendert werden.\n\n**Für benutzerdefinierte Komponenten siehe [tui.md](tui.md)**, das Muster zum Kopieren und Einfügen enthält für:\n- Auswahldialoge (SelectList)\n- Asynchrone Vorgänge mit Abbrechen (BorderedLoader)\n- Einstellungen umschalten (SettingsList)\n- Statusanzeigen (setStatus)\n- Arbeitsmeldung, Sichtbarkeit und Anzeige während des Streamings (`setWorkingMessage`, `setWorkingVisible`, `setWorkingIndicator`)\n- Widgets über/unter dem Editor (setWidget)\n- Autovervollständigungsanbieter, die über der integrierten Schrägstrich-/Pfadvervollständigung liegen (addAutocompleteProvider)\n- Benutzerdefinierte Fußzeilen (setFooter)\n\n### Dialoge\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#### Zeitgesteuerte Dialoge mit Countdown\n\nDialoge unterstützen eine `timeout`-Option, die automatisch mit einer Live-Countdown-Anzeige geschlossen wird:\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**Rückgabewerte bei Timeout:**\n- `select()` gibt `undefined` zurück\n- `confirm()` gibt `false` zurück\n- `input()` gibt `undefined` zurück\n\n#### Manuelle Entlassung mit AbortSignal\n\nFür mehr Kontrolle (z. B. um Timeout von Benutzerabbruch zu unterscheiden) verwenden Sie `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\nVollständige Beispiele finden Sie unter [examples/extensions/timed-confirm.ts](../examples/extensions/timed-confirm.ts).\n\n### Widgets, Status und Fußzeile\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\nBenutzerdefinierte Arbeitsindikatorrahmen werden wörtlich wiedergegeben. Wenn Sie Farben wünschen, fügen Sie diese selbst zu den Rahmenleisten hinzu, zum Beispiel mit `ctx.ui.theme.fg(...)`.\n\n### Automatische Vervollständigung Providers\n\nVerwenden Sie `ctx.ui.addAutocompleteProvider()`, um benutzerdefinierte Autovervollständigungslogik über den integrierten Schrägstrichbefehl und den Pfadanbieter zu stapeln. Legen Sie `triggerCharacters` für benutzerdefinierte natürliche Auslöser wie `Verwenden Sie `ctx.ui.addAutocompleteProvider()`, um benutzerdefinierte Autovervollständigungslogik über den integrierten Schrägstrichbefehl und den Pfadanbieter zu stapeln. Legen Sie `triggerCharacters` für benutzerdefinierte natürliche Auslöser wie  fest.\n\nTypisches Muster:\n\n- Überprüfen Sie den Text vor dem Cursor\n- Geben Sie Ihre eigenen Vorschläge zurück, wenn Ihre erweiterungsspezifische Syntax übereinstimmt\n- andernfalls delegieren an `current.getSuggestions(...)`\n- delegieren Sie `applyCompletion(...)`, es sei denn, Sie benötigen ein benutzerdefiniertes Einfügeverhalten\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\nUnter [github-issue-autocomplete.ts](../examples/extensions/github-issue-autocomplete.ts) finden Sie ein vollständiges Beispiel, das die neuesten offenen GitHub-Probleme mit `gh issue list` vorlädt und sie lokal filtert, um eine schnelle `#...`-Vervollständigung zu ermöglichen. Es erfordert GitHub CLI (`gh`) und einen GitHub Repository-Checkout.\n\n### Benutzerdefinierte Komponenten\n\nFür eine komplexe Benutzeroberfläche verwenden Sie `ctx.ui.custom()`. Dadurch wird der Editor vorübergehend durch Ihre Komponente ersetzt, bis `done()` aufgerufen wird:\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\nDer Rückruf erhält:\n- `tui` – TUI Instanz (für Bildschirmabmessungen, Fokusverwaltung)\n- `theme` – Aktuelles Thema für das Styling\n- `keybindings` – App-Tastenkombinationsmanager (zum Überprüfen von Verknüpfungen)\n- `done(value)` – Aufruf zum Schließen der Komponente und Rückgabewert\n\nSiehe [tui.md](tui.md) für die vollständige Komponente API.\n\n#### Overlay-Modus (experimentell)\n\nÜbergeben Sie `{ overlay: true }`, um die Komponente als schwebendes Modal über dem vorhandenen Inhalt darzustellen, ohne den Bildschirm zu löschen:\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Übergeben Sie für erweiterte Positionierung (Anker, Ränder, Prozentsätze, reaktionsfähige Sichtbarkeit) `overlayOptions`. Verwenden Sie `onHandle`, um Fokus oder Sichtbarkeit programmgesteuert zu steuern:\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\nEin fokussiertes sichtbares Overlay kann Eingaben zurückfordern, nachdem die vorübergehende benutzerdefinierte Benutzeroberfläche ohne Overlay geschlossen wird. Wenn Sie absichtlich möchten, dass eine andere Komponente die Eingabe beibehält, während die Überlagerung sichtbar bleibt, rufen Sie `handle.unfocus({ target })` auf. Das Übergeben von `{ target: null }` gibt die Überlagerung frei, ohne eine andere Komponente zu fokussieren.\n\nSiehe [tui.md](tui.md) für die vollständigen `OverlayOptions` und `OverlayHandle` API und [overlay-qa-tests.ts](../examples/extensions/overlay-qa-tests.ts) für Beispiele.\n\n### Benutzerdefinierter Editor\n\nErsetzen Sie den Haupteingabeeditor durch eine benutzerdefinierte Implementierung (VIM-Modus, Emacs-Modus usw.):\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**Wichtige Punkte:**\n- Erweitern Sie `CustomEditor` (nicht Basis `Editor`), um App-Tastenkombinationen zu erhalten (Escape zum Abbrechen, Strg+D, Modellwechsel).\n- Rufen Sie `super.handleInput(data)` für Schlüssel an, die Sie nicht verwalten\n- Factory empfängt `tui`, `theme` und `keybindings` von der App\n- Verwenden Sie `ctx.ui.getEditorComponent()` vor `setEditorComponent()`, um den zuvor konfigurierten benutzerdefinierten Editor zu umschließen\n- Übergeben Sie `undefined`, um den Standardwert wiederherzustellen: `ctx.ui.setEditorComponent(undefined)`\n\nUm mit einer anderen Erweiterung zu komponieren, die den Editor bereits ersetzt hat, erfassen Sie die vorherige Factory, bevor Sie Ihre festlegen:\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\nEin vollständiges Beispiel mit Modusanzeige finden Sie unter [tui.md](tui.md) Muster 7.\n\n### Nachrichten- und Eintragsrendering\n\nRegistrieren Sie einen benutzerdefinierten Renderer für Nachrichten bei Ihrem `customType`. Verwenden Sie Nachrichtenrenderer für Inhalte, die am LLM-Kontext teilnehmen sollen:\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\nNachrichten werden über `pi.sendMessage()` gesendet:\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\nFür nur TUI-Inhalte, die nicht an das LLM gesendet werden sollen, rendern Sie stattdessen benutzerdefinierte Einträge:\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### Themenfarben\n\nAlle Renderfunktionen erhalten ein `theme`-Objekt. Weitere Informationen zum Erstellen benutzerdefinierter Designs und der vollständigen Farbpalette finden Sie unter [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\nZur Syntaxhervorhebung in benutzerdefinierten Tool-Renderern:\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## Fehlerbehandlung\n\n- Erweiterungsfehler werden protokolliert, der Agent fährt fort\n- `tool_call` Fehler blockieren das Tool (ausfallsicher)\n- Werkzeugfehler `execute` müssen durch Werfen gemeldet werden; Der ausgegebene Fehler wird abgefangen, dem LLM mit `isError: true` gemeldet und die Ausführung wird fortgesetzt\n\n## Modusverhalten\n\n| Modus | `ctx.mode` | `ctx.hasUI` | Notizen |\n|------|------------|-------------|-------|\n| Interaktiv | `\"tui\"` | `true` | Vollständig TUI mit Terminal-Rendering |\n| RPC (`--mode rpc`) | `\"rpc\"` | `true` | Dialoge und Benachrichtigungen über das JSON-Protokoll; `custom()` gibt `undefined` zurück. Siehe [rpc.md](rpc.md) |\n| JSON (`--mode json`) | `\"json\"` | `false` | Ereignisstrom zu stdout; UI-Methoden sind No-Ops |\n| Drucken (`-p`) | `\"print\"` | `false` | Extensions wird ausgeführt, kann aber nicht aufgefordert werden |\n\nVerwenden Sie `ctx.mode === \"tui\"` vor TUI-spezifischen Funktionen (`custom()`, Komponentenfabriken, Terminaleingabe). Verwenden Sie `ctx.hasUI` vor Dialog- und Benachrichtigungsmethoden, die sowohl im TUI- als auch im RPC-Modus funktionieren.\n\n## Beispielreferenz\n\nAlle Beispiele in [examples/extensions/](../examples/extensions/).\n\n| Beispiel | Beschreibung | Taste APIs |\n|---------|-------------|----------|\n| **Werkzeuge** |  |  |\n| `hello.ts` | Minimale Werkzeugregistrierung | `registerTool` |\n| `question.ts` | Tool mit Benutzerinteraktion | `registerTool`, `ui.select` |\n| `questionnaire.ts` | Mehrstufiges Assistententool | `registerTool`, `ui.custom` |\n| `todo.ts` | Zustandsbehaftetes Tool mit Persistenz | `registerTool`, `appendEntry`, `renderResult`, Sitzungsereignisse |\n| `dynamic-tools.ts` | Registrieren Sie Tools nach dem Start und während Befehlen | `registerTool`, `session_start`, `registerCommand` |\n| `structured-output.ts` | Endgültiges strukturiertes Ausgabetool mit `terminate: true` | `registerTool`, Werkzeugergebnisse beenden |\n| `truncated-tool.ts` | Beispiel für Ausgabekürzung | `registerTool`, `truncateHead` |\n| `tool-override.ts` | Überschreiben Sie das integrierte Lesetool | `registerTool` (gleicher Name wie integriert) |\n| **Befehle** |  |  |\n| `pirate.ts` | Ändern Sie die Systemaufforderung pro Runde | `registerCommand`, `before_agent_start` |\n| `summarize.ts` | Befehl zur Konversationszusammenfassung | `registerCommand`, `ui.custom` |\n| `handoff.ts` | Anbieterübergreifende Modellübergabe | `registerCommand`, `ui.editor`, `ui.custom` |\n| `qna.ts` | Fragen und Antworten mit benutzerdefinierter Benutzeroberfläche | `registerCommand`, `ui.custom`, `setEditorText` |\n| `send-user-message.ts` | Benutzernachrichten einfügen | `registerCommand`, `sendUserMessage` |\n| `reload-runtime.ts` | Befehl zum erneuten Laden und Übergabe des LLM-Tools | `registerCommand`, `ctx.reload()`, `sendUserMessage` |\n| `shutdown-command.ts` | Befehl zum ordnungsgemäßen Herunterfahren | `registerCommand`, `shutdown()` |\n| **Veranstaltungen & Tore** |  |  |\n| `permission-gate.ts` | Blockieren Sie gefährliche Befehle | `on(\"tool_call\")`, `ui.confirm` |\n| `project-trust.ts` | Entscheiden oder verschieben Sie die Projektvertrauenswürdigkeit von einem Benutzer/einer globalen oder CLI-Erweiterung | `on(\"project_trust\")`, Vertrauens-UI, erforderliches Vertrauensergebnis |\n| `protected-paths.ts` | Schreibvorgänge in bestimmte Pfade blockieren | `on(\"tool_call\")` |\n| `confirm-destructive.ts` | Bestätigen Sie Sitzungsänderungen | `on(\"session_before_switch\")`, `on(\"session_before_fork\")` |\n| `dirty-repo-guard.ts` | Warnung vor Dirty-Git-Repo | `on(\"session_before_*\")`, `exec` |\n| `input-transform.ts` | Benutzereingaben transformieren | `on(\"input\")` |\n| `input-transform-streaming.ts` | Streaming-fähige Eingabetransformation | `on(\"input\")`, `streamingBehavior` |\n| `model-status.ts` | React für Modelländerungen | `on(\"model_select\")`, `setStatus` |\n| `provider-payload.ts` | Untersuchen Sie Nutzlasten und Antwortheader des Anbieters | `on(\"before_provider_request\")`, `on(\"after_provider_response\")` |\n| `system-prompt-header.ts` | Systemaufforderungsinformationen anzeigen | `on(\"agent_start\")`, `getSystemPrompt` |\n| `claude-rules.ts` | Laden Sie Regeln aus Dateien | `on(\"session_start\")`, `on(\"before_agent_start\")` |\n| `prompt-customizer.ts` | Fügen Sie mit `systemPromptOptions` eine kontextbezogene Werkzeugführung hinzu | `on(\"before_agent_start\")`, `BuildSystemPromptOptions` |\n| `file-trigger.ts` | File Watcher löst Meldungen aus | `sendMessage` |\n| **Verdichtung & Sitzungen** |  |  |\n| `custom-compaction.ts` | Zusammenfassung der benutzerdefinierten Komprimierung | `on(\"session_before_compact\")` |\n| `trigger-compact.ts` | Komprimierung manuell auslösen | `compact()` |\n| `git-checkpoint.ts` | Git in Runden verstauen | `on(\"turn_start\")`, `on(\"session_before_fork\")`, `exec` |\n| `git-merge-and-resolve.ts` | Konflikte abrufen, zusammenführen und lösen | `on(\"agent_end\")`, `exec`, `sendUserMessage` |\n| `auto-commit-on-exit.ts` | Commit beim Herunterfahren | `on(\"session_shutdown\")`, `exec` |\n| **UI-Komponenten** |  |  |\n| `status-line.ts` | Statusanzeige für die Fußzeile | `setStatus`, Sitzungsereignisse |\n| `working-indicator.ts` | Passen Sie die Streaming-Arbeitsanzeige an | `setWorkingIndicator`, `registerCommand` |\n| `github-issue-autocomplete.ts` | Fügen Sie `#1234` Problemabschlüsse zusätzlich zur integrierten automatischen Vervollständigung hinzu, indem Sie die letzten offenen Probleme von `gh issue list` vorab laden | `addAutocompleteProvider`, `on(\"session_start\")`, `exec` |\n| `custom-footer.ts` | Fußzeile vollständig ersetzen | `registerCommand`, `setFooter` |\n| `custom-header.ts` | Ersetzen Sie den Start-Header | `on(\"session_start\")`, `setHeader` |\n| `modal-editor.ts` | Modaler Editor im Vim-Stil | `setEditorComponent`, `CustomEditor` |\n| `rainbow-editor.ts` | Benutzerdefiniertes Editor-Styling | `setEditorComponent` |\n| `widget-placement.ts` | Widget über/unter dem Editor | `setWidget` |\n| `overlay-test.ts` | Overlay-Komponenten | `ui.custom` mit Overlay-Optionen |\n| `overlay-qa-tests.ts` | Umfangreiche Overlay-Tests | `ui.custom`, alle Overlay-Optionen |\n| `notify.ts` | Einfache Benachrichtigungen | `ui.notify` |\n| `timed-confirm.ts` | Dialoge mit Timeout | `ui.confirm` mit Timeout/Signal |\n| `mac-system-theme.ts` | Thema automatisch wechseln | `setTheme`, `exec` |\n| **Komplex Extensions** |  |  |\n| `plan-mode/` | Vollständige Implementierung des Planmodus | Alle Ereignistypen, `registerCommand`, `registerShortcut`, `registerFlag`, `setStatus`, `setWidget`, `sendMessage`, `setActiveTools` |\n| `preset.ts` | Speicherbare Voreinstellungen (Modell, Werkzeuge, Denken) | `registerCommand`, `registerShortcut`, `registerFlag`, `setModel`, `setActiveTools`, `setThinkingLevel`, `appendEntry` |\n| `tools.ts` | Schalten Sie die Benutzeroberfläche für Tools ein/aus | `registerCommand`, `setActiveTools`, `SettingsList`, Sitzungsereignisse |\n| **Remote & Sandbox** |  |  |\n| `ssh.ts` | SSH Fernausführung | `registerFlag`, `on(\"user_bash\")`, `on(\"before_agent_start\")`, Werkzeugoperationen |\n| `interactive-shell.ts` | Persistente Shell-Sitzung | `on(\"user_bash\")` |\n| `sandbox/` | Ausführung von Sandbox-Tools | Werkzeugoperationen |\n| `gondolin/` | Leiten Sie integrierte Tools und `!`-Befehle in eine Gondolin-Mikro-VM weiter | Werkzeugoperationen, integrierte Werkzeugüberschreibungen, `on(\"user_bash\")` |\n| `subagent/` | Unteragenten erzeugen | `registerTool`, `exec` |\n| **Spiele** |  |  |\n| `snake.ts` | Schlangenspiel | `registerCommand`, `ui.custom`, Tastaturbedienung |\n| `space-invaders.ts` | Space Invaders-Spiel | `registerCommand`, `ui.custom` |\n| `doom-overlay/` | Untergang im Overlay | `ui.custom` mit Overlay |\n| **Providers** |  |  |\n| `custom-provider-anthropic/` | Benutzerdefinierter Anthropic-Proxy | `registerProvider` |\n| `custom-provider-gitlab-duo/` | GitLab Duo-Integration | `registerProvider` mit OAuth |\n| **Nachrichten und Kommunikation** |  |  |\n| `message-renderer.ts` | Benutzerdefinierte Nachrichtenwiedergabe | `registerMessageRenderer`, `sendMessage` |\n| `entry-renderer.ts` | TUI-nur benutzerdefiniertes Eintragsrendering | `registerEntryRenderer`, `appendEntry` |\n| `event-bus.ts` | Ereignisse zwischen Erweiterungen | `pi.events` |\n| **Sitzungsmetadaten** |  |  |\n| `session-name.ts` | Benennen Sie Sitzungen für den Selektor | `setSessionName`, `getSessionName` |\n| `bookmark.ts` | Lesezeicheneinträge für /tree | `setLabel` |\n| **Verschiedenes** |  |  |\n| `inline-bash.ts` | Inline bash in Werkzeugaufrufen | `on(\"tool_call\")` |\n| `bash-spawn-hook.ts` | Passen Sie bash command, cwd und env vor der Ausführung an | `createBashTool`, `spawnHook` |\n| `with-deps/` | Erweiterung mit npm Abhängigkeiten | Paketstruktur mit `package.json` |","sourceFile":"extensions.md"},"index":{"title":"Pi Dokumentation","markdown":"Pi ist ein Kabelbaum mit minimaler Klemmenkodierung. Es ist so konzipiert, dass es im Kern klein bleibt und gleichzeitig durch TypeScript Erweiterungen, Fähigkeiten, prompt templates, Themen und Pi-Pakete erweitert wird.\n\n## Schnellstart\n\nInstallieren Sie Pi mit npm:\n\n```bash\nnpm install -g --ignore-scripts @earendil-works/pi-coding-agent\n```\n\n`--ignore-scripts` deaktiviert Abhängigkeitslebenszyklusskripte während der Installation. Pi erfordert keine Installationsskripte für normale npm-Installationen.\n\nUnter Linux oder macOS können Sie auch den Installer verwenden:\n\n```bash\ncurl -fsSL https://pi.dev/install.sh | sh\n```\n\nUm Pi selbst zu deinstallieren, verwenden Sie npm für Curl und npm für die Installation:\n\n```bash\nnpm uninstall -g @earendil-works/pi-coding-agent\n```\n\nVerwenden Sie für pnpm-, Yarn- oder Bun-Installationen den entsprechenden globalen Entfernungsbefehl: `pnpm remove -g @earendil-works/pi-coding-agent`, `yarn global remove @earendil-works/pi-coding-agent` oder `bun uninstall -g @earendil-works/pi-coding-agent`.\n\nFühren Sie es dann in einem Projektverzeichnis aus:\n\n```bash\npi\n```\n\nAuthentifizieren Sie sich mit `/login` für subscription providers oder legen Sie ein API key wie `ANTHROPIC_API_KEY` fest, bevor Sie pi starten.\n\nDen vollständigen Ablauf für den ersten Lauf finden Sie unter [Quickstart](quickstart.md).\n\n## Beginnen Sie hier\n\n- [Quickstart](quickstart.md) – Installieren, authentifizieren und eine erste Sitzung ausführen.\n- [Using Pi](usage.md) – interaktiver Modus, slash commands, context files und CLI Referenz.\n- [Providers](providers.md) – Abonnement und API-Schlüssel-Setup für integrierte Anbieter.\n- [llama.cpp](llama-cpp.md) – Führen Sie einen lokalen Router aus und verwalten Sie Modelle mit `/llama`.\n- [Security](security.md) – Projektvertrauen, sandbox Grenzen und Schwachstellenmeldung.\n- [Containerization](containerization.md) - sandbox pi mit Gondolin, Docker oder OpenShell.\n- [Settings](settings.md) – globale und Projekteinstellungen.\n- [Keybindings](keybindings.md) – Standardverknüpfungen und benutzerdefinierte Tastenkombinationen.\n- [Sessions](sessions.md) – Sitzungsverwaltung, Verzweigung und Baumnavigation.\n- [Compaction](compaction.md) - context compaction und branch summarization.\n\n## Anpassung\n\n- [Extensions](extensions.md) – TypeScript Module für Tools, Befehle, Ereignisse und benutzerdefinierte Benutzeroberfläche.\n- [Skills](skills.md) – Agent Skills für wiederverwendbare On-Demand-Funktionen.\n- [Prompt templates](prompt-templates.md) – wiederverwendbare Eingabeaufforderungen, die ab slash commands erweitert werden.\n- [Themes](themes.md) – integriert und benutzerdefiniert terminal themes.\n- [Pi packages](packages.md) – Bündeln und teilen Sie Erweiterungen, Fähigkeiten, Eingabeaufforderungen und Themen.\n- [Custom models](models.md) – Modelleinträge für unterstützte Anbieter APIs hinzufügen.\n- [Custom providers](custom-provider.md) – benutzerdefinierte APIs- und OAuth-Flows implementieren.\n\n## Programmatische Nutzung\n\n- [SDK](sdk.md) – Pi in Node.js Anwendungen einbetten.\n- [RPC mode](rpc.md) – Integration über stdin/stdout JSONL.\n- [JSON event stream mode](json.md) – Druckmodus mit strukturierten Ereignissen.\n- [TUI components](tui.md) – Erstellen Sie eine benutzerdefinierte Terminal-Benutzeroberfläche für Erweiterungen.\n\n## Referenz\n\n- [Environment variables](environment-variables.md) – Pi Prozesskonfiguration und Sitzungsmetadaten, die für bash Tools verfügbar sind.\n- [Session format](session-format.md) - JSONL Sitzungsdateiformat, Eintragstypen und SessionManager API.\n\n## Plattform-Setup\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## Entwicklung\n\n- [Development](development.md) – lokale Einrichtung, Projektstruktur und Debugging.","sourceFile":"index.md"},"json":{"title":"JSON Ereignis-Stream-Modus","markdown":"```bash\npi --mode json \"Your prompt\"\n```\n\nGibt alle Sitzungsereignisse als JSON-Zeilen bis stdout aus. Nützlich für die Integration von Pi in andere Tools oder benutzerdefinierte Benutzeroberflächen.\n\n## Ereignistypen\n\nWire-Ereignisse verwenden `JsonAgentSessionEvent`. Es passt\n[`AgentSessionEvent`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/agent-session.ts)\nmit der Ausnahme, dass Streaming-Nachrichtenaktualisierungen kumulative Snapshots auslassen:\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` gibt bei jeder Änderung die vollständigen ausstehenden Lenkungs- und Folgewarteschlangen aus. `compaction_start` und `compaction_end` decken sowohl die manuelle als auch die automatische Verdichtung ab.\n\nAndere Basisereignisse stammen aus\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## Nachrichtentypen\n\nBasisnachrichten von [`packages/ai/src/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/types.ts#L134):\n- `UserMessage` (Zeile 134)\n- `AssistantMessage` (Zeile 140)\n- `ToolResultMessage` (Zeile 152)\n\nErweiterte Nachrichten von [`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` (Zeile 29)\n- `CustomMessage` (Zeile 46)\n- `BranchSummaryMessage` (Zeile 55)\n- `CompactionSummaryMessage` (Zeile 62)\n\n## Ausgabeformat\n\nJede Zeile ist ein JSON-Objekt. Die erste Zeile ist der Sitzungsheader:\n\n```json\n{\"type\":\"session\",\"version\":3,\"id\":\"uuid\",\"timestamp\":\"...\",\"cwd\":\"/path\"}\n```\n\nGefolgt von den Ereignissen, sobald sie eintreten:\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` Datensätze sind nur Delta-Datensätze. Sie lassen sowohl das kumulative `message`-Feld als auch weg\n`assistantMessageEvent.partial`, um die Streamgröße linear zu halten. Verwenden Sie `contentIndex` und `delta`\num bei Bedarf Live-Text, Denk- oder Tool-Call-Argumente zusammenzustellen. `message_end` enthält\ndie endgültige maßgebliche Botschaft.\n\n## Beispiel\n\n```bash\npi --mode json \"List files\" 2>/dev/null | jq -c 'select(.type == \"message_end\")'\n```","sourceFile":"json.md"},"keybindings":{"title":"Tastenkombinationen","markdown":"Alle Tastaturkürzel können über `~/.pi/agent/keybindings.json` angepasst werden. Jede Aktion kann an einen oder mehrere Schlüssel gebunden werden.\n\nDie Konfigurationsdatei verwendet dieselben namespaced-Tastenkombinations-IDs, die pi intern verwendet und die Erweiterungsautoren in `keyHint()`- und injizierten `keybindings`-Managern verwenden.\n\nÄltere Konfigurationen, die vorab benannte IDs wie `cursorUp` oder `expandTools` verwenden, werden beim Start automatisch auf die benannten IDs migriert.\n\nNachdem Sie `keybindings.json` bearbeitet haben, führen Sie `/reload` in pi aus, um die Änderungen zu übernehmen, ohne die Sitzung neu zu starten.\n\n## Schlüsselformat\n\n`modifier+key` wobei Modifikatoren `ctrl`, `shift`, `alt`, `super` (kombinierbar) und Schlüssel sind:\n\n- **Buchstaben:** `a-z`\n- **Ziffern:** `0-9`\n- **Sondertasten:** `escape`, `esc`, `enter`, `return`, `tab`, `space`, `backspace`, `delete`, `insert`, `clear`, `home`, `end`, `pageUp`, `pageDown`, `up`, `down`, `left`, `right`\n- **Funktionstasten:** `f1`-`f12`\n- **Symbole:** `` ` ``, `-`, `=`, `[`, `]`, `\\`, `;`, `'`, `,`, `.`, `/`, `!`, `@`, `#`, `$`, `%`, `^`, `&`, `*`, `(`, `)`, `_`, `+`, `|`, `~`, `{`, `}`, `:`, `<`, `>`, `?`\n\nModifikatorkombinationen: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `super+k`, `ctrl+super+k`, `ctrl+1` usw.\n\n`super`-Bindungen erfordern ein Terminal, das den Modifikator separat meldet, normalerweise über das Kitty-Tastaturprotokoll. Ohne diese Unterstützung funktionieren sie möglicherweise nicht in Terminals.\n\n## Alle Aktionen\n\n### TUI Bewegung des Editor-Cursors\n\n| Tastenkombinations-ID | Standard | Beschreibung |\n|--------|---------|-------------|\n| `tui.editor.cursorUp` | `up` | Bewegen Sie den Cursor nach oben und durchsuchen Sie oben den älteren Verlauf |\n| `tui.editor.cursorDown` | `down` | Bewegen Sie den Cursor nach unten und durchsuchen Sie unten den neueren Verlauf |\n| `tui.editor.historyPrevious` | *(keiner)* | Wählen Sie den vorherigen Eintrag im Eingabeaufforderungsverlauf aus |\n| `tui.editor.historyNext` | *(keiner)* | Wählen Sie den nächsten Eingabeaufforderungsverlaufseintrag aus |\n| `tui.editor.cursorLeft` | `left`, `ctrl+b` | Cursor nach links bewegen |\n| `tui.editor.cursorRight` | `right`, `ctrl+f` | Cursor nach rechts bewegen |\n| `tui.editor.cursorWordLeft` | `alt+left`, `ctrl+left`, `alt+b` | Bewegen Sie das Cursorwort nach links |\n| `tui.editor.cursorWordRight` | `alt+right`, `ctrl+right`, `alt+f` | Cursorwort nach rechts bewegen |\n| `tui.editor.cursorLineStart` | `home`, `ctrl+home`, `ctrl+a` | Zum Zeilenanfang wechseln |\n| `tui.editor.cursorLineEnd` | `end`, `ctrl+end`, `ctrl+e` | Zum Zeilenende wechseln |\n| `tui.editor.jumpForward` | `ctrl+]` | Springe vorwärts zum Charakter |\n| `tui.editor.jumpBackward` | `ctrl+alt+]` | Springe zurück zum Charakter |\n| `tui.editor.pageUp` | `pageUp`, `ctrl+pageUp` | Scrollen Sie seitenweise nach oben |\n| `tui.editor.pageDown` | `pageDown`, `ctrl+pageDown` | Scrollen Sie seitenweise nach unten |\n\nDie dedizierten Verlaufsaktionen ändern stets Verlaufseinträge, unabhängig von der Cursorposition in einer mehrzeiligen Eingabeaufforderung. Explizite Verlaufsbindungen haben Vorrang vor Anwendungsaktionen, während der Haupteditor fokussiert ist. Die Bindung von `tui.editor.historyPrevious` an `ctrl+p` überschreibt also den Modellwechsel in diesem Kontext, ohne `Ctrl+P` in Selektoren zu ändern.\n\n### TUI Editor-Löschung\n\n| Tastenkombinations-ID | Standard | Beschreibung |\n|--------|---------|-------------|\n| `tui.editor.deleteCharBackward` | `backspace` | Zeichen rückwärts löschen |\n| `tui.editor.deleteCharForward` | `delete`, `ctrl+d` | Zeichen vorwärts löschen |\n| `tui.editor.deleteWordBackward` | `ctrl+w`, `alt+backspace` | Wort rückwärts löschen |\n| `tui.editor.deleteWordForward` | `alt+d`, `alt+delete` | Wort vorwärts löschen |\n| `tui.editor.deleteToLineStart` | `ctrl+u` | Bis zum Zeilenanfang löschen |\n| `tui.editor.deleteToLineEnd` | `ctrl+k` | Bis zum Zeilenende löschen |\n\n### TUI Eingabe\n\n| Tastenkombinations-ID | Standard | Beschreibung |\n|--------|---------|-------------|\n| `tui.input.newLine` | `shift+enter`, `ctrl+j` | Neue Zeile einfügen |\n| `tui.input.submit` | `enter` | Eingabe abschicken |\n| `tui.input.tab` | `tab` | Tab / Autovervollständigung |\n\n### TUI Ring töten\n\n| Tastenkombinations-ID | Standard | Beschreibung |\n|--------|---------|-------------|\n| `tui.editor.yank` | `ctrl+y` | Fügen Sie den zuletzt gelöschten Text ein |\n| `tui.editor.yankPop` | `alt+y` | Nach dem Ziehen durch den gelöschten Text blättern |\n| `tui.editor.undo` | `ctrl+-` | Letzte Bearbeitung rückgängig machen |\n\n### TUI Zwischenablage und Auswahl\n\n| Tastenkombinations-ID | Standard | Beschreibung |\n|--------|---------|-------------|\n| `tui.input.copy` | `ctrl+c` | Auswahl kopieren |\n| `tui.select.up` | `up` | Auswahl nach oben verschieben |\n| `tui.select.down` | `down` | Auswahl nach unten verschieben |\n| `tui.select.pageUp` | `pageUp` | Seite nach oben in der Liste |\n| `tui.select.pageDown` | `pageDown` | Seite nach unten in der Liste |\n| `tui.select.confirm` | `enter` | Auswahl bestätigen |\n| `tui.select.cancel` | `escape`, `ctrl+c` | Auswahl abbrechen |\n\n### TUI Vollbild-Ansichtsfenster\n\nDiese Aktionen gelten, wenn der interaktive Modus `--tui-mode fullscreen` verwendet und auf den primären Transkript-Bildlaufbereich abzielt. Mit der Zwei-Finger-Trackpad- und Mausrad-Eingabe scrollen Sie durch den Bereich unter dem Zeiger und kehren zum Transkript über dem festen Editor-/Status-/Fußzeilen-Dock zurück. Wenn Sie auf einen OSC 8-Hyperlink klicken, wird dieser im Standardhandler geöffnet. Durch Ziehen mit der primären Maustaste wird Text ausgewählt und in die Zwischenablage kopiert. Wenn Sie den oberen oder unteren Rand des Transkripts gedrückt halten, wird automatisch in den Off-Screen-Inhalt gescrollt.\n\nVollbild-Transkriptbindungen haben Vorrang vor Editorbindungen. Die standardmäßigen unveränderten Navigationstasten steuern daher das Transkript im Vollbildmodus, während ihre `ctrl`-Varianten weiterhin den Editor steuern. Außerhalb des Vollbildmodus steuern beide Varianten den Editor.\n\n| Schlüssel | Standardmodus | Vollbildmodus |\n|-----|--------------|-----------------|\n| `home`, `end` | Editor | Transkript |\n| `ctrl+home`, `ctrl+end` | Editor | Editor |\n| `pageUp`, `pageDown` | Editor | Transkript |\n| `ctrl+pageUp`, `ctrl+pageDown` | Editor | Editor |\n\nDieses Routing bleibt über die normalen Aktionsbindungen konfigurierbar. Beispielsweise steuert `\"tui.altScreen.pageUp\": \"ctrl+pageUp\"` `pageUp` den Editor und `ctrl+pageUp` das Transkript im Vollbildmodus. Binden Sie `tui.altScreen.halfPageUp` und `tui.altScreen.halfPageDown` für kleinere Transkriptschritte und behalten Sie dabei die ganzseitigen Bindungen bei. Durch die Einstellung `\"tui.altScreen.pageUp\": []` wird diese Transkriptverknüpfung vollständig deaktiviert. Benutzerbindungen ersetzen die Standardeinstellungen für diese Aktion.\n\n| Tastenkombinations-ID | Standard | Beschreibung |\n|--------|---------|-------------|\n| `tui.altScreen.pageUp` | `pageUp` | Scrollen Sie im Transkript eine Seite nach oben |\n| `tui.altScreen.pageDown` | `pageDown` | Scrollen Sie im Transkript eine Seite nach unten |\n| `tui.altScreen.halfPageUp` | *(keiner)* | Scrollen Sie im Transkript eine halbe Seite nach oben |\n| `tui.altScreen.halfPageDown` | *(keiner)* | Scrollen Sie im Transkript eine halbe Seite nach unten |\n| `tui.altScreen.previousPrompt` | `ctrl+shift+up` | Zur zuvor markierten Nachricht springen |\n| `tui.altScreen.nextPrompt` | `ctrl+shift+down` | Zur nächsten markierten Nachricht springen |\n| `tui.altScreen.top` | `home` | Scrollen Sie zum Anfang des Transkripts |\n| `tui.altScreen.bottom` | `end` | Scrollen Sie zum Ende des Transkripts und folgen Sie der neuen Ausgabe |\n\n### Anwendung\n\n| Tastenkombinations-ID | Standard | Beschreibung |\n|--------|---------|-------------|\n| `app.interrupt` | `escape` | Abbrechen / Abbruch |\n| `app.clear` | `ctrl+c` | Editor löschen (erster) / Beenden (zweiter) |\n| `app.exit` | `ctrl+d` | Beenden (wenn der Editor leer ist) |\n| `app.suspend` | `ctrl+z` (keine unter Windows) | Im Hintergrund anhalten |\n| `app.editor.external` | `ctrl+g` | Im externen Editor öffnen (`externalEditor`, `$VISUAL`, `$EDITOR`, Notepad unter Windows oder `nano` anderswo) |\n| `app.clipboard.pasteImage` | `ctrl+v` (`alt+v` unter Windows) | Bild oder Text aus der Zwischenablage einfügen |\n\n### Sitzungen\n\n| Tastenkombinations-ID | Standard | Beschreibung |\n|--------|---------|-------------|\n| `app.session.new` | *(keiner)* | Starten Sie eine neue Sitzung (`/new`) |\n| `app.session.tree` | *(keiner)* | session tree Navigator öffnen (`/tree`) |\n| `app.session.fork` | *(keiner)* | Aktuelle Sitzung verzweigen (`/fork`) |\n| `app.session.resume` | *(keiner)* | Sitzungs-Lebenslauf-Auswahl öffnen (`/resume`) |\n| `app.session.togglePath` | `ctrl+p` | Pfadanzeige umschalten |\n| `app.session.toggleSort` | `ctrl+s` | Sortiermodus umschalten |\n| `app.session.toggleNamedFilter` | `ctrl+n` | Schalten Sie den Nur-Namen-Filter um |\n| `app.session.rename` | `ctrl+r` | Sitzung umbenennen |\n| `app.session.delete` | `ctrl+d` | Sitzung löschen |\n| `app.session.deleteNoninvasive` | `ctrl+backspace` | Sitzung löschen, wenn die Abfrage leer ist |\n\n### Models und Denken\n\n| Tastenkombinations-ID | Standard | Beschreibung |\n|--------|---------|-------------|\n| `app.model.select` | `ctrl+l` | Modellauswahl öffnen |\n| `app.model.cycleForward` | `ctrl+p` | Wechseln Sie zum nächsten Modell |\n| `app.model.cycleBackward` | `shift+ctrl+p` | Wechseln Sie zum vorherigen Modell |\n| `app.thinking.cycle` | `shift+tab` | Zyklus-Denkebene |\n| `app.thinking.toggle` | `ctrl+t` | Denkblockaden abbauen oder erweitern |\n\n### Anzeige und Nachrichtenwarteschlange\n\n| Tastenkombinations-ID | Standard | Beschreibung |\n|--------|---------|-------------|\n| `app.tools.expand` | `ctrl+o` | Reduzieren oder erweitern Sie die Werkzeugausgabe |\n| `app.message.copy` | `ctrl+x` | Kopieren Sie die letzte Assistentennachricht oder die ausgewählte Nachricht in `/tree` |\n| `app.message.followUp` | `alt+enter` | Folgenachricht in der Warteschlange |\n| `app.message.dequeue` | `alt+up` | Stellen Sie Nachrichten in der Warteschlange im Editor wieder her |\n\n### Baumnavigation\n\n| Tastenkombinations-ID | Standard | Beschreibung |\n|--------|---------|-------------|\n| `app.tree.foldOrUp` | `ctrl+left`, `alt+left` | Aktuelles Zweigsegment falten oder zum vorherigen Segmentanfang springen |\n| `app.tree.unfoldOrDown` | `ctrl+right`, `alt+right` | Klappen Sie das aktuelle Zweigsegment auf oder springen Sie zum nächsten Segmentanfang oder Zweigende |\n| `app.tree.editLabel` | `shift+l` | Bearbeiten Sie die Beschriftung des ausgewählten Baumknotens |\n| `app.tree.toggleLabelTimestamp` | `shift+t` | Schalten Sie die Label-Zeitstempel in der Baumstruktur um |\n| `app.tree.filter.default` | `ctrl+d` | Stellen Sie den Baumfilter auf die Standardansicht ein |\n| `app.tree.filter.noTools` | `ctrl+t` | Schalten Sie den Baumfilter um, der die Werkzeugergebnisse ausblendet |\n| `app.tree.filter.userOnly` | `ctrl+u` | Schalten Sie den Baumfilter um, der nur Benutzernachrichten anzeigt |\n| `app.tree.filter.labeledOnly` | `ctrl+l` | Schalten Sie den Baumfilter um, der nur beschriftete Einträge anzeigt |\n| `app.tree.filter.all` | `ctrl+a` | Baumfilter umschalten, der alle Einträge anzeigt |\n| `app.tree.filter.cycleForward` | `ctrl+o` | Zyklusbaumfilter vorwärts |\n| `app.tree.filter.cycleBackward` | `shift+ctrl+o` | Zyklusbaumfilter rückwärts |\n\n### Bereichsbezogener Models-Selektor\n\nWird in der Modellauswahl mit Gültigkeitsbereich verwendet (geöffnet über `/scoped-models`).\n\n| Tastenkombinations-ID | Standard | Beschreibung |\n|--------|---------|-------------|\n| `app.models.save` | `ctrl+s` | Aktuelle Modellauswahl in den Einstellungen speichern |\n| `app.models.enableAll` | `ctrl+a` | Alle Modelle aktivieren (oder alle, die der aktuellen Suche entsprechen) |\n| `app.models.clearAll` | `ctrl+x` | Alle Modelle löschen (oder alle, die der aktuellen Suche entsprechen) |\n| `app.models.toggleProvider` | `ctrl+p` | Alle Modelle für den aktuellen Anbieter umschalten |\n| `app.models.reorderUp` | `alt+up` | Verschiebt das ausgewählte Modell in der Zyklusreihenfolge nach oben |\n| `app.models.reorderDown` | `alt+down` | Verschiebt das ausgewählte Modell in der Zyklusreihenfolge nach unten |\n\n## Benutzerdefinierte Konfiguration\n\nErstellen Sie `~/.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\nJede Aktion kann einen einzelnen Schlüssel oder eine Reihe von Schlüsseln haben. Die Benutzerkonfiguration überschreibt die Standardeinstellungen.\n\nUnter nativem Windows verfügt `app.suspend` über keine Standardbindung, da Windows-Terminals die Unix-Jobsteuerung nicht unterstützen. Wenn Sie es manuell binden, zeigt Pi eine Statusmeldung an, anstatt es anzuhalten. In der WSL gilt weiterhin das normale Linux `ctrl+z`/`fg`-Verhalten.\n\n### Emacs-Beispiel\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-Beispiel\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 unterstützt den [llama.cpp](https://github.com/ggml-org/llama.cpp) Router-Server. Der Router erkennt mehrere GGUF-Modelle und lädt oder entlädt sie bei Bedarf.\n\nVerwenden Sie einen aktuellen llama.cpp-Build mit Router-Unterstützung. Folgen Sie der [build instructions](https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md) oder installieren Sie eine [prebuilt release](https://github.com/ggml-org/llama.cpp/releases) für Ihre Plattform.\n\n## Starten Sie den Router\n\nBeginnen Sie `llama-server` ohne `--model` oder `-m`. Durch die Übergabe eines Modells wird der Einzelmodellmodus anstelle des Routermodus gestartet.\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\nWichtige Optionen:\n\n- `--models-dir ~/models` erkennt lokale GGUF-Dateien.\n- `--no-models-autoload` lädt weiterhin explizit bis `/llama`.\n- `--jinja` ermöglicht kompatible Chat-Vorlagen und Tool-Anrufe.\n- `-ngl 999` lädt so viele Schichten wie möglich auf die GPU.\n- `-c 32768` legt das Kontextfenster für jedes geladene Modell fest. Lassen Sie es weg, um den nativen Kontext des Modells zu verwenden, was möglicherweise wesentlich mehr Speicher erfordert.\n\nEin Einzeldateimodell kann direkt im Modellverzeichnis liegen. Platzieren Sie multimodale und Multi-Shard-Modelle in separaten Unterverzeichnissen:\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\nStarten Sie den Router neu, nachdem Sie Dateien manuell hinzugefügt haben. Für modellspezifische Kontextgrößen und andere Optionen verwenden Sie [llama.cpp model presets](https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md#model-presets).\n\n## Konfigurieren Sie Pi\n\nStarten Sie Pi und konfigurieren Sie den Anbieter:\n\n```text\n/login llama.cpp\n```\n\nGeben Sie die Router-URL und optional API key ein. Die Standard-URL ist `http://127.0.0.1:8080`.\n\nUmgebungsvariablen können dieselben Werte ohne `/login` konfigurieren:\n\n```bash\nexport LLAMA_BASE_URL=http://127.0.0.1:8080\nexport LLAMA_API_KEY=optional-secret\npi\n```\n\nWenn der Server einen API key verwendet, beginnen Sie `llama-server` mit dem passenden `--api-key`-Wert. Behalten Sie `--host 127.0.0.1` für nur lokalen Zugriff bei.\n\n## Modelle verwalten\n\nLaufen:\n\n```text\n/llama\n```\n\n- Wählen Sie ein entladenes Modell aus, um es zu laden.\n- Wählen Sie ein geladenes Modell aus, um es zu entladen.\n- Wählen Sie **Modell herunterladen…**, suchen Sie nach Hugging Face und wählen Sie dann ein Repository und eine Quantisierung aus. Genaue `owner/repository[:quant]`-Werte funktionieren auch.\n- Drücken Sie während eines Ladevorgangs oder Downloads die Escape-Taste, um den Abbruch zu bestätigen.\n\nDie Hugging Face-Suche verwendet `HF_TOKEN`, wenn sie festgelegt ist, und prüft dann `$HF_TOKEN_PATH`, `$HF_HOME/token`, `$XDG_CACHE_HOME/huggingface/token` und `~/.cache/huggingface/token`. Die Suche funktioniert auch ohne Authentifizierung, vorbehaltlich niedrigerer Ratengrenzen. Pi warnt vor dem Herunterladen von geschlossenen Repositories und Links zu deren Zugriffsseite. Der llama.cpp-Server führt den Download durch, daher muss sein Prozess auch `HF_TOKEN` haben, wenn das ausgewählte Repository Zugriff erfordert.\n\nWenn andere Modelle geladen sind, fragt Pi, ob diese zuerst entladen oder geladen bleiben sollen. Pi entlädt Modelle nicht stillschweigend und löscht niemals Modelldateien. Der Router kann mit anderen Clients geteilt werden, daher zeigt `/llama` immer den aktuellen Status des Routers an.\n\nIn `/model` werden nur geladene Modelle angezeigt. Nachdem Sie ein Modell geladen haben, führen Sie `/model` aus, um es für die aktuelle Pi Sitzung auszuwählen.\n\nWenn der Router die Verbindung trennt, zeigt `/llama` **Wiederholen** und **Schließen** an. Bei einem erneuten Versuch wird die Verbindung wiederhergestellt und der Modellstatus aktualisiert, ohne dass der unterbrochene Vorgang erneut abgespielt wird.\n\n## Fehlerbehebung\n\nÜberprüfen Sie, ob der Router erreichbar ist:\n\n```bash\ncurl http://127.0.0.1:8080/health\ncurl http://127.0.0.1:8080/models\n```\n\n- **Keine Modelle in `/llama`:** Überprüfen Sie `--models-dir`, das Verzeichnislayout, und starten Sie den Router neu.\n- **Modell fehlt in `/model`:** Laden Sie es zuerst mit `/llama`.\n- **Laden schlägt fehl oder verbraucht zu viel Speicher:** Senken Sie `-c` oder entladen Sie ein anderes Modell.\n- **Server ist nicht im Router-Modus:** Starten Sie ihn ohne `--model`, `-m` oder `-hf`.","sourceFile":"llama-cpp.md"},"models":{"title":"Benutzerdefiniert Models","markdown":"Fügen Sie benutzerdefinierte Anbieter und Modelle (Ollama, vLLM, LM Studio, Proxys) über `~/.pi/agent/models.json` hinzu.\n\n## Inhaltsverzeichnis\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## Minimales Beispiel\n\nFür lokale Modelle (Ollama, LM Studio, vLLM) ist nur `id` pro Modell erforderlich:\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\nDer Wert `apiKey` ist ein Platzhalter, da Ollama ihn ignoriert. pi behandelt Modelle immer noch so, dass eine Authentifizierung erforderlich ist, bevor sie in `/model` erscheinen, daher sollten schlüssellose lokale Server einen Dummy-Wert behalten, einen Schlüssel für diesen Anbieter mit `/login` speichern oder `--api-key` bei der Auswahl des Modells übergeben.\n\nEinige OpenAI-kompatible Server verstehen die `developer`-Rolle nicht, die für Reasoning-fähige Modelle verwendet wird. Setzen Sie für diese Anbieter `compat.supportsDeveloperRole` auf `false`, damit Pi die Systemaufforderung stattdessen als `system`-Nachricht sendet. Wenn der Server auch `reasoning_effort` nicht unterstützt, setzen Sie `compat.supportsReasoningEffort` ebenfalls auf `false`.\n\nSie können `compat` auf Anbieterebene so festlegen, dass es für alle Modelle gilt, oder auf Modellebene, um ein bestimmtes Modell zu überschreiben. Dies gilt im Allgemeinen für Ollama, vLLM, SGLang und ähnliche OpenAI-kompatible Server.\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## Vollständiges Beispiel\n\nÜberschreiben Sie die Standardwerte, wenn Sie bestimmte Werte benötigen:\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\nDie Datei wird jedes Mal neu geladen, wenn Sie `/model` öffnen. Während der Sitzung bearbeiten; Kein Neustart erforderlich.\n\n## Beispiel für Google AI Studio\n\nVerwenden Sie `google-generative-ai` mit einem `baseUrl`, um Modelle aus Google AI Studio hinzuzufügen, einschließlich benutzerdefinierter Gemma 4-Einträge:\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\nDie `baseUrl` ist erforderlich, wenn benutzerdefinierte Modelle zum Typ `google-generative-ai` API hinzugefügt werden.\n\n## Unterstützte APIs\n\n| API | Beschreibung |\n|-----|-------------|\n| `openai-completions` | OpenAI-Chat-Abschlüsse (am kompatibelsten) |\n| `openai-responses` | OpenAI-Antworten API |\n| `anthropic-messages` | Anthropische Botschaften API |\n| `google-generative-ai` | Generative KI von Google |\n\nLegen Sie `api` auf Anbieterebene (Standard für alle Modelle) oder Modellebene (Überschreibung pro Modell) fest.\n\n## Anbieterkonfiguration\n\n| Feld | Beschreibung |\n|-------|-------------|\n| `baseUrl` | API Endpunkt-URL |\n| `api` | API Typ (siehe oben) |\n| `apiKey` | Optionale API key-Konfiguration (siehe Werteauflösung unten). Lassen Sie es weg, wenn die Authentifizierung durch `/login`/`auth.json` oder CLI `--api-key` bereitgestellt wird. |\n| `oauth` | Dynamischer OAuth Anbietertyp. Unterstützt derzeit `\"radius\"`; erfordert das Gateway `baseUrl`. |\n| `headers` | Benutzerdefinierte Header (siehe Werteauflösung unten) |\n| `authHeader` | Stellen Sie `true` ein, um `Authorization: Bearer <apiKey>` automatisch hinzuzufügen |\n| `models` | Reihe von Modellkonfigurationen |\n| `modelOverrides` | Modellspezifische Überschreibungen für integrierte oder erweiterungsregistrierte Modelle bei diesem Anbieter |\n\nFür Anbieter mit `models` benötigen nicht integrierte Anbieterkonfigurationen `baseUrl` und einen `api`-Wert entweder auf Anbieter- oder Modellebene. `apiKey` ist zum Laden der Datei nicht erforderlich: Modelle werden verfügbar, wenn die Authentifizierung über `/login`/`auth.json`, CLI `--api-key` oder Anbieter `apiKey` konfiguriert wird. Wenn keine Authentifizierung konfiguriert ist, werden die Modelle geladen, bleiben aber in `/model` und `--list-models` nicht verfügbar.\n\n### Wertauflösung\n\nDie Felder `apiKey` und `headers` unterstützen die Befehlsausführung, Umgebungsinterpolation und Literale:\n\n- **Shell-Befehl:** `\"!command\"` führt beim Start den gesamten Wert als Befehl aus und verwendet stdout\n  ```json\n  \"apiKey\": \"!security find-generic-password -ws 'anthropic'\"\n  \"apiKey\": \"!op read 'op://vault/item/credential'\"\n  ```\n- **Umgebungsinterpolation:** `\"$ENV_VAR\"` oder `\"${ENV_VAR}\"` verwendet den Wert der benannten Variablen. Die Interpolation funktioniert innerhalb größerer Literale.\n  ```json\n  \"apiKey\": \"$MY_API_KEY\"\n  \"apiKey\": \"${KEY_PREFIX}_${KEY_SUFFIX}\"\n  ```\n  `$FOO_BAR` ist die Variable `FOO_BAR`; Verwenden Sie `${FOO}_BAR`, wenn `BAR` wörtlicher Text ist. Fehlende Umgebungsvariablen machen den Wert unaufgelöst.\n- **Escapes:** `\"$\"` gibt ein Literal `\"$\"` aus; `\"$!\"` gibt ein Literal `\"!\"` aus, ohne die Befehlsausführung auszulösen.\n  ```json\n  \"apiKey\": \"$$literal-dollar-prefix\"\n  \"apiKey\": \"$!literal-bang-prefix\"\n  ```\n- **Wörtlicher Wert:** Wird direkt verwendet. Einfache Zeichenfolgen in Großbuchstaben wie `MY_API_KEY` sind Literale; Verwenden Sie `$MY_API_KEY` für Umgebungsvariablen.\n  ```json\n  \"apiKey\": \"sk-...\"\n  ```\n\nFür `models.json` werden Shell-Befehle zum Zeitpunkt der Anforderung aufgelöst. pi wendet absichtlich keine integrierte TTL-, veraltete Wiederverwendungs- oder Wiederherstellungslogik für beliebige Befehle an. Unterschiedliche Befehle erfordern unterschiedliche Caching- und Fehlerstrategien, und Pi kann nicht auf die richtige schließen.\n\nWenn Ihr Befehl langsam, teuer oder geschwindigkeitsbegrenzt ist oder bei vorübergehenden Fehlern weiterhin einen vorherigen Wert verwenden soll, binden Sie ihn in Ihr eigenes Skript oder Ihren eigenen Befehl ein, der das gewünschte Caching- oder TTL-Verhalten implementiert.\n\n`/model` Verfügbarkeitsprüfungen nutzen die konfigurierte Authentifizierungspräsenz und führen keine Shell-Befehle aus.\n\n### Benutzerdefinierte Header\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## Modellkonfiguration\n\n| Feld | Erforderlich | Standard | Beschreibung |\n|-------|----------|---------|-------------|\n| `id` | Ja | — | Modell-ID (an API übergeben) |\n| `name` | NEIN | `id` | Für Menschen lesbares Modelletikett. Wird für den Abgleich (`--model` Muster) verwendet und als sekundärer Modelldetailtext angezeigt. |\n| `api` | NEIN | Anbieter `api` | Überschreiben Sie die API des Anbieters für dieses Modell |\n| `reasoning` | NEIN | `false` | Unterstützt erweitertes Denken |\n| `thinkingLevelMap` | NEIN | weggelassen | Ordnet die Pi-Denkebenen den Anbieterwerten zu und markiert nicht unterstützte Ebenen (siehe unten). |\n| `input` | NEIN | `[\"text\"]` | Eingabetypen: `[\"text\"]` oder `[\"text\", \"image\"]` |\n| `contextWindow` | NEIN | `128000` | Größe des Kontextfensters in Token |\n| `maxTokens` | NEIN | `16384` | Maximale Ausgabetoken |\n| `samplingParams` | NEIN | weggelassen | Stichprobenparameter werden wörtlich in jeden Anfragetext eingefügt (siehe unten) |\n| `cost` | NEIN | alles Nullen | Preise pro Million Token mit optionalen Preisstufen für die gesamte Anfrage |\n| `compat` | NEIN | Anbieter `compat` | Anbieterkompatibilitätsüberschreibungen. Wird mit Anbieterebene `compat` zusammengeführt, wenn beide festgelegt sind. |\n\nEine Kostenstufe stellt einen vollständigen alternativen Tarifsatz bereit und gilt für die gesamte Anfrage, wenn die Gesamteingabenutzung (`input + cacheRead + cacheWrite`) `inputTokensAbove` übersteigt. Wenn mehrere Stufen übereinstimmen, gewinnt der höchste Schwellenwert.\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\nAktuelles Verhalten:\n- `/model`, `--list-models` und die interaktive Fußzeile zeigt Einträge nach Modell an `id`.\n- Die konfigurierte `name` wird für den Modellabgleich und sekundären Modelldetailtext verwendet. Es ersetzt nicht die Fußzeilen-/Statusleisten-Modell-ID.\n\n### Probenahmeparameter\n\n`samplingParams` ist ein Freiformobjekt, das wörtlich in jeden Anforderungshauptteil für das Modell eingefügt wird, nachdem sich die Felder pi selbst festgelegt haben, sodass seine Schlüssel gewinnen. Verwenden Sie es, um Stichprobenparameter zu senden, die pi nicht modelliert – einschließlich serverspezifischer Parameter wie llama.cpps `min_p` oder vLLMs `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\nNur OpenAI-kompatible APIs wenden es an (`openai-completions`, `openai-responses`, `azure-openai-responses`); andere APIs ignorieren es. Schlüssel haben Vorrang vor den benannten Anforderungsfeldern von pi (z. B. übertrifft hier ein `temperature`-Schlüssel die Temperatur auf Anforderungsebene). Bevorzugen Sie ihn daher als einzige Quelle der Stichprobenwahrheit für ein Modell. In `modelOverrides` verschmilzt `samplingParams` pro Schlüssel mit dem Wert des Basismodells.\n\n### Denkebenenkarte\n\nVerwenden Sie `thinkingLevelMap` für ein Modell, um modellspezifische Denkkontrollen zu beschreiben. Schlüssel sind Pi-Denkstufen: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Karten können Löcher enthalten; Beispielsweise kann ein Modell `high` und `max` freilegen, ohne `xhigh` freizulegen.\n\nDie Werte sind dreistufig:\n\n| Wert | Bedeutung |\n|-------|---------|\n| weggelassen | Standardstufen bis `high` verwenden die Standardzuordnung des Anbieters; Die erweiterten Stufen `xhigh` und `max` werden nicht unterstützt |\n| Zeichenfolge | Level wird unterstützt und dieser Wert wird an den Anbieter gesendet |\n| `null` | Ebene ist nicht unterstützt und versteckt/übersprungen/weggeklemmt |\n\nBeispiel für ein Modell, das nur Off-, High- und Max-Argumentation unterstützt:\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\nBeispiel für ein Modell, bei dem das Denken nicht deaktiviert werden kann:\n\n```json\n{\n  \"id\": \"always-thinking-model\",\n  \"reasoning\": true,\n  \"thinkingLevelMap\": {\n    \"off\": null\n  }\n}\n```\n\nMigration: Ältere Konfigurationen, die `compat.reasoningEffortMap` verwendet haben, sollten diese Zuordnung auf Modellebene `thinkingLevelMap` verschieben. Verwenden Sie `null` für Ebenen, die nicht in der Benutzeroberfläche angezeigt werden sollen.\n\n## Überschreiben der integrierten Providers\n\nLeiten Sie einen integrierten Anbieter über einen Proxy weiter, ohne Modelle neu zu definieren:\n\n```json\n{\n  \"providers\": {\n    \"anthropic\": {\n      \"baseUrl\": \"https://my-proxy.example.com/v1\"\n    }\n  }\n}\n```\n\nAlle eingebauten Anthropic-Modelle bleiben verfügbar. Die vorhandene OAuth- oder API key-Authentifizierung funktioniert weiterhin.\n\nUm benutzerdefinierte Modelle mit einem integrierten Anbieter zusammenzuführen, schließen Sie das Array `models` ein:\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\nSemantik zusammenführen:\n- Eingebaute Modelle bleiben erhalten.\n- Benutzerdefinierte Modelle werden innerhalb des Anbieters durch `id` ersetzt.\n- Wenn ein benutzerdefiniertes Modell `id` mit einem integrierten Modell `id` übereinstimmt, ersetzt das benutzerdefinierte Modell dieses integrierte Modell.\n- Wenn ein benutzerdefiniertes Modell `id` neu ist, wird es neben den integrierten Modellen hinzugefügt.\n\n## Überschreibungen pro Modell\n\nVerwenden Sie `modelOverrides`, um integrierte Modelle und passende erweiterungsregistrierte Modelle anzupassen, ohne die vollständige Modellliste des Anbieters zu ersetzen.\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` unterstützt diese Felder pro Modell: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (teilweise), `contextWindow`, `maxTokens`, `samplingParams` (zusammengeführt pro Schlüssel), `headers`, `compat`.\n\nDirect OpenAI GPT-5.6 Sol, Terra und Luna verwenden standardmäßig ein `272000`-Kontextfenster, sodass Anfragen innerhalb der Preisstufe für kurze Kontexte von OpenAI bleiben. Um sich für das 1,05-Millionen-Kontextfenster von OpenAI zu entscheiden, vergrößern Sie es für jedes von Ihnen verwendete Modell:\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\nDurch die Überschreibung bleiben die integrierten Preismetadaten erhalten. Anfragen mit insgesamt mehr als 272.000 Eingabetokens verwenden die Long-Context-Raten von GPT-5.6 für die gesamte Anfrage. Wenden Sie bei Bedarf die gleiche Überschreibung auf `gpt-5.6-terra` oder `gpt-5.6-luna` an.\n\nVerhaltenshinweise:\n- `modelOverrides` werden auf integrierte Anbietermodelle und passende erweiterungsregistrierte Anbietermodelle angewendet.\n- Unbekannte Modell-IDs werden ignoriert.\n- Sie können die Anbieterebene `baseUrl`/`headers` mit `modelOverrides` kombinieren.\n- Das Überschreiben von `name` ändert nur den Modellabgleich und den sekundären Detailtext; In der Fußzeile und in den primären Modelllisten wird weiterhin das Modell `id` angezeigt.\n- Wenn `models` auch für einen Anbieter definiert ist, werden benutzerdefinierte Modelle nach integrierten Überschreibungen zusammengeführt. Ein benutzerdefiniertes Modell mit demselben `id` ersetzt den überschriebenen integrierten Modelleintrag.\n\n## Kompatibilität mit anthropischen Nachrichten\n\nFür Anbieter oder Proxys, die `api: \"anthropic-messages\"` verwenden, verwenden Sie `compat`, um die Anthropic-spezifische Anforderungskompatibilität zu steuern.\n\nStandardmäßig sendet pi pro Werkzeug `eager_input_streaming: true`. Wenn ein Proxy oder ein Anthropic-kompatibles Backend dieses Feld ablehnt, setzen Sie `supportsEagerToolInputStreaming` auf `false`. Pi lässt `tools[].eager_input_streaming` weg und sendet stattdessen den alten Beta-Header `fine-grained-tool-streaming-2025-05-14` für Tool-fähige Anfragen.\n\nEinige anthropische Modelle erfordern adaptives Denken (`thinking.type: \"adaptive\"` plus `output_config.effort`) anstelle der alten budgetbasierten Denklast. Bei Einbaumodellen wird dies automatisch eingestellt. Für benutzerdefinierte Anbieter oder Aliase, die an diese Modelle weiterleiten, setzen Sie `forceAdaptiveThinking` auf `true`.\n\nEinige Anthropic-kompatible Anbieter geben Denkblöcke mit leeren Signaturen aus und erwarten sie dennoch bei der Wiedergabe. Setzen Sie `allowEmptySignature` nur für diese Anbieter auf `true`; Real Anthropic lehnt leere Denksignaturen ab.\n\nIntegrierte Anthropic-Modelle ermöglichen `supportsStrictTools` in ihren Modellmetadaten. Benutzerdefinierte Anthropic-kompatible Modelle müssen es auf `true` setzen, wenn ihr Endpunkt strikte JSON-Schema-Tooldefinitionen akzeptiert.\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| Feld | Beschreibung |\n|-------|-------------|\n| `supportsEagerToolInputStreaming` | Ob der Anbieter pro Werkzeug `eager_input_streaming` akzeptiert. Standard: `true`. Legen Sie den Wert auf `false` fest, um dieses Feld wegzulassen und den alten Beta-Header für das feinkörnige Tool-Streaming für Tool-fähige Anfragen zu verwenden. |\n| `supportsLongCacheRetention` | Ob der Anbieter die lange Cache-Aufbewahrung von Anthropic (`cache_control.ttl: \"1h\"`) akzeptiert, wenn die Cache-Aufbewahrung `long` ist. Standard: `true`. |\n| `sendSessionAffinityHeaders` | Ob `x-session-affinity` von der Sitzungs-ID gesendet werden soll, wenn Caching aktiviert ist. Standard: Bei bekannten Anbietern automatisch erkannt. |\n| `supportsCacheControlOnTools` | Ob der Anbieter `cache_control`-Markierungen im Anthropic-Stil für Werkzeugdefinitionen akzeptiert. Standard: `true`. |\n| `forceAdaptiveThinking` | Ob adaptives Denken (`thinking.type: \"adaptive\"` plus `output_config.effort`) für dieses Modell gesendet werden soll. Integrierte adaptive Modelle stellen dies automatisch ein. Standard: `false`. |\n| `allowEmptySignature` | Ob leere Denksignaturen als `signature: \"\"` wiedergegeben werden sollen, anstatt Denken in Text umzuwandeln. Standard: `false`. |\n| `supportsStrictTools` | Ob der Anbieter strenge JSON-Schema-Tooldefinitionen akzeptiert. Standard: `false`; Integrierte anthropische Modelle ermöglichen dies in generierten Metadaten. |\n\n## OpenAI-Kompatibilität\n\nFür Anbieter mit teilweiser OpenAI-Kompatibilität verwenden Sie das Feld `compat`.\n\n- Anbieterebene `compat` wendet Standardeinstellungen auf alle Modelle dieses Anbieters an.\n- Modellebene `compat` überschreibt Werte auf Anbieterebene für dieses Modell.\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| Feld | Beschreibung |\n|-------|-------------|\n| `supportsStore` | Der Anbieter unterstützt das Feld `store` |\n| `supportsDeveloperRole` | Verwenden Sie die Rolle `developer` vs. `system` |\n| `supportsReasoningEffort` | Unterstützung für den Parameter `reasoning_effort` |\n| `supportsUsageInStreaming` | Unterstützt `stream_options: { include_usage: true }` (Standard: `true`) |\n| `supportsFinishReason` | Ob gestreamte Antworten `finish_reason` enthalten. Wenn `false`, leitet pi `stop` oder `toolUse` ab, wenn der Stream endet. Standard: `true`. |\n| `maxTokensField` | Verwenden Sie `max_completion_tokens` oder `max_tokens` |\n| `requiresToolResultName` | Fügen Sie `name` in Werkzeugergebnismeldungen ein |\n| `requiresAssistantAfterToolResult` | Fügen Sie eine Assistentennachricht vor einer Benutzernachricht nach den Werkzeugergebnissen ein |\n| `requiresThinkingAsText` | Konvertieren Sie Denkblöcke in einfachen Text |\n| `requiresReasoningContentOnAssistantMessages` | Fügen Sie in allen wiedergegebenen Assistentennachrichten ein leeres `reasoning_content` ein, wenn die Argumentation aktiviert ist |\n| `thinkingFormat` | Verwenden Sie die Denkparameter `reasoning_effort`, `openrouter`, `deepseek`, `together`, `baseten`, `zai`, `qwen`, `chat-template` oder `qwen-chat-template` |\n| `chatTemplateKwargs` | `chat_template_kwargs` Werte für `thinkingFormat: \"chat-template\"`; Verwenden Sie `{ \"$var\": \"thinking.enabled\" }` oder `{ \"$var\": \"thinking.effort\" }` für pi-kontrollierte Denkwerte |\n| `chatTemplateArgs` | `chat_template_args` Werte für `thinkingFormat: \"baseten\"`; Verwenden Sie `{ \"$var\": \"thinking.enabled\" }` oder `{ \"$var\": \"thinking.effort\" }` für pi-kontrollierte Denkwerte |\n| `cacheControlFormat` | Verwenden Sie `cache_control`-Markierungen im Anthropic-Stil für die Systemeingabeaufforderung, die letzte Werkzeugdefinition und den Textinhalt des letzten Benutzers, Assistenten oder Werkzeugergebnisses. Derzeit wird nur `anthropic` unterstützt. |\n| `sendSessionAffinityHeaders` | Senden Sie für `openai-completions` Sitzungsaffinitätsheader von der Sitzungs-ID, wenn Caching aktiviert ist. Standard: `false`. |\n| `sessionAffinityFormat` | Für `openai-completions` und `openai-responses` gilt das Session-Affinity-Header-Format: `openai` sendet `session_id`/`x-client-request-id` (Vervollständigungen auch `x-session-affinity`), `openai-nosession` lässt den Unterstrich enthaltenden `session_id`-Header weg, `openrouter` sendet `x-session-id`. Hat keinen Einfluss auf den Körperparameter `prompt_cache_key`. Standard: automatisch erkannt. |\n| `supportsStrictMode` | Ob der Anbieter strenge JSON-Schema-Funktionstooldefinitionen akzeptiert. Die Standardeinstellungen hängen von der API ab; Integrierte OpenAI-Modelle enthalten explizite Fähigkeitsmetadaten. |\n| `supportsOpenAIGrammarTools` | Ob OpenAI-kompatible APIs benutzerdefinierte Lark/Regex-Grammatiktools ausgeben. Bei `false` greifen grammatikbeschränkte Werkzeuge auf normale Funktionswerkzeuge zurück. Standard: `false`; Der integrierte Modellkatalog ermöglicht dies für GPT-5+-Modelle auf OpenAI, OpenAI Codex, Azure OpenAI, GitHub Copilot, Opencode und Cloudflare AI Gateway. |\n| `deferredToolsMode` | Verwenden Sie die anbieterspezifische verzögerte Tool-Serialisierung. Derzeit wird nur `\"kimi\"` für Kimis OpenAI-kompatibles Chat Completions-Format unterstützt. |\n| `supportsLongCacheRetention` | Ob der Anbieter eine lange Cache-Aufbewahrung akzeptiert, wenn die Cache-Aufbewahrung `long` ist: `prompt_cache_retention: \"24h\"` für OpenAI-Prompt-Caching oder `cache_control.ttl: \"1h\"`, wenn `cacheControlFormat` `anthropic` ist. Standard: `true`. |\n| `openRouterRouting` | Routing-Einstellungen des OpenRouter-Anbieters. Dieses Objekt wird unverändert im Feld `provider` von [OpenRouter API request](https://openrouter.ai/docs/guides/routing/provider-selection) gesendet. |\n| `vercelGatewayRouting` | Vercel AI Gateway-Routing-Konfiguration für die Anbieterauswahl (`only`, `order`) |\n\n`openrouter` verwendet `reasoning: { effort }`. `together` verwendet `reasoning: { enabled }` und auch `reasoning_effort`, wenn `supportsReasoningEffort` aktiviert ist. `qwen` verwendet `enable_thinking` der obersten Ebene. Verwenden Sie `qwen-chat-template` für lokale Qwen-kompatible Server, die `chat_template_kwargs.enable_thinking` und `preserve_thinking` erfordern. Verwenden Sie `chat-template` für vLLM/Hugging Face-Chat-Vorlagen, die konfigurierbares `chat_template_kwargs` benötigen, z. B. `chatTemplateKwargs: { \"thinking\": { \"$var\": \"thinking.enabled\" } }` für DeepSeek V3.x-Vorlagen. Verwenden Sie `thinkingFormat: \"baseten\"` mit `chatTemplateArgs` für Anbieter, die Umschaltsteuerungen bis `chat_template_args` verfügbar machen und optional `reasoning_effort` der obersten Ebene unterstützen.\n\n`cacheControlFormat: \"anthropic\"` ist für OpenAI-kompatible Anbieter, die Prompt-Caching im Anthropic-Stil durch `cache_control`-Markierungen für Textinhalte und Tooldefinitionen verfügbar machen.\n\nBeispiel:\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\nBeispiel für ein Vercel AI Gateway:\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 kann Ihnen beim Erstellen von Pi-Paketen helfen. Bitten Sie es, Ihre Erweiterungen, Fähigkeiten, prompt templates oder Themen zu bündeln.\n\n\nPi-Pakete bündeln Erweiterungen, Fähigkeiten, prompt templates und Themen, sodass Sie sie über npm oder Git teilen können. Ein Paket kann Ressourcen in `package.json` unter dem Schlüssel `pi` deklarieren oder herkömmliche Verzeichnisse verwenden.\n\n## Inhaltsverzeichnis\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## Installieren und verwalten\n\n> **Sicherheit:** Pi Pakete werden mit vollem Systemzugriff ausgeführt. Extensions Führen Sie beliebigen Code aus, und Fähigkeiten können das Modell anweisen, jede Aktion auszuführen, einschließlich der Ausführung ausführbarer Dateien. Überprüfen Sie den Quellcode, bevor Sie Pakete von Drittanbietern installieren.\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\nDiese Befehle verwalten Pi-Pakete und `pi update` kann die Pi CLI-Installation aktualisieren. Informationen zur Deinstallation von Pi selbst finden Sie unter [Quickstart](quickstart.md#uninstall).\n\nStandardmäßig schreiben `install` und `remove` in die Benutzereinstellungen (`~/.pi/agent/settings.json`). Verwenden Sie stattdessen `-l`, um in die Projekteinstellungen (`.pi/settings.json`) zu schreiben. Projekteinstellungen können mit Ihrem Team geteilt werden und pi installiert alle fehlenden Pakete automatisch beim Start, nachdem das Projekt vertrauenswürdig ist.\n\nUm ein Paket auszuprobieren, ohne es zu installieren, verwenden Sie `--extension` oder `-e`. Dadurch wird nur für die aktuelle Ausführung ein temporäres Verzeichnis installiert:\n\n```bash\npi -e npm:@foo/bar\npi -e git:github.com/user/repo\n```\n\n## Paketquellen\n\nPi akzeptiert drei Quellentypen in den Einstellungen und `pi install`.\n\n### npm\n\n```\nnpm:@scope/pkg@1.2.3\nnpm:pkg\n```\n\n- Versionierte Spezifikationen werden von Paketaktualisierungen angeheftet und übersprungen (`pi update --extensions`, `pi update --all`).\n- Benutzerinstallationen gehen unter `~/.pi/agent/npm/`.\n- Projektinstallationen gehen unter `.pi/npm/`.\n- Setzen Sie `npmCommand` in `settings.json`, um npm Paketsuch- und Installationsvorgänge an einen bestimmten Wrapper-Befehl wie `mise` oder `asdf` anzuheften.\n\nBeispiel:\n\n```json\n{\n  \"npmCommand\": [\"mise\", \"exec\", \"node@20\", \"--\", \"npm\"]\n}\n```\n\n### Idiot\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- Ohne das Präfix `git:` werden nur Protokoll-URLs akzeptiert (`https://`, `http://`, `ssh://`, `git://`).\n- Mit dem Präfix `git:` werden Kurzformate akzeptiert, einschließlich `github.com/user/repo` und `git@github.com:user/repo`.\n- HTTPS- und SSH-URLs werden beide unterstützt.\n- SSH URLs verwenden automatisch Ihre konfigurierten SSH Schlüssel (respektiert `~/.ssh/config`).\n- Für nicht interaktive Ausführungen (z. B. CI) können Sie `GIT_TERMINAL_PROMPT=0` festlegen, um Anmeldeaufforderungen zu deaktivieren, und `GIT_SSH_COMMAND` (z. B. `ssh -o BatchMode=yes -o ConnectTimeout=5`) festlegen, um schnell fehlzuschlagen.\n- Refs sind angeheftete Tags oder Commits. `pi update --extensions` und `pi update --all` verschieben sie nicht auf neuere Referenzen, aber sie gleichen einen vorhandenen Klon mit der konfigurierten Referenz ab.\n- Verwenden Sie `pi install git:host/user/repo@new-ref`, um Einstellungen zu aktualisieren und ein vorhandenes Paket in eine neue angeheftete Referenz zu verschieben.\n- Geklont auf `~/.pi/agent/git/<host>/<path>` (global) oder `.pi/git/<host>/<path>` (Projekt).\n- Wenn der Abgleich den Checkout ändert, setzt pi den Klon zurück und bereinigt ihn und führt dann `npm install` aus, wenn `package.json` vorhanden ist.\n\n**SSH Beispiele:**\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### Lokale Pfade\n\n```\n/absolute/path/to/package\n./relative/path/to/package\n```\n\nLokale Pfade verweisen auf Dateien oder Verzeichnisse auf der Festplatte und werden ohne Kopieren zu den Einstellungen hinzugefügt. Relative Pfade werden anhand der Einstellungsdatei aufgelöst, in der sie erscheinen. Wenn es sich bei dem Pfad um eine Datei handelt, wird sie als einzelne Erweiterung geladen. Wenn es sich um ein Verzeichnis handelt, lädt pi Ressourcen mithilfe von Paketregeln.\n\n## Erstellen eines Pi-Pakets\n\nFügen Sie ein `pi`-Manifest zu `package.json` hinzu oder verwenden Sie herkömmliche Verzeichnisse. Fügen Sie zur besseren Auffindbarkeit das Schlüsselwort `pi-package` ein.\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\nPfade sind relativ zum Paketstamm. Arrays unterstützen Glob-Muster und `!exclusions`.\n\n### Galerie-Metadaten\n\nDie [package gallery](https://pi.dev/packages) zeigt Pakete an, die mit `pi-package` markiert sind. Fügen Sie `video` oder `image` Felder hinzu, um eine Vorschau anzuzeigen:\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- **Video**: Nur MP4. Auf dem Desktop erfolgt die automatische Wiedergabe beim Schweben. Durch Klicken wird ein Vollbild-Player geöffnet.\n- **Bild**: PNG, JPEG, GIF oder WebP. Wird als statische Vorschau angezeigt.\n\nWenn beides eingestellt ist, hat Video Vorrang.\n\n## Paketstruktur\n\n### Kongressverzeichnisse\n\nWenn kein `pi`-Manifest vorhanden ist, erkennt pi automatisch Ressourcen aus diesen Verzeichnissen:\n\n- `extensions/` lädt `.ts` und `.js` Dateien\n- `skills/` findet rekursiv `SKILL.md` Ordner und lädt `.md` Dateien der obersten Ebene als Fertigkeiten\n- `prompts/` lädt `.md` Dateien\n- `themes/` lädt `.json` Dateien\n\n## Abhängigkeiten\n\nLaufzeitabhängigkeiten von Drittanbietern gehören in `dependencies` in `package.json`. Abhängigkeiten, die keine Erweiterungen, Fähigkeiten, prompt templates oder Themen registrieren, gehören ebenfalls zu `dependencies`. Wenn pi ein Paket von npm oder Git installiert, führt es `npm install` aus, sodass diese Abhängigkeiten automatisch installiert werden.\n\nPi bündelt Kernpakete für Erweiterungen und Fertigkeiten. Wenn Sie eines davon importieren, listen Sie es in `peerDependencies` mit einem `\"*\"`-Bereich auf und bündeln Sie es nicht: `@earendil-works/pi-ai`, `@earendil-works/pi-agent-core`, `@earendil-works/pi-coding-agent`, `@earendil-works/pi-tui`, `typebox`.\n\nAndere Pi-Pakete müssen in Ihrem Tarball gebündelt sein. Fügen Sie sie zu `dependencies` und `bundledDependencies` hinzu und verweisen Sie dann über `node_modules/`-Pfade auf ihre Ressourcen. Pi lädt Pakete mit separaten Modulstämmen, sodass separate Installationen nicht kollidieren oder Module gemeinsam nutzen.\n\nBeispiel:\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## Paketfilterung\n\nFiltern Sie mithilfe des Objektformulars in den Einstellungen, was ein Paket lädt:\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` und `-path` sind genaue Pfade relativ zum Paketstamm.\n\n- Lassen Sie einen Schlüssel weg, um alles dieses Typs zu laden.\n- Verwenden Sie `[]`, um nichts von diesem Typ zu laden.\n- `!pattern` schließt Übereinstimmungen aus.\n- `+path` Force – beinhaltet einen genauen Pfad.\n- `-path` erzwingt den Ausschluss eines genauen Pfads.\n- Filterebene über dem Manifest. Sie schränken ein, was bereits erlaubt ist.\n\n## Ressourcen aktivieren und deaktivieren\n\nVerwenden Sie `pi config`, um Erweiterungen, Fähigkeiten, prompt templates und Themes aus installierten Paketen und lokalen Verzeichnissen zu aktivieren oder zu deaktivieren. `pi config` startet in den globalen Einstellungen (`~/.pi/agent/settings.json`); Drücken Sie die Tabulatortaste, um zwischen dem globalen und dem projektlokalen Modus zu wechseln. Verwenden Sie `pi config -l`, um in Projektüberschreibungen (`.pi/settings.json`) mit abgeblendeten geerbten globalen Ressourcen zu beginnen.\n\n## Umfang und Deduplizierung\n\nPakete können sowohl in globalen als auch in Projekteinstellungen angezeigt werden. Wenn in beiden das gleiche Paket vorkommt, gewinnt der Projekteintrag, es sei denn, der Projekteintrag hat `autoload: false`. In diesem Fall wird es als Delta über den globalen Eintrag angewendet. Die Identität wird bestimmt durch:\n\n- npm: Paketname\n- git: Repository-URL ohne Referenz\n- local: aufgelöster absoluter Pfad","sourceFile":"packages.md"},"prompt-templates":{"title":"Eingabeaufforderungsvorlagen","markdown":"> pi kann prompt templates erstellen. Bitten Sie es, eines für Ihren Workflow zu erstellen.\n\n\nEingabeaufforderungsvorlagen sind Markdown Snippets, die sich zu vollständigen Eingabeaufforderungen erweitern lassen. Geben Sie `/name` in den Editor ein, um eine Vorlage aufzurufen, wobei `name` der Dateiname ohne `.md` ist.\n\n## Standorte\n\nPi lädt prompt templates von:\n\n- Global: `~/.pi/agent/prompts/*.md`\n- Projekt: `.pi/prompts/*.md` (nur nachdem das Projekt vertrauenswürdig ist)\n- Pakete: `prompts/` Verzeichnisse oder `pi.prompts` Einträge in `package.json`\n- Einstellungen: `prompts` Array mit Dateien oder Verzeichnissen\n- CLI: `--prompt-template <path>` (wiederholbar)\n\nDeaktivieren Sie die Erkennung mit `--no-prompt-templates`.\n\n## Format\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- Der Dateiname wird zum Befehlsnamen. `review.md` wird zu `/review`.\n- `description` ist optional. Bei Fehlen wird die erste nicht leere Zeile verwendet.\n- `argument-hint` ist optional. Wenn festgelegt, wird der Hinweis vor der Beschreibung im Dropdown-Menü für die automatische Vervollständigung angezeigt.\n\n### Argumentationshinweise\n\nVerwenden Sie `argument-hint` in frontmatter, um erwartete Argumente bei der automatischen Vervollständigung anzuzeigen. Verwenden Sie `<angle brackets>` für erforderliche Argumente und `[square brackets]` für optionale:\n\n```markdown\n---\ndescription: Review PRs from URLs with structured issue and code analysis\nargument-hint: \"<PR-URL>\"\n---\n```\n\nDies wird im Dropdown-Menü für die automatische Vervollständigung wie folgt dargestellt:\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## Verwendung\n\nGeben Sie im Editor `/` gefolgt vom Namen der Vorlage ein. Die automatische Vervollständigung zeigt verfügbare Vorlagen mit Beschreibungen an.\n\n```\n/review                           # Expands review.md\n/component Button                 # Expands with argument\n/component Button \"click handler\" # Multiple arguments\n```\n\n## Argumente\n\nVorlagen unterstützen Positionsargumente, Standardwerte und einfaches Slicing:\n\n- `$1`, `$2`,... Positionsargumente\n- `$@` oder `$ARGUMENTS` für alle verbundenen Argumente\n- `${1:-default}` verwendet arg 1, wenn vorhanden/nicht leer, andernfalls `default`\n- `${@:-default}` oder `${ARGUMENTS:-default}` verwendet alle Argumente, wenn vorhanden/nicht leer, andernfalls `default`\n- `${@:N}` für Argumente ab der N-ten Position (1-indiziert)\n- `${@:N:L}` für `L` Argumente beginnend bei N\n\nBeispiel:\n\n```markdown\n---\ndescription: Create a component\n---\nCreate a React component named $1 with features: $@\n```\n\nStandardwerte sind für optionale Argumente nützlich:\n\n```markdown\nSummarize the current state in ${1:-7} bullet points.\n```\n\nVerwendung: `/component Button \"onClick handler\" \"disabled support\"`\n\n## Laderegeln\n\n- Die Vorlagenerkennung in `prompts/` ist nicht rekursiv.\n- Wenn Sie Vorlagen in Unterverzeichnissen wünschen, fügen Sie diese explizit über `prompts`-Einstellungen oder ein Paketmanifest hinzu.","sourceFile":"prompt-templates.md"},"providers":{"title":"Providers","markdown":"Pi unterstützt abonnementbasierte Anbieter über OAuth- und API key-Anbieter über Umgebungsvariablen oder Authentifizierungsdatei. Integrierte Kataloge werden mit Pi geliefert; Konfigurierte Anbieter können neuere Kataloge aktualisieren und sie für die Offline-Verwendung in `~/.pi/agent/models-store.json` zwischenspeichern.\n\n## Inhaltsverzeichnis\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## Abonnements\n\nVerwenden Sie `/login` im interaktiven Modus und wählen Sie dann einen Anbieter aus:\n\n- ChatGPT Plus/Pro (Codex)\n- Claude Pro/Max\n- GitHub Copilot\n- xAI (Grok/X-Abonnement)\n- OpenRouter (OAuth-minted API key abgerechnet aus OpenRouter-Credits)\n- Radius\n\nVerwenden Sie `/logout`, um Anmeldeinformationen zu löschen. Token werden in `~/.pi/agent/auth.json` gespeichert und automatisch aktualisiert, wenn sie abgelaufen sind. OpenRouter prägt stattdessen ein benutzergesteuertes API key, das nicht automatisch abläuft.\n\n### OpenAI-Codex\n\n- Erfordert ein ChatGPT Plus- oder Pro-Abonnement\n- Offiziell von OpenAI unterstützt: [Codex for OSS](https://developers.openai.com/community/codex-for-oss)\n\n### Claude Pro/Max\n\nDie Anthropic-Abonnementauthentifizierung ist für Claude Pro/Max-Konten aktiv. Die Nutzung des Kabelbaums von Drittanbietern kostet ab [extra usage](https://claude.ai/settings/usage) und wird pro Token abgerechnet, nicht anhand der Limits des Claude-Plans.\n\n### GitHub Copilot\n\n- Drücken Sie die Eingabetaste für github.com oder geben Sie Ihre GitHub Enterprise Server-Domäne ein\n- Wenn Sie „Modell nicht unterstützt“ erhalten, aktivieren Sie es in VS Code: Copilot Chat → Modellauswahl → Modell auswählen → „Aktivieren“\n\n### xAI (Grok/X-Abonnement)\n\n- Führen Sie `/login xai` aus und wählen Sie dann **Abonnement verwenden**\n- `XAI_API_KEY` bleibt verfügbar bis **Verwenden Sie ein API key**\n\n### OpenRouter\n\n- Führen Sie `/login openrouter` aus und wählen Sie dann **Mit OpenRouter anmelden** aus, um den OpenRouter PKCE-Autorisierungsfluss zu öffnen\n- Die Autorisierung erstellt einen benutzergesteuerten OpenRouter API key, der von Ihrem OpenRouter-Guthaben abgerechnet wird\n- Auf Remote-/Headless-Maschinen (z. B. über SSH) kann der Browser den Loopback-Callback nicht erreichen; Fügen Sie stattdessen die endgültige Weiterleitungs-URL (oder den Autorisierungscode) in die Anmeldeaufforderung ein\n- `OPENROUTER_API_KEY` bleibt verfügbar bis **Verwenden Sie ein API key**\n\n### Radius\n\nRadius ist ein dynamisches `pi-messages` Gateway. `/login radius` speichert OAuth Token in `auth.json`; Der Gateway-Katalog wird unabhängig aktualisiert und in `models-store.json` zwischengespeichert. Benutzerdefinierte Radius-Gateways können in `models.json` mit `\"oauth\": \"radius\"` und einem Gateway `baseUrl` deklariert werden.\n\n## API Tasten\n\n### Umgebungsvariablen oder Auth-Datei\n\nVerwenden Sie `/login` im interaktiven Modus und wählen Sie einen Anbieter aus, um einen API key in `auth.json` zu speichern, oder legen Sie Anmeldeinformationen über eine Umgebungsvariable fest:\n\n```bash\nexport ANTHROPIC_API_KEY=sk-ant-...\npi\n```\n\n| Anbieter | Umgebungsvariable | `auth.json`-Taste |\n|----------|----------------------|------------------|\n| Anthropisch | `ANTHROPIC_API_KEY` | `anthropic` |\n| Ant Ling | `ANT_LING_API_KEY` | `ant-ling` |\n| Azure OpenAI-Antworten | `AZURE_OPENAI_API_KEY` | `azure-openai-responses` |\n| OpenAI | `OPENAI_API_KEY` | `openai` |\n| DeepSeek | `DEEPSEEK_API_KEY` | `deepseek` |\n| NVIDIA NIM | `NVIDIA_API_KEY` | `nvidia` |\n| Google Gemini | `GEMINI_API_KEY` | `google` |\n| Amazonas-Grundgestein | `AWS_BEARER_TOKEN_BEDROCK` | `amazon-bedrock` |\n| Mistral | `MISTRAL_API_KEY` | `mistral` |\n| Groq | `GROQ_API_KEY` | `groq` |\n| Großhirn | `CEREBRAS_API_KEY` | `cerebras` |\n| Cloudflare AI Gateway | `CLOUDFLARE_API_KEY` (+ `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_GATEWAY_ID`) | `cloudflare-ai-gateway` |\n| Cloudflare Workers AI | `CLOUDFLARE_API_KEY` (+ `CLOUDFLARE_ACCOUNT_ID`) | `cloudflare-workers-ai` |\n| xAI | `XAI_API_KEY` | `xai` |\n| OpenRouter | `OPENROUTER_API_KEY` | `openrouter` |\n| Vercel AI Gateway | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway` |\n| ZAI-Kodierungsplan (global) | `ZAI_API_KEY` | `zai` |\n| ZAI-Kodierungsplan (China) | `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 | `RADIUS_API_KEY` | `radius` |\n| Hugging Face | `HF_TOKEN` | `huggingface` |\n| Feuerwerk | `FIREWORKS_API_KEY` | `fireworks` |\n| Gemeinsam KI | `TOGETHER_API_KEY` | `together` |\n| Baseten | `BASETEN_API_KEY` | `baseten` |\n| Kimi zum Codieren | `KIMI_API_KEY` | `kimi-coding` |\n| MiniMax | `MINIMAX_API_KEY` | `minimax` |\n| MiniMax (China) | `MINIMAX_CN_API_KEY` | `minimax-cn` |\n| Qwen-Token-Plan (vorhandener Katalog) | `QWEN_TOKEN_PLAN_API_KEY` | `qwen-token-plan` |\n| Qwen-Token-Plan (individuell) | `QWEN_TOKEN_PLAN_API_KEY` | `qwen-token-plan-individual` |\n| Qwen-Token-Plan (China) | `QWEN_TOKEN_PLAN_CN_API_KEY` | `qwen-token-plan-cn` |\n| Xiaomi MiMo | `XIAOMI_API_KEY` | `xiaomi` |\n| Xiaomi MiMo-Token-Plan (China) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` | `xiaomi-token-plan-cn` |\n| Xiaomi MiMo-Token-Plan (Amsterdam) | `XIAOMI_TOKEN_PLAN_AMS_API_KEY` | `xiaomi-token-plan-ams` |\n| Xiaomi MiMo Token Plan (Singapur) | `XIAOMI_TOKEN_PLAN_SGP_API_KEY` | `xiaomi-token-plan-sgp` |\n\nReferenz für Umgebungsvariablen und `auth.json` Schlüssel: [`const envMap`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/env-api-keys.ts) in [`packages/ai/src/env-api-keys.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/env-api-keys.ts).\n\n#### Auth-Datei\n\nAnmeldeinformationen in `~/.pi/agent/auth.json` speichern:\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` verwendet denselben internationalen Endpunkt und `QWEN_TOKEN_PLAN_API_KEY` wie\n`qwen-token-plan`, beschränkt die Auswahl jedoch auf die für Einzelabonnements dokumentierten Modelle. Das Vorhandene\nAus Gründen der Abwärtskompatibilität behält der Anbieter seinen breiteren Katalog bei. Wenn Sie `auth.json` verwenden, speichern Sie die\nZugangsdaten des von Ihnen ausgewählten Anbieters; Eine Umgebungsvariable wird von beiden internationalen Anbietern gemeinsam genutzt.\n\nDie Datei wird mit `0600`-Berechtigungen erstellt (Benutzer nur Lesen/Schreiben). Anmeldeinformationen der Authentifizierungsdatei haben Vorrang vor Umgebungsvariablen.\n\nAPI key Anmeldeinformationen können auch anbieterspezifische Umgebungswerte enthalten. Diese Werte werden vor Prozessumgebungsvariablen verwendet, wenn der Anmeldeinformationsschlüssel, Anbieter-/Modell-Header und Anbieterkonfigurationen wie Cloudflare-Konto-IDs, Azure OpenAI-Einstellungen, Vertex-Projekt/Standort, Bedrock-Einstellungen, `PI_CACHE_RETENTION` und `HTTP_PROXY`/`HTTPS_PROXY` aufgelöst werden.\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\nVerwenden Sie dies, wenn Pi andere Anbietereinstellungen als die Projekt-Shell-Umgebung verwenden soll.\n\n### Schlüsselauflösung\n\nDas Feld `key` unterstützt die Befehlsausführung, Umgebungsinterpolation und Literale:\n\n- **Shell-Befehl:** `\"!command\"` führt beim Start den gesamten Wert als Befehl aus und verwendet stdout (im Cache für die Prozesslebensdauer)\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- **Umgebungsinterpolation:** `\"$ENV_VAR\"` oder `\"${ENV_VAR}\"` verwendet den Wert der benannten Variablen. Die Interpolation funktioniert innerhalb größerer Literale.\n  ```json\n  { \"type\": \"api_key\", \"key\": \"$MY_ANTHROPIC_KEY\" }\n  { \"type\": \"api_key\", \"key\": \"${KEY_PREFIX}_${KEY_SUFFIX}\" }\n  ```\n  `$FOO_BAR` ist die Variable `FOO_BAR`; Verwenden Sie `${FOO}_BAR`, wenn `BAR` wörtlicher Text ist. Fehlende Umgebungsvariablen machen den Wert unaufgelöst.\n- **Escapes:** `\"$\"` gibt ein Literal `\"$\"` aus; `\"$!\"` gibt ein Literal `\"!\"` aus, ohne die Befehlsausführung auszulösen.\n  ```json\n  { \"type\": \"api_key\", \"key\": \"$$literal-dollar-prefix\" }\n  { \"type\": \"api_key\", \"key\": \"$!literal-bang-prefix\" }\n  ```\n- **Wörtlicher Wert:** Wird direkt verwendet. Einfache Zeichenfolgen in Großbuchstaben wie `MY_API_KEY` sind Literale; Verwenden Sie `$MY_API_KEY` für Umgebungsvariablen.\n  ```json\n  { \"type\": \"api_key\", \"key\": \"sk-ant-...\" }\n  { \"type\": \"api_key\", \"key\": \"public\" }\n  ```\n\nOAuth Zugangsdaten werden hier auch nach `/login` gespeichert und automatisch verwaltet.\n\n## Wolke 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### Amazonas-Grundgestein\n\nVerwenden Sie `/login amazon-bedrock`, um ein Bedrock API key zu speichern, oder konfigurieren Sie eine der folgenden AWS-Anmeldeinformationsquellen:\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\nUnterstützt auch ECS-Aufgabenrollen (`AWS_CONTAINER_CREDENTIALS_*`) und IRSA (`AWS_WEB_IDENTITY_TOKEN_FILE`).\n\n```bash\npi --provider amazon-bedrock --model us.anthropic.claude-sonnet-4-20250514-v1:0\n```\n\nPrompt-Caching wird automatisch für Claude-Modelle aktiviert, deren ID einen erkennbaren Modellnamen enthält (Basismodelle und systemdefinierte Inferenzprofile). Legen Sie für Anwendungsinferenzprofile (deren ARNs den Modellnamen nicht enthalten) `AWS_BEDROCK_FORCE_CACHE=1` fest, um Cache-Punkte zu aktivieren:\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\nWenn Sie eine Verbindung zu einem Bedrock API-Proxy herstellen, können die folgenden Umgebungsvariablen verwendet werden:\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 Gateway\n\n`CLOUDFLARE_API_KEY` kann über `/login` eingestellt werden. Die Konto-ID und der Gateway-Slug können als Umgebungsvariablen oder im `env`-Objekt der API key-Anmeldeinformationen in `auth.json` festgelegt werden.\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\nRouten zu OpenAI, Anthropic und Workers AI über Cloudflare AI Gateway. Workers AI verwendet die einheitlichen API (`/compat`) und vorangestellten Modell-IDs (`workers-ai/@cf/...`). OpenAI verwendet die OpenAI-Passthrough-Route (`/openai`) mit nativen OpenAI-Modell-IDs wie `gpt-5.1`. Anthropic verwendet die Anthropic-Passthrough-Route (`/anthropic`) mit nativen Anthropic-Modell-IDs wie `claude-sonnet-4-5`.\n\nDie AI Gateway-Authentifizierung verwendet `CLOUDFLARE_API_KEY` als `cf-aig-authorization`. Die Upstream-Authentifizierung kann eine der folgenden sein:\n\n| Modus | Autorisierung anfordern | Upstream-Authentifizierung |\n|------|--------------|---------------|\n| Arbeiter-KI | Nur Cloudflare-Token | Cloudflare-nativ |\n| Einheitliche Abrechnung | Nur Cloudflare-Token | Cloudflare übernimmt die Upstream-Authentifizierung und zieht Credits ab |\n| Gespeichert BYOK | Nur Cloudflare-Token | Cloudflare fügt Anbieterschlüssel ein, die im AI Gateway-Dashboard gespeichert sind |\n| Inline-BYOK | Cloudflare-Token plus Upstream-Header `Authorization` | Die Anfrage liefert den Upstream-Provider-Schlüssel |\n\nFür die normale Pi-Nutzung bevorzugen Sie eine einheitliche Abrechnung oder gespeichertes BYOK. Inline BYOK erfordert die Konfiguration eines zusätzlichen Upstream-`Authorization`-Headers für den Cloudflare AI Gateway-Anbieter, beispielsweise über eine `models.json`-Anbieter-/Modell-Überschreibung.\n\n### Cloudflare Workers AI\n\n`CLOUDFLARE_API_KEY` kann über `/login` eingestellt werden. `CLOUDFLARE_ACCOUNT_ID` kann als Umgebungsvariable oder im `env`-Objekt der API key Anmeldeinformationen in `auth.json` festgelegt werden.\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 setzt automatisch `x-session-affinity` für [prefix caching](https://developers.cloudflare.com/workers-ai/features/prompt-caching/) Rabatte.\n\n### Google Vertex AI\n\nVerwendet Standardanmeldeinformationen der Anwendung:\n\n```bash\ngcloud auth application-default login\nexport GOOGLE_CLOUD_PROJECT=your-project\nexport GOOGLE_CLOUD_LOCATION=us-central1\n```\n\nOder legen Sie `GOOGLE_APPLICATION_CREDENTIALS` auf eine Dienstkontoschlüsseldatei fest.\n\n## llama.cpp\n\nPi unterstützt den llama.cpp Router-Server. Konfigurieren Sie es mit `/login llama.cpp`, verwalten Sie geladene Modelle mit `/llama` und wählen Sie ein geladenes Modell mit `/model` aus.\n\nSiehe [llama.cpp](llama-cpp.md) für Server-Setup, Modellverzeichnislayout, Umgebungsvariablen und Befehlsverwendung.\n\n## Benutzerdefiniert Providers\n\n**Über models.json:** Fügen Sie Ollama, LM Studio, vLLM oder einen beliebigen Anbieter hinzu, der ein unterstütztes API spricht (OpenAI Completions, OpenAI Responses, Anthropic Messages, Google Generative AI). Siehe [models.md](models.md).\n\n**Über Erweiterungen:** Erstellen Sie für Anbieter, die benutzerdefinierte API-Implementierungen oder OAuth-Flows benötigen, eine Erweiterung. Siehe [custom-provider.md](custom-provider.md) und [examples/extensions/custom-provider-gitlab-duo](../examples/extensions/custom-provider-gitlab-duo/).\n\n## Auflösungsanordnung\n\nBeim Auflösen von Anmeldeinformationen für einen Anbieter:\n\n1. CLI `--api-key` Flagge\n2. `auth.json` Eintrag (API key oder OAuth Token)\n3. Umgebungsvariable\n4. Benutzerdefinierte Anbieterschlüssel von `models.json`","sourceFile":"providers.md"},"quickstart":{"title":"Schnellstart","markdown":"Diese Seite führt Sie von der Installation bis zu einer nützlichen ersten Pi-Sitzung.\n\n## Installieren\n\nPi wird als npm-Paket verteilt:\n\n```bash\nnpm install -g --ignore-scripts @earendil-works/pi-coding-agent\n```\n\n`--ignore-scripts` deaktiviert Abhängigkeitslebenszyklusskripte während der Installation. Pi erfordert keine Installationsskripte für normale npm-Installationen.\n\n### Deinstallieren\n\nVerwenden Sie den Paketmanager, der pi installiert hat. Das Curl-Installationsprogramm verwendet npm global, daher werden Curl- und npm-Installationen mit npm entfernt:\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\nBei der Deinstallation von pi verbleiben Einstellungen, Anmeldeinformationen, Sitzungen und installierte Pi-Pakete in `~/.pi/agent/`.\n\nStarten Sie dann pi in dem Projektverzeichnis, in dem es arbeiten soll:\n\n```bash\ncd /path/to/project\npi\n```\n\n## Authentifizieren\n\nPi kann subscription providers bis `/login` oder API-Schlüsselanbieter über Umgebungsvariablen oder die Authentifizierungsdatei verwenden.\n\n### Option 1: Abonnement-Login\n\nPi starten und ausführen:\n\n```text\n/login\n```\n\nWählen Sie dann einen Anbieter aus. Zu den integrierten Abonnement-Logins gehören Claude Pro/Max, ChatGPT Plus/Pro (Codex) und GitHub Copilot.\n\n### Option 2: API key\n\nStellen Sie eine API key ein, bevor Sie pi starten:\n\n```bash\nexport ANTHROPIC_API_KEY=sk-ant-...\npi\n```\n\nSie können auch `/login` ausführen und einen API-Schlüsselanbieter auswählen, um den Schlüssel in `~/.pi/agent/auth.json` zu speichern.\n\nUnter [Providers](providers.md) finden Sie alle unterstützten Anbieter, Umgebungsvariablen und die Einrichtung des Cloud-Anbieters.\n\n## Erste Sitzung\n\nSobald Pi startet, geben Sie eine Anfrage ein und drücken Sie die Eingabetaste:\n\n```text\nSummarize this repository and tell me how to run its checks.\n```\n\nStandardmäßig stellt Pi dem Modell vier Werkzeuge zur Verfügung:\n\n- `read` – Dateien lesen\n- `write` – Dateien erstellen oder überschreiben\n- `edit` – Patchdateien\n- `bash` – Shell-Befehle ausführen\n\nZusätzliche integrierte schreibgeschützte Tools (`grep`, `find`, `ls`) sind über die Tool-Optionen verfügbar. Pi läuft in Ihrem aktuellen Arbeitsverzeichnis und kann dort Dateien ändern. Verwenden Sie Git oder einen anderen Checkpointing-Workflow, wenn Sie ein einfaches Rollback wünschen.\n\n## Geben Sie Anweisungen für das Pi-Projekt\n\nPi lädt context files beim Start. Fügen Sie eine `AGENTS.md`-Datei hinzu, um ihm mitzuteilen, wie in einem Projekt gearbeitet werden soll:\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 lädt:\n\n- `~/.pi/agent/AGENTS.md` für globale Anweisungen\n- `AGENTS.md` oder `CLAUDE.md` aus übergeordneten Verzeichnissen und dem aktuellen Verzeichnis\n\nWenn ein Verzeichnis `AGENTS.override.md` enthält, lädt Pi es anstelle von `AGENTS.md` oder `CLAUDE.md` aus diesem Verzeichnis.\n\nStarten Sie pi neu oder führen Sie `/reload` aus, nachdem Sie context files geändert haben.\n\n## Häufige Dinge zum Ausprobieren\n\n### Referenzdateien\n\nGeben Sie `@` in den Editor ein, um eine Fuzzy-Suche nach Dateien durchzuführen, oder übergeben Sie Dateien in der Befehlszeile:\n\n```bash\npi @README.md \"Summarize this\"\npi @src/app.ts @src/app.test.ts \"Review these together\"\n```\n\nBilder oder Text können mit Strg+V (Alt+V unter Windows) eingefügt werden; Bilder können auch in unterstützte Terminals gezogen werden.\n\n### Führen Sie Shell-Befehle aus\n\nIm interaktiven Modus:\n\n```text\n!npm run lint\n```\n\nDie Befehlsausgabe wird an das Modell gesendet. Verwenden Sie `!!command`, um einen Befehl auszuführen, ohne seine Ausgabe zum Modellkontext hinzuzufügen.\n\n### Modelle wechseln\n\nVerwenden Sie `/model` oder Strg+L, um ein Modell auszuwählen. Verwenden Sie Umschalt+Tab, um die Denkebene zu wechseln. Verwenden Sie Strg+P/Umschalt+Strg+P, um durch die bereichsbezogenen Modelle zu blättern.\n\n### Fahren Sie später fort\n\nSitzungen werden automatisch gespeichert:\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\nVerwenden Sie in pi `/resume`, `/new`, `/tree`, `/fork` und `/clone`, um Sitzungen zu verwalten.\n\n### Nicht interaktiver Modus\n\nFür einmalige Eingabeaufforderungen:\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\nVerwenden Sie `--mode json` für die JSON-Ereignisausgabe oder `--mode rpc` für die Prozessintegration.\n\n## Nächste Schritte\n\n- [Using Pi](usage.md) – interaktiver Modus, slash commands Sitzungen, context files und CLI Referenz.\n- [Providers](providers.md) – Authentifizierung und Modelleinrichtung.\n- [Settings](settings.md) – globale und Projektkonfiguration.\n- [Keybindings](keybindings.md) – Verknüpfungen und Anpassung.\n- [Pi Packages](packages.md) – Gemeinsam genutzte Erweiterungen, Fähigkeiten, Eingabeaufforderungen und Themen installieren.\n\nPlattformhinweise: [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 Modus","markdown":"Der RPC-Modus ermöglicht den kopflosen Betrieb des Codierungsagenten über ein JSON-Protokoll über stdin/stdout. Dies ist nützlich, um den Agenten in andere Anwendungen, IDEs oder benutzerdefinierte UIs einzubetten.\n\n**Hinweis für Node.js/TypeScript-Benutzer**: Wenn Sie eine Node.js-Anwendung erstellen, sollten Sie erwägen, `AgentSession` direkt aus `@earendil-works/pi-coding-agent` zu verwenden, anstatt einen Unterprozess zu erzeugen. Siehe [`src/core/agent-session.ts`](../src/core/agent-session.ts) für API. Informationen zu einem unterprozessbasierten TypeScript-Client finden Sie unter [`src/modes/rpc/rpc-client.ts`](../src/modes/rpc/rpc-client.ts).\n\n## Starten des RPC-Modus\n\n```bash\npi --mode rpc [options]\n```\n\nHäufige Optionen:\n- `--provider <name>`: Legen Sie den LLM-Anbieter fest (anthropic, openai, google usw.)\n- `--model <pattern>`: Modellmuster oder ID (unterstützt `provider/id` und optional `:<thinking>`)\n- `--name <name>` / `-n <name>`: Legen Sie den Anzeigenamen der Sitzung beim Start fest\n- `--no-session`: Sitzungspersistenz deaktivieren\n- `--session-dir <path>`: Benutzerdefiniertes Sitzungsspeicherverzeichnis\n\n## Protokollübersicht\n\n- **Befehle**: JSON Objekte werden an stdin gesendet, eines pro Zeile\n- **Antworten**: JSON Objekte mit `type: \"response\"`, die den Erfolg/Fehler des Befehls anzeigen\n- **Ereignisse**: Agentenereignisse werden als JSON-Zeilen an stdout gestreamt\n\nAlle Befehle unterstützen ein optionales `id`-Feld für die Anforderungs-/Antwortkorrelation. Sofern angegeben, enthält die entsprechende Antwort dasselbe `id`. `bash_execution_update`-Ereignisse umfassen auch die `id` ihres ursprünglichen `bash`-Befehls.\n\n### Rahmen\n\nDer RPC-Modus verwendet die strikte JSONL-Semantik mit LF (`\\n`) als einzigem Datensatztrennzeichen.\n\nDas ist für Kunden wichtig:\n- Datensätze nur am `\\n` teilen\n- Akzeptieren Sie die optionale `\\r\\n`-Eingabe, indem Sie ein nachgestelltes `\\r` entfernen.\n- Verwenden Sie keine generischen Zeilenleser, die Unicode-Trennzeichen als Zeilenumbrüche behandeln\n\nInsbesondere ist Knoten `readline` nicht protokollkonform für den RPC-Modus, da er auch auf `U+2028` und `U+2029` aufteilt, die innerhalb von JSON-Strings gültig sind.\n\n## Befehle\n\n### Aufforderung\n\n#### prompt\n\nSenden Sie eine Benutzeraufforderung an den Agenten. Die Befehlsantwort wird ausgegeben, nachdem die Eingabeaufforderung akzeptiert, in die Warteschlange gestellt oder verarbeitet wurde. Ereignisse werden nach der Annahme weiterhin asynchron gestreamt.\n\n```json\n{\"id\": \"req-1\", \"type\": \"prompt\", \"message\": \"Hello, world!\"}\n```\n\nMit Bildern:\n```json\n{\"type\": \"prompt\", \"message\": \"What's in this image?\", \"images\": [{\"type\": \"image\", \"data\": \"base64-encoded-data\", \"mimeType\": \"image/png\"}]}\n```\n\n**Während des Streamings**: Wenn der Agent bereits streamt, müssen Sie `streamingBehavior` angeben, um die Nachricht in die Warteschlange zu stellen:\n\n```json\n{\"type\": \"prompt\", \"message\": \"New instruction\", \"streamingBehavior\": \"steer\"}\n```\n\n- `\"steer\"`: Die Nachricht in die Warteschlange stellen, während der Agent ausgeführt wird. Es wird geliefert, nachdem der aktuelle Assistentenzug die Ausführung seiner Werkzeugaufrufe abgeschlossen hat, und zwar vor dem nächsten LLM-Aufruf.\n- `\"followUp\"`: Warten Sie, bis der Agent fertig ist. Die Nachricht wird nur zugestellt, wenn der Agent stoppt.\n\nWenn der Agent streamt und kein `streamingBehavior` angegeben ist, gibt der Befehl einen Fehler zurück.\n\n**Erweiterungsbefehle**: Wenn es sich bei der Nachricht um einen Erweiterungsbefehl handelt (z. B. `/mycommand`), wird dieser auch während des Streamings sofort ausgeführt. Erweiterungsbefehle verwalten ihre eigene LLM-Interaktion über `pi.sendMessage()`.\n\n**Eingabeerweiterung**: Skill-Befehle (`/skill:name`) und prompt templates (`/template`) werden vor dem Senden/in die Warteschlange erweitert.\n\nAntwort:\n```json\n{\"id\": \"req-1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true}\n```\n\n`success: true` bedeutet, dass die Eingabeaufforderung angenommen, in die Warteschlange gestellt oder sofort bearbeitet wurde. `success: false` bedeutet, dass die Eingabeaufforderung vor der Annahme abgelehnt wurde. Fehler nach der Annahme werden über den normalen Ereignis- und Nachrichtenstrom gemeldet, nicht als zweites `response` für dieselbe Anforderungs-ID.\n\nDas Feld `images` ist optional. Jedes Bild verwendet das `ImageContent`-Format: `{\"type\": \"image\", \"data\": \"base64-encoded-data\", \"mimeType\": \"image/png\"}`.\n\n#### steuern\n\nStellen Sie eine Lenkungsnachricht in die Warteschlange, während der Agent ausgeführt wird. Es wird geliefert, nachdem der aktuelle Assistentenzug die Ausführung seiner Werkzeugaufrufe abgeschlossen hat, und zwar vor dem nächsten LLM-Aufruf. Fertigkeitsbefehle und prompt templates werden erweitert. Erweiterungsbefehle sind nicht zulässig (verwenden Sie stattdessen `prompt`).\n\n```json\n{\"type\": \"steer\", \"message\": \"Stop and do this instead\"}\n```\n\nMit Bildern:\n```json\n{\"type\": \"steer\", \"message\": \"Look at this instead\", \"images\": [{\"type\": \"image\", \"data\": \"base64-encoded-data\", \"mimeType\": \"image/png\"}]}\n```\n\nDas Feld `images` ist optional. Jedes Bild verwendet das Format `ImageContent` (dasselbe wie `prompt`).\n\nAntwort:\n```json\n{\"type\": \"response\", \"command\": \"steer\", \"success\": true}\n```\n\nSiehe [set_steering_mode](#set_steering_mode) zur Steuerung der Verarbeitung von Lenkungsnachrichten.\n\n#### nachverfolgen\n\nStellen Sie eine Folgenachricht in die Warteschlange, die verarbeitet werden soll, nachdem der Agent fertig ist. Wird nur zugestellt, wenn der Agent keine Tool-Anrufe oder Steuerungsnachrichten mehr hat. Fertigkeitsbefehle und prompt templates werden erweitert. Erweiterungsbefehle sind nicht zulässig (verwenden Sie stattdessen `prompt`).\n\n```json\n{\"type\": \"follow_up\", \"message\": \"After you're done, also do this\"}\n```\n\nMit Bildern:\n```json\n{\"type\": \"follow_up\", \"message\": \"Also check this image\", \"images\": [{\"type\": \"image\", \"data\": \"base64-encoded-data\", \"mimeType\": \"image/png\"}]}\n```\n\nDas Feld `images` ist optional. Jedes Bild verwendet das Format `ImageContent` (dasselbe wie `prompt`).\n\nAntwort:\n```json\n{\"type\": \"response\", \"command\": \"follow_up\", \"success\": true}\n```\n\nSiehe [set_follow_up_mode](#set_follow_up_mode) für die Steuerung, wie Folgenachrichten verarbeitet werden.\n\n#### abbrechen\n\nBrechen Sie den aktuellen Agentenvorgang ab.\n\n```json\n{\"type\": \"abort\"}\n```\n\nAntwort:\n```json\n{\"type\": \"response\", \"command\": \"abort\", \"success\": true}\n```\n\n#### neue_Sitzung\n\nStarten Sie eine neue Sitzung. Kann durch einen `session_before_switch`-Erweiterungsereignishandler abgebrochen werden.\n\n```json\n{\"type\": \"new_session\"}\n```\n\nMit optionaler übergeordneter Sitzungsverfolgung:\n```json\n{\"type\": \"new_session\", \"parentSession\": \"/path/to/parent-session.jsonl\"}\n```\n\nAntwort:\n```json\n{\"type\": \"response\", \"command\": \"new_session\", \"success\": true, \"data\": {\"cancelled\": false}}\n```\n\nWenn eine Verlängerung storniert wird:\n```json\n{\"type\": \"response\", \"command\": \"new_session\", \"success\": true, \"data\": {\"cancelled\": true}}\n```\n\n### Zustand\n\n#### get_state\n\nAktuellen Sitzungsstatus abrufen.\n\n```json\n{\"type\": \"get_state\"}\n```\n\nAntwort:\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\nDas Feld `model` ist ein vollständiges [Model](#model)-Objekt oder `null`. Das Feld `sessionName` ist der über `set_session_name` festgelegte Anzeigename oder wird weggelassen, wenn es nicht festgelegt ist.\n\n#### get_messages\n\nErhalten Sie alle Nachrichten in der Konversation.\n\n```json\n{\"type\": \"get_messages\"}\n```\n\nAntwort:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_messages\",\n  \"success\": true,\n  \"data\": {\"messages\": [...]}\n}\n```\n\nNachrichten sind `AgentMessage` Objekte (siehe [Message Types](#message-types)).\n\n### Modell\n\n#### set_model\n\nWechseln Sie zu einem bestimmten Modell.\n\n```json\n{\"type\": \"set_model\", \"provider\": \"anthropic\", \"modelId\": \"claude-sonnet-4-20250514\"}\n```\n\nDie Antwort enthält das vollständige [Model](#model)-Objekt:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"set_model\",\n  \"success\": true,\n  \"data\": {...}\n}\n```\n\n#### zyklusmodell\n\nWechseln Sie zum nächsten verfügbaren Modell. Gibt `null` Daten zurück, wenn nur ein Modell verfügbar ist.\n\n```json\n{\"type\": \"cycle_model\"}\n```\n\nAntwort:\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\nDas Feld `model` ist ein vollständiges [Model](#model)-Objekt.\n\n#### get_available_models\n\nListen Sie alle konfigurierten Modelle auf.\n\n```json\n{\"type\": \"get_available_models\"}\n```\n\nDie Antwort enthält ein Array vollständiger [Model](#model)-Objekte:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_available_models\",\n  \"success\": true,\n  \"data\": {\n    \"models\": [...]\n  }\n}\n```\n\n### Denken\n\n#### set_thinking_level\n\nLegen Sie die Argumentations-/Denkebene für Modelle fest, die dies unterstützen.\n\n```json\n{\"type\": \"set_thinking_level\", \"level\": \"high\"}\n```\n\nStufen: `\"off\"`, `\"minimal\"`, `\"low\"`, `\"medium\"`, `\"high\"`, `\"xhigh\"`, `\"max\"`\n\n`\"xhigh\"` und `\"max\"` werden nur angezeigt, wenn sie vom ausgewählten Modell unterstützt werden. Einige Modelle, darunter GPT-5.6, stellen beides zur Verfügung.\n\nAntwort:\n```json\n{\"type\": \"response\", \"command\": \"set_thinking_level\", \"success\": true}\n```\n\n#### Cycle_thinking_level\n\nDurchlaufen Sie die verfügbaren Denkebenen. Gibt `null` Daten zurück, wenn das Modell das Denken nicht unterstützt.\n\n```json\n{\"type\": \"cycle_thinking_level\"}\n```\n\nAntwort:\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\nListen Sie die vom aktuellen Modell unterstützten Denkebenen auf. Gibt `[\"off\"]` für ein Modell ohne Begründungsunterstützung zurück.\n\n```json\n{\"type\": \"get_available_thinking_levels\"}\n```\n\nAntwort:\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### Warteschlangenmodi\n\n#### set_steering_mode\n\nSteuern Sie, wie Leitnachrichten (ab `steer`) übermittelt werden.\n\n```json\n{\"type\": \"set_steering_mode\", \"mode\": \"one-at-a-time\"}\n```\n\nModi:\n- `\"all\"`: Übermitteln Sie alle Lenknachrichten, nachdem der aktuelle Assistentenzug die Ausführung seiner Werkzeugaufrufe abgeschlossen hat\n- `\"one-at-a-time\"`: Übermittlung einer Lenkungsnachricht pro abgeschlossener Assistentendrehung (Standard)\n\nAntwort:\n```json\n{\"type\": \"response\", \"command\": \"set_steering_mode\", \"success\": true}\n```\n\n#### set_follow_up_mode\n\nSteuern Sie, wie Folgenachrichten (ab `follow_up`) zugestellt werden.\n\n```json\n{\"type\": \"set_follow_up_mode\", \"mode\": \"one-at-a-time\"}\n```\n\nModi:\n- `\"all\"`: Alle Folgenachrichten zustellen, wenn der Agent fertig ist\n- `\"one-at-a-time\"`: Eine Folgenachricht pro Agent-Abschluss senden (Standard)\n\nAntwort:\n```json\n{\"type\": \"response\", \"command\": \"set_follow_up_mode\", \"success\": true}\n```\n\n### Verdichtung\n\n#### kompakt\n\nKonversationskontext manuell komprimieren, um die Token-Nutzung zu reduzieren.\n\n```json\n{\"type\": \"compact\"}\n```\n\nMit individueller Anleitung:\n```json\n{\"type\": \"compact\", \"customInstructions\": \"Focus on code changes\"}\n```\n\nAntwort:\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` ist eine heuristische Schätzung des neu erstellten Nachrichtenkontexts unmittelbar nach der Komprimierung, keine anbietergenaue Tokenanzahl. `usage` meldet den oder die LLM-Aufrufe, die die Zusammenfassung generiert haben, und kann von benutzerdefinierten Komprimierungshandlern weggelassen werden.\n\n#### set_auto_compaction\n\nAktivieren oder deaktivieren Sie die automatische Komprimierung, wenn der Kontext fast voll ist.\n\n```json\n{\"type\": \"set_auto_compaction\", \"enabled\": true}\n```\n\nAntwort:\n```json\n{\"type\": \"response\", \"command\": \"set_auto_compaction\", \"success\": true}\n```\n\n### Wiederholen\n\n#### set_auto_retry\n\nAktivieren oder deaktivieren Sie die automatische Wiederholung bei vorübergehenden Fehlern (Überlastung, Ratenbegrenzung, 5xx).\n\n```json\n{\"type\": \"set_auto_retry\", \"enabled\": true}\n```\n\nAntwort:\n```json\n{\"type\": \"response\", \"command\": \"set_auto_retry\", \"success\": true}\n```\n\n#### abort_retry\n\nEinen laufenden Wiederholungsversuch abbrechen (die Verzögerung aufheben und den Wiederholungsversuch beenden).\n\n```json\n{\"type\": \"abort_retry\"}\n```\n\nAntwort:\n```json\n{\"type\": \"response\", \"command\": \"abort_retry\", \"success\": true}\n```\n\n### Bash\n\n#### bash\n\nFühren Sie einen Shell-Befehl aus und fügen Sie die Ausgabe zum Konversationskontext hinzu. Geben Sie Streams als `bash_execution_update`-Ereignisse aus, während der Befehl ausgeführt wird. Die Antwort enthält das Endergebnis.\n\n```json\n{\"id\": \"req-1\", \"type\": \"bash\", \"command\": \"ls -la\"}\n```\n\nFügen Sie eine `id` ein, um gestreamte `bash_execution_update`-Ereignisse mit diesem Befehl zu verknüpfen.\n\nAntwort:\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\nWenn die Ausgabe abgeschnitten wurde, enthält sie `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**Wie bash Ergebnisse das LLM erreichen:**\n\nDer Befehl `bash` wird sofort ausgeführt und gibt eine `BashResult` zurück. Intern wird ein `BashExecutionMessage` erstellt und im Nachrichtenstatus des Agenten gespeichert.\n\nWenn der nächste `prompt`-Befehl gesendet wird, werden alle Nachrichten (einschließlich `BashExecutionMessage`) umgewandelt, bevor sie an das LLM gesendet werden. Die `BashExecutionMessage` wird mit diesem Format in eine `UserMessage` umgewandelt:\n\n````\nRan `ls -la`\n```\ninsgesamt 48\ndrwxr-xr-x...\n```\n````\n\nDas heisst:\n1. Die Bash-Ausgabe wird nicht sofort, sondern erst bei der **nächsten Eingabeaufforderung** in den LLM-Kontext eingebunden\n2. Vor einer Eingabeaufforderung können mehrere bash-Befehle ausgeführt werden; Alle Ausgaben werden einbezogen\n\n#### abort_bash\n\nBrechen Sie einen laufenden bash-Befehl ab.\n\n```json\n{\"type\": \"abort_bash\"}\n```\n\nAntwort:\n```json\n{\"type\": \"response\", \"command\": \"abort_bash\", \"success\": true}\n```\n\n### Sitzung\n\n#### get_session_stats\n\nErhalten Sie die Token-Nutzung, Kostenstatistiken und die aktuelle Kontextfensternutzung.\n\n```json\n{\"type\": \"get_session_stats\"}\n```\n\nAntwort:\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` und `cost` umfassen Assistentenmeldungen, von Tools gemeldete Nutzung und die Erstellung von Komprimierungs-/Zweigzusammenfassungen über die gesamte Sitzung hinweg. `contextUsage` enthält die tatsächliche aktuelle Kontextfensterschätzung, die für die Komprimierung und Fußzeilenanzeige verwendet wird.\n\n`contextUsage` wird weggelassen, wenn kein Modell- oder Kontextfenster verfügbar ist. `contextUsage.tokens` und `contextUsage.percent` sind `null` unmittelbar nach der Verdichtung, bis eine neue Antwort des Assistenten nach der Verdichtung gültige Nutzungsdaten liefert.\n\n#### export_html\n\nSitzung in eine HTML-Datei exportieren.\n\n```json\n{\"type\": \"export_html\"}\n```\n\nMit benutzerdefiniertem Pfad:\n```json\n{\"type\": \"export_html\", \"outputPath\": \"/tmp/session.html\"}\n```\n\nAntwort:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"export_html\",\n  \"success\": true,\n  \"data\": {\"path\": \"/tmp/session.html\"}\n}\n```\n\n#### switch_session\n\nLaden Sie eine andere Sitzungsdatei. Kann durch einen `session_before_switch`-Erweiterungsereignishandler abgebrochen werden.\n\n```json\n{\"type\": \"switch_session\", \"sessionPath\": \"/path/to/session.jsonl\"}\n```\n\nAntwort:\n```json\n{\"type\": \"response\", \"command\": \"switch_session\", \"success\": true, \"data\": {\"cancelled\": false}}\n```\n\nWenn eine Erweiterung den Wechsel abgebrochen hat:\n```json\n{\"type\": \"response\", \"command\": \"switch_session\", \"success\": true, \"data\": {\"cancelled\": true}}\n```\n\n#### Gabel\n\nErstellen Sie einen neuen Fork aus einer vorherigen Benutzernachricht im aktiven Zweig. Kann durch einen `session_before_fork`-Erweiterungsereignishandler abgebrochen werden. Gibt den Text der Nachricht zurück, aus der geforkt wird.\n\n```json\n{\"type\": \"fork\", \"entryId\": \"abc123\"}\n```\n\nAntwort:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"fork\",\n  \"success\": true,\n  \"data\": {\"text\": \"The original prompt text...\", \"cancelled\": false}\n}\n```\n\nWenn eine Erweiterung den Fork abgebrochen hat:\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#### Klon\n\nDuplizieren Sie den aktuell aktiven Zweig in eine neue Sitzung an der aktuellen Position. Kann durch einen `session_before_fork`-Erweiterungsereignishandler abgebrochen werden.\n\n```json\n{\"type\": \"clone\"}\n```\n\nAntwort:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"clone\",\n  \"success\": true,\n  \"data\": {\"cancelled\": false}\n}\n```\n\nWenn eine Erweiterung den Klon abgebrochen hat:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"clone\",\n  \"success\": true,\n  \"data\": {\"cancelled\": true}\n}\n```\n\n#### get_fork_messages\n\nErhalten Sie Benutzernachrichten, die zum Forken verfügbar sind.\n\n```json\n{\"type\": \"get_fork_messages\"}\n```\n\nAntwort:\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\nRuft alle Sitzungseinträge in der Anhängereihenfolge ab (mit Ausnahme des Sitzungsheaders). Bei der Sitzung handelt es sich um einen Nur-Anhänge-Baum von Einträgen mit stabilen IDs, sodass eine Eintrags-ID als dauerhafter Cursor fungiert: Übergeben Sie die letzte Eintrags-ID, die Sie gesehen haben, als `since`, um nur Einträge direkt danach zu erhalten, auch über Client-Neustarts hinweg. Im Gegensatz zu `get_messages` umfasst dies auch den Verlauf vor der Verdichtung und verlassene Äste.\n\n```json\n{\"type\": \"get_entries\"}\n```\n\nMit einem Cursor:\n```json\n{\"type\": \"get_entries\", \"since\": \"abc123\"}\n```\n\nAntwort:\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` ist die ID des aktuellen Blatteintrags (`null` für eine leere Sitzung), sodass ein Client in einem Roundtrip erkennen kann, ob der aktive Zweig verschoben wurde. Wenn `since` mit keiner Eintrags-ID übereinstimmt, lautet die Antwort `success: false`.\n\n#### get_tree\n\nRufen Sie die Sitzung als Baumstruktur mit Einträgen ab. Jeder Knoten ist `{entry, children, label?, labelTimestamp?}`. Eine wohlgeformte Sitzung hat einen einzigen Stamm; verwaiste Einträge (unterbrochene übergeordnete Kette) erscheinen ebenfalls als Wurzeln.\n\n```json\n{\"type\": \"get_tree\"}\n```\n\nAntwort:\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\nRufen Sie den Textinhalt der letzten Assistentennachricht ab.\n\n```json\n{\"type\": \"get_last_assistant_text\"}\n```\n\nAntwort:\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\nGibt `{\"text\": null}` zurück, wenn keine Assistentenmeldungen vorhanden sind.\n\n#### set_session_name\n\nLegen Sie einen Anzeigenamen für die aktuelle Sitzung fest. Der Name erscheint in Sitzungslisten und hilft bei der Identifizierung von Sitzungen.\n\n```json\n{\"type\": \"set_session_name\", \"name\": \"my-feature-work\"}\n```\n\nAntwort:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"set_session_name\",\n  \"success\": true\n}\n```\n\nDer aktuelle Sitzungsname ist über `get_state` im Feld `sessionName` verfügbar. Um den Anfangsnamen beim Starten des RPC-Modus festzulegen, übergeben Sie `--name <name>` oder `-n <name>` an den `pi --mode rpc`-Prozess.\n\n### Befehle\n\n#### get_commands\n\nErhalten Sie verfügbare Befehle (Erweiterungsbefehle, prompt templates und Fähigkeiten). Diese können über den Befehl `prompt` mit dem Präfix `/` aufgerufen werden.\n\n```json\n{\"type\": \"get_commands\"}\n```\n\nAntwort:\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\nJeder Befehl hat:\n- `name`: Befehlsname (Aufruf mit `/name`)\n- `description`: Für Menschen lesbare Beschreibung (optional für Erweiterungsbefehle)\n- `source`: Was für ein Befehl:\n  - `\"extension\"`: Registriert über `pi.registerCommand()` in einer Nebenstelle\n  - `\"prompt\"`: Wird aus einer Eingabeaufforderungsvorlagendatei `.md` geladen\n  - `\"skill\"`: Aus einem Skill-Verzeichnis geladen (Name wird mit `skill:` vorangestellt)\n- `location`: Woher es geladen wurde (optional, bei Erweiterungen nicht vorhanden):\n  - `\"user\"`: Benutzerebene (`~/.pi/agent/`)\n  - `\"project\"`: Projektebene (`./.pi/agent/`)\n  - `\"path\"`: Expliziter Pfad über CLI oder Einstellungen\n- `path`: Absoluter Dateipfad zur Befehlsquelle (optional)\n\n**Hinweis**: Integrierte TUI-Befehle (`/settings`, `/hotkeys` usw.) sind nicht enthalten. Sie werden nur im interaktiven Modus verarbeitet und würden nicht ausgeführt, wenn sie über `prompt` gesendet würden.\n\n## Veranstaltungen\n\nEreignisse werden während des Agentenbetriebs als JSON-Leitungen an stdout gestreamt. Ereignisse enthalten im Allgemeinen kein `id`-Feld; `bash_execution_update` enthält die `id` des ursprünglichen `bash`-Befehls, wenn einer bereitgestellt wurde.\n\n### Ereignistypen\n\n| Ereignis | Beschreibung |\n|-------|-------------|\n| `agent_start` | Der Agent beginnt mit der Verarbeitung |\n| `agent_end` | Eine Agentenausführung auf niedriger Ebene wird abgeschlossen (es können noch Wiederholungsversuche, eine Komprimierung oder Fortsetzungen in der Warteschlange folgen). |\n| `agent_settled` | Der Agentenlauf ist vollständig abgewickelt; Es bleibt kein automatischer Wiederholungsversuch, kein Komprimierungswiederholungsversuch oder keine Fortsetzung in der Warteschlange übrig |\n| `turn_start` | Eine neue Runde beginnt |\n| `turn_end` | Drehung abgeschlossen (einschließlich Assistentenmeldung und Werkzeugergebnisse) |\n| `message_start` | Die Nachricht beginnt |\n| `message_update` | Streaming-Update (Text-/Denk-/Toolcall-Deltas) |\n| `message_end` | Nachricht abgeschlossen |\n| `bash_execution_update` | Direkter RPC bash Befehlsausgabeblock |\n| `tool_execution_start` | Das Tool beginnt mit der Ausführung |\n| `tool_execution_update` | Fortschritt der Tool-Ausführung (Streaming-Ausgabe) |\n| `tool_execution_end` | Das Werkzeug ist fertig |\n| `queue_update` | Ausstehende Lenkungs-/Folgewarteschlange geändert |\n| `compaction_start` | Die Verdichtung beginnt |\n| `compaction_end` | Die Verdichtung ist abgeschlossen |\n| `auto_retry_start` | Automatischer Wiederholungsversuch beginnt (nach vorübergehendem Fehler) |\n| `auto_retry_end` | Automatischer Wiederholungsversuch abgeschlossen (Erfolg oder endgültiger Fehler) |\n| `summarization_retry_scheduled` | Ein Wiederholungsversuch ist für einen vorübergehenden Komprimierungs- oder Branch-Summary-Zusammenfassungsfehler geplant |\n| `summarization_retry_attempt_start` | Die wiederholte Zusammenfassungsanforderung wird gestartet |\n| `summarization_retry_finished` | Die Wiederholungsschleife für die Zusammenfassung ist abgeschlossen |\n| `extension_error` | Die Erweiterung hat einen Fehler ausgegeben |\n\n### agent_start\n\nWird ausgegeben, wenn der Agent mit der Verarbeitung einer Eingabeaufforderung beginnt.\n\n```json\n{\"type\": \"agent_start\"}\n```\n\n### agent_end\n\nWird ausgegeben, wenn die Ausführung eines Low-Level-Agents abgeschlossen ist. Enthält alle während dieses Laufs generierten Nachrichten. Wenn `willRetry` wahr ist, folgt ein automatischer Wiederholungsversuch.\n\n```json\n{\n  \"type\": \"agent_end\",\n  \"messages\": [...],\n  \"willRetry\": false\n}\n```\n\n### agent_settled\n\nWird ausgegeben, nachdem der vollständige Lauf auf Sitzungsebene abgeschlossen ist. Zu diesem Zeitpunkt wird Pi nicht automatisch durch Wiederholungsversuche, Komprimierungswiederholungsversuche oder in der Warteschlange befindliche Folgenachrichten fortgesetzt.\n\n```json\n{\"type\": \"agent_settled\"}\n```\n\n### turn_start / turn_end\n\nEine Runde besteht aus einer Assistentenantwort sowie allen daraus resultierenden Werkzeugaufrufen und Ergebnissen.\n\n```json\n{\"type\": \"turn_start\"}\n```\n\n```json\n{\n  \"type\": \"turn_end\",\n  \"message\": {...},\n  \"toolResults\": [...]\n}\n```\n\n### message_start / message_end\n\nWird ausgegeben, wenn eine Nachricht beginnt und endet. Das Feld `message` enthält eine `AgentMessage`.\n\n```json\n{\"type\": \"message_start\", \"message\": {...}}\n{\"type\": \"message_end\", \"message\": {...}}\n```\n\n### message_update (Streaming)\n\nWird während des Streamings von Assistentennachrichten ausgegeben. Enthält ein Delta-Ereignis ohne kumulativen Nachrichten-Snapshot.\n\n```json\n{\n  \"type\": \"message_update\",\n  \"assistantMessageEvent\": {\n    \"type\": \"text_delta\",\n    \"contentIndex\": 0,\n    \"delta\": \"Hello \"\n  }\n}\n```\n\nDas Feld `assistantMessageEvent` enthält einen dieser Deltatypen:\n\n| Typ | Beschreibung |\n|------|-------------|\n| `text_start` | Textinhaltsblock gestartet |\n| `text_delta` | Textinhaltsblock |\n| `text_end` | Textinhaltsblock beendet |\n| `thinking_start` | Denkblockade begann |\n| `thinking_delta` | Denkender Inhaltsblock |\n| `thinking_end` | Denkblockade beendet |\n| `toolcall_start` | Werkzeugaufruf gestartet |\n| `toolcall_delta` | Block mit Toolaufrufargumenten |\n| `toolcall_end` | Werkzeugaufruf beendet (einschließlich vollständigem `toolCall`-Objekt) |\n\nBeispiel für das Streamen einer Textantwort:\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` lässt das frühere kumulative `message`-Feld absichtlich weg und\n`assistantMessageEvent.partial`. Clients, die eine Live-Teilnachricht benötigen, müssen diese zusammenstellen\nab `message_start` und Folgeereignisse mit `contentIndex`. Behandeln Sie `message_end.message`\nals maßgeblich. Für Werkzeugaufrufe Puffer `toolcall_delta.delta`; `toolcall_end.toolCall`\nenthält den abgeschlossenen Anruf.\n\n### bash_execution_update\n\nWird einmal für jeden Ausgabeblock eines direkten `bash`-Befehls ausgegeben. `id` stimmt mit `id` des Befehls überein, sodass Clients die Ausgabe dem richtigen Befehl zuordnen können.\n\nEreignisse streamen die gesamte Ausgabe, während der Befehl ausgeführt wird, auch wenn die endgültige `bash`-Antwort `output` abgeschnitten ist.\n\n```json\n{\n  \"type\": \"bash_execution_update\",\n  \"id\": \"req-1\",\n  \"delta\": \"total 48\\n\"\n}\n```\n\n### tool_execution_start / tool_execution_update / tool_execution_end\n\nWird ausgegeben, wenn ein Tool startet, den Fortschritt streamt und die Ausführung abschließt.\n\n```json\n{\n  \"type\": \"tool_execution_start\",\n  \"toolCallId\": \"call_abc123\",\n  \"toolName\": \"bash\",\n  \"args\": {\"command\": \"ls -la\"}\n}\n```\n\nWährend der Ausführung strömen `tool_execution_update` Ereignisse Teilergebnisse (z. B. bash Ausgabe, sobald sie eintreffen):\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\nWenn es fertig ist:\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\nVerwenden Sie `toolCallId`, um Ereignisse zu korrelieren. Die `partialResult` in `tool_execution_update` enthält die bisher akkumulierte Ausgabe (nicht nur das Delta), sodass Clients ihre Anzeige bei jedem Update einfach ersetzen können.\n\n### queue_update\n\nWird immer dann ausgegeben, wenn sich die ausstehende Steuerungs- oder Folgewarteschlange ändert.\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### Verdichtungsstart / Verdichtungsende\n\nWird ausgegeben, wenn die Verdichtung ausgeführt wird, egal ob manuell oder automatisch.\n\n```json\n{\"type\": \"compaction_start\", \"reason\": \"threshold\"}\n```\n\nDas Feld `reason` ist `\"manual\"`, `\"threshold\"` oder `\"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\nWenn `reason` `\"overflow\"` war und die Komprimierung erfolgreich war, ist `willRetry` `true` und der Agent wird die Eingabeaufforderung automatisch wiederholen.\n\nWenn die Komprimierung abgebrochen wurde, ist `result` `null` und `aborted` ist `true`.\n\nWenn die Komprimierung fehlgeschlagen ist (z. B. API Kontingent überschritten), ist `result` `null`, `aborted` ist `false` und `errorMessage` enthält die Fehlerbeschreibung.\n\n### auto_retry_start / auto_retry_end\n\nWird ausgegeben, wenn nach einem vorübergehenden Fehler (Überlastung, Ratenbegrenzung, 5xx) ein automatischer Wiederholungsversuch ausgelöst wird.\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\nBei endgültigem Fehlschlag (maximale Wiederholungsversuche überschritten):\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\nWird ausgegeben, wenn die Komprimierung oder Verzweigungszusammenfassung nach einem vorübergehenden Anbieterfehler erneut versucht wird. Für diese Ereignisse werden dieselben Wiederholungseinstellungen wie für automatische Wiederholungsversuche beim Assistenten verwendet.\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\nFür Zweigzusammenfassungen ist `source` gleich `\"branchSummary\"` und es ist kein `reason` vorhanden.\n\n```json\n{\n  \"type\": \"summarization_retry_finished\"\n}\n```\n\n### extension_error\n\nWird ausgegeben, wenn eine Erweiterung einen Fehler auslöst.\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## Erweiterungs-UI-Protokoll\n\nExtensions kann Benutzerinteraktion über `ctx.ui.select()`, `ctx.ui.confirm()` usw. anfordern. Im RPC-Modus werden diese in ein Anforderungs-/Antwort-Unterprotokoll über dem Basisbefehls-/Ereignisfluss übersetzt.\n\nEs gibt zwei Kategorien von Erweiterungs-UI-Methoden:\n\n- **Dialogmethoden** (`select`, `confirm`, `input`, `editor`): Geben Sie ein `extension_ui_request` auf stdout aus und blockieren Sie, bis der Client ein `extension_ui_response` auf stdin mit dem passenden `id` zurücksendet.\n- **Fire-and-Forget-Methoden** (`notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`): Geben Sie bei stdout eine `extension_ui_request` aus, erwarten Sie jedoch keine Antwort. Der Client kann die Informationen anzeigen oder ignorieren.\n\nWenn eine Dialogmethode ein `timeout`-Feld enthält, führt die Agentenseite nach Ablauf des Timeouts automatisch eine Lösung mit einem Standardwert durch. Der Client muss keine Zeitüberschreitungen verfolgen.\n\nEinige `ExtensionUIContext`-Methoden werden im RPC-Modus nicht unterstützt oder sind eingeschränkt, da sie direkten TUI-Zugriff erfordern:\n- `custom()` gibt `undefined` zurück\n- `setWorkingMessage()`, `setWorkingIndicator()`, `setFooter()`, `setHeader()`, `setEditorComponent()`, `setToolsExpanded()` sind No-Ops\n- `getEditorText()` gibt `\"\"` zurück\n- `getToolsExpanded()` gibt `false` zurück\n- `pasteToEditor()` delegiert an `setEditorText()` (keine Einfüge-/Reduzierungsbehandlung)\n- `getAllThemes()` gibt `[]` zurück\n- `getTheme()` gibt `undefined` zurück\n- `setTheme()` gibt `{ success: false, error: \"...\" }` zurück\n\nHinweis: `ctx.mode` ist `\"rpc\"` und `ctx.hasUI` ist `true` im RPC-Modus, da die Dialog- und Fire-and-Forget-Methoden über das Erweiterungs-UI-Unterprotokoll funktionieren. Verwenden Sie `ctx.mode === \"tui\"`, um TUI-spezifische Funktionen wie `custom()` zu schützen, die ein echtes Terminal erfordern.\n\n### Erweiterungs-UI-Anfragen (stdout)\n\nAlle Anfragen haben `type: \"extension_ui_request\"`, ein eindeutiges `id` und ein `method`-Feld.\n\n#### wählen\n\nFordern Sie den Benutzer auf, aus einer Liste auszuwählen. Dialogmethoden mit einem `timeout`-Feld enthalten den Timeout in Millisekunden; Der Agent führt automatisch eine Lösung mit `undefined` aus, wenn der Client nicht rechtzeitig antwortet.\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\nErwartete Antwort: `extension_ui_response` mit `value` (der ausgewählten Optionszeichenfolge) oder `cancelled: true`.\n\n#### bestätigen\n\nFordern Sie den Benutzer zur Ja/Nein-Bestätigung auf.\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\nErwartete Antwort: `extension_ui_response` mit `confirmed: true/false` oder `cancelled: true`.\n\n#### Eingang\n\nFordern Sie den Benutzer auf, Freitext einzugeben.\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\nErwartete Antwort: `extension_ui_response` mit `value` (der eingegebene Text) oder `cancelled: true`.\n\n#### Editor\n\nÖffnen Sie einen mehrzeiligen Texteditor mit optionalem vorab ausgefülltem Inhalt.\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\nErwartete Antwort: `extension_ui_response` mit `value` (der bearbeitete Text) oder `cancelled: true`.\n\n#### benachrichtigen\n\nEine Benachrichtigung anzeigen. Feuer und Vergessen, keine Antwort erwartet.\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\nDas Feld `notifyType` ist `\"info\"`, `\"warning\"` oder `\"error\"`. Der Standardwert ist `\"info\"`, wenn er weggelassen wird.\n\n#### setStatus\n\nSetzen oder löschen Sie einen Statuseintrag in der Fußzeile/Statusleiste. Feuer-und-vergessen.\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\nSenden Sie `statusText: undefined` (oder lassen Sie es weg), um den Statuseintrag für diese Taste zu löschen.\n\n#### setWidget\n\nLegen Sie ein Widget (Textzeilenblock) fest oder löschen Sie es, das über oder unter dem Editor angezeigt wird. Feuer-und-vergessen.\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\nSenden Sie `widgetLines: undefined` (oder lassen Sie es weg), um das Widget zu löschen. Das Feld `widgetPlacement` ist `\"aboveEditor\"` (Standard) oder `\"belowEditor\"`. Im RPC-Modus werden nur String-Arrays unterstützt; Komponentenfabriken werden ignoriert.\n\n#### setTitle\n\nLegen Sie den Titel des Terminalfensters/der Registerkarte fest. Feuer-und-vergessen.\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\nLegen Sie den Text im Eingabeeditor fest. Feuer-und-vergessen.\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### Antworten auf die Erweiterungs-UI (stdin)\n\nAntworten werden nur für Dialogmethoden gesendet (`select`, `confirm`, `input`, `editor`). Die `id` muss mit der Anfrage übereinstimmen.\n\n#### Wertantwort (auswählen, eingeben, bearbeiten)\n\n```json\n{\"type\": \"extension_ui_response\", \"id\": \"uuid-1\", \"value\": \"Allow\"}\n```\n\n#### Bestätigungsantwort (Bestätigen)\n\n```json\n{\"type\": \"extension_ui_response\", \"id\": \"uuid-2\", \"confirmed\": true}\n```\n\n#### Stornierungsantwort (beliebiger Dialog)\n\nVerwerfen Sie alle Dialogmethoden. Die Erweiterung erhält `undefined` (zum Auswählen/Eingeben/Bearbeiten) oder `false` (zum Bestätigen).\n\n```json\n{\"type\": \"extension_ui_response\", \"id\": \"uuid-3\", \"cancelled\": true}\n```\n\n## Fehlerbehandlung\n\nFehlgeschlagene Befehle geben eine Antwort mit `success: false` zurück:\n\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"set_model\",\n  \"success\": false,\n  \"error\": \"Model not found: invalid/model\"\n}\n```\n\nAnalysefehler:\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## Typen\n\nQuelldateien:\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 Befehls-/Antworttypen, Erweiterungs-UI-Anforderungs-/Antworttypen\n\n### Modell\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### Benutzernachricht\n\n```json\n{\n  \"role\": \"user\",\n  \"content\": \"Hello!\",\n  \"timestamp\": 1733234567890,\n  \"attachments\": []\n}\n```\n\nDas Feld `content` kann eine Zeichenfolge oder ein Array aus `TextContent`/`ImageContent` Blöcken sein.\n\n### AssistantMessage\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\nStoppgründe: `\"stop\"`, `\"length\"`, `\"toolUse\"`, `\"error\"`, `\"aborted\"`\n\n### ToolResultMessage\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` ist optional und meldet verschachtelte LLM-Arbeiten, die vom Tool ausgeführt werden. Wenn es vorhanden ist, trägt es zum Sitzungs-Token und den Gesamtkosten bei.\n\n### BashExecutionMessage\n\nErstellt durch den Befehl `bash` RPC (nicht durch LLM-Tool-Aufrufe):\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### Anhang\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## Beispiel: Basic Client (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## Beispiel: Interaktiver Client (Node.js)\n\nSiehe [`test/rpc-example.ts`](../test/rpc-example.ts) für ein vollständiges interaktives Beispiel oder [`src/modes/rpc/rpc-client.ts`](../src/modes/rpc/rpc-client.ts) für eine typisierte Client-Implementierung.\n\nEin vollständiges Beispiel für die Handhabung des Erweiterungs-UI-Protokolls finden Sie unter [`examples/rpc-extension-ui.ts`](../examples/rpc-extension-ui.ts), das mit der Erweiterung [`examples/extensions/rpc-demo.ts`](../examples/extensions/rpc-demo.ts) gepaart ist.\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 kann Ihnen bei der Verwendung von SDK helfen. Bitten Sie es, eine Integration für Ihren Anwendungsfall zu erstellen.\n\n\nDas SDK bietet programmgesteuerten Zugriff auf die Agentenfunktionen von pi. Verwenden Sie es, um Pi in andere Anwendungen einzubetten, benutzerdefinierte Schnittstellen zu erstellen oder in automatisierte Arbeitsabläufe zu integrieren.\n\n**Beispielhafte Anwendungsfälle:**\n- Erstellen Sie eine benutzerdefinierte Benutzeroberfläche (Web, Desktop, Mobilgerät)\n- Integrieren Sie Agentenfunktionen in bestehende Anwendungen\n- Erstellen Sie automatisierte Pipelines mit Agent Reasoning\n- Erstellen Sie benutzerdefinierte Tools, die Subagenten erzeugen\n- Testen Sie das Agentenverhalten programmgesteuert\n\nUnter [examples/sdk/](../examples/sdk/) finden Sie Arbeitsbeispiele von minimaler bis vollständiger Kontrolle.\n\n## Schnellstart\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## Installation\n\n```bash\nnpm install @earendil-works/pi-coding-agent\n```\n\nDas SDK ist im Hauptpaket enthalten. Keine separate Installation erforderlich.\n\n## Kernkonzepte\n\n### createAgentSession()\n\nDie Hauptfabrikfunktion für ein einzelnes `AgentSession`.\n\n`createAgentSession()` verwendet eine `ResourceLoader`, um Erweiterungen, Fähigkeiten, prompt templates, Themen und context files bereitzustellen. Wenn Sie keines bereitstellen, wird `DefaultResourceLoader` mit Standarderkennung verwendet.\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### AgentSession\n\nDie Sitzung verwaltet den Agentenlebenszyklus, den Nachrichtenverlauf, den Modellstatus, die Komprimierung und das Ereignis-Streaming.\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\nSitzungsersetzungs-APIs wie „Neue Sitzung“, „Fortsetzen“, „Fork“ und „Live-Import“ am `AgentSessionRuntime`, nicht am `AgentSession`.\n\n### createAgentSessionRuntime() und AgentSessionRuntime\n\nVerwenden Sie die Laufzeit API, wenn Sie die aktive Sitzung ersetzen und den cwd-gebundenen Laufzeitstatus neu erstellen müssen.\nDies ist dieselbe Ebene, die von den integrierten Modi „Interaktiv“, „Drucken“ und „RPC“ verwendet wird.\n\n`createAgentSessionRuntime()` benötigt eine Laufzeitfabrik plus das anfängliche CWD-/Sitzungsziel. Die Factory wird über prozessglobale feste Eingaben geschlossen, erstellt cwd-gebundene Dienste für den effektiven cwd neu, löst Sitzungsoptionen für diese Dienste auf und gibt ein vollständiges Laufzeitergebnis zurück.\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` besitzt den Ersatz der aktiven Laufzeit über:\n\n- `newSession()`\n- `switchSession()`\n- `fork()`\n- Flows über `fork(entryId, { position: \"at\" })` klonen\n- `importFromJsonl()`\n\nWichtiges Verhalten:\n\n- `runtime.session` ändert sich nach diesen Vorgängen\n- Veranstaltungsabonnements sind an eine bestimmte `AgentSession` gebunden, also abonnieren Sie sie nach dem Austausch erneut\n- Wenn Sie Erweiterungen verwenden, rufen Sie `runtime.session.bindExtensions(...)` für die neue Sitzung erneut auf\n- Erstellung gibt Diagnose am `runtime.diagnostics` zurück\n- Wenn das Erstellen oder Ersetzen zur Laufzeit fehlschlägt, löst die Methode aus und der Aufrufer entscheidet, wie damit umgegangen werden soll\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### Eingabeaufforderung und Nachrichtenwarteschlange\n\n`PromptOptions` steuert die Aufforderungserweiterung, das Warteschlangenverhalten beim Streaming und Aufforderungs-Preflight-Benachrichtigungen:\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` wird einmal pro `prompt()`-Aufruf aufgerufen:\n\n- `true` wenn die Eingabeaufforderung angenommen, in die Warteschlange gestellt oder sofort bearbeitet wurde\n- `false` wenn der sofortige Preflight vor der Annahme abgelehnt wird\n\nEs wird ausgelöst, bevor `prompt()` verrechnet wird. `prompt()` wird immer noch erst aufgelöst, nachdem der gesamte akzeptierte Lauf abgeschlossen ist, einschließlich Wiederholungsversuchen. Fehler nach der Abnahme werden über den normalen Ereignis- und Nachrichtenstrom gemeldet, nicht über `preflightResult(false)`.\n\nDie `prompt()`-Methode verarbeitet prompt templates, Erweiterungsbefehle und das Senden von Nachrichten:\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**Verhalten:**\n- **Erweiterungsbefehle** (z. B. `/mycommand`): Sofort ausführen, auch während des Streamings. Sie verwalten ihre eigene LLM-Interaktion über `pi.sendMessage()`.\n- **Dateibasiert prompt templates** (aus `.md` Dateien): Vor dem Senden oder Einreihen in die Warteschlange erweitert.\n- **Beim Streaming ohne `streamingBehavior`**: Löst einen Fehler aus. Verwenden Sie `steer()` oder `followUp()` direkt oder geben Sie die Option an.\n- **`preflightResult(true)`**: Bedeutet, dass die Eingabeaufforderung angenommen, in die Warteschlange gestellt oder sofort bearbeitet wurde.\n- **`preflightResult(false)`**: Bedeutet, dass der Preflight vor der Annahme abgelehnt wurde.\n\nFür explizite Warteschlangen während des Streamings:\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\nSowohl `steer()` als auch `followUp()` erweitern dateibasiert prompt templates, aber Fehler bei Erweiterungsbefehlen (Erweiterungsbefehle können nicht in die Warteschlange gestellt werden).\n\n### Agent und AgentState\n\nDie Klasse `Agent` (von `@earendil-works/pi-agent-core`) übernimmt die Kern-LLM-Interaktion. Greifen Sie über `session.agent` darauf zu.\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### Veranstaltungen\n\nAbonnieren Sie Ereignisse, um Streaming-Ausgaben und Lebenszyklusbenachrichtigungen zu erhalten.\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## Optionsreferenz\n\n### Verzeichnisse\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` wird von `DefaultResourceLoader` verwendet für:\n- Projekterweiterungen (`.pi/extensions/`)\n- Projektkompetenzen:\n  - `.pi/skills/`\n  - `.agents/skills/` in `cwd` und Vorgängerverzeichnissen (bis zum Git-Repo-Root oder Dateisystem-Root, wenn nicht in einem Repo)\n- Projektaufforderungen (`.pi/prompts/`)\n- Kontextdateien (`AGENTS.md` beim Aufsteigen von cwd)\n- Benennung des Sitzungsverzeichnisses\n\n`agentDir` wird von `DefaultResourceLoader` verwendet für:\n- Globale Erweiterungen (`extensions/`)\n- Globale Kompetenzen:\n  - `skills/` unter `agentDir` (zum Beispiel `~/.pi/agent/skills/`)\n  - `~/.agents/skills/`\n- Globale Eingabeaufforderungen (`prompts/`)\n- Globale Kontextdatei (`AGENTS.md`)\n- Einstellungen (`settings.json`)\n- Benutzerdefinierte Modelle (`models.json`)\n- Anmeldeinformationen (`auth.json`)\n- Sitzungen (`sessions/`)\n\nWenn Sie eine benutzerdefinierte `ResourceLoader` übergeben, steuern `cwd` und `agentDir` die Ressourcenerkennung nicht mehr. Sie beeinflussen weiterhin die Benennung der Sitzung und die Auflösung des Werkzeugwegs.\n\n### Modell\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\nWenn kein Modell angegeben ist:\n1. Versucht eine Wiederherstellung aus der Sitzung (falls fortgesetzt)\n2. Verwendet die Standardeinstellungen\n3. Fällt auf das erste verfügbare Modell zurück\n\nUm die Modellanalyse mit CLI abzugleichen, verwenden Sie die exportierten Resolver-Helfer:\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()` verwendet alle registrierten Modelle, sodass die erstmalige Einrichtung des Stils `--api-key` ein Modell auflösen kann, bevor eine gespeicherte Authentifizierung vorhanden ist. `resolveModelScopeWithDiagnostics()` stimmt mit der Semantik von `--models` und `enabledModels` überein und gibt Warnungen zurück, anstatt sie zu drucken.\n\n> Siehe [examples/sdk/02-custom-model.ts](../examples/sdk/02-custom-model.ts)\n\n### API Tasten und OAuth\n\nPriorität der Authentifizierungsauflösung (verwaltet von `ModelRuntime`):\n1. Laufzeitüberschreibungen (über `setRuntimeApiKey`, nicht persistent)\n2. Gespeicherte Anmeldeinformationen in `auth.json` (API keys oder OAuth Tokens)\n3. Umgebungsvariablen (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY` usw.)\n4. Fallback-Resolver (für benutzerdefinierte Anbieterschlüssel von `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()` und `removeRuntimeApiKey()` werden aufgelöst, nachdem der zwischengespeicherte/integrierte Katalog, die Zusammensetzung und der Verfügbarkeits-Snapshot des betroffenen Anbieters lokal konsistent sind. Sie warten nicht auf die Aktualität des Remote-Katalogs. Wenn Anmeldeinformationen festgeschrieben wurden, die lokale Synchronisierung jedoch fehlschlägt, werden sie mit dem exportierten `CredentialSynchronizationError` abgelehnt; Überprüfen Sie die Felder `providerId`, `operation`, `credential` und `cause`, anstatt die Anmeldeinformationsmutation blind zu wiederholen.\n\nÖffentliche Modell-/Authentifizierungsoperationen und `ModelRuntime.create({ signal })` akzeptieren optionale Abbruchsignale und sind unbegrenzt, wenn sie weggelassen werden. SDK Anwendungseigene Fristenrichtlinie für die Aktualität des Remote-Katalogs:\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\nEine fehlgeschlagene oder abgelaufene Netzwerkaktualisierung macht einen erfolgreichen Anmeldeinformationsvorgang nicht rückgängig. `refresh()` startet eine neue Anbietergeneration, damit nicht hinter einer älteren, blockierten Aktualisierung gewartet wird und veraltete Generationen danach nicht veröffentlicht werden können.\n\n> Siehe [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)\n\n### Systemaufforderung\n\nVerwenden Sie eine `ResourceLoader`, um die Systemaufforderung zu überschreiben:\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> Siehe [examples/sdk/03-custom-prompt.ts](../examples/sdk/03-custom-prompt.ts)\n\n### Werkzeuge\n\nGeben Sie an, welche integrierten Tools aktiviert werden sollen:\n\n- Integrierte Werkzeugnamen: `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`\n- Standardintegrierte Funktionen: `read`, `bash`, `edit`, `write`\n- `noTools: \"all\"` deaktiviert alle Tools\n- `noTools: \"builtin\"` deaktiviert standardmäßige integrierte Funktionen, während Erweiterungen und benutzerdefinierte Tools aktiviert bleiben\n- `excludeTools` deaktiviert bestimmte integrierte, erweiterte oder benutzerdefinierte Toolnamen, nachdem eine `tools`-Zulassungsliste angewendet wurde\n\nDas `edit`-Tool gibt `details.diff` für die TUI-Anzeige von Pi und `details.patch` als einheitlichen Standardpatch für SDK-Verbraucher zurück.\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#### Werkzeuge mit benutzerdefiniertem cwd\n\nWenn Sie ein benutzerdefiniertes `cwd` übergeben, erstellt `createAgentSession()` ausgewählte integrierte Tools für dieses 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> Siehe [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)\n\n### Benutzerdefinierte Werkzeuge\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\nVerwenden Sie `defineTool()` für eigenständige Definitionen und Arrays wie `customTools: [myTool]`. Inline `pi.registerTool({... })` leitet Parametertypen bereits korrekt ab.\n\nÜber `customTools` übergebene benutzerdefinierte Tools werden mit erweiterungsregistrierten Tools kombiniert. Extensions, das vom ResourceLoader geladen wird, kann Werkzeuge auch über `pi.registerTool()` registrieren.\n\nWenn Sie `tools` übergeben, geben Sie alle benutzerdefinierten oder Erweiterungstoolnamen an, die aktiviert werden sollen, zum Beispiel `tools: [\"read\", \"bash\", \"my_tool\"]`.\n\n> Siehe [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)\n\n### Extensions\n\nExtensions werden von der `ResourceLoader` geladen. `DefaultResourceLoader` erkennt Erweiterungen aus `~/.pi/agent/extensions/`, `.pi/extensions/` und den Erweiterungsquellen „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 kann Werkzeuge registrieren, Ereignisse abonnieren, Befehle hinzufügen und mehr. Siehe [extensions.md](extensions.md) für die vollständige API.\n\n**Benannte Inline-Erweiterungen:** Standardmäßig werden Inline-Factorys als `<inline:1>`, `<inline:2>` usw. in der Startliste Extensions angezeigt. Um stattdessen einen beschreibenden Namen anzuzeigen, schließen Sie die Factory ein:\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\nDies wird als `<inline:my-provider>` anstelle von `<inline:1>` angezeigt. Aus Gründen der Abwärtskompatibilität werden weiterhin reine Werksfunktionen akzeptiert.\n\n**Ereignisbus:** Extensions kann über `pi.events` kommunizieren. Übergeben Sie eine gemeinsame `eventBus` an `DefaultResourceLoader`, wenn Sie von außen etwas senden oder abhören müssen:\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> Siehe [examples/sdk/06-extensions.ts](../examples/sdk/06-extensions.ts) und [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> Siehe [examples/sdk/04-skills.ts](../examples/sdk/04-skills.ts)\n\n### Kontextdateien\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> Siehe [examples/sdk/07-context-files.ts](../examples/sdk/07-context-files.ts)\n\n### Slash-Befehle\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> Siehe [examples/sdk/08-prompt-templates.ts](../examples/sdk/08-prompt-templates.ts)\n\n### Sitzungsverwaltung\n\nSitzungen verwenden eine Baumstruktur mit `id`/`parentId`-Verknüpfung, die eine direkte Verzweigung ermöglicht.\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-Baum 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> Siehe [examples/sdk/11-sessions.ts](../examples/sdk/11-sessions.ts) und [Session Format](session-format.md)\n\n### Einstellungsverwaltung\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**Statische Fabriken:**\n- `SettingsManager.create(cwd?, agentDir?)` – Aus Dateien laden\n- `SettingsManager.inMemory(settings?)` – Keine Datei-E/A\n\n**Projektspezifische Einstellungen:**\n\nEinstellungen werden von zwei Speicherorten geladen und zusammengeführt:\n1. Global: `~/.pi/agent/settings.json`\n2. Projekt: `<cwd>/.pi/settings.json`\n\nProjekt überschreibt global. Verschachtelte Objekte führen Schlüssel zusammen. Setter ändern standardmäßig globale Einstellungen.\n\n**Persistenz und Fehlerbehandlungssemantik:**\n\n- Einstellungs-Getter/Setter sind für den In-Memory-Status synchron.\n- Setter stellen Persistenzschreibvorgänge asynchron in die Warteschlange.\n- Rufen Sie `await settingsManager.flush()` auf, wenn Sie eine Haltbarkeitsgrenze benötigen (z. B. vor dem Beenden des Prozesses oder vor der Bestätigung von Dateiinhalten in Tests).\n- `SettingsManager` druckt keine Einstellungen. E/A-Fehler. Verwenden Sie `settingsManager.drainErrors()` und melden Sie sie in Ihrer App-Ebene.\n\n> Siehe [examples/sdk/10-settings.ts](../examples/sdk/10-settings.ts)\n\n## ResourceLoader\n\nVerwenden Sie `DefaultResourceLoader`, um Erweiterungen, Fähigkeiten, Eingabeaufforderungen, Themen und context files zu entdecken.\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## Rückgabewert\n\n`createAgentSession()` gibt Folgendes zurück:\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## Vollständiges Beispiel\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## Laufmodi\n\nDie SDK exportiert Dienstprogramme im Ausführungsmodus zum Erstellen benutzerdefinierter Schnittstellen zusätzlich zu `createAgentSession()`:\n\n### Interaktiver Modus\n\nVollständiger TUI interaktiver Modus mit Editor, Chat-Verlauf und allen integrierten Befehlen:\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\nSingle-Shot-Modus: Eingabeaufforderungen senden, Ergebnis ausgeben, beenden:\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\nJSON-RPC Modus für die Teilprozessintegration:\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\nSiehe [RPC documentation](rpc.md) für das JSON-Protokoll.\n\n## RPC Modusalternative\n\nFür eine unterprozessbasierte Integration ohne Erstellung mit SDK verwenden Sie CLI direkt:\n\n```bash\npi --mode rpc --no-session\n```\n\nSiehe [RPC documentation](rpc.md) für das JSON-Protokoll.\n\nDie SDK wird bevorzugt, wenn:\n- Sie wollen Typsicherheit\n- Sie befinden sich im selben Node.js Prozess\n- Sie benötigen direkten Zugriff auf den Agentenstatus\n- Sie möchten Tools/Erweiterungen programmgesteuert anpassen\n\nDer Modus RPC wird bevorzugt, wenn:\n- Sie integrieren eine andere Sprache\n- Sie möchten eine Prozessisolation\n- Sie erstellen einen sprachunabhängigen Client\n\n## Exporte\n\nDer Haupteinstiegspunkt exportiert:\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\nInformationen zu Erweiterungstypen finden Sie unter [extensions.md](extensions.md) für die vollständige API.","sourceFile":"sdk.md"},"security":{"title":"Sicherheit","markdown":"Pi ist ein lokaler Codierungsagent. Es wird mit den Berechtigungen des Benutzerkontos ausgeführt, das es startet, und es behandelt Dateien, auf die dieser Benutzer schreiben kann, als innerhalb derselben lokalen Vertrauensgrenze.\n\n## Projektvertrauen\n\nDie Projektvertrauenswürdigkeit steuert, ob Pi projektlokale Einstellungen, Ressourcen, Pakete und Erweiterungen lädt. Es ist kein sandbox und es schränkt nicht ein, was das Modell von Tools verlangen kann, nachdem Sie mit der Arbeit in einem Verzeichnis begonnen haben.\n\nPi betrachtet ein Projekt als Ressourcen, die Vertrauen erfordern, wenn es diese im aktuellen Arbeitsverzeichnis findet:\n\n- `.pi/settings.json`\n- `.pi/extensions`, `.pi/skills`, `.pi/prompts` oder `.pi/themes`\n- `.pi/SYSTEM.md` oder `.pi/APPEND_SYSTEM.md`\n- Projekt `.agents/skills` im aktuellen Verzeichnis oder einem Vorgängerverzeichnis\n\nEin leeres `.pi`-Verzeichnis zählt nicht als Projektressource, die Vertrauen erfordert.\n\nWenn eine interaktive Sitzung in einem Projekt mit Ressourcen beginnt, die Vertrauen und keine gespeicherte Entscheidung für das aktuelle Verzeichnis oder ein übergeordnetes Verzeichnis erfordern, folgt pi `defaultProjectTrust` aus den globalen Einstellungen. Der Standardwert ist `\"ask\"` und fragt, ob dem Projekt vertraut werden soll, wenn die Benutzeroberfläche verfügbar ist. Gespeicherte Entscheidungen werden nach kanonischem Verzeichnis in `~/.pi/agent/trust.json` gespeichert, und die nächstgelegene gespeicherte Entscheidung im aktuellen oder übergeordneten Pfad gilt vor dem globalen Standard.\n\nDurch das Vertrauen in ein Projekt kann Pi Projektressourcen laden, die Vertrauen erfordern, einschließlich:\n\n- `.pi/settings.json`\n- `.pi` Ressourcen wie Erweiterungen, Fähigkeiten, prompt templates, Themen und Systemaufforderungsdateien\n- Fehlende Projektpakete, die über die Projekteinstellungen konfiguriert wurden\n- Projektlokale Erweiterungen und projektpaketverwaltete Erweiterungen\n\nSinkendes Vertrauen lässt geschützte Ressourcen überspringen. Kontextdateien wie `AGENTS.override.md`, `AGENTS.md` und `CLAUDE.md` werden unabhängig von der Projektvertrauensstellung geladen, es sei denn, das Laden von Kontexten ist deaktiviert. Bevor die Vertrauensstellung aufgelöst wird, lädt pi nur context files, Benutzer-/globale Erweiterungen und CLI `-e`-Erweiterungen. Benutzer-/globale und CLI-Erweiterungen können das `project_trust`-Ereignis verarbeiten; Die erste Erweiterung, die eine Ja/Nein-Entscheidung zurückgibt, besitzt die Entscheidung.\n\nIn den nicht interaktiven Modi (`-p`, `--mode json` und `--mode rpc`) wird keine Vertrauensaufforderung angezeigt. Ohne eine anwendbare gespeicherte Vertrauensentscheidung ignorieren `defaultProjectTrust: \"ask\"` und `\"never\"` solche Ressourcen, während `\"always\"` ihnen vertraut. Verwenden Sie `--approve`/`-a` oder `--no-approve`/`-na`, um die Projektvertrauenswürdigkeit für einen Lauf zu überschreiben.\n\n## Keine integrierte Sandbox\n\nPi enthält kein integriertes sandbox. Integrierte Tools können mit den Berechtigungen des Pi-Prozesses Dateien lesen, schreiben, bearbeiten und Shell-Befehle ausführen. Extensions sind TypeScript Module, die mit den gleichen Berechtigungen laufen. Paketinstallationen, Shell-Befehle, Sprachserver, Testbefehle und andere Entwicklertools verhalten sich wie normale lokale Prozesse.\n\nDas ist Absicht. Pi ist für den Betrieb mit lokalen Quellbäumen, den Aufruf von Projekt-Toolchains und die Integration in die vorhandene Entwicklungsumgebung des Benutzers konzipiert. Ein teilweise prozessinternes sandbox könnte leicht als Sicherheitsgrenze missverstanden werden, obwohl es dennoch von der Host-Shell, dem Dateisystem, den Paketmanagern, den Anmeldeinformationen und dem Erweiterungscode abhängt. Eine echte Isolation muss vom Betriebssystem oder einer Virtualisierungs-/Containergrenze ausgehen.\n\nProjektvertrauen ist nur ein Wächter zum Laden von Eingaben. Es verhindert, dass ein Repository stillschweigend die Einstellungen oder Erweiterungen von pi ändert, bevor Sie es genehmigen. Es macht nicht vertrauenswürdigen Code, nicht vertrauenswürdige Eingabeaufforderungen oder nicht vertrauenswürdige Modellausgaben nicht sicher. Eine sofortige Injektion aus Repository-Dateien, Kommentaren, Dokumentation, context files oder Build-Ausgaben ist ein erwartetes lokales Agentenrisiko und kann von pi nicht zuverlässig verhindert werden.\n\n## Ausführen nicht vertrauenswürdiger oder nicht überwachter Arbeiten\n\nFür nicht vertrauenswürdige Repositorys, generierten Code, den Sie nicht genau überwachen möchten, oder unbeaufsichtigte Automatisierung führen Sie pi in einer geschlossenen Umgebung aus. Verwenden Sie einen Container, eine VM, eine Mikro-VM, eine Remote-sandbox oder eine richtliniengesteuerte sandbox mit nur den Dateien und Anmeldeinformationen, die für die Aufgabe erforderlich sind.\n\nHäufige Muster sind in [Containerization](containerization.md) dokumentiert:\n\n- Führen Sie den gesamten `pi`-Prozess in einem Container/sandbox aus\n- Führen Sie Host-Pi aus, während Sie die Ausführung des integrierten Tools in eine Gondolin-Mikro-VM weiterleiten\n- Mounten Sie nur die Arbeitsbereichspfade, auf die der Agent zugreifen sollte\n- Vermeiden Sie das Mounten von Host `~/.pi/agent`, es sei denn, der Container soll auf Hostsitzungen, Einstellungen und Anmeldeinformationen zugreifen\n- Bestehen Sie die mindestens erforderlichen API keys oder verwenden Sie kurzlebige Anmeldeinformationen\n- Beschränken Sie den Netzwerkzugriff, wenn die Aufgabe ihn nicht benötigt\n- Überprüfen Sie Unterschiede und Ausgaben, bevor Sie die Ergebnisse zurück auf vertrauenswürdige Systeme kopieren\n\nWenn Sie einen Host-Arbeitsbereich mit Lese-/Schreibzugriff binden, können Schreibvorgänge aus dem Container oder der VM weiterhin Hostdateien ändern. Verwenden Sie schreibgeschützte Mounts oder kopieren Sie Dateien in und aus sandbox, wenn Sie einen stärkeren Schutz vor unbeabsichtigten Schreibvorgängen benötigen.\n\n## Sicherheitsprobleme melden\n\nUm ein Sicherheitsproblem zu melden, folgen Sie dem Repository [Security Policy](https://github.com/earendil-works/pi-mono/blob/main/SECURITY.md). Öffnen Sie keine öffentliche Ausgabe für sicherheitsrelevante Berichte.\n\nDas erwartete Verhalten lokaler Agenten, das Fehlen eines integrierten sandbox, die sofortige Einschleusung von nicht vertrauenswürdigen Inhalten und das Verhalten von vom Benutzer installierten Erweiterungen oder Fähigkeiten liegen im Allgemeinen außerhalb der Sicherheitsgrenzen, es sei denn, der Bericht weist auf eine echte Umgehung der Berechtigungsgrenzen hin oder zeigt, wie pi Zugriff gewährt, den der lokale Benutzer noch nicht hatte.","sourceFile":"security.md"},"session-format":{"title":"Sitzungsdateiformat","markdown":"Sitzungen werden als JSONL (JSON Zeilen) Dateien gespeichert. Jede Zeile ist ein JSON-Objekt mit einem `type`-Feld. Sitzungseinträge bilden über `id`/`parentId`-Felder eine Baumstruktur und ermöglichen eine direkte Verzweigung, ohne dass neue Dateien erstellt werden müssen.\n\n## Dateispeicherort\n\n```\n~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl\n```\n\nDabei ist `<path>` das Arbeitsverzeichnis, wobei `/` durch `-` ersetzt wird.\n\n## Sitzungen löschen\n\nSitzungen können entfernt werden, indem ihre `.jsonl`-Dateien unter `~/.pi/agent/sessions/` gelöscht werden.\n\nPi unterstützt auch das interaktive Löschen von Sitzungen aus `/resume` (wählen Sie eine Sitzung aus und drücken Sie `Ctrl+D`, dann bestätigen). Wenn verfügbar, verwendet Pi `trash` CLI, um ein dauerhaftes Löschen zu vermeiden.\n\n## Sitzungsversion\n\nSitzungen haben ein Versionsfeld in der Kopfzeile:\n\n- **Version 1**: Lineare Eingabesequenz (alt, beim Laden automatisch migriert)\n- **Version 2**: Baumstruktur mit `id`/`parentId`-Verknüpfung\n- **Version 3**: Rolle `hookMessage` in `custom` umbenannt (Vereinheitlichung der Erweiterungen)\n\nBestehende Sitzungen werden beim Laden automatisch auf die aktuelle Version (v3) migriert.\n\n## Quelldateien\n\nQuelle am GitHub ([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) – Sitzungseintragstypen und 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) – Erweiterte Nachrichtentypen (BashExecutionMessage, CustomMessage usw.)\n- [`packages/ai/src/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/types.ts) – Basisnachrichtentypen (UserMessage, AssistantMessage, ToolResultMessage)\n- [`packages/agent/src/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/agent/src/types.ts) – AgentMessage-Union-Typ\n\nÜberprüfen Sie für TypeScript-Definitionen in Ihrem Projekt `node_modules/@earendil-works/pi-coding-agent/dist/` und `node_modules/@earendil-works/pi-ai/dist/`.\n\n## Nachrichtentypen\n\nSitzungseinträge enthalten `AgentMessage` Objekte. Das Verständnis dieser Typen ist für das Parsen von Sitzungen und das Schreiben von Erweiterungen unerlässlich.\n\n### Inhaltsblöcke\n\nNachrichten enthalten Arrays typisierter Inhaltsblöcke:\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### Basisnachrichtentypen (von 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\nDer exportierte pi-ai-Typ `StopReason` enthält auch `\"pending\"`, dieser Wert ist jedoch für Teilnachrichten in Streaming-Ereignissen reserviert. Terminal-Nachrichten `done`/`error` ersetzen es durch einen Abschlussgrund, bevor pi die Assistentennachricht beibehält, sodass `\"pending\"` niemals in Sitzung JSONL erscheinen sollte.\n\n### Erweiterte Nachrichtentypen (von 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### AgentMessage Union\n\n```typescript\ntype AgentMessage =\n  | UserMessage\n  | AssistantMessage\n  | ToolResultMessage\n  | BashExecutionMessage\n  | CustomMessage\n  | BranchSummaryMessage\n  | CompactionSummaryMessage;\n```\n\n## Einstiegsbasis\n\nAlle Einträge (außer `SessionHeader`) erweitern `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## Eintragstypen\n\n### SessionHeader\n\nErste Zeile der Datei. Nur Metadaten, kein Teil des Baums (kein `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\nFür Sitzungen mit einem Elternteil (erstellt über `/fork`, `/clone` oder `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### SessionMessageEntry\n\nEine Nachricht im Gespräch. Das Feld `message` enthält eine `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\nWird ausgegeben, wenn der Benutzer mitten in der Sitzung das Modell wechselt.\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\nWird ausgegeben, wenn der Benutzer die Denk-/Argumentationsebene ändert.\n\n```json\n{\"type\":\"thinking_level_change\",\"id\":\"e5f6g7h8\",\"parentId\":\"d4e5f6g7\",\"timestamp\":\"2024-12-03T14:06:00.000Z\",\"thinkingLevel\":\"high\"}\n```\n\n### Verdichtungseintrag\n\nWird erstellt, wenn der Kontext komprimiert wird. Speichert eine Zusammenfassung früherer Nachrichten.\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\nNeuere, durch Kabelbäume generierte Verdichtungen betten den beibehaltenen Post-Verdichtungskontext direkt in den Eintrag ein, statt `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\nOptionale Felder:\n- `usage`: LLM-Nutzung durch Generierung der Zusammenfassung; im Sitzungs-Token und in den Gesamtkosten enthalten\n- `retainedTail`: Materialisiert `AgentMessage[]` bleibt nach der Verdichtung erhalten. Dies ist nur aus Gründen der Abwärtskompatibilität mit älteren Sitzungen optional. Neuere, durch Kabelbäume generierte Komprimierungen enthalten es, sodass wir den Kontext von diesem Prüfpunkt aus neu erstellen können, ohne ältere Einträge vor dem Komprimierungseintrag zu durchlaufen.\n- `details`: Implementierungsspezifische Daten (z. B. `{ readFiles: string[], modifiedFiles: string[] }` für Standard oder benutzerdefinierte Daten für Erweiterungen)\n- `fromHook`: `true`, wenn durch eine Erweiterung generiert, `false`/`undefined`, wenn Pi-generiert (Legacy-Feldname)\n- `firstKeptEntryId`: für Kompatibilität mit dem alten Eingabeformat.\n\n### BranchSummaryEntry\n\nWird beim Zweigwechsel über `/tree` mit einer LLM-generierten Zusammenfassung des linken Zweigs bis zum gemeinsamen Vorfahren erstellt. Erfasst den Kontext des verlassenen Pfads.\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\nOptionale Felder:\n- `usage`: LLM-Nutzung durch Generierung der Zusammenfassung; im Sitzungs-Token und in den Gesamtkosten enthalten\n- `details`: Dateiverfolgungsdaten (`{ readFiles: string[], modifiedFiles: string[] }`) für Standard oder benutzerdefinierte Daten für Erweiterungen\n- `fromHook`: `true`, wenn durch eine Erweiterung generiert, `false`/`undefined`, wenn Pi-generiert (Legacy-Feldname)\n\n### Benutzerdefinierter Eintrag\n\nPersistenz des Erweiterungsstatus. Nimmt NICHT am LLM-Kontext teil.\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\nVerwenden Sie `customType`, um die Einträge Ihrer Erweiterung beim Neuladen zu identifizieren. Der interaktive Modus kann benutzerdefinierte Einträge über `pi.registerEntryRenderer(customType, renderer)` rendern, sie nehmen jedoch immer noch nicht am LLM-Kontext teil.\n\n### CustomMessageEntry\n\nDurch Erweiterungen eingefügte Nachrichten, die am LLM-Kontext beteiligt sind.\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\nFelder:\n- `content`: String oder `(TextContent | ImageContent)[]` (wie UserMessage)\n- `display`: `true` = in TUI mit eindeutigem Stil anzeigen, `false` = ausgeblendet\n- `details`: Optionale erweiterungsspezifische Metadaten (nicht an LLM gesendet)\n\n### Etiketteneintrag\n\nBenutzerdefiniertes Lesezeichen/Markierung für einen Eintrag.\n\n```json\n{\"type\":\"label\",\"id\":\"j0k1l2m3\",\"parentId\":\"i9j0k1l2\",\"timestamp\":\"2024-12-03T14:30:00.000Z\",\"targetId\":\"a1b2c3d4\",\"label\":\"checkpoint-1\"}\n```\n\nSetzen Sie `label` auf `undefined`, um ein Etikett zu löschen.\n\n### SessionInfoEntry\n\nSitzungsmetadaten (z. B. benutzerdefinierter Anzeigename). Wird über `/name`, `--name` / `-n` oder `pi.setSessionName()` in Erweiterungen eingestellt.\n\n```json\n{\"type\":\"session_info\",\"id\":\"k1l2m3n4\",\"parentId\":\"j0k1l2m3\",\"timestamp\":\"2024-12-03T14:35:00.000Z\",\"name\":\"Refactor auth module\"}\n```\n\nDer Sitzungsname wird in der Sitzungsauswahl (`/resume`) anstelle der ersten Nachricht angezeigt, wenn diese festgelegt ist.\n\n## Baumstruktur\n\nEinträge bilden einen Baum:\n- Erster Eintrag hat `parentId: null`\n- Jeder nachfolgende Eintrag verweist über `parentId` auf seinen übergeordneten Eintrag.\n- Durch die Verzweigung werden neue untergeordnete Elemente aus einem früheren Eintrag erstellt\n- Das „Blatt“ ist die aktuelle Position im Baum\n\n```\n[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf\n                                                            │\n                                                            └─ [branch_summary] ─── [user msg] ← alternate branch\n```\n\n## Kontextbildung\n\n`buildContextEntries()` geht vom aktuellen Blatt zur Wurzel und erstellt die aktive Eintragsliste unter Berücksichtigung der Komprimierung:\n\n1. Sammelt alle Einträge auf dem Pfad\n2. Wenn sich eine `CompactionEntry` auf dem Pfad befindet:\n   - Beinhaltet zuerst den Komprimierungseintrag\n   - Wenn `retainedTail` vorhanden ist, fungiert es als eigenständiger Prüfpunkt und Einträge nach der Komprimierung werden einbezogen\n   - Ansonsten sind Einträge von `firstKeptEntryId` bis zur Verdichtung enthalten\n   - Dann werden Einträge nach der Komprimierung einbezogen\n3. Behält Nicht-Nachrichteneinträge im ausgewählten Bereich bei, sodass sie im interaktiven Modus gerendert werden können\n\n`buildSessionContext()` baut auf dieser Eintragsliste auf, um die Nachrichtenliste für das LLM zu erstellen:\n\n1. Extrahiert aktuelle Modell- und Denkebeneneinstellungen aus dem vollständigen Pfad\n2. Konvertiert ausgewählte Einträge in Nachrichten:\n   - `message` -> gespeichert `AgentMessage`\n   - `compaction` -> `compactionSummary` plus `retainedTail`, falls vorhanden\n   - `branch_summary` -> `branchSummary`\n   - `custom_message` -> `CustomMessage`\n   - `custom` -> keine Kontextmeldung\n\nDadurch wirken neuere Verdichtungen wie eigenständige Kontrollpunkte. `retainedTail` ist nur optional, damit ältere Sitzungen, die nur `firstKeptEntryId` speichern, weiterhin korrekt geladen werden.\n\n## Parsing-Beispiel\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## SessionManager API\n\nSchlüsselmethoden für die programmgesteuerte Arbeit mit Sitzungen.\n\n### Statische Erstellungsmethoden\n- `SessionManager.create(cwd, sessionDir?)` – Neue Sitzung\n- `SessionManager.open(path, sessionDir?)` – Vorhandene Sitzungsdatei öffnen\n- `SessionManager.continueRecent(cwd, sessionDir?)` – Mit der neuesten Version fortfahren oder eine neue erstellen\n- `SessionManager.inMemory(cwd?)` – Keine Dateipersistenz\n- `SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?)` – Fork-Sitzung von einem anderen Projekt\n\n### Statische Auflistungsmethoden\n- `SessionManager.list(cwd, sessionDir?, onProgress?)` – Sitzungen für ein Verzeichnis auflisten\n- `SessionManager.listAll(onProgress?)` – Alle Sitzungen in allen Projekten auflisten\n\n### Instanzmethoden – Sitzungsverwaltung\n- `newSession(options?)` – Eine neue Sitzung starten (Optionen: `{ parentSession?: string }`)\n- `setSessionFile(path)` – Wechseln Sie zu einer anderen Sitzungsdatei\n- `createBranchedSession(leafId)` – Zweig in neue Sitzungsdatei extrahieren\n\n### Instanzmethoden – Anhängen (alle Rückgabeeintrags-ID)\n- `appendMessage(message)` – Nachricht hinzufügen\n- `appendThinkingLevelChange(level)` – Denkänderungen aufzeichnen\n- `appendModelChange(provider, modelId)` – Modellwechsel aufzeichnen\n- `appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?)` – Komprimierung hinzufügen\n- `appendCustomEntry(customType, data?)` – Erweiterungsstatus (nicht im Kontext)\n- `appendSessionInfo(name)` – Sitzungsanzeigenamen festlegen\n- `appendCustomMessageEntry(customType, content, display, details?)` – Erweiterungsnachricht (im Kontext)\n- `appendLabelChange(targetId, label)` – Beschriftung festlegen/löschen\n\n### Instanzmethoden – Baumnavigation\n- `getLeafId()` – Aktuelle Position\n- `getLeafEntry()` – Aktuellen Blatteintrag abrufen\n- `getEntry(id)` – Erhalten Sie Zutritt per ID\n- `getBranch(fromId?)` – Gehen Sie vom Eingang zur Wurzel\n- `getTree()` – Vollständige Baumstruktur erhalten\n- `getChildren(parentId)` – Holen Sie sich direkte Kinder\n- `getLabel(id)` – Label für den Eintrag abrufen\n- `branch(entryId)` – Blatt zum früheren Eintrag verschieben\n- `resetLeaf()` – Blatt auf Null zurücksetzen (vor irgendwelchen Einträgen)\n- `branchWithSummary(entryId, summary, details?, fromHook?)` – Zweig mit Kontextzusammenfassung\n\n### Instanzmethoden – Kontext und Informationen\n- `buildContextEntries()` – Aktive Zweigeinträge mit angewendeter Komprimierung abrufen\n- `buildSessionContext()` – Erhalten Sie Nachrichten, Denkebene und Modell für LLM\n- `getEntries()` – Alle Einträge (außer Header)\n- `getHeader()` – Sitzungsheader-Metadaten\n- `getSessionName()` – Anzeigenamen aus dem letzten session_info-Eintrag abrufen\n- `getCwd()` – Arbeitsverzeichnis\n- `getSessionDir()` – Sitzungsspeicherverzeichnis\n- `getSessionId()` – Sitzungs-UUID\n- `getSessionFile()` – Sitzungsdateipfad (undefiniert für In-Memory)\n- `isPersisted()` – Ob die Sitzung auf der Festplatte gespeichert wird","sourceFile":"session-format.md"},"sessions":{"title":"Sitzungen","markdown":"Pi speichert Gespräche als Sitzungen, sodass Sie mit der Arbeit fortfahren, von früheren Runden abzweigen und frühere Pfade erneut besuchen können.\n\n## Sitzungsspeicher\n\nSitzungen werden automatisch unter `~/.pi/agent/sessions/` gespeichert, organisiert nach Arbeitsverzeichnis. Jede Sitzung ist eine JSONL-Datei mit einer Baumstruktur.\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\nVerwenden Sie `/session` im interaktiven Modus, um die aktuelle Sitzungsdatei, Sitzungs-ID, Nachrichtenanzahl, Token und Kosten anzuzeigen.\n\nInformationen zum Dateiformat JSONL und zum SessionManager API finden Sie unter [Session Format](session-format.md).\n\n## Sitzungsbefehle\n\n| Befehl | Beschreibung |\n|---------|-------------|\n| `/resume` | Durchsuchen Sie frühere Sitzungen und wählen Sie sie aus |\n| `/new` | Starten Sie eine neue Sitzung |\n| `/name <name>` | Legen Sie den Anzeigenamen der aktuellen Sitzung fest |\n| `/session` | Sitzungsinformationen anzeigen |\n| `/tree` | Navigieren Sie durch die aktuelle session tree |\n| `/fork` | Erstellen Sie eine neue Sitzung aus einer vorherigen Benutzernachricht |\n| `/clone` | Duplizieren Sie den aktuell aktiven Zweig in eine neue Sitzung |\n| `/compact [prompt]` | Älteren Kontext zusammenfassen; siehe [Compaction](compaction.md) |\n| `/export [file]` | Sitzung nach HTML exportieren |\n| `/share` | Als privates GitHub Gist mit gemeinsam nutzbarem HTML-Link hochladen |\n\n## Fortsetzen und Löschen von Sitzungen\n\n`/resume` öffnet eine interaktive Sitzungsauswahl für das aktuelle Projekt. `pi -r` öffnet den gleichen Picker beim Start.\n\nIm Picker können Sie:\n\n- Suche durch Eingabe\n- Schalten Sie die Pfadanzeige mit Strg+P um\n- Schalten Sie den Sortiermodus mit Strg+S um\n- Filtern Sie mit Strg+N nach benannten Sitzungen\n- mit Strg+R umbenennen\n- Mit Strg+D löschen, dann bestätigen\n\nWenn verfügbar, verwendet Pi `trash` CLI zum Löschen, anstatt Dateien dauerhaft zu entfernen.\n\n## Benennung von Sitzungen\n\nVerwenden Sie `/name <name>`, um einen für Menschen lesbaren Sitzungsnamen festzulegen:\n\n```text\n/name Refactor auth module\n```\n\nLegen Sie den Namen beim Start mit `--name` oder `-n` fest:\n\n```bash\npi --name \"Refactor auth module\"\npi --name \"CI audit\" -p \"Review this build failure\"\n```\n\nBenannte Sitzungen sind in `/resume` und `pi -r` leichter zu finden.\n\n## Verzweigung mit `/tree`\n\nSitzungen werden als Bäume gespeichert. Jeder Eintrag hat eine `id` und `parentId`, und die aktuelle Position ist der Gangflügel. Mit `/tree` können Sie zu jedem vorherigen Punkt springen und von dort aus fortfahren, ohne eine neue Datei zu erstellen.\n\n<p align=\"center\"><img src=\"images/tree-view.png\" alt=\"Tree View\" width=\"600\"></p>\n\nBeispielform:\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### Baumkontrollen\n\n| Schlüssel | Aktion |\n|-----|--------|\n| ↑/↓ | Navigieren Sie durch sichtbare Einträge |\n| ←/→ | Seite hoch/runter |\n| Strg+←/Strg+→ oder Alt+←/Alt+→ | Falten/entfalten oder zwischen Astsegmenten springen |\n| Umschalt+L | Legen Sie eine Beschriftung für den ausgewählten Eintrag fest oder löschen Sie sie |\n| Umschalt+T | Label-Zeitstempel umschalten |\n| Eingeben | Eintrag auswählen |\n| Escape/Strg+C | Stornieren |\n| Strg+O | Zyklusfiltermodus |\n\nDie Filtermodi sind: Standard, keine Tools, nur Benutzer, nur beschriftet und alle. Konfigurieren Sie die Standardeinstellung mit `treeFilterMode` in [Settings](settings.md).\n\n### Auswahlverhalten\n\nAuswählen eines Benutzers oder einer benutzerdefinierten Nachricht:\n\n1. Verschiebt das Blatt zum übergeordneten Element der ausgewählten Nachricht.\n2. Platziert den ausgewählten Nachrichtentext im Editor.\n3. Ermöglicht das Bearbeiten und erneute Senden sowie das Erstellen eines neuen Zweigs.\n\nAuswählen eines Assistenten, Werkzeugs, einer Verdichtung oder eines anderen Nichtbenutzereintrags:\n\n1. Verschiebt das Blatt zu diesem Eintrag.\n2. Lässt den Editor leer.\n3. Ermöglicht Ihnen, von diesem Punkt aus fortzufahren.\n\nDurch Auswahl der Root-Benutzernachricht wird das Blatt auf eine leere Konversation zurückgesetzt und die ursprüngliche Eingabeaufforderung im Editor platziert.\n\n## `/tree`, `/fork` und `/clone`\n\n| Besonderheit | `/tree` | `/fork` | `/clone` |\n|---------|---------|---------|----------|\n| Ausgabe | Gleiche Sitzungsdatei | Neue Sitzungsdatei | Neue Sitzungsdatei |\n| Sicht | Vollständiger Baum | Auswahl für Benutzernachrichten | Derzeitiger aktiver Zweig |\n| Typische Verwendung | Entdecken Sie Alternativen vor Ort | Starten Sie eine neue Sitzung an einer früheren Eingabeaufforderung | Duplizieren Sie die aktuelle Arbeit, bevor Sie fortfahren |\n| Zusammenfassung | Optionale Zweigzusammenfassung | Keiner | Keiner |\n\nVerwenden Sie `/tree`, wenn Sie Alternativen zusammenhalten möchten. Verwenden Sie `/fork` oder `/clone`, wenn Sie eine separate Sitzungsdatei wünschen.\n\n## Branchenzusammenfassungen\n\nWenn `/tree` von einem Zweig zum anderen wechselt, kann pi den verlassenen Zweig zusammenfassen und diese Zusammenfassung an der neuen Position anhängen. Dadurch bleibt wichtiger Kontext des von Ihnen verlassenen Pfads erhalten, ohne dass der gesamte Zweig erneut abgespielt werden muss.\n\nWenn Sie dazu aufgefordert werden, wählen Sie eine der folgenden Optionen:\n\n1. keine Zusammenfassung\n2. Zusammenfassen mit der Standardaufforderung\n3. Zusammenfassen mit benutzerdefinierten Fokusanweisungen\n\nSiehe [Compaction](compaction.md) für branch summarization Einbauten und Verlängerungshaken.\n\n## Sitzungsformat\n\nSitzungsdateien sind JSONL und enthalten Nachrichteneinträge, Modelländerungen, Änderungen auf Denkebene, Beschriftungen, Verdichtungen, Zweigzusammenfassungen und Erweiterungseinträge.\n\nInformationen zu Parsern, Erweiterungen, SDK-Nutzung und dem vollständigen SessionManager API finden Sie unter [Session Format](session-format.md).","sourceFile":"sessions.md"},"settings":{"title":"Einstellungen","markdown":"Pi verwendet JSON Einstellungsdateien, wobei die Projekteinstellungen die globalen Einstellungen überschreiben.\n\n| Standort | Umfang |\n|----------|-------|\n| `~/.pi/agent/settings.json` | Global (alle Projekte) |\n| `.pi/settings.json` | Projekt (aktuelles Verzeichnis) |\n\nDirekt bearbeiten oder `/settings` für allgemeine Optionen verwenden.\n\n## Projektvertrauen\n\nBeim interaktiven Start fragt pi nach, bevor es einem Projektordner vertraut, der projektlokale Einstellungen, Ressourcen oder Projekt `.agents/skills` enthält und keine gespeicherte Entscheidung für den Ordner oder einen übergeordneten Ordner in `~/.pi/agent/trust.json` hat. Durch das Vertrauen in ein Projekt kann Pi `.pi/settings.json`- und `.pi`-Ressourcen laden, fehlende Projektpakete installieren und Projekterweiterungen ausführen.\n\nIn den nicht interaktiven Modi (`-p`, `--mode json` und `--mode rpc`) wird keine Vertrauensaufforderung angezeigt. Ohne eine anwendbare gespeicherte Vertrauensentscheidung verwenden sie `defaultProjectTrust` aus den globalen Einstellungen: `ask` (Standard) und `never` ignorieren diese Projektressourcen, während `always` ihnen vertraut. Übergeben Sie `--approve`/`-a` oder `--no-approve`/`-na`, um die Projektvertrauenswürdigkeit für einen Lauf zu überschreiben.\n\nWenn keine Erweiterung oder gespeicherte Entscheidung gilt, steuert `defaultProjectTrust` das Fallback-Verhalten. Stellen Sie es auf `\"ask\"`, `\"always\"` oder `\"never\"` in `~/.pi/agent/settings.json` ein oder ändern Sie es mit `/settings`.\n\n`pi config`- ​​und Paketbefehle verwenden denselben Projekt-Vertrauensfluss, mit der Ausnahme, dass `pi update` nie dazu auffordert. Übergeben Sie `--approve`, um projektlokalen Einstellungen für einen Befehl zu vertrauen, oder `--no-approve`, um sie zu ignorieren.\n\nVerwenden Sie `/trust` im interaktiven Modus, um eine Projektvertrauensentscheidung für zukünftige Sitzungen zu speichern, einschließlich der Vertrauenswürdigkeit für den unmittelbar übergeordneten Ordner. Es schreibt nur `~/.pi/agent/trust.json`; Die aktuelle Sitzung wird nicht neu geladen. Starten Sie daher pi neu, damit die Änderungen wirksam werden.\n\n## Alle Einstellungen\n\n### Modell & Denken\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `defaultProvider` | Zeichenfolge | - | Standardanbieter (z. B. `\"anthropic\"`, `\"openai\"`) |\n| `defaultModel` | Zeichenfolge | - | Standardmodell-ID |\n| `defaultThinkingLevel` | Zeichenfolge | - | `\"off\"`, `\"minimal\"`, `\"low\"`, `\"medium\"`, `\"high\"`, `\"xhigh\"`, `\"max\"` |\n| `hideThinkingBlock` | Boolescher Wert | `false` | Verstecken Sie Denkblockaden in der Ausgabe |\n| `showCacheMissNotices` | Boolescher Wert | `false` | Zeigen Sie Transkripthinweise für erhebliche Fehler im Prompt-Cache an |\n| `thinkingBudgets` | Objekt | - | Benutzerdefinierte Token-Budgets pro Denkebene |\n\n#### denkenBudgets\n\n```json\n{\n  \"thinkingBudgets\": {\n    \"minimal\": 1024,\n    \"low\": 4096,\n    \"medium\": 10240,\n    \"high\": 32768\n  }\n}\n```\n\n### Benutzeroberfläche und Anzeige\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `theme` | Zeichenfolge | `\"dark\"` | Designname (`\"dark\"`, `\"light\"` oder benutzerdefiniert) |\n| `externalEditor` | Zeichenfolge | `$VISUAL`, dann `$EDITOR`, dann Notepad unter Windows oder `nano` anderswo | Befehl für den externen Editor Strg+G; hat Vorrang vor Umgebungsvariablen |\n| `quietStartup` | Boolescher Wert | `false` | Startup-Header ausblenden |\n| `defaultProjectTrust` | Zeichenfolge | `\"ask\"` | Vertrauensverhalten des Fallback-Projekts: `\"ask\"`, `\"always\"` oder `\"never\"`. Nur globale Einstellung |\n| `collapseChangelog` | Boolescher Wert | `false` | Nach Aktualisierungen komprimiertes Änderungsprotokoll anzeigen |\n| `enableInstallTelemetry` | Boolescher Wert | `true` | Senden Sie nach der Erstinstallation oder nach im Änderungsprotokoll erkannten Updates einen anonymen Installations-/Update-Versions-Ping. Dadurch werden Update-Prüfungen nicht gesteuert |\n| `enableAnalytics` | Boolescher Wert | `false` | Opt-in-Analytics-Datenfreigabe. Wird derzeit nur beim experimentellen Erstaufbau benötigt (`PI_EXPERIMENTAL=1`) |\n| `trackingId` | Zeichenfolge | - | Analytics-Tracking-ID, generiert, wenn `enableAnalytics` aktiviert ist |\n| `doubleEscapeAction` | Zeichenfolge | `\"tree\"` | Aktion für Double-Escape: `\"tree\"`, `\"fork\"` oder `\"none\"` |\n| `treeFilterMode` | Zeichenfolge | `\"default\"` | Standardfilter für `/tree`: `\"default\"`, `\"no-tools\"`, `\"user-only\"`, `\"labeled-only\"`, `\"all\"` |\n| `editorPaddingX` | Nummer | `0` | Horizontaler Abstand für den Eingabeeditor (0-3) |\n| `outputPad` | Nummer | `1` | Horizontaler Abstand für Benutzernachrichten, Assistentennachrichten und Gedanken (0 oder 1) |\n| `autocompleteMaxVisible` | Nummer | `5` | Maximal sichtbare Elemente im Dropdown-Menü für die automatische Vervollständigung (3–20) |\n| `showHardwareCursor` | Boolescher Wert | `false` | Zeigen Sie den Terminalcursor an, während TUI ihn für die IME-Unterstützung positioniert |\n| `tuiMode` | Zeichenfolge | `\"regular\"` | Interaktiver TUI-Modus: `\"regular\"` oder experimenteller `\"fullscreen\"`. Änderungen von `/settings` gelten sofort; `--tui-mode` überschreibt diese Einstellung beim Start |\n| `fullscreenExitOutput` | Zeichenfolge | `\"transcript\"` | Ausgabe im Vollbildmodus beenden: `\"transcript\"` druckt das endgültige Transkript und den Lebenslaufhinweis, während `\"resume-hint\"` den vorherigen Bildschirm wiederherstellt und nur den Lebenslaufhinweis druckt. Hat im regulären TUI-Modus keine Auswirkung |\n| `fullscreenScrollbar` | Zeichenfolge | `\"auto\"` | Vollbild-Transkript-Bildlaufleiste: `\"auto\"` zeigt sie vorübergehend beim Scrollen an, `\"always\"` reserviert die Spalte ganz rechts und lässt sie sichtbar und `\"hidden\"` blendet sie aus. Hat im regulären TUI-Modus keine Auswirkung |\n\nFügen Sie für VS-Code `--wait` ein, damit Pi nach dem Beenden des Editors fortgesetzt wird:\n\n```json\n{\n  \"externalEditor\": \"code --wait\"\n}\n```\n\n### Telemetrie- und Update-Prüfungen\n\n`enableInstallTelemetry` steuert nur den anonymen Installations-/Update-Ping auf `https://pi.dev/api/report-install`. Durch die Deaktivierung der Telemetrie werden Updateprüfungen nicht deaktiviert; Pi kann weiterhin `https://pi.dev/api/latest-version` abrufen, um nach der neuesten Version zu suchen.\n\nStellen Sie `PI_SKIP_VERSION_CHECK=1` ein, um die Pi Versionsaktualisierungsprüfung zu deaktivieren. Verwenden Sie `--offline` oder `PI_OFFLINE=1`, um alle hier beschriebenen Startnetzwerkvorgänge zu deaktivieren, einschließlich Updateprüfungen, Paketaktualisierungsprüfungen und Installations-/Update-Telemetrie.\n\n### Netzwerk\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `httpProxy` | Zeichenfolge | - | HTTP-Proxy-URL wird als `HTTP_PROXY` und `HTTPS_PROXY` angewendet. Nur globale Einstellung. |\n\n```json\n{\n  \"httpProxy\": \"http://127.0.0.1:7890\"\n}\n```\n\n### Warnungen\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `warnings.anthropicExtraUsage` | Boolescher Wert | `true` | Zeigt eine Warnung an, wenn die Anthropic-Abonnementauthentifizierung möglicherweise eine kostenpflichtige zusätzliche Nutzung erfordert |\n\n```json\n{\n  \"warnings\": {\n    \"anthropicExtraUsage\": false\n  }\n}\n```\n\n### Verdichtung\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `compaction.enabled` | Boolescher Wert | `true` | Aktivieren Sie die automatische Komprimierung |\n| `compaction.reserveTokens` | Nummer | `16384` | Für die LLM-Antwort reservierte Token |\n| `compaction.keepRecentTokens` | Nummer | `20000` | Kürzlich zu behaltende Token (nicht zusammengefasst) |\n\n```json\n{\n  \"compaction\": {\n    \"enabled\": true,\n    \"reserveTokens\": 16384,\n    \"keepRecentTokens\": 20000\n  }\n}\n```\n\n### Branchenzusammenfassung\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `branchSummary.reserveTokens` | Nummer | `16384` | Token reserviert für branch summarization |\n| `branchSummary.skipPrompt` | Boolescher Wert | `false` | „Zweig zusammenfassen?“ überspringen Eingabeaufforderung bei `/tree` Navigation (standardmäßig keine Zusammenfassung) |\n\n### Wiederholen\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `retry.enabled` | Boolescher Wert | `true` | Aktivieren Sie automatische Wiederholungsversuche auf Agentenebene bei vorübergehenden Fehlern |\n| `retry.maxRetries` | Nummer | `3` | Maximale Wiederholungsversuche auf Agentenebene |\n| `retry.baseDelayMs` | Nummer | `2000` | Basisverzögerung für exponentielles Backoff auf Agentenebene (2 s, 4 s, 8 s) |\n| `retry.provider.timeoutMs` | Nummer | SDK Standard | Anbieter/SDK Anforderungszeitlimit in Millisekunden |\n| `retry.provider.maxRetries` | Nummer | `0` | Anbieter/SDK Wiederholungsversuche |\n| `retry.provider.maxRetryDelayMs` | Nummer | `60000` | Maximale vom Server angeforderte Verzögerung vor dem Ausfall (60 Sekunden) |\n\nWenn ein Anbieter eine Wiederholungsverzögerung von mehr als `retry.provider.maxRetryDelayMs` anfordert, schlägt die Anfrage sofort mit einem informativen Fehler fehl, anstatt stillschweigend zu warten. Setzen Sie es auf `0`, um das Limit zu deaktivieren.\n\nBehalten Sie `retry.provider.maxRetries` bei `0`, es sei denn, Wiederholungsversuche auf Anbieterebene sind ausdrücklich erforderlich. Wenn Sie den Wert über `0` setzen, kann dies dazu führen, dass SDK/Provider-Wiederholungsfehler Fehler außerhalb des Nutzungslimits behandeln, bevor Pi sie erkennt, was unter bestimmten Umständen den Agenten blockieren kann, bis das Provider-Kontingent zurückgesetzt wird.\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### Nachrichtenübermittlung\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `steeringMode` | Zeichenfolge | `\"one-at-a-time\"` | So werden Lenknachrichten gesendet: `\"all\"` oder `\"one-at-a-time\"` |\n| `followUpMode` | Zeichenfolge | `\"one-at-a-time\"` | So werden Folgenachrichten gesendet: `\"all\"` oder `\"one-at-a-time\"` |\n| `transport` | Zeichenfolge | `\"auto\"` | Bevorzugter Transport für Anbieter, die mehrere Transporte unterstützen: `\"sse\"`, `\"websocket\"`, `\"websocket-cached\"` oder `\"auto\"` |\n| `httpIdleTimeoutMs` | Nummer | `300000` | HTTP-Header/Body-Leerlauf-Timeout in Millisekunden, wird auch von Anbietern mit expliziten Stream-Leerlauf-Timeouts verwendet. Zum Deaktivieren auf `0` einstellen. |\n| `websocketConnectTimeoutMs` | Nummer | `15000` | WebSocket-Verbindungs-/Öffnungs-Handshake-Timeout in Millisekunden für Anbieter, die WebSocket-Transporte unterstützen. Zum Deaktivieren auf `0` einstellen. |\n\n### Terminal & Bilder\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `terminal.showImages` | Boolescher Wert | `true` | Bilder im Terminal anzeigen (falls unterstützt) |\n| `terminal.imageWidthCells` | Nummer | `60` | Bevorzugte Inline-Bildbreite in Terminalzellen |\n| `terminal.clearOnShrink` | Boolescher Wert | `false` | Leere Zeilen löschen, wenn der Inhalt kleiner wird (kann zu Flimmern führen) |\n| `images.autoResize` | Boolescher Wert | `true` | Ändern Sie die Größe der Bilder auf maximal 2000 x 2000. Gilt für `@file` Anhänge, `read` und von Tools zurückgegebene Bilder |\n| `images.blockImages` | Boolescher Wert | `false` | Blockieren Sie das Senden aller Bilder an LLM |\n\n### Hülse\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `shellPath` | Zeichenfolge | - | Benutzerdefinierter Shell-Pfad (z. B. für Cygwin unter Windows); unterstützt eine führende `~` für das Home-Verzeichnis |\n| `shellCommandPrefix` | Zeichenfolge | - | Präfix für jeden bash-Befehl (z. B. `\"shopt -s expand_aliases\"`) |\n| `npmCommand` | string[] | - | Befehl argv, der für npm Paketsuch-/Installationsvorgänge verwendet wird (z. B. `[\"mise\", \"exec\", \"node@20\", \"--\", \"npm\"]`) |\n\n```json\n{\n  \"npmCommand\": [\"mise\", \"exec\", \"node@20\", \"--\", \"npm\"]\n}\n```\n\n`npmCommand` wird für alle npm Paketmanager-Vorgänge verwendet, einschließlich Installationen, Deinstallationen und Abhängigkeitsinstallationen innerhalb von Git-Paketen. Benutzerspezifische npm-Pakete werden unter `~/.pi/agent/npm/` installiert; Projektbezogene npm-Pakete werden unter `.pi/npm/` installiert. Verwenden Sie Einträge im argv-Stil genau so, wie der Prozess gestartet werden soll. Wenn `npmCommand` konfiguriert ist, verwenden Git-Paketabhängigkeitsinstallationen einfaches `install`, um npm-spezifische Flags in Wrappern oder alternativen Paketmanagern zu vermeiden.\n\n### Sitzungen\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `sessionDir` | Zeichenfolge | - | Verzeichnis, in dem Sitzungsdateien gespeichert werden. Akzeptiert absolute oder relative Pfade plus `~`. |\n\n```json\n{ \"sessionDir\": \".pi/sessions\" }\n```\n\nWenn mehrere Quellen ein Sitzungsverzeichnis angeben, ist die Priorität `--session-dir`, `PI_CODING_AGENT_SESSION_DIR` und dann `sessionDir` in Settings.json.\n\n### Modellradfahren\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `enabledModels` | string[] | - | Modellmuster für den Strg+P-Wechsel (gleiches Format wie `--models` CLI Flag) |\n\n```json\n{\n  \"enabledModels\": [\"claude-*\", \"gpt-4o\", \"gemini-2*\"]\n}\n```\n\n### Markdown\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `markdown.codeBlockIndent` | Zeichenfolge | `\"  \"` | Einrückung für Codeblöcke |\n| `markdown.mermaid` | Zeichenfolge | `\"streaming\"` | Meerjungfrau-Rendering-Modus: `\"off\"`, `\"final\"` oder `\"streaming\"` |\n\n### Ressourcen\n\nDiese Einstellungen legen fest, woher Erweiterungen, Fertigkeiten, Eingabeaufforderungen und Themen geladen werden sollen.\n\nPfade in `~/.pi/agent/settings.json` werden relativ zu `~/.pi/agent` aufgelöst. Pfade in `.pi/settings.json` werden relativ zu `.pi` aufgelöst. Absolute Pfade und `~` werden unterstützt.\n\n| Einstellung | Typ | Standard | Beschreibung |\n|---------|------|---------|-------------|\n| `packages` | Array | `[]` | npm/git-Pakete zum Laden von Ressourcen |\n| `extensions` | string[] | `[]` | Lokale Dateipfade oder Verzeichnisse für Erweiterungen |\n| `skills` | string[] | `[]` | Lokale Skilldateipfade oder -verzeichnisse |\n| `prompts` | string[] | `[]` | Lokale Eingabeaufforderungsvorlagenpfade oder -verzeichnisse |\n| `themes` | string[] | `[]` | Lokale Dateipfade oder Verzeichnisse für Designs |\n| `enableSkillCommands` | Boolescher Wert | `true` | Registrieren Sie Fähigkeiten als `/skill:name`-Befehle |\n\nArrays unterstützen Globmuster und Ausschlüsse. Verwenden Sie `!pattern` zum Ausschließen. Verwenden Sie `+path`, um das Einschließen eines genauen Pfads zu erzwingen, und `-path`, um das Ausschließen eines genauen Pfads zu erzwingen.\n\n#### Pakete\n\nDie Zeichenfolgenform lädt alle Ressourcen aus einem Paket:\n\n```json\n{\n  \"packages\": [\"pi-skills\", \"@org/my-extension\"]\n}\n```\n\nDas Objektformular filtert, welche Ressourcen geladen werden sollen:\n\n```json\n{\n  \"packages\": [\n    {\n      \"source\": \"pi-skills\",\n      \"skills\": [\"brave-search\", \"transcribe\"],\n      \"extensions\": []\n    }\n  ]\n}\n```\n\nEinzelheiten zur Paketverwaltung finden Sie unter [packages.md](packages.md).\n\n## Beispiel\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## Projektüberschreibungen\n\nProjekteinstellungen (`.pi/settings.json`) überschreiben globale Einstellungen. Verschachtelte Objekte werden zusammengeführt:\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":"Shell-Aliase","markdown":"Pi führt bash im nicht interaktiven Modus (`bash -c`) aus, wodurch Aliase standardmäßig nicht erweitert werden.\n\nUm Ihre Shell-Aliase zu aktivieren, fügen Sie zu `~/.pi/agent/settings.json` hinzu:\n\n```json\n{\n  \"shellCommandPrefix\": \"shopt -s expand_aliases\\neval \\\"$(grep '^alias ' ~/.zshrc)\\\"\"\n}\n```\n\nPassen Sie den Pfad (`~/.zshrc`, `~/.bashrc` usw.) an Ihre Shell-Konfiguration an.","sourceFile":"shell-aliases.md"},"skills":{"title":"Skills","markdown":"> Pi kann Fähigkeiten schaffen. Bitten Sie es, eines für Ihren Anwendungsfall zu erstellen.\n\n\nSkills sind eigenständige Funktionspakete, die der Agent bei Bedarf lädt. Ein Skill stellt spezielle Arbeitsabläufe, Einrichtungsanweisungen, Hilfsskripts und Referenzdokumentation für bestimmte Aufgaben bereit.\n\nPi implementiert [Agent Skills standard](https://agentskills.io/specification) und warnt vor den meisten Verstößen, bleibt aber nachsichtig. Pi erlaubt, dass sich Skill-Namen vom übergeordneten Verzeichnis unterscheiden, auch wenn der Standard dies nicht zulässt; Diese Regel ist für gemeinsam genutzte Skill-Verzeichnisse, die über mehrere Agentenstrukturen hinweg verwendet werden, nicht optimal.\n\n## Inhaltsverzeichnis\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## Standorte\n\n> **Sicherheit:** Skills kann das Modell anweisen, eine beliebige Aktion auszuführen und kann ausführbaren Code enthalten, den das Modell aufruft. Überprüfen Sie den Inhalt der Fertigkeiten vor der Verwendung.\n\nPi lädt Fähigkeiten von:\n\n- Global:\n  - `~/.pi/agent/skills/`\n  - `~/.agents/skills/`\n- Projekt (nur nachdem das Projekt vertrauenswürdig ist):\n  - `.pi/skills/`\n  - `.agents/skills/` in `cwd` und Vorgängerverzeichnissen (bis zum Git-Repo-Root oder Dateisystem-Root, wenn nicht in einem Repo)\n- Pakete: `skills/` Verzeichnisse oder `pi.skills` Einträge in `package.json`\n- Einstellungen: `skills` Array mit Dateien oder Verzeichnissen\n- CLI: `--skill <path>` (wiederholbar, additiv auch mit `--no-skills`)\n\nDiscovery-Regeln:\n- In `~/.pi/agent/skills/` und `.pi/skills/` werden direkte Stammdateien `.md` als individuelle Fähigkeiten erkannt\n- An allen Skill-Standorten werden Verzeichnisse, die `SKILL.md` enthalten, rekursiv erkannt\n- In `~/.agents/skills/` und Projekt `.agents/skills/` werden Root-Dateien `.md` ignoriert\n\nDeaktivieren Sie die Erkennung mit `--no-skills` (explizite `--skill` Pfade werden weiterhin geladen).\n\n### Verwendung von Skills von anderen Geschirren\n\nUm Fähigkeiten von Claude Code oder OpenAI Codex zu verwenden, fügen Sie deren Verzeichnisse zu den Einstellungen hinzu:\n\n```json\n{\n  \"skills\": [\n    \"~/.claude/skills\",\n    \"~/.codex/skills\"\n  ]\n}\n```\n\nFür Claude-Code-Kenntnisse auf Projektebene fügen Sie zu `.pi/settings.json` hinzu:\n\n```json\n{\n  \"skills\": [\"../.claude/skills\"]\n}\n```\n\n## Wie Skills funktioniert\n\n1. Beim Start scannt Pi die Standorte der Fertigkeiten und extrahiert Namen und Beschreibungen\n2. Die Systemaufforderung enthält verfügbare Fertigkeiten im XML-Format gemäß [specification](https://agentskills.io/integrate-skills)\n3. Wenn eine Aufgabe übereinstimmt, verwendet der Agent `read`, um die vollständige SKILL.md zu laden (Modelle tun dies nicht immer; verwenden Sie Eingabeaufforderungen oder `/skill:name`, um es zu erzwingen).\n4. Der Agent befolgt die Anweisungen und verwendet relative Pfade, um auf Skripts und Assets zu verweisen\n\nDies ist eine progressive Offenlegung: Nur Beschreibungen sind immer im Kontext, vollständige Anweisungen werden bei Bedarf geladen.\n\n## Fertigkeitsbefehle\n\nSkills als `/skill:name` Befehle registrieren:\n\n```bash\n/skill:brave-search           # Load and execute the skill\n/skill:pdf-tools extract      # Load skill with arguments\n```\n\nArgumente nach dem Befehl werden als `User: <args>` an den Skill-Inhalt angehängt.\n\nSchalten Sie die Fertigkeitsbefehle über `/settings` im interaktiven Modus oder in `settings.json` um:\n\n```json\n{\n  \"enableSkillCommands\": true\n}\n```\n\n## Fähigkeitsstruktur\n\nEin Skill ist ein Verzeichnis mit einer `SKILL.md`-Datei. Alles andere ist Freiform.\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-Format\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 install\n```\n\n## Usage\n\n```bash\n./scripts/process.sh <input>\n```\n````\n\nVerwenden Sie relative Pfade aus dem Skill-Verzeichnis:\n\n```markdown\nSee [the reference guide](references/REFERENCE.md) for details.\n```\n\n## Frontmatter\n\nLaut [Agent Skills specification](https://agentskills.io/specification#frontmatter-required):\n\n| Feld | Erforderlich | Beschreibung |\n|-------|----------|-------------|\n| `name` | Ja | Maximal 64 Zeichen. Kleinbuchstaben a–z, 0–9, Bindestriche. Im Gegensatz zum Standard erfordert Pi nicht, dass dies mit dem übergeordneten Verzeichnis übereinstimmt, da diese Standardanforderung für gemeinsam genutzte Skill-Verzeichnisse nicht optimal ist. |\n| `description` | Ja | Maximal 1024 Zeichen. Was die Fertigkeit bewirkt und wann sie eingesetzt werden sollte. |\n| `license` | NEIN | Lizenzname oder Verweis auf die gebündelte Datei. |\n| `compatibility` | NEIN | Maximal 500 Zeichen. Umgebungsanforderungen. |\n| `metadata` | NEIN | Beliebige Schlüsselwertzuordnung. |\n| `allowed-tools` | NEIN | Durch Leerzeichen getrennte Liste vorab genehmigter Tools (experimentell). |\n| `disable-model-invocation` | NEIN | Bei `true` wird der Skill in der Systemaufforderung ausgeblendet. Benutzer müssen `/skill:name` verwenden. |\n\n### Namensregeln\n\n- 1-64 Zeichen\n- Nur Kleinbuchstaben, Zahlen, Bindestriche\n- Keine führenden/nachgestellten Bindestriche\n- Keine aufeinanderfolgenden Bindestriche\nPi erfordert nicht, dass der Name mit dem übergeordneten Verzeichnis übereinstimmt. Der Agent Skills-Standard tut dies, aber diese Anforderung ist für gemeinsame Skill-Verzeichnisse, die von mehreren Tools verwendet werden, nicht optimal.\n\nGültig: `pdf-processing`, `data-analysis`, `code-review`\nUngültig: `PDF-Processing`, `-pdf`, `pdf--processing`\n\n### Beschreibung Best Practices\n\nDie Beschreibung bestimmt, wann der Agent den Skill lädt. Seien Sie konkret.\n\nGut:\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\nArm:\n```yaml\ndescription: Helps with PDFs.\n```\n\n## Validierung\n\nPi validiert Fähigkeiten anhand des Standards des Agenten Skills. Bei den meisten Problemen werden Warnungen ausgegeben, der Skill wird jedoch trotzdem geladen:\n\n- Der Name ist länger als 64 Zeichen oder enthält ungültige Zeichen\n- Der Name beginnt/endet mit einem Bindestrich oder hat aufeinanderfolgende Bindestriche\n- Die Beschreibung umfasst mehr als 1024 Zeichen\n\nUnbekannte Frontmatter-Felder werden ignoriert.\n\n**Ausnahme:** Skills mit fehlender Beschreibung werden nicht geladen.\n\nNamenskollisionen (gleicher Name an verschiedenen Orten) warnen und behalten den zuerst gefundenen Skill bei.\n\n## Beispiel\n\n```\nbrave-search/\n├── SKILL.md\n├── search.js\n└── content.js\n```\n\n**SKILL.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 install\n```\n\n## Search\n\n```bash\n./search.js \"query\" # Einfache Suche\n./search.js \"query\" --content # Seiteninhalt einschließen\n```\n\n## Extract Page Content\n\n```bash\n./content.js https://example.com\n```\n````\n\n## Skill-Repositories\n\n- [Anthropic Skills](https://github.com/anthropics/skills) – Dokumentenverarbeitung (docx, pdf, pptx, xlsx), Webentwicklung\n- [Pi Skills](https://github.com/badlogic/pi-skills) – Websuche, Browser-Automatisierung, Google APIs, Transkription","sourceFile":"skills.md"},"terminal-setup":{"title":"Terminal-Setup","markdown":"Pi verwendet [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) zur zuverlässigen Erkennung von Zusatztasten. Die meisten modernen Terminals unterstützen dieses Protokoll, einige erfordern jedoch eine Konfiguration.\n\n## Kitty, iTerm2\n\nArbeiten Sie out of the box.\n\n## Apple-Terminal\n\nPi ermöglicht erweiterte Schlüsselberichte, sofern verfügbar. Wenn Terminal.app immer noch eine einfache Rückgabe für `Shift+Enter` sendet, verwendet pi einen lokalen macOS-Modifikator-Fallback, um diese Rückgabe als `Shift+Enter` zu behandeln.\n\nDieser Fallback funktioniert nur, wenn pi auf demselben Mac wie Terminal.app läuft. Die lokale Tastatur kann über Remote SSH nicht erkannt werden.\n\n## Geisterhaft\n\nFügen Sie Ihrer Ghostty-Konfiguration hinzu (`~/Library/Application Support/com.mitchellh.ghostty/config` unter macOS, `~/.config/ghostty/config` unter Linux):\n\n```\nkeybind = alt+backspace=text:\\x1b\\x7f\n```\n\nÄltere Claude Code-Versionen haben möglicherweise diese Ghostty-Zuordnung hinzugefügt:\n\n```\nkeybind = shift+enter=text:\\n\n```\n\nDiese Zuordnung sendet ein rohes Linefeed-Byte. Innerhalb von Pi ist das nicht von `Ctrl+J` zu unterscheiden, daher sehen tmux und Pi kein echtes `shift+enter`-Schlüsselereignis mehr.\n\nWenn Claude Code 2.x oder neuer der einzige Grund ist, warum Sie diese Zuordnung hinzugefügt haben, können Sie sie entfernen, es sei denn, Sie möchten Claude Code in tmux verwenden, wo diese Ghostty-Zuordnung weiterhin erforderlich ist.\n\nPi bindet `Ctrl+J` als Standard-Neuzeilen-Alias, sodass `Shift+Enter` über diese Neuzuordnung ohne zusätzliche Pi-Konfiguration weiterhin in tmux funktioniert.\n\n## WezTerm\n\nWezTerm funktioniert normalerweise sofort für `Shift+Enter` über xterm changesOtherKeys. Um das Kitty-Tastaturprotokoll explizit zu verwenden, erstellen Sie `~/.wezterm.lua`:\n\n```lua\nlocal wezterm = require 'wezterm'\nlocal config = wezterm.config_builder()\nconfig.enable_kitty_keyboard = true\nreturn config\n```\n\nUnter macOS bindet WezTerm `Option+Enter` standardmäßig an den Vollbildmodus. Um `Option+Enter` für die Pi-Folgewarteschlange zu verwenden, fügen Sie diese Schlüsselüberschreibung hinzu:\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\nWenn Sie bereits über eine `config.keys`-Tabelle verfügen, fügen Sie den Eintrag hinzu.\n\nUnter WSL erfordert WezTerm möglicherweise einen sichtbaren Hardware-Cursor für die Positionierung des IME-Kandidatenfensters. Wenn CJK-IME-Kandidaten dem Textcursor nicht folgen, legen Sie `PI_HARDWARE_CURSOR=1` fest, bevor Sie pi ausführen, oder setzen Sie `showHardwareCursor` in den Einstellungen auf `true`.\n\n## Alacritty\n\nAlacritty funktioniert normalerweise sofort für `Shift+Enter`. Unter macOS erscheint `Option+Enter` möglicherweise als einfaches `Enter`. Um `Option+Enter` für die Pi-Folgewarteschlange zu verwenden, fügen Sie `~/.config/alacritty/alacritty.toml` hinzu:\n\n```toml\n[[keyboard.bindings]]\nkey = \"Enter\"\nmods = \"Alt\"\nchars = \"\\u001b[13;3u\"\n```\n\nStarten Sie Alacritty neu, nachdem Sie die Konfiguration geändert haben.\n\n## VS-Code (Integriertes Terminal)\n\nVS Code 1.109.5 und höher aktivieren standardmäßig das Kitty-Tastaturprotokoll im integrierten Terminal, sodass `Shift+Enter` sofort funktionieren sollte.\n\nVS-Code-Versionen vor 1.109.5 benötigen eine explizite Terminal-Tastenkombination für `Shift+Enter`.\n\n`keybindings.json` Standorte:\n- macOS: `~/Library/Application Support/Code/User/keybindings.json`\n- Linux: `~/.config/Code/User/keybindings.json`\n- Windows: `%APPDATA%\\\\Code\\\\User\\\\keybindings.json`\n\nZu `keybindings.json` hinzufügen:\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-Terminal\n\nZu `settings.json` hinzufügen (Strg+Umschalt+ oder Einstellungen → JSON-Datei öffnen), um die geänderten Eingabetasten weiterzuleiten, die pi verwendet:\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` fügt eine neue Zeile ein.\n- Windows Terminal bindet `Alt+Enter` standardmäßig an den Vollbildmodus. Dadurch wird verhindert, dass pi `Alt+Enter` für die Folgewarteschlange empfängt.\n- Durch die Neuzuordnung von `Alt+Enter` zu `sendInput` wird der echte Schlüsselakkord stattdessen an Pi weitergeleitet.\n\nWenn Sie bereits über ein `actions`-Array verfügen, fügen Sie die Objekte hinzu. Wenn das alte Vollbildverhalten weiterhin besteht, schließen Sie Windows Terminal vollständig und öffnen Sie es erneut.\n\n## xfce4-Terminal, Terminator\n\nDiese Terminals bieten nur begrenzte Unterstützung für Escape-Sequenzen. Geänderte Eingabetasten wie `Ctrl+Enter` und `Shift+Enter` können nicht von der einfachen `Enter` unterschieden werden, sodass benutzerdefinierte Tastenkombinationen wie `submit: [\"ctrl+enter\"]` nicht funktionieren.\n\nFür das beste Erlebnis verwenden Sie ein Terminal, das das Kitty-Tastaturprotokoll unterstützt:\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) (erfordert Kompilierung mit Kitty-Protokollunterstützung)\n\n## IntelliJ IDEA (Integriertes Terminal)\n\nDas integrierte Terminal bietet nur begrenzte Unterstützung für Escape-Sequenzen. Umschalt+Eingabetaste kann im IntelliJ-Terminal nicht von Eingabetaste unterschieden werden.\n\nWenn Sie möchten, dass der Hardware-Cursor sichtbar ist, stellen Sie `PI_HARDWARE_CURSOR=1` ein, bevor Sie pi ausführen (aus Kompatibilitätsgründen standardmäßig deaktiviert).\n\nErwägen Sie die Verwendung eines dedizierten Terminalemulators, um das beste Erlebnis zu erzielen.","sourceFile":"terminal-setup.md"},"termux":{"title":"Termux (Android) Setup","markdown":"Pi läuft auf Android über [Termux](https://termux.dev/), einen Terminalemulator und eine Linux-Umgebung für Android.\n\n## Voraussetzungen\n\n1. Installieren Sie [Termux](https://github.com/termux/termux-app#installation) von GitHub oder F-Droid (nicht Google Play, diese Version ist veraltet)\n2. Installieren Sie [Termux:API](https://github.com/termux/termux-api#installation) von GitHub oder F-Droid für die Zwischenablage und andere Geräteintegrationen\n\n## Installation\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## Unterstützung für die Zwischenablage\n\nZwischenablageoperationen verwenden `termux-clipboard-set` und `termux-clipboard-get`, wenn sie in Termux ausgeführt werden. Damit diese funktionieren, muss die App Termux:API installiert sein.\n\nDie Bildzwischenablage wird auf Termux nicht unterstützt (die Funktion zum Einfügen von Bildern in `ctrl+v` funktioniert nicht).\n\n## Beispiel AGENTS.md für Termux\n\nErstellen Sie `~/.pi/agent/AGENTS.md`, um dem Agenten zu helfen, die Termux-Umgebung zu verstehen:\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 # Öffnet mit der Standard-App\ntermux-open --chooser image.jpg # App auswählen\n```\n\n## Clipboard\n```bash\ntermux-clipboard-set \"text\" # Kopieren\ntermux-clipboard-get # Einfügen\n```\n\n## Notifications\n```bash\ntermux-notification -t „Titel“ -c „Inhalt“\n```\n\n## Device Info\n```bash\ntermux-battery-status # Batterieinfo\ntermux-wifi-connectioninfo # WLAN-Info\ntermux-telephony-deviceinfo # Geräteinfo\n```\n\n## Sharing\n```bash\ntermux-share -a send file.txt # Datei teilen\n```\n\n## Other Useful Commands\n```bash\ntermux-toast „message“ # Schnelles Toast-Popup\ntermux-vibrate # Gerät vibrieren\ntermux-tts-speak „hello“ # Text in Sprache\ntermux-camera-photo out.jpg # Foto aufnehmen\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## Einschränkungen\n\n- **Keine Bild-Zwischenablage**: Termux Zwischenablage API unterstützt nur Text\n- **Keine nativen Binärdateien**: Einige optionale native Abhängigkeiten (wie das Zwischenablagemodul) sind auf Android ARM64 nicht verfügbar und werden während der Installation übersprungen\n- **Speicherzugriff**: Um auf Dateien in `/storage/emulated/0` (Downloads usw.) zuzugreifen, führen Sie `termux-setup-storage` einmal aus, um Berechtigungen zu erteilen\n\n## Fehlerbehebung\n\n### Zwischenablage funktioniert nicht\n\nStellen Sie sicher, dass beide Apps installiert sind:\n1. Termux (von GitHub oder F-Droid)\n2. Termux:API (von GitHub oder F-Droid)\n\nAnschließend installieren Sie die CLI Tools:\n```bash\npkg install termux-api\n```\n\n### Berechtigung für freigegebenen Speicher verweigert\n\nEinmal ausführen, um Speicherberechtigungen zu erteilen:\n```bash\ntermux-setup-storage\n```\n\n### Node.js Installationsprobleme\n\nWenn npm fehlschlägt, versuchen Sie, den Cache zu leeren:\n```bash\nnpm cache clean --force\n```","sourceFile":"termux.md"},"themes":{"title":"Themen","markdown":"> pi kann Themen erstellen. Bitten Sie es, eines für Ihr Setup zu erstellen.\n\n\nThemen sind JSON Dateien, die Farben für TUI definieren.\n\n## Inhaltsverzeichnis\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## Standorte\n\nPi lädt Themen von:\n\n- Eingebaut: `dark`, `light`\n- Global: `~/.pi/agent/themes/*.json`\n- Projekt: `.pi/themes/*.json` (nur nachdem das Projekt vertrauenswürdig ist)\n- Pakete: `themes/` Verzeichnisse oder `pi.themes` Einträge in `package.json`\n- Einstellungen: `themes` Array mit Dateien oder Verzeichnissen\n- CLI: `--theme <path>` (wiederholbar)\n\nDeaktivieren Sie die Erkennung mit `--no-themes`.\n\n## Auswählen eines Themas\n\nWählen Sie ein Thema über `/settings` oder in `settings.json`:\n\n```json\n{\n  \"theme\": \"my-theme\"\n}\n```\n\nBeim ersten Start erkennt pi den Hintergrund Ihres Terminals und stellt standardmäßig `dark` oder `light` ein.\n\n## Erstellen eines benutzerdefinierten Themes\n\n1. Erstellen Sie eine Theme-Datei:\n\n```bash\nmkdir -p ~/.pi/agent/themes\nvim ~/.pi/agent/themes/my-theme.json\n```\n\n2. Definieren Sie das Theme mit allen benötigten Farben (siehe [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. Wählen Sie das Thema über `/settings` aus.\n\n**Hot Reload:** Wenn Sie die aktuell aktive benutzerdefinierte Designdatei bearbeiten, lädt Pi sie automatisch neu, um sofortiges visuelles Feedback zu erhalten.\n\n## Themenformat\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` ist erforderlich, muss eindeutig sein und darf `/` nicht enthalten.\n- `vars` ist optional. Definieren Sie hier wiederverwendbare Farben und referenzieren Sie sie dann in `colors`.\n- `colors` muss alle 51 erforderlichen Token definieren. `thinkingMax` ist optional und greift auf `thinkingXhigh` zurück; `scrollbarThumb` ist optional und greift auf `selectedBg` zurück.\n\nDas Feld `$schema` ermöglicht die automatische Vervollständigung und Validierung des Editors.\n\n## Farbtoken\n\nJedes Thema muss alle 51 erforderlichen Farbtoken definieren. `thinkingMax` und `scrollbarThumb` sind aus Kompatibilitätsgründen mit vorhandenen Themes optional; Wenn es weggelassen wird, verwenden sie `thinkingXhigh` bzw. `selectedBg`.\n\n### Kern-Benutzeroberfläche (11 Farben)\n\n| Token | Zweck |\n|-------|---------|\n| `accent` | Hauptakzent (Logo, ausgewählte Elemente, Cursor) |\n| `border` | Normale Grenzen |\n| `borderAccent` | Hervorgehobene Grenzen |\n| `borderMuted` | Subtile Grenzen (Herausgeber) |\n| `success` | Erfolgszustände |\n| `error` | Fehlerzustände |\n| `warning` | Warnzustände |\n| `muted` | Sekundärtext |\n| `dim` | Tertiärer Text |\n| `text` | Standardtext (normalerweise `\"\"`) |\n| `thinkingText` | Denkblocktext |\n\n### Hintergründe und Inhalte (11 erforderlich, 1 optional)\n\n| Token | Zweck |\n|-------|---------|\n| `selectedBg` | Ausgewählter Zeilenhintergrund |\n| `scrollbarThumb` | Vollbild-Bildlaufleisten-Daumenhintergrund; optional, fällt auf `selectedBg` zurück |\n| `userMessageBg` | Hintergrund der Benutzernachricht |\n| `userMessageText` | Text der Benutzernachricht |\n| `customMessageBg` | Hintergrund der Erweiterungsnachricht |\n| `customMessageText` | Text der Erweiterungsnachricht |\n| `customMessageLabel` | Beschriftung der Erweiterungsnachricht |\n| `toolPendingBg` | Werkzeugkasten (ausstehend) |\n| `toolSuccessBg` | Werkzeugkasten (Erfolg) |\n| `toolErrorBg` | Werkzeugkasten (Fehler) |\n| `toolTitle` | Werkzeugtitel |\n| `toolOutput` | Werkzeugausgabetext |\n\n### Markdown (10 Farben)\n\n| Token | Zweck |\n|-------|---------|\n| `mdHeading` | Überschriften |\n| `mdLink` | Linktext |\n| `mdLinkUrl` | Link-URL |\n| `mdCode` | Inline-Code |\n| `mdCodeBlock` | Inhalt des Codeblocks |\n| `mdCodeBlockBorder` | Codeblockzäune |\n| `mdQuote` | Blockquote-Text |\n| `mdQuoteBorder` | Blockquote-Grenze |\n| `mdHr` | Horizontale Regel |\n| `mdListBullet` | Listen Sie Aufzählungszeichen auf |\n\n### Werkzeugunterschiede (3 Farben)\n\n| Token | Zweck |\n|-------|---------|\n| `toolDiffAdded` | Zeilen hinzugefügt |\n| `toolDiffRemoved` | Zeilen entfernt |\n| `toolDiffContext` | Kontextzeilen |\n\n### Syntaxhervorhebung (9 Farben)\n\n| Token | Zweck |\n|-------|---------|\n| `syntaxComment` | Kommentare |\n| `syntaxKeyword` | Schlüsselwörter |\n| `syntaxFunction` | Funktionsnamen |\n| `syntaxVariable` | Variablen |\n| `syntaxString` | Saiten |\n| `syntaxNumber` | Zahlen |\n| `syntaxType` | Typen |\n| `syntaxOperator` | Betreiber |\n| `syntaxPunctuation` | Interpunktion |\n\n### Grenzen der Denkebene (6 erforderlich, 1 optional)\n\nDie Rahmenfarben des Editors geben die Denkebene an (visuelle Hierarchie von subtil bis prominent):\n\n| Token | Zweck |\n|-------|---------|\n| `thinkingOff` | Nachdenken |\n| `thinkingMinimal` | Minimales Denken |\n| `thinkingLow` | Niedriges Denken |\n| `thinkingMedium` | Mittleres Denken |\n| `thinkingHigh` | Hohes Denken |\n| `thinkingXhigh` | Extra hohes Denken |\n| `thinkingMax` | Maximales Denken; optional, fällt auf `thinkingXhigh` zurück |\n\n### Bash-Modus (1 Farbe)\n\n| Token | Zweck |\n|-------|---------|\n| `bashMode` | Editorrahmen im bash-Modus (`!`-Präfix) |\n\n### HTML-Export (optional)\n\nDer Abschnitt `export` steuert die Farben für die `/export` HTML-Ausgabe. Wenn es weggelassen wird, werden die Farben von `userMessageBg` abgeleitet.\n\n```json\n{\n  \"export\": {\n    \"pageBg\": \"#18181e\",\n    \"cardBg\": \"#1e1e24\",\n    \"infoBg\": \"#3c3728\"\n  }\n}\n```\n\n## Farbwerte\n\nVier Formate werden unterstützt:\n\n| Format | Beispiel | Beschreibung |\n|--------|---------|-------------|\n| Verhexen | `\"#ff0000\"` | 6-stelliges Hex-RGB |\n| 256 Farben | `39` | xterm 256-Farbpalettenindex (0-255) |\n| Variable | `\"primary\"` | Verweis auf einen `vars`-Eintrag |\n| Standard | `\"\"` | Die Standardfarbe des Terminals |\n\n### 256-Farben-Palette\n\n- `0-15`: Grundlegende ANSI-Farben (terminalabhängig)\n- `16-231`: 6×6×6 RGB-Würfel (`16 + 36×R + 6×G + B` wobei R,G,B 0-5 sind)\n- `232-255`: Graustufenrampe\n\n### Terminalkompatibilität\n\nPi verwendet 24-Bit-RGB-Farben. Die meisten modernen Terminals unterstützen dies (iTerm2, Kitty, WezTerm, Windows Terminal, VS Code). Bei älteren Terminals mit nur 256-Farben-Unterstützung fällt pi auf die nächste Näherung zurück.\n\nÜberprüfen Sie die Truecolor-Unterstützung:\n\n```bash\necho $COLORTERM  # Should output \"truecolor\" or \"24bit\"\n```\n\n## Tipps\n\n**Dunkle Terminals:** Verwenden Sie helle, gesättigte Farben mit höherem Kontrast.\n\n**Helle Terminals:** Verwenden Sie dunklere, gedämpfte Farben mit geringerem Kontrast.\n\n**Farbharmonie:** Beginnen Sie mit einer Basispalette (Nord, Gruvbox, Tokyo Night), definieren Sie sie in `vars` und referenzieren Sie sie konsistent.\n\n**Testen:** Überprüfen Sie Ihr Theme mit verschiedenen Nachrichtentypen, Tool-Status, Markdown-Inhalten und langem umbrochenem Text.\n\n**VS-Code:** Setzen Sie `terminal.integrated.minimumContrastRatio` auf `1` für genaue Farben.\n\n## Beispiele\n\nSehen Sie sich die integrierten Themen an:\n- [dark.json](../src/modes/interactive/theme/dark.json)\n- [light.json](../src/modes/interactive/theme/light.json)","sourceFile":"themes.md"},"tmux":{"title":"tmux Einrichtung","markdown":"Pi funktioniert innerhalb von tmux, aber tmux entfernt standardmäßig Modifikatorinformationen von bestimmten Tasten. Ohne Konfiguration sind `Shift+Enter` und `Ctrl+Enter` normalerweise nicht vom einfachen `Enter` zu unterscheiden.\n\n## Empfohlene Konfiguration\n\nZu `~/.tmux.conf` hinzufügen:\n\n```tmux\nset -g extended-keys on\nset -g extended-keys-format csi-u\n```\n\nAnschließend tmux vollständig neu starten:\n\n```bash\ntmux kill-server\ntmux\n```\n\nPi fordert automatisch eine erweiterte Tastenberichterstattung an, wenn das Kitty-Tastaturprotokoll nicht verfügbar ist. Mit `extended-keys-format csi-u` leitet tmux geänderte Schlüssel im CSI-u-Format weiter, was die zuverlässigste Konfiguration darstellt. Die Option `extended-keys-format` erfordert tmux 3.5 oder höher.\n\n## Warum `csi-u` empfohlen wird\n\nMit nur:\n\n```tmux\nset -g extended-keys on\n```\n\ntmux ist standardmäßig `extended-keys-format xterm`. Wenn eine Anwendung eine erweiterte Schlüsselberichterstattung anfordert, werden geänderte Schlüssel im xterm `modifyOtherKeys`-Format weitergeleitet, z. B.:\n\n- `Ctrl+C` → `\\x1b[27;5;99~`\n- `Ctrl+D` → `\\x1b[27;5;100~`\n- `Ctrl+Enter` → `\\x1b[27;5;13~`\n\nMit `extended-keys-format csi-u` werden die gleichen Schlüssel weitergeleitet wie:\n\n- `Ctrl+C` → `\\x1b[99;5u`\n- `Ctrl+D` → `\\x1b[100;5u`\n- `Ctrl+Enter` → `\\x1b[13;5u`\n\nPi unterstützt beide Formate, aber `csi-u` ist das empfohlene tmux-Setup.\n\n## Was dies behebt\n\nOhne tmux erweiterte Tasten werden geänderte Eingabetasten auf alte Sequenzen reduziert:\n\n| Schlüssel | Ohne Extkeys | Mit `csi-u` |\n|-----|-----------------|--------------|\n| Eingeben | `\\r` | `\\r` |\n| Umschalt+Eingabetaste | `\\r` | `\\x1b[13;2u` |\n| Strg+Eingabetaste | `\\r` | `\\x1b[13;5u` |\n| Alt/Wahl+Eingabetaste | `\\x1b\\r` | `\\x1b[13;3u` |\n\nDies betrifft die Standard-Tastenkombinationen (`Enter` zum Senden, `Shift+Enter` für Zeilenumbruch) und alle benutzerdefinierten Tastenkombinationen, die die modifizierte Eingabetaste verwenden.\n\n## Anforderungen\n\n- tmux 3.5 oder höher für `extended-keys-format csi-u` (führen Sie `tmux -V` zur Überprüfung aus)\n- Ein Terminalemulator, der erweiterte Tasten unterstützt (Ghostty, Kitty, iTerm2, WezTerm, Windows Terminal)\n\nBei tmux 3.2 bis 3.4 `extended-keys-format csi-u` weglassen; Pi unterstützt weiterhin das Standardformat xterm `modifyOtherKeys` von tmux.","sourceFile":"tmux.md"},"tui":{"title":"TUI Komponenten","markdown":"> pi kann TUI Komponenten erstellen. Bitten Sie es, eines für Ihren Anwendungsfall zu erstellen.\n\n\nExtensions und benutzerdefinierte Tools können benutzerdefinierte TUI Komponenten für interaktive Benutzeroberflächen rendern. Auf dieser Seite werden das Komponentensystem und die verfügbaren Bausteine ​​behandelt.\n\n**Quelle:** [`@earendil-works/pi-tui`](https://github.com/earendil-works/pi-mono/tree/main/packages/tui)\n\n## Komponentenschnittstelle\n\nAlle Komponenten implementieren:\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| Verfahren | Beschreibung |\n|--------|-------------|\n| `render(width)` | Gibt ein Array von Zeichenfolgen zurück (eine pro Zeile). Jede Zeile **darf `width`** nicht überschreiten. |\n| `handleInput?(data)` | Empfangen Sie Tastatureingaben, wenn die Komponente den Fokus hat. |\n| `wantsKeyRelease?` | Wenn „true“, empfängt die Komponente Schlüsselfreigabeereignisse (Kitty-Protokoll). Standard: false. |\n| `invalidate()` | Zwischengespeicherten Renderstatus löschen. Bei Themenänderungen aufgerufen. |\n\nDie TUI fügt am Ende jeder gerenderten Zeile einen vollständigen SGR-Reset und einen OSC 8-Reset hinzu. Stile werden nicht über Zeilen hinweg übertragen. Wenn Sie mehrzeiligen Text mit Stil ausgeben, wenden Sie die Stile pro Zeile erneut an oder verwenden Sie `wrapTextWithAnsi()`, damit die Stile für jede umbrochene Zeile erhalten bleiben.\n\n## Fokussierbare Schnittstelle (IME-Unterstützung)\n\nKomponenten, die einen Textcursor anzeigen und IME-Unterstützung (Input Method Editor) benötigen, sollten die `Focusable`-Schnittstelle implementieren:\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\nWenn eine `Focusable`-Komponente den Fokus hat, TUI:\n1. Setzt `focused = true` für die Komponente\n2. Durchsucht die gerenderte Ausgabe nach `CURSOR_MARKER` (einer APC-Escape-Sequenz mit der Breite Null)\n3. Positioniert den Hardware-Terminal-Cursor an dieser Stelle\n4. Zeigt den Hardware-Cursor nur an, wenn `showHardwareCursor` aktiviert ist\n\nDer Cursor bleibt standardmäßig ausgeblendet. Dadurch bleibt die Wiedergabe des gefälschten Cursors erhalten, während der Hardware-Cursor weiterhin für Terminals positioniert wird, die IME-Kandidatenfenster mit versteckten Cursorn verfolgen. Einige Terminals erfordern einen sichtbaren Hardware-Cursor für die IME-Positionierung. Aktivieren Sie es mit `showHardwareCursor`, `setShowHardwareCursor(true)` oder `PI_HARDWARE_CURSOR=1`. Die eingebauten Komponenten `Editor` und `Input` implementieren diese Schnittstelle bereits.\n\n### Containerkomponenten mit eingebetteten Eingaben\n\nWenn eine Containerkomponente (Dialog, Selektor usw.) ein untergeordnetes Element vom Typ `Input` oder `Editor` enthält, muss der Container `Focusable` implementieren und den Fokusstatus an das untergeordnete Element weitergeben. Andernfalls wird der Hardware-Cursor für die IME-Eingabe nicht richtig positioniert.\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\nOhne diese Weitergabe wird bei der Eingabe mit einem IME (Chinesisch, Japanisch, Koreanisch usw.) das Kandidatenfenster an der falschen Position auf dem Bildschirm angezeigt.\n\n## Komponenten verwenden\n\n**In Erweiterungen** über `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**In benutzerdefinierten Tools** über `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## Überlagerungen\n\nOverlays rendern Komponenten über vorhandenen Inhalten, ohne den Bildschirm zu leeren. Übergeben Sie `{ overlay: true }` an `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\nVerwenden Sie zur Positionierung und Größenanpassung `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### Overlay-Fokus\n\nEin fokussiertes sichtbares Overlay behält die Eingabeverantwortung über die temporäre Nicht-Overlay-Benutzeroberfläche hinweg. Wenn ein Overlay eine andere `ctx.ui.custom()`-Komponente ohne `{ overlay: true }` öffnet, empfängt diese Ersatz-UI Eingaben, während sie aktiv ist; Wenn es geschlossen wird, kann das fokussierte Overlay Eingaben zurückfordern.\n\nVerwenden Sie `handle.unfocus()`, wenn ein sichtbares Overlay keine Eingaben mehr besitzen soll, und lassen Sie TUI auf ein anderes sichtbares Erfassungs-Overlay oder das vorherige Fokusziel zurückgreifen. Verwenden Sie `handle.unfocus({ target })`, wenn eine bestimmte Komponente Eingaben empfangen soll, während das Overlay sichtbar bleibt. Das Bestehen von `{ target: null }` lässt absichtlich keine fokussierte Komponente zurück, bis der Fokus erneut gesetzt wird.\n\n### Overlay-Lebenszyklus\n\nOverlay-Komponenten werden im geschlossenen Zustand entsorgt. Referenzen nicht wiederverwenden – neue Instanzen erstellen:\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\nUnter [overlay-qa-tests.ts](../examples/extensions/overlay-qa-tests.ts) finden Sie umfassende Beispiele zu Ankern, Rändern, Stapelung, reaktionsfähiger Sichtbarkeit und Animation.\n\n## Integrierte Komponenten\n\nImport aus `@earendil-works/pi-tui`:\n\n```typescript\nimport { Text, Box, Container, Spacer, Markdown } from \"@earendil-works/pi-tui\";\n```\n\n### Text\n\nMehrzeiliger Text mit Zeilenumbruch.\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### Kasten\n\nBehälter mit Polsterung und Hintergrundfarbe.\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### Container\n\nGruppiert untergeordnete Komponenten vertikal.\n\n```typescript\nconst container = new Container();\ncontainer.addChild(component1);\ncontainer.addChild(component2);\ncontainer.removeChild(component1);\n```\n\n### Abstandshalter\n\nLeerer vertikaler Raum.\n\n```typescript\nconst spacer = new Spacer(2);  // 2 empty lines\n```\n\n### Markdown\n\nRendert Markdown mit Syntaxhervorhebung.\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### Bild\n\nRendert Bilder in unterstützten Terminals (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## Tastatureingabe\n\nVerwenden Sie `matchesKey()` zur Schlüsselerkennung:\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**Schlüsselbezeichner** (verwenden Sie `Key.*` für die automatische Vervollständigung oder Zeichenfolgenliterale):\n- Grundtasten: `Key.enter`, `Key.escape`, `Key.tab`, `Key.space`, `Key.backspace`, `Key.delete`, `Key.home`, `Key.end`\n- Pfeiltasten: `Key.up`, `Key.down`, `Key.left`, `Key.right`\n- Mit Modifikatoren: `Key.ctrl(\"c\")`, `Key.shift(\"tab\")`, `Key.alt(\"left\")`, `Key.ctrlShift(\"p\")`\n- Das String-Format funktioniert auch: `\"enter\"`, `\"ctrl+c\"`, `\"shift+tab\"`, `\"ctrl+shift+p\"`\n\n## Linienbreite\n\n**Kritisch:** Jede Zeile ab `render()` darf den Parameter `width` nicht überschreiten.\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\nDienstprogramme:\n- `visibleWidth(str)` – Anzeigebreite abrufen (ignoriert ANSI-Codes)\n- `truncateToWidth(str, width, ellipsis?)` – Mit optionalen Auslassungspunkten abschneiden\n- `wrapTextWithAnsi(str, width)` – Zeilenumbruch unter Beibehaltung von ANSI-Codes\n\n## Erstellen benutzerdefinierter Komponenten\n\nBeispiel: Interaktiver Selektor\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\nVerwendung in einer Erweiterung:\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## Thematisierung\n\nKomponenten akzeptieren Designobjekte für die Gestaltung.\n\n**In `renderCall`/`renderResult`** verwenden Sie den Parameter `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**Vordergrundfarben** (`theme.fg(color, text)`):\n\n| Kategorie | Farben |\n|----------|--------|\n| Allgemein | `text`, `accent`, `muted`, `dim` |\n| Status | `success`, `error`, `warning` |\n| Grenzen | `border`, `borderAccent`, `borderMuted` |\n| Nachrichten | `userMessageText`, `customMessageText`, `customMessageLabel` |\n| Werkzeuge | `toolTitle`, `toolOutput` |\n| Unterschiede | `toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext` |\n| Markdown | `mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet` |\n| Syntax | `syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation` |\n| Denken | `thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `thinkingMax` |\n| Modi | `bashMode` |\n\n**Hintergrundfarben** (`theme.bg(color, text)`):\n\n`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`\n\n**Für Markdown** verwenden Sie `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**Für benutzerdefinierte Komponenten** definieren Sie Ihre eigene Designoberfläche:\n\n```typescript\ninterface MyTheme {\n  selected: (s: string) => string;\n  normal: (s: string) => string;\n}\n```\n\n## Debug-Protokollierung\n\nLegen Sie `PI_TUI_WRITE_LOG` fest, um den rohen ANSI-Stream zu erfassen, der in stdout geschrieben wird.\n\n```bash\nPI_TUI_WRITE_LOG=/tmp/tui-ansi.log npx tsx packages/tui/test/chat-simple.ts\n```\n\n## Leistung\n\nZwischenspeichern der gerenderten Ausgabe, wenn möglich:\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\nRufen Sie `invalidate()` auf, wenn sich der Status ändert, und verwenden Sie dann das injizierte `tui.requestRender()`, um ein erneutes Rendern auszulösen.\n\n## Ungültigmachung und Themenänderungen\n\nWenn sich das Thema ändert, ruft TUI `invalidate()` für alle Komponenten auf, um deren Caches zu leeren. Komponenten müssen `invalidate()` ordnungsgemäß implementieren, um sicherzustellen, dass Designänderungen wirksam werden.\n\n### Das Problem\n\nWenn eine Komponente Theme-Farben vorab in Strings speichert (über `theme.fg()`, `theme.bg()` usw.) und diese zwischenspeichert, enthalten die zwischengespeicherten Strings ANSI-Escape-Codes aus dem alten Theme. Das bloße Leeren des Rendercaches reicht nicht aus, wenn die Komponente den thematischen Inhalt separat speichert.\n\n**Falscher Ansatz** (Designfarben werden nicht aktualisiert):\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### Die Lösung\n\nKomponenten, die Inhalte mit Designfarben erstellen, müssen diesen Inhalt neu erstellen, wenn `invalidate()` aufgerufen wird:\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### Muster: Bei Invalidierung neu erstellen\n\nFür Komponenten mit komplexem Inhalt:\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### Wenn es darauf ankommt\n\nDieses Muster wird benötigt, wenn:\n\n1. **Designfarben vor dem Backen** – Verwenden Sie `theme.fg()` oder `theme.bg()`, um gestaltete Zeichenfolgen zu erstellen, die in untergeordneten Komponenten gespeichert sind\n2. **Syntaxhervorhebung** – Verwendung von `highlightCode()`, das themenbasierte Syntaxfarben anwendet\n3. **Komplexe Layouts** – Erstellen von untergeordneten Komponentenbäumen, die Themenfarben einbetten\n\nDieses Muster wird NICHT benötigt, wenn:\n\n1. **Themenrückrufe verwenden** – Übergeben von Funktionen wie `(text) => theme.fg(\"accent\", text)`, die während des Renderns aufgerufen werden\n2. **Einfache Container** – Nur andere Komponenten gruppieren, ohne thematische Inhalte hinzuzufügen\n3. **Zustandsloses Rendern** – Die thematische Ausgabe wird bei jedem `render()`-Aufruf frisch berechnet (kein Caching)\n\n## Gemeinsame Muster\n\nDiese Muster decken die häufigsten UI-Anforderungen in Erweiterungen ab. **Kopieren Sie diese Muster, anstatt sie von Grund auf neu zu erstellen.**\n\n### Muster 1: Auswahldialog (SelectList)\n\nDamit Benutzer aus einer Liste von Optionen auswählen können. Verwenden Sie `SelectList` von `@earendil-works/pi-tui` mit `DynamicBorder` zum Einrahmen.\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**Beispiele:** [preset.ts](../examples/extensions/preset.ts), [tools.ts](../examples/extensions/tools.ts)\n\n### Muster 2: Asynchroner Vorgang mit Abbrechen (BorderedLoader)\n\nFür Vorgänge, die Zeit in Anspruch nehmen und stornierbar sein sollen. `BorderedLoader` zeigt einen Spinner und verarbeitet Escape zum Abbrechen.\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**Beispiele:** [qna.ts](../examples/extensions/qna.ts), [handoff.ts](../examples/extensions/handoff.ts)\n\n### Muster 3: Einstellungen/Umschaltungen (SettingsList)\n\nZum Umschalten mehrerer Einstellungen. Verwenden Sie `SettingsList` von `@earendil-works/pi-tui` mit `getSettingsListTheme()`.\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**Beispiele:** [tools.ts](../examples/extensions/tools.ts)\n\n### Muster 4: Permanente Statusanzeige\n\nZeigt den Status in der Fußzeile an, der über alle Renderings hinweg bestehen bleibt. Gut für Modusanzeigen.\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**Beispiele:** [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### Muster 4b: Anpassung des Arbeitsindikators\n\nPassen Sie die Inline-Arbeitsanzeige an, die angezeigt wird, während Pi eine Antwort streamt.\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\nDies betrifft nur die normale Streaming-Arbeitsanzeige. Komprimierungs- und Wiederholungslader behalten ihr integriertes Design. Benutzerdefinierte Rahmen werden wörtlich gerendert, sodass Erweiterungen bei Bedarf ihre eigenen Farben hinzufügen müssen.\n\n**Beispiele:** [working-indicator.ts](../examples/extensions/working-indicator.ts)\n\n### Muster 5: Widgets über/unter dem Editor\n\nZeigen Sie persistenten Inhalt über oder unter dem Eingabeeditor an. Gut für To-do-Listen, Fortschritt.\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**Beispiele:** [plan-mode/index.ts](../examples/extensions/plan-mode/index.ts)\n\n### Muster 6: Benutzerdefinierte Fußzeile\n\nErsetzen Sie die Fußzeile. `footerData` macht Daten verfügbar, auf die Erweiterungen ansonsten nicht zugreifen können.\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\nToken-Statistiken verfügbar über `ctx.sessionManager.getBranch()` und `ctx.model`.\n\n**Beispiele:** [custom-footer.ts](../examples/extensions/custom-footer.ts)\n\n### Muster 7: Benutzerdefinierter Editor (VIM-Modus usw.)\n\nErsetzen Sie den Haupteingabeeditor durch eine benutzerdefinierte Implementierung. Nützlich für modale Bearbeitung (vim), verschiedene Tastenkombinationen (emacs) oder spezielle Eingabeverarbeitung.\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**Wichtige Punkte:**\n\n- **Erweitern Sie `CustomEditor`** (nicht Basis `Editor`), um App-Tastenkombinationen zu erhalten (Escape zum Abbrechen, Strg+D zum Beenden, Modellwechsel usw.)\n- **Rufen Sie `super.handleInput(data)`** an, wenn Sie Schlüssel benötigen, die Sie nicht verwalten\n- **Factory-Muster**: `setEditorComponent` empfängt eine Factory-Funktion, die `tui`, `theme` und `keybindings` erhält\n- **Übergeben Sie `undefined`**, um den Standardeditor wiederherzustellen: `ctx.ui.setEditorComponent(undefined)`\n\n**Beispiele:** [modal-editor.ts](../examples/extensions/modal-editor.ts)\n\n## Schlüsselregeln\n\n1. **Theme immer aus Rückruf verwenden** – Theme nicht direkt importieren. Verwenden Sie `theme` aus dem `ctx.ui.custom((tui, theme, keybindings, done) =>...)`-Rückruf.\n\n2. **Geben Sie immer den DynamicBorder-Farbparameter ein** – Schreiben Sie `(s: string) => theme.fg(\"accent\", s)`, nicht `(s) => theme.fg(\"accent\", s)`.\n\n3. **Tui.requestRender() nach Statusänderungen aufrufen** – Rufen Sie in `handleInput` `tui.requestRender()` auf, nachdem Sie den Status aktualisiert haben.\n\n4. **Gibt das Drei-Methoden-Objekt zurück** – Benutzerdefinierte Komponenten benötigen `{ render, invalidate, handleInput }`.\n\n5. **Vorhandene Komponenten nutzen** – `SelectList`, `SettingsList`, `BorderedLoader` decken 90 % der Fälle ab. Bauen Sie sie nicht wieder auf.\n\n## Beispiele\n\n- **Auswahl-Benutzeroberfläche**: [examples/extensions/preset.ts](../examples/extensions/preset.ts) – SelectList mit DynamicBorder-Rahmen\n- **Asynchron mit Abbrechen**: [examples/extensions/qna.ts](../examples/extensions/qna.ts) – BorderedLoader für LLM-Aufrufe\n- **Einstellungen umschalten**: [examples/extensions/tools.ts](../examples/extensions/tools.ts) – Einstellungsliste zum Aktivieren/Deaktivieren des Tools\n- **Statusanzeigen**: [examples/extensions/plan-mode/index.ts](../examples/extensions/plan-mode/index.ts) – setStatus und setWidget\n- **Arbeitsindikator**: [examples/extensions/working-indicator.ts](../examples/extensions/working-indicator.ts) – setWorkingIndicator\n- **Benutzerdefinierte Fußzeile**: [examples/extensions/custom-footer.ts](../examples/extensions/custom-footer.ts) – setFooter mit Statistiken\n- **Benutzerdefinierter Editor**: [examples/extensions/modal-editor.ts](../examples/extensions/modal-editor.ts) – Vim-ähnliche modale Bearbeitung\n- **Schlangenspiel**: [examples/extensions/snake.ts](../examples/extensions/snake.ts) – Vollständiges Spiel mit Tastatureingabe, Spielschleife\n- **Benutzerdefiniertes Tool-Rendering**: [examples/extensions/todo.ts](../examples/extensions/todo.ts) – renderCall und renderResult","sourceFile":"tui.md"},"usage":{"title":"Verwendung von Pi","markdown":"Auf dieser Seite werden alltägliche Nutzungsdetails erfasst, die nicht auf die Schnellstartseite passen.\n\n## Interaktiver Modus\n\n<p align=\"center\"><img src=\"images/interactive-mode.png\" alt=\"Interactive Mode\" width=\"600\"></p>\n\nDie Schnittstelle besteht aus vier Hauptbereichen:\n\n- **Startup-Header** – Verknüpfungen, geladene context files, prompt templates, Fertigkeiten und Erweiterungen\n- **Nachrichten** – Benutzernachrichten, Assistentenantworten, Toolaufrufe, Toolergebnisse, Benachrichtigungen, Fehler und Erweiterungs-Benutzeroberfläche\n- **Editor** – wo Sie tippen; Die Randfarbe zeigt die aktuelle Denkebene an\n- **Fußzeile** – Arbeitsverzeichnis, Sitzungsname, Token-/Cache-Nutzung, Kosten, Kontextnutzung und aktuelles Modell. Die Gesamtwerte umfassen Assistentenantworten, von Tools gemeldete Nutzung und Erstellung von Zusammenfassungen.\n\nDer Editor kann vorübergehend durch eine integrierte Benutzeroberfläche wie `/settings` oder durch eine benutzerdefinierte Erweiterungs-Benutzeroberfläche ersetzt werden.\n\n### Editorfunktionen\n\n| Besonderheit | Wie |\n|---------|-----|\n| Dateireferenz | Geben Sie `@` ein, um eine Fuzzy-Suche nach Projektdateien durchzuführen |\n| Pfadvervollständigung | Drücken Sie die Tabulatortaste, um Pfade zu vervollständigen |\n| Mehrzeilige Eingabe | Umschalt+Eingabetaste oder Strg+Eingabetaste auf dem Windows-Terminal |\n| Antwort kopieren | Strg+X kopiert die letzte Assistentennachricht; in `/tree` kopiert es die ausgewählte Nachricht |\n| Bilder | Mit Strg+V bzw. Alt+V unter Windows einfügen oder in das Terminal ziehen |\n| Shell-Befehl | `!command` wird ausgeführt und sendet die Ausgabe an das Modell |\n| Versteckter Shell-Befehl | `!!command` wird ausgeführt, ohne dass eine Ausgabe an das Modell gesendet wird |\n| Externer Redakteur | Strg+G öffnet `externalEditor`, `$VISUAL`, `$EDITOR`, Notepad unter Windows oder `nano` anderswo |\n\nSiehe [Keybindings](keybindings.md) für alle Verknüpfungen und Anpassungen.\n\n## Slash-Befehle\n\nGeben Sie `/` in den Editor ein, um die Befehlsvervollständigung zu öffnen. Extensions kann benutzerdefinierte Befehle registrieren, Fähigkeiten sind als `/skill:name` verfügbar und prompt templates kann über `/templatename` erweitert werden.\n\n| Befehl | Beschreibung |\n|---------|-------------|\n| `/login`, `/logout` | Verwalten Sie Anmeldeinformationen mit OAuth- oder API-Schlüsseln |\n| [`/llama`](llama-cpp.md) | Laden Sie llama.cpp Router-Modelle herunter, laden und entladen Sie sie |\n| `/model` | Modelle wechseln |\n| `/scoped-models` | Modelle für den Strg+P-Wechsel aktivieren/deaktivieren |\n| `/settings` | Denkebene, Thema, Nachrichtenübermittlung, Transport |\n| `/resume` | PiZurück aus früheren Sitzungen |\n| `/new` | Starten Sie eine neue Sitzung |\n| `/name <name>` | Legen Sie den Anzeigenamen der Sitzung fest |\n| `/session` | Sitzungsdatei, ID, Nachrichten, Token und Kosten anzeigen |\n| `/tree` | Springen Sie zu einem beliebigen Punkt in der Sitzung und fahren Sie von dort aus fort |\n| `/trust` | Speichern Sie die Projektvertrauensentscheidung für zukünftige Sitzungen |\n| `/fork` | Erstellen Sie eine neue Sitzung aus einer vorherigen Benutzernachricht |\n| `/clone` | Duplizieren Sie den aktuell aktiven Zweig in eine neue Sitzung |\n| `/compact [prompt]` | Kontext manuell verdichten, optional mit benutzerdefinierten Anweisungen |\n| `/copy` | Kopieren Sie die letzte Assistentennachricht in die Zwischenablage |\n| `/export [file]` | Sitzung nach HTML oder JSONL exportieren |\n| `/import <file>` | Importieren Sie eine Sitzung aus einer JSONL-Datei und setzen Sie sie fort |\n| `/share` | Als privates GitHub Gist mit gemeinsam nutzbarem HTML-Link hochladen |\n| `/reload` | Tastenkombinationen, Erweiterungen, Fertigkeiten, Eingabeaufforderungen, Themen und context files neu laden |\n| `/hotkeys` | Alle Tastaturkürzel anzeigen |\n| `/changelog` | Versionsverlauf anzeigen |\n| `/quit` | Beenden Sie Pi |\n\n## Nachrichtenwarteschlange\n\nSie können Nachrichten senden, während der Agent noch arbeitet:\n\n- **Enter** stellt eine Steuerungsnachricht in die Warteschlange, die zugestellt wird, nachdem der aktuelle Assistent an der Reihe ist und die Ausführung seiner Tool-Aufrufe abgeschlossen hat.\n- **Alt+Enter** stellt eine Folgenachricht in die Warteschlange, die zugestellt wird, nachdem der Agent alle Arbeiten abgeschlossen hat.\n- **Escape** bricht Nachrichten in der Warteschlange ab und stellt sie im Editor wieder her.\n- **Alt+Up** ruft Nachrichten in der Warteschlange zurück zum Editor.\n\nAuf Windows Terminal ist Alt+Enter standardmäßig im Vollbildmodus. Ordnen Sie es wie in [Terminal setup](terminal-setup.md) beschrieben neu zu, wenn Sie möchten, dass Pi die Verknüpfung erhält.\n\nKonfigurieren Sie die Lieferung in [Settings](settings.md) mit `steeringMode` und `followUpMode`.\n\n## Sitzungen\n\nSitzungen werden automatisch unter `~/.pi/agent/sessions/` gespeichert und nach Arbeitsverzeichnis geordnet.\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\nNützliche Sitzungsbefehle:\n\n- `/session` zeigt die aktuelle Sitzungsdatei und ID an.\n- `/tree` navigiert durch die In-Datei session tree und kann verlassene Zweige zusammenfassen.\n- `/fork` erstellt eine neue Sitzung aus einer früheren Benutzernachricht.\n- `/clone` dupliziert den aktuell aktiven Zweig in eine neue Sitzungsdatei.\n- `/compact` fasst ältere Nachrichten im freien Kontext zusammen.\n\nEinzelheiten finden Sie unter [Sessions](sessions.md) und [Compaction](compaction.md).\n\n## Kontextdateien\n\nPi lädt `AGENTS.md` oder `CLAUDE.md` beim Start von:\n\n- `~/.pi/agent/AGENTS.md` für globale Anweisungen\n- übergeordnete Verzeichnisse, ausgehend vom aktuellen Arbeitsverzeichnis\n- das aktuelle Verzeichnis\n\nWenn ein Verzeichnis `AGENTS.override.md` enthält, lädt Pi es anstelle von `AGENTS.md` oder `CLAUDE.md` aus diesem Verzeichnis. Kontextdateien aus anderen Verzeichnissen überlagern sich weiterhin normal.\n\nVerwenden Sie context files für Projektkonventionen, Befehle, Sicherheitsregeln und Präferenzen. Deaktivieren Sie das Laden mit `--no-context-files` oder `-nc`.\n\n### System-Eingabeaufforderungsdateien\n\nErsetzen Sie die Standard-Systemaufforderung durch:\n\n- `.pi/SYSTEM.md` für ein Projekt\n- `~/.pi/agent/SYSTEM.md` weltweit\n\nAn die Standardeingabeaufforderung anhängen, ohne sie an einer der Stellen durch `APPEND_SYSTEM.md` zu ersetzen.\n\n### Projektvertrauen\n\nBeim interaktiven Start fragt pi nach, bevor es einem Projektordner vertraut, der projektlokale Einstellungen, Ressourcen oder Projekt `.agents/skills` enthält und keine gespeicherte Entscheidung für den Ordner oder einen übergeordneten Ordner in `~/.pi/agent/trust.json` hat. Durch das Vertrauen in ein Projekt kann Pi `.pi/settings.json`- und `.pi`-Ressourcen laden, fehlende Projektpakete installieren und Projekterweiterungen ausführen.\n\nVor der Vertrauensentscheidung lädt pi nur context files, Benutzer-/globale Erweiterungen und CLI `-e`-Erweiterungen, damit sie das `project_trust`-Ereignis verarbeiten können. Projektlokale Erweiterungen, vom Projektpaket verwaltete Erweiterungen und Projekteinstellungen werden erst geladen, nachdem das Projekt vertrauenswürdig ist. Diese Aufteilung gilt auch beim Wechsel zu einer Sitzung von einem anderen CWD, dessen Vertrauen im aktuellen Prozess nicht aufgelöst wurde.\n\nIn den nicht interaktiven Modi (`-p`, `--mode json` und `--mode rpc`) wird keine Vertrauensaufforderung angezeigt. Ohne eine anwendbare gespeicherte Vertrauensentscheidung verwenden sie `defaultProjectTrust` aus den globalen Einstellungen: `ask` (Standard) und `never` ignorieren diese Projektressourcen, während `always` ihnen vertraut. Übergeben Sie `--approve`/`-a` oder `--no-approve`/`-na`, um die Projektvertrauenswürdigkeit für einen Lauf zu überschreiben.\n\nWenn keine Erweiterung oder gespeicherte Entscheidung gilt, steuert `defaultProjectTrust` das Fallback-Verhalten. Stellen Sie es auf `\"ask\"`, `\"always\"` oder `\"never\"` in `~/.pi/agent/settings.json` ein oder ändern Sie es mit `/settings`.\n\n`pi config`- ​​und Paketbefehle verwenden denselben Projekt-Vertrauensfluss, mit der Ausnahme, dass `pi update` nie dazu auffordert. Übergeben Sie `--approve`, um projektlokalen Einstellungen für einen Befehl zu vertrauen, oder `--no-approve`, um sie zu ignorieren.\n\nVerwenden Sie `/trust` im interaktiven Modus, um eine Projektvertrauensentscheidung für zukünftige Sitzungen zu speichern, einschließlich der Vertrauenswürdigkeit für den unmittelbar übergeordneten Ordner. Es schreibt nur `~/.pi/agent/trust.json`; Die aktuelle Sitzung wird nicht neu geladen. Starten Sie daher pi neu, damit die Änderungen wirksam werden.\n\n\n## Sitzungen exportieren und teilen\n\nVerwenden Sie `/export [file]`, um eine Sitzung in HTML zu schreiben.\n\nVerwenden Sie `/share`, um einen privaten GitHub-Inhalt mit einem gemeinsam nutzbaren HTML-Link hochzuladen.\n\nWenn Sie Pi für Open-Source-Arbeiten verwenden und Sitzungen für Modell-, Eingabeaufforderungs-, Tool- und Evaluierungsforschung veröffentlichen möchten, lesen Sie [`badlogic/pi-share-hf`](https://github.com/badlogic/pi-share-hf). Es veröffentlicht Sitzungen für Hugging Face Datensätze.\n\n## CLI Referenz\n\n```bash\npi [options] [@files...] [messages...]\n```\n\n### Paketbefehle\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\nDiese Befehle verwalten Pi-Pakete und `pi update` kann die Pi CLI-Installation aktualisieren. Informationen zur Deinstallation von Pi selbst finden Sie unter [Quickstart](quickstart.md#uninstall). `pi config` und Projektpaketbefehle akzeptieren `--approve`/`--no-approve`, um projektlokalen Einstellungen für einen Befehl zu vertrauen oder sie zu ignorieren. `pi update` fordert niemals zur Projektvertrauensstellung auf.\n\nSiehe [Pi Packages](packages.md) für Paketquellen und Sicherheitshinweise.\n\n### Modi\n\n| Flagge | Beschreibung |\n|------|-------------|\n| Standard | Interaktiver Modus |\n| `-p`, `--print` | Antwort drucken und beenden |\n| `--mode json` | Alle Ereignisse als JSON Zeilen ausgeben; siehe [JSON mode](json.md) |\n| `--mode rpc` | RPC Modus über stdin/stdout; siehe [RPC mode](rpc.md) |\n| `--export <in> [out]` | Exportieren Sie eine Sitzung nach HTML |\n\nIm Druckmodus liest pi auch die weitergeleitete stdin und fügt sie in die anfängliche Eingabeaufforderung ein:\n\n```bash\ncat README.md | pi -p \"Summarize this text\"\n```\n\n### Modelloptionen\n\n| Option | Beschreibung |\n|--------|-------------|\n| `--provider <name>` | Anbieter, z. B. `anthropic`, `openai` oder `google` |\n| `--model <pattern>` | Modellmuster oder ID; unterstützt `provider/id` und optional `:<thinking>` |\n| `--api-key <key>` | API key, überschreibt Umgebungsvariablen |\n| `--thinking <level>` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max` |\n| `--models <patterns>` | Durch Kommas getrennte Muster für den Strg+P-Wechsel |\n| `--list-models [search]` | Verfügbare Modelle auflisten |\n\n### Sitzungsoptionen\n\n| Option | Beschreibung |\n|--------|-------------|\n| `-c`, `--continue` | Setzen Sie die letzte Sitzung fort |\n| `-r`, `--resume` | Durchsuchen Sie eine Sitzung und wählen Sie sie aus |\n| `--session <Pfad\\ | id>` | Verwenden Sie eine bestimmte Sitzungsdatei oder eine Teil-UUID |\n| `--fork <Pfad\\ | id>` | Verzweigen Sie eine Sitzungsdatei oder eine Teil-UUID in eine neue Sitzung |\n| `--session-dir <dir>` | Benutzerdefiniertes Sitzungsspeicherverzeichnis |\n| `--no-session` | Ephemerer Modus; nicht speichern |\n| `--name <name>`, `-n <name>` | Legen Sie den Anzeigenamen der Sitzung beim Start fest |\n\n### Werkzeugoptionen\n\n| Option | Beschreibung |\n|--------|-------------|\n| `--tools <list>`, `-t <list>` | Bestimmte integrierte Tools, Erweiterungen und benutzerdefinierte Tools werden auf die Zulassungsliste gesetzt |\n| `--exclude-tools <list>`, `-xt <list>` | Deaktivieren Sie bestimmte integrierte Tools, Erweiterungen und benutzerdefinierte Tools |\n| `--no-builtin-tools`, `-nbt` | Deaktivieren Sie integrierte Tools, lassen Sie jedoch Erweiterungs-/benutzerdefinierte Tools aktiviert |\n| `--no-tools`, `-nt` | Deaktivieren Sie alle Tools |\n\nIntegrierte Werkzeuge: `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`.\n\n### Ressourcenoptionen\n\n| Option | Beschreibung |\n|--------|-------------|\n| `-e`, `--extension <source>` | Laden Sie eine Erweiterung von path, npm oder git; wiederholbar |\n| `--no-extensions` | Deaktivieren Sie die Erweiterungserkennung |\n| `--skill <path>` | Laden Sie eine Fertigkeit; wiederholbar |\n| `--no-skills` | Deaktivieren Sie die Fähigkeitserkennung |\n| `--prompt-template <path>` | Laden Sie eine Eingabeaufforderungsvorlage. wiederholbar |\n| `--no-prompt-templates` | Deaktivieren Sie die Erkennung von Eingabeaufforderungsvorlagen |\n| `--theme <path>` | Laden Sie ein Thema; wiederholbar |\n| `--no-themes` | Deaktivieren Sie die Themenerkennung |\n| `--no-context-files`, `-nc` | Deaktivieren Sie die Erkennung von `AGENTS.md` und `CLAUDE.md` |\n\nKombinieren Sie `--no-*` mit expliziten Flags, um genau das zu laden, was Sie benötigen, und ignorieren Sie dabei die Einstellungen. Beispiel:\n\n```bash\npi --no-extensions -e ./my-extension.ts\n```\n\n### Andere Optionen\n\n| Option | Beschreibung |\n|--------|-------------|\n| `--system-prompt <text>` | Standard-Eingabeaufforderung ersetzen; context files und Fähigkeiten werden weiterhin angehängt |\n| `--append-system-prompt <text>` | An Systemaufforderung anhängen |\n| `--tui-mode <mode>` | TUI Modus: `regular` (Standard) oder experimentell `fullscreen` |\n| `--verbose` | Erzwingen Sie einen ausführlichen Start |\n| `-a`, `--approve` | Vertrauen Sie für diese Ausführung projektlokalen Dateien |\n| `-na`, `--no-approve` | Projektlokale Dateien für diesen Lauf ignorieren |\n| `-h`, `--help` | Hilfe anzeigen |\n| `-v`, `--version` | Version anzeigen |\n\nIm `fullscreen`-Modus scrollt das Transkript im Terminal-Ansichtsfenster, während Nachrichten in der Warteschlange, Arbeitsstatus, Erweiterungs-Widgets, Editor und Fußzeile unten fixiert bleiben. Durch die Maus-/Trackpad-Eingabe wird der Bereich unter dem Zeiger gescrollt; Aktionen im Tastatur-Ansichtsfenster bleiben immer verfügbar. Inline-Bilder funktionieren in Terminals, die das Kitty-Grafikprotokoll unterstützen, einschließlich Kitty und Ghostty. In iTerm2 werden sie als Textplatzhalter gerendert, da das Inline-Image-Protokoll Platzierungen während des anwendungseigenen Scrollens nicht löschen oder zuschneiden kann. Im `regular`-Modus verwendet pi den Hauptbildschirm und den terminaleigenen Scrollback, und iTerm2-Inline-Bilder werden weiterhin normal gerendert.\n\nStellen Sie den **TUI-Modus** in `/settings` ein, um sofort zwischen `regular` und `fullscreen` zu wechseln und die Standardeinstellung für zukünftige Sitzungen auszuwählen. **Vollbild-Exit-Ausgabe** steuert, ob beim Beenden des Vollbildmodus das endgültige Transkript gedruckt wird oder der vorherige Bildschirm wiederhergestellt wird und nur der Hinweis zur Sitzungsfortsetzung gedruckt wird.\n\n### Dateiargumente\n\nStellen Sie den Dateien `@` voran, um sie in die Nachricht einzuschließen:\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### Beispiele\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## Designprinzipien\n\nPi hält den Kern klein und verschiebt Workflow-spezifisches Verhalten in Erweiterungen, Fähigkeiten, prompt templates und Pakete.\n\nEs enthält absichtlich keine integrierten MCP, Subagenten, Berechtigungs-Popups, Planmodus, Aufgaben oder Hintergrund bash. Sie können diese Workflows als Erweiterungen oder Pakete erstellen oder installieren oder externe Tools wie Container und tmux verwenden.\n\nDie vollständige Begründung finden Sie im [blog post](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/).","sourceFile":"usage.md"},"windows":{"title":"Windows-Setup","markdown":"Pi erfordert eine bash-Shell unter Windows. Überprüfte Standorte (in der Reihenfolge):\n\n1. Benutzerdefinierter Pfad von `~/.pi/agent/settings.json`\n2. Git Bash (`C:\\Program Files\\Git\\bin\\bash.exe`)\n3. `bash.exe` auf PATH (Cygwin, MSYS2, WSL)\n\nFür die meisten Benutzer ist [Git for Windows](https://git-scm.com/download/win) ausreichend.\n\n## Benutzerdefinierter Shell-Pfad\n\n```json\n{\n  \"shellPath\": \"C:\\\\cygwin64\\\\bin\\\\bash.exe\"\n}\n```","sourceFile":"windows.md"}}},"navigation":{"de":[{"title":"Hier beginnen","items":[{"title":"Pi Dokumentation","path":"/docs/latest","slug":"index"},{"title":"Schnellstart","path":"/docs/latest/quickstart","slug":"quickstart"},{"title":"Verwendung von Pi","path":"/docs/latest/usage","slug":"usage"},{"title":"Providers","path":"/docs/latest/providers","slug":"providers"},{"title":"Sicherheit","path":"/docs/latest/security","slug":"security"},{"title":"Containerisierung","path":"/docs/latest/containerization","slug":"containerization"},{"title":"Einstellungen","path":"/docs/latest/settings","slug":"settings"},{"title":"Tastenkombinationen","path":"/docs/latest/keybindings","slug":"keybindings"},{"title":"Sitzungen","path":"/docs/latest/sessions","slug":"sessions"},{"title":"Komprimierung und Zweigzusammenfassung","path":"/docs/latest/compaction","slug":"compaction"}]},{"title":"Anpassung","items":[{"title":"Extensions","path":"/docs/latest/extensions","slug":"extensions"},{"title":"Skills","path":"/docs/latest/skills","slug":"skills"},{"title":"Eingabeaufforderungsvorlagen","path":"/docs/latest/prompt-templates","slug":"prompt-templates"},{"title":"Themen","path":"/docs/latest/themes","slug":"themes"},{"title":"Pi Packages","path":"/docs/latest/packages","slug":"packages"},{"title":"Benutzerdefiniert Models","path":"/docs/latest/models","slug":"models"},{"title":"Benutzerdefiniert Providers","path":"/docs/latest/custom-provider","slug":"custom-provider"}]},{"title":"Referenz","items":[{"title":"Sitzungsdateiformat","path":"/docs/latest/session-format","slug":"session-format"}]},{"title":"Programmgesteuerte Nutzung","items":[{"title":"SDK","path":"/docs/latest/sdk","slug":"sdk"},{"title":"RPC Modus","path":"/docs/latest/rpc","slug":"rpc"},{"title":"JSON Ereignis-Stream-Modus","path":"/docs/latest/json","slug":"json"},{"title":"TUI Komponenten","path":"/docs/latest/tui","slug":"tui"}]},{"title":"Plattform-Einrichtung","items":[{"title":"Windows-Setup","path":"/docs/latest/windows","slug":"windows"},{"title":"Termux (Android) Setup","path":"/docs/latest/termux","slug":"termux"},{"title":"tmux Einrichtung","path":"/docs/latest/tmux","slug":"tmux"},{"title":"Terminal-Setup","path":"/docs/latest/terminal-setup","slug":"terminal-setup"},{"title":"Shell-Aliase","path":"/docs/latest/shell-aliases","slug":"shell-aliases"}]},{"title":"Entwicklung","items":[{"title":"Entwicklung","path":"/docs/latest/development","slug":"development"}]}]}}
