codex-multi-auth 2.21.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.
Files changed (70) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +8 -5
  3. package/config/schema/config.schema.json +8 -0
  4. package/dist/lib/accounts.d.ts +5 -0
  5. package/dist/lib/accounts.js +33 -4
  6. package/dist/lib/api-route-store.d.ts +7 -0
  7. package/dist/lib/api-route-store.js +100 -31
  8. package/dist/lib/auth/account-access.js +2 -1
  9. package/dist/lib/auth/auth.js +3 -2
  10. package/dist/lib/auth/device-auth.js +2 -1
  11. package/dist/lib/auth/http.d.ts +1 -0
  12. package/dist/lib/auth/http.js +1 -0
  13. package/dist/lib/codex-manager/account-manager-commands.js +1 -0
  14. package/dist/lib/codex-manager/api-login-menu.d.ts +1 -1
  15. package/dist/lib/codex-manager/api-login-menu.js +4 -6
  16. package/dist/lib/codex-manager/commands/locale.d.ts +12 -0
  17. package/dist/lib/codex-manager/commands/locale.js +25 -0
  18. package/dist/lib/codex-manager/commands/status.d.ts +5 -1
  19. package/dist/lib/codex-manager/commands/status.js +501 -13
  20. package/dist/lib/codex-manager/help.js +3 -1
  21. package/dist/lib/codex-manager.js +98 -23
  22. package/dist/lib/config.js +3 -0
  23. package/dist/lib/constants.d.ts +1 -0
  24. package/dist/lib/constants.js +1 -0
  25. package/dist/lib/locale.d.ts +7 -0
  26. package/dist/lib/locale.js +35 -0
  27. package/dist/lib/model-route-policy.d.ts +1 -0
  28. package/dist/lib/model-route-policy.js +2 -1
  29. package/dist/lib/quota-probe.js +2 -1
  30. package/dist/lib/request/proxy-fetch.d.ts +5 -0
  31. package/dist/lib/request/proxy-fetch.js +37 -0
  32. package/dist/lib/request/url-rewriting.js +17 -0
  33. package/dist/lib/runtime/account-model-catalog.d.ts +3 -0
  34. package/dist/lib/runtime/account-model-catalog.js +7 -2
  35. package/dist/lib/runtime/api-model-capabilities.d.ts +11 -2
  36. package/dist/lib/runtime/api-model-capabilities.js +155 -24
  37. package/dist/lib/runtime/app-bind.d.ts +7 -0
  38. package/dist/lib/runtime/app-bind.js +92 -1
  39. package/dist/lib/runtime/automatic-api-checks.d.ts +15 -0
  40. package/dist/lib/runtime/automatic-api-checks.js +188 -0
  41. package/dist/lib/runtime/model-discovery-status.d.ts +11 -1
  42. package/dist/lib/runtime/model-discovery-status.js +64 -49
  43. package/dist/lib/runtime/persisted-api-capabilities.d.ts +20 -0
  44. package/dist/lib/runtime/persisted-api-capabilities.js +387 -0
  45. package/dist/lib/runtime/responses-websocket.d.ts +48 -5
  46. package/dist/lib/runtime/responses-websocket.js +275 -39
  47. package/dist/lib/runtime/rotation-account-selection.d.ts +25 -5
  48. package/dist/lib/runtime/rotation-account-selection.js +94 -4
  49. package/dist/lib/runtime/rotation-proxy-state.d.ts +26 -0
  50. package/dist/lib/runtime/rotation-proxy-state.js +376 -4
  51. package/dist/lib/runtime/rotation-server-types.d.ts +6 -0
  52. package/dist/lib/runtime/rotation-token-refresh.d.ts +23 -1
  53. package/dist/lib/runtime/rotation-token-refresh.js +218 -27
  54. package/dist/lib/runtime-rotation-proxy.js +601 -105
  55. package/dist/lib/schemas.d.ts +5 -0
  56. package/dist/lib/schemas.js +1 -0
  57. package/dist/lib/storage/credential-sidecars.js +34 -2
  58. package/dist/lib/storage/file-lock.d.ts +2 -0
  59. package/dist/lib/storage/file-lock.js +44 -30
  60. package/dist/lib/usage/ledger.js +1 -0
  61. package/dist/lib/usage/redaction.js +1 -0
  62. package/dist/lib/usage/types.d.ts +1 -1
  63. package/dist/lib/web-dashboard-html.d.ts +1 -0
  64. package/dist/lib/web-dashboard-html.js +66 -0
  65. package/dist/lib/web-dashboard.d.ts +36 -0
  66. package/dist/lib/web-dashboard.js +322 -0
  67. package/package.json +3 -1
  68. package/scripts/codex-app-router.js +86 -14
  69. package/scripts/codex-routing.js +1 -0
  70. 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
- const providerTable = `model_providers.${RUNTIME_ROTATION_PROXY_PROVIDER_ID}`;
4270
- const configArgs = [
4271
- `${providerTable}.name=${configTomlModule.tomlStringLiteral(APP_SERVER_ACCOUNT_DISPLAY_NAME)}`,
4272
- `${providerTable}.base_url=${configTomlModule.tomlStringLiteral(proxyBaseUrl)}`,
4273
- `${providerTable}.env_key=${configTomlModule.tomlStringLiteral("OPENAI_API_KEY")}`,
4274
- `${providerTable}.requires_openai_auth=false`,
4275
- `${providerTable}.http_headers.x-openai-actor-authorization=${configTomlModule.tomlStringLiteral("codex-multi-auth-local")}`,
4276
- `${providerTable}.wire_api=${configTomlModule.tomlStringLiteral("responses")}`,
4277
- "disable_response_storage=false",
4278
- ].flatMap((assignment) => ["-c", assignment]);
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(RUNTIME_ROTATION_PROXY_PROVIDER_ID)}`,
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(RUNTIME_ROTATION_PROXY_PROVIDER_ID)}`,
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
- if (isCodexAppServerCommand(rawArgs)) {
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);