codex-multi-auth 2.1.9 → 2.1.11
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 +4 -2
- package/assets/codex-multi-auth-icon.svg +39 -0
- package/dist/lib/accounts.d.ts +2 -0
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +26 -2
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.d.ts +5 -1
- package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.js +14 -1
- package/dist/lib/codex-manager/commands/forecast.js.map +1 -1
- package/dist/lib/codex-manager/commands/report.d.ts +2 -0
- package/dist/lib/codex-manager/commands/report.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/report.js +17 -1
- package/dist/lib/codex-manager/commands/report.js.map +1 -1
- package/dist/lib/codex-manager/commands/rotation.d.ts +1 -1
- package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/rotation.js +71 -1
- package/dist/lib/codex-manager/commands/rotation.js.map +1 -1
- package/dist/lib/codex-manager/repair-commands.d.ts.map +1 -1
- package/dist/lib/codex-manager/repair-commands.js +35 -0
- package/dist/lib/codex-manager/repair-commands.js.map +1 -1
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +2 -0
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/forecast.d.ts +9 -0
- package/dist/lib/forecast.d.ts.map +1 -1
- package/dist/lib/forecast.js +36 -0
- package/dist/lib/forecast.js.map +1 -1
- package/dist/lib/runtime/runtime-observability.d.ts +17 -0
- package/dist/lib/runtime/runtime-observability.d.ts.map +1 -1
- package/dist/lib/runtime/runtime-observability.js +48 -0
- package/dist/lib/runtime/runtime-observability.js.map +1 -1
- package/dist/lib/runtime-rotation-proxy.d.ts +1 -0
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +119 -20
- package/dist/lib/runtime-rotation-proxy.js.map +1 -1
- package/package.json +12 -4
- package/scripts/audit-dev-allowlist.js +0 -220
- package/scripts/bench-format/codex-host.mjs +0 -275
- package/scripts/bench-format/hashline-v2.mjs +0 -642
- package/scripts/bench-format/models.mjs +0 -110
- package/scripts/bench-format/render.mjs +0 -496
- package/scripts/bench-format/stats.mjs +0 -54
- package/scripts/bench-format/tasks.mjs +0 -151
- package/scripts/benchmark-edit-formats.mjs +0 -1164
- package/scripts/benchmark-render-dashboard.mjs +0 -49
- package/scripts/benchmark-runtime-path.mjs +0 -166
- package/scripts/check-pack-budget-lib.js +0 -157
- package/scripts/check-pack-budget.mjs +0 -10
- package/scripts/copy-oauth-success.js +0 -76
- package/scripts/install-codex-auth.js +0 -258
- package/scripts/repo-hygiene.js +0 -320
- package/scripts/test-all-models.sh +0 -7
- package/scripts/test-model-matrix.js +0 -687
- package/scripts/update-vendor-provenance.mjs +0 -96
- package/scripts/validate-model-map.sh +0 -7
- package/scripts/verify-vendor-provenance.mjs +0 -53
|
@@ -1,687 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
5
|
-
import process from "node:process";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
|
|
8
|
-
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const repoRoot = resolve(scriptDir, "..");
|
|
10
|
-
const localConfigPaths = [
|
|
11
|
-
join(repoRoot, ".codex.json"),
|
|
12
|
-
join(repoRoot, "Codex.json"),
|
|
13
|
-
];
|
|
14
|
-
const scenarioTemplates = {
|
|
15
|
-
legacy: join(repoRoot, "config", "codex-legacy.json"),
|
|
16
|
-
modern: join(repoRoot, "config", "codex-modern.json"),
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const pluginPackageName = "codex-multi-auth";
|
|
20
|
-
const DEFAULT_MATRIX_TIMEOUT_MS = 120000;
|
|
21
|
-
const DEFAULT_SMOKE_MATRIX_TIMEOUT_MS = 15000;
|
|
22
|
-
|
|
23
|
-
function resolveCmdScriptEntry(commandPath) {
|
|
24
|
-
if (!/\.cmd$/i.test(commandPath)) {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
const raw = readFileSync(commandPath, "utf8");
|
|
29
|
-
const match = raw.match(/"%dp0%\\([^"\r\n]+\.js)"/i);
|
|
30
|
-
if (!match || typeof match[1] !== "string") {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
const relScriptPath = match[1].replace(/[\\/]+/g, "/");
|
|
34
|
-
const scriptPath = resolve(dirname(commandPath), relScriptPath);
|
|
35
|
-
return existsSync(scriptPath) ? scriptPath : null;
|
|
36
|
-
} catch {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function buildExecutable(command) {
|
|
42
|
-
const scriptEntry = resolveCmdScriptEntry(command);
|
|
43
|
-
if (scriptEntry) {
|
|
44
|
-
return {
|
|
45
|
-
command: process.execPath,
|
|
46
|
-
shell: false,
|
|
47
|
-
prefixArgs: [scriptEntry],
|
|
48
|
-
displayCommand: command,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
return { command, shell: /\.cmd$/i.test(command) };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function resolveCodexExecutable() {
|
|
55
|
-
const envOverride = process.env.CODEX_BIN;
|
|
56
|
-
if (envOverride && envOverride.trim().length > 0) {
|
|
57
|
-
const command = envOverride.trim();
|
|
58
|
-
return buildExecutable(command);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (process.platform !== "win32") {
|
|
62
|
-
return { command: "codex", shell: false };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const whereResult = spawnSync("where", ["Codex"], {
|
|
66
|
-
encoding: "utf8",
|
|
67
|
-
windowsHide: true,
|
|
68
|
-
});
|
|
69
|
-
const candidates = `${whereResult.stdout ?? ""}`
|
|
70
|
-
.split(/\r?\n/)
|
|
71
|
-
.map((line) => line.trim())
|
|
72
|
-
.filter((line) => /^[A-Za-z]:\\.+\.(exe|cmd)$/i.test(line));
|
|
73
|
-
|
|
74
|
-
if (candidates.length === 0) {
|
|
75
|
-
return { command: "codex", shell: false };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const exactExe = candidates.find((candidate) =>
|
|
79
|
-
/npm\\Codex\.exe$/i.test(candidate),
|
|
80
|
-
);
|
|
81
|
-
if (exactExe) {
|
|
82
|
-
return { command: exactExe, shell: false };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const exactCmd = candidates.find((candidate) =>
|
|
86
|
-
/npm\\Codex\.cmd$/i.test(candidate),
|
|
87
|
-
);
|
|
88
|
-
if (exactCmd) {
|
|
89
|
-
return buildExecutable(exactCmd);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const anyCmd = candidates.find((candidate) => /\.cmd$/i.test(candidate));
|
|
93
|
-
if (anyCmd) {
|
|
94
|
-
return buildExecutable(anyCmd);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return { command: candidates[0], shell: false };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const CodexExecutable = resolveCodexExecutable();
|
|
101
|
-
|
|
102
|
-
function printUsage() {
|
|
103
|
-
console.log(
|
|
104
|
-
[
|
|
105
|
-
"Usage: node scripts/test-model-matrix.js [options]",
|
|
106
|
-
"",
|
|
107
|
-
"Options:",
|
|
108
|
-
" --scenario=legacy|modern|all Which template(s) to audit (default: all)",
|
|
109
|
-
" --smoke Run reduced per-model checks",
|
|
110
|
-
" --plugin=dist|package Load plugin from local dist URI or package name (default: dist)",
|
|
111
|
-
" --max-cases=N Hard cap number of cases per scenario",
|
|
112
|
-
" --report-json=PATH Write JSON report to PATH (relative to repo root)",
|
|
113
|
-
" --strict-capabilities Fail unsupported account/model capabilities instead of skipping them",
|
|
114
|
-
" --no-restore Keep generated local config files after run",
|
|
115
|
-
" -h, --help Show help",
|
|
116
|
-
].join("\n"),
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function parseArgValue(args, name) {
|
|
121
|
-
const prefix = `${name}=`;
|
|
122
|
-
const hit = args.find((arg) => arg.startsWith(prefix));
|
|
123
|
-
if (!hit) return undefined;
|
|
124
|
-
return hit.slice(prefix.length);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function toFileUri(pathValue) {
|
|
128
|
-
const normalized = pathValue.replace(/\\/g, "/");
|
|
129
|
-
if (/^[A-Za-z]:\//.test(normalized)) {
|
|
130
|
-
return `file:///${normalized}`;
|
|
131
|
-
}
|
|
132
|
-
if (normalized.startsWith("/")) {
|
|
133
|
-
return `file://${normalized}`;
|
|
134
|
-
}
|
|
135
|
-
return `file:///${normalized}`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
let stopCodexServersQueue = Promise.resolve();
|
|
139
|
-
const spawnedCodexPids = new Set();
|
|
140
|
-
|
|
141
|
-
function runQuiet(command, commandArgs) {
|
|
142
|
-
try {
|
|
143
|
-
spawnSync(command, commandArgs, {
|
|
144
|
-
stdio: "ignore",
|
|
145
|
-
windowsHide: true,
|
|
146
|
-
});
|
|
147
|
-
} catch {
|
|
148
|
-
// Ignore cleanup failures.
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function runQuietWindowsTaskkill(pid) {
|
|
153
|
-
runQuiet("cmd.exe", [
|
|
154
|
-
"/d",
|
|
155
|
-
"/s",
|
|
156
|
-
"/c",
|
|
157
|
-
`taskkill /F /T /PID ${pid}`,
|
|
158
|
-
]);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function registerSpawnedCodex(pid) {
|
|
162
|
-
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
163
|
-
spawnedCodexPids.add(pid);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function __resetTrackedCodexPidsForTests() {
|
|
167
|
-
spawnedCodexPids.clear();
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export function resolveMatrixTimeoutMs(smoke = false) {
|
|
171
|
-
const fallback = smoke
|
|
172
|
-
? DEFAULT_SMOKE_MATRIX_TIMEOUT_MS
|
|
173
|
-
: DEFAULT_MATRIX_TIMEOUT_MS;
|
|
174
|
-
const parsedTimeout = Number.parseInt(
|
|
175
|
-
process.env.CODEX_MATRIX_TIMEOUT_MS ?? String(fallback),
|
|
176
|
-
10,
|
|
177
|
-
);
|
|
178
|
-
if (!Number.isFinite(parsedTimeout) || parsedTimeout <= 0) {
|
|
179
|
-
return fallback;
|
|
180
|
-
}
|
|
181
|
-
return parsedTimeout;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function parseNdjsonEvents(output) {
|
|
185
|
-
const events = [];
|
|
186
|
-
for (const line of output.split(/\r?\n/)) {
|
|
187
|
-
const trimmed = line.trim();
|
|
188
|
-
if (!trimmed.startsWith("{")) {
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
try {
|
|
192
|
-
events.push(JSON.parse(trimmed));
|
|
193
|
-
} catch {
|
|
194
|
-
// Ignore wrapper noise and partial lines.
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return events;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function findLastIndex(items, predicate) {
|
|
201
|
-
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
202
|
-
if (predicate(items[index], index)) {
|
|
203
|
-
return index;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return -1;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function hasTerminalFailure(events) {
|
|
210
|
-
const lastCompletedIndex = findLastIndex(
|
|
211
|
-
events,
|
|
212
|
-
(event) =>
|
|
213
|
-
event?.type === "turn.completed" || event?.type === "response.completed",
|
|
214
|
-
);
|
|
215
|
-
return (
|
|
216
|
-
findLastIndex(
|
|
217
|
-
events,
|
|
218
|
-
(event, index) =>
|
|
219
|
-
index > lastCompletedIndex &&
|
|
220
|
-
(event?.type === "error" ||
|
|
221
|
-
event?.type === "turn.failed" ||
|
|
222
|
-
event?.type === "response.failed" ||
|
|
223
|
-
event?.type === "response.error" ||
|
|
224
|
-
event?.type === "response.incomplete"),
|
|
225
|
-
) >= 0
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function hasCompletedSuccessfully(output, token) {
|
|
230
|
-
const events = parseNdjsonEvents(output);
|
|
231
|
-
if (events.length > 0) {
|
|
232
|
-
if (hasTerminalFailure(events)) {
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
return events.some(
|
|
236
|
-
(event) =>
|
|
237
|
-
event?.type === "turn.completed" ||
|
|
238
|
-
event?.type === "response.completed",
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
return output.includes(token);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function getCapabilitySkipReason(exitCode, output, smoke) {
|
|
245
|
-
if (/not supported when using codex with a chatgpt account/i.test(output)) {
|
|
246
|
-
return "unsupported-model";
|
|
247
|
-
}
|
|
248
|
-
if (
|
|
249
|
-
/unsupported value:\s*['"]xhigh['"]/i.test(output) ||
|
|
250
|
-
/unsupported_value/i.test(output)
|
|
251
|
-
) {
|
|
252
|
-
return "unsupported-reasoning";
|
|
253
|
-
}
|
|
254
|
-
if (smoke && exitCode === 124) {
|
|
255
|
-
return "timed-out";
|
|
256
|
-
}
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function finalizeModelCaseResult(
|
|
261
|
-
caseInfo,
|
|
262
|
-
exitCode,
|
|
263
|
-
output,
|
|
264
|
-
token,
|
|
265
|
-
{ smoke, strictCapabilities } = {},
|
|
266
|
-
) {
|
|
267
|
-
const hasToken = output.includes(token);
|
|
268
|
-
const completed = hasCompletedSuccessfully(output, token);
|
|
269
|
-
const ok = exitCode === 0 && completed;
|
|
270
|
-
const skipReason =
|
|
271
|
-
!ok && strictCapabilities !== true
|
|
272
|
-
? getCapabilitySkipReason(exitCode, output, smoke === true)
|
|
273
|
-
: null;
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
...caseInfo,
|
|
277
|
-
ok,
|
|
278
|
-
exitCode,
|
|
279
|
-
hasToken,
|
|
280
|
-
completed,
|
|
281
|
-
skipped: skipReason !== null,
|
|
282
|
-
skipReason,
|
|
283
|
-
output,
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
export function __finalizeModelCaseResultForTests(
|
|
288
|
-
caseInfo,
|
|
289
|
-
exitCode,
|
|
290
|
-
output,
|
|
291
|
-
token,
|
|
292
|
-
smoke = false,
|
|
293
|
-
strictCapabilities = false,
|
|
294
|
-
) {
|
|
295
|
-
return finalizeModelCaseResult(caseInfo, exitCode, output, token, {
|
|
296
|
-
smoke,
|
|
297
|
-
strictCapabilities,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function stopCodexServersInternal() {
|
|
302
|
-
const tracked = [...spawnedCodexPids];
|
|
303
|
-
spawnedCodexPids.clear();
|
|
304
|
-
for (const pid of tracked) {
|
|
305
|
-
if (process.platform === "win32") {
|
|
306
|
-
runQuietWindowsTaskkill(pid);
|
|
307
|
-
continue;
|
|
308
|
-
}
|
|
309
|
-
runQuiet("kill", ["-9", String(pid)]);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export function stopCodexServers() {
|
|
314
|
-
// Avoid overlapping global process cleanup when matrix scripts are run concurrently.
|
|
315
|
-
stopCodexServersQueue = stopCodexServersQueue.then(async () => {
|
|
316
|
-
stopCodexServersInternal();
|
|
317
|
-
});
|
|
318
|
-
return stopCodexServersQueue;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function normalizePluginList(value, pluginRef) {
|
|
322
|
-
const entries = Array.isArray(value) ? value.filter(Boolean) : [];
|
|
323
|
-
const filtered = entries.filter((entry) => {
|
|
324
|
-
if (typeof entry !== "string") return true;
|
|
325
|
-
return !entry.startsWith(pluginPackageName);
|
|
326
|
-
});
|
|
327
|
-
return [...filtered, pluginRef];
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function enumerateCases(models, smoke, maxCases) {
|
|
331
|
-
const modelEntries = Object.entries(models).sort(([left], [right]) =>
|
|
332
|
-
left.localeCompare(right),
|
|
333
|
-
);
|
|
334
|
-
const cases = [];
|
|
335
|
-
|
|
336
|
-
for (const [modelId, modelDef] of modelEntries) {
|
|
337
|
-
const variants =
|
|
338
|
-
modelDef && typeof modelDef === "object" && modelDef !== null
|
|
339
|
-
? modelDef.variants
|
|
340
|
-
: undefined;
|
|
341
|
-
const variantNames =
|
|
342
|
-
variants && typeof variants === "object"
|
|
343
|
-
? Object.keys(variants).sort()
|
|
344
|
-
: [];
|
|
345
|
-
|
|
346
|
-
cases.push({ model: modelId, variant: undefined });
|
|
347
|
-
for (const variant of variantNames) {
|
|
348
|
-
cases.push({ model: modelId, variant });
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
let selected = cases;
|
|
353
|
-
if (smoke) {
|
|
354
|
-
const reduced = [];
|
|
355
|
-
for (const [modelId, modelDef] of modelEntries) {
|
|
356
|
-
reduced.push({ model: modelId, variant: undefined });
|
|
357
|
-
const variants =
|
|
358
|
-
modelDef && typeof modelDef === "object" && modelDef !== null
|
|
359
|
-
? modelDef.variants
|
|
360
|
-
: undefined;
|
|
361
|
-
const variantNames =
|
|
362
|
-
variants && typeof variants === "object"
|
|
363
|
-
? Object.keys(variants).sort()
|
|
364
|
-
: [];
|
|
365
|
-
if (variantNames.length > 0) {
|
|
366
|
-
if (variantNames.includes("high")) {
|
|
367
|
-
reduced.push({ model: modelId, variant: "high" });
|
|
368
|
-
} else if (variantNames.includes("medium")) {
|
|
369
|
-
reduced.push({ model: modelId, variant: "medium" });
|
|
370
|
-
} else {
|
|
371
|
-
reduced.push({ model: modelId, variant: variantNames[0] });
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
selected = reduced;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (maxCases > 0 && selected.length > maxCases) {
|
|
379
|
-
return selected.slice(0, maxCases);
|
|
380
|
-
}
|
|
381
|
-
return selected;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function buildModelCaseArgs(caseInfo, index) {
|
|
385
|
-
const token = `MODEL_MATRIX_OK_${index}`;
|
|
386
|
-
const args = [
|
|
387
|
-
"exec",
|
|
388
|
-
token,
|
|
389
|
-
"--model",
|
|
390
|
-
caseInfo.model,
|
|
391
|
-
"--json",
|
|
392
|
-
"--skip-git-repo-check",
|
|
393
|
-
];
|
|
394
|
-
if (caseInfo.variant) {
|
|
395
|
-
args.push("-c", `model_reasoning_effort="${caseInfo.variant}"`);
|
|
396
|
-
}
|
|
397
|
-
return { token, args };
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
export function __buildModelCaseArgsForTests(caseInfo, index) {
|
|
401
|
-
return buildModelCaseArgs(caseInfo, index);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function executeModelCase(caseInfo, index) {
|
|
405
|
-
const { token, args } = buildModelCaseArgs(caseInfo, index);
|
|
406
|
-
|
|
407
|
-
const timeoutMs = resolveMatrixTimeoutMs(caseInfo.smoke === true);
|
|
408
|
-
const commandArgs = [...(CodexExecutable.prefixArgs ?? []), ...args];
|
|
409
|
-
const finalized = spawnSync(CodexExecutable.command, commandArgs, {
|
|
410
|
-
cwd: repoRoot,
|
|
411
|
-
encoding: "utf8",
|
|
412
|
-
windowsHide: true,
|
|
413
|
-
shell: CodexExecutable.shell,
|
|
414
|
-
timeout: timeoutMs,
|
|
415
|
-
killSignal: "SIGKILL",
|
|
416
|
-
env: {
|
|
417
|
-
...process.env,
|
|
418
|
-
ENABLE_PLUGIN_REQUEST_LOGGING: "0",
|
|
419
|
-
CODEX_PLUGIN_LOG_BODIES: "0",
|
|
420
|
-
DEBUG_CODEX_PLUGIN: "0",
|
|
421
|
-
},
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
if (finalized.error && finalized.error.code === "ETIMEDOUT") {
|
|
425
|
-
return finalizeModelCaseResult(
|
|
426
|
-
caseInfo,
|
|
427
|
-
124,
|
|
428
|
-
`Timed out after ${timeoutMs}ms`,
|
|
429
|
-
token,
|
|
430
|
-
{
|
|
431
|
-
smoke: caseInfo.smoke === true,
|
|
432
|
-
strictCapabilities: caseInfo.strictCapabilities === true,
|
|
433
|
-
},
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const combinedOutput =
|
|
438
|
-
`${finalized.stdout ?? ""}\n${finalized.stderr ?? ""}`.trim();
|
|
439
|
-
const exitCode = finalized.status ?? 1;
|
|
440
|
-
return finalizeModelCaseResult(
|
|
441
|
-
caseInfo,
|
|
442
|
-
exitCode,
|
|
443
|
-
combinedOutput,
|
|
444
|
-
token,
|
|
445
|
-
{
|
|
446
|
-
smoke: caseInfo.smoke === true,
|
|
447
|
-
strictCapabilities: caseInfo.strictCapabilities === true,
|
|
448
|
-
},
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
async function readJson(pathValue) {
|
|
453
|
-
return JSON.parse(await readFile(pathValue, "utf8"));
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
async function writeJson(pathValue, value) {
|
|
457
|
-
await writeFile(pathValue, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
async function backupLocalConfigs() {
|
|
461
|
-
const backups = new Map();
|
|
462
|
-
for (const configPath of localConfigPaths) {
|
|
463
|
-
if (existsSync(configPath)) {
|
|
464
|
-
backups.set(configPath, await readFile(configPath, "utf8"));
|
|
465
|
-
} else {
|
|
466
|
-
backups.set(configPath, null);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
return backups;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
async function restoreLocalConfigs(backups) {
|
|
473
|
-
for (const [configPath, content] of backups.entries()) {
|
|
474
|
-
if (content === null) {
|
|
475
|
-
if (existsSync(configPath)) {
|
|
476
|
-
await rm(configPath, { force: true });
|
|
477
|
-
}
|
|
478
|
-
continue;
|
|
479
|
-
}
|
|
480
|
-
await writeFile(configPath, content, "utf8");
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
function resolvePluginReference(mode) {
|
|
485
|
-
if (mode === "package") {
|
|
486
|
-
return pluginPackageName;
|
|
487
|
-
}
|
|
488
|
-
const distEntry = join(repoRoot, "dist", "index.js");
|
|
489
|
-
if (!existsSync(distEntry)) {
|
|
490
|
-
throw new Error(
|
|
491
|
-
`dist build missing at ${distEntry}. Run 'npm run build' before matrix audit.`,
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
return toFileUri(join(repoRoot, "dist"));
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
async function prepareScenarioConfig(templatePath, pluginRef) {
|
|
498
|
-
const config = await readJson(templatePath);
|
|
499
|
-
config.plugin = normalizePluginList(config.plugin, pluginRef);
|
|
500
|
-
for (const configPath of localConfigPaths) {
|
|
501
|
-
await writeJson(configPath, config);
|
|
502
|
-
}
|
|
503
|
-
return config;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
async function runScenario(scenario, options) {
|
|
507
|
-
const templatePath = scenarioTemplates[scenario];
|
|
508
|
-
if (!templatePath || !existsSync(templatePath)) {
|
|
509
|
-
throw new Error(
|
|
510
|
-
`Template not found for scenario '${scenario}': ${templatePath}`,
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const config = await prepareScenarioConfig(templatePath, options.pluginRef);
|
|
515
|
-
const models = config?.provider?.openai?.models;
|
|
516
|
-
if (!models || typeof models !== "object") {
|
|
517
|
-
throw new Error(
|
|
518
|
-
`Scenario '${scenario}' has no provider.openai.models object`,
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
const cases = enumerateCases(models, options.smoke, options.maxCases).map(
|
|
523
|
-
(caseInfo) => ({
|
|
524
|
-
...caseInfo,
|
|
525
|
-
smoke: options.smoke,
|
|
526
|
-
strictCapabilities: options.strictCapabilities,
|
|
527
|
-
}),
|
|
528
|
-
);
|
|
529
|
-
console.log(`\n=== ${scenario.toUpperCase()} (${cases.length} cases) ===`);
|
|
530
|
-
|
|
531
|
-
const results = [];
|
|
532
|
-
for (let i = 0; i < cases.length; i += 1) {
|
|
533
|
-
const caseInfo = cases[i];
|
|
534
|
-
const result = executeModelCase(caseInfo, i + 1);
|
|
535
|
-
results.push(result);
|
|
536
|
-
const variantLabel = result.variant ? ` [variant=${result.variant}]` : "";
|
|
537
|
-
if (result.ok) {
|
|
538
|
-
console.log(`PASS ${result.model}${variantLabel}`);
|
|
539
|
-
} else if (result.skipped) {
|
|
540
|
-
console.log(
|
|
541
|
-
`SKIP ${result.model}${variantLabel} (${result.skipReason})`,
|
|
542
|
-
);
|
|
543
|
-
} else {
|
|
544
|
-
console.log(
|
|
545
|
-
`FAIL ${result.model}${variantLabel} (exit=${result.exitCode}, token=${result.hasToken})`,
|
|
546
|
-
);
|
|
547
|
-
const tail = result.output.split(/\r?\n/).slice(-12).join("\n");
|
|
548
|
-
if (tail.trim().length > 0) {
|
|
549
|
-
console.log(tail);
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
return results;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
async function main() {
|
|
558
|
-
const args = process.argv.slice(2);
|
|
559
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
560
|
-
printUsage();
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const smoke = args.includes("--smoke");
|
|
565
|
-
const scenarioValue =
|
|
566
|
-
parseArgValue(args, "--scenario") ?? (smoke ? "modern" : "all");
|
|
567
|
-
const pluginMode = parseArgValue(args, "--plugin") ?? "dist";
|
|
568
|
-
const strictCapabilities = args.includes("--strict-capabilities");
|
|
569
|
-
const noRestore = args.includes("--no-restore");
|
|
570
|
-
const maxCasesRaw = parseArgValue(args, "--max-cases");
|
|
571
|
-
const maxCases = maxCasesRaw ? Number.parseInt(maxCasesRaw, 10) : 0;
|
|
572
|
-
const reportJsonPathRaw = parseArgValue(args, "--report-json");
|
|
573
|
-
const reportJsonPath = reportJsonPathRaw
|
|
574
|
-
? resolve(repoRoot, reportJsonPathRaw)
|
|
575
|
-
: undefined;
|
|
576
|
-
|
|
577
|
-
if (!["all", "legacy", "modern"].includes(scenarioValue)) {
|
|
578
|
-
throw new Error(
|
|
579
|
-
`Invalid --scenario value '${scenarioValue}'. Use legacy, modern, or all.`,
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
if (!["dist", "package"].includes(pluginMode)) {
|
|
583
|
-
throw new Error(
|
|
584
|
-
`Invalid --plugin value '${pluginMode}'. Use dist or package.`,
|
|
585
|
-
);
|
|
586
|
-
}
|
|
587
|
-
if (Number.isNaN(maxCases) || maxCases < 0) {
|
|
588
|
-
throw new Error(
|
|
589
|
-
`Invalid --max-cases value '${maxCasesRaw}'. Use a non-negative integer.`,
|
|
590
|
-
);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const pluginRef = resolvePluginReference(pluginMode);
|
|
594
|
-
const scenarios =
|
|
595
|
-
scenarioValue === "all" ? ["legacy", "modern"] : [scenarioValue];
|
|
596
|
-
|
|
597
|
-
console.log("Codex Model Matrix Audit");
|
|
598
|
-
console.log(`Repo: ${repoRoot}`);
|
|
599
|
-
console.log(`Scenarios: ${scenarios.join(", ")}`);
|
|
600
|
-
console.log(`Mode: ${smoke ? "smoke" : "full"}`);
|
|
601
|
-
console.log(`Plugin: ${pluginRef}`);
|
|
602
|
-
console.log(
|
|
603
|
-
`Codex command: ${CodexExecutable.displayCommand ?? CodexExecutable.command}`,
|
|
604
|
-
);
|
|
605
|
-
|
|
606
|
-
const backups = await backupLocalConfigs();
|
|
607
|
-
const allResults = [];
|
|
608
|
-
try {
|
|
609
|
-
for (let i = 0; i < scenarios.length; i += 1) {
|
|
610
|
-
const scenario = scenarios[i];
|
|
611
|
-
await stopCodexServers();
|
|
612
|
-
const scenarioResults = await runScenario(scenario, {
|
|
613
|
-
smoke,
|
|
614
|
-
maxCases,
|
|
615
|
-
pluginRef,
|
|
616
|
-
strictCapabilities,
|
|
617
|
-
});
|
|
618
|
-
allResults.push(
|
|
619
|
-
...scenarioResults.map((item) => ({ ...item, scenario })),
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
} finally {
|
|
623
|
-
if (!noRestore) {
|
|
624
|
-
await restoreLocalConfigs(backups);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
const passed = allResults.filter((result) => result.ok);
|
|
629
|
-
const skipped = allResults.filter((result) => result.skipped);
|
|
630
|
-
const failed = allResults.filter((result) => !result.ok && !result.skipped);
|
|
631
|
-
console.log("\n=== SUMMARY ===");
|
|
632
|
-
console.log(`Total: ${allResults.length}`);
|
|
633
|
-
console.log(`Passed: ${passed.length}`);
|
|
634
|
-
console.log(`Skipped: ${skipped.length}`);
|
|
635
|
-
console.log(`Failed: ${failed.length}`);
|
|
636
|
-
|
|
637
|
-
if (reportJsonPath) {
|
|
638
|
-
const report = {
|
|
639
|
-
generatedAt: new Date().toISOString(),
|
|
640
|
-
repoRoot,
|
|
641
|
-
scenarios,
|
|
642
|
-
mode: smoke ? "smoke" : "full",
|
|
643
|
-
plugin: pluginRef,
|
|
644
|
-
CodexCommand: CodexExecutable.displayCommand ?? CodexExecutable.command,
|
|
645
|
-
totals: {
|
|
646
|
-
total: allResults.length,
|
|
647
|
-
passed: passed.length,
|
|
648
|
-
skipped: skipped.length,
|
|
649
|
-
failed: failed.length,
|
|
650
|
-
},
|
|
651
|
-
results: allResults,
|
|
652
|
-
};
|
|
653
|
-
await mkdir(dirname(reportJsonPath), { recursive: true });
|
|
654
|
-
await writeFile(
|
|
655
|
-
reportJsonPath,
|
|
656
|
-
`${JSON.stringify(report, null, 2)}\n`,
|
|
657
|
-
"utf8",
|
|
658
|
-
);
|
|
659
|
-
console.log(`Report written: ${reportJsonPath}`);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
if (failed.length > 0) {
|
|
663
|
-
console.log("\nFailed cases:");
|
|
664
|
-
for (const result of failed) {
|
|
665
|
-
const variantLabel = result.variant ? ` [variant=${result.variant}]` : "";
|
|
666
|
-
console.log(`- ${result.scenario}: ${result.model}${variantLabel}`);
|
|
667
|
-
}
|
|
668
|
-
process.exitCode = 1;
|
|
669
|
-
} else if (smoke && passed.length === 0) {
|
|
670
|
-
console.log(
|
|
671
|
-
"\nSmoke matrix was inconclusive: all cases were skipped for this current runtime/account capability set.",
|
|
672
|
-
);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
const isDirectRun =
|
|
677
|
-
process.argv.length > 1 &&
|
|
678
|
-
resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
679
|
-
|
|
680
|
-
if (isDirectRun) {
|
|
681
|
-
main().catch((error) => {
|
|
682
|
-
console.error(
|
|
683
|
-
`Model matrix audit failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
684
|
-
);
|
|
685
|
-
process.exit(1);
|
|
686
|
-
});
|
|
687
|
-
}
|