@martinloop/mcp 0.5.1 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -14
- package/dist/server-validation.js +2 -0
- package/dist/server.js +30 -3
- package/dist/tools/run-loop.d.ts +1 -0
- package/dist/tools/run-loop.js +25 -4
- package/dist/tools/tool-response.d.ts +9 -1
- package/dist/tools/tool-response.js +7 -5
- package/dist/tools/tool-support.js +62 -271
- package/dist/vendor/adapters/claude-cli.d.ts +9 -0
- package/dist/vendor/adapters/claude-cli.js +19 -2
- package/dist/vendor/adapters/codex-capabilities.d.ts +69 -0
- package/dist/vendor/adapters/codex-capabilities.js +265 -0
- package/dist/vendor/adapters/codex-cli.d.ts +23 -0
- package/dist/vendor/adapters/codex-cli.js +72 -0
- package/dist/vendor/adapters/codex-host.d.ts +107 -0
- package/dist/vendor/adapters/codex-host.js +595 -0
- package/dist/vendor/adapters/codex-launcher.d.ts +7 -135
- package/dist/vendor/adapters/codex-launcher.js +17 -766
- package/dist/vendor/adapters/index.d.ts +4 -2
- package/dist/vendor/adapters/index.js +4 -2
- package/dist/vendor/contracts/agent-execution.d.ts +9 -0
- package/dist/vendor/contracts/agent-execution.js +14 -0
- package/dist/vendor/contracts/index.d.ts +9 -0
- package/dist/vendor/contracts/index.js +1 -0
- package/dist/vendor/core/grounding.js +42 -7
- package/dist/vendor/core/index.d.ts +5 -2
- package/dist/vendor/core/index.js +12 -3
- package/dist/vendor/presentation/governed-plan.d.ts +33 -0
- package/dist/vendor/presentation/governed-plan.js +146 -0
- package/dist/vendor/presentation/index.d.ts +7 -0
- package/dist/vendor/presentation/index.js +7 -0
- package/dist/vendor/presentation/motion.d.ts +12 -0
- package/dist/vendor/presentation/motion.js +123 -0
- package/dist/vendor/presentation/status.d.ts +8 -0
- package/dist/vendor/presentation/status.js +11 -0
- package/dist/vendor/presentation/table.d.ts +11 -0
- package/dist/vendor/presentation/table.js +47 -0
- package/dist/vendor/presentation/text.d.ts +6 -0
- package/dist/vendor/presentation/text.js +30 -0
- package/dist/vendor/presentation/theme.d.ts +31 -0
- package/dist/vendor/presentation/theme.js +82 -0
- package/dist/vendor/presentation/verified-handoff.d.ts +15 -0
- package/dist/vendor/presentation/verified-handoff.js +151 -0
- package/package.json +3 -2
- package/server.json +2 -2
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { type AgentExecutionIntent } from "../contracts/index.js";
|
|
3
|
+
export type CodexFlagScope = "global" | "exec";
|
|
4
|
+
export type CodexPromptTransport = "stdin-dash" | "argv";
|
|
5
|
+
export type CodexWriteStrategy = "sandbox+approval" | "automation";
|
|
6
|
+
export interface CodexCapabilityFlag {
|
|
7
|
+
flag: string;
|
|
8
|
+
scope: CodexFlagScope;
|
|
9
|
+
}
|
|
10
|
+
export interface CodexSandboxCapability extends CodexCapabilityFlag {
|
|
11
|
+
values: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface CodexApprovalCapability extends CodexCapabilityFlag {
|
|
14
|
+
semantics: "approval-policy" | "automation-mode";
|
|
15
|
+
values?: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface CodexAutonomyResolution {
|
|
18
|
+
binaryPath: string;
|
|
19
|
+
intent: AgentExecutionIntent;
|
|
20
|
+
strategy: "automation" | "sandbox+approval";
|
|
21
|
+
sandboxValue?: "workspace-write";
|
|
22
|
+
approvalValue?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface CodexCapabilityProfile {
|
|
25
|
+
binaryPath: string;
|
|
26
|
+
supportsExec: boolean;
|
|
27
|
+
probeSucceeded: boolean;
|
|
28
|
+
probeError?: string;
|
|
29
|
+
model?: CodexCapabilityFlag;
|
|
30
|
+
cwd?: CodexCapabilityFlag;
|
|
31
|
+
sandbox?: CodexSandboxCapability;
|
|
32
|
+
/** Preferred compatibility alias. `automation` and `approvalPolicy` are authoritative. */
|
|
33
|
+
approval?: CodexApprovalCapability;
|
|
34
|
+
automation?: CodexApprovalCapability;
|
|
35
|
+
approvalPolicy?: CodexApprovalCapability;
|
|
36
|
+
json?: CodexCapabilityFlag;
|
|
37
|
+
color?: CodexCapabilityFlag & {
|
|
38
|
+
neverValue?: string;
|
|
39
|
+
};
|
|
40
|
+
userConfigIsolation?: CodexCapabilityFlag;
|
|
41
|
+
promptTransport: CodexPromptTransport;
|
|
42
|
+
promptTransports?: CodexPromptTransport[];
|
|
43
|
+
}
|
|
44
|
+
export interface CodexExecArgsOptions {
|
|
45
|
+
command?: string;
|
|
46
|
+
workingDirectory: string;
|
|
47
|
+
sandbox?: "read-only" | "workspace-write" | "danger-full-access";
|
|
48
|
+
model?: string;
|
|
49
|
+
extraArgs?: string[];
|
|
50
|
+
mode?: "prompt" | "probe";
|
|
51
|
+
prompt?: string;
|
|
52
|
+
capabilityProfile?: CodexCapabilityProfile;
|
|
53
|
+
promptTransport?: CodexPromptTransport;
|
|
54
|
+
writeStrategy?: CodexWriteStrategy;
|
|
55
|
+
autonomyResolution?: CodexAutonomyResolution;
|
|
56
|
+
}
|
|
57
|
+
type SpawnSyncLike = typeof spawnSync;
|
|
58
|
+
export declare function clearCodexCapabilityCacheForTests(): void;
|
|
59
|
+
export declare function cacheCodexCapabilityProfile(profile: CodexCapabilityProfile, platform?: NodeJS.Platform): CodexCapabilityProfile;
|
|
60
|
+
export declare function probeCodexCapabilities(binaryPath: string, options?: {
|
|
61
|
+
platform?: NodeJS.Platform;
|
|
62
|
+
spawnSyncImpl?: SpawnSyncLike;
|
|
63
|
+
cache?: boolean;
|
|
64
|
+
}): CodexCapabilityProfile;
|
|
65
|
+
export declare function codexWriteStrategies(profile: CodexCapabilityProfile): CodexWriteStrategy[];
|
|
66
|
+
export declare function resolveCodexAutonomyCandidates(profile: CodexCapabilityProfile, intent?: AgentExecutionIntent): CodexAutonomyResolution[];
|
|
67
|
+
export declare function buildCodexExecArgs(options: CodexExecArgsOptions): string[];
|
|
68
|
+
export declare function buildCodexStdin(profile: CodexCapabilityProfile, prompt: string, transport?: CodexPromptTransport): string | undefined;
|
|
69
|
+
export {};
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { extname } from "node:path";
|
|
3
|
+
import { createSpawnPlan, resolveNpmShimScript } from "./cli-bridge.js";
|
|
4
|
+
import { DEFAULT_AGENT_EXECUTION_INTENT } from "../contracts/index.js";
|
|
5
|
+
const capabilityCache = new Map();
|
|
6
|
+
function escapeRegex(value) {
|
|
7
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
8
|
+
}
|
|
9
|
+
function flagPattern(flag) {
|
|
10
|
+
return new RegExp(`(?:^|\\s)${escapeRegex(flag)}(?=\\s|[=,<\\[]|$)`, "imu");
|
|
11
|
+
}
|
|
12
|
+
function locateFlag(globalHelp, execHelp, candidates) {
|
|
13
|
+
for (const flag of candidates) {
|
|
14
|
+
if (flagPattern(flag).test(execHelp))
|
|
15
|
+
return { flag, scope: "exec" };
|
|
16
|
+
}
|
|
17
|
+
for (const flag of candidates) {
|
|
18
|
+
if (flagPattern(flag).test(globalHelp))
|
|
19
|
+
return { flag, scope: "global" };
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
function flagContext(help, flag) {
|
|
24
|
+
const lines = help.split(/\r?\n/u);
|
|
25
|
+
const index = lines.findIndex((line) => line.includes(flag));
|
|
26
|
+
if (index < 0)
|
|
27
|
+
return "";
|
|
28
|
+
return lines.slice(Math.max(0, index - 1), Math.min(lines.length, index + 3)).join("\n");
|
|
29
|
+
}
|
|
30
|
+
function parseAdvertisedValues(help, flag) {
|
|
31
|
+
const flagIndex = help.indexOf(flag);
|
|
32
|
+
if (flagIndex < 0)
|
|
33
|
+
return [];
|
|
34
|
+
const tail = help.slice(flagIndex);
|
|
35
|
+
const nextFlagOffset = tail.slice(flag.length).search(/\n\s*-/u);
|
|
36
|
+
const scoped = nextFlagOffset < 0
|
|
37
|
+
? tail
|
|
38
|
+
: tail.slice(0, flag.length + nextFlagOffset);
|
|
39
|
+
const match = scoped.match(/possible values:\s*([^\]\n]+)/iu);
|
|
40
|
+
if (!match?.[1])
|
|
41
|
+
return [];
|
|
42
|
+
return match[1]
|
|
43
|
+
.split(",")
|
|
44
|
+
.map((value) => value.trim().replace(/[\].]$/u, ""))
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
function buildInjectedSpawnPlan(binaryPath, args, platform) {
|
|
48
|
+
if (platform !== "win32")
|
|
49
|
+
return { command: binaryPath, args };
|
|
50
|
+
const extension = extname(binaryPath).toLowerCase();
|
|
51
|
+
if (extension === ".cmd" || extension === ".bat" || extension === ".ps1") {
|
|
52
|
+
const script = resolveNpmShimScript(binaryPath);
|
|
53
|
+
if (script)
|
|
54
|
+
return { command: process.execPath, args: [script, ...args] };
|
|
55
|
+
if (extension === ".ps1") {
|
|
56
|
+
return {
|
|
57
|
+
command: "powershell.exe",
|
|
58
|
+
args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", binaryPath, ...args]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
command: process.env.ComSpec || "cmd.exe",
|
|
63
|
+
args: ["/d", "/c", binaryPath, ...args]
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return { command: binaryPath, args };
|
|
67
|
+
}
|
|
68
|
+
function runHelpProbe(input) {
|
|
69
|
+
const plan = input.injected
|
|
70
|
+
? buildInjectedSpawnPlan(input.binaryPath, input.args, input.platform)
|
|
71
|
+
: createSpawnPlan(input.binaryPath, input.args, process.cwd(), false);
|
|
72
|
+
const result = input.spawnSyncImpl(plan.command, plan.args, {
|
|
73
|
+
encoding: "utf8",
|
|
74
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
75
|
+
timeout: 8_000
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
text: `${result.stdout ?? ""}\n${result.stderr ?? ""}`,
|
|
79
|
+
status: result.status,
|
|
80
|
+
...(result.error ? { error: result.error.message } : {})
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function parsePromptTransports(execHelp) {
|
|
84
|
+
const transports = [];
|
|
85
|
+
const stdinAdvertised = /\bstdin\b/iu.test(execHelp) || /(?:^|\s)-(?:\s|,|$).*prompt/imu.test(execHelp);
|
|
86
|
+
const argvAdvertised = /(?:\[|<)PROMPT(?:\]|>)/iu.test(execHelp) || /\bPROMPT\b/iu.test(execHelp);
|
|
87
|
+
if (stdinAdvertised)
|
|
88
|
+
transports.push("stdin-dash");
|
|
89
|
+
if (argvAdvertised || !stdinAdvertised)
|
|
90
|
+
transports.push("argv");
|
|
91
|
+
return [...new Set(transports)];
|
|
92
|
+
}
|
|
93
|
+
export function clearCodexCapabilityCacheForTests() {
|
|
94
|
+
capabilityCache.clear();
|
|
95
|
+
}
|
|
96
|
+
export function cacheCodexCapabilityProfile(profile, platform = process.platform) {
|
|
97
|
+
capabilityCache.set(`${platform}:${profile.binaryPath}`, profile);
|
|
98
|
+
return profile;
|
|
99
|
+
}
|
|
100
|
+
export function probeCodexCapabilities(binaryPath, options = {}) {
|
|
101
|
+
const platform = options.platform ?? process.platform;
|
|
102
|
+
const key = `${platform}:${binaryPath}`;
|
|
103
|
+
const useCache = options.cache ?? options.spawnSyncImpl === undefined;
|
|
104
|
+
if (useCache) {
|
|
105
|
+
const cached = capabilityCache.get(key);
|
|
106
|
+
if (cached)
|
|
107
|
+
return cached;
|
|
108
|
+
}
|
|
109
|
+
const spawnSyncImpl = options.spawnSyncImpl ?? spawnSync;
|
|
110
|
+
const injected = options.spawnSyncImpl !== undefined;
|
|
111
|
+
const globalProbe = runHelpProbe({
|
|
112
|
+
binaryPath,
|
|
113
|
+
args: ["--help"],
|
|
114
|
+
platform,
|
|
115
|
+
spawnSyncImpl,
|
|
116
|
+
injected
|
|
117
|
+
});
|
|
118
|
+
const execProbe = runHelpProbe({
|
|
119
|
+
binaryPath,
|
|
120
|
+
args: ["exec", "--help"],
|
|
121
|
+
platform,
|
|
122
|
+
spawnSyncImpl,
|
|
123
|
+
injected
|
|
124
|
+
});
|
|
125
|
+
const globalHelp = globalProbe.text;
|
|
126
|
+
const execHelp = execProbe.text;
|
|
127
|
+
const supportsExec = execProbe.status === 0 &&
|
|
128
|
+
!/(unknown command|unrecognized subcommand|unexpected argument ['"]?exec)/iu.test(execHelp);
|
|
129
|
+
const sandboxFlag = locateFlag(globalHelp, execHelp, ["--sandbox", "-s"]);
|
|
130
|
+
const sandboxHelp = sandboxFlag
|
|
131
|
+
? `${flagContext(globalHelp, sandboxFlag.flag)}\n${flagContext(execHelp, sandboxFlag.flag)}`
|
|
132
|
+
: "";
|
|
133
|
+
const sandboxValues = ["read-only", "workspace-write", "danger-full-access"].filter((value) => new RegExp(`\\b${escapeRegex(value)}\\b`, "iu").test(sandboxHelp));
|
|
134
|
+
const automationFlag = locateFlag(globalHelp, execHelp, ["--approve-for-me", "--full-auto"]);
|
|
135
|
+
const automation = automationFlag
|
|
136
|
+
? { ...automationFlag, semantics: "automation-mode" }
|
|
137
|
+
: undefined;
|
|
138
|
+
const approvalFlag = locateFlag(globalHelp, execHelp, ["--ask-for-approval", "-a"]);
|
|
139
|
+
const approvalHelp = approvalFlag
|
|
140
|
+
? `${flagContext(globalHelp, approvalFlag.flag)}\n${flagContext(execHelp, approvalFlag.flag)}`
|
|
141
|
+
: "";
|
|
142
|
+
const approvalValues = approvalFlag ? parseAdvertisedValues(approvalHelp, approvalFlag.flag) : [];
|
|
143
|
+
const approvalPolicy = approvalFlag
|
|
144
|
+
? { ...approvalFlag, semantics: "approval-policy", values: approvalValues }
|
|
145
|
+
: undefined;
|
|
146
|
+
const colorFlag = locateFlag(globalHelp, execHelp, ["--color"]);
|
|
147
|
+
const colorHelp = colorFlag
|
|
148
|
+
? `${flagContext(globalHelp, colorFlag.flag)}\n${flagContext(execHelp, colorFlag.flag)}`
|
|
149
|
+
: "";
|
|
150
|
+
const promptTransports = parsePromptTransports(execHelp);
|
|
151
|
+
const probeError = globalProbe.error ?? execProbe.error;
|
|
152
|
+
const profile = {
|
|
153
|
+
binaryPath,
|
|
154
|
+
supportsExec,
|
|
155
|
+
probeSucceeded: supportsExec && `${globalHelp}\n${execHelp}`.trim().length > 0,
|
|
156
|
+
...(probeError ? { probeError } : {}),
|
|
157
|
+
...(locateFlag(globalHelp, execHelp, ["--model", "-m"])
|
|
158
|
+
? { model: locateFlag(globalHelp, execHelp, ["--model", "-m"]) }
|
|
159
|
+
: {}),
|
|
160
|
+
...(locateFlag(globalHelp, execHelp, ["--cd", "--cwd", "--working-dir", "-C"])
|
|
161
|
+
? { cwd: locateFlag(globalHelp, execHelp, ["--cd", "--cwd", "--working-dir", "-C"]) }
|
|
162
|
+
: {}),
|
|
163
|
+
...(sandboxFlag ? { sandbox: { ...sandboxFlag, values: sandboxValues } } : {}),
|
|
164
|
+
...(automation ? { automation, approval: automation } : {}),
|
|
165
|
+
...(approvalPolicy
|
|
166
|
+
? {
|
|
167
|
+
approvalPolicy,
|
|
168
|
+
...(!automation ? { approval: approvalPolicy } : {})
|
|
169
|
+
}
|
|
170
|
+
: {}),
|
|
171
|
+
...(locateFlag(globalHelp, execHelp, ["--json"])
|
|
172
|
+
? { json: locateFlag(globalHelp, execHelp, ["--json"]) }
|
|
173
|
+
: {}),
|
|
174
|
+
...(colorFlag
|
|
175
|
+
? {
|
|
176
|
+
color: {
|
|
177
|
+
...colorFlag,
|
|
178
|
+
...(/\bnever\b/iu.test(colorHelp) ? { neverValue: "never" } : {})
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
: {}),
|
|
182
|
+
...(locateFlag(globalHelp, execHelp, ["--ignore-user-config", "--no-user-config"])
|
|
183
|
+
? { userConfigIsolation: locateFlag(globalHelp, execHelp, ["--ignore-user-config", "--no-user-config"]) }
|
|
184
|
+
: {}),
|
|
185
|
+
promptTransport: promptTransports[0] ?? "argv",
|
|
186
|
+
promptTransports
|
|
187
|
+
};
|
|
188
|
+
if (useCache)
|
|
189
|
+
cacheCodexCapabilityProfile(profile, platform);
|
|
190
|
+
return profile;
|
|
191
|
+
}
|
|
192
|
+
function pushCapabilityArg(globalArgs, execArgs, capability, ...values) {
|
|
193
|
+
(capability.scope === "global" ? globalArgs : execArgs).push(capability.flag, ...values);
|
|
194
|
+
}
|
|
195
|
+
export function codexWriteStrategies(profile) {
|
|
196
|
+
return resolveCodexAutonomyCandidates(profile).map((candidate) => candidate.strategy);
|
|
197
|
+
}
|
|
198
|
+
export function resolveCodexAutonomyCandidates(profile, intent = DEFAULT_AGENT_EXECUTION_INTENT) {
|
|
199
|
+
const candidates = [];
|
|
200
|
+
if (profile.automation) {
|
|
201
|
+
candidates.push({
|
|
202
|
+
binaryPath: profile.binaryPath,
|
|
203
|
+
intent,
|
|
204
|
+
strategy: "automation"
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return candidates;
|
|
208
|
+
}
|
|
209
|
+
export function buildCodexExecArgs(options) {
|
|
210
|
+
const profile = options.capabilityProfile ?? probeCodexCapabilities(options.command ?? "codex");
|
|
211
|
+
if (!profile.supportsExec) {
|
|
212
|
+
throw new Error(`Resolved Codex binary ${profile.binaryPath} does not advertise a usable exec subcommand.`);
|
|
213
|
+
}
|
|
214
|
+
const resolution = options.autonomyResolution;
|
|
215
|
+
if (options.mode !== "probe" && !resolution) {
|
|
216
|
+
throw new Error(`Resolved Codex binary ${profile.binaryPath} has no negotiated governed-autonomous execution resolution.`);
|
|
217
|
+
}
|
|
218
|
+
const permissionFlags = [profile.sandbox?.flag, profile.automation?.flag, profile.approvalPolicy?.flag]
|
|
219
|
+
.filter((value) => Boolean(value));
|
|
220
|
+
if ((options.extraArgs ?? []).some((arg) => arg === "danger-full-access" || permissionFlags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)))) {
|
|
221
|
+
throw new Error("Permission and sandbox controls cannot be supplied through extraArgs.");
|
|
222
|
+
}
|
|
223
|
+
const globalArgs = [];
|
|
224
|
+
const execArgs = [];
|
|
225
|
+
const requestedSandbox = options.sandbox ?? "workspace-write";
|
|
226
|
+
const strategy = resolution?.strategy ?? options.writeStrategy;
|
|
227
|
+
const promptTransport = options.promptTransport ?? profile.promptTransport;
|
|
228
|
+
if (profile.userConfigIsolation)
|
|
229
|
+
pushCapabilityArg(globalArgs, execArgs, profile.userConfigIsolation);
|
|
230
|
+
if (profile.cwd)
|
|
231
|
+
pushCapabilityArg(globalArgs, execArgs, profile.cwd, options.workingDirectory);
|
|
232
|
+
if (requestedSandbox === "workspace-write") {
|
|
233
|
+
if (strategy === "sandbox+approval" && profile.sandbox?.values.includes("workspace-write")) {
|
|
234
|
+
pushCapabilityArg(globalArgs, execArgs, profile.sandbox, "workspace-write");
|
|
235
|
+
}
|
|
236
|
+
if (strategy === "automation" && profile.automation) {
|
|
237
|
+
pushCapabilityArg(globalArgs, execArgs, profile.automation);
|
|
238
|
+
}
|
|
239
|
+
if (strategy === "sandbox+approval" && profile.approvalPolicy && resolution?.approvalValue) {
|
|
240
|
+
pushCapabilityArg(globalArgs, execArgs, profile.approvalPolicy, resolution.approvalValue);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
if (!profile.sandbox?.values.includes(requestedSandbox)) {
|
|
245
|
+
throw new Error(`Resolved Codex binary ${profile.binaryPath} does not advertise requested sandbox mode ${requestedSandbox}.`);
|
|
246
|
+
}
|
|
247
|
+
pushCapabilityArg(globalArgs, execArgs, profile.sandbox, requestedSandbox);
|
|
248
|
+
}
|
|
249
|
+
if (profile.json)
|
|
250
|
+
pushCapabilityArg(globalArgs, execArgs, profile.json);
|
|
251
|
+
if (profile.color?.neverValue)
|
|
252
|
+
pushCapabilityArg(globalArgs, execArgs, profile.color, profile.color.neverValue);
|
|
253
|
+
if (options.model) {
|
|
254
|
+
if (!profile.model) {
|
|
255
|
+
throw new Error(`Resolved Codex binary ${profile.binaryPath} does not advertise a model override flag.`);
|
|
256
|
+
}
|
|
257
|
+
pushCapabilityArg(globalArgs, execArgs, profile.model, options.model);
|
|
258
|
+
}
|
|
259
|
+
const prompt = options.prompt ?? "";
|
|
260
|
+
const promptArgs = promptTransport === "stdin-dash" ? ["-"] : prompt ? [prompt] : [];
|
|
261
|
+
return [...globalArgs, "exec", ...execArgs, ...(options.extraArgs ?? []), ...promptArgs];
|
|
262
|
+
}
|
|
263
|
+
export function buildCodexStdin(profile, prompt, transport = profile.promptTransport) {
|
|
264
|
+
return transport === "stdin-dash" ? prompt : undefined;
|
|
265
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type CodexCliAdapterOptions as LegacyCodexCliAdapterOptions } from "./claude-cli.js";
|
|
2
|
+
import { type CodexAutonomyResolution, type CodexCapabilityProfile } from "./codex-launcher.js";
|
|
3
|
+
export interface CodexCliAdapterOptions extends Omit<LegacyCodexCliAdapterOptions, "command"> {
|
|
4
|
+
/** Exact resolved executable selected by the Codex launch probe. */
|
|
5
|
+
command?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Optional pre-probed capability profile. Production callers normally omit
|
|
8
|
+
* this because probeCodexCapabilities caches profiles by exact binary path
|
|
9
|
+
* for the process lifetime. Exposed for deterministic integrations/tests.
|
|
10
|
+
*/
|
|
11
|
+
capabilityProfile?: CodexCapabilityProfile;
|
|
12
|
+
autonomyResolution?: CodexAutonomyResolution;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Capability-driven Codex CLI adapter.
|
|
16
|
+
*
|
|
17
|
+
* Provider identity remains `codex` even when doctor/preflight selected an
|
|
18
|
+
* absolute native binary or npm shim. The selected binary's cached capability
|
|
19
|
+
* profile builds both the real argv and stdin transport, while the spawn router
|
|
20
|
+
* sends only the Codex subprocess to that exact executable. Git/verifier
|
|
21
|
+
* subprocesses retain their normal commands.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createCodexCliAdapter(options?: CodexCliAdapterOptions): import("../core/index.js").MartinAdapter;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createAgentCliAdapter } from "./claude-cli.js";
|
|
3
|
+
import { buildCodexExecArgs, buildCodexStdin, probeCodexCapabilities } from "./codex-launcher.js";
|
|
4
|
+
import { createSpawnPlan } from "./cli-bridge.js";
|
|
5
|
+
function createCodexSpawnRouter(input) {
|
|
6
|
+
if (input.injectedSpawn) {
|
|
7
|
+
return (command, args = [], options) => input.injectedSpawn?.(command === "codex" ? input.selectedBinary : command, args, options);
|
|
8
|
+
}
|
|
9
|
+
return (command, args = [], options) => {
|
|
10
|
+
const executable = command === "codex" ? input.selectedBinary : command;
|
|
11
|
+
const cwd = typeof options?.cwd === "string" ? options.cwd : input.workingDirectory;
|
|
12
|
+
const plan = createSpawnPlan(executable, [...args], cwd, false);
|
|
13
|
+
return spawn(plan.command, plan.args, options ?? {});
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Capability-driven Codex CLI adapter.
|
|
18
|
+
*
|
|
19
|
+
* Provider identity remains `codex` even when doctor/preflight selected an
|
|
20
|
+
* absolute native binary or npm shim. The selected binary's cached capability
|
|
21
|
+
* profile builds both the real argv and stdin transport, while the spawn router
|
|
22
|
+
* sends only the Codex subprocess to that exact executable. Git/verifier
|
|
23
|
+
* subprocesses retain their normal commands.
|
|
24
|
+
*/
|
|
25
|
+
export function createCodexCliAdapter(options = {}) {
|
|
26
|
+
const workingDirectory = options.workingDirectory ?? process.cwd();
|
|
27
|
+
const selectedBinary = options.command ?? options.capabilityProfile?.binaryPath ?? "codex";
|
|
28
|
+
const capabilityProfile = options.capabilityProfile ?? probeCodexCapabilities(selectedBinary);
|
|
29
|
+
const autonomyResolution = options.autonomyResolution;
|
|
30
|
+
if (capabilityProfile.binaryPath !== selectedBinary) {
|
|
31
|
+
throw new Error("Codex exact binary capability profile mismatch.");
|
|
32
|
+
}
|
|
33
|
+
if (autonomyResolution && autonomyResolution.binaryPath !== selectedBinary) {
|
|
34
|
+
throw new Error("Codex exact binary autonomy resolution mismatch.");
|
|
35
|
+
}
|
|
36
|
+
const sandbox = options.sandbox ?? "workspace-write";
|
|
37
|
+
const extraArgs = options.extraArgs ?? [];
|
|
38
|
+
const launchModel = options.model;
|
|
39
|
+
const spawnImpl = createCodexSpawnRouter({
|
|
40
|
+
selectedBinary,
|
|
41
|
+
workingDirectory,
|
|
42
|
+
...(options.spawnImpl ? { injectedSpawn: options.spawnImpl } : {})
|
|
43
|
+
});
|
|
44
|
+
return createAgentCliAdapter({
|
|
45
|
+
// Keep semantic provider identity stable for usage parsing, pricing,
|
|
46
|
+
// diagnostics, and adapter metadata. The spawn router maps this to the
|
|
47
|
+
// exact selected binary at process launch time.
|
|
48
|
+
command: "codex",
|
|
49
|
+
adapterIdSuffix: "codex",
|
|
50
|
+
model: options.model,
|
|
51
|
+
label: options.label ?? "Codex CLI adapter",
|
|
52
|
+
workingDirectory,
|
|
53
|
+
timeoutMs: options.timeoutMs,
|
|
54
|
+
agentExecutionIntent: options.agentExecutionIntent,
|
|
55
|
+
providerExecutionTimeoutMs: options.providerExecutionTimeoutMs,
|
|
56
|
+
verifyTimeoutMs: options.verifyTimeoutMs,
|
|
57
|
+
supportsJsonOutput: false,
|
|
58
|
+
spawnImpl,
|
|
59
|
+
argsBuilder: (prompt) => buildCodexExecArgs({
|
|
60
|
+
command: selectedBinary,
|
|
61
|
+
workingDirectory,
|
|
62
|
+
sandbox,
|
|
63
|
+
...(launchModel ? { model: launchModel } : {}),
|
|
64
|
+
extraArgs,
|
|
65
|
+
mode: "prompt",
|
|
66
|
+
prompt,
|
|
67
|
+
capabilityProfile,
|
|
68
|
+
...(autonomyResolution ? { autonomyResolution } : {})
|
|
69
|
+
}),
|
|
70
|
+
stdinBuilder: (prompt) => buildCodexStdin(capabilityProfile, prompt)
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { type CodexAutonomyResolution, type CodexCapabilityProfile, type CodexPromptTransport, type CodexWriteStrategy } from "./codex-capabilities.js";
|
|
3
|
+
export interface CliCommandAvailability {
|
|
4
|
+
command: string;
|
|
5
|
+
available: boolean;
|
|
6
|
+
locator: string;
|
|
7
|
+
detail: string;
|
|
8
|
+
resolvedPath?: string;
|
|
9
|
+
candidatePaths?: string[];
|
|
10
|
+
}
|
|
11
|
+
export type CodexHostPlatform = "windows" | "linux" | "wsl" | "macos";
|
|
12
|
+
export type CodexInstallKind = "missing" | "native" | "windows_shim" | "windows_mounted_path";
|
|
13
|
+
export type CodexInvocationMode = "direct" | "cmd_shell" | "powershell";
|
|
14
|
+
export interface CodexHostDiagnosis {
|
|
15
|
+
hostPlatform: CodexHostPlatform;
|
|
16
|
+
nativeInstallValid: boolean;
|
|
17
|
+
installKind: CodexInstallKind;
|
|
18
|
+
invocationMode: CodexInvocationMode;
|
|
19
|
+
sandboxMode: "workspace-write";
|
|
20
|
+
sandboxCompatible: boolean;
|
|
21
|
+
resolvedPath?: string;
|
|
22
|
+
nativeDependencyStatus?: "unknown" | "missing";
|
|
23
|
+
nativeDependencyPackage?: string;
|
|
24
|
+
warnings: string[];
|
|
25
|
+
remediation?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface CodexProbeCandidateResult {
|
|
28
|
+
path: string;
|
|
29
|
+
installKind: CodexInstallKind;
|
|
30
|
+
invocationMode: CodexInvocationMode;
|
|
31
|
+
nativeInstallValid: boolean;
|
|
32
|
+
sandboxCompatible: boolean;
|
|
33
|
+
launchReady: boolean;
|
|
34
|
+
summary: string;
|
|
35
|
+
remediation?: string;
|
|
36
|
+
nativeDependencyStatus?: "unknown" | "missing";
|
|
37
|
+
nativeDependencyPackage?: string;
|
|
38
|
+
capabilityProfile?: CodexCapabilityProfile;
|
|
39
|
+
writeStrategy?: CodexWriteStrategy;
|
|
40
|
+
autonomyResolution?: CodexAutonomyResolution;
|
|
41
|
+
promptTransport?: CodexPromptTransport;
|
|
42
|
+
}
|
|
43
|
+
export interface CodexLaunchProbeResult {
|
|
44
|
+
ok: boolean;
|
|
45
|
+
summary: string;
|
|
46
|
+
availability: CliCommandAvailability;
|
|
47
|
+
diagnosis: CodexHostDiagnosis;
|
|
48
|
+
command: string;
|
|
49
|
+
args: string[];
|
|
50
|
+
capabilityProfile?: CodexCapabilityProfile;
|
|
51
|
+
writeStrategy?: CodexWriteStrategy;
|
|
52
|
+
autonomyResolution?: CodexAutonomyResolution;
|
|
53
|
+
promptTransport?: CodexPromptTransport;
|
|
54
|
+
exitCode?: number;
|
|
55
|
+
stdout?: string;
|
|
56
|
+
stderr?: string;
|
|
57
|
+
candidateProbeResults?: CodexProbeCandidateResult[];
|
|
58
|
+
}
|
|
59
|
+
type SpawnSyncLike = typeof spawnSync;
|
|
60
|
+
export interface CodexSandboxPreflightOk {
|
|
61
|
+
ok: true;
|
|
62
|
+
effectiveSandbox: "read-only" | "workspace-write";
|
|
63
|
+
capabilitySource: "probe";
|
|
64
|
+
writableRoot: string;
|
|
65
|
+
}
|
|
66
|
+
export interface CodexSandboxPreflightReadOnly {
|
|
67
|
+
ok: false;
|
|
68
|
+
code: "provider_sandbox_read_only";
|
|
69
|
+
requestedCapability: "workspace-write";
|
|
70
|
+
detectedCapability: "read-only";
|
|
71
|
+
effectiveSandbox: "read-only";
|
|
72
|
+
affectedPath: string;
|
|
73
|
+
writableRoot: string;
|
|
74
|
+
capabilitySource: "probe";
|
|
75
|
+
remediation: string;
|
|
76
|
+
}
|
|
77
|
+
export type CodexSandboxPreflightOutcome = CodexSandboxPreflightOk | CodexSandboxPreflightReadOnly;
|
|
78
|
+
export declare function probeFilesystemWriteCapability(directory: string): {
|
|
79
|
+
writable: true;
|
|
80
|
+
} | {
|
|
81
|
+
writable: false;
|
|
82
|
+
reason: string;
|
|
83
|
+
};
|
|
84
|
+
export declare function checkCodexSandboxPreflight(input: {
|
|
85
|
+
requestedSandbox: "read-only" | "workspace-write";
|
|
86
|
+
workingDirectory: string;
|
|
87
|
+
}): CodexSandboxPreflightOutcome;
|
|
88
|
+
export declare function resolveCliCommandAvailability(command: string, options?: {
|
|
89
|
+
platform?: NodeJS.Platform;
|
|
90
|
+
env?: NodeJS.ProcessEnv;
|
|
91
|
+
spawnSyncImpl?: SpawnSyncLike;
|
|
92
|
+
}): CliCommandAvailability;
|
|
93
|
+
export declare function detectCodexHostPlatform(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): CodexHostPlatform;
|
|
94
|
+
export declare function diagnoseCodexHost(availability: CliCommandAvailability, options?: {
|
|
95
|
+
env?: NodeJS.ProcessEnv;
|
|
96
|
+
platform?: NodeJS.Platform;
|
|
97
|
+
}): CodexHostDiagnosis;
|
|
98
|
+
export declare function probeCodexLaunch(input: {
|
|
99
|
+
workingDirectory: string;
|
|
100
|
+
availability?: CliCommandAvailability;
|
|
101
|
+
env?: NodeJS.ProcessEnv;
|
|
102
|
+
platform?: NodeJS.Platform;
|
|
103
|
+
spawnSyncImpl?: SpawnSyncLike;
|
|
104
|
+
model?: string;
|
|
105
|
+
providerExecutionTimeoutMs?: number;
|
|
106
|
+
}): CodexLaunchProbeResult;
|
|
107
|
+
export {};
|