@juspay/neurolink 10.8.9 → 10.8.10
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 +6 -0
- package/dist/browser/neurolink.min.js +382 -382
- package/dist/lib/proxy/accountQuota.d.ts +8 -0
- package/dist/lib/proxy/accountQuota.js +23 -0
- package/dist/lib/proxy/proxyAnalysis.js +10 -0
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +13 -4
- package/dist/lib/server/routes/claudeProxyRoutes.js +77 -33
- package/dist/lib/types/proxy.d.ts +18 -0
- package/dist/proxy/accountQuota.d.ts +8 -0
- package/dist/proxy/accountQuota.js +23 -0
- package/dist/proxy/proxyAnalysis.js +10 -0
- package/dist/server/routes/claudeProxyRoutes.d.ts +13 -4
- package/dist/server/routes/claudeProxyRoutes.js +77 -33
- package/dist/types/proxy.d.ts +18 -0
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@ import { join } from "node:path";
|
|
|
15
15
|
import { buildStableClaudeCodeBillingHeader, CLAUDE_CLI_USER_AGENT, CLAUDE_CODE_OAUTH_BETAS, getOrCreateClaudeCodeIdentity, parseClaudeCodeUserId, } from "../../auth/anthropicOAuth.js";
|
|
16
16
|
import { clearAccountCooldown, loadAccountCooldowns, saveAccountCooldown, } from "../../proxy/accountCooldown.js";
|
|
17
17
|
import { anthropicAccountKeysEqual, ENV_ANTHROPIC_ACCOUNT_KEY, isAccountAllowed, LEGACY_ANTHROPIC_ACCOUNT_KEY, shouldLoadFallbackCredential, } from "../../proxy/accountSelection.js";
|
|
18
|
-
import { getUnifiedRateLimitStatus, loadAccountQuotas, parseQuotaHeaders, saveAccountQuota, } from "../../proxy/accountQuota.js";
|
|
18
|
+
import { getUnifiedRateLimitStatus, isQuotaOverageAvailable, loadAccountQuotas, parseQuotaHeaders, saveAccountQuota, } from "../../proxy/accountQuota.js";
|
|
19
19
|
import { buildClaudeError, ClaudeStreamSerializer, generateToolUseId, parseClaudeRequest, serializeClaudeResponse, } from "../../proxy/claudeFormat.js";
|
|
20
20
|
import { buildAnthropicModelsListResponse, buildTranslationOptions, extractText, extractToolArgs, extractUsageFromStreamResult, handleTranslatedJsonRequest, handleTranslatedStreamRequest, hasTranslatedOutput, } from "../../proxy/proxyTranslationEngine.js";
|
|
21
21
|
import { tracers } from "../../telemetry/tracers.js";
|
|
@@ -411,9 +411,9 @@ function clampCooldownUntil(untilMs, now) {
|
|
|
411
411
|
* The unified subscription limits expose per-window status + reset:
|
|
412
412
|
* - weekly (7d) "rejected" → hard cap for the week; cool until the 7d reset.
|
|
413
413
|
* - session (5h) "rejected" → paced out for this session; cool until the 5h reset.
|
|
414
|
-
* Both mean "retrying this account is futile until its window resets"
|
|
415
|
-
*
|
|
416
|
-
*
|
|
414
|
+
* Both mean "retrying this account is futile until its window resets" unless
|
|
415
|
+
* the provider explicitly enables overage. In that case, the subscription
|
|
416
|
+
* window is exhausted but the account remains usable for paid fallback.
|
|
417
417
|
*
|
|
418
418
|
* Anything else (window still "allowed" but momentarily 429'd — a per-minute
|
|
419
419
|
* burst / acceleration limit) is transient: honor retry-after as a floor,
|
|
@@ -430,7 +430,8 @@ function planCooldownFor429(quota, retryAfterMs, now, unifiedStatus = quota?.uni
|
|
|
430
430
|
rotateImmediately: true,
|
|
431
431
|
};
|
|
432
432
|
}
|
|
433
|
-
|
|
433
|
+
const overageAvailable = isQuotaOverageAvailable(quota);
|
|
434
|
+
if (quota && quota.sessionStatus === "rejected" && !overageAvailable) {
|
|
434
435
|
const reset = resetEpochToMs(quota.sessionResetAt, now) ??
|
|
435
436
|
(retryAfterMs > 0 ? now + retryAfterMs : now + DEFAULT_COOLING_PERIOD_MS);
|
|
436
437
|
return {
|
|
@@ -442,7 +443,7 @@ function planCooldownFor429(quota, retryAfterMs, now, unifiedStatus = quota?.uni
|
|
|
442
443
|
// Anthropic may reject the authoritative top-level unified limit while both
|
|
443
444
|
// 5h and 7d sub-window statuses still say "allowed". Treating this as a
|
|
444
445
|
// transient burst retries a known-exhausted account and delays failover.
|
|
445
|
-
if (unifiedStatus?.trim().toLowerCase() === "rejected") {
|
|
446
|
+
if (unifiedStatus?.trim().toLowerCase() === "rejected" && !overageAvailable) {
|
|
446
447
|
const reset = retryAfterMs > 0 ? now + retryAfterMs : now + DEFAULT_HARD_COOLDOWN_MS;
|
|
447
448
|
return {
|
|
448
449
|
reason: "unified",
|
|
@@ -470,34 +471,53 @@ function minutesUntil(untilMs, now) {
|
|
|
470
471
|
* flipped to "rejected" (the boundary request that spends the last of the quota
|
|
471
472
|
* still returns 200 but reports rejected/next-reset). Parks the account until
|
|
472
473
|
* its reset so the next request skips it instead of discovering the limit via a
|
|
473
|
-
* 429
|
|
474
|
+
* 429, except when the provider explicitly enables paid overage.
|
|
474
475
|
*/
|
|
475
|
-
function
|
|
476
|
+
function reconcileCooldownFromQuota(state, quota, now) {
|
|
477
|
+
const overageAvailable = isQuotaOverageAvailable(quota);
|
|
476
478
|
let until;
|
|
477
479
|
let reason;
|
|
478
480
|
if (quota.weeklyStatus === "rejected") {
|
|
479
481
|
until = resetEpochToMs(quota.weeklyResetAt, now);
|
|
480
482
|
reason = "weekly";
|
|
481
483
|
}
|
|
482
|
-
|
|
484
|
+
if (until === undefined &&
|
|
485
|
+
overageAvailable &&
|
|
486
|
+
state.coolingUntil &&
|
|
487
|
+
(state.coolingReason === "session" || state.coolingReason === "unified")) {
|
|
488
|
+
const previousCoolingUntil = state.coolingUntil;
|
|
489
|
+
state.coolingUntil = undefined;
|
|
490
|
+
state.coolingReason = undefined;
|
|
491
|
+
logger.always("[proxy] clearing subscription cooldown because Anthropic explicitly permits overage");
|
|
492
|
+
return { kind: "cleared", coolingUntil: previousCoolingUntil };
|
|
493
|
+
}
|
|
494
|
+
if (until === undefined &&
|
|
495
|
+
quota.sessionStatus === "rejected" &&
|
|
496
|
+
!overageAvailable) {
|
|
483
497
|
until = resetEpochToMs(quota.sessionResetAt, now);
|
|
484
498
|
reason = "session";
|
|
485
499
|
}
|
|
486
|
-
else if (
|
|
500
|
+
else if (until === undefined &&
|
|
501
|
+
quota.unifiedStatus === "rejected" &&
|
|
502
|
+
!overageAvailable) {
|
|
487
503
|
until = now + DEFAULT_HARD_COOLDOWN_MS;
|
|
488
504
|
reason = "unified";
|
|
489
505
|
}
|
|
490
506
|
if (until === undefined) {
|
|
491
|
-
return
|
|
507
|
+
return null;
|
|
492
508
|
}
|
|
493
509
|
const clamped = clampCooldownUntil(until, now);
|
|
494
510
|
if (!state.coolingUntil || clamped > state.coolingUntil) {
|
|
495
511
|
state.coolingUntil = clamped;
|
|
496
512
|
state.coolingReason = reason;
|
|
497
513
|
logger.always(`[proxy] proactively cooling account (${reason}) ~${minutesUntil(clamped, now)}m from success-response quota (status rejected)`);
|
|
498
|
-
return
|
|
514
|
+
return {
|
|
515
|
+
kind: "cooled",
|
|
516
|
+
coolingUntil: clamped,
|
|
517
|
+
coolingReason: reason ?? "unified",
|
|
518
|
+
};
|
|
499
519
|
}
|
|
500
|
-
return
|
|
520
|
+
return null;
|
|
501
521
|
}
|
|
502
522
|
/**
|
|
503
523
|
* Seed each account's runtime quota from the persisted snapshots in
|
|
@@ -528,6 +548,15 @@ async function seedRuntimeQuotasFromDisk(accounts) {
|
|
|
528
548
|
state.coolingUntil = persistedCooldown.coolingUntil;
|
|
529
549
|
state.coolingReason = persistedCooldown.reason;
|
|
530
550
|
}
|
|
551
|
+
if (state.quota) {
|
|
552
|
+
const cooldownUpdate = reconcileCooldownFromQuota(state, state.quota, now);
|
|
553
|
+
if (cooldownUpdate?.kind === "cooled") {
|
|
554
|
+
await saveAccountCooldown(account.key, cooldownUpdate.coolingUntil, cooldownUpdate.coolingReason);
|
|
555
|
+
}
|
|
556
|
+
else if (cooldownUpdate?.kind === "cleared") {
|
|
557
|
+
await clearAccountCooldown(account.key, cooldownUpdate.coolingUntil);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
531
560
|
}
|
|
532
561
|
}
|
|
533
562
|
catch {
|
|
@@ -594,13 +623,16 @@ function accountSortMetrics(accountKey, now, sessionSoftLimit, sessionResetToler
|
|
|
594
623
|
? (q.weeklyStatus ?? "unknown")
|
|
595
624
|
: "allowed"
|
|
596
625
|
: null;
|
|
626
|
+
const overageEligible = isQuotaOverageAvailable(q);
|
|
597
627
|
const saturated = sessionStatus === "throttled" ||
|
|
598
628
|
(sessionTicking && (sessionUsed ?? 0) >= sessionSoftLimit);
|
|
599
629
|
const quotaLastUpdated = q && Number.isFinite(q.lastUpdated) ? q.lastUpdated : null;
|
|
600
630
|
return {
|
|
601
631
|
usable: !coolingActive &&
|
|
602
632
|
weeklyStatus !== "rejected" &&
|
|
603
|
-
sessionStatus !== "rejected"
|
|
633
|
+
(sessionStatus !== "rejected" || overageEligible) &&
|
|
634
|
+
(q?.unifiedStatus?.trim().toLowerCase() !== "rejected" ||
|
|
635
|
+
overageEligible),
|
|
604
636
|
saturated,
|
|
605
637
|
hasQuota: !!q,
|
|
606
638
|
quotaLastUpdated,
|
|
@@ -609,6 +641,9 @@ function accountSortMetrics(accountKey, now, sessionSoftLimit, sessionResetToler
|
|
|
609
641
|
coolingReason: st?.coolingReason ?? null,
|
|
610
642
|
coolingUntil: st?.coolingUntil ?? 0,
|
|
611
643
|
unifiedStatus: q?.unifiedStatus ?? null,
|
|
644
|
+
fallbackStatus: q?.fallbackStatus ?? null,
|
|
645
|
+
upgradePaths: q?.upgradePaths ?? null,
|
|
646
|
+
overageEligible,
|
|
612
647
|
overageStatus: q?.overageStatus ?? null,
|
|
613
648
|
sessionStatus,
|
|
614
649
|
sessionUsed,
|
|
@@ -735,6 +770,9 @@ function buildRoutingDecision(args) {
|
|
|
735
770
|
? metrics.coolingUntil
|
|
736
771
|
: null,
|
|
737
772
|
unifiedStatus: metrics.unifiedStatus,
|
|
773
|
+
fallbackStatus: metrics.fallbackStatus,
|
|
774
|
+
upgradePaths: metrics.upgradePaths,
|
|
775
|
+
overageEligible: metrics.overageEligible,
|
|
738
776
|
overageStatus: metrics.overageStatus,
|
|
739
777
|
sessionStatus: metrics.sessionStatus,
|
|
740
778
|
sessionUsed: metrics.sessionUsed,
|
|
@@ -2385,15 +2423,18 @@ async function handleAnthropicSuccessfulResponse(args) {
|
|
|
2385
2423
|
if (quota) {
|
|
2386
2424
|
// Stash the latest quota on runtime state so the next request can pick the
|
|
2387
2425
|
// account whose window resets soonest (max-utilization) and proactively
|
|
2388
|
-
// skip
|
|
2426
|
+
// skip rejected windows unless Anthropic explicitly permits overage.
|
|
2389
2427
|
accountState.quota = quota;
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2428
|
+
const cooldownUpdate = reconcileCooldownFromQuota(accountState, quota, Date.now());
|
|
2429
|
+
if (cooldownUpdate?.kind === "cooled") {
|
|
2430
|
+
saveAccountCooldown(account.key, cooldownUpdate.coolingUntil, cooldownUpdate.coolingReason).catch(() => {
|
|
2431
|
+
// Non-fatal: cooldown is already active in memory.
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
else if (cooldownUpdate?.kind === "cleared") {
|
|
2435
|
+
clearAccountCooldown(account.key, cooldownUpdate.coolingUntil).catch(() => {
|
|
2436
|
+
// Non-fatal: the next successful response will reconcile again.
|
|
2437
|
+
});
|
|
2397
2438
|
}
|
|
2398
2439
|
saveAccountQuota(account.label, quota).catch(() => {
|
|
2399
2440
|
// Non-fatal: quota persistence is best-effort
|
|
@@ -2995,16 +3036,18 @@ async function handleAnthropicSuccessfulNonStreamRetryResponse(args) {
|
|
|
2995
3036
|
const retryQuota = parseQuotaHeaders(retryResp.headers);
|
|
2996
3037
|
if (retryQuota) {
|
|
2997
3038
|
// Keep the auth-retry success path in parity with the main success path:
|
|
2998
|
-
// stash quota for proactive selection and
|
|
2999
|
-
// response reveals the window flipped to "rejected".
|
|
3039
|
+
// stash quota for proactive selection and reconcile a rejected window.
|
|
3000
3040
|
accountState.quota = retryQuota;
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3041
|
+
const cooldownUpdate = reconcileCooldownFromQuota(accountState, retryQuota, Date.now());
|
|
3042
|
+
if (cooldownUpdate?.kind === "cooled") {
|
|
3043
|
+
saveAccountCooldown(account.key, cooldownUpdate.coolingUntil, cooldownUpdate.coolingReason).catch(() => {
|
|
3044
|
+
// Non-fatal: cooldown is already active in memory.
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
else if (cooldownUpdate?.kind === "cleared") {
|
|
3048
|
+
clearAccountCooldown(account.key, cooldownUpdate.coolingUntil).catch(() => {
|
|
3049
|
+
// Non-fatal: the next successful response will reconcile again.
|
|
3050
|
+
});
|
|
3008
3051
|
}
|
|
3009
3052
|
saveAccountQuota(account.label, retryQuota).catch((error) => {
|
|
3010
3053
|
logger.debug("[proxy] Failed to persist account quota after auth retry", {
|
|
@@ -4461,8 +4504,8 @@ async function handleAnthropicRoutedClaudeRequest(args) {
|
|
|
4461
4504
|
// Clear cooling on success — but only if the stored cooldown has already
|
|
4462
4505
|
// expired, so an older in-flight success can't wipe an active exhaustion
|
|
4463
4506
|
// cooldown just set by a concurrent 429. The success handler re-applies a
|
|
4464
|
-
// cooldown via
|
|
4465
|
-
//
|
|
4507
|
+
// cooldown via reconcileCooldownFromQuota when fresh quota headers
|
|
4508
|
+
// report a rejected window without explicit overage availability.
|
|
4466
4509
|
if (accountState.coolingUntil &&
|
|
4467
4510
|
Date.now() >= accountState.coolingUntil) {
|
|
4468
4511
|
const expiredCooldown = accountState.coolingUntil;
|
|
@@ -5114,6 +5157,7 @@ export const __testHooks = {
|
|
|
5114
5157
|
resolveHomeIndex,
|
|
5115
5158
|
maybeResetPrimaryToHome,
|
|
5116
5159
|
planCooldownFor429,
|
|
5160
|
+
reconcileCooldownFromQuota,
|
|
5117
5161
|
isPermanentRefreshFailure,
|
|
5118
5162
|
getStreamFailureDetails,
|
|
5119
5163
|
trackUpstreamReadableStream,
|
package/dist/types/proxy.d.ts
CHANGED
|
@@ -445,6 +445,9 @@ export type ProxyAccountRoutingCandidate = {
|
|
|
445
445
|
coolingReason: AccountCoolingReason | null;
|
|
446
446
|
coolingUntil: number | null;
|
|
447
447
|
unifiedStatus: string | null;
|
|
448
|
+
fallbackStatus?: string | null;
|
|
449
|
+
upgradePaths?: string | null;
|
|
450
|
+
overageEligible?: boolean;
|
|
448
451
|
overageStatus: string | null;
|
|
449
452
|
sessionStatus: string | null;
|
|
450
453
|
sessionUsed: number | null;
|
|
@@ -480,6 +483,9 @@ export type ProxyAccountSortMetrics = {
|
|
|
480
483
|
coolingReason: AccountCoolingReason | null;
|
|
481
484
|
coolingUntil: number;
|
|
482
485
|
unifiedStatus: string | null;
|
|
486
|
+
fallbackStatus?: string | null;
|
|
487
|
+
upgradePaths?: string | null;
|
|
488
|
+
overageEligible?: boolean;
|
|
483
489
|
overageStatus: string | null;
|
|
484
490
|
sessionStatus: string | null;
|
|
485
491
|
sessionUsed: number | null;
|
|
@@ -737,6 +743,14 @@ export type AccountCooldownPlan = {
|
|
|
737
743
|
* burst), a small number of jittered same-account retries is allowed first. */
|
|
738
744
|
rotateImmediately: boolean;
|
|
739
745
|
};
|
|
746
|
+
export type ProxyQuotaCooldownUpdate = {
|
|
747
|
+
kind: "cooled";
|
|
748
|
+
coolingUntil: number;
|
|
749
|
+
coolingReason: AccountCoolingReason;
|
|
750
|
+
} | {
|
|
751
|
+
kind: "cleared";
|
|
752
|
+
coolingUntil: number;
|
|
753
|
+
} | null;
|
|
740
754
|
export type TransientRateLimitRetryBudget = {
|
|
741
755
|
coolingUntil: number;
|
|
742
756
|
retriesClaimed: number;
|
|
@@ -921,6 +935,10 @@ export type AccountQuota = {
|
|
|
921
935
|
weeklyResetAt: number;
|
|
922
936
|
/** 0.0-1.0 (from fallback-percentage) */
|
|
923
937
|
fallbackPercentage: number;
|
|
938
|
+
/** Provider fallback availability, for example "available". */
|
|
939
|
+
fallbackStatus?: string;
|
|
940
|
+
/** Comma-separated provider upgrade paths, for example "overage". */
|
|
941
|
+
upgradePaths?: string;
|
|
924
942
|
/** "allowed" | "rejected" */
|
|
925
943
|
overageStatus: string;
|
|
926
944
|
/** Epoch ms when we last captured this data */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "10.8.
|
|
3
|
+
"version": "10.8.10",
|
|
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": {
|