codex-multi-auth 0.1.8 → 1.1.10

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 CHANGED
@@ -50,7 +50,6 @@ Codex CLI-first multi-account OAuth manager for the official `@openai/codex` CLI
50
50
  ### Option A: Standard install
51
51
 
52
52
  ```bash
53
- npm i -g @openai/codex
54
53
  npm i -g codex-multi-auth
55
54
  ```
56
55
 
@@ -75,18 +74,16 @@ codex auth status
75
74
 
76
75
  ### Step-by-step
77
76
 
78
- 1. Install global packages:
79
- - `npm i -g @openai/codex`
77
+ 1. Install global package:
80
78
  - `npm i -g codex-multi-auth`
81
79
  2. Run first login flow with `codex auth login`
82
- 3. Validate state with `codex auth list` and `codex auth check`
80
+ 3. Validate state with `codex auth status` and `codex auth check`
83
81
  4. Confirm routing with `codex auth forecast --live`
84
82
 
85
83
  ### Verification
86
84
 
87
85
  ```bash
88
86
  codex auth status
89
- codex auth list
90
87
  codex auth check
91
88
  ```
92
89
 
@@ -98,7 +95,7 @@ codex auth check
98
95
 
99
96
  ```bash
100
97
  codex auth login
101
- codex auth list
98
+ codex auth status
102
99
  codex auth check
103
100
  codex auth forecast --live
104
101
  ```
@@ -278,9 +275,9 @@ codex auth doctor --json
278
275
 
279
276
  ## Release Notes
280
277
 
281
- - Current stable: [docs/releases/v0.1.5.md](docs/releases/v0.1.5.md)
282
- - Previous stable: [docs/releases/v0.1.4.md](docs/releases/v0.1.4.md)
283
- - Earlier stable: [docs/releases/v0.1.3.md](docs/releases/v0.1.3.md)
278
+ - Current stable: [docs/releases/v1.1.10.md](docs/releases/v1.1.10.md)
279
+ - Previous stable: [docs/releases/v0.1.9.md](docs/releases/v0.1.9.md)
280
+ - Earlier stable: [docs/releases/v0.1.8.md](docs/releases/v0.1.8.md)
284
281
  - Archived prerelease: [docs/releases/v0.1.0-beta.0.md](docs/releases/v0.1.0-beta.0.md)
285
282
 
286
283
  ## License
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAC;AAmL5D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MAk2H/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,KAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAC;AAmL5D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MA60H/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,KAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ import { initLogger, logRequest, logDebug, logInfo, logWarn, logError, setCorrel
34
34
  import { checkAndNotify } from "./lib/auto-update-checker.js";
35
35
  import { handleContextOverflow } from "./lib/context-overflow.js";
36
36
  import { AccountManager, getAccountIdCandidates, extractAccountEmail, extractAccountId, formatAccountLabel, formatCooldown, formatWaitTime, sanitizeEmail, selectBestAccountCandidate, shouldUpdateAccountIdFromToken, resolveRequestAccountId, parseRateLimitReason, lookupCodexCliTokensByEmail, isCodexCliSyncEnabled, } from "./lib/accounts.js";
37
- import { getStoragePath, loadAccounts, saveAccounts, withAccountStorageTransaction, clearAccounts, setStoragePath, exportAccounts, importAccounts, loadFlaggedAccounts, saveFlaggedAccounts, clearFlaggedAccounts, normalizeEmailKey, StorageError, formatStorageErrorHint, setStorageBackupEnabled, } from "./lib/storage.js";
37
+ import { getStoragePath, loadAccounts, saveAccounts, withAccountStorageTransaction, clearAccounts, setStoragePath, exportAccounts, importAccounts, loadFlaggedAccounts, saveFlaggedAccounts, clearFlaggedAccounts, findMatchingAccountIndex, StorageError, formatStorageErrorHint, setStorageBackupEnabled, } from "./lib/storage.js";
38
38
  import { createCodexHeaders, extractRequestUrl, handleErrorResponse, handleSuccessResponse, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, refreshAndUpdateToken, rewriteUrlForCodex, shouldRefreshToken, transformRequestForCodex, } from "./lib/request/fetch-helpers.js";
39
39
  import { applyFastSessionDefaults } from "./lib/request/request-transformer.js";
40
40
  import { getRateLimitBackoff, RATE_LIMIT_SHORT_RETRY_THRESHOLD_MS, resetRateLimitBackoff, } from "./lib/request/rate-limit-backoff.js";
@@ -315,24 +315,6 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
315
315
  const now = Date.now();
316
316
  const stored = replaceAll ? null : loadedStorage;
317
317
  const accounts = stored?.accounts ? [...stored.accounts] : [];
318
- const indexByRefreshToken = new Map();
319
- const indexByAccountId = new Map();
320
- const indexByEmail = new Map();
321
- for (let i = 0; i < accounts.length; i += 1) {
322
- const account = accounts[i];
323
- if (!account)
324
- continue;
325
- if (account.refreshToken) {
326
- indexByRefreshToken.set(account.refreshToken, i);
327
- }
328
- if (account.accountId) {
329
- indexByAccountId.set(account.accountId, i);
330
- }
331
- const emailKey = normalizeEmailKey(account.email);
332
- if (emailKey) {
333
- indexByEmail.set(emailKey, i);
334
- }
335
- }
336
318
  for (const result of results) {
337
319
  const accountId = result.accountIdOverride ?? extractAccountId(result.access);
338
320
  const accountIdSource = accountId
@@ -341,16 +323,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
341
323
  : undefined;
342
324
  const accountLabel = result.accountLabel;
343
325
  const accountEmail = sanitizeEmail(extractAccountEmail(result.access, result.idToken));
344
- const existingByEmail = accountEmail && indexByEmail.has(accountEmail)
345
- ? indexByEmail.get(accountEmail)
346
- : undefined;
347
- const existingById = accountId && indexByAccountId.has(accountId)
348
- ? indexByAccountId.get(accountId)
349
- : undefined;
350
- const existingByToken = indexByRefreshToken.get(result.refresh);
351
- const existingIndex = existingById ?? existingByEmail ?? existingByToken;
326
+ const existingIndex = findMatchingAccountIndex(accounts, {
327
+ accountId,
328
+ email: accountEmail,
329
+ refreshToken: result.refresh,
330
+ }, {
331
+ allowUniqueAccountIdFallbackWithoutEmail: true,
332
+ });
352
333
  if (existingIndex === undefined) {
353
- const newIndex = accounts.length;
354
334
  accounts.push({
355
335
  accountId,
356
336
  accountIdSource,
@@ -362,20 +342,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
362
342
  addedAt: now,
363
343
  lastUsed: now,
364
344
  });
365
- indexByRefreshToken.set(result.refresh, newIndex);
366
- if (accountId) {
367
- indexByAccountId.set(accountId, newIndex);
368
- }
369
- if (accountEmail) {
370
- indexByEmail.set(accountEmail, newIndex);
371
- }
372
345
  continue;
373
346
  }
374
347
  const existing = accounts[existingIndex];
375
348
  if (!existing)
376
349
  continue;
377
- const oldToken = existing.refreshToken;
378
- const oldEmail = existing.email;
379
350
  const nextEmail = accountEmail ?? sanitizeEmail(existing.email);
380
351
  const nextAccountId = accountId ?? existing.accountId;
381
352
  const nextAccountIdSource = accountId ? accountIdSource ?? existing.accountIdSource : existing.accountIdSource;
@@ -391,21 +362,6 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
391
362
  expiresAt: result.expires,
392
363
  lastUsed: now,
393
364
  };
394
- if (oldToken !== result.refresh) {
395
- indexByRefreshToken.delete(oldToken);
396
- indexByRefreshToken.set(result.refresh, existingIndex);
397
- }
398
- if (accountId) {
399
- indexByAccountId.set(accountId, existingIndex);
400
- }
401
- const oldEmailKey = normalizeEmailKey(oldEmail);
402
- const nextEmailKey = normalizeEmailKey(nextEmail);
403
- if (oldEmailKey && oldEmailKey !== nextEmailKey) {
404
- indexByEmail.delete(oldEmailKey);
405
- }
406
- if (nextEmailKey) {
407
- indexByEmail.set(nextEmailKey, existingIndex);
408
- }
409
365
  }
410
366
  if (accounts.length === 0)
411
367
  return;
@@ -1023,6 +979,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1023
979
  const accountCount = accountManager.getAccountCount();
1024
980
  const attempted = new Set();
1025
981
  let restartAccountTraversalWithFallback = false;
982
+ let retryNextAccountBeforeFallback = false;
1026
983
  let usedPreferredSessionAccount = false;
1027
984
  const capabilityBoostByAccount = {};
1028
985
  const accountSnapshotSource = accountManager;
@@ -1111,22 +1068,23 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1111
1068
  const hadAccountId = !!account.accountId;
1112
1069
  const tokenAccountId = extractAccountId(accountAuth.access);
1113
1070
  const accountId = resolveRequestAccountId(account.accountId, account.accountIdSource, tokenAccountId);
1114
- const entitlementAccountKey = resolveEntitlementAccountKey({
1115
- accountId: hadAccountId ? account.accountId : undefined,
1116
- email: account.email,
1117
- index: account.index,
1118
- });
1119
1071
  if (!accountId) {
1120
1072
  accountManager.markAccountCoolingDown(account, ACCOUNT_LIMITS.AUTH_FAILURE_COOLDOWN_MS, "auth-failure");
1121
1073
  accountManager.saveToDiskDebounced();
1122
1074
  continue;
1123
1075
  }
1076
+ const resolvedEmail = extractAccountEmail(accountAuth.access) ?? account.email;
1077
+ const entitlementAccountKey = resolveEntitlementAccountKey({
1078
+ accountId: account.accountId ?? accountId,
1079
+ email: resolvedEmail,
1080
+ refreshToken: account.refreshToken,
1081
+ index: account.index,
1082
+ });
1124
1083
  account.accountId = accountId;
1125
1084
  if (!hadAccountId && tokenAccountId && accountId === tokenAccountId) {
1126
1085
  account.accountIdSource = account.accountIdSource ?? "token";
1127
1086
  }
1128
- account.email =
1129
- extractAccountEmail(accountAuth.access) ?? account.email;
1087
+ account.email = resolvedEmail;
1130
1088
  const entitlementBlock = entitlementCache.isBlocked(entitlementAccountKey, model ?? modelFamily);
1131
1089
  if (entitlementBlock.blocked) {
1132
1090
  runtimeMetrics.accountRotations++;
@@ -1299,6 +1257,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1299
1257
  fallbackApplied: false,
1300
1258
  fallbackReason: "unsupported-model-entitlement",
1301
1259
  });
1260
+ retryNextAccountBeforeFallback = true;
1302
1261
  break;
1303
1262
  }
1304
1263
  const fallbackModel = resolveUnsupportedCodexFallbackModel({
@@ -1626,7 +1585,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1626
1585
  if (successAccountForResponse.index !== account.index) {
1627
1586
  accountManager.markSwitched(successAccountForResponse, "rotation", modelFamily);
1628
1587
  }
1629
- const successAccountKey = resolveEntitlementAccountKey(successAccountForResponse);
1588
+ const successAccountKey = successAccountForResponse.index === account.index
1589
+ ? entitlementAccountKey
1590
+ : resolveEntitlementAccountKey(successAccountForResponse);
1630
1591
  accountManager.recordSuccess(successAccountForResponse, modelFamily, model);
1631
1592
  capabilityPolicyStore.recordSuccess(successAccountKey, capabilityModelKey);
1632
1593
  entitlementCache.clear(successAccountKey, capabilityModelKey);
@@ -1639,6 +1600,10 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1639
1600
  }
1640
1601
  return successResponse;
1641
1602
  }
1603
+ if (retryNextAccountBeforeFallback) {
1604
+ retryNextAccountBeforeFallback = false;
1605
+ continue;
1606
+ }
1642
1607
  if (restartAccountTraversalWithFallback) {
1643
1608
  break;
1644
1609
  }
@@ -2398,9 +2363,17 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2398
2363
  const accountId = resolved.accountIdOverride ?? extractAccountId(resolved.access);
2399
2364
  const label = resolved.accountLabel ?? email ?? accountId ?? "Unknown account";
2400
2365
  logInfo(`Authenticated as: ${label}`);
2401
- const isDuplicate = accounts.some((account) => (accountId &&
2402
- (account.accountIdOverride ?? extractAccountId(account.access)) === accountId) ||
2403
- (email && extractAccountEmail(account.access, account.idToken) === email));
2366
+ const isDuplicate = findMatchingAccountIndex(accounts.map((account) => ({
2367
+ accountId: account.accountIdOverride ?? extractAccountId(account.access),
2368
+ email: sanitizeEmail(extractAccountEmail(account.access, account.idToken)),
2369
+ refreshToken: account.refresh,
2370
+ })), {
2371
+ accountId,
2372
+ email: sanitizeEmail(email),
2373
+ refreshToken: resolved.refresh,
2374
+ }, {
2375
+ allowUniqueAccountIdFallbackWithoutEmail: true,
2376
+ }) !== undefined;
2404
2377
  if (isDuplicate) {
2405
2378
  logWarn(`WARNING: duplicate account login detected (${label}). Existing entry will be updated.`);
2406
2379
  }