Observação
O suporte à ACP CLI do GitHub Copilot se encontra em prévia pública e está sujeito a alterações.
Visão geral
O Protocolo de Cliente do Agente (ACP) é um protocolo que padroniza a comunicação entre clientes (como editores de código e IDEs) e agentes (como CLI do Copilot). Para obter mais detalhes sobre esse protocolo, consulte a introdução oficial.
Casos de uso
- Integrações do IDE: Crie Copilot suporte em qualquer editor ou ambiente de desenvolvimento.
- Pipelines de CI/CD: Orquestrar tarefas de codificação autônomas em fluxos de trabalho automatizados.
- Front-ends personalizados: Crie interfaces especializadas para fluxos de trabalho de desenvolvedor específicos.
- Sistemas de vários agentes: Coordene Copilot com outros agentes de IA usando um protocolo padrão.
Iniciando o servidor ACP
Use a opção --acp do copilot comando para iniciar o servidor ACP da CLI. Você pode especificar o modo de transporte com a opção --stdio ou --port. Se nenhum modo de transporte for especificado, o servidor usará o modo stdio como padrão.
Opções aplicadas a cada sessão
A solicitação ACP session/new só permite que um cliente defina alguns parâmetros de sessão, como o diretório de trabalho e os servidores MCP a serem usados. Ele não contém configurações de filtragem de ferramentas ou de raciocínio. Para configurá-las, passe as opções correspondentes ao iniciar o servidor. O servidor armazena os valores e os aplica como a configuração inicial para cada sessão que ele cria ou carrega, para qualquer cliente que se conecta. Um cliente de conexão não escolhe esses valores— quem inicia o servidor escolhe.
| Opção de servidor | Valor aceito | Efeito em todas as sessões |
|---|---|---|
--available-tools=TOOL ... | Uma lista entre aspas e separada por vírgulas de nomes de ferramentas | A sessão pode usar apenas as ferramentas listadas. |
--excluded-tools=TOOL ... | Uma lista entre aspas e separada por vírgulas de nomes de ferramentas | As ferramentas listadas são removidas da sessão. |
--effort=LEVEL, --reasoning-effort=LEVEL | ||
low, medium, high, xhigh ou max | Define o esforço de raciocínio inicial da sessão. |
Por exemplo, este comando inicia um servidor cujas sessões usam o nível máximo de esforço de raciocínio e expõem apenas as ferramentas bash e view:
copilot --acp --port 3000 --effort=max --available-tools="bash,view"
Cada sessão que o cliente conectado abre nesse servidor herda essas configurações. Como os valores são corrigidos quando o servidor é iniciado, um cliente não pode alterá-los por sessão.session/new
modo stdio
O modo stdio é inferido por padrão quando você inicia o servidor ACP. Você também pode usar a opção --stdio de desambiguação.
copilot --acp --stdio
Modo TCP
Se a opção --port for fornecida em combinação com a opção --acp , o servidor será iniciado no modo TCP.
copilot --acp --port 3000
Escolhendo entre stdio e TCP
Ambos os modos de transporte transportam as mesmas mensagens ACP, codificadas como JSON delimitado por novas linhas (NDJSON). Elas diferem apenas na forma como um cliente se conecta ao servidor e como o ciclo de vida do servidor é gerenciado. Os dois modos são mutuamente exclusivos: passar --stdio e --port ao mesmo tempo é recusado.
| Aspecto | modo stdio | Modo TCP |
|---|---|---|
| Como o cliente se conecta | O cliente inicia copilot --acp como um processo filho e troca mensagens por meio da entrada padrão e da saída padrão do processo. | O servidor abre um ouvinte TCP ao qual os clientes se conectam por meio de um soquete de rede. Por padrão, ele se vincula ao endereço 127.0.0.1 de loopback. |
| Número de clientes | Um único cliente– o processo que gerou o servidor e é o proprietário do pipe. | O listener aceita conexões de socket, e cada conexão é tratada como uma conexão de agente independente. |
| Ciclo de vida | Vinculado ao processo pai. Quando o fluxo de entrada fecha, porque o pai sai ou fecha o pipe, o servidor é desligado automaticamente. | Independente de qualquer cliente único. O servidor continua escutando na porta até ser interrompido, por exemplo, com Ctrl+C. |
| Saída padrão | Reservado para o fluxo de protocolo NDJSON, portanto, ele não pode ser usado para logs ou outro texto. | Livre para outros usos, pois o tráfego do protocolo passa pelo soquete. |
Quando usar cada modo:
- Use o modo stdio quando um editor, IDE ou script iniciar CLI do Copilot diretamente como subprocesso. Esta é a configuração padrão e a recomendada para a integração com a IDE, pois o transporte é estabelecido automaticamente quando o processo é iniciado e encerrado quando é finalizado.
- Use o modo TCP quando um cliente precisar acessar o servidor por meio de um soquete em vez de um pipe, por exemplo, de um processo ou contêiner separado ou ao se conectar a um servidor de vida mais longa em uma porta conhecida.
Exemplo: integração com o servidor ACP
O exemplo a seguir é um aplicativo cliente que usa Copilot interagindo com CLI do GitHub Copiloto servidor ACP. Ele inicia o servidor ACP no modo stdio, abre uma sessão, solicita que você insira um prompt, envie-o e imprima a resposta transmitida.
Há um ecossistema crescente de bibliotecas para interagir com servidores ACP programaticamente. Este exemplo usa a biblioteca TypeScript ACP.
Para executar este exemplo, você precisa das seguintes dependências:
- Node.js versão 18 ou posterior.
- CLI do GitHub Copilot, instalado e autenticado.
- O pacote
@agentclientprotocol/sdk, que fornece a biblioteca ACP para TypeScript. Instale-a executandonpm install @agentclientprotocol/sdk.
import * as acp from "@agentclientprotocol/sdk";
import { spawn } from "node:child_process";
import { Readable, Writable } from "node:stream";
import * as readline from "node:readline/promises";
async function main() {
const executable = process.env.COPILOT_CLI_PATH ?? "copilot";
// ACP uses standard input/output (stdin/stdout) for transport; we pipe these for the NDJSON stream.
const copilotProcess = spawn(executable, ["--acp", "--stdio"], {
stdio: ["pipe", "pipe", "inherit"],
});
if (!copilotProcess.stdin || !copilotProcess.stdout) {
throw new Error("Failed to start Copilot ACP process with piped stdio.");
}
// Create ACP streams (NDJSON over stdio)
const output = Writable.toWeb(copilotProcess.stdin) as WritableStream<Uint8Array>;
const input = Readable.toWeb(copilotProcess.stdout) as ReadableStream<Uint8Array>;
const stream = acp.ndJsonStream(output, input);
const client: acp.Client = {
async requestPermission(params) {
// This example should not trigger tool calls; if it does, refuse.
return { outcome: { outcome: "cancelled" } };
},
async sessionUpdate(params) {
const update = params.update;
if (update.sessionUpdate === "agent_message_chunk" && update.content.type === "text") {
process.stdout.write(update.content.text);
}
},
};
const connection = new acp.ClientSideConnection((_agent) => client, stream);
await connection.initialize({
protocolVersion: acp.PROTOCOL_VERSION,
clientCapabilities: {},
});
const sessionResult = await connection.newSession({
cwd: process.cwd(),
mcpServers: [],
});
process.stdout.write("Session started!\n");
// Ask the user to enter a prompt instead of using a hard-coded one.
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const promptText = await rl.question("Enter a prompt: ");
rl.close();
const promptResult = await connection.prompt({
sessionId: sessionResult.sessionId,
prompt: [{ type: "text", text: promptText }],
});
process.stdout.write("\n");
if (promptResult.stopReason !== "end_turn") {
process.stderr.write(`Prompt finished with stopReason=${promptResult.stopReason}\n`);
}
// Best-effort cleanup
copilotProcess.stdin.end();
copilotProcess.kill("SIGTERM");
await new Promise<void>((resolve) => {
copilotProcess.once("exit", () => resolve());
setTimeout(() => resolve(), 2000);
});
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
import * as acp from "@agentclientprotocol/sdk";
import { spawn } from "node:child_process";
import { Readable, Writable } from "node:stream";
import * as readline from "node:readline/promises";
async function main() {
const executable = process.env.COPILOT_CLI_PATH ?? "copilot";
// ACP uses standard input/output (stdin/stdout) for transport; we pipe these for the NDJSON stream.
const copilotProcess = spawn(executable, ["--acp", "--stdio"], {
stdio: ["pipe", "pipe", "inherit"],
});
if (!copilotProcess.stdin || !copilotProcess.stdout) {
throw new Error("Failed to start Copilot ACP process with piped stdio.");
}
// Create ACP streams (NDJSON over stdio)
const output = Writable.toWeb(copilotProcess.stdin) as WritableStream<Uint8Array>;
const input = Readable.toWeb(copilotProcess.stdout) as ReadableStream<Uint8Array>;
const stream = acp.ndJsonStream(output, input);
const client: acp.Client = {
async requestPermission(params) {
// This example should not trigger tool calls; if it does, refuse.
return { outcome: { outcome: "cancelled" } };
},
async sessionUpdate(params) {
const update = params.update;
if (update.sessionUpdate === "agent_message_chunk" && update.content.type === "text") {
process.stdout.write(update.content.text);
}
},
};
const connection = new acp.ClientSideConnection((_agent) => client, stream);
await connection.initialize({
protocolVersion: acp.PROTOCOL_VERSION,
clientCapabilities: {},
});
const sessionResult = await connection.newSession({
cwd: process.cwd(),
mcpServers: [],
});
process.stdout.write("Session started!\n");
// Ask the user to enter a prompt instead of using a hard-coded one.
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const promptText = await rl.question("Enter a prompt: ");
rl.close();
const promptResult = await connection.prompt({
sessionId: sessionResult.sessionId,
prompt: [{ type: "text", text: promptText }],
});
process.stdout.write("\n");
if (promptResult.stopReason !== "end_turn") {
process.stderr.write(`Prompt finished with stopReason=${promptResult.stopReason}\n`);
}
// Best-effort cleanup
copilotProcess.stdin.end();
copilotProcess.kill("SIGTERM");
await new Promise<void>((resolve) => {
copilotProcess.once("exit", () => resolve());
setTimeout(() => resolve(), 2000);
});
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Para executar o exemplo:
-
Salve o código acima em um arquivo chamado
acp-client.ts. -
Execute o arquivo com
npx tsx, que executa o TypeScript diretamente sem uma etapa de build separada:npx tsx acp-client.ts
Como usar comandos de barra/
Os comandos de barra integrados do CLI do GitHub Copilot podem ser executados via ACP. Para invocar um, envie-o como um prompt comum cujo texto é o comando, passado como um único bloco de conteúdo de texto, por exemplo, /context ou /session info. O servidor reconhece o comando e o executa diretamente: comandos informativos, como /usage ou /context retornam a saída sem invocar o modelo, enquanto comandos de ação, como /plan ou /review iniciar a tarefa do agente correspondente. De qualquer forma, o texto do comando não é enviado para o modelo como uma pergunta.
Descobrindo comandos disponíveis
O servidor anuncia os comandos compatíveis por meio da notificação de sessão ACP available_commands_update padrão. Ele é enviado depois que uma sessão é criada ou carregada e novamente sempre que o conjunto é alterado, por exemplo, quando as habilidades terminam de carregar. Essa lista anunciada é o conjunto autoritativo e sempre atual de comandos que você pode executar em ACP e os clientes normalmente a aparecem em um menu de comando.
A lista anunciada contém:
- Comandos internos, como
/compact,/context,/usage,/env,/model,/mcp,/plan,/review,/research,/sessione/rename. - Habilidades habilitadas e invocadas pelo usuário, que aparecem como
/SKILL-NAMEcomandos.
Os comandos que o próprio cliente registra não são anunciados novamente para ele.
Acessando a lista no seu cliente
Como a lista chega como uma notificação em vez de em resposta a uma solicitação, não há nenhum método para buscá-la sob demanda. Seu cliente o acessa manipulando a session/update notificação e reagindo a atualizações cujo tipo é available_commands_update. Cada entrada tem uma name (sem a barra principal), uma descriptione uma opcional input.hint que descreve os argumentos do comando. A notificação é enviada novamente sempre que o conjunto é alterado, portanto, trate cada uma delas como uma substituição completa de qualquer lista que você tenha armazenado em cache.
O manipulador sessionUpdate a seguir captura os comandos anunciados, estendendo o objeto client do exemplo mostrado anteriormente.
// Track the latest advertised commands for the session.
let availableCommands: acp.AvailableCommand[] = [];
const client: acp.Client = {
async sessionUpdate(params) {
const update = params.update;
if (update.sessionUpdate === "available_commands_update") {
// This notification is a full snapshot—replace any cached list.
availableCommands = update.availableCommands;
for (const command of availableCommands) {
// command.name has no leading slash; invoke it by sending "/<name>" as a prompt.
console.log(`/${command.name} — ${command.description}`);
}
return;
}
// ...handle other updates, such as agent_message_chunk
},
// ...other client methods, such as requestPermission
};
// Track the latest advertised commands for the session.
let availableCommands: acp.AvailableCommand[] = [];
const client: acp.Client = {
async sessionUpdate(params) {
const update = params.update;
if (update.sessionUpdate === "available_commands_update") {
// This notification is a full snapshot—replace any cached list.
availableCommands = update.availableCommands;
for (const command of availableCommands) {
// command.name has no leading slash; invoke it by sending "/<name>" as a prompt.
console.log(`/${command.name} — ${command.description}`);
}
return;
}
// ...handle other updates, such as agent_message_chunk
},
// ...other client methods, such as requestPermission
};
Para executar um dos comandos anunciados, envie seu nome como um prompt em um único bloco de conteúdo de texto, por exemplo, { type: "text", text: "/context" }conforme descrito em Usar comandos de barra.
Comandos que não podem ser usados por ACP
Comandos de barra que dependem da interface de terminal interativa não são tratados pelo servidor ACP. Isso inclui comandos que abrem um seletor, caixa de diálogo ou exibição de tela inteira, como /diff, , /resume, /theme, /settings, /login, /help, /taskse /undo. Como regra, se um comando não aparecer na available_commands_update lista, ele não será executado por ACP: o servidor tratará o texto como um prompt comum e o encaminhará para o modelo em vez de executá-lo.
Como os clientes ACP não têm seletores interativos, um comando interno que normalmente abriria um submenu retorna suas opções como texto. Forneça o subcomando explicitamente para obter um resultado direto — por exemplo, /session info ou /mcp list, em vez de /session ou /mcp isoladamente.
Para obter uma lista completa dos comandos de barra de CLI do Copilot, consulte referência de comando da CLI GitHub Copilot.