codex-multi-auth 1.2.4 → 1.2.6
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 +23 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +212 -24
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +8 -0
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/auto-update-checker.d.ts.map +1 -1
- package/dist/lib/auto-update-checker.js +72 -36
- package/dist/lib/auto-update-checker.js.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.js +1 -76
- package/dist/lib/codex-manager/commands/forecast.js.map +1 -1
- package/dist/lib/codex-manager/commands/report.d.ts +4 -1
- package/dist/lib/codex-manager/commands/report.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/report.js +128 -75
- package/dist/lib/codex-manager/commands/report.js.map +1 -1
- package/dist/lib/codex-manager/commands/status.d.ts +4 -1
- package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/status.js +48 -1
- package/dist/lib/codex-manager/commands/status.js.map +1 -1
- package/dist/lib/codex-manager/forecast-report-shared.d.ts +35 -0
- package/dist/lib/codex-manager/forecast-report-shared.d.ts.map +1 -0
- package/dist/lib/codex-manager/forecast-report-shared.js +79 -0
- package/dist/lib/codex-manager/forecast-report-shared.js.map +1 -0
- package/dist/lib/codex-manager/repair-commands.d.ts.map +1 -1
- package/dist/lib/codex-manager/repair-commands.js +20 -12
- 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 +39 -8
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/config.d.ts +4 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +42 -5
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/parallel-probe.d.ts.map +1 -1
- package/dist/lib/parallel-probe.js +28 -1
- package/dist/lib/parallel-probe.js.map +1 -1
- package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -1
- package/dist/lib/preemptive-quota-scheduler.js +16 -6
- package/dist/lib/preemptive-quota-scheduler.js.map +1 -1
- package/dist/lib/proactive-refresh.d.ts.map +1 -1
- package/dist/lib/proactive-refresh.js +16 -5
- package/dist/lib/proactive-refresh.js.map +1 -1
- package/dist/lib/quota-cache.d.ts.map +1 -1
- package/dist/lib/quota-cache.js +36 -30
- package/dist/lib/quota-cache.js.map +1 -1
- package/dist/lib/quota-probe.d.ts.map +1 -1
- package/dist/lib/quota-probe.js +5 -0
- package/dist/lib/quota-probe.js.map +1 -1
- package/dist/lib/request/failure-policy.js +1 -1
- package/dist/lib/request/failure-policy.js.map +1 -1
- package/dist/lib/request/fetch-helpers.js +12 -4
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/rate-limit-backoff.d.ts +24 -4
- package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -1
- package/dist/lib/request/rate-limit-backoff.js +84 -19
- package/dist/lib/request/rate-limit-backoff.js.map +1 -1
- package/dist/lib/request/request-attempt-budget.d.ts +24 -0
- package/dist/lib/request/request-attempt-budget.d.ts.map +1 -0
- package/dist/lib/request/request-attempt-budget.js +49 -0
- package/dist/lib/request/request-attempt-budget.js.map +1 -0
- package/dist/lib/request/request-resilience.d.ts +10 -0
- package/dist/lib/request/request-resilience.d.ts.map +1 -0
- package/dist/lib/request/request-resilience.js +80 -0
- package/dist/lib/request/request-resilience.js.map +1 -0
- package/dist/lib/request/response-metadata.js +1 -1
- package/dist/lib/request/response-metadata.js.map +1 -1
- package/dist/lib/request/stream-failover.d.ts +5 -1
- package/dist/lib/request/stream-failover.d.ts.map +1 -1
- package/dist/lib/request/stream-failover.js +8 -1
- package/dist/lib/request/stream-failover.js.map +1 -1
- package/dist/lib/runtime/live-sync-entry.d.ts +7 -0
- package/dist/lib/runtime/live-sync-entry.d.ts.map +1 -1
- package/dist/lib/runtime/live-sync-entry.js +4 -0
- package/dist/lib/runtime/live-sync-entry.js.map +1 -1
- package/dist/lib/runtime/live-sync.d.ts +3 -0
- package/dist/lib/runtime/live-sync.d.ts.map +1 -1
- package/dist/lib/runtime/live-sync.js +17 -3
- package/dist/lib/runtime/live-sync.js.map +1 -1
- package/dist/lib/runtime/runtime-observability.d.ts +46 -0
- package/dist/lib/runtime/runtime-observability.d.ts.map +1 -0
- package/dist/lib/runtime/runtime-observability.js +167 -0
- package/dist/lib/runtime/runtime-observability.js.map +1 -0
- package/dist/lib/runtime/runtime-services.d.ts +3 -0
- package/dist/lib/runtime/runtime-services.d.ts.map +1 -1
- package/dist/lib/runtime/runtime-services.js +19 -7
- package/dist/lib/runtime/runtime-services.js.map +1 -1
- package/dist/lib/schemas.d.ts +11 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +5 -1
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/session-affinity.d.ts +4 -1
- package/dist/lib/session-affinity.d.ts.map +1 -1
- package/dist/lib/session-affinity.js +26 -1
- package/dist/lib/session-affinity.js.map +1 -1
- package/dist/lib/storage/account-clear.d.ts.map +1 -1
- package/dist/lib/storage/account-clear.js +43 -13
- package/dist/lib/storage/account-clear.js.map +1 -1
- package/dist/lib/storage/health.d.ts +22 -0
- package/dist/lib/storage/health.d.ts.map +1 -0
- package/dist/lib/storage/health.js +15 -0
- package/dist/lib/storage/health.js.map +1 -0
- package/dist/lib/storage/migrations.d.ts +1 -1
- package/dist/lib/storage/migrations.d.ts.map +1 -1
- package/dist/lib/storage.d.ts +3 -1
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +108 -4
- package/dist/lib/storage.js.map +1 -1
- package/package.json +1 -1
- package/scripts/codex-multi-auth.js +1 -1
- package/scripts/codex.js +205 -20
- package/dist/lib/runtime/metrics.d.ts +0 -29
- package/dist/lib/runtime/metrics.d.ts.map +0 -1
- package/dist/lib/runtime/metrics.js +0 -97
- package/dist/lib/runtime/metrics.js.map +0 -1
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ Codex CLI-first multi-account OAuth manager for the official `@openai/codex` CLI
|
|
|
19
19
|
- Interactive dashboard for account actions and settings
|
|
20
20
|
- Experimental settings tab for staged sync, backup, and refresh-guard controls
|
|
21
21
|
- Forecast, report, fix, and doctor commands for operational safety
|
|
22
|
+
- Runtime counters, budget/cooldown state, and multi-auth probe visibility in `codex auth status` / `codex auth report`
|
|
22
23
|
- Flagged account verification and restore flow
|
|
23
24
|
- Session affinity and live account sync controls
|
|
24
25
|
- Proactive refresh and preemptive quota deferral controls
|
|
@@ -156,6 +157,14 @@ If browser launch is blocked, use the alternate login paths in [docs/getting-sta
|
|
|
156
157
|
| `codex auth report --live --json` | How do I get the full machine-readable health report? |
|
|
157
158
|
| `codex auth fix --live --model gpt-5-codex` | How do I run live repair probes with a chosen model? |
|
|
158
159
|
|
|
160
|
+
### Reliability behavior
|
|
161
|
+
|
|
162
|
+
- whole-pool replay is disabled by default when every account is rate-limited
|
|
163
|
+
- active requests use a bounded outbound request budget so one prompt cannot walk the full pool indefinitely
|
|
164
|
+
- repeated cross-account 5xx bursts trigger a short cooldown instead of continuing aggressive rotation
|
|
165
|
+
- proactive refresh is staggered to reduce background refresh bursts
|
|
166
|
+
- `codex auth status` surfaces recent runtime request metrics in text output, and `codex auth report --json` exposes the machine-readable cooldown/runtime snapshot
|
|
167
|
+
|
|
159
168
|
---
|
|
160
169
|
|
|
161
170
|
## Dashboard Hotkeys
|
|
@@ -187,11 +196,12 @@ If browser launch is blocked, use the alternate login paths in [docs/getting-sta
|
|
|
187
196
|
| File | Default path |
|
|
188
197
|
| --- | --- |
|
|
189
198
|
| Settings | `~/.codex/multi-auth/settings.json` |
|
|
190
|
-
| Accounts | `~/.codex/multi-auth/openai-codex-accounts.json` |
|
|
191
|
-
| Flagged accounts | `~/.codex/multi-auth/openai-codex-flagged-accounts.json` |
|
|
192
|
-
| Quota cache | `~/.codex/multi-auth/quota-cache.json` |
|
|
193
|
-
|
|
|
194
|
-
|
|
|
199
|
+
| Accounts | `~/.codex/multi-auth/openai-codex-accounts.json` |
|
|
200
|
+
| Flagged accounts | `~/.codex/multi-auth/openai-codex-flagged-accounts.json` |
|
|
201
|
+
| Quota cache | `~/.codex/multi-auth/quota-cache.json` |
|
|
202
|
+
| Runtime observability | `~/.codex/multi-auth/runtime-observability.json` |
|
|
203
|
+
| Logs | `~/.codex/multi-auth/logs/codex-plugin/` |
|
|
204
|
+
| Per-project accounts | `~/.codex/multi-auth/projects/<project-key>/openai-codex-accounts.json` |
|
|
195
205
|
|
|
196
206
|
Override root with `CODEX_MULTI_AUTH_DIR=<path>`.
|
|
197
207
|
|
|
@@ -265,7 +275,10 @@ codex auth login
|
|
|
265
275
|
|
|
266
276
|
- `codex auth` unrecognized: run `where codex`, then follow `docs/troubleshooting.md` for routing fallback commands
|
|
267
277
|
- Switch succeeds but wrong account appears active: run `codex auth switch <index>`, then restart session
|
|
268
|
-
-
|
|
278
|
+
- Requests fail fast with a pool cooldown message: wait for the cooldown window or inspect `codex auth status`
|
|
279
|
+
- Requests fail fast after repeated upstream 5xx errors: inspect `codex auth report --json` for runtime traffic and cooldown details
|
|
280
|
+
- Storage cleanup fails with `EBUSY` / `EPERM` (Windows): run `codex auth doctor --fix` to retry, or manually remove `~/.codex/multi-auth/<project-key>/` and re-login
|
|
281
|
+
- OAuth callback on port `1455` fails: free the port and re-run `codex auth login`
|
|
269
282
|
- Browser launch is blocked or you are in a headless shell: re-run `codex auth login --manual` or set `CODEX_AUTH_NO_BROWSER=1`
|
|
270
283
|
- `missing field id_token` / `token_expired` / `refresh_token_reused`: re-login affected account
|
|
271
284
|
|
|
@@ -308,9 +321,10 @@ codex auth doctor --json
|
|
|
308
321
|
|
|
309
322
|
## Release Notes
|
|
310
323
|
|
|
311
|
-
- Current stable: [docs/releases/v1.2.
|
|
312
|
-
-
|
|
313
|
-
-
|
|
324
|
+
- Current stable: [docs/releases/v1.2.6.md](docs/releases/v1.2.6.md)
|
|
325
|
+
- Recent patch release: [docs/releases/v1.2.5.md](docs/releases/v1.2.5.md)
|
|
326
|
+
- Previous stable: [docs/releases/v1.2.2.md](docs/releases/v1.2.2.md)
|
|
327
|
+
- Earlier stable: [docs/releases/v1.2.1.md](docs/releases/v1.2.1.md)
|
|
314
328
|
- Archived prerelease: [docs/releases/v0.1.0-beta.0.md](docs/releases/v0.1.0-beta.0.md)
|
|
315
329
|
|
|
316
330
|
## 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;AAEH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAC;AAyR5D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MA2/H/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,KAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ import { startLocalOAuthServer } from "./lib/auth/server.js";
|
|
|
30
30
|
import { checkAndNotify } from "./lib/auto-update-checker.js";
|
|
31
31
|
import { CapabilityPolicyStore } from "./lib/capability-policy.js";
|
|
32
32
|
import { promptAddAnotherAccount, promptLoginMode } from "./lib/cli.js";
|
|
33
|
-
import { getAutoResume, getCodexMode, getEmptyResponseMaxRetries, getEmptyResponseRetryDelayMs, getFallbackToGpt52OnUnsupportedGpt53, getFastSession, getFastSessionMaxInputItems, getFastSessionStrategy, getFetchTimeoutMs, getLiveAccountSync, getLiveAccountSyncDebounceMs, getLiveAccountSyncPollMs, getNetworkErrorCooldownMs, getPerProjectAccounts, getPidOffsetEnabled, getPreemptiveQuotaEnabled, getPreemptiveQuotaMaxDeferralMs, getPreemptiveQuotaRemainingPercent5h, getPreemptiveQuotaRemainingPercent7d, getProactiveRefreshBufferMs, getProactiveRefreshGuardian, getProactiveRefreshIntervalMs, getRateLimitToastDebounceMs, getRetryAllAccountsMaxRetries, getRetryAllAccountsMaxWaitMs, getRetryAllAccountsRateLimited, getBackgroundResponses, getResponseContinuation, getServerErrorCooldownMs, getSessionAffinity, getSessionAffinityMaxEntries, getSessionAffinityTtlMs, getSessionRecovery, getStorageBackupEnabled, getStreamStallTimeoutMs, getToastDurationMs, getTokenRefreshSkewMs, getUnsupportedCodexFallbackChain, getUnsupportedCodexPolicy, loadPluginConfig, } from "./lib/config.js";
|
|
33
|
+
import { getAutoResume, getCodexMode, getEmptyResponseMaxRetries, getEmptyResponseRetryDelayMs, getFallbackToGpt52OnUnsupportedGpt53, getFastSession, getFastSessionMaxInputItems, getFastSessionStrategy, getFetchTimeoutMs, getRateLimitDedupWindowMs, getRateLimitMaxBackoffMs, getRateLimitShortRetryThresholdMs, getRateLimitStateResetMs, getLiveAccountSync, getLiveAccountSyncDebounceMs, getLiveAccountSyncPollMs, getNetworkErrorCooldownMs, getPerProjectAccounts, getPidOffsetEnabled, getPreemptiveQuotaEnabled, getPreemptiveQuotaMaxDeferralMs, getPreemptiveQuotaRemainingPercent5h, getPreemptiveQuotaRemainingPercent7d, getProactiveRefreshBufferMs, getProactiveRefreshGuardian, getProactiveRefreshIntervalMs, getRateLimitToastDebounceMs, getRetryAllAccountsMaxRetries, getRetryAllAccountsMaxWaitMs, getRetryAllAccountsRateLimited, getBackgroundResponses, getResponseContinuation, getServerErrorCooldownMs, getSessionAffinity, getSessionAffinityMaxEntries, getSessionAffinityTtlMs, getSessionRecovery, getStorageBackupEnabled, getStreamStallTimeoutMs, getToastDurationMs, getTokenRefreshSkewMs, getUnsupportedCodexFallbackChain, getUnsupportedCodexPolicy, loadPluginConfig, } from "./lib/config.js";
|
|
34
34
|
import { ACCOUNT_LIMITS, AUTH_LABELS, CODEX_BASE_URL, DUMMY_API_KEY, LOG_STAGES, PLUGIN_NAME, PROVIDER_ID, } from "./lib/constants.js";
|
|
35
35
|
import { handleContextOverflow } from "./lib/context-overflow.js";
|
|
36
36
|
import { EntitlementCache, resolveEntitlementAccountKey, } from "./lib/entitlement-cache.js";
|
|
@@ -44,9 +44,11 @@ import { detectErrorType, getRecoveryToastContent, isRecoverableError, } from ".
|
|
|
44
44
|
import { RefreshGuardian } from "./lib/refresh-guardian.js";
|
|
45
45
|
import { queuedRefresh } from "./lib/refresh-queue.js";
|
|
46
46
|
import { parseEnvInt, parseFailoverMode, } from "./lib/request/failover-config.js";
|
|
47
|
+
import { capStreamFailoverMax, computeOutboundRequestAttemptBudget, } from "./lib/request/request-attempt-budget.js";
|
|
48
|
+
import { armPoolExhaustionCooldown, buildAdaptiveStreamFailoverCandidateOrder, clearPoolExhaustionCooldown, clearServerBurstCooldown, getPoolExhaustionCooldownRemaining, getServerBurstCooldownRemaining, recordServerBurstFailure, } from "./lib/request/request-resilience.js";
|
|
47
49
|
import { evaluateFailurePolicy, } from "./lib/request/failure-policy.js";
|
|
48
50
|
import { applyProxyCompatibleInit, createCodexHeaders, extractRequestUrl, getUnsupportedCodexModelInfo, handleErrorResponse, handleSuccessResponse, isWorkspaceDisabledError, refreshAndUpdateToken, resolveUnsupportedCodexFallbackModel, rewriteUrlForCodex, shouldRefreshToken, transformRequestForCodex, } from "./lib/request/fetch-helpers.js";
|
|
49
|
-
import { getRateLimitBackoff,
|
|
51
|
+
import { configureRateLimitBackoff, getRateLimitBackoff, MAX_SHORT_RETRY_ATTEMPTS, getRateLimitShortRetryThresholdMs as getConfiguredRateLimitShortRetryThresholdMs, resetRateLimitBackoff, } from "./lib/request/rate-limit-backoff.js";
|
|
50
52
|
import { normalizeRequestInit, parseRequestBodyFromInit, } from "./lib/request/request-init.js";
|
|
51
53
|
import { applyFastSessionDefaults } from "./lib/request/request-transformer.js";
|
|
52
54
|
import { applyResponseCompaction } from "./lib/request/response-compaction.js";
|
|
@@ -74,6 +76,7 @@ import { applyLoaderRuntimeSetup } from "./lib/runtime/loader-setup.js";
|
|
|
74
76
|
import { buildManualOAuthFlow } from "./lib/runtime/manual-oauth-flow.js";
|
|
75
77
|
import { applyPreemptiveQuotaSettingsFromConfig, } from "./lib/runtime/quota-settings.js";
|
|
76
78
|
import { ensureLiveAccountSyncState, ensureRefreshGuardianState, ensureSessionAffinityState, } from "./lib/runtime/runtime-services.js";
|
|
79
|
+
import { mutateRuntimeObservabilitySnapshot } from "./lib/runtime/runtime-observability.js";
|
|
77
80
|
import { applyAccountStorageScopeFromConfig } from "./lib/runtime/storage-scope.js";
|
|
78
81
|
import { showRuntimeToast } from "./lib/runtime/toast.js";
|
|
79
82
|
import { createRuntimeSessionRecoveryHook } from "./lib/runtime/session-recovery.js";
|
|
@@ -113,10 +116,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
113
116
|
let perProjectStorageWarningShown = false;
|
|
114
117
|
let liveAccountSync = null;
|
|
115
118
|
let liveAccountSyncPath = null;
|
|
119
|
+
let liveAccountSyncConfigKey = null;
|
|
116
120
|
let refreshGuardian = null;
|
|
117
121
|
let refreshGuardianConfigKey = null;
|
|
118
122
|
let refreshGuardianCleanupRegistered = false;
|
|
119
123
|
let sessionAffinityStore = new SessionAffinityStore();
|
|
124
|
+
let sessionAffinityWriteVersion = 0;
|
|
120
125
|
let sessionAffinityConfigKey = null;
|
|
121
126
|
const entitlementCache = new EntitlementCache();
|
|
122
127
|
const preemptiveQuotaScheduler = new PreemptiveQuotaScheduler();
|
|
@@ -139,6 +144,14 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
139
144
|
totalRequests: 0,
|
|
140
145
|
successfulRequests: 0,
|
|
141
146
|
failedRequests: 0,
|
|
147
|
+
responsesRequests: 0,
|
|
148
|
+
authRefreshRequests: 0,
|
|
149
|
+
diagnosticProbeRequests: 0,
|
|
150
|
+
outboundRequestAttemptBudget: null,
|
|
151
|
+
outboundRequestAttemptsConsumed: 0,
|
|
152
|
+
requestAttemptBudgetExhaustions: 0,
|
|
153
|
+
poolExhaustionFastFails: 0,
|
|
154
|
+
serverBurstFastFails: 0,
|
|
142
155
|
rateLimitedResponses: 0,
|
|
143
156
|
serverErrors: 0,
|
|
144
157
|
networkErrors: 0,
|
|
@@ -148,6 +161,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
148
161
|
accountRotations: 0,
|
|
149
162
|
sameAccountRetries: 0,
|
|
150
163
|
streamFailoverAttempts: 0,
|
|
164
|
+
streamFailoverCandidatesConsidered: 0,
|
|
165
|
+
lastStreamFailoverCandidateCount: 0,
|
|
151
166
|
streamFailoverRecoveries: 0,
|
|
152
167
|
streamFailoverCrossAccountRecoveries: 0,
|
|
153
168
|
cumulativeLatencyMs: 0,
|
|
@@ -169,6 +184,26 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
169
184
|
findMatchingAccountIndex,
|
|
170
185
|
modelFamilies: MODEL_FAMILIES,
|
|
171
186
|
});
|
|
187
|
+
const syncRuntimeObservability = (requestId) => {
|
|
188
|
+
mutateRuntimeObservabilitySnapshot((snapshot) => {
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
const poolRemaining = getPoolExhaustionCooldownRemaining(now);
|
|
191
|
+
const burstRemaining = getServerBurstCooldownRemaining(now);
|
|
192
|
+
snapshot.currentRequestId = requestId;
|
|
193
|
+
snapshot.poolExhaustionCooldownUntil =
|
|
194
|
+
poolRemaining > 0
|
|
195
|
+
? now + poolRemaining
|
|
196
|
+
: null;
|
|
197
|
+
snapshot.serverBurstCooldownUntil =
|
|
198
|
+
burstRemaining > 0
|
|
199
|
+
? now + burstRemaining
|
|
200
|
+
: null;
|
|
201
|
+
snapshot.responsesRequests = runtimeMetrics.responsesRequests;
|
|
202
|
+
snapshot.authRefreshRequests = runtimeMetrics.authRefreshRequests;
|
|
203
|
+
snapshot.diagnosticProbeRequests = runtimeMetrics.diagnosticProbeRequests;
|
|
204
|
+
snapshot.runtimeMetrics = { ...runtimeMetrics };
|
|
205
|
+
});
|
|
206
|
+
};
|
|
172
207
|
const persistAccountPoolAndFlagged = async (results, flaggedStorage, replaceAll = false) => withAccountAndFlaggedStorageTransaction(async (loadedStorage, persist) => {
|
|
173
208
|
await persistAccountPoolResults({
|
|
174
209
|
results,
|
|
@@ -228,7 +263,10 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
228
263
|
authFallback,
|
|
229
264
|
currentSync: liveAccountSync,
|
|
230
265
|
currentPath: liveAccountSyncPath,
|
|
266
|
+
currentConfigKey: liveAccountSyncConfigKey,
|
|
231
267
|
getLiveAccountSync,
|
|
268
|
+
getLiveAccountSyncDebounceMs,
|
|
269
|
+
getLiveAccountSyncPollMs,
|
|
232
270
|
getStoragePath,
|
|
233
271
|
createSync: (oauthFallback) => new LiveAccountSync(async () => {
|
|
234
272
|
await reloadRuntimeAccountManager(makeReloadAccountManagerDeps(oauthFallback));
|
|
@@ -243,6 +281,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
243
281
|
});
|
|
244
282
|
liveAccountSync = next.liveAccountSync;
|
|
245
283
|
liveAccountSyncPath = next.liveAccountSyncPath;
|
|
284
|
+
liveAccountSyncConfigKey = next.liveAccountSyncConfigKey;
|
|
246
285
|
};
|
|
247
286
|
const ensureRefreshGuardian = (pluginConfig) => {
|
|
248
287
|
const next = ensureRefreshGuardianState({
|
|
@@ -387,6 +426,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
387
426
|
const fastSessionStrategy = getFastSessionStrategy(pluginConfig);
|
|
388
427
|
const fastSessionMaxInputItems = getFastSessionMaxInputItems(pluginConfig);
|
|
389
428
|
const tokenRefreshSkewMs = getTokenRefreshSkewMs(pluginConfig);
|
|
429
|
+
configureRateLimitBackoff({
|
|
430
|
+
dedupWindowMs: getRateLimitDedupWindowMs(pluginConfig),
|
|
431
|
+
stateResetMs: getRateLimitStateResetMs(pluginConfig),
|
|
432
|
+
maxBackoffMs: getRateLimitMaxBackoffMs(pluginConfig),
|
|
433
|
+
shortRetryThresholdMs: getRateLimitShortRetryThresholdMs(pluginConfig),
|
|
434
|
+
});
|
|
390
435
|
const rateLimitToastDebounceMs = getRateLimitToastDebounceMs(pluginConfig);
|
|
391
436
|
const retryAllAccountsRateLimited = getRetryAllAccountsRateLimited(pluginConfig);
|
|
392
437
|
const retryAllAccountsMaxWaitMs = getRetryAllAccountsMaxWaitMs(pluginConfig);
|
|
@@ -401,8 +446,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
401
446
|
const networkErrorCooldownMs = getNetworkErrorCooldownMs(pluginConfig);
|
|
402
447
|
const serverErrorCooldownMs = getServerErrorCooldownMs(pluginConfig);
|
|
403
448
|
const failoverMode = parseFailoverMode(process.env.CODEX_AUTH_FAILOVER_MODE);
|
|
404
|
-
const streamFailoverMax = Math.max(0, parseEnvInt(process.env.CODEX_AUTH_STREAM_FAILOVER_MAX) ??
|
|
405
|
-
STREAM_FAILOVER_MAX_BY_MODE[failoverMode]);
|
|
449
|
+
const streamFailoverMax = Math.max(0, capStreamFailoverMax(parseEnvInt(process.env.CODEX_AUTH_STREAM_FAILOVER_MAX) ??
|
|
450
|
+
STREAM_FAILOVER_MAX_BY_MODE[failoverMode]));
|
|
406
451
|
const streamFailoverSoftTimeoutMs = Math.max(1_000, parseEnvInt(process.env.CODEX_AUTH_STREAM_STALL_SOFT_TIMEOUT_MS) ??
|
|
407
452
|
STREAM_FAILOVER_SOFT_TIMEOUT_BY_MODE[failoverMode]);
|
|
408
453
|
const streamFailoverHardTimeoutMs = Math.max(streamFailoverSoftTimeoutMs, parseEnvInt(process.env.CODEX_AUTH_STREAM_STALL_HARD_TIMEOUT_MS) ??
|
|
@@ -508,6 +553,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
508
553
|
.toString()
|
|
509
554
|
.trim() || undefined;
|
|
510
555
|
const sessionAffinityKey = threadIdCandidate ?? promptCacheKey ?? null;
|
|
556
|
+
const sessionAffinityVersion = (sessionAffinityWriteVersion += 1);
|
|
511
557
|
const effectivePromptCacheKey = (sessionAffinityKey ?? promptCacheKey ?? "")
|
|
512
558
|
.toString()
|
|
513
559
|
.trim() || undefined;
|
|
@@ -532,9 +578,72 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
532
578
|
const requestCorrelationId = setCorrelationId(threadIdCandidate
|
|
533
579
|
? `${threadIdCandidate}:${Date.now()}`
|
|
534
580
|
: undefined);
|
|
581
|
+
const requestTraceId = requestCorrelationId;
|
|
535
582
|
runtimeMetrics.lastRequestAt = Date.now();
|
|
583
|
+
syncRuntimeObservability(requestTraceId);
|
|
536
584
|
const abortSignal = requestInit?.signal ?? init?.signal ?? null;
|
|
537
585
|
const sleep = createAbortableSleep(abortSignal);
|
|
586
|
+
const poolCooldownRemainingMs = getPoolExhaustionCooldownRemaining();
|
|
587
|
+
if (poolCooldownRemainingMs > 0) {
|
|
588
|
+
runtimeMetrics.failedRequests += 1;
|
|
589
|
+
runtimeMetrics.poolExhaustionFastFails += 1;
|
|
590
|
+
runtimeMetrics.lastError = "Pool exhaustion cooldown active";
|
|
591
|
+
syncRuntimeObservability(requestTraceId);
|
|
592
|
+
return new Response(JSON.stringify({
|
|
593
|
+
error: {
|
|
594
|
+
message: `The account pool is cooling down after recent rate-limit exhaustion. Try again in ${formatWaitTime(poolCooldownRemainingMs)} or inspect \`codex auth status\`.`,
|
|
595
|
+
},
|
|
596
|
+
}), { status: 429, headers: { "content-type": "application/json; charset=utf-8" } });
|
|
597
|
+
}
|
|
598
|
+
const serverBurstCooldownRemainingMs = getServerBurstCooldownRemaining();
|
|
599
|
+
if (serverBurstCooldownRemainingMs > 0) {
|
|
600
|
+
runtimeMetrics.failedRequests += 1;
|
|
601
|
+
runtimeMetrics.serverBurstFastFails += 1;
|
|
602
|
+
runtimeMetrics.lastError = "Server burst cooldown active";
|
|
603
|
+
syncRuntimeObservability(requestTraceId);
|
|
604
|
+
return new Response(JSON.stringify({
|
|
605
|
+
error: {
|
|
606
|
+
message: `Multiple accounts recently failed with upstream server errors. Try again in ${formatWaitTime(serverBurstCooldownRemainingMs)} or inspect \`codex auth report --json\`.`,
|
|
607
|
+
},
|
|
608
|
+
}), { status: 503, headers: { "content-type": "application/json; charset=utf-8" } });
|
|
609
|
+
}
|
|
610
|
+
const maxOutboundRequestAttempts = computeOutboundRequestAttemptBudget({
|
|
611
|
+
accountCount: accountManager.getAccountCount(),
|
|
612
|
+
maxSameAccountRetries,
|
|
613
|
+
emptyResponseMaxRetries,
|
|
614
|
+
streamFailoverMax,
|
|
615
|
+
});
|
|
616
|
+
runtimeMetrics.outboundRequestAttemptBudget ??=
|
|
617
|
+
maxOutboundRequestAttempts;
|
|
618
|
+
logDebug("Configured outbound request attempt budget.", {
|
|
619
|
+
requestTraceId,
|
|
620
|
+
budget: maxOutboundRequestAttempts,
|
|
621
|
+
accountCount: accountManager.getAccountCount(),
|
|
622
|
+
maxSameAccountRetries,
|
|
623
|
+
emptyResponseMaxRetries,
|
|
624
|
+
streamFailoverMax,
|
|
625
|
+
});
|
|
626
|
+
syncRuntimeObservability(requestTraceId);
|
|
627
|
+
let outboundRequestAttemptsRemaining = maxOutboundRequestAttempts;
|
|
628
|
+
const tryConsumeOutboundRequestAttempt = (reason, accountIndex) => {
|
|
629
|
+
if (outboundRequestAttemptsRemaining <= 0) {
|
|
630
|
+
runtimeMetrics.requestAttemptBudgetExhaustions += 1;
|
|
631
|
+
runtimeMetrics.lastError =
|
|
632
|
+
`Request attempt budget exhausted after ${maxOutboundRequestAttempts} outbound request(s)`;
|
|
633
|
+
logWarn("Request attempt budget exhausted.", {
|
|
634
|
+
requestTraceId,
|
|
635
|
+
reason,
|
|
636
|
+
accountIndex,
|
|
637
|
+
budget: maxOutboundRequestAttempts,
|
|
638
|
+
consumed: maxOutboundRequestAttempts,
|
|
639
|
+
});
|
|
640
|
+
syncRuntimeObservability(requestTraceId);
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
runtimeMetrics.outboundRequestAttemptsConsumed += 1;
|
|
644
|
+
outboundRequestAttemptsRemaining -= 1;
|
|
645
|
+
return true;
|
|
646
|
+
};
|
|
538
647
|
let allRateLimitedRetries = 0;
|
|
539
648
|
let emptyResponseRetries = 0;
|
|
540
649
|
const attemptedUnsupportedFallbackModels = new Set();
|
|
@@ -599,6 +708,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
599
708
|
let accountAuth = accountManager.toAuthDetails(account);
|
|
600
709
|
try {
|
|
601
710
|
if (shouldRefreshToken(accountAuth, tokenRefreshSkewMs)) {
|
|
711
|
+
runtimeMetrics.authRefreshRequests += 1;
|
|
712
|
+
syncRuntimeObservability(requestTraceId);
|
|
602
713
|
accountAuth = (await refreshAndUpdateToken(accountAuth, client));
|
|
603
714
|
await accountManager.commitRefreshedAuth(account, accountAuth);
|
|
604
715
|
}
|
|
@@ -748,9 +859,23 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
748
859
|
continue;
|
|
749
860
|
}
|
|
750
861
|
let sameAccountRetryCount = 0;
|
|
862
|
+
let shortRateLimitRetryCount = 0;
|
|
751
863
|
let successAccountForResponse = account;
|
|
752
864
|
let successEntitlementAccountKey = entitlementAccountKey;
|
|
753
865
|
while (true) {
|
|
866
|
+
if (!tryConsumeOutboundRequestAttempt("primary", account.index)) {
|
|
867
|
+
runtimeMetrics.failedRequests++;
|
|
868
|
+
const lastErrorDetail = runtimeMetrics.lastError;
|
|
869
|
+
const message = lastErrorDetail
|
|
870
|
+
? `${lastErrorDetail}. Try again after the current retries settle.`
|
|
871
|
+
: "Request attempt budget exhausted. Try again shortly.";
|
|
872
|
+
return new Response(JSON.stringify({ error: { message } }), {
|
|
873
|
+
status: 503,
|
|
874
|
+
headers: {
|
|
875
|
+
"content-type": "application/json; charset=utf-8",
|
|
876
|
+
},
|
|
877
|
+
});
|
|
878
|
+
}
|
|
754
879
|
let response;
|
|
755
880
|
const fetchStart = performance.now();
|
|
756
881
|
// Merge user AbortSignal with timeout (Node 18 compatible - no AbortSignal.any)
|
|
@@ -777,6 +902,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
777
902
|
}
|
|
778
903
|
try {
|
|
779
904
|
runtimeMetrics.totalRequests++;
|
|
905
|
+
runtimeMetrics.responsesRequests++;
|
|
906
|
+
syncRuntimeObservability(requestTraceId);
|
|
780
907
|
response = await fetch(url, applyProxyCompatibleInit(url, {
|
|
781
908
|
...requestInit,
|
|
782
909
|
headers,
|
|
@@ -1049,6 +1176,23 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1049
1176
|
failoverMode,
|
|
1050
1177
|
serverRetryAfterMs: serverRetryAfterMs ?? undefined,
|
|
1051
1178
|
}, { serverCooldownMs: serverErrorCooldownMs });
|
|
1179
|
+
const serverBurstCooldownUntil = recordServerBurstFailure(account.index);
|
|
1180
|
+
if (serverBurstCooldownUntil > 0) {
|
|
1181
|
+
runtimeMetrics.lastError =
|
|
1182
|
+
"Repeated upstream server errors across the account pool";
|
|
1183
|
+
syncRuntimeObservability(requestTraceId);
|
|
1184
|
+
}
|
|
1185
|
+
// Overload-type server errors (502 Bad Gateway, 503 Service
|
|
1186
|
+
// Unavailable, 529 Overloaded) signal upstream capacity
|
|
1187
|
+
// pressure. Notify the quota scheduler so it can proactively
|
|
1188
|
+
// defer subsequent requests for this quota key, mirroring the
|
|
1189
|
+
// 429 handler's scheduler awareness.
|
|
1190
|
+
if ((response.status === 502 ||
|
|
1191
|
+
response.status === 503 ||
|
|
1192
|
+
response.status === 529) &&
|
|
1193
|
+
typeof policy.cooldownMs === "number") {
|
|
1194
|
+
preemptiveQuotaScheduler.markRateLimited(quotaScheduleKey, policy.cooldownMs);
|
|
1195
|
+
}
|
|
1052
1196
|
if (policy.refundToken) {
|
|
1053
1197
|
accountManager.refundToken(account, modelFamily, model);
|
|
1054
1198
|
}
|
|
@@ -1072,16 +1216,20 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1072
1216
|
sessionAffinityStore?.forgetSession(sessionAffinityKey);
|
|
1073
1217
|
break;
|
|
1074
1218
|
}
|
|
1075
|
-
if (
|
|
1219
|
+
if (errorResponse.status === 429 && rateLimit) {
|
|
1076
1220
|
runtimeMetrics.rateLimitedResponses++;
|
|
1077
1221
|
const retryAfterMs = rateLimit?.retryAfterMs ?? 60_000;
|
|
1078
|
-
const {
|
|
1222
|
+
const { delayMs } = getRateLimitBackoff(account.index, quotaKey, retryAfterMs, account.accountId ?? account.email ?? null);
|
|
1079
1223
|
const cooldownMs = Math.max(delayMs, retryAfterMs);
|
|
1080
1224
|
preemptiveQuotaScheduler.markRateLimited(quotaScheduleKey, cooldownMs);
|
|
1081
1225
|
const waitLabel = formatWaitTime(cooldownMs);
|
|
1082
|
-
|
|
1226
|
+
const shortRetryThresholdMs = getConfiguredRateLimitShortRetryThresholdMs();
|
|
1227
|
+
if (cooldownMs <=
|
|
1228
|
+
shortRetryThresholdMs &&
|
|
1229
|
+
shortRateLimitRetryCount < MAX_SHORT_RETRY_ATTEMPTS) {
|
|
1230
|
+
shortRateLimitRetryCount += 1;
|
|
1083
1231
|
if (accountManager.shouldShowAccountToast(account.index, rateLimitToastDebounceMs)) {
|
|
1084
|
-
await showRuntimeToast(client, `Rate limited. Retrying in ${waitLabel} (attempt ${
|
|
1232
|
+
await showRuntimeToast(client, `Rate limited. Retrying in ${waitLabel} (attempt ${shortRateLimitRetryCount})...`, "warning", { duration: toastDurationMs });
|
|
1085
1233
|
accountManager.markToastShown(account.index);
|
|
1086
1234
|
}
|
|
1087
1235
|
await sleep(addJitter(Math.max(MIN_BACKOFF_MS, cooldownMs), 0.2));
|
|
@@ -1114,13 +1262,18 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1114
1262
|
runtimeMetrics.cumulativeLatencyMs += fetchLatencyMs;
|
|
1115
1263
|
let responseForSuccess = response;
|
|
1116
1264
|
if (isStreaming) {
|
|
1117
|
-
const streamFallbackCandidateOrder =
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1265
|
+
const streamFallbackCandidateOrder = buildAdaptiveStreamFailoverCandidateOrder(account.index, accountSnapshotList);
|
|
1266
|
+
runtimeMetrics.lastStreamFailoverCandidateCount =
|
|
1267
|
+
streamFallbackCandidateOrder.length;
|
|
1268
|
+
runtimeMetrics.streamFailoverCandidatesConsidered +=
|
|
1269
|
+
streamFallbackCandidateOrder.length;
|
|
1270
|
+
logDebug("Prepared stream failover candidates.", {
|
|
1271
|
+
requestTraceId,
|
|
1272
|
+
primaryAccountIndex: account.index,
|
|
1273
|
+
candidateCount: streamFallbackCandidateOrder.length,
|
|
1274
|
+
candidateIndices: streamFallbackCandidateOrder,
|
|
1275
|
+
});
|
|
1276
|
+
syncRuntimeObservability(requestTraceId);
|
|
1124
1277
|
responseForSuccess = withStreamingFailover(response, async (failoverAttempt, emittedBytes) => {
|
|
1125
1278
|
if (abortSignal?.aborted) {
|
|
1126
1279
|
return null;
|
|
@@ -1139,6 +1292,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1139
1292
|
let fallbackAuth = accountManager.toAuthDetails(fallbackAccount);
|
|
1140
1293
|
try {
|
|
1141
1294
|
if (shouldRefreshToken(fallbackAuth, tokenRefreshSkewMs)) {
|
|
1295
|
+
runtimeMetrics.authRefreshRequests += 1;
|
|
1296
|
+
syncRuntimeObservability(requestTraceId);
|
|
1142
1297
|
fallbackAuth = (await refreshAndUpdateToken(fallbackAuth, client));
|
|
1143
1298
|
await accountManager.commitRefreshedAuth(fallbackAccount, fallbackAuth);
|
|
1144
1299
|
}
|
|
@@ -1182,6 +1337,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1182
1337
|
logWarn(`Skipping account ${fallbackAccount.index + 1} due to cached entitlement block (${formatWaitTime(fallbackEntitlementBlock.waitMs)} remaining).`);
|
|
1183
1338
|
continue;
|
|
1184
1339
|
}
|
|
1340
|
+
if (!tryConsumeOutboundRequestAttempt("stream-failover", fallbackAccount.index)) {
|
|
1341
|
+
return null;
|
|
1342
|
+
}
|
|
1185
1343
|
if (!accountManager.consumeToken(fallbackAccount, modelFamily, model)) {
|
|
1186
1344
|
continue;
|
|
1187
1345
|
}
|
|
@@ -1212,6 +1370,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1212
1370
|
}
|
|
1213
1371
|
try {
|
|
1214
1372
|
runtimeMetrics.totalRequests++;
|
|
1373
|
+
runtimeMetrics.responsesRequests++;
|
|
1374
|
+
syncRuntimeObservability(requestTraceId);
|
|
1215
1375
|
const fallbackResponse = await fetch(url, applyProxyCompatibleInit(url, {
|
|
1216
1376
|
...requestInit,
|
|
1217
1377
|
headers: fallbackHeaders,
|
|
@@ -1253,7 +1413,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1253
1413
|
runtimeMetrics.streamFailoverCrossAccountRecoveries += 1;
|
|
1254
1414
|
runtimeMetrics.accountRotations += 1;
|
|
1255
1415
|
if (!responseContinuationEnabled) {
|
|
1256
|
-
sessionAffinityStore?.
|
|
1416
|
+
sessionAffinityStore?.rememberWithVersion(sessionAffinityKey, fallbackAccount.index, Date.now(), sessionAffinityVersion);
|
|
1257
1417
|
}
|
|
1258
1418
|
}
|
|
1259
1419
|
logInfo(`Recovered stream via failover attempt ${failoverAttempt} using account ${fallbackAccount.index + 1}.`, { emittedBytes });
|
|
@@ -1290,8 +1450,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1290
1450
|
onResponseId: (responseId) => {
|
|
1291
1451
|
if (!responseContinuationEnabled)
|
|
1292
1452
|
return;
|
|
1293
|
-
sessionAffinityStore?.
|
|
1294
|
-
sessionAffinityStore?.updateLastResponseId(sessionAffinityKey, responseId);
|
|
1453
|
+
sessionAffinityStore?.rememberWithVersion(sessionAffinityKey, successAccountForResponse.index, Date.now(), sessionAffinityVersion);
|
|
1454
|
+
sessionAffinityStore?.updateLastResponseId(sessionAffinityKey, responseId, Date.now(), sessionAffinityVersion);
|
|
1295
1455
|
storedResponseIdForSuccess = true;
|
|
1296
1456
|
},
|
|
1297
1457
|
streamStallTimeoutMs,
|
|
@@ -1331,6 +1491,20 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1331
1491
|
await sleep(addJitter(emptyResponseRetryDelayMs, 0.2));
|
|
1332
1492
|
break;
|
|
1333
1493
|
}
|
|
1494
|
+
if (response.status >= 500) {
|
|
1495
|
+
const serverBurstCooldownUntil = recordServerBurstFailure(account.index);
|
|
1496
|
+
if (serverBurstCooldownUntil > 0) {
|
|
1497
|
+
runtimeMetrics.serverBurstFastFails += 1;
|
|
1498
|
+
runtimeMetrics.lastError =
|
|
1499
|
+
"Repeated upstream server errors across the account pool";
|
|
1500
|
+
syncRuntimeObservability(requestTraceId);
|
|
1501
|
+
return new Response(JSON.stringify({
|
|
1502
|
+
error: {
|
|
1503
|
+
message: `Upstream server failures were observed across multiple accounts. Pausing retries for ${formatWaitTime(serverBurstCooldownUntil - Date.now())}. Check \`codex auth report --json\` for runtime metrics.`,
|
|
1504
|
+
},
|
|
1505
|
+
}), { status: 503, headers: { "content-type": "application/json; charset=utf-8" } });
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1334
1508
|
logWarn(`Empty response after ${emptyResponseMaxRetries} retries. Returning as-is.`);
|
|
1335
1509
|
}
|
|
1336
1510
|
}
|
|
@@ -1347,7 +1521,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1347
1521
|
entitlementCache.clear(successAccountKey, capabilityModelKey);
|
|
1348
1522
|
if (!responseContinuationEnabled ||
|
|
1349
1523
|
(!isStreaming && !storedResponseIdForSuccess)) {
|
|
1350
|
-
sessionAffinityStore?.
|
|
1524
|
+
sessionAffinityStore?.rememberWithVersion(sessionAffinityKey, successAccountForResponse.index, Date.now(), sessionAffinityVersion);
|
|
1351
1525
|
}
|
|
1352
1526
|
runtimeMetrics.successfulRequests++;
|
|
1353
1527
|
runtimeMetrics.lastError = null;
|
|
@@ -1357,6 +1531,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1357
1531
|
lastCodexCliActiveSyncIndex =
|
|
1358
1532
|
successAccountForResponse.index;
|
|
1359
1533
|
}
|
|
1534
|
+
clearPoolExhaustionCooldown();
|
|
1535
|
+
clearServerBurstCooldown();
|
|
1536
|
+
syncRuntimeObservability(requestTraceId);
|
|
1360
1537
|
return successResponse;
|
|
1361
1538
|
}
|
|
1362
1539
|
if (retryNextAccountBeforeFallback) {
|
|
@@ -1391,16 +1568,26 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1391
1568
|
allRateLimitedRetries++;
|
|
1392
1569
|
continue;
|
|
1393
1570
|
}
|
|
1394
|
-
const
|
|
1571
|
+
const now = Date.now();
|
|
1572
|
+
const poolCooldownUntil = count > 0 && waitMs > 0
|
|
1573
|
+
? armPoolExhaustionCooldown(waitMs, now)
|
|
1574
|
+
: 0;
|
|
1575
|
+
const effectiveWaitMs = poolCooldownUntil > 0
|
|
1576
|
+
? Math.max(0, poolCooldownUntil - now)
|
|
1577
|
+
: waitMs;
|
|
1578
|
+
const waitLabel = effectiveWaitMs > 0
|
|
1579
|
+
? formatWaitTime(effectiveWaitMs)
|
|
1580
|
+
: "a bit";
|
|
1395
1581
|
const message = count === 0
|
|
1396
1582
|
? "No Codex accounts configured. Run `codex login`."
|
|
1397
|
-
:
|
|
1398
|
-
? `All ${count} account(s) are rate-limited.
|
|
1399
|
-
: `All ${count} account(s) failed (server errors or auth issues). Check account health with \`codex
|
|
1583
|
+
: effectiveWaitMs > 0
|
|
1584
|
+
? `All ${count} account(s) are rate-limited. A short pool cooldown is now active for ${waitLabel}. Try again later or inspect \`codex auth status\`.`
|
|
1585
|
+
: `All ${count} account(s) failed (server errors or auth issues). Check account health with \`codex auth report --json\`.`;
|
|
1400
1586
|
runtimeMetrics.failedRequests++;
|
|
1401
1587
|
runtimeMetrics.lastError = message;
|
|
1588
|
+
syncRuntimeObservability(requestTraceId);
|
|
1402
1589
|
return new Response(JSON.stringify({ error: { message } }), {
|
|
1403
|
-
status:
|
|
1590
|
+
status: effectiveWaitMs > 0 ? 429 : 503,
|
|
1404
1591
|
headers: {
|
|
1405
1592
|
"content-type": "application/json; charset=utf-8",
|
|
1406
1593
|
},
|
|
@@ -1408,6 +1595,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
|
|
|
1408
1595
|
}
|
|
1409
1596
|
}
|
|
1410
1597
|
finally {
|
|
1598
|
+
syncRuntimeObservability(null);
|
|
1411
1599
|
clearCorrelationId();
|
|
1412
1600
|
}
|
|
1413
1601
|
},
|