@juspay/neurolink 12.24.0 → 12.25.0
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.
- package/CHANGELOG.md +2 -2
- package/dist/browser/neurolink.min.js +407 -407
- package/dist/cli/commands/proxy.js +30 -0
- package/dist/proxy/accountRanking.d.ts +67 -4
- package/dist/proxy/accountRanking.js +188 -8
- package/dist/proxy/proxyAnalysis.js +76 -0
- package/dist/proxy/proxyConfig.d.ts +24 -1
- package/dist/proxy/proxyConfig.js +131 -3
- package/dist/proxy/routingEvidence.d.ts +1 -1
- package/dist/proxy/routingEvidence.js +5 -0
- package/dist/proxy/runtimeConfig.js +15 -0
- package/dist/proxy/sessionAffinity.d.ts +23 -0
- package/dist/proxy/sessionAffinity.js +62 -0
- package/dist/server/routes/claudeProxyRoutes.d.ts +48 -3
- package/dist/server/routes/claudeProxyRoutes.js +324 -29
- package/dist/types/proxy.d.ts +48 -0
- package/dist/types/subscription.d.ts +16 -4
- package/docs-site/static/search-index.json +11045 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
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_evidence", "quota_probe", "session_headroom", "scoped_headroom", "session_reset", "weekly_reset", "weekly_utilization", "scoped_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", "scoped_headroom", "session_reset", "weekly_reset", "weekly_utilization", "scoped_utilization", "headroom", "session_affinity", "preferred_primary", "spill_inflight", "routing_policy_error"];
|
|
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"];
|
|
7
7
|
/**
|
|
@@ -26,6 +26,11 @@ export const PROXY_ACCOUNT_ROUTING_REASONS = [
|
|
|
26
26
|
"weekly_reset",
|
|
27
27
|
"weekly_utilization",
|
|
28
28
|
"scoped_utilization",
|
|
29
|
+
"headroom",
|
|
30
|
+
"session_affinity",
|
|
31
|
+
"preferred_primary",
|
|
32
|
+
"spill_inflight",
|
|
33
|
+
"routing_policy_error",
|
|
29
34
|
];
|
|
30
35
|
export const PROXY_ACCOUNT_TYPES = ["oauth", "api_key"];
|
|
31
36
|
export const ACCOUNT_COOLING_REASONS = [
|
|
@@ -229,6 +229,11 @@ async function buildCandidate(options, generation, allowMissingConfig, allowMiss
|
|
|
229
229
|
.digest("hex")
|
|
230
230
|
.slice(0, 16);
|
|
231
231
|
const useOverage = routing?.useOverage ?? "auto";
|
|
232
|
+
const accountRanking = routing?.accountRanking ?? "expiry-first";
|
|
233
|
+
const preferPrimary = routing?.preferPrimary ?? false;
|
|
234
|
+
const sessionAffinity = routing?.sessionAffinity ?? false;
|
|
235
|
+
const sessionAffinityIdleTtlMs = routing?.sessionAffinityIdleTtlMs ?? 3_600_000;
|
|
236
|
+
const spillInflight = routing?.spillInflight ?? 0;
|
|
232
237
|
const fingerprintSource = JSON.stringify({
|
|
233
238
|
strategy,
|
|
234
239
|
passthrough: options.passthrough,
|
|
@@ -239,6 +244,11 @@ async function buildCandidate(options, generation, allowMissingConfig, allowMiss
|
|
|
239
244
|
sessionSoftLimit,
|
|
240
245
|
sessionResetToleranceMs,
|
|
241
246
|
useOverage,
|
|
247
|
+
accountRanking,
|
|
248
|
+
preferPrimary,
|
|
249
|
+
sessionAffinity,
|
|
250
|
+
sessionAffinityIdleTtlMs,
|
|
251
|
+
spillInflight,
|
|
242
252
|
});
|
|
243
253
|
const configHash = createHash("sha256")
|
|
244
254
|
.update(fingerprintSource)
|
|
@@ -259,6 +269,11 @@ async function buildCandidate(options, generation, allowMissingConfig, allowMiss
|
|
|
259
269
|
sessionSoftLimit,
|
|
260
270
|
sessionResetToleranceMs,
|
|
261
271
|
useOverage,
|
|
272
|
+
accountRanking,
|
|
273
|
+
preferPrimary,
|
|
274
|
+
sessionAffinity,
|
|
275
|
+
sessionAffinityIdleTtlMs,
|
|
276
|
+
spillInflight,
|
|
262
277
|
}),
|
|
263
278
|
configFilePresent,
|
|
264
279
|
envFilePresent,
|
|
@@ -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,
|
|
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,
|
|
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;
|