codex-multi-auth 1.1.10 → 1.2.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/README.md +19 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +185 -29
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts.d.ts +22 -2
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +84 -1
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/auth/browser.d.ts +1 -0
- package/dist/lib/auth/browser.d.ts.map +1 -1
- package/dist/lib/auth/browser.js +13 -0
- package/dist/lib/auth/browser.js.map +1 -1
- package/dist/lib/auth/token-utils.d.ts +16 -0
- package/dist/lib/auth/token-utils.d.ts.map +1 -1
- package/dist/lib/auth/token-utils.js +13 -0
- package/dist/lib/auth/token-utils.js.map +1 -1
- package/dist/lib/codex-manager/settings-hub.d.ts +21 -0
- package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -1
- package/dist/lib/codex-manager/settings-hub.js +48 -65
- package/dist/lib/codex-manager/settings-hub.js.map +1 -1
- package/dist/lib/codex-manager.d.ts +1 -0
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +517 -78
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts +18 -0
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +157 -0
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/schemas.d.ts +7 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +7 -1
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/storage/migrations.d.ts +5 -2
- package/dist/lib/storage/migrations.d.ts.map +1 -1
- package/dist/lib/storage/migrations.js.map +1 -1
- package/dist/lib/storage.d.ts +10 -0
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +191 -40
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/ui/copy.d.ts +24 -4
- package/dist/lib/ui/copy.d.ts.map +1 -1
- package/dist/lib/ui/copy.js +24 -4
- package/dist/lib/ui/copy.js.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -109,6 +109,21 @@ codex auth fix --dry-run
|
|
|
109
109
|
codex auth doctor --fix
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
+
If the shell should not launch a browser, use the manual callback flow:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
codex auth login --manual
|
|
116
|
+
CODEX_AUTH_NO_BROWSER=1 codex auth login
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
In non-TTY/manual shells, provide the full redirect URL on stdin instead of waiting for a browser callback:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
echo "http://127.0.0.1:1455/auth/callback?code=..." | codex auth login --manual
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
No new npm scripts or storage migration steps are required for this login-flow update.
|
|
126
|
+
|
|
112
127
|
---
|
|
113
128
|
|
|
114
129
|
## Command Toolkit
|
|
@@ -234,6 +249,7 @@ codex auth login
|
|
|
234
249
|
- `codex auth` unrecognized: run `where codex`, then follow `docs/troubleshooting.md` for routing fallback commands
|
|
235
250
|
- Switch succeeds but wrong account appears active: run `codex auth switch <index>`, then restart session
|
|
236
251
|
- OAuth callback on port `1455` fails: free the port and re-run `codex auth login`
|
|
252
|
+
- Browser launch is blocked or you are in a headless shell: re-run `codex auth login --manual` or set `CODEX_AUTH_NO_BROWSER=1`
|
|
237
253
|
- `missing field id_token` / `token_expired` / `refresh_token_reused`: re-login affected account
|
|
238
254
|
|
|
239
255
|
</details>
|
|
@@ -275,9 +291,9 @@ codex auth doctor --json
|
|
|
275
291
|
|
|
276
292
|
## Release Notes
|
|
277
293
|
|
|
278
|
-
- Current stable: [docs/releases/v1.
|
|
279
|
-
- Previous stable: [docs/releases/
|
|
280
|
-
- Earlier stable: [docs/releases/v0.1.
|
|
294
|
+
- Current stable: [docs/releases/v1.2.0.md](docs/releases/v1.2.0.md)
|
|
295
|
+
- Previous stable: [docs/releases/v1.1.10.md](docs/releases/v1.1.10.md)
|
|
296
|
+
- Earlier stable: [docs/releases/v0.1.9.md](docs/releases/v0.1.9.md)
|
|
281
297
|
- Archived prerelease: [docs/releases/v0.1.0-beta.0.md](docs/releases/v0.1.0-beta.0.md)
|
|
282
298
|
|
|
283
299
|
## License
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAC;AAuL5D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MAwhI/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,KAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
import { tool } from "@codex-ai/plugin/tool";
|
|
26
26
|
import { createAuthorizationFlow, exchangeAuthorizationCode, parseAuthorizationInput, redactOAuthUrlForLog, REDIRECT_URI, } from "./lib/auth/auth.js";
|
|
27
27
|
import { queuedRefresh } from "./lib/refresh-queue.js";
|
|
28
|
-
import { openBrowserUrl } from "./lib/auth/browser.js";
|
|
28
|
+
import { isBrowserLaunchSuppressed, openBrowserUrl } from "./lib/auth/browser.js";
|
|
29
29
|
import { startLocalOAuthServer } from "./lib/auth/server.js";
|
|
30
30
|
import { promptAddAnotherAccount, promptLoginMode } from "./lib/cli.js";
|
|
31
31
|
import { getCodexMode, getFastSession, getFastSessionStrategy, getFastSessionMaxInputItems, getRateLimitToastDebounceMs, getRetryAllAccountsMaxRetries, getRetryAllAccountsMaxWaitMs, getRetryAllAccountsRateLimited, getFallbackToGpt52OnUnsupportedGpt53, getUnsupportedCodexPolicy, getUnsupportedCodexFallbackChain, getTokenRefreshSkewMs, getSessionRecovery, getAutoResume, getToastDurationMs, getPerProjectAccounts, getEmptyResponseMaxRetries, getEmptyResponseRetryDelayMs, getPidOffsetEnabled, getFetchTimeoutMs, getStreamStallTimeoutMs, getCodexTuiV2, getCodexTuiColorProfile, getCodexTuiGlyphMode, getLiveAccountSync, getLiveAccountSyncDebounceMs, getLiveAccountSyncPollMs, getSessionAffinity, getSessionAffinityTtlMs, getSessionAffinityMaxEntries, getProactiveRefreshGuardian, getProactiveRefreshIntervalMs, getProactiveRefreshBufferMs, getNetworkErrorCooldownMs, getServerErrorCooldownMs, getStorageBackupEnabled, getPreemptiveQuotaEnabled, getPreemptiveQuotaRemainingPercent5h, getPreemptiveQuotaRemainingPercent7d, getPreemptiveQuotaMaxDeferralMs, loadPluginConfig, } from "./lib/config.js";
|
|
@@ -33,9 +33,9 @@ import { AUTH_LABELS, CODEX_BASE_URL, DUMMY_API_KEY, LOG_STAGES, PLUGIN_NAME, PR
|
|
|
33
33
|
import { initLogger, logRequest, logDebug, logInfo, logWarn, logError, setCorrelationId, clearCorrelationId, } from "./lib/logger.js";
|
|
34
34
|
import { checkAndNotify } from "./lib/auto-update-checker.js";
|
|
35
35
|
import { handleContextOverflow } from "./lib/context-overflow.js";
|
|
36
|
-
import { AccountManager, getAccountIdCandidates, extractAccountEmail, extractAccountId, formatAccountLabel, formatCooldown, formatWaitTime, sanitizeEmail, selectBestAccountCandidate, shouldUpdateAccountIdFromToken, resolveRequestAccountId, parseRateLimitReason, lookupCodexCliTokensByEmail, isCodexCliSyncEnabled, } from "./lib/accounts.js";
|
|
36
|
+
import { AccountManager, getAccountIdCandidates, extractAccountEmail, extractAccountId, formatAccountLabel, formatCooldown, formatWaitTime, resolveRuntimeRequestIdentity, sanitizeEmail, selectBestAccountCandidate, shouldUpdateAccountIdFromToken, resolveRequestAccountId, parseRateLimitReason, lookupCodexCliTokensByEmail, isCodexCliSyncEnabled, } from "./lib/accounts.js";
|
|
37
37
|
import { getStoragePath, loadAccounts, saveAccounts, withAccountStorageTransaction, clearAccounts, setStoragePath, exportAccounts, importAccounts, loadFlaggedAccounts, saveFlaggedAccounts, clearFlaggedAccounts, findMatchingAccountIndex, StorageError, formatStorageErrorHint, setStorageBackupEnabled, } from "./lib/storage.js";
|
|
38
|
-
import { createCodexHeaders, extractRequestUrl, handleErrorResponse, handleSuccessResponse, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, refreshAndUpdateToken, rewriteUrlForCodex, shouldRefreshToken, transformRequestForCodex, } from "./lib/request/fetch-helpers.js";
|
|
38
|
+
import { applyProxyCompatibleInit, createCodexHeaders, extractRequestUrl, handleErrorResponse, handleSuccessResponse, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, refreshAndUpdateToken, rewriteUrlForCodex, shouldRefreshToken, transformRequestForCodex, isWorkspaceDisabledError, } 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";
|
|
41
41
|
import { isEmptyResponse } from "./lib/request/response-handler.js";
|
|
@@ -213,6 +213,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
213
213
|
if (candidates.length === 0) {
|
|
214
214
|
return tokens;
|
|
215
215
|
}
|
|
216
|
+
// Convert candidates to workspaces
|
|
217
|
+
const workspaces = candidates.map((c) => ({
|
|
218
|
+
id: c.accountId,
|
|
219
|
+
name: c.label,
|
|
220
|
+
enabled: true,
|
|
221
|
+
isDefault: c.isDefault,
|
|
222
|
+
}));
|
|
216
223
|
if (candidates.length === 1) {
|
|
217
224
|
const [candidate] = candidates;
|
|
218
225
|
if (candidate) {
|
|
@@ -221,6 +228,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
221
228
|
accountIdOverride: candidate.accountId,
|
|
222
229
|
accountIdSource: candidate.source,
|
|
223
230
|
accountLabel: candidate.label,
|
|
231
|
+
workspaces,
|
|
224
232
|
};
|
|
225
233
|
}
|
|
226
234
|
}
|
|
@@ -234,6 +242,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
234
242
|
accountIdOverride: choice.accountId,
|
|
235
243
|
accountIdSource: choice.source ?? "token",
|
|
236
244
|
accountLabel: choice.label,
|
|
245
|
+
workspaces,
|
|
237
246
|
};
|
|
238
247
|
};
|
|
239
248
|
const buildManualOAuthFlow = (pkce, url, expectedState, onSuccess) => ({
|
|
@@ -331,6 +340,18 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
331
340
|
allowUniqueAccountIdFallbackWithoutEmail: true,
|
|
332
341
|
});
|
|
333
342
|
if (existingIndex === undefined) {
|
|
343
|
+
const initialWorkspaceIndex = result.workspaces && result.workspaces.length > 0
|
|
344
|
+
? (() => {
|
|
345
|
+
if (accountId) {
|
|
346
|
+
const matchingWorkspaceIndex = result.workspaces.findIndex((workspace) => workspace.id === accountId);
|
|
347
|
+
if (matchingWorkspaceIndex >= 0) {
|
|
348
|
+
return matchingWorkspaceIndex;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const firstEnabledWorkspaceIndex = result.workspaces.findIndex((workspace) => workspace.enabled !== false);
|
|
352
|
+
return firstEnabledWorkspaceIndex >= 0 ? firstEnabledWorkspaceIndex : 0;
|
|
353
|
+
})()
|
|
354
|
+
: undefined;
|
|
334
355
|
accounts.push({
|
|
335
356
|
accountId,
|
|
336
357
|
accountIdSource,
|
|
@@ -341,6 +362,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
341
362
|
expiresAt: result.expires,
|
|
342
363
|
addedAt: now,
|
|
343
364
|
lastUsed: now,
|
|
365
|
+
workspaces: result.workspaces,
|
|
366
|
+
currentWorkspaceIndex: initialWorkspaceIndex,
|
|
344
367
|
});
|
|
345
368
|
continue;
|
|
346
369
|
}
|
|
@@ -351,6 +374,38 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
351
374
|
const nextAccountId = accountId ?? existing.accountId;
|
|
352
375
|
const nextAccountIdSource = accountId ? accountIdSource ?? existing.accountIdSource : existing.accountIdSource;
|
|
353
376
|
const nextAccountLabel = accountLabel ?? existing.accountLabel;
|
|
377
|
+
// Preserve tracked workspace state when auth refreshes do not return workspace metadata.
|
|
378
|
+
const mergedWorkspaces = result.workspaces
|
|
379
|
+
? result.workspaces.map((newWs) => {
|
|
380
|
+
const existingWs = existing.workspaces?.find((w) => w.id === newWs.id);
|
|
381
|
+
return existingWs
|
|
382
|
+
? {
|
|
383
|
+
...newWs,
|
|
384
|
+
enabled: existingWs.enabled,
|
|
385
|
+
disabledAt: existingWs.disabledAt,
|
|
386
|
+
}
|
|
387
|
+
: newWs;
|
|
388
|
+
})
|
|
389
|
+
: existing.workspaces;
|
|
390
|
+
const currentWorkspaceId = existing.workspaces?.[typeof existing.currentWorkspaceIndex === "number"
|
|
391
|
+
? existing.currentWorkspaceIndex
|
|
392
|
+
: 0]?.id;
|
|
393
|
+
const nextCurrentWorkspaceIndex = mergedWorkspaces && mergedWorkspaces.length > 0
|
|
394
|
+
? (() => {
|
|
395
|
+
if (currentWorkspaceId) {
|
|
396
|
+
const matchingWorkspaceIndex = mergedWorkspaces.findIndex((workspace) => workspace.id === currentWorkspaceId);
|
|
397
|
+
if (matchingWorkspaceIndex >= 0) {
|
|
398
|
+
return matchingWorkspaceIndex;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const defaultWorkspaceIndex = mergedWorkspaces.findIndex((workspace) => workspace.isDefault === true);
|
|
402
|
+
if (defaultWorkspaceIndex >= 0) {
|
|
403
|
+
return defaultWorkspaceIndex;
|
|
404
|
+
}
|
|
405
|
+
const firstEnabledWorkspaceIndex = mergedWorkspaces.findIndex((workspace) => workspace.enabled !== false);
|
|
406
|
+
return firstEnabledWorkspaceIndex >= 0 ? firstEnabledWorkspaceIndex : 0;
|
|
407
|
+
})()
|
|
408
|
+
: existing.currentWorkspaceIndex;
|
|
354
409
|
accounts[existingIndex] = {
|
|
355
410
|
...existing,
|
|
356
411
|
accountId: nextAccountId,
|
|
@@ -361,6 +416,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
361
416
|
accessToken: result.access,
|
|
362
417
|
expiresAt: result.expires,
|
|
363
418
|
lastUsed: now,
|
|
419
|
+
workspaces: mergedWorkspaces,
|
|
420
|
+
currentWorkspaceIndex: nextCurrentWorkspaceIndex,
|
|
364
421
|
};
|
|
365
422
|
}
|
|
366
423
|
if (accounts.length === 0)
|
|
@@ -999,7 +1056,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
999
1056
|
const accountKey = resolveEntitlementAccountKey(candidate);
|
|
1000
1057
|
capabilityBoostByAccount[candidate.index] = capabilityPolicyStore.getBoost(accountKey, model ?? modelFamily);
|
|
1001
1058
|
}
|
|
1002
|
-
while (attempted.size < Math.max(1, accountCount)) {
|
|
1059
|
+
accountAttemptLoop: while (attempted.size < Math.max(1, accountCount)) {
|
|
1003
1060
|
let account = null;
|
|
1004
1061
|
if (!usedPreferredSessionAccount &&
|
|
1005
1062
|
typeof preferredSessionAccountIndex === "number") {
|
|
@@ -1065,26 +1122,34 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1065
1122
|
accountManager.saveToDiskDebounced();
|
|
1066
1123
|
continue;
|
|
1067
1124
|
}
|
|
1068
|
-
const
|
|
1069
|
-
const
|
|
1070
|
-
const
|
|
1125
|
+
const currentWorkspace = accountManager.getCurrentWorkspace(account);
|
|
1126
|
+
const storedAccountId = currentWorkspace?.id ?? account.accountId;
|
|
1127
|
+
const storedAccountIdSource = currentWorkspace
|
|
1128
|
+
? "manual"
|
|
1129
|
+
: account.accountIdSource;
|
|
1130
|
+
const storedEmail = account.email;
|
|
1131
|
+
const hadAccountId = !!storedAccountId;
|
|
1132
|
+
const runtimeIdentity = resolveRuntimeRequestIdentity({
|
|
1133
|
+
storedAccountId,
|
|
1134
|
+
source: storedAccountIdSource,
|
|
1135
|
+
storedEmail,
|
|
1136
|
+
accessToken: accountAuth.access,
|
|
1137
|
+
idToken: accountAuth.idToken,
|
|
1138
|
+
});
|
|
1139
|
+
const tokenAccountId = runtimeIdentity.tokenAccountId;
|
|
1140
|
+
const accountId = runtimeIdentity.accountId;
|
|
1071
1141
|
if (!accountId) {
|
|
1072
1142
|
accountManager.markAccountCoolingDown(account, ACCOUNT_LIMITS.AUTH_FAILURE_COOLDOWN_MS, "auth-failure");
|
|
1073
1143
|
accountManager.saveToDiskDebounced();
|
|
1074
1144
|
continue;
|
|
1075
1145
|
}
|
|
1076
|
-
const resolvedEmail =
|
|
1146
|
+
const resolvedEmail = runtimeIdentity.email;
|
|
1077
1147
|
const entitlementAccountKey = resolveEntitlementAccountKey({
|
|
1078
|
-
accountId:
|
|
1148
|
+
accountId: storedAccountId ?? accountId,
|
|
1079
1149
|
email: resolvedEmail,
|
|
1080
1150
|
refreshToken: account.refreshToken,
|
|
1081
1151
|
index: account.index,
|
|
1082
1152
|
});
|
|
1083
|
-
account.accountId = accountId;
|
|
1084
|
-
if (!hadAccountId && tokenAccountId && accountId === tokenAccountId) {
|
|
1085
|
-
account.accountIdSource = account.accountIdSource ?? "token";
|
|
1086
|
-
}
|
|
1087
|
-
account.email = resolvedEmail;
|
|
1088
1153
|
const entitlementBlock = entitlementCache.isBlocked(entitlementAccountKey, model ?? modelFamily);
|
|
1089
1154
|
if (entitlementBlock.blocked) {
|
|
1090
1155
|
runtimeMetrics.accountRotations++;
|
|
@@ -1092,6 +1157,13 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1092
1157
|
logWarn(`Skipping account ${account.index + 1} due to cached entitlement block (${formatWaitTime(entitlementBlock.waitMs)} remaining).`);
|
|
1093
1158
|
continue;
|
|
1094
1159
|
}
|
|
1160
|
+
account.accountId = accountId;
|
|
1161
|
+
if (!hadAccountId && tokenAccountId && accountId === tokenAccountId) {
|
|
1162
|
+
account.accountIdSource = storedAccountIdSource ?? "token";
|
|
1163
|
+
}
|
|
1164
|
+
if (resolvedEmail) {
|
|
1165
|
+
account.email = resolvedEmail;
|
|
1166
|
+
}
|
|
1095
1167
|
if (accountCount > 1 &&
|
|
1096
1168
|
accountManager.shouldShowAccountToast(account.index, rateLimitToastDebounceMs)) {
|
|
1097
1169
|
const accountLabel = formatAccountLabel(account, account.index);
|
|
@@ -1125,6 +1197,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1125
1197
|
}
|
|
1126
1198
|
let sameAccountRetryCount = 0;
|
|
1127
1199
|
let successAccountForResponse = account;
|
|
1200
|
+
let successEntitlementAccountKey = entitlementAccountKey;
|
|
1128
1201
|
while (true) {
|
|
1129
1202
|
let response;
|
|
1130
1203
|
const fetchStart = performance.now();
|
|
@@ -1149,11 +1222,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1149
1222
|
}
|
|
1150
1223
|
try {
|
|
1151
1224
|
runtimeMetrics.totalRequests++;
|
|
1152
|
-
response = await fetch(url, {
|
|
1225
|
+
response = await fetch(url, applyProxyCompatibleInit(url, {
|
|
1153
1226
|
...requestInit,
|
|
1154
1227
|
headers,
|
|
1155
1228
|
signal: fetchController.signal,
|
|
1156
|
-
});
|
|
1229
|
+
}));
|
|
1157
1230
|
}
|
|
1158
1231
|
catch (networkError) {
|
|
1159
1232
|
const fetchAbortReason = fetchController.signal.reason;
|
|
@@ -1331,7 +1404,54 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1331
1404
|
entitlementCache.markBlocked(entitlementAccountKey, blockedModel, "unsupported-model");
|
|
1332
1405
|
capabilityPolicyStore.recordUnsupported(entitlementAccountKey, blockedModel);
|
|
1333
1406
|
}
|
|
1334
|
-
|
|
1407
|
+
const workspaceErrorCode = errorBody?.error?.code ?? "";
|
|
1408
|
+
const workspaceErrorMessage = errorBody?.error?.message ?? "";
|
|
1409
|
+
const isDisabledWorkspaceError = isWorkspaceDisabledError(errorResponse.status, workspaceErrorCode, workspaceErrorMessage);
|
|
1410
|
+
// Handle workspace disabled/expired errors by rotating to the next workspace
|
|
1411
|
+
// within the same account before falling back to another account.
|
|
1412
|
+
if (isDisabledWorkspaceError) {
|
|
1413
|
+
runtimeMetrics.failedRequests++;
|
|
1414
|
+
runtimeMetrics.lastError = `Workspace disabled for account ${account.index + 1}`;
|
|
1415
|
+
if (!account.workspaces || account.workspaces.length === 0) {
|
|
1416
|
+
logWarn(`Workspace disabled/expired for account ${account.index + 1} without tracked workspaces. Leaving account enabled.`, { errorCode: workspaceErrorCode });
|
|
1417
|
+
if (hasRemainingAccounts) {
|
|
1418
|
+
continue accountAttemptLoop;
|
|
1419
|
+
}
|
|
1420
|
+
return errorResponse;
|
|
1421
|
+
}
|
|
1422
|
+
else {
|
|
1423
|
+
const currentWorkspace = accountManager.getCurrentWorkspace(account);
|
|
1424
|
+
const workspaceName = currentWorkspace?.name ?? currentWorkspace?.id ?? "unknown";
|
|
1425
|
+
logWarn(`Workspace disabled/expired for account ${account.index + 1} - workspace: ${workspaceName}. Rotating to next workspace.`, { errorCode: workspaceErrorCode });
|
|
1426
|
+
const disabledWorkspace = currentWorkspace
|
|
1427
|
+
? accountManager.disableCurrentWorkspace(account, currentWorkspace.id)
|
|
1428
|
+
: false;
|
|
1429
|
+
let nextWorkspace = disabledWorkspace
|
|
1430
|
+
? accountManager.rotateToNextWorkspace(account)
|
|
1431
|
+
: accountManager.getCurrentWorkspace(account);
|
|
1432
|
+
if (!disabledWorkspace && (!nextWorkspace || nextWorkspace.enabled === false)) {
|
|
1433
|
+
nextWorkspace = accountManager.rotateToNextWorkspace(account);
|
|
1434
|
+
}
|
|
1435
|
+
if (nextWorkspace) {
|
|
1436
|
+
accountManager.saveToDiskDebounced();
|
|
1437
|
+
const newWorkspaceName = nextWorkspace.name ?? nextWorkspace.id;
|
|
1438
|
+
await showToast(`Workspace ${workspaceName} disabled. Switched to ${newWorkspaceName}.`, "warning", { duration: toastDurationMs });
|
|
1439
|
+
logInfo(`Rotated to workspace ${newWorkspaceName} for account ${account.index + 1}`);
|
|
1440
|
+
// Allow the same account to be selected again with fresh request state.
|
|
1441
|
+
attempted.delete(account.index);
|
|
1442
|
+
continue accountAttemptLoop;
|
|
1443
|
+
}
|
|
1444
|
+
logWarn(`All workspaces disabled for account ${account.index + 1}. Disabling account.`);
|
|
1445
|
+
accountManager.setAccountEnabled(account.index, false);
|
|
1446
|
+
accountManager.saveToDiskDebounced();
|
|
1447
|
+
await showToast(`All workspaces disabled for account ${account.index + 1}. Switching to another account.`, "warning", { duration: toastDurationMs });
|
|
1448
|
+
// Forget session affinity and continue the outer loop so another
|
|
1449
|
+
// enabled account can service the request.
|
|
1450
|
+
sessionAffinityStore?.forgetSession(sessionAffinityKey);
|
|
1451
|
+
continue accountAttemptLoop;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
if (errorResponse.status === 403 && !unsupportedModelInfo.isUnsupported && !isDisabledWorkspaceError) {
|
|
1335
1455
|
entitlementCache.markBlocked(entitlementAccountKey, model ?? modelFamily, "plan-entitlement");
|
|
1336
1456
|
capabilityPolicyStore.recordFailure(entitlementAccountKey, capabilityModelKey);
|
|
1337
1457
|
}
|
|
@@ -1452,14 +1572,50 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1452
1572
|
});
|
|
1453
1573
|
continue;
|
|
1454
1574
|
}
|
|
1455
|
-
const
|
|
1456
|
-
const
|
|
1575
|
+
const fallbackStoredAccountId = fallbackAccount.accountId;
|
|
1576
|
+
const fallbackStoredAccountIdSource = fallbackAccount.accountIdSource;
|
|
1577
|
+
const fallbackStoredEmail = fallbackAccount.email;
|
|
1578
|
+
const hadFallbackAccountId = !!fallbackStoredAccountId;
|
|
1579
|
+
const fallbackRuntimeIdentity = resolveRuntimeRequestIdentity({
|
|
1580
|
+
storedAccountId: fallbackStoredAccountId,
|
|
1581
|
+
source: fallbackStoredAccountIdSource,
|
|
1582
|
+
storedEmail: fallbackStoredEmail,
|
|
1583
|
+
accessToken: fallbackAuth.access,
|
|
1584
|
+
idToken: fallbackAuth.idToken,
|
|
1585
|
+
});
|
|
1586
|
+
const fallbackTokenAccountId = fallbackRuntimeIdentity.tokenAccountId;
|
|
1587
|
+
const fallbackAccountId = fallbackRuntimeIdentity.accountId;
|
|
1457
1588
|
if (!fallbackAccountId) {
|
|
1458
1589
|
continue;
|
|
1459
1590
|
}
|
|
1591
|
+
const fallbackResolvedEmail = fallbackRuntimeIdentity.email;
|
|
1592
|
+
const fallbackEntitlementAccountKey = resolveEntitlementAccountKey({
|
|
1593
|
+
accountId: fallbackStoredAccountId ?? fallbackAccountId,
|
|
1594
|
+
email: fallbackResolvedEmail,
|
|
1595
|
+
refreshToken: fallbackAccount.refreshToken,
|
|
1596
|
+
index: fallbackAccount.index,
|
|
1597
|
+
});
|
|
1598
|
+
const fallbackEntitlementBlock = entitlementCache.isBlocked(fallbackEntitlementAccountKey, model ?? modelFamily);
|
|
1599
|
+
if (fallbackEntitlementBlock.blocked) {
|
|
1600
|
+
runtimeMetrics.accountRotations++;
|
|
1601
|
+
runtimeMetrics.lastError =
|
|
1602
|
+
`Entitlement cached block for account ${fallbackAccount.index + 1}`;
|
|
1603
|
+
logWarn(`Skipping account ${fallbackAccount.index + 1} due to cached entitlement block (${formatWaitTime(fallbackEntitlementBlock.waitMs)} remaining).`);
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1460
1606
|
if (!accountManager.consumeToken(fallbackAccount, modelFamily, model)) {
|
|
1461
1607
|
continue;
|
|
1462
1608
|
}
|
|
1609
|
+
fallbackAccount.accountId = fallbackAccountId;
|
|
1610
|
+
if (!hadFallbackAccountId &&
|
|
1611
|
+
fallbackTokenAccountId &&
|
|
1612
|
+
fallbackAccountId === fallbackTokenAccountId) {
|
|
1613
|
+
fallbackAccount.accountIdSource =
|
|
1614
|
+
fallbackStoredAccountIdSource ?? "token";
|
|
1615
|
+
}
|
|
1616
|
+
if (fallbackResolvedEmail) {
|
|
1617
|
+
fallbackAccount.email = fallbackResolvedEmail;
|
|
1618
|
+
}
|
|
1463
1619
|
const fallbackHeaders = createCodexHeaders(requestInit, fallbackAccountId, fallbackAuth.access, {
|
|
1464
1620
|
model,
|
|
1465
1621
|
promptCacheKey: effectivePromptCacheKey,
|
|
@@ -1476,14 +1632,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1476
1632
|
}
|
|
1477
1633
|
try {
|
|
1478
1634
|
runtimeMetrics.totalRequests++;
|
|
1479
|
-
const fallbackResponse = await fetch(url, {
|
|
1635
|
+
const fallbackResponse = await fetch(url, applyProxyCompatibleInit(url, {
|
|
1480
1636
|
...requestInit,
|
|
1481
1637
|
headers: fallbackHeaders,
|
|
1482
1638
|
signal: fallbackController.signal,
|
|
1483
|
-
});
|
|
1639
|
+
}));
|
|
1484
1640
|
const fallbackSnapshot = readQuotaSchedulerSnapshot(fallbackResponse.headers, fallbackResponse.status);
|
|
1485
1641
|
if (fallbackSnapshot) {
|
|
1486
|
-
preemptiveQuotaScheduler.update(`${
|
|
1642
|
+
preemptiveQuotaScheduler.update(`${fallbackEntitlementAccountKey}:${model ?? modelFamily}`, fallbackSnapshot);
|
|
1487
1643
|
}
|
|
1488
1644
|
if (!fallbackResponse.ok) {
|
|
1489
1645
|
try {
|
|
@@ -1500,10 +1656,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1500
1656
|
else {
|
|
1501
1657
|
accountManager.recordFailure(fallbackAccount, modelFamily, model);
|
|
1502
1658
|
}
|
|
1503
|
-
capabilityPolicyStore.recordFailure(
|
|
1659
|
+
capabilityPolicyStore.recordFailure(fallbackEntitlementAccountKey, capabilityModelKey);
|
|
1504
1660
|
continue;
|
|
1505
1661
|
}
|
|
1506
1662
|
successAccountForResponse = fallbackAccount;
|
|
1663
|
+
successEntitlementAccountKey = fallbackEntitlementAccountKey;
|
|
1507
1664
|
runtimeMetrics.streamFailoverRecoveries += 1;
|
|
1508
1665
|
if (fallbackAccount.index !== account.index) {
|
|
1509
1666
|
runtimeMetrics.streamFailoverCrossAccountRecoveries += 1;
|
|
@@ -1516,7 +1673,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1516
1673
|
catch (streamFailoverError) {
|
|
1517
1674
|
accountManager.refundToken(fallbackAccount, modelFamily, model);
|
|
1518
1675
|
accountManager.recordFailure(fallbackAccount, modelFamily, model);
|
|
1519
|
-
capabilityPolicyStore.recordFailure(
|
|
1676
|
+
capabilityPolicyStore.recordFailure(fallbackEntitlementAccountKey, capabilityModelKey);
|
|
1520
1677
|
logWarn(`Stream failover attempt ${failoverAttempt} failed for account ${fallbackAccount.index + 1}.`, {
|
|
1521
1678
|
emittedBytes,
|
|
1522
1679
|
error: streamFailoverError instanceof Error
|
|
@@ -1585,9 +1742,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1585
1742
|
if (successAccountForResponse.index !== account.index) {
|
|
1586
1743
|
accountManager.markSwitched(successAccountForResponse, "rotation", modelFamily);
|
|
1587
1744
|
}
|
|
1588
|
-
const successAccountKey =
|
|
1589
|
-
? entitlementAccountKey
|
|
1590
|
-
: resolveEntitlementAccountKey(successAccountForResponse);
|
|
1745
|
+
const successAccountKey = successEntitlementAccountKey;
|
|
1591
1746
|
accountManager.recordSuccess(successAccountForResponse, modelFamily, model);
|
|
1592
1747
|
capabilityPolicyStore.recordSuccess(successAccountKey, capabilityModelKey);
|
|
1593
1748
|
entitlementCache.clear(successAccountKey, capabilityModelKey);
|
|
@@ -1660,9 +1815,10 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1660
1815
|
applyUiRuntimeFromConfig(authPluginConfig);
|
|
1661
1816
|
applyAccountStorageScope(authPluginConfig);
|
|
1662
1817
|
const accounts = [];
|
|
1663
|
-
const noBrowser = inputs?.
|
|
1818
|
+
const noBrowser = inputs?.manual === "true" ||
|
|
1819
|
+
inputs?.noBrowser === "true" ||
|
|
1664
1820
|
inputs?.["no-browser"] === "true";
|
|
1665
|
-
const useManualMode = noBrowser;
|
|
1821
|
+
const useManualMode = noBrowser || isBrowserLaunchSuppressed();
|
|
1666
1822
|
const explicitLoginMode = inputs?.loginMode === "fresh" || inputs?.loginMode === "add"
|
|
1667
1823
|
? inputs.loginMode
|
|
1668
1824
|
: null;
|