@juspay/neurolink 12.24.1 → 12.25.1

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.
@@ -0,0 +1,23 @@
1
+ /** Bind a session to the account that just served it, refreshing its idle clock. */
2
+ export declare function bind(sessionId: string, accountKey: string, now: number): void;
3
+ /**
4
+ * The account a session is bound to, or undefined if unbound or idle-expired.
5
+ * A read never refreshes the binding — only a served request (bind) does,
6
+ * since the idle TTL counts from the session's last served request.
7
+ */
8
+ export declare function get(sessionId: string, now: number, idleTtlMs: number): string | undefined;
9
+ export declare function clear(): void;
10
+ export declare function size(): number;
11
+ /**
12
+ * Sessions whose binding has not idle-expired. Expired entries are dropped
13
+ * here as on read, since otherwise they would linger until the cap evicted
14
+ * them.
15
+ */
16
+ export declare function countActive(now: number, idleTtlMs: number): number;
17
+ export declare const sessionAffinity: Readonly<{
18
+ bind: typeof bind;
19
+ get: typeof get;
20
+ clear: typeof clear;
21
+ size: typeof size;
22
+ countActive: typeof countActive;
23
+ }>;
@@ -0,0 +1,62 @@
1
+ const MAX_BOUND_SESSIONS = 5_000;
2
+ // Per-record shape is an inline type on the Map's generic parameter rather
3
+ // than a named `type` alias: it is a private, single-use implementation
4
+ // detail with no external consumer, so it does not belong in
5
+ // src/lib/types/ (CLAUDE.md rule 2 targets shared type definitions).
6
+ const bindings = new Map();
7
+ /** Bind a session to the account that just served it, refreshing its idle clock. */
8
+ export function bind(sessionId, accountKey, now) {
9
+ // Delete-then-set moves the entry to the end of Map iteration order, which
10
+ // is what makes that order double as least-recently-bound-first for the
11
+ // cap eviction below.
12
+ bindings.delete(sessionId);
13
+ bindings.set(sessionId, { accountKey, lastServedAt: now });
14
+ if (bindings.size > MAX_BOUND_SESSIONS) {
15
+ const oldestKey = bindings.keys().next().value;
16
+ if (oldestKey !== undefined) {
17
+ bindings.delete(oldestKey);
18
+ }
19
+ }
20
+ }
21
+ /**
22
+ * The account a session is bound to, or undefined if unbound or idle-expired.
23
+ * A read never refreshes the binding — only a served request (bind) does,
24
+ * since the idle TTL counts from the session's last served request.
25
+ */
26
+ export function get(sessionId, now, idleTtlMs) {
27
+ const record = bindings.get(sessionId);
28
+ if (!record) {
29
+ return undefined;
30
+ }
31
+ if (now - record.lastServedAt > idleTtlMs) {
32
+ bindings.delete(sessionId);
33
+ return undefined;
34
+ }
35
+ return record.accountKey;
36
+ }
37
+ export function clear() {
38
+ bindings.clear();
39
+ }
40
+ export function size() {
41
+ return bindings.size;
42
+ }
43
+ /**
44
+ * Sessions whose binding has not idle-expired. Expired entries are dropped
45
+ * here as on read, since otherwise they would linger until the cap evicted
46
+ * them.
47
+ */
48
+ export function countActive(now, idleTtlMs) {
49
+ for (const [sessionId, record] of bindings) {
50
+ if (now - record.lastServedAt > idleTtlMs) {
51
+ bindings.delete(sessionId);
52
+ }
53
+ }
54
+ return bindings.size;
55
+ }
56
+ export const sessionAffinity = Object.freeze({
57
+ bind,
58
+ get,
59
+ clear,
60
+ size,
61
+ countActive,
62
+ });
@@ -2,7 +2,7 @@ import { buildTranslationOptions } from "../../proxy/proxyTranslationEngine.js";
2
2
  import { ProxyTracer } from "../../proxy/proxyTracer.js";
3
3
  import { isPermanentRefreshFailure } from "../../proxy/tokenRefresh.js";
4
4
  import type { ProxyAccountDirectoryOverride } from "../../types/index.js";
5
- import type { AccountAllowlist, AccountAdmissionLease, JsonObject, AccountCooldownPlan, AccountQuota, AccountQuotaWindow, AccountUsageFetchResult, AnthropicAttemptLogger, AnthropicAuthRetryResult, AnthropicEntitlementFailure, AnthropicInvalidRequestFailure, AnthropicLoopState, AnthropicScopedExhaustion, AnthropicNonOkResult, AnthropicSuccessResult, AnthropicUpstreamBody, AnthropicUpstreamBodyBuilder, AnthropicUpstreamFetchResult, ClaudeFinalRequestLogger, ClaudeLoggedErrorBuilder, ClaudeRequest, ClaudeProxyRouteRuntimeOptions, ModelRouterInterface, ParsedClaudeError, ProxyAccountRoutingDecision, ProxyAccountSortMetrics, ProxyBodyCaptureLogger, ProxyLimitsRefreshResponse, ProxyQuotaCooldownUpdate, ProxyOveragePolicy, ProxyPassthroughAccount, QueuedAccountAdmission, RouteGroup, RuntimeAccountState, ServerContext, StreamTerminalOutcome } from "../../types/index.js";
5
+ import type { AccountAllowlist, AccountAdmissionLease, JsonObject, AccountCooldownPlan, AccountQuota, AccountQuotaWindow, AccountUsageFetchResult, AnthropicAttemptLogger, AnthropicAuthRetryResult, AnthropicEntitlementFailure, AnthropicInvalidRequestFailure, AnthropicLoopState, AnthropicScopedExhaustion, AnthropicNonOkResult, AnthropicSuccessResult, AnthropicUpstreamBody, AnthropicUpstreamBodyBuilder, AnthropicUpstreamFetchResult, ClaudeFinalRequestLogger, ClaudeLoggedErrorBuilder, ClaudeRequest, ClaudeProxyRouteRuntimeOptions, ModelRouterInterface, ParsedClaudeError, ProxyAccountRankingPolicy, ProxyAccountRoutingAffinityEvidence, ProxyAccountRoutingDecision, ProxyAccountRoutingReason, ProxyAccountRoutingSpillEvidence, ProxyAccountSortMetrics, ProxyAffinityPrecedenceSkipReason, ProxyBodyCaptureLogger, ProxyLimitsRefreshResponse, ProxyQuotaCooldownUpdate, ProxyOveragePolicy, ProxyPassthroughAccount, ProxyRoutingPolicySnapshot, QueuedAccountAdmission, RouteGroup, RuntimeAccountState, ServerContext, StreamTerminalOutcome } from "../../types/index.js";
6
6
  declare function tryAcquireAccountAdmission(accountKey: string, capacity: number | undefined, generation?: number): AccountAdmissionLease | undefined;
7
7
  /** Current in-flight request count for one account, unlimited or capped. */
8
8
  declare function getAccountInflight(accountKey: string): number;
@@ -121,9 +121,18 @@ declare function evaluateScopedExhaustion(accounts: ProxyPassthroughAccount[], r
121
121
  eligible: ProxyPassthroughAccount[];
122
122
  exhaustion: AnthropicScopedExhaustion | null;
123
123
  };
124
- declare function orderAccountsByQuotaWithMetrics(accounts: ProxyPassthroughAccount[], now: number, primaryKey: string | undefined, sessionSoftLimit: number, sessionResetToleranceMs: number, requestedModel?: string): {
124
+ declare function orderAccountsByQuotaWithMetrics(accounts: ProxyPassthroughAccount[], now: number, primaryKey: string | undefined, sessionSoftLimit: number, sessionResetToleranceMs: number, options?: {
125
+ requestedModel?: string;
126
+ policy?: {
127
+ ranking?: ProxyAccountRankingPolicy;
128
+ affinityKey?: string;
129
+ preferPrimary?: boolean;
130
+ };
131
+ }): {
125
132
  orderedAccounts: ProxyPassthroughAccount[];
126
133
  metricsByKey: Map<string, ProxyAccountSortMetrics>;
134
+ reason: ProxyAccountRoutingReason;
135
+ affinitySkippedReason: ProxyAffinityPrecedenceSkipReason | null;
127
136
  };
128
137
  /**
129
138
  * Order accounts to MAXIMIZE quota utilization (fill-first, smart order):
@@ -164,7 +173,30 @@ declare function buildRoutingDecision(args: {
164
173
  sessionSoftLimit: number;
165
174
  sessionResetToleranceMs: number;
166
175
  rotationOffset: number;
176
+ policySelectionReason?: ProxyAccountRoutingReason;
177
+ policy?: ProxyRoutingPolicySnapshot;
178
+ affinity?: ProxyAccountRoutingAffinityEvidence;
179
+ spill?: ProxyAccountRoutingSpillEvidence;
167
180
  }): ProxyAccountRoutingDecision | undefined;
181
+ declare function selectClaudeProxyAccountOrder(args: {
182
+ enabledAccounts: ProxyPassthroughAccount[];
183
+ accountStrategy: "round-robin" | "fill-first";
184
+ primaryAccountKey: string | undefined;
185
+ quotaRoutingEnabled: boolean;
186
+ sessionSoftLimit: number;
187
+ sessionResetToleranceMs: number;
188
+ requestedModel?: string;
189
+ sessionId?: string;
190
+ ranking: ProxyAccountRankingPolicy;
191
+ preferPrimary: boolean;
192
+ sessionAffinityEnabled: boolean;
193
+ sessionAffinityIdleTtlMs: number;
194
+ spillInflight: number;
195
+ setRoutingDecision: (decision: ProxyAccountRoutingDecision) => void;
196
+ }): {
197
+ orderedAccounts: ProxyPassthroughAccount[];
198
+ metricsByKey: Map<string, ProxyAccountSortMetrics>;
199
+ };
168
200
  declare function trackUpstreamReadableStream(source: ReadableStream<Uint8Array>): {
169
201
  stream: ReadableStream<Uint8Array>;
170
202
  outcome: Promise<StreamTerminalOutcome>;
@@ -314,6 +346,7 @@ declare function handleAnthropicAuthRetry(args: {
314
346
  sawRateLimit: boolean;
315
347
  sawTransientFailure: boolean;
316
348
  sawNetworkError: boolean;
349
+ bindServedSession?: (servingAccountKey: string) => void;
317
350
  }): Promise<AnthropicAuthRetryResult>;
318
351
  declare function finalizeAnthropicTerminalFetchError(args: {
319
352
  terminalError: NonNullable<AnthropicUpstreamFetchResult["terminalError"]>;
@@ -506,7 +539,14 @@ export declare const __testHooks: {
506
539
  scheduleAdaptiveQuotaRefreshes: typeof scheduleAdaptiveQuotaRefreshes;
507
540
  scheduleHandoffQuotaRefresh: typeof scheduleHandoffQuotaRefresh;
508
541
  getQuotaRefreshState: (key: string) => import("../../index.js").ProxyQuotaRefreshRuntimeState;
509
- buildQuotaRoutingDecision: (accounts: ProxyPassthroughAccount[], now: number, primaryKey: string | undefined, sessionSoftLimit?: number, sessionResetToleranceMs?: number, requestedModel?: string) => ProxyAccountRoutingDecision | undefined;
542
+ buildQuotaRoutingDecision: (accounts: ProxyPassthroughAccount[], now: number, primaryKey: string | undefined, sessionSoftLimit?: number, sessionResetToleranceMs?: number, options?: {
543
+ requestedModel?: string;
544
+ policy?: {
545
+ ranking?: ProxyAccountRankingPolicy;
546
+ affinityKey?: string;
547
+ preferPrimary?: boolean;
548
+ };
549
+ }) => ProxyAccountRoutingDecision | undefined;
510
550
  buildRoutingDecision: typeof buildRoutingDecision;
511
551
  resetEpochToMs: typeof resetEpochToMs;
512
552
  seedRuntimeQuotasFromDisk: typeof seedRuntimeQuotasFromDisk;
@@ -520,6 +560,11 @@ export declare const __testHooks: {
520
560
  bodyStr: string;
521
561
  sessionId?: string;
522
562
  };
563
+ extractSnapshotBodySessionIdForTests: (body: unknown) => string | undefined;
564
+ selectClaudeProxyAccountOrderForTests: typeof selectClaudeProxyAccountOrder;
565
+ setForceSpillThrowForTests: (value: boolean) => void;
566
+ setForceAffinityLookupThrowForTests: (value: boolean) => void;
567
+ setForceBindRecheckThrowForTests: (value: boolean) => void;
523
568
  isAntiAbuseConstruction429: typeof isAntiAbuseConstruction429;
524
569
  fetchAnthropicAccountResponse: typeof fetchAnthropicAccountResponse;
525
570
  finalizeAnthropicTerminalFetchError: typeof finalizeAnthropicTerminalFetchError;