codex-multi-auth 2.22.0 → 2.23.0
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/.codex-plugin/plugin.json +1 -1
- package/README.md +4 -2
- package/config/schema/config.schema.json +8 -0
- package/dist/lib/api-route-store.d.ts +1 -0
- package/dist/lib/api-route-store.js +2 -1
- package/dist/lib/auth/account-access.js +2 -1
- package/dist/lib/auth/auth.js +3 -2
- package/dist/lib/auth/device-auth.js +2 -1
- package/dist/lib/auth/http.d.ts +1 -0
- package/dist/lib/auth/http.js +1 -0
- package/dist/lib/codex-manager/account-manager-commands.js +1 -0
- package/dist/lib/codex-manager/commands/locale.d.ts +12 -0
- package/dist/lib/codex-manager/commands/locale.js +25 -0
- package/dist/lib/codex-manager/commands/status.d.ts +4 -0
- package/dist/lib/codex-manager/commands/status.js +476 -5
- package/dist/lib/codex-manager/help.js +3 -1
- package/dist/lib/codex-manager.js +98 -29
- package/dist/lib/config.js +3 -0
- package/dist/lib/locale.d.ts +7 -0
- package/dist/lib/locale.js +35 -0
- package/dist/lib/quota-probe.js +2 -1
- package/dist/lib/request/proxy-fetch.d.ts +5 -0
- package/dist/lib/request/proxy-fetch.js +37 -0
- package/dist/lib/request/url-rewriting.js +17 -0
- package/dist/lib/runtime/account-model-catalog.d.ts +2 -0
- package/dist/lib/runtime/account-model-catalog.js +7 -2
- package/dist/lib/runtime/app-bind.d.ts +4 -1
- package/dist/lib/runtime/app-bind.js +63 -3
- package/dist/lib/runtime/responses-websocket.d.ts +30 -0
- package/dist/lib/runtime/responses-websocket.js +243 -29
- package/dist/lib/runtime/rotation-account-selection.d.ts +5 -4
- package/dist/lib/runtime/rotation-server-types.d.ts +4 -1
- package/dist/lib/runtime-rotation-proxy.js +30 -9
- package/dist/lib/schemas.d.ts +5 -0
- package/dist/lib/schemas.js +1 -0
- package/dist/lib/web-dashboard-html.d.ts +1 -0
- package/dist/lib/web-dashboard-html.js +66 -0
- package/dist/lib/web-dashboard.d.ts +36 -0
- package/dist/lib/web-dashboard.js +322 -0
- package/package.json +3 -1
- package/scripts/codex-app-router.js +45 -1
- package/scripts/codex-routing.js +1 -0
- package/scripts/codex.js +150 -13
package/scripts/codex.js
CHANGED
|
@@ -4,15 +4,18 @@ import { execFile, execFileSync, spawn } from "node:child_process";
|
|
|
4
4
|
import { createHash, randomBytes } from "node:crypto";
|
|
5
5
|
import {
|
|
6
6
|
chmodSync,
|
|
7
|
+
closeSync,
|
|
7
8
|
copyFileSync,
|
|
8
9
|
cpSync,
|
|
9
10
|
existsSync,
|
|
10
11
|
linkSync,
|
|
11
12
|
mkdirSync,
|
|
12
13
|
mkdtempSync,
|
|
14
|
+
openSync,
|
|
13
15
|
readdirSync,
|
|
14
16
|
renameSync,
|
|
15
17
|
readFileSync,
|
|
18
|
+
readSync,
|
|
16
19
|
rmSync,
|
|
17
20
|
statSync,
|
|
18
21
|
symlinkSync,
|
|
@@ -86,6 +89,8 @@ const APP_RUNTIME_HELPER_USE_CANONICAL_HOME_ENV =
|
|
|
86
89
|
"CODEX_MULTI_AUTH_APP_ROTATION_USE_CANONICAL_HOME";
|
|
87
90
|
const APP_RUNTIME_HELPER_INSTALL_APP_SERVER_SHIM_ENV =
|
|
88
91
|
"CODEX_MULTI_AUTH_APP_ROTATION_INSTALL_APP_SERVER_SHIM";
|
|
92
|
+
const APP_RUNTIME_HELPER_PROVIDER_ID_ENV =
|
|
93
|
+
"CODEX_MULTI_AUTH_APP_ROTATION_PROVIDER_ID";
|
|
89
94
|
const RUNTIME_ROTATION_PROXY_UPSTREAM_BASE_URL_ENV =
|
|
90
95
|
"CODEX_MULTI_AUTH_RUNTIME_PROXY_UPSTREAM_BASE_URL";
|
|
91
96
|
const APP_SERVER_CONFIG_ARGS_ENV =
|
|
@@ -4264,18 +4269,33 @@ function createRuntimeRotationProxyCanonicalCodexHome(
|
|
|
4264
4269
|
proxyBaseUrl,
|
|
4265
4270
|
clientApiKey,
|
|
4266
4271
|
configTomlModule,
|
|
4272
|
+
providerId = RUNTIME_ROTATION_PROXY_PROVIDER_ID,
|
|
4267
4273
|
) {
|
|
4268
4274
|
const originalCodexHome = resolveRuntimeRotationProxyOriginalCodexHome(baseEnv);
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4275
|
+
// Codex reserves the built-in `openai` provider ID and rejects a
|
|
4276
|
+
// `[model_providers.openai]` table before the TUI can load a session. The
|
|
4277
|
+
// built-in provider has a dedicated top-level base URL setting, so use that
|
|
4278
|
+
// for native OpenAI rollouts and keep `model_provider=openai` for resume
|
|
4279
|
+
// catalog compatibility. Custom provider IDs still use the normal provider
|
|
4280
|
+
// table because they are not reserved by Codex.
|
|
4281
|
+
const configAssignments = providerId === "openai"
|
|
4282
|
+
? [
|
|
4283
|
+
`openai_base_url=${configTomlModule.tomlStringLiteral(proxyBaseUrl)}`,
|
|
4284
|
+
"disable_response_storage=false",
|
|
4285
|
+
]
|
|
4286
|
+
: (() => {
|
|
4287
|
+
const providerTable = `model_providers.${providerId}`;
|
|
4288
|
+
return [
|
|
4289
|
+
`${providerTable}.name=${configTomlModule.tomlStringLiteral(APP_SERVER_ACCOUNT_DISPLAY_NAME)}`,
|
|
4290
|
+
`${providerTable}.base_url=${configTomlModule.tomlStringLiteral(proxyBaseUrl)}`,
|
|
4291
|
+
`${providerTable}.env_key=${configTomlModule.tomlStringLiteral("OPENAI_API_KEY")}`,
|
|
4292
|
+
`${providerTable}.requires_openai_auth=false`,
|
|
4293
|
+
`${providerTable}.http_headers.x-openai-actor-authorization=${configTomlModule.tomlStringLiteral("codex-multi-auth-local")}`,
|
|
4294
|
+
`${providerTable}.wire_api=${configTomlModule.tomlStringLiteral("responses")}`,
|
|
4295
|
+
"disable_response_storage=false",
|
|
4296
|
+
];
|
|
4297
|
+
})();
|
|
4298
|
+
const configArgs = configAssignments.flatMap((assignment) => ["-c", assignment]);
|
|
4279
4299
|
|
|
4280
4300
|
return {
|
|
4281
4301
|
args: configArgs,
|
|
@@ -5192,6 +5212,7 @@ async function runRuntimeRotationAppHelper(identityToken = "") {
|
|
|
5192
5212
|
proxyServer.baseUrl,
|
|
5193
5213
|
clientApiKey,
|
|
5194
5214
|
configTomlModule,
|
|
5215
|
+
process.env[APP_RUNTIME_HELPER_PROVIDER_ID_ENV] ?? RUNTIME_ROTATION_PROXY_PROVIDER_ID,
|
|
5195
5216
|
)
|
|
5196
5217
|
: createRuntimeRotationProxyCodexHome(
|
|
5197
5218
|
process.env,
|
|
@@ -5211,11 +5232,12 @@ async function runRuntimeRotationAppHelper(identityToken = "") {
|
|
|
5211
5232
|
(process.env[APP_RUNTIME_HELPER_INSTALL_APP_SERVER_SHIM_ENV] ?? "1").trim() !==
|
|
5212
5233
|
"0";
|
|
5213
5234
|
if (installAppServerShim) {
|
|
5235
|
+
const providerId = process.env[APP_RUNTIME_HELPER_PROVIDER_ID_ENV] ?? RUNTIME_ROTATION_PROXY_PROVIDER_ID;
|
|
5214
5236
|
const appServerConfigArgs = useCanonicalHome
|
|
5215
5237
|
? [
|
|
5216
5238
|
...(runtimeContext.args ?? []),
|
|
5217
5239
|
"-c",
|
|
5218
|
-
`model_provider=${configTomlModule.tomlStringLiteral(
|
|
5240
|
+
`model_provider=${configTomlModule.tomlStringLiteral(providerId)}`,
|
|
5219
5241
|
]
|
|
5220
5242
|
: [];
|
|
5221
5243
|
appServerShimDir = installRuntimeRotationAppServerCliShim(
|
|
@@ -5402,6 +5424,8 @@ function startRuntimeRotationAppHelper(baseContext, options = {}) {
|
|
|
5402
5424
|
options.useCanonicalHome === true ? "1" : "0",
|
|
5403
5425
|
[APP_RUNTIME_HELPER_INSTALL_APP_SERVER_SHIM_ENV]:
|
|
5404
5426
|
options.installAppServerShim === false ? "0" : "1",
|
|
5427
|
+
[APP_RUNTIME_HELPER_PROVIDER_ID_ENV]:
|
|
5428
|
+
options.providerId ?? RUNTIME_ROTATION_PROXY_PROVIDER_ID,
|
|
5405
5429
|
};
|
|
5406
5430
|
const helper = spawn(
|
|
5407
5431
|
process.execPath,
|
|
@@ -5546,10 +5570,11 @@ async function createRuntimeRotationAppHelperContext(
|
|
|
5546
5570
|
}
|
|
5547
5571
|
};
|
|
5548
5572
|
|
|
5573
|
+
const providerId = options.providerId ?? RUNTIME_ROTATION_PROXY_PROVIDER_ID;
|
|
5549
5574
|
const injectedArgs = [
|
|
5550
5575
|
...helperArgs,
|
|
5551
5576
|
"-c",
|
|
5552
|
-
`model_provider=${configTomlModule.tomlStringLiteral(
|
|
5577
|
+
`model_provider=${configTomlModule.tomlStringLiteral(providerId)}`,
|
|
5553
5578
|
];
|
|
5554
5579
|
return {
|
|
5555
5580
|
// Root TUI argv can end in the optional `[PROMPT]` positional, and `--`
|
|
@@ -5684,9 +5709,11 @@ async function createRuntimeRotationProxyContextIfEnabled(
|
|
|
5684
5709
|
// answer is needed twice below.
|
|
5685
5710
|
const isRootTuiLaunch = isCodexInteractiveTuiCommand(rawArgs);
|
|
5686
5711
|
if (isRootTuiLaunch || isCodexInteractiveResumeCommand(rawArgs)) {
|
|
5712
|
+
const resumeProvider = resolveResumeSessionProvider(rawArgs, baseContext.env);
|
|
5687
5713
|
return startAppHelperContext({
|
|
5688
5714
|
detachOnExit: true,
|
|
5689
5715
|
useCanonicalHome: true,
|
|
5716
|
+
providerId: resumeProvider === "openai" ? "openai" : RUNTIME_ROTATION_PROXY_PROVIDER_ID,
|
|
5690
5717
|
// Root TUI launches may carry the optional `[PROMPT]` positional
|
|
5691
5718
|
// (possibly forced by `--`); the provider overrides must stay on the
|
|
5692
5719
|
// option side of it. `resume`/`fork` keep the appended ordering their
|
|
@@ -6096,6 +6123,107 @@ function isCodexInteractiveResumeCommand(rawArgs) {
|
|
|
6096
6123
|
return command === "resume" || command === "fork";
|
|
6097
6124
|
}
|
|
6098
6125
|
|
|
6126
|
+
// Codex filters the resume catalog by the active model provider. Keep an
|
|
6127
|
+
// existing native `openai` rollout visible by using the local proxy under the
|
|
6128
|
+
// same provider name for this invocation. This only changes the transport
|
|
6129
|
+
// endpoint; account selection still happens inside the rotation proxy.
|
|
6130
|
+
// `fork` resolves the source session's provider the same way so a forked
|
|
6131
|
+
// native rollout keeps `model_provider=openai` instead of being re-labeled.
|
|
6132
|
+
function resolveResumeSessionProvider(rawArgs, env = process.env) {
|
|
6133
|
+
const command = findForwardedCommand(rawArgs);
|
|
6134
|
+
if (command?.command !== "resume" && command?.command !== "fork") return null;
|
|
6135
|
+
let requestedId = null;
|
|
6136
|
+
let useLast = false;
|
|
6137
|
+
let showAll = false;
|
|
6138
|
+
for (let index = command.index + 1; index < rawArgs.length; index += 1) {
|
|
6139
|
+
const arg = rawArgs[index];
|
|
6140
|
+
if (arg === "--") break;
|
|
6141
|
+
if (arg === "--last") { useLast = true; continue; }
|
|
6142
|
+
if (arg === "--all") { showAll = true; continue; }
|
|
6143
|
+
if (arg === "--help" || arg === "-h") return null;
|
|
6144
|
+
if (consumesNextArg(arg)) {
|
|
6145
|
+
index += 1;
|
|
6146
|
+
continue;
|
|
6147
|
+
}
|
|
6148
|
+
if (typeof arg === "string" && arg.startsWith("-")) continue;
|
|
6149
|
+
// With `--last`, a positional token is the prompt (`resume --last "…"`),
|
|
6150
|
+
// not a session id — keep scanning instead of rejecting the lookup.
|
|
6151
|
+
if (!/^[0-9a-f]{8}-[0-9a-f-]{27,}$/i.test(arg)) {
|
|
6152
|
+
if (useLast) continue;
|
|
6153
|
+
return null;
|
|
6154
|
+
}
|
|
6155
|
+
requestedId = arg;
|
|
6156
|
+
break;
|
|
6157
|
+
}
|
|
6158
|
+
if (!requestedId && !useLast) return null;
|
|
6159
|
+
const sessionsRoot = join(resolveCodexHomeDir(env), "sessions");
|
|
6160
|
+
const targetSuffix = requestedId ? `-${requestedId}.jsonl`.toLowerCase() : null;
|
|
6161
|
+
const currentDirectory = process.cwd();
|
|
6162
|
+
const candidates = [];
|
|
6163
|
+
const collectRollouts = (directory) => {
|
|
6164
|
+
let entries;
|
|
6165
|
+
try { entries = readdirSync(directory, { withFileTypes: true }); } catch { return; }
|
|
6166
|
+
for (const entry of entries) {
|
|
6167
|
+
const path = join(directory, entry.name);
|
|
6168
|
+
if (entry.isDirectory()) {
|
|
6169
|
+
collectRollouts(path);
|
|
6170
|
+
} else if (entry.isFile() && (!targetSuffix || entry.name.toLowerCase().endsWith(targetSuffix))) {
|
|
6171
|
+
let mtimeMs = 0;
|
|
6172
|
+
try { mtimeMs = statSync(path).mtimeMs; } catch { /* partial rollout */ }
|
|
6173
|
+
candidates.push({ path, mtimeMs });
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
};
|
|
6177
|
+
collectRollouts(sessionsRoot);
|
|
6178
|
+
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
6179
|
+
// A rollout's provider and cwd live in its first `session_meta` record, so a
|
|
6180
|
+
// bounded header read is enough — loading whole multi-MB transcripts just to
|
|
6181
|
+
// pick a provider would multiply resume's startup cost (#743 review).
|
|
6182
|
+
for (const candidate of candidates) {
|
|
6183
|
+
const meta = readRolloutSessionMeta(candidate.path);
|
|
6184
|
+
if (!meta) continue;
|
|
6185
|
+
// Without `--all`, Codex's `--last` picks the newest session recorded in
|
|
6186
|
+
// the current directory; a newer rollout elsewhere must not win it.
|
|
6187
|
+
if (!requestedId && !showAll && meta.cwd !== currentDirectory) continue;
|
|
6188
|
+
return typeof meta.model_provider === "string" ? meta.model_provider : null;
|
|
6189
|
+
}
|
|
6190
|
+
return null;
|
|
6191
|
+
}
|
|
6192
|
+
|
|
6193
|
+
function readRolloutSessionMeta(path) {
|
|
6194
|
+
let fd;
|
|
6195
|
+
try {
|
|
6196
|
+
fd = openSync(path, "r");
|
|
6197
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
6198
|
+
const bytesRead = readSync(fd, buffer, 0, buffer.length, 0);
|
|
6199
|
+
const head = buffer.toString("utf8", 0, bytesRead);
|
|
6200
|
+
for (const line of head.split(/\r?\n/).slice(0, 12)) {
|
|
6201
|
+
if (!line.trim()) continue;
|
|
6202
|
+
let record;
|
|
6203
|
+
try {
|
|
6204
|
+
record = JSON.parse(line);
|
|
6205
|
+
} catch {
|
|
6206
|
+
// The bounded read can cut a line mid-record; earlier records
|
|
6207
|
+
// already parsed still count.
|
|
6208
|
+
break;
|
|
6209
|
+
}
|
|
6210
|
+
if (record?.type === "session_meta" && record.payload && typeof record.payload === "object") {
|
|
6211
|
+
return {
|
|
6212
|
+
model_provider: record.payload.model_provider,
|
|
6213
|
+
cwd: typeof record.payload.cwd === "string" ? record.payload.cwd : null,
|
|
6214
|
+
};
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6217
|
+
} catch {
|
|
6218
|
+
return null;
|
|
6219
|
+
} finally {
|
|
6220
|
+
if (fd !== undefined) {
|
|
6221
|
+
try { closeSync(fd); } catch { /* best effort */ }
|
|
6222
|
+
}
|
|
6223
|
+
}
|
|
6224
|
+
return null;
|
|
6225
|
+
}
|
|
6226
|
+
|
|
6099
6227
|
function shouldUseRuntimeRoutingForForwardedArgs(rawArgs) {
|
|
6100
6228
|
if (!Array.isArray(rawArgs) || rawArgs.length === 0) {
|
|
6101
6229
|
return true;
|
|
@@ -6169,7 +6297,16 @@ function shouldTrackForwardedRuntimeObservability(rawArgs) {
|
|
|
6169
6297
|
}
|
|
6170
6298
|
|
|
6171
6299
|
function shouldCaptureForwardedOutputForArgs(rawArgs, env) {
|
|
6172
|
-
|
|
6300
|
+
// Codex's TUI and resume/fork commands require their child stdout to remain
|
|
6301
|
+
// a terminal. Capturing them into a pipe (even when an explicit retry
|
|
6302
|
+
// override is set) makes the official CLI abort with "stdout is not a
|
|
6303
|
+
// terminal" before it can load the requested session. Interactive commands
|
|
6304
|
+
// never use the wrapper's non-interactive unsupported-model retry parser.
|
|
6305
|
+
if (
|
|
6306
|
+
isCodexAppServerCommand(rawArgs) ||
|
|
6307
|
+
isCodexInteractiveTuiCommand(rawArgs) ||
|
|
6308
|
+
isCodexInteractiveResumeCommand(rawArgs)
|
|
6309
|
+
) {
|
|
6173
6310
|
return false;
|
|
6174
6311
|
}
|
|
6175
6312
|
return shouldCaptureForwardedCodexOutput(env);
|