|
| 1 | +--- |
| 2 | +title: Caminho Prático para Integrar Pi Agent ao HagiCode |
| 3 | +date: 2026-06-08 |
| 4 | +tags: [ai-integration, hagicode, pi-agent, thin-adapter, cli] |
| 5 | +--- |
| 6 | + |
| 7 | +## Caminho Prático para Integrar Pi Agent ao HagiCode |
| 8 | + |
| 9 | +> Este artigo compartilha nossa experiência prática na integração do Pi agent como um ponto de entrada de fluxo de trabalho de primeira classe no projeto HagiCode, incluindo design de arquitetura, implementação de componentes principais e detalhes de adaptação frontend e backend. |
| 10 | +
|
| 11 | +## Contexto |
| 12 | + |
| 13 | +Tudo começou com aquela pergunta: no projeto HagiCode Mono, embora `repos/Hagicode.Libs` já tenha implementado o `PiProvider` reutilizável, `repos/hagicode-core` e `repos/web` ainda não elevaram o Pi a um Agent CLI de primeira classe em nível de projeto. É como ter um bom par de sapatos mas ainda não ter amarrado os cadarços — você consegue caminhar, mas sempre sente que falta algo. |
| 14 | + |
| 15 | +O `PiProvider` existente está localizado em `HagiCode.Libs/src/HagiCode.Libs.Providers/Pi/PiProvider.cs`, ele implementa o protocolo de comunicação CLI baseado em JSON delimitado por linhas (`--mode json --print`), suportando gerenciamento de sessão, chamada de ferramentas e saída em streaming. Este código está bem escrito, apenas está lá adormecido, esperando ser acionado. |
| 16 | + |
| 17 | +Esta situação criou uma lacuna: as capacidades subjacentes do Pi estão completas, mas o caminho de integração em nível de projeto (da configuração do usuário ao monitoramento de execução) está ausente. É como ter pintado um bom quadro, mas não tê-lo pendurado na parede para apreciação. Portanto, precisamos integrar o Pi como um novo provider ativo `PiCli` em todo o sistema, tornando-o um ponto de entrada de fluxo de trabalho de primeira classe igual a outros Agentes CLI. |
| 18 | + |
| 19 | +Afinal, queremos uma experiência completa, não fragmentos dispersos. |
| 20 | + |
| 21 | +## Sobre HagiCode |
| 22 | + |
| 23 | +A solução compartilhada neste artigo vem de nossa experiência prática no projeto [HagiCode](https://hagicode.com). HagiCode é um projeto de assistente de código inteligente, e durante o desenvolvimento precisamos integrar múltiplos provedores de capacidades de IA. A solução de integração do Pi agent descrita neste artigo é exatamente um padrão de integração reutilizável que resumimos em nossa prática, com valor de referência para cenários semelhantes de integração de múltiplos providers. O endereço GitHub do projeto é [github.com/HagiCode-org/site](https://github.com/HagiCode-org/site). |
| 24 | + |
| 25 | +## Design de Arquitetura |
| 26 | + |
| 27 | +A integração do Pi agent adotou o **padrão thin adapter**, em vez de reimplementar o protocolo de processo Pi na camada core. Esta decisão de design não tem nada de complexa, é apenas pensar: por que reinventar a roda? |
| 28 | + |
| 29 | +Afinal: |
| 30 | + |
| 31 | +1. **Evitar reimplementação**: A lógica de construção de parâmetros, inicialização de processo, análise de JSON e tratamento de erros no `PiProvider` já está completa, reimplementar criaria duas fontes de comportamento e duas matrizes de teste. Tudo bem, mas a manutenção será problemática. |
| 32 | +2. **Manter consistência**: Mantém consistência com a forma de integração de CLIs existentes como Kimi, Gemini, Reasonix, DeepAgents, todos delegando para implementações na camada libs através de thin adapter. Todo mundo faz assim, só seguir o fluxo. |
| 33 | +3. **Separação de responsabilidades**: `hagicode-core` foca em contratos de runtime e lógica de negócio, detalhes de processo são deixados para `HagiCode.Libs` processar. Cada um com sua responsabilidade, não é perfeito? |
| 34 | + |
| 35 | +Este design permite que HagiCode mantenha a camada core concisa enquanto integra rapidamente novos provedores de capacidades de IA. Afinal, simplicidade é beleza, eficiência é dinheiro. |
| 36 | + |
| 37 | +## Implementação de Componentes Principais |
| 38 | + |
| 39 | +### Enumeração e Factory do Provider |
| 40 | + |
| 41 | +Em `hagicode-core/src/PCode.Models/AIProviderType.cs` foi adicionado o valor de enumeração `PiCli = 13`: |
| 42 | + |
| 43 | +```csharp |
| 44 | +public enum AIProviderType |
| 45 | +{ |
| 46 | + ClaudeCodeCli = 0, |
| 47 | + // ... outros providers |
| 48 | + PiCli = 13, |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +Este valor de enumeração é a raiz da identidade do provider, afetará a enumeração frontend `PCode_Models_AIProviderType.ts` gerada pelo OpenAPI, o branch de criação do `AIProviderFactory`, bem como a lógica de análise de Provider e julgamento de provider ativo. Registrando o novo branch de criação no `AIProviderFactory`: |
| 53 | + |
| 54 | +```csharp |
| 55 | +case AIProviderType.PiCli: |
| 56 | + provider = new PiCliProvider( |
| 57 | + logger, |
| 58 | + configuration, |
| 59 | + providerFactory.GetRequiredService<ICliProvider<PiOptions>>(), |
| 60 | + providerFactory.GetService<IAgentCliRuntimeEnvironmentResolver>()); |
| 61 | + break; |
| 62 | +``` |
| 63 | + |
| 64 | +Na verdade, este código não tem nada de especial, é apenas uma enumeração e um switch case. Mas eles são importantes, como notas em uma partitura, adicionadas uma por uma para tocar a melodia completa. |
| 65 | + |
| 66 | +### Implementação do Thin Adapter |
| 67 | + |
| 68 | +`PiCliProvider.cs` é o thin adapter central, ele implementa as interfaces `IAIProvider`, `IVersionedAIProvider` e `IAsyncDisposable`. Através do construtor recebe `ICliProvider<PiOptions>` (de `HagiCode.Libs`), mapeia `AIRequest` / `ProviderConfiguration` para `PiOptions`, depois delega execução, ping, consulta de versão e outras operações para o provider subjacente. |
| 69 | + |
| 70 | +O ponto chave é tratar o fluxo de eventos JSON específico do Pi, incluindo eventos como `assistant.thought`, `assistant`, `terminal.completed`, etc. Estes eventos precisam ser corretamente analisados e convertidos para o formato padrão do sistema durante o processo de saída em streaming. É um pouco como tradução, traduzir de um idioma para outro, o significado precisa ser transmitido adequadamente. |
| 71 | + |
| 72 | +### Preset de Profissão Principal |
| 73 | + |
| 74 | +Em `main-professions.yaml` foi adicionada a entrada de profissão principal `profession-pi`: |
| 75 | + |
| 76 | +```yaml |
| 77 | +- Id: "profession-pi" |
| 78 | + Name: "Pi" |
| 79 | + Family: "pi" |
| 80 | + Summary: "hero.professionCopy.primary.pi.summary" |
| 81 | + Icon: "executor-avatar:Pi" |
| 82 | + SourceLabel: "hero.professionCopy.sources.aiProvidersPiCli" |
| 83 | + ProviderType: "PiCli" |
| 84 | + SortOrder: 59 |
| 85 | + DefaultEnabled: true |
| 86 | + DefaultParameters: |
| 87 | + binary: "pi" |
| 88 | + provider: "omniroute" |
| 89 | + thinking: "balanced" |
| 90 | +``` |
| 91 | +
|
| 92 | +Isso garante que o snapshot backend e o diretório fallback frontend tenham identidade Pi consistente, podem gerenciar configuração Pi através do editor Hero existente, e a adição do Pi não quebrará a consumibilidade das entradas de profissão principal existentes. Afinal, não queremos quebrar as coisas existentes por adicionar uma nova funcionalidade, isso seria perder o essencial pelo trivial. |
| 93 | +
|
| 94 | +### Registro de Monitoramento |
| 95 | +
|
| 96 | +`AgentCliMonitoringRegistry` precisa adicionar o descriptor de monitoramento do Pi, permitindo que o sistema resolva caminhos executáveis, exiba nomes de marca, faça health checks, e exiba status do Pi na barra de status e detalhes de saúde: |
| 97 | + |
| 98 | +```csharp |
| 99 | +new AgentCliMonitoringDescriptor( |
| 100 | + CliId: "pi", |
| 101 | + DisplayName: "Pi", |
| 102 | + ProviderType: AIProviderType.PiCli, |
| 103 | + DisplayOrder: 13, |
| 104 | + Strategy: AgentCliMonitoringStrategy.Grain, |
| 105 | + NotConfiguredMessage: "Pi CLI is not configured or executable not found.", |
| 106 | + EnabledPaths: [], |
| 107 | + ExecutablePathConfigPaths: ["Hero:PrimaryProfessions:Pi:ExecutablePath"], |
| 108 | + DefaultExecutablePath: "pi") |
| 109 | +``` |
| 110 | + |
| 111 | +O sistema de monitoramento é como um painel de instrumentos, mostrando como o carro está rodando. O status do Pi fica claro a um olhar, para que os usuários saibam se tudo está normal. |
| 112 | + |
| 113 | +## Adaptação Frontend |
| 114 | + |
| 115 | +### Adaptação de Tipo de Executor |
| 116 | + |
| 117 | +O frontend precisa atualizar `executorTypeAdapter.ts`, adicionando lógica de identificação de tipo do Pi: |
| 118 | + |
| 119 | +```typescript |
| 120 | +const PI_IDS = new Set<string>([ |
| 121 | + PCode_Models_AIProviderType.PI_CLI, |
| 122 | + 'Pi', |
| 123 | + 'PiCli', |
| 124 | + 'pi', |
| 125 | + 'picli', |
| 126 | + 'pi-cli', |
| 127 | +]); |
| 128 | +
|
| 129 | +export function isPi(value: string): boolean { |
| 130 | + const normalized = normalize(value); |
| 131 | + const normalizedLower = normalized.toLowerCase(); |
| 132 | + return PI_IDS.has(normalized) |
| 133 | + || normalizedLower.includes('pi-cli') |
| 134 | + || normalizedLower.includes('picli') |
| 135 | + || normalizedLower === 'pi'; |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +É como dar ao Pi vários nomes, não importa como você o chama, ele sabe que você está chamando ele. Afinal, o nome não é importante, o importante é saber quem você é. |
| 140 | + |
| 141 | +### Diretório Fallback do Hero |
| 142 | + |
| 143 | +Em `hero.ts` adicionar entrada fallback do Pi, garantindo que mesmo se os dados backend não forem carregados, o frontend possa exibir normalmente a configuração Pi: |
| 144 | + |
| 145 | +```typescript |
| 146 | +{ |
| 147 | + id: "profession-pi", |
| 148 | + name: "Pi", |
| 149 | + family: "pi", |
| 150 | + summary: "hero.professionCopy.primary.pi.summary", |
| 151 | + icon: "executor-avatar:Pi", |
| 152 | + sourceLabel: "hero.professionCopy.sources.aiProvidersPiCli", |
| 153 | + providerType: AIProviderType.PI_CLI, |
| 154 | + sortOrder: 59, |
| 155 | + isReadOnly: true, |
| 156 | + managedParameterKeys: { |
| 157 | + // chaves de parâmetro suportadas na primeira versão |
| 158 | + }, |
| 159 | + defaultParameters: { |
| 160 | + binary: "pi", |
| 161 | + provider: "omniroute", |
| 162 | + thinking: "balanced", |
| 163 | + }, |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +Fallback é como um plano de contingência, caso o backend caia ou os dados não sejam carregados, o frontend ainda pode funcionar normalmente. Afinal, ninguém quer entrar em pânico quando isso acontecer. |
| 168 | + |
| 169 | +### Textos Localizados e Formulários |
| 170 | + |
| 171 | +Em `locales/*/common/{hero,settings}.yml` adicionar traduções relacionadas ao Pi, e em `HeroCliEquipmentForm.tsx` adicionar seção de campos de configuração para o Pi, suportando campos binary, provider, thinking, sessionDirectory e chaves de ferramenta/sessão. |
| 172 | + |
| 173 | +A primeira versão do Pi expõe apenas campos minimamente necessários, funcionalidades complexas como allowlist/denylist de ferramentas e editor de variáveis de ambiente foram explicitamente adiadas para mudanças subsequentes. Afinal, não se deve comer tudo de uma vez, devagar se vai mais longe. |
| 174 | + |
| 175 | +## Exemplos de Configuração |
| 176 | + |
| 177 | +### Configuração Backend |
| 178 | + |
| 179 | +Configurar parâmetros do Pi em `appsettings.yml`: |
| 180 | + |
| 181 | +```yaml |
| 182 | +Hero: |
| 183 | + PrimaryProfessions: |
| 184 | + Pi: |
| 185 | + ExecutablePath: /usr/local/bin/pi |
| 186 | + Provider: omniroute |
| 187 | + Thinking: balanced |
| 188 | + SessionDirectory: ~/.pi/sessions |
| 189 | + NoSession: false |
| 190 | + DisableAllTools: false |
| 191 | + DisableBuiltinTools: false |
| 192 | +``` |
| 193 | + |
| 194 | +### Configuração Frontend |
| 195 | + |
| 196 | +Configurar profissão do Pi no editor Hero: |
| 197 | + |
| 198 | +```typescript |
| 199 | +{ |
| 200 | + id: "my-pi-profession", |
| 201 | + name: "My Pi Profession", |
| 202 | + family: "pi", |
| 203 | + providerType: AIProviderType.PI_CLI, |
| 204 | + primaryModel: { |
| 205 | + provider: "PiCli", |
| 206 | + model: "glm-4.7", |
| 207 | + providerSettings: { |
| 208 | + provider: "omniroute", |
| 209 | + thinking: "balanced", |
| 210 | + sessionDirectory: "/Users/username/.pi/sessions", |
| 211 | + noSession: false, |
| 212 | + disableAllTools: false, |
| 213 | + disableBuiltinTools: false, |
| 214 | + }, |
| 215 | + }, |
| 216 | +} |
| 217 | +``` |
| 218 | + |
| 219 | +Arquivos de configuração são como receitas, seguindo você faz um bom prato. Às vezes, mesmo seguindo a receita, você pode queimar a comida... mas desta vez a configuração está clara, não deve haver problemas. |
| 220 | + |
| 221 | +## Validação e Testes |
| 222 | + |
| 223 | +### Validação Backend |
| 224 | + |
| 225 | +Validar preset de profissão principal em testes: |
| 226 | + |
| 227 | +```csharp |
| 228 | +var snapshot = await presetProvider.GetSnapshotAsync(); |
| 229 | +var piProfession = snapshot.FindById("profession-pi"); |
| 230 | +piProfession.ShouldNotBeNull(); |
| 231 | +piProfession.ProviderType.ShouldBe(AIProviderType.PiCli); |
| 232 | +piProfession.Family.ShouldBe("pi"); |
| 233 | +``` |
| 234 | + |
| 235 | +Também precisa validar disponibilidade do Pi e health check: |
| 236 | + |
| 237 | +```bash |
| 238 | +# Verificar executável do Pi |
| 239 | +which pi |
| 240 | +pi --version |
| 241 | +
|
| 242 | +# Validar registro do provider backend |
| 243 | +curl http://localhost:35168/api/health/agent-cli/pi |
| 244 | +``` |
| 245 | + |
| 246 | +Testes são como exames, passar prova que você realmente sabe. Às vezes, mesmo passando no exame não significa que você realmente entende, mas pelo menos mostra que você consegue acertar as questões. |
| 247 | + |
| 248 | +### Validação Frontend |
| 249 | + |
| 250 | +```typescript |
| 251 | +// Validar análise de tipo e nome de exibição |
| 252 | +expect(resolveExecutorVisualType('pi-cli')).toBe('Pi'); |
| 253 | +expect(resolveExecutorVisualType(PCode_Models_AIProviderType.PI_CLI)).toBe('Pi'); |
| 254 | +expect(resolveExecutorDisplayName('PiCli')).toBe('Pi'); |
| 255 | +
|
| 256 | +// Validar diretório fallback |
| 257 | +const piFallback = findFallbackProfessionById('profession-pi'); |
| 258 | +expect(piFallback?.providerType).toBe(AIProviderType.PI_CLI); |
| 259 | +``` |
| 260 | + |
| 261 | +Estes casos de teste cobrem os principais caminhos lógicos, garantindo que o Pi possa ser corretamente identificado e exibido. Afinal, o que é exibido aos usuários não pode ter erros. |
| 262 | + |
| 263 | +## Resolução de Problemas Comuns |
| 264 | + |
| 265 | +### Executável do Pi Não Encontrado |
| 266 | + |
| 267 | +Se o health check retornar "Pi executable was not found.", precisa verificar se o pi está no PATH, ou confirmar se o caminho configurado está correto. A solução é garantir que `pi` esteja instalado e no PATH, ou configurar o `ExecutablePath` correto em `appsettings.yml`. |
| 268 | + |
| 269 | +É como não encontrar a chave de casa, precisa pensar se colocou no lugar errado. Na verdade, a solução é simples, ou colocar a chave de volta no lugar original, ou trocar a fechadura. |
| 270 | + |
| 271 | +### Campos de Configuração Não Reconhecidos |
| 272 | + |
| 273 | +Se durante a inicialização for lançado o erro "PiCli runtime settings [...] are not supported", verificar se estão usando apenas campos de configuração suportados na primeira versão. Os campos suportados na primeira versão são: `provider`, `thinking`, `sessionDirectory`, `noSession`, `disableAllTools`, `disableBuiltinTools`. |
| 274 | + |
| 275 | +Às vezes é ganância, querer muitas funcionalidades, resultado o sistema não suporta. Na verdade, as funcionalidades da primeira versão já são suficientes, querer demais é ineficiente. |
| 276 | + |
| 277 | +### Não Conseguir Selecionar Pi no Frontend |
| 278 | + |
| 279 | +Se não houver opção Pi no editor Hero, verificar se executou `npm run generate:api` para regenerar as enumerações frontend, se há entrada `profession-pi` em `hero.ts`, e se os textos localizados foram corretamente adicionados. |
| 280 | + |
| 281 | +Resolver problemas é como procurar objetos perdidos, precisa ser passo a passo. Afinal, procurar aleatoriamente não encontra, precisa ter lógica. |
| 282 | + |
| 283 | +## Melhores Práticas |
| 284 | + |
| 285 | +1. **Usar padrão thin adapter**: Não reimplemente protocolos de processo na camada core, delegue para providers da camada libs. Isso evita reimplementação, mantém consistência de código. Afinal, reinventar a roda não é apenas cansativo, também propenso a problemas. |
| 286 | + |
| 287 | +2. **Manter consistência de nomenclatura**: Use convenções de nomenclatura unificadas entre frontend e backend, evitando confusão. Enumeração Provider usa `PiCli`, CLI ID usa `"pi"`, nome de exibição usa `"Pi"`. Bons nomes reduzem custos de comunicação. |
| 288 | + |
| 289 | +3. **Priorizar presets**: A primeira versão deve ser baseada no preset `profession-pi`, em vez de exigir configuração manual dos usuários. Isso permite que os usuários comecem rapidamente, reduzindo complexidade de configuração. Usuários gostam de coisas simples, deixem o complexo para mim. |
| 290 | + |
| 291 | +4. **Focar em mensagens de erro**: Garanta que mensagens de erro sejam claras e acionáveis, ajudando os usuários a localizar rapidamente os problemas. Mensagens de erro bem escritas impedem que os usuários entrem em pânico por um erro. |
| 292 | + |
| 293 | +5. **Compatibilidade de versão**: Considerando a estabilidade de serialização dos valores de enumeração `AIProviderType`, mudanças precisam ser tratadas com cautela. O valor de enumeração `AIProviderType.PiCli = 13` não pode ser facilmente modificado. Afinal, mudar este valor pode quebrar compatibilidade reversa, o que seria problemático. |
| 294 | + |
| 295 | +## Conclusão |
| 296 | + |
| 297 | +Através do padrão thin adapter, integramos com sucesso o Pi agent no sistema HagiCode, tornando-o um ponto de entrada de fluxo de trabalho de primeira classe igual a outros Agentes CLI. As vantagens principais desta solução são: |
| 298 | + |
| 299 | +- Evita reimplementação, reutiliza o `PiProvider` existente na camada libs |
| 300 | +- Mantém forma de integração consistente com providers existentes, reduzindo custos de manutenção |
| 301 | +- Implementa o caminho completo da configuração do usuário ao monitoramento de execução |
| 302 | + |
| 303 | +Esta prática do HagiCode prova que o padrão thin adapter é uma solução eficaz para integrar provedores de capacidades de IA. Ele nos permite suportar rapidamente novos agentes, mantendo estabilidade e capacidade de manutenção do sistema. |
| 304 | + |
| 305 | +Na verdade, fazer tecnologia é assim, encontrar um bom padrão, então reutilizá-lo. Assim você pode avançar rapidamente sem se perder. É como caminhar, encontrou um bom caminho, continua seguindo, apenas ocasionalmente para para apreciar a paisagem... |
| 306 | + |
| 307 | +Se você também está fazendo integração de capacidades de IA de múltiplos providers, espero que esta solução possa te dar alguma inspiração. Se você está interessado no projeto HagiCode, bem-vindo ao GitHub para trocar ideias. Afinal, tecnologia é algo que progredimos mais com troca. |
| 308 | + |
| 309 | +## Referências |
| 310 | + |
| 311 | +- [Repositório GitHub do HagiCode](https://github.com/HagiCode-org/site) |
| 312 | +- [Site Oficial do HagiCode](https://hagicode.com) |
| 313 | +- [Guia de Instalação do HagiCode](https://docs.hagicode.com/installation/docker-compose) |
| 314 | +- [Documentação do Pi Agent](https://github.com/earendil-works/pi-coding-agent) |
| 315 | + |
| 316 | +## Conclusão |
| 317 | + |
| 318 | +Em torno de "Caminho Prático para Integrar Pi Agent ao HagiCode", uma forma mais estável de avanço é primeiro fazer funcionar progressivamente configurações críticas, limites de dependência e caminhos de implementação, depois completar detalhes de otimização. |
| 319 | + |
| 320 | +Quando os objetivos, passos e pontos de aceitação estão claros, este tipo de solução geralmente pode entrar mais suavemente na entrega real. |
0 commit comments