Início/Trilha 3 · Técnicas Avançadas/Módulo 3.2
MÓDULO 3.2

🔗 Agent Client Protocol (ACP) & adapters

JSON-RPC sobre stdio bidirecional — "LSP para coding agents". 6 famílias de stream format e como adicionar um adapter novo.

7
Tópicos
~60 min
Duração
Avançado
Nível
11 CLIs
Suportados

🏁 Resultado: Você entende o ACP, reconhece as 6 famílias de stream format, e adiciona um adapter novo ao OD.

1

📡 O que é ACP — JSON-RPC 2.0 sobre stdio

Bidirecional, simétrico, language-agnostic.

O que é

Agent Client Protocol é um protocolo proposto em agentclientprotocol.com para padronizar comunicação entre IDE/orchestrator e agente de coding. JSON-RPC 2.0 sobre stdio (stdin/stdout do processo do agente), bidirecional (cliente→agente e agente→cliente), simétrico (cada lado pode iniciar request).

// cliente envia
{"jsonrpc":"2.0","id":1,"method":"agent/run","params":{"prompt":"..."}}

// agente streams
{"jsonrpc":"2.0","method":"output/append","params":{"chunk":"Hello"}}
{"jsonrpc":"2.0","method":"todo/update","params":{"items":[...]}}

Por que aprender

É a aposta da indústria para "LSP do mundo de agentes". Hoje, cada IDE inventa protocolo (Cursor, Continue, Cline, Aider…). ACP padroniza. Saber ACP = entender futuro próximo da integração agente-IDE.

Conceitos-chave

  • JSON-RPC 2.0: mesmo protocolo de LSP
  • stdio transport: stdin/stdout, sem porta
  • Bidirecional: agente pode pedir input ao cliente
  • Simétrico: mesmo schema nos dois sentidos
  • Implementações: Hermes, Kimi K2, Kiro, e crescendo
2

📞 Por que stdio (não HTTP)

Process isolation, language independence, sem porta.

O que é

stdio > HTTP para 3 razões:

  • Process isolation: mata o processo = mata a sessão; sem socket leak
  • Language independence: agente em Rust, Go, Python, Node — mesma interface
  • Sem porta: sem conflito, sem firewall, sem auth

Por que aprender

Mesma decisão do LSP, MCP, Jupyter kernel. Padrão consagrado para tooling local: stdio reduz superfície de ataque, simplifica spawn de subprocess. Saber por que stdio = entender por que ACP encaixa naturalmente em IDEs.

Conceitos-chave

  • child_process.spawn(): Node API canônica
  • Newline-delimited JSON: 1 mensagem por linha
  • stderr para logs: stdout só pro protocolo
  • Process group: kill cascateia para children
3

🌳 Os 11 CLIs suportados — por stream format

Taxonomia em 6 famílias.

O que é

11 CLIs, 6 famílias:

claude-stream-json: Claude Code (Anthropic native)
copilot-stream-json: GitHub Copilot CLI
json-event-stream: Codex, Gemini CLI, OpenCode, Cursor Agent (4 CLIs)
acp-json-rpc: Hermes, Kimi K2, Kiro (3 ACP-compatíveis)
pi-rpc: Aider, Continue (1 família, 2 CLIs com small variations)
plain: Qwen CLI (texto livre, OD parseia)

Por que aprender

Cada família tem semântica de eventos diferente. Saber a taxonomia = saber por que adicionar um CLI novo cabe em uma das 6 (e não 7). Reduz superfície de adapter para tamanho conhecido.

Conceitos-chave

  • claude-stream-json: evento por chunk, tool_use blocks
  • json-event-stream: eventos tipados (output, tool_call, finish)
  • acp-json-rpc: RPC bi-direcional
  • plain: texto livre + sentinel string
4

🔍 Detecção automática no boot — PATH scan

O daemon descobre CLIs no startup.

O que é

No boot, o daemon escaneia $PATH procurando por executáveis conhecidos. Para cada match, registra a CLI no availableAgents:

async function detectAgents() {
  const found = [];
  for (const def of AGENT_DEFS) {
    const path = await which(def.executable);
    if (path) found.push({ ...def, path });
  }
  return found;
}

UI exibe lista; usuário escolhe.

Por que aprender

É a UX-chave do "BYOK em todas camadas": OD não vem com agente; usa o que você já tem. Saber o mecanismo = saber configurar PATH custom (ex: ~/bin/claude), debugar "agente não aparece", e adicionar CLI nova.

Conceitos-chave

  • which() / npm which: resolução cross-platform
  • PATH override: OD_PATH=/custom/bin:$PATH
  • Re-scan: botão "Refresh agents" na UI
  • Health check: cli --version antes de listar
5

🪟 Argv builders & promptViaStdin

Windows-friendly fallbacks.

O que é

Cada CLI espera prompt de jeito diferente:

  • Argv: claude "prompt aqui" — string como argumento
  • Stdin: echo "prompt" | aider — pipe via stdin
  • File: codex --input-file prompt.txt — referência a arquivo

Em Windows, argv tem limites de tamanho e quoting fora do padrão; fallback para stdin resolve.

Por que aprender

"Funciona no meu Mac" não basta. Adapter sério lida com Windows. Saber qual CLI usa qual transport = não falhar em deploy multi-OS.

Conceitos-chave

  • argvBuilder(prompt) → string[]: função no AGENT_DEFS
  • promptViaStdin: true: flag que ativa pipe
  • Windows quoting: escape diferente de POSIX
  • Tempfile fallback: prompt > 8k chars
6

📚 AGENT_DEFS — anatomia

A tabela canônica de CLIs em apps/daemon/src/agents.ts.

O que é

Cada entrada em AGENT_DEFS:

{
  id: 'claude-code',
  label: 'Claude Code',
  executable: 'claude',
  family: 'claude-stream-json',
  argvBuilder: (prompt) => ['--no-color', '--stream-json', prompt],
  promptViaStdin: false,
  eventParser: parseClaudeStreamJson,
  envVars: ['ANTHROPIC_API_KEY'],
}

Por que aprender

É onde se adiciona um agente novo. Sem editar código fora dessa tabela, novo CLI fica disponível. Se você precisa entender uma coisa só sobre adapters, é AGENT_DEFS.

Conceitos-chave

  • id: chave única (kebab-case)
  • family: qual dos 6 stream formats
  • eventParser: referência a função em apps/daemon/src/<family>-stream.ts
  • envVars: chaves API que precisam estar no env
  • timeout, maxTokens: overrides opcionais
7

➕ Como adicionar um novo CLI

Uma entrada em AGENT_DEFS + um eventParser.

O que é

Receita em 4 passos:

  1. Identifique a família: CLI emite stream-json? json-event-stream? acp? plain?
  2. Reusa parser: se família existe, importe o parser correspondente
  3. Cria parser novo: se família nova, escreva parseFooStream() em apps/daemon/src/foo-stream.ts
  4. Adiciona em AGENT_DEFS: 1 entrada com id, executable, family, argvBuilder, eventParser

Reinicia daemon → CLI aparece no picker.

Por que aprender

A maioria dos times tem CLI custom (interna, fork, build próprio). Saber adicionar = não esperar PR no upstream. 15 minutos por CLI nova.

Conceitos-chave

  • eventParser signature: (line: string) → AgentEvent | null
  • AgentEvent types: output, tool_call, todo_update, finish, error
  • Tests: golden file de stream + assert events
  • PR upstream: CLIs públicos vão pro repo OD oficial

🛠️ Hands-on

Brief: Adicionar um adapter "fake" (mock CLI que responde com texto fixo) ao AGENT_DEFS e ver aparecer no picker.

  1. Crie executável fake: shell script ~/bin/fake-agent que faz echo "Olá! Sou o fake." e sai.
  2. Registre em AGENT_DEFS: id fake, family plain, eventParser parsePlainStream.
  3. Reinicie daemon: pnpm tools-dev restart
  4. Verifique picker: "Fake Agent" aparece. Inicie sessão e digite qualquer brief.
  5. Output esperado: "Olá! Sou o fake." aparece na resposta — confirma que pipeline está funcionando até o end.

Snippet de adapter

// apps/daemon/src/agents.ts
export const AGENT_DEFS = [
  // ...existentes
  {
    id: 'fake',
    label: 'Fake Agent',
    executable: 'fake-agent',
    family: 'plain',
    argvBuilder: () => [],
    promptViaStdin: true,
    eventParser: parsePlainStream,
  },
]

📚 Fontes

No repositório

  • apps/daemon/src/agents.ts
  • docs/agent-adapters.md (279L)
  • apps/daemon/src/claude-stream.ts

Externas

📌 Resumo do Módulo

1. ACP = JSON-RPC 2.0 sobre stdio bidirecional simétrico — "LSP de coding agents".

2. stdio > HTTP por process isolation, language independence, sem porta.

3. 11 CLIs em 6 famílias: claude-stream-json, copilot, json-event-stream, acp-json-rpc, pi-rpc, plain.

4. Boot scan via $PATH detecta CLIs e popula picker.

5. argvBuilder + promptViaStdin lidam com Windows quoting e tamanho de prompt.

6. AGENT_DEFS é a tabela canônica — single source of truth.

7. Adicionar CLI nova = 1 entrada em AGENT_DEFS + reuso ou novo eventParser.

← Módulo 3.1 Módulo 3.3 →