@juspay/neurolink 9.91.1 → 9.92.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.
- package/CHANGELOG.md +12 -0
- package/dist/browser/neurolink.min.js +357 -357
- package/dist/cli/commands/auth.js +25 -15
- package/dist/cli/commands/proxy.d.ts +4 -3
- package/dist/cli/commands/proxy.js +187 -130
- package/dist/core/baseProvider.js +5 -0
- package/dist/lib/core/baseProvider.js +5 -0
- package/dist/lib/neurolink.js +8 -3
- package/dist/lib/proxy/proxyConfig.js +66 -0
- package/dist/lib/proxy/runtimeConfig.d.ts +26 -0
- package/dist/lib/proxy/runtimeConfig.js +428 -0
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +5 -6
- package/dist/lib/server/routes/claudeProxyRoutes.js +68 -37
- package/dist/lib/server/routes/openaiProxyRoutes.d.ts +2 -3
- package/dist/lib/server/routes/openaiProxyRoutes.js +11 -5
- package/dist/lib/types/cli.d.ts +8 -0
- package/dist/lib/types/proxy.d.ts +67 -1
- package/dist/lib/types/subscription.d.ts +6 -0
- package/dist/neurolink.js +8 -3
- package/dist/proxy/proxyConfig.js +66 -0
- package/dist/proxy/runtimeConfig.d.ts +26 -0
- package/dist/proxy/runtimeConfig.js +427 -0
- package/dist/server/routes/claudeProxyRoutes.d.ts +5 -6
- package/dist/server/routes/claudeProxyRoutes.js +68 -37
- package/dist/server/routes/openaiProxyRoutes.d.ts +2 -3
- package/dist/server/routes/openaiProxyRoutes.js +11 -5
- package/dist/types/cli.d.ts +8 -0
- package/dist/types/proxy.d.ts +67 -1
- package/dist/types/subscription.d.ts +6 -0
- package/package.json +1 -1
|
@@ -53,10 +53,9 @@ 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
|
|
57
|
-
*
|
|
58
|
-
* When undefined, home semantics
|
|
59
|
-
* order) — preserves pre-existing behavior. */
|
|
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. */
|
|
60
59
|
let configuredPrimaryAccountKey;
|
|
61
60
|
let configuredAccountAllowlist;
|
|
62
61
|
const MAX_AUTH_RETRIES = 5;
|
|
@@ -130,8 +129,7 @@ function advancePrimaryIfCurrent(accountKey, enabledCount, primaryAccountKey) {
|
|
|
130
129
|
* no key is configured or the key cannot be matched (account disabled/
|
|
131
130
|
* removed). The resolution is per-request because enabledAccounts membership
|
|
132
131
|
* can shift between requests. */
|
|
133
|
-
function resolveHomeIndex(enabledAccounts) {
|
|
134
|
-
const primaryAccountKey = configuredPrimaryAccountKey;
|
|
132
|
+
function resolveHomeIndex(enabledAccounts, primaryAccountKey = configuredPrimaryAccountKey) {
|
|
135
133
|
if (!primaryAccountKey) {
|
|
136
134
|
return 0;
|
|
137
135
|
}
|
|
@@ -142,11 +140,11 @@ function resolveHomeIndex(enabledAccounts) {
|
|
|
142
140
|
* primaryAccountIndex back to its index so traffic returns to the preferred
|
|
143
141
|
* account once its rate limit window expires. Called at the start of each
|
|
144
142
|
* request. Home is resolved fresh per call via resolveHomeIndex. */
|
|
145
|
-
function maybeResetPrimaryToHome(enabledAccounts) {
|
|
143
|
+
function maybeResetPrimaryToHome(enabledAccounts, primaryAccountKey = configuredPrimaryAccountKey) {
|
|
146
144
|
if (enabledAccounts.length <= 1) {
|
|
147
145
|
return;
|
|
148
146
|
}
|
|
149
|
-
const homeIndex = resolveHomeIndex(enabledAccounts);
|
|
147
|
+
const homeIndex = resolveHomeIndex(enabledAccounts, primaryAccountKey);
|
|
150
148
|
if (primaryAccountIndex === homeIndex) {
|
|
151
149
|
return;
|
|
152
150
|
}
|
|
@@ -486,12 +484,7 @@ function accountSortMetrics(accountKey, now, sessionSoftLimit, sessionResetToler
|
|
|
486
484
|
* Cooling/rejected accounts sort last, soonest-back-to-service first, as
|
|
487
485
|
* last resort.
|
|
488
486
|
*/
|
|
489
|
-
function orderAccountsByQuota(accounts, now) {
|
|
490
|
-
const primaryKey = configuredPrimaryAccountKey;
|
|
491
|
-
// Read the env-tunable thresholds once per sort: cheaper than per-compare,
|
|
492
|
-
// and a mid-sort env change can never make the comparator intransitive.
|
|
493
|
-
const sessionSoftLimit = getSessionSoftLimit();
|
|
494
|
-
const sessionResetToleranceMs = getSessionResetToleranceMs();
|
|
487
|
+
function orderAccountsByQuota(accounts, now, primaryKey = configuredPrimaryAccountKey, sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs()) {
|
|
495
488
|
const metrics = (key) => accountSortMetrics(key, now, sessionSoftLimit, sessionResetToleranceMs);
|
|
496
489
|
return [...accounts].sort((a, b) => {
|
|
497
490
|
const ma = metrics(a.key);
|
|
@@ -1545,7 +1538,7 @@ async function handleClaudePassthroughJsonResponse(args) {
|
|
|
1545
1538
|
return responseJson;
|
|
1546
1539
|
}
|
|
1547
1540
|
async function loadClaudeProxyAccounts(args) {
|
|
1548
|
-
const { ctx, body, tracer, requestStartTime, accountStrategy, buildLoggedClaudeError, } = args;
|
|
1541
|
+
const { ctx, body, tracer, requestStartTime, accountStrategy, primaryAccountKey = configuredPrimaryAccountKey, accountAllowlist = configuredAccountAllowlist, quotaRoutingEnabled = isQuotaRoutingEnabled(), sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs(), buildLoggedClaudeError, } = args;
|
|
1549
1542
|
const fs = await import("fs");
|
|
1550
1543
|
const os = await import("os");
|
|
1551
1544
|
const accounts = [];
|
|
@@ -1558,7 +1551,7 @@ async function loadClaudeProxyAccounts(args) {
|
|
|
1558
1551
|
}
|
|
1559
1552
|
const compoundKeys = await tokenStore.listByPrefix("anthropic:");
|
|
1560
1553
|
for (const key of compoundKeys) {
|
|
1561
|
-
if (!isAccountAllowed(key,
|
|
1554
|
+
if (!isAccountAllowed(key, accountAllowlist)) {
|
|
1562
1555
|
logger.debug(`[proxy] skipping account=${key} (not in account allowlist)`);
|
|
1563
1556
|
continue;
|
|
1564
1557
|
}
|
|
@@ -1665,7 +1658,7 @@ async function loadClaudeProxyAccounts(args) {
|
|
|
1665
1658
|
addAccount();
|
|
1666
1659
|
}
|
|
1667
1660
|
if (accounts.length === 0 &&
|
|
1668
|
-
shouldLoadFallbackCredential(compoundKeys.length, LEGACY_ANTHROPIC_ACCOUNT_KEY,
|
|
1661
|
+
shouldLoadFallbackCredential(compoundKeys.length, LEGACY_ANTHROPIC_ACCOUNT_KEY, accountAllowlist)) {
|
|
1669
1662
|
try {
|
|
1670
1663
|
const creds = JSON.parse(fs.readFileSync(legacyCredPath, "utf8"));
|
|
1671
1664
|
const legacyAccount = await tryLoadLegacyAccount(creds, legacyCredPath);
|
|
@@ -1679,7 +1672,7 @@ async function loadClaudeProxyAccounts(args) {
|
|
|
1679
1672
|
}
|
|
1680
1673
|
if (process.env.ANTHROPIC_API_KEY &&
|
|
1681
1674
|
accounts.length === 0 &&
|
|
1682
|
-
shouldLoadFallbackCredential(compoundKeys.length, ENV_ANTHROPIC_ACCOUNT_KEY,
|
|
1675
|
+
shouldLoadFallbackCredential(compoundKeys.length, ENV_ANTHROPIC_ACCOUNT_KEY, accountAllowlist)) {
|
|
1683
1676
|
accounts.push({
|
|
1684
1677
|
key: ENV_ANTHROPIC_ACCOUNT_KEY,
|
|
1685
1678
|
label: "env",
|
|
@@ -1688,7 +1681,7 @@ async function loadClaudeProxyAccounts(args) {
|
|
|
1688
1681
|
});
|
|
1689
1682
|
}
|
|
1690
1683
|
if (accounts.length === 0) {
|
|
1691
|
-
const noCredentialsMessage =
|
|
1684
|
+
const noCredentialsMessage = accountAllowlist
|
|
1692
1685
|
? "No allowed Anthropic credentials are currently available"
|
|
1693
1686
|
: compoundKeys.length > 0
|
|
1694
1687
|
? "Configured Anthropic accounts are disabled or unavailable"
|
|
@@ -1728,12 +1721,12 @@ async function loadClaudeProxyAccounts(args) {
|
|
|
1728
1721
|
let orderedAccounts = [...enabledAccounts];
|
|
1729
1722
|
const quotaOrdered = accountStrategy === "fill-first" &&
|
|
1730
1723
|
orderedAccounts.length > 1 &&
|
|
1731
|
-
|
|
1724
|
+
quotaRoutingEnabled;
|
|
1732
1725
|
if (quotaOrdered) {
|
|
1733
1726
|
// Fill-first with a smart fill order: spend the account whose window resets
|
|
1734
1727
|
// soonest first (max utilization), proactively skipping any whose window is
|
|
1735
1728
|
// rejected until its reset. Supersedes the static home/primary index.
|
|
1736
|
-
orderedAccounts = orderAccountsByQuota(enabledAccounts, Date.now());
|
|
1729
|
+
orderedAccounts = orderAccountsByQuota(enabledAccounts, Date.now(), primaryAccountKey, sessionSoftLimit, sessionResetToleranceMs);
|
|
1737
1730
|
if (logger.shouldLog("debug")) {
|
|
1738
1731
|
logger.debug(`[proxy] quota-ordered fill sequence: ${orderedAccounts
|
|
1739
1732
|
.map((a) => a.label)
|
|
@@ -1743,7 +1736,7 @@ async function loadClaudeProxyAccounts(args) {
|
|
|
1743
1736
|
else {
|
|
1744
1737
|
if (accountStrategy === "round-robin" &&
|
|
1745
1738
|
orderedAccounts.length !== lastKnownAccountCount) {
|
|
1746
|
-
primaryAccountIndex = resolveHomeIndex(orderedAccounts);
|
|
1739
|
+
primaryAccountIndex = resolveHomeIndex(orderedAccounts, primaryAccountKey);
|
|
1747
1740
|
lastKnownAccountCount = orderedAccounts.length;
|
|
1748
1741
|
}
|
|
1749
1742
|
if (orderedAccounts.length > 1) {
|
|
@@ -3768,7 +3761,7 @@ function shouldAttemptClaudeFallback(loopState) {
|
|
|
3768
3761
|
return loopState.invalidRequestFailure === null;
|
|
3769
3762
|
}
|
|
3770
3763
|
async function handleAnthropicRoutedClaudeRequest(args) {
|
|
3771
|
-
const { ctx, body, modelRouter, tracer, requestStartTime, accountStrategy, buildLoggedClaudeError, logProxyBody, logFinalRequest, } = args;
|
|
3764
|
+
const { ctx, body, modelRouter, tracer, requestStartTime, accountStrategy, primaryAccountKey, accountAllowlist, quotaRoutingEnabled = isQuotaRoutingEnabled(), sessionSoftLimit = getSessionSoftLimit(), sessionResetToleranceMs = getSessionResetToleranceMs(), buildLoggedClaudeError, logProxyBody, logFinalRequest, } = args;
|
|
3772
3765
|
const parsedRequest = parseClaudeRequest(body);
|
|
3773
3766
|
const loadedAccounts = await loadClaudeProxyAccounts({
|
|
3774
3767
|
ctx,
|
|
@@ -3776,6 +3769,11 @@ async function handleAnthropicRoutedClaudeRequest(args) {
|
|
|
3776
3769
|
tracer,
|
|
3777
3770
|
requestStartTime,
|
|
3778
3771
|
accountStrategy,
|
|
3772
|
+
primaryAccountKey,
|
|
3773
|
+
accountAllowlist,
|
|
3774
|
+
quotaRoutingEnabled,
|
|
3775
|
+
sessionSoftLimit,
|
|
3776
|
+
sessionResetToleranceMs,
|
|
3779
3777
|
buildLoggedClaudeError,
|
|
3780
3778
|
});
|
|
3781
3779
|
if ("response" in loadedAccounts) {
|
|
@@ -3798,9 +3796,9 @@ async function handleAnthropicRoutedClaudeRequest(args) {
|
|
|
3798
3796
|
// from live quota (soonest-reset-first) rather than a static home index.
|
|
3799
3797
|
const usingQuotaOrder = accountStrategy === "fill-first" &&
|
|
3800
3798
|
enabledAccounts.length > 1 &&
|
|
3801
|
-
|
|
3799
|
+
quotaRoutingEnabled;
|
|
3802
3800
|
if (!usingQuotaOrder) {
|
|
3803
|
-
maybeResetPrimaryToHome(enabledAccounts);
|
|
3801
|
+
maybeResetPrimaryToHome(enabledAccounts, primaryAccountKey);
|
|
3804
3802
|
}
|
|
3805
3803
|
// Never re-hammer accounts with a known active cooldown. When every account
|
|
3806
3804
|
// is cooling, report the earliest persisted retry time without an upstream
|
|
@@ -4154,6 +4152,11 @@ async function handleAnthropicRoutedClaudeRequest(args) {
|
|
|
4154
4152
|
// ---------------------------------------------------------------------------
|
|
4155
4153
|
// Route factory
|
|
4156
4154
|
// ---------------------------------------------------------------------------
|
|
4155
|
+
function isClaudeProxyRouteRuntimeOptions(value) {
|
|
4156
|
+
return (value !== undefined &&
|
|
4157
|
+
"runtimeConfigProvider" in value &&
|
|
4158
|
+
typeof value.runtimeConfigProvider === "function");
|
|
4159
|
+
}
|
|
4157
4160
|
/**
|
|
4158
4161
|
* Create Claude-compatible proxy routes.
|
|
4159
4162
|
*
|
|
@@ -4164,7 +4167,13 @@ async function handleAnthropicRoutedClaudeRequest(args) {
|
|
|
4164
4167
|
* @param basePath - Base path prefix (default: "" since Claude API uses /v1/...).
|
|
4165
4168
|
* @returns RouteGroup with Claude-compatible endpoints.
|
|
4166
4169
|
*/
|
|
4167
|
-
export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrategy = "fill-first", passthroughMode = false, primaryAccountKey,
|
|
4170
|
+
export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrategy = "fill-first", passthroughMode = false, primaryAccountKey, accountAllowlistOrRuntimeOptions) {
|
|
4171
|
+
const accountAllowlist = isClaudeProxyRouteRuntimeOptions(accountAllowlistOrRuntimeOptions)
|
|
4172
|
+
? accountAllowlistOrRuntimeOptions.accountAllowlist
|
|
4173
|
+
: accountAllowlistOrRuntimeOptions;
|
|
4174
|
+
const runtimeConfigProvider = isClaudeProxyRouteRuntimeOptions(accountAllowlistOrRuntimeOptions)
|
|
4175
|
+
? accountAllowlistOrRuntimeOptions.runtimeConfigProvider
|
|
4176
|
+
: undefined;
|
|
4168
4177
|
configuredPrimaryAccountKey = primaryAccountKey;
|
|
4169
4178
|
configuredAccountAllowlist = accountAllowlist
|
|
4170
4179
|
? new Set(accountAllowlist)
|
|
@@ -4179,6 +4188,18 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
|
|
|
4179
4188
|
method: "POST",
|
|
4180
4189
|
path: `${basePath}/v1/messages`,
|
|
4181
4190
|
handler: async (ctx) => {
|
|
4191
|
+
const requestRouting = runtimeConfigProvider?.() ?? {
|
|
4192
|
+
generation: 0,
|
|
4193
|
+
strategy: accountStrategy,
|
|
4194
|
+
modelRouter,
|
|
4195
|
+
passthrough: passthroughMode,
|
|
4196
|
+
primaryAccountKey,
|
|
4197
|
+
accountAllowlist,
|
|
4198
|
+
quotaRoutingEnabled: isQuotaRoutingEnabled(),
|
|
4199
|
+
sessionSoftLimit: getSessionSoftLimit(),
|
|
4200
|
+
sessionResetToleranceMs: getSessionResetToleranceMs(),
|
|
4201
|
+
};
|
|
4202
|
+
const requestModelRouter = requestRouting.modelRouter;
|
|
4182
4203
|
const body = ctx.body;
|
|
4183
4204
|
// 1. Validate
|
|
4184
4205
|
if (typeof body?.model !== "string" ||
|
|
@@ -4188,12 +4209,12 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
|
|
|
4188
4209
|
// 2. Resolve model via router (or pass through to anthropic)
|
|
4189
4210
|
// Guard: without a model router, only Claude models are allowed.
|
|
4190
4211
|
const modelLower = body.model.toLowerCase();
|
|
4191
|
-
if (!
|
|
4212
|
+
if (!requestModelRouter && !modelLower.startsWith("claude-")) {
|
|
4192
4213
|
return buildClaudeError(404, `Model '${body.model}' is not an Anthropic model. ` +
|
|
4193
4214
|
`The proxy only supports Claude models. ` +
|
|
4194
4215
|
`Use a model router to route non-Claude models to other providers.`);
|
|
4195
4216
|
}
|
|
4196
|
-
const route =
|
|
4217
|
+
const route = requestModelRouter?.resolve(body.model) ?? {
|
|
4197
4218
|
provider: "anthropic",
|
|
4198
4219
|
model: body.model,
|
|
4199
4220
|
};
|
|
@@ -4213,7 +4234,7 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
|
|
|
4213
4234
|
}
|
|
4214
4235
|
if (route.provider === "anthropic") {
|
|
4215
4236
|
tracer?.setMode("passthrough");
|
|
4216
|
-
if (
|
|
4237
|
+
if (requestRouting.passthrough) {
|
|
4217
4238
|
return handleClaudePassthroughRequest({
|
|
4218
4239
|
ctx,
|
|
4219
4240
|
body,
|
|
@@ -4226,10 +4247,15 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
|
|
|
4226
4247
|
return handleAnthropicRoutedClaudeRequest({
|
|
4227
4248
|
ctx,
|
|
4228
4249
|
body,
|
|
4229
|
-
modelRouter,
|
|
4250
|
+
modelRouter: requestModelRouter,
|
|
4230
4251
|
tracer,
|
|
4231
4252
|
requestStartTime,
|
|
4232
|
-
accountStrategy,
|
|
4253
|
+
accountStrategy: requestRouting.strategy,
|
|
4254
|
+
primaryAccountKey: requestRouting.primaryAccountKey,
|
|
4255
|
+
accountAllowlist: requestRouting.accountAllowlist,
|
|
4256
|
+
quotaRoutingEnabled: requestRouting.quotaRoutingEnabled,
|
|
4257
|
+
sessionSoftLimit: requestRouting.sessionSoftLimit,
|
|
4258
|
+
sessionResetToleranceMs: requestRouting.sessionResetToleranceMs,
|
|
4233
4259
|
buildLoggedClaudeError,
|
|
4234
4260
|
logProxyBody,
|
|
4235
4261
|
logFinalRequest,
|
|
@@ -4243,7 +4269,7 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
|
|
|
4243
4269
|
provider: route.provider,
|
|
4244
4270
|
model: route.model,
|
|
4245
4271
|
},
|
|
4246
|
-
modelRouter,
|
|
4272
|
+
modelRouter: requestModelRouter,
|
|
4247
4273
|
tracer,
|
|
4248
4274
|
requestStartTime,
|
|
4249
4275
|
logProxyBody,
|
|
@@ -4274,11 +4300,16 @@ export function createClaudeProxyRoutes(modelRouter, basePath = "", accountStrat
|
|
|
4274
4300
|
{
|
|
4275
4301
|
method: "GET",
|
|
4276
4302
|
path: `${basePath}/v1/models`,
|
|
4277
|
-
handler: async (_ctx) =>
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4303
|
+
handler: async (_ctx) => {
|
|
4304
|
+
const requestModelRouter = runtimeConfigProvider
|
|
4305
|
+
? runtimeConfigProvider().modelRouter
|
|
4306
|
+
: modelRouter;
|
|
4307
|
+
return withSpan({
|
|
4308
|
+
name: "neurolink.http.claudeProxy.listModels",
|
|
4309
|
+
tracer: tracers.http,
|
|
4310
|
+
attributes: { "http.route": `${basePath}/v1/models` },
|
|
4311
|
+
}, async () => buildAnthropicModelsListResponse(requestModelRouter));
|
|
4312
|
+
},
|
|
4282
4313
|
description: "List available models (Anthropic schema)",
|
|
4283
4314
|
tags: ["claude-proxy", "models"],
|
|
4284
4315
|
},
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
* An optional ModelRouter can remap incoming model names to different
|
|
12
12
|
* provider/model pairs (e.g. "gpt-4o" -> vertex/gemini-2.5-pro).
|
|
13
13
|
*/
|
|
14
|
-
import type {
|
|
15
|
-
import type { RouteGroup } from "../../types/index.js";
|
|
14
|
+
import type { ModelRouterInterface, ProxyRuntimeConfigProvider, RouteGroup } from "../../types/index.js";
|
|
16
15
|
/**
|
|
17
16
|
* Create OpenAI-compatible proxy routes.
|
|
18
17
|
*
|
|
@@ -27,4 +26,4 @@ import type { RouteGroup } from "../../types/index.js";
|
|
|
27
26
|
* headers — to avoid SSRF.
|
|
28
27
|
* @returns RouteGroup with OpenAI-compatible endpoints.
|
|
29
28
|
*/
|
|
30
|
-
export declare function createOpenAIProxyRoutes(modelRouter?:
|
|
29
|
+
export declare function createOpenAIProxyRoutes(modelRouter?: ModelRouterInterface, basePath?: string, loopbackPort?: number, runtimeConfigProvider?: ProxyRuntimeConfigProvider): RouteGroup;
|
|
@@ -227,7 +227,7 @@ async function handleOpenAIToAnthropicBridge(args) {
|
|
|
227
227
|
* headers — to avoid SSRF.
|
|
228
228
|
* @returns RouteGroup with OpenAI-compatible endpoints.
|
|
229
229
|
*/
|
|
230
|
-
export function createOpenAIProxyRoutes(modelRouter, basePath = "", loopbackPort = DEFAULT_LOOPBACK_PORT) {
|
|
230
|
+
export function createOpenAIProxyRoutes(modelRouter, basePath = "", loopbackPort = DEFAULT_LOOPBACK_PORT, runtimeConfigProvider) {
|
|
231
231
|
return {
|
|
232
232
|
prefix: `${basePath}/v1`,
|
|
233
233
|
routes: [
|
|
@@ -239,6 +239,9 @@ export function createOpenAIProxyRoutes(modelRouter, basePath = "", loopbackPort
|
|
|
239
239
|
path: `${basePath}/v1/chat/completions`,
|
|
240
240
|
description: "OpenAI-compatible chat completions (translation mode)",
|
|
241
241
|
handler: async (ctx) => {
|
|
242
|
+
const requestModelRouter = runtimeConfigProvider
|
|
243
|
+
? runtimeConfigProvider().modelRouter
|
|
244
|
+
: modelRouter;
|
|
242
245
|
const requestStartTime = Date.now();
|
|
243
246
|
const body = ctx.body;
|
|
244
247
|
// --- Validation ---
|
|
@@ -246,8 +249,8 @@ export function createOpenAIProxyRoutes(modelRouter, basePath = "", loopbackPort
|
|
|
246
249
|
return buildOpenAIErrorResponse(400, "Request must include 'model' and 'messages' fields");
|
|
247
250
|
}
|
|
248
251
|
// --- Resolve target provider/model ---
|
|
249
|
-
const route =
|
|
250
|
-
?
|
|
252
|
+
const route = requestModelRouter
|
|
253
|
+
? requestModelRouter.resolve(body.model)
|
|
251
254
|
: { provider: null, model: body.model };
|
|
252
255
|
const targetProvider = route.provider ?? undefined;
|
|
253
256
|
const targetModel = route.model ?? body.model;
|
|
@@ -302,7 +305,7 @@ export function createOpenAIProxyRoutes(modelRouter, basePath = "", loopbackPort
|
|
|
302
305
|
const plan = buildProxyTranslationPlan({
|
|
303
306
|
provider: targetProvider ?? "auto",
|
|
304
307
|
model: targetModel,
|
|
305
|
-
},
|
|
308
|
+
}, requestModelRouter?.getFallbackChain() ?? [], body.model,
|
|
306
309
|
// The classifier only reads fields present on both types.
|
|
307
310
|
adapted);
|
|
308
311
|
const attempts = plan.attempts;
|
|
@@ -369,7 +372,10 @@ export function createOpenAIProxyRoutes(modelRouter, basePath = "", loopbackPort
|
|
|
369
372
|
path: `${basePath}/v1/models`,
|
|
370
373
|
description: "List available models in OpenAI format",
|
|
371
374
|
handler: async () => {
|
|
372
|
-
|
|
375
|
+
const requestModelRouter = runtimeConfigProvider
|
|
376
|
+
? runtimeConfigProvider().modelRouter
|
|
377
|
+
: modelRouter;
|
|
378
|
+
return buildModelsListResponse(requestModelRouter);
|
|
373
379
|
},
|
|
374
380
|
},
|
|
375
381
|
],
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -837,6 +837,14 @@ export type ProxyState = {
|
|
|
837
837
|
managedBy?: "launchd" | "manual";
|
|
838
838
|
/** Whether the proxy is running in transparent passthrough mode */
|
|
839
839
|
passthrough?: boolean;
|
|
840
|
+
/** Active hot-reload configuration generation. */
|
|
841
|
+
configGeneration?: number;
|
|
842
|
+
/** Timestamp when the active configuration generation was loaded. */
|
|
843
|
+
configLoadedAt?: string;
|
|
844
|
+
/** Last rejected hot-reload error, when any. */
|
|
845
|
+
lastConfigReloadError?: string;
|
|
846
|
+
/** Absolute path watched for proxy routing configuration changes. */
|
|
847
|
+
configFile?: string;
|
|
840
848
|
};
|
|
841
849
|
/** Stored credentials for an authenticated provider. */
|
|
842
850
|
export type StoredCredentials = {
|
package/dist/types/proxy.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { Hono } from "hono";
|
|
|
18
18
|
import type { Ora } from "ora";
|
|
19
19
|
import type { MCPToolRegistry } from "../mcp/toolRegistry.js";
|
|
20
20
|
import type { ProxyTracer } from "../proxy/proxyTracer.js";
|
|
21
|
-
import type { FallbackEntry, ProxyRoutingConfig, CloakingConfig } from "./subscription.js";
|
|
21
|
+
import type { FallbackEntry, ModelMapping, ProxyRoutingConfig, CloakingConfig } from "./subscription.js";
|
|
22
22
|
import type { RouteDeprecation } from "./server.js";
|
|
23
23
|
/**
|
|
24
24
|
* Type describing the ModelRouter contract.
|
|
@@ -28,6 +28,8 @@ export type ModelRouterInterface = {
|
|
|
28
28
|
resolve(requestedModel: string): RouteResult;
|
|
29
29
|
isClaudeTarget(requestedModel: string): boolean;
|
|
30
30
|
getFallbackChain(): FallbackEntry[];
|
|
31
|
+
getModelMappings?: () => ModelMapping[];
|
|
32
|
+
getPassthroughModels?: () => string[];
|
|
31
33
|
};
|
|
32
34
|
/** A single text block in a Claude content array. */
|
|
33
35
|
export type ClaudeTextBlock = {
|
|
@@ -1245,6 +1247,70 @@ export type LoadedProxyConfig = {
|
|
|
1245
1247
|
strategy?: ProxyStartStrategy;
|
|
1246
1248
|
};
|
|
1247
1249
|
};
|
|
1250
|
+
/** Routing values captured once when a proxy request begins. */
|
|
1251
|
+
export type ProxyRequestRoutingSnapshot = {
|
|
1252
|
+
generation: number;
|
|
1253
|
+
strategy: ProxyStartStrategy;
|
|
1254
|
+
modelRouter?: ModelRouterInterface;
|
|
1255
|
+
passthrough: boolean;
|
|
1256
|
+
primaryAccountKey?: string;
|
|
1257
|
+
accountAllowlist?: ReadonlySet<string>;
|
|
1258
|
+
quotaRoutingEnabled: boolean;
|
|
1259
|
+
sessionSoftLimit: number;
|
|
1260
|
+
sessionResetToleranceMs: number;
|
|
1261
|
+
};
|
|
1262
|
+
/** Immutable last-known-good proxy configuration published at runtime. */
|
|
1263
|
+
export type ProxyRuntimeConfigSnapshot = ProxyRequestRoutingSnapshot & {
|
|
1264
|
+
loadedAt: string;
|
|
1265
|
+
configHash: string;
|
|
1266
|
+
proxyConfig: LoadedProxyConfig | null;
|
|
1267
|
+
};
|
|
1268
|
+
/** Source that requested a runtime configuration reload. */
|
|
1269
|
+
export type ProxyRuntimeConfigReloadSource = "startup" | "watch" | "sighup" | "manual";
|
|
1270
|
+
/** Result returned after a serialized runtime configuration reload attempt. */
|
|
1271
|
+
export type ProxyRuntimeConfigReloadResult = {
|
|
1272
|
+
applied: boolean;
|
|
1273
|
+
changed: boolean;
|
|
1274
|
+
generation: number;
|
|
1275
|
+
error?: string;
|
|
1276
|
+
};
|
|
1277
|
+
/** Safe runtime configuration diagnostics exposed through proxy status. */
|
|
1278
|
+
export type ProxyRuntimeConfigStatus = {
|
|
1279
|
+
configPath: string;
|
|
1280
|
+
envFilePath?: string;
|
|
1281
|
+
generation: number;
|
|
1282
|
+
loadedAt: string;
|
|
1283
|
+
configHash: string;
|
|
1284
|
+
watching: boolean;
|
|
1285
|
+
lastReloadAttemptAt?: string;
|
|
1286
|
+
lastReloadAt?: string;
|
|
1287
|
+
lastReloadSource?: ProxyRuntimeConfigReloadSource;
|
|
1288
|
+
lastReloadError?: string;
|
|
1289
|
+
consecutiveFailures: number;
|
|
1290
|
+
};
|
|
1291
|
+
/** Constructor options for the proxy runtime configuration store. */
|
|
1292
|
+
export type ProxyRuntimeConfigStoreOptions = {
|
|
1293
|
+
configPath: string;
|
|
1294
|
+
configRequired: boolean;
|
|
1295
|
+
envFilePath?: string;
|
|
1296
|
+
envFileRequired?: boolean;
|
|
1297
|
+
baseEnv: Record<string, string | undefined>;
|
|
1298
|
+
strategyOverride?: ProxyStartStrategy;
|
|
1299
|
+
passthrough: boolean;
|
|
1300
|
+
watchIntervalMs?: number;
|
|
1301
|
+
watchDebounceMs?: number;
|
|
1302
|
+
};
|
|
1303
|
+
/** Runtime configuration provider captured by route factories. */
|
|
1304
|
+
export type ProxyRuntimeConfigProvider = () => ProxyRequestRoutingSnapshot;
|
|
1305
|
+
/** Optional runtime configuration wiring for Claude proxy route factories. */
|
|
1306
|
+
export type ClaudeProxyRouteRuntimeOptions = {
|
|
1307
|
+
accountAllowlist?: AccountAllowlist;
|
|
1308
|
+
runtimeConfigProvider: ProxyRuntimeConfigProvider;
|
|
1309
|
+
};
|
|
1310
|
+
/** Listener invoked after a new configuration generation is published. */
|
|
1311
|
+
export type ProxyRuntimeConfigListener = (snapshot: ProxyRuntimeConfigSnapshot) => void;
|
|
1312
|
+
/** Listener invoked after every successful or rejected reload attempt. */
|
|
1313
|
+
export type ProxyRuntimeConfigReloadListener = (result: ProxyRuntimeConfigReloadResult, status: ProxyRuntimeConfigStatus) => void;
|
|
1248
1314
|
/**
|
|
1249
1315
|
* Handle for a NeuroLink runtime created by the proxy start command.
|
|
1250
1316
|
* The `neurolink` field is typed structurally (only the method used by the
|
|
@@ -909,6 +909,12 @@ export type ProxyRoutingConfig = {
|
|
|
909
909
|
modelMappings: ModelMapping[];
|
|
910
910
|
fallbackChain: FallbackEntry[];
|
|
911
911
|
passthroughModels?: string[];
|
|
912
|
+
/** Enable quota-aware fill-first account ordering. Defaults to true. */
|
|
913
|
+
quotaRouting?: boolean;
|
|
914
|
+
/** Session utilization threshold used to proactively demote an account. */
|
|
915
|
+
sessionSoftLimit?: number;
|
|
916
|
+
/** Reset-time bucket width used when ordering quota windows. */
|
|
917
|
+
sessionResetToleranceMs?: number;
|
|
912
918
|
/** Email/label of the Anthropic account that should be tried first
|
|
913
919
|
* ("home"). When absent, falls back to insertion-order index 0.
|
|
914
920
|
* Resolved per-request to a stable key (anthropic:<email>); does not
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.92.1",
|
|
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": {
|