@juspay/neurolink 10.12.7 → 10.12.8

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.
@@ -18,6 +18,8 @@ import { clearAccountCooldown, loadAccountCooldowns, saveAccountCooldown, } from
18
18
  import { anthropicAccountKeysEqual, ENV_ANTHROPIC_ACCOUNT_KEY, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, shouldLoadFallbackCredential, } from "../../proxy/accountSelection.js";
19
19
  import { getUnifiedRateLimitStatus, isQuotaOverageAvailable, loadAccountQuotas, parseQuotaHeaders, saveAccountQuota, } from "../../proxy/accountQuota.js";
20
20
  import { fetchAccountUsage, listAnthropicAccountsForUsage, usageToQuota, } from "../../proxy/accountUsage.js";
21
+ import { AccountQuotaRefreshCoordinator } from "../../proxy/accountQuotaRefreshCoordinator.js";
22
+ import { ProviderTransportCoordinator } from "../../proxy/providerTransportCoordinator.js";
21
23
  import { buildProxyLimitHeaders, summarizePoolHeadroom, } from "../../proxy/quotaHeaders.js";
22
24
  import { buildClaudeError, ClaudeStreamSerializer, generateToolUseId, parseClaudeRequest, serializeClaudeResponse, } from "../../proxy/claudeFormat.js";
23
25
  import { buildAnthropicModelsListResponse, buildTranslationOptions, extractText, extractToolArgs, extractUsageFromStreamResult, handleTranslatedJsonRequest, handleTranslatedStreamRequest, hasTranslatedOutput, } from "../../proxy/proxyTranslationEngine.js";
@@ -111,8 +113,11 @@ function fetchAnthropicUpstream(url, init) {
111
113
  });
112
114
  }
113
115
  const accountRuntimeState = new Map();
114
- /** Persisted quota is advisory after a restart. Older snapshots cannot reject
115
- * an account or create a new cooldown because the provider may have reset it. */
116
+ const accountQuotaRefreshCoordinator = new AccountQuotaRefreshCoordinator();
117
+ const providerTransportCoordinator = new ProviderTransportCoordinator();
118
+ /** Snapshot age is diagnostic and controls lightweight refresh eligibility. It
119
+ * must never discard known healthy routing evidence or trigger a user request
120
+ * on another account solely to rediscover quota. */
116
121
  const QUOTA_SNAPSHOT_FRESHNESS_MS = 15 * 60 * 1000;
117
122
  /** Shared across requests so a concurrent burst gets at most two retries for
118
123
  * the account/window, rather than every request starting its own retry chain. */
@@ -646,6 +651,65 @@ const MIN_USAGE_REFETCH_INTERVAL_MS = 15_000;
646
651
  const lastUsageFetchAt = new Map();
647
652
  let limitsRefreshInFlight = null;
648
653
  const USAGE_REFRESH_CONCURRENCY = 4;
654
+ async function applyAccountUsageResult(account, fetchResult, observedAt, prior) {
655
+ if (fetchResult.ok === false) {
656
+ return null;
657
+ }
658
+ const state = getOrCreateRuntimeState(account.key);
659
+ const quota = usageToQuota(fetchResult.usage, {
660
+ now: observedAt,
661
+ prior: state.quota ?? prior ?? null,
662
+ });
663
+ if (!quota) {
664
+ return null;
665
+ }
666
+ // The usage payload cannot be newer than the request that fetched it. A
667
+ // passive response captured after that request started wins, including its
668
+ // cooldown decision and persisted snapshot.
669
+ if (quota.lastUpdated < (state.quota?.lastUpdated ?? 0)) {
670
+ return state.quota ?? null;
671
+ }
672
+ state.quota = quota;
673
+ const cooldownUpdate = reconcileCooldownFromQuota(state, quota, Date.now());
674
+ if (cooldownUpdate?.kind === "cooled") {
675
+ await saveAccountCooldown(account.key, cooldownUpdate.coolingUntil, cooldownUpdate.coolingReason).catch(() => {
676
+ // Non-fatal: the cooldown is already active in memory.
677
+ });
678
+ }
679
+ else if (cooldownUpdate?.kind === "cleared") {
680
+ await clearAccountCooldown(account.key, cooldownUpdate.coolingUntil).catch(() => {
681
+ // Non-fatal: the next successful response will reconcile again.
682
+ });
683
+ }
684
+ await saveAccountQuota(account.label, quota).catch(() => {
685
+ // Non-fatal: quota persistence is best-effort.
686
+ });
687
+ return quota;
688
+ }
689
+ async function fetchValidatedAccountUsage(account) {
690
+ const result = await fetchAccountUsage(account);
691
+ if (result.ok === false) {
692
+ return result;
693
+ }
694
+ const recognizable = usageToQuota(result.usage, {
695
+ now: Date.now(),
696
+ prior: accountRuntimeState.get(account.key)?.quota ?? null,
697
+ });
698
+ return recognizable
699
+ ? result
700
+ : {
701
+ ok: false,
702
+ reason: "parse",
703
+ error: "usage payload had no recognizable limit windows",
704
+ };
705
+ }
706
+ async function refreshAccountQuotaInBackground(account, trigger) {
707
+ const refresh = await accountQuotaRefreshCoordinator.run(account, trigger, fetchValidatedAccountUsage);
708
+ if (refresh.kind !== "completed" || refresh.result.ok === false) {
709
+ return;
710
+ }
711
+ await applyAccountUsageResult(account, refresh.result, refresh.startedAt);
712
+ }
649
713
  /**
650
714
  * Fetch fresh limits from Anthropic's usage endpoint for every eligible OAuth
651
715
  * account and write them through the exact same chain the passive header
@@ -686,6 +750,7 @@ async function refreshAccountLimits(options = {}) {
686
750
  fetchedAt,
687
751
  snapshot: true,
688
752
  results: accounts.map((account) => buildResult(account, "snapshot", null)),
753
+ refreshMetrics: accountQuotaRefreshCoordinator.getMetrics(),
689
754
  };
690
755
  }
691
756
  const results = new Array(accounts.length);
@@ -710,7 +775,12 @@ async function refreshAccountLimits(options = {}) {
710
775
  // Isolate failures per account: an unexpected rejection must not abort
711
776
  // the Promise.all sweep and turn the whole /limits response into a 502.
712
777
  try {
713
- const fetchResult = await fetchAccountUsage(account);
778
+ const refresh = await accountQuotaRefreshCoordinator.run(account, `manual:${account.key}`, fetchValidatedAccountUsage, { force: true });
779
+ if (refresh.kind !== "completed") {
780
+ results[index] = buildResult(account, "throttled", null);
781
+ continue;
782
+ }
783
+ const fetchResult = refresh.result;
714
784
  // `=== false` (not `!ok`) — the react-hooks sub-build compiles this
715
785
  // file without strictNullChecks, where negated boolean-discriminant
716
786
  // narrowing does not apply.
@@ -718,35 +788,11 @@ async function refreshAccountLimits(options = {}) {
718
788
  results[index] = buildResult(account, "error", null, fetchResult.error);
719
789
  continue;
720
790
  }
721
- const state = getOrCreateRuntimeState(account.key);
722
- const capturedAt = Date.now();
723
- const quota = usageToQuota(fetchResult.usage, {
724
- now: capturedAt,
725
- prior: state.quota ?? persisted[account.label] ?? null,
726
- });
791
+ const quota = await applyAccountUsageResult(account, fetchResult, refresh.startedAt, persisted[account.label] ?? null);
727
792
  if (!quota) {
728
793
  results[index] = buildResult(account, "error", null, "usage payload had no recognizable limit windows");
729
794
  continue;
730
795
  }
731
- // Guard against a passive header capture that landed mid-fetch: never
732
- // replace a fresher runtime snapshot with an older reading.
733
- if (quota.lastUpdated >= (state.quota?.lastUpdated ?? 0)) {
734
- state.quota = quota;
735
- }
736
- const cooldownUpdate = reconcileCooldownFromQuota(state, quota, capturedAt);
737
- if (cooldownUpdate?.kind === "cooled") {
738
- await saveAccountCooldown(account.key, cooldownUpdate.coolingUntil, cooldownUpdate.coolingReason).catch(() => {
739
- // Non-fatal: cooldown is already active in memory.
740
- });
741
- }
742
- else if (cooldownUpdate?.kind === "cleared") {
743
- await clearAccountCooldown(account.key, cooldownUpdate.coolingUntil).catch(() => {
744
- // Non-fatal: the next successful response will reconcile again.
745
- });
746
- }
747
- await saveAccountQuota(account.label, quota).catch(() => {
748
- // Non-fatal: quota persistence is best-effort
749
- });
750
796
  results[index] = buildResult(account, "refreshed", quota);
751
797
  }
752
798
  catch (err) {
@@ -755,7 +801,12 @@ async function refreshAccountLimits(options = {}) {
755
801
  }
756
802
  };
757
803
  await Promise.all(Array.from({ length: Math.min(USAGE_REFRESH_CONCURRENCY, accounts.length || 1) }, () => worker()));
758
- return { fetchedAt, snapshot: false, results };
804
+ return {
805
+ fetchedAt,
806
+ snapshot: false,
807
+ results,
808
+ refreshMetrics: accountQuotaRefreshCoordinator.getMetrics(),
809
+ };
759
810
  }
760
811
  /** Quota-aware selection is on by default; disable with
761
812
  * NEUROLINK_PROXY_QUOTA_ROUTING=off|false|0. Only affects the fill-first
@@ -800,8 +851,30 @@ function accountSortMetrics(accountKey, now, sessionSoftLimit, sessionResetToler
800
851
  const q = st?.quota;
801
852
  const quotaLastUpdated = q && Number.isFinite(q.lastUpdated) ? q.lastUpdated : null;
802
853
  const quotaAgeMs = quotaLastUpdated === null ? null : Math.max(0, now - quotaLastUpdated);
854
+ const refreshState = accountQuotaRefreshCoordinator.getState(accountKey);
803
855
  const quotaStale = quotaAgeMs !== null && quotaAgeMs > QUOTA_SNAPSHOT_FRESHNESS_MS;
804
- const routingQuota = quotaStale ? undefined : q;
856
+ const normalizeStatus = (status) => status.trim().toLowerCase();
857
+ const rawOverageEligible = isQuotaOverageAvailable(q);
858
+ const observedStatuses = [
859
+ q?.unifiedStatus,
860
+ q?.sessionStatus,
861
+ q?.weeklyStatus,
862
+ ].filter((status) => !!status?.trim());
863
+ const staleHardOrAmbiguous = quotaStale &&
864
+ !!q &&
865
+ !rawOverageEligible &&
866
+ observedStatuses.some((status) => normalizeStatus(status) !== "allowed");
867
+ const quotaFreshness = !q
868
+ ? "unknown"
869
+ : !quotaStale
870
+ ? "fresh"
871
+ : staleHardOrAmbiguous
872
+ ? "refresh_due"
873
+ : "stale_known";
874
+ // A stale rejection is advisory after restart and cannot quarantine an
875
+ // account. Keep it as evidence, rank it behind known-healthy accounts, and
876
+ // refresh it through the usage endpoint before relying on the old status.
877
+ const routingQuota = staleHardOrAmbiguous ? undefined : q;
805
878
  const coolingActive = !!st?.coolingUntil && now < st.coolingUntil;
806
879
  // resetEpochToMs returns undefined for absent OR passed resets, so a
807
880
  // ticking window is exactly "reset !== undefined".
@@ -830,17 +903,46 @@ function accountSortMetrics(accountKey, now, sessionSoftLimit, sessionResetToler
830
903
  : "allowed"
831
904
  : null;
832
905
  const overageEligible = isQuotaOverageAvailable(routingQuota);
833
- const saturated = sessionStatus === "throttled" ||
834
- (sessionTicking && (sessionUsed ?? 0) >= sessionSoftLimit);
906
+ const hardSaturated = !overageEligible &&
907
+ (sessionStatus === "rejected" ||
908
+ sessionStatus === "throttled" ||
909
+ weeklyStatus === "rejected" ||
910
+ weeklyStatus === "throttled" ||
911
+ routingQuota?.unifiedStatus?.trim().toLowerCase() === "rejected" ||
912
+ (sessionTicking && (sessionUsed ?? 0) >= 1) ||
913
+ (weeklyTicking && (weeklyUsed ?? 0) >= 1));
914
+ const softSaturated = !hardSaturated &&
915
+ !overageEligible &&
916
+ sessionTicking &&
917
+ (sessionUsed ?? 0) >= sessionSoftLimit;
918
+ const saturated = hardSaturated || softSaturated;
835
919
  return {
836
- usable: !coolingActive &&
837
- weeklyStatus !== "rejected" &&
838
- (sessionStatus !== "rejected" || overageEligible) &&
839
- (routingQuota?.unifiedStatus?.trim().toLowerCase() !== "rejected" ||
840
- overageEligible),
841
- saturated: !quotaStale && saturated,
842
- hasQuota: !!routingQuota,
920
+ usable: !coolingActive && !hardSaturated,
921
+ saturated,
922
+ hasQuota: !!q,
923
+ quotaEvidenceRank: quotaFreshness === "fresh" || quotaFreshness === "stale_known"
924
+ ? 0
925
+ : quotaFreshness === "refresh_due"
926
+ ? 1
927
+ : 2,
843
928
  quotaStale,
929
+ quotaFreshness,
930
+ refreshNeeded: quotaFreshness === "unknown" || quotaFreshness === "refresh_due",
931
+ refreshReason: quotaFreshness === "unknown"
932
+ ? "startup_unknown"
933
+ : quotaFreshness === "refresh_due"
934
+ ? "ambiguous_snapshot"
935
+ : null,
936
+ refreshInFlight: refreshState.inFlight,
937
+ lastRefreshAttemptAt: refreshState.lastAttemptAt ?? null,
938
+ lastRefreshSuccessAt: refreshState.lastSuccessAt ?? null,
939
+ nextRefreshEligibleAt: refreshState.nextEligibleAt ?? null,
940
+ saturationKind: hardSaturated ? "hard" : softSaturated ? "soft" : "none",
941
+ softLimitOverrideReason: overageEligible &&
942
+ sessionTicking &&
943
+ (sessionUsed ?? 0) >= sessionSoftLimit
944
+ ? "overage"
945
+ : null,
844
946
  quotaLastUpdated,
845
947
  quotaAgeMs,
846
948
  coolingActive,
@@ -880,8 +982,8 @@ function compareAccountRoutingFactors(a, b, metricsByKey, primaryKey) {
880
982
  au === bu ? "insertion_order" : "cooldown_recovery",
881
983
  ];
882
984
  }
883
- if (ma.hasQuota !== mb.hasQuota) {
884
- return [ma.hasQuota ? 1 : -1, "quota_probe"];
985
+ if (ma.quotaEvidenceRank !== mb.quotaEvidenceRank) {
986
+ return [ma.quotaEvidenceRank - mb.quotaEvidenceRank, "quota_evidence"];
885
987
  }
886
988
  if (ma.saturated !== mb.saturated) {
887
989
  return [ma.saturated ? 1 : -1, "session_headroom"];
@@ -928,9 +1030,8 @@ function orderAccountsByQuotaWithMetrics(accounts, now, primaryKey, sessionSoftL
928
1030
  * temporarily demoted until that session resets.
929
1031
  *
930
1032
  * Priority among usable accounts:
931
- * 1. no quota data yet probe first: one request reveals its windows and
932
- * self-corrects the ordering. (Ranking unknowns last would starve them
933
- * forever: never picked → never observed → never comparable.)
1033
+ * 1. known healthy quota before unknown or ambiguous stale evidence. Quota
1034
+ * discovery is handled by a lightweight usage GET, never a user request.
934
1035
  * 2. session headroom before session-saturated (>= soft limit or
935
1036
  * "throttled") — do not re-hammer an urgent weekly account while its 5h
936
1037
  * capacity is temporarily unavailable.
@@ -948,6 +1049,62 @@ function orderAccountsByQuotaWithMetrics(accounts, now, primaryKey, sessionSoftL
948
1049
  function orderAccountsByQuota(accounts, now, primaryKey, sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs()) {
949
1050
  return orderAccountsByQuotaWithMetrics(accounts, now, primaryKey, sessionSoftLimit, sessionResetToleranceMs).orderedAccounts;
950
1051
  }
1052
+ function scheduleAdaptiveQuotaRefreshes(accounts, orderedAccounts, sessionSoftLimit, routingMetrics) {
1053
+ for (const account of accounts) {
1054
+ if (account.type !== "oauth") {
1055
+ continue;
1056
+ }
1057
+ const metrics = routingMetrics?.get(account.key) ??
1058
+ accountSortMetrics(account.key, Date.now(), sessionSoftLimit, getSessionResetToleranceMs());
1059
+ if (metrics.quotaFreshness === "unknown") {
1060
+ void refreshAccountQuotaInBackground(account, `startup-unknown:${account.key}`).catch((error) => {
1061
+ logger.debug(`[proxy] background quota discovery failed account=${account.label}: ${describeTransportError(error)}`);
1062
+ });
1063
+ }
1064
+ else if (metrics.quotaFreshness === "refresh_due") {
1065
+ void refreshAccountQuotaInBackground(account, `ambiguous:${metrics.quotaLastUpdated ?? 0}`).catch((error) => {
1066
+ logger.debug(`[proxy] ambiguous quota refresh failed account=${account.label}: ${describeTransportError(error)}`);
1067
+ });
1068
+ }
1069
+ }
1070
+ const active = orderedAccounts[0];
1071
+ const candidate = orderedAccounts[1];
1072
+ if (!active || !candidate || candidate.type !== "oauth") {
1073
+ return;
1074
+ }
1075
+ const activeQuota = accountRuntimeState.get(active.key)?.quota;
1076
+ if (!activeQuota) {
1077
+ return;
1078
+ }
1079
+ const sessionPrewarmAt = Math.max(0, sessionSoftLimit - 0.05);
1080
+ const needsPrewarm = (activeQuota.sessionUsed ?? 0) >= sessionPrewarmAt ||
1081
+ (activeQuota.weeklyUsed ?? 0) >= 0.9;
1082
+ if (!needsPrewarm) {
1083
+ return;
1084
+ }
1085
+ const trigger = [
1086
+ "handoff",
1087
+ active.key,
1088
+ activeQuota.sessionResetAt ?? 0,
1089
+ activeQuota.weeklyResetAt ?? 0,
1090
+ candidate.key,
1091
+ ].join(":");
1092
+ void refreshAccountQuotaInBackground(candidate, trigger).catch((error) => {
1093
+ logger.debug(`[proxy] quota handoff prewarm failed account=${candidate.label}: ${describeTransportError(error)}`);
1094
+ });
1095
+ }
1096
+ function scheduleHandoffQuotaRefresh(current, candidate, handoffEpoch, sessionSoftLimit) {
1097
+ if (!candidate || candidate.type !== "oauth") {
1098
+ return;
1099
+ }
1100
+ const metrics = accountSortMetrics(candidate.key, Date.now(), sessionSoftLimit, getSessionResetToleranceMs());
1101
+ if (metrics.quotaFreshness === "fresh") {
1102
+ return;
1103
+ }
1104
+ void refreshAccountQuotaInBackground(candidate, `hard-handoff:${current.key}:${handoffEpoch}:${candidate.key}`).catch((error) => {
1105
+ logger.debug(`[proxy] hard-handoff quota refresh failed account=${candidate.label}: ${describeTransportError(error)}`);
1106
+ });
1107
+ }
951
1108
  function buildRoutingDecision(args) {
952
1109
  const { accounts, orderedAccounts, metricsByKey, evaluatedAt, strategy, primaryKey, quotaRoutingEnabled, quotaOrdered, sessionSoftLimit, sessionResetToleranceMs, rotationOffset, } = args;
953
1110
  const sourceIndexes = new Map(accounts.map((account, index) => [account.key, index]));
@@ -969,6 +1126,15 @@ function buildRoutingDecision(args) {
969
1126
  saturated: metrics.saturated,
970
1127
  quotaObserved: metrics.hasQuota,
971
1128
  quotaStale: metrics.quotaStale,
1129
+ quotaFreshness: metrics.quotaFreshness,
1130
+ refreshNeeded: metrics.refreshNeeded,
1131
+ refreshReason: metrics.refreshReason,
1132
+ refreshInFlight: metrics.refreshInFlight,
1133
+ lastRefreshAttemptAt: metrics.lastRefreshAttemptAt,
1134
+ lastRefreshSuccessAt: metrics.lastRefreshSuccessAt,
1135
+ nextRefreshEligibleAt: metrics.nextRefreshEligibleAt,
1136
+ saturationKind: metrics.saturationKind,
1137
+ softLimitOverrideReason: metrics.softLimitOverrideReason,
972
1138
  quotaLastUpdated: metrics.quotaLastUpdated,
973
1139
  quotaAgeMs: metrics.quotaAgeMs,
974
1140
  coolingActive: metrics.coolingActive,
@@ -1096,7 +1262,7 @@ function selectClaudeProxyAccountOrder(args) {
1096
1262
  if (routingDecision) {
1097
1263
  setRoutingDecision(routingDecision);
1098
1264
  }
1099
- return orderedAccounts;
1265
+ return { orderedAccounts, metricsByKey };
1100
1266
  }
1101
1267
  // ---------------------------------------------------------------------------
1102
1268
  // OAuth polyfill helpers (extracted to reduce block nesting)
@@ -2207,7 +2373,7 @@ async function loadClaudeProxyAccounts(args) {
2207
2373
  tracer?.end(401, Date.now() - requestStartTime);
2208
2374
  return { response: buildLoggedClaudeError(401, reauthMsg) };
2209
2375
  }
2210
- const orderedAccounts = selectClaudeProxyAccountOrder({
2376
+ const { orderedAccounts, metricsByKey } = selectClaudeProxyAccountOrder({
2211
2377
  enabledAccounts,
2212
2378
  accountStrategy,
2213
2379
  primaryAccountKey,
@@ -2216,6 +2382,11 @@ async function loadClaudeProxyAccounts(args) {
2216
2382
  sessionResetToleranceMs,
2217
2383
  setRoutingDecision,
2218
2384
  });
2385
+ if (accountStrategy === "fill-first" &&
2386
+ quotaRoutingEnabled &&
2387
+ enabledAccounts.length > 1) {
2388
+ scheduleAdaptiveQuotaRefreshes(enabledAccounts, orderedAccounts, sessionSoftLimit, metricsByKey);
2389
+ }
2219
2390
  const normalizedAnthropicBody = normalizeClaudeRequestForAnthropic(body);
2220
2391
  const bodyStr = JSON.stringify(normalizedAnthropicBody);
2221
2392
  const requestStart = Date.now();
@@ -2560,7 +2731,7 @@ async function tryAutoClaudeFallback(args) {
2560
2731
  }
2561
2732
  }
2562
2733
  function buildClaudeAnthropicFailureResponse(args) {
2563
- const { tracer, requestStartTime, authFailureMessage, authCooldownMessage, invalidRequestFailure, sawNetworkError, sawTransientFailure, sawRateLimit, lastError, fallbackFailureMessage, orderedAccounts, buildLoggedClaudeError, logProxyBody, logFinalRequest, } = args;
2734
+ const { tracer, requestStartTime, authFailureMessage, authCooldownMessage, invalidRequestFailure, sawNetworkError, sawTransientFailure, sawRateLimit, lastError, lastTransportErrorCode, lastTransportScope, fallbackFailureMessage, orderedAccounts, buildLoggedClaudeError, logProxyBody, logFinalRequest, } = args;
2564
2735
  if (authFailureMessage && !sawRateLimit) {
2565
2736
  tracer?.setError("authentication_error", authFailureMessage);
2566
2737
  tracer?.end(401, Date.now() - requestStartTime);
@@ -2598,12 +2769,22 @@ function buildClaudeAnthropicFailureResponse(args) {
2598
2769
  const fallbackSuffix = fallbackFailureMessage
2599
2770
  ? ` Fallback also failed: ${fallbackFailureMessage}`
2600
2771
  : "";
2601
- const msg = `All Anthropic accounts failed due to transient upstream/network errors. Last error: ${lastError instanceof Error
2772
+ const transportDescription = lastTransportScope === "shared_provider_transport"
2773
+ ? "shared Anthropic network"
2774
+ : lastTransportScope === "connection_transport"
2775
+ ? "Anthropic connection"
2776
+ : null;
2777
+ const msg = `${transportDescription ? `${transportDescription} failure prevented safe cross-account rotation` : "All Anthropic accounts failed due to transient upstream/network errors"}. Last error${lastTransportErrorCode ? ` (${lastTransportErrorCode})` : ""}: ${lastError instanceof Error
2602
2778
  ? lastError.message
2603
2779
  : String(lastError ?? "unknown")}.${fallbackSuffix}`;
2604
2780
  tracer?.setError("transient_error", msg.slice(0, 500));
2605
2781
  tracer?.end(502, Date.now() - requestStartTime);
2606
- return buildLoggedClaudeError(502, msg, fallbackFailureMessage ? "fallback_exhausted" : "transient_error");
2782
+ return buildLoggedClaudeError(502, msg, fallbackFailureMessage ? "fallback_exhausted" : "transient_error", {
2783
+ ...(lastTransportErrorCode
2784
+ ? { errorCode: lastTransportErrorCode }
2785
+ : {}),
2786
+ ...(lastTransportScope ? { transportScope: lastTransportScope } : {}),
2787
+ });
2607
2788
  }
2608
2789
  if (!sawRateLimit) {
2609
2790
  const fallbackSuffix = fallbackFailureMessage
@@ -4063,6 +4244,7 @@ function createClaudeRequestRuntimeContext(args) {
4063
4244
  ? "stream_error"
4064
4245
  : "handler_error",
4065
4246
  message: errorMessage,
4247
+ errorCode: extra?.errorCode,
4066
4248
  });
4067
4249
  }
4068
4250
  else {
@@ -4083,6 +4265,10 @@ function createClaudeRequestRuntimeContext(args) {
4083
4265
  responseTimeMs: Date.now() - requestStartTime,
4084
4266
  ...(errorType ? { errorType } : {}),
4085
4267
  ...(errorMessage ? { errorMessage } : {}),
4268
+ ...(extra?.errorCode ? { errorCode: extra.errorCode } : {}),
4269
+ ...(extra?.transportScope
4270
+ ? { transportScope: extra.transportScope }
4271
+ : {}),
4086
4272
  ...(extra?.inputTokens !== undefined
4087
4273
  ? { inputTokens: extra.inputTokens }
4088
4274
  : {}),
@@ -4104,7 +4290,7 @@ function createClaudeRequestRuntimeContext(args) {
4104
4290
  const buildLoggedClaudeError = (status, message, errorType, extra) => {
4105
4291
  const errorBody = buildClaudeError(status, message, errorType);
4106
4292
  const errorBodyText = JSON.stringify(errorBody);
4107
- logFinalRequest(status, extra?.account ?? "", extra?.accountType ?? "final", errorType, message);
4293
+ logFinalRequest(status, extra?.account ?? "", extra?.accountType ?? "final", errorType, message, extra);
4108
4294
  logProxyBody({
4109
4295
  phase: "client_response",
4110
4296
  headers: { "content-type": "application/json" },
@@ -4156,6 +4342,9 @@ function createAnthropicAttemptLogger(args) {
4156
4342
  ...(errorType ? { errorType } : {}),
4157
4343
  ...(errorMessage ? { errorMessage } : {}),
4158
4344
  ...(extra?.errorCode ? { errorCode: extra.errorCode } : {}),
4345
+ ...(extra?.transportScope
4346
+ ? { transportScope: extra.transportScope }
4347
+ : {}),
4159
4348
  ...(extra?.inputTokens !== undefined
4160
4349
  ? { inputTokens: extra.inputTokens }
4161
4350
  : {}),
@@ -4372,6 +4561,7 @@ async function fetchAnthropicAccountResponse(args) {
4372
4561
  }
4373
4562
  catch (fetchErr) {
4374
4563
  const retryable = isRetryableNetworkError(fetchErr);
4564
+ const transportScope = classifyNetworkTransportScope(fetchErr);
4375
4565
  // Every dispatched upstream request is an attempt, including terminal
4376
4566
  // transport failures. Record it once before preserving the throw behavior.
4377
4567
  sawNetworkError = true;
@@ -4383,6 +4573,7 @@ async function fetchAnthropicAccountResponse(args) {
4383
4573
  logAttempt(502, "network_error", errorMessage, {
4384
4574
  retryable,
4385
4575
  errorCode,
4576
+ transportScope,
4386
4577
  });
4387
4578
  tracer?.setError("network_error", errorMessage);
4388
4579
  if (retryable) {
@@ -4395,6 +4586,8 @@ async function fetchAnthropicAccountResponse(args) {
4395
4586
  return {
4396
4587
  continueLoop: true,
4397
4588
  retrySameAccount: true,
4589
+ transportScope,
4590
+ errorCode,
4398
4591
  lastError,
4399
4592
  sawRateLimit,
4400
4593
  sawNetworkError,
@@ -4572,6 +4765,15 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4572
4765
  let transientSameAccountRetries = 0;
4573
4766
  let rateLimitSameAccountRetries = 0;
4574
4767
  while (true) {
4768
+ const transportPermit = await providerTransportCoordinator.acquire(ctx.abortSignal);
4769
+ if (transportPermit.allowed === false) {
4770
+ loopState.sawNetworkError = true;
4771
+ loopState.lastTransportErrorCode =
4772
+ transportPermit.errorCode ?? undefined;
4773
+ loopState.lastTransportScope = transportPermit.transportScope;
4774
+ loopState.lastError = `Anthropic transport recovery probe failed (${transportPermit.errorCode ?? "unknown"})`;
4775
+ break accountLoop;
4776
+ }
4575
4777
  loopState.attemptNumber += 1;
4576
4778
  if (tracer && loopState.attemptNumber === 1 && acctSelectionSpan) {
4577
4779
  tracer.setAccountSelection({
@@ -4613,6 +4815,9 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4613
4815
  !preparedAttempt.buildUpstreamBody ||
4614
4816
  !preparedAttempt.finalBodyStr ||
4615
4817
  preparedAttempt.fetchStartMs === undefined) {
4818
+ if (transportPermit.probe) {
4819
+ providerTransportCoordinator.reportProbeAbandoned(transportPermit);
4820
+ }
4616
4821
  continue accountLoop;
4617
4822
  }
4618
4823
  let admissionLease;
@@ -4632,31 +4837,53 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4632
4837
  if (!admissionLease) {
4633
4838
  // A later account may be immediately available. Preserve the routing
4634
4839
  // order but do not leave a request queued behind a busy first choice.
4840
+ if (transportPermit.probe) {
4841
+ providerTransportCoordinator.reportProbeAbandoned(transportPermit);
4842
+ }
4635
4843
  continue accountLoop;
4636
4844
  }
4637
4845
  let admissionTransferredToStream = false;
4638
4846
  try {
4639
- const fetchResult = await fetchAnthropicAccountResponse({
4640
- url,
4641
- headers: preparedAttempt.headers,
4642
- finalBodyStr: preparedAttempt.finalBodyStr,
4643
- account,
4644
- accountState,
4645
- enabledAccounts,
4646
- orderedAccounts,
4647
- tracer,
4648
- logAttempt,
4649
- logProxyBody,
4650
- fetchStartMs: preparedAttempt.fetchStartMs,
4651
- attemptNumber: loopState.attemptNumber,
4652
- currentLastError: loopState.lastError,
4653
- currentSawRateLimit: loopState.sawRateLimit,
4654
- currentSawNetworkError: loopState.sawNetworkError,
4655
- upstreamSpan: preparedAttempt.upstreamSpan,
4656
- });
4847
+ let fetchResult;
4848
+ try {
4849
+ fetchResult = await fetchAnthropicAccountResponse({
4850
+ url,
4851
+ headers: preparedAttempt.headers,
4852
+ finalBodyStr: preparedAttempt.finalBodyStr,
4853
+ account,
4854
+ accountState,
4855
+ enabledAccounts,
4856
+ orderedAccounts,
4857
+ tracer,
4858
+ logAttempt,
4859
+ logProxyBody,
4860
+ fetchStartMs: preparedAttempt.fetchStartMs,
4861
+ attemptNumber: loopState.attemptNumber,
4862
+ currentLastError: loopState.lastError,
4863
+ currentSawRateLimit: loopState.sawRateLimit,
4864
+ currentSawNetworkError: loopState.sawNetworkError,
4865
+ upstreamSpan: preparedAttempt.upstreamSpan,
4866
+ });
4867
+ }
4868
+ catch (error) {
4869
+ if (transportPermit.probe) {
4870
+ providerTransportCoordinator.reportProbeAbandoned(transportPermit);
4871
+ }
4872
+ throw error;
4873
+ }
4874
+ if (fetchResult.transportScope) {
4875
+ providerTransportCoordinator.reportTransportFailure(fetchResult.errorCode, fetchResult.transportScope, transportPermit);
4876
+ }
4877
+ else {
4878
+ providerTransportCoordinator.reportSuccess(transportPermit);
4879
+ }
4657
4880
  loopState.lastError = fetchResult.lastError;
4658
4881
  loopState.sawRateLimit = fetchResult.sawRateLimit;
4659
4882
  loopState.sawNetworkError = fetchResult.sawNetworkError;
4883
+ if (fetchResult.transportScope) {
4884
+ loopState.lastTransportScope = fetchResult.transportScope;
4885
+ loopState.lastTransportErrorCode = fetchResult.errorCode;
4886
+ }
4660
4887
  if (fetchResult.terminalError) {
4661
4888
  return finalizeAnthropicTerminalFetchError({
4662
4889
  terminalError: fetchResult.terminalError,
@@ -4717,6 +4944,9 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4717
4944
  // rotate while the already-published cooldown remains active.
4718
4945
  advancePrimaryIfCurrent(account.key, enabledAccounts.length, orderedAccounts[0]?.key);
4719
4946
  logger.always(`[proxy] account=${account.label} rate-limited (${plan.reason}); cooling ~${minutesUntil(plan.coolingUntil, Date.now())}m until ${new Date(plan.coolingUntil).toISOString()}, rotating`);
4947
+ if (plan.rotateImmediately) {
4948
+ scheduleHandoffQuotaRefresh(account, effectiveAccounts[accountIndex + 1], plan.coolingUntil, sessionSoftLimit);
4949
+ }
4720
4950
  continue accountLoop;
4721
4951
  }
4722
4952
  // Transient error retry (network errors, 529 overloaded)
@@ -4728,9 +4958,13 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4728
4958
  await sleep(delayMs);
4729
4959
  continue;
4730
4960
  }
4731
- if (fetchResult.retrySameAccount) {
4961
+ if (fetchResult.retrySameAccount && !fetchResult.transportScope) {
4732
4962
  logger.always(`[proxy] exhausted transient same-account retries for account=${account.label}; rotating`);
4733
4963
  }
4964
+ if (fetchResult.transportScope) {
4965
+ logger.always(`[proxy] Anthropic ${fetchResult.transportScope} failure code=${fetchResult.errorCode ?? "unknown"}; suppressing cross-account rotation after ${transientSameAccountRetries + 1} attempts`);
4966
+ break accountLoop;
4967
+ }
4734
4968
  continue accountLoop;
4735
4969
  }
4736
4970
  let upstreamSpan = fetchResult.upstreamSpan;
@@ -4955,6 +5189,8 @@ async function handleAnthropicRoutedClaudeRequest(args) {
4955
5189
  sawTransientFailure: loopState.sawTransientFailure,
4956
5190
  sawRateLimit: loopState.sawRateLimit,
4957
5191
  lastError: loopState.lastError,
5192
+ lastTransportErrorCode: loopState.lastTransportErrorCode,
5193
+ lastTransportScope: loopState.lastTransportScope,
4958
5194
  fallbackFailureMessage: loopState.fallbackFailureMessage,
4959
5195
  orderedAccounts,
4960
5196
  buildLoggedClaudeError,
@@ -5406,11 +5642,18 @@ function isRetryableNetworkError(error) {
5406
5642
  // The Anthropic host is fixed, so ENOTFOUND can be a transient resolver
5407
5643
  // outage. Keep it inside the existing bounded same-account retry budget.
5408
5644
  "ENOTFOUND",
5645
+ "EAI_AGAIN",
5409
5646
  "EHOSTUNREACH",
5410
5647
  "UND_ERR_CONNECT_TIMEOUT",
5411
5648
  "UND_ERR_CONNECT",
5412
5649
  ].includes(code));
5413
5650
  }
5651
+ function classifyNetworkTransportScope(error) {
5652
+ const code = getErrorCode(error);
5653
+ return code === "ENOTFOUND" || code === "EAI_AGAIN"
5654
+ ? "shared_provider_transport"
5655
+ : "connection_transport";
5656
+ }
5414
5657
  const TRANSIENT_HTTP_STATUSES = new Set([
5415
5658
  408, 500, 502, 503, 504, 520, 521, 522, 523, 524, 525, 526, 529,
5416
5659
  ]);
@@ -5540,15 +5783,20 @@ export const __testHooks = {
5540
5783
  planCooldownFor429,
5541
5784
  reconcileCooldownFromQuota,
5542
5785
  refreshAccountLimits,
5786
+ applyAccountUsageResult,
5543
5787
  clearLimitsRefreshStateForTests: () => {
5544
5788
  lastUsageFetchAt.clear();
5545
5789
  limitsRefreshInFlight = null;
5790
+ accountQuotaRefreshCoordinator.clear();
5546
5791
  },
5547
5792
  isRetryableNetworkError,
5548
5793
  isPermanentRefreshFailure,
5549
5794
  getStreamFailureDetails,
5550
5795
  trackUpstreamReadableStream,
5551
5796
  orderAccountsByQuota,
5797
+ scheduleAdaptiveQuotaRefreshes,
5798
+ scheduleHandoffQuotaRefresh,
5799
+ getQuotaRefreshState: (key) => accountQuotaRefreshCoordinator.getState(key),
5552
5800
  buildQuotaRoutingDecision: (accounts, now, primaryKey, sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs()) => {
5553
5801
  const order = orderAccountsByQuotaWithMetrics(accounts, now, primaryKey, sessionSoftLimit, sessionResetToleranceMs);
5554
5802
  return buildRoutingDecision({
@@ -5587,6 +5835,8 @@ export const __testHooks = {
5587
5835
  transientRateLimitRetryBudgets.clear();
5588
5836
  transientCooldownAdmissionSchedules.clear();
5589
5837
  accountAdmissionStates.clear();
5838
+ accountQuotaRefreshCoordinator.clear();
5839
+ providerTransportCoordinator.clear();
5590
5840
  primaryAccountIndex = 0;
5591
5841
  lastKnownAccountCount = 0;
5592
5842
  },