@juspay/neurolink 9.92.1 → 9.92.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.
@@ -53,11 +53,6 @@ const BLOCKED_UPSTREAM_HEADERS = new Set([
53
53
  let primaryAccountIndex = 0;
54
54
  /** Track account count so we can reset primaryAccountIndex when it changes. */
55
55
  let lastKnownAccountCount = 0;
56
- /** Stable account key used by legacy fixed-config route factories. Runtime
57
- * config providers pass the request generation's primary key explicitly.
58
- * When undefined, home semantics retain insertion-order behavior. */
59
- let configuredPrimaryAccountKey;
60
- let configuredAccountAllowlist;
61
56
  const MAX_AUTH_RETRIES = 5;
62
57
  const MAX_TRANSIENT_SAME_ACCOUNT_RETRIES = 2;
63
58
  const TRANSIENT_SAME_ACCOUNT_RETRY_DELAYS_MS = [250, 1_000];
@@ -129,7 +124,7 @@ function advancePrimaryIfCurrent(accountKey, enabledCount, primaryAccountKey) {
129
124
  * no key is configured or the key cannot be matched (account disabled/
130
125
  * removed). The resolution is per-request because enabledAccounts membership
131
126
  * can shift between requests. */
132
- function resolveHomeIndex(enabledAccounts, primaryAccountKey = configuredPrimaryAccountKey) {
127
+ function resolveHomeIndex(enabledAccounts, primaryAccountKey) {
133
128
  if (!primaryAccountKey) {
134
129
  return 0;
135
130
  }
@@ -140,7 +135,7 @@ function resolveHomeIndex(enabledAccounts, primaryAccountKey = configuredPrimary
140
135
  * primaryAccountIndex back to its index so traffic returns to the preferred
141
136
  * account once its rate limit window expires. Called at the start of each
142
137
  * request. Home is resolved fresh per call via resolveHomeIndex. */
143
- function maybeResetPrimaryToHome(enabledAccounts, primaryAccountKey = configuredPrimaryAccountKey) {
138
+ function maybeResetPrimaryToHome(enabledAccounts, primaryAccountKey) {
144
139
  if (enabledAccounts.length <= 1) {
145
140
  return;
146
141
  }
@@ -463,28 +458,30 @@ function accountSortMetrics(accountKey, now, sessionSoftLimit, sessionResetToler
463
458
  }
464
459
  /**
465
460
  * Order accounts to MAXIMIZE quota utilization (fill-first, smart order):
466
- * spend the window that expires SOONEST first, so its about-to-reset
467
- * allowance isn't wasted, then move to accounts with longer-dated resets.
461
+ * spend the overall weekly allowance that expires SOONEST first, so quota
462
+ * cannot disappear while traffic is consuming a newer weekly window. The 5h
463
+ * window remains an availability boundary: a session at its soft limit is
464
+ * temporarily demoted until that session resets.
468
465
  *
469
466
  * Priority among usable accounts:
470
467
  * 1. no quota data yet — probe first: one request reveals its windows and
471
468
  * self-corrects the ordering. (Ranking unknowns last would starve them
472
469
  * forever: never picked → never observed → never comparable.)
473
470
  * 2. session headroom before session-saturated (>= soft limit or
474
- * "throttled") — saturated accounts then follow the same bucketed
475
- * session ordering below: soonest back in service first, weekly
476
- * deciding same-bucket ties.
477
- * 3. soonest SESSION (5h) reset — the capacity expiring soonest; compared
478
- * in tolerance buckets so near-simultaneous resets count as equal.
479
- * 4. soonest WEEKLY (7d) reset — decides between same-bucket sessions.
480
- * 5. highest weekly utilization — finish off the one closest to done
471
+ * "throttled") — do not re-hammer an urgent weekly account while its 5h
472
+ * capacity is temporarily unavailable.
473
+ * 3. soonest WEEKLY (7d) reset — consume the oldest overall allowance
474
+ * before it expires.
475
+ * 4. soonest SESSION (5h) reset — decides equal-weekly or fresh-weekly ties,
476
+ * using tolerance buckets for comparator stability.
477
+ * 5. highest weekly utilization — finish off the one closest to done.
481
478
  * 6. configured primary account, then insertion order
482
- * An account with NO ticking session window (fresh, not yet started) has
483
- * nothing expiring and sorts after ticking windows in step 3.
479
+ * Saturated pairs are ordered by soonest 5h recovery first, then weekly reset,
480
+ * because neither can consume more quota until session capacity returns.
484
481
  * Cooling/rejected accounts sort last, soonest-back-to-service first, as
485
482
  * last resort.
486
483
  */
487
- function orderAccountsByQuota(accounts, now, primaryKey = configuredPrimaryAccountKey, sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs()) {
484
+ function orderAccountsByQuota(accounts, now, primaryKey, sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs()) {
488
485
  const metrics = (key) => accountSortMetrics(key, now, sessionSoftLimit, sessionResetToleranceMs);
489
486
  return [...accounts].sort((a, b) => {
490
487
  const ma = metrics(a.key);
@@ -503,14 +500,21 @@ function orderAccountsByQuota(accounts, now, primaryKey = configuredPrimaryAccou
503
500
  if (ma.saturated !== mb.saturated) {
504
501
  return ma.saturated ? 1 : -1;
505
502
  }
506
- // Saturated pairs fall through to the same bucketed session ordering:
507
- // soonest-back-in-service at bucket granularity, weekly deciding ties —
508
- // honoring the configured tolerance instead of exact-epoch comparison.
509
- if (ma.sessionResetBucket !== mb.sessionResetBucket) {
510
- return ma.sessionResetBucket - mb.sessionResetBucket;
503
+ if (ma.saturated && mb.saturated) {
504
+ if (ma.sessionResetBucket !== mb.sessionResetBucket) {
505
+ return ma.sessionResetBucket - mb.sessionResetBucket;
506
+ }
507
+ if (ma.weeklyReset !== mb.weeklyReset) {
508
+ return ma.weeklyReset - mb.weeklyReset;
509
+ }
511
510
  }
512
- if (ma.weeklyReset !== mb.weeklyReset) {
513
- return ma.weeklyReset - mb.weeklyReset;
511
+ else {
512
+ if (ma.weeklyReset !== mb.weeklyReset) {
513
+ return ma.weeklyReset - mb.weeklyReset;
514
+ }
515
+ if (ma.sessionResetBucket !== mb.sessionResetBucket) {
516
+ return ma.sessionResetBucket - mb.sessionResetBucket;
517
+ }
514
518
  }
515
519
  if (ma.weeklyUsed !== mb.weeklyUsed) {
516
520
  return mb.weeklyUsed - ma.weeklyUsed;
@@ -1538,7 +1542,7 @@ async function handleClaudePassthroughJsonResponse(args) {
1538
1542
  return responseJson;
1539
1543
  }
1540
1544
  async function loadClaudeProxyAccounts(args) {
1541
- const { ctx, body, tracer, requestStartTime, accountStrategy, primaryAccountKey = configuredPrimaryAccountKey, accountAllowlist = configuredAccountAllowlist, quotaRoutingEnabled = isQuotaRoutingEnabled(), sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs(), buildLoggedClaudeError, } = args;
1545
+ const { ctx, body, tracer, requestStartTime, accountStrategy, primaryAccountKey, accountAllowlist, quotaRoutingEnabled = isQuotaRoutingEnabled(), sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs(), buildLoggedClaudeError, } = args;
1542
1546
  const fs = await import("fs");
1543
1547
  const os = await import("os");
1544
1548
  const accounts = [];
@@ -1722,10 +1726,16 @@ async function loadClaudeProxyAccounts(args) {
1722
1726
  const quotaOrdered = accountStrategy === "fill-first" &&
1723
1727
  orderedAccounts.length > 1 &&
1724
1728
  quotaRoutingEnabled;
1729
+ if (!quotaOrdered && accountStrategy === "fill-first") {
1730
+ // Apply the request-scoped home before deriving this request's order. A
1731
+ // hot-reloaded primary change must not lag by one request. Round-robin
1732
+ // deliberately skips this reset so its rotating index remains strict.
1733
+ maybeResetPrimaryToHome(enabledAccounts, primaryAccountKey);
1734
+ }
1725
1735
  if (quotaOrdered) {
1726
- // Fill-first with a smart fill order: spend the account whose window resets
1727
- // soonest first (max utilization), proactively skipping any whose window is
1728
- // rejected until its reset. Supersedes the static home/primary index.
1736
+ // Fill-first with a smart fill order: spend the account whose weekly window
1737
+ // expires soonest while temporarily demoting sessions without headroom.
1738
+ // Supersedes the static home/primary index.
1729
1739
  orderedAccounts = orderAccountsByQuota(enabledAccounts, Date.now(), primaryAccountKey, sessionSoftLimit, sessionResetToleranceMs);
1730
1740
  if (logger.shouldLog("debug")) {
1731
1741
  logger.debug(`[proxy] quota-ordered fill sequence: ${orderedAccounts
@@ -3791,15 +3801,6 @@ async function handleAnthropicRoutedClaudeRequest(args) {
3791
3801
  attemptNumber: 0,
3792
3802
  };
3793
3803
  const acctSelectionSpan = tracer?.startAccountSelection();
3794
- // Try to return to the home primary account if its cooling has expired.
3795
- // Skipped under quota routing, where the fill order is derived per-request
3796
- // from live quota (soonest-reset-first) rather than a static home index.
3797
- const usingQuotaOrder = accountStrategy === "fill-first" &&
3798
- enabledAccounts.length > 1 &&
3799
- quotaRoutingEnabled;
3800
- if (!usingQuotaOrder) {
3801
- maybeResetPrimaryToHome(enabledAccounts, primaryAccountKey);
3802
- }
3803
3804
  // Never re-hammer accounts with a known active cooldown. When every account
3804
3805
  // is cooling, report the earliest persisted retry time without an upstream
3805
3806
  // call; restarting the proxy must not erase or bypass this quarantine.
@@ -4174,10 +4175,6 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
4174
4175
  const runtimeConfigProvider = isClaudeProxyRouteRuntimeOptions(accountAllowlistOrRuntimeOptions)
4175
4176
  ? accountAllowlistOrRuntimeOptions.runtimeConfigProvider
4176
4177
  : undefined;
4177
- configuredPrimaryAccountKey = primaryAccountKey;
4178
- configuredAccountAllowlist = accountAllowlist
4179
- ? new Set(accountAllowlist)
4180
- : undefined;
4181
4178
  return {
4182
4179
  prefix: `${basePath}/v1`,
4183
4180
  routes: [
@@ -4608,14 +4605,6 @@ export const __testHooks = {
4608
4605
  const state = accountRuntimeState.get(key);
4609
4606
  return state ? { ...state } : undefined;
4610
4607
  },
4611
- setConfiguredPrimaryAccountKey: (key) => {
4612
- configuredPrimaryAccountKey = key;
4613
- },
4614
- getConfiguredPrimaryAccountKey: () => configuredPrimaryAccountKey,
4615
- setConfiguredAccountAllowlist: (allowlist) => {
4616
- configuredAccountAllowlist = allowlist ? new Set(allowlist) : undefined;
4617
- },
4618
- getConfiguredAccountAllowlist: () => configuredAccountAllowlist ? [...configuredAccountAllowlist] : undefined,
4619
4608
  setPrimaryAccountIndex: (index) => {
4620
4609
  primaryAccountIndex = index;
4621
4610
  },
@@ -4631,8 +4620,6 @@ export const __testHooks = {
4631
4620
  transientCooldownAdmissionSchedules.clear();
4632
4621
  primaryAccountIndex = 0;
4633
4622
  lastKnownAccountCount = 0;
4634
- configuredPrimaryAccountKey = undefined;
4635
- configuredAccountAllowlist = undefined;
4636
4623
  },
4637
4624
  polyfillOAuthBody: (bodyStr, isClaudeClientRequest) => polyfillOAuthBody(bodyStr, "test-account-token", null, isClaudeClientRequest),
4638
4625
  isAntiAbuseConstruction429,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "9.92.1",
3
+ "version": "9.92.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": {
@@ -128,6 +128,7 @@
128
128
  "test:autoresearch": "npx tsx test/continuous-test-suite-autoresearch.ts",
129
129
  "test:autoresearch:redis": "npx tsx test/continuous-test-suite-autoresearch-redis.ts",
130
130
  "test:anthropic-tools-policy": "npx tsx test/continuous-test-suite-anthropic-tools-policy.ts",
131
+ "test:sagemaker-tools": "npx tsx test/continuous-test-suite-sagemaker-tools.ts",
131
132
  "test:anthropic-multimodal": "npx tsx test/continuous-test-suite-anthropic-multimodal.ts",
132
133
  "test:excel-interop": "npx tsx test/continuous-test-suite-excel-interop.ts",
133
134
  "test:envguard": "npx tsx test/helpers/envGuard.test.ts",
@@ -145,7 +146,7 @@
145
146
  "test:system-messages:vitest": "pnpm exec vitest run test/systemMessages.test.ts",
146
147
  "test:tool-routing-semantic:vitest": "pnpm exec vitest run test/toolRoutingSemantic.test.ts",
147
148
  "test:tool-routing-semantic": "pnpm run test:tool-routing-semantic:vitest && npx tsx test/continuous-test-suite-tool-routing-semantic.ts",
148
- "test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:unit:vitest && pnpm run test:tool-routing-cli:vitest && pnpm run test:tool-dedup:vitest && pnpm run test:model-pool:vitest && pnpm run test:system-messages:vitest && pnpm run test:tool-routing-semantic:vitest && pnpm run test:anthropic-tools-policy && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop",
149
+ "test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:unit:vitest && pnpm run test:tool-routing-cli:vitest && pnpm run test:tool-dedup:vitest && pnpm run test:model-pool:vitest && pnpm run test:system-messages:vitest && pnpm run test:tool-routing-semantic:vitest && pnpm run test:anthropic-tools-policy && pnpm run test:sagemaker-tools && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop",
149
150
  "// CI tier — live providers, runs only when API keys are present (test:credentials and test:dynamic make real provider calls when keys are set, so they live here, not in test:unit)": "",
150
151
  "test:live": "pnpm run test:providers && pnpm run test:mcp:http && pnpm run test:mcp:sdk && pnpm run test:mcp:cli && pnpm run test:observability && pnpm run test:context && pnpm run test:memory && pnpm run test:tool-reliability && pnpm run test:evaluation && pnpm run test:autoresearch && pnpm run test:credentials && pnpm run test:dynamic",
151
152
  "// CI tier — product output (image/video/TTS/PPT) — costs $$ per run": "",