| import path from "node:path"; |
| import { fileURLToPath } from "node:url"; |
| import { spawnAndCollect, type SpawnCommandOptions } from "./process.js"; |
|
|
| const ACPX_BUILTIN_AGENT_COMMANDS: Record<string, string> = { |
| codex: "npx @zed-industries/codex-acp", |
| claude: "npx -y @zed-industries/claude-agent-acp", |
| gemini: "gemini", |
| opencode: "npx -y opencode-ai acp", |
| pi: "npx pi-acp", |
| }; |
|
|
| const MCP_PROXY_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "mcp-proxy.mjs"); |
|
|
| type AcpxConfigDisplay = { |
| agents?: Record<string, { command?: unknown }>; |
| }; |
|
|
| type AcpMcpServer = { |
| name: string; |
| command: string; |
| args: string[]; |
| env: Array<{ name: string; value: string }>; |
| }; |
|
|
| function normalizeAgentName(value: string): string { |
| return value.trim().toLowerCase(); |
| } |
|
|
| function quoteCommandPart(value: string): string { |
| if (value === "") { |
| return '""'; |
| } |
| if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) { |
| return value; |
| } |
| return `"${value.replace(/["\\]/g, "\\$&")}"`; |
| } |
| |
| export const __testing = { |
| quoteCommandPart, |
| }; |
| |
| function toCommandLine(parts: string[]): string { |
| return parts.map(quoteCommandPart).join(" "); |
| } |
| |
| function readConfiguredAgentOverrides(value: unknown): Record<string, string> { |
| if (!value || typeof value !== "object" || Array.isArray(value)) { |
| return {}; |
| } |
| const overrides: Record<string, string> = {}; |
| for (const [name, entry] of Object.entries(value)) { |
| if (!entry || typeof entry !== "object" || Array.isArray(entry)) { |
| continue; |
| } |
| const command = (entry as { command?: unknown }).command; |
| if (typeof command !== "string" || command.trim() === "") { |
| continue; |
| } |
| overrides[normalizeAgentName(name)] = command.trim(); |
| } |
| return overrides; |
| } |
| |
| async function loadAgentOverrides(params: { |
| acpxCommand: string; |
| cwd: string; |
| stripProviderAuthEnvVars?: boolean; |
| spawnOptions?: SpawnCommandOptions; |
| }): Promise<Record<string, string>> { |
| const result = await spawnAndCollect( |
| { |
| command: params.acpxCommand, |
| args: ["--cwd", params.cwd, "config", "show"], |
| cwd: params.cwd, |
| stripProviderAuthEnvVars: params.stripProviderAuthEnvVars, |
| }, |
| params.spawnOptions, |
| ); |
| if (result.error || (result.code ?? 0) !== 0) { |
| return {}; |
| } |
| try { |
| const parsed = JSON.parse(result.stdout) as AcpxConfigDisplay; |
| return readConfiguredAgentOverrides(parsed.agents); |
| } catch { |
| return {}; |
| } |
| } |
| |
| export async function resolveAcpxAgentCommand(params: { |
| acpxCommand: string; |
| cwd: string; |
| agent: string; |
| stripProviderAuthEnvVars?: boolean; |
| spawnOptions?: SpawnCommandOptions; |
| }): Promise<string> { |
| const normalizedAgent = normalizeAgentName(params.agent); |
| const overrides = await loadAgentOverrides({ |
| acpxCommand: params.acpxCommand, |
| cwd: params.cwd, |
| stripProviderAuthEnvVars: params.stripProviderAuthEnvVars, |
| spawnOptions: params.spawnOptions, |
| }); |
| return overrides[normalizedAgent] ?? ACPX_BUILTIN_AGENT_COMMANDS[normalizedAgent] ?? params.agent; |
| } |
| |
| export function buildMcpProxyAgentCommand(params: { |
| targetCommand: string; |
| mcpServers: AcpMcpServer[]; |
| }): string { |
| const payload = Buffer.from( |
| JSON.stringify({ |
| targetCommand: params.targetCommand, |
| mcpServers: params.mcpServers, |
| }), |
| "utf8", |
| ).toString("base64url"); |
| return toCommandLine([process.execPath, MCP_PROXY_PATH, "--payload", payload]); |
| } |
| |