@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.
@@ -28,6 +28,9 @@ function printAnalysis(report) {
28
28
  }
29
29
  if (report.coverage.attempts) {
30
30
  logger.always(` Attempts: ${report.attempts.total}, ${report.attempts.errors} errors${report.attempts.errors > 0 ? ` ${JSON.stringify(report.attempts.errorTypes)}` : ""}`);
31
+ if (Object.keys(report.attempts.transportScopes).length > 0) {
32
+ logger.always(` Transport scopes: ${JSON.stringify(report.attempts.transportScopes)}`);
33
+ }
31
34
  }
32
35
  logger.always(report.coverage.lifecycle
33
36
  ? ` Lifecycle: ${report.lifecycle.accepted} accepted, ${report.lifecycle.terminal} terminal, ${report.lifecycle.unsettled} unsettled`
@@ -49,6 +52,10 @@ function printAnalysis(report) {
49
52
  if (report.coverage.routingDecisions) {
50
53
  logger.always(` Decisions: ${report.routing.totalRecords} (${report.routing.records.length} retained), modes: ${JSON.stringify(report.routing.modes)}`);
51
54
  logger.always(` Selection reasons: ${JSON.stringify(report.routing.selectionReasons)}`);
55
+ const productionQuotaProbes = report.routing.selectionReasons.quota_probe ?? 0;
56
+ if (productionQuotaProbes > 0) {
57
+ logger.always(chalk.yellow(` WARNING: ${productionQuotaProbes} production request(s) were selected for quota discovery`));
58
+ }
52
59
  logger.always(` Initial accounts: ${JSON.stringify(report.routing.initialAccounts)}`);
53
60
  logger.always(` Final account changed after retry: ${report.routing.finalAccountChanges}, outside candidate set: ${report.routing.finalOutsideCandidateSet}`);
54
61
  }
@@ -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,105 @@
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
+ }
105
+ //# sourceMappingURL=accountQuotaRefreshCoordinator.js.map
@@ -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,156 @@
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
+ }
156
+ //# sourceMappingURL=providerTransportCoordinator.js.map
@@ -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",
@@ -12,7 +12,7 @@
12
12
  import { buildTranslationOptions } from "../../proxy/proxyTranslationEngine.js";
13
13
  import { ProxyTracer } from "../../proxy/proxyTracer.js";
14
14
  import { isPermanentRefreshFailure } from "../../proxy/tokenRefresh.js";
15
- import type { AccountAllowlist, AccountAdmissionLease, AccountCooldownPlan, AccountQuota, AnthropicAttemptLogger, AnthropicAuthRetryResult, AnthropicLoopState, AnthropicNonOkResult, AnthropicSuccessResult, AnthropicUpstreamFetchResult, ClaudeFinalRequestLogger, ClaudeLoggedErrorBuilder, ClaudeRequest, ClaudeProxyRouteRuntimeOptions, ModelRouterInterface, ParsedClaudeError, ProxyAccountRoutingDecision, ProxyAccountSortMetrics, ProxyBodyCaptureLogger, ProxyLimitsRefreshResponse, ProxyQuotaCooldownUpdate, ProxyPassthroughAccount, QueuedAccountAdmission, RouteGroup, RuntimeAccountState, ServerContext, StreamTerminalOutcome } from "../../types/index.js";
15
+ import type { AccountAllowlist, AccountAdmissionLease, AccountCooldownPlan, AccountQuota, AccountUsageFetchResult, AnthropicAttemptLogger, AnthropicAuthRetryResult, AnthropicLoopState, AnthropicNonOkResult, AnthropicSuccessResult, AnthropicUpstreamFetchResult, ClaudeFinalRequestLogger, ClaudeLoggedErrorBuilder, ClaudeRequest, ClaudeProxyRouteRuntimeOptions, ModelRouterInterface, ParsedClaudeError, ProxyAccountRoutingDecision, ProxyAccountSortMetrics, ProxyBodyCaptureLogger, ProxyLimitsRefreshResponse, ProxyQuotaCooldownUpdate, ProxyPassthroughAccount, QueuedAccountAdmission, RouteGroup, RuntimeAccountState, ServerContext, StreamTerminalOutcome } from "../../types/index.js";
16
16
  declare function tryAcquireAccountAdmission(accountKey: string, capacity: number | undefined): AccountAdmissionLease | undefined;
17
17
  declare function enqueueAccountAdmission(accountKey: string, capacity: number): QueuedAccountAdmission;
18
18
  declare function acquireAccountAdmission(accountKey: string, capacity: number, abortSignal?: AbortSignal, timeoutMs?: number): Promise<AccountAdmissionLease | undefined>;
@@ -75,6 +75,7 @@ declare function reconcileCooldownFromQuota(state: RuntimeAccountState, quota: A
75
75
  * headers can change admission state.
76
76
  */
77
77
  declare function seedRuntimeQuotasFromDisk(accounts: ProxyPassthroughAccount[]): Promise<void>;
78
+ declare function applyAccountUsageResult(account: ProxyPassthroughAccount, fetchResult: AccountUsageFetchResult, observedAt: number, prior?: AccountQuota | null): Promise<AccountQuota | null>;
78
79
  /**
79
80
  * Fetch fresh limits from Anthropic's usage endpoint for every eligible OAuth
80
81
  * account and write them through the exact same chain the passive header
@@ -95,9 +96,8 @@ declare function refreshAccountLimits(options?: {
95
96
  * temporarily demoted until that session resets.
96
97
  *
97
98
  * Priority among usable accounts:
98
- * 1. no quota data yet probe first: one request reveals its windows and
99
- * self-corrects the ordering. (Ranking unknowns last would starve them
100
- * forever: never picked → never observed → never comparable.)
99
+ * 1. known healthy quota before unknown or ambiguous stale evidence. Quota
100
+ * discovery is handled by a lightweight usage GET, never a user request.
101
101
  * 2. session headroom before session-saturated (>= soft limit or
102
102
  * "throttled") — do not re-hammer an urgent weekly account while its 5h
103
103
  * capacity is temporarily unavailable.
@@ -113,6 +113,8 @@ declare function refreshAccountLimits(options?: {
113
113
  * last resort.
114
114
  */
115
115
  declare function orderAccountsByQuota(accounts: ProxyPassthroughAccount[], now: number, primaryKey: string | undefined, sessionSoftLimit?: number, sessionResetToleranceMs?: number): ProxyPassthroughAccount[];
116
+ declare function scheduleAdaptiveQuotaRefreshes(accounts: ProxyPassthroughAccount[], orderedAccounts: ProxyPassthroughAccount[], sessionSoftLimit: number, routingMetrics?: ReadonlyMap<string, ProxyAccountSortMetrics>): void;
117
+ declare function scheduleHandoffQuotaRefresh(current: ProxyPassthroughAccount, candidate: ProxyPassthroughAccount | undefined, handoffEpoch: number, sessionSoftLimit: number): void;
116
118
  declare function buildRoutingDecision(args: {
117
119
  accounts: ProxyPassthroughAccount[];
118
120
  orderedAccounts: ProxyPassthroughAccount[];
@@ -160,6 +162,8 @@ declare function buildClaudeAnthropicFailureResponse(args: {
160
162
  sawTransientFailure: boolean;
161
163
  sawRateLimit: boolean;
162
164
  lastError: unknown;
165
+ lastTransportErrorCode?: string;
166
+ lastTransportScope?: "shared_provider_transport" | "connection_transport";
163
167
  fallbackFailureMessage?: string;
164
168
  orderedAccounts: ProxyPassthroughAccount[];
165
169
  buildLoggedClaudeError: ClaudeLoggedErrorBuilder;
@@ -360,12 +364,16 @@ export declare const __testHooks: {
360
364
  planCooldownFor429: typeof planCooldownFor429;
361
365
  reconcileCooldownFromQuota: typeof reconcileCooldownFromQuota;
362
366
  refreshAccountLimits: typeof refreshAccountLimits;
367
+ applyAccountUsageResult: typeof applyAccountUsageResult;
363
368
  clearLimitsRefreshStateForTests: () => void;
364
369
  isRetryableNetworkError: typeof isRetryableNetworkError;
365
370
  isPermanentRefreshFailure: typeof isPermanentRefreshFailure;
366
371
  getStreamFailureDetails: typeof getStreamFailureDetails;
367
372
  trackUpstreamReadableStream: typeof trackUpstreamReadableStream;
368
373
  orderAccountsByQuota: typeof orderAccountsByQuota;
374
+ scheduleAdaptiveQuotaRefreshes: typeof scheduleAdaptiveQuotaRefreshes;
375
+ scheduleHandoffQuotaRefresh: typeof scheduleHandoffQuotaRefresh;
376
+ getQuotaRefreshState: (key: string) => import("../../types/proxy.js").ProxyQuotaRefreshRuntimeState;
369
377
  buildQuotaRoutingDecision: (accounts: ProxyPassthroughAccount[], now: number, primaryKey: string | undefined, sessionSoftLimit?: number, sessionResetToleranceMs?: number) => ProxyAccountRoutingDecision | undefined;
370
378
  buildRoutingDecision: typeof buildRoutingDecision;
371
379
  resetEpochToMs: typeof resetEpochToMs;