{"locale":"pt","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":{"pt":{"compaction":{"title":"Compactação e Resumo de Filiais","markdown":"LLMs têm janelas de contexto limitadas. Quando as conversas ficam muito longas, Pi usa a compactação para resumir o conteúdo mais antigo, preservando o trabalho recente. Esta página cobre compactação automática e branch summarization.\n\n**Arquivos de origem** ([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) - Lógica de compactação automática\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) - Resumo da filial\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) - Utilitários compartilhados (rastreamento de arquivos, serialização)\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) - Tipos de entrada (`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) - Tipos de eventos de extensão\n\nPara definições de TypeScript em seu projeto, inspecione `node_modules/@earendil-works/pi-coding-agent/dist/`.\n\n## Visão geral\n\nPi possui dois mecanismos de resumo:\n\n| Mecanismo | Acionar | Propósito |\n|-----------|---------|---------|\n| Compactação | O contexto excede o limite ou `/compact` | Resuma mensagens antigas para liberar contexto |\n| Resumo da filial | `/tree` navegação | Preservar o contexto ao mudar de ramificação |\n\nAmbos usam o mesmo formato de resumo estruturado e rastreiam operações de arquivo cumulativamente. Solicitações de compactação e resumo de ramificação usam novos IDs de sessão de roteamento e, quando suportados pelo provedor, desativam gravações de cache de prompt porque é improvável que esses prompts únicos sejam reutilizados.\n\n## Compactação\n\n### Quando isso desencadeia\n\nA compactação automática é acionada quando:\n\n```\ncontextTokens > contextWindow - reserveTokens\n```\n\nPor padrão, `reserveTokens` são 16384 tokens (configuráveis ​​em `~/.pi/agent/settings.json` ou `<project-dir>/.pi/settings.json`). Isto deixa espaço para a resposta do LLM.\n\nVocê também pode acionar manualmente com `/compact [instructions]`, onde instruções opcionais concentram o resumo.\n\n### Como funciona\n\n1. **Encontrar ponto de corte**: retrocede a partir da mensagem mais recente, acumulando estimativas de token até que `keepRecentTokens` (padrão 20k, configurável em `~/.pi/agent/settings.json` ou `<project-dir>/.pi/settings.json`) seja alcançado\n2. **Extrair mensagens**: Colete mensagens do limite mantido anteriormente (ou início da sessão) até o ponto de corte\n3. **Gerar resumo**: Chame o LLM para resumir com formato estruturado, passando o resumo anterior como contexto iterativo quando presente\n4. **Anexar entrada**: Salve `CompactionEntry` com resumo e `firstKeptEntryId`\n5. **Recarregar**: A sessão é recarregada, usando resumo + mensagens de `firstKeptEntryId` em diante\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\nEm compactações repetidas, o vão resumido começa no limite mantido da compactação anterior (`firstKeptEntryId`), e não na entrada de compactação em si, voltando para a entrada após a compactação anterior se essa entrada mantida não puder ser encontrada no caminho. Isso preserva as mensagens que sobreviveram à compactação anterior, incluindo-as também na próxima passagem de resumo. Pi também recalcula `tokensBefore` a partir do contexto da sessão reconstruída antes de escrever o novo `CompactionEntry`, portanto, a contagem de tokens reflete o contexto real de pré-compactação que está sendo substituído.\n\n### Turnos Divididos\n\nUm \"turno\" começa com uma mensagem do usuário e inclui todas as respostas do assistente e chamadas de ferramentas até a próxima mensagem do usuário. Normalmente, a compactação corta nos limites das curvas.\n\nQuando uma única curva excede `keepRecentTokens`, o ponto de corte chega no meio da curva em uma mensagem do assistente. Esta é uma \"virada dividida\":\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\nPara turnos divididos, Pi gera dois resumos e os mescla:\n1. **Resumo do histórico**: contexto anterior (se houver)\n2. **Resumo do prefixo de curva**: a parte inicial da curva dividida\n\n### Regras de ponto de corte\n\nOs pontos de corte válidos são:\n- Mensagens do usuário\n- Mensagens do assistente\n- Mensagens BashExecution\n- Mensagens personalizadas (custom_message, branch_summary)\n\nNunca corte nos resultados da ferramenta (eles devem permanecer com a chamada da ferramenta).\n\n### Estrutura de entrada de compactação\n\nDefinido em [`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 pode armazenar qualquer dado serializável JSON em `details`. A compactação padrão rastreia operações de arquivo, mas implementações de extensões personalizadas podem usar sua própria estrutura. Os resumos gerados e fornecidos pela extensão armazenam seu LLM `usage` quando disponível, de modo que os totais das sessões incluam o trabalho de resumo.\n\nVeja [`prepareCompaction()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/compaction.ts) e [`compact()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/compaction.ts) para a implementação. Para resumo programático direto, `generateSummary()` retorna o texto do resumo e `generateSummaryWithUsage()` retorna `{ text, usage }`.\n\n## Resumo de Filiais\n\n### Quando isso desencadeia\n\nQuando você usa `/tree` para navegar para um branch diferente, Pi se oferece para resumir o trabalho que você está deixando. Isso injeta o contexto do branch esquerdo no novo branch.\n\n### Como funciona\n\n1. **Encontrar ancestral comum**: nó mais profundo compartilhado por posições antigas e novas\n2. **Coletar entradas**: caminhar da folha antiga até o ancestral comum\n3. **Prepare-se com orçamento**: inclua mensagens até o orçamento simbólico (as mais recentes primeiro)\n4. **Gerar resumo**: Ligue para LLM com formato estruturado\n5. **Anexar entrada**: Salve `BranchSummaryEntry` no ponto de navegação\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### Rastreamento cumulativo de arquivos\n\nTanto a compactação quanto o branch summarization rastreiam arquivos cumulativamente. Ao gerar um resumo, pi extrai operações de arquivo de:\n- Chamadas de ferramentas nas mensagens sendo resumidas\n- Compactação anterior ou resumo de ramificação `details` (se houver)\n\nIsso significa que o rastreamento de arquivos se acumula em diversas compactações ou resumos de ramificações aninhadas, preservando o histórico completo de arquivos lidos e modificados.\n\n### Estrutura BranchSummaryEntry\n\nDefinido em [`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\nAssim como a compactação, as extensões podem armazenar dados personalizados em `details`.\n\nConsulte [`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) e [`generateBranchSummary()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/branch-summarization.ts) para a implementação.\n\n## Formato de resumo\n\nTanto a compactação quanto o branch summarization usam o mesmo formato estruturado:\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### Serialização de mensagens\n\nAntes do resumo, as mensagens são serializadas em texto via [`serializeConversation()`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/compaction/utils.ts):\n\n```\n[User]: What they said\n[Assistant thinking]: Internal reasoning\n[Assistant]: Response text\n[Assistant tool calls]: read(path=\"foo.ts\"); edit(path=\"bar.ts\", ...)\n[Tool result]: Output from tool\n```\n\nIsso evita que o modelo trate isso como uma conversa para continuar.\n\nOs resultados da ferramenta são truncados para 2.000 caracteres durante a serialização. O conteúdo além desse limite é substituído por um marcador que indica quantos caracteres foram truncados. Isso mantém as solicitações de resumo dentro de orçamentos de tokens razoáveis, uma vez que os resultados da ferramenta (especialmente de `read` e `bash`) são normalmente os que mais contribuem para o tamanho do contexto.\n\n## Resumo personalizado via Extensions\n\nExtensions pode interceptar e personalizar compactação e branch summarization. Veja [`extensions/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/extensions/types.ts) para definições de tipo de evento.\n\n### session_before_compact\n\nDisparado antes da compactação automática ou `/compact`. Pode cancelar ou fornecer um resumo personalizado. Veja `SessionBeforeCompactEvent` e `CompactionPreparation` no arquivo de tipos.\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#### Convertendo mensagens em texto\n\nPara gerar um resumo com seu próprio modelo, converta mensagens em texto usando `serializeConversation`:\n\n```typescript\nimport { convertToLlm, serializeConversation } from \"@earendil-works/pi-coding-agent\";\n\npi.on(\"session_before_compact\", async (event, ctx) => {\n  const { preparation } = event;\n  \n  // Convert AgentMessage[] to Message[], then serialize to text\n  const conversationText = serializeConversation(\n    convertToLlm(preparation.messagesToSummarize)\n  );\n  // Returns:\n  // [User]: message text\n  // [Assistant thinking]: thinking content\n  // [Assistant]: response text\n  // [Assistant tool calls]: read(path=\"...\"); bash(command=\"...\")\n  // [Tool result]: output text\n\n  // Now send to your model for summarization\n  const { summary, usage } = await myModel.summarize(conversationText);\n  \n  return {\n    compaction: {\n      summary,\n      firstKeptEntryId: preparation.firstKeptEntryId,\n      tokensBefore: preparation.tokensBefore,\n      usage,\n    }\n  };\n});\n```\n\nVeja [custom-compaction.ts](../examples/extensions/custom-compaction.ts) para um exemplo completo usando um modelo diferente.\n\n### sessão_antes_árvore\n\nDisparado antes da navegação `/tree`. Sempre é acionado independentemente de o usuário optar por resumir. Pode cancelar a navegação ou fornecer um resumo personalizado.\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\nVeja `SessionBeforeTreeEvent` e `TreePreparation` no arquivo de tipos.\n\n## Configurações\n\nConfigure a compactação em `~/.pi/agent/settings.json` ou `<project-dir>/.pi/settings.json`:\n\n```json\n{\n  \"compaction\": {\n    \"enabled\": true,\n    \"reserveTokens\": 16384,\n    \"keepRecentTokens\": 20000\n  }\n}\n```\n\n| Contexto | Padrão | Descrição |\n|---------|---------|-------------|\n| `enabled` | `true` | Ativar compactação automática |\n| `reserveTokens` | `16384` | Tokens para reservar para resposta LLM |\n| `keepRecentTokens` | `20000` | Tokens recentes para manter (não resumidos) |\n\nDesative a compactação automática com `\"enabled\": false`. Você ainda pode compactar manualmente com `/compact`.","sourceFile":"compaction.md"},"containerization":{"title":"Conteinerização","markdown":"Pi é executado com todas as permissões por padrão, mas em alguns casos, você desejará ter mais controle sobre quais diretórios Pi podem gravar e quais acessos ele possui.\n\nExistem duas opções gerais. Você pode\n1. execute todo o processo `pi` dentro de um ambiente isolado, ou\n2. execute `pi` no host e roteie a execução da ferramenta para um ambiente isolado.\n\n## Escolha um padrão\n\n| Padrão | O que está isolado | Melhor para | Notas |\n| --- | --- | --- | --- |\n| Gondolin extensão | Ferramentas integradas e comandos `!` | Isolamento local de micro-VM enquanto mantém a autenticação no host | Consulte [`examples/extensions/gondolin/`](../examples/extensions/gondolin/). |\n| Simples Docker | Todo o processo `pi` em um contêiner local | Isolamento local simples | Os provedores API keys entram no contêiner. |\n| OpenShell | Todo o processo `pi` em um sandbox controlado por política | Gerenciado local ou remotamente sandbox | Requer um gateway OpenShell |\n\nExtensions é executado onde quer que o processo `pi` seja executado. Se você executar o host `pi` com uma extensão de roteamento de ferramentas, outras ferramentas de extensão customizadas ainda serão executadas no host, a menos que também deleguem suas operações.\n\n## Gondolin\n\n[Gondolin](https://github.com/earendil-works/gondolin) é uma micro-VM Linux local.\nUse [example extension](../examples/extensions/gondolin) quando quiser `pi` no host, mas todas as ferramentas integradas roteadas para a VM.\n\nConfigurar:\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\nExecute a partir do projeto que você deseja montar:\n\n```bash\ncd /path/to/project\npi -e ~/.pi/agent/extensions/gondolin\n```\n\nA extensão monta o host cwd em `/workspace` na VM e substitui `read`, `write`, `edit`, `bash`, `grep`, `find` e `ls`.\nOs comandos do usuário `!` também são roteados para a VM.\nAs alterações do arquivo em `/workspace` são gravadas no host.\n\nRequisitos: Node.js >= 23.6.0 para `@earendil-works/gondolin`, mais QEMU (requer instalação através do seu gerenciador de pacotes).\n\n## Simples Docker\n\nExecute todo o processo `pi` em Docker quando desejar o limite de contêiner local mais simples.\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\nConstrua e execute:\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\nO `-v \"$PWD:/workspace\"` monta seu diretório atual no contêiner em /workspace de forma que leituras e gravações em `/workspace` dentro de Docker afetem diretamente seus arquivos host, como no exemplo Gondolin.\n\nUse um volume nomeado para `/root/.pi/agent` se desejar configurações e sessões locais do contêiner. Montar seu host `~/.pi/agent` expõe a autenticação do host e os arquivos de sessão ao contêiner.\n\n## OpenShell\n\nUse [NVIDIA OpenShell](https://docs.nvidia.com/openshell/about/overview) quando desejar um sandbox controlado por política com sistemas de arquivos, processos, rede, credenciais e controles de inferência.\nOpenShell pode executar sandboxes por meio de um gateway local apoiado por Docker, Podman ou um tempo de execução de VM, ou por meio de um gateway Kubernetes remoto.\n\nCada sandbox requer um gateway ativo.\nRegistre-se e selecione um antes de criar um sandbox:\n\n```bash\nopenshell gateway add <gateway-url> --name <name>\nopenshell gateway select <name>\n```\n\nInicie `pi` dentro de um OpenShell sandbox:\n\n```bash\nopenshell sandbox create --name pi-sandbox --from pi -- pi\n```\n\nNeste padrão, todo o processo `pi` é executado dentro de sandbox.\nFerramentas integradas, comandos `!` e ferramentas de extensão são executadas dentro do limite OpenShell.\n\nSe o gateway for remoto, os arquivos do projeto não serão montados por ligação a partir do host, o que significa que as gravações em sandbox não serão refletidas em sua máquina.\nClone o repositório dentro de sandbox ou use comandos de transferência de arquivo OpenShell:\n\n```bash\nopenshell sandbox upload pi-sandbox ./repo /workspace\nopenshell sandbox download pi-sandbox /workspace/repo ./repo-out\n```\n\nOs provedores OpenShell podem manter o modelo bruto API keys fora do sandbox.\nQuando o roteamento de inferência é configurado, o código dentro de sandbox pode chamar `https://inference.local` e o gateway injeta as credenciais do provedor configuradas upstream.\nConfigure Pi para usar o endpoint compatível com OpenAI ou Anthropic correspondente se desejar que o tráfego do modelo use esta rota.","sourceFile":"containerization.md"},"custom-provider":{"title":"Personalizado Providers","markdown":"Extensions pode registrar provedores de modelos personalizados via `pi.registerProvider()`. Isso permite:\n\n- **Proxies** - Encaminhe solicitações por meio de proxies corporativos ou gateways API\n- **Endpoints personalizados** – Use implantações de modelo auto-hospedado ou privado\n- **OAuth/SSO** – Adicione fluxos de autenticação para provedores corporativos\n- **APIs personalizados** - Implemente streaming para APIs LLM não padrão\n\n## Exemplo Extensions\n\nVeja estes exemplos completos de provedores:\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## Índice\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## Referência rápida\n\nExtensions pode registrar um pi-ai `Provider` completo ou usar o formulário legado de configuração do provedor. Prefira um provedor completo quando for necessário comportamento personalizado de autenticação, filtragem, atualização ou streaming. Pi compõe `models.json` substituições acima dos provedores nativos registrados.\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\nA fábrica de extensão também pode ser `async`. Para descoberta de modelo dinâmico, busque e registre modelos na fábrica em vez de `session_start`. pi espera pela fábrica antes de a inicialização continuar, então o provedor está disponível durante a inicialização interativa e para `pi --list-models`.\n\n## Substituir provedor existente\n\nO caso de uso mais simples: redirecionar um provedor existente por meio de um proxy.\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\nQuando apenas `baseUrl` e/ou `headers` são fornecidos (sem `models`), todos os modelos existentes para esse provedor são preservados com o novo endpoint.\n\n## Cadastrar novo provedor\n\nPara adicionar um provedor completamente novo, especifique `models` junto com a configuração necessária.\n\nSe a lista de modelos vier de um endpoint remoto, use uma fábrica de extensões assíncrona:\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\nIsso registra os modelos buscados antes do término da inicialização.\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\nQuando `models` é fornecido, ele **substitui** todos os modelos existentes para esse provedor.\n\n`apiKey` e valores de cabeçalho personalizados usam a mesma sintaxe de valor de configuração que `models.json`: `!command` no início executa um comando para o valor inteiro, `$ENV_VAR` e `${ENV_VAR}` interpolam variáveis ​​de ambiente, `$` emite um literal ``apiKey` e valores de cabeçalho personalizados usam a mesma sintaxe de valor de configuração que `models.json`: `!command` no início executa um comando para o valor inteiro, `$ENV_VAR` e `${ENV_VAR}` interpolam variáveis ​​de ambiente, `$` emite um literal  e `$!` emite um literal `!`.\n\n## Cancelar registro do provedor\n\nUse `pi.unregisterProvider(name)` para remover um provedor que foi registrado anteriormente via `pi.registerProvider(name,...)`:\n\n```typescript\n// Register\npi.registerProvider(\"my-llm\", {\n  baseUrl: \"https://api.my-llm.com/v1\",\n  apiKey: \"$MY_LLM_API_KEY\",\n  api: \"openai-completions\",\n  models: [\n    {\n      id: \"my-llm-large\",\n      name: \"My LLM Large\",\n      reasoning: true,\n      input: [\"text\", \"image\"],\n      cost: { input: 3.0, output: 15.0, cacheRead: 0.3, cacheWrite: 3.75 },\n      contextWindow: 200000,\n      maxTokens: 16384\n    }\n  ]\n});\n\n// Later, remove it\npi.unregisterProvider(\"my-llm\");\n```\n\nO cancelamento do registro remove os modelos dinâmicos, API key fallback, OAuth registro do provedor e registros do manipulador de fluxo personalizado desse provedor. Quaisquer modelos integrados ou comportamento do provedor que foram substituídos serão restaurados.\n\nAs chamadas feitas após a fase inicial de carga do ramal são aplicadas imediatamente, portanto não é necessário `/reload`.\n\n### API Tipos\n\nO campo `api` determina qual implementação de streaming é usada:\n\n| API | Usar para |\n|-----|---------|\n| `anthropic-messages` | Claude antrópico API e compatíveis |\n| `openai-completions` | Conclusões do OpenAI Chat API e compatíveis |\n| `openai-responses` | Respostas OpenAI API |\n| `azure-openai-responses` | Respostas do Azure OpenAI API |\n| `openai-codex-responses` | Respostas do OpenAI Codex API |\n| `mistral-conversations` | Streaming de conclusões de bate-papo Mistral nativo |\n| `google-generative-ai` | IA generativa do Google API |\n| `google-vertex` | Google Vertex AI API |\n| `bedrock-converse-stream` | Converse Amazon Bedrock API |\n\nA maioria dos provedores compatíveis com OpenAI trabalham com `openai-completions`. Use `thinkingLevelMap` no nível do modelo para níveis de pensamento específicos do modelo e `compat` para peculiaridades do provedor. Os níveis `xhigh` e `max` são opcionais, exigem entradas de mapa não nulas e podem ser separados por buracos não suportados:\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\nUse `openrouter` para controles `reasoning: { effort }` no estilo OpenRouter. Use `together` para controles `reasoning: { enabled }` no estilo Together; com `supportsReasoningEffort`, também envia `reasoning_effort`. Use `qwen-chat-template` para servidores locais compatíveis com Qwen que leem `chat_template_kwargs.enable_thinking` e precisam de `preserve_thinking`.\nUse `cacheControlFormat: \"anthropic\"` para provedores compatíveis com OpenAI que expõem o cache de prompt no estilo Anthropic por meio de `cache_control` no prompt do sistema, última definição de ferramenta e conteúdo de texto do último usuário, assistente ou resultado da ferramenta.\n\nPara provedores compatíveis com Antrópicos usando `api: \"anthropic-messages\"`, defina `compat.forceAdaptiveThinking: true` em modelos ou provedores cujo modelo upstream requer pensamento adaptativo (`thinking.type: \"adaptive\"` mais `output_config.effort`). Os modelos Claude adaptativos integrados definem isso automaticamente. Defina `compat.allowEmptySignature: true` apenas para provedores que emitem assinaturas de pensamento vazias e esperam `signature: \"\"` na repetição.\n\n> Nota de migração: Mistral mudou de `openai-completions` para `mistral-conversations`.\n> Use `mistral-conversations` para modelos Mistral nativos.\n> Se você rotear intencionalmente endpoints personalizados/compatíveis com Mistral por meio de `openai-completions`, defina sinalizadores `compat` explicitamente conforme necessário.\n\n### Cabeçalho de autenticação\n\nSe o seu provedor espera `Authorization: Bearer <key>` mas não usa um API padrão, defina `authHeader: true`:\n\n```typescript\npi.registerProvider(\"custom-api\", {\n  baseUrl: \"https://api.example.com\",\n  apiKey: \"$MY_API_KEY\",\n  authHeader: true,  // adds Authorization: Bearer header\n  api: \"openai-completions\",\n  models: [...]\n});\n```\n\nA chave é resolvida para cada solicitação. Um cabeçalho de solicitação explícito `Authorization` tem precedência sobre o valor gerado.\n\n## OAuth Suporte\n\nAdicione autenticação OAuth/SSO que se integra com `/login`:\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\nApós o registro, os usuários podem autenticar via `/login corporate-ai`.\n\n### OAuthLoginCallbacks\n\nO objeto `callbacks` fornece interações neutras de UI para o fluxo de propriedade do provedor:\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### OAuthCredenciais\n\nAs credenciais são persistidas em `~/.pi/agent/auth.json`:\n\n```typescript\ninterface OAuthCredentials {\n  refresh: string;   // Refresh token (for refreshToken())\n  access: string;    // Access token (returned by getApiKey())\n  expires: number;   // Expiration timestamp in milliseconds\n}\n```\n\n## Transmissão personalizada API\n\nPara provedores com APIs não padrão, implemente `streamSimple`. Estude as implementações de provedores existentes antes de escrever as suas próprias:\n\n**Implementações de referência:**\n- [anthropic.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/anthropic.ts) - Mensagens Antrópicas API\n- [mistral.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/mistral.ts) - Conversas Mistral API\n- [openai-completions.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/openai-completions.ts) - Conclusões do bate-papo OpenAI\n- [openai-responses.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/openai-responses.ts) - Respostas OpenAI API\n- [google.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/google.ts) - IA generativa do Google\n- [amazon-bedrock.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/providers/amazon-bedrock.ts) - AWS Base\n\n### Padrão de fluxo\n\nTodos os provedores seguem o mesmo padrão:\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### Tipos de eventos\n\nEnvie eventos via `stream.push()` nesta ordem:\n\n1. `{ type: \"start\", partial: output }` - Transmissão iniciada\n\n2. Eventos de conteúdo (repetíveis, faixa `contentIndex` para cada bloco):\n   - `{ type: \"text_start\", contentIndex, partial }` - Bloco de texto iniciado\n   - `{ type: \"text_delta\", contentIndex, delta, partial }` - Pedaço de texto\n   - `{ type: \"text_end\", contentIndex, content, partial }` - Bloco de texto encerrado\n   - `{ type: \"thinking_start\", contentIndex, partial }` - O pensamento começou\n   - `{ type: \"thinking_delta\", contentIndex, delta, partial }` - Pedaço de pensamento\n   - `{ type: \"thinking_end\", contentIndex, content, partial }` - Pensamento encerrado\n   - `{ type: \"toolcall_start\", contentIndex, partial }` - Chamada de ferramenta iniciada\n   - `{ type: \"toolcall_delta\", contentIndex, delta, partial }` - Chamada de ferramenta JSON pedaço\n   - `{ type: \"toolcall_end\", contentIndex, toolCall, partial }` - Chamada de ferramenta encerrada\n\n3. `{ type: \"done\", reason, message }` ou `{ type: \"error\", reason, error }` - Transmissão encerrada\n\nO campo `partial` em cada evento contém o estado `AssistantMessage` atual. Atualize `output.content` conforme você recebe dados e inclua `output` como `partial`.\n\n### Blocos de conteúdo\n\nAdicione blocos de conteúdo a `output.content` conforme eles chegam:\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### Chamadas de ferramentas\n\nAs chamadas de ferramenta requerem acumulação JSON e análise:\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### Uso e Custo\n\nAtualize o uso da resposta API e calcule o custo:\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### Erros de estouro de contexto\n\nQuando uma solicitação excede a janela de contexto do modelo, pi pode se recuperar automaticamente compactando a conversa e tentando novamente. Essa recuperação só entra em ação se pi reconhecer a falha como um estouro.\n\nA detecção é executada na mensagem do assistente finalizada:\n\n- `stopReason === \"error\"`\n- `errorMessage` corresponde a um dos padrões de estouro conhecidos de pi (consulte [`packages/ai/src/utils/overflow.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/utils/overflow.ts))\n\nSe o seu provedor retornar erros de overflow com uma mensagem que pi não reconhece, normalize o erro a partir da mesma extensão que registra o provedor. Use um manipulador `message_end` para reescrever a mensagem do assistente de forma que seu `errorMessage` comece com uma frase que pi reconhece. O substituto genérico `context_length_exceeded` é a escolha mais segura.\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` é executado antes de pi rastrear a mensagem do assistente para compactação automática, então o `errorMessage` reescrito é o que pi verifica. Com isso implementado, pi irá:\n\n1. Detecte o estouro de `errorMessage`.\n2. Elimine a mensagem do assistente com falha do contexto ao vivo.\n3. Execute a compactação.\n4. Tente novamente a solicitação uma vez.\n\nGuarde a reescrita com cuidado:\n\n- Defina o escopo para o seu provedor (`message.provider` e `ctx.model?.provider`) para que erros não relacionados de outros provedores permaneçam intactos.\n- Corresponda a um padrão específico do provedor, não aos padrões genéricos de estouro do pi. Reescrever erros de limite de taxa ou limitação (`rate limit`, `too many requests`) acionaria falsamente a compactação em vez do caminho normal de nova tentativa com retirada.\n- Ignore quando `errorMessage` já inclui `context_length_exceeded` para que o manipulador seja idempotente.\n\n### Cadastro\n\nRegistre sua função de stream:\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## Testando sua implementação\n\nTeste seu provedor com os mesmos conjuntos de testes usados ​​por provedores integrados. Copie e adapte estes arquivos de teste de [packages/ai/test/](https://github.com/earendil-works/pi-mono/tree/main/packages/ai/test):\n\n| Teste | Propósito |\n|------|---------|\n| `stream.test.ts` | Streaming básico, saída de texto |\n| `tokens.test.ts` | Contagem e uso de tokens |\n| `abort.test.ts` | Manipulação de AbortSignal |\n| `empty.test.ts` | Respostas vazias/mínimas |\n| `context-overflow.test.ts` | Limites da janela de contexto |\n| `image-limits.test.ts` | Tratamento de entrada de imagem |\n| `unicode-surrogate.test.ts` | Casos extremos Unicode |\n| `tool-call-without-result.test.ts` | Casos extremos de chamada de ferramenta |\n| `image-tool-result.test.ts` | Imagens nos resultados da ferramenta |\n| `total-tokens.test.ts` | Cálculo total de tokens |\n| `cross-provider-handoff.test.ts` | Transferência de contexto entre provedores |\n\nExecute testes com seus pares provedor/modelo para verificar a compatibilidade.\n\n## Referência de configuração\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## Referência de definição de modelo\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` envia `reasoning: { effort }`. `deepseek` envia `thinking: { type: \"enabled\" | \"disabled\" }` e `reasoning_effort` quando habilitado. `together` envia `reasoning: { enabled }` e também `reasoning_effort` quando `supportsReasoningEffort` está habilitado. `qwen` é para nível superior do estilo DashScope `enable_thinking`. Use `qwen-chat-template` para servidores locais compatíveis com Qwen que leem `chat_template_kwargs.enable_thinking` e precisam de `preserve_thinking`. Use `chat-template` para `chat_template_kwargs` configurável, por exemplo DeepSeek V3.x atrás de vLLM com `chatTemplateKwargs: { \"thinking\": { \"$var\": \"thinking.enabled\" } }`. Use `thinkingFormat: \"baseten\"` com `chatTemplateArgs` quando o provedor espera valores de alternância abaixo de `chat_template_args` e opcionalmente suporta `reasoning_effort` de nível superior.\n`cacheControlFormat: \"anthropic\"` aplica marcadores `cache_control` no estilo antrópico ao prompt do sistema, à última definição de ferramenta e ao conteúdo de texto do último usuário, assistente ou resultado da ferramenta.","sourceFile":"custom-provider.md"},"development":{"title":"Desenvolvimento","markdown":"Consulte [AGENTS.md](https://github.com/earendil-works/pi-mono/blob/main/AGENTS.md) para orientações adicionais.\n\n## Configurar\n\n```bash\ngit clone https://github.com/earendil-works/pi-mono\ncd pi-mono\nnpm install\nnpm run build\n```\n\nExecute a partir da fonte:\n\n```bash\n/path/to/pi-mono/pi-test.sh\n```\n\nO script pode ser executado em qualquer diretório. Pi mantém o diretório de trabalho atual do chamador.\n\n## Bifurcação / Rebranding\n\nConfigurar via `package.json`:\n\n```json\n{\n  \"piConfig\": {\n    \"name\": \"pi\",\n    \"configDir\": \".pi\"\n  }\n}\n```\n\nAltere os campos `name`, `configDir` e `bin` para seu fork. Afeta o banner CLI, caminhos de configuração e nomes de variáveis ​​de ambiente.\n\n## Resolução de caminho\n\nTrês modos de execução: npm instalação, binário independente, tsx da fonte.\n\n**Sempre use `src/config.ts`** para ativos de pacote:\n\n```typescript\nimport { getPackageDir, getThemeDir } from \"./config.js\";\n```\n\nNunca use `__dirname` diretamente para ativos de pacote.\n\n## Comando de depuração\n\n`/debug` (oculto) escreve em `~/.pi/agent/pi-debug.log`:\n- Linhas TUI renderizadas com códigos ANSI\n- Últimas mensagens enviadas para o LLM\n\n## Teste\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## Estrutura do Projeto\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":"Variáveis ​​de ambiente","markdown":"Pi usa variáveis ​​de ambiente de três maneiras:\n\n- Variáveis ​​como `PI_OFFLINE` configuram o processo Pi.\n- Pi define `PI_CODING_AGENT` para que os processos filhos possam detectar que eles são executados dentro de Pi.\n- Os comandos executados pela ferramenta bash que pode ser chamada por LLM recebem variáveis ​​`PI_*` que descrevem a sessão atual.\n\nAs variáveis ​​de chave API do provedor são documentadas separadamente em [Providers](providers.md#environment-variables-or-auth-file).\n\n## Marcador de Processo\n\nOs pontos de entrada CLI e RPC definem `PI_CODING_AGENT=true`. Os processos filhos o herdam e podem usá-lo para detectar que são executados dentro de Pi. Não é específico da sessão e não é definido automaticamente quando Pi é incorporado através de SDK.\n\n## Ambiente de sessão da ferramenta Bash\n\nOs comandos executados pela ferramenta bash recebem o estado atual da sessão Pi:\n\n| Variável | Descrição |\n|----------|-------------|\n| `PI_SESSION_ID` | ID da sessão atual |\n| `PI_SESSION_FILE` | Caminho absoluto para o arquivo JSONL da sessão atual; não definido para sessões efêmeras |\n| `PI_PROVIDER` | Provedor de modelo atualmente selecionado |\n| `PI_MODEL` | ID do modelo atualmente selecionado |\n| `PI_REASONING_LEVEL` | Nível de raciocínio efetivo atual: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` ou `max` |\n\nOs valores são resolvidos quando cada comando é iniciado. Mudar de modelo ou alterar o nível de raciocínio afeta o próximo comando bash sem reiniciar Pi. `PI_PROVIDER` e `PI_MODEL` identificam o modelo Pi selecionado, não um modelo upstream diferente que um roteador possa escolher internamente.\n\nQuando questionado sobre qual modelo ou provedor está em execução, inspecione essas variáveis ​​em vez de inferir a resposta do prompt do sistema:\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\nO arquivo da sessão pode ser inspecionado diretamente quando a sessão é persistente:\n\n```bash\nif [ -n \"$PI_SESSION_FILE\" ]; then\n  tail -n 1 \"$PI_SESSION_FILE\"\nfi\n```\n\nEssas variáveis ​​são injetadas na ferramenta bash que pode ser chamada pelo LLM. Eles não são injetados nos comandos `!` ou `!!` inseridos pelo usuário.\n\n### Ferramentas Bash personalizadas\n\nAs ferramentas Bash criadas com `createBashTool()` expõem o ambiente de sessão por padrão quando registradas com Pi. A injeção acontece antes de `spawnHook`, então um gancho recebe as variáveis ​​em `ctx.env`:\n\n```typescript\nconst bashTool = createBashTool(cwd, {\n  spawnHook: (ctx) => ({\n    ...ctx,\n    env: { ...ctx.env, CI: \"1\" },\n  }),\n});\n```\n\nDesative os metadados da sessão independentemente do gancho de geração:\n\n```typescript\nconst bashTool = createBashTool(cwd, {\n  exposeSessionEnvironment: false,\n  spawnHook: (ctx) => ctx,\n});\n```\n\nQuando desabilitado, Pi remove valores herdados para essas variáveis, de forma que processos Pi aninhados não exponham metadados obsoletos da sessão pai.\n\n## Pi Configuração do Processo\n\nEssas variáveis ​​são lidas pelo próprio Pi:\n\n| Variável | Descrição |\n|----------|-------------|\n| `PI_CODING_AGENT_DIR` | Substitua o diretório de configuração; o padrão é `~/.pi/agent` |\n| `PI_CODING_AGENT_SESSION_DIR` | Substituir o armazenamento da sessão; substituído por `--session-dir` |\n| `PI_PACKAGE_DIR` | Substitua o diretório do pacote, útil para caminhos de armazenamento Nix/Guix |\n| `PI_OFFLINE` | Desativar operações de rede de inicialização, incluindo verificações de atualização, atualizações de pacotes e telemetria de instalação/atualização |\n| `PI_SKIP_VERSION_CHECK` | Desative a solicitação de versão mais recente `pi.dev` |\n| `PI_TELEMETRY` | Substituir telemetria de instalação/atualização e cabeçalhos de atribuição de provedor: `1`/`true`/`yes` ou `0`/`false`/`no` |\n| `PI_CACHE_RETENTION` | Defina como `long` para cache estendido de prompt do provedor onde houver suporte |\n| `PI_SHARE_VIEWER_URL` | Substitua o URL base usado por `/share` |\n| `PI_HARDWARE_CURSOR` | Defina como `1` para mostrar o cursor de hardware; veja [Terminal setup](terminal-setup.md) |\n| `VISUAL`, `EDITOR` | Fallback do editor externo quando `externalEditor` não está definido |\n| `HTTP_PROXY`, `HTTPS_PROXY` | Solicitações HTTP de saída de proxy |\n\nAs credenciais do provedor, como `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, e a configuração do provedor de nuvem estão listadas em [Providers](providers.md#environment-variables-or-auth-file).","sourceFile":"environment-variables.md"},"extensions":{"title":"Extensions","markdown":"> pi pode criar extensões. Peça para criar um para o seu caso de uso.\n\n\nExtensions são módulos TypeScript que estendem o comportamento do pi. Eles podem assinar eventos de ciclo de vida, registrar ferramentas personalizadas que podem ser chamadas pelo LLM, adicionar comandos e muito mais.\n\n> **Posicionamento para /reload:** Coloque extensões em `~/.pi/agent/extensions/` (global) ou `.pi/extensions/` (projeto local) para descoberta automática. Use `pi -e./path.ts` apenas para testes rápidos. Extensions em locais descobertos automaticamente pode ser recarregado a quente com `/reload`.\n\n**Principais capacidades:**\n- **Ferramentas personalizadas** - Registre ferramentas que o LLM pode chamar via `pi.registerTool()`\n- **Interceptação de eventos** - Bloqueie ou modifique chamadas de ferramentas, injete contexto, personalize compactação\n- **Interação do usuário** - Avisar os usuários via `ctx.ui` (selecionar, confirmar, inserir, notificar)\n- **Componentes de UI personalizados** - Componentes TUI completos com entrada de teclado via `ctx.ui.custom()` para interações complexas\n- **Comandos personalizados** - Registre comandos como `/mycommand` via `pi.registerCommand()`\n- **Persistência de sessão** - Armazena estado que sobrevive a reinicializações via `pi.appendEntry()`\n- **Renderização personalizada** - Controle como as chamadas/resultados e mensagens da ferramenta aparecem em TUI\n\n**Exemplos de casos de uso:**\n- Portas de permissão (confirme antes de `rm -rf`, `sudo`, etc.)\n- Git checkpoint (esconderijo em cada turno, restaurar na filial)\n- Proteção de caminho (bloqueia gravações em `.env`, `node_modules/`)\n- Compactação personalizada (resuma a conversa do seu jeito)\n- Resumos de conversas (veja o exemplo `summarize.ts`)\n- Ferramentas interativas (perguntas, assistentes, caixas de diálogo personalizadas)\n- Ferramentas com estado (listas de tarefas, pools de conexões)\n- Integrações externas (observadores de arquivos, webhooks, gatilhos de CI)\n- Jogos enquanto você espera (veja o exemplo `snake.ts`)\n\nVeja [examples/extensions/](../examples/extensions/) para implementações funcionais.\n\n## Índice\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## Início rápido\n\nCrie `~/.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\nTeste com sinalizador `--extension` (ou `-e`):\n\n```bash\npi -e ./my-extension.ts\n```\n\n## Locais de extensão\n\n> **Segurança:** Extensions executa com todas as permissões do sistema e pode executar código arbitrário. Instale apenas de fontes em que você confia.\n\nExtensions são descobertos automaticamente em locais confiáveis. As entradas `.pi/extensions` locais do projeto são carregadas somente depois que o projeto é confiável.\n\n| Localização | Escopo |\n|----------|-------|\n| `~/.pi/agent/extensions/*.ts` | Global (todos os projetos) |\n| `~/.pi/agent/extensions/*/index.ts` | Global (subdiretório) |\n| `.pi/extensions/*.ts` | Local do projeto |\n| `.pi/extensions/*/index.ts` | Local do projeto (subdiretório) |\n\nCaminhos adicionais via `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\nPara compartilhar extensões via npm ou git como pacotes pi, veja [packages.md](packages.md).\n\n## Importações disponíveis\n\n| Pacote | Propósito |\n|---------|---------|\n| `@earendil-works/pi-coding-agent` | Tipos de extensão (`ExtensionAPI`, `ExtensionContext`, eventos) |\n| `typebox` | Definições de esquema para parâmetros de ferramenta |\n| `@earendil-works/pi-ai` | Utilitários de IA (`StringEnum` para enumerações compatíveis com o Google) |\n| `@earendil-works/pi-tui` | TUI componentes para renderização personalizada |\n\nnpm dependências também funcionam. Adicione um `package.json` próximo à sua extensão (ou em um diretório pai), execute `npm install` e as importações de `node_modules/` serão resolvidas automaticamente.\n\nPara pacotes pi distribuídos instalados com `pi install` (npm ou git), as dependências de tempo de execução devem estar em `dependencies`. A instalação do pacote usa instalações de produção (`npm install --omit=dev`) por padrão, então `devDependencies` não estão disponíveis em tempo de execução; quando `npmCommand` é configurado, os pacotes git usam `install` simples para compatibilidade com wrappers.\n\nNode.js integrados (`node:fs`, `node:path`, etc.) também estão disponíveis.\n\n## Escrevendo uma extensão\n\nUma extensão exporta uma função de fábrica padrão que recebe `ExtensionAPI`. A fábrica pode ser síncrona ou assíncrona:\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 são carregados via [jiti](https://github.com/unjs/jiti), então TypeScript funciona sem compilação.\n\nSe a fábrica retornar `Promise`, pi aguardará antes de continuar a inicialização. Isso significa que a inicialização assíncrona é concluída antes de `session_start`, antes de `resources_discover` e antes que os registros do provedor enfileirados por meio de `pi.registerProvider()` sejam liberados.\n\n### Funções de fábrica assíncronas\n\nUse uma fábrica assíncrona para trabalhos de inicialização únicos, como buscar configuração remota ou descobrir dinamicamente modelos disponíveis.\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\nEste padrão disponibiliza os modelos buscados durante a inicialização normal e para `pi --list-models`.\n\n### Recursos de longa duração e desligamento\n\nAs fábricas de extensão podem ser executadas em invocações que nunca iniciam uma sessão. Não inicie recursos em segundo plano, como processos, soquetes, observadores de arquivos ou temporizadores de fábrica.\n\nAdie a inicialização do recurso em segundo plano até `session_start` ou o comando/ferramenta/evento que precisa do recurso. Registre um manipulador `session_shutdown` idempotente para fechar quaisquer recursos com escopo de sessão que você iniciar.\n\n### Estilos de extensão\n\n**Arquivo único** - mais simples, para extensões pequenas:\n\n```\n~/.pi/agent/extensions/\n└── my-extension.ts\n```\n\n**Diretório com index.ts** – para extensões de vários arquivos:\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**Pacote com dependências** - para extensões que precisam de pacotes npm:\n\n```\n~/.pi/agent/extensions/\n└── my-extension/\n    ├── package.json    # Declares dependencies and entry points\n    ├── package-lock.json\n    ├── node_modules/   # After npm install\n    └── src/\n        └── index.ts\n```\n\n```json\n// package.json\n{\n  \"name\": \"my-extension\",\n  \"dependencies\": {\n    \"zod\": \"^3.0.0\",\n    \"chalk\": \"^5.0.0\"\n  },\n  \"pi\": {\n    \"extensions\": [\"./src/index.ts\"]\n  }\n}\n```\n\nExecute `npm install` no diretório de extensão e as importações de `node_modules/` funcionam automaticamente.\n\n## Eventos\n\n### Visão geral do ciclo de vida\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### Eventos de inicialização\n\n#### projeto_confiança\n\nDisparado antes de pi decidir se deve confiar em um projeto com configurações dinâmicas (`.pi` ou `.agents/skills`). Ele é executado durante a inicialização e quando a substituição da sessão (por exemplo `/resume`) insere um cwd cuja confiança não foi resolvida no processo atual. Somente extensões de usuário/globais e extensões CLI `-e` participam; as extensões locais do projeto não são carregadas até que a confiança seja resolvida.\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\nUm manipulador `project_trust` deve retornar `{ trusted: \"yes\" | \"no\" | \"undecided\" }`. Uma extensão de usuário/global ou CLI que retorna `\"yes\"` ou `\"no\"` possui a decisão; a primeira decisão sim/não vence e suprime o prompt de confiança integrado. Use `remember: true` para persistir uma decisão sim/não; caso contrário, aplica-se apenas ao processo atual. Retorne `\"undecided\"` para permitir que manipuladores posteriores ou o fluxo de confiança integrado decidam. Verifique `ctx.hasUI` antes de perguntar. Se nenhum manipulador retornar sim/não, a resolução de confiança normal continua: as decisões `trust.json` salvas se aplicam primeiro, então `defaultProjectTrust` controla se pi pergunta, confia ou recusa por padrão.\n\n### Eventos de recursos\n\n#### recursos_descobrir\n\nDisparado após `session_start` para que as extensões possam contribuir com habilidades adicionais, prompts e caminhos de tema.\nO caminho de inicialização usa `reason: \"startup\"`. Recarregar usa `reason: \"reload\"`.\n\n```typescript\npi.on(\"resources_discover\", async (event, _ctx) => {\n  // event.cwd - current working directory\n  // event.reason - \"startup\" | \"reload\"\n  return {\n    skillPaths: [\"/path/to/skills\"],\n    promptPaths: [\"/path/to/prompts\"],\n    themePaths: [\"/path/to/themes\"],\n  };\n});\n```\n\n### Eventos de sessão\n\nConsulte [Session Format](session-format.md) para informações internas de armazenamento de sessão e o SessionManager API.\n\n#### sessão_início\n\nDisparado quando uma sessão é iniciada, carregada ou recarregada.\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\nDisparado quando o nome de exibição da sessão atual é definido via `/name`, RPC ou `pi.setSessionName()`.\n\n```typescript\npi.on(\"session_info_changed\", async (event, ctx) => {\n  // event.name - current normalized name, or undefined if cleared\n  ctx.ui.notify(`Session renamed: ${event.name ?? \"(none)\"}`, \"info\");\n});\n```\n\n#### session_before_switch\n\nDisparado antes de iniciar uma nova sessão (`/new`) ou trocar de sessão (`/resume`).\n\n```typescript\npi.on(\"session_before_switch\", async (event, ctx) => {\n  // event.reason - \"new\" or \"resume\"\n  // event.targetSessionFile - session we're switching to (only for \"resume\")\n\n  if (event.reason === \"new\") {\n    const ok = await ctx.ui.confirm(\"Clear?\", \"Delete all messages?\");\n    if (!ok) return { cancel: true };\n  }\n});\n```\n\nApós uma troca bem-sucedida ou ação de nova sessão, pi emite `session_shutdown` para a instância de extensão antiga, recarrega e religa as extensões para a nova sessão e, em seguida, emite `session_start` com `reason: \"new\" | \"resume\"` e `previousSessionFile`.\nFaça o trabalho de limpeza em `session_shutdown` e restabeleça qualquer estado da memória em `session_start`.\n\n#### sessão_before_fork\n\nDisparado ao bifurcar via `/fork` ou clonar via `/clone`.\n\n```typescript\npi.on(\"session_before_fork\", async (event, ctx) => {\n  // event.entryId - ID of the selected entry\n  // event.position - \"before\" for /fork, \"at\" for /clone\n  return { cancel: true }; // Cancel fork/clone\n  // OR\n  return { skipConversationRestore: true }; // Reserved for future conversation restore control\n});\n```\n\nApós uma bifurcação ou clonagem bem-sucedida, pi emite `session_shutdown` para a instância de extensão antiga, recarrega e religa as extensões para a nova sessão e, em seguida, emite `session_start` com `reason: \"fork\"` e `previousSessionFile`.\nFaça o trabalho de limpeza em `session_shutdown` e restabeleça qualquer estado da memória em `session_start`.\n\n#### session_before_compact / session_compact\n\nDisparado na compactação. Veja [compaction.md](compaction.md) para detalhes.\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\nDisparado na navegação `/tree`. Veja [Sessions](sessions.md) para conceitos de navegação em árvore.\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#### sessão_desligamento\n\nDisparado antes que o tempo de execução de uma sessão iniciada seja interrompido. Use isto para limpar recursos abertos em `session_start` ou outros ganchos com escopo de sessão.\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### Eventos do agente\n\n#### antes_agente_start\n\nDisparado após o usuário enviar o prompt, antes do loop do agente. Pode injetar uma mensagem e/ou modificar o prompt do sistema.\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\nO campo `systemPromptOptions` dá às extensões acesso aos mesmos dados estruturados que Pi usa para construir o prompt do sistema. Isso permite inspecionar o que Pi foi carregado — prompts personalizados, diretrizes, trechos de ferramentas, context files, habilidades — sem redescobrir recursos ou analisar novamente sinalizadores. Use-o quando sua extensão precisar fazer alterações profundas e informadas no prompt do sistema, respeitando a configuração fornecida pelo usuário.\n\nDentro de `before_agent_start`, `event.systemPrompt` e `ctx.getSystemPrompt()` refletem o prompt do sistema encadeado do manipulador atual. Os manipuladores `before_agent_start` posteriores ainda podem modificá-lo novamente.\n\n#### agente_start / agente_end / agente_settled\n\n`agent_start` é acionado quando uma execução de agente de baixo nível começa. `agent_end` é acionado quando a execução termina, mas Pi ainda pode tentar novamente, compactar automaticamente e tentar novamente, ou continuar com mensagens de acompanhamento enfileiradas. Use `agent_settled` para integrações de status que precisam saber que Pi não continuarão sendo executadas automaticamente.\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\nDisparado a cada turno (uma resposta LLM + chamadas de ferramenta).\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#### mensagem_início / mensagem_atualização / mensagem_fim\n\nDisparado para atualizações do ciclo de vida da mensagem.\n\n- `message_start` e `message_end` disparam para mensagens de usuário, assistente e toolResult.\n- `message_update` dispara para atualizações de streaming do assistente.\n- Os manipuladores `message_end` podem retornar `{ message }` para substituir a mensagem finalizada. A substituição deve manter o mesmo `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\nDisparado para atualizações do ciclo de vida de execução da ferramenta.\n\nNo modo de ferramenta paralela:\n- `tool_execution_start` é emitido na ordem da fonte assistente durante a fase de comprovação\n- `tool_execution_update` eventos podem intercalar-se entre ferramentas\n- `tool_execution_end` é emitido na ordem de conclusão da ferramenta após cada ferramenta ser finalizada\n- eventos de mensagem final `toolResult` ainda são emitidos posteriormente na ordem de origem do assistente\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#### contexto\n\nDisparado antes de cada chamada LLM. Modifique mensagens de forma não destrutiva. Veja [Session Format](session-format.md) para tipos de mensagens.\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\nDisparado após a montagem dos cabeçalhos HTTP de saída. Use-o para adicionar, substituir ou remover cabeçalhos de solicitação.\n\nOs manipuladores mudam `event.headers` no lugar. Defina uma chave para uma string para adicioná-la ou substituí-la, ou para `null` para excluí-la.\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\nExecuta uma vez por solicitação do provedor; tenta reutilizar os mesmos cabeçalhos em vez de disparar novamente o gancho.\n\n#### before_provider_request\n\nDisparado após a construção da carga específica do provedor, logo antes do envio da solicitação. Os manipuladores são executados na ordem de carregamento da extensão. Retornar `undefined` mantém a carga inalterada. Retornar qualquer outro valor substitui a carga útil para manipuladores posteriores e para a solicitação real.\n\nEste gancho pode reescrever as instruções do sistema no nível do provedor ou removê-las completamente. Essas alterações no nível da carga útil não são refletidas por `ctx.getSystemPrompt()`, que relata a string de prompt do sistema Pi em vez da carga útil final do provedor serializado.\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\nIsso é útil principalmente para depurar a serialização do provedor e o comportamento do cache.\n\n#### after_provider_response\n\nDisparado depois que uma resposta HTTP é recebida e antes que seu corpo de fluxo seja consumido. Os manipuladores são executados na ordem de carregamento da extensão.\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\nA disponibilidade do cabeçalho depende do fornecedor e do transporte. Providers que respostas HTTP abstratas não podem expor cabeçalhos.\n\n### Eventos Modelo\n\n#### seleção_modelo\n\nDisparado quando o modelo é alterado por meio do comando `/model`, ciclo de modelo (`Ctrl+P`) ou restauração de sessão.\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\nUse isto para atualizar elementos da UI (barras de status, rodapés) ou executar inicialização específica do modelo quando o modelo ativo for alterado.\n\n#### pensando_nível_select\n\nDisparado quando o nível de pensamento muda. Isso é apenas para notificação; os valores de retorno do manipulador são ignorados.\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\nUse isto para atualizar a interface do usuário da extensão quando `pi.setThinkingLevel()`, alterações de modelo ou controles de nível de pensamento integrados alteram o nível de pensamento ativo.\n\n### Eventos de ferramentas\n\n#### ferramenta_call\n\nDisparado após `tool_execution_start`, antes da execução da ferramenta. **Pode bloquear.** Use `isToolCallEventType` para restringir e obter entradas digitadas.\n\nAntes da execução de `tool_call`, pi espera que os eventos do Agente emitidos anteriormente terminem de ser drenados por `AgentSession`. Isso significa que `ctx.sessionManager` está atualizado por meio da mensagem atual de chamada da ferramenta do assistente.\n\nNo modo de execução de ferramenta paralela padrão, as chamadas de ferramentas irmãs da mesma mensagem do assistente são pré-flightadas sequencialmente e, em seguida, executadas simultaneamente. Não é garantido que `tool_call` veja os resultados da ferramenta irmã da mesma mensagem do assistente em `ctx.sessionManager`.\n\n`event.input` é mutável. Mude-o para corrigir os argumentos da ferramenta antes da execução.\n\nGarantias de comportamento:\n- Mutações para `event.input` afetam a execução real da ferramenta\n- Os manipuladores `tool_call` posteriores veem as mutações feitas pelos manipuladores anteriores\n- Nenhuma revalidação é realizada após sua mutação\n- Retornar valores do bloqueio de controle `tool_call` via `{ block: true, reason?: string, terminate?: boolean }`\n- `terminate` aplica-se apenas a uma chamada bloqueada; o agente para mais cedo somente quando todos os resultados finalizados no lote estão terminando\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#### Digitando entrada de ferramenta personalizada\n\nAs ferramentas personalizadas devem exportar seu tipo de entrada:\n\n```typescript\n// my-extension.ts\nexport type MyToolInput = Static<typeof myToolSchema>;\n```\n\nUse `isToolCallEventType` com parâmetros de tipo explícitos:\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#### resultado_ferramenta\n\nDisparado após o término da execução da ferramenta e antes de `tool_execution_end` mais os eventos de mensagem de resultado final da ferramenta serem emitidos. **Pode modificar o resultado.**\n\nNo modo de ferramenta paralela, `tool_result` e `tool_execution_end` podem intercalar na ordem de conclusão da ferramenta, enquanto os eventos de mensagem `toolResult` finais ainda são emitidos posteriormente na ordem de origem do assistente.\n\n`tool_result` cadeia de manipuladores como middleware:\n- Os manipuladores são executados na ordem de carregamento da extensão\n- Cada manipulador vê o resultado mais recente após alterações anteriores no manipulador\n- Os manipuladores podem retornar patches parciais (`content`, `details`, `isError` ou `usage`); campos omitidos mantêm seus valores atuais\n\nUse `ctx.signal` para trabalho assíncrono aninhado dentro do manipulador. Isso permite que Esc cancele chamadas de modelo, `fetch()`, e outras operações com reconhecimento de aborto iniciadas pela extensão.\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### Eventos Bash do usuário\n\n#### usuário_bash\n\nDisparado quando o usuário executa os comandos `!` ou `!!`. **Pode interceptar.**\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### Eventos de entrada\n\n#### entrada\n\nDisparado quando a entrada do usuário é recebida, após a verificação dos comandos de extensão, mas antes da expansão da habilidade e do modelo. O evento vê o texto de entrada bruto, portanto `/skill:foo` e `/template` ainda não foram expandidos.\n\n**Ordem de processamento:**\n1. Comandos de extensão (`/cmd`) verificados primeiro - se encontrados, o manipulador é executado e o evento de entrada é ignorado\n2. `input` eventos disparados - podem interceptar, transformar ou manipular\n3. Se não for tratado: comandos de habilidade (`/skill:name`) expandidos para conteúdo de habilidade\n4. Se não for tratado: prompt templates (`/template`) expandido para o conteúdo do modelo\n5. O processamento do agente começa (`before_agent_start`, etc.)\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**Resultados:**\n- `continue` - passa inalterado (padrão se o manipulador não retornar nada)\n- `transform` - modifique texto/imagens e continue a expansão\n- `handled` - ignora totalmente o agente (o primeiro manipulador a retornar vence)\n\nTransforma a cadeia entre manipuladores. Consulte [input-transform.ts](../examples/extensions/input-transform.ts) e [input-transform-streaming.ts](../examples/extensions/input-transform-streaming.ts) para roteamento com reconhecimento de `streamingBehavior`.\n\n## ExtensãoContexto\n\nTodos os manipuladores recebem `ctx: ExtensionContext`.\n\n### ctx.ui\n\nMétodos de UI para interação do usuário. Veja [Custom UI](#custom-ui) para detalhes completos.\n\n### ctx.modo\n\nModo de execução atual: `\"tui\"`, `\"rpc\"`, `\"json\"` ou `\"print\"`. Use `ctx.mode === \"tui\"` para proteger recursos somente de terminal, como `custom()`, fábricas de componentes, entrada de terminal e renderização direta de TUI.\n\n### ctx.hasUI\n\n`true` nos modos TUI e RPC. `false` no modo de impressão (`-p`) e no modo JSON. Use isto para proteger métodos de diálogo (`select`, `confirm`, `input`, `editor`) e métodos de disparar e esquecer (`notify`, `setStatus`, `setWidget`, `setTitle`, `setEditorText`) que funcionam em TUI e RPC modos. No modo RPC, alguns métodos específicos de TUI são autônomos ou retornam padrões (consulte [rpc.md](rpc.md#extension-ui-protocol)).\n\n### ctx.cwd\n\nDiretório de trabalho atual.\n\nUse `CONFIG_DIR_NAME` em vez de codificar `.pi` ao construir caminhos de configuração local do projeto. Distribuições renomeadas podem usar um nome de diretório de configuração diferente.\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\nRetorna se a confiança local do projeto está ativa para o contexto da sessão atual. Isso inclui decisões de confiança temporárias e substituições de confiança CLI, não apenas decisões salvas no armazenamento confiável global.\n\nUse isto antes de ler a configuração da extensão local do projeto que só deve ser respeitada para projetos confiáveis.\n\n### ctx.sessionManager\n\nAcesso somente leitura ao estado da sessão. Veja [Session Format](session-format.md) para o SessionManager API completo e tipos de entrada.\n\nPara `tool_call`, esse estado é sincronizado por meio da mensagem do assistente atual antes da execução dos manipuladores. No modo de execução de ferramenta paralela ainda não é garantido que inclua resultados de ferramentas irmãs da mesma mensagem do assistente.\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\nAcesso a modelos, provedores e autenticação resolvida. `ctx.modelRegistry.getProvider(id)` retorna o provedor pi-ai efetivo, enquanto `getProviderAuth(id)` resolve seu API key atual, cabeçalhos, URL base e ambiente com escopo do provedor sem exigir um modelo carregado. `ctx.model` é o modelo ativo e `ctx.thinkingLevel` é seu atual nível de pensamento efetivo.\n\n`ctx.scopedModels` é a lista somente leitura de modelos com escopo definido para a sessão atual — o mesmo conjunto que o comando `/scoped-models` mostra. É resolvido no início da sessão a partir do sinalizador `--models` CLI e da configuração `enabledModels` (comparado com o catálogo disponível com minimatch em `provider/modelId` ou um `modelId` simples). Fica vazio quando nenhum escopo está configurado, o que significa que todos os modelos disponíveis podem ser usados. Cada entrada é `{ model, thinkingLevel? }`, onde `thinkingLevel` é definido apenas quando um padrão a fixa (por exemplo, `anthropic/*:high`). Use-o para preencher um seletor de modelo que espelhe o integrado em vez de enumerar todo o catálogo via `ctx.modelRegistry.getAvailable()`.\n\n### ctx.signal\n\nO sinal de aborto do agente atual, ou `undefined` quando nenhum turno do agente está ativo.\n\nUse isto para trabalhos aninhados com reconhecimento de interrupção iniciados por manipuladores de extensão, por exemplo:\n- `fetch(..., { signal: ctx.signal })`\n- chamadas de modelo que aceitam `signal`\n- auxiliares de arquivo ou processo que aceitam `AbortSignal`\n\n`ctx.signal` é normalmente definido durante eventos de turno ativo, como `tool_call`, `tool_result`, `message_update` e `turn_end`.\nGeralmente é `undefined` em contextos ociosos ou sem turno, como eventos de sessão, comandos de extensão e atalhos disparados enquanto pi está ocioso.\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\nAuxiliares de fluxo de controle. `ctx.isIdle()` é falso enquanto Pi está processando uma execução de agente, nova tentativa automática, nova tentativa de compactação automática ou continuação na fila.\n\n### ctx.shutdown()\n\nSolicite um desligamento normal do pi.\n\n- **Modo interativo:** Adiado até que o agente fique ocioso (depois de processar todas as mensagens de orientação e acompanhamento enfileiradas).\n- **Modo RPC:** Adiado até o próximo estado inativo (após completar a resposta do comando atual, ao aguardar o próximo comando).\n- **Modo de impressão:** No-op. O processo é encerrado automaticamente quando todos os prompts são processados.\n\nEmite o evento `session_shutdown` para todas as extensões antes de sair. Disponível em todos os contextos (manipuladores de eventos, ferramentas, comandos, atalhos).\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\nRetorna o uso do contexto atual para o modelo ativo. Usa o último uso do assistente quando disponível e, em seguida, estima tokens para mensagens finais.\n\n```typescript\nconst usage = ctx.getContextUsage();\nif (usage && usage.tokens > 100_000) {\n  // ...\n}\n```\n\n### ctx.compact()\n\nAcione a compactação sem aguardar a conclusão. Use `onComplete` e `onError` para ações de acompanhamento.\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\nRetorna a string de prompt do sistema atual de Pi.\n\n- Durante `before_agent_start`, isso reflete as alterações encadeadas no prompt do sistema feitas até agora para o turno atual.\n- Não inclui mutações de mensagens `context` posteriores.\n- Não inclui reescritas de carga útil `before_provider_request`.\n- Se as extensões carregadas posteriormente forem executadas depois das suas, elas ainda poderão alterar o que será enviado.\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## ExtensãoCommandContext\n\nOs manipuladores de comando recebem `ExtensionCommandContext`, que estende `ExtensionContext` com métodos de controle de sessão. Eles estão disponíveis apenas em comandos porque podem travar se chamados a partir de manipuladores de eventos.\n\n### ctx.getSystemPromptOptions()\n\nRetorna as entradas básicas Pi usadas atualmente para construir o prompt do sistema.\n\n```typescript\nconst options = ctx.getSystemPromptOptions();\nconst contextPaths = options.contextFiles?.map((file) => file.path) ?? [];\n```\n\nTem a mesma forma e mutabilidade que `before_agent_start` `event.systemPromptOptions`: prompt personalizado, ferramentas ativas, trechos de ferramentas, diretrizes de prompt, texto de prompt do sistema anexado, cwd, context files carregado e habilidades carregadas. Ele pode incluir o conteúdo completo do arquivo de contexto, portanto, trate-o como dados confidenciais de extensão local e evite expô-lo por meio de listas de comandos, logs ou metadados de preenchimento automático.\n\nIsso relata as entradas atuais do prompt de base. Não inclui alterações de prompt do sistema encadeadas `before_agent_start` por turno, mutações posteriores de mensagens de evento `context` ou reescritas de carga útil `before_provider_request`.\n\n### ctx.waitForIdle()\n\nAguarde até que o agente seja totalmente liquidado, incluindo novas tentativas automáticas, novas tentativas de compactação automática e continuações na fila:\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(opções?)\n\nCrie uma nova sessão:\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\nOpções:\n- `parentSession`: arquivo da sessão pai para gravar no novo cabeçalho da sessão\n- `setup`: altera o `SessionManager` da nova sessão antes de `withSession` ser executado\n- `withSession`: execute o trabalho pós-troca em um novo contexto de sessão de substituição. Não use o antigo `pi` / comando `ctx` capturado; veja [Session replacement lifecycle and footguns](#session-replacement-lifecycle-and-footguns).\n\n### ctx.fork(entryId, opções?)\n\nBifurque uma entrada específica, criando um novo arquivo de sessão:\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\nOpções:\n- `position`: `\"before\"` (padrão) bifurca-se antes da mensagem do usuário selecionado, restaurando esse prompt no editor\n- `position`: `\"at\"` duplica o caminho ativo através da entrada selecionada sem restaurar o texto do editor\n- `withSession`: execute o trabalho pós-troca em um novo contexto de sessão de substituição. Não use o antigo `pi` / comando `ctx` capturado; veja [Session replacement lifecycle and footguns](#session-replacement-lifecycle-and-footguns).\n\n### ctx.navigateTree(targetId, opções?)\n\nNavegue para um ponto diferente no 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\nOpções:\n- `summarize`: Se deve gerar um resumo da filial abandonada\n- `customInstructions`: Instruções personalizadas para o resumidor\n- `replaceInstructions`: Se verdadeiro, `customInstructions` substitui o prompt padrão em vez de ser anexado\n- `label`: Etiqueta a ser anexada à entrada de resumo da ramificação (ou entrada de destino, se não estiver resumindo)\n\n### ctx.switchSession(sessionPath, opções?)\n\nMude para um arquivo de sessão diferente:\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\nOpções:\n- `withSession`: execute o trabalho pós-troca em um novo contexto de sessão de substituição. Não use o antigo `pi` / comando `ctx` capturado; veja [Session replacement lifecycle and footguns](#session-replacement-lifecycle-and-footguns).\n\nPara descobrir sessões disponíveis, use os métodos estáticos `SessionManager.list()` ou `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### Ciclo de vida de substituição de sessão e armas de pé\n\n`withSession` recebe um novo `ReplacedSessionContext`, que estende `ExtensionCommandContext` com auxiliares assíncronos `sendMessage()` e `sendUserMessage()` vinculados à sessão de substituição.\n\nCiclo de vida e armas de pé:\n- `withSession` é executado somente depois que a sessão antiga emitiu `session_shutdown`, o tempo de execução antigo foi interrompido, a sessão de substituição foi recuperada e a nova instância de extensão já recebeu `session_start`.\n- O retorno de chamada ainda é executado no encerramento original, não dentro da nova instância de extensão. Isso significa que sua antiga instância de extensão já pode ter executado a limpeza de desligamento antes de `withSession` iniciar.\n- Objetos antigos `pi` / comando antigo `ctx` capturados e vinculados à sessão ficam obsoletos após a substituição e serão lançados se usados. Use apenas `ctx` passado para `withSession` para trabalho vinculado à sessão.\n- Objetos brutos extraídos anteriormente ainda são de sua responsabilidade. Por exemplo, se você capturar `const sm = ctx.sessionManager` antes da substituição, `sm` ainda será o antigo objeto `SessionManager`. Não o reutilize após a substituição.\n- O código em `withSession` deve assumir que qualquer estado invalidado pelo seu manipulador `session_shutdown` já desapareceu. Capture apenas dados simples que sobrevivem ao desligamento de forma limpa, como strings, ids e configuração serializada.\n\nPadrão seguro:\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\nPadrão inseguro:\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\nExecute o mesmo fluxo de recarga de `/reload`.\n\n```typescript\npi.registerCommand(\"reload-runtime\", {\n  description: \"Reload extensions, skills, prompts, themes, and context files\",\n  handler: async (_args, ctx) => {\n    await ctx.reload();\n    return;\n  },\n});\n```\n\nComportamento importante:\n- `await ctx.reload()` emite `session_shutdown` para o tempo de execução da extensão atual\n- Em seguida, recarrega recursos e emite `session_start` com `reason: \"reload\"` e `resources_discover` com motivo `\"reload\"`\n- O manipulador de comandos atualmente em execução ainda continua no quadro de chamada antigo\n- O código após `await ctx.reload()` ainda é executado na versão pré-recarregamento\n- O código após `await ctx.reload()` não deve assumir que o estado antigo da extensão na memória ainda é válido\n- Após o retorno do manipulador, futuros comandos/eventos/chamadas de ferramentas usarão a nova versão da extensão\n\nPara um comportamento previsível, trate reload como terminal para esse manipulador (`await ctx.reload(); return;`).\n\nAs ferramentas são executadas com `ExtensionContext`, portanto não podem chamar `ctx.reload()` diretamente. Use um comando como ponto de entrada de recarga e, em seguida, exponha uma ferramenta que coloque esse comando na fila como uma mensagem de acompanhamento do usuário.\n\nExemplo de ferramenta que o LLM pode chamar para acionar o recarregamento:\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## ExtensãoAPI Métodos\n\n### pi.on(evento, manipulador)\n\nInscreva-se em eventos. Consulte [Events](#events) para tipos de eventos e valores de retorno.\n\n### pi.registerTool (definição)\n\nRegistre uma ferramenta personalizada que pode ser chamada pelo LLM. Veja [Custom Tools](#custom-tools) para detalhes completos.\n\n`pi.registerTool()` funciona durante o carregamento da extensão e após a inicialização. Você pode chamá-lo dentro de `session_start`, manipuladores de comandos ou outros manipuladores de eventos. Novas ferramentas são atualizadas imediatamente na mesma sessão, então elas aparecem em `pi.getAllTools()` e podem ser chamadas pelo LLM sem `/reload`.\n\nUse `pi.setActiveTools()` para ativar ou desativar ferramentas (incluindo ferramentas adicionadas dinamicamente) em tempo de execução.\n\nUse `promptSnippet` para incluir uma ferramenta personalizada em uma entrada de uma linha em `Available tools` e `promptGuidelines` para anexar marcadores específicos da ferramenta à seção `Guidelines` padrão quando a ferramenta estiver ativa.\n\n**Importante:** os marcadores `promptGuidelines` são anexados na seção `Guidelines` sem prefixo de nome de ferramenta. Cada diretriz deve nomear a ferramenta a que se refere - evite \"Use esta ferramenta quando...\" porque o LLM não pode dizer qual ferramenta \"isto\" significa. Escreva \"Use my_tool quando...\".\n\nVeja [dynamic-tools.ts](../examples/extensions/dynamic-tools.ts) para um exemplo completo.\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(mensagem, opções?)\n\nInjete uma mensagem personalizada na sessão. Mensagens personalizadas participam do contexto LLM. Para conteúdo durável apenas TUI que não deve ser enviado para o LLM, use [`pi.appendEntry()`](#piappendentrycustomtype-data) com [`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**Opções:**\n- `deliverAs` - Modo de entrega:\n  - `\"steer\"` (padrão) – Coloca a mensagem na fila durante o streaming. Entregue após o turno do assistente atual terminar de executar suas chamadas de ferramenta, antes da próxima chamada do LLM.\n  - `\"followUp\"` - Espera o agente terminar. Entregue somente quando o agente não tiver mais chamadas de ferramenta.\n  - `\"nextTurn\"` - Na fila para o próximo prompt do usuário. Não interrompe nem desencadeia nada.\n- `triggerTurn: true` - Se o agente estiver ocioso, acione uma resposta LLM imediatamente. Aplica-se apenas aos modos `\"steer\"` e `\"followUp\"` (ignorado para `\"nextTurn\"`).\n\n### pi.sendUserMessage(conteúdo, opções?)\n\nEnvie uma mensagem do usuário ao agente. Ao contrário de `sendMessage()` que envia mensagens personalizadas, este envia uma mensagem real do usuário que aparece como se tivesse sido digitada pelo usuário. Sempre aciona um turno.\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**Opções:**\n- `deliverAs` - Obrigatório quando o agente está transmitindo:\n  - `\"steer\"` - Coloca a mensagem na fila para entrega após o turno atual do assistente terminar de executar suas chamadas de ferramenta\n  - `\"followUp\"` - Espera o agente terminar todas as ferramentas\n\nQuando não está transmitindo, a mensagem é enviada imediatamente e aciona um novo turno. Ao transmitir sem `deliverAs`, gera um erro.\n\nVeja [send-user-message.ts](../examples/extensions/send-user-message.ts) para um exemplo completo.\n\n### pi.appendEntry(customType, dados?)\n\nPersistir dados de extensão. As entradas personalizadas NÃO participam do contexto LLM. No modo interativo, eles também podem ser renderizados dentro da transcrição do bate-papo quando combinados com `pi.registerEntryRenderer()`.\n\n```typescript\npi.appendEntry(\"my-state\", { count: 42 });\npi.appendEntry(\"status-card\", { title: \"Indexed files\", count: 17 });\n\n// Restore on reload\npi.on(\"session_start\", async (_event, ctx) => {\n  for (const entry of ctx.sessionManager.getEntries()) {\n    if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n      // Reconstruct from entry.data\n    }\n  }\n});\n```\n\n### pi.setSessionName(nome)\n\nDefina o nome de exibição da sessão (mostrado no seletor de sessão em vez da primeira mensagem).\n\n```typescript\npi.setSessionName(\"Refactor auth module\");\n```\n\n### pi.getSessionName()\n\nObtenha o nome da sessão atual, se definido.\n\n```typescript\nconst name = pi.getSessionName();\nif (name) {\n  console.log(`Session: ${name}`);\n}\n```\n\n### pi.setLabel(entryId, rótulo)\n\nDefina ou desmarque um rótulo em uma entrada. Etiquetas são marcadores definidos pelo usuário para marcação e navegação (mostrados no seletor `/tree`).\n\n```typescript\n// Set a label\npi.setLabel(entryId, \"checkpoint-before-refactor\");\n\n// Clear a label\npi.setLabel(entryId, undefined);\n\n// Read labels via sessionManager\nconst label = ctx.sessionManager.getLabel(entryId);\n```\n\nOs rótulos persistem na sessão e sobrevivem às reinicializações. Use-os para marcar pontos importantes (curvas, pontos de controle) na árvore de conversação.\n\n### pi.registerCommand(nome, opções)\n\nRegistre um comando.\n\nSe múltiplas extensões registrarem o mesmo nome de comando, pi mantém todas elas e atribui sufixos de invocação numérica na ordem de carregamento, por exemplo `/review:1` e `/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\nOpcional: adicione preenchimento automático de argumento para `/command...`:\n\n```typescript\nimport type { AutocompleteItem } from \"@earendil-works/pi-tui\";\n\npi.registerCommand(\"deploy\", {\n  description: \"Deploy to an environment\",\n  getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {\n    const envs = [\"dev\", \"staging\", \"prod\"];\n    const items = envs.map((e) => ({ value: e, label: e }));\n    const filtered = items.filter((i) => i.value.startsWith(prefix));\n    return filtered.length > 0 ? filtered : null;\n  },\n  handler: async (args, ctx) => {\n    ctx.ui.notify(`Deploying: ${args}`, \"info\");\n  },\n});\n```\n\n### pi.getCommands()\n\nObtenha o slash commands disponível para invocação via `prompt` na sessão atual. Inclui comandos de extensão, prompt templates e comandos de habilidade.\nA lista corresponde à ordem RPC `get_commands`: primeiro as extensões, depois os modelos e depois as habilidades.\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\nCada entrada tem este formato:\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\nUse `sourceInfo` como campo de proveniência canônica. Não infira a propriedade a partir de nomes de comandos ou de análise de caminho ad hoc.\n\nComandos interativos integrados (como `/model` e `/settings`) não estão incluídos aqui. Eles são tratados apenas de forma interativa\nmodo e não seria executado se enviado via `prompt`.\n\n### pi.registerMessageRenderer(customType, renderizador)\n\nRegistre um renderizador TUI personalizado para mensagens personalizadas com seu `customType`. Mensagens personalizadas são criadas com `pi.sendMessage()` e participam do contexto LLM. Consulte [Custom UI](#custom-ui).\n\n### pi.registerMarkdownTransformador(transformador)\n\nRegistre um transformador para Markdown em texto normal do usuário, texto assistente e blocos de pensamento. Os transformadores são executados em ordem de carregamento de extensão e cada transformador recebe o Markdown retornado pelo transformador anterior. Após o término da cadeia, Pi renderiza o conteúdo transformado com seu renderizador integrado.\n\nO transformador recebe a string Markdown e um contexto com:\n\n- `messageType` — `\"user\"`, `\"assistant\"` ou `\"assistant-thinking\"`\n- `isStreaming` — `true` para atualizações parciais do assistente; `false` para usuário, assistente finalizado e mensagens restauradas\n- `availableWidth` — colunas terminais exatas disponíveis para o conteúdo Markdown transformado\n\nRetorne o transformado Markdown:\n\n```typescript\npi.registerMarkdownTransformer((markdown, { messageType, isStreaming }) => {\n  if (isStreaming || messageType === \"assistant-thinking\") return markdown;\n  return markdown.replaceAll(\"-->\", \"→\");\n});\n```\n\nSe um transformador for acionado, Pi mantém o Markdown produzido até agora e continua com o próximo transformador. O gancho é somente para exibição: a mensagem original permanece inalterada no contexto da sessão e do modelo. Ele é executado para mensagens de novos usuários, atualizações de streaming do assistente, mensagens de sessão restauradas e alterações na largura do terminal, portanto, os transformadores devem permanecer síncronos e baratos.\n\n### pi.registerEntryRenderer(customType, renderizador)\n\nRegistre um renderizador TUI personalizado para entradas personalizadas com seu `customType`. As entradas personalizadas são criadas com `pi.appendEntry()` e não participam do contexto LLM.\n\n```typescript\nimport { Box, Text } from \"@earendil-works/pi-tui\";\n\npi.registerEntryRenderer(\"status-card\", (entry, { expanded }, theme) => {\n  const data = entry.data as { title: string; count: number };\n  const box = new Box(1, 1, (text) => theme.bg(\"customMessageBg\", text));\n  box.addChild(new Text(`${theme.bold(data.title)}: ${data.count}`));\n  if (expanded) {\n    box.addChild(new Text(theme.fg(\"dim\", JSON.stringify(data, null, 2))));\n  }\n  return box;\n});\n\npi.appendEntry(\"status-card\", { title: \"Indexed files\", count: 17 });\n```\n\n### pi.registerShortcut(atalho, opções)\n\nRegistre um atalho de teclado. Veja [keybindings.md](keybindings.md) para o formato do atalho e atalhos de teclado integrados.\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(nome, opções)\n\nRegistre um sinalizador CLI.\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(comando, argumentos, opções?)\n\nExecute um comando shell.\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(nomes)\n\nGerenciar ferramentas ativas. Isso funciona tanto para ferramentas integradas quanto para ferramentas registradas dinamicamente. `pi.getActiveTools()` retorna os nomes das ferramentas ativas como `string[]`; `pi.getAllTools()` retorna metadados para todas as ferramentas configuradas.\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()` retorna `name`, `description`, `parameters`, `promptGuidelines` e `sourceInfo`.\n\nValores típicos de `sourceInfo.source`:\n- `builtin` para ferramentas integradas\n- `sdk` para ferramentas passadas via `createAgentSession({ customTools })`\n- metadados de origem de extensão para ferramentas registradas por extensões\n\n### pi.setModel(modelo)\n\nDefina o modelo atual. Retorna `false` se nenhum API key estiver disponível para o modelo. Consulte [models.md](models.md) para configurar modelos personalizados.\n\n```typescript\nconst model = ctx.modelRegistry.find(\"anthropic\", \"claude-sonnet-4-5\");\nif (model) {\n  const success = await pi.setModel(model);\n  if (!success) {\n    ctx.ui.notify(\"No API key for this model\", \"error\");\n  }\n}\n```\n\n### pi.getThinkingLevel() / pi.setThinkingLevel(nível)\n\nObtenha ou defina o nível de pensamento. O nível é limitado às capacidades do modelo (modelos sem raciocínio sempre usam \"off\"). As alterações emitem `thinking_level_select`.\n\n```typescript\nconst current = pi.getThinkingLevel();  // \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\"\npi.setThinkingLevel(\"high\");\n```\n\n### pi.eventos\n\nBarramento de eventos compartilhado para comunicação entre ramais:\n\n```typescript\npi.events.on(\"my:event\", (data) => { ... });\npi.events.emit(\"my:event\", { ... });\n```\n\n### pi.registerProvider(nome, configuração)\n\nRegistre ou substitua um provedor de modelo dinamicamente. Útil para proxies, endpoints personalizados ou configurações de modelo para toda a equipe.\n\nAs chamadas feitas durante a função de fábrica do ramal são enfileiradas e aplicadas assim que o executor é inicializado. As chamadas feitas depois disso — por exemplo, de um manipulador de comando seguindo um fluxo de configuração do usuário — entram em vigor imediatamente sem exigir um `/reload`.\n\nProvedores dinâmicos podem implementar `refreshModels`. Pi chama-o durante a atualização do modelo, publica a lista retornada de forma síncrona por meio do provedor e passa o contexto canônico de credencial/catálogo armazenado/rede/sinal. A extensão decide se persiste os metadados do catálogo por meio de `context.publish({ persist: entry })` com verificação de geração; servidores live como llama.cpp podem retornar modelos sem persisti-los.\n\n`context.signal` é sempre um sinal concreto e os retornos de chamada do provedor devem passá-lo para bloquear I/O. As chamadas públicas `ModelRuntime.refresh()` e `ModelRegistry.refresh()` aceitam um sinal opcional e são ilimitadas quando ele é omitido; extensões e inscrições escolhem seus próprios prazos. O cancelamento interrompe a espera do chamador, mesmo que um provedor ignore o sinal, mas a cooperação ainda é necessária para interromper o trabalho subjacente.\n\nExtensions que precisam de autenticação, filtragem, atualização ou comportamento de fluxo do provedor nativo podem registrar um `Provider` completo de `@earendil-works/pi-ai`. O provedor se torna a base da composição e as substituições `models.json` ainda se aplicam acima dele.\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\nO formulário do objeto aceita um pi-ai `Provider` completo, incluindo comportamento nativo `auth`, `getModels`, `refreshModels`, `filterModels`, `stream` e `streamSimple`.\n\n**Opções de configuração legadas:**\n- `name` - Nome de exibição do provedor na UI, como `/login`.\n- `baseUrl` - API URL do terminal. Obrigatório ao definir modelos.\n- `apiKey` - API key literal, interpolação de ambiente (`$ENV_VAR` ou `${ENV_VAR}`) ou `!command` inicial. Obrigatório ao definir modelos (a menos que `oauth` seja fornecido). `$` escapa ``apiKey` - API key literal, interpolação de ambiente (`$ENV_VAR` ou `${ENV_VAR}`) ou `!command` inicial. Obrigatório ao definir modelos (a menos que `oauth` seja fornecido). `$` escapa  e `$!` escapa de um literal `!` sem acionar a execução do comando.\n- `api` - API tipo: `\"anthropic-messages\"`, `\"openai-completions\"`, `\"openai-responses\"`, etc.\n- `headers` - Cabeçalhos personalizados para incluir nas solicitações.\n- `authHeader` - Se verdadeiro, adiciona o cabeçalho `Authorization: Bearer` automaticamente.\n- `models` - Matriz de definições de modelo. Se fornecido, substitui todos os modelos existentes para este fornecedor. As definições de modelo podem definir `baseUrl` para substituir o terminal do provedor desse modelo.\n- `refreshModels` - Retorno de chamada de descoberta dinâmica assíncrona. Seus modelos retornados substituem os modelos fornecidos por extensão. `context.stored` contém o instantâneo do provedor persistente; use `context.publish({ persist: entry })` com verificação de geração somente quando os dados do catálogo atualizados persistirem. Use `persist: null` para excluir esse instantâneo.\n- `oauth` - configuração do provedor OAuth para suporte `/login`. Quando fornecido, o provedor aparece no menu de login.\n- `streamSimple` - Implementação de streaming personalizada para APIs não padrão.\n\nConsulte [custom-provider.md](custom-provider.md) para tópicos avançados: streaming personalizado APIs, OAuth detalhes, referência de definição de modelo.\n\n### pi.unregisterProvider(nome)\n\nRemova um provedor previamente cadastrado e seus modelos. Os modelos integrados que foram substituídos pelo provedor são restaurados. Não tem efeito se o provedor não estiver cadastrado.\n\nAssim como `registerProvider`, isso entra em vigor imediatamente quando chamado após a fase inicial de carregamento, portanto, `/reload` não é necessário.\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## Gestão Estadual\n\nExtensions com estado deve armazená-lo no resultado da ferramenta `details` para suporte adequado à ramificação:\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## Ferramentas personalizadas\n\nRegistre ferramentas que o LLM pode chamar via `pi.registerTool()`. As ferramentas aparecem no prompt do sistema e podem ter renderização personalizada.\n\nUse `promptSnippet` para uma entrada curta de uma linha na seção `Available tools` no prompt padrão do sistema. Se omitido, as ferramentas personalizadas serão deixadas de fora dessa seção.\n\nUse `promptGuidelines` para adicionar marcadores específicos da ferramenta à seção `Guidelines` do prompt padrão do sistema. Esses marcadores são incluídos apenas enquanto a ferramenta está ativa (por exemplo, após `pi.setActiveTools([...])`).\n\n**Importante:** os marcadores `promptGuidelines` são anexados na seção `Guidelines` sem prefixo ou agrupamento de nome de ferramenta. Cada diretriz deve nomear a ferramenta a que se refere - evite \"Use esta ferramenta quando...\" porque o LLM não pode dizer qual ferramenta \"isto\" significa. Escreva \"Use my_tool quando...\".\n\nNota: Alguns modelos são idiotas e incluem o prefixo @ nos argumentos do caminho da ferramenta. As ferramentas integradas retiram um @ inicial antes de resolver os caminhos. Se sua ferramenta personalizada aceitar um caminho, normalize um @ inicial também.\n\nSe sua ferramenta personalizada modificar arquivos, use `withFileMutationQueue()` para que ela participe da mesma fila por arquivo que `edit` e `write` integrados. Isso é importante porque as chamadas de ferramentas são executadas em paralelo por padrão. Sem a fila, duas ferramentas podem ler o mesmo conteúdo de arquivo antigo, calcular atualizações diferentes e, em seguida, a última gravação sobrescreve a outra.\n\nExemplo de caso de falha: sua ferramenta personalizada edita `foo.ts` enquanto o `edit` integrado também altera `foo.ts` no mesmo turno do assistente. Se a sua ferramenta não participar da fila, ambas poderão ler o `foo.ts` original, aplicar alterações separadas e uma dessas alterações será perdida.\n\nPasse o caminho real do arquivo de destino para `withFileMutationQueue()`, não o argumento bruto do usuário. Resolva-o primeiro para um caminho absoluto, relativo a `ctx.cwd` ou ao diretório de trabalho da sua ferramenta. Para arquivos existentes, o auxiliar canoniza por meio de `realpath()`, portanto, os aliases de links simbólicos para o mesmo arquivo compartilham uma fila. Para novos arquivos, ele retorna ao caminho absoluto resolvido porque ainda não há nada para `realpath()`.\n\nColoque toda a janela de mutação na fila nesse caminho de destino. Isso inclui a lógica de leitura-modificação-gravação, não apenas a gravação final.\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### Definição de ferramenta\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**Contabilidade de uso:** Se uma ferramenta fizer chamadas LLM aninhadas, retorne seu `Usage` combinado como `usage`. Pi persiste no resultado da ferramenta e inclui-o no rodapé, `/session` e RPC totais da sessão. `tool_result` manipuladores podem inspecionar ou substituir este valor.\n\n**Erros de sinalização:** Para marcar a execução de uma ferramenta como falhada (definir `isError: true` no resultado e reportá-lo ao LLM), gere um erro de `execute`. Retornar um valor nunca define o sinalizador de erro, independentemente das propriedades incluídas no objeto de retorno.\n\n**Encerramento antecipado:** Retorne `terminate: true` de `execute()` para sugerir que a chamada LLM de acompanhamento automático deve ser ignorada após o lote de ferramentas atual. Isso só entra em vigor quando cada resultado de ferramenta finalizado nesse lote estiver sendo finalizado. Consulte [examples/extensions/structured-output.ts](../examples/extensions/structured-output.ts) para obter um exemplo mínimo de onde o agente termina em uma chamada final da ferramenta de saída estruturada.\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**Importante:** Use `StringEnum` de `@earendil-works/pi-ai` para enumerações de strings. `Type.Union`/`Type.Literal` não funciona com API do Google.\n\n**Preparação de argumentos:** `prepareArguments(args)` é opcional. Se definido, ele é executado antes da validação do esquema e antes de `execute()`. Use-o para imitar uma forma de entrada aceita mais antiga quando pi retoma uma sessão mais antiga cujos argumentos de chamada de ferramenta armazenados não correspondem mais ao esquema atual. Retorne o objeto que você deseja validar em `parameters`. Mantenha o esquema público rigoroso. Não adicione campos de compatibilidade obsoletos a `parameters` apenas para manter sessões antigas retomadas funcionando.\n\nExemplo: uma sessão mais antiga pode conter uma chamada de ferramenta `edit` com `oldText` e `newText` de nível superior, enquanto o esquema atual aceita apenas `edits: [{ oldText, newText }]`.\n\n```typescript\npi.registerTool({\n  name: \"edit\",\n  label: \"Edit\",\n  description: \"Edit a single file using exact text replacement\",\n  parameters: Type.Object({\n    path: Type.String(),\n    edits: Type.Array(\n      Type.Object({\n        oldText: Type.String(),\n        newText: Type.String(),\n      }),\n    ),\n  }),\n  prepareArguments(args) {\n    if (!args || typeof args !== \"object\") return args;\n\n    const input = args as {\n      path?: string;\n      edits?: Array<{ oldText: string; newText: string }>;\n      oldText?: unknown;\n      newText?: unknown;\n    };\n\n    if (typeof input.oldText !== \"string\" || typeof input.newText !== \"string\") {\n      return args;\n    }\n\n    return {\n      ...input,\n      edits: [...(input.edits ?? []), { oldText: input.oldText, newText: input.newText }],\n    };\n  },\n  async execute(toolCallId, params, signal, onUpdate, ctx) {\n    // params now matches the current schema\n    return {\n      content: [{ type: \"text\", text: `Applying ${params.edits.length} edit block(s)` }],\n      details: {},\n    };\n  },\n});\n```\n\n### Substituindo ferramentas integradas\n\nExtensions pode substituir ferramentas integradas (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`) registrando uma ferramenta com o mesmo nome. O modo interativo exibe um aviso quando isso acontece.\n\n```bash\n# Extension's read tool replaces built-in read\npi -e ./tool-override.ts\n```\n\nAlternativamente, use `--no-builtin-tools` para iniciar sem nenhuma ferramenta integrada, mantendo as ferramentas de extensão habilitadas:\n```bash\n# No built-in tools, only extension tools\npi --no-builtin-tools -e ./my-extension.ts\n```\n\nVeja [examples/extensions/tool-override.ts](../examples/extensions/tool-override.ts) para um exemplo completo que substitui `read` pelo registro e controle de acesso.\n\n**Renderização:** A herança do renderizador integrado é resolvida por slot. A substituição de execução e a substituição de renderização são independentes. Se sua substituição omitir `renderCall`, o `renderCall` integrado será usado. Se sua substituição omitir `renderResult`, o `renderResult` integrado será usado. Se sua substituição omitir ambos, o renderizador integrado será usado automaticamente (destaque de sintaxe, diferenças, etc.). Isso permite agrupar ferramentas integradas para registro ou controle de acesso sem reimplementar a IU.\n\n**Metadados de prompt:** `promptSnippet` e `promptGuidelines` não são herdados da ferramenta integrada. Se sua substituição deve manter essas instruções imediatas, defina-as explicitamente na substituição.\n\n**Sua implementação deve corresponder exatamente ao formato do resultado**, incluindo o tipo `details`. A UI e a lógica da sessão dependem dessas formas para renderização e rastreamento de estado.\n\nImplementações de ferramentas integradas:\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### Execução Remota\n\nFerramentas integradas suportam operações conectáveis ​​para delegação a sistemas remotos (SSH, contêineres, etc.):\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**Interfaces de operações:** `ReadOperations`, `WriteOperations`, `EditOperations`, `BashOperations`, `LsOperations`, `GrepOperations`, `FindOperations`\n\nPara `user_bash`, as extensões podem reutilizar o back-end do shell local do pi via `createLocalBashOperations()` em vez de reimplementar a geração de processos locais, resolução de shell e encerramento da árvore de processos.\n\nA ferramenta bash também suporta um gancho de spawn para ajustar o comando, cwd ou env antes da execução:\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()` expõe a sessão atual aos comandos através de `PI_SESSION_ID`, `PI_SESSION_FILE`, `PI_PROVIDER`, `PI_MODEL` e `PI_REASONING_LEVEL`. A injeção acontece antes de `spawnHook`, então os ganchos recebem esses valores em `env` e os preservam quando espalham o ambiente existente como acima. Defina `exposeSessionEnvironment: false` para desativá-los:\n\n```typescript\nconst bashTool = createBashTool(cwd, {\n  exposeSessionEnvironment: false,\n});\n```\n\nVeja [Bash tool session environment](environment-variables.md#bash-tool-session-environment) para semântica de variáveis. Veja [examples/extensions/ssh.ts](../examples/extensions/ssh.ts) para um exemplo completo de SSH com flag `--ssh`.\n\n### Truncamento de saída\n\n**As ferramentas DEVEM truncar sua saída** para evitar sobrecarregar o contexto do LLM. Grandes saídas podem causar:\n- Erros de estouro de contexto (prompt muito longo)\n- Falhas de compactação\n- Desempenho do modelo degradado\n\nO limite integrado é de **50 KB** (~10 mil tokens) e **2.000 linhas**, o que for atingido primeiro. Use os utilitários de truncamento exportados:\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**Pontos principais:**\n- Use `truncateHead` para conteúdo onde o início importa (resultados de pesquisa, leituras de arquivos)\n- Use `truncateTail` para conteúdo onde o final importa (logs, saída de comando)\n- Sempre informe o LLM quando a saída estiver truncada e onde encontrar a versão completa\n- Documente os limites de truncamento na descrição da sua ferramenta\n\nVeja [examples/extensions/truncated-tool.ts](../examples/extensions/truncated-tool.ts) para um exemplo completo envolvendo `rg` (ripgrep) com truncamento adequado.\n\n### Várias ferramentas\n\nUma extensão pode registrar diversas ferramentas com estado compartilhado:\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### Renderização personalizada\n\nAs ferramentas podem fornecer `renderCall` e `renderResult` para exibição personalizada de TUI. Consulte [tui.md](tui.md) para o componente completo API e [tool-execution.ts](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/modes/interactive/components/tool-execution.ts) para saber como as linhas de ferramentas são compostas.\n\nPor padrão, a saída da ferramenta é encapsulada em `Box` que trata do preenchimento e do plano de fundo. Um `renderCall` ou `renderResult` definido deve retornar um `Component`. Se um renderizador de slot não estiver definido, `tool-execution.ts` usa renderização substituta para esse slot.\n\nDefina `renderShell: \"self\"` quando a ferramenta deve renderizar seu próprio shell em vez de usar o padrão `Box`. Isso é útil para ferramentas que precisam de controle total sobre o enquadramento ou o comportamento do plano de fundo, por exemplo, visualizações grandes que devem permanecer visualmente estáveis ​​após a estabilização da ferramenta.\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` e `renderResult` recebem cada um um objeto `context` com:\n- `args` - os argumentos atuais da chamada da ferramenta\n- `state` - estado local de linha compartilhado entre `renderCall` e `renderResult`\n- `lastComponent` - o componente retornado anteriormente para esse slot, se houver\n- `invalidate()` - solicita uma nova renderização desta linha de ferramenta\n- `toolCallId`, `cwd`, `executionStarted`, `argsComplete`, `isPartial`, `expanded`, `showImages`, `isError`\n\nUse `context.state` para estado compartilhado entre slots. Mantenha caches locais de slot na instância do componente retornado quando quiser reutilizar e alterar o mesmo componente nas renderizações.\n\n#### renderCall\n\nRenderiza a chamada ou cabeçalho da ferramenta:\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\nRenderiza o resultado ou saída da ferramenta:\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\nSe um slot intencionalmente não tiver conteúdo visível, retorne um `Component` vazio, como um `Container` vazio.\n\n#### Dicas de atalho de teclado\n\nUse `keyHint()` para exibir dicas de atalhos de teclado que respeitam a configuração de atalhos de teclado ativa:\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\nFunções disponíveis:\n- `keyHint(keybinding, description)` - Formata um ID de atalho de teclado configurado, como `\"app.tools.expand\"` ou `\"tui.select.confirm\"`\n- `keyText(keybinding)` - Retorna o texto da chave configurada bruta para um ID de atalho de teclado\n- `rawKeyHint(key, description)` - Formatar uma string de chave bruta\n\nUse IDs de atalhos de teclado com namespace:\n- Os IDs do agente de codificação usam o namespace `app.*`, por exemplo `app.tools.expand`, `app.editor.external`, `app.session.rename`\n- Os ids TUI compartilhados usam o namespace `tui.*`, por exemplo `tui.select.confirm`, `tui.select.cancel`, `tui.input.tab`\n\nPara a lista completa de ids e padrões de atalhos de teclado, consulte [keybindings.md](keybindings.md). `keybindings.json` usa os mesmos IDs com namespace.\n\nEditores personalizados e componentes `ctx.ui.custom()` recebem `keybindings: KeybindingsManager` como argumento injetado. Eles deveriam usar esse gerenciador injetado diretamente em vez de chamar `getKeybindings()` ou `setKeybindings()`.\n\n#### Melhores Práticas\n\n- Use `Text` com preenchimento `(0, 0)`. A caixa padrão lida com o preenchimento.\n- Use `\\n` para conteúdo multilinha.\n- Identificador `isPartial` para progresso de streaming.\n- Suporte `expanded` para detalhes sob demanda.\n- Mantenha a visualização padrão compacta.\n- Leia `context.args` em `renderResult` em vez de copiar argumentos em `context.state`.\n- Use `context.state` apenas para dados que devem ser compartilhados entre slots de chamadas e resultados.\n- Reutilize `context.lastComponent` quando a mesma instância do componente puder ser atualizada no local.\n- Use `renderShell: \"self\"` somente quando o shell em caixa padrão atrapalhar. No modo self-shell, a ferramenta é responsável por seu próprio enquadramento, preenchimento e plano de fundo.\n\n#### Cair pra trás\n\nSe um renderizador de slot não estiver definido ou gerar:\n- `renderCall`: Mostra o nome da ferramenta\n- `renderResult`: Mostra texto bruto de `content`\n\n### Carregamento dinâmico de ferramentas\n\nExtensions pode registrar muitas ferramentas enquanto mantém ativo apenas um pequeno conjunto inicial. Uma ferramenta pode então adicionar mais ferramentas com `pi.setActiveTools()` durante a execução. Pi detecta alterações puramente aditivas, registra os nomes de ferramentas recentemente disponíveis no resultado da ferramenta e aplica o conjunto ativo atualizado antes da próxima solicitação de modelo.\n\nIsso funciona com todos os modelos. Models com suporte nativo de carregamento diferido preserva o prefixo de prompt estável e carrega as novas definições na posição do resultado da ferramenta. Outros modelos usam o substituto descrito abaixo.\n\nO ciclo de vida é:\n\n1. Registre cada ferramenta com `pi.registerTool()` para que apareça em `pi.getAllTools()`.\n2. Mantenha as ferramentas do carregador, como `search_tools`, ativas e deixe as ferramentas pesquisáveis ​​inativas.\n3. Durante a execução do carregador, chame `pi.setActiveTools([...currentTools,...matchingTools])`. A mudança deve ser aditiva: não remova ferramentas atualmente ativas na mesma chamada.\n4. Pi registra quais ferramentas foram adicionadas no resultado da ferramenta do carregador.\n5. Antes da próxima resposta do modelo, Pi expõe as definições adicionadas usando carregamento adiado nativo quando suportado, ou a lista de ferramentas ativas normais caso contrário.\n\nVocê não precisa retornar referências de ferramentas específicas do provedor ou marcar o carregador como uma ferramenta de pesquisa especial. A troca de ferramenta ativa é o sinal. Os nomes passados ​​para `pi.setActiveTools()` já devem estar registrados; nomes desconhecidos são ignorados.\n\n#### Models com carregamento diferido nativo\n\n- **Antrópico**\n  - **Models:** Sonnet, Opus, Fable versão 4.5 ou mais recente (sem Haiku)\n  - **Representação nativa:** As definições diferidas usam `defer_loading`; o ponto de carregamento usa conteúdo `tool_reference`.\n- **AbertaAI**\n  - **Models:** `gpt-5.4` e família mais recente\n  - **Representação nativa:** Pi adiciona itens de cliente `tool_search_call` e `tool_search_output` concluídos no ponto de carregamento.\n\nPara um modelo personalizado ou proxy verificado, a manipulação nativa pode ser habilitada com `compat.supportsToolReferences: true` para `anthropic-messages` ou `compat.supportsToolSearch: true` para `openai-responses` e `openai-codex-responses`. Deixe-os desabilitados, a menos que o endpoint e o modelo aceitem o protocolo nativo correspondente.\n\n#### Comportamento alternativo\n\nPara todos os outros modelos e provedores, a ativação dinâmica ainda funciona: Pi envia a lista completa de ferramentas ativas atualmente normalmente na próxima solicitação. O modelo pode chamar as ferramentas recém-ativadas, mas adicionar suas definições pode invalidar o prefixo de prompt armazenado em cache do provedor.\n\nPi também utiliza esse recurso seguro quando o conjunto ativo não é puramente aditivo, como a substituição de um grupo de ferramentas por outro. Portanto, as remoções de ferramentas funcionam, mas não utilizam carregamento diferido.\n\nPara obter o melhor comportamento do cache, mantenha a ferramenta de carregamento ativa durante toda a sessão e adicione ferramentas em vez de substituir o conjunto ativo. Observe também que ativar uma ferramenta com `promptSnippet` ou `promptGuidelines` reconstrói o prompt do sistema; essa alteração no prompt do sistema pode invalidar o prefixo mesmo quando o provedor oferece suporte a esquemas adiados. Ferramentas carregadas lentamente geralmente devem confiar em sua ferramenta `description` e omitir metadados de prompt somente ativos.\n\n#### Exemplo de ferramenta de pesquisa\n\nA extensão a seguir registra duas ferramentas pesquisáveis, remove-as do conjunto ativo inicial e mantém apenas `search_tools` como seu carregador. O exemplo usa correspondência simples de palavras-chave, mas a implementação de pesquisa poderia usar BM25, embeddings, um catálogo remoto ou roteamento específico do projeto.\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\nQuando `search_tools` adiciona uma correspondência, o modelo recebe essa definição na solicitação imediatamente seguinte. Em um modelo com capacidade nativa, a definição é ancorada após o resultado da pesquisa sem alterar o prefixo do esquema de ferramenta inicial. Em outros modelos, ele aparece na lista normal de ferramentas na mesma solicitação seguinte.\n\n## IU personalizada\n\nExtensions pode interagir com os usuários por meio de métodos `ctx.ui` e personalizar como as mensagens/ferramentas são renderizadas.\n\n**Para componentes personalizados, consulte [tui.md](tui.md)** que possui padrões de copiar e colar para:\n- Diálogos de seleção (SelectList)\n- Operações assíncronas com cancelamento (BorderedLoader)\n- Alternância de configurações (SettingsList)\n- Indicadores de status (setStatus)\n- Mensagem de trabalho, visibilidade e indicador durante a transmissão (`setWorkingMessage`, `setWorkingVisible`, `setWorkingIndicator`)\n- Editor de widgets acima/abaixo (setWidget)\n- Provedores de preenchimento automático em camadas sobre a conclusão de barra/caminho integrada (addAutocompleteProvider)\n- Rodapés personalizados (setFooter)\n\n### Diálogos\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#### Diálogos cronometrados com contagem regressiva\n\nAs caixas de diálogo suportam uma opção `timeout` que é descartada automaticamente com uma exibição de contagem regressiva ao vivo:\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**Valores retornados no tempo limite:**\n- `select()` retorna `undefined`\n- `confirm()` retorna `false`\n- `input()` retorna `undefined`\n\n#### Demissão manual com AbortSignal\n\nPara obter mais controle (por exemplo, para distinguir o tempo limite do cancelamento do usuário), use `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\nVeja [examples/extensions/timed-confirm.ts](../examples/extensions/timed-confirm.ts) para exemplos completos.\n\n### Widgets, status e rodapé\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\nOs quadros de indicadores de trabalho personalizados são renderizados literalmente. Se você quiser cores, adicione-as você mesmo às strings do quadro, por exemplo, com `ctx.ui.theme.fg(...)`.\n\n### Preenchimento automático Providers\n\nUse `ctx.ui.addAutocompleteProvider()` para empilhar a lógica de preenchimento automático personalizada sobre o comando de barra integrado e o provedor de caminho. Defina `triggerCharacters` para gatilhos naturais personalizados, como `Use `ctx.ui.addAutocompleteProvider()` para empilhar a lógica de preenchimento automático personalizada sobre o comando de barra integrado e o provedor de caminho. Defina `triggerCharacters` para gatilhos naturais personalizados, como.\n\nPadrão típico:\n\n- inspecionar o texto antes do cursor\n- retorne suas próprias sugestões quando a sintaxe específica da extensão corresponder\n- caso contrário, delegue para `current.getSuggestions(...)`\n- delegar `applyCompletion(...)` a menos que você precise de um comportamento de inserção personalizado\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\nVeja [github-issue-autocomplete.ts](../examples/extensions/github-issue-autocomplete.ts) para um exemplo completo que pré-carrega os últimos problemas GitHub abertos com `gh issue list` e os filtra localmente para conclusão rápida de `#...`. Requer GitHub CLI (`gh`) e um checkout de repositório GitHub.\n\n### Componentes personalizados\n\nPara UI complexa, use `ctx.ui.custom()`. Isso substitui temporariamente o editor pelo seu componente até que `done()` seja chamado:\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\nO retorno de chamada recebe:\n- `tui` - TUI instância (para dimensões da tela, gerenciamento de foco)\n- `theme` - Tema atual para estilo\n- `keybindings` - Gerenciador de atalhos de teclado do aplicativo (para verificar atalhos)\n- `done(value)` - Chamada para fechar componente e retornar valor\n\nVeja [tui.md](tui.md) para o componente completo API.\n\n#### Modo de sobreposição (experimental)\n\nPasse `{ overlay: true }` para renderizar o componente como um modal flutuante sobre o conteúdo existente, sem limpar a tela:\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\nPara posicionamento avançado (âncoras, margens, porcentagens, visibilidade responsiva), passe `overlayOptions`. Use `onHandle` para controlar o foco ou a visibilidade programaticamente:\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\nUma sobreposição visível focada pode recuperar a entrada após o fechamento da UI personalizada temporária sem sobreposição. Se você quiser intencionalmente que outro componente mantenha a entrada enquanto a sobreposição permanece visível, chame `handle.unfocus({ target })`. Passar `{ target: null }` libera a sobreposição sem focar outro componente.\n\nVeja [tui.md](tui.md) para `OverlayOptions` completo e `OverlayHandle` API e [overlay-qa-tests.ts](../examples/extensions/overlay-qa-tests.ts) para exemplos.\n\n### Editor personalizado\n\nSubstitua o editor de entrada principal por uma implementação personalizada (modo vim, modo emacs, etc.):\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**Pontos principais:**\n- Estenda `CustomEditor` (não base `Editor`) para obter atalhos de teclado do aplicativo (escape para abortar, ctrl+d, troca de modelo)\n- Ligue para `super.handleInput(data)` para chaves que você não manuseia\n- A fábrica recebe `tui`, `theme` e `keybindings` do aplicativo\n- Use `ctx.ui.getEditorComponent()` antes de `setEditorComponent()` para agrupar o editor personalizado configurado anteriormente\n- Passe `undefined` para restaurar o padrão: `ctx.ui.setEditorComponent(undefined)`\n\nPara compor com outra extensão que já substituiu o editor, capture a fábrica anterior antes de configurar a sua:\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\nVeja [tui.md](tui.md) Padrão 7 para um exemplo completo com indicador de modo.\n\n### Renderização de mensagens e entradas\n\nRegistre um renderizador personalizado para mensagens com seu `customType`. Use renderizadores de mensagens para conteúdo que deve participar do contexto LLM:\n\n```typescript\nimport { Text } from \"@earendil-works/pi-tui\";\n\npi.registerMessageRenderer(\"my-extension\", (message, options, theme) => {\n  const { expanded, outputPad } = options;\n  let text = theme.fg(\"accent\", `[${message.customType}] `);\n  text += message.content;\n\n  if (expanded && message.details) {\n    text += \"\\n\" + theme.fg(\"dim\", JSON.stringify(message.details, null, 2));\n  }\n\n  return new Text(text, outputPad, 0);\n});\n```\n\nAs mensagens são enviadas via `pi.sendMessage()`:\n\n```typescript\npi.sendMessage({\n  customType: \"my-extension\",  // Matches registerMessageRenderer\n  content: \"Status update\",\n  display: true,               // Show in TUI\n  details: { ... },            // Available in renderer\n});\n```\n\nPara conteúdo somente TUI que não deve ser enviado ao LLM, renderize entradas personalizadas:\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### Cores do tema\n\nTodas as funções de renderização recebem um objeto `theme`. Consulte [themes.md](themes.md) para criar temas personalizados e a paleta de cores completa.\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\nPara realce de sintaxe em renderizadores de ferramentas personalizadas:\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## Tratamento de erros\n\n- Erros de extensão são registrados, o agente continua\n- `tool_call` erros bloqueiam a ferramenta (à prova de falhas)\n- Erros da ferramenta `execute` devem ser sinalizados por arremesso; o erro gerado é detectado, relatado ao LLM com `isError: true` e a execução continua\n\n## Comportamento do modo\n\n| Modo | `ctx.mode` | `ctx.hasUI` | Notas |\n|------|------------|-------------|-------|\n| Interativo | `\"tui\"` | `true` | TUI completo com renderização de terminal |\n| RPC (`--mode rpc`) | `\"rpc\"` | `true` | Diálogos e notificações via protocolo JSON; `custom()` retorna `undefined`. Veja [rpc.md](rpc.md) |\n| JSON (`--mode json`) | `\"json\"` | `false` | Fluxo de eventos para stdout; Os métodos de UI são autônomos |\n| Imprimir (`-p`) | `\"print\"` | `false` | Extensions executa mas não consegue avisar |\n\nUse `ctx.mode === \"tui\"` antes de recursos específicos de TUI (`custom()`, fábricas de componentes, entrada de terminal). Use `ctx.hasUI` antes dos métodos de diálogo e notificação que funcionam nos modos TUI e RPC.\n\n## Referência de exemplos\n\nTodos os exemplos em [examples/extensions/](../examples/extensions/).\n\n| Exemplo | Descrição | Chave APIs |\n|---------|-------------|----------|\n| **Ferramentas** |  |  |\n| `hello.ts` | Registro mínimo de ferramenta | `registerTool` |\n| `question.ts` | Ferramenta com interação do usuário | `registerTool`, `ui.select` |\n| `questionnaire.ts` | Ferramenta de assistente de várias etapas | `registerTool`, `ui.custom` |\n| `todo.ts` | Ferramenta stateful com persistência | `registerTool`, `appendEntry`, `renderResult`, eventos de sessão |\n| `dynamic-tools.ts` | Registrar ferramentas após inicialização e durante comandos | `registerTool`, `session_start`, `registerCommand` |\n| `structured-output.ts` | Ferramenta final de saída estruturada com `terminate: true` | `registerTool`, finalizando resultados da ferramenta |\n| `truncated-tool.ts` | Exemplo de truncamento de saída | `registerTool`, `truncateHead` |\n| `tool-override.ts` | Substituir ferramenta de leitura integrada | `registerTool` (mesmo nome do integrado) |\n| **Comandos** |  |  |\n| `pirate.ts` | Modificar prompt do sistema por turno | `registerCommand`, `before_agent_start` |\n| `summarize.ts` | Comando de resumo de conversa | `registerCommand`, `ui.custom` |\n| `handoff.ts` | Transferência de modelo entre provedores | `registerCommand`, `ui.editor`, `ui.custom` |\n| `qna.ts` | Perguntas e respostas com interface personalizada | `registerCommand`, `ui.custom`, `setEditorText` |\n| `send-user-message.ts` | Injetar mensagens do usuário | `registerCommand`, `sendUserMessage` |\n| `reload-runtime.ts` | Comando de recarga e transferência de ferramenta LLM | `registerCommand`, `ctx.reload()`, `sendUserMessage` |\n| `shutdown-command.ts` | Comando de desligamento elegante | `registerCommand`, `shutdown()` |\n| **Eventos e portões** |  |  |\n| `permission-gate.ts` | Bloqueie comandos perigosos | `on(\"tool_call\")`, `ui.confirm` |\n| `project-trust.ts` | Decidir ou adiar a confiança do projeto de um usuário/global ou extensão CLI | `on(\"project_trust\")`, UI confiável, resultado de confiança necessário |\n| `protected-paths.ts` | Bloquear gravações em caminhos específicos | `on(\"tool_call\")` |\n| `confirm-destructive.ts` | Confirmar alterações de sessão | `on(\"session_before_switch\")`, `on(\"session_before_fork\")` |\n| `dirty-repo-guard.ts` | Avisar sobre repositório git sujo | `on(\"session_before_*\")`, `exec` |\n| `input-transform.ts` | Transformar a entrada do usuário | `on(\"input\")` |\n| `input-transform-streaming.ts` | Transformação de entrada com reconhecimento de streaming | `on(\"input\")`, `streamingBehavior` |\n| `model-status.ts` | React para modelar mudanças | `on(\"model_select\")`, `setStatus` |\n| `provider-payload.ts` | Inspecione cargas úteis e cabeçalhos de resposta do provedor | `on(\"before_provider_request\")`, `on(\"after_provider_response\")` |\n| `system-prompt-header.ts` | Exibir informações de prompt do sistema | `on(\"agent_start\")`, `getSystemPrompt` |\n| `claude-rules.ts` | Carregar regras de arquivos | `on(\"session_start\")`, `on(\"before_agent_start\")` |\n| `prompt-customizer.ts` | Adicione orientação de ferramenta sensível ao contexto usando `systemPromptOptions` | `on(\"before_agent_start\")`, `BuildSystemPromptOptions` |\n| `file-trigger.ts` | O observador de arquivos aciona mensagens | `sendMessage` |\n| **Compactação e Sessões** |  |  |\n| `custom-compaction.ts` | Resumo de compactação personalizado | `on(\"session_before_compact\")` |\n| `trigger-compact.ts` | Acionar a compactação manualmente | `compact()` |\n| `git-checkpoint.ts` | Git estoque em turnos | `on(\"turn_start\")`, `on(\"session_before_fork\")`, `exec` |\n| `git-merge-and-resolve.ts` | Buscar, mesclar e resolver conflitos | `on(\"agent_end\")`, `exec`, `sendUserMessage` |\n| `auto-commit-on-exit.ts` | Confirmar no desligamento | `on(\"session_shutdown\")`, `exec` |\n| **Componentes da IU** |  |  |\n| `status-line.ts` | Indicador de status do rodapé | `setStatus`, eventos de sessão |\n| `working-indicator.ts` | Personalize o indicador de funcionamento do streaming | `setWorkingIndicator`, `registerCommand` |\n| `github-issue-autocomplete.ts` | Adicione conclusões de problemas `#1234` além do preenchimento automático integrado, pré-carregando problemas abertos recentes de `gh issue list` | `addAutocompleteProvider`, `on(\"session_start\")`, `exec` |\n| `custom-footer.ts` | Substitua totalmente o rodapé | `registerCommand`, `setFooter` |\n| `custom-header.ts` | Substituir cabeçalho de inicialização | `on(\"session_start\")`, `setHeader` |\n| `modal-editor.ts` | Editor modal estilo Vim | `setEditorComponent`, `CustomEditor` |\n| `rainbow-editor.ts` | Estilo de editor personalizado | `setEditorComponent` |\n| `widget-placement.ts` | Editor de widget acima/abaixo | `setWidget` |\n| `overlay-test.ts` | Componentes de sobreposição | `ui.custom` com opções de sobreposição |\n| `overlay-qa-tests.ts` | Testes de sobreposição abrangentes | `ui.custom`, todas as opções de sobreposição |\n| `notify.ts` | Notificações simples | `ui.notify` |\n| `timed-confirm.ts` | Diálogos com tempo limite | `ui.confirm` com tempo limite/sinal |\n| `mac-system-theme.ts` | Tema de troca automática | `setTheme`, `exec` |\n| **Complexo Extensions** |  |  |\n| `plan-mode/` | Implementação completa do modo de plano | Todos os tipos de eventos, `registerCommand`, `registerShortcut`, `registerFlag`, `setStatus`, `setWidget`, `sendMessage`, `setActiveTools` |\n| `preset.ts` | Predefinições salváveis ​​(modelo, ferramentas, pensamento) | `registerCommand`, `registerShortcut`, `registerFlag`, `setModel`, `setActiveTools`, `setThinkingLevel`, `appendEntry` |\n| `tools.ts` | Ativar/desativar ferramentas da interface do usuário | `registerCommand`, `setActiveTools`, `SettingsList`, eventos de sessão |\n| **Remoto e Sandbox** |  |  |\n| `ssh.ts` | SSH execução remota | `registerFlag`, `on(\"user_bash\")`, `on(\"before_agent_start\")`, operações de ferramenta |\n| `interactive-shell.ts` | Sessão de shell persistente | `on(\"user_bash\")` |\n| `sandbox/` | Execução de ferramenta em sandbox | Operações de ferramentas |\n| `gondolin/` | Roteie ferramentas integradas e comandos `!` para uma micro-VM Gondolin | Operações de ferramentas, substituições de ferramentas integradas, `on(\"user_bash\")` |\n| `subagent/` | Gerar subagentes | `registerTool`, `exec` |\n| **Jogos** |  |  |\n| `snake.ts` | Jogo de cobra | `registerCommand`, `ui.custom`, manuseio do teclado |\n| `space-invaders.ts` | Jogo Invasores do Espaço | `registerCommand`, `ui.custom` |\n| `doom-overlay/` | Perdição em sobreposição | `ui.custom` com sobreposição |\n| **Providers** |  |  |\n| `custom-provider-anthropic/` | Proxy antrópico personalizado | `registerProvider` |\n| `custom-provider-gitlab-duo/` | GitIntegração do Lab Duo | `registerProvider` com OAuth |\n| **Mensagens e comunicação** |  |  |\n| `message-renderer.ts` | Renderização de mensagem personalizada | `registerMessageRenderer`, `sendMessage` |\n| `entry-renderer.ts` | TUI renderização de entrada personalizada somente | `registerEntryRenderer`, `appendEntry` |\n| `event-bus.ts` | Eventos entre extensões | `pi.events` |\n| **Metadados da sessão** |  |  |\n| `session-name.ts` | Nomear sessões para o seletor | `setSessionName`, `getSessionName` |\n| `bookmark.ts` | Marcar entradas para /tree | `setLabel` |\n| **Diversos** |  |  |\n| `inline-bash.ts` | Inline bash em chamadas de ferramenta | `on(\"tool_call\")` |\n| `bash-spawn-hook.ts` | Ajuste o comando bash, cwd e env antes da execução | `createBashTool`, `spawnHook` |\n| `with-deps/` | Extensão com dependências npm | Estrutura do pacote com `package.json` |","sourceFile":"extensions.md"},"index":{"title":"Pi Documentação","markdown":"Pi é um conjunto mínimo de codificação de terminal. Ele foi projetado para permanecer pequeno no núcleo enquanto é estendido por meio de extensões TypeScript, habilidades, prompt templates, temas e pacotes pi.\n\n## Início rápido\n\nInstale Pi com npm:\n\n```bash\nnpm install -g --ignore-scripts @earendil-works/pi-coding-agent\n```\n\n`--ignore-scripts` desativa scripts de ciclo de vida de dependência durante a instalação. Pi não requer scripts de instalação para instalações normais do npm.\n\nNo Linux ou macOS, você também pode usar o instalador:\n\n```bash\ncurl -fsSL https://pi.dev/install.sh | sh\n```\n\nPara desinstalar o próprio pi, use npm para instalações curl e npm:\n\n```bash\nnpm uninstall -g @earendil-works/pi-coding-agent\n```\n\nPara instalações pnpm, Yarn ou Bun, use o comando de remoção global correspondente: `pnpm remove -g @earendil-works/pi-coding-agent`, `yarn global remove @earendil-works/pi-coding-agent` ou `bun uninstall -g @earendil-works/pi-coding-agent`.\n\nEm seguida, execute-o em um diretório do projeto:\n\n```bash\npi\n```\n\nAutentique com `/login` para subscription providers ou defina um API key como `ANTHROPIC_API_KEY` antes de iniciar o pi.\n\nPara o fluxo completo da primeira execução, consulte [Quickstart](quickstart.md).\n\n## Comece aqui\n\n- [Quickstart](quickstart.md) - instale, autentique e execute uma primeira sessão.\n- [Using Pi](usage.md) - modo interativo, referência slash commands, context files e CLI.\n- [Providers](providers.md) - assinatura e configuração de API chave para provedores integrados.\n- [llama.cpp](llama-cpp.md) - execute um roteador local e gerencie modelos com `/llama`.\n- [Security](security.md) - confiança do projeto, sandbox limites e relatórios de vulnerabilidade.\n- [Containerization](containerization.md) - sandbox pi com Gondolin, Docker ou OpenShell.\n- [Settings](settings.md) - configurações globais e do projeto.\n- [Keybindings](keybindings.md) - atalhos padrão e combinações de teclas personalizadas.\n- [Sessions](sessions.md) - gerenciamento de sessão, ramificação e navegação em árvore.\n- [Compaction](compaction.md) - context compaction e branch summarization.\n\n## Personalização\n\n- [Extensions](extensions.md) - TypeScript módulos para ferramentas, comandos, eventos e UI personalizada.\n- [Skills](skills.md) - Agente Skills para recursos reutilizáveis ​​sob demanda.\n- [Prompt templates](prompt-templates.md) - prompts reutilizáveis ​​que se expandem de slash commands.\n- [Themes](themes.md) - integrado e personalizado terminal themes.\n- [Pi packages](packages.md) - agrupe e compartilhe extensões, habilidades, prompts e temas.\n- [Custom models](models.md) - adiciona entradas de modelo para provedores suportados APIs.\n- [Custom providers](custom-provider.md) - implementa fluxos APIs e OAuth personalizados.\n\n## Uso programático\n\n- [SDK](sdk.md) - incorpora pi em aplicativos Node.js.\n- [RPC mode](rpc.md) - integra sobre stdin/stdout JSONL.\n- [JSON event stream mode](json.md) - modo de impressão com eventos estruturados.\n- [TUI components](tui.md) - construa UI de terminal personalizada para extensões.\n\n## Referência\n\n- [Environment variables](environment-variables.md) - Pi configuração do processo e metadados de sessão disponíveis para ferramentas bash.\n- [Session format](session-format.md) - JSONL formato de arquivo de sessão, tipos de entrada e SessionManager API.\n\n## Configuração da plataforma\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## Desenvolvimento\n\n- [Development](development.md) - configuração local, estrutura do projeto e depuração.","sourceFile":"index.md"},"json":{"title":"JSON Modo de transmissão de eventos","markdown":"```bash\npi --mode json \"Your prompt\"\n```\n\nGera todos os eventos de sessão como linhas JSON para stdout. Útil para integrar pi em outras ferramentas ou UIs personalizadas.\n\n## Tipos de eventos\n\nOs eventos de transmissão usam `JsonAgentSessionEvent`. Combina\n[`AgentSessionEvent`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/agent-session.ts)\nexceto que as atualizações de mensagens de streaming omitem os instantâneos cumulativos:\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` emite todas as filas de orientação e acompanhamento pendentes sempre que elas mudam. `compaction_start` e `compaction_end` abrangem compactação manual e automática.\n\nOutros eventos de base vêm de\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## Tipos de mensagens\n\nMensagens básicas de [`packages/ai/src/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/types.ts#L134):\n- `UserMessage` (linha 134)\n- `AssistantMessage` (linha 140)\n- `ToolResultMessage` (linha 152)\n\nMensagens estendidas de [`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` (linha 29)\n- `CustomMessage` (linha 46)\n- `BranchSummaryMessage` (linha 55)\n- `CompactionSummaryMessage` (linha 62)\n\n## Formato de saída\n\nCada linha é um objeto JSON. A primeira linha é o cabeçalho da sessão:\n\n```json\n{\"type\":\"session\",\"version\":3,\"id\":\"uuid\",\"timestamp\":\"...\",\"cwd\":\"/path\"}\n```\n\nSeguido por eventos à medida que ocorrem:\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` registros são apenas delta. Eles omitem o campo cumulativo `message` e\n`assistantMessageEvent.partial` para manter o tamanho do fluxo linear. Use `contentIndex` e `delta`\npara montar argumentos de texto ativo, pensamento ou chamada de ferramenta, se necessário. `message_end` contém\na mensagem oficial final.\n\n## Exemplo\n\n```bash\npi --mode json \"List files\" 2>/dev/null | jq -c 'select(.type == \"message_end\")'\n```","sourceFile":"json.md"},"keybindings":{"title":"Atalhos de teclado","markdown":"Todos os atalhos de teclado podem ser personalizados via `~/.pi/agent/keybindings.json`. Cada ação pode estar vinculada a uma ou mais chaves.\n\nO arquivo de configuração usa os mesmos ids de atalho de teclado com namespace que pi usa internamente e que os autores de extensão usam nos gerenciadores `keyHint()` e `keybindings` injetados.\n\nConfigurações mais antigas usando IDs com namespace pré-como `cursorUp` ou `expandTools` são migradas automaticamente para os IDs com namespace na inicialização.\n\nApós editar `keybindings.json`, execute `/reload` em pi para aplicar as alterações sem reiniciar a sessão.\n\n## Formato chave\n\n`modifier+key` onde os modificadores são `ctrl`, `shift`, `alt`, `super` (combináveis) e as chaves são:\n\n- **Letras:** `a-z`\n- **Dígitos:** `0-9`\n- **Teclas especiais:** `escape`, `esc`, `enter`, `return`, `tab`, `space`, `backspace`, `delete`, `insert`, `clear`, `home`, `end`, `pageUp`, `pageDown`, `up`, `down`, `left`, `right`\n- **Teclas de função:** `f1`-`f12`\n- **Símbolos:** `` ` ``, `-`, `=`, `[`, `]`, `\\`, `;`, `'`, `,`, `.`, `/`, `!`, `@`, `#`, `$`, `%`, `^`, `&`, `*`, `(`, `)`, `_`, `+`, `|`, `~`, `{`, `}`, `:`, `<`, `>`, `?`\n\nCombinações de modificadores: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `super+k`, `ctrl+super+k`, `ctrl+1`, etc.\n\nAs ligações `super` requerem um terminal que reporte o modificador separadamente, normalmente por meio do protocolo de teclado Kitty. Eles podem não funcionar em terminais sem esse suporte.\n\n## Todas as ações\n\n### TUI Movimento do Cursor do Editor\n\n| ID de atalho de teclado | Padrão | Descrição |\n|--------|---------|-------------|\n| `tui.editor.cursorUp` | `up` | Mova o cursor para cima, navegando pelo histórico antigo na parte superior |\n| `tui.editor.cursorDown` | `down` | Mova o cursor para baixo, navegando pelo histórico mais recente na parte inferior |\n| `tui.editor.historyPrevious` | *(nenhum)* | Selecione a entrada anterior do histórico de prompts |\n| `tui.editor.historyNext` | *(nenhum)* | Selecione a próxima entrada do histórico de prompt |\n| `tui.editor.cursorLeft` | `left`, `ctrl+b` | Mova o cursor para a esquerda |\n| `tui.editor.cursorRight` | `right`, `ctrl+f` | Mova o cursor para a direita |\n| `tui.editor.cursorWordLeft` | `alt+left`, `ctrl+left`, `alt+b` | Mover a palavra do cursor para a esquerda |\n| `tui.editor.cursorWordRight` | `alt+right`, `ctrl+right`, `alt+f` | Mova a palavra do cursor para a direita |\n| `tui.editor.cursorLineStart` | `home`, `ctrl+home`, `ctrl+a` | Mover para o início da linha |\n| `tui.editor.cursorLineEnd` | `end`, `ctrl+end`, `ctrl+e` | Mover para o fim da linha |\n| `tui.editor.jumpForward` | `ctrl+]` | Avance para o personagem |\n| `tui.editor.jumpBackward` | `ctrl+alt+]` | Pule para trás para o personagem |\n| `tui.editor.pageUp` | `pageUp`, `ctrl+pageUp` | Rolar para cima por página |\n| `tui.editor.pageDown` | `pageDown`, `ctrl+pageDown` | Role para baixo por página |\n\nAs ações de histórico dedicadas sempre alteram as entradas do histórico, independentemente da posição do cursor em um prompt multilinha. As ligações explícitas do histórico têm precedência sobre as ações do aplicativo enquanto o editor principal está em foco, portanto, a ligação `tui.editor.historyPrevious` a `ctrl+p` substitui o ciclo do modelo nesse contexto sem alterar `Ctrl+P` nos seletores.\n\n### TUI Exclusão do Editor\n\n| ID de atalho de teclado | Padrão | Descrição |\n|--------|---------|-------------|\n| `tui.editor.deleteCharBackward` | `backspace` | Excluir caractere para trás |\n| `tui.editor.deleteCharForward` | `delete`, `ctrl+d` | Excluir caractere para frente |\n| `tui.editor.deleteWordBackward` | `ctrl+w`, `alt+backspace` | Excluir palavra para trás |\n| `tui.editor.deleteWordForward` | `alt+d`, `alt+delete` | Excluir palavra adiante |\n| `tui.editor.deleteToLineStart` | `ctrl+u` | Excluir para início da linha |\n| `tui.editor.deleteToLineEnd` | `ctrl+k` | Excluir até o final da linha |\n\n### TUI Entrada\n\n| ID de atalho de teclado | Padrão | Descrição |\n|--------|---------|-------------|\n| `tui.input.newLine` | `shift+enter`, `ctrl+j` | Inserir nova linha |\n| `tui.input.submit` | `enter` | Enviar entrada |\n| `tui.input.tab` | `tab` | Guia/preenchimento automático |\n\n### TUI Anel de Morte\n\n| ID de atalho de teclado | Padrão | Descrição |\n|--------|---------|-------------|\n| `tui.editor.yank` | `ctrl+y` | Colar o texto excluído mais recentemente |\n| `tui.editor.yankPop` | `alt+y` | Percorrer o texto excluído após arrancar |\n| `tui.editor.undo` | `ctrl+-` | Desfazer última edição |\n\n### TUI Área de transferência e seleção\n\n| ID de atalho de teclado | Padrão | Descrição |\n|--------|---------|-------------|\n| `tui.input.copy` | `ctrl+c` | Copiar seleção |\n| `tui.select.up` | `up` | Mover seleção para cima |\n| `tui.select.down` | `down` | Mover seleção para baixo |\n| `tui.select.pageUp` | `pageUp` | Subir página na lista |\n| `tui.select.pageDown` | `pageDown` | Página para baixo na lista |\n| `tui.select.confirm` | `enter` | Confirmar seleção |\n| `tui.select.cancel` | `escape`, `ctrl+c` | Cancelar seleção |\n\n### TUI Janela de visualização em tela cheia\n\nEssas ações se aplicam quando o modo interativo usa `--tui-mode fullscreen` e tem como alvo a região de rolagem da transcrição primária. O trackpad com dois dedos e a roda do mouse rolam a região sob o ponteiro, retornando à transcrição sobre o encaixe fixo de editor/status/rodapé. Clicar em um hiperlink OSC 8 o abre no manipulador padrão. Arrastar com o botão principal do mouse seleciona o texto e o copia para a área de transferência; segurar a borda superior ou inferior da transcrição rola automaticamente para o conteúdo fora da tela.\n\nAs vinculações de transcrição em tela cheia têm precedência sobre as vinculações do editor. As teclas de navegação padrão não modificadas, portanto, controlam a transcrição no modo de tela cheia, enquanto suas variantes `ctrl` continuam a controlar o editor. Fora do modo de tela cheia, ambas as variantes controlam o editor.\n\n| Chave | Modo padrão | Modo tela cheia |\n|-----|--------------|-----------------|\n| `home`, `end` | Editor | Transcrição |\n| `ctrl+home`, `ctrl+end` | Editor | Editor |\n| `pageUp`, `pageDown` | Editor | Transcrição |\n| `ctrl+pageUp`, `ctrl+pageDown` | Editor | Editor |\n\nEste roteamento permanece configurável através das ligações de ações comuns. Por exemplo, `\"tui.altScreen.pageUp\": \"ctrl+pageUp\"` faz com que `pageUp` controle o editor e `ctrl+pageUp` controle a transcrição no modo de tela cheia. Vincule `tui.altScreen.halfPageUp` e `tui.altScreen.halfPageDown` para etapas de transcrição menores, mantendo as encadernações de página inteira. A configuração `\"tui.altScreen.pageUp\": []` desativa totalmente esse atalho de transcrição. As ligações de usuário substituem os padrões dessa ação.\n\n| ID de atalho de teclado | Padrão | Descrição |\n|--------|---------|-------------|\n| `tui.altScreen.pageUp` | `pageUp` | Role a transcrição uma página para cima |\n| `tui.altScreen.pageDown` | `pageDown` | Role a transcrição uma página para baixo |\n| `tui.altScreen.halfPageUp` | *(nenhum)* | Role a transcrição meia página para cima |\n| `tui.altScreen.halfPageDown` | *(nenhum)* | Role a transcrição meia página para baixo |\n| `tui.altScreen.previousPrompt` | `ctrl+shift+up` | Ir para a mensagem marcada anteriormente |\n| `tui.altScreen.nextPrompt` | `ctrl+shift+down` | Ir para a próxima mensagem marcada |\n| `tui.altScreen.top` | `home` | Role até o início da transcrição |\n| `tui.altScreen.bottom` | `end` | Role até o final da transcrição e siga a nova saída |\n\n### Aplicativo\n\n| ID de atalho de teclado | Padrão | Descrição |\n|--------|---------|-------------|\n| `app.interrupt` | `escape` | Cancelar/abortar |\n| `app.clear` | `ctrl+c` | Limpar editor (primeiro) / sair (segundo) |\n| `app.exit` | `ctrl+d` | Sair (quando o editor estiver vazio) |\n| `app.suspend` | `ctrl+z` (nenhum no Windows) | Suspender para segundo plano |\n| `app.editor.external` | `ctrl+g` | Abra em editor externo (`externalEditor`, `$VISUAL`, `$EDITOR`, Bloco de notas no Windows ou `nano` em outro lugar) |\n| `app.clipboard.pasteImage` | `ctrl+v` (`alt+v` no Windows) | Colar imagem ou texto da área de transferência |\n\n### Sessões\n\n| ID de atalho de teclado | Padrão | Descrição |\n|--------|---------|-------------|\n| `app.session.new` | *(nenhum)* | Iniciar uma nova sessão (`/new`) |\n| `app.session.tree` | *(nenhum)* | Abra o navegador session tree (`/tree`) |\n| `app.session.fork` | *(nenhum)* | Bifurcar sessão atual (`/fork`) |\n| `app.session.resume` | *(nenhum)* | Abrir seletor de currículo de sessão (`/resume`) |\n| `app.session.togglePath` | `ctrl+p` | Alternar exibição do caminho |\n| `app.session.toggleSort` | `ctrl+s` | Alternar modo de classificação |\n| `app.session.toggleNamedFilter` | `ctrl+n` | Alternar filtro somente nomeado |\n| `app.session.rename` | `ctrl+r` | Renomear sessão |\n| `app.session.delete` | `ctrl+d` | Excluir sessão |\n| `app.session.deleteNoninvasive` | `ctrl+backspace` | Excluir sessão quando a consulta estiver vazia |\n\n### Models e pensando\n\n| ID de atalho de teclado | Padrão | Descrição |\n|--------|---------|-------------|\n| `app.model.select` | `ctrl+l` | Abrir seletor de modelo |\n| `app.model.cycleForward` | `ctrl+p` | Alternar para o próximo modelo |\n| `app.model.cycleBackward` | `shift+ctrl+p` | Alternar para o modelo anterior |\n| `app.thinking.cycle` | `shift+tab` | Nível de pensamento de ciclo |\n| `app.thinking.toggle` | `ctrl+t` | Recolher ou expandir blocos de pensamento |\n\n### Fila de exibição e mensagens\n\n| ID de atalho de teclado | Padrão | Descrição |\n|--------|---------|-------------|\n| `app.tools.expand` | `ctrl+o` | Recolher ou expandir a saída da ferramenta |\n| `app.message.copy` | `ctrl+x` | Copie a última mensagem do assistente ou a mensagem selecionada em `/tree` |\n| `app.message.followUp` | `alt+enter` | Mensagem de acompanhamento da fila |\n| `app.message.dequeue` | `alt+up` | Restaurar mensagens na fila para o editor |\n\n### Navegação em árvore\n\n| ID de atalho de teclado | Padrão | Descrição |\n|--------|---------|-------------|\n| `app.tree.foldOrUp` | `ctrl+left`, `alt+left` | Dobre o segmento de ramificação atual ou pule para o início do segmento anterior |\n| `app.tree.unfoldOrDown` | `ctrl+right`, `alt+right` | Desdobre o segmento de ramificação atual ou pule para o início ou final do próximo segmento |\n| `app.tree.editLabel` | `shift+l` | Edite o rótulo no nó da árvore selecionado |\n| `app.tree.toggleLabelTimestamp` | `shift+t` | Alternar carimbos de data/hora do rótulo na árvore |\n| `app.tree.filter.default` | `ctrl+d` | Definir filtro de árvore para visualização padrão |\n| `app.tree.filter.noTools` | `ctrl+t` | Alternar filtro de árvore que oculta os resultados da ferramenta |\n| `app.tree.filter.userOnly` | `ctrl+u` | Alternar filtro de árvore que mostra apenas mensagens do usuário |\n| `app.tree.filter.labeledOnly` | `ctrl+l` | Alternar filtro de árvore que mostra apenas entradas rotuladas |\n| `app.tree.filter.all` | `ctrl+a` | Alternar filtro de árvore que mostra todas as entradas |\n| `app.tree.filter.cycleForward` | `ctrl+o` | Ciclo de filtro de árvore para frente |\n| `app.tree.filter.cycleBackward` | `shift+ctrl+o` | Ciclo de filtro de árvore para trás |\n\n### Escopo Models Seletor\n\nUsado dentro do seletor de modelos com escopo (aberto via `/scoped-models`).\n\n| ID de atalho de teclado | Padrão | Descrição |\n|--------|---------|-------------|\n| `app.models.save` | `ctrl+s` | Salvar a seleção do modelo atual nas configurações |\n| `app.models.enableAll` | `ctrl+a` | Habilite todos os modelos (ou todos que correspondam à pesquisa atual) |\n| `app.models.clearAll` | `ctrl+x` | Limpar todos os modelos (ou todos que correspondam à pesquisa atual) |\n| `app.models.toggleProvider` | `ctrl+p` | Alternar todos os modelos para o provedor atual |\n| `app.models.reorderUp` | `alt+up` | Mova o modelo selecionado para cima na ordem do ciclo |\n| `app.models.reorderDown` | `alt+down` | Mova o modelo selecionado para baixo na ordem do ciclo |\n\n## Configuração personalizada\n\nCrie `~/.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\nCada ação pode ter uma única chave ou um conjunto de chaves. A configuração do usuário substitui os padrões.\n\nNo Windows nativo, `app.suspend` não possui ligação padrão porque os terminais do Windows não suportam controle de trabalho Unix. Se você vinculá-lo manualmente, pi mostrará uma mensagem de status em vez de suspender. No WSL, o comportamento normal do Linux `ctrl+z`/`fg` ainda se aplica.\n\n### Exemplo Emacs\n\n```json\n{\n  \"tui.editor.historyPrevious\": \"ctrl+p\",\n  \"tui.editor.historyNext\": \"ctrl+n\",\n  \"tui.editor.cursorLeft\": [\"left\", \"ctrl+b\"],\n  \"tui.editor.cursorRight\": [\"right\", \"ctrl+f\"],\n  \"tui.editor.cursorWordLeft\": [\"alt+left\", \"alt+b\"],\n  \"tui.editor.cursorWordRight\": [\"alt+right\", \"alt+f\"],\n  \"tui.editor.deleteCharForward\": [\"delete\", \"ctrl+d\"],\n  \"tui.editor.deleteCharBackward\": [\"backspace\", \"ctrl+h\"],\n  \"tui.input.newLine\": [\"shift+enter\", \"ctrl+j\"]\n}\n```\n\n### Exemplo de Vim\n\n```json\n{\n  \"tui.editor.cursorUp\": [\"up\", \"alt+k\"],\n  \"tui.editor.cursorDown\": [\"down\", \"alt+j\"],\n  \"tui.editor.cursorLeft\": [\"left\", \"alt+h\"],\n  \"tui.editor.cursorRight\": [\"right\", \"alt+l\"],\n  \"tui.editor.cursorWordLeft\": [\"alt+left\", \"alt+b\"],\n  \"tui.editor.cursorWordRight\": [\"alt+right\", \"alt+w\"]\n}\n```","sourceFile":"keybindings.md"},"llama-cpp":{"title":"llama.cpp","markdown":"Pi suporta o servidor roteador [llama.cpp](https://github.com/ggml-org/llama.cpp). O roteador descobre vários modelos GGUF e os carrega ou descarrega sob demanda.\n\nUse uma versão llama.cpp atual com suporte para roteador. Siga o [build instructions](https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md) ou instale um [prebuilt release](https://github.com/ggml-org/llama.cpp/releases) para sua plataforma.\n\n## Inicie o roteador\n\nComece `llama-server` sem `--model` ou `-m`. A passagem de um modelo inicia o modo de modelo único em vez do modo roteador.\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\nOpções importantes:\n\n- `--models-dir ~/models` descobre arquivos GGUF locais.\n- `--no-models-autoload` continua carregando explícito até `/llama`.\n- `--jinja` permite modelos de bate-papo compatíveis e chamadas de ferramentas.\n- `-ngl 999` descarrega tantas camadas quanto possível para a GPU.\n- `-c 32768` define a janela de contexto para cada modelo carregado. Omita-o para usar o contexto nativo do modelo, o que pode exigir substancialmente mais memória.\n\nUm modelo de arquivo único pode ficar diretamente no diretório do modelo. Coloque modelos multimodais e multifragmentos em subdiretórios separados:\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\nReinicie o roteador após adicionar arquivos manualmente. Para tamanhos de contexto por modelo e outras opções, use [llama.cpp model presets](https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md#model-presets).\n\n## Configurar Pi\n\nInicie Pi e configure o provedor:\n\n```text\n/login llama.cpp\n```\n\nInsira o URL do roteador e opcional API key. O URL padrão é `http://127.0.0.1:8080`.\n\nVariáveis ​​de ambiente podem configurar os mesmos valores sem `/login`:\n\n```bash\nexport LLAMA_BASE_URL=http://127.0.0.1:8080\nexport LLAMA_API_KEY=optional-secret\npi\n```\n\nSe o servidor usar API key, inicie `llama-server` com o valor `--api-key` correspondente. Mantenha `--host 127.0.0.1` para acesso somente local.\n\n## Gerenciar modelos\n\nCorrer:\n\n```text\n/llama\n```\n\n- Selecione um modelo descarregado para carregá-lo.\n- Selecione um modelo carregado para descarregá-lo.\n- Selecione **Baixar modelo…**, pesquise Hugging Face e escolha um repositório e quantização. Os valores `owner/repository[:quant]` exatos também funcionam.\n- Pressione Escape durante um carregamento ou download para confirmar o cancelamento.\n\nA pesquisa Hugging Face usa `HF_TOKEN` quando definida e verifica `$HF_TOKEN_PATH`, `$HF_HOME/token`, `$XDG_CACHE_HOME/huggingface/token` e `~/.cache/huggingface/token`. A pesquisa também funciona sem autenticação, sujeita a limites de taxas mais baixos. Pi avisa antes de baixar repositórios bloqueados e links para sua página de acesso. O servidor llama.cpp realiza o download, portanto seu processo também deverá ter `HF_TOKEN` quando o repositório selecionado necessitar de acesso.\n\nSe outros modelos estiverem carregados, Pi pergunta se deve descarregá-los primeiro ou mantê-los carregados. Pi não descarrega modelos silenciosamente e nunca exclui arquivos de modelo. O roteador pode ser compartilhado com outros clientes, portanto `/llama` sempre exibe o estado atual do roteador.\n\nApenas modelos carregados aparecem em `/model`. Após carregar um modelo, execute `/model` para selecioná-lo para a sessão Pi atual.\n\nSe o roteador for desconectado, `/llama` mostrará **Tentar novamente** e **Fechar**. A nova tentativa reconecta e atualiza o estado do modelo sem repetir a operação interrompida.\n\n## Solução de problemas\n\nVerifique se o roteador está acessível:\n\n```bash\ncurl http://127.0.0.1:8080/health\ncurl http://127.0.0.1:8080/models\n```\n\n- **Nenhum modelo em `/llama`:** Verifique `--models-dir`, o layout do diretório, e reinicie o roteador.\n- **Modelo ausente em `/model`:** Carregue-o com `/llama` primeiro.\n- **O carregamento falha ou usa muita memória:** Reduza `-c` ou descarregue outro modelo.\n- **O servidor não está no modo roteador:** Inicie-o sem `--model`, `-m` ou `-hf`.","sourceFile":"llama-cpp.md"},"models":{"title":"Personalizado Models","markdown":"Adicione provedores e modelos personalizados (Ollama, vLLM, LM Studio, proxies) via `~/.pi/agent/models.json`.\n\n## Índice\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## Exemplo Mínimo\n\nPara modelos locais (Ollama, LM Studio, vLLM), apenas `id` é necessário por modelo:\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\nO valor `apiKey` é um espaço reservado porque Ollama o ignora. pi ainda trata os modelos como exigindo autenticação antes de aparecerem em `/model`, portanto, os servidores locais sem chave devem manter um valor fictício, salvar uma chave para esse provedor com `/login` ou passar `--api-key` ao selecionar o modelo.\n\nAlguns servidores compatíveis com OpenAI não entendem a função `developer` usada para modelos com capacidade de raciocínio. Para esses provedores, defina `compat.supportsDeveloperRole` como `false` para que pi envie o prompt do sistema como uma mensagem `system`. Se o servidor também não suportar `reasoning_effort`, defina `compat.supportsReasoningEffort` para `false` também.\n\nVocê pode definir `compat` no nível do provedor para aplicar a todos os modelos ou no nível do modelo para substituir um modelo específico. Isso geralmente se aplica a Ollama, vLLM, SGLang e servidores semelhantes compatíveis com OpenAI.\n\n```json\n{\n  \"providers\": {\n    \"ollama\": {\n      \"baseUrl\": \"http://localhost:11434/v1\",\n      \"api\": \"openai-completions\",\n      \"apiKey\": \"ollama\",\n      \"compat\": {\n        \"supportsDeveloperRole\": false,\n        \"supportsReasoningEffort\": false\n      },\n      \"models\": [\n        {\n          \"id\": \"gpt-oss:20b\",\n          \"reasoning\": true\n        }\n      ]\n    }\n  }\n}\n```\n\n## Exemplo completo\n\nSubstitua os padrões quando precisar de valores específicos:\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\nO arquivo é recarregado cada vez que você abre `/model`. Editar durante a sessão; não é necessário reiniciar.\n\n## Exemplo do Google AI Studio\n\nUse `google-generative-ai` com `baseUrl` para adicionar modelos do Google AI Studio, incluindo entradas personalizadas do Gemma 4:\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\nO `baseUrl` é necessário ao adicionar modelos personalizados ao tipo `google-generative-ai` API.\n\n## APIs suportados\n\n| API | Descrição |\n|-----|-------------|\n| `openai-completions` | Conclusões de bate-papo OpenAI (mais compatíveis) |\n| `openai-responses` | Respostas OpenAI API |\n| `anthropic-messages` | Mensagens Antrópicas API |\n| `google-generative-ai` | IA generativa do Google |\n\nDefina `api` no nível do provedor (padrão para todos os modelos) ou no nível do modelo (substituição por modelo).\n\n## Configuração do provedor\n\n| Campo | Descrição |\n|-------|-------------|\n| `baseUrl` | API URL do terminal |\n| `api` | API tipo (veja acima) |\n| `apiKey` | Configuração API key opcional (veja a resolução do valor abaixo). Omita quando a autenticação for fornecida por `/login`/`auth.json` ou CLI `--api-key`. |\n| `oauth` | Tipo de provedor dinâmico OAuth. Atualmente suporta `\"radius\"`; requer o gateway `baseUrl`. |\n| `headers` | Cabeçalhos personalizados (veja a resolução do valor abaixo) |\n| `authHeader` | Defina `true` para adicionar `Authorization: Bearer <apiKey>` automaticamente |\n| `models` | Matriz de configurações de modelo |\n| `modelOverrides` | Substituições por modelo para modelos integrados ou registrados em extensão neste provedor |\n\nPara provedores com `models`, as configurações de provedor não integradas precisam de `baseUrl` e um valor `api` no nível do provedor ou do modelo. `apiKey` não é necessário para carregar o arquivo: os modelos ficam disponíveis quando a autenticação é configurada por meio de `/login`/`auth.json`, CLI `--api-key` ou provedor `apiKey`. Se nenhuma autenticação estiver configurada, os modelos serão carregados, mas permanecerão indisponíveis em `/model` e `--list-models`.\n\n### Resolução de valor\n\nOs campos `apiKey` e `headers` suportam execução de comandos, interpolação de ambiente e literais:\n\n- **Comando Shell:** `\"!command\"` no início executa todo o valor como um comando e usa stdout\n  ```json\n  \"apiKey\": \"!security find-generic-password -ws 'anthropic'\"\n  \"apiKey\": \"!op read 'op://vault/item/credential'\"\n  ```\n- **Interpolação de ambiente:** `\"$ENV_VAR\"` ou `\"${ENV_VAR}\"` usa o valor da variável nomeada. A interpolação funciona dentro de literais maiores.\n  ```json\n  \"apiKey\": \"$MY_API_KEY\"\n  \"apiKey\": \"${KEY_PREFIX}_${KEY_SUFFIX}\"\n  ```\n  `$FOO_BAR` é a variável `FOO_BAR`; use `${FOO}_BAR` quando `BAR` for texto literal. Variáveis ​​de ambiente ausentes tornam o valor não resolvido.\n- **Escapes:** `\"$\"` emite um literal `\"$\"`; `\"$!\"` emite um literal `\"!\"` sem acionar a execução do comando.\n  ```json\n  \"apiKey\": \"$$literal-dollar-prefix\"\n  \"apiKey\": \"$!literal-bang-prefix\"\n  ```\n- **Valor literal:** Usado diretamente. Strings simples em maiúsculas como `MY_API_KEY` são literais; use `$MY_API_KEY` para variáveis ​​de ambiente.\n  ```json\n  \"apiKey\": \"sk-...\"\n  ```\n\nPara `models.json`, os comandos shell são resolvidos no momento da solicitação. pi intencionalmente não aplica TTL integrado, reutilização obsoleta ou lógica de recuperação para comandos arbitrários. Comandos diferentes precisam de estratégias diferentes de cache e falha, e pi não consegue inferir qual é a correta.\n\nSe o seu comando for lento, caro, com taxa limitada ou precisar continuar usando um valor anterior em falhas transitórias, envolva-o em seu próprio script ou comando que implemente o cache ou o comportamento TTL desejado.\n\n`/model` verificações de disponibilidade usam presença de autenticação configurada e não executam comandos shell.\n\n### Cabeçalhos personalizados\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## Configuração do modelo\n\n| Campo | Obrigatório | Padrão | Descrição |\n|-------|----------|---------|-------------|\n| `id` | Sim | — | Identificador do modelo (passado para API) |\n| `name` | Não | `id` | Etiqueta do modelo legível por humanos. Usado para correspondência (padrões `--model`) e mostrado como texto de detalhe do modelo secundário. |\n| `api` | Não | provedor `api` | Substituir API do provedor para este modelo |\n| `reasoning` | Não | `false` | Suporta pensamento estendido |\n| `thinkingLevelMap` | Não | omitido | Mapeia os níveis de pensamento pi para os valores do provedor e marca os níveis não suportados (veja abaixo) |\n| `input` | Não | `[\"text\"]` | Tipos de entrada: `[\"text\"]` ou `[\"text\", \"image\"]` |\n| `contextWindow` | Não | `128000` | Tamanho da janela de contexto em tokens |\n| `maxTokens` | Não | `16384` | Tokens de saída máximo |\n| `samplingParams` | Não | omitido | Parâmetros de amostragem mesclados literalmente em cada corpo da solicitação (veja abaixo) |\n| `cost` | Não | todos os zeros | Taxas por milhão de tokens com níveis opcionais de preços de entrada para toda a solicitação |\n| `compat` | Não | provedor `compat` | Substituições de compatibilidade do provedor. Mesclado com o nível do provedor `compat` quando ambos estão definidos. |\n\nUma camada de custo fornece um conjunto completo de taxas alternativas e se aplica à solicitação completa quando o uso total de entrada (`input + cacheRead + cacheWrite`) excede `inputTokensAbove`. Quando vários níveis coincidem, o limite mais alto vence.\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\nComportamento atual:\n- `/model`, `--list-models` e o rodapé interativo exibem entradas por modelo `id`.\n- O `name` configurado é usado para correspondência de modelo e texto de detalhes do modelo secundário. Ele não substitui o ID do modelo do rodapé/barra de status.\n\n### Parâmetros de Amostragem\n\n`samplingParams` é um objeto de formato livre mesclado literalmente em cada corpo de solicitação do modelo, depois que os campos pi se definem, para que suas chaves ganhem. Use-o para enviar parâmetros de amostragem que pi não modela - incluindo aqueles específicos do servidor, como llama.cpp's `min_p` ou vLLM's `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\nSomente APIs compatíveis com OpenAI o aplicam (`openai-completions`, `openai-responses`, `azure-openai-responses`); outros APIs ignoram. As chaves substituem os campos de solicitação nomeados do pi (por exemplo, uma chave `temperature` aqui supera a temperatura no nível da solicitação), portanto, prefira-a como a única fonte de amostragem verdadeira para um modelo. Em `modelOverrides`, `samplingParams` mescla por chave com o valor do modelo base.\n\n### Mapa de nível de pensamento\n\nUse `thinkingLevelMap` em um modelo para descrever controles de pensamento específicos do modelo. As chaves são níveis de pensamento pi: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Os mapas podem conter buracos; por exemplo, um modelo pode expor `high` e `max` sem expor `xhigh`.\n\nOs valores são tristate:\n\n| Valor | Significado |\n|-------|---------|\n| omitido | Os níveis padrão até `high` usam o mapeamento padrão do provedor; níveis estendidos `xhigh` e `max` não são suportados |\n| corda | O nível é suportado e esse valor é enviado ao provedor |\n| `null` | O nível não é suportado e está oculto/ignorado/fixado |\n\nExemplo de um modelo que suporta apenas raciocínios off, high e max:\n\n```json\n{\n  \"id\": \"deepseek-v4-pro\",\n  \"reasoning\": true,\n  \"thinkingLevelMap\": {\n    \"minimal\": null,\n    \"low\": null,\n    \"medium\": null,\n    \"high\": \"high\",\n    \"xhigh\": null,\n    \"max\": \"max\"\n  }\n}\n```\n\nExemplo de um modelo onde o pensamento não pode ser desativado:\n\n```json\n{\n  \"id\": \"always-thinking-model\",\n  \"reasoning\": true,\n  \"thinkingLevelMap\": {\n    \"off\": null\n  }\n}\n```\n\nMigração: configurações mais antigas que usavam `compat.reasoningEffortMap` deveriam mover esse mapeamento para o nível de modelo `thinkingLevelMap`. Use `null` para níveis que não devem aparecer na UI.\n\n## Substituindo o integrado Providers\n\nRoteie um provedor integrado por meio de um proxy sem redefinir modelos:\n\n```json\n{\n  \"providers\": {\n    \"anthropic\": {\n      \"baseUrl\": \"https://my-proxy.example.com/v1\"\n    }\n  }\n}\n```\n\nTodos os modelos Antrópicos integrados permanecem disponíveis. A autenticação OAuth ou API key existente continua funcionando.\n\nPara mesclar modelos personalizados em um provedor integrado, inclua o array `models`:\n\n```json\n{\n  \"providers\": {\n    \"anthropic\": {\n      \"baseUrl\": \"https://my-proxy.example.com/v1\",\n      \"apiKey\": \"$ANTHROPIC_API_KEY\",\n      \"api\": \"anthropic-messages\",\n      \"models\": [...]\n    }\n  }\n}\n```\n\nMesclar semântica:\n- Os modelos integrados são mantidos.\n- Os modelos personalizados são atualizados por `id` no provedor.\n- Se um modelo customizado `id` corresponder a um modelo integrado `id`, o modelo customizado substituirá esse modelo integrado.\n- Se um modelo personalizado `id` for novo, ele será adicionado junto com os modelos integrados.\n\n## Substituições por modelo\n\nUse `modelOverrides` para personalizar modelos integrados e combinar modelos registrados em extensão sem substituir a lista completa de modelos do provedor.\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` suporta estes campos por modelo: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (parcial), `contextWindow`, `maxTokens`, `samplingParams` (mesclado por chave), `headers`, `compat`.\n\nDirect OpenAI GPT-5.6 Sol, Terra e Luna têm como padrão uma janela de contexto `272000` para que as solicitações permaneçam dentro do nível de preços de contexto curto do OpenAI. Para aceitar a janela de contexto de 1,05M do OpenAI, aumente-a para cada modelo que você usar:\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\nA substituição preserva os metadados de preços integrados. Solicitações com mais de 272 mil tokens de entrada totais usam as taxas de contexto longo do GPT-5.6 para toda a solicitação. Aplique a mesma substituição a `gpt-5.6-terra` ou `gpt-5.6-luna` quando necessário.\n\nNotas de comportamento:\n- `modelOverrides` são aplicados a modelos de provedores integrados e modelos de provedores registrados em extensão correspondentes.\n- IDs de modelo desconhecidos são ignorados.\n- Você pode combinar `baseUrl`/`headers` de nível de provedor com `modelOverrides`.\n- A substituição de `name` altera apenas a correspondência do modelo e o texto de detalhes secundários; o rodapé e as listas de modelos primários continuam mostrando o modelo `id`.\n- Se `models` também for definido para um provedor, os modelos customizados serão mesclados após substituições integradas. Um modelo personalizado com o mesmo `id` substitui a entrada do modelo integrado substituída.\n\n## Compatibilidade de Mensagens Antrópicas\n\nPara provedores ou proxies que usam `api: \"anthropic-messages\"`, use `compat` para controlar a compatibilidade de solicitações específicas do Antrópico.\n\nPor padrão, pi envia por ferramenta `eager_input_streaming: true`. Se um proxy ou back-end compatível com Anthropic rejeitar esse campo, defina `supportsEagerToolInputStreaming` como `false`. Pi omitirá `tools[].eager_input_streaming` e enviará o cabeçalho beta `fine-grained-tool-streaming-2025-05-14` herdado para solicitações habilitadas para ferramenta.\n\nAlguns modelos antrópicos requerem pensamento adaptativo (`thinking.type: \"adaptive\"` mais `output_config.effort`) em vez da carga útil de pensamento legado baseado em orçamento. Os modelos integrados definem isso automaticamente. Para provedores personalizados ou aliases que roteiam para esses modelos, defina `forceAdaptiveThinking` como `true`.\n\nAlguns provedores compatíveis com o Anthropic emitem blocos de pensamento com assinaturas vazias e ainda os esperam na repetição. Defina `allowEmptySignature` como `true` apenas para esses provedores; o verdadeiro Antrópico rejeita assinaturas de pensamento vazias.\n\nOs modelos Antrópicos integrados habilitam `supportsStrictTools` em seus metadados de modelo. Modelos customizados compatíveis com Anthropic devem defini-lo como `true` quando seu endpoint aceita definições estritas de ferramenta de esquema JSON.\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| Campo | Descrição |\n|-------|-------------|\n| `supportsEagerToolInputStreaming` | Se o provedor aceita `eager_input_streaming` por ferramenta. Padrão: `true`. Defina como `false` para omitir esse campo e usar o cabeçalho beta de streaming da ferramenta legada e refinada em solicitações habilitadas para ferramenta. |\n| `supportsLongCacheRetention` | Se o provedor aceita retenção de cache longa antrópica (`cache_control.ttl: \"1h\"`) quando a retenção de cache é `long`. Padrão: `true`. |\n| `sendSessionAffinityHeaders` | Se deve ser enviado `x-session-affinity` do ID da sessão quando o cache estiver habilitado. Padrão: detectado automaticamente para provedores conhecidos. |\n| `supportsCacheControlOnTools` | Se o provedor aceita marcadores `cache_control` de estilo antrópico nas definições de ferramentas. Padrão: `true`. |\n| `forceAdaptiveThinking` | Se deve enviar pensamento adaptativo (`thinking.type: \"adaptive\"` mais `output_config.effort`) para este modelo. Os modelos adaptativos integrados definem isso automaticamente. Padrão: `false`. |\n| `allowEmptySignature` | Se deve reproduzir assinaturas de pensamento vazias como `signature: \"\"` em vez de converter o pensamento em texto. Padrão: `false`. |\n| `supportsStrictTools` | Se o provedor aceita definições estritas de ferramentas de esquema JSON. Padrão: `false`; modelos antrópicos integrados permitem isso nos metadados gerados. |\n\n## Compatibilidade OpenAI\n\nPara provedores com compatibilidade parcial com OpenAI, use o campo `compat`.\n\n- O nível de provedor `compat` aplica padrões a todos os modelos desse provedor.\n- O nível do modelo `compat` substitui os valores do nível do provedor para esse modelo.\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| Campo | Descrição |\n|-------|-------------|\n| `supportsStore` | O provedor suporta o campo `store` |\n| `supportsDeveloperRole` | Use a função `developer` vs `system` |\n| `supportsReasoningEffort` | Suporte para parâmetro `reasoning_effort` |\n| `supportsUsageInStreaming` | Suporta `stream_options: { include_usage: true }` (padrão: `true`) |\n| `supportsFinishReason` | Se as respostas transmitidas incluem `finish_reason`. Quando `false`, pi infere `stop` ou `toolUse` quando o fluxo termina. Padrão: `true`. |\n| `maxTokensField` | Use `max_completion_tokens` ou `max_tokens` |\n| `requiresToolResultName` | Incluir `name` nas mensagens de resultados da ferramenta |\n| `requiresAssistantAfterToolResult` | Insira uma mensagem do assistente antes de uma mensagem do usuário após os resultados da ferramenta |\n| `requiresThinkingAsText` | Converta blocos de pensamento em texto simples |\n| `requiresReasoningContentOnAssistantMessages` | Incluir `reasoning_content` vazio em todas as mensagens do assistente reproduzidas quando o raciocínio estiver ativado |\n| `thinkingFormat` | Use parâmetros de pensamento `reasoning_effort`, `openrouter`, `deepseek`, `together`, `baseten`, `zai`, `qwen`, `chat-template` ou `qwen-chat-template` |\n| `chatTemplateKwargs` | `chat_template_kwargs` valores para `thinkingFormat: \"chat-template\"`; use `{ \"$var\": \"thinking.enabled\" }` ou `{ \"$var\": \"thinking.effort\" }` para valores de pensamento controlados por pi |\n| `chatTemplateArgs` | `chat_template_args` valores para `thinkingFormat: \"baseten\"`; use `{ \"$var\": \"thinking.enabled\" }` ou `{ \"$var\": \"thinking.effort\" }` para valores de pensamento controlados por pi |\n| `cacheControlFormat` | Use marcadores `cache_control` de estilo antrópico no prompt do sistema, na última definição de ferramenta e no conteúdo de texto do último usuário, assistente ou resultado da ferramenta. Atualmente apenas `anthropic` é suportado. |\n| `sendSessionAffinityHeaders` | Para `openai-completions`, envie cabeçalhos de afinidade de sessão do ID da sessão quando o cache estiver ativado. Padrão: `false`. |\n| `sessionAffinityFormat` | Para `openai-completions` e `openai-responses`, o formato do cabeçalho de afinidade de sessão: `openai` envia `session_id`/`x-client-request-id` (conclusões também `x-session-affinity`), `openai-nosession` omite o cabeçalho `session_id` contendo sublinhado, `openrouter` envia `x-session-id`. Não afeta o parâmetro do corpo `prompt_cache_key`. Padrão: detectado automaticamente. |\n| `supportsStrictMode` | Se o provedor aceita definições estritas de ferramentas de função de esquema JSON. Os padrões dependem de API; modelos OpenAI integrados carregam metadados de capacidade explícitos. |\n| `supportsOpenAIGrammarTools` | Se APIs compatíveis com OpenAI emitem ferramentas gramaticais Lark/regex personalizadas. Quando `false`, as ferramentas com restrição gramatical voltam às ferramentas de função normais. Padrão: `false`; o catálogo de modelos integrado permite modelos GPT-5+ em OpenAI, OpenAI Codex, Azure OpenAI, GitHub Copilot, opencode e Cloudflare AI Gateway. |\n| `deferredToolsMode` | Use serialização de ferramenta adiada específica do provedor. Atualmente, apenas `\"kimi\"` é compatível com o formato de conclusão de bate-papo compatível com OpenAI do Kimi. |\n| `supportsLongCacheRetention` | Se o provedor aceita retenção de cache longa quando a retenção de cache é `long`: `prompt_cache_retention: \"24h\"` para cache de prompt OpenAI ou `cache_control.ttl: \"1h\"` quando `cacheControlFormat` é `anthropic`. Padrão: `true`. |\n| `openRouterRouting` | Preferências de roteamento do provedor OpenRouter. Este objeto é enviado como está no campo `provider` do [OpenRouter API request](https://openrouter.ai/docs/guides/routing/provider-selection). |\n| `vercelGatewayRouting` | Configuração de roteamento do Vercel AI Gateway para seleção de provedor (`only`, `order`) |\n\n`openrouter` usa `reasoning: { effort }`. `together` usa `reasoning: { enabled }` e também `reasoning_effort` quando `supportsReasoningEffort` está habilitado. `qwen` usa `enable_thinking` de nível superior. Use `qwen-chat-template` para servidores locais compatíveis com Qwen que requerem `chat_template_kwargs.enable_thinking` e `preserve_thinking`. Use `chat-template` para modelos de bate-papo vLLM/Hugging Face que precisam de `chat_template_kwargs` configurável, como `chatTemplateKwargs: { \"thinking\": { \"$var\": \"thinking.enabled\" } }` para modelos DeepSeek V3.x. Use `thinkingFormat: \"baseten\"` com `chatTemplateArgs` para provedores que expõem controles de alternância por meio de `chat_template_args` e, opcionalmente, suportam `reasoning_effort` de nível superior.\n\n`cacheControlFormat: \"anthropic\"` é para provedores compatíveis com OpenAI que expõem o cache de prompt no estilo Anthropic por meio de marcadores `cache_control` no conteúdo de texto e definições de ferramentas.\n\nExemplo:\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\nExemplo de gateway Vercel AI:\n\n```json\n{\n  \"providers\": {\n    \"vercel-ai-gateway\": {\n      \"baseUrl\": \"https://ai-gateway.vercel.sh/v1\",\n      \"apiKey\": \"$AI_GATEWAY_API_KEY\",\n      \"api\": \"openai-completions\",\n      \"models\": [\n        {\n          \"id\": \"moonshotai/kimi-k2.5\",\n          \"name\": \"Kimi K2.5 (Fireworks via Vercel)\",\n          \"reasoning\": true,\n          \"input\": [\"text\", \"image\"],\n          \"cost\": { \"input\": 0.6, \"output\": 3, \"cacheRead\": 0, \"cacheWrite\": 0 },\n          \"contextWindow\": 262144,\n          \"maxTokens\": 262144,\n          \"compat\": {\n            \"vercelGatewayRouting\": {\n              \"only\": [\"fireworks\", \"novita\"],\n              \"order\": [\"fireworks\", \"novita\"]\n            }\n          }\n        }\n      ]\n    }\n  }\n}\n```","sourceFile":"models.md"},"packages":{"title":"Pi Packages","markdown":"> pi pode ajudá-lo a criar pacotes pi. Peça para agrupar suas extensões, habilidades, prompt templates ou temas.\n\n\nOs pacotes Pi agrupam extensões, habilidades, prompt templates e temas para que você possa compartilhá-los por meio de npm ou git. Um pacote pode declarar recursos em `package.json` sob a chave `pi` ou usar diretórios convencionais.\n\n## Índice\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## Instalar e gerenciar\n\n> **Segurança:** Pi pacotes são executados com acesso total ao sistema. Extensions executa código arbitrário e as habilidades podem instruir o modelo a executar qualquer ação, incluindo a execução de executáveis. Revise o código-fonte antes de instalar pacotes de terceiros.\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\nEsses comandos gerenciam pacotes pi e `pi update` podem atualizar a instalação do pi CLI. Para desinstalar o próprio pi, consulte [Quickstart](quickstart.md#uninstall).\n\nPor padrão, `install` e `remove` gravam nas configurações do usuário (`~/.pi/agent/settings.json`). Use `-l` para gravar nas configurações do projeto (`.pi/settings.json`). As configurações do projeto podem ser compartilhadas com sua equipe e o pi instala todos os pacotes ausentes automaticamente na inicialização, após o projeto ser confiável.\n\nPara testar um pacote sem instalá-lo, use `--extension` ou `-e`. Isso é instalado em um diretório temporário apenas para a execução atual:\n\n```bash\npi -e npm:@foo/bar\npi -e git:github.com/user/repo\n```\n\n## Fontes de pacotes\n\nPi aceita três tipos de fontes nas configurações e `pi install`.\n\n### npm\n\n```\nnpm:@scope/pkg@1.2.3\nnpm:pkg\n```\n\n- As especificações versionadas são fixadas e ignoradas pelas atualizações de pacotes (`pi update --extensions`, `pi update --all`).\n- As instalações do usuário ficam abaixo de `~/.pi/agent/npm/`.\n- As instalações do projeto ficam abaixo de `.pi/npm/`.\n- Defina `npmCommand` em `settings.json` para fixar a pesquisa de pacote npm e as operações de instalação em um comando wrapper específico, como `mise` ou `asdf`.\n\nExemplo:\n\n```json\n{\n  \"npmCommand\": [\"mise\", \"exec\", \"node@20\", \"--\", \"npm\"]\n}\n```\n\n### idiota\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- Sem o prefixo `git:`, apenas URLs de protocolo são aceitos (`https://`, `http://`, `ssh://`, `git://`).\n- Com o prefixo `git:`, formatos abreviados são aceitos, incluindo `github.com/user/repo` e `git@github.com:user/repo`.\n- URLs HTTPS e SSH são suportados.\n- SSH URLs usam suas chaves SSH configuradas automaticamente (respeita `~/.ssh/config`).\n- Para execuções não interativas (por exemplo, CI), você pode definir `GIT_TERMINAL_PROMPT=0` para desabilitar prompts de credenciais e definir `GIT_SSH_COMMAND` (por exemplo, `ssh -o BatchMode=yes -o ConnectTimeout=5`) para falhar rapidamente.\n- Refs são tags fixadas ou commits. `pi update --extensions` e `pi update --all` não os movem para referências mais recentes, mas reconciliam um clone existente com a referência configurada.\n- Use `pi install git:host/user/repo@new-ref` para atualizar as configurações e mover um pacote existente para uma nova referência fixada.\n- Clonado para `~/.pi/agent/git/<host>/<path>` (global) ou `.pi/git/<host>/<path>` (projeto).\n- Quando a reconciliação altera o checkout, pi redefine e limpa o clone e, em seguida, executa `npm install` se `package.json` existir.\n\n**SSH exemplos:**\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### Caminhos locais\n\n```\n/absolute/path/to/package\n./relative/path/to/package\n```\n\nOs caminhos locais apontam para arquivos ou diretórios no disco e são adicionados às configurações sem serem copiados. Os caminhos relativos são resolvidos em relação ao arquivo de configurações em que aparecem. Se o caminho for um arquivo, ele será carregado como uma única extensão. Se for um diretório, pi carrega recursos usando regras de pacote.\n\n## Criando um pacote Pi\n\nAdicione um manifesto `pi` a `package.json` ou use diretórios convencionais. Inclua a palavra-chave `pi-package` para descoberta.\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\nOs caminhos são relativos à raiz do pacote. Matrizes suportam padrões glob e `!exclusions`.\n\n### Metadados da Galeria\n\nO [package gallery](https://pi.dev/packages) exibe pacotes marcados com `pi-package`. Adicione os campos `video` ou `image` para mostrar uma visualização:\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- **vídeo**: somente MP4. No desktop, é reproduzido automaticamente ao passar o mouse. Clicar abre um player em tela cheia.\n- **imagem**: PNG, JPEG, GIF ou WebP. Exibido como uma visualização estática.\n\nSe ambos estiverem definidos, o vídeo terá precedência.\n\n## Estrutura do pacote\n\n### Diretórios de Convenções\n\nSe nenhum manifesto `pi` estiver presente, pi descobre automaticamente recursos destes diretórios:\n\n- `extensions/` carrega arquivos `.ts` e `.js`\n- `skills/` encontra recursivamente pastas `SKILL.md` e carrega arquivos `.md` de nível superior como habilidades\n- `prompts/` carrega `.md` arquivos\n- `themes/` carrega `.json` arquivos\n\n## Dependências\n\nAs dependências de tempo de execução de terceiros pertencem a `dependencies` em `package.json`. Dependências que não registram extensões, habilidades, prompt templates ou temas também pertencem a `dependencies`. Quando pi instala um pacote de npm ou git, ele executa `npm install`, então essas dependências são instaladas automaticamente.\n\nPi agrupa pacotes principais para extensões e habilidades. Se você importar algum deles, liste-os em `peerDependencies` com um intervalo `\"*\"` e não os agrupe: `@earendil-works/pi-ai`, `@earendil-works/pi-agent-core`, `@earendil-works/pi-coding-agent`, `@earendil-works/pi-tui`, `typebox`.\n\nOutros pacotes pi devem ser incluídos no seu tarball. Adicione-os a `dependencies` e `bundledDependencies` e faça referência a seus recursos por meio de caminhos `node_modules/`. Pi carrega pacotes com raízes de módulo separadas, para que instalações separadas não colidam ou compartilhem módulos.\n\nExemplo:\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## Filtragem de pacotes\n\nFiltre o que um pacote carrega usando o formulário do objeto nas configurações:\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` e `-path` são caminhos exatos relativos à raiz do pacote.\n\n- Omita uma chave para carregar todo esse tipo.\n- Use `[]` para não carregar nada desse tipo.\n- `!pattern` exclui correspondências.\n- `+path` force-inclui um caminho exato.\n- `-path` força-exclui um caminho exato.\n- Camada de filtros na parte superior do manifesto. Eles restringem o que já é permitido.\n\n## Habilitar e desabilitar recursos\n\nUse `pi config` para ativar ou desativar extensões, habilidades, prompt templates e temas de pacotes instalados e diretórios locais. `pi config` inicia nas configurações globais (`~/.pi/agent/settings.json`); pressione Tab para alternar entre os modos global e local do projeto. Use `pi config -l` para iniciar substituições de projeto (`.pi/settings.json`) com recursos globais herdados esmaecidos.\n\n## Escopo e desduplicação\n\nOs pacotes podem aparecer nas configurações globais e do projeto. Se o mesmo pacote aparecer em ambos, a entrada do projeto vence, a menos que a entrada do projeto tenha `autoload: false`, caso em que é aplicado como um delta sobre a entrada global. A identidade é determinada por:\n\n- npm: nome do pacote\n- git: URL do repositório sem ref\n- local: caminho absoluto resolvido","sourceFile":"packages.md"},"prompt-templates":{"title":"Modelos de prompt","markdown":"> pi pode criar prompt templates. Peça para criar um para o seu fluxo de trabalho.\n\n\nOs modelos de prompt são Markdown trechos que se expandem em prompts completos. Digite `/name` no editor para invocar um modelo, onde `name` é o nome do arquivo sem `.md`.\n\n## Locais\n\nPi carrega prompt templates de:\n\n- Globais: `~/.pi/agent/prompts/*.md`\n- Projeto: `.pi/prompts/*.md` (somente depois que o projeto for confiável)\n- Pacotes: diretórios `prompts/` ou entradas `pi.prompts` em `package.json`\n- Configurações: `prompts` array com arquivos ou diretórios\n- CLI: `--prompt-template <path>` (repetível)\n\nDesative a descoberta com `--no-prompt-templates`.\n\n## Formatar\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- O nome do arquivo se torna o nome do comando. `review.md` torna-se `/review`.\n- `description` é opcional. Se estiver faltando, a primeira linha não vazia será usada.\n- `argument-hint` é opcional. Quando definida, a dica é exibida antes da descrição no menu suspenso de preenchimento automático.\n\n### Dicas de argumento\n\nUse `argument-hint` no frontmatter para mostrar os argumentos esperados no preenchimento automático. Use `<angle brackets>` para argumentos obrigatórios e `[square brackets]` para argumentos opcionais:\n\n```markdown\n---\ndescription: Review PRs from URLs with structured issue and code analysis\nargument-hint: \"<PR-URL>\"\n---\n```\n\nIsso é renderizado no menu suspenso de preenchimento automático como:\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## Uso\n\nDigite `/` seguido do nome do modelo no editor. O preenchimento automático mostra os modelos disponíveis com descrições.\n\n```\n/review                           # Expands review.md\n/component Button                 # Expands with argument\n/component Button \"click handler\" # Multiple arguments\n```\n\n## Argumentos\n\nOs modelos suportam argumentos posicionais, padrões e fatiamento simples:\n\n- `$1`, `$2`,... argumentos posicionais\n- `$@` ou `$ARGUMENTS` para todos os argumentos unidos\n- `${1:-default}` usa argumento 1 quando presente/não vazio, caso contrário `default`\n- `${@:-default}` ou `${ARGUMENTS:-default}` usa todos os argumentos quando presentes/não vazios, caso contrário `default`\n- `${@:N}` para argumentos da enésima posição (indexado 1)\n- `${@:N:L}` para `L` argumentos começando em N\n\nExemplo:\n\n```markdown\n---\ndescription: Create a component\n---\nCreate a React component named $1 with features: $@\n```\n\nOs valores padrão são úteis para argumentos opcionais:\n\n```markdown\nSummarize the current state in ${1:-7} bullet points.\n```\n\nUso: `/component Button \"onClick handler\" \"disabled support\"`\n\n## Carregando regras\n\n- A descoberta de modelos em `prompts/` não é recursiva.\n- Se você quiser modelos em subdiretórios, adicione-os explicitamente através das configurações `prompts` ou de um manifesto de pacote.","sourceFile":"prompt-templates.md"},"providers":{"title":"Providers","markdown":"Pi oferece suporte a provedores baseados em assinatura por meio de provedores OAuth e API key por meio de variáveis ​​de ambiente ou arquivo de autenticação. Catálogos integrados são fornecidos com pi; provedores configurados podem atualizar catálogos mais recentes e armazená-los em cache em `~/.pi/agent/models-store.json` para uso offline.\n\n## Índice\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## Assinaturas\n\nUse `/login` no modo interativo e selecione um provedor:\n\n- ChatGPT Plus/Pro (Codex)\n- Cláudio Pro/Max\n- GitHub Copiloto\n- xAI (assinatura Grok/X)\n- OpenRouter (OAuth cunhado API key cobrado de créditos OpenRouter)\n- Raio\n\nUse `/logout` para limpar credenciais. Os tokens são armazenados em `~/.pi/agent/auth.json` e atualizados automaticamente quando expiram. Em vez disso, o OpenRouter cria um API key controlado pelo usuário que não expira automaticamente.\n\n### Códice OpenAI\n\n- Requer assinatura ChatGPT Plus ou Pro\n- Aprovado oficialmente pela OpenAI: [Codex for OSS](https://developers.openai.com/community/codex-for-oss)\n\n### Cláudio Pro/Max\n\nA autenticação de assinatura Antrópica está ativa para contas Claude Pro/Max. O uso de chicotes de terceiros depende de [extra usage](https://claude.ai/settings/usage) e é cobrado por token, não de acordo com os limites do plano Claude.\n\n### GitHub Copiloto\n\n- Pressione Enter para github.com ou insira seu domínio GitHub Enterprise Server\n- Se você obtiver \"modelo não suportado\", habilite-o no VS Code: Copilot Chat → seletor de modelo → selecione modelo → \"Ativar\"\n\n### xAI (assinatura Grok/X)\n\n- Execute `/login xai` e selecione **Usar uma assinatura**\n- `XAI_API_KEY` permanece disponível através de **Use um API key**\n\n### OpenRouter\n\n- Execute `/login openrouter` e selecione **Entrar com OpenRouter** para abrir o fluxo de autorização OpenRouter PKCE\n- A autorização cria um OpenRouter API key controlado pelo usuário, cobrado de seus créditos OpenRouter\n- Em máquinas remotas/sem cabeça (por exemplo, acima de SSH) o navegador não pode alcançar o retorno de chamada de loopback; cole o URL de redirecionamento final (ou o código de autorização) no prompt de login\n- `OPENROUTER_API_KEY` permanece disponível através de **Use um API key**\n\n### Raio\n\nRadius é um gateway `pi-messages` dinâmico. `/login radius` armazena OAuth tokens em `auth.json`; o catálogo do gateway é atualizado de forma independente e armazenado em cache em `models-store.json`. Gateways Radius personalizados podem ser declarados em `models.json` com `\"oauth\": \"radius\"` e um gateway `baseUrl`.\n\n## API Chaves\n\n### Variáveis ​​de ambiente ou arquivo de autenticação\n\nUse `/login` no modo interativo e selecione um provedor para armazenar um API key em `auth.json` ou defina credenciais por meio de variável de ambiente:\n\n```bash\nexport ANTHROPIC_API_KEY=sk-ant-...\npi\n```\n\n| Provedor | Variável de ambiente | tecla `auth.json` |\n|----------|----------------------|------------------|\n| Antrópico | `ANTHROPIC_API_KEY` | `anthropic` |\n| Formiga Ling | `ANT_LING_API_KEY` | `ant-ling` |\n| Respostas OpenAI do Azure | `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 Gêmeos | `GEMINI_API_KEY` | `google` |\n| Base Amazônica | `AWS_BEARER_TOKEN_BEDROCK` | `amazon-bedrock` |\n| Mistral | `MISTRAL_API_KEY` | `mistral` |\n| Groq | `GROQ_API_KEY` | `groq` |\n| Cérebros | `CEREBRAS_API_KEY` | `cerebras` |\n| Gateway de IA da Cloudflare | `CLOUDFLARE_API_KEY` (+ `CLOUDFLARE_ACCOUNT_ID`, `CLOUDFLARE_GATEWAY_ID`) | `cloudflare-ai-gateway` |\n| IA de trabalhadores da Cloudflare | `CLOUDFLARE_API_KEY` (+ `CLOUDFLARE_ACCOUNT_ID`) | `cloudflare-workers-ai` |\n| xAI | `XAI_API_KEY` | `xai` |\n| OpenRouter | `OPENROUTER_API_KEY` | `openrouter` |\n| Gateway Vercel AI | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway` |\n| Plano de Codificação ZAI (Global) | `ZAI_API_KEY` | `zai` |\n| Plano de Codificação ZAI (China) | `ZAI_CODING_CN_API_KEY` | `zai-coding-cn` |\n| OpenCodeZen | `OPENCODE_API_KEY` | `opencode` |\n| OpenCode Go | `OPENCODE_API_KEY` | `opencode-go` |\n| Raio | `RADIUS_API_KEY` | `radius` |\n| Hugging Face | `HF_TOKEN` | `huggingface` |\n| Fogos de artifício | `FIREWORKS_API_KEY` | `fireworks` |\n| Juntos IA | `TOGETHER_API_KEY` | `together` |\n| Baseten | `BASETEN_API_KEY` | `baseten` |\n| Kimi para codificação | `KIMI_API_KEY` | `kimi-coding` |\n| MiniMax | `MINIMAX_API_KEY` | `minimax` |\n| MiniMax (China) | `MINIMAX_CN_API_KEY` | `minimax-cn` |\n| Plano Qwen Token (catálogo existente) | `QWEN_TOKEN_PLAN_API_KEY` | `qwen-token-plan` |\n| Plano de token Qwen (individual) | `QWEN_TOKEN_PLAN_API_KEY` | `qwen-token-plan-individual` |\n| Plano de Token Qwen (China) | `QWEN_TOKEN_PLAN_CN_API_KEY` | `qwen-token-plan-cn` |\n| Xiaomi MiMo | `XIAOMI_API_KEY` | `xiaomi` |\n| Plano de token Xiaomi MiMo (China) | `XIAOMI_TOKEN_PLAN_CN_API_KEY` | `xiaomi-token-plan-cn` |\n| Plano de token Xiaomi MiMo (Amsterdã) | `XIAOMI_TOKEN_PLAN_AMS_API_KEY` | `xiaomi-token-plan-ams` |\n| Plano de token Xiaomi MiMo (Singapura) | `XIAOMI_TOKEN_PLAN_SGP_API_KEY` | `xiaomi-token-plan-sgp` |\n\nReferência para variáveis ​​de ambiente e chaves `auth.json`: [`const envMap`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/env-api-keys.ts) em [`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#### Arquivo de autenticação\n\nArmazene credenciais em `~/.pi/agent/auth.json`:\n\n```json\n{\n  \"anthropic\": { \"type\": \"api_key\", \"key\": \"sk-ant-...\" },\n  \"ant-ling\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"openai\": { \"type\": \"api_key\", \"key\": \"sk-...\" },\n  \"deepseek\": { \"type\": \"api_key\", \"key\": \"sk-...\" },\n  \"nvidia\": { \"type\": \"api_key\", \"key\": \"nvapi-...\" },\n  \"google\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"opencode\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"opencode-go\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"together\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"qwen-token-plan\":  { \"type\": \"api_key\", \"key\": \"sk-sp-...\" },\n  \"qwen-token-plan-individual\": { \"type\": \"api_key\", \"key\": \"sk-sp-...\" },\n  \"qwen-token-plan-cn\": { \"type\": \"api_key\", \"key\": \"sk-sp-...\" },\n  \"xiaomi\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"xiaomi-token-plan-cn\":  { \"type\": \"api_key\", \"key\": \"...\" },\n  \"xiaomi-token-plan-ams\": { \"type\": \"api_key\", \"key\": \"...\" },\n  \"xiaomi-token-plan-sgp\": { \"type\": \"api_key\", \"key\": \"...\" }\n}\n```\n\n`qwen-token-plan-individual` usa o mesmo endpoint internacional e `QWEN_TOKEN_PLAN_API_KEY` que\n`qwen-token-plan`, mas limita o seletor aos modelos documentados para assinaturas individuais. O existente\nprovedor mantém seu catálogo mais amplo para compatibilidade com versões anteriores. Ao usar `auth.json`, armazene o\ncredencial no provedor que você selecionar; uma variável de ambiente é compartilhada por ambos os provedores internacionais.\n\nO arquivo é criado com permissões `0600` (somente leitura/gravação do usuário). As credenciais do arquivo de autenticação têm prioridade sobre as variáveis ​​de ambiente.\n\nAPI key as credenciais também podem incluir valores de ambiente no escopo do provedor. Esses valores são usados ​​antes das variáveis ​​de ambiente do processo ao resolver a chave de credencial, os cabeçalhos do provedor/modelo e a configuração do provedor, como IDs de conta Cloudflare, configurações do Azure OpenAI, projeto/localização do Vertex, configurações do Bedrock, `PI_CACHE_RETENTION` e `HTTP_PROXY`/`HTTPS_PROXY`.\n\n```json\n{\n  \"cloudflare-ai-gateway\": {\n    \"type\": \"api_key\",\n    \"key\": \"$CLOUDFLARE_API_KEY\",\n    \"env\": {\n      \"CLOUDFLARE_API_KEY\": \"...\",\n      \"CLOUDFLARE_ACCOUNT_ID\": \"account-id\",\n      \"CLOUDFLARE_GATEWAY_ID\": \"gateway-id\"\n    }\n  }\n}\n```\n\nUse isto quando pi precisar usar configurações de provedor diferentes das do ambiente shell do projeto.\n\n### Resolução chave\n\nO campo `key` suporta execução de comandos, interpolação de ambiente e literais:\n\n- **Comando Shell:** `\"!command\"` no início executa todo o valor como um comando e usa stdout (armazenado em cache durante a vida útil do processo)\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- **Interpolação de ambiente:** `\"$ENV_VAR\"` ou `\"${ENV_VAR}\"` usa o valor da variável nomeada. A interpolação funciona dentro de literais maiores.\n  ```json\n  { \"type\": \"api_key\", \"key\": \"$MY_ANTHROPIC_KEY\" }\n  { \"type\": \"api_key\", \"key\": \"${KEY_PREFIX}_${KEY_SUFFIX}\" }\n  ```\n  `$FOO_BAR` é a variável `FOO_BAR`; use `${FOO}_BAR` quando `BAR` for texto literal. Variáveis ​​de ambiente ausentes tornam o valor não resolvido.\n- **Escapes:** `\"$\"` emite um literal `\"$\"`; `\"$!\"` emite um literal `\"!\"` sem acionar a execução do comando.\n  ```json\n  { \"type\": \"api_key\", \"key\": \"$$literal-dollar-prefix\" }\n  { \"type\": \"api_key\", \"key\": \"$!literal-bang-prefix\" }\n  ```\n- **Valor literal:** Usado diretamente. Strings simples em maiúsculas como `MY_API_KEY` são literais; use `$MY_API_KEY` para variáveis ​​de ambiente.\n  ```json\n  { \"type\": \"api_key\", \"key\": \"sk-ant-...\" }\n  { \"type\": \"api_key\", \"key\": \"public\" }\n  ```\n\nAs credenciais OAuth também são armazenadas aqui após `/login` e gerenciadas automaticamente.\n\n## Nuvem 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### Base Amazônica\n\nUse `/login amazon-bedrock` para armazenar um Bedrock API key ou configure uma das fontes de credenciais ambientais da AWS abaixo:\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\nTambém suporta funções de tarefa ECS (`AWS_CONTAINER_CREDENTIALS_*`) e IRSA (`AWS_WEB_IDENTITY_TOKEN_FILE`).\n\n```bash\npi --provider amazon-bedrock --model us.anthropic.claude-sonnet-4-20250514-v1:0\n```\n\nO cache de prompt é habilitado automaticamente para modelos Claude cujo ID contém um nome de modelo reconhecível (modelos básicos e perfis de inferência definidos pelo sistema). Para perfis de inferência de aplicativos (cujos ARNs não contêm o nome do modelo), defina `AWS_BEDROCK_FORCE_CACHE=1` para ativar pontos de cache:\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\nSe você estiver se conectando a um proxy Bedrock API, as seguintes variáveis ​​de ambiente poderão ser usadas:\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### Gateway de IA da Cloudflare\n\n`CLOUDFLARE_API_KEY` pode ser definido via `/login`. O ID da conta e o slug do gateway podem ser definidos como variáveis ​​de ambiente ou no objeto `env` da credencial API key em `auth.json`.\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\nRotas para OpenAI, Anthropic e Workers AI por meio do Cloudflare AI Gateway. Workers AI usa o Unified API (`/compat`) e IDs de modelo prefixados (`workers-ai/@cf/...`). OpenAI usa a rota de passagem OpenAI (`/openai`) com IDs de modelo OpenAI nativos, como `gpt-5.1`. Anthropic usa a rota de passagem Anthropic (`/anthropic`) com IDs de modelo antrópicos nativos, como `claude-sonnet-4-5`.\n\nA autenticação do AI Gateway usa `CLOUDFLARE_API_KEY` como `cf-aig-authorization`. A autenticação upstream pode ser uma das seguintes:\n\n| Modo | Solicitar autorização | Autenticação upstream |\n|------|--------------|---------------|\n| IA dos trabalhadores | Somente token Cloudflare | Nativo da Cloudflare |\n| Faturamento unificado | Somente token Cloudflare | Cloudflare lida com autenticação upstream e deduz créditos |\n| BYOK armazenado | Somente token Cloudflare | Cloudflare injeta chaves de provedor armazenadas no painel do AI Gateway |\n| BYOK embutido | Token Cloudflare mais cabeçalho upstream `Authorization` | A solicitação fornece a chave do provedor upstream |\n\nPara uso normal do pi, prefira faturamento unificado ou BYOK armazenado. O BYOK inline requer a configuração de um cabeçalho upstream `Authorization` adicional para o provedor Cloudflare AI Gateway, por exemplo, por meio de uma substituição de provedor/modelo `models.json`.\n\n### IA de trabalhadores da Cloudflare\n\n`CLOUDFLARE_API_KEY` pode ser definido via `/login`. `CLOUDFLARE_ACCOUNT_ID` pode ser definido como uma variável de ambiente ou no objeto `env` da credencial API key em `auth.json`.\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 define automaticamente `x-session-affinity` para descontos de [prefix caching](https://developers.cloudflare.com/workers-ai/features/prompt-caching/).\n\n### Google Vertex AI\n\nUsa credenciais padrão do aplicativo:\n\n```bash\ngcloud auth application-default login\nexport GOOGLE_CLOUD_PROJECT=your-project\nexport GOOGLE_CLOUD_LOCATION=us-central1\n```\n\nOu defina `GOOGLE_APPLICATION_CREDENTIALS` para um arquivo de chave de conta de serviço.\n\n## llama.cpp\n\nPi suporta o servidor roteador llama.cpp. Configure-o com `/login llama.cpp`, gerencie modelos carregados com `/llama` e selecione um modelo carregado com `/model`.\n\nConsulte [llama.cpp](llama-cpp.md) para configuração do servidor, layout do diretório do modelo, variáveis ​​de ambiente e uso de comandos.\n\n## Personalizado Providers\n\n**Via models.json:** Adicione Ollama, LM Studio, vLLM ou qualquer provedor que fale um API compatível (conclusões OpenAI, respostas OpenAI, mensagens antrópicas, IA generativa do Google). Consulte [models.md](models.md).\n\n**Através de extensões:** Para provedores que precisam de implementações API personalizadas ou fluxos OAuth, crie uma extensão. Consulte [custom-provider.md](custom-provider.md) e [examples/extensions/custom-provider-gitlab-duo](../examples/extensions/custom-provider-gitlab-duo/).\n\n## Ordem de Resolução\n\nAo resolver credenciais para um provedor:\n\n1. CLI `--api-key` bandeira\n2. Entrada `auth.json` (token API key ou OAuth)\n3. Variável de ambiente\n4. Chaves de provedor personalizadas de `models.json`","sourceFile":"providers.md"},"quickstart":{"title":"Início rápido","markdown":"Esta página leva você desde a instalação até uma primeira sessão útil do pi.\n\n## Instalar\n\nPi é distribuído como um pacote npm:\n\n```bash\nnpm install -g --ignore-scripts @earendil-works/pi-coding-agent\n```\n\n`--ignore-scripts` desativa scripts de ciclo de vida de dependência durante a instalação. Pi não requer scripts de instalação para instalações normais do npm.\n\n### Desinstalar\n\nUse o gerenciador de pacotes que instalou o pi. O instalador curl usa npm globalmente, então as instalações curl e npm são removidas com npm:\n\n```bash\n# curl installer or npm install -g\nnpm uninstall -g @earendil-works/pi-coding-agent\n\n# pnpm\npnpm remove -g @earendil-works/pi-coding-agent\n\n# Yarn\nyarn global remove @earendil-works/pi-coding-agent\n\n# Bun\nbun uninstall -g @earendil-works/pi-coding-agent\n```\n\nA desinstalação do pi deixa configurações, credenciais, sessões e pacotes pi instalados em `~/.pi/agent/`.\n\nEm seguida, inicie o pi no diretório do projeto em que deseja que ele funcione:\n\n```bash\ncd /path/to/project\npi\n```\n\n## Autenticar\n\nPi pode usar provedores de chave subscription providers a `/login` ou API por meio de variáveis ​​de ambiente ou do arquivo de autenticação.\n\n### Opção 1: login de assinatura\n\nInicie pi e execute:\n\n```text\n/login\n```\n\nEm seguida, selecione um provedor. Os logins de assinatura integrados incluem Claude Pro/Max, ChatGPT Plus/Pro (Codex) e GitHub Copilot.\n\n### Opção 2: API key\n\nDefina um API key antes de iniciar o pi:\n\n```bash\nexport ANTHROPIC_API_KEY=sk-ant-...\npi\n```\n\nVocê também pode executar `/login` e selecionar um provedor de chave API para armazenar a chave em `~/.pi/agent/auth.json`.\n\nConsulte [Providers](providers.md) para todos os provedores suportados, variáveis ​​de ambiente e configuração do provedor de nuvem.\n\n## Primeira sessão\n\nAssim que o pi iniciar, digite uma solicitação e pressione Enter:\n\n```text\nSummarize this repository and tell me how to run its checks.\n```\n\nPor padrão, pi fornece ao modelo quatro ferramentas:\n\n- `read` - lê arquivos\n- `write` - cria ou sobrescreve arquivos\n- `edit` - corrigir arquivos\n- `bash` - executa comandos shell\n\nFerramentas adicionais somente leitura integradas (`grep`, `find`, `ls`) estão disponíveis através de opções de ferramentas. Pi é executado em seu diretório de trabalho atual e pode modificar arquivos lá. Use git ou outro fluxo de trabalho de checkpoint se desejar uma reversão fácil.\n\n## Dê instruções do projeto pi\n\nPi carrega context files na inicialização. Adicione um arquivo `AGENTS.md` para informar como trabalhar em um projeto:\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 cargas:\n\n- `~/.pi/agent/AGENTS.md` para instruções globais\n- `AGENTS.md` ou `CLAUDE.md` dos diretórios pai e do diretório atual\n\nSe um diretório contém `AGENTS.override.md`, Pi carrega-o em vez de `AGENTS.md` ou `CLAUDE.md` desse diretório.\n\nReinicie o pi ou execute `/reload`, após alterar context files.\n\n## Coisas comuns para tentar\n\n### Arquivos de referência\n\nDigite `@` no editor para pesquisar arquivos difusos ou passe os arquivos na linha de comando:\n\n```bash\npi @README.md \"Summarize this\"\npi @src/app.ts @src/app.test.ts \"Review these together\"\n```\n\nImagens ou texto podem ser colados com Ctrl+V (Alt+V no Windows); as imagens também podem ser arrastadas para terminais suportados.\n\n### Execute comandos shell\n\nNo modo interativo:\n\n```text\n!npm run lint\n```\n\nA saída do comando é enviada ao modelo. Use `!!command` para executar um comando sem adicionar sua saída ao contexto do modelo.\n\n### Trocar modelos\n\nUse `/model` ou Ctrl+L para escolher um modelo. Use Shift+Tab para alternar o nível de pensamento. Use Ctrl+P / Shift+Ctrl+P para percorrer os modelos com escopo definido.\n\n### Continuar mais tarde\n\nAs sessões são salvas automaticamente:\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\nDentro do pi, use `/resume`, `/new`, `/tree`, `/fork` e `/clone` para gerenciar sessões.\n\n### Modo não interativo\n\nPara solicitações únicas:\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\nUse `--mode json` para saída de evento JSON ou `--mode rpc` para integração de processos.\n\n## Próximas etapas\n\n- [Using Pi](usage.md) - modo interativo, slash commands, sessões, context files e CLI referência.\n- [Providers](providers.md) - autenticação e configuração do modelo.\n- [Settings](settings.md) - configuração global e do projeto.\n- [Keybindings](keybindings.md) - atalhos e personalização.\n- [Pi Packages](packages.md) - instale extensões, habilidades, prompts e temas compartilhados.\n\nNotas da plataforma: [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 Modo","markdown":"O modo RPC permite a operação sem cabeça do agente de codificação por meio de um protocolo JSON sobre stdin/stdout. Isso é útil para incorporar o agente em outros aplicativos, IDEs ou UIs personalizadas.\n\n**Nota para usuários Node.js/TypeScript**: Se você estiver construindo uma aplicação Node.js, considere usar `AgentSession` diretamente de `@earendil-works/pi-coding-agent` em vez de gerar um subprocesso. Veja [`src/core/agent-session.ts`](../src/core/agent-session.ts) para API. Para um cliente TypeScript baseado em subprocesso, consulte [`src/modes/rpc/rpc-client.ts`](../src/modes/rpc/rpc-client.ts).\n\n## Iniciando o modo RPC\n\n```bash\npi --mode rpc [options]\n```\n\nOpções comuns:\n- `--provider <name>`: Defina o provedor LLM (antrópico, openai, google, etc.)\n- `--model <pattern>`: Padrão ou ID do modelo (suporta `provider/id` e opcional `:<thinking>`)\n- `--name <name>` / `-n <name>`: Defina o nome de exibição da sessão na inicialização\n- `--no-session`: Desativa a persistência da sessão\n- `--session-dir <path>`: Diretório de armazenamento de sessão personalizado\n\n## Visão geral do protocolo\n\n- **Comandos**: objetos JSON enviados para stdin, um por linha\n- **Respostas**: JSON objetos com `type: \"response\"` indicando sucesso/falha do comando\n- **Eventos**: eventos do agente transmitidos para stdout como JSON linhas\n\nTodos os comandos suportam um campo opcional `id` para correlação solicitação/resposta. Se fornecido, a resposta correspondente incluirá o mesmo `id`. Os eventos `bash_execution_update` também incluem o `id` do comando `bash` de origem.\n\n### Enquadramento\n\nO modo RPC usa semântica JSONL estrita com LF (`\\n`) como o único delimitador de registro.\n\nIsso é importante para os clientes:\n- Dividir registros apenas em `\\n`\n- Aceite a entrada opcional `\\r\\n` removendo um `\\r` final\n- Não use leitores de linha genéricos que tratam separadores Unicode como novas linhas\n\nEm particular, o nó `readline` não é compatível com protocolo para o modo RPC porque também se divide em `U+2028` e `U+2029`, que são válidos dentro de strings JSON.\n\n## Comandos\n\n### Solicitando\n\n#### incitar\n\nEnvie um prompt do usuário ao agente. A resposta do comando é emitida depois que o prompt é aceito, colocado na fila ou manipulado. Os eventos continuam sendo transmitidos de forma assíncrona após a aceitação.\n\n```json\n{\"id\": \"req-1\", \"type\": \"prompt\", \"message\": \"Hello, world!\"}\n```\n\nCom imagens:\n```json\n{\"type\": \"prompt\", \"message\": \"What's in this image?\", \"images\": [{\"type\": \"image\", \"data\": \"base64-encoded-data\", \"mimeType\": \"image/png\"}]}\n```\n\n**Durante o streaming**: se o agente já estiver transmitindo, você deverá especificar `streamingBehavior` para enfileirar a mensagem:\n\n```json\n{\"type\": \"prompt\", \"message\": \"New instruction\", \"streamingBehavior\": \"steer\"}\n```\n\n- `\"steer\"`: Coloca a mensagem na fila enquanto o agente está em execução. Ele é entregue após o turno atual do assistente terminar de executar suas chamadas de ferramenta, antes da próxima chamada do LLM.\n- `\"followUp\"`: Espere até o agente terminar. A mensagem é entregue somente quando o agente para.\n\nSe o agente estiver transmitindo e nenhum `streamingBehavior` for especificado, o comando retornará um erro.\n\n**Comandos de extensão**: Se a mensagem for um comando de extensão (por exemplo, `/mycommand`), ela será executada imediatamente, mesmo durante o streaming. Os comandos de extensão gerenciam sua própria interação LLM via `pi.sendMessage()`.\n\n**Expansão de entrada**: Os comandos de habilidade (`/skill:name`) e prompt templates (`/template`) são expandidos antes do envio/enfileiramento.\n\nResposta:\n```json\n{\"id\": \"req-1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true}\n```\n\n`success: true` significa que o prompt foi aceito, colocado na fila ou tratado imediatamente. `success: false` significa que o prompt foi rejeitado antes da aceitação. As falhas após a aceitação são relatadas através do evento normal e do fluxo de mensagens, não como um segundo `response` para o mesmo ID de solicitação.\n\nO campo `images` é opcional. Cada imagem usa o formato `ImageContent`: `{\"type\": \"image\", \"data\": \"base64-encoded-data\", \"mimeType\": \"image/png\"}`.\n\n#### dirigir\n\nColoque uma mensagem de orientação na fila enquanto o agente está em execução. Ele é entregue após o turno atual do assistente terminar de executar suas chamadas de ferramenta, antes da próxima chamada do LLM. Os comandos de habilidade e prompt templates foram expandidos. Comandos de extensão não são permitidos (use `prompt`).\n\n```json\n{\"type\": \"steer\", \"message\": \"Stop and do this instead\"}\n```\n\nCom imagens:\n```json\n{\"type\": \"steer\", \"message\": \"Look at this instead\", \"images\": [{\"type\": \"image\", \"data\": \"base64-encoded-data\", \"mimeType\": \"image/png\"}]}\n```\n\nO campo `images` é opcional. Cada imagem usa o formato `ImageContent` (igual a `prompt`).\n\nResposta:\n```json\n{\"type\": \"response\", \"command\": \"steer\", \"success\": true}\n```\n\nConsulte [set_steering_mode](#set_steering_mode) para controlar como as mensagens de direção são processadas.\n\n#### seguir\n\nColoque uma mensagem de acompanhamento na fila para ser processada após a conclusão do agente. Entregue somente quando o agente não tiver mais chamadas de ferramenta ou mensagens de orientação. Os comandos de habilidade e prompt templates foram expandidos. Comandos de extensão não são permitidos (use `prompt`).\n\n```json\n{\"type\": \"follow_up\", \"message\": \"After you're done, also do this\"}\n```\n\nCom imagens:\n```json\n{\"type\": \"follow_up\", \"message\": \"Also check this image\", \"images\": [{\"type\": \"image\", \"data\": \"base64-encoded-data\", \"mimeType\": \"image/png\"}]}\n```\n\nO campo `images` é opcional. Cada imagem usa o formato `ImageContent` (igual a `prompt`).\n\nResposta:\n```json\n{\"type\": \"response\", \"command\": \"follow_up\", \"success\": true}\n```\n\nConsulte [set_follow_up_mode](#set_follow_up_mode) para controlar como as mensagens de acompanhamento são processadas.\n\n#### abortar\n\nAnule a operação do agente atual.\n\n```json\n{\"type\": \"abort\"}\n```\n\nResposta:\n```json\n{\"type\": \"response\", \"command\": \"abort\", \"success\": true}\n```\n\n#### nova_sessão\n\nInicie uma nova sessão. Pode ser cancelado por um manipulador de eventos de extensão `session_before_switch`.\n\n```json\n{\"type\": \"new_session\"}\n```\n\nCom rastreamento opcional da sessão pai:\n```json\n{\"type\": \"new_session\", \"parentSession\": \"/path/to/parent-session.jsonl\"}\n```\n\nResposta:\n```json\n{\"type\": \"response\", \"command\": \"new_session\", \"success\": true, \"data\": {\"cancelled\": false}}\n```\n\nSe uma extensão for cancelada:\n```json\n{\"type\": \"response\", \"command\": \"new_session\", \"success\": true, \"data\": {\"cancelled\": true}}\n```\n\n### Estado\n\n#### get_state\n\nObtenha o estado atual da sessão.\n\n```json\n{\"type\": \"get_state\"}\n```\n\nResposta:\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\nO campo `model` é um objeto [Model](#model) completo ou `null`. O campo `sessionName` é o nome de exibição definido por meio de `set_session_name` ou omitido se não for definido.\n\n#### get_messages\n\nReceba todas as mensagens da conversa.\n\n```json\n{\"type\": \"get_messages\"}\n```\n\nResposta:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_messages\",\n  \"success\": true,\n  \"data\": {\"messages\": [...]}\n}\n```\n\nAs mensagens são objetos `AgentMessage` (veja [Message Types](#message-types)).\n\n### Modelo\n\n#### conjunto_modelo\n\nMude para um modelo específico.\n\n```json\n{\"type\": \"set_model\", \"provider\": \"anthropic\", \"modelId\": \"claude-sonnet-4-20250514\"}\n```\n\nA resposta contém o objeto [Model](#model) completo:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"set_model\",\n  \"success\": true,\n  \"data\": {...}\n}\n```\n\n#### modelo_de_ciclo\n\nPasse para o próximo modelo disponível. Retorna dados `null` se apenas um modelo estiver disponível.\n\n```json\n{\"type\": \"cycle_model\"}\n```\n\nResposta:\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\nO campo `model` é um objeto [Model](#model) completo.\n\n#### get_available_models\n\nListe todos os modelos configurados.\n\n```json\n{\"type\": \"get_available_models\"}\n```\n\nA resposta contém uma matriz de objetos [Model](#model) completos:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"get_available_models\",\n  \"success\": true,\n  \"data\": {\n    \"models\": [...]\n  }\n}\n```\n\n### Pensamento\n\n#### set_thinking_level\n\nDefina o nível de raciocínio/pensamento para modelos que o suportem.\n\n```json\n{\"type\": \"set_thinking_level\", \"level\": \"high\"}\n```\n\nNíveis: `\"off\"`, `\"minimal\"`, `\"low\"`, `\"medium\"`, `\"high\"`, `\"xhigh\"`, `\"max\"`\n\n`\"xhigh\"` e `\"max\"` são expostos somente quando suportados pelo modelo selecionado. Alguns modelos, incluindo o GPT-5.6, expõem ambos.\n\nResposta:\n```json\n{\"type\": \"response\", \"command\": \"set_thinking_level\", \"success\": true}\n```\n\n#### nível_de_pensamento_de_ciclo\n\nPercorra os níveis de pensamento disponíveis. Retorna dados `null` se o modelo não suportar o pensamento.\n\n```json\n{\"type\": \"cycle_thinking_level\"}\n```\n\nResposta:\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\nListe os níveis de pensamento suportados pelo modelo atual. Retorna `[\"off\"]` para um modelo sem suporte de raciocínio.\n\n```json\n{\"type\": \"get_available_thinking_levels\"}\n```\n\nResposta:\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### Modos de fila\n\n#### set_steering_mode\n\nControle como as mensagens de direção (de `steer`) são entregues.\n\n```json\n{\"type\": \"set_steering_mode\", \"mode\": \"one-at-a-time\"}\n```\n\nModos:\n- `\"all\"`: Entrega todas as mensagens de direção após o turno atual do assistente terminar de executar suas chamadas de ferramenta\n- `\"one-at-a-time\"`: Entrega uma mensagem de direção por curva de assistente concluída (padrão)\n\nResposta:\n```json\n{\"type\": \"response\", \"command\": \"set_steering_mode\", \"success\": true}\n```\n\n#### set_follow_up_mode\n\nControle como as mensagens de acompanhamento (de `follow_up`) são entregues.\n\n```json\n{\"type\": \"set_follow_up_mode\", \"mode\": \"one-at-a-time\"}\n```\n\nModos:\n- `\"all\"`: Entrega todas as mensagens de acompanhamento quando o agente termina\n- `\"one-at-a-time\"`: Entrega uma mensagem de acompanhamento por conclusão do agente (padrão)\n\nResposta:\n```json\n{\"type\": \"response\", \"command\": \"set_follow_up_mode\", \"success\": true}\n```\n\n### Compactação\n\n#### compactar\n\nCompacte manualmente o contexto da conversa para reduzir o uso de token.\n\n```json\n{\"type\": \"compact\"}\n```\n\nCom instruções personalizadas:\n```json\n{\"type\": \"compact\", \"customInstructions\": \"Focus on code changes\"}\n```\n\nResposta:\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` é uma estimativa heurística sobre o contexto da mensagem reconstruída imediatamente após a compactação, não uma contagem exata de tokens do provedor. `usage` relata a chamada ou chamadas LLM que geraram o resumo e podem ser omitidas por manipuladores de compactação customizados.\n\n#### set_auto_compaction\n\nAtive ou desative a compactação automática quando o contexto estiver quase cheio.\n\n```json\n{\"type\": \"set_auto_compaction\", \"enabled\": true}\n```\n\nResposta:\n```json\n{\"type\": \"response\", \"command\": \"set_auto_compaction\", \"success\": true}\n```\n\n### Tentar novamente\n\n#### set_auto_retry\n\nHabilite ou desabilite a nova tentativa automática em erros transitórios (sobrecarregado, limite de taxa, 5xx).\n\n```json\n{\"type\": \"set_auto_retry\", \"enabled\": true}\n```\n\nResposta:\n```json\n{\"type\": \"response\", \"command\": \"set_auto_retry\", \"success\": true}\n```\n\n#### abortar_retry\n\nAbortar uma nova tentativa em andamento (cancelar o atraso e parar de tentar novamente).\n\n```json\n{\"type\": \"abort_retry\"}\n```\n\nResposta:\n```json\n{\"type\": \"response\", \"command\": \"abort_retry\", \"success\": true}\n```\n\n### Bash\n\n#### bash\n\nExecute um comando shell e adicione saída ao contexto da conversa. Fluxos de saída como eventos `bash_execution_update` enquanto o comando é executado; a resposta contém o resultado final.\n\n```json\n{\"id\": \"req-1\", \"type\": \"bash\", \"command\": \"ls -la\"}\n```\n\nInclua um `id` para associar eventos `bash_execution_update` transmitidos a este comando.\n\nResposta:\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\nSe a saída foi truncada, inclui `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**Como os resultados bash chegam ao LLM:**\n\nO comando `bash` é executado imediatamente e retorna `BashResult`. Internamente, um `BashExecutionMessage` é criado e armazenado no estado de mensagem do agente.\n\nQuando o próximo comando `prompt` é enviado, todas as mensagens (incluindo `BashExecutionMessage`) são transformadas antes de serem enviadas ao LLM. O `BashExecutionMessage` é convertido em `UserMessage` com este formato:\n\n````\nRan `ls -la`\n```\ntotal 48\ndrwxr-xr-x...\n```\n````\n\nIsso significa:\n1. A saída do Bash é incluída no contexto LLM no **próximo prompt**, não imediatamente\n2. Vários comandos bash podem ser executados antes de um prompt; todas as saídas serão incluídas\n\n#### abortar_bash\n\nAbortar um comando bash em execução.\n\n```json\n{\"type\": \"abort_bash\"}\n```\n\nResposta:\n```json\n{\"type\": \"response\", \"command\": \"abort_bash\", \"success\": true}\n```\n\n### Sessão\n\n#### get_session_stats\n\nObtenha uso de token, estatísticas de custo e uso atual da janela de contexto.\n\n```json\n{\"type\": \"get_session_stats\"}\n```\n\nResposta:\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` e `cost` incluem mensagens de assistente, uso relatado por ferramentas e geração de compactação/resumo de ramificação em toda a sessão. `contextUsage` contém a estimativa atual da janela de contexto usada para compactação e exibição de rodapé.\n\n`contextUsage` é omitido quando nenhum modelo ou janela de contexto está disponível. `contextUsage.tokens` e `contextUsage.percent` são `null` imediatamente após a compactação até que uma nova resposta do assistente pós-compactação forneça dados de uso válidos.\n\n#### exportação_html\n\nExporte a sessão para um arquivo HTML.\n\n```json\n{\"type\": \"export_html\"}\n```\n\nCom caminho personalizado:\n```json\n{\"type\": \"export_html\", \"outputPath\": \"/tmp/session.html\"}\n```\n\nResposta:\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\nCarregue um arquivo de sessão diferente. Pode ser cancelado por um manipulador de eventos de extensão `session_before_switch`.\n\n```json\n{\"type\": \"switch_session\", \"sessionPath\": \"/path/to/session.jsonl\"}\n```\n\nResposta:\n```json\n{\"type\": \"response\", \"command\": \"switch_session\", \"success\": true, \"data\": {\"cancelled\": false}}\n```\n\nSe um ramal cancelou a troca:\n```json\n{\"type\": \"response\", \"command\": \"switch_session\", \"success\": true, \"data\": {\"cancelled\": true}}\n```\n\n#### garfo\n\nCrie uma nova bifurcação a partir de uma mensagem de usuário anterior na ramificação ativa. Pode ser cancelado por um manipulador de eventos de extensão `session_before_fork`. Retorna o texto da mensagem que está sendo bifurcada.\n\n```json\n{\"type\": \"fork\", \"entryId\": \"abc123\"}\n```\n\nResposta:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"fork\",\n  \"success\": true,\n  \"data\": {\"text\": \"The original prompt text...\", \"cancelled\": false}\n}\n```\n\nSe uma extensão cancelou a bifurcação:\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#### clone\n\nDuplique a ramificação ativa atual em uma nova sessão na posição atual. Pode ser cancelado por um manipulador de eventos de extensão `session_before_fork`.\n\n```json\n{\"type\": \"clone\"}\n```\n\nResposta:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"clone\",\n  \"success\": true,\n  \"data\": {\"cancelled\": false}\n}\n```\n\nSe uma extensão cancelou o clone:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"clone\",\n  \"success\": true,\n  \"data\": {\"cancelled\": true}\n}\n```\n\n#### get_fork_messages\n\nObtenha mensagens do usuário disponíveis para bifurcação.\n\n```json\n{\"type\": \"get_fork_messages\"}\n```\n\nResposta:\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\nObtenha todas as entradas da sessão em ordem de acréscimo (excluindo o cabeçalho da sessão). A sessão é uma árvore de entradas somente anexadas com IDs estáveis, portanto, um ID de entrada funciona como um cursor durável: passe o último ID de entrada que você viu como `since` para obter apenas entradas estritamente depois dele, mesmo após reinicializações do cliente. Ao contrário de `get_messages`, isso inclui histórico de pré-compactação e ramificações abandonadas.\n\n```json\n{\"type\": \"get_entries\"}\n```\n\nCom um cursor:\n```json\n{\"type\": \"get_entries\", \"since\": \"abc123\"}\n```\n\nResposta:\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` é o id da entrada folha atual (`null` para uma sessão vazia), para que um cliente possa dizer em uma viagem de ida e volta se a filial ativa foi movida. Se `since` não corresponder a nenhum ID de entrada, a resposta será `success: false`.\n\n#### get_tree\n\nObtenha a sessão como uma árvore de entradas. Cada nó é `{entry, children, label?, labelTimestamp?}`. Uma sessão bem formada possui uma única raiz; entradas órfãs (cadeia pai quebrada) também aparecem como raízes.\n\n```json\n{\"type\": \"get_tree\"}\n```\n\nResposta:\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\nObtenha o conteúdo de texto da última mensagem do assistente.\n\n```json\n{\"type\": \"get_last_assistant_text\"}\n```\n\nResposta:\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\nRetorna `{\"text\": null}` se não existirem mensagens do assistente.\n\n#### set_session_name\n\nDefina um nome de exibição para a sessão atual. O nome aparece nas listagens de sessões e ajuda a identificar as sessões.\n\n```json\n{\"type\": \"set_session_name\", \"name\": \"my-feature-work\"}\n```\n\nResposta:\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"set_session_name\",\n  \"success\": true\n}\n```\n\nO nome da sessão atual está disponível através de `get_state` no campo `sessionName`. Para definir o nome inicial ao iniciar o modo RPC, passe `--name <name>` ou `-n <name>` para o processo `pi --mode rpc`.\n\n### Comandos\n\n#### obter_comandos\n\nObtenha os comandos disponíveis (comandos de extensão, prompt templates e habilidades). Eles podem ser invocados por meio do comando `prompt` prefixando `/`.\n\n```json\n{\"type\": \"get_commands\"}\n```\n\nResposta:\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\nCada comando possui:\n- `name`: Nome do comando (invocar com `/name`)\n- `description`: Descrição legível por humanos (opcional para comandos de extensão)\n- `source`: Que tipo de comando:\n  - `\"extension\"`: Registrado via `pi.registerCommand()` em uma extensão\n  - `\"prompt\"`: Carregado de um arquivo de modelo de prompt `.md`\n  - `\"skill\"`: Carregado de um diretório de habilidades (o nome é prefixado com `skill:`)\n- `location`: De onde foi carregado (opcional, não presente para extensões):\n  - `\"user\"`: Nível do usuário (`~/.pi/agent/`)\n  - `\"project\"`: Nível do projeto (`./.pi/agent/`)\n  - `\"path\"`: Caminho explícito via CLI ou configurações\n- `path`: Caminho absoluto do arquivo para a fonte do comando (opcional)\n\n**Nota**: Os comandos TUI integrados (`/settings`, `/hotkeys`, etc.) não estão incluídos. Eles são tratados apenas no modo interativo e não seriam executados se enviados via `prompt`.\n\n## Eventos\n\nOs eventos são transmitidos para stdout como JSON linhas durante a operação do agente. Os eventos geralmente não incluem um campo `id`; `bash_execution_update` inclui o `id` de seu comando `bash` de origem quando um foi fornecido.\n\n### Tipos de eventos\n\n| Evento | Descrição |\n|-------|-------------|\n| `agent_start` | Agente começa a processar |\n| `agent_end` | Uma execução do agente de baixo nível é concluída (ainda pode ser seguida por nova tentativa, compactação ou continuações na fila) |\n| `agent_settled` | A execução do agente está totalmente liquidada; nenhuma nova tentativa automática, nova tentativa de compactação ou continuação na fila permanece |\n| `turn_start` | Novo turno começa |\n| `turn_end` | Turno concluído (inclui mensagem do assistente e resultados da ferramenta) |\n| `message_start` | A mensagem começa |\n| `message_update` | Atualização de streaming (deltas de texto/pensamento/toolcall) |\n| `message_end` | Mensagem concluída |\n| `bash_execution_update` | Bloco de saída de comando direto RPC bash |\n| `tool_execution_start` | Ferramenta inicia execução |\n| `tool_execution_update` | Progresso da execução da ferramenta (saída de streaming) |\n| `tool_execution_end` | Ferramenta concluída |\n| `queue_update` | Fila de orientação/acompanhamento pendente alterada |\n| `compaction_start` | A compactação começa |\n| `compaction_end` | Compactação concluída |\n| `auto_retry_start` | A nova tentativa automática começa (após erro transitório) |\n| `auto_retry_end` | A nova tentativa automática é concluída (sucesso ou falha final) |\n| `summarization_retry_scheduled` | Nova tentativa agendada para um erro de compactação transitória ou de resumo de ramificação |\n| `summarization_retry_attempt_start` | A solicitação de resumo repetida é iniciada |\n| `summarization_retry_finished` | Loop de nova tentativa de resumo concluído |\n| `extension_error` | A extensão gerou um erro |\n\n### agente_start\n\nEmitido quando o agente começa a processar um prompt.\n\n```json\n{\"type\": \"agent_start\"}\n```\n\n### agente_end\n\nEmitido quando uma execução de agente de baixo nível é concluída. Contém todas as mensagens geradas durante esta execução. Se `willRetry` for verdadeiro, uma nova tentativa automática ocorrerá.\n\n```json\n{\n  \"type\": \"agent_end\",\n  \"messages\": [...],\n  \"willRetry\": false\n}\n```\n\n### agente_settled\n\nEmitido após a conclusão da execução completa no nível da sessão. Neste ponto, Pi não continuará automaticamente através de novas tentativas, novas tentativas de compactação ou mensagens de acompanhamento enfileiradas.\n\n```json\n{\"type\": \"agent_settled\"}\n```\n\n### turn_start / turn_end\n\nUm turno consiste em uma resposta do assistente mais quaisquer chamadas e resultados de ferramenta resultantes.\n\n```json\n{\"type\": \"turn_start\"}\n```\n\n```json\n{\n  \"type\": \"turn_end\",\n  \"message\": {...},\n  \"toolResults\": [...]\n}\n```\n\n### mensagem_início / mensagem_fim\n\nEmitido quando uma mensagem começa e é concluída. O campo `message` contém um `AgentMessage`.\n\n```json\n{\"type\": \"message_start\", \"message\": {...}}\n{\"type\": \"message_end\", \"message\": {...}}\n```\n\n### message_update (transmissão)\n\nEmitido durante o streaming de mensagens do assistente. Contém um evento delta sem um instantâneo de mensagem cumulativo.\n\n```json\n{\n  \"type\": \"message_update\",\n  \"assistantMessageEvent\": {\n    \"type\": \"text_delta\",\n    \"contentIndex\": 0,\n    \"delta\": \"Hello \"\n  }\n}\n```\n\nO campo `assistantMessageEvent` contém um destes tipos delta:\n\n| Tipo | Descrição |\n|------|-------------|\n| `text_start` | Bloqueio de conteúdo de texto iniciado |\n| `text_delta` | Pedaço de conteúdo de texto |\n| `text_end` | O bloco de conteúdo de texto terminou |\n| `thinking_start` | Bloqueio de pensamento iniciado |\n| `thinking_delta` | Pedaço de conteúdo pensando |\n| `thinking_end` | O bloqueio de pensamento terminou |\n| `toolcall_start` | Chamada de ferramenta iniciada |\n| `toolcall_delta` | Parte de argumentos de chamada de ferramenta |\n| `toolcall_end` | Chamada de ferramenta encerrada (inclui objeto `toolCall` completo) |\n\nExemplo de streaming de uma resposta de texto:\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` omite intencionalmente o antigo campo cumulativo `message` e\n`assistantMessageEvent.partial`. Os clientes que precisam de uma mensagem parcial ao vivo devem montá-la\nde `message_start` e eventos subsequentes usando `contentIndex`. Tratar `message_end.message`\ncomo autoritário. Para chamadas de ferramenta, buffer `toolcall_delta.delta`; `toolcall_end.toolCall`\ncontém a chamada concluída.\n\n### bash_execution_update\n\nEmitido uma vez para cada pedaço de saída de um comando `bash` direto. `id` corresponde ao `id` do comando, permitindo que os clientes associem a saída ao comando correto.\n\nOs eventos transmitem toda a saída enquanto o comando é executado, mesmo que o `output` da resposta `bash` final esteja truncado.\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\nEmitido quando uma ferramenta é iniciada, transmite o progresso e conclui a execução.\n\n```json\n{\n  \"type\": \"tool_execution_start\",\n  \"toolCallId\": \"call_abc123\",\n  \"toolName\": \"bash\",\n  \"args\": {\"command\": \"ls -la\"}\n}\n```\n\nDurante a execução, os eventos `tool_execution_update` transmitem resultados parciais (por exemplo, bash produz a saída quando chega):\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\nQuando concluído:\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\nUse `toolCallId` para correlacionar eventos. O `partialResult` em `tool_execution_update` contém a saída acumulada até agora (não apenas o delta), permitindo que os clientes simplesmente substituam sua exibição em cada atualização.\n\n### queue_update\n\nEmitido sempre que a fila de direcionamento ou acompanhamento pendente é alterada.\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### compactação_início / compactação_fim\n\nEmitido durante a execução da compactação, seja ela manual ou automática.\n\n```json\n{\"type\": \"compaction_start\", \"reason\": \"threshold\"}\n```\n\nO campo `reason` é `\"manual\"`, `\"threshold\"` ou `\"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\nSe `reason` for `\"overflow\"` e a compactação for bem-sucedida, `willRetry` será `true` e o agente tentará novamente o prompt automaticamente.\n\nSe a compactação foi abortada, `result` é `null` e `aborted` é `true`.\n\nSe a compactação falhou (por exemplo, API cota excedida), `result` é `null`, `aborted` é `false` e `errorMessage` contém a descrição do erro.\n\n### auto_retry_start /auto_retry_end\n\nEmitido quando uma nova tentativa automática é acionada após um erro transitório (sobrecarregado, limite de taxa, 5xx).\n\n```json\n{\n  \"type\": \"auto_retry_start\",\n  \"attempt\": 1,\n  \"maxAttempts\": 3,\n  \"delayMs\": 2000,\n  \"errorMessage\": \"529 {\\\"type\\\":\\\"error\\\",\\\"error\\\":{\\\"type\\\":\\\"overloaded_error\\\",\\\"message\\\":\\\"Overloaded\\\"}}\"\n}\n```\n\n```json\n{\n  \"type\": \"auto_retry_end\",\n  \"success\": true,\n  \"attempt\": 2\n}\n```\n\nEm caso de falha final (máximo de tentativas excedido):\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\nEmitido quando a compactação ou o resumo do branch são repetidos após um erro transitório do provedor. Esses eventos usam as mesmas configurações de novas tentativas que as novas tentativas automáticas de giro do assistente.\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\nPara resumos de ramificações, `source` é `\"branchSummary\"` e nenhum `reason` está presente.\n\n```json\n{\n  \"type\": \"summarization_retry_finished\"\n}\n```\n\n### erro_de_extensão\n\nEmitido quando uma extensão gera um erro.\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## Protocolo de UI de extensão\n\nExtensions pode solicitar interação do usuário via `ctx.ui.select()`, `ctx.ui.confirm()`, etc. No modo RPC, eles são traduzidos em um subprotocolo de solicitação/resposta no topo do fluxo base de comando/evento.\n\nExistem duas categorias de métodos de extensão de UI:\n\n- **Métodos de diálogo** (`select`, `confirm`, `input`, `editor`): emite um `extension_ui_request` em stdout e bloqueia até que o cliente envie de volta um `extension_ui_response` em stdin com o `id` correspondente.\n- **Métodos disparar e esquecer** (`notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`): emite um `extension_ui_request` em stdout, mas não espera uma resposta. O cliente pode exibir as informações ou ignorá-las.\n\nSe um método de diálogo incluir um campo `timeout`, o lado do agente resolverá automaticamente com um valor padrão quando o tempo limite expirar. O cliente não precisa rastrear tempos limite.\n\nAlguns métodos `ExtensionUIContext` não são suportados ou degradados no modo RPC porque requerem acesso direto TUI:\n- `custom()` retorna `undefined`\n- `setWorkingMessage()`, `setWorkingIndicator()`, `setFooter()`, `setHeader()`, `setEditorComponent()`, `setToolsExpanded()` são autônomos\n- `getEditorText()` retorna `\"\"`\n- `getToolsExpanded()` retorna `false`\n- `pasteToEditor()` delega para `setEditorText()` (sem manipulação de colar/recolher)\n- `getAllThemes()` retorna `[]`\n- `getTheme()` retorna `undefined`\n- `setTheme()` retorna `{ success: false, error: \"...\" }`\n\nNota: `ctx.mode` é `\"rpc\"` e `ctx.hasUI` é `true` no modo RPC porque os métodos de diálogo e disparar e esquecer são funcionais por meio do subprotocolo de extensão da UI. Use `ctx.mode === \"tui\"` para proteger recursos específicos de TUI, como `custom()`, que requerem um terminal real.\n\n### Solicitações de UI de extensão (stdout)\n\nTodas as solicitações possuem `type: \"extension_ui_request\"`, um campo `id` exclusivo e um campo `method`.\n\n#### selecione\n\nSolicita ao usuário que escolha em uma lista. Os métodos de diálogo com um campo `timeout` incluem o tempo limite em milissegundos; o agente resolve automaticamente com `undefined` se o cliente não responder a tempo.\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\nResposta esperada: `extension_ui_response` com `value` (a string de opção selecionada) ou `cancelled: true`.\n\n#### confirmar\n\nSolicita ao usuário uma confirmação sim/não.\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\nResposta esperada: `extension_ui_response` com `confirmed: true/false` ou `cancelled: true`.\n\n#### entrada\n\nSolicita ao usuário um texto de formato livre.\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\nResposta esperada: `extension_ui_response` com `value` (o texto inserido) ou `cancelled: true`.\n\n#### editor\n\nAbra um editor de texto multilinhas com conteúdo pré-preenchido opcional.\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\nResposta esperada: `extension_ui_response` com `value` (o texto editado) ou `cancelled: true`.\n\n#### notificar\n\nExibir uma notificação. Dispare e esqueça, nenhuma resposta é esperada.\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\nO campo `notifyType` é `\"info\"`, `\"warning\"` ou `\"error\"`. O padrão é `\"info\"` se omitido.\n\n#### definirStatus\n\nDefina ou desmarque uma entrada de status no rodapé/barra de status. Dispare e esqueça.\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\nEnvie `statusText: undefined` (ou omita) para limpar a entrada de status dessa chave.\n\n#### setWidget\n\nDefina ou desmarque um widget (bloco de linhas de texto) exibido acima ou abaixo do editor. Dispare e esqueça.\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\nEnvie `widgetLines: undefined` (ou omita) para limpar o widget. O campo `widgetPlacement` é `\"aboveEditor\"` (padrão) ou `\"belowEditor\"`. Apenas matrizes de string são suportadas no modo RPC; fábricas de componentes são ignoradas.\n\n#### definirTítulo\n\nDefina o título da janela/guia do terminal. Dispare e esqueça.\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\nDefina o texto no editor de entrada. Dispare e esqueça.\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### Respostas da UI de extensão (stdin)\n\nAs respostas são enviadas apenas para métodos de diálogo (`select`, `confirm`, `input`, `editor`). O `id` deve corresponder à solicitação.\n\n#### Resposta de valor (seleção, entrada, editor)\n\n```json\n{\"type\": \"extension_ui_response\", \"id\": \"uuid-1\", \"value\": \"Allow\"}\n```\n\n#### Resposta de confirmação (confirmar)\n\n```json\n{\"type\": \"extension_ui_response\", \"id\": \"uuid-2\", \"confirmed\": true}\n```\n\n#### Resposta de cancelamento (qualquer caixa de diálogo)\n\nIgnore qualquer método de diálogo. A extensão recebe `undefined` (para seleção/entrada/editor) ou `false` (para confirmação).\n\n```json\n{\"type\": \"extension_ui_response\", \"id\": \"uuid-3\", \"cancelled\": true}\n```\n\n## Tratamento de erros\n\nComandos com falha retornam uma resposta com `success: false`:\n\n```json\n{\n  \"type\": \"response\",\n  \"command\": \"set_model\",\n  \"success\": false,\n  \"error\": \"Model not found: invalid/model\"\n}\n```\n\nErros de análise:\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## Tipos\n\nArquivos de origem:\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 tipos de comando/resposta, tipos de solicitação/resposta da UI de extensão\n\n### Modelo\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### Mensagem do usuário\n\n```json\n{\n  \"role\": \"user\",\n  \"content\": \"Hello!\",\n  \"timestamp\": 1733234567890,\n  \"attachments\": []\n}\n```\n\nO campo `content` pode ser uma string ou um array de blocos `TextContent`/`ImageContent`.\n\n### Mensagem do assistente\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\nMotivos de parada: `\"stop\"`, `\"length\"`, `\"toolUse\"`, `\"error\"`, `\"aborted\"`\n\n### FerramentaResultMessage\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` é opcional e relata o trabalho LLM aninhado realizado pela ferramenta. Quando presente, contribui para o token da sessão e para os totais de custos.\n\n### BashExecutionMessage\n\nCriado pelo comando `bash` RPC (não por chamadas de ferramenta LLM):\n\n```json\n{\n  \"role\": \"bashExecution\",\n  \"command\": \"ls -la\",\n  \"output\": \"total 48\\ndrwxr-xr-x ...\",\n  \"exitCode\": 0,\n  \"cancelled\": false,\n  \"truncated\": false,\n  \"fullOutputPath\": null,\n  \"timestamp\": 1733234567890\n}\n```\n\n### Anexo\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## Exemplo: Cliente Básico (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## Exemplo: Cliente Interativo (Node.js)\n\nVeja [`test/rpc-example.ts`](../test/rpc-example.ts) para um exemplo interativo completo, ou [`src/modes/rpc/rpc-client.ts`](../src/modes/rpc/rpc-client.ts) para uma implementação de cliente digitada.\n\nPara obter um exemplo completo de como lidar com o protocolo UI de extensão, consulte [`examples/rpc-extension-ui.ts`](../examples/rpc-extension-ui.ts) que emparelha com a extensão [`examples/extensions/rpc-demo.ts`](../examples/extensions/rpc-demo.ts).\n\n```javascript\nconst { spawn } = require(\"child_process\");\nconst { StringDecoder } = require(\"string_decoder\");\n\nconst agent = spawn(\"pi\", [\"--mode\", \"rpc\", \"--no-session\"]);\n\nfunction attachJsonlReader(stream, onLine) {\n    const decoder = new StringDecoder(\"utf8\");\n    let buffer = \"\";\n\n    stream.on(\"data\", (chunk) => {\n        buffer += typeof chunk === \"string\" ? chunk : decoder.write(chunk);\n\n        while (true) {\n            const newlineIndex = buffer.indexOf(\"\\n\");\n            if (newlineIndex === -1) break;\n\n            let line = buffer.slice(0, newlineIndex);\n            buffer = buffer.slice(newlineIndex + 1);\n            if (line.endsWith(\"\\r\")) line = line.slice(0, -1);\n            onLine(line);\n        }\n    });\n\n    stream.on(\"end\", () => {\n        buffer += decoder.end();\n        if (buffer.length > 0) {\n            onLine(buffer.endsWith(\"\\r\") ? buffer.slice(0, -1) : buffer);\n        }\n    });\n}\n\nattachJsonlReader(agent.stdout, (line) => {\n    const event = JSON.parse(line);\n\n    if (event.type === \"message_update\") {\n        const { assistantMessageEvent } = event;\n        if (assistantMessageEvent.type === \"text_delta\") {\n            process.stdout.write(assistantMessageEvent.delta);\n        }\n    }\n});\n\n// Send prompt\nagent.stdin.write(JSON.stringify({ type: \"prompt\", message: \"Hello\" }) + \"\\n\");\n\n// Abort on Ctrl+C\nprocess.on(\"SIGINT\", () => {\n    agent.stdin.write(JSON.stringify({ type: \"abort\" }) + \"\\n\");\n});\n```","sourceFile":"rpc.md"},"sdk":{"title":"SDK","markdown":"> pi pode ajudá-lo a usar o SDK. Peça para criar uma integração para o seu caso de uso.\n\n\nO SDK fornece acesso programático aos recursos do agente pi. Use-o para incorporar pi em outros aplicativos, criar interfaces personalizadas ou integrar com fluxos de trabalho automatizados.\n\n**Exemplos de casos de uso:**\n- Crie uma UI personalizada (web, desktop, celular)\n- Integre recursos de agente em aplicativos existentes\n- Crie pipelines automatizados com raciocínio do agente\n- Crie ferramentas personalizadas que geram subagentes\n- Testar o comportamento do agente programaticamente\n\nVeja [examples/sdk/](../examples/sdk/) para exemplos de trabalho desde controle mínimo até controle total.\n\n## Início rápido\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## Instalação\n\n```bash\nnpm install @earendil-works/pi-coding-agent\n```\n\nO SDK está incluído no pacote principal. Não é necessária instalação separada.\n\n## Conceitos Básicos\n\n### createAgentSession()\n\nA principal função de fábrica para um único `AgentSession`.\n\n`createAgentSession()` usa `ResourceLoader` para fornecer extensões, habilidades, prompt templates, temas e context files. Se você não fornecer um, ele usará `DefaultResourceLoader` com descoberta padrão.\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### Sessão do Agente\n\nA sessão gerencia o ciclo de vida do agente, o histórico de mensagens, o estado do modelo, a compactação e o streaming de eventos.\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\nSubstituição de sessão APIs, como nova sessão, currículo, bifurcação e importação ao vivo em `AgentSessionRuntime`, não em `AgentSession`.\n\n### createAgentSessionRuntime() e AgentSessionRuntime\n\nUse o tempo de execução API quando precisar substituir a sessão ativa e reconstruir o estado do tempo de execução vinculado ao cwd.\nEsta é a mesma camada usada pelos modos interativo, de impressão e RPC integrados.\n\n`createAgentSessionRuntime()` leva uma fábrica de tempo de execução mais o destino inicial do cwd/sessão. A fábrica fecha as entradas fixas globais do processo, recria os serviços vinculados ao cwd para o cwd efetivo, resolve as opções de sessão nesses serviços e retorna um resultado de tempo de execução completo.\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` possui substituição do tempo de execução ativo em:\n\n- `newSession()`\n- `switchSession()`\n- `fork()`\n- clonar fluxos via `fork(entryId, { position: \"at\" })`\n- `importFromJsonl()`\n\nComportamento importante:\n\n- `runtime.session` alterações após essas operações\n- assinaturas de eventos são anexadas a um `AgentSession` específico, então inscreva-se novamente após a substituição\n- se você usa extensões, ligue `runtime.session.bindExtensions(...)` novamente para a nova sessão\n- criação retorna diagnóstico em `runtime.diagnostics`\n- se a criação ou substituição do tempo de execução falhar, o método será lançado e o chamador decidirá como lidar com isso\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### Solicitação e enfileiramento de mensagens\n\n`PromptOptions` controla a expansão de prompt, comportamento de fila durante a transmissão e notificações de simulação de prompt:\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` é chamado uma vez por invocação de `prompt()`:\n\n- `true` quando o prompt foi aceito, colocado na fila ou tratado imediatamente\n- `false` quando o comprovante imediato é rejeitado antes da aceitação\n\nEle é acionado antes de `prompt()` ser resolvido. `prompt()` ainda é resolvido somente após o término da execução completa aceita, incluindo novas tentativas. As falhas após a aceitação são relatadas através do evento normal e do fluxo de mensagens, não através de `preflightResult(false)`.\n\nO método `prompt()` lida com prompt templates, comandos de extensão e envio de mensagens:\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**Comportamento:**\n- **Comandos de extensão** (por exemplo, `/mycommand`): Execute imediatamente, mesmo durante o streaming. Eles gerenciam sua própria interação LLM via `pi.sendMessage()`.\n- **Baseado em arquivo prompt templates** (de arquivos `.md`): Expandido para seu conteúdo antes de enviar ou enfileirar.\n- **Durante streaming sem `streamingBehavior`**: Gera um erro. Use `steer()` ou `followUp()` diretamente ou especifique a opção.\n- **`preflightResult(true)`**: Significa que o prompt foi aceito, colocado na fila ou tratado imediatamente.\n- **`preflightResult(false)`**: Significa que o comprovante foi rejeitado antes da aceitação.\n\nPara enfileiramento explícito durante o streaming:\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\nAmbos `steer()` e `followUp()` expandem prompt templates baseado em arquivo, mas erro nos comandos de extensão (comandos de extensão não podem ser enfileirados).\n\n### Agente e AgentState\n\nA classe `Agent` (de `@earendil-works/pi-agent-core`) lida com a interação principal do LLM. Acesse-o via `session.agent`.\n\n```typescript\n// Access current state\nconst state = session.agent.state;\n\n// state.messages: AgentMessage[] - conversation history\n// state.model: Model - current model\n// state.thinkingLevel: ThinkingLevel - current thinking level\n// state.systemPrompt: string - system prompt\n// state.tools: AgentTool[] - available tools\n// state.streamingMessage?: AgentMessage - current partial assistant message\n// state.errorMessage?: string - latest assistant error\n\n// Replace messages (useful for branching or restoration)\nsession.agent.state.messages = messages; // copies the top-level array\n\n// Replace tools\nsession.agent.state.tools = tools; // copies the top-level array\n\n// Wait for agent to finish processing\nawait session.agent.waitForIdle();\n```\n\n### Eventos\n\nAssine eventos para receber resultados de streaming e notificações de ciclo de vida.\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## Referência de opções\n\n### Diretórios\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` é usado por `DefaultResourceLoader` para:\n- Extensões do projeto (`.pi/extensions/`)\n- Habilidades de projeto:\n  - `.pi/skills/`\n  - `.agents/skills/` em `cwd` e diretórios ancestrais (até git repo root ou filesystem root quando não estiver em um repo)\n- Solicitações do projeto (`.pi/prompts/`)\n- Arquivos de contexto (`AGENTS.md` subindo do cwd)\n- Nomenclatura do diretório de sessão\n\n`agentDir` é usado por `DefaultResourceLoader` para:\n- Extensões globais (`extensions/`)\n- Habilidades globais:\n  - `skills/` em `agentDir` (por exemplo `~/.pi/agent/skills/`)\n  - `~/.agents/skills/`\n- Solicitações globais (`prompts/`)\n- Arquivo de contexto global (`AGENTS.md`)\n- Configurações (`settings.json`)\n- Modelos personalizados (`models.json`)\n- Credenciais (`auth.json`)\n- Sessões (`sessions/`)\n\nQuando você passa um `ResourceLoader` personalizado, `cwd` e `agentDir` não controlam mais a descoberta de recursos. Eles ainda influenciam a nomenclatura da sessão e a resolução do caminho da ferramenta.\n\n### Modelo\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\nSe nenhum modelo for fornecido:\n1. Tenta restaurar da sessão (se continuar)\n2. Usa o padrão das configurações\n3. Volta ao primeiro modelo disponível\n\nPara corresponder à análise do modelo CLI, use os auxiliares do resolvedor exportados:\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()` usa todos os modelos registrados, portanto, a configuração inicial do estilo `--api-key` pode resolver um modelo antes que a autenticação armazenada exista. `resolveModelScopeWithDiagnostics()` corresponde à semântica `--models` e `enabledModels` enquanto retorna avisos em vez de imprimi-los.\n\n> Veja [examples/sdk/02-custom-model.ts](../examples/sdk/02-custom-model.ts)\n\n### API Chaves e OAuth\n\nPrioridade de resolução de autenticação (tratada por `ModelRuntime`):\n1. Substituições de tempo de execução (via `setRuntimeApiKey`, não persistentes)\n2. Credenciais armazenadas em `auth.json` (API keys ou OAuth tokens)\n3. Variáveis ​​de ambiente (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)\n4. Resolvedor substituto (para chaves de provedor personalizadas de `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()` e `removeRuntimeApiKey()` são resolvidos depois que o catálogo em cache/integrado, a composição e o instantâneo de disponibilidade do provedor afetado são localmente consistentes. Eles não esperam pela atualização remota do catálogo. Se as credenciais foram confirmadas, mas a sincronização local falhar, elas serão rejeitadas com o `CredentialSynchronizationError` exportado; inspecione seus campos `providerId`, `operation`, `credential` e `cause` em vez de tentar novamente a mutação da credencial às cegas.\n\nAs operações de modelo público/autenticação e `ModelRuntime.create({ signal })` aceitam sinais de interrupção opcionais e são ilimitadas quando omitidas. SDK os aplicativos possuem política de prazo para atualização remota do catálogo:\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\nUma atualização de rede com falha ou expirado não desfaz uma operação de credencial bem-sucedida. `refresh()` inicia uma nova geração de provedor, portanto, ele não espera por uma atualização antiga e paralisada e as gerações obsoletas não podem publicar depois.\n\n> Veja [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)\n\n### Alerta do sistema\n\nUse um `ResourceLoader` para substituir o prompt do sistema:\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> Veja [examples/sdk/03-custom-prompt.ts](../examples/sdk/03-custom-prompt.ts)\n\n### Ferramentas\n\nEspecifique quais ferramentas integradas ativar:\n\n- Nomes de ferramentas integradas: `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`\n- Integrados padrão: `read`, `bash`, `edit`, `write`\n- `noTools: \"all\"` desativa todas as ferramentas\n- `noTools: \"builtin\"` desativa os recursos integrados padrão, mantendo as extensões e as ferramentas personalizadas ativadas\n- `excludeTools` desativa nomes específicos de ferramentas integradas, de extensão ou personalizadas após qualquer lista de permissões `tools` ser aplicada\n\nA ferramenta `edit` retorna `details.diff` para a exibição TUI de Pi e `details.patch` como um patch unificado padrão para consumidores SDK.\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#### Ferramentas com cwd personalizado\n\nQuando você passa um `cwd` personalizado, `createAgentSession()` cria ferramentas integradas selecionadas para esse 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> Veja [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)\n\n### Ferramentas personalizadas\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\nUse `defineTool()` para definições independentes e matrizes como `customTools: [myTool]`. Inline `pi.registerTool({... })` já infere os tipos de parâmetros corretamente.\n\nFerramentas personalizadas passadas por `customTools` são combinadas com ferramentas registradas em extensão. Extensions carregado pelo ResourceLoader também pode registrar ferramentas via `pi.registerTool()`.\n\nSe você passar `tools`, inclua cada nome de ferramenta personalizada ou de extensão que deseja ativar, por exemplo `tools: [\"read\", \"bash\", \"my_tool\"]`.\n\n> Veja [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)\n\n### Extensions\n\nExtensions são carregados pelo `ResourceLoader`. `DefaultResourceLoader` descobre extensões das fontes de extensão `~/.pi/agent/extensions/`, `.pi/extensions/` e 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 pode registrar ferramentas, assinar eventos, adicionar comandos e muito mais. Veja [extensions.md](extensions.md) para o API completo.\n\n**Extensões inline nomeadas:** Por padrão, as fábricas inline são exibidas como `<inline:1>`, `<inline:2>`, etc. na lista de inicialização Extensions. Para mostrar um nome descritivo, envolva a fábrica:\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\nIsso é exibido como `<inline:my-provider>` em vez de `<inline:1>`. Funções básicas de fábrica ainda são aceitas para compatibilidade com versões anteriores.\n\n**Barramento de Evento:** Extensions pode se comunicar via `pi.events`. Passe um `eventBus` para `DefaultResourceLoader` compartilhado se precisar emitir ou ouvir de fora:\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> Veja [examples/sdk/06-extensions.ts](../examples/sdk/06-extensions.ts) e [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> Veja [examples/sdk/04-skills.ts](../examples/sdk/04-skills.ts)\n\n### Arquivos de Contexto\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> Veja [examples/sdk/07-context-files.ts](../examples/sdk/07-context-files.ts)\n\n### Comandos de barra\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> Veja [examples/sdk/08-prompt-templates.ts](../examples/sdk/08-prompt-templates.ts)\n\n### Gerenciamento de sessão\n\nAs sessões usam uma estrutura em árvore com vinculação `id`/`parentId`, permitindo ramificação no local.\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**Árvore do SessionManager API:**\n\n```typescript\nconst sm = SessionManager.open(\"/path/to/session.jsonl\");\n\n// Session listing\nconst currentProjectSessions = await SessionManager.list(process.cwd());\nconst allSessions = await SessionManager.listAll(process.cwd());\n\n// Tree traversal\nconst entries = sm.getEntries();        // All entries (excludes header)\nconst tree = sm.getTree();              // Full tree structure\nconst path = sm.getPath();              // Path from root to current leaf\nconst leaf = sm.getLeafEntry();         // Current leaf entry\nconst entry = sm.getEntry(id);          // Get entry by ID\nconst children = sm.getChildren(id);    // Direct children of entry\n\n// Labels\nconst label = sm.getLabel(id);          // Get label for entry\nsm.appendLabelChange(id, \"checkpoint\"); // Set label\n\n// Branching\nsm.branch(entryId);                     // Move leaf to earlier entry\nsm.branchWithSummary(id, \"Summary...\");  // Branch with context summary\nsm.createBranchedSession(leafId);       // Extract path to new file\n```\n\n> Veja [examples/sdk/11-sessions.ts](../examples/sdk/11-sessions.ts) e [Session Format](session-format.md)\n\n### Gerenciamento de configurações\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**Fábricas estáticas:**\n- `SettingsManager.create(cwd?, agentDir?)` - Carregar de arquivos\n- `SettingsManager.inMemory(settings?)` - Sem E/S de arquivo\n\n**Configurações específicas do projeto:**\n\nAs configurações são carregadas de dois locais e mescladas:\n1. Globais: `~/.pi/agent/settings.json`\n2. Projeto: `<cwd>/.pi/settings.json`\n\nO projeto substitui global. Objetos aninhados mesclam chaves. Os setters modificam as configurações globais por padrão.\n\n**Semântica de persistência e tratamento de erros:**\n\n- Os getters/setters de configurações são síncronos para o estado na memória.\n- Os setters enfileiram gravações de persistência de forma assíncrona.\n- Chame `await settingsManager.flush()` quando precisar de um limite de durabilidade (por exemplo, antes da saída do processo ou antes de declarar o conteúdo do arquivo em testes).\n- `SettingsManager` não imprime erros de E/S de configurações. Use `settingsManager.drainErrors()` e relate-os na camada do seu aplicativo.\n\n> Veja [examples/sdk/10-settings.ts](../examples/sdk/10-settings.ts)\n\n## Carregador de recursos\n\nUse `DefaultResourceLoader` para descobrir extensões, habilidades, prompts, temas e context files.\n\n```typescript\nimport {\n  DefaultResourceLoader,\n  getAgentDir,\n} from \"@earendil-works/pi-coding-agent\";\n\nconst loader = new DefaultResourceLoader({\n  cwd,\n  agentDir: getAgentDir(),\n});\nawait loader.reload();\n\nconst extensions = loader.getExtensions();\nconst skills = loader.getSkills();\nconst prompts = loader.getPrompts();\nconst themes = loader.getThemes();\nconst contextFiles = loader.getAgentsFiles().agentsFiles;\n```\n\n## Valor de retorno\n\n`createAgentSession()` retorna:\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## Exemplo completo\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## Modos de execução\n\nO SDK exporta utilitários em modo de execução para construir interfaces personalizadas sobre `createAgentSession()`:\n\n### Modo interativo\n\nModo interativo TUI completo com editor, histórico de bate-papo e todos os comandos integrados:\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### executarPrintMode\n\nModo de disparo único: enviar prompts, resultado de saída, sair:\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\nModo JSON-RPC para integração de subprocessos:\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\nConsulte [RPC documentation](rpc.md) para o protocolo JSON.\n\n## RPC Alternativa de modo\n\nPara integração baseada em subprocessos sem construir com SDK, use CLI diretamente:\n\n```bash\npi --mode rpc --no-session\n```\n\nConsulte [RPC documentation](rpc.md) para o protocolo JSON.\n\nO SDK é preferido quando:\n- Você quer segurança de tipo\n- Você está no mesmo processo Node.js\n- Você precisa de acesso direto ao estado do agente\n- Você deseja personalizar ferramentas/extensões programaticamente\n\nO modo RPC é preferido quando:\n- Você está integrando de outro idioma\n- Você quer isolamento de processos\n- Você está construindo um cliente independente de idioma\n\n## Exportações\n\nO principal ponto de entrada das exportações:\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\nPara tipos de extensão, consulte [extensions.md](extensions.md) para o API completo.","sourceFile":"sdk.md"},"security":{"title":"Segurança","markdown":"Pi é um agente de codificação local. Ele é executado com as permissões da conta de usuário que o inicia e trata os arquivos graváveis ​​por esse usuário como dentro do mesmo limite de confiança local.\n\n## Confiança do Projeto\n\nA confiança do projeto controla se pi carrega configurações, recursos, pacotes e extensões locais do projeto. Não é um sandbox e não restringe o que o modelo pode solicitar às ferramentas depois que você começa a trabalhar em um diretório.\n\nPi considera que um projeto possui recursos que exigem confiança quando encontra qualquer um destes no diretório de trabalho atual:\n\n- `.pi/settings.json`\n- `.pi/extensions`, `.pi/skills`, `.pi/prompts` ou `.pi/themes`\n- `.pi/SYSTEM.md` ou `.pi/APPEND_SYSTEM.md`\n- projeto `.agents/skills` no diretório atual ou em um diretório ancestral\n\nUm diretório `.pi` vazio não conta como um recurso de projeto que requer confiança.\n\nQuando uma sessão interativa é iniciada em um projeto com recursos que exigem confiança e nenhuma decisão salva para o diretório atual ou diretório pai, pi segue `defaultProjectTrust` das configurações globais. O valor padrão é `\"ask\"`, que pergunta se o projeto deve ser confiável quando a UI estiver disponível. As decisões salvas são armazenadas pelo diretório canônico em `~/.pi/agent/trust.json`, e a decisão salva mais próxima no caminho atual ou pai se aplica antes do padrão global.\n\nConfiar em um projeto permite que pi carregue recursos do projeto que exigem confiança, incluindo:\n\n- `.pi/settings.json`\n- `.pi` recursos como extensões, habilidades, prompt templates, temas e arquivos de prompt do sistema\n- pacotes de projeto ausentes configurados através das configurações do projeto\n- extensões locais do projeto e extensões gerenciadas pelo pacote do projeto\n\nA diminuição da confiança ignora recursos protegidos. Arquivos de contexto como `AGENTS.override.md`, `AGENTS.md` e `CLAUDE.md` são carregados independentemente da confiança do projeto, a menos que o carregamento de contexto esteja desabilitado. Antes que a confiança seja resolvida, pi carrega apenas extensões context files, de usuário/globais e extensões CLI `-e`. As extensões User/global e CLI podem lidar com o evento `project_trust`; a primeira extensão que retornar uma decisão sim/não possui a decisão.\n\nOs modos não interativos (`-p`, `--mode json` e `--mode rpc`) não mostram um prompt de confiança. Sem uma decisão de confiança salva aplicável, `defaultProjectTrust: \"ask\"` e `\"never\"` ignoram tais recursos, enquanto `\"always\"` confia neles. Use `--approve`/`-a` ou `--no-approve`/`-na` para substituir a confiança do projeto em uma execução.\n\n## Sem sandbox integrado\n\nPi não inclui um sandbox integrado. Ferramentas integradas podem ler arquivos, gravar arquivos, editar arquivos e executar comandos shell com as permissões do processo pi. Extensions são módulos TypeScript que rodam com as mesmas permissões. Instalações de pacotes, comandos shell, servidores de linguagem, comandos de teste e outras ferramentas de desenvolvedor se comportam como processos locais comuns.\n\nIsso é intencional. Pi foi projetado para operar em árvores de origem locais, invocar cadeias de ferramentas do projeto e integrar-se ao ambiente de desenvolvimento existente do usuário. Um processo parcial sandbox seria fácil de ser mal interpretado como um limite de segurança, embora ainda dependa do shell do host, do sistema de arquivos, dos gerenciadores de pacotes, das credenciais e do código de extensão. O isolamento real precisa vir do sistema operacional ou de um limite de virtualização/contêiner.\n\nA confiança do projeto é apenas uma proteção para o carregamento de entradas. Ele evita que um repositório altere silenciosamente as configurações ou extensões do pi antes de você aprová-lo. Ele não torna seguro o código não confiável, os prompts não confiáveis ​​ou a saída do modelo não confiável. A injeção imediata de arquivos de repositório, comentários, documentação, context files ou saída de compilação é um risco esperado do agente local e não pode ser evitado de forma confiável pelo pi.\n\n## Executando trabalho não confiável ou não monitorado\n\nPara repositórios não confiáveis, código gerado que você não pretende monitorar de perto ou automação autônoma, execute pi em um ambiente contido. Use um contêiner, VM, micro-VM, sandbox remoto ou sandbox controlado por política apenas com os arquivos e credenciais necessários para a tarefa.\n\nPadrões comuns estão documentados em [Containerization](containerization.md):\n\n- execute todo o processo `pi` dentro de um contêiner/sandbox\n- execute o host pi enquanto roteia a execução da ferramenta integrada para uma micro-VM Gondolin\n- monte apenas os caminhos do espaço de trabalho que o agente deve acessar\n- evite montar o host `~/.pi/agent`, a menos que o contêiner deva acessar sessões, configurações e credenciais do host\n- passe nos API keys mínimos exigidos ou use credenciais de curta duração\n- restringir o acesso à rede quando a tarefa não precisar dele\n- revise diferenças e resultados antes de copiar os resultados de volta para sistemas confiáveis\n\nSe você montar uma leitura/gravação de espaço de trabalho do host, as gravações de dentro do contêiner ou da VM ainda poderão modificar os arquivos do host. Use montagens somente leitura ou copie arquivos dentro e fora do sandbox quando precisar de proteção mais forte contra gravações não intencionais.\n\n## Relatando problemas de segurança\n\nPara relatar um problema de segurança, siga o repositório [Security Policy](https://github.com/earendil-works/pi-mono/blob/main/SECURITY.md). Não abra um problema público para relatórios sensíveis à segurança.\n\nO comportamento esperado do agente local, a falta de um sandbox integrado, a injeção imediata de conteúdo não confiável e o comportamento de extensões ou habilidades instaladas pelo usuário geralmente estão fora dos limites de segurança, a menos que o relatório demonstre um desvio real do limite de privilégios ou mostre como pi concede acesso que o usuário local ainda não tinha.","sourceFile":"security.md"},"session-format":{"title":"Formato de arquivo de sessão","markdown":"As sessões são armazenadas como arquivos JSONL (JSON Linhas). Cada linha é um objeto JSON com um campo `type`. As entradas de sessão formam uma estrutura em árvore por meio dos campos `id`/`parentId`, permitindo ramificações no local sem criar novos arquivos.\n\n## Localização do arquivo\n\n```\n~/.pi/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl\n```\n\nOnde `<path>` é o diretório de trabalho com `/` substituído por `-`.\n\n## Excluindo Sessões\n\nAs sessões podem ser removidas excluindo seus arquivos `.jsonl` em `~/.pi/agent/sessions/`.\n\nPi também suporta a exclusão interativa de sessões de `/resume` (selecione uma sessão e pressione `Ctrl+D` e confirme). Quando disponível, pi usa `trash` CLI para evitar exclusão permanente.\n\n## Versão da sessão\n\nAs sessões têm um campo de versão no cabeçalho:\n\n- **Versão 1**: sequência de entrada linear (herdada, migrada automaticamente durante o carregamento)\n- **Versão 2**: Estrutura em árvore com ligação `id`/`parentId`\n- **Versão 3**: Função `hookMessage` renomeada para `custom` (unificação de extensões)\n\nAs sessões existentes são migradas automaticamente para a versão atual (v3) quando carregadas.\n\n## Arquivos de origem\n\nFonte em 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) - Tipos de entrada de sessão e 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) - Tipos de mensagens estendidas (BashExecutionMessage, CustomMessage, etc.)\n- [`packages/ai/src/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/types.ts) - Tipos de mensagens base (UserMessage, AssistantMessage, ToolResultMessage)\n- [`packages/agent/src/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/agent/src/types.ts) - Tipo de união AgentMessage\n\nPara definições de TypeScript em seu projeto, inspecione `node_modules/@earendil-works/pi-coding-agent/dist/` e `node_modules/@earendil-works/pi-ai/dist/`.\n\n## Tipos de mensagens\n\nAs entradas de sessão contêm objetos `AgentMessage`. Compreender esses tipos é essencial para analisar sessões e escrever extensões.\n\n### Blocos de conteúdo\n\nAs mensagens contêm matrizes de blocos de conteúdo digitados:\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### Tipos básicos de mensagens (de 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\nO tipo pi-ai `StopReason` exportado também inclui `\"pending\"`, mas esse valor é reservado para mensagens parciais em eventos de streaming. As mensagens `done`/`error` do terminal substituem-no por um motivo de conclusão antes que pi persista a mensagem do assistente, então `\"pending\"` nunca deve aparecer na sessão JSONL.\n\n### Tipos de mensagens estendidas (do 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### União de mensagem do agente\n\n```typescript\ntype AgentMessage =\n  | UserMessage\n  | AssistantMessage\n  | ToolResultMessage\n  | BashExecutionMessage\n  | CustomMessage\n  | BranchSummaryMessage\n  | CompactionSummaryMessage;\n```\n\n## Base de Entrada\n\nTodas as entradas (exceto `SessionHeader`) estendem `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## Tipos de entrada\n\n### Cabeçalho da Sessão\n\nPrimeira linha do arquivo. Apenas metadados, não fazem parte da árvore (não `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\nPara sessões com um pai (criadas via `/fork`, `/clone` ou `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### SessãoMessageEntry\n\nUma mensagem na conversa. O campo `message` contém um `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\nEmitido quando o usuário troca de modelo no meio da sessão.\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\nEmitido quando o usuário altera o nível de pensamento/raciocínio.\n\n```json\n{\"type\":\"thinking_level_change\",\"id\":\"e5f6g7h8\",\"parentId\":\"d4e5f6g7\",\"timestamp\":\"2024-12-03T14:06:00.000Z\",\"thinkingLevel\":\"high\"}\n```\n\n### Entrada de compactação\n\nCriado quando o contexto é compactado. Armazena um resumo de mensagens anteriores.\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\nAs compactações geradas por chicotes mais recentes incorporam o contexto pós-compactação retido diretamente na entrada, em vez de `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\nCampos opcionais:\n- `usage`: uso do LLM a partir da geração do resumo; incluído no token de sessão e nos totais de custo\n- `retainedTail`: Materializado `AgentMessage[]` mantido após compactação. Isto é opcional apenas para compatibilidade retroativa com sessões mais antigas. As compactações geradas por chicotes mais recentes incluem-no para que possamos reconstruir o contexto a partir deste ponto de verificação sem percorrer entradas mais antigas antes da entrada de compactação.\n- `details`: Dados específicos da implementação (por exemplo, `{ readFiles: string[], modifiedFiles: string[] }` para padrão ou dados personalizados para extensões)\n- `fromHook`: `true` se gerado por uma extensão, `false`/`undefined` se gerado por pi (nome do campo legado)\n- `firstKeptEntryId`: para compatibilidade com formato de entrada antigo.\n\n### FilialSummaryEntry\n\nCriado ao alternar ramificações via `/tree` com um resumo gerado por LLM da ramificação esquerda até o ancestral comum. Captura o contexto do caminho abandonado.\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\nCampos opcionais:\n- `usage`: uso do LLM a partir da geração do resumo; incluído no token de sessão e nos totais de custo\n- `details`: Dados de rastreamento de arquivo (`{ readFiles: string[], modifiedFiles: string[] }`) para padrão ou dados personalizados para extensões\n- `fromHook`: `true` se gerado por uma extensão, `false`/`undefined` se gerado por pi (nome do campo legado)\n\n### Entrada personalizada\n\nPersistência do estado de extensão. NÃO participa do contexto LLM.\n\n```json\n{\"type\":\"custom\",\"id\":\"h8i9j0k1\",\"parentId\":\"g7h8i9j0\",\"timestamp\":\"2024-12-03T14:20:00.000Z\",\"customType\":\"my-extension\",\"data\":{\"count\":42}}\n```\n\nUse `customType` para identificar as entradas da sua extensão ao recarregar. O modo interativo pode renderizar entradas personalizadas via `pi.registerEntryRenderer(customType, renderer)`, mas elas ainda não participam do contexto LLM.\n\n### Entrada de mensagem personalizada\n\nMensagens injetadas por extensão que participam do contexto LLM.\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\nCampos:\n- `content`: String ou `(TextContent | ImageContent)[]` (igual a UserMessage)\n- `display`: `true` = mostrar em TUI com estilo distinto, `false` = oculto\n- `details`: Metadados opcionais específicos da extensão (não enviados para LLM)\n\n### LabelEntry\n\nMarcador/marcador definido pelo usuário em uma entrada.\n\n```json\n{\"type\":\"label\",\"id\":\"j0k1l2m3\",\"parentId\":\"i9j0k1l2\",\"timestamp\":\"2024-12-03T14:30:00.000Z\",\"targetId\":\"a1b2c3d4\",\"label\":\"checkpoint-1\"}\n```\n\nDefina `label` como `undefined` para limpar um rótulo.\n\n### SessãoInfoEntry\n\nMetadados da sessão (por exemplo, nome de exibição definido pelo usuário). Defina via `/name`, `--name` / `-n` ou `pi.setSessionName()` nas extensões.\n\n```json\n{\"type\":\"session_info\",\"id\":\"k1l2m3n4\",\"parentId\":\"j0k1l2m3\",\"timestamp\":\"2024-12-03T14:35:00.000Z\",\"name\":\"Refactor auth module\"}\n```\n\nO nome da sessão é exibido no seletor de sessão (`/resume`) em vez da primeira mensagem quando definido.\n\n## Estrutura da árvore\n\nAs entradas formam uma árvore:\n- A primeira entrada tem `parentId: null`\n- Cada entrada subsequente aponta para seu pai via `parentId`\n- A ramificação cria novos filhos a partir de uma entrada anterior\n- A \"folha\" é a posição atual na árvore\n\n```\n[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf\n                                                            │\n                                                            └─ [branch_summary] ─── [user msg] ← alternate branch\n```\n\n## Construção de Contexto\n\n`buildContextEntries()` caminha da folha atual até a raiz, produzindo a lista de entradas ativas enquanto respeita a compactação:\n\n1. Coleta todas as entradas no caminho\n2. Se um `CompactionEntry` estiver no caminho:\n   - Inclui a entrada de compactação primeiro\n   - Se `retainedTail` estiver presente, ele atua como um ponto de verificação independente e as entradas após a compactação são incluídas\n   - Caso contrário, as entradas de `firstKeptEntryId` para a compactação serão incluídas\n   - Então as entradas após a compactação são incluídas\n3. Preserva entradas que não são de mensagem no intervalo selecionado para que o modo interativo possa renderizá-las\n\n`buildSessionContext()` baseia-se nessa lista de entradas para produzir a lista de mensagens para o LLM:\n\n1. Extrai o modelo atual e as configurações de nível de pensamento do caminho completo\n2. Converte entradas selecionadas em mensagens:\n   - `message` -> armazenado `AgentMessage`\n   - `compaction` -> `compactionSummary` mais `retainedTail` quando presente\n   - `branch_summary` -> `branchSummary`\n   - `custom_message` -> `CustomMessage`\n   - `custom` -> nenhuma mensagem de contexto\n\nIsso faz com que as compactações mais recentes atuem como pontos de verificação independentes. `retainedTail` é opcional apenas para que sessões mais antigas que armazenam apenas `firstKeptEntryId` continuem a carregar corretamente.\n\n## Exemplo de análise\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## Gerenciador de Sessão API\n\nPrincipais métodos para trabalhar com sessões programaticamente.\n\n### Métodos de criação estática\n- `SessionManager.create(cwd, sessionDir?)` - Nova sessão\n- `SessionManager.open(path, sessionDir?)` - Abra o arquivo de sessão existente\n- `SessionManager.continueRecent(cwd, sessionDir?)` - Continue o mais recente ou crie um novo\n- `SessionManager.inMemory(cwd?)` - Sem persistência de arquivo\n- `SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?)` - Sessão bifurcada de outro projeto\n\n### Métodos de listagem estática\n- `SessionManager.list(cwd, sessionDir?, onProgress?)` - Lista sessões para um diretório\n- `SessionManager.listAll(onProgress?)` - Lista todas as sessões em todos os projetos\n\n### Métodos de Instância - Gerenciamento de Sessão\n- `newSession(options?)` - Iniciar uma nova sessão (opções: `{ parentSession?: string }`)\n- `setSessionFile(path)` - Mudar para um arquivo de sessão diferente\n- `createBranchedSession(leafId)` - Extraia branch para novo arquivo de sessão\n\n### Métodos de instância - Anexando (todos os IDs de entrada de retorno)\n- `appendMessage(message)` - Adicionar mensagem\n- `appendThinkingLevelChange(level)` - Registrar mudança de pensamento\n- `appendModelChange(provider, modelId)` - Registrar mudança de modelo\n- `appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?)` - Adicionar compactação\n- `appendCustomEntry(customType, data?)` - Estado da extensão (fora do contexto)\n- `appendSessionInfo(name)` - Definir nome de exibição da sessão\n- `appendCustomMessageEntry(customType, content, display, details?)` - Mensagem de extensão (no contexto)\n- `appendLabelChange(targetId, label)` - Definir/limpar rótulo\n\n### Métodos de Instância - Navegação em Árvore\n- `getLeafId()` - Posição atual\n- `getLeafEntry()` - Obtenha a entrada atual da folha\n- `getEntry(id)` - Obtenha entrada por ID\n- `getBranch(fromId?)` - Caminhe da entrada até a raiz\n- `getTree()` - Obtenha estrutura de árvore completa\n- `getChildren(parentId)` - Obtenha filhos diretos\n- `getLabel(id)` - Obtenha etiqueta para entrada\n- `branch(entryId)` - Mover folha para entrada anterior\n- `resetLeaf()` - Redefinir folha para nulo (antes de qualquer entrada)\n- `branchWithSummary(entryId, summary, details?, fromHook?)` - Ramificação com resumo de contexto\n\n### Métodos de Instância - Contexto e Informações\n- `buildContextEntries()` - Obtenha entradas de ramificação ativas com compactação aplicada\n- `buildSessionContext()` - Obtenha mensagens, nível de pensamento e modelo para LLM\n- `getEntries()` - Todas as entradas (excluindo cabeçalho)\n- `getHeader()` - Metadados do cabeçalho da sessão\n- `getSessionName()` - Obtenha o nome de exibição da última entrada session_info\n- `getCwd()` - Diretório de trabalho\n- `getSessionDir()` - Diretório de armazenamento de sessão\n- `getSessionId()` - UUID da sessão\n- `getSessionFile()` - Caminho do arquivo da sessão (indefinido para memória)\n- `isPersisted()` - Se a sessão é salva no disco","sourceFile":"session-format.md"},"sessions":{"title":"Sessões","markdown":"Pi salva conversas como sessões para que você possa continuar o trabalho, ramificar-se de turnos anteriores e revisitar caminhos anteriores.\n\n## Armazenamento de sessão\n\nAs sessões são salvas automaticamente em `~/.pi/agent/sessions/`, organizadas por diretório de trabalho. Cada sessão é um arquivo JSONL com uma estrutura em árvore.\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\nUse `/session` no modo interativo para ver o arquivo da sessão atual, ID da sessão, contagem de mensagens, tokens e custo.\n\nPara o formato de arquivo JSONL e SessionManager API, consulte [Session Format](session-format.md).\n\n## Comandos de sessão\n\n| Comando | Descrição |\n|---------|-------------|\n| `/resume` | Navegue e selecione sessões anteriores |\n| `/new` | Iniciar uma nova sessão |\n| `/name <name>` | Defina o nome de exibição da sessão atual |\n| `/session` | Mostrar informações da sessão |\n| `/tree` | Navegue pelo session tree atual |\n| `/fork` | Crie uma nova sessão a partir de uma mensagem de usuário anterior |\n| `/clone` | Duplicar o branch ativo atual em uma nova sessão |\n| `/compact [prompt]` | Resuma o contexto mais antigo; veja [Compaction](compaction.md) |\n| `/export [file]` | Exportar sessão para HTML |\n| `/share` | Carregar como essência GitHub privada com link HTML compartilhável |\n\n## Retomando e excluindo sessões\n\n`/resume` abre um seletor de sessão interativo para o projeto atual. `pi -r` abre o mesmo seletor na inicialização.\n\nNo seletor você pode:\n\n- pesquise digitando\n- alternar exibição do caminho com Ctrl+P\n- alterne o modo de classificação com Ctrl+S\n- filtrar para sessões nomeadas com Ctrl+N\n- renomear com Ctrl+R\n- exclua com Ctrl+D e confirme\n\nQuando disponível, pi usa `trash` CLI para exclusão em vez de remover arquivos permanentemente.\n\n## Nomeando Sessões\n\nUse `/name <name>` para definir um nome de sessão legível:\n\n```text\n/name Refactor auth module\n```\n\nDefina o nome na inicialização com `--name` ou `-n`:\n\n```bash\npi --name \"Refactor auth module\"\npi --name \"CI audit\" -p \"Review this build failure\"\n```\n\nSessões nomeadas são mais fáceis de encontrar em `/resume` e `pi -r`.\n\n## Ramificação com `/tree`\n\nAs sessões são armazenadas como árvores. Cada entrada possui `id` e `parentId`, e a posição atual é a folha ativa. `/tree` permite pular para qualquer ponto anterior e continuar a partir daí sem criar um novo arquivo.\n\n<p align=\"center\"><img src=\"images/tree-view.png\" alt=\"Tree View\" width=\"600\"></p>\n\nForma de exemplo:\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### Controles de árvore\n\n| Chave | Ação |\n|-----|--------|\n| ↑/↓ | Navegue pelas entradas visíveis |\n| ←/→ | Página para cima/para baixo |\n| Ctrl+←/Ctrl+→ ou Alt+←/Alt+→ | Dobre/desdobre ou salte entre segmentos de ramificação |\n| Shift+L | Definir ou limpar um rótulo na entrada selecionada |\n| Mudança + T | Alternar carimbos de data/hora do rótulo |\n| Digitar | Selecione a entrada |\n| Escapar/Ctrl+C | Cancelar |\n| Ctrl+O | Modo de filtro de ciclo |\n\nOs modos de filtro são: padrão, sem ferramentas, somente usuário, somente rotulado e todos. Configure o padrão com `treeFilterMode` em [Settings](settings.md).\n\n### Comportamento de seleção\n\nSelecionando um usuário ou mensagem personalizada:\n\n1. Move a folha para o pai da mensagem selecionada.\n2. Coloca o texto da mensagem selecionado no editor.\n3. Permite editar e reenviar, criando um novo branch.\n\nSelecionando um assistente, ferramenta, compactação ou outra entrada que não seja do usuário:\n\n1. Move a folha para essa entrada.\n2. Deixa o editor vazio.\n3. Permite que você continue a partir desse ponto.\n\nSelecionar a mensagem do usuário root redefine a folha para uma conversa vazia e coloca o prompt original no editor.\n\n## `/tree`, `/fork` e `/clone`\n\n| Recurso | `/tree` | `/fork` | `/clone` |\n|---------|---------|---------|----------|\n| Saída | Mesmo arquivo de sessão | Novo arquivo de sessão | Novo arquivo de sessão |\n| Visualizar | Árvore completa | Seletor de mensagens do usuário | Filial ativa atual |\n| Uso típico | Explorar alternativas existentes | Iniciar uma nova sessão a partir de um prompt anterior | Duplique o trabalho atual antes de continuar |\n| Resumo | Resumo da filial opcional | Nenhum | Nenhum |\n\nUse `/tree` quando quiser manter as alternativas juntas. Use `/fork` ou `/clone` quando desejar um arquivo de sessão separado.\n\n## Resumos de filiais\n\nQuando `/tree` muda de um ramo para outro, pi pode resumir o ramo abandonado e anexar esse resumo na nova posição. Isso preserva o contexto importante do caminho que você deixou sem repetir todo o branch.\n\nQuando solicitado, escolha um dos seguintes:\n\n1. sem resumo\n2. resumir com o prompt padrão\n3. resumir com instruções de foco personalizadas\n\nVeja [Compaction](compaction.md) para branch summarization internos e ganchos de extensão.\n\n## Formato de sessão\n\nOs arquivos de sessão são JSONL e contêm entradas de mensagens, alterações de modelo, alterações de nível de pensamento, rótulos, compactações, resumos de ramificação e entradas de extensão.\n\nPara analisadores, extensões, uso de SDK e o SessionManager API completo, consulte [Session Format](session-format.md).","sourceFile":"sessions.md"},"settings":{"title":"Configurações","markdown":"Pi usa arquivos de configurações JSON com configurações de projeto substituindo configurações globais.\n\n| Localização | Escopo |\n|----------|-------|\n| `~/.pi/agent/settings.json` | Global (todos os projetos) |\n| `.pi/settings.json` | Projeto (diretório atual) |\n\nEdite diretamente ou use `/settings` para opções comuns.\n\n## Confiança do Projeto\n\nNa inicialização interativa, pi pergunta antes de confiar em uma pasta de projeto que contém configurações locais do projeto, recursos ou projeto `.agents/skills` e não tem decisão salva para a pasta ou pasta pai em `~/.pi/agent/trust.json`. Confiar em um projeto permite que pi carregue recursos `.pi/settings.json` e `.pi`, instale pacotes de projeto ausentes e execute extensões de projeto.\n\nOs modos não interativos (`-p`, `--mode json` e `--mode rpc`) não mostram um prompt de confiança. Sem uma decisão de confiança salva aplicável, eles usam `defaultProjectTrust` das configurações globais: `ask` (padrão) e `never` ignoram esses recursos do projeto, enquanto `always` confia neles. Passe `--approve`/`-a` ou `--no-approve`/`-na` para substituir a confiança do projeto em uma execução.\n\nSe nenhuma extensão ou decisão salva se aplicar, `defaultProjectTrust` controla o comportamento de fallback. Defina-o como `\"ask\"`, `\"always\"` ou `\"never\"` em `~/.pi/agent/settings.json` ou altere-o com `/settings`.\n\nOs comandos `pi config` e pacote usam o mesmo fluxo de confiança do projeto, exceto que `pi update` nunca solicita. Passe `--approve` para confiar nas configurações locais do projeto para um comando ou `--no-approve` para ignorá-las.\n\nUse `/trust` no modo interativo para salvar uma decisão de confiança do projeto para sessões futuras, incluindo confiança para a pasta pai imediata. Ele escreve apenas `~/.pi/agent/trust.json`; a sessão atual não é recarregada, então reinicie o pi para que as alterações tenham efeito.\n\n## Todas as configurações\n\n### Modelo e Pensamento\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `defaultProvider` | corda | - | Provedor padrão (por exemplo, `\"anthropic\"`, `\"openai\"`) |\n| `defaultModel` | corda | - | ID do modelo padrão |\n| `defaultThinkingLevel` | corda | - | `\"off\"`, `\"minimal\"`, `\"low\"`, `\"medium\"`, `\"high\"`, `\"xhigh\"`, `\"max\"` |\n| `hideThinkingBlock` | booleano | `false` | Ocultar blocos de pensamento na saída |\n| `showCacheMissNotices` | booleano | `false` | Mostrar avisos de transcrição para falhas significativas no cache de prompt |\n| `thinkingBudgets` | objeto | - | Orçamentos de tokens personalizados por nível de pensamento |\n\n#### pensando Orçamentos\n\n```json\n{\n  \"thinkingBudgets\": {\n    \"minimal\": 1024,\n    \"low\": 4096,\n    \"medium\": 10240,\n    \"high\": 32768\n  }\n}\n```\n\n### IU e exibição\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `theme` | corda | `\"dark\"` | Nome do tema (`\"dark\"`, `\"light\"` ou personalizado) |\n| `externalEditor` | corda | `$VISUAL`, depois `$EDITOR`, depois Bloco de Notas no Windows ou `nano` em outro lugar | Comando para editor externo Ctrl+G; tem precedência sobre variáveis ​​de ambiente |\n| `quietStartup` | booleano | `false` | Ocultar cabeçalho de inicialização |\n| `defaultProjectTrust` | corda | `\"ask\"` | Comportamento de confiança do projeto substituto: `\"ask\"`, `\"always\"` ou `\"never\"`. Somente configuração global |\n| `collapseChangelog` | booleano | `false` | Mostrar changelog condensado após atualizações |\n| `enableInstallTelemetry` | booleano | `true` | Envie um ping anônimo de instalação/atualização da versão após a primeira instalação ou atualizações detectadas pelo changelog. Isso não controla verificações de atualização |\n| `enableAnalytics` | booleano | `false` | Compartilhamento de dados analíticos opcional. Atualmente solicitado apenas durante a configuração experimental inicial (`PI_EXPERIMENTAL=1`) |\n| `trackingId` | corda | - | Identificador de rastreamento do Analytics, gerado quando `enableAnalytics` está ativado |\n| `doubleEscapeAction` | corda | `\"tree\"` | Ação para escape duplo: `\"tree\"`, `\"fork\"` ou `\"none\"` |\n| `treeFilterMode` | corda | `\"default\"` | Filtro padrão para `/tree`: `\"default\"`, `\"no-tools\"`, `\"user-only\"`, `\"labeled-only\"`, `\"all\"` |\n| `editorPaddingX` | número | `0` | Preenchimento horizontal para editor de entrada (0-3) |\n| `outputPad` | número | `1` | Preenchimento horizontal para mensagens do usuário, mensagens do assistente e pensamentos (0 ou 1) |\n| `autocompleteMaxVisible` | número | `5` | Máximo de itens visíveis no menu suspenso de preenchimento automático (3-20) |\n| `showHardwareCursor` | booleano | `false` | Mostre o cursor do terminal enquanto TUI o posiciona para suporte IME |\n| `tuiMode` | corda | `\"regular\"` | Modo interativo TUI: `\"regular\"` ou experimental `\"fullscreen\"`. As alterações de `/settings` aplicam-se imediatamente; `--tui-mode` substitui esta configuração na inicialização |\n| `fullscreenExitOutput` | corda | `\"transcript\"` | Saída de saída em tela cheia: `\"transcript\"` imprime a transcrição final e a dica de currículo, enquanto `\"resume-hint\"` restaura a tela anterior e imprime apenas a dica de currículo. Não tem efeito no modo TUI normal |\n| `fullscreenScrollbar` | corda | `\"auto\"` | Barra de rolagem de transcrição em tela cheia: `\"auto\"` mostra-a temporariamente durante a rolagem, `\"always\"` reserva a coluna mais à direita e a mantém visível e `\"hidden\"` a oculta. Não tem efeito no modo TUI normal |\n\nPara VS Code, inclua `--wait` para que pi seja retomado após a saída do editor:\n\n```json\n{\n  \"externalEditor\": \"code --wait\"\n}\n```\n\n### Telemetria e verificações de atualização\n\n`enableInstallTelemetry` controla apenas o ping anônimo de instalação/atualização para `https://pi.dev/api/report-install`. A desativação da telemetria não desativa as verificações de atualização; Pi ainda pode buscar `https://pi.dev/api/latest-version` para procurar a versão mais recente.\n\nDefina `PI_SKIP_VERSION_CHECK=1` para desativar a verificação de atualização de versão Pi. Use `--offline` ou `PI_OFFLINE=1` para desabilitar todas as operações de inicialização da rede descritas aqui, incluindo verificações de atualização, verificações de atualização de pacotes e telemetria de instalação/atualização.\n\n### Rede\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `httpProxy` | corda | - | URL do proxy HTTP aplicado como `HTTP_PROXY` e `HTTPS_PROXY`. Somente configuração global. |\n\n```json\n{\n  \"httpProxy\": \"http://127.0.0.1:7890\"\n}\n```\n\n### Avisos\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `warnings.anthropicExtraUsage` | booleano | `true` | Mostrar um aviso quando a autenticação de assinatura da Anthropic puder usar uso extra pago |\n\n```json\n{\n  \"warnings\": {\n    \"anthropicExtraUsage\": false\n  }\n}\n```\n\n### Compactação\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `compaction.enabled` | booleano | `true` | Ativar compactação automática |\n| `compaction.reserveTokens` | número | `16384` | Tokens reservados para resposta LLM |\n| `compaction.keepRecentTokens` | número | `20000` | Tokens recentes para manter (não resumidos) |\n\n```json\n{\n  \"compaction\": {\n    \"enabled\": true,\n    \"reserveTokens\": 16384,\n    \"keepRecentTokens\": 20000\n  }\n}\n```\n\n### Resumo da filial\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `branchSummary.reserveTokens` | número | `16384` | Tokens reservados para branch summarization |\n| `branchSummary.skipPrompt` | booleano | `false` | Pule \"Resumir ramificação?\" prompt na navegação `/tree` (o padrão é sem resumo) |\n\n### Tentar novamente\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `retry.enabled` | booleano | `true` | Habilitar novas tentativas automáticas no nível do agente em erros transitórios |\n| `retry.maxRetries` | número | `3` | Máximo de novas tentativas no nível do agente |\n| `retry.baseDelayMs` | número | `2000` | Atraso base para espera exponencial em nível de agente (2s, 4s, 8s) |\n| `retry.provider.timeoutMs` | número | SDK padrão | Tempo limite de solicitação do provedor/SDK em milissegundos |\n| `retry.provider.maxRetries` | número | `0` | Provedor/SDK novas tentativas |\n| `retry.provider.maxRetryDelayMs` | número | `60000` | Atraso máximo solicitado pelo servidor antes da falha (60s) |\n\nQuando um provedor solicita um atraso de repetição maior que `retry.provider.maxRetryDelayMs`, a solicitação falha imediatamente com um erro informativo em vez de esperar silenciosamente. Defina como `0` para desativar o limite.\n\nMantenha `retry.provider.maxRetries` em `0`, a menos que novas tentativas no nível do provedor sejam explicitamente necessárias. Definir acima de `0` pode fazer com que SDK/novas tentativas do provedor lide com erros de limite fora de uso antes que Pi os veja, o que pode bloquear o agente até que a cota do provedor seja redefinida em algumas circunstâncias.\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### Entrega de mensagens\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `steeringMode` | corda | `\"one-at-a-time\"` | Como as mensagens de direção são enviadas: `\"all\"` ou `\"one-at-a-time\"` |\n| `followUpMode` | corda | `\"one-at-a-time\"` | Como as mensagens de acompanhamento são enviadas: `\"all\"` ou `\"one-at-a-time\"` |\n| `transport` | corda | `\"auto\"` | Transporte preferido para provedores que suportam vários transportes: `\"sse\"`, `\"websocket\"`, `\"websocket-cached\"` ou `\"auto\"` |\n| `httpIdleTimeoutMs` | número | `300000` | Tempo limite de inatividade do cabeçalho/corpo HTTP em milissegundos, também usado por provedores com tempos limite de inatividade de fluxo explícitos. Defina como `0` para desativar. |\n| `websocketConnectTimeoutMs` | número | `15000` | Tempo limite de handshake de conexão/abertura do WebSocket em milissegundos para provedores que suportam transportes WebSocket. Defina como `0` para desativar. |\n\n### Terminal e imagens\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `terminal.showImages` | booleano | `true` | Mostrar imagens no terminal (se compatível) |\n| `terminal.imageWidthCells` | número | `60` | Largura de imagem embutida preferencial em células terminais |\n| `terminal.clearOnShrink` | booleano | `false` | Limpe as linhas vazias quando o conteúdo diminuir (pode causar oscilação) |\n| `images.autoResize` | booleano | `true` | Redimensione imagens para 2.000x2.000 no máximo. Aplica-se a `@file` anexos, `read` e imagens retornadas por ferramentas |\n| `images.blockImages` | booleano | `false` | Impedir que todas as imagens sejam enviadas para o LLM |\n\n### Concha\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `shellPath` | corda | - | Caminho de shell personalizado (por exemplo, para Cygwin no Windows); suporta um `~` inicial para o diretório inicial |\n| `shellCommandPrefix` | corda | - | Prefixo para cada comando bash (por exemplo, `\"shopt -s expand_aliases\"`) |\n| `npmCommand` | corda[] | - | Comando argv usado para operações de pesquisa/instalação de pacote npm (por exemplo, `[\"mise\", \"exec\", \"node@20\", \"--\", \"npm\"]`) |\n\n```json\n{\n  \"npmCommand\": [\"mise\", \"exec\", \"node@20\", \"--\", \"npm\"]\n}\n```\n\n`npmCommand` é usado para todas as operações do gerenciador de pacotes npm, incluindo instalações, desinstalações e instalações de dependências dentro de pacotes git. Pacotes npm com escopo de usuário são instalados em `~/.pi/agent/npm/`; pacotes npm com escopo de projeto são instalados em `.pi/npm/`. Use entradas no estilo argv exatamente como o processo deve ser iniciado. Quando `npmCommand` é configurado, as instalações de dependência do pacote git usam `install` simples para evitar sinalizadores específicos de npm em wrappers ou gerenciadores de pacotes alternativos.\n\n### Sessões\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `sessionDir` | corda | - | Diretório onde os arquivos da sessão são armazenados. Aceita caminhos absolutos ou relativos, mais `~`. |\n\n```json\n{ \"sessionDir\": \".pi/sessions\" }\n```\n\nQuando várias fontes especificam um diretório de sessão, a precedência é `--session-dir`, `PI_CODING_AGENT_SESSION_DIR` e, em seguida, `sessionDir` em settings.json.\n\n### Modelo de ciclismo\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `enabledModels` | corda[] | - | Padrões de modelo para ciclismo Ctrl+P (mesmo formato do sinalizador `--models` CLI) |\n\n```json\n{\n  \"enabledModels\": [\"claude-*\", \"gpt-4o\", \"gemini-2*\"]\n}\n```\n\n### Markdown\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `markdown.codeBlockIndent` | corda | `\"  \"` | Recuo para blocos de código |\n| `markdown.mermaid` | corda | `\"streaming\"` | Modo de renderização sereia: `\"off\"`, `\"final\"` ou `\"streaming\"` |\n\n### Recursos\n\nEssas configurações definem de onde carregar extensões, habilidades, prompts e temas.\n\nCaminhos em `~/.pi/agent/settings.json` são resolvidos em relação a `~/.pi/agent`. Caminhos em `.pi/settings.json` são resolvidos em relação a `.pi`. Caminhos absolutos e `~` são suportados.\n\n| Contexto | Tipo | Padrão | Descrição |\n|---------|------|---------|-------------|\n| `packages` | variedade | `[]` | npm/git pacotes para carregar recursos |\n| `extensions` | corda[] | `[]` | Caminhos ou diretórios de arquivos de extensão local |\n| `skills` | corda[] | `[]` | Caminhos ou diretórios de arquivos de habilidades locais |\n| `prompts` | corda[] | `[]` | Caminhos ou diretórios de modelos de prompt locais |\n| `themes` | corda[] | `[]` | Caminhos ou diretórios de arquivos de tema local |\n| `enableSkillCommands` | booleano | `true` | Registre habilidades como comandos `/skill:name` |\n\nMatrizes suportam padrões globais e exclusões. Use `!pattern` para excluir. Use `+path` para forçar a inclusão de um caminho exato e `-path` para forçar a exclusão de um caminho exato.\n\n#### pacotes\n\nO formulário String carrega todos os recursos de um pacote:\n\n```json\n{\n  \"packages\": [\"pi-skills\", \"@org/my-extension\"]\n}\n```\n\nO formulário do objeto filtra quais recursos carregar:\n\n```json\n{\n  \"packages\": [\n    {\n      \"source\": \"pi-skills\",\n      \"skills\": [\"brave-search\", \"transcribe\"],\n      \"extensions\": []\n    }\n  ]\n}\n```\n\nVeja [packages.md](packages.md) para detalhes de gerenciamento de pacotes.\n\n## Exemplo\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## Substituições de projeto\n\nAs configurações do projeto (`.pi/settings.json`) substituem as configurações globais. Objetos aninhados são mesclados:\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":"Aliases de shell","markdown":"Pi executa bash em modo não interativo (`bash -c`), que não expande aliases por padrão.\n\nPara habilitar seus aliases de shell, adicione a `~/.pi/agent/settings.json`:\n\n```json\n{\n  \"shellCommandPrefix\": \"shopt -s expand_aliases\\neval \\\"$(grep '^alias ' ~/.zshrc)\\\"\"\n}\n```\n\nAjuste o caminho (`~/.zshrc`, `~/.bashrc`, etc.) para corresponder à configuração do seu shell.","sourceFile":"shell-aliases.md"},"skills":{"title":"Skills","markdown":"> pi pode criar habilidades. Peça para criar um para o seu caso de uso.\n\n\nSkills são pacotes de recursos independentes que o agente carrega sob demanda. Uma habilidade fornece fluxos de trabalho especializados, instruções de configuração, scripts auxiliares e documentação de referência para tarefas específicas.\n\nPi implementa o [Agent Skills standard](https://agentskills.io/specification), alertando sobre a maioria das violações, mas permanecendo tolerante. Pi permite que os nomes das habilidades sejam diferentes de seu diretório pai, mesmo que o padrão não permita isso; essa regra não é ideal para diretórios de habilidades compartilhados usados ​​em vários equipamentos de agente.\n\n## Índice\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## Locais\n\n> **Segurança:** Skills pode instruir o modelo a executar qualquer ação e pode incluir código executável que o modelo invoca. Revise o conteúdo da habilidade antes de usar.\n\nPi carrega habilidades de:\n\n- Global:\n  - `~/.pi/agent/skills/`\n  - `~/.agents/skills/`\n- Projeto (somente depois que o projeto for confiável):\n  - `.pi/skills/`\n  - `.agents/skills/` em `cwd` e diretórios ancestrais (até git repo root ou filesystem root quando não estiver em um repo)\n- Pacotes: diretórios `skills/` ou entradas `pi.skills` em `package.json`\n- Configurações: `skills` array com arquivos ou diretórios\n- CLI: `--skill <path>` (repetível, aditivo mesmo com `--no-skills`)\n\nRegras de descoberta:\n- Em `~/.pi/agent/skills/` e `.pi/skills/`, arquivos raiz direta `.md` são descobertos como habilidades individuais\n- Em todos os locais de habilidade, os diretórios contendo `SKILL.md` são descobertos recursivamente\n- Em `~/.agents/skills/` e no projeto `.agents/skills/`, os arquivos raiz `.md` são ignorados\n\nDesative a descoberta com `--no-skills` (caminhos `--skill` explícitos ainda são carregados).\n\n### Usando Skills de outros chicotes\n\nPara usar habilidades do Claude Code ou OpenAI Codex, adicione seus diretórios às configurações:\n\n```json\n{\n  \"skills\": [\n    \"~/.claude/skills\",\n    \"~/.codex/skills\"\n  ]\n}\n```\n\nPara habilidades de Claude Code em nível de projeto, adicione a `.pi/settings.json`:\n\n```json\n{\n  \"skills\": [\"../.claude/skills\"]\n}\n```\n\n## Como Skills funciona\n\n1. Na inicialização, o pi verifica os locais das habilidades e extrai nomes e descrições\n2. O prompt do sistema inclui habilidades disponíveis em formato XML de acordo com [specification](https://agentskills.io/integrate-skills)\n3. Quando uma tarefa corresponde, o agente usa `read` para carregar o SKILL.md completo (os modelos nem sempre fazem isso; use prompts ou `/skill:name` para forçá-lo)\n4. O agente segue as instruções, usando caminhos relativos para referenciar scripts e ativos\n\nEsta é uma divulgação progressiva: apenas as descrições estão sempre no contexto, as instruções completas são carregadas sob demanda.\n\n## Comandos de habilidade\n\nSkills registre-se como `/skill:name` comandos:\n\n```bash\n/skill:brave-search           # Load and execute the skill\n/skill:pdf-tools extract      # Load skill with arguments\n```\n\nOs argumentos após o comando são anexados ao conteúdo da habilidade como `User: <args>`.\n\nAlterne os comandos de habilidade via `/settings` no modo interativo ou em `settings.json`:\n\n```json\n{\n  \"enableSkillCommands\": true\n}\n```\n\n## Estrutura de habilidades\n\nUma habilidade é um diretório com um arquivo `SKILL.md`. Todo o resto é de forma livre.\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### Formato HABILIDADE.md\n\n````markdown\n---\nname: my-skill\ndescription: What this skill does and when to use it. Be specific.\n---\n\n# My Skill\n\n## Setup\n\nRun once before first use:\n```bash\ncd /caminho/para/habilidade && npm instalar\n```\n\n## Usage\n\n```bash\n./scripts/process.sh <input>\n```\n````\n\nUse caminhos relativos do diretório de habilidades:\n\n```markdown\nSee [the reference guide](references/REFERENCE.md) for details.\n```\n\n## Matéria inicial\n\nDe acordo com [Agent Skills specification](https://agentskills.io/specification#frontmatter-required):\n\n| Campo | Obrigatório | Descrição |\n|-------|----------|-------------|\n| `name` | Sim | Máximo de 64 caracteres. A-z minúsculo, 0-9, hífens. Ao contrário do padrão, Pi não exige que isso corresponda ao diretório pai porque esse requisito padrão não é ideal para diretórios de habilidades compartilhados. |\n| `description` | Sim | Máximo de 1024 caracteres. O que a habilidade faz e quando usá-la. |\n| `license` | Não | Nome da licença ou referência ao arquivo incluído. |\n| `compatibility` | Não | Máximo de 500 caracteres. Requisitos ambientais. |\n| `metadata` | Não | Mapeamento arbitrário de valores-chave. |\n| `allowed-tools` | Não | Lista delimitada por espaço de ferramentas pré-aprovadas (experimental). |\n| `disable-model-invocation` | Não | Quando `true`, a habilidade fica oculta no prompt do sistema. Os usuários devem usar `/skill:name`. |\n\n### Regras de nomes\n\n- 1-64 caracteres\n- Somente letras minúsculas, números e hífens\n- Sem hífens iniciais/finais\n- Sem hífens consecutivos\nPi não requer que o nome corresponda ao diretório pai. O padrão Agent Skills sim, mas esse requisito é abaixo do ideal para diretórios de habilidades compartilhados usados ​​por múltiplas ferramentas.\n\nVálido: `pdf-processing`, `data-analysis`, `code-review`\nInválido: `PDF-Processing`, `-pdf`, `pdf--processing`\n\n### Descrição Melhores Práticas\n\nA descrição determina quando o agente carrega a habilidade. Seja específico.\n\nBom:\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\nPobre:\n```yaml\ndescription: Helps with PDFs.\n```\n\n## Validação\n\nPi valida habilidades em relação ao padrão do Agente Skills. A maioria dos problemas produz avisos, mas ainda carrega a habilidade:\n\n- O nome excede 64 caracteres ou contém caracteres inválidos\n- O nome começa/termina com hífen ou tem hífens consecutivos\n- A descrição excede 1.024 caracteres\n\nCampos frontmatter desconhecidos são ignorados.\n\n**Exceção:** Skills com descrição ausente não são carregados.\n\nColisões de nomes (mesmo nome em locais diferentes) avisam e mantêm a primeira habilidade encontrada.\n\n## Exemplo\n\n```\nbrave-search/\n├── SKILL.md\n├── search.js\n└── content.js\n```\n\n**HABILIDADE.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 /caminho/para/brave-search && npm instalar\n```\n\n## Search\n\n```bash\n./search.js \"query\" # Pesquisa básica\n./search.js \"query\" --content # Inclui o conteúdo da página\n```\n\n## Extract Page Content\n\n```bash\n./content.js https://example.com\n```\n````\n\n## Repositórios de Habilidades\n\n- [Anthropic Skills](https://github.com/anthropics/skills) - Processamento de documentos (docx, pdf, pptx, xlsx), desenvolvimento web\n- [Pi Skills](https://github.com/badlogic/pi-skills) - Pesquisa na Web, automação do navegador, Google APIs, transcrição","sourceFile":"skills.md"},"terminal-setup":{"title":"Configuração do terminal","markdown":"Pi usa [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) para detecção confiável de tecla modificadora. A maioria dos terminais modernos suporta este protocolo, mas alguns requerem configuração.\n\n## Gatinha, iTerm2\n\nTrabalhe fora da caixa.\n\n## Terminal Apple\n\nPi permite relatórios de chaves aprimorados quando disponíveis. Se Terminal.app ainda enviar Return simples para `Shift+Enter`, pi usará um modificador local do macOS para tratar esse Return como `Shift+Enter`.\n\nEste substituto só funciona quando pi é executado no mesmo Mac que Terminal.app. Ele não consegue detectar o teclado local no SSH remoto.\n\n## Fantasmagórico\n\nAdicione à configuração do Ghostty (`~/Library/Application Support/com.mitchellh.ghostty/config` no macOS, `~/.config/ghostty/config` no Linux):\n\n```\nkeybind = alt+backspace=text:\\x1b\\x7f\n```\n\nVersões mais antigas do Claude Code podem ter adicionado este mapeamento Ghostty:\n\n```\nkeybind = shift+enter=text:\\n\n```\n\nEsse mapeamento envia um byte de avanço de linha bruto. Dentro de pi, isso é indistinguível de `Ctrl+J`, então tmux e pi não veem mais um evento chave `shift+enter` real.\n\nSe o Claude Code 2.x ou mais recente for o único motivo pelo qual você adicionou esse mapeamento, você poderá removê-lo, a menos que queira usar o Claude Code em tmux, onde ainda requer o mapeamento do Ghostty.\n\nPi vincula `Ctrl+J` como um alias de nova linha padrão, então `Shift+Enter` continua trabalhando em tmux por meio desse remapeamento sem configuração extra de pi.\n\n## WezTerm\n\nWezTerm geralmente funciona imediatamente para `Shift+Enter` via xterm modificarOtherKeys. Para usar o protocolo de teclado Kitty explicitamente, crie `~/.wezterm.lua`:\n\n```lua\nlocal wezterm = require 'wezterm'\nlocal config = wezterm.config_builder()\nconfig.enable_kitty_keyboard = true\nreturn config\n```\n\nNo macOS, o WezTerm vincula `Option+Enter` à tela cheia por padrão. Para usar `Option+Enter` para enfileiramento de acompanhamento pi, adicione esta substituição de chave:\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\nSe você já possui uma tabela `config.keys`, adicione a entrada a ela.\n\nNo WSL, o WezTerm pode exigir um cursor de hardware visível para o posicionamento da janela candidata ao IME. Se os candidatos CJK IME não seguirem o cursor de texto, defina `PI_HARDWARE_CURSOR=1` antes de executar pi ou defina `showHardwareCursor` para `true` nas configurações.\n\n## Alacritty\n\nAlacritty geralmente funciona imediatamente por `Shift+Enter`. No macOS, `Option+Enter` pode chegar como `Enter` simples. Para usar `Option+Enter` para fila de acompanhamento pi, adicione a `~/.config/alacritty/alacritty.toml`:\n\n```toml\n[[keyboard.bindings]]\nkey = \"Enter\"\nmods = \"Alt\"\nchars = \"\\u001b[13;3u\"\n```\n\nReinicie o Alacritty após alterar a configuração.\n\n## Código VS (Terminal Integrado)\n\nO VS Code 1.109.5 e mais recente habilitam o protocolo de teclado Kitty no terminal integrado por padrão, então `Shift+Enter` deve funcionar imediatamente.\n\nVersões do VS Code anteriores a 1.109.5 precisam de um atalho de teclado explícito para `Shift+Enter`.\n\n`keybindings.json` locais:\n- macOS: `~/Library/Application Support/Code/User/keybindings.json`\n- Linux: `~/.config/Code/User/keybindings.json`\n- Janelas: `%APPDATA%\\\\Code\\\\User\\\\keybindings.json`\n\nAdicione a `keybindings.json`:\n\n```json\n{\n  \"key\": \"shift+enter\",\n  \"command\": \"workbench.action.terminal.sendSequence\",\n  \"args\": { \"text\": \"\\u001b[13;2u\" },\n  \"when\": \"terminalFocus\"\n}\n```\n\n## Terminal do Windows\n\nAdicione a `settings.json` (Ctrl+Shift+ ou Configurações → Abrir arquivo JSON) para encaminhar as teclas Enter modificadas que pi usa:\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` insere uma nova linha.\n- O Terminal do Windows vincula `Alt+Enter` à tela inteira por padrão. Isso evita que pi receba `Alt+Enter` para enfileiramento de acompanhamento.\n- Remapear `Alt+Enter` para `sendInput` encaminha o acorde real para pi.\n\nSe você já possui um array `actions`, adicione os objetos a ele. Se o antigo comportamento de tela cheia persistir, feche totalmente e reabra o Terminal do Windows.\n\n## terminal xfce4, terminador\n\nEsses terminais têm suporte limitado à sequência de escape. As teclas Enter modificadas como `Ctrl+Enter` e `Shift+Enter` não podem ser distinguidas do `Enter` simples, impedindo que atalhos de teclado personalizados como `submit: [\"ctrl+enter\"]` funcionem.\n\nPara obter a melhor experiência, use um terminal que suporte o protocolo de teclado Kitty:\n- [Kitty](https://sw.kovidgoyal.net/kitty/)\n- [Ghostty](https://ghostty.org/)\n- [WezTerm](https://wezfurlong.org/wezterm/)\n- [iTerm2](https://iterm2.com/)\n- [Alacritty](https://github.com/alacritty/alacritty) (requer compilação com suporte ao protocolo Kitty)\n\n## IntelliJ IDEA (Terminal Integrado)\n\nO terminal integrado possui suporte limitado à sequência de escape. Shift+Enter não pode ser diferenciado de Enter no terminal do IntelliJ.\n\nSe você quiser que o cursor do hardware fique visível, defina `PI_HARDWARE_CURSOR=1` antes de executar o pi (desativado por padrão para compatibilidade).\n\nConsidere usar um emulador de terminal dedicado para obter a melhor experiência.","sourceFile":"terminal-setup.md"},"termux":{"title":"Termux (Android) Configuração","markdown":"Pi roda em Android via [Termux](https://termux.dev/), um emulador de terminal e ambiente Linux para Android.\n\n## Pré-requisitos\n\n1. Instale [Termux](https://github.com/termux/termux-app#installation) de GitHub ou F-Droid (não do Google Play, essa versão está obsoleta)\n2. Instale [Termux:API](https://github.com/termux/termux-api#installation) de GitHub ou F-Droid para área de transferência e outras integrações de dispositivos\n\n## Instalação\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## Suporte para área de transferência\n\nAs operações da área de transferência usam `termux-clipboard-set` e `termux-clipboard-get` quando executadas em Termux. O aplicativo Termux:API deve estar instalado para que funcionem.\n\nA área de transferência de imagens não é suportada em Termux (o recurso de colagem de imagens `ctrl+v` não funcionará).\n\n## Exemplo AGENTS.md para Termux\n\nCrie `~/.pi/agent/AGENTS.md` para ajudar o agente a entender o ambiente Termux:\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 # Abre com o aplicativo padrão\ntermux-open --chooser image.jpg # Escolha o aplicativo\n```\n\n## Clipboard\n```bash\ntermux-clipboard-set \"texto\" # Copiar\ntermux-clipboard-get # Colar\n```\n\n## Notifications\n```bash\ntermux-notification -t \"Título\" -c \"Conteúdo\"\n```\n\n## Device Info\n```bash\ntermux-battery-status # Informações da bateria\ntermux-wifi-connectioninfo # Informações WiFi\ntermux-telephony-deviceinfo # Informações do dispositivo\n```\n\n## Sharing\n```bash\ntermux-share -a enviar arquivo.txt # Compartilhar arquivo\n```\n\n## Other Useful Commands\n```bash\ntermux-toast \"mensagem\" # Pop-up rápido do brinde\ntermux-vibrate # Vibra dispositivo\ntermux-tts-speak \"olá\" # Texto para fala\ntermux-camera-photo out.jpg # Tirar foto\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## Limitações\n\n- **Sem área de transferência de imagem**: Termux área de transferência API suporta apenas texto\n- **Sem binários nativos**: algumas dependências nativas opcionais (como o módulo da área de transferência) não estão disponíveis no Android ARM64 e são ignoradas durante a instalação\n- **Acesso ao armazenamento**: Para acessar arquivos em `/storage/emulated/0` (Downloads, etc.), execute `termux-setup-storage` uma vez para conceder permissões\n\n## Solução de problemas\n\n### A área de transferência não funciona\n\nCertifique-se de que ambos os aplicativos estejam instalados:\n1. Termux (de GitHub ou F-Droid)\n2. Termux:API (de GitHub ou F-Droid)\n\nEm seguida, instale as ferramentas CLI:\n```bash\npkg install termux-api\n```\n\n### Permissão negada para armazenamento compartilhado\n\nExecute uma vez para conceder permissões de armazenamento:\n```bash\ntermux-setup-storage\n```\n\n### Node.js problemas de instalação\n\nSe npm falhar, tente limpar o cache:\n```bash\nnpm cache clean --force\n```","sourceFile":"termux.md"},"themes":{"title":"Temas","markdown":"> pi pode criar temas. Peça para construir um para sua configuração.\n\n\nOs temas são arquivos JSON que definem as cores do TUI.\n\n## Índice\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## Locais\n\nPi carrega temas de:\n\n- Integrado: `dark`, `light`\n- Globais: `~/.pi/agent/themes/*.json`\n- Projeto: `.pi/themes/*.json` (somente depois que o projeto for confiável)\n- Pacotes: diretórios `themes/` ou entradas `pi.themes` em `package.json`\n- Configurações: `themes` array com arquivos ou diretórios\n- CLI: `--theme <path>` (repetível)\n\nDesative a descoberta com `--no-themes`.\n\n## Selecionando um tema\n\nSelecione um tema via `/settings` ou em `settings.json`:\n\n```json\n{\n  \"theme\": \"my-theme\"\n}\n```\n\nNa primeira execução, pi detecta o plano de fundo do seu terminal e o padrão é `dark` ou `light`.\n\n## Criando um tema personalizado\n\n1. Crie um arquivo de tema:\n\n```bash\nmkdir -p ~/.pi/agent/themes\nvim ~/.pi/agent/themes/my-theme.json\n```\n\n2. Defina o tema com todas as cores necessárias (veja [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. Selecione o tema via `/settings`.\n\n**Recarregamento a quente:** Quando você edita o arquivo de tema personalizado atualmente ativo, o pi o recarrega automaticamente para feedback visual imediato.\n\n## Formato do tema\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` é obrigatório, deve ser exclusivo e não deve conter `/`.\n- `vars` é opcional. Defina cores reutilizáveis ​​aqui e referencie-as em `colors`.\n- `colors` deve definir todos os 51 tokens necessários. `thinkingMax` é opcional e volta para `thinkingXhigh`; `scrollbarThumb` é opcional e volta para `selectedBg`.\n\nO campo `$schema` permite o preenchimento automático e a validação do editor.\n\n## Fichas de cores\n\nCada tema deve definir todos os 51 tokens de cores necessários. `thinkingMax` e `scrollbarThumb` são opcionais para compatibilidade com temas existentes; quando omitidos, eles usam `thinkingXhigh` e `selectedBg`, respectivamente.\n\n### UI principal (11 cores)\n\n| Símbolo | Propósito |\n|-------|---------|\n| `accent` | Acento primário (logotipo, itens selecionados, cursor) |\n| `border` | Fronteiras normais |\n| `borderAccent` | Bordas destacadas |\n| `borderMuted` | Fronteiras sutis (editor) |\n| `success` | Estados de sucesso |\n| `error` | Estados de erro |\n| `warning` | Estados de aviso |\n| `muted` | Texto secundário |\n| `dim` | Texto terciário |\n| `text` | Texto padrão (geralmente `\"\"`) |\n| `thinkingText` | Texto de bloco de pensamento |\n\n### Planos de fundo e conteúdo (11 obrigatórios, 1 opcional)\n\n| Símbolo | Propósito |\n|-------|---------|\n| `selectedBg` | Plano de fundo da linha selecionada |\n| `scrollbarThumb` | Fundo do polegar da barra de rolagem em tela cheia; opcional, volta para `selectedBg` |\n| `userMessageBg` | Plano de fundo da mensagem do usuário |\n| `userMessageText` | Texto da mensagem do usuário |\n| `customMessageBg` | Plano de fundo da mensagem de extensão |\n| `customMessageText` | Texto da mensagem de extensão |\n| `customMessageLabel` | Etiqueta da mensagem de extensão |\n| `toolPendingBg` | Caixa de ferramentas (pendente) |\n| `toolSuccessBg` | Caixa de ferramentas (sucesso) |\n| `toolErrorBg` | Caixa de ferramentas (erro) |\n| `toolTitle` | Título da ferramenta |\n| `toolOutput` | Texto de saída da ferramenta |\n\n### Markdown (10 cores)\n\n| Símbolo | Propósito |\n|-------|---------|\n| `mdHeading` | Títulos |\n| `mdLink` | Texto do link |\n| `mdLinkUrl` | URL do link |\n| `mdCode` | Código embutido |\n| `mdCodeBlock` | Conteúdo do bloco de código |\n| `mdCodeBlockBorder` | Cercas de bloqueio de código |\n| `mdQuote` | Texto de citação em bloco |\n| `mdQuoteBorder` | Borda de citação |\n| `mdHr` | Regra horizontal |\n| `mdListBullet` | Listar marcadores |\n\n### Diferenças de ferramentas (3 cores)\n\n| Símbolo | Propósito |\n|-------|---------|\n| `toolDiffAdded` | Linhas adicionadas |\n| `toolDiffRemoved` | Linhas removidas |\n| `toolDiffContext` | Linhas de contexto |\n\n### Destaque de sintaxe (9 cores)\n\n| Símbolo | Propósito |\n|-------|---------|\n| `syntaxComment` | Comentários |\n| `syntaxKeyword` | Palavras-chave |\n| `syntaxFunction` | Nomes de funções |\n| `syntaxVariable` | Variáveis |\n| `syntaxString` | Cordas |\n| `syntaxNumber` | Números |\n| `syntaxType` | Tipos |\n| `syntaxOperator` | Operadores |\n| `syntaxPunctuation` | Pontuação |\n\n### Fronteiras de nível de pensamento (6 obrigatórias, 1 opcional)\n\nCores da borda do editor indicando o nível de pensamento (hierarquia visual de sutil a proeminente):\n\n| Símbolo | Propósito |\n|-------|---------|\n| `thinkingOff` | Pensando |\n| `thinkingMinimal` | Pensamento mínimo |\n| `thinkingLow` | Pensamento baixo |\n| `thinkingMedium` | Pensamento médio |\n| `thinkingHigh` | Pensamento elevado |\n| `thinkingXhigh` | Pensamento extra elevado |\n| `thinkingMax` | Pensamento máximo; opcional, volta para `thinkingXhigh` |\n\n### Modo Bash (1 cor)\n\n| Símbolo | Propósito |\n|-------|---------|\n| `bashMode` | Borda do editor no modo bash (prefixo `!`) |\n\n### Exportação HTML (opcional)\n\nA seção `export` controla as cores da saída HTML `/export`. Se omitido, as cores serão derivadas de `userMessageBg`.\n\n```json\n{\n  \"export\": {\n    \"pageBg\": \"#18181e\",\n    \"cardBg\": \"#1e1e24\",\n    \"infoBg\": \"#3c3728\"\n  }\n}\n```\n\n## Valores de cores\n\nQuatro formatos são suportados:\n\n| Formatar | Exemplo | Descrição |\n|--------|---------|-------------|\n| Feitiço | `\"#ff0000\"` | RGB hexadecimal de 6 dígitos |\n| 256 cores | `39` | Índice de paleta de 256 cores xterm (0-255) |\n| Variável | `\"primary\"` | Referência a uma entrada `vars` |\n| Padrão | `\"\"` | Cor padrão do terminal |\n\n### Paleta de 256 cores\n\n- `0-15`: Cores ANSI básicas (dependente do terminal)\n- `16-231`: cubo RGB 6×6×6 (`16 + 36×R + 6×G + B` onde R,G,B são 0-5)\n- `232-255`: Rampa em tons de cinza\n\n### Compatibilidade de terminais\n\nPi usa cores RGB de 24 bits. A maioria dos terminais modernos suporta isso (iTerm2, Kitty, WezTerm, Windows Terminal, VS Code). Para terminais mais antigos com suporte apenas para 256 cores, pi volta para a aproximação mais próxima.\n\nVerifique o suporte truecolor:\n\n```bash\necho $COLORTERM  # Should output \"truecolor\" or \"24bit\"\n```\n\n## Pontas\n\n**Terminais escuros:** Use cores brilhantes e saturadas com maior contraste.\n\n**Terminais claros:** use cores mais escuras e suaves com menor contraste.\n\n**Harmonia de cores:** Comece com uma paleta base (Nord, Gruvbox, Tokyo Night), defina-a em `vars` e faça referência de forma consistente.\n\n**Testes:** Verifique seu tema com diferentes tipos de mensagens, estados de ferramentas, conteúdo de marcação e texto longo.\n\n**Código VS:** Defina `terminal.integrated.minimumContrastRatio` como `1` para cores precisas.\n\n## Exemplos\n\nVeja os temas integrados:\n- [dark.json](../src/modes/interactive/theme/dark.json)\n- [light.json](../src/modes/interactive/theme/light.json)","sourceFile":"themes.md"},"tmux":{"title":"tmux Configuração","markdown":"Pi funciona dentro de tmux, mas tmux remove informações do modificador de certas teclas por padrão. Sem configuração, `Shift+Enter` e `Ctrl+Enter` são geralmente indistinguíveis do `Enter` simples.\n\n## Configuração recomendada\n\nAdicione a `~/.tmux.conf`:\n\n```tmux\nset -g extended-keys on\nset -g extended-keys-format csi-u\n```\n\nEntão reinicie tmux completamente:\n\n```bash\ntmux kill-server\ntmux\n```\n\nPi solicita relatórios de teclas estendidas automaticamente quando o protocolo de teclado Kitty não está disponível. Com `extended-keys-format csi-u`, tmux encaminha chaves modificadas no formato CSI-u, que é a configuração mais confiável. A opção `extended-keys-format` requer tmux 3.5 ou posterior.\n\n## Por que `csi-u` é recomendado\n\nCom apenas:\n\n```tmux\nset -g extended-keys on\n```\n\ntmux o padrão é `extended-keys-format xterm`. Quando um aplicativo solicita relatórios de chave estendidos, as chaves modificadas são encaminhadas no formato xterm `modifyOtherKeys`, como:\n\n- `Ctrl+C` → `\\x1b[27;5;99~`\n- `Ctrl+D` → `\\x1b[27;5;100~`\n- `Ctrl+Enter` → `\\x1b[27;5;13~`\n\nCom `extended-keys-format csi-u`, as mesmas chaves são encaminhadas como:\n\n- `Ctrl+C` → `\\x1b[99;5u`\n- `Ctrl+D` → `\\x1b[100;5u`\n- `Ctrl+Enter` → `\\x1b[13;5u`\n\nPi suporta ambos os formatos, mas `csi-u` é a configuração tmux recomendada.\n\n## O que isso corrige\n\nSem as chaves estendidas tmux, as teclas Enter modificadas são reduzidas às sequências herdadas:\n\n| Chave | Sem extkeys | Com `csi-u` |\n|-----|-----------------|--------------|\n| Digitar | `\\r` | `\\r` |\n| Shift + Enter | `\\r` | `\\x1b[13;2u` |\n| Ctrl+Enter | `\\r` | `\\x1b[13;5u` |\n| Alt/Opção+Enter | `\\x1b\\r` | `\\x1b[13;3u` |\n\nIsso afeta os atalhos de teclado padrão (`Enter` para enviar, `Shift+Enter` para nova linha) e quaisquer atalhos de teclado personalizados usando Enter modificado.\n\n## Requisitos\n\n- tmux 3.5 ou posterior para `extended-keys-format csi-u` (execute `tmux -V` para verificar)\n- Um emulador de terminal que suporta chaves estendidas (Ghostty, Kitty, iTerm2, WezTerm, Windows Terminal)\n\nCom tmux 3.2 a 3.4, omita `extended-keys-format csi-u`; Pi ainda suporta o formato xterm `modifyOtherKeys` padrão de tmux.","sourceFile":"tmux.md"},"tui":{"title":"TUI Componentes","markdown":"> pi pode criar componentes TUI. Peça para criar um para o seu caso de uso.\n\n\nExtensions e ferramentas personalizadas podem renderizar componentes TUI personalizados para interfaces de usuário interativas. Esta página aborda o sistema de componentes e os blocos de construção disponíveis.\n\n**Fonte:** [`@earendil-works/pi-tui`](https://github.com/earendil-works/pi-mono/tree/main/packages/tui)\n\n## Interface de componentes\n\nTodos os componentes implementam:\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| Método | Descrição |\n|--------|-------------|\n| `render(width)` | Retorna uma matriz de strings (uma por linha). Cada linha **não deve exceder `width`**. |\n| `handleInput?(data)` | Receba entrada do teclado quando o componente estiver em foco. |\n| `wantsKeyRelease?` | Se verdadeiro, o componente recebe eventos de liberação de chave (protocolo Kitty). Padrão: falso. |\n| `invalidate()` | Limpe o estado de renderização em cache. Solicitado mudanças de tema. |\n\nO TUI anexa uma redefinição completa do SGR e uma redefinição do OSC 8 no final de cada linha renderizada. Os estilos não atravessam as linhas. Se você emitir texto multilinha com estilo, reaplique estilos por linha ou use `wrapTextWithAnsi()` para que os estilos sejam preservados para cada linha quebrada.\n\n## Interface Focável (Suporte IME)\n\nComponentes que exibem um cursor de texto e precisam de suporte IME (Input Method Editor) devem implementar a interface `Focusable`:\n\n```typescript\nimport { CURSOR_MARKER, type Component, type Focusable } from \"@earendil-works/pi-tui\";\n\nclass MyInput implements Component, Focusable {\n  focused: boolean = false;  // Set by TUI when focus changes\n  \n  render(width: number): string[] {\n    const marker = this.focused ? CURSOR_MARKER : \"\";\n    // Emit marker right before the fake cursor\n    return [`> ${beforeCursor}${marker}\\x1b[7m${atCursor}\\x1b[27m${afterCursor}`];\n  }\n}\n```\n\nQuando um componente `Focusable` está em foco, TUI:\n1. Define `focused = true` no componente\n2. Verifica a saída renderizada para `CURSOR_MARKER` (uma sequência de escape APC de largura zero)\n3. Posiciona o cursor do terminal de hardware nesse local\n4. Mostra o cursor de hardware apenas quando `showHardwareCursor` está habilitado\n\nO cursor permanece oculto por padrão. Isso mantém a renderização falsa do cursor, enquanto ainda posiciona o cursor de hardware para terminais que rastreiam janelas candidatas a IME com cursores ocultos. Alguns terminais requerem um cursor de hardware visível para posicionamento do IME; habilite-o com `showHardwareCursor`, `setShowHardwareCursor(true)` ou `PI_HARDWARE_CURSOR=1`. Os componentes integrados `Editor` e `Input` já implementam esta interface.\n\n### Componentes de contêiner com entradas incorporadas\n\nQuando um componente contêiner (diálogo, seletor, etc.) contém um filho `Input` ou `Editor`, o contêiner deve implementar `Focusable` e propagar o estado de foco para o filho. Caso contrário, o cursor de hardware não será posicionado corretamente para entrada IME.\n\n```typescript\nimport { Container, type Focusable, Input } from \"@earendil-works/pi-tui\";\n\nclass SearchDialog extends Container implements Focusable {\n  private searchInput: Input;\n\n  // Focusable implementation - propagate to child input for IME cursor positioning\n  private _focused = false;\n  get focused(): boolean {\n    return this._focused;\n  }\n  set focused(value: boolean) {\n    this._focused = value;\n    this.searchInput.focused = value;\n  }\n\n  constructor() {\n    super();\n    this.searchInput = new Input();\n    this.addChild(this.searchInput);\n  }\n}\n```\n\nSem esta propagação, digitar com um IME (chinês, japonês, coreano, etc.) mostrará a janela candidata na posição errada na tela.\n\n## Usando componentes\n\n**Em extensões** via `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**Em ferramentas personalizadas** via `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## Sobreposições\n\nAs sobreposições renderizam componentes sobre o conteúdo existente sem limpar a tela. Passe `{ overlay: true }` para `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\nPara posicionamento e dimensionamento, use `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### Foco de sobreposição\n\nUma sobreposição visível focada mantém a propriedade de entrada em uma interface de usuário temporária sem sobreposição. Se uma sobreposição abrir outro componente `ctx.ui.custom()` sem `{ overlay: true }`, essa UI substituta receberá entrada enquanto estiver ativa; quando fecha, a sobreposição focada pode recuperar a entrada.\n\nUse `handle.unfocus()` quando uma sobreposição visível deixar de possuir a entrada e deixar TUI voltar para outra sobreposição de captura visível ou para o alvo de foco anterior. Use `handle.unfocus({ target })` quando um componente específico deve receber entrada enquanto a sobreposição permanece visível. Passar `{ target: null }` intencionalmente não deixa nenhum componente em foco até que o foco seja definido novamente.\n\n### Ciclo de vida da sobreposição\n\nOs componentes de sobreposição são descartados quando fechados. Não reutilize referências – crie novas instâncias:\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\nConsulte [overlay-qa-tests.ts](../examples/extensions/overlay-qa-tests.ts) para exemplos abrangentes que abrangem âncoras, margens, empilhamento, visibilidade responsiva e animação.\n\n## Componentes integrados\n\nImportar de `@earendil-works/pi-tui`:\n\n```typescript\nimport { Text, Box, Container, Spacer, Markdown } from \"@earendil-works/pi-tui\";\n```\n\n### Texto\n\nTexto multilinha com quebra automática de linha.\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### Caixa\n\nContainer com preenchimento e cor de fundo.\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### Recipiente\n\nAgrupa componentes filhos verticalmente.\n\n```typescript\nconst container = new Container();\ncontainer.addChild(component1);\ncontainer.addChild(component2);\ncontainer.removeChild(component1);\n```\n\n### Espaçador\n\nEspaço vertical vazio.\n\n```typescript\nconst spacer = new Spacer(2);  // 2 empty lines\n```\n\n### Markdown\n\nRenderiza markdown com destaque de sintaxe.\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### Imagem\n\nRenderiza imagens em terminais suportados (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## Entrada de teclado\n\nUse `matchesKey()` para detecção de chave:\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**Identificadores de chave** (use `Key.*` para preenchimento automático ou literais de string):\n- Teclas básicas: `Key.enter`, `Key.escape`, `Key.tab`, `Key.space`, `Key.backspace`, `Key.delete`, `Key.home`, `Key.end`\n- Teclas de seta: `Key.up`, `Key.down`, `Key.left`, `Key.right`\n- Com modificadores: `Key.ctrl(\"c\")`, `Key.shift(\"tab\")`, `Key.alt(\"left\")`, `Key.ctrlShift(\"p\")`\n- O formato de string também funciona: `\"enter\"`, `\"ctrl+c\"`, `\"shift+tab\"`, `\"ctrl+shift+p\"`\n\n## Largura da linha\n\n**Crítico:** Cada linha de `render()` não deve exceder o parâmetro `width`.\n\n```typescript\nimport { visibleWidth, truncateToWidth } from \"@earendil-works/pi-tui\";\n\nrender(width: number): string[] {\n  // Truncate long lines\n  return [truncateToWidth(this.text, width)];\n}\n```\n\nUtilitários:\n- `visibleWidth(str)` - Obtenha largura de exibição (ignora códigos ANSI)\n- `truncateToWidth(str, width, ellipsis?)` - Truncar com reticências opcionais\n- `wrapTextWithAnsi(str, width)` - Quebra de linha preservando códigos ANSI\n\n## Criando componentes personalizados\n\nExemplo: seletor interativo\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\nUso em uma extensão:\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## Tema\n\nOs componentes aceitam objetos de tema para estilização.\n\n**Em `renderCall`/`renderResult`**, use o parâmetro `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**Cores de primeiro plano** (`theme.fg(color, text)`):\n\n| Categoria | Cores |\n|----------|--------|\n| Em geral | `text`, `accent`, `muted`, `dim` |\n| Status | `success`, `error`, `warning` |\n| Fronteiras | `border`, `borderAccent`, `borderMuted` |\n| Mensagens | `userMessageText`, `customMessageText`, `customMessageLabel` |\n| Ferramentas | `toolTitle`, `toolOutput` |\n| Diferenças | `toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext` |\n| Markdown | `mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet` |\n| Sintaxe | `syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation` |\n| Pensamento | `thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `thinkingMax` |\n| Modos | `bashMode` |\n\n**Cores de fundo** (`theme.bg(color, text)`):\n\n`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`\n\n**Para Markdown**, use `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**Para componentes personalizados**, defina sua própria interface de tema:\n\n```typescript\ninterface MyTheme {\n  selected: (s: string) => string;\n  normal: (s: string) => string;\n}\n```\n\n## Registro de depuração\n\nDefina `PI_TUI_WRITE_LOG` para capturar o fluxo ANSI bruto gravado em stdout.\n\n```bash\nPI_TUI_WRITE_LOG=/tmp/tui-ansi.log npx tsx packages/tui/test/chat-simple.ts\n```\n\n## Desempenho\n\nArmazene em cache a saída renderizada quando possível:\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\nChame `invalidate()` quando o estado mudar e use o `tui.requestRender()` injetado para acionar a nova renderização.\n\n## Invalidação e alterações de tema\n\nQuando o tema muda, TUI chama `invalidate()` em todos os componentes para limpar seus caches. Os componentes devem implementar `invalidate()` adequadamente para garantir que as alterações do tema entrem em vigor.\n\n### O problema\n\nSe um componente pré-incorpora as cores do tema em strings (via `theme.fg()`, `theme.bg()`, etc.) e as armazena em cache, as strings armazenadas em cache contêm códigos de escape ANSI do tema antigo. Simplesmente limpar o cache de renderização não é suficiente se o componente armazena o conteúdo temático separadamente.\n\n**Abordagem errada** (as cores do tema não serão atualizadas):\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### A solução\n\nOs componentes que criam conteúdo com cores de tema devem reconstruir esse conteúdo quando `invalidate()` for chamado:\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### Padrão: reconstruir ao invalidar\n\nPara componentes com conteúdo complexo:\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### Quando isso importa\n\nEste padrão é necessário quando:\n\n1. **Cores do tema pré-preparado** - Usando `theme.fg()` ou `theme.bg()` para criar strings estilizadas armazenadas em componentes filhos\n2. **Destaque de sintaxe** - Usando `highlightCode()` que aplica cores de sintaxe baseadas em tema\n3. **Layouts complexos** – Criação de árvores de componentes filhos que incorporam cores de tema\n\nEste padrão NÃO é necessário quando:\n\n1. **Usando callbacks de tema** - Passando funções como `(text) => theme.fg(\"accent\", text)` que são chamadas durante a renderização\n2. **Contêineres simples** - Apenas agrupando outros componentes sem adicionar conteúdo temático\n3. **Renderização sem estado** - Computando saída temática atualizada em cada chamada `render()` (sem cache)\n\n## Padrões Comuns\n\nEsses padrões cobrem as necessidades de UI mais comuns em extensões. **Copie esses padrões em vez de criar do zero.**\n\n### Padrão 1: Caixa de diálogo de seleção (SelectList)\n\nPara permitir que os usuários escolham em uma lista de opções. Use `SelectList` de `@earendil-works/pi-tui` com `DynamicBorder` para enquadramento.\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**Exemplos:** [preset.ts](../examples/extensions/preset.ts), [tools.ts](../examples/extensions/tools.ts)\n\n### Padrão 2: Operação Assíncrona com Cancel (BorderedLoader)\n\nPara operações que demoram e devem ser canceláveis. `BorderedLoader` mostra um botão giratório e controla o escape para cancelar.\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**Exemplos:** [qna.ts](../examples/extensions/qna.ts), [handoff.ts](../examples/extensions/handoff.ts)\n\n### Padrão 3: Configurações/Alterações (SettingsList)\n\nPara alternar várias configurações. Use `SettingsList` de `@earendil-works/pi-tui` com `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**Exemplos:** [tools.ts](../examples/extensions/tools.ts)\n\n### Padrão 4: Indicador de Status Persistente\n\nMostra o status no rodapé que persiste nas renderizações. Bom para indicadores de modo.\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**Exemplos:** [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### Padrão 4b: Personalização do Indicador de Trabalho\n\nPersonalize o indicador de trabalho embutido mostrado enquanto pi está transmitindo uma resposta.\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\nIsso afeta apenas o indicador normal de funcionamento do streaming. Os carregadores de compactação e nova tentativa mantêm seu estilo integrado. Os quadros personalizados são renderizados literalmente, portanto as extensões devem adicionar suas próprias cores quando necessário.\n\n**Exemplos:** [working-indicator.ts](../examples/extensions/working-indicator.ts)\n\n### Padrão 5: Widgets acima/abaixo do editor\n\nMostre conteúdo persistente acima ou abaixo do editor de entrada. Bom para listas de tarefas, progresso.\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**Exemplos:** [plan-mode/index.ts](../examples/extensions/plan-mode/index.ts)\n\n### Padrão 6: rodapé personalizado\n\nSubstitua o rodapé. `footerData` expõe dados que de outra forma não seriam acessíveis às extensões.\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\nEstatísticas de token disponíveis via `ctx.sessionManager.getBranch()` e `ctx.model`.\n\n**Exemplos:** [custom-footer.ts](../examples/extensions/custom-footer.ts)\n\n### Padrão 7: Editor Personalizado (modo vim, etc.)\n\nSubstitua o editor de entrada principal por uma implementação customizada. Útil para edição modal (vim), diferentes combinações de teclas (emacs) ou manipulação de entrada especializada.\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**Pontos principais:**\n\n- **Estenda `CustomEditor`** (não base `Editor`) para obter atalhos de teclado do aplicativo (escape para abortar, ctrl+d para sair, troca de modelo, etc.)\n- **Ligue para `super.handleInput(data)`** para chaves que você não manuseia\n- **Padrão de fábrica**: `setEditorComponent` recebe uma função de fábrica que obtém `tui`, `theme` e `keybindings`\n- **Passe `undefined`** para restaurar o editor padrão: `ctx.ui.setEditorComponent(undefined)`\n\n**Exemplos:** [modal-editor.ts](../examples/extensions/modal-editor.ts)\n\n## Regras principais\n\n1. **Sempre use o tema do retorno de chamada** - Não importe o tema diretamente. Use `theme` no retorno de chamada `ctx.ui.custom((tui, theme, keybindings, done) =>...)`.\n\n2. **Sempre digite parâmetro de cor DynamicBorder** - Escreva `(s: string) => theme.fg(\"accent\", s)`, não `(s) => theme.fg(\"accent\", s)`.\n\n3. **Chame tui.requestRender() após mudanças de estado** - Em `handleInput`, chame `tui.requestRender()` após atualizar o estado.\n\n4. **Retorne o objeto de três métodos** - Os componentes personalizados precisam de `{ render, invalidate, handleInput }`.\n\n5. **Use componentes existentes** - `SelectList`, `SettingsList`, `BorderedLoader` cobrem 90% dos casos. Não os reconstrua.\n\n## Exemplos\n\n- **IU de seleção**: [examples/extensions/preset.ts](../examples/extensions/preset.ts) - SelectList com enquadramento DynamicBorder\n- **Assíncrono com cancelamento**: [examples/extensions/qna.ts](../examples/extensions/qna.ts) - BorderedLoader para chamadas LLM\n- **Alterações de configurações**: [examples/extensions/tools.ts](../examples/extensions/tools.ts) - Lista de configurações para ativar/desativar ferramentas\n- **Indicadores de status**: [examples/extensions/plan-mode/index.ts](../examples/extensions/plan-mode/index.ts) - setStatus e setWidget\n- **Indicador de funcionamento**: [examples/extensions/working-indicator.ts](../examples/extensions/working-indicator.ts) - setWorkingIndicator\n- **Rodapé personalizado**: [examples/extensions/custom-footer.ts](../examples/extensions/custom-footer.ts) - setFooter com estatísticas\n- **Editor personalizado**: [examples/extensions/modal-editor.ts](../examples/extensions/modal-editor.ts) - Edição modal semelhante ao Vim\n- **Jogo Snake**: [examples/extensions/snake.ts](../examples/extensions/snake.ts) - Jogo completo com entrada de teclado, loop de jogo\n- **Renderização de ferramenta personalizada**: [examples/extensions/todo.ts](../examples/extensions/todo.ts) - renderCall e renderResult","sourceFile":"tui.md"},"usage":{"title":"Usando Pi","markdown":"Esta página coleta detalhes de uso diário que não cabem na página de início rápido.\n\n## Modo interativo\n\n<p align=\"center\"><img src=\"images/interactive-mode.png\" alt=\"Interactive Mode\" width=\"600\"></p>\n\nA interface possui quatro áreas principais:\n\n- **Cabeçalho de inicialização** - atalhos, context files, prompt templates carregados, habilidades e extensões\n- **Mensagens**: mensagens do usuário, respostas do assistente, chamadas de ferramentas, resultados de ferramentas, notificações, erros e UI de extensão\n- **Editor** - onde você digita; a cor da borda indica o nível de pensamento atual\n- **Rodapé** - diretório de trabalho, nome da sessão, uso de token/cache, custo, uso de contexto e modelo atual. Os totais incluem respostas do assistente, uso relatado por ferramentas e geração de resumo.\n\nO editor pode ser substituído temporariamente pela UI integrada, como `/settings` ou pela UI de extensão personalizada.\n\n### Recursos do editor\n\n| Recurso | Como |\n|---------|-----|\n| Referência de arquivo | Digite `@` para pesquisar arquivos de projeto de forma difusa |\n| Conclusão do caminho | Pressione Tab para completar caminhos |\n| Entrada multilinha | Shift+Enter ou Ctrl+Enter no Terminal do Windows |\n| Copiar resposta | Ctrl+X copia a última mensagem do assistente; em `/tree`, copia a mensagem selecionada |\n| Imagens | Cole com Ctrl+V, Alt+V no Windows ou arraste para o terminal |\n| Comando shell | `!command` executa e envia saída para o modelo |\n| Comando de shell oculto | `!!command` é executado sem enviar saída para o modelo |\n| Editor externo | Ctrl+G abre `externalEditor`, `$VISUAL`, `$EDITOR`, Bloco de notas no Windows ou `nano` em outro lugar |\n\nConsulte [Keybindings](keybindings.md) para todos os atalhos e personalizações.\n\n## Comandos de barra\n\nDigite `/` no editor para abrir a conclusão do comando. Extensions pode registrar comandos personalizados, habilidades estão disponíveis como `/skill:name` e prompt templates expandem via `/templatename`.\n\n| Comando | Descrição |\n|---------|-------------|\n| `/login`, `/logout` | Gerenciar credenciais de chave OAuth ou API |\n| [`/llama`](llama-cpp.md) | Baixe, carregue e descarregue modelos de roteador llama.cpp |\n| `/model` | Trocar modelos |\n| `/scoped-models` | Ativar/desativar modelos para ciclismo Ctrl+P |\n| `/settings` | Nível de pensamento, tema, entrega de mensagens, transporte |\n| `/resume` | Pick de sessões anteriores |\n| `/new` | Iniciar uma nova sessão |\n| `/name <name>` | Definir nome de exibição da sessão |\n| `/session` | Mostrar arquivo de sessão, ID, mensagens, tokens e custo |\n| `/tree` | Vá para qualquer ponto da sessão e continue a partir daí |\n| `/trust` | Salve a decisão de confiança do projeto para sessões futuras |\n| `/fork` | Crie uma nova sessão a partir de uma mensagem de usuário anterior |\n| `/clone` | Duplicar o branch ativo atual em uma nova sessão |\n| `/compact [prompt]` | Contexto compacto manualmente, opcionalmente com instruções personalizadas |\n| `/copy` | Copie a última mensagem do assistente para a área de transferência |\n| `/export [file]` | Exportar sessão para HTML ou JSONL |\n| `/import <file>` | Importe e retome uma sessão de um arquivo JSONL |\n| `/share` | Carregar como essência GitHub privada com link HTML compartilhável |\n| `/reload` | Recarregue atalhos de teclado, extensões, habilidades, prompts, temas e context files |\n| `/hotkeys` | Mostrar todos os atalhos de teclado |\n| `/changelog` | Exibir histórico de versões |\n| `/quit` | Sair do pi |\n\n## Fila de mensagens\n\nVocê pode enviar mensagens enquanto o agente ainda está trabalhando:\n\n- **Enter** coloca uma mensagem de direção na fila, entregue após o turno do assistente atual terminar de executar suas chamadas de ferramenta.\n- **Alt+Enter** coloca uma mensagem de acompanhamento na fila, entregue depois que o agente termina todo o trabalho.\n- **Escape** aborta e restaura mensagens enfileiradas no editor.\n- **Alt+Up** recupera mensagens na fila de volta para o editor.\n\nNo Terminal Windows, Alt+Enter fica em tela cheia por padrão. Remapeie-o conforme descrito em [Terminal setup](terminal-setup.md) se desejar que pi receba o atalho.\n\nConfigure a entrega em [Settings](settings.md) com `steeringMode` e `followUpMode`.\n\n## Sessões\n\nAs sessões são salvas automaticamente em `~/.pi/agent/sessions/`, organizadas por diretório de trabalho.\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\nComandos de sessão úteis:\n\n- `/session` mostra o arquivo e ID da sessão atual.\n- `/tree` navega no arquivo session tree e pode resumir ramificações abandonadas.\n- `/fork` cria uma nova sessão a partir de uma mensagem anterior do usuário.\n- `/clone` duplica o branch ativo atual em um novo arquivo de sessão.\n- `/compact` resume mensagens mais antigas para liberar contexto.\n\nConsulte [Sessions](sessions.md) e [Compaction](compaction.md) para obter detalhes.\n\n## Arquivos de Contexto\n\nPi carrega `AGENTS.md` ou `CLAUDE.md` na inicialização de:\n\n- `~/.pi/agent/AGENTS.md` para instruções globais\n- diretórios pai, subindo do diretório de trabalho atual\n- o diretório atual\n\nSe um diretório contém `AGENTS.override.md`, Pi carrega-o em vez de `AGENTS.md` ou `CLAUDE.md` desse diretório. Arquivos de contexto de outros diretórios ainda estão em camadas normalmente.\n\nUse context files para convenções, comandos, regras de segurança e preferências do projeto. Desative o carregamento com `--no-context-files` ou `-nc`.\n\n### Arquivos de prompt do sistema\n\nSubstitua o prompt padrão do sistema por:\n\n- `.pi/SYSTEM.md` para um projeto\n- `~/.pi/agent/SYSTEM.md` globalmente\n\nAnexe ao prompt padrão sem substituí-lo por `APPEND_SYSTEM.md` em qualquer local.\n\n### Confiança do Projeto\n\nNa inicialização interativa, pi pergunta antes de confiar em uma pasta de projeto que contém configurações locais do projeto, recursos ou projeto `.agents/skills` e não tem decisão salva para a pasta ou pasta pai em `~/.pi/agent/trust.json`. Confiar em um projeto permite que pi carregue recursos `.pi/settings.json` e `.pi`, instale pacotes de projeto ausentes e execute extensões de projeto.\n\nAntes da decisão de confiança, pi carrega apenas context files, extensões de usuário/globais e CLI `-e` extensões para que possam lidar com o evento `project_trust`. Extensões locais do projeto, extensões gerenciadas por pacote de projeto e configurações do projeto são carregadas somente depois que o projeto é confiável. Essa divisão também se aplica ao alternar para uma sessão de um cwd diferente cuja confiança não foi resolvida no processo atual.\n\nOs modos não interativos (`-p`, `--mode json` e `--mode rpc`) não mostram um prompt de confiança. Sem uma decisão de confiança salva aplicável, eles usam `defaultProjectTrust` das configurações globais: `ask` (padrão) e `never` ignoram esses recursos do projeto, enquanto `always` confia neles. Passe `--approve`/`-a` ou `--no-approve`/`-na` para substituir a confiança do projeto em uma execução.\n\nSe nenhuma extensão ou decisão salva se aplicar, `defaultProjectTrust` controla o comportamento de fallback. Defina-o como `\"ask\"`, `\"always\"` ou `\"never\"` em `~/.pi/agent/settings.json` ou altere-o com `/settings`.\n\nOs comandos `pi config` e pacote usam o mesmo fluxo de confiança do projeto, exceto que `pi update` nunca solicita. Passe `--approve` para confiar nas configurações locais do projeto para um comando ou `--no-approve` para ignorá-las.\n\nUse `/trust` no modo interativo para salvar uma decisão de confiança do projeto para sessões futuras, incluindo confiança para a pasta pai imediata. Ele escreve apenas `~/.pi/agent/trust.json`; a sessão atual não é recarregada, então reinicie o pi para que as alterações tenham efeito.\n\n\n## Exportando e compartilhando sessões\n\nUse `/export [file]` para escrever uma sessão em HTML.\n\nUse `/share` para fazer upload de uma essência GitHub privada com um link HTML compartilhável.\n\nSe você usa pi para trabalho de código aberto e deseja publicar sessões para pesquisa de modelo, prompt, ferramenta e avaliação, consulte [`badlogic/pi-share-hf`](https://github.com/badlogic/pi-share-hf). Publica sessões em conjuntos de dados Hugging Face.\n\n## CLI Referência\n\n```bash\npi [options] [@files...] [messages...]\n```\n\n### Comandos de pacote\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\nEsses comandos gerenciam pacotes pi e `pi update` podem atualizar a instalação do pi CLI. Para desinstalar o próprio pi, consulte [Quickstart](quickstart.md#uninstall). Os comandos `pi config` e pacote de projeto aceitam `--approve`/`--no-approve` para confiar ou ignorar as configurações locais do projeto para um comando. `pi update` nunca solicita confiança no projeto.\n\nVeja [Pi Packages](packages.md) para fontes de pacotes e notas de segurança.\n\n### Modos\n\n| Bandeira | Descrição |\n|------|-------------|\n| padrão | Modo interativo |\n| `-p`, `--print` | Imprimir resposta e sair |\n| `--mode json` | Produza todos os eventos como JSON linhas; veja [JSON mode](json.md) |\n| `--mode rpc` | Modo RPC acima de stdin/stdout; veja [RPC mode](rpc.md) |\n| `--export <in> [out]` | Exportar uma sessão para HTML |\n\nNo modo de impressão, pi também lê canalizado stdin e o mescla no prompt inicial:\n\n```bash\ncat README.md | pi -p \"Summarize this text\"\n```\n\n### Opções de modelo\n\n| Opção | Descrição |\n|--------|-------------|\n| `--provider <name>` | Provedor, como `anthropic`, `openai` ou `google` |\n| `--model <pattern>` | Padrão ou ID do modelo; suporta `provider/id` e opcional `:<thinking>` |\n| `--api-key <key>` | API key, substituindo variáveis ​​de ambiente |\n| `--thinking <level>` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max` |\n| `--models <patterns>` | Padrões separados por vírgula para ciclismo Ctrl+P |\n| `--list-models [search]` | Listar modelos disponíveis |\n\n### Opções de sessão\n\n| Opção | Descrição |\n|--------|-------------|\n| `-c`, `--continue` | Continuar a sessão mais recente |\n| `-r`, `--resume` | Navegue e selecione uma sessão |\n| `--sessão <caminho\\ | id>` | Use um arquivo de sessão específico ou UUID parcial |\n| `--fork <caminho\\ | id>` | Bifurque um arquivo de sessão ou UUID parcial em uma nova sessão |\n| `--session-dir <dir>` | Diretório de armazenamento de sessão personalizado |\n| `--no-session` | Modo efêmero; não salve |\n| `--name <name>`, `-n <name>` | Definir o nome de exibição da sessão na inicialização |\n\n### Opções de ferramentas\n\n| Opção | Descrição |\n|--------|-------------|\n| `--tools <list>`, `-t <list>` | Lista de permissões específicas de ferramentas integradas, de extensão e personalizadas |\n| `--exclude-tools <list>`, `-xt <list>` | Desative ferramentas específicas integradas, de extensão e personalizadas |\n| `--no-builtin-tools`, `-nbt` | Desative as ferramentas integradas, mas mantenha as ferramentas de extensão/personalizadas ativadas |\n| `--no-tools`, `-nt` | Desative todas as ferramentas |\n\nFerramentas integradas: `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`.\n\n### Opções de recursos\n\n| Opção | Descrição |\n|--------|-------------|\n| `-e`, `--extension <source>` | Carregue uma extensão do caminho, npm ou git; repetível |\n| `--no-extensions` | Desativar descoberta de extensão |\n| `--skill <path>` | Carregue uma habilidade; repetível |\n| `--no-skills` | Desativar descoberta de habilidades |\n| `--prompt-template <path>` | Carregue um modelo de prompt; repetível |\n| `--no-prompt-templates` | Desativar descoberta de modelo de prompt |\n| `--theme <path>` | Carregue um tema; repetível |\n| `--no-themes` | Desativar descoberta de tema |\n| `--no-context-files`, `-nc` | Desativar descoberta `AGENTS.md` e `CLAUDE.md` |\n\nCombine `--no-*` com sinalizadores explícitos para carregar exatamente o que você precisa, ignorando as configurações. Exemplo:\n\n```bash\npi --no-extensions -e ./my-extension.ts\n```\n\n### Outras opções\n\n| Opção | Descrição |\n|--------|-------------|\n| `--system-prompt <text>` | Substitua o prompt padrão; context files e habilidades ainda estão anexadas |\n| `--append-system-prompt <text>` | Anexar ao prompt do sistema |\n| `--tui-mode <mode>` | Modo TUI: `regular` (padrão) ou experimental `fullscreen` |\n| `--verbose` | Forçar inicialização detalhada |\n| `-a`, `--approve` | Confie nos arquivos locais do projeto para esta execução |\n| `-na`, `--no-approve` | Ignore os arquivos locais do projeto para esta execução |\n| `-h`, `--help` | Mostrar ajuda |\n| `-v`, `--version` | Mostrar versão |\n\nNo modo `fullscreen`, a transcrição rola dentro da janela de visualização do terminal enquanto as mensagens na fila, o status de trabalho, os widgets de extensão, o editor e o rodapé permanecem fixos na parte inferior. A entrada do mouse/trackpad rola a região sob o ponteiro; as ações da janela de visualização do teclado sempre permanecem disponíveis. As imagens embutidas funcionam em terminais que suportam o protocolo gráfico Kitty, incluindo Kitty e Ghostty. No iTerm2, eles são renderizados como espaços reservados para texto porque seu protocolo de imagem embutido não pode excluir ou cortar posicionamentos durante a rolagem do aplicativo. No modo `regular`, pi usa a tela principal e a rolagem do terminal, e as imagens embutidas do iTerm2 continuam a renderizar normalmente.\n\nDefina o modo **TUI** em `/settings` para alternar entre `regular` e `fullscreen` imediatamente e escolha o padrão para sessões futuras. **Saída de saída em tela cheia** controla se sair da tela cheia imprime a transcrição final ou restaura a tela anterior e imprime apenas a dica de retomada da sessão.\n\n### Argumentos de arquivo\n\nPrefixe os arquivos com `@` para incluí-los na mensagem:\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### Exemplos\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## Princípios de Design\n\nPi mantém o núcleo pequeno e empurra o comportamento específico do fluxo de trabalho para extensões, habilidades, prompt templates e pacotes.\n\nIntencionalmente não inclui MCP integrado, subagentes, pop-ups de permissão, modo de plano, tarefas ou bash em segundo plano. Você pode criar ou instalar esses fluxos de trabalho como extensões ou pacotes, ou usar ferramentas externas, como contêineres e tmux.\n\nPara o raciocínio completo, leia o [blog post](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/).","sourceFile":"usage.md"},"windows":{"title":"Configuração do Windows","markdown":"Pi requer um shell bash no Windows. Locais verificados (em ordem):\n\n1. Caminho personalizado de `~/.pi/agent/settings.json`\n2. Git Bash (`C:\\Program Files\\Git\\bin\\bash.exe`)\n3. `bash.exe` no PATH (Cygwin, MSYS2, WSL)\n\nPara a maioria dos usuários, [Git for Windows](https://git-scm.com/download/win) é suficiente.\n\n## Caminho de shell personalizado\n\n```json\n{\n  \"shellPath\": \"C:\\\\cygwin64\\\\bin\\\\bash.exe\"\n}\n```","sourceFile":"windows.md"}}},"navigation":{"pt":[{"title":"Comece aqui","items":[{"title":"Pi Documentação","path":"/docs/latest","slug":"index"},{"title":"Início rápido","path":"/docs/latest/quickstart","slug":"quickstart"},{"title":"Usando Pi","path":"/docs/latest/usage","slug":"usage"},{"title":"Providers","path":"/docs/latest/providers","slug":"providers"},{"title":"Segurança","path":"/docs/latest/security","slug":"security"},{"title":"Conteinerização","path":"/docs/latest/containerization","slug":"containerization"},{"title":"Configurações","path":"/docs/latest/settings","slug":"settings"},{"title":"Atalhos de teclado","path":"/docs/latest/keybindings","slug":"keybindings"},{"title":"Sessões","path":"/docs/latest/sessions","slug":"sessions"},{"title":"Compactação e Resumo de Filiais","path":"/docs/latest/compaction","slug":"compaction"}]},{"title":"Personalização","items":[{"title":"Extensions","path":"/docs/latest/extensions","slug":"extensions"},{"title":"Skills","path":"/docs/latest/skills","slug":"skills"},{"title":"Modelos de prompt","path":"/docs/latest/prompt-templates","slug":"prompt-templates"},{"title":"Temas","path":"/docs/latest/themes","slug":"themes"},{"title":"Pi Packages","path":"/docs/latest/packages","slug":"packages"},{"title":"Personalizado Models","path":"/docs/latest/models","slug":"models"},{"title":"Personalizado Providers","path":"/docs/latest/custom-provider","slug":"custom-provider"}]},{"title":"Referência","items":[{"title":"Formato de arquivo de sessão","path":"/docs/latest/session-format","slug":"session-format"}]},{"title":"Uso programático","items":[{"title":"SDK","path":"/docs/latest/sdk","slug":"sdk"},{"title":"RPC Modo","path":"/docs/latest/rpc","slug":"rpc"},{"title":"JSON Modo de transmissão de eventos","path":"/docs/latest/json","slug":"json"},{"title":"TUI Componentes","path":"/docs/latest/tui","slug":"tui"}]},{"title":"Configuração da plataforma","items":[{"title":"Configuração do Windows","path":"/docs/latest/windows","slug":"windows"},{"title":"Termux (Android) Configuração","path":"/docs/latest/termux","slug":"termux"},{"title":"tmux Configuração","path":"/docs/latest/tmux","slug":"tmux"},{"title":"Configuração do terminal","path":"/docs/latest/terminal-setup","slug":"terminal-setup"},{"title":"Aliases de shell","path":"/docs/latest/shell-aliases","slug":"shell-aliases"}]},{"title":"Desenvolvimento","items":[{"title":"Desenvolvimento","path":"/docs/latest/development","slug":"development"}]}]}}
