メモ
GitHub Copilot CLI(コマンドラインインターフェース)での ACP のサポートはパブリック プレビューであり、変更される可能性があります。
概要
エージェント クライアント プロトコル (ACP) は、クライアント (コード エディターや IDE など) とエージェント ( Copilot CLI (コパイロット CLI) など) 間の通信を標準化するプロトコルです。 このプロトコルの詳細については、 公式の概要を参照してください。
利用事例
- **IDE 統合:**Copilotサポートを任意のエディターまたは開発環境に組み込みます。
- CI/CD パイプライン: 自動化されたワークフローでエージェント コーディング タスクを調整します。
- カスタム フロントエンド: 特定の開発者ワークフロー用の特殊なインターフェイスを作成します。
- マルチエージェント システム: 標準プロトコルを使用して、 Copilot を他の AI エージェントと調整します。
ACP サーバーの起動
cli の ACP サーバーを開始するには、--acp コマンドの copilot オプションを使用します。 トランスポート モードは、 --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 モード
stdio モードは、ACP サーバーを起動するときに既定で推論されます。
--stdio オプションを使用してあいまいさを解消することもできます。
copilot --acp --stdio
TCP モード
--port オプションが --acp オプションと組み合わせて指定されている場合、サーバーは TCP モードで開始されます。
copilot --acp --port 3000
stdio と TCP の選択
どちらのトランスポート モードも、改行で区切られた JSON (NDJSON) としてエンコードされた同じ ACP メッセージを伝送します。 クライアントがサーバーに接続する方法と、サーバーのライフサイクルを管理する方法のみが異なります。 2 つのモードは相互に排他的です。 --stdio と --port の両方を渡すことは拒否されます。
| 特徴 | stdio モード | TCP モード |
|---|---|---|
| クライアントの接続方法 | クライアントは子プロセスとして copilot --acp を起動し、プロセスの標準入力と出力を介してメッセージを交換します。 | サーバーは、クライアントがネットワーク ソケット経由で接続する TCP リスナーを開きます。 既定では、ループバック アドレス 127.0.0.1にバインドされます。 |
| クライアントの数 | 単一のクライアント:サーバーを生成し、パイプを所有するプロセス。 | リスナーはソケット接続を受け入れ、それぞれが独自のエージェント接続として処理されます。 |
| ライフサイクル | 親プロセスに関連付けられています。 親プロセスが終了するかパイプを閉じることで入力ストリームが閉じると、サーバーは自動的にシャットダウンします。 | 任意の単一のクライアントに依存しません。 サーバーは、停止されるまでそのポートで待ち受けます。たとえば Ctrl+C で停止できます。 |
| 標準出力 | NDJSON プロトコル ストリーム用に予約されているため、ログやその他のテキストには使用できません。 | プロトコル トラフィックがソケット経由で移動するため、他の用途には無料です。 |
各モードを使用する場合:
- エディター、IDE、またはスクリプトがサブプロセスとして直接**** 生成する場合は、Copilot CLI (コパイロット 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 で実行できます。 1 つを呼び出すには、テキストがコマンドである通常のプロンプトとして送信し、単一のテキスト コンテンツ ブロック ( /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 (コパイロット CLI)のスラッシュ コマンドの完全な一覧については、AUTOTITLE を参照してください。