codex-multi-auth 1.3.2 → 2.0.1
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 +40 -26
- package/config/README.md +4 -4
- package/config/codex-legacy.json +571 -646
- package/config/codex-modern.json +258 -274
- package/config/minimal-codex.json +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +85 -14
- package/dist/index.js.map +1 -1
- package/dist/lib/auto-update-checker.d.ts +23 -1
- package/dist/lib/auto-update-checker.d.ts.map +1 -1
- package/dist/lib/auto-update-checker.js +481 -8
- package/dist/lib/auto-update-checker.js.map +1 -1
- package/dist/lib/cli.d.ts +5 -0
- package/dist/lib/cli.d.ts.map +1 -1
- package/dist/lib/cli.js.map +1 -1
- package/dist/lib/codex-manager/commands/best.js +1 -1
- package/dist/lib/codex-manager/commands/best.js.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.js +3 -3
- package/dist/lib/codex-manager/commands/forecast.js.map +1 -1
- package/dist/lib/codex-manager/commands/report.js +3 -3
- package/dist/lib/codex-manager/commands/report.js.map +1 -1
- package/dist/lib/codex-manager/commands/rotation.d.ts +26 -0
- package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -0
- package/dist/lib/codex-manager/commands/rotation.js +313 -0
- package/dist/lib/codex-manager/commands/rotation.js.map +1 -0
- package/dist/lib/codex-manager/commands/status.d.ts +6 -0
- package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/status.js +64 -10
- package/dist/lib/codex-manager/commands/status.js.map +1 -1
- package/dist/lib/codex-manager/forecast-report-commands.js +4 -4
- package/dist/lib/codex-manager/forecast-report-commands.js.map +1 -1
- package/dist/lib/codex-manager/help.d.ts.map +1 -1
- package/dist/lib/codex-manager/help.js +3 -2
- package/dist/lib/codex-manager/help.js.map +1 -1
- package/dist/lib/codex-manager/rate-limit-markers.d.ts +2 -0
- package/dist/lib/codex-manager/rate-limit-markers.d.ts.map +1 -0
- package/dist/lib/codex-manager/rate-limit-markers.js +4 -0
- package/dist/lib/codex-manager/rate-limit-markers.js.map +1 -0
- package/dist/lib/codex-manager/repair-commands.js +3 -3
- 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 +115 -87
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +9 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/constants.d.ts +2 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +2 -0
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/fs-retry.d.ts +7 -0
- package/dist/lib/fs-retry.d.ts.map +1 -0
- package/dist/lib/fs-retry.js +35 -0
- package/dist/lib/fs-retry.js.map +1 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +1 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/prompts/codex.d.ts +1 -1
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +3 -3
- package/dist/lib/prompts/codex.js.map +1 -1
- package/dist/lib/quota-probe.js +1 -1
- package/dist/lib/quota-probe.js.map +1 -1
- package/dist/lib/quota-readiness.d.ts +16 -0
- package/dist/lib/quota-readiness.d.ts.map +1 -0
- package/dist/lib/quota-readiness.js +82 -0
- package/dist/lib/quota-readiness.js.map +1 -0
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +19 -8
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/helpers/model-map.d.ts +2 -1
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
- package/dist/lib/request/helpers/model-map.js +52 -60
- package/dist/lib/request/helpers/model-map.js.map +1 -1
- package/dist/lib/request/request-transformer.d.ts +2 -2
- package/dist/lib/request/request-transformer.js +4 -3
- package/dist/lib/request/request-transformer.js.map +1 -1
- package/dist/lib/runtime/app-bind.d.ts +74 -0
- package/dist/lib/runtime/app-bind.d.ts.map +1 -0
- package/dist/lib/runtime/app-bind.js +620 -0
- package/dist/lib/runtime/app-bind.js.map +1 -0
- package/dist/lib/runtime/config-toml.d.ts +10 -0
- package/dist/lib/runtime/config-toml.d.ts.map +1 -0
- package/dist/lib/runtime/config-toml.js +128 -0
- package/dist/lib/runtime/config-toml.js.map +1 -0
- package/dist/lib/runtime/login-menu-accounts.d.ts +5 -0
- package/dist/lib/runtime/login-menu-accounts.d.ts.map +1 -1
- package/dist/lib/runtime/login-menu-accounts.js +7 -2
- package/dist/lib/runtime/login-menu-accounts.js.map +1 -1
- package/dist/lib/runtime/quota-probe.js +1 -1
- package/dist/lib/runtime/quota-probe.js.map +1 -1
- package/dist/lib/runtime/runtime-current-account.d.ts +52 -0
- package/dist/lib/runtime/runtime-current-account.d.ts.map +1 -0
- package/dist/lib/runtime/runtime-current-account.js +252 -0
- package/dist/lib/runtime/runtime-current-account.js.map +1 -0
- package/dist/lib/runtime/runtime-observability.d.ts +5 -0
- package/dist/lib/runtime/runtime-observability.d.ts.map +1 -1
- package/dist/lib/runtime/runtime-observability.js +5 -0
- package/dist/lib/runtime/runtime-observability.js.map +1 -1
- package/dist/lib/runtime-constants.d.ts +3 -0
- package/dist/lib/runtime-constants.d.ts.map +1 -0
- package/dist/lib/runtime-constants.js +3 -0
- package/dist/lib/runtime-constants.js.map +1 -0
- package/dist/lib/runtime-rotation-proxy.d.ts +36 -0
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -0
- package/dist/lib/runtime-rotation-proxy.js +828 -0
- package/dist/lib/runtime-rotation-proxy.js.map +1 -0
- package/dist/lib/schemas.d.ts +1 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +1 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/ui/auth-menu.d.ts +6 -1
- package/dist/lib/ui/auth-menu.d.ts.map +1 -1
- package/dist/lib/ui/auth-menu.js +22 -2
- package/dist/lib/ui/auth-menu.js.map +1 -1
- package/package.json +13 -11
- package/scripts/bench-format/codex-host.mjs +142 -73
- package/scripts/bench-format/models.mjs +23 -24
- package/scripts/benchmark-edit-formats.mjs +9 -7
- package/scripts/check-pack-budget-lib.js +49 -6
- package/scripts/codex-app-launcher.js +653 -0
- package/scripts/codex-app-router.js +322 -0
- package/scripts/codex-multi-auth.js +3 -1
- package/scripts/codex-routing.js +6 -0
- package/scripts/codex.js +2428 -445
- package/scripts/postinstall.js +343 -0
package/scripts/codex.js
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
4
5
|
import {
|
|
5
6
|
chmodSync,
|
|
6
7
|
copyFileSync,
|
|
8
|
+
cpSync,
|
|
7
9
|
existsSync,
|
|
10
|
+
linkSync,
|
|
8
11
|
mkdirSync,
|
|
9
12
|
mkdtempSync,
|
|
13
|
+
readdirSync,
|
|
10
14
|
renameSync,
|
|
11
15
|
readFileSync,
|
|
12
16
|
rmSync,
|
|
13
17
|
statSync,
|
|
18
|
+
symlinkSync,
|
|
14
19
|
writeFileSync,
|
|
15
20
|
} from "node:fs";
|
|
16
21
|
import { createRequire } from "node:module";
|
|
17
22
|
import { homedir, tmpdir } from "node:os";
|
|
18
23
|
import { basename, delimiter, dirname, join, resolve as resolvePath } from "node:path";
|
|
19
24
|
import process from "node:process";
|
|
20
|
-
import {
|
|
25
|
+
import { StringDecoder } from "node:string_decoder";
|
|
26
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
21
27
|
import {
|
|
22
28
|
resolveRealCodexBin as resolveRealCodexBinFromEnvironment,
|
|
23
29
|
splitPathEntries,
|
|
@@ -26,7 +32,39 @@ import { normalizeAuthAlias, shouldHandleMultiAuthAuth } from "./codex-routing.j
|
|
|
26
32
|
|
|
27
33
|
const RETRYABLE_SHADOW_HOME_CLEANUP_CODES = new Set(["EBUSY", "EPERM", "ENOTEMPTY"]);
|
|
28
34
|
const SHADOW_HOME_CLEANUP_BACKOFF_MS = [20, 60, 120];
|
|
35
|
+
const SHADOW_HOME_ORPHAN_LOCK_STALE_AGE_MS = 2_000;
|
|
36
|
+
const SHADOW_HOME_SYNC_LOCK_WAIT_TIMEOUT_MS =
|
|
37
|
+
SHADOW_HOME_ORPHAN_LOCK_STALE_AGE_MS +
|
|
38
|
+
SHADOW_HOME_CLEANUP_BACKOFF_MS.reduce((total, value) => total + value, 0);
|
|
29
39
|
const SHADOW_HOME_STATE_FILES = ["auth.json", "accounts.json", ".codex-global-state.json"];
|
|
40
|
+
const RUNTIME_ROTATION_SHADOW_HOME_OMIT_STATE_FILES = new Set([
|
|
41
|
+
"auth.json",
|
|
42
|
+
"accounts.json",
|
|
43
|
+
]);
|
|
44
|
+
const SHADOW_HOME_STATE_FILE_SET = new Set(SHADOW_HOME_STATE_FILES);
|
|
45
|
+
const SHADOW_HOME_CONFIG_FILE = "config.toml";
|
|
46
|
+
const SHADOW_HOME_SYNC_LOCK_DIR = ".codex-multi-auth-shadow-sync.lock";
|
|
47
|
+
const SHADOW_HOME_SYNC_STATE_FILE = ".codex-multi-auth-shadow-sync-state.json";
|
|
48
|
+
const APP_SERVER_ACCOUNT_DISPLAY_NAME = "codex-multi-auth";
|
|
49
|
+
const RUNTIME_CONSTANTS = await loadRuntimeConstants();
|
|
50
|
+
const RUNTIME_ROTATION_PROXY_PROVIDER_ID =
|
|
51
|
+
RUNTIME_CONSTANTS.RUNTIME_ROTATION_PROXY_PROVIDER_ID;
|
|
52
|
+
const APP_SERVER_ACCOUNT_LABEL_ENV = "CODEX_MULTI_AUTH_APP_SERVER_ACCOUNT_LABEL";
|
|
53
|
+
const INTERNAL_RUNTIME_ROTATION_APP_HELPER_ARG =
|
|
54
|
+
"--codex-multi-auth-runtime-app-helper";
|
|
55
|
+
const APP_RUNTIME_HELPER_OWNER_PID_ENV =
|
|
56
|
+
"CODEX_MULTI_AUTH_APP_ROTATION_OWNER_PID";
|
|
57
|
+
const APP_RUNTIME_HELPER_REAL_CODEX_HOME_ENV =
|
|
58
|
+
"CODEX_MULTI_AUTH_REAL_CODEX_HOME";
|
|
59
|
+
const APP_RUNTIME_HELPER_STATUS_FILE =
|
|
60
|
+
RUNTIME_CONSTANTS.APP_RUNTIME_HELPER_STATUS_FILE;
|
|
61
|
+
const DEFAULT_APP_RUNTIME_HELPER_IDLE_MS = 12 * 60 * 60 * 1000;
|
|
62
|
+
const DEFAULT_APP_RUNTIME_HELPER_DETACH_GRACE_MS = 5_000;
|
|
63
|
+
const APP_RUNTIME_HELPER_LAUNCH_TIMEOUT_MS = 15_000;
|
|
64
|
+
const APP_SERVER_SHIM_DIR_NAME = "app-server-shims";
|
|
65
|
+
const APP_SERVER_SHIM_HELPER_PREFIX = "helper-";
|
|
66
|
+
const DEFAULT_STARTUP_AUTO_UPDATE_BUDGET_MS = 3_000;
|
|
67
|
+
const STARTUP_AUTO_UPDATE_TIMED_OUT = Symbol("startup-auto-update-timed-out");
|
|
30
68
|
let shadowHomeCleanupBusyFailuresRemaining = Number.parseInt(
|
|
31
69
|
process.env.CODEX_MULTI_AUTH_TEST_SHADOW_CLEANUP_BUSY_FAILURES ?? "0",
|
|
32
70
|
10,
|
|
@@ -35,8 +73,45 @@ let shadowHomeCleanupPreflightReadBusyFailuresRemaining = Number.parseInt(
|
|
|
35
73
|
process.env.CODEX_MULTI_AUTH_TEST_SHADOW_PREFLIGHT_READ_BUSY_FAILURES ?? "0",
|
|
36
74
|
10,
|
|
37
75
|
);
|
|
76
|
+
let shadowHomeSyncLockRecreateStaleCount = Number.parseInt(
|
|
77
|
+
process.env.CODEX_MULTI_AUTH_TEST_SHADOW_LOCK_RECREATE_STALE_COUNT ?? "0",
|
|
78
|
+
10,
|
|
79
|
+
);
|
|
80
|
+
let shadowHomeSyncMetadataBusyFailuresRemaining = Number.parseInt(
|
|
81
|
+
process.env.CODEX_MULTI_AUTH_TEST_SHADOW_SYNC_METADATA_BUSY_FAILURES ?? "0",
|
|
82
|
+
10,
|
|
83
|
+
);
|
|
84
|
+
let shadowHomeSyncLockOwnerWriteFailuresRemaining = Number.parseInt(
|
|
85
|
+
process.env.CODEX_MULTI_AUTH_TEST_SHADOW_LOCK_OWNER_WRITE_FAILURES ?? "0",
|
|
86
|
+
10,
|
|
87
|
+
);
|
|
38
88
|
const shadowHomeCleanupRetryMarkerDir =
|
|
39
89
|
(process.env.CODEX_MULTI_AUTH_TEST_SHADOW_RETRY_MARKER_DIR ?? "").trim();
|
|
90
|
+
let warnedInvalidRuntimeRotationProxyEnv = false;
|
|
91
|
+
let warnedPendingAccountReadIdOverflow = false;
|
|
92
|
+
|
|
93
|
+
async function loadRuntimeConstants() {
|
|
94
|
+
const fallback = {
|
|
95
|
+
RUNTIME_ROTATION_PROXY_PROVIDER_ID: `${APP_SERVER_ACCOUNT_DISPLAY_NAME}-runtime-proxy`,
|
|
96
|
+
APP_RUNTIME_HELPER_STATUS_FILE: "runtime-rotation-app-helper.json",
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
const mod = await import("../dist/lib/runtime-constants.js");
|
|
100
|
+
return {
|
|
101
|
+
RUNTIME_ROTATION_PROXY_PROVIDER_ID:
|
|
102
|
+
typeof mod.RUNTIME_ROTATION_PROXY_PROVIDER_ID === "string"
|
|
103
|
+
? mod.RUNTIME_ROTATION_PROXY_PROVIDER_ID
|
|
104
|
+
: fallback.RUNTIME_ROTATION_PROXY_PROVIDER_ID,
|
|
105
|
+
APP_RUNTIME_HELPER_STATUS_FILE:
|
|
106
|
+
typeof mod.APP_RUNTIME_HELPER_STATUS_FILE === "string"
|
|
107
|
+
? mod.APP_RUNTIME_HELPER_STATUS_FILE
|
|
108
|
+
: fallback.APP_RUNTIME_HELPER_STATUS_FILE,
|
|
109
|
+
};
|
|
110
|
+
} catch {
|
|
111
|
+
// Keep wrapper startup resilient when dist has not been built yet.
|
|
112
|
+
}
|
|
113
|
+
return fallback;
|
|
114
|
+
}
|
|
40
115
|
|
|
41
116
|
function isRetryableShadowHomeCleanupError(error) {
|
|
42
117
|
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
@@ -81,6 +156,37 @@ function hydrateCliVersionEnv() {
|
|
|
81
156
|
}
|
|
82
157
|
}
|
|
83
158
|
|
|
159
|
+
function isRotationEnableCommand(args) {
|
|
160
|
+
return args[0] === "auth" && args[1] === "rotation" && args[2] === "enable";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function shouldAutoInstallCodexAppLauncher(env = process.env) {
|
|
164
|
+
const override = (env.CODEX_MULTI_AUTH_APP_LAUNCHER_INSTALL ?? "1").trim().toLowerCase();
|
|
165
|
+
return !new Set(["0", "false", "no"]).has(override);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function maybeInstallCodexAppLauncherAfterRotationEnable(args, exitCode) {
|
|
169
|
+
if (exitCode !== 0 || !isRotationEnableCommand(args)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (!shouldAutoInstallCodexAppLauncher()) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const mod = await import("./codex-app-launcher.js");
|
|
177
|
+
if (typeof mod.installCodexAppLauncher !== "function") {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
await mod.installCodexAppLauncher({
|
|
181
|
+
log: (message) => console.error(`codex-multi-auth: ${message}`),
|
|
182
|
+
});
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error(
|
|
185
|
+
`codex-multi-auth: could not route Codex app launchers: ${error instanceof Error ? error.message : String(error)}`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
84
190
|
async function loadRunCodexMultiAuthCli() {
|
|
85
191
|
try {
|
|
86
192
|
const mod = await import("../dist/lib/codex-manager.js");
|
|
@@ -105,6 +211,191 @@ async function loadRunCodexMultiAuthCli() {
|
|
|
105
211
|
}
|
|
106
212
|
}
|
|
107
213
|
|
|
214
|
+
async function loadRuntimeRotationProxyModule() {
|
|
215
|
+
try {
|
|
216
|
+
const mod = await import("../dist/lib/runtime-rotation-proxy.js");
|
|
217
|
+
if (typeof mod.startRuntimeRotationProxy !== "function") {
|
|
218
|
+
console.error(
|
|
219
|
+
"dist/lib/runtime-rotation-proxy.js is missing required export: startRuntimeRotationProxy",
|
|
220
|
+
);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return mod;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND") {
|
|
226
|
+
console.error(
|
|
227
|
+
[
|
|
228
|
+
"codex-multi-auth runtime rotation proxy requires built runtime files, but dist output is missing.",
|
|
229
|
+
"Run: npm run build",
|
|
230
|
+
].join("\n"),
|
|
231
|
+
);
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function loadRuntimeRotationConfigModule() {
|
|
239
|
+
try {
|
|
240
|
+
const mod = await import("../dist/lib/config.js");
|
|
241
|
+
if (
|
|
242
|
+
typeof mod.loadPluginConfig !== "function" ||
|
|
243
|
+
typeof mod.getCodexRuntimeRotationProxy !== "function"
|
|
244
|
+
) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
return mod;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND") {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function readBooleanEnvFlag(name) {
|
|
257
|
+
const normalized = (process.env[name] ?? "").trim().toLowerCase();
|
|
258
|
+
if (["1", "true", "yes"].includes(normalized)) return true;
|
|
259
|
+
if (["0", "false", "no"].includes(normalized)) return false;
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function isStartupAutoUpdateDebugEnabled() {
|
|
264
|
+
return readBooleanEnvFlag("CODEX_MULTI_AUTH_DEBUG") === true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function shouldLogStartupAutoUpdateProgress() {
|
|
268
|
+
return process.stderr.isTTY === true || isStartupAutoUpdateDebugEnabled();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function readStartupAutoUpdateBudgetMs() {
|
|
272
|
+
const raw =
|
|
273
|
+
process.env.CODEX_MULTI_AUTH_AUTO_UPDATE_STARTUP_BUDGET_MS ??
|
|
274
|
+
process.env.CODEX_MULTI_AUTH_TEST_STARTUP_AUTO_UPDATE_BUDGET_MS;
|
|
275
|
+
if (!raw) return DEFAULT_STARTUP_AUTO_UPDATE_BUDGET_MS;
|
|
276
|
+
const parsed = Number.parseInt(raw, 10);
|
|
277
|
+
return Number.isFinite(parsed) && parsed > 0
|
|
278
|
+
? parsed
|
|
279
|
+
: DEFAULT_STARTUP_AUTO_UPDATE_BUDGET_MS;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function resolveStartupAutoUpdateTimeouts(budgetMs) {
|
|
283
|
+
const fetchTimeoutMs = Math.max(1, Math.floor(budgetMs * 0.4));
|
|
284
|
+
return {
|
|
285
|
+
fetchTimeoutMs,
|
|
286
|
+
updateTimeoutMs: Math.max(1, budgetMs - fetchTimeoutMs),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function isModuleNotFoundError(error) {
|
|
291
|
+
return (
|
|
292
|
+
error &&
|
|
293
|
+
typeof error === "object" &&
|
|
294
|
+
"code" in error &&
|
|
295
|
+
error.code === "ERR_MODULE_NOT_FOUND"
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function shouldRunStartupAutoUpdate(rawArgs, normalizedArgs) {
|
|
300
|
+
if ((process.env.CODEX_MULTI_AUTH_BYPASS ?? "").trim() === "1") {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
if (isPureHelpOrVersionArgs(rawArgs)) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
if (shouldHandleMultiAuthAuth(normalizedArgs)) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function withStartupAutoUpdateBudget(promise, budgetMs) {
|
|
313
|
+
let timeout = null;
|
|
314
|
+
try {
|
|
315
|
+
return await Promise.race([
|
|
316
|
+
promise,
|
|
317
|
+
new Promise((resolve) => {
|
|
318
|
+
timeout = setTimeout(
|
|
319
|
+
() => {
|
|
320
|
+
timeout?.unref?.();
|
|
321
|
+
resolve(STARTUP_AUTO_UPDATE_TIMED_OUT);
|
|
322
|
+
},
|
|
323
|
+
budgetMs,
|
|
324
|
+
);
|
|
325
|
+
}),
|
|
326
|
+
]);
|
|
327
|
+
} finally {
|
|
328
|
+
if (timeout) clearTimeout(timeout);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function logStartupAutoUpdateDebug(message) {
|
|
333
|
+
if (isStartupAutoUpdateDebugEnabled()) {
|
|
334
|
+
console.error(`codex-multi-auth: ${message}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function autoUpdatePackageIfEnabled(rawArgs, normalizedArgs) {
|
|
339
|
+
if (!shouldRunStartupAutoUpdate(rawArgs, normalizedArgs)) return;
|
|
340
|
+
const budgetMs = readStartupAutoUpdateBudgetMs();
|
|
341
|
+
const { fetchTimeoutMs, updateTimeoutMs } =
|
|
342
|
+
resolveStartupAutoUpdateTimeouts(budgetMs);
|
|
343
|
+
try {
|
|
344
|
+
const mod = await import("../dist/lib/auto-update-checker.js");
|
|
345
|
+
if (typeof mod.autoUpdateIfAvailable !== "function") {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const updatePromise = mod.autoUpdateIfAvailable({
|
|
349
|
+
fetchTimeoutMs,
|
|
350
|
+
timeoutMs: updateTimeoutMs,
|
|
351
|
+
onUpdateStart: (update) => {
|
|
352
|
+
if (!update?.latestVersion) return;
|
|
353
|
+
if (!shouldLogStartupAutoUpdateProgress()) return;
|
|
354
|
+
console.error(
|
|
355
|
+
`codex-multi-auth: auto-update found ${update.latestVersion}; starting npm update -g codex-multi-auth. Startup will continue if it exceeds ${budgetMs}ms.`,
|
|
356
|
+
);
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
const result = await withStartupAutoUpdateBudget(updatePromise, budgetMs);
|
|
360
|
+
if (result === STARTUP_AUTO_UPDATE_TIMED_OUT) {
|
|
361
|
+
logStartupAutoUpdateDebug(
|
|
362
|
+
`auto-update skipped: startup budget exceeded after ${budgetMs}ms`,
|
|
363
|
+
);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (result?.updated && result.latestVersion) {
|
|
367
|
+
if (shouldLogStartupAutoUpdateProgress()) {
|
|
368
|
+
console.error(
|
|
369
|
+
`codex-multi-auth: auto-updated to ${result.latestVersion}. New sessions will use the latest package.`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (isModuleNotFoundError(error)) return;
|
|
375
|
+
logStartupAutoUpdateDebug(
|
|
376
|
+
`auto-update skipped: ${error instanceof Error ? error.message : String(error)}`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function loadRuntimeConfigTomlModule() {
|
|
382
|
+
try {
|
|
383
|
+
const mod = await import("../dist/lib/runtime/config-toml.js");
|
|
384
|
+
if (
|
|
385
|
+
typeof mod.rewriteConfigTomlForRuntimeRotationProvider !== "function" ||
|
|
386
|
+
typeof mod.tomlStringLiteral !== "function"
|
|
387
|
+
) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
return mod;
|
|
391
|
+
} catch (error) {
|
|
392
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND") {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
108
399
|
async function autoSyncManagerActiveSelectionIfEnabled() {
|
|
109
400
|
const enabled = (process.env.CODEX_MULTI_AUTH_AUTO_SYNC_ON_STARTUP ?? "1").trim() !== "0";
|
|
110
401
|
if (!enabled) return;
|
|
@@ -149,49 +440,76 @@ const NORMALIZED_UNSUPPORTED_MODEL_PATTERN =
|
|
|
149
440
|
/model ['"]([^'"]+)['"] is not currently available for this chatgpt account when using codex oauth/i;
|
|
150
441
|
const MODEL_ACCESS_DENIED_PATTERN =
|
|
151
442
|
/the model [`'"]([^`'"]+)[`'"] does not exist or you do not have access to it/i;
|
|
152
|
-
const DIRECT_UNSUPPORTED_MODEL_PATTERN =
|
|
153
|
-
/['"]([^'"]+)['"]\s+model is not supported when using codex with a chatgpt account/i;
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
443
|
+
const DIRECT_UNSUPPORTED_MODEL_PATTERN =
|
|
444
|
+
/['"]([^'"]+)['"]\s+model is not supported when using codex with a chatgpt account/i;
|
|
445
|
+
const CURRENT_CODEX_MODEL = "gpt-5.3-codex";
|
|
446
|
+
const LEGACY_CODEX_MODEL = "gpt-5-codex";
|
|
447
|
+
const WRAPPER_UNSUPPORTED_MODEL_FALLBACK_CHAIN = {
|
|
448
|
+
"gpt-5": ["gpt-5.5"],
|
|
449
|
+
"gpt-5-pro": ["gpt-5.5-pro"],
|
|
450
|
+
"gpt-5-chat-latest": ["gpt-5.5"],
|
|
451
|
+
"gpt-5.5": ["gpt-5.4"],
|
|
452
|
+
"gpt-5.5-pro": ["gpt-5.4"],
|
|
453
|
+
"gpt-5.5-2026-04-23": ["gpt-5.4"],
|
|
454
|
+
"gpt-5.5-pro-2026-04-23": ["gpt-5.4"],
|
|
455
|
+
"gpt-5.5-20260423": ["gpt-5.4"],
|
|
456
|
+
"gpt-5.5-pro-20260423": ["gpt-5.4"],
|
|
457
|
+
"gpt-5.3-codex-spark": [CURRENT_CODEX_MODEL],
|
|
458
|
+
"codex-max": [CURRENT_CODEX_MODEL],
|
|
459
|
+
"gpt-5.1-codex-max": [CURRENT_CODEX_MODEL],
|
|
460
|
+
"codex-mini-latest": [CURRENT_CODEX_MODEL],
|
|
461
|
+
"gpt-5-codex-mini": [CURRENT_CODEX_MODEL],
|
|
462
|
+
"gpt-5.1-codex-mini": [CURRENT_CODEX_MODEL],
|
|
463
|
+
[LEGACY_CODEX_MODEL]: [CURRENT_CODEX_MODEL],
|
|
464
|
+
"gpt-5.2-codex": [CURRENT_CODEX_MODEL],
|
|
465
|
+
"gpt-5.1-codex": [CURRENT_CODEX_MODEL],
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
function canonicalizeRequestedModelName(model) {
|
|
469
|
+
if (typeof model !== "string") return "";
|
|
470
|
+
const normalizedModel = normalizeRequestedModel(model);
|
|
471
|
+
if (normalizedModel) {
|
|
472
|
+
return normalizedModel;
|
|
164
473
|
}
|
|
165
474
|
|
|
166
475
|
const stripped = stripProviderPrefix(model).trim().toLowerCase();
|
|
167
476
|
if (!stripped) {
|
|
168
477
|
return "";
|
|
169
478
|
}
|
|
170
|
-
|
|
171
|
-
return stripped.replace(/-(none|minimal|low|medium|high|xhigh)$/i, "");
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function
|
|
175
|
-
if (typeof
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
if (
|
|
190
|
-
return
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
479
|
+
|
|
480
|
+
return stripped.replace(/-(none|minimal|low|medium|high|xhigh)$/i, "");
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function canonicalizeUnsupportedModelKey(model) {
|
|
484
|
+
if (typeof model !== "string") return "";
|
|
485
|
+
const stripped = stripProviderPrefix(model).trim().toLowerCase();
|
|
486
|
+
if (!stripped) {
|
|
487
|
+
return "";
|
|
488
|
+
}
|
|
489
|
+
return stripped.replace(/-(none|minimal|low|medium|high|xhigh)$/i, "");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function extractUnsupportedModelFromOutput(output) {
|
|
493
|
+
if (typeof output !== "string" || output.length === 0) {
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const directMatch = output.match(DIRECT_UNSUPPORTED_MODEL_PATTERN);
|
|
498
|
+
if (directMatch?.[1]) {
|
|
499
|
+
return canonicalizeUnsupportedModelKey(directMatch[1]);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const normalizedMatch = output.match(NORMALIZED_UNSUPPORTED_MODEL_PATTERN);
|
|
503
|
+
if (normalizedMatch?.[1]) {
|
|
504
|
+
return canonicalizeUnsupportedModelKey(normalizedMatch[1]);
|
|
505
|
+
}
|
|
506
|
+
const accessDeniedMatch = output.match(MODEL_ACCESS_DENIED_PATTERN);
|
|
507
|
+
if (accessDeniedMatch?.[1]) {
|
|
508
|
+
return canonicalizeUnsupportedModelKey(accessDeniedMatch[1]);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
195
513
|
|
|
196
514
|
function resolveUnsupportedModelRetryTarget(
|
|
197
515
|
requestedModel,
|
|
@@ -208,14 +526,14 @@ function resolveUnsupportedModelRetryTarget(
|
|
|
208
526
|
return undefined;
|
|
209
527
|
}
|
|
210
528
|
|
|
211
|
-
const attempted = new Set(
|
|
212
|
-
attemptedModels
|
|
213
|
-
.map((model) =>
|
|
214
|
-
.filter(Boolean),
|
|
215
|
-
);
|
|
216
|
-
const normalizedRequestedModel =
|
|
217
|
-
const blockedModel =
|
|
218
|
-
extractUnsupportedModelFromOutput(output) ?? normalizedRequestedModel;
|
|
529
|
+
const attempted = new Set(
|
|
530
|
+
attemptedModels
|
|
531
|
+
.map((model) => canonicalizeUnsupportedModelKey(model))
|
|
532
|
+
.filter(Boolean),
|
|
533
|
+
);
|
|
534
|
+
const normalizedRequestedModel = canonicalizeUnsupportedModelKey(requestedModel);
|
|
535
|
+
const blockedModel =
|
|
536
|
+
extractUnsupportedModelFromOutput(output) ?? normalizedRequestedModel;
|
|
219
537
|
const fallbackChain =
|
|
220
538
|
WRAPPER_UNSUPPORTED_MODEL_FALLBACK_CHAIN[blockedModel] ??
|
|
221
539
|
(normalizedRequestedModel
|
|
@@ -232,173 +550,432 @@ function resolveUnsupportedModelRetryTarget(
|
|
|
232
550
|
return undefined;
|
|
233
551
|
}
|
|
234
552
|
|
|
235
|
-
function replaceRequestedModel(args, nextModel) {
|
|
236
|
-
if (!nextModel) {
|
|
237
|
-
return [...args];
|
|
238
|
-
}
|
|
553
|
+
function replaceRequestedModel(args, nextModel) {
|
|
554
|
+
if (!nextModel) {
|
|
555
|
+
return [...args];
|
|
556
|
+
}
|
|
239
557
|
|
|
240
558
|
const nextArgs = [...args];
|
|
241
559
|
for (let i = 0; i < nextArgs.length; i += 1) {
|
|
242
|
-
const arg = nextArgs[i];
|
|
243
|
-
if (arg === "--model" && typeof nextArgs[i + 1] === "string") {
|
|
244
|
-
nextArgs[i + 1] = nextModel;
|
|
245
|
-
return nextArgs;
|
|
246
|
-
}
|
|
560
|
+
const arg = nextArgs[i];
|
|
561
|
+
if ((arg === "--model" || arg === "-m") && typeof nextArgs[i + 1] === "string") {
|
|
562
|
+
nextArgs[i + 1] = nextModel;
|
|
563
|
+
return nextArgs;
|
|
564
|
+
}
|
|
247
565
|
if (typeof arg === "string" && arg.startsWith("--model=")) {
|
|
248
566
|
nextArgs[i] = `--model=${nextModel}`;
|
|
249
567
|
return nextArgs;
|
|
250
568
|
}
|
|
251
569
|
}
|
|
252
570
|
|
|
253
|
-
return nextArgs;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function shouldCaptureForwardedCodexOutput(env = process.env) {
|
|
257
|
-
const override = (env.CODEX_MULTI_AUTH_CAPTURE_FORWARD_OUTPUT ?? "").trim();
|
|
258
|
-
if (override === "1") {
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
261
|
-
if (override === "0") {
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
// Windows child processes can report undefined isTTY; treat that as non-TTY so retry capture remains available.
|
|
265
|
-
return process.stdout.isTTY !== true || process.stderr.isTTY !== true;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function forwardToRealCodexOnce(
|
|
269
|
-
codexBin,
|
|
270
|
-
args,
|
|
271
|
-
env = process.env,
|
|
272
|
-
cleanup,
|
|
273
|
-
options = {},
|
|
274
|
-
) {
|
|
275
|
-
return new Promise((resolve) => {
|
|
276
|
-
let settled = false;
|
|
277
|
-
let stdout = "";
|
|
278
|
-
let stderr = "";
|
|
279
|
-
const captureOutput = options.captureOutput === true;
|
|
280
|
-
const finalize = (exitCode) => {
|
|
281
|
-
if (settled) {
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
settled = true;
|
|
285
|
-
try {
|
|
286
|
-
cleanup?.();
|
|
287
|
-
} catch {
|
|
288
|
-
// Best-effort cleanup only.
|
|
289
|
-
}
|
|
290
|
-
resolve({
|
|
291
|
-
exitCode,
|
|
292
|
-
// No-capture output stays empty by design so retry parsing cannot
|
|
293
|
-
// reintroduce pipes that break terminal passthrough.
|
|
294
|
-
output: `${stdout}\n${stderr}`.trim(),
|
|
295
|
-
});
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
const command = codexBin.launchWithNode ? process.execPath : codexBin.path;
|
|
299
|
-
const commandArgs = codexBin.launchWithNode ? [codexBin.path, ...args] : args;
|
|
300
|
-
let child;
|
|
301
|
-
const failLaunch = (error) => {
|
|
302
|
-
const message = `Failed to launch real Codex CLI: ${String(error)}`;
|
|
303
|
-
stderr += `${stderr ? "\n" : ""}${message}`;
|
|
304
|
-
console.error(message);
|
|
305
|
-
finalize(1);
|
|
306
|
-
};
|
|
307
|
-
try {
|
|
308
|
-
child = spawn(command, commandArgs, {
|
|
309
|
-
stdio: captureOutput ? ["inherit", "pipe", "pipe"] : "inherit",
|
|
310
|
-
env,
|
|
311
|
-
});
|
|
312
|
-
} catch (error) {
|
|
313
|
-
failLaunch(error);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (captureOutput) {
|
|
318
|
-
child.stdout?.on("data", (chunk) => {
|
|
319
|
-
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
320
|
-
stdout += text;
|
|
321
|
-
process.stdout.write(chunk);
|
|
322
|
-
});
|
|
323
|
-
child.stderr?.on("data", (chunk) => {
|
|
324
|
-
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
325
|
-
stderr += text;
|
|
326
|
-
process.stderr.write(chunk);
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
child.once("error", (error) => {
|
|
331
|
-
failLaunch(error);
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
child.once("close", (code, signal) => {
|
|
335
|
-
if (signal) {
|
|
336
|
-
const signalNumber = signal === "SIGINT" ? 130 : 1;
|
|
337
|
-
finalize(signalNumber);
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
finalize(typeof code === "number" ? code : 1);
|
|
341
|
-
});
|
|
342
|
-
});
|
|
571
|
+
return nextArgs;
|
|
343
572
|
}
|
|
344
573
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
574
|
+
function shouldCaptureForwardedCodexOutput(env = process.env) {
|
|
575
|
+
const override = (env.CODEX_MULTI_AUTH_CAPTURE_FORWARD_OUTPUT ?? "").trim();
|
|
576
|
+
if (override === "1") {
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
if (override === "0") {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
// Windows child processes can report undefined isTTY; treat that as non-TTY so retry capture remains available.
|
|
583
|
+
return process.stdout.isTTY !== true || process.stderr.isTTY !== true;
|
|
584
|
+
}
|
|
349
585
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
586
|
+
function jsonRpcIdKey(id) {
|
|
587
|
+
if (
|
|
588
|
+
typeof id === "string" ||
|
|
589
|
+
typeof id === "number" ||
|
|
590
|
+
typeof id === "boolean" ||
|
|
591
|
+
id === null
|
|
592
|
+
) {
|
|
593
|
+
return `${typeof id}:${JSON.stringify(id)}`;
|
|
594
|
+
}
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
355
597
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
},
|
|
371
|
-
);
|
|
372
|
-
lastExitCode = result.exitCode;
|
|
373
|
-
if (result.exitCode === 0) {
|
|
374
|
-
return result.exitCode;
|
|
375
|
-
}
|
|
598
|
+
function parseJsonObjectLine(line) {
|
|
599
|
+
const trimmed = line.trim();
|
|
600
|
+
if (!trimmed.startsWith("{")) {
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
const parsed = JSON.parse(trimmed);
|
|
605
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
606
|
+
? parsed
|
|
607
|
+
: null;
|
|
608
|
+
} catch {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
376
612
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
613
|
+
function splitProtocolLineEnding(line) {
|
|
614
|
+
if (line.endsWith("\r\n")) {
|
|
615
|
+
return { body: line.slice(0, -2), lineEnding: "\r\n" };
|
|
616
|
+
}
|
|
617
|
+
if (line.endsWith("\n")) {
|
|
618
|
+
return { body: line.slice(0, -1), lineEnding: "\n" };
|
|
619
|
+
}
|
|
620
|
+
return { body: line, lineEnding: "" };
|
|
621
|
+
}
|
|
385
622
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
623
|
+
function createProtocolLineAccumulator(onLine) {
|
|
624
|
+
const decoder = new StringDecoder("utf8");
|
|
625
|
+
let buffer = "";
|
|
626
|
+
const drain = () => {
|
|
627
|
+
let newlineIndex = buffer.indexOf("\n");
|
|
628
|
+
while (newlineIndex >= 0) {
|
|
629
|
+
const line = buffer.slice(0, newlineIndex + 1);
|
|
630
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
631
|
+
onLine(line);
|
|
632
|
+
newlineIndex = buffer.indexOf("\n");
|
|
393
633
|
}
|
|
394
|
-
|
|
395
|
-
}
|
|
634
|
+
};
|
|
396
635
|
|
|
397
|
-
return
|
|
636
|
+
return {
|
|
637
|
+
write(chunk) {
|
|
638
|
+
buffer += typeof chunk === "string" ? chunk : decoder.write(chunk);
|
|
639
|
+
drain();
|
|
640
|
+
},
|
|
641
|
+
end() {
|
|
642
|
+
buffer += decoder.end();
|
|
643
|
+
if (buffer.length > 0) {
|
|
644
|
+
onLine(buffer);
|
|
645
|
+
buffer = "";
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
};
|
|
398
649
|
}
|
|
399
650
|
|
|
400
|
-
function
|
|
401
|
-
|
|
651
|
+
function createSyntheticAppServerAccountReadResult() {
|
|
652
|
+
return {
|
|
653
|
+
account: {
|
|
654
|
+
type: "chatgpt",
|
|
655
|
+
email: APP_SERVER_ACCOUNT_DISPLAY_NAME,
|
|
656
|
+
planType: "unknown",
|
|
657
|
+
},
|
|
658
|
+
requiresOpenaiAuth: false,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function createSyntheticAppServerAuthStatusResult() {
|
|
663
|
+
return {
|
|
664
|
+
authMethod: "apikey",
|
|
665
|
+
authToken: null,
|
|
666
|
+
requiresOpenaiAuth: false,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function createSyntheticAppServerRateLimitsResult() {
|
|
671
|
+
return {
|
|
672
|
+
rateLimits: {
|
|
673
|
+
limitId: null,
|
|
674
|
+
limitName: null,
|
|
675
|
+
primary: null,
|
|
676
|
+
secondary: null,
|
|
677
|
+
credits: null,
|
|
678
|
+
planType: null,
|
|
679
|
+
rateLimitReachedType: null,
|
|
680
|
+
},
|
|
681
|
+
rateLimitsByLimitId: null,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function createAppServerAccountReadProtocolProxy() {
|
|
686
|
+
const maxPendingAuthRequestIds = 4096;
|
|
687
|
+
const pendingAuthRequestMethodsById = new Map();
|
|
688
|
+
const inputLines = createProtocolLineAccumulator((line) => {
|
|
689
|
+
const { body } = splitProtocolLineEnding(line);
|
|
690
|
+
const message = parseJsonObjectLine(body);
|
|
691
|
+
if (
|
|
692
|
+
![
|
|
693
|
+
"account/read",
|
|
694
|
+
"account/rateLimits/read",
|
|
695
|
+
"getAuthStatus",
|
|
696
|
+
].includes(message?.method) ||
|
|
697
|
+
!Object.hasOwn(message, "id")
|
|
698
|
+
) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const key = jsonRpcIdKey(message.id);
|
|
702
|
+
if (key) {
|
|
703
|
+
if (pendingAuthRequestMethodsById.size >= maxPendingAuthRequestIds) {
|
|
704
|
+
pendingAuthRequestMethodsById.clear();
|
|
705
|
+
if (!warnedPendingAccountReadIdOverflow) {
|
|
706
|
+
warnedPendingAccountReadIdOverflow = true;
|
|
707
|
+
console.error(
|
|
708
|
+
"codex-multi-auth: cleared pending app-server auth request ids after exceeding the safety cap.",
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
pendingAuthRequestMethodsById.set(key, message.method);
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
const outputLines = createProtocolLineAccumulator((line) => {
|
|
716
|
+
process.stdout.write(
|
|
717
|
+
rewriteAppServerAccountReadResponseLine(line, pendingAuthRequestMethodsById),
|
|
718
|
+
);
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
return {
|
|
722
|
+
observeInput(chunk) {
|
|
723
|
+
inputLines.write(chunk);
|
|
724
|
+
},
|
|
725
|
+
flushInput() {
|
|
726
|
+
inputLines.end();
|
|
727
|
+
},
|
|
728
|
+
writeOutput(chunk) {
|
|
729
|
+
outputLines.write(chunk);
|
|
730
|
+
},
|
|
731
|
+
flushOutput() {
|
|
732
|
+
outputLines.end();
|
|
733
|
+
},
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function rewriteAppServerAccountReadResponseLine(line, pendingAuthRequestMethodsById) {
|
|
738
|
+
const { body, lineEnding } = splitProtocolLineEnding(line);
|
|
739
|
+
const message = parseJsonObjectLine(body);
|
|
740
|
+
if (!message || !Object.hasOwn(message, "id")) {
|
|
741
|
+
return line;
|
|
742
|
+
}
|
|
743
|
+
const key = jsonRpcIdKey(message.id);
|
|
744
|
+
if (!key || !pendingAuthRequestMethodsById.has(key)) {
|
|
745
|
+
return line;
|
|
746
|
+
}
|
|
747
|
+
const method = pendingAuthRequestMethodsById.get(key);
|
|
748
|
+
pendingAuthRequestMethodsById.delete(key);
|
|
749
|
+
const result =
|
|
750
|
+
method === "account/read"
|
|
751
|
+
? createSyntheticAppServerAccountReadResult()
|
|
752
|
+
: method === "account/rateLimits/read"
|
|
753
|
+
? createSyntheticAppServerRateLimitsResult()
|
|
754
|
+
: method === "getAuthStatus"
|
|
755
|
+
? createSyntheticAppServerAuthStatusResult()
|
|
756
|
+
: null;
|
|
757
|
+
if (!result) {
|
|
758
|
+
return line;
|
|
759
|
+
}
|
|
760
|
+
return `${JSON.stringify({
|
|
761
|
+
jsonrpc: typeof message.jsonrpc === "string" ? message.jsonrpc : "2.0",
|
|
762
|
+
id: message.id,
|
|
763
|
+
result,
|
|
764
|
+
})}${lineEnding}`;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function forwardToRealCodexOnce(
|
|
768
|
+
codexBin,
|
|
769
|
+
args,
|
|
770
|
+
env = process.env,
|
|
771
|
+
cleanup,
|
|
772
|
+
options = {},
|
|
773
|
+
) {
|
|
774
|
+
return new Promise((resolve) => {
|
|
775
|
+
let settled = false;
|
|
776
|
+
let stdout = "";
|
|
777
|
+
let stderr = "";
|
|
778
|
+
const captureOutput = options.captureOutput === true;
|
|
779
|
+
const proxyAppServerAccountRead =
|
|
780
|
+
options.proxyAppServerAccountRead === true;
|
|
781
|
+
const protocolProxy = proxyAppServerAccountRead
|
|
782
|
+
? createAppServerAccountReadProtocolProxy()
|
|
783
|
+
: null;
|
|
784
|
+
let cleanupProtocolProxy = () => {};
|
|
785
|
+
const finalize = async (exitCode) => {
|
|
786
|
+
if (settled) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
settled = true;
|
|
790
|
+
cleanupProtocolProxy();
|
|
791
|
+
protocolProxy?.flushOutput();
|
|
792
|
+
try {
|
|
793
|
+
await cleanup?.({ exitCode });
|
|
794
|
+
} catch {
|
|
795
|
+
// Best-effort cleanup only.
|
|
796
|
+
}
|
|
797
|
+
resolve({
|
|
798
|
+
exitCode,
|
|
799
|
+
// No-capture output stays empty by design so retry parsing cannot
|
|
800
|
+
// reintroduce pipes that break terminal passthrough.
|
|
801
|
+
output: `${stdout}\n${stderr}`.trim(),
|
|
802
|
+
});
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
const command = codexBin.launchWithNode ? process.execPath : codexBin.path;
|
|
806
|
+
const commandArgs = codexBin.launchWithNode ? [codexBin.path, ...args] : args;
|
|
807
|
+
let child;
|
|
808
|
+
const failLaunch = (error) => {
|
|
809
|
+
const message = `Failed to launch real Codex CLI: ${String(error)}`;
|
|
810
|
+
stderr += `${stderr ? "\n" : ""}${message}`;
|
|
811
|
+
console.error(message);
|
|
812
|
+
finalize(1);
|
|
813
|
+
};
|
|
814
|
+
try {
|
|
815
|
+
child = spawn(command, commandArgs, {
|
|
816
|
+
stdio: proxyAppServerAccountRead
|
|
817
|
+
? ["pipe", "pipe", "pipe"]
|
|
818
|
+
: captureOutput
|
|
819
|
+
? ["inherit", "pipe", "pipe"]
|
|
820
|
+
: "inherit",
|
|
821
|
+
env,
|
|
822
|
+
});
|
|
823
|
+
} catch (error) {
|
|
824
|
+
failLaunch(error);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (proxyAppServerAccountRead && protocolProxy) {
|
|
829
|
+
let stdinClosed = false;
|
|
830
|
+
let stdoutClosed = false;
|
|
831
|
+
const closeChildStdin = () => {
|
|
832
|
+
if (stdinClosed) return;
|
|
833
|
+
stdinClosed = true;
|
|
834
|
+
protocolProxy.flushInput();
|
|
835
|
+
child.stdin?.end();
|
|
836
|
+
};
|
|
837
|
+
const onProcessStdinData = (chunk) => {
|
|
838
|
+
protocolProxy.observeInput(chunk);
|
|
839
|
+
if (child.stdin && !child.stdin.destroyed && !child.stdin.write(chunk)) {
|
|
840
|
+
process.stdin.pause();
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
const onProcessStdinEnd = () => {
|
|
844
|
+
closeChildStdin();
|
|
845
|
+
};
|
|
846
|
+
const onProcessStdinError = () => {
|
|
847
|
+
closeChildStdin();
|
|
848
|
+
};
|
|
849
|
+
const onChildStdinDrain = () => {
|
|
850
|
+
process.stdin.resume();
|
|
851
|
+
};
|
|
852
|
+
const onChildStdoutData = (chunk) => {
|
|
853
|
+
protocolProxy.writeOutput(chunk);
|
|
854
|
+
};
|
|
855
|
+
const onChildStdoutEnd = () => {
|
|
856
|
+
if (stdoutClosed) return;
|
|
857
|
+
stdoutClosed = true;
|
|
858
|
+
protocolProxy.flushOutput();
|
|
859
|
+
};
|
|
860
|
+
const onChildStderrData = (chunk) => {
|
|
861
|
+
process.stderr.write(chunk);
|
|
862
|
+
};
|
|
863
|
+
cleanupProtocolProxy = () => {
|
|
864
|
+
process.stdin.removeListener("data", onProcessStdinData);
|
|
865
|
+
process.stdin.removeListener("end", onProcessStdinEnd);
|
|
866
|
+
process.stdin.removeListener("close", onProcessStdinEnd);
|
|
867
|
+
process.stdin.removeListener("error", onProcessStdinError);
|
|
868
|
+
child.stdin?.removeListener("drain", onChildStdinDrain);
|
|
869
|
+
child.stdout?.removeListener("data", onChildStdoutData);
|
|
870
|
+
child.stdout?.removeListener("end", onChildStdoutEnd);
|
|
871
|
+
child.stderr?.removeListener("data", onChildStderrData);
|
|
872
|
+
process.stdin.resume();
|
|
873
|
+
};
|
|
874
|
+
process.stdin.on("data", onProcessStdinData);
|
|
875
|
+
process.stdin.once("end", onProcessStdinEnd);
|
|
876
|
+
process.stdin.once("close", onProcessStdinEnd);
|
|
877
|
+
process.stdin.once("error", onProcessStdinError);
|
|
878
|
+
child.stdin?.on("drain", onChildStdinDrain);
|
|
879
|
+
child.stdout?.on("data", onChildStdoutData);
|
|
880
|
+
child.stdout?.on("end", onChildStdoutEnd);
|
|
881
|
+
child.stderr?.on("data", onChildStderrData);
|
|
882
|
+
} else if (captureOutput) {
|
|
883
|
+
child.stdout?.on("data", (chunk) => {
|
|
884
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
885
|
+
stdout += text;
|
|
886
|
+
process.stdout.write(chunk);
|
|
887
|
+
});
|
|
888
|
+
child.stderr?.on("data", (chunk) => {
|
|
889
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
890
|
+
stderr += text;
|
|
891
|
+
process.stderr.write(chunk);
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
child.once("error", (error) => {
|
|
896
|
+
failLaunch(error);
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
child.once("close", (code, signal) => {
|
|
900
|
+
if (signal) {
|
|
901
|
+
const signalNumber = signal === "SIGINT" ? 130 : 1;
|
|
902
|
+
finalize(signalNumber);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
finalize(typeof code === "number" ? code : 1);
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
async function forwardToRealCodex(codexBin, rawArgs, baseEnv = process.env) {
|
|
911
|
+
let currentArgs = [...rawArgs];
|
|
912
|
+
let lastExitCode = 1;
|
|
913
|
+
const attemptedModels = new Set();
|
|
914
|
+
|
|
915
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
916
|
+
const requestedModel = extractRequestedModel(currentArgs);
|
|
917
|
+
if (requestedModel) {
|
|
918
|
+
attemptedModels.add(requestedModel);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const { args: forwardArgs, requestedModel: compatibilityRequestedModel } =
|
|
922
|
+
buildForwardArgs(currentArgs);
|
|
923
|
+
const compatibility = createCompatibilityCodexHome(
|
|
924
|
+
forwardArgs,
|
|
925
|
+
compatibilityRequestedModel,
|
|
926
|
+
baseEnv,
|
|
927
|
+
);
|
|
928
|
+
const runtimeProxyContext = await createRuntimeRotationProxyContextIfEnabled(
|
|
929
|
+
compatibility,
|
|
930
|
+
rawArgs,
|
|
931
|
+
);
|
|
932
|
+
const result = await forwardToRealCodexOnce(
|
|
933
|
+
codexBin,
|
|
934
|
+
runtimeProxyContext.args,
|
|
935
|
+
runtimeProxyContext.env,
|
|
936
|
+
runtimeProxyContext.cleanup,
|
|
937
|
+
{
|
|
938
|
+
captureOutput: shouldCaptureForwardedOutputForArgs(
|
|
939
|
+
rawArgs,
|
|
940
|
+
runtimeProxyContext.env,
|
|
941
|
+
),
|
|
942
|
+
proxyAppServerAccountRead:
|
|
943
|
+
isCodexAppServerCommand(rawArgs) &&
|
|
944
|
+
(runtimeProxyContext.proxyAppServerAccountRead === true ||
|
|
945
|
+
(runtimeProxyContext.env[APP_SERVER_ACCOUNT_LABEL_ENV] ?? "").trim() ===
|
|
946
|
+
"1"),
|
|
947
|
+
},
|
|
948
|
+
);
|
|
949
|
+
lastExitCode = result.exitCode;
|
|
950
|
+
if (result.exitCode === 0) {
|
|
951
|
+
return result.exitCode;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const fallbackModel = resolveUnsupportedModelRetryTarget(
|
|
955
|
+
requestedModel,
|
|
956
|
+
result.output,
|
|
957
|
+
[...attemptedModels],
|
|
958
|
+
);
|
|
959
|
+
if (!fallbackModel) {
|
|
960
|
+
return result.exitCode;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
console.error(
|
|
964
|
+
`codex-multi-auth: model ${requestedModel ?? "requested model"} is unsupported on this ChatGPT Codex surface. Retrying with ${fallbackModel}.`,
|
|
965
|
+
);
|
|
966
|
+
attemptedModels.add(fallbackModel);
|
|
967
|
+
const nextArgs = replaceRequestedModel(currentArgs, fallbackModel);
|
|
968
|
+
if (JSON.stringify(nextArgs) === JSON.stringify(currentArgs)) {
|
|
969
|
+
return result.exitCode;
|
|
970
|
+
}
|
|
971
|
+
currentArgs = nextArgs;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
return lastExitCode;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function hasCliAuthCredentialsStoreOverride(args) {
|
|
978
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
402
979
|
const arg = args[i];
|
|
403
980
|
if (arg === "-c" || arg === "--config") {
|
|
404
981
|
const next = args[i + 1];
|
|
@@ -424,19 +1001,17 @@ function hasCliAuthCredentialsStoreOverride(args) {
|
|
|
424
1001
|
// `lib/request/helpers/model-map.ts` `MODEL_PROFILES`
|
|
425
1002
|
// and its GPT-5 normalization helpers.
|
|
426
1003
|
// This wrapper runs before the TypeScript build, so it cannot import that source.
|
|
427
|
-
const SUPPORTED_REASONING_EFFORTS_BY_MODEL = {
|
|
428
|
-
|
|
429
|
-
"gpt-5.
|
|
430
|
-
"gpt-5.
|
|
431
|
-
"gpt-5.
|
|
432
|
-
"gpt-5.
|
|
433
|
-
"gpt-5.4": ["
|
|
434
|
-
"gpt-5.4-
|
|
435
|
-
"gpt-5.2-pro": ["medium", "high", "xhigh"],
|
|
436
|
-
"gpt-5-pro": ["high"],
|
|
1004
|
+
const SUPPORTED_REASONING_EFFORTS_BY_MODEL = {
|
|
1005
|
+
[CURRENT_CODEX_MODEL]: ["low", "medium", "high", "xhigh"],
|
|
1006
|
+
"gpt-5.5": ["none", "low", "medium", "high", "xhigh"],
|
|
1007
|
+
"gpt-5.5-pro": ["medium", "high", "xhigh"],
|
|
1008
|
+
"gpt-5.4": ["none", "low", "medium", "high", "xhigh"],
|
|
1009
|
+
"gpt-5.4-pro": ["medium", "high", "xhigh"],
|
|
1010
|
+
"gpt-5.4-mini": ["medium"],
|
|
1011
|
+
"gpt-5.4-nano": ["medium"],
|
|
1012
|
+
"gpt-5.2-pro": ["medium", "high", "xhigh"],
|
|
437
1013
|
"gpt-5.2": ["none", "low", "medium", "high", "xhigh"],
|
|
438
1014
|
"gpt-5.1": ["none", "low", "medium", "high"],
|
|
439
|
-
"gpt-5": ["minimal", "low", "medium", "high"],
|
|
440
1015
|
"gpt-5-mini": ["medium"],
|
|
441
1016
|
"gpt-5-nano": ["medium"],
|
|
442
1017
|
};
|
|
@@ -452,11 +1027,13 @@ const REASONING_FALLBACKS = {
|
|
|
452
1027
|
|
|
453
1028
|
const KNOWN_REASONING_EFFORTS = new Set(Object.keys(REASONING_FALLBACKS));
|
|
454
1029
|
const REQUESTED_MODEL_ALIASES = new Map();
|
|
455
|
-
const DEFAULT_GENERAL_GPT5_MODEL = "gpt-5.
|
|
456
|
-
const GPT_5_5_CANONICAL_MODEL = "gpt-5.5";
|
|
457
|
-
const GPT_5_5_PRO_CANONICAL_MODEL = "gpt-5.5-pro";
|
|
458
|
-
const GPT_5_5_RELEASE_MODEL = "gpt-5.5-
|
|
459
|
-
const GPT_5_5_PRO_RELEASE_MODEL = "gpt-5.5-pro-
|
|
1030
|
+
const DEFAULT_GENERAL_GPT5_MODEL = "gpt-5.5";
|
|
1031
|
+
const GPT_5_5_CANONICAL_MODEL = "gpt-5.5";
|
|
1032
|
+
const GPT_5_5_PRO_CANONICAL_MODEL = "gpt-5.5-pro";
|
|
1033
|
+
const GPT_5_5_RELEASE_MODEL = "gpt-5.5-2026-04-23";
|
|
1034
|
+
const GPT_5_5_PRO_RELEASE_MODEL = "gpt-5.5-pro-2026-04-23";
|
|
1035
|
+
const GPT_5_5_RELEASE_COMPAT_MODEL = "gpt-5.5-20260423";
|
|
1036
|
+
const GPT_5_5_PRO_RELEASE_COMPAT_MODEL = "gpt-5.5-pro-20260423";
|
|
460
1037
|
const GENERAL_GPT5_VERSION_CATALOG = {
|
|
461
1038
|
1: {
|
|
462
1039
|
base: "gpt-5.1",
|
|
@@ -465,12 +1042,12 @@ const GENERAL_GPT5_VERSION_CATALOG = {
|
|
|
465
1042
|
base: "gpt-5.2",
|
|
466
1043
|
pro: "gpt-5.2-pro",
|
|
467
1044
|
},
|
|
468
|
-
4: {
|
|
469
|
-
base: DEFAULT_GENERAL_GPT5_MODEL,
|
|
470
|
-
pro: "gpt-5.4-pro",
|
|
471
|
-
mini: "gpt-5-mini",
|
|
472
|
-
nano: "gpt-5-nano",
|
|
473
|
-
},
|
|
1045
|
+
4: {
|
|
1046
|
+
base: DEFAULT_GENERAL_GPT5_MODEL,
|
|
1047
|
+
pro: "gpt-5.4-pro",
|
|
1048
|
+
mini: "gpt-5.4-mini",
|
|
1049
|
+
nano: "gpt-5.4-nano",
|
|
1050
|
+
},
|
|
474
1051
|
5: {
|
|
475
1052
|
base: GPT_5_5_CANONICAL_MODEL,
|
|
476
1053
|
pro: GPT_5_5_PRO_CANONICAL_MODEL,
|
|
@@ -478,13 +1055,13 @@ const GENERAL_GPT5_VERSION_CATALOG = {
|
|
|
478
1055
|
nano: "gpt-5-nano",
|
|
479
1056
|
},
|
|
480
1057
|
};
|
|
481
|
-
const GENERAL_GPT5_STABLE_VARIANTS = GENERAL_GPT5_VERSION_CATALOG[
|
|
482
|
-
const GENERAL_GPT5_GENERIC_VARIANTS = {
|
|
483
|
-
base: DEFAULT_GENERAL_GPT5_MODEL,
|
|
484
|
-
pro:
|
|
485
|
-
mini: "gpt-5-mini",
|
|
486
|
-
nano: "gpt-5-nano",
|
|
487
|
-
};
|
|
1058
|
+
const GENERAL_GPT5_STABLE_VARIANTS = GENERAL_GPT5_VERSION_CATALOG[5];
|
|
1059
|
+
const GENERAL_GPT5_GENERIC_VARIANTS = {
|
|
1060
|
+
base: DEFAULT_GENERAL_GPT5_MODEL,
|
|
1061
|
+
pro: GPT_5_5_PRO_CANONICAL_MODEL,
|
|
1062
|
+
mini: "gpt-5-mini",
|
|
1063
|
+
nano: "gpt-5-nano",
|
|
1064
|
+
};
|
|
488
1065
|
|
|
489
1066
|
function addRequestedModelAlias(alias, normalizedModel) {
|
|
490
1067
|
REQUESTED_MODEL_ALIASES.set(alias, normalizedModel);
|
|
@@ -515,6 +1092,27 @@ function maybeThrowSimulatedShadowHomePreflightReadBusyError() {
|
|
|
515
1092
|
}
|
|
516
1093
|
}
|
|
517
1094
|
|
|
1095
|
+
function maybeThrowSimulatedShadowHomeSyncMetadataBusyError(targetPath) {
|
|
1096
|
+
if (
|
|
1097
|
+
basename(targetPath) === SHADOW_HOME_SYNC_STATE_FILE &&
|
|
1098
|
+
shadowHomeSyncMetadataBusyFailuresRemaining > 0
|
|
1099
|
+
) {
|
|
1100
|
+
shadowHomeSyncMetadataBusyFailuresRemaining -= 1;
|
|
1101
|
+
const error = new Error("simulated busy shadow-home sync metadata write");
|
|
1102
|
+
error.code = "EBUSY";
|
|
1103
|
+
throw error;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function maybeThrowSimulatedShadowHomeSyncLockOwnerWriteError() {
|
|
1108
|
+
if (shadowHomeSyncLockOwnerWriteFailuresRemaining > 0) {
|
|
1109
|
+
shadowHomeSyncLockOwnerWriteFailuresRemaining -= 1;
|
|
1110
|
+
const error = new Error("simulated shadow sync lock owner write failure");
|
|
1111
|
+
error.code = "EPERM";
|
|
1112
|
+
throw error;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
518
1116
|
function writeShadowHomeCleanupRetryMarker(destinationPath, attempt) {
|
|
519
1117
|
if (shadowHomeCleanupRetryMarkerDir.length === 0) {
|
|
520
1118
|
return;
|
|
@@ -576,42 +1174,50 @@ function seedRequestedModelAliases() {
|
|
|
576
1174
|
GPT_5_5_CANONICAL_MODEL,
|
|
577
1175
|
GPT_5_5_CANONICAL_MODEL,
|
|
578
1176
|
);
|
|
579
|
-
addRequestedModelReasoningAliases(
|
|
580
|
-
GPT_5_5_RELEASE_MODEL,
|
|
581
|
-
GPT_5_5_CANONICAL_MODEL,
|
|
582
|
-
);
|
|
1177
|
+
addRequestedModelReasoningAliases(
|
|
1178
|
+
GPT_5_5_RELEASE_MODEL,
|
|
1179
|
+
GPT_5_5_CANONICAL_MODEL,
|
|
1180
|
+
);
|
|
1181
|
+
addRequestedModelReasoningAliases(
|
|
1182
|
+
GPT_5_5_RELEASE_COMPAT_MODEL,
|
|
1183
|
+
GPT_5_5_CANONICAL_MODEL,
|
|
1184
|
+
);
|
|
583
1185
|
addRequestedModelReasoningAliases(
|
|
584
1186
|
GPT_5_5_PRO_CANONICAL_MODEL,
|
|
585
1187
|
GPT_5_5_PRO_CANONICAL_MODEL,
|
|
586
1188
|
);
|
|
587
|
-
addRequestedModelReasoningAliases(
|
|
588
|
-
GPT_5_5_PRO_RELEASE_MODEL,
|
|
589
|
-
GPT_5_5_PRO_CANONICAL_MODEL,
|
|
590
|
-
);
|
|
591
|
-
addRequestedModelReasoningAliases(
|
|
592
|
-
|
|
1189
|
+
addRequestedModelReasoningAliases(
|
|
1190
|
+
GPT_5_5_PRO_RELEASE_MODEL,
|
|
1191
|
+
GPT_5_5_PRO_CANONICAL_MODEL,
|
|
1192
|
+
);
|
|
1193
|
+
addRequestedModelReasoningAliases(
|
|
1194
|
+
GPT_5_5_PRO_RELEASE_COMPAT_MODEL,
|
|
1195
|
+
GPT_5_5_PRO_CANONICAL_MODEL,
|
|
1196
|
+
);
|
|
1197
|
+
addRequestedModelReasoningAliases("gpt-5.4", "gpt-5.4");
|
|
1198
|
+
addRequestedModelReasoningAliases("gpt-5.4-pro", "gpt-5.4-pro");
|
|
1199
|
+
addRequestedModelReasoningAliases("gpt-5.4-mini", "gpt-5.4-mini");
|
|
1200
|
+
addRequestedModelReasoningAliases("gpt-5.4-nano", "gpt-5.4-nano");
|
|
593
1201
|
addRequestedModelReasoningAliases("gpt-5.2-pro", "gpt-5.2-pro");
|
|
594
|
-
addRequestedModelReasoningAliases("gpt-5-pro",
|
|
1202
|
+
addRequestedModelReasoningAliases("gpt-5-pro", GPT_5_5_PRO_CANONICAL_MODEL);
|
|
595
1203
|
addRequestedModelReasoningAliases("gpt-5.2", "gpt-5.2");
|
|
596
1204
|
addRequestedModelReasoningAliases("gpt-5.1", "gpt-5.1");
|
|
597
|
-
addRequestedModelReasoningAliases("gpt-5",
|
|
1205
|
+
addRequestedModelReasoningAliases("gpt-5", DEFAULT_GENERAL_GPT5_MODEL);
|
|
598
1206
|
addRequestedModelReasoningAliases("gpt-5-mini", "gpt-5-mini");
|
|
599
1207
|
addRequestedModelReasoningAliases("gpt-5-nano", "gpt-5-nano");
|
|
600
1208
|
addRequestedModelReasoningAliases("gpt-5.1-chat-latest", "gpt-5.1");
|
|
601
|
-
addRequestedModelReasoningAliases("gpt-5-chat-latest",
|
|
602
|
-
addRequestedModelReasoningAliases(
|
|
603
|
-
addRequestedModelReasoningAliases("gpt-5.
|
|
604
|
-
addRequestedModelReasoningAliases(
|
|
605
|
-
addRequestedModelReasoningAliases("gpt-5.
|
|
606
|
-
addRequestedModelReasoningAliases("gpt-5.
|
|
607
|
-
|
|
608
|
-
addRequestedModelReasoningAliases("
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
addRequestedModelReasoningAliases("gpt-5
|
|
612
|
-
|
|
613
|
-
addRequestedModelReasoningAliases("gpt-5-codex-mini", "gpt-5.1-codex-mini");
|
|
614
|
-
addRequestedModelReasoningAliases("gpt-5.1-codex-mini", "gpt-5.1-codex-mini");
|
|
1209
|
+
addRequestedModelReasoningAliases("gpt-5-chat-latest", DEFAULT_GENERAL_GPT5_MODEL);
|
|
1210
|
+
addRequestedModelReasoningAliases(CURRENT_CODEX_MODEL, CURRENT_CODEX_MODEL);
|
|
1211
|
+
addRequestedModelReasoningAliases("gpt-5.3-codex-spark", CURRENT_CODEX_MODEL);
|
|
1212
|
+
addRequestedModelReasoningAliases(LEGACY_CODEX_MODEL, CURRENT_CODEX_MODEL);
|
|
1213
|
+
addRequestedModelReasoningAliases("gpt-5.2-codex", CURRENT_CODEX_MODEL);
|
|
1214
|
+
addRequestedModelReasoningAliases("gpt-5.1-codex", CURRENT_CODEX_MODEL);
|
|
1215
|
+
addRequestedModelAlias("gpt_5_codex", CURRENT_CODEX_MODEL);
|
|
1216
|
+
addRequestedModelReasoningAliases("codex-max", CURRENT_CODEX_MODEL);
|
|
1217
|
+
addRequestedModelReasoningAliases("gpt-5.1-codex-max", CURRENT_CODEX_MODEL);
|
|
1218
|
+
addRequestedModelAlias("codex-mini-latest", CURRENT_CODEX_MODEL);
|
|
1219
|
+
addRequestedModelReasoningAliases("gpt-5-codex-mini", CURRENT_CODEX_MODEL);
|
|
1220
|
+
addRequestedModelReasoningAliases("gpt-5.1-codex-mini", CURRENT_CODEX_MODEL);
|
|
615
1221
|
}
|
|
616
1222
|
|
|
617
1223
|
seedRequestedModelAliases();
|
|
@@ -658,24 +1264,24 @@ function resolveStableGeneralGpt5Variant(variant) {
|
|
|
658
1264
|
|
|
659
1265
|
function resolveCodexRequestedModel(normalized) {
|
|
660
1266
|
if (
|
|
661
|
-
normalized.includes("gpt-5.1-codex-max") ||
|
|
662
|
-
normalized.includes("gpt 5.1 codex max") ||
|
|
663
|
-
normalized.includes("codex-max")
|
|
664
|
-
) {
|
|
665
|
-
return
|
|
666
|
-
}
|
|
1267
|
+
normalized.includes("gpt-5.1-codex-max") ||
|
|
1268
|
+
normalized.includes("gpt 5.1 codex max") ||
|
|
1269
|
+
normalized.includes("codex-max")
|
|
1270
|
+
) {
|
|
1271
|
+
return CURRENT_CODEX_MODEL;
|
|
1272
|
+
}
|
|
667
1273
|
if (
|
|
668
1274
|
normalized.includes("gpt-5.1-codex-mini") ||
|
|
669
1275
|
normalized.includes("gpt 5.1 codex mini") ||
|
|
670
1276
|
normalized.includes("gpt-5-codex-mini") ||
|
|
671
|
-
normalized.includes("gpt 5 codex mini") ||
|
|
672
|
-
normalized.includes("codex-mini-latest")
|
|
673
|
-
) {
|
|
674
|
-
return
|
|
675
|
-
}
|
|
676
|
-
if (
|
|
677
|
-
normalized.includes("gpt-5.3-codex-spark") ||
|
|
678
|
-
normalized.includes("gpt 5.3 codex spark") ||
|
|
1277
|
+
normalized.includes("gpt 5 codex mini") ||
|
|
1278
|
+
normalized.includes("codex-mini-latest")
|
|
1279
|
+
) {
|
|
1280
|
+
return CURRENT_CODEX_MODEL;
|
|
1281
|
+
}
|
|
1282
|
+
if (
|
|
1283
|
+
normalized.includes("gpt-5.3-codex-spark") ||
|
|
1284
|
+
normalized.includes("gpt 5.3 codex spark") ||
|
|
679
1285
|
normalized.includes("gpt-5.3-codex") ||
|
|
680
1286
|
normalized.includes("gpt 5.3 codex") ||
|
|
681
1287
|
normalized.includes("gpt-5.2-codex") ||
|
|
@@ -683,11 +1289,11 @@ function resolveCodexRequestedModel(normalized) {
|
|
|
683
1289
|
normalized.includes("gpt-5.1-codex") ||
|
|
684
1290
|
normalized.includes("gpt 5.1 codex") ||
|
|
685
1291
|
normalized.includes("gpt-5-codex") ||
|
|
686
|
-
normalized.includes("gpt 5 codex") ||
|
|
687
|
-
normalized === "codex"
|
|
688
|
-
) {
|
|
689
|
-
return
|
|
690
|
-
}
|
|
1292
|
+
normalized.includes("gpt 5 codex") ||
|
|
1293
|
+
normalized === "codex"
|
|
1294
|
+
) {
|
|
1295
|
+
return CURRENT_CODEX_MODEL;
|
|
1296
|
+
}
|
|
691
1297
|
|
|
692
1298
|
return "";
|
|
693
1299
|
}
|
|
@@ -773,12 +1379,12 @@ function coerceReasoningEffortForModel(model, effort) {
|
|
|
773
1379
|
}
|
|
774
1380
|
|
|
775
1381
|
function extractRequestedModel(args) {
|
|
776
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
777
|
-
const arg = args[i];
|
|
778
|
-
if (arg === "--model") {
|
|
779
|
-
const next = args[i + 1];
|
|
780
|
-
if (typeof next === "string" && next.trim().length > 0) {
|
|
781
|
-
return next.trim();
|
|
1382
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1383
|
+
const arg = args[i];
|
|
1384
|
+
if (arg === "--model" || arg === "-m") {
|
|
1385
|
+
const next = args[i + 1];
|
|
1386
|
+
if (typeof next === "string" && next.trim().length > 0) {
|
|
1387
|
+
return next.trim();
|
|
782
1388
|
}
|
|
783
1389
|
continue;
|
|
784
1390
|
}
|
|
@@ -860,126 +1466,1421 @@ function rewriteReasoningConfigArgs(rawArgs) {
|
|
|
860
1466
|
requestedModel,
|
|
861
1467
|
)}`;
|
|
862
1468
|
}
|
|
863
|
-
}
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
return {
|
|
1472
|
+
args: nextArgs,
|
|
1473
|
+
requestedModel,
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
function resolveCodexHomeDir(env = process.env) {
|
|
1478
|
+
const override = (env.CODEX_HOME ?? "").trim();
|
|
1479
|
+
if (override.length > 0) return override;
|
|
1480
|
+
if (process.platform === "win32") {
|
|
1481
|
+
const homeDir = resolveWindowsUserHomeDir();
|
|
1482
|
+
if (homeDir) {
|
|
1483
|
+
return join(homeDir, ".codex");
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
return join(env.HOME ?? homedir(), ".codex");
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
function ensureTrailingNewline(value) {
|
|
1490
|
+
return value.endsWith("\n") ? value : `${value}\n`;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
function captureShadowHomeState(filePath, options = {}) {
|
|
1494
|
+
try {
|
|
1495
|
+
if (!existsSync(filePath)) {
|
|
1496
|
+
return { exists: false, content: null };
|
|
1497
|
+
}
|
|
1498
|
+
if (options.rethrowRetryableReadErrors) {
|
|
1499
|
+
maybeThrowSimulatedShadowHomePreflightReadBusyError();
|
|
1500
|
+
}
|
|
1501
|
+
return {
|
|
1502
|
+
exists: true,
|
|
1503
|
+
content: readFileSync(filePath, "utf8"),
|
|
1504
|
+
};
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
if (options.rethrowRetryableReadErrors && isRetryableShadowHomeCleanupError(error)) {
|
|
1507
|
+
throw error;
|
|
1508
|
+
}
|
|
1509
|
+
return { exists: true, content: null, unreadable: true };
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
function shadowHomeStateMatches(left, right) {
|
|
1514
|
+
return (
|
|
1515
|
+
left.exists === right.exists &&
|
|
1516
|
+
left.content === right.content &&
|
|
1517
|
+
Boolean(left.unreadable) === Boolean(right.unreadable)
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
function hashShadowHomeState(state) {
|
|
1522
|
+
if (state.unreadable) {
|
|
1523
|
+
return null;
|
|
1524
|
+
}
|
|
1525
|
+
if (!state.exists) {
|
|
1526
|
+
return "missing";
|
|
1527
|
+
}
|
|
1528
|
+
if (typeof state.content !== "string") {
|
|
1529
|
+
return null;
|
|
1530
|
+
}
|
|
1531
|
+
return `sha256:${createHash("sha256").update(state.content).digest("hex")}`;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
function readShadowHomeSyncState(originalCodexHome) {
|
|
1535
|
+
try {
|
|
1536
|
+
const parsed = JSON.parse(
|
|
1537
|
+
readFileSync(join(originalCodexHome, SHADOW_HOME_SYNC_STATE_FILE), "utf8"),
|
|
1538
|
+
);
|
|
1539
|
+
if (
|
|
1540
|
+
!parsed ||
|
|
1541
|
+
typeof parsed !== "object" ||
|
|
1542
|
+
parsed.version !== 1 ||
|
|
1543
|
+
!parsed.files ||
|
|
1544
|
+
typeof parsed.files !== "object"
|
|
1545
|
+
) {
|
|
1546
|
+
return { version: 1, files: {} };
|
|
1547
|
+
}
|
|
1548
|
+
return parsed;
|
|
1549
|
+
} catch {
|
|
1550
|
+
return { version: 1, files: {} };
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
function rememberShadowHomeSyncState(
|
|
1555
|
+
originalCodexHome,
|
|
1556
|
+
syncState,
|
|
1557
|
+
name,
|
|
1558
|
+
baseState,
|
|
1559
|
+
syncedState,
|
|
1560
|
+
) {
|
|
1561
|
+
const baseHash = hashShadowHomeState(baseState);
|
|
1562
|
+
const syncedHash = hashShadowHomeState(syncedState);
|
|
1563
|
+
if (!baseHash || !syncedHash) {
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
syncState.files[name] = {
|
|
1567
|
+
baseHash,
|
|
1568
|
+
syncedHash,
|
|
1569
|
+
updatedAt: Date.now(),
|
|
1570
|
+
};
|
|
1571
|
+
try {
|
|
1572
|
+
writeOwnerOnlyJsonFileAtomicSync(
|
|
1573
|
+
join(originalCodexHome, SHADOW_HOME_SYNC_STATE_FILE),
|
|
1574
|
+
syncState,
|
|
1575
|
+
);
|
|
1576
|
+
} catch {
|
|
1577
|
+
// Best-effort metadata; failed metadata must not fail auth cleanup.
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
function canRebaseShadowHomeSyncState(syncState, name, baseState, currentState) {
|
|
1582
|
+
const entry = syncState.files?.[name];
|
|
1583
|
+
if (!entry || typeof entry !== "object") {
|
|
1584
|
+
return false;
|
|
1585
|
+
}
|
|
1586
|
+
// Permit a later shadow session to write over an earlier shadow sync from
|
|
1587
|
+
// the same launch snapshot, while still refusing unrelated external edits.
|
|
1588
|
+
return (
|
|
1589
|
+
entry.baseHash === hashShadowHomeState(baseState) &&
|
|
1590
|
+
entry.syncedHash === hashShadowHomeState(currentState)
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
function readShadowHomeSyncLockOwnerPid(lockPath) {
|
|
1595
|
+
try {
|
|
1596
|
+
const rawOwner = JSON.parse(readFileSync(join(lockPath, "owner.json"), "utf8"));
|
|
1597
|
+
const pid = Number(rawOwner?.pid);
|
|
1598
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
1599
|
+
} catch {
|
|
1600
|
+
return null;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
function isShadowHomeSyncLockOldEnoughToSteal(lockPath) {
|
|
1605
|
+
try {
|
|
1606
|
+
const stats = statSync(lockPath);
|
|
1607
|
+
const newestTimestamp = Math.max(stats.mtimeMs, stats.ctimeMs);
|
|
1608
|
+
return Date.now() - newestTimestamp >= SHADOW_HOME_ORPHAN_LOCK_STALE_AGE_MS;
|
|
1609
|
+
} catch {
|
|
1610
|
+
return true;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function removeStaleShadowHomeSyncLock(lockPath) {
|
|
1615
|
+
const ownerPid = readShadowHomeSyncLockOwnerPid(lockPath);
|
|
1616
|
+
if (ownerPid !== null && isProcessAlive(ownerPid)) {
|
|
1617
|
+
return false;
|
|
1618
|
+
}
|
|
1619
|
+
if (ownerPid === null && !isShadowHomeSyncLockOldEnoughToSteal(lockPath)) {
|
|
1620
|
+
return false;
|
|
1621
|
+
}
|
|
1622
|
+
try {
|
|
1623
|
+
removeDirectoryWithRetry(lockPath);
|
|
1624
|
+
if (shadowHomeSyncLockRecreateStaleCount > 0) {
|
|
1625
|
+
shadowHomeSyncLockRecreateStaleCount -= 1;
|
|
1626
|
+
mkdirSync(lockPath, { recursive: true });
|
|
1627
|
+
writeShadowHomeSyncLockOwner(lockPath, {
|
|
1628
|
+
pid: 2_147_483_647,
|
|
1629
|
+
createdAt: 1,
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
return true;
|
|
1633
|
+
} catch {
|
|
1634
|
+
return false;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
function writeShadowHomeSyncLockOwner(lockPath, owner) {
|
|
1639
|
+
const ownerPath = join(lockPath, "owner.json");
|
|
1640
|
+
maybeThrowSimulatedShadowHomeSyncLockOwnerWriteError();
|
|
1641
|
+
writeFileSync(ownerPath, `${JSON.stringify(owner)}\n`, {
|
|
1642
|
+
encoding: "utf8",
|
|
1643
|
+
mode: 0o600,
|
|
1644
|
+
});
|
|
1645
|
+
try {
|
|
1646
|
+
chmodSync(ownerPath, 0o600);
|
|
1647
|
+
} catch {
|
|
1648
|
+
// Best-effort only; permission semantics vary by platform.
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
function writeShadowHomeSyncLockOwnerWithRetry(lockPath, owner) {
|
|
1653
|
+
for (let attempt = 0; attempt <= SHADOW_HOME_CLEANUP_BACKOFF_MS.length; attempt += 1) {
|
|
1654
|
+
try {
|
|
1655
|
+
writeShadowHomeSyncLockOwner(lockPath, owner);
|
|
1656
|
+
return;
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
if (
|
|
1659
|
+
!isRetryableShadowHomeCleanupError(error) ||
|
|
1660
|
+
attempt === SHADOW_HOME_CLEANUP_BACKOFF_MS.length
|
|
1661
|
+
) {
|
|
1662
|
+
throw error;
|
|
1663
|
+
}
|
|
1664
|
+
sleepSync(SHADOW_HOME_CLEANUP_BACKOFF_MS[attempt]);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
function acquireShadowHomeSyncLock(originalCodexHome) {
|
|
1670
|
+
const lockPath = join(originalCodexHome, SHADOW_HOME_SYNC_LOCK_DIR);
|
|
1671
|
+
mkdirSync(originalCodexHome, { recursive: true });
|
|
1672
|
+
const maxStaleRecoveries = SHADOW_HOME_CLEANUP_BACKOFF_MS.length + 1;
|
|
1673
|
+
let staleRecoveries = 0;
|
|
1674
|
+
let attempt = 0;
|
|
1675
|
+
const deadline = Date.now() + SHADOW_HOME_SYNC_LOCK_WAIT_TIMEOUT_MS;
|
|
1676
|
+
while (true) {
|
|
1677
|
+
try {
|
|
1678
|
+
mkdirSync(lockPath);
|
|
1679
|
+
try {
|
|
1680
|
+
writeShadowHomeSyncLockOwnerWithRetry(lockPath, {
|
|
1681
|
+
pid: process.pid,
|
|
1682
|
+
createdAt: Date.now(),
|
|
1683
|
+
});
|
|
1684
|
+
} catch (error) {
|
|
1685
|
+
try {
|
|
1686
|
+
removeDirectoryWithRetry(lockPath);
|
|
1687
|
+
} catch {
|
|
1688
|
+
// Preserve the owner write failure while avoiding orphaned locks when possible.
|
|
1689
|
+
}
|
|
1690
|
+
throw error;
|
|
1691
|
+
}
|
|
1692
|
+
return () => {
|
|
1693
|
+
try {
|
|
1694
|
+
removeDirectoryWithRetry(lockPath);
|
|
1695
|
+
} catch {
|
|
1696
|
+
// Best-effort lock cleanup only.
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
} catch (error) {
|
|
1700
|
+
const code =
|
|
1701
|
+
error && typeof error === "object" && "code" in error
|
|
1702
|
+
? error.code
|
|
1703
|
+
: undefined;
|
|
1704
|
+
if (code !== "EEXIST") {
|
|
1705
|
+
throw error;
|
|
1706
|
+
}
|
|
1707
|
+
if (
|
|
1708
|
+
staleRecoveries < maxStaleRecoveries &&
|
|
1709
|
+
removeStaleShadowHomeSyncLock(lockPath)
|
|
1710
|
+
) {
|
|
1711
|
+
staleRecoveries += 1;
|
|
1712
|
+
attempt = 0;
|
|
1713
|
+
continue;
|
|
1714
|
+
}
|
|
1715
|
+
const remainingMs = deadline - Date.now();
|
|
1716
|
+
if (remainingMs <= 0) {
|
|
1717
|
+
throw error;
|
|
1718
|
+
}
|
|
1719
|
+
const backoffMs =
|
|
1720
|
+
SHADOW_HOME_CLEANUP_BACKOFF_MS[
|
|
1721
|
+
Math.min(attempt, SHADOW_HOME_CLEANUP_BACKOFF_MS.length - 1)
|
|
1722
|
+
] ?? SHADOW_HOME_CLEANUP_BACKOFF_MS[0];
|
|
1723
|
+
sleepSync(Math.min(backoffMs, remainingMs));
|
|
1724
|
+
attempt += 1;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
function syncShadowHomeStateFile(
|
|
1730
|
+
sourcePath,
|
|
1731
|
+
destinationPath,
|
|
1732
|
+
expectedDestinationState,
|
|
1733
|
+
) {
|
|
1734
|
+
const tempPath = join(
|
|
1735
|
+
dirname(destinationPath),
|
|
1736
|
+
`.${basename(destinationPath)}.codex-multi-auth-sync-${process.pid}.tmp`,
|
|
1737
|
+
);
|
|
1738
|
+
try {
|
|
1739
|
+
mkdirSync(dirname(destinationPath), { recursive: true });
|
|
1740
|
+
copyFileSync(sourcePath, tempPath);
|
|
1741
|
+
renameFileWithRetry(tempPath, destinationPath, expectedDestinationState);
|
|
1742
|
+
} catch (error) {
|
|
1743
|
+
try {
|
|
1744
|
+
rmSync(tempPath, { force: true });
|
|
1745
|
+
} catch {
|
|
1746
|
+
// Best-effort cleanup only.
|
|
1747
|
+
}
|
|
1748
|
+
throw error;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
function syncShadowHomeStateFileBestEffort(
|
|
1753
|
+
sourcePath,
|
|
1754
|
+
destinationPath,
|
|
1755
|
+
expectedDestinationState,
|
|
1756
|
+
tightenFile,
|
|
1757
|
+
) {
|
|
1758
|
+
try {
|
|
1759
|
+
syncShadowHomeStateFile(
|
|
1760
|
+
sourcePath,
|
|
1761
|
+
destinationPath,
|
|
1762
|
+
expectedDestinationState,
|
|
1763
|
+
);
|
|
1764
|
+
tightenFile(destinationPath);
|
|
1765
|
+
return true;
|
|
1766
|
+
} catch {
|
|
1767
|
+
// Best-effort sync-back: keep attempting sibling files after Windows locks.
|
|
1768
|
+
return false;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
function isDirectoryLike(path) {
|
|
1773
|
+
try {
|
|
1774
|
+
return statSync(path).isDirectory();
|
|
1775
|
+
} catch {
|
|
1776
|
+
return false;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
function isFileLike(path) {
|
|
1781
|
+
try {
|
|
1782
|
+
return statSync(path).isFile();
|
|
1783
|
+
} catch {
|
|
1784
|
+
return false;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
function mirrorDirectoryIntoShadowHome(sourcePath, destinationPath) {
|
|
1789
|
+
try {
|
|
1790
|
+
if ((process.env.CODEX_MULTI_AUTH_TEST_FORCE_SHADOW_DIR_COPY ?? "").trim() === "1") {
|
|
1791
|
+
throw new Error("simulated directory link failure");
|
|
1792
|
+
}
|
|
1793
|
+
symlinkSync(
|
|
1794
|
+
sourcePath,
|
|
1795
|
+
destinationPath,
|
|
1796
|
+
process.platform === "win32" ? "junction" : "dir",
|
|
1797
|
+
);
|
|
1798
|
+
return "linked";
|
|
1799
|
+
} catch {
|
|
1800
|
+
// Fall back to a copy when links are unavailable. Directory links are
|
|
1801
|
+
// preferred because they keep sessions, plugins, and skills live.
|
|
1802
|
+
}
|
|
1803
|
+
cpSync(sourcePath, destinationPath, {
|
|
1804
|
+
recursive: true,
|
|
1805
|
+
dereference: false,
|
|
1806
|
+
});
|
|
1807
|
+
return "copied";
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
function linkFileIntoShadowHome(sourcePath, destinationPath) {
|
|
1811
|
+
try {
|
|
1812
|
+
symlinkSync(sourcePath, destinationPath, "file");
|
|
1813
|
+
return true;
|
|
1814
|
+
} catch {
|
|
1815
|
+
// File symlinks keep SQLite/cache-style root files realtime when allowed.
|
|
1816
|
+
}
|
|
1817
|
+
try {
|
|
1818
|
+
linkSync(sourcePath, destinationPath);
|
|
1819
|
+
return true;
|
|
1820
|
+
} catch {
|
|
1821
|
+
// Hard links cover platforms where file symlinks require extra privileges.
|
|
1822
|
+
}
|
|
1823
|
+
return false;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
function mirrorFileIntoShadowHome(sourcePath, destinationPath, tightenFile) {
|
|
1827
|
+
if (linkFileIntoShadowHome(sourcePath, destinationPath)) {
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
copyFileSync(sourcePath, destinationPath);
|
|
1831
|
+
tightenFile(destinationPath);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
function collectShadowHomeSyncFileNames(shadowCodexHome, syncFileNames) {
|
|
1835
|
+
try {
|
|
1836
|
+
for (const entry of readdirSync(shadowCodexHome, { withFileTypes: true })) {
|
|
1837
|
+
const name = entry.name;
|
|
1838
|
+
if (
|
|
1839
|
+
name === SHADOW_HOME_CONFIG_FILE ||
|
|
1840
|
+
name === SHADOW_HOME_SYNC_STATE_FILE ||
|
|
1841
|
+
syncFileNames.has(name)
|
|
1842
|
+
) {
|
|
1843
|
+
continue;
|
|
1844
|
+
}
|
|
1845
|
+
const shadowPath = join(shadowCodexHome, name);
|
|
1846
|
+
let fileLike = entry.isFile();
|
|
1847
|
+
if (entry.isSymbolicLink()) {
|
|
1848
|
+
fileLike = isFileLike(shadowPath);
|
|
1849
|
+
}
|
|
1850
|
+
if (fileLike) {
|
|
1851
|
+
syncFileNames.add(name);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
} catch {
|
|
1855
|
+
// Best-effort; cleanup still syncs the known state files.
|
|
1856
|
+
}
|
|
1857
|
+
return syncFileNames;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
function syncCopiedShadowHomeDirectories(originalCodexHome, shadowCodexHome, names) {
|
|
1861
|
+
for (const name of names) {
|
|
1862
|
+
const shadowPath = join(shadowCodexHome, name);
|
|
1863
|
+
if (!isDirectoryLike(shadowPath)) {
|
|
1864
|
+
continue;
|
|
1865
|
+
}
|
|
1866
|
+
try {
|
|
1867
|
+
cpSync(shadowPath, join(originalCodexHome, name), {
|
|
1868
|
+
recursive: true,
|
|
1869
|
+
dereference: false,
|
|
1870
|
+
force: true,
|
|
1871
|
+
});
|
|
1872
|
+
} catch {
|
|
1873
|
+
// Best-effort sync-back; sibling directories and state files still run.
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
function syncShadowHomeAuthBundle(
|
|
1879
|
+
originalCodexHome,
|
|
1880
|
+
shadowCodexHome,
|
|
1881
|
+
originalFileStates,
|
|
1882
|
+
tightenFile,
|
|
1883
|
+
skipSyncBackNames = new Set(),
|
|
1884
|
+
) {
|
|
1885
|
+
const syncState = readShadowHomeSyncState(originalCodexHome);
|
|
1886
|
+
for (const name of SHADOW_HOME_STATE_FILES) {
|
|
1887
|
+
if (skipSyncBackNames.has(name)) {
|
|
1888
|
+
continue;
|
|
1889
|
+
}
|
|
1890
|
+
const shadowPath = join(shadowCodexHome, name);
|
|
1891
|
+
const shadowState = captureShadowHomeState(shadowPath);
|
|
1892
|
+
if (!shadowState.exists || shadowState.unreadable) {
|
|
1893
|
+
continue;
|
|
1894
|
+
}
|
|
1895
|
+
const originalPath = join(originalCodexHome, name);
|
|
1896
|
+
const originalSnapshot =
|
|
1897
|
+
originalFileStates.get(name) ?? { exists: false, content: null };
|
|
1898
|
+
const currentOriginalState = captureShadowHomeState(originalPath);
|
|
1899
|
+
let expectedDestinationState = originalSnapshot;
|
|
1900
|
+
if (!shadowHomeStateMatches(currentOriginalState, originalSnapshot)) {
|
|
1901
|
+
if (
|
|
1902
|
+
!canRebaseShadowHomeSyncState(
|
|
1903
|
+
syncState,
|
|
1904
|
+
name,
|
|
1905
|
+
originalSnapshot,
|
|
1906
|
+
currentOriginalState,
|
|
1907
|
+
)
|
|
1908
|
+
) {
|
|
1909
|
+
continue;
|
|
1910
|
+
}
|
|
1911
|
+
expectedDestinationState = currentOriginalState;
|
|
1912
|
+
}
|
|
1913
|
+
if (
|
|
1914
|
+
expectedDestinationState.unreadable ||
|
|
1915
|
+
shadowHomeStateMatches(shadowState, expectedDestinationState)
|
|
1916
|
+
) {
|
|
1917
|
+
continue;
|
|
1918
|
+
}
|
|
1919
|
+
if (syncShadowHomeStateFileBestEffort(
|
|
1920
|
+
shadowPath,
|
|
1921
|
+
originalPath,
|
|
1922
|
+
expectedDestinationState,
|
|
1923
|
+
tightenFile,
|
|
1924
|
+
)) {
|
|
1925
|
+
rememberShadowHomeSyncState(
|
|
1926
|
+
originalCodexHome,
|
|
1927
|
+
syncState,
|
|
1928
|
+
name,
|
|
1929
|
+
originalSnapshot,
|
|
1930
|
+
shadowState,
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
function syncAdditionalShadowHomeFiles(
|
|
1937
|
+
originalCodexHome,
|
|
1938
|
+
shadowCodexHome,
|
|
1939
|
+
names,
|
|
1940
|
+
originalFileStates,
|
|
1941
|
+
tightenFile,
|
|
1942
|
+
skipSyncBackNames = new Set(),
|
|
1943
|
+
) {
|
|
1944
|
+
for (const name of names) {
|
|
1945
|
+
if (SHADOW_HOME_STATE_FILE_SET.has(name) || skipSyncBackNames.has(name)) {
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
const shadowPath = join(shadowCodexHome, name);
|
|
1949
|
+
const shadowState = captureShadowHomeState(shadowPath);
|
|
1950
|
+
if (!shadowState.exists || shadowState.unreadable) {
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
const originalPath = join(originalCodexHome, name);
|
|
1955
|
+
const originalSnapshot =
|
|
1956
|
+
originalFileStates.get(name) ?? { exists: false, content: null };
|
|
1957
|
+
const currentOriginalState = captureShadowHomeState(originalPath);
|
|
1958
|
+
if (!shadowHomeStateMatches(currentOriginalState, originalSnapshot)) {
|
|
1959
|
+
continue;
|
|
1960
|
+
}
|
|
1961
|
+
if (shadowHomeStateMatches(shadowState, originalSnapshot)) {
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
syncShadowHomeStateFileBestEffort(
|
|
1965
|
+
shadowPath,
|
|
1966
|
+
originalPath,
|
|
1967
|
+
originalSnapshot,
|
|
1968
|
+
tightenFile,
|
|
1969
|
+
);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
function createShadowHomeMirror(
|
|
1974
|
+
originalCodexHome,
|
|
1975
|
+
shadowCodexHome,
|
|
1976
|
+
tightenFile,
|
|
1977
|
+
options = {},
|
|
1978
|
+
) {
|
|
1979
|
+
const syncFileNames = new Set(SHADOW_HOME_STATE_FILES);
|
|
1980
|
+
const skipSyncBackNames = new Set(options.skipSyncBackNames ?? []);
|
|
1981
|
+
const originalFileStates = new Map();
|
|
1982
|
+
const copiedDirectoryNames = new Set();
|
|
1983
|
+
const rememberSyncFile = (name) => {
|
|
1984
|
+
if (!originalFileStates.has(name)) {
|
|
1985
|
+
originalFileStates.set(
|
|
1986
|
+
name,
|
|
1987
|
+
captureShadowHomeState(join(originalCodexHome, name)),
|
|
1988
|
+
);
|
|
1989
|
+
}
|
|
1990
|
+
syncFileNames.add(name);
|
|
1991
|
+
};
|
|
1992
|
+
for (const name of SHADOW_HOME_STATE_FILES) {
|
|
1993
|
+
rememberSyncFile(name);
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
if (existsSync(originalCodexHome)) {
|
|
1997
|
+
for (const entry of readdirSync(originalCodexHome, { withFileTypes: true })) {
|
|
1998
|
+
const name = entry.name;
|
|
1999
|
+
if (
|
|
2000
|
+
name === SHADOW_HOME_CONFIG_FILE ||
|
|
2001
|
+
name === SHADOW_HOME_SYNC_STATE_FILE ||
|
|
2002
|
+
name === SHADOW_HOME_SYNC_LOCK_DIR
|
|
2003
|
+
) {
|
|
2004
|
+
continue;
|
|
2005
|
+
}
|
|
2006
|
+
const isKnownStateFile = SHADOW_HOME_STATE_FILE_SET.has(name);
|
|
2007
|
+
const sourcePath = join(originalCodexHome, name);
|
|
2008
|
+
const destinationPath = join(shadowCodexHome, name);
|
|
2009
|
+
if (existsSync(destinationPath)) {
|
|
2010
|
+
continue;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
let directoryLike = entry.isDirectory();
|
|
2014
|
+
let fileLike = entry.isFile();
|
|
2015
|
+
if (entry.isSymbolicLink()) {
|
|
2016
|
+
directoryLike = isDirectoryLike(sourcePath);
|
|
2017
|
+
fileLike = !directoryLike && isFileLike(sourcePath);
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
try {
|
|
2021
|
+
if (isKnownStateFile && !fileLike) {
|
|
2022
|
+
throw new Error(`Expected ${name} to be a file`);
|
|
2023
|
+
}
|
|
2024
|
+
if (directoryLike) {
|
|
2025
|
+
if (mirrorDirectoryIntoShadowHome(sourcePath, destinationPath) === "copied") {
|
|
2026
|
+
copiedDirectoryNames.add(name);
|
|
2027
|
+
}
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
if (fileLike) {
|
|
2031
|
+
rememberSyncFile(name);
|
|
2032
|
+
if (isKnownStateFile) {
|
|
2033
|
+
copyFileSync(sourcePath, destinationPath);
|
|
2034
|
+
tightenFile(destinationPath);
|
|
2035
|
+
} else {
|
|
2036
|
+
mirrorFileIntoShadowHome(sourcePath, destinationPath, tightenFile);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
} catch (error) {
|
|
2040
|
+
if (isKnownStateFile) {
|
|
2041
|
+
throw error;
|
|
2042
|
+
}
|
|
2043
|
+
// A missing or locked optional home entry should not block runtime
|
|
2044
|
+
// launch; auth/config files still get handled explicitly.
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
return () => {
|
|
2050
|
+
let releaseLock = () => {};
|
|
2051
|
+
try {
|
|
2052
|
+
const names = collectShadowHomeSyncFileNames(shadowCodexHome, syncFileNames);
|
|
2053
|
+
releaseLock = acquireShadowHomeSyncLock(originalCodexHome);
|
|
2054
|
+
syncShadowHomeAuthBundle(
|
|
2055
|
+
originalCodexHome,
|
|
2056
|
+
shadowCodexHome,
|
|
2057
|
+
originalFileStates,
|
|
2058
|
+
tightenFile,
|
|
2059
|
+
skipSyncBackNames,
|
|
2060
|
+
);
|
|
2061
|
+
syncCopiedShadowHomeDirectories(
|
|
2062
|
+
originalCodexHome,
|
|
2063
|
+
shadowCodexHome,
|
|
2064
|
+
copiedDirectoryNames,
|
|
2065
|
+
);
|
|
2066
|
+
syncAdditionalShadowHomeFiles(
|
|
2067
|
+
originalCodexHome,
|
|
2068
|
+
shadowCodexHome,
|
|
2069
|
+
names,
|
|
2070
|
+
originalFileStates,
|
|
2071
|
+
tightenFile,
|
|
2072
|
+
skipSyncBackNames,
|
|
2073
|
+
);
|
|
2074
|
+
} catch {
|
|
2075
|
+
// Best-effort only; runtime auth refreshes should not fail cleanup.
|
|
2076
|
+
} finally {
|
|
2077
|
+
releaseLock();
|
|
2078
|
+
}
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
function rewriteConfigTomlReasoningEffort(rawConfig, requestedModel) {
|
|
2083
|
+
const lineEnding = rawConfig.includes("\r\n") ? "\r\n" : "\n";
|
|
2084
|
+
let changed = false;
|
|
2085
|
+
const nextLines = rawConfig.split(/\r?\n/).map((line) => {
|
|
2086
|
+
const quotedMatch = line.match(
|
|
2087
|
+
/^(\s*model_reasoning_effort\s*=\s*)(["'])([^"']+)(\2.*)$/,
|
|
2088
|
+
);
|
|
2089
|
+
const bareMatch = quotedMatch
|
|
2090
|
+
? null
|
|
2091
|
+
: line.match(
|
|
2092
|
+
/^(\s*model_reasoning_effort\s*=\s*)([^\s#]+)(\s*(?:#.*)?)$/,
|
|
2093
|
+
);
|
|
2094
|
+
if (!quotedMatch && !bareMatch) return line;
|
|
2095
|
+
|
|
2096
|
+
const prefix = quotedMatch?.[1] ?? bareMatch?.[1] ?? "";
|
|
2097
|
+
const openingQuote = quotedMatch?.[2] ?? "";
|
|
2098
|
+
const currentEffort = quotedMatch?.[3] ?? bareMatch?.[2] ?? "";
|
|
2099
|
+
const suffix = quotedMatch?.[4] ?? bareMatch?.[3] ?? "";
|
|
2100
|
+
const coercedEffort = coerceReasoningEffortForModel(
|
|
2101
|
+
requestedModel,
|
|
2102
|
+
currentEffort,
|
|
2103
|
+
);
|
|
2104
|
+
if (coercedEffort === currentEffort) {
|
|
2105
|
+
return line;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
changed = true;
|
|
2109
|
+
return quotedMatch
|
|
2110
|
+
? `${prefix}${openingQuote}${coercedEffort}${suffix}`
|
|
2111
|
+
: `${prefix}${coercedEffort}${suffix}`;
|
|
2112
|
+
});
|
|
2113
|
+
|
|
2114
|
+
if (!changed) {
|
|
2115
|
+
return rawConfig;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
return ensureTrailingNewline(nextLines.join(lineEnding));
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
function resolveOriginalMultiAuthDir(env) {
|
|
2122
|
+
const explicit = (env.CODEX_MULTI_AUTH_DIR ?? "").trim();
|
|
2123
|
+
if (explicit.length > 0) {
|
|
2124
|
+
return explicit;
|
|
2125
|
+
}
|
|
2126
|
+
return undefined;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
function parseRuntimeRotationProxyEnv(value) {
|
|
2130
|
+
if (value === undefined) return undefined;
|
|
2131
|
+
const normalized = value.trim().toLowerCase();
|
|
2132
|
+
if (normalized.length === 0) return undefined;
|
|
2133
|
+
if (normalized === "1" || normalized === "true" || normalized === "yes") {
|
|
2134
|
+
return true;
|
|
2135
|
+
}
|
|
2136
|
+
if (normalized === "0" || normalized === "false" || normalized === "no") {
|
|
2137
|
+
return false;
|
|
2138
|
+
}
|
|
2139
|
+
if (!warnedInvalidRuntimeRotationProxyEnv) {
|
|
2140
|
+
warnedInvalidRuntimeRotationProxyEnv = true;
|
|
2141
|
+
console.error(
|
|
2142
|
+
"codex-multi-auth: ignoring invalid CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY value. Expected 0/1, true/false, or yes/no.",
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2145
|
+
return undefined;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
async function isRuntimeRotationProxyEnabled(rawArgs, baseEnv = process.env) {
|
|
2149
|
+
if ((baseEnv.CODEX_MULTI_AUTH_BYPASS ?? "").trim() === "1") {
|
|
2150
|
+
return false;
|
|
2151
|
+
}
|
|
2152
|
+
if (!shouldUseRuntimeRoutingForForwardedArgs(rawArgs)) {
|
|
2153
|
+
return false;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
const envOverride = parseRuntimeRotationProxyEnv(
|
|
2157
|
+
baseEnv.CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY,
|
|
2158
|
+
);
|
|
2159
|
+
if (envOverride !== undefined) {
|
|
2160
|
+
return envOverride;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
const configModule = await loadRuntimeRotationConfigModule();
|
|
2164
|
+
if (!configModule) {
|
|
2165
|
+
return false;
|
|
2166
|
+
}
|
|
2167
|
+
const pluginConfig = configModule.loadPluginConfig();
|
|
2168
|
+
return configModule.getCodexRuntimeRotationProxy(pluginConfig) === true;
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
function createRuntimeRotationProxyClientApiKey() {
|
|
2172
|
+
return randomBytes(32).toString("hex");
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
function omitRuntimeRotationShadowHomeStateFiles(shadowCodexHome) {
|
|
2176
|
+
for (const name of RUNTIME_ROTATION_SHADOW_HOME_OMIT_STATE_FILES) {
|
|
2177
|
+
const targetPath = join(shadowCodexHome, name);
|
|
2178
|
+
try {
|
|
2179
|
+
if (!existsSync(targetPath)) {
|
|
2180
|
+
continue;
|
|
2181
|
+
}
|
|
2182
|
+
if (isDirectoryLike(targetPath)) {
|
|
2183
|
+
removeDirectoryWithRetry(targetPath);
|
|
2184
|
+
} else {
|
|
2185
|
+
rmSync(targetPath, { force: true });
|
|
2186
|
+
}
|
|
2187
|
+
} catch {
|
|
2188
|
+
// Best-effort: stale official auth state should not block runtime rotation.
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
function resolveRuntimeRotationProxyOriginalCodexHome(baseEnv) {
|
|
2194
|
+
const override = (baseEnv[APP_RUNTIME_HELPER_REAL_CODEX_HOME_ENV] ?? "").trim();
|
|
2195
|
+
return override || resolveCodexHomeDir(baseEnv);
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
function createRuntimeRotationProxyCodexHome(
|
|
2199
|
+
baseEnv,
|
|
2200
|
+
proxyBaseUrl,
|
|
2201
|
+
clientApiKey,
|
|
2202
|
+
configTomlModule,
|
|
2203
|
+
) {
|
|
2204
|
+
const originalCodexHome = resolveRuntimeRotationProxyOriginalCodexHome(baseEnv);
|
|
2205
|
+
const shadowCodexHome = mkdtempSync(join(tmpdir(), "codex-multi-auth-runtime-home-"));
|
|
2206
|
+
let syncShadowHomeStateBack = () => {};
|
|
2207
|
+
const cleanup = () => {
|
|
2208
|
+
try {
|
|
2209
|
+
removeDirectoryWithRetry(shadowCodexHome);
|
|
2210
|
+
} catch {
|
|
2211
|
+
// Best-effort cleanup only.
|
|
2212
|
+
}
|
|
2213
|
+
};
|
|
2214
|
+
const tightenShadowHomePermissions = (path) => {
|
|
2215
|
+
try {
|
|
2216
|
+
chmodSync(path, 0o600);
|
|
2217
|
+
} catch {
|
|
2218
|
+
// Best-effort only; permission semantics vary by platform.
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
|
|
2222
|
+
try {
|
|
2223
|
+
syncShadowHomeStateBack = createShadowHomeMirror(
|
|
2224
|
+
originalCodexHome,
|
|
2225
|
+
shadowCodexHome,
|
|
2226
|
+
tightenShadowHomePermissions,
|
|
2227
|
+
{
|
|
2228
|
+
skipSyncBackNames: RUNTIME_ROTATION_SHADOW_HOME_OMIT_STATE_FILES,
|
|
2229
|
+
},
|
|
2230
|
+
);
|
|
2231
|
+
omitRuntimeRotationShadowHomeStateFiles(shadowCodexHome);
|
|
2232
|
+
const originalConfigPath = join(originalCodexHome, "config.toml");
|
|
2233
|
+
const rawConfig = existsSync(originalConfigPath)
|
|
2234
|
+
? readFileSync(originalConfigPath, "utf8")
|
|
2235
|
+
: "";
|
|
2236
|
+
const runtimeConfig = configTomlModule.rewriteConfigTomlForRuntimeRotationProvider(
|
|
2237
|
+
rawConfig,
|
|
2238
|
+
proxyBaseUrl,
|
|
2239
|
+
clientApiKey,
|
|
2240
|
+
);
|
|
2241
|
+
const runtimeConfigPath = join(shadowCodexHome, "config.toml");
|
|
2242
|
+
writeFileSync(runtimeConfigPath, runtimeConfig, "utf8");
|
|
2243
|
+
tightenShadowHomePermissions(runtimeConfigPath);
|
|
2244
|
+
} catch (error) {
|
|
2245
|
+
cleanup();
|
|
2246
|
+
throw error;
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
const forwardedEnv = {
|
|
2250
|
+
...baseEnv,
|
|
2251
|
+
CODEX_HOME: shadowCodexHome,
|
|
2252
|
+
OPENAI_API_KEY: clientApiKey,
|
|
2253
|
+
};
|
|
2254
|
+
const originalMultiAuthDir = resolveOriginalMultiAuthDir(baseEnv);
|
|
2255
|
+
if (originalMultiAuthDir) {
|
|
2256
|
+
forwardedEnv.CODEX_MULTI_AUTH_DIR = originalMultiAuthDir;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
return {
|
|
2260
|
+
env: forwardedEnv,
|
|
2261
|
+
cleanup: () => {
|
|
2262
|
+
syncShadowHomeStateBack();
|
|
2263
|
+
cleanup();
|
|
2264
|
+
},
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
function appendNodeImportOption(nodeOptions, preloadPath) {
|
|
2269
|
+
const importOption = `--import=${pathToFileURL(preloadPath).href}`;
|
|
2270
|
+
const trimmed = (nodeOptions ?? "").trim();
|
|
2271
|
+
return trimmed.length > 0 ? `${trimmed} ${importOption}` : importOption;
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
function createRuntimeRotationAppServerPreloadSource(wrapperScriptPath) {
|
|
2275
|
+
return [
|
|
2276
|
+
'import { spawn } from "node:child_process";',
|
|
2277
|
+
'import { basename } from "node:path";',
|
|
2278
|
+
'import process from "node:process";',
|
|
2279
|
+
"",
|
|
2280
|
+
`const wrapperScriptPath = ${JSON.stringify(wrapperScriptPath)};`,
|
|
2281
|
+
`const accountLabelEnv = ${JSON.stringify(APP_SERVER_ACCOUNT_LABEL_ENV)};`,
|
|
2282
|
+
"const rawArgs = process.argv.slice(1);",
|
|
2283
|
+
"const firstArg = rawArgs[0] ?? \"\";",
|
|
2284
|
+
'if (basename(firstArg).toLowerCase() === "app-server") {',
|
|
2285
|
+
' const args = ["app-server", ...rawArgs.slice(1)];',
|
|
2286
|
+
" const env = {",
|
|
2287
|
+
" ...process.env,",
|
|
2288
|
+
' CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY: "0",',
|
|
2289
|
+
" [accountLabelEnv]: \"1\",",
|
|
2290
|
+
" };",
|
|
2291
|
+
" const child = spawn(process.execPath, [wrapperScriptPath, ...args], {",
|
|
2292
|
+
" stdio: \"inherit\",",
|
|
2293
|
+
" env,",
|
|
2294
|
+
" });",
|
|
2295
|
+
" child.once(\"error\", (error) => {",
|
|
2296
|
+
' console.error(`codex-multi-auth app-server shim failed: ${error instanceof Error ? error.message : String(error)}`);',
|
|
2297
|
+
" process.exit(1);",
|
|
2298
|
+
" });",
|
|
2299
|
+
" child.once(\"exit\", (code, signal) => {",
|
|
2300
|
+
" if (signal) {",
|
|
2301
|
+
' process.exit(signal === "SIGINT" ? 130 : 1);',
|
|
2302
|
+
" return;",
|
|
2303
|
+
" }",
|
|
2304
|
+
" process.exit(typeof code === \"number\" ? code : 1);",
|
|
2305
|
+
" });",
|
|
2306
|
+
" await new Promise(() => undefined);",
|
|
2307
|
+
"}",
|
|
2308
|
+
"",
|
|
2309
|
+
].join("\n");
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
function sweepStaleRuntimeRotationAppServerShimDirs(shimRootDir) {
|
|
2313
|
+
let entries = [];
|
|
2314
|
+
try {
|
|
2315
|
+
entries = readdirSync(shimRootDir, { withFileTypes: true });
|
|
2316
|
+
} catch {
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
for (const entry of entries) {
|
|
2320
|
+
if (!entry.isDirectory() || !entry.name.startsWith(APP_SERVER_SHIM_HELPER_PREFIX)) {
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2323
|
+
const pidText = entry.name.slice(APP_SERVER_SHIM_HELPER_PREFIX.length);
|
|
2324
|
+
const pid = Number.parseInt(pidText, 10);
|
|
2325
|
+
if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid || isProcessAlive(pid)) {
|
|
2326
|
+
continue;
|
|
2327
|
+
}
|
|
2328
|
+
try {
|
|
2329
|
+
removeDirectoryWithRetry(join(shimRootDir, entry.name));
|
|
2330
|
+
} catch {
|
|
2331
|
+
// Best-effort stale shim cleanup only.
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
function installRuntimeRotationAppServerCliShim(forwardedEnv) {
|
|
2337
|
+
const shadowCodexHome = forwardedEnv.CODEX_HOME;
|
|
2338
|
+
if (!shadowCodexHome) {
|
|
2339
|
+
throw new Error("runtime app-server shim requires CODEX_HOME");
|
|
2340
|
+
}
|
|
2341
|
+
const multiAuthDir =
|
|
2342
|
+
resolveOriginalMultiAuthDir(forwardedEnv) ??
|
|
2343
|
+
join(resolveRuntimeRotationProxyOriginalCodexHome(forwardedEnv), "multi-auth");
|
|
2344
|
+
const shimRootDir = join(multiAuthDir, APP_SERVER_SHIM_DIR_NAME);
|
|
2345
|
+
sweepStaleRuntimeRotationAppServerShimDirs(shimRootDir);
|
|
2346
|
+
const shimDir = join(
|
|
2347
|
+
shimRootDir,
|
|
2348
|
+
`${APP_SERVER_SHIM_HELPER_PREFIX}${process.pid}`,
|
|
2349
|
+
);
|
|
2350
|
+
mkdirSync(shimDir, { recursive: true });
|
|
2351
|
+
const executableName = process.platform === "win32" ? "codex.exe" : "codex";
|
|
2352
|
+
const executablePath = join(shimDir, executableName);
|
|
2353
|
+
const preloadPath = join(shimDir, "codex-multi-auth-app-server-preload.mjs");
|
|
2354
|
+
try {
|
|
2355
|
+
try {
|
|
2356
|
+
rmSync(executablePath, { force: true });
|
|
2357
|
+
} catch {
|
|
2358
|
+
// Best-effort stale shim cleanup only.
|
|
2359
|
+
}
|
|
2360
|
+
try {
|
|
2361
|
+
linkSync(process.execPath, executablePath);
|
|
2362
|
+
} catch {
|
|
2363
|
+
copyFileSync(process.execPath, executablePath);
|
|
2364
|
+
}
|
|
2365
|
+
if (process.platform !== "win32") {
|
|
2366
|
+
chmodSync(executablePath, 0o755);
|
|
2367
|
+
}
|
|
2368
|
+
writeFileSync(
|
|
2369
|
+
preloadPath,
|
|
2370
|
+
createRuntimeRotationAppServerPreloadSource(fileURLToPath(import.meta.url)),
|
|
2371
|
+
{ encoding: "utf8", mode: 0o600 },
|
|
2372
|
+
);
|
|
2373
|
+
try {
|
|
2374
|
+
chmodSync(preloadPath, 0o600);
|
|
2375
|
+
} catch {
|
|
2376
|
+
// Best-effort only; permission semantics vary by platform.
|
|
2377
|
+
}
|
|
2378
|
+
} catch (error) {
|
|
2379
|
+
try {
|
|
2380
|
+
removeDirectoryWithRetry(shimDir);
|
|
2381
|
+
} catch {
|
|
2382
|
+
// Preserve the original installation failure.
|
|
2383
|
+
}
|
|
2384
|
+
throw error;
|
|
2385
|
+
}
|
|
2386
|
+
forwardedEnv.CODEX_CLI_PATH = shimDir;
|
|
2387
|
+
forwardedEnv.NODE_OPTIONS = appendNodeImportOption(
|
|
2388
|
+
forwardedEnv.NODE_OPTIONS,
|
|
2389
|
+
preloadPath,
|
|
2390
|
+
);
|
|
2391
|
+
forwardedEnv.CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY = "0";
|
|
2392
|
+
forwardedEnv[APP_SERVER_ACCOUNT_LABEL_ENV] = "1";
|
|
2393
|
+
return shimDir;
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
function resolveRuntimeRotationAppHelperStatusPath(env = process.env) {
|
|
2397
|
+
const multiAuthDir =
|
|
2398
|
+
resolveOriginalMultiAuthDir(env) ?? join(resolveCodexHomeDir(env), "multi-auth");
|
|
2399
|
+
return join(multiAuthDir, APP_RUNTIME_HELPER_STATUS_FILE);
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
function writeOwnerOnlyJsonFileAtomicSync(targetPath, payload) {
|
|
2403
|
+
const targetDir = dirname(targetPath);
|
|
2404
|
+
mkdirSync(targetDir, { recursive: true });
|
|
2405
|
+
for (let attempt = 0; attempt <= SHADOW_HOME_CLEANUP_BACKOFF_MS.length; attempt += 1) {
|
|
2406
|
+
const tempPath = join(
|
|
2407
|
+
targetDir,
|
|
2408
|
+
[
|
|
2409
|
+
`.${basename(targetPath)}`,
|
|
2410
|
+
String(process.pid),
|
|
2411
|
+
String(Date.now()),
|
|
2412
|
+
randomBytes(4).toString("hex"),
|
|
2413
|
+
"tmp",
|
|
2414
|
+
].join("."),
|
|
2415
|
+
);
|
|
2416
|
+
try {
|
|
2417
|
+
writeFileSync(tempPath, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
2418
|
+
encoding: "utf8",
|
|
2419
|
+
mode: 0o600,
|
|
2420
|
+
});
|
|
2421
|
+
chmodSync(tempPath, 0o600);
|
|
2422
|
+
maybeThrowSimulatedShadowHomeSyncMetadataBusyError(targetPath);
|
|
2423
|
+
renameSync(tempPath, targetPath);
|
|
2424
|
+
chmodSync(targetPath, 0o600);
|
|
2425
|
+
return;
|
|
2426
|
+
} catch (error) {
|
|
2427
|
+
try {
|
|
2428
|
+
rmSync(tempPath, { force: true });
|
|
2429
|
+
} catch {
|
|
2430
|
+
// Preserve the original write failure.
|
|
2431
|
+
}
|
|
2432
|
+
if (
|
|
2433
|
+
isRetryableShadowHomeCleanupError(error) &&
|
|
2434
|
+
attempt < SHADOW_HOME_CLEANUP_BACKOFF_MS.length
|
|
2435
|
+
) {
|
|
2436
|
+
sleepSync(SHADOW_HOME_CLEANUP_BACKOFF_MS[attempt]);
|
|
2437
|
+
continue;
|
|
2438
|
+
}
|
|
2439
|
+
throw error;
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
function resolveRuntimeRotationAppHelperIdleMs(env = process.env) {
|
|
2445
|
+
const parsed = Number.parseInt(
|
|
2446
|
+
env.CODEX_MULTI_AUTH_APP_ROTATION_IDLE_MS ?? "",
|
|
2447
|
+
10,
|
|
2448
|
+
);
|
|
2449
|
+
return Number.isFinite(parsed) && parsed > 0
|
|
2450
|
+
? Math.max(50, parsed)
|
|
2451
|
+
: DEFAULT_APP_RUNTIME_HELPER_IDLE_MS;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
function resolveRuntimeRotationAppHelperOwnerPid(env = process.env) {
|
|
2455
|
+
const parsed = Number.parseInt(env[APP_RUNTIME_HELPER_OWNER_PID_ENV] ?? "", 10);
|
|
2456
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
function isProcessAlive(pid) {
|
|
2460
|
+
try {
|
|
2461
|
+
process.kill(pid, 0);
|
|
2462
|
+
return true;
|
|
2463
|
+
} catch (error) {
|
|
2464
|
+
return error && typeof error === "object" && error.code === "EPERM";
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
function isRuntimeRotationAppHelperOwnerAlive(pid) {
|
|
2469
|
+
return isProcessAlive(pid);
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
function resolveRuntimeRotationAppHelperDetachGraceMs(env = process.env) {
|
|
2473
|
+
const parsed = Number.parseInt(
|
|
2474
|
+
env.CODEX_MULTI_AUTH_APP_ROTATION_DETACH_GRACE_MS ?? "",
|
|
2475
|
+
10,
|
|
2476
|
+
);
|
|
2477
|
+
return Number.isFinite(parsed) && parsed >= 0
|
|
2478
|
+
? parsed
|
|
2479
|
+
: DEFAULT_APP_RUNTIME_HELPER_DETACH_GRACE_MS;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
function pickRuntimeRotationAppHelperEnv(env) {
|
|
2483
|
+
const picked = {
|
|
2484
|
+
CODEX_HOME: env.CODEX_HOME,
|
|
2485
|
+
OPENAI_API_KEY: env.OPENAI_API_KEY,
|
|
2486
|
+
};
|
|
2487
|
+
for (const name of [
|
|
2488
|
+
"CODEX_CLI_PATH",
|
|
2489
|
+
"NODE_OPTIONS",
|
|
2490
|
+
"CODEX_MULTI_AUTH_REAL_CODEX_BIN",
|
|
2491
|
+
"CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY",
|
|
2492
|
+
APP_SERVER_ACCOUNT_LABEL_ENV,
|
|
2493
|
+
]) {
|
|
2494
|
+
if (env[name]) {
|
|
2495
|
+
picked[name] = env[name];
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
if (env.CODEX_MULTI_AUTH_DIR) {
|
|
2499
|
+
picked.CODEX_MULTI_AUTH_DIR = env.CODEX_MULTI_AUTH_DIR;
|
|
2500
|
+
}
|
|
2501
|
+
return picked;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
function writeRuntimeRotationAppHelperStatus(payload, env = process.env) {
|
|
2505
|
+
try {
|
|
2506
|
+
const statusPath = resolveRuntimeRotationAppHelperStatusPath(env);
|
|
2507
|
+
writeOwnerOnlyJsonFileAtomicSync(statusPath, payload);
|
|
2508
|
+
} catch {
|
|
2509
|
+
// Best-effort status only; the helper must not fail because telemetry is unavailable.
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
function createRuntimeRotationAppHelperStatus({
|
|
2514
|
+
proxyServer,
|
|
2515
|
+
startedAt,
|
|
2516
|
+
idleTimeoutMs,
|
|
2517
|
+
lastActivityAt,
|
|
2518
|
+
state,
|
|
2519
|
+
}) {
|
|
2520
|
+
const proxyStatus =
|
|
2521
|
+
typeof proxyServer?.getStatus === "function" ? proxyServer.getStatus() : {};
|
|
2522
|
+
const lastAccountIndex = proxyStatus.lastAccountIndex ?? null;
|
|
2523
|
+
const lastAccountLabel =
|
|
2524
|
+
typeof proxyStatus.lastAccountLabel === "string" &&
|
|
2525
|
+
!proxyStatus.lastAccountLabel.includes("@")
|
|
2526
|
+
? proxyStatus.lastAccountLabel
|
|
2527
|
+
: typeof lastAccountIndex === "number"
|
|
2528
|
+
? `Account ${lastAccountIndex + 1}`
|
|
2529
|
+
: null;
|
|
2530
|
+
return {
|
|
2531
|
+
version: 1,
|
|
2532
|
+
kind: "codex-app-runtime-rotation-helper",
|
|
2533
|
+
state,
|
|
2534
|
+
pid: process.pid,
|
|
2535
|
+
startedAt,
|
|
2536
|
+
updatedAt: Date.now(),
|
|
2537
|
+
baseUrl: proxyServer?.baseUrl ?? null,
|
|
2538
|
+
idleTimeoutMs,
|
|
2539
|
+
idleExpiresAt: lastActivityAt + idleTimeoutMs,
|
|
2540
|
+
totalRequests: proxyStatus.totalRequests ?? 0,
|
|
2541
|
+
upstreamRequests: proxyStatus.upstreamRequests ?? 0,
|
|
2542
|
+
retries: proxyStatus.retries ?? 0,
|
|
2543
|
+
rotations: proxyStatus.rotations ?? 0,
|
|
2544
|
+
lastAccountIndex,
|
|
2545
|
+
lastAccountLabel,
|
|
2546
|
+
lastAccountId: proxyStatus.lastAccountId ?? null,
|
|
2547
|
+
lastAccountUpdatedAt: proxyStatus.lastAccountUpdatedAt ?? null,
|
|
2548
|
+
lastError: proxyStatus.lastError ?? null,
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
async function runRuntimeRotationAppHelper() {
|
|
2553
|
+
let proxyServer = null;
|
|
2554
|
+
let shadowContext = null;
|
|
2555
|
+
let appServerShimDir = null;
|
|
2556
|
+
let statusTimer = null;
|
|
2557
|
+
let closing = false;
|
|
2558
|
+
const startedAt = Date.now();
|
|
2559
|
+
const idleTimeoutMs = resolveRuntimeRotationAppHelperIdleMs();
|
|
2560
|
+
const ownerPid = resolveRuntimeRotationAppHelperOwnerPid();
|
|
2561
|
+
let lastActivityAt = startedAt;
|
|
2562
|
+
let lastRequestCount = 0;
|
|
2563
|
+
|
|
2564
|
+
const publishStatus = (state) => {
|
|
2565
|
+
writeRuntimeRotationAppHelperStatus(
|
|
2566
|
+
createRuntimeRotationAppHelperStatus({
|
|
2567
|
+
proxyServer,
|
|
2568
|
+
startedAt,
|
|
2569
|
+
idleTimeoutMs,
|
|
2570
|
+
lastActivityAt,
|
|
2571
|
+
state,
|
|
2572
|
+
}),
|
|
2573
|
+
);
|
|
2574
|
+
};
|
|
2575
|
+
|
|
2576
|
+
const cleanup = async (state = "stopped") => {
|
|
2577
|
+
if (closing) return;
|
|
2578
|
+
closing = true;
|
|
2579
|
+
if (statusTimer) {
|
|
2580
|
+
clearInterval(statusTimer);
|
|
2581
|
+
}
|
|
2582
|
+
try {
|
|
2583
|
+
if (appServerShimDir) {
|
|
2584
|
+
try {
|
|
2585
|
+
removeDirectoryWithRetry(appServerShimDir);
|
|
2586
|
+
} catch {
|
|
2587
|
+
// Best-effort shim cleanup only.
|
|
2588
|
+
}
|
|
2589
|
+
appServerShimDir = null;
|
|
2590
|
+
}
|
|
2591
|
+
shadowContext?.cleanup?.();
|
|
2592
|
+
} finally {
|
|
2593
|
+
try {
|
|
2594
|
+
await proxyServer?.close?.();
|
|
2595
|
+
} finally {
|
|
2596
|
+
publishStatus(state);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
|
|
2601
|
+
const exitAfterCleanup = (state, exitCode) => {
|
|
2602
|
+
void cleanup(state).finally(() => {
|
|
2603
|
+
process.exit(exitCode);
|
|
2604
|
+
});
|
|
2605
|
+
};
|
|
2606
|
+
|
|
2607
|
+
process.once("SIGINT", () => exitAfterCleanup("stopped", 130));
|
|
2608
|
+
process.once("SIGTERM", () => exitAfterCleanup("stopped", 0));
|
|
2609
|
+
process.once("SIGHUP", () => exitAfterCleanup("stopped", 0));
|
|
2610
|
+
|
|
2611
|
+
try {
|
|
2612
|
+
const proxyModule = await loadRuntimeRotationProxyModule();
|
|
2613
|
+
if (!proxyModule) {
|
|
2614
|
+
throw new Error("runtime rotation proxy module is unavailable");
|
|
2615
|
+
}
|
|
2616
|
+
const configTomlModule = await loadRuntimeConfigTomlModule();
|
|
2617
|
+
if (!configTomlModule) {
|
|
2618
|
+
throw new Error("runtime rotation config helpers are unavailable");
|
|
2619
|
+
}
|
|
2620
|
+
const clientApiKey = createRuntimeRotationProxyClientApiKey();
|
|
2621
|
+
proxyServer = await proxyModule.startRuntimeRotationProxy({ clientApiKey });
|
|
2622
|
+
shadowContext = createRuntimeRotationProxyCodexHome(
|
|
2623
|
+
process.env,
|
|
2624
|
+
proxyServer.baseUrl,
|
|
2625
|
+
clientApiKey,
|
|
2626
|
+
configTomlModule,
|
|
2627
|
+
);
|
|
2628
|
+
appServerShimDir = installRuntimeRotationAppServerCliShim(shadowContext.env);
|
|
2629
|
+
lastRequestCount = proxyServer.getStatus?.().totalRequests ?? 0;
|
|
2630
|
+
publishStatus("running");
|
|
2631
|
+
process.stdout.write(
|
|
2632
|
+
`${JSON.stringify({
|
|
2633
|
+
type: "ready",
|
|
2634
|
+
pid: process.pid,
|
|
2635
|
+
baseUrl: proxyServer.baseUrl,
|
|
2636
|
+
statusPath: resolveRuntimeRotationAppHelperStatusPath(),
|
|
2637
|
+
env: pickRuntimeRotationAppHelperEnv(shadowContext.env),
|
|
2638
|
+
})}\n`,
|
|
2639
|
+
);
|
|
2640
|
+
|
|
2641
|
+
statusTimer = setInterval(() => {
|
|
2642
|
+
const currentTime = Date.now();
|
|
2643
|
+
const requestCount = proxyServer?.getStatus?.().totalRequests ?? 0;
|
|
2644
|
+
if (requestCount !== lastRequestCount) {
|
|
2645
|
+
lastRequestCount = requestCount;
|
|
2646
|
+
lastActivityAt = currentTime;
|
|
2647
|
+
}
|
|
2648
|
+
if (ownerPid && isRuntimeRotationAppHelperOwnerAlive(ownerPid)) {
|
|
2649
|
+
lastActivityAt = currentTime;
|
|
2650
|
+
}
|
|
2651
|
+
publishStatus("running");
|
|
2652
|
+
if (currentTime - lastActivityAt >= idleTimeoutMs) {
|
|
2653
|
+
exitAfterCleanup("idle-timeout", 0);
|
|
2654
|
+
}
|
|
2655
|
+
}, Math.min(1_000, Math.max(50, Math.floor(idleTimeoutMs / 2))));
|
|
2656
|
+
} catch (error) {
|
|
2657
|
+
process.stdout.write(
|
|
2658
|
+
`${JSON.stringify({
|
|
2659
|
+
type: "error",
|
|
2660
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2661
|
+
})}\n`,
|
|
2662
|
+
);
|
|
2663
|
+
await cleanup("error");
|
|
2664
|
+
return 1;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
await new Promise(() => undefined);
|
|
2668
|
+
return 0;
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
function waitForRuntimeRotationAppHelperExit(helper, timeoutMs = 2_000) {
|
|
2672
|
+
return new Promise((resolve) => {
|
|
2673
|
+
let settled = false;
|
|
2674
|
+
let timer = null;
|
|
2675
|
+
const finish = () => {
|
|
2676
|
+
if (settled) return;
|
|
2677
|
+
settled = true;
|
|
2678
|
+
if (timer) clearTimeout(timer);
|
|
2679
|
+
resolve();
|
|
2680
|
+
};
|
|
2681
|
+
timer = setTimeout(finish, timeoutMs);
|
|
2682
|
+
helper.once("close", finish);
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
function stopRuntimeRotationAppHelper(helper) {
|
|
2687
|
+
if (!helper || helper.killed) {
|
|
2688
|
+
return Promise.resolve();
|
|
2689
|
+
}
|
|
2690
|
+
try {
|
|
2691
|
+
helper.kill("SIGTERM");
|
|
2692
|
+
} catch {
|
|
2693
|
+
return Promise.resolve();
|
|
2694
|
+
}
|
|
2695
|
+
return waitForRuntimeRotationAppHelperExit(helper);
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
function startRuntimeRotationAppHelper(baseContext) {
|
|
2699
|
+
const realCodexHome =
|
|
2700
|
+
baseContext.originalCodexHome ??
|
|
2701
|
+
resolveRuntimeRotationProxyOriginalCodexHome(baseContext.env);
|
|
2702
|
+
return new Promise((resolve, reject) => {
|
|
2703
|
+
let stdoutBuffer = "";
|
|
2704
|
+
let stderrBuffer = "";
|
|
2705
|
+
let settled = false;
|
|
2706
|
+
const helper = spawn(
|
|
2707
|
+
process.execPath,
|
|
2708
|
+
[fileURLToPath(import.meta.url), INTERNAL_RUNTIME_ROTATION_APP_HELPER_ARG],
|
|
2709
|
+
{
|
|
2710
|
+
env: {
|
|
2711
|
+
...baseContext.env,
|
|
2712
|
+
[APP_RUNTIME_HELPER_OWNER_PID_ENV]: String(process.pid),
|
|
2713
|
+
[APP_RUNTIME_HELPER_REAL_CODEX_HOME_ENV]: realCodexHome,
|
|
2714
|
+
},
|
|
2715
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2716
|
+
detached: true,
|
|
2717
|
+
},
|
|
2718
|
+
);
|
|
2719
|
+
let timeout = null;
|
|
2720
|
+
const finish = (result) => {
|
|
2721
|
+
if (settled) return;
|
|
2722
|
+
settled = true;
|
|
2723
|
+
if (timeout) clearTimeout(timeout);
|
|
2724
|
+
resolve(result);
|
|
2725
|
+
};
|
|
2726
|
+
const fail = (error) => {
|
|
2727
|
+
if (settled) return;
|
|
2728
|
+
settled = true;
|
|
2729
|
+
if (timeout) clearTimeout(timeout);
|
|
2730
|
+
void stopRuntimeRotationAppHelper(helper).finally(() => reject(error));
|
|
2731
|
+
};
|
|
2732
|
+
timeout = setTimeout(() => {
|
|
2733
|
+
fail(new Error("timed out waiting for runtime rotation app helper"));
|
|
2734
|
+
}, APP_RUNTIME_HELPER_LAUNCH_TIMEOUT_MS);
|
|
2735
|
+
helper.stdout?.setEncoding("utf8");
|
|
2736
|
+
helper.stdout?.on("data", (chunk) => {
|
|
2737
|
+
stdoutBuffer += chunk;
|
|
2738
|
+
const newlineIndex = stdoutBuffer.indexOf("\n");
|
|
2739
|
+
if (newlineIndex < 0) return;
|
|
2740
|
+
const line = stdoutBuffer.slice(0, newlineIndex).trim();
|
|
2741
|
+
try {
|
|
2742
|
+
const message = JSON.parse(line);
|
|
2743
|
+
if (message?.type === "ready" && message.env && message.pid) {
|
|
2744
|
+
finish({ helper, message });
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
fail(
|
|
2748
|
+
new Error(
|
|
2749
|
+
message?.message ??
|
|
2750
|
+
"runtime rotation app helper returned an invalid startup response",
|
|
2751
|
+
),
|
|
2752
|
+
);
|
|
2753
|
+
} catch (error) {
|
|
2754
|
+
fail(error);
|
|
2755
|
+
}
|
|
2756
|
+
});
|
|
2757
|
+
helper.stderr?.setEncoding("utf8");
|
|
2758
|
+
helper.stderr?.on("data", (chunk) => {
|
|
2759
|
+
stderrBuffer += chunk;
|
|
2760
|
+
});
|
|
2761
|
+
helper.once("error", fail);
|
|
2762
|
+
helper.once("close", (code) => {
|
|
2763
|
+
if (settled) return;
|
|
2764
|
+
fail(
|
|
2765
|
+
new Error(
|
|
2766
|
+
`runtime rotation app helper exited before startup (code ${code ?? "unknown"}): ${stderrBuffer.trim()}`,
|
|
2767
|
+
),
|
|
2768
|
+
);
|
|
2769
|
+
});
|
|
2770
|
+
});
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
async function createRuntimeRotationAppHelperContext(baseContext, configTomlModule) {
|
|
2774
|
+
const startedAt = Date.now();
|
|
2775
|
+
const { helper, message } = await startRuntimeRotationAppHelper(baseContext);
|
|
2776
|
+
const helperEnv = message.env ?? {};
|
|
2777
|
+
const detachGraceMs = resolveRuntimeRotationAppHelperDetachGraceMs(baseContext.env);
|
|
2778
|
+
|
|
2779
|
+
const cleanup = async ({ exitCode } = {}) => {
|
|
2780
|
+
const livedMs = Date.now() - startedAt;
|
|
2781
|
+
if (exitCode === 0 && livedMs < detachGraceMs) {
|
|
2782
|
+
helper.stdout?.destroy();
|
|
2783
|
+
helper.stderr?.destroy();
|
|
2784
|
+
helper.unref();
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
await stopRuntimeRotationAppHelper(helper);
|
|
2788
|
+
};
|
|
864
2789
|
|
|
865
2790
|
return {
|
|
866
|
-
args:
|
|
867
|
-
|
|
2791
|
+
args: [
|
|
2792
|
+
...baseContext.args,
|
|
2793
|
+
"-c",
|
|
2794
|
+
`model_provider=${configTomlModule.tomlStringLiteral(RUNTIME_ROTATION_PROXY_PROVIDER_ID)}`,
|
|
2795
|
+
],
|
|
2796
|
+
env: {
|
|
2797
|
+
...baseContext.env,
|
|
2798
|
+
...helperEnv,
|
|
2799
|
+
},
|
|
2800
|
+
cleanup: async (details) => {
|
|
2801
|
+
try {
|
|
2802
|
+
await cleanup(details);
|
|
2803
|
+
} finally {
|
|
2804
|
+
baseContext.cleanup?.();
|
|
2805
|
+
}
|
|
2806
|
+
},
|
|
868
2807
|
};
|
|
869
2808
|
}
|
|
870
2809
|
|
|
871
|
-
function
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
}
|
|
2810
|
+
async function createRuntimeRotationProxyContextIfEnabled(
|
|
2811
|
+
baseContext,
|
|
2812
|
+
rawArgs,
|
|
2813
|
+
) {
|
|
2814
|
+
const enabled = await isRuntimeRotationProxyEnabled(rawArgs, baseContext.env);
|
|
2815
|
+
if (!enabled) {
|
|
2816
|
+
return baseContext;
|
|
879
2817
|
}
|
|
880
|
-
return join(env.HOME ?? homedir(), ".codex");
|
|
881
|
-
}
|
|
882
2818
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
2819
|
+
const configTomlModule = await loadRuntimeConfigTomlModule();
|
|
2820
|
+
if (!configTomlModule) {
|
|
2821
|
+
console.error(
|
|
2822
|
+
"codex-multi-auth runtime rotation config helpers are unavailable; continuing without runtime rotation.",
|
|
2823
|
+
);
|
|
2824
|
+
return baseContext;
|
|
2825
|
+
}
|
|
886
2826
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
if (!existsSync(filePath)) {
|
|
890
|
-
return { exists: false, content: null };
|
|
891
|
-
}
|
|
892
|
-
if (options.rethrowRetryableReadErrors) {
|
|
893
|
-
maybeThrowSimulatedShadowHomePreflightReadBusyError();
|
|
894
|
-
}
|
|
895
|
-
return {
|
|
896
|
-
exists: true,
|
|
897
|
-
content: readFileSync(filePath, "utf8"),
|
|
898
|
-
};
|
|
899
|
-
} catch (error) {
|
|
900
|
-
if (options.rethrowRetryableReadErrors && isRetryableShadowHomeCleanupError(error)) {
|
|
901
|
-
throw error;
|
|
902
|
-
}
|
|
903
|
-
return { exists: true, content: null, unreadable: true };
|
|
2827
|
+
if (isCodexAppCommand(rawArgs)) {
|
|
2828
|
+
return createRuntimeRotationAppHelperContext(baseContext, configTomlModule);
|
|
904
2829
|
}
|
|
905
|
-
}
|
|
906
2830
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
}
|
|
2831
|
+
const proxyModule = await loadRuntimeRotationProxyModule();
|
|
2832
|
+
if (!proxyModule) {
|
|
2833
|
+
console.error(
|
|
2834
|
+
"codex-multi-auth runtime rotation proxy is unavailable; continuing without runtime rotation.",
|
|
2835
|
+
);
|
|
2836
|
+
return baseContext;
|
|
2837
|
+
}
|
|
914
2838
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
destinationPath,
|
|
918
|
-
expectedDestinationState,
|
|
919
|
-
) {
|
|
920
|
-
const tempPath = join(
|
|
921
|
-
dirname(destinationPath),
|
|
922
|
-
`.${basename(destinationPath)}.codex-multi-auth-sync-${process.pid}.tmp`,
|
|
923
|
-
);
|
|
2839
|
+
let proxyServer;
|
|
2840
|
+
let shadowContext;
|
|
924
2841
|
try {
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
2842
|
+
const clientApiKey = createRuntimeRotationProxyClientApiKey();
|
|
2843
|
+
proxyServer = await proxyModule.startRuntimeRotationProxy({ clientApiKey });
|
|
2844
|
+
shadowContext = createRuntimeRotationProxyCodexHome(
|
|
2845
|
+
baseContext.env,
|
|
2846
|
+
proxyServer.baseUrl,
|
|
2847
|
+
clientApiKey,
|
|
2848
|
+
configTomlModule,
|
|
2849
|
+
);
|
|
928
2850
|
} catch (error) {
|
|
929
2851
|
try {
|
|
930
|
-
|
|
2852
|
+
await proxyServer?.close?.();
|
|
931
2853
|
} catch {
|
|
932
2854
|
// Best-effort cleanup only.
|
|
933
2855
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
function rewriteConfigTomlReasoningEffort(rawConfig, requestedModel) {
|
|
939
|
-
const lineEnding = rawConfig.includes("\r\n") ? "\r\n" : "\n";
|
|
940
|
-
let changed = false;
|
|
941
|
-
const nextLines = rawConfig.split(/\r?\n/).map((line) => {
|
|
942
|
-
const quotedMatch = line.match(
|
|
943
|
-
/^(\s*model_reasoning_effort\s*=\s*)(["'])([^"']+)(\2.*)$/,
|
|
944
|
-
);
|
|
945
|
-
const bareMatch = quotedMatch
|
|
946
|
-
? null
|
|
947
|
-
: line.match(
|
|
948
|
-
/^(\s*model_reasoning_effort\s*=\s*)([^\s#]+)(\s*(?:#.*)?)$/,
|
|
949
|
-
);
|
|
950
|
-
if (!quotedMatch && !bareMatch) return line;
|
|
951
|
-
|
|
952
|
-
const prefix = quotedMatch?.[1] ?? bareMatch?.[1] ?? "";
|
|
953
|
-
const openingQuote = quotedMatch?.[2] ?? "";
|
|
954
|
-
const currentEffort = quotedMatch?.[3] ?? bareMatch?.[2] ?? "";
|
|
955
|
-
const suffix = quotedMatch?.[4] ?? bareMatch?.[3] ?? "";
|
|
956
|
-
const coercedEffort = coerceReasoningEffortForModel(
|
|
957
|
-
requestedModel,
|
|
958
|
-
currentEffort,
|
|
2856
|
+
console.error(
|
|
2857
|
+
`codex-multi-auth runtime rotation proxy failed to start; continuing without runtime rotation: ${error instanceof Error ? error.message : String(error)}`,
|
|
959
2858
|
);
|
|
960
|
-
|
|
961
|
-
return line;
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
changed = true;
|
|
965
|
-
return quotedMatch
|
|
966
|
-
? `${prefix}${openingQuote}${coercedEffort}${suffix}`
|
|
967
|
-
: `${prefix}${coercedEffort}${suffix}`;
|
|
968
|
-
});
|
|
969
|
-
|
|
970
|
-
if (!changed) {
|
|
971
|
-
return rawConfig;
|
|
2859
|
+
return baseContext;
|
|
972
2860
|
}
|
|
973
2861
|
|
|
974
|
-
|
|
975
|
-
|
|
2862
|
+
const cleanup = async () => {
|
|
2863
|
+
try {
|
|
2864
|
+
shadowContext.cleanup?.();
|
|
2865
|
+
} finally {
|
|
2866
|
+
try {
|
|
2867
|
+
await proxyServer.close();
|
|
2868
|
+
} finally {
|
|
2869
|
+
baseContext.cleanup?.();
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
};
|
|
976
2873
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
2874
|
+
return {
|
|
2875
|
+
args: [
|
|
2876
|
+
...baseContext.args,
|
|
2877
|
+
"-c",
|
|
2878
|
+
`model_provider=${configTomlModule.tomlStringLiteral(RUNTIME_ROTATION_PROXY_PROVIDER_ID)}`,
|
|
2879
|
+
],
|
|
2880
|
+
env: shadowContext.env,
|
|
2881
|
+
cleanup,
|
|
2882
|
+
proxyAppServerAccountRead: isCodexAppServerCommand(rawArgs),
|
|
2883
|
+
};
|
|
983
2884
|
}
|
|
984
2885
|
|
|
985
2886
|
async function loadRuntimeObservabilityModule() {
|
|
@@ -1020,8 +2921,17 @@ function consumesNextArg(arg) {
|
|
|
1020
2921
|
"--config",
|
|
1021
2922
|
"--enable",
|
|
1022
2923
|
"--disable",
|
|
2924
|
+
"--listen",
|
|
1023
2925
|
"--remote",
|
|
1024
2926
|
"--remote-auth-token-env",
|
|
2927
|
+
"--ws-auth",
|
|
2928
|
+
"--ws-token-file",
|
|
2929
|
+
"--ws-token-sha256",
|
|
2930
|
+
"--ws-shared-secret-file",
|
|
2931
|
+
"--ws-issuer",
|
|
2932
|
+
"--ws-audience",
|
|
2933
|
+
"--ws-max-clock-skew-seconds",
|
|
2934
|
+
"--download-url",
|
|
1025
2935
|
"-i",
|
|
1026
2936
|
"--image",
|
|
1027
2937
|
"-m",
|
|
@@ -1043,7 +2953,75 @@ function consumesNextArg(arg) {
|
|
|
1043
2953
|
]).has(arg);
|
|
1044
2954
|
}
|
|
1045
2955
|
|
|
1046
|
-
function
|
|
2956
|
+
function findForwardedCommand(rawArgs) {
|
|
2957
|
+
if (!Array.isArray(rawArgs) || rawArgs.length === 0) {
|
|
2958
|
+
return null;
|
|
2959
|
+
}
|
|
2960
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
2961
|
+
const arg = rawArgs[i];
|
|
2962
|
+
if (typeof arg !== "string" || arg.length === 0) continue;
|
|
2963
|
+
if (arg === "--") {
|
|
2964
|
+
return i + 1 < rawArgs.length
|
|
2965
|
+
? { command: rawArgs[i + 1], index: i + 1 }
|
|
2966
|
+
: null;
|
|
2967
|
+
}
|
|
2968
|
+
if (arg.startsWith("--config=")) {
|
|
2969
|
+
continue;
|
|
2970
|
+
}
|
|
2971
|
+
if (arg.startsWith("--") || (arg.startsWith("-") && arg !== "-")) {
|
|
2972
|
+
if (consumesNextArg(arg)) {
|
|
2973
|
+
i += 1;
|
|
2974
|
+
}
|
|
2975
|
+
continue;
|
|
2976
|
+
}
|
|
2977
|
+
return { command: arg, index: i };
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
return null;
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
function findForwardedSubcommand(rawArgs, commandIndex) {
|
|
2984
|
+
for (let i = commandIndex + 1; i < rawArgs.length; i += 1) {
|
|
2985
|
+
const arg = rawArgs[i];
|
|
2986
|
+
if (typeof arg !== "string" || arg.length === 0) continue;
|
|
2987
|
+
if (arg === "--") {
|
|
2988
|
+
return i + 1 < rawArgs.length ? rawArgs[i + 1] : null;
|
|
2989
|
+
}
|
|
2990
|
+
if (arg.startsWith("--config=")) {
|
|
2991
|
+
continue;
|
|
2992
|
+
}
|
|
2993
|
+
if (arg.startsWith("--") || (arg.startsWith("-") && arg !== "-")) {
|
|
2994
|
+
if (consumesNextArg(arg)) {
|
|
2995
|
+
i += 1;
|
|
2996
|
+
}
|
|
2997
|
+
continue;
|
|
2998
|
+
}
|
|
2999
|
+
return arg;
|
|
3000
|
+
}
|
|
3001
|
+
return null;
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
function hasHelpFlagAfterCommand(rawArgs, commandIndex) {
|
|
3005
|
+
for (let i = commandIndex + 1; i < rawArgs.length; i += 1) {
|
|
3006
|
+
const arg = rawArgs[i];
|
|
3007
|
+
if (arg === "--") return false;
|
|
3008
|
+
if (arg === "--help" || arg === "-h" || arg === "help") return true;
|
|
3009
|
+
if (typeof arg === "string" && consumesNextArg(arg)) {
|
|
3010
|
+
i += 1;
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
return false;
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
function isCodexAppCommand(rawArgs) {
|
|
3017
|
+
return findForwardedCommand(rawArgs)?.command === "app";
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
function isCodexAppServerCommand(rawArgs) {
|
|
3021
|
+
return findForwardedCommand(rawArgs)?.command === "app-server";
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
function shouldUseRuntimeRoutingForForwardedArgs(rawArgs) {
|
|
1047
3025
|
if (!Array.isArray(rawArgs) || rawArgs.length === 0) {
|
|
1048
3026
|
return true;
|
|
1049
3027
|
}
|
|
@@ -1051,7 +3029,12 @@ function shouldTrackForwardedRuntimeObservability(rawArgs) {
|
|
|
1051
3029
|
return false;
|
|
1052
3030
|
}
|
|
1053
3031
|
|
|
1054
|
-
const
|
|
3032
|
+
const command = findForwardedCommand(rawArgs);
|
|
3033
|
+
if (!command) {
|
|
3034
|
+
return true;
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
const requestCommands = new Set(["exec", "review", "resume", "fork", "app"]);
|
|
1055
3038
|
const nonRequestCommands = new Set([
|
|
1056
3039
|
"help",
|
|
1057
3040
|
"completion",
|
|
@@ -1059,7 +3042,6 @@ function shouldTrackForwardedRuntimeObservability(rawArgs) {
|
|
|
1059
3042
|
"logout",
|
|
1060
3043
|
"mcp",
|
|
1061
3044
|
"mcp-server",
|
|
1062
|
-
"app-server",
|
|
1063
3045
|
"sandbox",
|
|
1064
3046
|
"debug",
|
|
1065
3047
|
"apply",
|
|
@@ -1068,36 +3050,41 @@ function shouldTrackForwardedRuntimeObservability(rawArgs) {
|
|
|
1068
3050
|
"auth",
|
|
1069
3051
|
]);
|
|
1070
3052
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
if (typeof arg !== "string" || arg.length === 0) continue;
|
|
1074
|
-
if (arg === "--") {
|
|
1075
|
-
return i + 1 < rawArgs.length;
|
|
1076
|
-
}
|
|
1077
|
-
if (arg.startsWith("--config=")) {
|
|
1078
|
-
continue;
|
|
1079
|
-
}
|
|
1080
|
-
if (arg.startsWith("--") || (arg.startsWith("-") && arg !== "-")) {
|
|
1081
|
-
if (consumesNextArg(arg)) {
|
|
1082
|
-
i += 1;
|
|
1083
|
-
}
|
|
1084
|
-
continue;
|
|
1085
|
-
}
|
|
1086
|
-
if (requestCommands.has(arg)) {
|
|
1087
|
-
return true;
|
|
1088
|
-
}
|
|
1089
|
-
if (nonRequestCommands.has(arg)) {
|
|
3053
|
+
if (command.command === "app-server") {
|
|
3054
|
+
if (hasHelpFlagAfterCommand(rawArgs, command.index)) {
|
|
1090
3055
|
return false;
|
|
1091
3056
|
}
|
|
1092
|
-
|
|
3057
|
+
const subcommand = findForwardedSubcommand(rawArgs, command.index);
|
|
3058
|
+
return !new Set(["help", "generate-ts", "generate-json-schema"]).has(
|
|
3059
|
+
subcommand ?? "",
|
|
3060
|
+
);
|
|
1093
3061
|
}
|
|
1094
3062
|
|
|
3063
|
+
if (command.command === "app" && hasHelpFlagAfterCommand(rawArgs, command.index)) {
|
|
3064
|
+
return false;
|
|
3065
|
+
}
|
|
3066
|
+
if (requestCommands.has(command.command)) {
|
|
3067
|
+
return true;
|
|
3068
|
+
}
|
|
3069
|
+
if (nonRequestCommands.has(command.command)) {
|
|
3070
|
+
return false;
|
|
3071
|
+
}
|
|
1095
3072
|
return true;
|
|
1096
3073
|
}
|
|
1097
3074
|
|
|
3075
|
+
function shouldTrackForwardedRuntimeObservability(rawArgs) {
|
|
3076
|
+
return shouldUseRuntimeRoutingForForwardedArgs(rawArgs);
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
function shouldCaptureForwardedOutputForArgs(rawArgs, env) {
|
|
3080
|
+
if (isCodexAppServerCommand(rawArgs)) {
|
|
3081
|
+
return false;
|
|
3082
|
+
}
|
|
3083
|
+
return shouldCaptureForwardedCodexOutput(env);
|
|
3084
|
+
}
|
|
3085
|
+
|
|
1098
3086
|
function createRuntimeSnapshotChangeToken(snapshot) {
|
|
1099
3087
|
return JSON.stringify({
|
|
1100
|
-
updatedAt: snapshot?.updatedAt ?? null,
|
|
1101
3088
|
responsesRequests: snapshot?.responsesRequests ?? null,
|
|
1102
3089
|
authRefreshRequests: snapshot?.authRefreshRequests ?? null,
|
|
1103
3090
|
diagnosticProbeRequests: snapshot?.diagnosticProbeRequests ?? null,
|
|
@@ -1170,21 +3157,25 @@ function createCompatibilityCodexHome(
|
|
|
1170
3157
|
requestedModel,
|
|
1171
3158
|
baseEnv = process.env,
|
|
1172
3159
|
) {
|
|
3160
|
+
const originalCodexHome = resolveCodexHomeDir(baseEnv);
|
|
1173
3161
|
if (!requestedModel) {
|
|
1174
|
-
return {
|
|
3162
|
+
return {
|
|
3163
|
+
args: processedArgs,
|
|
3164
|
+
env: baseEnv,
|
|
3165
|
+
cleanup: undefined,
|
|
3166
|
+
originalCodexHome,
|
|
3167
|
+
};
|
|
1175
3168
|
}
|
|
1176
3169
|
|
|
1177
|
-
const originalCodexHome = resolveCodexHomeDir(baseEnv);
|
|
1178
3170
|
const configPath = join(originalCodexHome, "config.toml");
|
|
1179
3171
|
if (!existsSync(configPath)) {
|
|
1180
|
-
return {
|
|
3172
|
+
return {
|
|
3173
|
+
args: processedArgs,
|
|
3174
|
+
env: baseEnv,
|
|
3175
|
+
cleanup: undefined,
|
|
3176
|
+
originalCodexHome,
|
|
3177
|
+
};
|
|
1181
3178
|
}
|
|
1182
|
-
const originalShadowHomeState = new Map(
|
|
1183
|
-
SHADOW_HOME_STATE_FILES.map((name) => [
|
|
1184
|
-
name,
|
|
1185
|
-
captureShadowHomeState(join(originalCodexHome, name)),
|
|
1186
|
-
]),
|
|
1187
|
-
);
|
|
1188
3179
|
|
|
1189
3180
|
const rawConfig = readFileSync(configPath, "utf8");
|
|
1190
3181
|
const compatConfig = rewriteConfigTomlReasoningEffort(
|
|
@@ -1192,10 +3183,16 @@ function createCompatibilityCodexHome(
|
|
|
1192
3183
|
requestedModel,
|
|
1193
3184
|
);
|
|
1194
3185
|
if (compatConfig === rawConfig) {
|
|
1195
|
-
return {
|
|
3186
|
+
return {
|
|
3187
|
+
args: processedArgs,
|
|
3188
|
+
env: baseEnv,
|
|
3189
|
+
cleanup: undefined,
|
|
3190
|
+
originalCodexHome,
|
|
3191
|
+
};
|
|
1196
3192
|
}
|
|
1197
3193
|
|
|
1198
3194
|
const shadowCodexHome = mkdtempSync(join(tmpdir(), "codex-multi-auth-home-"));
|
|
3195
|
+
let syncShadowHomeStateBack = () => {};
|
|
1199
3196
|
const cleanup = () => {
|
|
1200
3197
|
try {
|
|
1201
3198
|
removeDirectoryWithRetry(shadowCodexHome);
|
|
@@ -1210,44 +3207,15 @@ function createCompatibilityCodexHome(
|
|
|
1210
3207
|
// Best-effort only; permission semantics vary by platform.
|
|
1211
3208
|
}
|
|
1212
3209
|
};
|
|
1213
|
-
const syncShadowHomeStateBack = () => {
|
|
1214
|
-
for (const name of SHADOW_HOME_STATE_FILES) {
|
|
1215
|
-
const shadowPath = join(shadowCodexHome, name);
|
|
1216
|
-
const shadowState = captureShadowHomeState(shadowPath);
|
|
1217
|
-
if (!shadowState.exists || shadowState.unreadable) {
|
|
1218
|
-
continue;
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
try {
|
|
1222
|
-
const originalPath = join(originalCodexHome, name);
|
|
1223
|
-
const originalSnapshot =
|
|
1224
|
-
originalShadowHomeState.get(name) ?? { exists: false, content: null };
|
|
1225
|
-
const currentOriginalState = captureShadowHomeState(originalPath);
|
|
1226
|
-
if (!shadowHomeStateMatches(currentOriginalState, originalSnapshot)) {
|
|
1227
|
-
continue;
|
|
1228
|
-
}
|
|
1229
|
-
if (shadowHomeStateMatches(shadowState, originalSnapshot)) {
|
|
1230
|
-
continue;
|
|
1231
|
-
}
|
|
1232
|
-
syncShadowHomeStateFile(shadowPath, originalPath, originalSnapshot);
|
|
1233
|
-
tightenShadowHomePermissions(originalPath);
|
|
1234
|
-
} catch {
|
|
1235
|
-
// Best-effort only; runtime auth refreshes should not fail cleanup.
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
};
|
|
1239
3210
|
try {
|
|
3211
|
+
syncShadowHomeStateBack = createShadowHomeMirror(
|
|
3212
|
+
originalCodexHome,
|
|
3213
|
+
shadowCodexHome,
|
|
3214
|
+
tightenShadowHomePermissions,
|
|
3215
|
+
);
|
|
1240
3216
|
const compatConfigPath = join(shadowCodexHome, "config.toml");
|
|
1241
3217
|
writeFileSync(compatConfigPath, compatConfig, "utf8");
|
|
1242
3218
|
tightenShadowHomePermissions(compatConfigPath);
|
|
1243
|
-
for (const name of SHADOW_HOME_STATE_FILES) {
|
|
1244
|
-
const sourcePath = join(originalCodexHome, name);
|
|
1245
|
-
if (existsSync(sourcePath)) {
|
|
1246
|
-
const destinationPath = join(shadowCodexHome, name);
|
|
1247
|
-
copyFileSync(sourcePath, destinationPath);
|
|
1248
|
-
tightenShadowHomePermissions(destinationPath);
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
3219
|
} catch (error) {
|
|
1252
3220
|
cleanup();
|
|
1253
3221
|
throw error;
|
|
@@ -1270,6 +3238,7 @@ function createCompatibilityCodexHome(
|
|
|
1270
3238
|
args: processedArgs,
|
|
1271
3239
|
env: forwardedEnv,
|
|
1272
3240
|
cleanup: cleanupWithSync,
|
|
3241
|
+
originalCodexHome,
|
|
1273
3242
|
};
|
|
1274
3243
|
}
|
|
1275
3244
|
|
|
@@ -1609,10 +3578,16 @@ function ensureWindowsShellShimGuards() {
|
|
|
1609
3578
|
|
|
1610
3579
|
async function main() {
|
|
1611
3580
|
hydrateCliVersionEnv();
|
|
1612
|
-
ensureWindowsShellShimGuards();
|
|
1613
3581
|
|
|
1614
3582
|
const rawArgs = process.argv.slice(2);
|
|
3583
|
+
if (rawArgs[0] === INTERNAL_RUNTIME_ROTATION_APP_HELPER_ARG) {
|
|
3584
|
+
return runRuntimeRotationAppHelper();
|
|
3585
|
+
}
|
|
3586
|
+
|
|
1615
3587
|
const normalizedArgs = normalizeAuthAlias(rawArgs);
|
|
3588
|
+
await autoUpdatePackageIfEnabled(rawArgs, normalizedArgs);
|
|
3589
|
+
ensureWindowsShellShimGuards();
|
|
3590
|
+
|
|
1616
3591
|
const bypass = (process.env.CODEX_MULTI_AUTH_BYPASS ?? "").trim() === "1";
|
|
1617
3592
|
|
|
1618
3593
|
if (!bypass && shouldHandleMultiAuthAuth(normalizedArgs)) {
|
|
@@ -1622,6 +3597,10 @@ async function main() {
|
|
|
1622
3597
|
return 1;
|
|
1623
3598
|
}
|
|
1624
3599
|
const exitCode = await runCodexMultiAuthCli(normalizedArgs);
|
|
3600
|
+
await maybeInstallCodexAppLauncherAfterRotationEnable(
|
|
3601
|
+
normalizedArgs,
|
|
3602
|
+
normalizeExitCode(exitCode),
|
|
3603
|
+
);
|
|
1625
3604
|
return normalizeExitCode(exitCode);
|
|
1626
3605
|
} catch (error) {
|
|
1627
3606
|
console.error(
|
|
@@ -1644,9 +3623,13 @@ async function main() {
|
|
|
1644
3623
|
}
|
|
1645
3624
|
|
|
1646
3625
|
await autoSyncManagerActiveSelectionIfEnabled();
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
3626
|
+
try {
|
|
3627
|
+
return await withForwardedRuntimeObservability(rawArgs, () =>
|
|
3628
|
+
forwardToRealCodex(realCodexBin, rawArgs),
|
|
3629
|
+
);
|
|
3630
|
+
} finally {
|
|
3631
|
+
await autoSyncManagerActiveSelectionIfEnabled();
|
|
3632
|
+
}
|
|
1650
3633
|
}
|
|
1651
3634
|
|
|
1652
3635
|
const exitCode = await main();
|