@mcoda/agents 0.1.35 → 0.1.37
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/dist/adapters/codex/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/codex/CodexAdapter.js +16 -2
- package/dist/adapters/codex/CodexCliRunner.d.ts +3 -3
- package/dist/adapters/codex/CodexCliRunner.d.ts.map +1 -1
- package/dist/adapters/codex/CodexCliRunner.js +345 -140
- package/dist/adapters/openai/OpenAiCliAdapter.js +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodexAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/codex/CodexAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"CodexAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/codex/CodexAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAmBtG,qBAAa,YAAa,YAAW,YAAY;IACnC,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAEnC,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIpC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IAYnC,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwB5D,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAAC,gBAAgB,EAAE,IAAI,EAAE,OAAO,CAAC;CAyBjG"}
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { cliHealthy, runCodexExec, runCodexExecStream } from "./CodexCliRunner.js";
|
|
2
|
+
const extractOutputSchema = (request) => {
|
|
3
|
+
const candidate = request.metadata?.outputSchema;
|
|
4
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
5
|
+
return undefined;
|
|
6
|
+
}
|
|
7
|
+
return candidate;
|
|
8
|
+
};
|
|
9
|
+
const extractTimeoutMs = (request) => {
|
|
10
|
+
const candidate = request.metadata?.timeoutMs;
|
|
11
|
+
if (typeof candidate !== "number" || !Number.isFinite(candidate) || candidate <= 0) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return Math.floor(candidate);
|
|
15
|
+
};
|
|
2
16
|
export class CodexAdapter {
|
|
3
17
|
constructor(config) {
|
|
4
18
|
this.config = config;
|
|
@@ -20,7 +34,7 @@ export class CodexAdapter {
|
|
|
20
34
|
async invoke(request) {
|
|
21
35
|
const health = cliHealthy(true);
|
|
22
36
|
const cliDetails = health.details;
|
|
23
|
-
const result = runCodexExec(request.input, this.config.model);
|
|
37
|
+
const result = await runCodexExec(request.input, this.config.model, extractOutputSchema(request), extractTimeoutMs(request));
|
|
24
38
|
return {
|
|
25
39
|
output: result.output,
|
|
26
40
|
adapter: this.config.adapter ?? "codex-cli",
|
|
@@ -38,7 +52,7 @@ export class CodexAdapter {
|
|
|
38
52
|
async *invokeStream(request) {
|
|
39
53
|
const health = cliHealthy(true);
|
|
40
54
|
const cliDetails = health.details;
|
|
41
|
-
for await (const chunk of runCodexExecStream(request.input, this.config.model)) {
|
|
55
|
+
for await (const chunk of runCodexExecStream(request.input, this.config.model, extractOutputSchema(request), extractTimeoutMs(request))) {
|
|
42
56
|
yield {
|
|
43
57
|
output: chunk.output,
|
|
44
58
|
adapter: this.config.adapter ?? "codex-cli",
|
|
@@ -2,11 +2,11 @@ export declare const cliHealthy: (throwOnError?: boolean) => {
|
|
|
2
2
|
ok: boolean;
|
|
3
3
|
details?: Record<string, unknown>;
|
|
4
4
|
};
|
|
5
|
-
export declare const runCodexExec: (prompt: string, model?: string) => {
|
|
5
|
+
export declare const runCodexExec: (prompt: string, model?: string, outputSchema?: Record<string, unknown>, timeoutMs?: number) => Promise<{
|
|
6
6
|
output: string;
|
|
7
7
|
raw: string;
|
|
8
|
-
}
|
|
9
|
-
export declare function runCodexExecStream(prompt: string, model?: string): AsyncGenerator<{
|
|
8
|
+
}>;
|
|
9
|
+
export declare function runCodexExecStream(prompt: string, model?: string, outputSchema?: Record<string, unknown>, timeoutMs?: number): AsyncGenerator<{
|
|
10
10
|
output: string;
|
|
11
11
|
raw: string;
|
|
12
12
|
}, void, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodexCliRunner.d.ts","sourceRoot":"","sources":["../../../src/adapters/codex/CodexCliRunner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CodexCliRunner.d.ts","sourceRoot":"","sources":["../../../src/adapters/codex/CodexCliRunner.ts"],"names":[],"mappings":"AAk1BA,eAAO,MAAM,UAAU,GAAI,sBAAoB,KAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CA+BjG,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,QAAQ,MAAM,EACd,QAAQ,MAAM,EACd,eAAe,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,YAAY,MAAM,KACjB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CASzC,CAAC;AAEF,wBAAuB,kBAAkB,CACvC,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,SAAS,CAAC,EAAE,MAAM,GACjB,cAAc,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,EAAE,OAAO,CAAC,CAmFhE"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
2
5
|
const CODEX_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
3
6
|
const CODEX_REASONING_ENV = "MCODA_CODEX_REASONING_EFFORT";
|
|
4
7
|
const CODEX_REASONING_ENV_FALLBACK = "CODEX_REASONING_EFFORT";
|
|
@@ -7,6 +10,12 @@ const CODEX_STREAM_IO_ENV = "MCODA_STREAM_IO";
|
|
|
7
10
|
const CODEX_STREAM_IO_FORMAT_ENV = "MCODA_STREAM_IO_FORMAT";
|
|
8
11
|
const CODEX_STREAM_IO_COLOR_ENV = "MCODA_STREAM_IO_COLOR";
|
|
9
12
|
const CODEX_STREAM_IO_PREFIX = "codex-cli";
|
|
13
|
+
const CODEX_COMMAND_ENV = "MCODA_CODEX_COMMAND";
|
|
14
|
+
const CODEX_COMMAND_ARGS_ENV = "MCODA_CODEX_COMMAND_ARGS";
|
|
15
|
+
const CODEX_TIMEOUT_ENV = "MCODA_CODEX_TIMEOUT_MS";
|
|
16
|
+
const CODEX_EXIT_GRACE_ENV = "MCODA_CODEX_EXIT_GRACE_MS";
|
|
17
|
+
const CODEX_DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
18
|
+
const CODEX_DEFAULT_EXIT_GRACE_MS = 5000;
|
|
10
19
|
const ANSI = {
|
|
11
20
|
reset: "\u001b[0m",
|
|
12
21
|
bold: "\u001b[1m",
|
|
@@ -114,6 +123,95 @@ const extractItemText = (item) => {
|
|
|
114
123
|
}
|
|
115
124
|
return "";
|
|
116
125
|
};
|
|
126
|
+
const parseCommandArgs = (raw) => {
|
|
127
|
+
const trimmed = raw?.trim();
|
|
128
|
+
if (!trimmed)
|
|
129
|
+
return [];
|
|
130
|
+
if (trimmed.startsWith("[")) {
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(trimmed);
|
|
133
|
+
if (Array.isArray(parsed)) {
|
|
134
|
+
return parsed
|
|
135
|
+
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
|
136
|
+
.filter(Boolean);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// fall through to whitespace parsing
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return trimmed.split(/\s+/).filter(Boolean);
|
|
144
|
+
};
|
|
145
|
+
const resolveCodexCommand = () => {
|
|
146
|
+
const command = process.env[CODEX_COMMAND_ENV]?.trim() || "codex";
|
|
147
|
+
const preArgs = parseCommandArgs(process.env[CODEX_COMMAND_ARGS_ENV]);
|
|
148
|
+
return { command, preArgs };
|
|
149
|
+
};
|
|
150
|
+
const parsePositiveIntEnv = (name, fallback) => {
|
|
151
|
+
const raw = process.env[name]?.trim();
|
|
152
|
+
if (!raw)
|
|
153
|
+
return fallback;
|
|
154
|
+
const parsed = Number(raw);
|
|
155
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
156
|
+
};
|
|
157
|
+
const resolveCodexTimeoutMs = () => parsePositiveIntEnv(CODEX_TIMEOUT_ENV, CODEX_DEFAULT_TIMEOUT_MS);
|
|
158
|
+
const resolveCodexExitGraceMs = () => parsePositiveIntEnv(CODEX_EXIT_GRACE_ENV, CODEX_DEFAULT_EXIT_GRACE_MS);
|
|
159
|
+
const isIgnorableStdinError = (error) => error.code === "EPIPE" || error.code === "ERR_STREAM_DESTROYED";
|
|
160
|
+
const safeKill = (child, signal) => {
|
|
161
|
+
try {
|
|
162
|
+
if (!child.killed) {
|
|
163
|
+
child.kill(signal);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
/* ignore kill errors */
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const scheduleHardKill = (child, delayMs = 250) => {
|
|
171
|
+
const timer = setTimeout(() => {
|
|
172
|
+
safeKill(child, "SIGKILL");
|
|
173
|
+
}, delayMs);
|
|
174
|
+
timer.unref();
|
|
175
|
+
};
|
|
176
|
+
const extractProtocolError = (parsed) => {
|
|
177
|
+
if (!parsed || typeof parsed !== "object")
|
|
178
|
+
return undefined;
|
|
179
|
+
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
180
|
+
if (type === "turn.failed") {
|
|
181
|
+
const message = parsed?.error?.message ?? parsed?.error;
|
|
182
|
+
return message ? String(message) : "codex turn failed";
|
|
183
|
+
}
|
|
184
|
+
if (type === "error" && parsed?.message) {
|
|
185
|
+
return String(parsed.message);
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
};
|
|
189
|
+
const isCompletionEvent = (parsed) => {
|
|
190
|
+
if (!parsed || typeof parsed !== "object")
|
|
191
|
+
return false;
|
|
192
|
+
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
193
|
+
return type === "turn.completed" || type === "response.completed";
|
|
194
|
+
};
|
|
195
|
+
const parseCodexOutput = (raw) => {
|
|
196
|
+
const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
197
|
+
let message = "";
|
|
198
|
+
for (const line of lines) {
|
|
199
|
+
const parsed = safeJsonParse(line);
|
|
200
|
+
const event = extractAssistantText(parsed);
|
|
201
|
+
if (!event)
|
|
202
|
+
continue;
|
|
203
|
+
if (event.kind === "delta") {
|
|
204
|
+
message += event.text;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
message = event.text;
|
|
208
|
+
}
|
|
209
|
+
if (!message) {
|
|
210
|
+
return lines[lines.length - 1] ?? "";
|
|
211
|
+
}
|
|
212
|
+
return message;
|
|
213
|
+
};
|
|
214
|
+
const normalizeComparableAssistantText = (value) => value.replace(/\r\n/g, "\n").trim();
|
|
117
215
|
const normalizeValue = (value) => {
|
|
118
216
|
if (typeof value !== "string")
|
|
119
217
|
return value;
|
|
@@ -541,6 +639,185 @@ const extractAssistantText = (parsed) => {
|
|
|
541
639
|
}
|
|
542
640
|
return null;
|
|
543
641
|
};
|
|
642
|
+
const materializeOutputSchema = async (outputSchema) => {
|
|
643
|
+
if (!outputSchema || typeof outputSchema !== "object" || Array.isArray(outputSchema)) {
|
|
644
|
+
return undefined;
|
|
645
|
+
}
|
|
646
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), "mcoda-codex-schema-"));
|
|
647
|
+
const schemaPath = path.join(tempDir, "output-schema.json");
|
|
648
|
+
await writeFile(schemaPath, `${JSON.stringify(outputSchema, null, 2)}\n`, "utf8");
|
|
649
|
+
return {
|
|
650
|
+
schemaPath,
|
|
651
|
+
cleanup: async () => {
|
|
652
|
+
try {
|
|
653
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
/* ignore cleanup errors */
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
};
|
|
660
|
+
};
|
|
661
|
+
const invokeCodexProcess = async (prompt, model, options) => {
|
|
662
|
+
const resolvedModel = model ?? "gpt-5.1-codex-max";
|
|
663
|
+
const sandboxArgs = resolveSandboxArgs();
|
|
664
|
+
const reasoningEffort = resolveReasoningEffort(resolvedModel);
|
|
665
|
+
const codexCommand = resolveCodexCommand();
|
|
666
|
+
const args = [...codexCommand.preArgs, ...sandboxArgs.args, "exec", "--model", resolvedModel, "--json"];
|
|
667
|
+
const schemaFile = await materializeOutputSchema(options?.outputSchema);
|
|
668
|
+
if (schemaFile) {
|
|
669
|
+
args.push("--output-schema", schemaFile.schemaPath);
|
|
670
|
+
}
|
|
671
|
+
if (!sandboxArgs.bypass) {
|
|
672
|
+
args.push("--full-auto");
|
|
673
|
+
}
|
|
674
|
+
if (reasoningEffort) {
|
|
675
|
+
args.push("-c", `model_reasoning_effort=${reasoningEffort}`);
|
|
676
|
+
}
|
|
677
|
+
args.push("-");
|
|
678
|
+
const timeoutMs = typeof options?.timeoutMs === "number" && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0
|
|
679
|
+
? Math.floor(options.timeoutMs)
|
|
680
|
+
: resolveCodexTimeoutMs();
|
|
681
|
+
const exitGraceMs = resolveCodexExitGraceMs();
|
|
682
|
+
try {
|
|
683
|
+
return await new Promise((resolve, reject) => {
|
|
684
|
+
const child = spawn(codexCommand.command, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
685
|
+
let raw = "";
|
|
686
|
+
let stderr = "";
|
|
687
|
+
let lineBuffer = "";
|
|
688
|
+
let message = "";
|
|
689
|
+
let protocolError;
|
|
690
|
+
let settled = false;
|
|
691
|
+
let forcedExit = false;
|
|
692
|
+
let completionTimer;
|
|
693
|
+
const clearTimers = () => {
|
|
694
|
+
if (completionTimer) {
|
|
695
|
+
clearTimeout(completionTimer);
|
|
696
|
+
completionTimer = undefined;
|
|
697
|
+
}
|
|
698
|
+
clearTimeout(timeoutHandle);
|
|
699
|
+
};
|
|
700
|
+
const finalizeOutput = () => {
|
|
701
|
+
const parsedOutput = parseCodexOutput(raw).trim();
|
|
702
|
+
return parsedOutput || message.trim();
|
|
703
|
+
};
|
|
704
|
+
const finishResolve = () => {
|
|
705
|
+
if (settled)
|
|
706
|
+
return;
|
|
707
|
+
settled = true;
|
|
708
|
+
clearTimers();
|
|
709
|
+
resolve({ output: finalizeOutput(), raw, forcedExit });
|
|
710
|
+
};
|
|
711
|
+
const finishReject = (error) => {
|
|
712
|
+
if (settled)
|
|
713
|
+
return;
|
|
714
|
+
settled = true;
|
|
715
|
+
clearTimers();
|
|
716
|
+
reject(error);
|
|
717
|
+
};
|
|
718
|
+
const scheduleCompletionGrace = () => {
|
|
719
|
+
if (settled)
|
|
720
|
+
return;
|
|
721
|
+
if (completionTimer) {
|
|
722
|
+
clearTimeout(completionTimer);
|
|
723
|
+
}
|
|
724
|
+
completionTimer = setTimeout(() => {
|
|
725
|
+
if (settled)
|
|
726
|
+
return;
|
|
727
|
+
forcedExit = true;
|
|
728
|
+
safeKill(child, "SIGTERM");
|
|
729
|
+
scheduleHardKill(child);
|
|
730
|
+
finishResolve();
|
|
731
|
+
}, exitGraceMs);
|
|
732
|
+
completionTimer.unref();
|
|
733
|
+
};
|
|
734
|
+
const handleLine = (line) => {
|
|
735
|
+
const normalized = line.replace(/\r$/, "");
|
|
736
|
+
options?.hooks?.onLine?.(normalized);
|
|
737
|
+
const parsed = safeJsonParse(normalized);
|
|
738
|
+
const event = extractAssistantText(parsed);
|
|
739
|
+
if (event) {
|
|
740
|
+
if (event.kind === "delta") {
|
|
741
|
+
message += event.text;
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
message = event.text;
|
|
745
|
+
scheduleCompletionGrace();
|
|
746
|
+
}
|
|
747
|
+
options?.hooks?.onAssistantEvent?.(event, normalized);
|
|
748
|
+
}
|
|
749
|
+
const parsedError = extractProtocolError(parsed);
|
|
750
|
+
if (parsedError) {
|
|
751
|
+
protocolError = parsedError;
|
|
752
|
+
}
|
|
753
|
+
if (isCompletionEvent(parsed)) {
|
|
754
|
+
scheduleCompletionGrace();
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
const timeoutHandle = setTimeout(() => {
|
|
758
|
+
if (settled)
|
|
759
|
+
return;
|
|
760
|
+
forcedExit = true;
|
|
761
|
+
safeKill(child, "SIGTERM");
|
|
762
|
+
scheduleHardKill(child);
|
|
763
|
+
const detail = stderr.trim().length > 0 ? `: ${stderr.trim()}` : "";
|
|
764
|
+
finishReject(new Error(`AUTH_ERROR: codex CLI timed out after ${timeoutMs}ms${detail}`));
|
|
765
|
+
}, timeoutMs);
|
|
766
|
+
timeoutHandle.unref();
|
|
767
|
+
child.stdout?.setEncoding("utf8");
|
|
768
|
+
child.stdout?.on("data", (chunk) => {
|
|
769
|
+
const text = chunk.toString();
|
|
770
|
+
raw += text;
|
|
771
|
+
lineBuffer += text;
|
|
772
|
+
let idx;
|
|
773
|
+
while ((idx = lineBuffer.indexOf("\n")) !== -1) {
|
|
774
|
+
const line = lineBuffer.slice(0, idx);
|
|
775
|
+
lineBuffer = lineBuffer.slice(idx + 1);
|
|
776
|
+
handleLine(line);
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
child.stderr?.setEncoding("utf8");
|
|
780
|
+
child.stderr?.on("data", (chunk) => {
|
|
781
|
+
stderr += chunk.toString();
|
|
782
|
+
});
|
|
783
|
+
child.stdin?.on("error", (error) => {
|
|
784
|
+
if (settled || isIgnorableStdinError(error))
|
|
785
|
+
return;
|
|
786
|
+
finishReject(new Error(`AUTH_ERROR: codex CLI stdin failed (${error.message})`));
|
|
787
|
+
});
|
|
788
|
+
child.on("error", (error) => {
|
|
789
|
+
finishReject(new Error(`AUTH_ERROR: codex CLI failed (${error.message})`));
|
|
790
|
+
});
|
|
791
|
+
child.on("close", (code) => {
|
|
792
|
+
if (lineBuffer.trim()) {
|
|
793
|
+
handleLine(lineBuffer);
|
|
794
|
+
lineBuffer = "";
|
|
795
|
+
}
|
|
796
|
+
if (settled)
|
|
797
|
+
return;
|
|
798
|
+
if ((code ?? 0) !== 0) {
|
|
799
|
+
finishReject(new Error(`AUTH_ERROR: codex CLI failed (exit ${code ?? 0}): ${stderr || protocolError || "no output"}`));
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (protocolError && !finalizeOutput()) {
|
|
803
|
+
finishReject(new Error(`AUTH_ERROR: codex CLI failed (${protocolError})`));
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
finishResolve();
|
|
807
|
+
});
|
|
808
|
+
try {
|
|
809
|
+
child.stdin?.write(prompt);
|
|
810
|
+
child.stdin?.end();
|
|
811
|
+
}
|
|
812
|
+
catch (error) {
|
|
813
|
+
finishReject(new Error(`AUTH_ERROR: codex CLI stdin failed (${error.message})`));
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
finally {
|
|
818
|
+
await schemaFile?.cleanup();
|
|
819
|
+
}
|
|
820
|
+
};
|
|
544
821
|
export const cliHealthy = (throwOnError = false) => {
|
|
545
822
|
if (process.env.MCODA_CLI_STUB === "1") {
|
|
546
823
|
return { ok: true, details: { stub: true } };
|
|
@@ -548,7 +825,11 @@ export const cliHealthy = (throwOnError = false) => {
|
|
|
548
825
|
if (process.env.MCODA_SKIP_CLI_CHECKS === "1") {
|
|
549
826
|
return { ok: true, details: { skipped: true } };
|
|
550
827
|
}
|
|
551
|
-
const
|
|
828
|
+
const codexCommand = resolveCodexCommand();
|
|
829
|
+
const result = spawnSync(codexCommand.command, [...codexCommand.preArgs, "--version"], {
|
|
830
|
+
encoding: "utf8",
|
|
831
|
+
maxBuffer: CODEX_MAX_BUFFER_BYTES,
|
|
832
|
+
});
|
|
552
833
|
if (result.error) {
|
|
553
834
|
const details = { reason: "missing_cli", error: result.error.message };
|
|
554
835
|
if (throwOnError) {
|
|
@@ -569,64 +850,17 @@ export const cliHealthy = (throwOnError = false) => {
|
|
|
569
850
|
}
|
|
570
851
|
return { ok: true, details: { version: result.stdout?.toString().trim() } };
|
|
571
852
|
};
|
|
572
|
-
export const runCodexExec = (prompt, model) => {
|
|
853
|
+
export const runCodexExec = async (prompt, model, outputSchema, timeoutMs) => {
|
|
573
854
|
if (process.env.MCODA_CLI_STUB === "1") {
|
|
574
855
|
const output = `qa-stub:${prompt}`;
|
|
575
856
|
const raw = JSON.stringify({ type: "item.completed", item: { type: "agent_message", text: output } });
|
|
576
857
|
return { output, raw };
|
|
577
858
|
}
|
|
578
|
-
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
const args = [...sandboxArgs.args, "exec", "--model", resolvedModel, "--json"];
|
|
582
|
-
if (!sandboxArgs.bypass) {
|
|
583
|
-
args.push("--full-auto");
|
|
584
|
-
}
|
|
585
|
-
const reasoningEffort = resolveReasoningEffort(resolvedModel);
|
|
586
|
-
if (reasoningEffort) {
|
|
587
|
-
args.push("-c", `model_reasoning_effort=${reasoningEffort}`);
|
|
588
|
-
}
|
|
589
|
-
args.push("-");
|
|
590
|
-
const result = spawnSync("codex", args, {
|
|
591
|
-
input: prompt,
|
|
592
|
-
encoding: "utf8",
|
|
593
|
-
maxBuffer: CODEX_MAX_BUFFER_BYTES,
|
|
594
|
-
});
|
|
595
|
-
if (result.error) {
|
|
596
|
-
const error = new Error(`AUTH_ERROR: codex CLI failed (${result.error.message})`);
|
|
597
|
-
error.details = { reason: "cli_error", cli: health.details };
|
|
598
|
-
throw error;
|
|
599
|
-
}
|
|
600
|
-
if (result.status !== 0) {
|
|
601
|
-
const error = new Error(`AUTH_ERROR: codex CLI failed (exit ${result.status}): ${result.stderr ?? result.stdout ?? ""}`);
|
|
602
|
-
error.details = { reason: "cli_error", exitCode: result.status, stderr: result.stderr };
|
|
603
|
-
throw error;
|
|
604
|
-
}
|
|
605
|
-
const raw = result.stdout?.toString() ?? "";
|
|
606
|
-
const lines = raw.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
607
|
-
let message = "";
|
|
608
|
-
for (const line of lines) {
|
|
609
|
-
try {
|
|
610
|
-
const parsed = JSON.parse(line);
|
|
611
|
-
const event = extractAssistantText(parsed);
|
|
612
|
-
if (!event)
|
|
613
|
-
continue;
|
|
614
|
-
if (event.kind === "delta") {
|
|
615
|
-
message += event.text;
|
|
616
|
-
continue;
|
|
617
|
-
}
|
|
618
|
-
message = event.text;
|
|
619
|
-
}
|
|
620
|
-
catch {
|
|
621
|
-
/* ignore parse errors */
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
if (!message) {
|
|
625
|
-
message = lines[lines.length - 1] ?? "";
|
|
626
|
-
}
|
|
627
|
-
return { output: message.trim(), raw };
|
|
859
|
+
cliHealthy(true);
|
|
860
|
+
const result = await invokeCodexProcess(prompt, model, { outputSchema, timeoutMs });
|
|
861
|
+
return { output: result.output, raw: result.raw };
|
|
628
862
|
};
|
|
629
|
-
export async function* runCodexExecStream(prompt, model) {
|
|
863
|
+
export async function* runCodexExecStream(prompt, model, outputSchema, timeoutMs) {
|
|
630
864
|
if (process.env.MCODA_CLI_STUB === "1") {
|
|
631
865
|
const output = `qa-stub:${prompt}\n`;
|
|
632
866
|
const raw = JSON.stringify({ type: "item.delta", item: { type: "agent_message", text: output } });
|
|
@@ -635,103 +869,74 @@ export async function* runCodexExecStream(prompt, model) {
|
|
|
635
869
|
}
|
|
636
870
|
cliHealthy(true);
|
|
637
871
|
const resolvedModel = model ?? "gpt-5.1-codex-max";
|
|
638
|
-
const sandboxArgs = resolveSandboxArgs();
|
|
639
|
-
const args = [...sandboxArgs.args, "exec", "--model", resolvedModel, "--json"];
|
|
640
|
-
if (!sandboxArgs.bypass) {
|
|
641
|
-
args.push("--full-auto");
|
|
642
|
-
}
|
|
643
|
-
const reasoningEffort = resolveReasoningEffort(resolvedModel);
|
|
644
|
-
if (reasoningEffort) {
|
|
645
|
-
args.push("-c", `model_reasoning_effort=${reasoningEffort}`);
|
|
646
|
-
}
|
|
647
|
-
args.push("-");
|
|
648
|
-
const child = spawn("codex", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
649
|
-
child.stdin.write(prompt);
|
|
650
|
-
child.stdin.end();
|
|
651
|
-
let stderr = "";
|
|
652
|
-
child.stderr?.setEncoding("utf8");
|
|
653
|
-
child.stderr?.on("data", (chunk) => {
|
|
654
|
-
stderr += chunk.toString();
|
|
655
|
-
});
|
|
656
|
-
const closePromise = new Promise((resolve, reject) => {
|
|
657
|
-
child.on("error", (err) => reject(err));
|
|
658
|
-
child.on("close", (code) => resolve(code ?? 0));
|
|
659
|
-
});
|
|
660
|
-
const parseLine = (line) => {
|
|
661
|
-
try {
|
|
662
|
-
const parsed = JSON.parse(line);
|
|
663
|
-
return extractAssistantText(parsed);
|
|
664
|
-
}
|
|
665
|
-
catch {
|
|
666
|
-
return null;
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
const stream = child.stdout;
|
|
670
|
-
stream?.setEncoding("utf8");
|
|
671
872
|
const formatter = createStreamFormatter(resolvedModel);
|
|
672
|
-
|
|
873
|
+
const queue = [];
|
|
874
|
+
const waiters = [];
|
|
875
|
+
let done = false;
|
|
876
|
+
let failure;
|
|
673
877
|
let sawDelta = false;
|
|
674
|
-
let
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
878
|
+
let assistantDeltaBuffer = "";
|
|
879
|
+
const notify = () => {
|
|
880
|
+
while (waiters.length) {
|
|
881
|
+
waiters.shift()?.();
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
void invokeCodexProcess(prompt, model, {
|
|
885
|
+
outputSchema,
|
|
886
|
+
timeoutMs,
|
|
887
|
+
hooks: {
|
|
888
|
+
onLine: (line) => {
|
|
889
|
+
formatter.handleLine(line);
|
|
890
|
+
},
|
|
891
|
+
onAssistantEvent: (event, rawLine) => {
|
|
892
|
+
if (event.kind === "delta") {
|
|
688
893
|
sawDelta = true;
|
|
689
|
-
|
|
690
|
-
|
|
894
|
+
assistantDeltaBuffer += event.text;
|
|
895
|
+
queue.push({ output: event.text, raw: rawLine });
|
|
896
|
+
notify();
|
|
897
|
+
return;
|
|
691
898
|
}
|
|
899
|
+
const finalText = event.text;
|
|
900
|
+
const bufferedText = assistantDeltaBuffer;
|
|
901
|
+
assistantDeltaBuffer = "";
|
|
692
902
|
if (!sawDelta) {
|
|
693
|
-
const output =
|
|
694
|
-
|
|
903
|
+
const output = finalText.endsWith("\n") ? finalText : `${finalText}\n`;
|
|
904
|
+
queue.push({ output, raw: rawLine });
|
|
905
|
+
notify();
|
|
906
|
+
return;
|
|
695
907
|
}
|
|
696
908
|
sawDelta = false;
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
formatter.handleLine(trailing);
|
|
702
|
-
const parsed = parseLine(trailing);
|
|
703
|
-
if (parsed) {
|
|
704
|
-
if (parsed.kind === "delta") {
|
|
705
|
-
sawDelta = true;
|
|
706
|
-
yield { output: parsed.text, raw: trailing };
|
|
707
|
-
}
|
|
708
|
-
else if (!sawDelta) {
|
|
709
|
-
const output = parsed.text.endsWith("\n") ? parsed.text : `${parsed.text}\n`;
|
|
710
|
-
yield { output, raw: trailing };
|
|
711
|
-
sawDelta = false;
|
|
909
|
+
const normalizedFinal = normalizeComparableAssistantText(finalText);
|
|
910
|
+
const normalizedBuffered = normalizeComparableAssistantText(bufferedText);
|
|
911
|
+
if (!normalizedFinal || normalizedFinal === normalizedBuffered) {
|
|
912
|
+
return;
|
|
712
913
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
error.details = { reason: "cli_error", exitCode, stderr };
|
|
914
|
+
const suffix = finalText.startsWith(bufferedText) ? finalText.slice(bufferedText.length) : finalText;
|
|
915
|
+
const output = suffix.endsWith("\n") ? suffix : `${suffix}\n`;
|
|
916
|
+
queue.push({ output, raw: rawLine });
|
|
917
|
+
notify();
|
|
918
|
+
},
|
|
919
|
+
},
|
|
920
|
+
})
|
|
921
|
+
.catch((error) => {
|
|
922
|
+
failure = error;
|
|
923
|
+
formatter.handleLine(JSON.stringify({ type: "error", message: failure.message }));
|
|
924
|
+
})
|
|
925
|
+
.finally(() => {
|
|
926
|
+
done = true;
|
|
727
927
|
formatter.end();
|
|
728
|
-
|
|
729
|
-
|
|
928
|
+
notify();
|
|
929
|
+
});
|
|
930
|
+
while (!done || queue.length > 0) {
|
|
931
|
+
if (queue.length > 0) {
|
|
932
|
+
yield queue.shift();
|
|
933
|
+
continue;
|
|
730
934
|
}
|
|
731
|
-
|
|
935
|
+
await new Promise((resolve) => {
|
|
936
|
+
waiters.push(resolve);
|
|
937
|
+
});
|
|
732
938
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
throw streamError;
|
|
939
|
+
if (failure) {
|
|
940
|
+
throw failure;
|
|
736
941
|
}
|
|
737
942
|
}
|
|
@@ -19,7 +19,7 @@ export class OpenAiCliAdapter {
|
|
|
19
19
|
}
|
|
20
20
|
async invoke(request) {
|
|
21
21
|
const cliDetails = codexCliHealthy(true);
|
|
22
|
-
const result = runCodexExec(request.input, this.config.model);
|
|
22
|
+
const result = await runCodexExec(request.input, this.config.model);
|
|
23
23
|
return {
|
|
24
24
|
output: result.output,
|
|
25
25
|
adapter: this.config.adapter ?? "codex-cli",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcoda/agents",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
4
4
|
"description": "Agent registry and capabilities for mcoda.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@mcoda/shared": "0.1.
|
|
34
|
-
"@mcoda/db": "0.1.
|
|
33
|
+
"@mcoda/shared": "0.1.37",
|
|
34
|
+
"@mcoda/db": "0.1.37"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsc -p tsconfig.json",
|