コンテンツにスキップ
OmniRoute source

ACP (Agent Client Protocol) (日本語)

ACP(Agent Client Protocol)は、OmniRoute向けの**「バックエンドとしてのCLI」トランスポートです。AIプロバイダーへのHTTP API呼び出しをインターセプトする代わりに、ACPはCLIエージェントを子プロセスとして起動**し、それぞれのネイティブインターフェースを介してプロンプトを送信します。

メリット 説明
APIキーが不要 既存のCLI認証を使用します
ネイティブプロトコル 各CLIのネイティブな入出力形式を使用します
自動検出 システムにインストールされているCLIを検出します
15個の組み込みエージェント 一般的なCLIツール向けに事前設定されています
カスタムエージェント 設定から独自のCLIツールを追加できます
プロセス管理 ライフサイクル(起動、送信、終了)を処理します

ACPは、標準で15個の組み込みCLIエージェントをサポートしています。

エージェントID 表示名 バイナリ プロトコル
codex OpenAI Codex CLI codex stdio
claude Claude Code CLI claude stdio
goose Goose CLI goose stdio
openclaw OpenClaw openclaw stdio
aider Aider aider stdio
opencode OpenCode opencode stdio
cline Cline cline stdio
qwen Qwen Code qwen --acp stdio
forge ForgeCode forge stdio
amazon-q Amazon Q Developer q stdio
interpreter Open Interpreter interpreter stdio
cursor-cli Cursor CLI cursor stdio
warp Warp AI warp stdio
gemini Gemini CLI gemini stdio
zcode ZCode zcode stdio

設定から独自のCLIエージェントを追加できます。カスタムエージェントは、組み込みエージェントと同じ機能をサポートします。


ステップ1:CLIエージェントをインストールする

Section titled “ステップ1:CLIエージェントをインストールする”
ターミナルウィンドウ
# 例:Claude Code CLIをインストール
npm install -g @anthropic-ai/claude-code
# インストールを確認
claude --version

ACPは、システムにインストールされているCLIエージェントを自動的に検出します。設定は不要です!

ステップ3:ACPトランスポートを使用する

Section titled “ステップ3:ACPトランスポートを使用する”

検出後、ACPをサポート対象の任意のプロバイダーのトランスポートとして使用できます。CLIが利用可能な場合、OmniRouteはACPを自動的に使用します。


┌─────────────────┐
│ OmniRoute │
│ (HTTPプロキシ) │
└────────┬────────┘
│
│ spawn()
▼
┌─────────────────┐
│ 子プロセス │
│ (CLIエージェント) │
│ │
│ stdin ◄──────┤ プロンプトを送信
│ stdout ──────►│ 応答を受信
│ stderr ──────►│ エラーを受信
└─────────────────┘
  1. 起動 — ACPがCLIエージェントの子プロセスを作成します
  2. 送信 — ACPがプロンプトをプロセスのstdinに書き込みます
  3. 受信 — ACPがstdout/stderrから応答を読み取ります
  4. アイドル検出 — ACPは、応答が完了したと判断する前に2秒間出力がないことを待ちます
  5. 終了 — ACPがプロセスを終了します(SIGTERMを送信し、5秒後にSIGKILLを送信)

ACPは、CLIエージェントとの通信にstdio(標準入出力)を使用します。プロトコルは次のとおりです。

  1. プロンプトを送信 — 改行を付けてstdinに書き込みます
  2. 応答を待機 — アイドル状態になるまでstdoutから読み取ります(2秒間出力なし)
  3. タイムアウト — デフォルトは120秒です(設定可能)

システムにインストールされているすべてのCLIエージェントを検出します。結果は60秒間キャッシュされます。

import { detectInstalledAgents } from "@/lib/acp";
const agents = detectInstalledAgents();
// 戻り値: CliAgentInfo[]
interface CliAgentInfo {
id: string; // 例: "codex", "claude"
name: string; // 表示名
binary: string; // 起動するバイナリ名
versionCommand: string; // バージョン検出コマンド
version: string | null; // 検出されたバージョン(インストールされていない場合はnull)
installed: boolean; // エージェントがインストールされているかどうか
providerAlias: string; // OmniRoute内のプロバイダーID
spawnArgs: string[]; // 起動時に渡す引数
protocol: "stdio" | "http"; // 通信プロトコル
isCustom?: boolean; // ユーザー定義のカスタムエージェントかどうか
}

インストール済みでACPに使用可能なエージェントのみを取得します。

import { getAvailableAgents } from "@/lib/acp";
const available = getAvailableAgents();
// 戻り値: CliAgentInfo[](インストール済みのエージェントのみ)

IDを指定して特定のエージェントを取得します。

import { getAgentById } from "@/lib/acp";
const agent = getAgentById("claude");
// 戻り値: CliAgentInfo | undefined

設定からカスタムエージェント定義を設定します。

import { setCustomAgents } from "@/lib/acp";
setCustomAgents([
{
id: "my-custom-cli",
name: "My Custom CLI",
binary: "mycli",
versionCommand: "mycli --version",
providerAlias: "my-provider",
spawnArgs: [],
protocol: "stdio",
},
]);

acpManager.spawn(agentId, binary, args, env)

Section titled “acpManager.spawn(agentId, binary, args, env)”

新しいCLIエージェントプロセスを起動します。

import { acpManager } from "@/lib/acp";
const session = acpManager.spawn("claude", "claude", ["--print", "--output-format", "json"], {
/* カスタム環境変数 */
});
// 戻り値: AcpSession

許可されているエージェントID: ["claude", "codex", "gemini", "qwen"]

acpManager.sendPrompt(sessionId, prompt, timeoutMs)

Section titled “acpManager.sendPrompt(sessionId, prompt, timeoutMs)”

CLIエージェントにプロンプトを送信し、レスポンスを収集します。

import { acpManager } from "@/lib/acp";
const response = await acpManager.sendPrompt(
"acp-claude-1234567890-abc123",
"What is 2+2?",
120000 // 2分のタイムアウト
);
// 戻り値: Promise<string>

セッションを終了し、クリーンアップします。

import { acpManager } from "@/lib/acp";
const killed = acpManager.kill("acp-claude-1234567890-abc123");
// 戻り値: boolean

すべてのアクティブなセッションを取得します。

import { acpManager } from "@/lib/acp";
const sessions = acpManager.getActiveSessions();
// 戻り値: AcpSession[]

すべてのセッションを終了します。

import { acpManager } from "@/lib/acp";
acpManager.killAll();
interface AcpSession {
id: string; // 一意のセッションID
agentId: string; // エージェントID(例: "claude")
process: ChildProcess; // 子プロセスのハンドル
alive: boolean; // プロセスが実行中かどうか
stdoutBuffer: string; // 累積されたstdoutバッファ
stderrBuffer: string; // 累積されたstderrバッファ
createdAt: Date; // 作成日時
}

AcpManagerはEventEmitterを拡張し、以下のイベントを発行します。

CLIエージェントがstdoutに書き込んだときに発行されます。

acpManager.on("stdout", ({ sessionId, data }) => {
console.log(`[${sessionId}] stdout: ${data}`);
});

CLIエージェントがstderrに書き込んだときに発行されます。

acpManager.on("stderr", ({ sessionId, data }) => {
console.error(`[${sessionId}] stderr: ${data}`);
});

CLIエージェントプロセスが終了したときに発行されます。

acpManager.on("exit", ({ sessionId, code, signal }) => {
console.log(`[${sessionId}] exited with code ${code}, signal ${signal}`);
});

CLIエージェントプロセスでエラーが発生したときに発行されます。

acpManager.on("error", ({ sessionId, error }) => {
console.error(`[${sessionId}] error: ${error}`);
});

ACP は親プロセスからすべての環境変数を継承し、カスタム環境変数で拡張できます。

acpManager.spawn("claude", "claude", [], {
ANTHROPIC_API_KEY: "sk-...",
DEBUG: "true",
});

各エージェントには、レジストリで定義されたデフォルトの起動引数があります。これらは上書きできます。

acpManager.spawn("claude", "claude", ["--print", "--verbose"], {});

デフォルトのプロンプトタイムアウトは 120 秒(2 分)です。次のように上書きできます。

await acpManager.sendPrompt(sessionId, prompt, 300000); // 5 分

負荷の高いファイルシステムスキャンを避けるため、エージェントの検出結果は 60 秒間キャッシュされます。強制的に更新するには、次のようにします。

import { refreshAgentCache } from "@/lib/acp";
refreshAgentCache();

コマンドインジェクションの防止

Section titled “コマンドインジェクションの防止”

ACP は、コマンドインジェクション攻撃を防止するためにバージョンコマンドを検証します。

const DISALLOWED_VERSION_COMMAND_CHARS = /[;&|<>`$\r\n]/;

以下の文字を含むバージョンコマンドは拒否されます。

  • ; — コマンド区切り文字
  • & — バックグラウンドプロセス
  • | — パイプ
  • <, > — リダイレクト
  • ` — コマンド置換
  • $ — 変数展開
  • \r, \n — 改行

ACP は、バージョンコマンドのバイナリが想定されるバイナリ名と一致することを検証します(カスタムエージェントの場合を除く)。

各 ACP セッションは、それぞれ独立した子プロセスで実行されます。セッションが終了するかタイムアウトすると、プロセスは強制終了されます。


  • 初回呼び出し: 約 50~200ms(各エージェントの version コマンドを実行)
  • キャッシュ済みの呼び出し: 1ms 未満(キャッシュから返却)
  • キャッシュ TTL: 60 秒
  • 起動: 約 50~100ms
  • プロンプト送信: 約 10~50ms
  • 応答待機: CLI エージェントに依存(通常は 1~30 秒)
  • 強制終了: 約 5 秒(SIGTERM)+ 即時(SIGKILL)
  • セッションあたりのメモリ: 約 10~50MB(CLI エージェントに依存)
  • CPU: 最小限(I/O バウンド)
  • ディスク: なし

問題: acpManager.spawn() が Unknown agent: &lt;id&gt; をスローする

解決策: spawn() で使用できるのは、次のエージェントのみです。

  • claude
  • codex
  • gemini
  • qwen

その他のエージェントは、手動またはカスタムエージェント定義を使用して起動する必要があります。

問題: acpManager.sendPrompt() が Session ${sessionId} is not alive をスローする

解決策: セッションが終了したか、強制終了された可能性があります。セッションの状態を確認してください。

const session = acpManager.getSession(sessionId);
if (!session?.alive) {
// セッションを再起動
acpManager.spawn("claude", "claude", [], {});
}

問題: acpManager.sendPrompt() が ACP timeout after 120000ms をスローする

解決策: タイムアウトを延長してください。

await acpManager.sendPrompt(sessionId, prompt, 300000); // 5 分

問題: detectInstalledAgents() が CLI を検出しない

解決策:

  1. PATH を確認: CLI がシステムの PATH に含まれていることを確認する
  2. バージョンコマンドを確認: claude --version を手動で実行する
  3. 権限を確認: CLI が実行可能であることを確認する
  4. カスタムエージェント: 非標準の CLI 用にカスタムエージェント定義を追加する

問題: ACP が CLI を実行できない

解決策:

  1. ファイル権限を確認: chmod +x /usr/local/bin/claude
  2. 所有権を確認: OmniRoute に読み取り/実行権限があることを確認する
  3. SELinux/AppArmor を確認: プロセスの起動がブロックされる場合があります

import { acpManager, detectInstalledAgents } from "@/lib/acp";
// インストール済みのエージェントを検出
const agents = detectInstalledAgents();
const claude = agents.find((a) => a.id === "claude");
if (claude?.installed) {
// 新しいセッションを起動
const session = acpManager.spawn("claude", claude.binary, ["--print", "--output-format", "json"]);
// プロンプトを送信
const response = await acpManager.sendPrompt(
session.id,
"量子コンピューティングについて100語で説明してください"
);
console.log("Claudeの応答:", response);
// クリーンアップ
acpManager.kill(session.id);
}

例 2: フォールバック付き自動検出

Section titled “例 2: フォールバック付き自動検出”
import { acpManager, getAvailableAgents } from "@/lib/acp";
const available = getAvailableAgents();
// まずClaudeを試し、見つからない場合はCodexにフォールバック
let agentId = "claude";
if (!available.find((a) => a.id === "claude")) {
if (available.find((a) => a.id === "codex")) {
agentId = "codex";
} else {
throw new Error("ACP互換のCLIエージェントが見つかりません");
}
}
const agent = available.find((a) => a.id === agentId)!;
const session = acpManager.spawn(agentId, agent.binary, agent.spawnArgs);
const response = await acpManager.sendPrompt(session.id, "こんにちは!");
acpManager.kill(session.id);
import { setCustomAgents, detectInstalledAgents } from "@/lib/acp";
// カスタムCLIエージェントを登録
setCustomAgents([
{
id: "my-llm-cli",
name: "My LLM CLI",
binary: "myllm",
versionCommand: "myllm --version",
providerAlias: "my-llm-provider",
spawnArgs: ["--format", "json"],
protocol: "stdio",
},
]);
// detectInstalledAgents()に「my-llm-cli」が含まれるようになります
const agents = detectInstalledAgents();


  • AionUiプロジェクト — ACP自動検出の着想元
  • ACPソースコード — 実装の詳細
    • manager.ts — プロセスのライフサイクル管理
    • registry.ts — エージェントの検出と登録
    • index.ts — 公開APIのエクスポート

OmniRoute ソースコード (a58000c7685f)

HagiCode

HagiCode は構造化ワークフロー、マルチエージェント実行、Hero Dungeon ビューを備えたエージェント型コーディングワークスペースです。

よりスマートで速く、楽しいエージェント型ワークフローで、使いやすいソフトウェアを形にします。

HagiCode ライトテーマのメイン画面
  • Smart構造化ワークフローは意図をアイデアから変更のリリースまで実行可能な道筋にします。
  • Efficientマルチエージェントのワークフローで調査、実装、レビューを並行して進めます。
  • FunHero Dungeon により長時間のコーディングを視覚的で協力的な体験にします。
HagiCode を見る