@juspay/neurolink 10.12.1 → 10.12.3

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.
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import type { CommandModule } from "yargs";
13
13
  import type { Hono } from "hono";
14
- import type { AccountAllowlist, LoadedProxyConfig, ModelRouterInterface, ProxyGuardArgs, ProxyNeurolinkRuntime, ProxyStartArgs, ProxyStartStrategy, ProxySupervisorState, ProxyStatusArgs, ProxyTelemetryArgs, ProxyReadinessState } from "../../lib/types/index.js";
14
+ import type { AccountAllowlist, LoadedProxyConfig, ModelRouterInterface, ProxyGuardArgs, ProxyHealthProbe, ProxyNeurolinkRuntime, ProxyStartArgs, ProxyStartStrategy, ProxySupervisorState, ProxyStatusArgs, ProxyTelemetryArgs, ProxyReadinessState } from "../../lib/types/index.js";
15
15
  import { ProxyRuntimeConfigStore } from "../../lib/proxy/runtimeConfig.js";
16
16
  /**
17
17
  * Drop a supervisor `version` that is not a string.
@@ -58,6 +58,7 @@ export declare function isRollingHandoffCapable(state: ProxySupervisorState | nu
58
58
  * confirm a mismatch" and falls through to the args-only result.
59
59
  */
60
60
  export declare function processLooksLikeProxySupervisor(pid: number, expectedStartTimeIso?: string): Promise<boolean>;
61
+ export declare function probeProxyHealth(host: string, port: number, timeoutMs: number): Promise<ProxyHealthProbe>;
61
62
  export declare function mapClaudeErrorTypeToStatus(errorType?: string): number;
62
63
  export declare function createProxyStartApp(params: {
63
64
  neurolink: ProxyNeurolinkRuntime["neurolink"];
@@ -41,6 +41,7 @@ import packageJson from "../../../package.json" with { type: "json" };
41
41
  const _require = createRequire(import.meta.url);
42
42
  const PROXY_VERSION = packageJson.version;
43
43
  const PROXY_INTERNAL_ACCOUNT_LABEL = "proxy/internal";
44
+ const PROXY_INTERNAL_ACCOUNT_TYPE = "internal";
44
45
  const PROXY_TELEMETRY_SCRIPT_PATH = fileURLToPath(new URL("../../../scripts/observability/manage-local-openobserve.sh", import.meta.url));
45
46
  const PROXY_LIFECYCLE_SHUTDOWN_TIMEOUT_MS = 5_000;
46
47
  const LEGACY_STATUS_ACCOUNT_CACHE_TTL_MS = 5_000;
@@ -623,17 +624,51 @@ async function clearOpenCodeProxySettings(expectedBaseUrl) {
623
624
  fs.writeFileSync(OPENCODE_CONFIG_PATH, JSON.stringify(config, null, 2));
624
625
  return hadNeurolink;
625
626
  }
626
- async function isProxyHealthy(host, port, timeoutMs) {
627
+ export async function probeProxyHealth(host, port, timeoutMs) {
628
+ const startedAt = Date.now();
627
629
  try {
628
630
  const response = await fetch(`http://${host}:${port}/health`, {
629
631
  signal: AbortSignal.timeout(timeoutMs),
630
632
  });
631
- return response.ok;
633
+ return {
634
+ healthy: response.ok,
635
+ durationMs: Date.now() - startedAt,
636
+ failure: response.ok ? null : "http_status",
637
+ statusCode: response.status,
638
+ errorCode: null,
639
+ };
632
640
  }
633
- catch {
634
- return false;
641
+ catch (error) {
642
+ const candidate = error;
643
+ const cause = candidate?.cause;
644
+ const errorCode = typeof candidate?.code === "string"
645
+ ? candidate.code
646
+ : typeof cause?.code === "string"
647
+ ? cause.code
648
+ : typeof candidate?.name === "string"
649
+ ? candidate.name
650
+ : null;
651
+ const isTimeout = candidate?.name === "TimeoutError" || candidate?.name === "AbortError";
652
+ return {
653
+ healthy: false,
654
+ durationMs: Date.now() - startedAt,
655
+ failure: isTimeout ? "timeout" : "network",
656
+ statusCode: null,
657
+ errorCode,
658
+ };
635
659
  }
636
660
  }
661
+ function formatProxyHealthProbe(probe) {
662
+ const details = [
663
+ `reason=${probe.failure ?? "none"}`,
664
+ `durationMs=${probe.durationMs}`,
665
+ probe.statusCode === null ? null : `status=${probe.statusCode}`,
666
+ probe.errorCode === null
667
+ ? null
668
+ : `errorCode=${sanitizeForLog(probe.errorCode)}`,
669
+ ].filter((detail) => detail !== null);
670
+ return details.join(" ");
671
+ }
637
672
  async function getProxyRuntimeActivity(host, port, timeoutMs = 3_000) {
638
673
  try {
639
674
  const response = await fetch(`http://${host}:${port}/status`, {
@@ -1312,7 +1347,7 @@ export async function createProxyStartApp(params) {
1312
1347
  const recordRuntimeError = async (metadata, status, errorType, errorMessage, options) => {
1313
1348
  const clientMessage = options?.clientMessage ?? errorMessage;
1314
1349
  const clientErrorType = options?.clientErrorType ?? errorType;
1315
- recordFinalError(status, undefined, undefined, {
1350
+ recordFinalError(status, PROXY_INTERNAL_ACCOUNT_LABEL, PROXY_INTERNAL_ACCOUNT_TYPE, {
1316
1351
  requestId: metadata.requestId,
1317
1352
  errorType,
1318
1353
  errorCode: options?.errorCode,
@@ -3730,19 +3765,23 @@ export const proxyGuardCommand = {
3730
3765
  const startedAt = Date.now();
3731
3766
  let parentStatus = getProcessStatus(parentPid);
3732
3767
  let consecutiveUnhealthy = 0;
3768
+ let lastUnhealthyProbe = null;
3733
3769
  // Keep monitoring for as long as the parent can affect Claude settings.
3734
3770
  while (true) {
3735
- const healthy = await isProxyHealthy(host, port, 1_500);
3771
+ const healthProbe = await probeProxyHealth(host, port, 1_500);
3772
+ const healthy = healthProbe.healthy;
3736
3773
  if (healthy) {
3737
3774
  if (updaterOnly && consecutiveUnhealthy >= failureThreshold) {
3738
- logger.always(`[updater] proxy health recovered after ${consecutiveUnhealthy} failed checks`);
3775
+ logger.always(`[updater] proxy health recovered after ${consecutiveUnhealthy} failed checks; ${formatProxyHealthProbe(lastUnhealthyProbe ?? healthProbe)}`);
3739
3776
  }
3740
3777
  consecutiveUnhealthy = 0;
3778
+ lastUnhealthyProbe = null;
3741
3779
  }
3742
3780
  else {
3743
3781
  consecutiveUnhealthy += 1;
3782
+ lastUnhealthyProbe = healthProbe;
3744
3783
  if (updaterOnly && consecutiveUnhealthy === failureThreshold) {
3745
- logger.always(`[updater] proxy health unavailable after ${consecutiveUnhealthy} checks; worker remains active`);
3784
+ logger.always(`[updater] proxy health unavailable after ${consecutiveUnhealthy} checks; worker remains active; ${formatProxyHealthProbe(healthProbe)}`);
3746
3785
  }
3747
3786
  }
3748
3787
  if (parentStatus === "not_running" && !updateRestartInProgress) {
@@ -14,12 +14,13 @@ import type { AccountQuota } from "../types/index.js";
14
14
  export declare function getUnifiedRateLimitStatus(headers: Headers | Record<string, string>): string | undefined;
15
15
  /**
16
16
  * Whether Anthropic explicitly permits a request to use overage after a
17
- * subscription window is exhausted. Fresh responses require all three
18
- * provider signals. Older persisted snapshots predate the raw fallback and
19
- * upgrade-path fields, but retain a positive fallback percentage together with
20
- * an allowed overage status, which is the equivalent provider state.
17
+ * subscription window is exhausted. An active overage signal is authoritative;
18
+ * otherwise fresh responses require explicit fallback and upgrade-path signals.
19
+ * Older persisted snapshots predate those raw fields, but retain a positive
20
+ * fallback percentage together with an allowed overage status, which is the
21
+ * equivalent provider state.
21
22
  */
22
- export declare function isQuotaOverageAvailable(quota: Pick<AccountQuota, "fallbackPercentage" | "fallbackStatus" | "overageStatus" | "upgradePaths"> | null | undefined): boolean;
23
+ export declare function isQuotaOverageAvailable(quota: Pick<AccountQuota, "fallbackPercentage" | "fallbackStatus" | "overageStatus" | "overageInUse" | "upgradePaths"> | null | undefined): boolean;
23
24
  /**
24
25
  * Parse Anthropic rate-limit / quota headers into an `AccountQuota`.
25
26
  * Returns `null` when key headers are absent.
@@ -41,15 +41,19 @@ export function getUnifiedRateLimitStatus(headers) {
41
41
  }
42
42
  /**
43
43
  * Whether Anthropic explicitly permits a request to use overage after a
44
- * subscription window is exhausted. Fresh responses require all three
45
- * provider signals. Older persisted snapshots predate the raw fallback and
46
- * upgrade-path fields, but retain a positive fallback percentage together with
47
- * an allowed overage status, which is the equivalent provider state.
44
+ * subscription window is exhausted. An active overage signal is authoritative;
45
+ * otherwise fresh responses require explicit fallback and upgrade-path signals.
46
+ * Older persisted snapshots predate those raw fields, but retain a positive
47
+ * fallback percentage together with an allowed overage status, which is the
48
+ * equivalent provider state.
48
49
  */
49
50
  export function isQuotaOverageAvailable(quota) {
50
51
  if (quota?.overageStatus?.trim().toLowerCase() !== "allowed") {
51
52
  return false;
52
53
  }
54
+ if (quota.overageInUse === true) {
55
+ return true;
56
+ }
53
57
  const explicitFallback = quota.fallbackStatus?.trim().toLowerCase();
54
58
  const hasExplicitOveragePath = (quota.upgradePaths ?? "")
55
59
  .split(",")
@@ -93,6 +97,8 @@ export function parseQuotaHeaders(headers) {
93
97
  fallbackStatus: getHeader(headers, `${P}unified-fallback`) ?? "unknown",
94
98
  upgradePaths: getHeader(headers, `${P}unified-upgrade-paths`),
95
99
  overageStatus: getHeader(headers, `${P}unified-overage-status`) ?? "unknown",
100
+ overageInUse: getHeader(headers, `${P}unified-overage-in-use`)?.trim().toLowerCase() ===
101
+ "true",
96
102
  lastUpdated: Date.now(),
97
103
  source: "headers",
98
104
  };
@@ -101,6 +101,9 @@ function routingCandidateValue(value) {
101
101
  optionalNullableStringFields.some((field) => field in candidate &&
102
102
  candidate[field] !== undefined &&
103
103
  !isNullableString(candidate[field])) ||
104
+ ("quotaStale" in candidate &&
105
+ candidate.quotaStale !== undefined &&
106
+ typeof candidate.quotaStale !== "boolean") ||
104
107
  ("overageEligible" in candidate &&
105
108
  candidate.overageEligible !== undefined &&
106
109
  typeof candidate.overageEligible !== "boolean") ||
@@ -118,6 +121,7 @@ function routingCandidateValue(value) {
118
121
  usable: candidate.usable,
119
122
  saturated: candidate.saturated,
120
123
  quotaObserved: candidate.quotaObserved,
124
+ quotaStale: candidate.quotaStale === true,
121
125
  quotaLastUpdated: candidate.quotaLastUpdated,
122
126
  quotaAgeMs: candidate.quotaAgeMs,
123
127
  coolingActive: candidate.coolingActive,
@@ -70,8 +70,9 @@ declare function reconcileCooldownFromQuota(state: RuntimeAccountState, quota: A
70
70
  * proxy restart: all accounts tie, selection falls back to token-store
71
71
  * enumeration order, and the first account served becomes self-reinforcing
72
72
  * (it alone has data) — starving the others regardless of their resets.
73
- * Never overwrites fresher in-memory quota; stale disk snapshots degrade
74
- * gracefully because past reset timestamps are ignored by resetEpochToMs.
73
+ * Never overwrites fresher in-memory quota. Persisted quota cannot create or
74
+ * clear a cooldown: only an existing cooldown or fresh upstream response
75
+ * headers can change admission state.
75
76
  */
76
77
  declare function seedRuntimeQuotasFromDisk(accounts: ProxyPassthroughAccount[]): Promise<void>;
77
78
  /**
@@ -110,6 +110,9 @@ function fetchAnthropicUpstream(url, init) {
110
110
  });
111
111
  }
112
112
  const accountRuntimeState = new Map();
113
+ /** Persisted quota is advisory after a restart. Older snapshots cannot reject
114
+ * an account or create a new cooldown because the provider may have reset it. */
115
+ const QUOTA_SNAPSHOT_FRESHNESS_MS = 15 * 60 * 1000;
113
116
  /** Shared across requests so a concurrent burst gets at most two retries for
114
117
  * the account/window, rather than every request starting its own retry chain. */
115
118
  const transientRateLimitRetryBudgets = new Map();
@@ -603,8 +606,9 @@ function reconcileCooldownFromQuota(state, quota, now) {
603
606
  * proxy restart: all accounts tie, selection falls back to token-store
604
607
  * enumeration order, and the first account served becomes self-reinforcing
605
608
  * (it alone has data) — starving the others regardless of their resets.
606
- * Never overwrites fresher in-memory quota; stale disk snapshots degrade
607
- * gracefully because past reset timestamps are ignored by resetEpochToMs.
609
+ * Never overwrites fresher in-memory quota. Persisted quota cannot create or
610
+ * clear a cooldown: only an existing cooldown or fresh upstream response
611
+ * headers can change admission state.
608
612
  */
609
613
  async function seedRuntimeQuotasFromDisk(accounts) {
610
614
  try {
@@ -625,15 +629,6 @@ async function seedRuntimeQuotasFromDisk(accounts) {
625
629
  state.coolingUntil = persistedCooldown.coolingUntil;
626
630
  state.coolingReason = persistedCooldown.reason;
627
631
  }
628
- if (state.quota) {
629
- const cooldownUpdate = reconcileCooldownFromQuota(state, state.quota, now);
630
- if (cooldownUpdate?.kind === "cooled") {
631
- await saveAccountCooldown(account.key, cooldownUpdate.coolingUntil, cooldownUpdate.coolingReason);
632
- }
633
- else if (cooldownUpdate?.kind === "cleared") {
634
- await clearAccountCooldown(account.key, cooldownUpdate.coolingUntil);
635
- }
636
- }
637
632
  }
638
633
  }
639
634
  catch {
@@ -802,47 +797,59 @@ function getSessionResetToleranceMs() {
802
797
  function accountSortMetrics(accountKey, now, sessionSoftLimit, sessionResetToleranceMs) {
803
798
  const st = accountRuntimeState.get(accountKey);
804
799
  const q = st?.quota;
800
+ const quotaLastUpdated = q && Number.isFinite(q.lastUpdated) ? q.lastUpdated : null;
801
+ const quotaAgeMs = quotaLastUpdated === null ? null : Math.max(0, now - quotaLastUpdated);
802
+ const quotaStale = quotaAgeMs !== null && quotaAgeMs > QUOTA_SNAPSHOT_FRESHNESS_MS;
803
+ const routingQuota = quotaStale ? undefined : q;
805
804
  const coolingActive = !!st?.coolingUntil && now < st.coolingUntil;
806
805
  // resetEpochToMs returns undefined for absent OR passed resets, so a
807
806
  // ticking window is exactly "reset !== undefined".
808
- const weeklyReset = resetEpochToMs(q?.weeklyResetAt, now);
809
- const sessionReset = resetEpochToMs(q?.sessionResetAt, now);
807
+ const weeklyReset = resetEpochToMs(routingQuota?.weeklyResetAt, now);
808
+ const sessionReset = resetEpochToMs(routingQuota?.sessionResetAt, now);
810
809
  const sessionTicking = sessionReset !== undefined;
811
810
  const weeklyTicking = weeklyReset !== undefined;
812
- const sessionUsed = q ? (sessionTicking ? (q.sessionUsed ?? 0) : 0) : null;
813
- const weeklyUsed = q ? (weeklyTicking ? (q.weeklyUsed ?? null) : 0) : null;
814
- const sessionStatus = q
811
+ const sessionUsed = routingQuota
815
812
  ? sessionTicking
816
- ? (q.sessionStatus ?? "unknown")
813
+ ? (routingQuota.sessionUsed ?? 0)
814
+ : 0
815
+ : null;
816
+ const weeklyUsed = routingQuota
817
+ ? weeklyTicking
818
+ ? (routingQuota.weeklyUsed ?? null)
819
+ : 0
820
+ : null;
821
+ const sessionStatus = routingQuota
822
+ ? sessionTicking
823
+ ? (routingQuota.sessionStatus ?? "unknown")
817
824
  : "allowed"
818
825
  : null;
819
- const weeklyStatus = q
826
+ const weeklyStatus = routingQuota
820
827
  ? weeklyTicking
821
- ? (q.weeklyStatus ?? "unknown")
828
+ ? (routingQuota.weeklyStatus ?? "unknown")
822
829
  : "allowed"
823
830
  : null;
824
- const overageEligible = isQuotaOverageAvailable(q);
831
+ const overageEligible = isQuotaOverageAvailable(routingQuota);
825
832
  const saturated = sessionStatus === "throttled" ||
826
833
  (sessionTicking && (sessionUsed ?? 0) >= sessionSoftLimit);
827
- const quotaLastUpdated = q && Number.isFinite(q.lastUpdated) ? q.lastUpdated : null;
828
834
  return {
829
835
  usable: !coolingActive &&
830
836
  weeklyStatus !== "rejected" &&
831
837
  (sessionStatus !== "rejected" || overageEligible) &&
832
- (q?.unifiedStatus?.trim().toLowerCase() !== "rejected" ||
838
+ (routingQuota?.unifiedStatus?.trim().toLowerCase() !== "rejected" ||
833
839
  overageEligible),
834
- saturated,
835
- hasQuota: !!q,
840
+ saturated: !quotaStale && saturated,
841
+ hasQuota: !!routingQuota,
842
+ quotaStale,
836
843
  quotaLastUpdated,
837
- quotaAgeMs: quotaLastUpdated === null ? null : Math.max(0, now - quotaLastUpdated),
844
+ quotaAgeMs,
838
845
  coolingActive,
839
846
  coolingReason: st?.coolingReason ?? null,
840
847
  coolingUntil: st?.coolingUntil ?? 0,
841
- unifiedStatus: q?.unifiedStatus ?? null,
842
- fallbackStatus: q?.fallbackStatus ?? null,
843
- upgradePaths: q?.upgradePaths ?? null,
848
+ unifiedStatus: routingQuota?.unifiedStatus ?? null,
849
+ fallbackStatus: routingQuota?.fallbackStatus ?? null,
850
+ upgradePaths: routingQuota?.upgradePaths ?? null,
844
851
  overageEligible,
845
- overageStatus: q?.overageStatus ?? null,
852
+ overageStatus: routingQuota?.overageStatus ?? null,
846
853
  sessionStatus,
847
854
  sessionUsed,
848
855
  sessionResetBucket: sessionTicking
@@ -960,6 +967,7 @@ function buildRoutingDecision(args) {
960
967
  usable: metrics.usable,
961
968
  saturated: metrics.saturated,
962
969
  quotaObserved: metrics.hasQuota,
970
+ quotaStale: metrics.quotaStale,
963
971
  quotaLastUpdated: metrics.quotaLastUpdated,
964
972
  quotaAgeMs: metrics.quotaAgeMs,
965
973
  coolingActive: metrics.coolingActive,
@@ -439,6 +439,7 @@ export type ProxyAccountRoutingCandidate = {
439
439
  usable: boolean;
440
440
  saturated: boolean;
441
441
  quotaObserved: boolean;
442
+ quotaStale: boolean;
442
443
  quotaLastUpdated: number | null;
443
444
  quotaAgeMs: number | null;
444
445
  coolingActive: boolean;
@@ -477,6 +478,7 @@ export type ProxyAccountSortMetrics = {
477
478
  usable: boolean;
478
479
  saturated: boolean;
479
480
  hasQuota: boolean;
481
+ quotaStale: boolean;
480
482
  quotaLastUpdated: number | null;
481
483
  quotaAgeMs: number | null;
482
484
  coolingActive: boolean;
@@ -941,6 +943,8 @@ export type AccountQuota = {
941
943
  upgradePaths?: string;
942
944
  /** "allowed" | "rejected" */
943
945
  overageStatus: string;
946
+ /** Whether Anthropic reports that paid overage is actively serving traffic. */
947
+ overageInUse?: boolean;
944
948
  /** Epoch ms when we last captured this data */
945
949
  lastUpdated: number;
946
950
  /** Dynamic per-plan limit buckets from the usage API `limits[]` array
@@ -1857,6 +1861,14 @@ export type UpdateCheckResult = {
1857
1861
  latestVersion: string;
1858
1862
  updateAvailable: boolean;
1859
1863
  };
1864
+ /** Result of one local proxy health probe by the updater or fail-open guard. */
1865
+ export type ProxyHealthProbe = {
1866
+ healthy: boolean;
1867
+ durationMs: number;
1868
+ failure: "http_status" | "network" | "timeout" | null;
1869
+ statusCode: number | null;
1870
+ errorCode: string | null;
1871
+ };
1860
1872
  /** Parsed major.minor.patch components of a semver string. */
1861
1873
  export type SemVer = {
1862
1874
  major: number;
@@ -14,12 +14,13 @@ import type { AccountQuota } from "../types/index.js";
14
14
  export declare function getUnifiedRateLimitStatus(headers: Headers | Record<string, string>): string | undefined;
15
15
  /**
16
16
  * Whether Anthropic explicitly permits a request to use overage after a
17
- * subscription window is exhausted. Fresh responses require all three
18
- * provider signals. Older persisted snapshots predate the raw fallback and
19
- * upgrade-path fields, but retain a positive fallback percentage together with
20
- * an allowed overage status, which is the equivalent provider state.
17
+ * subscription window is exhausted. An active overage signal is authoritative;
18
+ * otherwise fresh responses require explicit fallback and upgrade-path signals.
19
+ * Older persisted snapshots predate those raw fields, but retain a positive
20
+ * fallback percentage together with an allowed overage status, which is the
21
+ * equivalent provider state.
21
22
  */
22
- export declare function isQuotaOverageAvailable(quota: Pick<AccountQuota, "fallbackPercentage" | "fallbackStatus" | "overageStatus" | "upgradePaths"> | null | undefined): boolean;
23
+ export declare function isQuotaOverageAvailable(quota: Pick<AccountQuota, "fallbackPercentage" | "fallbackStatus" | "overageStatus" | "overageInUse" | "upgradePaths"> | null | undefined): boolean;
23
24
  /**
24
25
  * Parse Anthropic rate-limit / quota headers into an `AccountQuota`.
25
26
  * Returns `null` when key headers are absent.
@@ -41,15 +41,19 @@ export function getUnifiedRateLimitStatus(headers) {
41
41
  }
42
42
  /**
43
43
  * Whether Anthropic explicitly permits a request to use overage after a
44
- * subscription window is exhausted. Fresh responses require all three
45
- * provider signals. Older persisted snapshots predate the raw fallback and
46
- * upgrade-path fields, but retain a positive fallback percentage together with
47
- * an allowed overage status, which is the equivalent provider state.
44
+ * subscription window is exhausted. An active overage signal is authoritative;
45
+ * otherwise fresh responses require explicit fallback and upgrade-path signals.
46
+ * Older persisted snapshots predate those raw fields, but retain a positive
47
+ * fallback percentage together with an allowed overage status, which is the
48
+ * equivalent provider state.
48
49
  */
49
50
  export function isQuotaOverageAvailable(quota) {
50
51
  if (quota?.overageStatus?.trim().toLowerCase() !== "allowed") {
51
52
  return false;
52
53
  }
54
+ if (quota.overageInUse === true) {
55
+ return true;
56
+ }
53
57
  const explicitFallback = quota.fallbackStatus?.trim().toLowerCase();
54
58
  const hasExplicitOveragePath = (quota.upgradePaths ?? "")
55
59
  .split(",")
@@ -93,6 +97,8 @@ export function parseQuotaHeaders(headers) {
93
97
  fallbackStatus: getHeader(headers, `${P}unified-fallback`) ?? "unknown",
94
98
  upgradePaths: getHeader(headers, `${P}unified-upgrade-paths`),
95
99
  overageStatus: getHeader(headers, `${P}unified-overage-status`) ?? "unknown",
100
+ overageInUse: getHeader(headers, `${P}unified-overage-in-use`)?.trim().toLowerCase() ===
101
+ "true",
96
102
  lastUpdated: Date.now(),
97
103
  source: "headers",
98
104
  };
@@ -101,6 +101,9 @@ function routingCandidateValue(value) {
101
101
  optionalNullableStringFields.some((field) => field in candidate &&
102
102
  candidate[field] !== undefined &&
103
103
  !isNullableString(candidate[field])) ||
104
+ ("quotaStale" in candidate &&
105
+ candidate.quotaStale !== undefined &&
106
+ typeof candidate.quotaStale !== "boolean") ||
104
107
  ("overageEligible" in candidate &&
105
108
  candidate.overageEligible !== undefined &&
106
109
  typeof candidate.overageEligible !== "boolean") ||
@@ -118,6 +121,7 @@ function routingCandidateValue(value) {
118
121
  usable: candidate.usable,
119
122
  saturated: candidate.saturated,
120
123
  quotaObserved: candidate.quotaObserved,
124
+ quotaStale: candidate.quotaStale === true,
121
125
  quotaLastUpdated: candidate.quotaLastUpdated,
122
126
  quotaAgeMs: candidate.quotaAgeMs,
123
127
  coolingActive: candidate.coolingActive,
@@ -70,8 +70,9 @@ declare function reconcileCooldownFromQuota(state: RuntimeAccountState, quota: A
70
70
  * proxy restart: all accounts tie, selection falls back to token-store
71
71
  * enumeration order, and the first account served becomes self-reinforcing
72
72
  * (it alone has data) — starving the others regardless of their resets.
73
- * Never overwrites fresher in-memory quota; stale disk snapshots degrade
74
- * gracefully because past reset timestamps are ignored by resetEpochToMs.
73
+ * Never overwrites fresher in-memory quota. Persisted quota cannot create or
74
+ * clear a cooldown: only an existing cooldown or fresh upstream response
75
+ * headers can change admission state.
75
76
  */
76
77
  declare function seedRuntimeQuotasFromDisk(accounts: ProxyPassthroughAccount[]): Promise<void>;
77
78
  /**
@@ -110,6 +110,9 @@ function fetchAnthropicUpstream(url, init) {
110
110
  });
111
111
  }
112
112
  const accountRuntimeState = new Map();
113
+ /** Persisted quota is advisory after a restart. Older snapshots cannot reject
114
+ * an account or create a new cooldown because the provider may have reset it. */
115
+ const QUOTA_SNAPSHOT_FRESHNESS_MS = 15 * 60 * 1000;
113
116
  /** Shared across requests so a concurrent burst gets at most two retries for
114
117
  * the account/window, rather than every request starting its own retry chain. */
115
118
  const transientRateLimitRetryBudgets = new Map();
@@ -603,8 +606,9 @@ function reconcileCooldownFromQuota(state, quota, now) {
603
606
  * proxy restart: all accounts tie, selection falls back to token-store
604
607
  * enumeration order, and the first account served becomes self-reinforcing
605
608
  * (it alone has data) — starving the others regardless of their resets.
606
- * Never overwrites fresher in-memory quota; stale disk snapshots degrade
607
- * gracefully because past reset timestamps are ignored by resetEpochToMs.
609
+ * Never overwrites fresher in-memory quota. Persisted quota cannot create or
610
+ * clear a cooldown: only an existing cooldown or fresh upstream response
611
+ * headers can change admission state.
608
612
  */
609
613
  async function seedRuntimeQuotasFromDisk(accounts) {
610
614
  try {
@@ -625,15 +629,6 @@ async function seedRuntimeQuotasFromDisk(accounts) {
625
629
  state.coolingUntil = persistedCooldown.coolingUntil;
626
630
  state.coolingReason = persistedCooldown.reason;
627
631
  }
628
- if (state.quota) {
629
- const cooldownUpdate = reconcileCooldownFromQuota(state, state.quota, now);
630
- if (cooldownUpdate?.kind === "cooled") {
631
- await saveAccountCooldown(account.key, cooldownUpdate.coolingUntil, cooldownUpdate.coolingReason);
632
- }
633
- else if (cooldownUpdate?.kind === "cleared") {
634
- await clearAccountCooldown(account.key, cooldownUpdate.coolingUntil);
635
- }
636
- }
637
632
  }
638
633
  }
639
634
  catch {
@@ -802,47 +797,59 @@ function getSessionResetToleranceMs() {
802
797
  function accountSortMetrics(accountKey, now, sessionSoftLimit, sessionResetToleranceMs) {
803
798
  const st = accountRuntimeState.get(accountKey);
804
799
  const q = st?.quota;
800
+ const quotaLastUpdated = q && Number.isFinite(q.lastUpdated) ? q.lastUpdated : null;
801
+ const quotaAgeMs = quotaLastUpdated === null ? null : Math.max(0, now - quotaLastUpdated);
802
+ const quotaStale = quotaAgeMs !== null && quotaAgeMs > QUOTA_SNAPSHOT_FRESHNESS_MS;
803
+ const routingQuota = quotaStale ? undefined : q;
805
804
  const coolingActive = !!st?.coolingUntil && now < st.coolingUntil;
806
805
  // resetEpochToMs returns undefined for absent OR passed resets, so a
807
806
  // ticking window is exactly "reset !== undefined".
808
- const weeklyReset = resetEpochToMs(q?.weeklyResetAt, now);
809
- const sessionReset = resetEpochToMs(q?.sessionResetAt, now);
807
+ const weeklyReset = resetEpochToMs(routingQuota?.weeklyResetAt, now);
808
+ const sessionReset = resetEpochToMs(routingQuota?.sessionResetAt, now);
810
809
  const sessionTicking = sessionReset !== undefined;
811
810
  const weeklyTicking = weeklyReset !== undefined;
812
- const sessionUsed = q ? (sessionTicking ? (q.sessionUsed ?? 0) : 0) : null;
813
- const weeklyUsed = q ? (weeklyTicking ? (q.weeklyUsed ?? null) : 0) : null;
814
- const sessionStatus = q
811
+ const sessionUsed = routingQuota
815
812
  ? sessionTicking
816
- ? (q.sessionStatus ?? "unknown")
813
+ ? (routingQuota.sessionUsed ?? 0)
814
+ : 0
815
+ : null;
816
+ const weeklyUsed = routingQuota
817
+ ? weeklyTicking
818
+ ? (routingQuota.weeklyUsed ?? null)
819
+ : 0
820
+ : null;
821
+ const sessionStatus = routingQuota
822
+ ? sessionTicking
823
+ ? (routingQuota.sessionStatus ?? "unknown")
817
824
  : "allowed"
818
825
  : null;
819
- const weeklyStatus = q
826
+ const weeklyStatus = routingQuota
820
827
  ? weeklyTicking
821
- ? (q.weeklyStatus ?? "unknown")
828
+ ? (routingQuota.weeklyStatus ?? "unknown")
822
829
  : "allowed"
823
830
  : null;
824
- const overageEligible = isQuotaOverageAvailable(q);
831
+ const overageEligible = isQuotaOverageAvailable(routingQuota);
825
832
  const saturated = sessionStatus === "throttled" ||
826
833
  (sessionTicking && (sessionUsed ?? 0) >= sessionSoftLimit);
827
- const quotaLastUpdated = q && Number.isFinite(q.lastUpdated) ? q.lastUpdated : null;
828
834
  return {
829
835
  usable: !coolingActive &&
830
836
  weeklyStatus !== "rejected" &&
831
837
  (sessionStatus !== "rejected" || overageEligible) &&
832
- (q?.unifiedStatus?.trim().toLowerCase() !== "rejected" ||
838
+ (routingQuota?.unifiedStatus?.trim().toLowerCase() !== "rejected" ||
833
839
  overageEligible),
834
- saturated,
835
- hasQuota: !!q,
840
+ saturated: !quotaStale && saturated,
841
+ hasQuota: !!routingQuota,
842
+ quotaStale,
836
843
  quotaLastUpdated,
837
- quotaAgeMs: quotaLastUpdated === null ? null : Math.max(0, now - quotaLastUpdated),
844
+ quotaAgeMs,
838
845
  coolingActive,
839
846
  coolingReason: st?.coolingReason ?? null,
840
847
  coolingUntil: st?.coolingUntil ?? 0,
841
- unifiedStatus: q?.unifiedStatus ?? null,
842
- fallbackStatus: q?.fallbackStatus ?? null,
843
- upgradePaths: q?.upgradePaths ?? null,
848
+ unifiedStatus: routingQuota?.unifiedStatus ?? null,
849
+ fallbackStatus: routingQuota?.fallbackStatus ?? null,
850
+ upgradePaths: routingQuota?.upgradePaths ?? null,
844
851
  overageEligible,
845
- overageStatus: q?.overageStatus ?? null,
852
+ overageStatus: routingQuota?.overageStatus ?? null,
846
853
  sessionStatus,
847
854
  sessionUsed,
848
855
  sessionResetBucket: sessionTicking
@@ -960,6 +967,7 @@ function buildRoutingDecision(args) {
960
967
  usable: metrics.usable,
961
968
  saturated: metrics.saturated,
962
969
  quotaObserved: metrics.hasQuota,
970
+ quotaStale: metrics.quotaStale,
963
971
  quotaLastUpdated: metrics.quotaLastUpdated,
964
972
  quotaAgeMs: metrics.quotaAgeMs,
965
973
  coolingActive: metrics.coolingActive,
@@ -439,6 +439,7 @@ export type ProxyAccountRoutingCandidate = {
439
439
  usable: boolean;
440
440
  saturated: boolean;
441
441
  quotaObserved: boolean;
442
+ quotaStale: boolean;
442
443
  quotaLastUpdated: number | null;
443
444
  quotaAgeMs: number | null;
444
445
  coolingActive: boolean;
@@ -477,6 +478,7 @@ export type ProxyAccountSortMetrics = {
477
478
  usable: boolean;
478
479
  saturated: boolean;
479
480
  hasQuota: boolean;
481
+ quotaStale: boolean;
480
482
  quotaLastUpdated: number | null;
481
483
  quotaAgeMs: number | null;
482
484
  coolingActive: boolean;
@@ -941,6 +943,8 @@ export type AccountQuota = {
941
943
  upgradePaths?: string;
942
944
  /** "allowed" | "rejected" */
943
945
  overageStatus: string;
946
+ /** Whether Anthropic reports that paid overage is actively serving traffic. */
947
+ overageInUse?: boolean;
944
948
  /** Epoch ms when we last captured this data */
945
949
  lastUpdated: number;
946
950
  /** Dynamic per-plan limit buckets from the usage API `limits[]` array
@@ -1857,6 +1861,14 @@ export type UpdateCheckResult = {
1857
1861
  latestVersion: string;
1858
1862
  updateAvailable: boolean;
1859
1863
  };
1864
+ /** Result of one local proxy health probe by the updater or fail-open guard. */
1865
+ export type ProxyHealthProbe = {
1866
+ healthy: boolean;
1867
+ durationMs: number;
1868
+ failure: "http_status" | "network" | "timeout" | null;
1869
+ statusCode: number | null;
1870
+ errorCode: string | null;
1871
+ };
1860
1872
  /** Parsed major.minor.patch components of a semver string. */
1861
1873
  export type SemVer = {
1862
1874
  major: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "10.12.1",
3
+ "version": "10.12.3",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {