注意
GitHub Copilot 命令行界面 (CLI) 中的 ACP 支持处于 公开预览 状态,可能会发生变化。
概述
代理客户端协议(ACP)是一种协议,用于标准化客户端(如代码编辑器和 IDE)和代理(如 Copilot 命令行界面(CLI)) 之间的通信。 有关此协议的更多详细信息,请参阅 官方简介。
用例
- IDE 集成: 将 Copilot 支持构建到任何编辑器或开发环境中。
- CI/CD 管道: 在自动化工作流中协调代理编码任务。
- 自定义前端: 为特定开发人员工作流创建专用接口。
- 多代理系统: 使用标准协议与其他 AI 代理协调 Copilot 。
启动 ACP 服务器
使用 copilot 命令的 --acp 选项来启动 CLI 的 ACP 服务器。 您可以使用 --stdio 或 --port 选项来指定传输模式。 如果未指定传输模式,则服务器默认为 stdio 模式。
应用于每个会话的选项
ACP session/new 请求仅允许客户端设置几个会话参数,例如工作目录和 MCP 服务器。 它不包含工具过滤或推理设置。 若要配置这些配置,请在 启动服务器时传递相应的选项。 服务器存储这些值,并将其作为它创建或加载的每个会话的初始配置应用于连接的任何客户端。 发起连接的客户端不会选择这些值——而是由启动服务器的人来选择。
| 服务器选项 | 接受的值 | 对每个会话的影响 |
|---|---|---|
--available-tools=TOOL ... | 带引号的、以逗号分隔的工具名称列表 | 会话只能使用列出的工具。 |
--excluded-tools=TOOL ... | 带引号的、以逗号分隔的工具名称列表 | 列出的工具将从会话中删除。 |
--effort=LEVEL、--reasoning-effort=LEVEL | ||
low、medium、high、xhigh 或 max | 设置会话的初始推理强度。 |
例如,此命令会启动一个服务器,该服务器的所有会话均使用最高推理强度,并且仅开放 bash 和 view 这两个工具:
copilot --acp --port 3000 --effort=max --available-tools="bash,view"
连接客户端针对该服务器打开的每个会话都会继承这些设置。 由于服务器启动时的值是固定的,因此客户端无法通过 session/new每个会话更改这些值。
stdio 模式
启动 ACP 服务器时,默认推断 stdio 模式。 还可以使用选项 --stdio 消除歧义。
copilot --acp --stdio
TCP 模式
如果此选项 --port 与 --acp 该选项结合使用,则服务器以 TCP 模式启动。
copilot --acp --port 3000
在 stdio 和 TCP 之间进行选择
这两种传输模式都携带相同的 ACP 消息,编码为换行符分隔的 JSON(NDJSON)。 它们仅在客户端连接到服务器的方式和管理服务器的生命周期方面有所不同。 这两种模式是互斥的:同时传入 --stdio 和 --port 会被拒绝。
| 方面 | stdio 模式 | TCP 模式 |
|---|---|---|
| 客户端连接方式 | 客户端作为子进程启动 copilot --acp ,并通过进程的标准输入和输出交换消息。 | 服务器启动一个 TCP 监听器,客户端通过网络套接字连接到该监听器。 默认情况下,它绑定到环回地址 127.0.0.1。 |
| 客户端数 | 单个客户端——启动了服务器并拥有该管道的进程。 | 侦听器接受套接字连接,每个连接都作为自己的代理连接进行处理。 |
| 生命周期 | 绑定到父进程。 当输入流关闭时——例如父进程退出或关闭了管道——服务器会自动关闭。 | 独立于任何单个客户端。 服务器会一直在该端口上监听,直到停止运行,例如按下 Ctrl+C。 |
| 标准输出 | 为 NDJSON 协议流保留,因此不能用于日志或其他文本。 | 免费供其他使用,因为协议流量通过套接字传输。 |
何时使用每个模式:
- 当编辑器、IDE 或脚本直接作为子进程生成**** 时,请使用 Copilot 命令行界面(CLI)。 这是 IDE 集成的默认且推荐配置,因为传输通道会在进程启动时自动建立,并在进程退出时自动关闭。
- 当客户端需要通过套接字而不是管道访问服务器(例如,来自单独的进程或容器),或者在连接到已知端口上生存期较长的服务器时,请使用 TCP 模式 。
示例:与 ACP 服务器集成
以下示例是一个客户端应用程序,它通过与 Copilot 的 ACP 服务器交互来使用 GitHub Copilot 命令行界面 (CLI)。 它以 stdio 模式启动 ACP 服务器,打开会话,要求你输入提示,发送它,并打印流式响应。
库生态系统不断增加,用于以编程方式与 ACP 服务器交互。 此示例使用 ACP TypeScript 库。
若要运行此示例,需要以下依赖项:
- Node.js 版本 18 或更高版本。
- GitHub Copilot 命令行界面 (CLI),已安装并经过身份验证。
- 提供 ACP TypeScript 库的
@agentclientprotocol/sdk包。 通过运行npm 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;
});
运行示例:
-
将上面的代码保存为名为
acp-client.ts的文件。 -
使用
npx tsx运行文件,该文件直接运行 TypeScript,而无需单独的生成步骤:npx tsx acp-client.ts
使用斜杠命令
GitHub Copilot 命令行界面 (CLI)内置斜杠命令可以通过 ACP 运行。 若要调用一个,请将其作为普通提示发送,其文本是命令,作为单个文本内容块传递,例如, /context 或 /session info。 服务器可识别命令并直接运行该命令:信息命令(例如 /usage 或 /context 返回其输出),而无需调用模型,而操作命令(例如 /plan 或 /review 启动相应的代理任务)。 无论哪种方式,命令文本都不会作为问题发送到模型。
查找可用命令
服务器通过标准 ACP available_commands_update 会话通知播发它支持的命令。 它会在会话创建或加载后发送,并且在集合发生更改时再次发送——例如,当技能加载完成时。 此对外公布的列表是可通过 ACP 执行的权威且始终保持最新的命令集合,客户端通常会在命令菜单中向用户呈现这些命令。
公布的列表包含:
- 内置命令,例如
/compact,、/context、/usage``/env、/model、/mcp、、/plan、、/review、/research、/session和/rename。 - 已启用的、可由用户调用的技能,显示为
/SKILL-NAME命令。
客户端自身注册的命令不会再通告给该客户端。
从客户端访问列表
由于列表以通知的形式到达,而不是响应请求,因此没有按需提取它的方法。 客户端通过处理 session/update 通知并响应其类型为 available_commands_update的更新来访问它。 每个条目都有一个 name (没有前导斜杠)、一个 description和一个描述命令参数的可选 input.hint 项。 每当设置发生更改时,都会重新发送通知,因此请将每个通知视为已缓存的任何列表的完整替换项。
以下 sessionUpdate 处理程序会捕获已公布的命令,并在前文所示示例中的 client 对象基础上进行扩展。
// 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
};
若要运行其中一个列出的命令,请按 使用斜杠命令 中所述,在单个文本内容块中将其名称作为提示发送,例如 { type: "text", text: "/context" }。
不能通过 ACP 使用的命令
ACP 服务器不会处理依赖于交互式终端接口的斜杠命令。 这包括可打开选取器、对话框或全屏视图的命令,例如 /diff、/resume、/theme、/settings、/login、/help、/tasks 和 /undo。 作为规则,如果命令未出现在 available_commands_update 列表中,它将不会在 ACP 上运行:服务器将文本视为普通提示,并将其转发到模型,而不是执行它。
由于 ACP 客户端没有交互式选取器,因此通常打开子菜单的内置命令会将其选项作为文本返回。 明确提供子命令以获得直接结果,例如,使用 /session info 或 /mcp list,而不是单独使用 /session 或 /mcp。
有关 Copilot 命令行界面(CLI) 的斜杠命令完整列表,请参阅 GitHub Copilot CLI 命令参考。