oc-codex-multi-auth 6.19.0 → 6.20.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 (72) hide show
  1. package/dist/index.d.ts.map +1 -1
  2. package/dist/index.js +180 -79
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/accounts/persistence.d.ts +22 -0
  5. package/dist/lib/accounts/persistence.d.ts.map +1 -1
  6. package/dist/lib/accounts/persistence.js +79 -0
  7. package/dist/lib/accounts/persistence.js.map +1 -1
  8. package/dist/lib/accounts/rate-limits.d.ts +22 -0
  9. package/dist/lib/accounts/rate-limits.d.ts.map +1 -1
  10. package/dist/lib/accounts/rate-limits.js +25 -0
  11. package/dist/lib/accounts/rate-limits.js.map +1 -1
  12. package/dist/lib/accounts/rotation.d.ts +22 -23
  13. package/dist/lib/accounts/rotation.d.ts.map +1 -1
  14. package/dist/lib/accounts/rotation.js +62 -37
  15. package/dist/lib/accounts/rotation.js.map +1 -1
  16. package/dist/lib/accounts/stale-state.d.ts +31 -0
  17. package/dist/lib/accounts/stale-state.d.ts.map +1 -1
  18. package/dist/lib/accounts/stale-state.js +56 -1
  19. package/dist/lib/accounts/stale-state.js.map +1 -1
  20. package/dist/lib/accounts/state.d.ts +6 -0
  21. package/dist/lib/accounts/state.d.ts.map +1 -1
  22. package/dist/lib/accounts/state.js +15 -1
  23. package/dist/lib/accounts/state.js.map +1 -1
  24. package/dist/lib/auth/login-runner.d.ts +3 -0
  25. package/dist/lib/auth/login-runner.d.ts.map +1 -1
  26. package/dist/lib/auth/login-runner.js +35 -0
  27. package/dist/lib/auth/login-runner.js.map +1 -1
  28. package/dist/lib/codex-usage.d.ts +13 -5
  29. package/dist/lib/codex-usage.d.ts.map +1 -1
  30. package/dist/lib/codex-usage.js +33 -21
  31. package/dist/lib/codex-usage.js.map +1 -1
  32. package/dist/lib/parallel-probe.d.ts.map +1 -1
  33. package/dist/lib/parallel-probe.js +4 -2
  34. package/dist/lib/parallel-probe.js.map +1 -1
  35. package/dist/lib/request/fetch-helpers.d.ts +24 -0
  36. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  37. package/dist/lib/request/fetch-helpers.js +66 -20
  38. package/dist/lib/request/fetch-helpers.js.map +1 -1
  39. package/dist/lib/schemas.d.ts +9 -0
  40. package/dist/lib/schemas.d.ts.map +1 -1
  41. package/dist/lib/schemas.js +3 -0
  42. package/dist/lib/schemas.js.map +1 -1
  43. package/dist/lib/storage/flagged.d.ts.map +1 -1
  44. package/dist/lib/storage/flagged.js +5 -0
  45. package/dist/lib/storage/flagged.js.map +1 -1
  46. package/dist/lib/storage/migrations.d.ts +21 -0
  47. package/dist/lib/storage/migrations.d.ts.map +1 -1
  48. package/dist/lib/storage/migrations.js.map +1 -1
  49. package/dist/lib/storage/normalize.d.ts.map +1 -1
  50. package/dist/lib/storage/normalize.js +57 -1
  51. package/dist/lib/storage/normalize.js.map +1 -1
  52. package/dist/lib/tools/codex-doctor.d.ts.map +1 -1
  53. package/dist/lib/tools/codex-doctor.js +8 -66
  54. package/dist/lib/tools/codex-doctor.js.map +1 -1
  55. package/dist/lib/tools/codex-health.d.ts.map +1 -1
  56. package/dist/lib/tools/codex-health.js +7 -1
  57. package/dist/lib/tools/codex-health.js.map +1 -1
  58. package/dist/lib/tools/codex-list.d.ts.map +1 -1
  59. package/dist/lib/tools/codex-list.js +14 -1
  60. package/dist/lib/tools/codex-list.js.map +1 -1
  61. package/dist/lib/tools/codex-status.d.ts.map +1 -1
  62. package/dist/lib/tools/codex-status.js +6 -1
  63. package/dist/lib/tools/codex-status.js.map +1 -1
  64. package/dist/lib/tools/doctor-repair.d.ts +10 -0
  65. package/dist/lib/tools/doctor-repair.d.ts.map +1 -0
  66. package/dist/lib/tools/doctor-repair.js +86 -0
  67. package/dist/lib/tools/doctor-repair.js.map +1 -0
  68. package/dist/lib/tools/index.d.ts +3 -0
  69. package/dist/lib/tools/index.d.ts.map +1 -1
  70. package/dist/lib/tools/index.js.map +1 -1
  71. package/package.json +1 -1
  72. package/scripts/install-oc-codex-multi-auth-core.js +133 -6
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AA2U/D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MAqjI/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AA6U/D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MA6rI/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,QAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -45,7 +45,7 @@ import { resolveDisplayEmail } from "./lib/account-display.js";
45
45
  import { CodexAuthError } from "./lib/errors.js";
46
46
  import { getStoragePath, loadAccounts, withAccountStorageTransaction, clearAccounts, setStoragePath, loadFlaggedAccounts, withFlaggedAccountStorageTransaction, clearFlaggedAccounts, StorageError, formatStorageErrorHint, } from "./lib/storage.js";
47
47
  import { getWorkspaceIdentityKey } from "./lib/storage/identity.js";
48
- import { createCodexHeaders, extractRequestUrl, handleErrorResponse, handleSuccessResponse, isDeactivatedWorkspaceError, isInvalidatedAuthTokenError, createAbortError, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, refreshAndUpdateToken, rewriteUrlForCodex, shouldRefreshToken, transformRequestForCodex, } from "./lib/request/fetch-helpers.js";
48
+ import { createCodexHeaders, extractRequestUrl, handleErrorResponse, handleSuccessResponse, isDeactivatedWorkspaceError, isInvalidatedAuthTokenError, createAbortError, getUnsupportedCodexModelInfo, resolveUnsupportedCodexFallbackModel, isDefaultAutoFallbackModel, pickFallbackChainTarget, refreshAndUpdateToken, rewriteUrlForCodex, shouldRefreshToken, transformRequestForCodex, } from "./lib/request/fetch-helpers.js";
49
49
  import { shapeBodyForModel } from "./lib/request/helpers/responses-lite.js";
50
50
  import { DEACTIVATED_WORKSPACE_ERROR_CODE, isDeactivatedWorkspaceErrorMessage, isInvalidatedAuthTokenMessage, } from "./lib/error-sentinels.js";
51
51
  import { applyFastSessionDefaults, clampReasoningForModel, upsertBackendModelIdentityMessage, } from "./lib/request/request-transformer.js";
@@ -468,8 +468,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
468
468
  * served request and on a refused one alike, so the moment an account hits
469
469
  * 0% left we can record it instead of rediscovering it with a failed request
470
470
  * on every subsequent prompt. The block lands on the persisted
471
- * `rateLimitResetTimes` map, so it is remembered across restarts and shared
472
- * with other processes, and it clears itself once the window rolls over.
471
+ * account-wide `quotaExhaustedUntil` field, so it is remembered across
472
+ * restarts and clears itself once the window rolls over.
473
473
  *
474
474
  * Call this only for responses whose headers are authoritative: one the
475
475
  * backend served, or one it refused for a confirmed usage limit. Every other
@@ -495,7 +495,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
495
495
  return true;
496
496
  account.lastSwitchReason = "rate-limit";
497
497
  manager.saveToDiskDebounced();
498
- logWarn(`Account ${account.index + 1} (${account.email ?? "unknown"}) has no ${family} quota left; skipping it for ${formatWaitTime(resetAtMs - Date.now())}.`);
498
+ logWarn(`Account ${account.index + 1} has no shared subscription quota left; skipping it for ${formatWaitTime(resetAtMs - Date.now())}.`);
499
499
  return true;
500
500
  }
501
501
  catch (error) {
@@ -750,6 +750,23 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
750
750
  return null;
751
751
  return `resets in ${formatWaitTime(remaining)}`;
752
752
  };
753
+ // Account-wide subscription-quota exhaustion is a DIFFERENT block from the
754
+ // per-family rate limits above: it lives on its own field and is reported
755
+ // with its own label so a spent weekly quota is never shown as a transient
756
+ // 429 ("rate limit").
757
+ const getQuotaExhaustedUntil = (account, now) => {
758
+ const until = account.quotaExhaustedUntil;
759
+ if (typeof until !== "number" || !Number.isFinite(until) || until <= now) {
760
+ return null;
761
+ }
762
+ return until;
763
+ };
764
+ const formatQuotaExhaustionEntry = (account, now) => {
765
+ const until = getQuotaExhaustedUntil(account, now);
766
+ if (until === null)
767
+ return null;
768
+ return `quota exhausted, resets in ${formatWaitTime(until - now)}`;
769
+ };
753
770
  const applyUiRuntimeFromConfig = (pluginConfig) => {
754
771
  return setUiRuntimeOptions({
755
772
  v2Enabled: getCodexTuiV2(pluginConfig),
@@ -1265,6 +1282,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1265
1282
  resolveActiveIndex,
1266
1283
  getRateLimitResetTimeForFamily,
1267
1284
  formatRateLimitEntry,
1285
+ formatQuotaExhaustionEntry,
1268
1286
  buildJsonAccountIdentity,
1269
1287
  buildRoutingVisibilitySnapshot,
1270
1288
  appendRoutingVisibilityText,
@@ -1659,6 +1677,86 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1659
1677
  if (model) {
1660
1678
  attemptedUnsupportedFallbackModels.add(model);
1661
1679
  }
1680
+ // Degrading the model mid-request touches several coupled pieces:
1681
+ // the attempted-model set, the routing snapshot, the model's own
1682
+ // instructions, the reasoning clamp (only the 5.6 tiers accept
1683
+ // `max`, so an un-clamped sol -> gpt-5.5 hop turns a graceful
1684
+ // fallback into a hard 400) and the per-model body shape. Both the
1685
+ // entitlement fallback and the quota fallback go through here so
1686
+ // the two can never drift apart on any of them.
1687
+ const applyModelFallback = async (previousModel, target, reason) => {
1688
+ attemptedUnsupportedFallbackModels.add(previousModel);
1689
+ attemptedUnsupportedFallbackModels.add(target);
1690
+ model = target;
1691
+ modelFamily = getModelFamily(model);
1692
+ quotaKey = `${modelFamily}:${model}`;
1693
+ fallbackApplied = true;
1694
+ fallbackFrom = previousModel;
1695
+ fallbackTo = model;
1696
+ fallbackReason = reason;
1697
+ const fallbackInstructions = await getCodexInstructions(model);
1698
+ if (transformedBody && typeof transformedBody === "object") {
1699
+ transformedBody = {
1700
+ ...transformedBody,
1701
+ model,
1702
+ instructions: fallbackInstructions,
1703
+ input: upsertBackendModelIdentityMessage(transformedBody.input, model),
1704
+ };
1705
+ }
1706
+ else {
1707
+ let fallbackBody = {
1708
+ model,
1709
+ instructions: fallbackInstructions,
1710
+ };
1711
+ if (requestInit?.body && typeof requestInit.body === "string") {
1712
+ try {
1713
+ const parsed = JSON.parse(requestInit.body);
1714
+ fallbackBody = {
1715
+ ...parsed,
1716
+ model,
1717
+ instructions: fallbackInstructions,
1718
+ };
1719
+ if (Array.isArray(fallbackBody.input)) {
1720
+ fallbackBody.input = upsertBackendModelIdentityMessage(fallbackBody.input, model);
1721
+ }
1722
+ }
1723
+ catch {
1724
+ // Keep minimal fallback body if parsing fails.
1725
+ }
1726
+ }
1727
+ transformedBody = fallbackBody;
1728
+ }
1729
+ const clampedReasoning = clampReasoningForModel(transformedBody.reasoning, model);
1730
+ if (clampedReasoning !== transformedBody.reasoning) {
1731
+ transformedBody = {
1732
+ ...transformedBody,
1733
+ reasoning: clampedReasoning,
1734
+ };
1735
+ }
1736
+ requestInit = {
1737
+ ...(requestInit ?? {}),
1738
+ body: JSON.stringify(shapeBodyForModel(transformedBody)),
1739
+ };
1740
+ if (runtimeMetrics.lastSelectionSnapshot) {
1741
+ runtimeMetrics.lastSelectionSnapshot = {
1742
+ ...runtimeMetrics.lastSelectionSnapshot,
1743
+ family: modelFamily,
1744
+ model: model ?? null,
1745
+ requestedModel,
1746
+ effectiveModel: model ?? null,
1747
+ quotaKey,
1748
+ fallbackApplied,
1749
+ fallbackFrom,
1750
+ fallbackTo,
1751
+ fallbackReason,
1752
+ };
1753
+ }
1754
+ };
1755
+ // A degraded model must not degrade again without bound, even if a
1756
+ // custom chain is cyclic. The attempted set already prevents
1757
+ // revisiting a model; this caps the total hops per request.
1758
+ const MAX_QUOTA_FALLBACK_SWITCHES = 3;
1759
+ let quotaFallbackSwitches = 0;
1662
1760
  while (true) {
1663
1761
  let accountCount = accountManager.getAccountCount();
1664
1762
  const attempted = new Set();
@@ -1697,6 +1795,11 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1697
1795
  break;
1698
1796
  }
1699
1797
  attempted.add(account.index);
1798
+ // Hybrid's last-resort result is not necessarily eligible. Requests
1799
+ // must honor active blocks rather than sending it upstream anyway.
1800
+ if (selectionExplainability.some((entry) => entry.index === account.index && !entry.eligible)) {
1801
+ continue;
1802
+ }
1700
1803
  runtimeMetrics.lastSelectedAccountIndex = account.index;
1701
1804
  runtimeMetrics.lastQuotaKey = quotaKey;
1702
1805
  if (runtimeMetrics.lastSelectionSnapshot) {
@@ -2109,81 +2212,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2109
2212
  if (fallbackModel) {
2110
2213
  const previousModel = model ?? "gpt-5-codex";
2111
2214
  const previousModelFamily = modelFamily;
2112
- attemptedUnsupportedFallbackModels.add(previousModel);
2113
- attemptedUnsupportedFallbackModels.add(fallbackModel);
2114
2215
  accountManager.refundToken(account, previousModelFamily, previousModel);
2115
- model = fallbackModel;
2116
- modelFamily = getModelFamily(model);
2117
- quotaKey = `${modelFamily}:${model}`;
2118
- fallbackApplied = true;
2119
- fallbackFrom = previousModel;
2120
- fallbackTo = model;
2121
- fallbackReason = "fallback-unsupported-model-entitlement";
2122
- const fallbackInstructions = await getCodexInstructions(model);
2123
- if (transformedBody && typeof transformedBody === "object") {
2124
- transformedBody = {
2125
- ...transformedBody,
2126
- model,
2127
- instructions: fallbackInstructions,
2128
- input: upsertBackendModelIdentityMessage(transformedBody.input, model),
2129
- };
2130
- }
2131
- else {
2132
- let fallbackBody = {
2133
- model,
2134
- instructions: fallbackInstructions,
2135
- };
2136
- if (requestInit?.body && typeof requestInit.body === "string") {
2137
- try {
2138
- const parsed = JSON.parse(requestInit.body);
2139
- fallbackBody = {
2140
- ...parsed,
2141
- model,
2142
- instructions: fallbackInstructions,
2143
- };
2144
- if (Array.isArray(fallbackBody.input)) {
2145
- fallbackBody.input = upsertBackendModelIdentityMessage(fallbackBody.input, model);
2146
- }
2147
- }
2148
- catch {
2149
- // Keep minimal fallback body if parsing fails.
2150
- }
2151
- }
2152
- transformedBody = fallbackBody;
2153
- }
2154
- // The carried-over reasoning effort was clamped for the ORIGINAL
2155
- // model; the fallback target may not accept it (`max` exists only
2156
- // on the 5.6 tiers, so a sol -> gpt-5.5 hop must degrade it or the
2157
- // graceful fallback turns into a hard 400).
2158
- const clampedReasoning = clampReasoningForModel(transformedBody.reasoning, model);
2159
- if (clampedReasoning !== transformedBody.reasoning) {
2160
- transformedBody = {
2161
- ...transformedBody,
2162
- reasoning: clampedReasoning,
2163
- };
2164
- }
2165
- // Shape for whichever model this attempt targets. A 5.6 -> 5.5 fallback
2166
- // must go out in the classic shape, and a 5.6 -> 5.6 hop must re-fold
2167
- // the new model's instructions into `input` rather than leaving them
2168
- // at the top level.
2169
- requestInit = {
2170
- ...(requestInit ?? {}),
2171
- body: JSON.stringify(shapeBodyForModel(transformedBody)),
2172
- };
2173
- if (runtimeMetrics.lastSelectionSnapshot) {
2174
- runtimeMetrics.lastSelectionSnapshot = {
2175
- ...runtimeMetrics.lastSelectionSnapshot,
2176
- family: modelFamily,
2177
- model: model ?? null,
2178
- requestedModel,
2179
- effectiveModel: model ?? null,
2180
- quotaKey,
2181
- fallbackApplied,
2182
- fallbackFrom,
2183
- fallbackTo,
2184
- fallbackReason,
2185
- };
2186
- }
2216
+ await applyModelFallback(previousModel, fallbackModel, "fallback-unsupported-model-entitlement");
2187
2217
  runtimeMetrics.lastError = `Model fallback: ${previousModel} -> ${model}`;
2188
2218
  runtimeMetrics.lastErrorCategory = "model-fallback";
2189
2219
  logWarn(`Model ${previousModel} is unsupported for this ChatGPT account. Falling back to ${model}.`, {
@@ -2279,7 +2309,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2279
2309
  await sleep(addJitter(Math.max(MIN_BACKOFF_MS, delayMs), 0.2));
2280
2310
  continue;
2281
2311
  }
2282
- accountManager.markRateLimitedWithReason(account, delayMs, modelFamily, parseRateLimitReason(rateLimit.code), model);
2312
+ // Authoritative subscription exhaustion already has its own block.
2313
+ // Do not duplicate its reset as a model/family transient 429; retain
2314
+ // any genuine transient state written by other in-flight requests.
2315
+ if (!quotaExhausted) {
2316
+ accountManager.markRateLimitedWithReason(account, delayMs, modelFamily, parseRateLimitReason(rateLimit.code), model);
2317
+ }
2283
2318
  accountManager.recordRateLimit(account, modelFamily, model);
2284
2319
  account.lastSwitchReason = "rate-limit";
2285
2320
  runtimeMetrics.accountRotations++;
@@ -2507,6 +2542,72 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
2507
2542
  const unavailableCount = accountManager
2508
2543
  .getAccountsSnapshot()
2509
2544
  .filter((account) => !fetchedAccountKeys.has(getAccountDiagnosticsKey(account))).length;
2545
+ const enabledSelection = count > 0 ? accountManager
2546
+ .getSelectionExplainability(modelFamily, model)
2547
+ .filter((entry) => entry.enabled) : [];
2548
+ const upstreamBlocked = enabledSelection.length > 0 && enabledSelection.every((entry) => entry.rateLimitedUntil !== undefined || entry.quotaExhaustedUntil !== undefined);
2549
+ // Every enabled account has an active upstream block. Before waiting out a
2550
+ // block that can run for days (`retryAllAccountsMaxRetries`
2551
+ // defaults to Infinity), degrade to the next chain model that is
2552
+ // actually usable right now. Gated exactly like the entitlement
2553
+ // auto-fallback -- same default-selector entry models, same
2554
+ // opt-out env vars -- even when an entry ID was selected directly.
2555
+ // Local token depletion and auth cooldown alone never trigger it.
2556
+ // An account-wide quota block fails the eligibility test
2557
+ // on every candidate, so it correctly falls through to the wait.
2558
+ if (upstreamBlocked &&
2559
+ waitMs > 0 &&
2560
+ count > 0 &&
2561
+ model &&
2562
+ quotaFallbackSwitches < MAX_QUOTA_FALLBACK_SWITCHES &&
2563
+ isDefaultAutoFallbackModel(model, attemptedUnsupportedFallbackModels)) {
2564
+ const rejected = new Set();
2565
+ let usableFallback;
2566
+ while (true) {
2567
+ const candidate = pickFallbackChainTarget({
2568
+ currentModel: model,
2569
+ attemptedModels: new Set([
2570
+ ...attemptedUnsupportedFallbackModels,
2571
+ ...rejected,
2572
+ ]),
2573
+ customChain: unsupportedCodexFallbackChain,
2574
+ fallbackToGpt52OnUnsupportedGpt53,
2575
+ });
2576
+ if (!candidate || rejected.has(candidate))
2577
+ break;
2578
+ // Only degrade to a model some account can serve NOW,
2579
+ // otherwise the hop just moves the same block sideways.
2580
+ const candidatePool = getModelAccountPool(pluginConfig, candidate);
2581
+ const strictCandidatePool = candidatePool.length > 0 &&
2582
+ getModelAccountPoolMode(pluginConfig, candidate) === "strict";
2583
+ const candidateAccounts = accountManager.getAccountsSnapshot();
2584
+ const candidateEligible = accountManager.getSelectionExplainability(getModelFamily(candidate), candidate).some((entry) => entry.eligible && (!strictCandidatePool ||
2585
+ candidateAccounts.some((account) => account.index === entry.index &&
2586
+ candidatePool.some((key) => matchesModelPoolAccountKey(account, key)))));
2587
+ // A preferred pool may spill into general accounts; a strict
2588
+ // pool must contain an eligible member. This does not select
2589
+ // an account or advance any rotation cursor.
2590
+ if (candidateEligible) {
2591
+ usableFallback = candidate;
2592
+ break;
2593
+ }
2594
+ rejected.add(candidate);
2595
+ }
2596
+ if (usableFallback) {
2597
+ const previousModel = model;
2598
+ quotaFallbackSwitches++;
2599
+ await applyModelFallback(previousModel, usableFallback, "fallback-quota-exhausted");
2600
+ runtimeMetrics.lastError = `Model fallback: ${previousModel} -> ${model}`;
2601
+ runtimeMetrics.lastErrorCategory = "model-fallback";
2602
+ logWarn(`All ${count} account(s) are rate-limited or out of quota for ${previousModel}. Falling back to ${model}.`, {
2603
+ requestedModel: previousModel,
2604
+ effectiveModel: model,
2605
+ fallbackApplied: true,
2606
+ fallbackReason: "fallback-quota-exhausted",
2607
+ });
2608
+ continue;
2609
+ }
2610
+ }
2510
2611
  if (retryAllAccountsRateLimited &&
2511
2612
  count > 0 &&
2512
2613
  waitMs > 0 &&