codex-multi-auth 0.1.0 → 0.1.2
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/LICENSE +1 -1
- package/README.md +223 -98
- package/assets/readme-hero.svg +31 -31
- package/config/README.md +30 -87
- package/config/{opencode-legacy.json → codex-legacy.json} +571 -571
- package/config/{opencode-modern.json → codex-modern.json} +241 -239
- package/config/{minimal-opencode.json → minimal-codex.json} +15 -13
- package/config/schema/config.schema.json +21 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -20
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts.d.ts +1 -1
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/auth/auth.d.ts +1 -1
- package/dist/lib/auth/auth.js +1 -1
- package/dist/lib/cli.d.ts +1 -1
- package/dist/lib/cli.js +4 -4
- package/dist/lib/cli.js.map +1 -1
- package/dist/lib/codex-manager/settings-hub.d.ts +6 -0
- package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -0
- package/dist/lib/codex-manager/settings-hub.js +1786 -0
- package/dist/lib/codex-manager/settings-hub.js.map +1 -0
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +5 -1637
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +20 -11
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/context-overflow.d.ts +1 -1
- package/dist/lib/context-overflow.js +11 -11
- package/dist/lib/oauth-success.html +1 -1
- package/dist/lib/prompts/codex-host-bridge.d.ts +19 -0
- package/dist/lib/prompts/codex-host-bridge.d.ts.map +1 -0
- package/dist/lib/prompts/{codex-opencode-bridge.js → codex-host-bridge.js} +143 -143
- package/dist/lib/prompts/codex-host-bridge.js.map +1 -0
- package/dist/lib/prompts/codex.d.ts +2 -2
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +3 -3
- package/dist/lib/prompts/host-codex-prompt.d.ts +25 -0
- package/dist/lib/prompts/host-codex-prompt.d.ts.map +1 -0
- package/dist/lib/prompts/{opencode-codex.js → host-codex-prompt.js} +136 -47
- package/dist/lib/prompts/host-codex-prompt.js.map +1 -0
- package/dist/lib/recovery/storage.d.ts +2 -2
- package/dist/lib/recovery/storage.js +2 -2
- package/dist/lib/recovery/types.d.ts +1 -1
- package/dist/lib/recovery/types.js +1 -1
- package/dist/lib/recovery.d.ts +1 -1
- package/dist/lib/recovery.d.ts.map +1 -1
- package/dist/lib/recovery.js +1 -4
- package/dist/lib/recovery.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts +3 -3
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +6 -2
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/helpers/input-utils.d.ts +2 -2
- package/dist/lib/request/helpers/input-utils.d.ts.map +1 -1
- package/dist/lib/request/helpers/input-utils.js +14 -14
- package/dist/lib/request/helpers/input-utils.js.map +1 -1
- package/dist/lib/request/helpers/model-map.d.ts +2 -2
- package/dist/lib/request/helpers/model-map.js +2 -2
- package/dist/lib/request/request-transformer.d.ts +12 -12
- package/dist/lib/request/request-transformer.d.ts.map +1 -1
- package/dist/lib/request/request-transformer.js +23 -24
- package/dist/lib/request/request-transformer.js.map +1 -1
- package/dist/lib/runtime-paths.d.ts +4 -4
- package/dist/lib/runtime-paths.d.ts.map +1 -1
- package/dist/lib/runtime-paths.js +57 -13
- package/dist/lib/runtime-paths.js.map +1 -1
- package/dist/lib/storage/paths.d.ts +11 -0
- package/dist/lib/storage/paths.d.ts.map +1 -1
- package/dist/lib/storage/paths.js +167 -5
- package/dist/lib/storage/paths.js.map +1 -1
- package/dist/lib/storage.d.ts +1 -0
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +290 -81
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/tools/hashline-tools.d.ts +1 -1
- package/dist/lib/tools/hashline-tools.d.ts.map +1 -1
- package/dist/lib/tools/hashline-tools.js +1 -1
- package/dist/lib/tools/hashline-tools.js.map +1 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/ui/copy.d.ts +6 -6
- package/dist/lib/ui/copy.js +6 -6
- package/dist/lib/ui/copy.js.map +1 -1
- package/node_modules/@codex-ai/plugin/dist/index.d.ts +2 -0
- package/node_modules/@codex-ai/plugin/dist/index.js +2 -0
- package/node_modules/@codex-ai/plugin/dist/tool.d.ts +42 -0
- package/node_modules/@codex-ai/plugin/dist/tool.js +29 -0
- package/node_modules/@codex-ai/plugin/package.json +9 -0
- package/package.json +30 -16
- package/scripts/audit-dev-allowlist.js +128 -128
- package/scripts/bench-format/{opencode.mjs → codex-host.mjs} +206 -205
- package/scripts/bench-format/hashline-v2.mjs +642 -642
- package/scripts/bench-format/models.mjs +111 -105
- package/scripts/bench-format/render.mjs +496 -496
- package/scripts/bench-format/stats.mjs +54 -54
- package/scripts/bench-format/tasks.mjs +151 -151
- package/scripts/benchmark-edit-formats.mjs +1162 -1161
- package/scripts/benchmark-render-dashboard.mjs +49 -49
- package/scripts/codex.js +40 -2
- package/scripts/copy-oauth-success.js +37 -37
- package/scripts/install-codex-auth-utils.js +49 -0
- package/scripts/{install-opencode-codex-auth.js → install-codex-auth.js} +220 -193
- package/scripts/repo-hygiene.js +320 -0
- package/scripts/test-all-models.sh +7 -7
- package/scripts/test-model-matrix.js +475 -423
- package/scripts/validate-model-map.sh +7 -7
- package/vendor/codex-ai-plugin/dist/index.d.ts +2 -0
- package/vendor/codex-ai-plugin/dist/index.js +2 -0
- package/vendor/codex-ai-plugin/dist/tool.d.ts +42 -0
- package/vendor/codex-ai-plugin/dist/tool.js +29 -0
- package/vendor/codex-ai-plugin/package.json +9 -0
- package/vendor/codex-ai-sdk/dist/index.d.ts +4 -0
- package/vendor/codex-ai-sdk/dist/index.js +2 -0
- package/vendor/codex-ai-sdk/package.json +8 -0
- package/assets/opencode-logo-ornate-dark.svg +0 -18
- package/dist/lib/prompts/codex-opencode-bridge.d.ts +0 -19
- package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +0 -1
- package/dist/lib/prompts/codex-opencode-bridge.js.map +0 -1
- package/dist/lib/prompts/opencode-codex.d.ts +0 -25
- package/dist/lib/prompts/opencode-codex.d.ts.map +0 -1
- package/dist/lib/prompts/opencode-codex.js.map +0 -1
|
@@ -1,128 +1,128 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
4
|
-
|
|
5
|
-
const ALLOWED_HIGH_OR_CRITICAL_PACKAGES = new Set([
|
|
6
|
-
"eslint",
|
|
7
|
-
"ajv",
|
|
8
|
-
"@eslint-community/eslint-utils",
|
|
9
|
-
"@typescript-eslint/eslint-plugin",
|
|
10
|
-
"@typescript-eslint/parser",
|
|
11
|
-
"@typescript-eslint/type-utils",
|
|
12
|
-
"@typescript-eslint/typescript-estree",
|
|
13
|
-
"@typescript-eslint/utils",
|
|
14
|
-
"minimatch",
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
function summarizeVia(via) {
|
|
18
|
-
if (!Array.isArray(via)) return [];
|
|
19
|
-
return via
|
|
20
|
-
.map((item) => {
|
|
21
|
-
if (typeof item === "string") return item;
|
|
22
|
-
if (!item || typeof item !== "object") return "unknown";
|
|
23
|
-
const name = typeof item.name === "string" ? item.name : "unknown";
|
|
24
|
-
const range = typeof item.range === "string" ? item.range : "";
|
|
25
|
-
return range ? `${name}:${range}` : name;
|
|
26
|
-
})
|
|
27
|
-
.slice(0, 5);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const isWindows = process.platform === "win32";
|
|
31
|
-
const command = isWindows ? process.env.ComSpec || "cmd.exe" : "npm";
|
|
32
|
-
const commandArgs = isWindows
|
|
33
|
-
? ["/d", "/s", "/c", "npm audit --json"]
|
|
34
|
-
: ["audit", "--json"];
|
|
35
|
-
const audit = spawnSync(command, commandArgs, {
|
|
36
|
-
encoding: "utf8",
|
|
37
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
38
|
-
env: {
|
|
39
|
-
...process.env,
|
|
40
|
-
// npm run -s can suppress child npm JSON output; force a readable level.
|
|
41
|
-
npm_config_loglevel:
|
|
42
|
-
process.env.npm_config_loglevel === "silent"
|
|
43
|
-
? "notice"
|
|
44
|
-
: process.env.npm_config_loglevel || "notice",
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const stdout = (audit.stdout ?? "").trim();
|
|
49
|
-
const stderr = (audit.stderr ?? "").trim();
|
|
50
|
-
const combined = [stdout, stderr].filter(Boolean).join("\n");
|
|
51
|
-
|
|
52
|
-
if (!combined) {
|
|
53
|
-
if ((audit.status ?? 1) === 0) {
|
|
54
|
-
console.log("No vulnerabilities found in npm audit output.");
|
|
55
|
-
process.exit(0);
|
|
56
|
-
}
|
|
57
|
-
console.error("Failed to read npm audit output.");
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// npm can emit human-readable success text (no JSON) on some versions/configs.
|
|
62
|
-
if (!combined.includes("{") && /found 0 vulnerabilities/i.test(combined)) {
|
|
63
|
-
console.log("No vulnerabilities found in npm audit output.");
|
|
64
|
-
process.exit(0);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
let auditJson;
|
|
68
|
-
try {
|
|
69
|
-
const jsonCandidate =
|
|
70
|
-
(stdout.startsWith("{") ? stdout : "") ||
|
|
71
|
-
(stderr.startsWith("{") ? stderr : "") ||
|
|
72
|
-
combined.slice(combined.indexOf("{"));
|
|
73
|
-
auditJson = JSON.parse(jsonCandidate.replace(/^\uFEFF/, ""));
|
|
74
|
-
} catch (error) {
|
|
75
|
-
console.error("Failed to parse npm audit JSON output.");
|
|
76
|
-
if (stderr) {
|
|
77
|
-
console.error(stderr);
|
|
78
|
-
}
|
|
79
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
80
|
-
process.exit(1);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const vulnerabilities =
|
|
84
|
-
auditJson && typeof auditJson === "object" && auditJson.vulnerabilities && typeof auditJson.vulnerabilities === "object"
|
|
85
|
-
? auditJson.vulnerabilities
|
|
86
|
-
: {};
|
|
87
|
-
|
|
88
|
-
const unexpected = [];
|
|
89
|
-
const allowlisted = [];
|
|
90
|
-
|
|
91
|
-
for (const [name, details] of Object.entries(vulnerabilities)) {
|
|
92
|
-
if (!details || typeof details !== "object") continue;
|
|
93
|
-
const severity = typeof details.severity === "string" ? details.severity : "unknown";
|
|
94
|
-
if (severity !== "high" && severity !== "critical") continue;
|
|
95
|
-
|
|
96
|
-
const entry = {
|
|
97
|
-
name,
|
|
98
|
-
severity,
|
|
99
|
-
via: summarizeVia(details.via),
|
|
100
|
-
fixAvailable: details.fixAvailable ?? false,
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
if (ALLOWED_HIGH_OR_CRITICAL_PACKAGES.has(name)) {
|
|
104
|
-
allowlisted.push(entry);
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
unexpected.push(entry);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (unexpected.length > 0) {
|
|
111
|
-
console.error("Unexpected high/critical vulnerabilities detected in dev dependency audit:");
|
|
112
|
-
for (const entry of unexpected) {
|
|
113
|
-
console.error(
|
|
114
|
-
`- ${entry.name} (${entry.severity}) via ${entry.via.join(", ") || "unknown"} fixAvailable=${String(entry.fixAvailable)}`,
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (allowlisted.length > 0) {
|
|
121
|
-
console.warn("Allowlisted high/critical dev vulnerabilities detected:");
|
|
122
|
-
for (const entry of allowlisted) {
|
|
123
|
-
console.warn(
|
|
124
|
-
`- ${entry.name} (${entry.severity}) via ${entry.via.join(", ") || "unknown"} fixAvailable=${String(entry.fixAvailable)}`,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
console.warn("No unexpected high/critical vulnerabilities found.");
|
|
128
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
const ALLOWED_HIGH_OR_CRITICAL_PACKAGES = new Set([
|
|
6
|
+
"eslint",
|
|
7
|
+
"ajv",
|
|
8
|
+
"@eslint-community/eslint-utils",
|
|
9
|
+
"@typescript-eslint/eslint-plugin",
|
|
10
|
+
"@typescript-eslint/parser",
|
|
11
|
+
"@typescript-eslint/type-utils",
|
|
12
|
+
"@typescript-eslint/typescript-estree",
|
|
13
|
+
"@typescript-eslint/utils",
|
|
14
|
+
"minimatch",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function summarizeVia(via) {
|
|
18
|
+
if (!Array.isArray(via)) return [];
|
|
19
|
+
return via
|
|
20
|
+
.map((item) => {
|
|
21
|
+
if (typeof item === "string") return item;
|
|
22
|
+
if (!item || typeof item !== "object") return "unknown";
|
|
23
|
+
const name = typeof item.name === "string" ? item.name : "unknown";
|
|
24
|
+
const range = typeof item.range === "string" ? item.range : "";
|
|
25
|
+
return range ? `${name}:${range}` : name;
|
|
26
|
+
})
|
|
27
|
+
.slice(0, 5);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const isWindows = process.platform === "win32";
|
|
31
|
+
const command = isWindows ? process.env.ComSpec || "cmd.exe" : "npm";
|
|
32
|
+
const commandArgs = isWindows
|
|
33
|
+
? ["/d", "/s", "/c", "npm audit --json"]
|
|
34
|
+
: ["audit", "--json"];
|
|
35
|
+
const audit = spawnSync(command, commandArgs, {
|
|
36
|
+
encoding: "utf8",
|
|
37
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
38
|
+
env: {
|
|
39
|
+
...process.env,
|
|
40
|
+
// npm run -s can suppress child npm JSON output; force a readable level.
|
|
41
|
+
npm_config_loglevel:
|
|
42
|
+
process.env.npm_config_loglevel === "silent"
|
|
43
|
+
? "notice"
|
|
44
|
+
: process.env.npm_config_loglevel || "notice",
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const stdout = (audit.stdout ?? "").trim();
|
|
49
|
+
const stderr = (audit.stderr ?? "").trim();
|
|
50
|
+
const combined = [stdout, stderr].filter(Boolean).join("\n");
|
|
51
|
+
|
|
52
|
+
if (!combined) {
|
|
53
|
+
if ((audit.status ?? 1) === 0) {
|
|
54
|
+
console.log("No vulnerabilities found in npm audit output.");
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
console.error("Failed to read npm audit output.");
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// npm can emit human-readable success text (no JSON) on some versions/configs.
|
|
62
|
+
if (!combined.includes("{") && /found 0 vulnerabilities/i.test(combined)) {
|
|
63
|
+
console.log("No vulnerabilities found in npm audit output.");
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let auditJson;
|
|
68
|
+
try {
|
|
69
|
+
const jsonCandidate =
|
|
70
|
+
(stdout.startsWith("{") ? stdout : "") ||
|
|
71
|
+
(stderr.startsWith("{") ? stderr : "") ||
|
|
72
|
+
combined.slice(combined.indexOf("{"));
|
|
73
|
+
auditJson = JSON.parse(jsonCandidate.replace(/^\uFEFF/, ""));
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error("Failed to parse npm audit JSON output.");
|
|
76
|
+
if (stderr) {
|
|
77
|
+
console.error(stderr);
|
|
78
|
+
}
|
|
79
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const vulnerabilities =
|
|
84
|
+
auditJson && typeof auditJson === "object" && auditJson.vulnerabilities && typeof auditJson.vulnerabilities === "object"
|
|
85
|
+
? auditJson.vulnerabilities
|
|
86
|
+
: {};
|
|
87
|
+
|
|
88
|
+
const unexpected = [];
|
|
89
|
+
const allowlisted = [];
|
|
90
|
+
|
|
91
|
+
for (const [name, details] of Object.entries(vulnerabilities)) {
|
|
92
|
+
if (!details || typeof details !== "object") continue;
|
|
93
|
+
const severity = typeof details.severity === "string" ? details.severity : "unknown";
|
|
94
|
+
if (severity !== "high" && severity !== "critical") continue;
|
|
95
|
+
|
|
96
|
+
const entry = {
|
|
97
|
+
name,
|
|
98
|
+
severity,
|
|
99
|
+
via: summarizeVia(details.via),
|
|
100
|
+
fixAvailable: details.fixAvailable ?? false,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (ALLOWED_HIGH_OR_CRITICAL_PACKAGES.has(name)) {
|
|
104
|
+
allowlisted.push(entry);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
unexpected.push(entry);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (unexpected.length > 0) {
|
|
111
|
+
console.error("Unexpected high/critical vulnerabilities detected in dev dependency audit:");
|
|
112
|
+
for (const entry of unexpected) {
|
|
113
|
+
console.error(
|
|
114
|
+
`- ${entry.name} (${entry.severity}) via ${entry.via.join(", ") || "unknown"} fixAvailable=${String(entry.fixAvailable)}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (allowlisted.length > 0) {
|
|
121
|
+
console.warn("Allowlisted high/critical dev vulnerabilities detected:");
|
|
122
|
+
for (const entry of allowlisted) {
|
|
123
|
+
console.warn(
|
|
124
|
+
`- ${entry.name} (${entry.severity}) via ${entry.via.join(", ") || "unknown"} fixAvailable=${String(entry.fixAvailable)}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
console.warn("No unexpected high/critical vulnerabilities found.");
|
|
128
|
+
}
|
|
@@ -1,205 +1,206 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { dirname, resolve } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const repoRoot = resolve(scriptDir, "..", "..");
|
|
7
|
-
|
|
8
|
-
export function getRepoRoot() {
|
|
9
|
-
return repoRoot;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function
|
|
13
|
-
const envOverride = process.env.
|
|
14
|
-
if (envOverride && envOverride.trim().length > 0) {
|
|
15
|
-
const command = envOverride.trim();
|
|
16
|
-
return { command, shell: /\.cmd$/i.test(command) };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (process.platform !== "win32") {
|
|
20
|
-
return { command: "
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const whereResult = spawnSync("where", ["
|
|
24
|
-
encoding: "utf8",
|
|
25
|
-
windowsHide: true,
|
|
26
|
-
});
|
|
27
|
-
const candidates = `${whereResult.stdout ?? ""}
|
|
28
|
-
.split(/\r?\n/)
|
|
29
|
-
.map((line) => line.trim())
|
|
30
|
-
.filter(
|
|
31
|
-
|
|
32
|
-
if (candidates.length === 0) {
|
|
33
|
-
return { command: "
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const exactExe = candidates.find((candidate) => /npm\\
|
|
37
|
-
if (exactExe) {
|
|
38
|
-
return { command: exactExe, shell: false };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const exactCmd = candidates.find((candidate) => /npm\\
|
|
42
|
-
if (exactCmd) {
|
|
43
|
-
return { command: exactCmd, shell: true };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const anyCmd = candidates.find((candidate) => /\.cmd$/i.test(candidate));
|
|
47
|
-
if (anyCmd) {
|
|
48
|
-
return { command: anyCmd, shell: true };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return { command: candidates[0], shell: false };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function parseNdjson(text) {
|
|
55
|
-
const events = [];
|
|
56
|
-
for (const line of text.split(/\r?\n/)) {
|
|
57
|
-
const trimmed = line.trim();
|
|
58
|
-
if (!trimmed) {
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
events.push(JSON.parse(trimmed));
|
|
63
|
-
} catch {
|
|
64
|
-
// Ignore non-JSON lines emitted by wrappers (bun install, warnings, etc.).
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return events;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function getToolEvents(events) {
|
|
71
|
-
return events
|
|
72
|
-
.filter((event) => event?.type === "tool_use" && event?.part?.type === "tool")
|
|
73
|
-
.map((event) => ({
|
|
74
|
-
tool: event.part.tool,
|
|
75
|
-
input: event.part.state?.input ?? {},
|
|
76
|
-
output: event.part.state?.output,
|
|
77
|
-
status: event.part.state?.status,
|
|
78
|
-
start: event.part.state?.time?.start,
|
|
79
|
-
end: event.part.state?.time?.end,
|
|
80
|
-
durationMs:
|
|
81
|
-
typeof event.part.state?.time?.start === "number" &&
|
|
82
|
-
typeof event.part.state?.time?.end === "number"
|
|
83
|
-
? event.part.state.time.end - event.part.state.time.start
|
|
84
|
-
: null,
|
|
85
|
-
}));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function getSessionDuration(events) {
|
|
89
|
-
const starts = events
|
|
90
|
-
.filter((event) => event?.type === "step_start" && typeof event.timestamp === "number")
|
|
91
|
-
.map((event) => event.timestamp);
|
|
92
|
-
const finishes = events
|
|
93
|
-
.filter((event) => event?.type === "step_finish" && typeof event.timestamp === "number")
|
|
94
|
-
.map((event) => event.timestamp);
|
|
95
|
-
if (starts.length === 0 || finishes.length === 0) {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
return Math.max(...finishes) - Math.min(...starts);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function getTokenTotals(events) {
|
|
102
|
-
const stepFinishes = events.filter((event) => event?.type === "step_finish" && event?.part?.tokens);
|
|
103
|
-
if (stepFinishes.length === 0) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
const total = {
|
|
107
|
-
total: 0,
|
|
108
|
-
input: 0,
|
|
109
|
-
output: 0,
|
|
110
|
-
reasoning: 0,
|
|
111
|
-
cacheRead: 0,
|
|
112
|
-
cacheWrite: 0,
|
|
113
|
-
};
|
|
114
|
-
for (const event of stepFinishes) {
|
|
115
|
-
const tokens = event.part.tokens ?? {};
|
|
116
|
-
const input = Number(tokens.input ?? 0);
|
|
117
|
-
const output = Number(tokens.output ?? 0);
|
|
118
|
-
const reasoning = Number(tokens.reasoning ?? 0);
|
|
119
|
-
const explicitTotal = Number(tokens.total ?? NaN);
|
|
120
|
-
total.total += Number.isFinite(explicitTotal) ? explicitTotal : input + output + reasoning;
|
|
121
|
-
total.input += input;
|
|
122
|
-
total.output += output;
|
|
123
|
-
total.reasoning += reasoning;
|
|
124
|
-
total.cacheRead += Number(tokens.cache?.read ?? 0);
|
|
125
|
-
total.cacheWrite += Number(tokens.cache?.write ?? 0);
|
|
126
|
-
}
|
|
127
|
-
return total;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function getTextOutput(events) {
|
|
131
|
-
return events
|
|
132
|
-
.filter((event) => event?.type === "text" && typeof event?.part?.text === "string")
|
|
133
|
-
.map((event) => event.part.text)
|
|
134
|
-
.join("\n");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export function getEventError(events) {
|
|
138
|
-
const errorEvent = events.find((event) => event?.type === "error");
|
|
139
|
-
if (!errorEvent) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
name: errorEvent.error?.name ?? "UnknownError",
|
|
144
|
-
message: errorEvent.error?.data?.message ?? errorEvent.error?.message ?? "Unknown error",
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function
|
|
149
|
-
executable,
|
|
150
|
-
prompt,
|
|
151
|
-
model,
|
|
152
|
-
variant,
|
|
153
|
-
agent,
|
|
154
|
-
cwd,
|
|
155
|
-
homeDir,
|
|
156
|
-
timeoutMs,
|
|
157
|
-
extraEnv,
|
|
158
|
-
}) {
|
|
159
|
-
const startWall = Date.now();
|
|
160
|
-
const args = ["run", "--format", "json", "--agent", agent, "--model", model];
|
|
161
|
-
if (variant) {
|
|
162
|
-
args.push("--variant", variant);
|
|
163
|
-
}
|
|
164
|
-
args.push(prompt);
|
|
165
|
-
|
|
166
|
-
const child = spawnSync(executable.command, args, {
|
|
167
|
-
cwd: cwd ?? repoRoot,
|
|
168
|
-
encoding: "utf8",
|
|
169
|
-
windowsHide: true,
|
|
170
|
-
shell: executable.shell,
|
|
171
|
-
timeout: timeoutMs,
|
|
172
|
-
maxBuffer: 30 * 1024 * 1024,
|
|
173
|
-
env: {
|
|
174
|
-
...process.env,
|
|
175
|
-
...(homeDir ? { HOME: homeDir, USERPROFILE: homeDir } : {}),
|
|
176
|
-
...extraEnv,
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const wallMs = Date.now() - startWall;
|
|
181
|
-
const stdout = child.stdout ?? "";
|
|
182
|
-
const stderr = child.stderr ?? "";
|
|
183
|
-
const events = parseNdjson(stdout);
|
|
184
|
-
const eventError = getEventError(events);
|
|
185
|
-
const timedOut =
|
|
186
|
-
child.error?.code === "ETIMEDOUT" ||
|
|
187
|
-
child.signal === "SIGTERM" ||
|
|
188
|
-
/timed out/i.test(String(child.error?.message ?? ""));
|
|
189
|
-
if (child.error && !timedOut) {
|
|
190
|
-
throw child.error;
|
|
191
|
-
}
|
|
192
|
-
const modelNotFound = /Model not found|ProviderModelNotFoundError/i.test(`${stdout}\n${stderr}`) || /Model not found/i.test(eventError?.message ?? "");
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
status: child.status ?? 1,
|
|
196
|
-
signal: child.signal ?? null,
|
|
197
|
-
stdout,
|
|
198
|
-
stderr,
|
|
199
|
-
wallMs,
|
|
200
|
-
events,
|
|
201
|
-
eventError,
|
|
202
|
-
timedOut,
|
|
203
|
-
modelNotFound,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const repoRoot = resolve(scriptDir, "..", "..");
|
|
7
|
+
|
|
8
|
+
export function getRepoRoot() {
|
|
9
|
+
return repoRoot;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function resolveCodexExecutable() {
|
|
13
|
+
const envOverride = process.env.CODEX_BIN;
|
|
14
|
+
if (envOverride && envOverride.trim().length > 0) {
|
|
15
|
+
const command = envOverride.trim();
|
|
16
|
+
return { command, shell: /\.cmd$/i.test(command) };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (process.platform !== "win32") {
|
|
20
|
+
return { command: "codex", shell: false };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const whereResult = spawnSync("where", ["Codex"], {
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
windowsHide: true,
|
|
26
|
+
});
|
|
27
|
+
const candidates = `${whereResult.stdout ?? ""}`
|
|
28
|
+
.split(/\r?\n/)
|
|
29
|
+
.map((line) => line.trim())
|
|
30
|
+
.filter((line) => /^[A-Za-z]:\\.+\.(exe|cmd)$/i.test(line));
|
|
31
|
+
|
|
32
|
+
if (candidates.length === 0) {
|
|
33
|
+
return { command: "codex", shell: false };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const exactExe = candidates.find((candidate) => /npm\\Codex\.exe$/i.test(candidate));
|
|
37
|
+
if (exactExe) {
|
|
38
|
+
return { command: exactExe, shell: false };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const exactCmd = candidates.find((candidate) => /npm\\Codex\.cmd$/i.test(candidate));
|
|
42
|
+
if (exactCmd) {
|
|
43
|
+
return { command: exactCmd, shell: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const anyCmd = candidates.find((candidate) => /\.cmd$/i.test(candidate));
|
|
47
|
+
if (anyCmd) {
|
|
48
|
+
return { command: anyCmd, shell: true };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { command: candidates[0], shell: false };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function parseNdjson(text) {
|
|
55
|
+
const events = [];
|
|
56
|
+
for (const line of text.split(/\r?\n/)) {
|
|
57
|
+
const trimmed = line.trim();
|
|
58
|
+
if (!trimmed) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
events.push(JSON.parse(trimmed));
|
|
63
|
+
} catch {
|
|
64
|
+
// Ignore non-JSON lines emitted by wrappers (bun install, warnings, etc.).
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return events;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getToolEvents(events) {
|
|
71
|
+
return events
|
|
72
|
+
.filter((event) => event?.type === "tool_use" && event?.part?.type === "tool")
|
|
73
|
+
.map((event) => ({
|
|
74
|
+
tool: event.part.tool,
|
|
75
|
+
input: event.part.state?.input ?? {},
|
|
76
|
+
output: event.part.state?.output,
|
|
77
|
+
status: event.part.state?.status,
|
|
78
|
+
start: event.part.state?.time?.start,
|
|
79
|
+
end: event.part.state?.time?.end,
|
|
80
|
+
durationMs:
|
|
81
|
+
typeof event.part.state?.time?.start === "number" &&
|
|
82
|
+
typeof event.part.state?.time?.end === "number"
|
|
83
|
+
? event.part.state.time.end - event.part.state.time.start
|
|
84
|
+
: null,
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function getSessionDuration(events) {
|
|
89
|
+
const starts = events
|
|
90
|
+
.filter((event) => event?.type === "step_start" && typeof event.timestamp === "number")
|
|
91
|
+
.map((event) => event.timestamp);
|
|
92
|
+
const finishes = events
|
|
93
|
+
.filter((event) => event?.type === "step_finish" && typeof event.timestamp === "number")
|
|
94
|
+
.map((event) => event.timestamp);
|
|
95
|
+
if (starts.length === 0 || finishes.length === 0) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return Math.max(...finishes) - Math.min(...starts);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getTokenTotals(events) {
|
|
102
|
+
const stepFinishes = events.filter((event) => event?.type === "step_finish" && event?.part?.tokens);
|
|
103
|
+
if (stepFinishes.length === 0) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const total = {
|
|
107
|
+
total: 0,
|
|
108
|
+
input: 0,
|
|
109
|
+
output: 0,
|
|
110
|
+
reasoning: 0,
|
|
111
|
+
cacheRead: 0,
|
|
112
|
+
cacheWrite: 0,
|
|
113
|
+
};
|
|
114
|
+
for (const event of stepFinishes) {
|
|
115
|
+
const tokens = event.part.tokens ?? {};
|
|
116
|
+
const input = Number(tokens.input ?? 0);
|
|
117
|
+
const output = Number(tokens.output ?? 0);
|
|
118
|
+
const reasoning = Number(tokens.reasoning ?? 0);
|
|
119
|
+
const explicitTotal = Number(tokens.total ?? NaN);
|
|
120
|
+
total.total += Number.isFinite(explicitTotal) ? explicitTotal : input + output + reasoning;
|
|
121
|
+
total.input += input;
|
|
122
|
+
total.output += output;
|
|
123
|
+
total.reasoning += reasoning;
|
|
124
|
+
total.cacheRead += Number(tokens.cache?.read ?? 0);
|
|
125
|
+
total.cacheWrite += Number(tokens.cache?.write ?? 0);
|
|
126
|
+
}
|
|
127
|
+
return total;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function getTextOutput(events) {
|
|
131
|
+
return events
|
|
132
|
+
.filter((event) => event?.type === "text" && typeof event?.part?.text === "string")
|
|
133
|
+
.map((event) => event.part.text)
|
|
134
|
+
.join("\n");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function getEventError(events) {
|
|
138
|
+
const errorEvent = events.find((event) => event?.type === "error");
|
|
139
|
+
if (!errorEvent) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
name: errorEvent.error?.name ?? "UnknownError",
|
|
144
|
+
message: errorEvent.error?.data?.message ?? errorEvent.error?.message ?? "Unknown error",
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function runCodexJson({
|
|
149
|
+
executable,
|
|
150
|
+
prompt,
|
|
151
|
+
model,
|
|
152
|
+
variant,
|
|
153
|
+
agent,
|
|
154
|
+
cwd,
|
|
155
|
+
homeDir,
|
|
156
|
+
timeoutMs,
|
|
157
|
+
extraEnv,
|
|
158
|
+
}) {
|
|
159
|
+
const startWall = Date.now();
|
|
160
|
+
const args = ["run", "--format", "json", "--agent", agent, "--model", model];
|
|
161
|
+
if (variant) {
|
|
162
|
+
args.push("--variant", variant);
|
|
163
|
+
}
|
|
164
|
+
args.push(prompt);
|
|
165
|
+
|
|
166
|
+
const child = spawnSync(executable.command, args, {
|
|
167
|
+
cwd: cwd ?? repoRoot,
|
|
168
|
+
encoding: "utf8",
|
|
169
|
+
windowsHide: true,
|
|
170
|
+
shell: executable.shell,
|
|
171
|
+
timeout: timeoutMs,
|
|
172
|
+
maxBuffer: 30 * 1024 * 1024,
|
|
173
|
+
env: {
|
|
174
|
+
...process.env,
|
|
175
|
+
...(homeDir ? { HOME: homeDir, USERPROFILE: homeDir } : {}),
|
|
176
|
+
...extraEnv,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const wallMs = Date.now() - startWall;
|
|
181
|
+
const stdout = child.stdout ?? "";
|
|
182
|
+
const stderr = child.stderr ?? "";
|
|
183
|
+
const events = parseNdjson(stdout);
|
|
184
|
+
const eventError = getEventError(events);
|
|
185
|
+
const timedOut =
|
|
186
|
+
child.error?.code === "ETIMEDOUT" ||
|
|
187
|
+
child.signal === "SIGTERM" ||
|
|
188
|
+
/timed out/i.test(String(child.error?.message ?? ""));
|
|
189
|
+
if (child.error && !timedOut) {
|
|
190
|
+
throw child.error;
|
|
191
|
+
}
|
|
192
|
+
const modelNotFound = /Model not found|ProviderModelNotFoundError/i.test(`${stdout}\n${stderr}`) || /Model not found/i.test(eventError?.message ?? "");
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
status: child.status ?? 1,
|
|
196
|
+
signal: child.signal ?? null,
|
|
197
|
+
stdout,
|
|
198
|
+
stderr,
|
|
199
|
+
wallMs,
|
|
200
|
+
events,
|
|
201
|
+
eventError,
|
|
202
|
+
timedOut,
|
|
203
|
+
modelNotFound,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|