@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.
@@ -430,6 +430,21 @@ export type ProxyAccountRoutingStrategy = (typeof PROXY_ACCOUNT_ROUTING_STRATEGI
430
430
  export type ProxyAccountRoutingMode = (typeof PROXY_ACCOUNT_ROUTING_MODES)[number];
431
431
  export type ProxyAccountRoutingReason = (typeof PROXY_ACCOUNT_ROUTING_REASONS)[number];
432
432
  export type ProxyAccountType = (typeof PROXY_ACCOUNT_TYPES)[number];
433
+ export type ProxyQuotaFreshness = "unknown" | "fresh" | "stale_known" | "refresh_due";
434
+ export type ProxyQuotaRefreshReason = "startup_unknown" | "handoff_prewarm" | "ambiguous_snapshot" | "manual";
435
+ export type ProxyQuotaSaturationKind = "none" | "soft" | "hard";
436
+ export type ProxyTransportScope = "shared_provider_transport" | "connection_transport" | "account_specific";
437
+ export type ProxyNetworkTransportScope = Exclude<ProxyTransportScope, "account_specific">;
438
+ export type ProxyProviderTransportProbeOutcome = "recovered" | "failed" | "abandoned";
439
+ export type ProxyProviderTransportPermit = {
440
+ allowed: true;
441
+ probe: boolean;
442
+ generation: number;
443
+ } | {
444
+ allowed: false;
445
+ errorCode: string | null;
446
+ transportScope: ProxyNetworkTransportScope;
447
+ };
433
448
  export type ProxyAccountRoutingCandidate = {
434
449
  account: string;
435
450
  accountType: ProxyAccountType;
@@ -440,6 +455,15 @@ export type ProxyAccountRoutingCandidate = {
440
455
  saturated: boolean;
441
456
  quotaObserved: boolean;
442
457
  quotaStale: boolean;
458
+ quotaFreshness?: ProxyQuotaFreshness;
459
+ refreshNeeded?: boolean;
460
+ refreshReason?: ProxyQuotaRefreshReason | null;
461
+ refreshInFlight?: boolean;
462
+ lastRefreshAttemptAt?: number | null;
463
+ lastRefreshSuccessAt?: number | null;
464
+ nextRefreshEligibleAt?: number | null;
465
+ saturationKind?: ProxyQuotaSaturationKind;
466
+ softLimitOverrideReason?: "overage" | "weekly_expiry" | null;
443
467
  quotaLastUpdated: number | null;
444
468
  quotaAgeMs: number | null;
445
469
  coolingActive: boolean;
@@ -478,7 +502,17 @@ export type ProxyAccountSortMetrics = {
478
502
  usable: boolean;
479
503
  saturated: boolean;
480
504
  hasQuota: boolean;
505
+ quotaEvidenceRank: number;
481
506
  quotaStale: boolean;
507
+ quotaFreshness: ProxyQuotaFreshness;
508
+ refreshNeeded: boolean;
509
+ refreshReason: ProxyQuotaRefreshReason | null;
510
+ refreshInFlight: boolean;
511
+ lastRefreshAttemptAt: number | null;
512
+ lastRefreshSuccessAt: number | null;
513
+ nextRefreshEligibleAt: number | null;
514
+ saturationKind: ProxyQuotaSaturationKind;
515
+ softLimitOverrideReason: "overage" | "weekly_expiry" | null;
482
516
  quotaLastUpdated: number | null;
483
517
  quotaAgeMs: number | null;
484
518
  coolingActive: boolean;
@@ -514,6 +548,8 @@ export type RequestLogEntry = {
514
548
  errorMessage?: string;
515
549
  /** Low-level transport code such as ETIMEDOUT or EADDRNOTAVAIL. */
516
550
  errorCode?: string;
551
+ /** Whether changing credentials can affect this transport failure. */
552
+ transportScope?: ProxyTransportScope;
517
553
  inputTokens?: number;
518
554
  outputTokens?: number;
519
555
  cacheCreationTokens?: number;
@@ -545,6 +581,8 @@ export type RequestAttemptLogEntry = {
545
581
  errorMessage?: string;
546
582
  /** Low-level transport code such as ETIMEDOUT or EADDRNOTAVAIL. */
547
583
  errorCode?: string;
584
+ /** Whether changing credentials can affect this transport failure. */
585
+ transportScope?: ProxyTransportScope;
548
586
  /** Whether this failed attempt may be retried without changing the request. */
549
587
  retryable?: boolean;
550
588
  /** Distinguishes short-lived admission throttles from exhausted quota windows. */
@@ -579,11 +617,15 @@ export type ClaudeFinalRequestLogger = (status: number, accountLabel: string, ac
579
617
  outputTokens?: number;
580
618
  cacheCreationTokens?: number;
581
619
  cacheReadTokens?: number;
620
+ errorCode?: string;
621
+ transportScope?: ProxyTransportScope;
582
622
  }) => void;
583
623
  export type ClaudeLoggedErrorBuilder = (status: number, message: string, errorType?: string, extra?: {
584
624
  account?: string;
585
625
  accountType?: string;
586
626
  attempt?: number;
627
+ errorCode?: string;
628
+ transportScope?: ProxyTransportScope;
587
629
  }) => ClaudeErrorResponse;
588
630
  export type ClaudeRequestRuntimeContext = {
589
631
  tracer?: ProxyTracer;
@@ -603,6 +645,7 @@ export type AnthropicAttemptLogger = (status: number, errorType?: string, errorM
603
645
  retryable?: boolean;
604
646
  /** Low-level transport code such as ETIMEDOUT or EADDRNOTAVAIL. */
605
647
  errorCode?: string;
648
+ transportScope?: ProxyTransportScope;
606
649
  rateLimitKind?: "transient" | "quota";
607
650
  cooldownReason?: "transient" | "session" | "weekly" | "unified";
608
651
  /** Override for nested retries whose attempt starts after this logger. */
@@ -624,6 +667,8 @@ export type AnthropicLoopState = {
624
667
  authCooldownMessage: string | null;
625
668
  fallbackFailureMessage?: string;
626
669
  attemptNumber: number;
670
+ lastTransportErrorCode?: string;
671
+ lastTransportScope?: ProxyNetworkTransportScope;
627
672
  };
628
673
  export type AnthropicUpstreamBody = {
629
674
  bodyStr: string;
@@ -765,6 +810,8 @@ export type TransientRateLimitRetryBudget = {
765
810
  export type AnthropicUpstreamFetchResult = {
766
811
  continueLoop: boolean;
767
812
  retrySameAccount?: boolean;
813
+ transportScope?: ProxyNetworkTransportScope;
814
+ errorCode?: string;
768
815
  /** When set, the caller should wait this many ms before retrying (from upstream retry-after). */
769
816
  retryAfterMs?: number;
770
817
  /** Set on a genuine 429: how long / why to cool this account before rotating. */
@@ -1033,6 +1080,35 @@ export type AccountUsageFetchResult = {
1033
1080
  error: string;
1034
1081
  status?: number;
1035
1082
  };
1083
+ export type ProxyQuotaRefreshRuntimeState = {
1084
+ inFlight: boolean;
1085
+ lastAttemptAt?: number;
1086
+ lastSuccessAt?: number;
1087
+ nextEligibleAt?: number;
1088
+ consecutiveFailures: number;
1089
+ lastFailureReason?: string;
1090
+ lastCompletedTrigger?: string;
1091
+ coalesced: number;
1092
+ };
1093
+ export type ProxyQuotaRefreshMetrics = {
1094
+ attempted: number;
1095
+ succeeded: number;
1096
+ failed: number;
1097
+ coalesced: number;
1098
+ backoffSuppressed: number;
1099
+ triggerDeduplicated: number;
1100
+ };
1101
+ export type ProxyQuotaRefreshRunResult = {
1102
+ kind: "completed";
1103
+ result: AccountUsageFetchResult;
1104
+ /** Lower bound for the freshness of the returned usage snapshot. */
1105
+ startedAt: number;
1106
+ } | {
1107
+ kind: "backoff";
1108
+ nextEligibleAt: number;
1109
+ } | {
1110
+ kind: "not_due";
1111
+ };
1036
1112
  /** Per-account result inside a GET /limits response. */
1037
1113
  export type ProxyLimitsAccountResult = {
1038
1114
  /** Account label (quota-store key). */
@@ -1053,6 +1129,8 @@ export type ProxyLimitsRefreshResponse = {
1053
1129
  /** True when served from stored state without contacting Anthropic. */
1054
1130
  snapshot: boolean;
1055
1131
  results: ProxyLimitsAccountResult[];
1132
+ /** Process-local refresh activity; contains no credentials or response body. */
1133
+ refreshMetrics?: ProxyQuotaRefreshMetrics;
1056
1134
  };
1057
1135
  /**
1058
1136
  * Provenance of the quota numbers attached to a single proxy response.
@@ -1518,6 +1596,7 @@ export type ProxyAnalysisReport = {
1518
1596
  errors: number;
1519
1597
  errorTypes: Record<string, number>;
1520
1598
  errorCodes: Record<string, number>;
1599
+ transportScopes: Record<string, number>;
1521
1600
  };
1522
1601
  rateLimits: {
1523
1602
  attemptRateLimits: number;
@@ -0,0 +1,19 @@
1
+ import type { AccountUsageFetchResult, ProxyPassthroughAccount, ProxyQuotaRefreshRunResult, ProxyQuotaRefreshMetrics, ProxyQuotaRefreshRuntimeState } from "../types/index.js";
2
+ export declare class AccountQuotaRefreshCoordinator {
3
+ private readonly inFlight;
4
+ private readonly states;
5
+ private metrics;
6
+ getState(accountKey: string): ProxyQuotaRefreshRuntimeState;
7
+ /**
8
+ * Run one refresh per account. `trigger` must identify the quota window or
9
+ * handoff condition so repeated requests deduplicate without hiding a later
10
+ * reset window.
11
+ */
12
+ run(account: ProxyPassthroughAccount, trigger: string, fetcher: (candidate: ProxyPassthroughAccount) => Promise<AccountUsageFetchResult>, options?: {
13
+ force?: boolean;
14
+ now?: number;
15
+ }): Promise<ProxyQuotaRefreshRunResult>;
16
+ clear(): void;
17
+ getMetrics(): ProxyQuotaRefreshMetrics;
18
+ private getOrCreateState;
19
+ }
@@ -0,0 +1,104 @@
1
+ const FAILURE_BACKOFF_MS = [30_000, 2 * 60_000, 10 * 60_000];
2
+ const MAX_FAILURE_BACKOFF_MS = 30 * 60_000;
3
+ export class AccountQuotaRefreshCoordinator {
4
+ inFlight = new Map();
5
+ states = new Map();
6
+ metrics = {
7
+ attempted: 0,
8
+ succeeded: 0,
9
+ failed: 0,
10
+ coalesced: 0,
11
+ backoffSuppressed: 0,
12
+ triggerDeduplicated: 0,
13
+ };
14
+ getState(accountKey) {
15
+ const state = this.states.get(accountKey);
16
+ return state
17
+ ? { ...state, inFlight: this.inFlight.has(accountKey) }
18
+ : { inFlight: false, consecutiveFailures: 0, coalesced: 0 };
19
+ }
20
+ /**
21
+ * Run one refresh per account. `trigger` must identify the quota window or
22
+ * handoff condition so repeated requests deduplicate without hiding a later
23
+ * reset window.
24
+ */
25
+ run(account, trigger, fetcher, options = {}) {
26
+ const existing = this.inFlight.get(account.key);
27
+ if (existing) {
28
+ const state = this.getOrCreateState(account.key);
29
+ state.coalesced += 1;
30
+ this.metrics.coalesced += 1;
31
+ return existing;
32
+ }
33
+ const now = options.now ?? Date.now();
34
+ const state = this.getOrCreateState(account.key);
35
+ if (!options.force && state.lastCompletedTrigger === trigger) {
36
+ this.metrics.triggerDeduplicated += 1;
37
+ return Promise.resolve({ kind: "not_due" });
38
+ }
39
+ if (!options.force && now < (state.nextEligibleAt ?? 0)) {
40
+ this.metrics.backoffSuppressed += 1;
41
+ return Promise.resolve({
42
+ kind: "backoff",
43
+ nextEligibleAt: state.nextEligibleAt ?? now,
44
+ });
45
+ }
46
+ state.lastAttemptAt = now;
47
+ this.metrics.attempted += 1;
48
+ const task = fetcher(account)
49
+ .catch((error) => ({
50
+ ok: false,
51
+ reason: "network",
52
+ error: error instanceof Error ? error.message : String(error),
53
+ }))
54
+ .then((result) => {
55
+ const completedAt = options.now ?? Date.now();
56
+ if (result.ok === false) {
57
+ this.metrics.failed += 1;
58
+ state.consecutiveFailures += 1;
59
+ state.lastFailureReason = result.reason;
60
+ const backoffBase = FAILURE_BACKOFF_MS[Math.max(0, state.consecutiveFailures - 1)] ??
61
+ MAX_FAILURE_BACKOFF_MS;
62
+ const jitter = 0.9 + Math.random() * 0.2;
63
+ state.nextEligibleAt = completedAt + Math.round(backoffBase * jitter);
64
+ }
65
+ else {
66
+ this.metrics.succeeded += 1;
67
+ state.lastSuccessAt = completedAt;
68
+ state.nextEligibleAt = undefined;
69
+ state.consecutiveFailures = 0;
70
+ state.lastFailureReason = undefined;
71
+ state.lastCompletedTrigger = trigger;
72
+ }
73
+ return { kind: "completed", result, startedAt: now };
74
+ })
75
+ .finally(() => {
76
+ this.inFlight.delete(account.key);
77
+ });
78
+ this.inFlight.set(account.key, task);
79
+ return task;
80
+ }
81
+ clear() {
82
+ this.inFlight.clear();
83
+ this.states.clear();
84
+ this.metrics = {
85
+ attempted: 0,
86
+ succeeded: 0,
87
+ failed: 0,
88
+ coalesced: 0,
89
+ backoffSuppressed: 0,
90
+ triggerDeduplicated: 0,
91
+ };
92
+ }
93
+ getMetrics() {
94
+ return { ...this.metrics };
95
+ }
96
+ getOrCreateState(accountKey) {
97
+ let state = this.states.get(accountKey);
98
+ if (!state) {
99
+ state = { inFlight: false, consecutiveFailures: 0, coalesced: 0 };
100
+ this.states.set(accountKey, state);
101
+ }
102
+ return state;
103
+ }
104
+ }
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * On-Demand Account Usage Fetch
3
3
  *
4
- * Manual refetch path for account limits: queries Anthropic's OAuth usage
5
- * endpoint (the same call Claude Code's /usage makes) to get FRESH session /
6
- * weekly / model-scoped windows for an account without consuming any tokens
7
- * and without starting a 5h session window.
4
+ * Lightweight account-limit refresh: queries Anthropic's OAuth usage endpoint
5
+ * (the same call Claude Code's /usage makes) to get fresh session / weekly /
6
+ * model-scoped windows without consuming tokens or starting a 5h session.
8
7
  *
9
8
  * This complements — never replaces — the passive header capture in
10
9
  * accountQuota.ts: `usageToQuota` normalizes the endpoint payload into the
11
10
  * same `AccountQuota` shape so refreshed data flows through the existing
12
- * save/merge/cooldown chain.
11
+ * save/merge/cooldown chain. Manual refresh and adaptive proxy prewarming use
12
+ * the same transport through the route-owned single-flight coordinator.
13
13
  *
14
14
  * Only OAuth (Bearer) accounts have subscription windows; api_key accounts
15
15
  * are skipped and keep their header-derived absolute limits.
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * On-Demand Account Usage Fetch
3
3
  *
4
- * Manual refetch path for account limits: queries Anthropic's OAuth usage
5
- * endpoint (the same call Claude Code's /usage makes) to get FRESH session /
6
- * weekly / model-scoped windows for an account without consuming any tokens
7
- * and without starting a 5h session window.
4
+ * Lightweight account-limit refresh: queries Anthropic's OAuth usage endpoint
5
+ * (the same call Claude Code's /usage makes) to get fresh session / weekly /
6
+ * model-scoped windows without consuming tokens or starting a 5h session.
8
7
  *
9
8
  * This complements — never replaces — the passive header capture in
10
9
  * accountQuota.ts: `usageToQuota` normalizes the endpoint payload into the
11
10
  * same `AccountQuota` shape so refreshed data flows through the existing
12
- * save/merge/cooldown chain.
11
+ * save/merge/cooldown chain. Manual refresh and adaptive proxy prewarming use
12
+ * the same transport through the route-owned single-flight coordinator.
13
13
  *
14
14
  * Only OAuth (Bearer) accounts have subscription windows; api_key accounts
15
15
  * are skipped and keep their header-derived absolute limits.
@@ -0,0 +1,17 @@
1
+ import type { ProxyProviderTransportPermit } from "../types/index.js";
2
+ export declare class ProviderTransportCoordinator {
3
+ private generation;
4
+ private degraded;
5
+ private backoffUntil;
6
+ private lastErrorCode;
7
+ private lastTransportScope;
8
+ private probe;
9
+ acquire(signal?: AbortSignal): Promise<ProxyProviderTransportPermit>;
10
+ reportSuccess(permit: ProxyProviderTransportPermit): void;
11
+ reportTransportFailure(errorCode: string | undefined, transportScope: "shared_provider_transport" | "connection_transport", permit: ProxyProviderTransportPermit): void;
12
+ reportProbeAbandoned(permit: ProxyProviderTransportPermit): void;
13
+ clear(): void;
14
+ private wait;
15
+ private waitForProbe;
16
+ private waitForProbeOrAbort;
17
+ }
@@ -0,0 +1,155 @@
1
+ import { TimeoutError, withTimeout } from "../utils/async/withTimeout.js";
2
+ const SHARED_TRANSPORT_BACKOFF_MS = 250;
3
+ const RECOVERY_PROBE_WAIT_TIMEOUT_MS = 30_000;
4
+ export class ProviderTransportCoordinator {
5
+ generation = 0;
6
+ degraded = false;
7
+ backoffUntil = 0;
8
+ lastErrorCode = null;
9
+ lastTransportScope = "shared_provider_transport";
10
+ probe;
11
+ async acquire(signal) {
12
+ if (!this.degraded) {
13
+ return { allowed: true, probe: false, generation: this.generation };
14
+ }
15
+ const waitMs = Math.max(0, this.backoffUntil - Date.now());
16
+ if (waitMs > 0) {
17
+ await this.wait(waitMs, signal);
18
+ }
19
+ if (!this.degraded) {
20
+ return { allowed: true, probe: false, generation: this.generation };
21
+ }
22
+ if (this.probe) {
23
+ const outcome = await this.waitForProbe(this.probe.promise, signal);
24
+ if (outcome === "recovered") {
25
+ return { allowed: true, probe: false, generation: this.generation };
26
+ }
27
+ if (outcome === "abandoned") {
28
+ return this.acquire(signal);
29
+ }
30
+ return {
31
+ allowed: false,
32
+ errorCode: this.lastErrorCode,
33
+ transportScope: this.lastTransportScope,
34
+ };
35
+ }
36
+ let resolveProbe;
37
+ const promise = new Promise((resolve) => {
38
+ resolveProbe = resolve;
39
+ });
40
+ this.probe = {
41
+ generation: this.generation,
42
+ promise,
43
+ resolve: resolveProbe,
44
+ };
45
+ return { allowed: true, probe: true, generation: this.generation };
46
+ }
47
+ reportSuccess(permit) {
48
+ if (!permit.allowed || permit.generation !== this.generation) {
49
+ return;
50
+ }
51
+ if (permit.probe && this.probe?.generation !== permit.generation) {
52
+ return;
53
+ }
54
+ this.degraded = false;
55
+ this.backoffUntil = 0;
56
+ this.lastErrorCode = null;
57
+ this.lastTransportScope = "shared_provider_transport";
58
+ if (permit.probe && this.probe?.generation === permit.generation) {
59
+ this.probe.resolve("recovered");
60
+ this.probe = undefined;
61
+ }
62
+ }
63
+ reportTransportFailure(errorCode, transportScope, permit) {
64
+ if (!permit.allowed || permit.generation !== this.generation) {
65
+ return;
66
+ }
67
+ if (permit.probe && this.probe?.generation !== permit.generation) {
68
+ return;
69
+ }
70
+ this.degraded = true;
71
+ this.backoffUntil = Date.now() + SHARED_TRANSPORT_BACKOFF_MS;
72
+ this.lastErrorCode = errorCode ?? null;
73
+ this.lastTransportScope = transportScope;
74
+ if (permit.probe && this.probe?.generation === permit.generation) {
75
+ this.probe.resolve("failed");
76
+ this.probe = undefined;
77
+ }
78
+ this.generation += 1;
79
+ }
80
+ reportProbeAbandoned(permit) {
81
+ if (!permit.allowed ||
82
+ !permit.probe ||
83
+ permit.generation !== this.generation ||
84
+ this.probe?.generation !== permit.generation) {
85
+ return;
86
+ }
87
+ this.probe.resolve("abandoned");
88
+ this.probe = undefined;
89
+ this.generation += 1;
90
+ }
91
+ clear() {
92
+ this.probe?.resolve("failed");
93
+ this.probe = undefined;
94
+ this.generation += 1;
95
+ this.degraded = false;
96
+ this.backoffUntil = 0;
97
+ this.lastErrorCode = null;
98
+ this.lastTransportScope = "shared_provider_transport";
99
+ }
100
+ async wait(ms, signal) {
101
+ if (signal?.aborted) {
102
+ throw signal.reason;
103
+ }
104
+ await new Promise((resolve, reject) => {
105
+ const timeoutRef = {};
106
+ const onAbort = () => {
107
+ if (timeoutRef.current) {
108
+ clearTimeout(timeoutRef.current);
109
+ }
110
+ reject(signal?.reason);
111
+ };
112
+ timeoutRef.current = setTimeout(() => {
113
+ signal?.removeEventListener("abort", onAbort);
114
+ resolve();
115
+ }, ms);
116
+ timeoutRef.current.unref?.();
117
+ signal?.addEventListener("abort", onAbort, { once: true });
118
+ });
119
+ }
120
+ async waitForProbe(probe, signal) {
121
+ const boundedProbe = withTimeout(probe, RECOVERY_PROBE_WAIT_TIMEOUT_MS, "Anthropic transport recovery probe timed out");
122
+ try {
123
+ return await this.waitForProbeOrAbort(boundedProbe, signal);
124
+ }
125
+ catch (error) {
126
+ if (error instanceof TimeoutError && this.probe?.promise === probe) {
127
+ this.probe.resolve("failed");
128
+ this.probe = undefined;
129
+ this.generation += 1;
130
+ this.backoffUntil = Date.now() + SHARED_TRANSPORT_BACKOFF_MS;
131
+ return "failed";
132
+ }
133
+ throw error;
134
+ }
135
+ }
136
+ waitForProbeOrAbort(probe, signal) {
137
+ if (!signal) {
138
+ return probe;
139
+ }
140
+ if (signal.aborted) {
141
+ throw signal.reason;
142
+ }
143
+ return new Promise((resolve, reject) => {
144
+ const onAbort = () => reject(signal.reason);
145
+ signal.addEventListener("abort", onAbort, { once: true });
146
+ probe.then((result) => {
147
+ signal.removeEventListener("abort", onAbort);
148
+ resolve(result);
149
+ }, (error) => {
150
+ signal.removeEventListener("abort", onAbort);
151
+ reject(error);
152
+ });
153
+ });
154
+ }
155
+ }
@@ -20,6 +20,19 @@ const ROUTING_MODES = new Set(PROXY_ACCOUNT_ROUTING_MODES);
20
20
  const ROUTING_REASONS = new Set(PROXY_ACCOUNT_ROUTING_REASONS);
21
21
  const ROUTING_ACCOUNT_TYPES = new Set(PROXY_ACCOUNT_TYPES);
22
22
  const COOLING_REASONS = new Set(ACCOUNT_COOLING_REASONS);
23
+ const QUOTA_FRESHNESS_VALUES = new Set([
24
+ "unknown",
25
+ "fresh",
26
+ "stale_known",
27
+ "refresh_due",
28
+ ]);
29
+ const QUOTA_REFRESH_REASONS = new Set([
30
+ "startup_unknown",
31
+ "handoff_prewarm",
32
+ "ambiguous_snapshot",
33
+ "manual",
34
+ ]);
35
+ const QUOTA_SATURATION_KINDS = new Set(["none", "soft", "hard"]);
23
36
  const MAX_RETAINED_ROUTING_RECORDS = 200;
24
37
  const MAX_ROUTING_RECORDS_BEFORE_COMPACTION = MAX_RETAINED_ROUTING_RECORDS * 2;
25
38
  function isContainedPath(root, candidate) {
@@ -107,6 +120,37 @@ function routingCandidateValue(value) {
107
120
  ("overageEligible" in candidate &&
108
121
  candidate.overageEligible !== undefined &&
109
122
  typeof candidate.overageEligible !== "boolean") ||
123
+ ("quotaFreshness" in candidate &&
124
+ candidate.quotaFreshness !== undefined &&
125
+ (typeof candidate.quotaFreshness !== "string" ||
126
+ !QUOTA_FRESHNESS_VALUES.has(candidate.quotaFreshness))) ||
127
+ ("refreshNeeded" in candidate &&
128
+ candidate.refreshNeeded !== undefined &&
129
+ typeof candidate.refreshNeeded !== "boolean") ||
130
+ ("refreshInFlight" in candidate &&
131
+ candidate.refreshInFlight !== undefined &&
132
+ typeof candidate.refreshInFlight !== "boolean") ||
133
+ ("refreshReason" in candidate &&
134
+ candidate.refreshReason !== undefined &&
135
+ candidate.refreshReason !== null &&
136
+ (typeof candidate.refreshReason !== "string" ||
137
+ !QUOTA_REFRESH_REASONS.has(candidate.refreshReason))) ||
138
+ [
139
+ "lastRefreshAttemptAt",
140
+ "lastRefreshSuccessAt",
141
+ "nextRefreshEligibleAt",
142
+ ].some((field) => field in candidate &&
143
+ candidate[field] !== undefined &&
144
+ !isNullableFiniteNumber(candidate[field])) ||
145
+ ("saturationKind" in candidate &&
146
+ candidate.saturationKind !== undefined &&
147
+ (typeof candidate.saturationKind !== "string" ||
148
+ !QUOTA_SATURATION_KINDS.has(candidate.saturationKind))) ||
149
+ ("softLimitOverrideReason" in candidate &&
150
+ candidate.softLimitOverrideReason !== undefined &&
151
+ candidate.softLimitOverrideReason !== null &&
152
+ candidate.softLimitOverrideReason !== "overage" &&
153
+ candidate.softLimitOverrideReason !== "weekly_expiry") ||
110
154
  !(candidate.coolingReason === null ||
111
155
  (typeof candidate.coolingReason === "string" &&
112
156
  COOLING_REASONS.has(candidate.coolingReason)))) {
@@ -122,6 +166,15 @@ function routingCandidateValue(value) {
122
166
  saturated: candidate.saturated,
123
167
  quotaObserved: candidate.quotaObserved,
124
168
  quotaStale: candidate.quotaStale === true,
169
+ quotaFreshness: candidate.quotaFreshness,
170
+ refreshNeeded: candidate.refreshNeeded,
171
+ refreshReason: candidate.refreshReason,
172
+ refreshInFlight: candidate.refreshInFlight,
173
+ lastRefreshAttemptAt: candidate.lastRefreshAttemptAt,
174
+ lastRefreshSuccessAt: candidate.lastRefreshSuccessAt,
175
+ nextRefreshEligibleAt: candidate.nextRefreshEligibleAt,
176
+ saturationKind: candidate.saturationKind,
177
+ softLimitOverrideReason: candidate.softLimitOverrideReason,
125
178
  quotaLastUpdated: candidate.quotaLastUpdated,
126
179
  quotaAgeMs: candidate.quotaAgeMs,
127
180
  coolingActive: candidate.coolingActive,
@@ -554,6 +607,7 @@ export async function analyzeProxyLogs(options) {
554
607
  let totalAttemptErrors = 0;
555
608
  const attemptErrorTypes = {};
556
609
  const attemptErrorCodes = {};
610
+ const attemptTransportScopes = {};
557
611
  let attemptRateLimits = 0;
558
612
  let transientRateLimits = 0;
559
613
  let quotaRateLimits = 0;
@@ -585,6 +639,10 @@ export async function analyzeProxyLogs(options) {
585
639
  increment(attemptErrorCodes, errorCode);
586
640
  }
587
641
  }
642
+ const transportScope = stringValue(record.transportScope);
643
+ if (transportScope) {
644
+ increment(attemptTransportScopes, transportScope);
645
+ }
588
646
  const requestAttempts = attemptsByRequest.get(requestId) ?? {
589
647
  count: 0,
590
648
  hadError: false,
@@ -808,6 +866,7 @@ export async function analyzeProxyLogs(options) {
808
866
  errors: totalAttemptErrors,
809
867
  errorTypes: attemptErrorTypes,
810
868
  errorCodes: attemptErrorCodes,
869
+ transportScopes: attemptTransportScopes,
811
870
  },
812
871
  rateLimits: {
813
872
  attemptRateLimits,
@@ -1,6 +1,6 @@
1
1
  /** Schema-v1 routing evidence values shared by emitters and offline readers. */
2
2
  export declare const PROXY_ACCOUNT_ROUTING_STRATEGIES: readonly ["round-robin", "fill-first"];
3
3
  export declare const PROXY_ACCOUNT_ROUTING_MODES: readonly ["quota", "primary", "round_robin", "single_account"];
4
- export declare const PROXY_ACCOUNT_ROUTING_REASONS: readonly ["single_account", "round_robin", "configured_primary", "insertion_order", "availability", "cooldown_recovery", "quota_probe", "session_headroom", "session_reset", "weekly_reset", "weekly_utilization"];
4
+ export declare const PROXY_ACCOUNT_ROUTING_REASONS: readonly ["single_account", "round_robin", "configured_primary", "insertion_order", "availability", "cooldown_recovery", "quota_evidence", "quota_probe", "session_headroom", "session_reset", "weekly_reset", "weekly_utilization"];
5
5
  export declare const PROXY_ACCOUNT_TYPES: readonly ["oauth", "api_key"];
6
6
  export declare const ACCOUNT_COOLING_REASONS: readonly ["weekly", "session", "unified", "transient", "auth"];
@@ -16,6 +16,9 @@ export const PROXY_ACCOUNT_ROUTING_REASONS = [
16
16
  "insertion_order",
17
17
  "availability",
18
18
  "cooldown_recovery",
19
+ "quota_evidence",
20
+ // Retained for backwards-compatible analysis of pre-fix logs. New routing
21
+ // decisions must never select a production request for quota discovery.
19
22
  "quota_probe",
20
23
  "session_headroom",
21
24
  "session_reset",