File size: 3,410 Bytes
fc93158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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]);
}