@juspay/neurolink 9.91.0 → 9.92.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 +12 -0
- package/dist/browser/neurolink.min.js +352 -351
- 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/lib/providers/sagemaker/client.d.ts +9 -0
- package/dist/lib/providers/sagemaker/client.js +75 -30
- 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/providers/sagemaker/client.d.ts +9 -0
- package/dist/providers/sagemaker/client.js +75 -30
- 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 +2 -2
|
@@ -4,38 +4,41 @@
|
|
|
4
4
|
* This module provides a wrapper around the AWS SDK SageMaker Runtime client
|
|
5
5
|
* with enhanced error handling, retry logic, and NeuroLink-specific features.
|
|
6
6
|
*/
|
|
7
|
-
import { SageMakerRuntimeClient as AWSClient, InvokeEndpointCommand, InvokeEndpointWithResponseStreamCommand, } from "@aws-sdk/client-sagemaker-runtime";
|
|
8
7
|
import { handleSageMakerError, SageMakerError, isRetryableError, getRetryDelay, } from "./errors.js";
|
|
9
8
|
import { logger } from "../../utils/logger.js";
|
|
9
|
+
/**
|
|
10
|
+
* Lazily load `@aws-sdk/client-sagemaker-runtime`.
|
|
11
|
+
*
|
|
12
|
+
* The package is an optional dependency: importing it only happens here, on
|
|
13
|
+
* first actual endpoint invocation, so constructing a SageMakerRuntimeClient
|
|
14
|
+
* (e.g. while the CLI builds its command tree at startup) never requires the
|
|
15
|
+
* SDK to be installed. If it's missing, surface a comprehensible, actionable
|
|
16
|
+
* error instead of a raw resolution failure.
|
|
17
|
+
*/
|
|
18
|
+
async function loadSageMakerRuntime() {
|
|
19
|
+
try {
|
|
20
|
+
return await import(/* @vite-ignore */ "@aws-sdk/client-sagemaker-runtime");
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
const e = err instanceof Error ? err : null;
|
|
24
|
+
if (e?.code === "ERR_MODULE_NOT_FOUND" &&
|
|
25
|
+
e.message.includes("client-sagemaker-runtime")) {
|
|
26
|
+
throw new Error('SageMaker inference requires "@aws-sdk/client-sagemaker-runtime". Install it with:\n pnpm add @aws-sdk/client-sagemaker-runtime', { cause: err });
|
|
27
|
+
}
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
10
31
|
/**
|
|
11
32
|
* Enhanced SageMaker Runtime client with retry logic and error handling
|
|
12
33
|
*/
|
|
13
34
|
export class SageMakerRuntimeClient {
|
|
14
|
-
client;
|
|
35
|
+
client = null;
|
|
36
|
+
sdkModule = null;
|
|
15
37
|
config;
|
|
16
38
|
isDisposed = false;
|
|
17
39
|
constructor(config) {
|
|
18
40
|
this.config = config;
|
|
19
|
-
|
|
20
|
-
this.client = new AWSClient({
|
|
21
|
-
region: config.region,
|
|
22
|
-
credentials: {
|
|
23
|
-
accessKeyId: config.accessKeyId,
|
|
24
|
-
secretAccessKey: config.secretAccessKey,
|
|
25
|
-
sessionToken: config.sessionToken,
|
|
26
|
-
},
|
|
27
|
-
maxAttempts: config.maxRetries || 3,
|
|
28
|
-
requestHandler: {
|
|
29
|
-
requestTimeout: config.timeout || 30000,
|
|
30
|
-
httpsAgent: {
|
|
31
|
-
// Keep connections alive for better performance
|
|
32
|
-
keepAlive: true,
|
|
33
|
-
maxSockets: 50,
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
...(config.endpoint && { endpoint: config.endpoint }),
|
|
37
|
-
});
|
|
38
|
-
logger.debug("SageMaker Runtime client initialized", {
|
|
41
|
+
logger.debug("SageMaker Runtime client configured", {
|
|
39
42
|
region: config.region,
|
|
40
43
|
timeout: config.timeout,
|
|
41
44
|
maxRetries: config.maxRetries,
|
|
@@ -43,6 +46,51 @@ export class SageMakerRuntimeClient {
|
|
|
43
46
|
customEndpoint: !!config.endpoint,
|
|
44
47
|
});
|
|
45
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Lazily load (and cache) the `@aws-sdk/client-sagemaker-runtime` module.
|
|
51
|
+
*/
|
|
52
|
+
async getSdk() {
|
|
53
|
+
if (!this.sdkModule) {
|
|
54
|
+
this.sdkModule = await loadSageMakerRuntime();
|
|
55
|
+
}
|
|
56
|
+
return this.sdkModule;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Lazily construct (and cache) the underlying AWS SDK client.
|
|
60
|
+
*/
|
|
61
|
+
async getClient() {
|
|
62
|
+
if (this.isDisposed) {
|
|
63
|
+
throw new SageMakerError("Cannot perform operation on disposed SageMaker client", {
|
|
64
|
+
code: "VALIDATION_ERROR",
|
|
65
|
+
statusCode: 400,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (!this.client) {
|
|
69
|
+
const { SageMakerRuntimeClient: AWSClientCtor } = await this.getSdk();
|
|
70
|
+
this.client = new AWSClientCtor({
|
|
71
|
+
region: this.config.region,
|
|
72
|
+
credentials: {
|
|
73
|
+
accessKeyId: this.config.accessKeyId,
|
|
74
|
+
secretAccessKey: this.config.secretAccessKey,
|
|
75
|
+
sessionToken: this.config.sessionToken,
|
|
76
|
+
},
|
|
77
|
+
maxAttempts: this.config.maxRetries || 3,
|
|
78
|
+
requestHandler: {
|
|
79
|
+
requestTimeout: this.config.timeout || 30000,
|
|
80
|
+
httpsAgent: {
|
|
81
|
+
// Keep connections alive for better performance
|
|
82
|
+
keepAlive: true,
|
|
83
|
+
maxSockets: 50,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
...(this.config.endpoint && { endpoint: this.config.endpoint }),
|
|
87
|
+
});
|
|
88
|
+
logger.debug("SageMaker Runtime AWS SDK client initialized", {
|
|
89
|
+
region: this.config.region,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return this.client;
|
|
93
|
+
}
|
|
46
94
|
/**
|
|
47
95
|
* Invoke a SageMaker endpoint for synchronous inference
|
|
48
96
|
*
|
|
@@ -61,6 +109,7 @@ export class SageMakerRuntimeClient {
|
|
|
61
109
|
? params.Body.length
|
|
62
110
|
: params.Body?.length || 0,
|
|
63
111
|
});
|
|
112
|
+
const { InvokeEndpointCommand } = await this.getSdk();
|
|
64
113
|
// Prepare the command input
|
|
65
114
|
const input = {
|
|
66
115
|
EndpointName: params.EndpointName,
|
|
@@ -73,10 +122,7 @@ export class SageMakerRuntimeClient {
|
|
|
73
122
|
InferenceId: params.InferenceId,
|
|
74
123
|
};
|
|
75
124
|
const command = new InvokeEndpointCommand(input);
|
|
76
|
-
const client = this.
|
|
77
|
-
if (!client) {
|
|
78
|
-
throw new Error("SageMaker client has been disposed");
|
|
79
|
-
}
|
|
125
|
+
const client = await this.getClient();
|
|
80
126
|
const response = (await this.executeWithRetry(() => client.send(command), params.EndpointName));
|
|
81
127
|
const duration = Date.now() - startTime;
|
|
82
128
|
logger.debug("SageMaker endpoint invocation successful", {
|
|
@@ -120,6 +166,7 @@ export class SageMakerRuntimeClient {
|
|
|
120
166
|
? params.Body.length
|
|
121
167
|
: params.Body?.length || 0,
|
|
122
168
|
});
|
|
169
|
+
const { InvokeEndpointWithResponseStreamCommand } = await this.getSdk();
|
|
123
170
|
// Prepare the command input for streaming
|
|
124
171
|
const input = {
|
|
125
172
|
EndpointName: params.EndpointName,
|
|
@@ -130,10 +177,7 @@ export class SageMakerRuntimeClient {
|
|
|
130
177
|
// Note: TargetModel, TargetVariant, InferenceId not available in streaming interface
|
|
131
178
|
};
|
|
132
179
|
const command = new InvokeEndpointWithResponseStreamCommand(input);
|
|
133
|
-
const client = this.
|
|
134
|
-
if (!client) {
|
|
135
|
-
throw new Error("SageMaker client has been disposed");
|
|
136
|
-
}
|
|
180
|
+
const client = await this.getClient();
|
|
137
181
|
const response = (await this.executeWithRetry(() => client.send(command), params.EndpointName));
|
|
138
182
|
logger.debug("SageMaker streaming invocation started", {
|
|
139
183
|
endpointName: params.EndpointName,
|
|
@@ -415,6 +459,7 @@ export class SageMakerRuntimeClient {
|
|
|
415
459
|
// Clear our client reference to enable garbage collection
|
|
416
460
|
// Note: AWS SDK v3 handles all internal resource cleanup automatically
|
|
417
461
|
this.client = null;
|
|
462
|
+
this.sdkModule = null;
|
|
418
463
|
logger.debug("SageMaker Runtime client disposed", {
|
|
419
464
|
note: "AWS SDK v3 handles internal resource cleanup automatically",
|
|
420
465
|
});
|
|
@@ -227,6 +227,33 @@ export function validateProxyConfig(config) {
|
|
|
227
227
|
});
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
|
+
const rawQuotaRouting = routing["quota-routing"] ?? routing.quotaRouting;
|
|
231
|
+
const normalizedQuotaRouting = typeof rawQuotaRouting === "string"
|
|
232
|
+
? rawQuotaRouting.trim().toLowerCase()
|
|
233
|
+
: undefined;
|
|
234
|
+
if (rawQuotaRouting !== undefined &&
|
|
235
|
+
typeof rawQuotaRouting !== "boolean" &&
|
|
236
|
+
normalizedQuotaRouting !== "true" &&
|
|
237
|
+
normalizedQuotaRouting !== "false") {
|
|
238
|
+
errors.push("routing.quota-routing must be a boolean");
|
|
239
|
+
}
|
|
240
|
+
const rawSessionSoftLimit = routing["session-soft-limit"] ?? routing.sessionSoftLimit;
|
|
241
|
+
if (rawSessionSoftLimit !== undefined) {
|
|
242
|
+
const sessionSoftLimit = Number(rawSessionSoftLimit);
|
|
243
|
+
if (!Number.isFinite(sessionSoftLimit) ||
|
|
244
|
+
sessionSoftLimit <= 0 ||
|
|
245
|
+
sessionSoftLimit > 1) {
|
|
246
|
+
errors.push("routing.session-soft-limit must be a number in (0, 1]");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const rawSessionResetToleranceMs = routing["session-reset-tolerance-ms"] ?? routing.sessionResetToleranceMs;
|
|
250
|
+
if (rawSessionResetToleranceMs !== undefined) {
|
|
251
|
+
const sessionResetToleranceMs = Number(rawSessionResetToleranceMs);
|
|
252
|
+
if (!Number.isInteger(sessionResetToleranceMs) ||
|
|
253
|
+
sessionResetToleranceMs <= 0) {
|
|
254
|
+
errors.push("routing.session-reset-tolerance-ms must be a positive integer");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
230
257
|
}
|
|
231
258
|
if (!hasAccounts && !hasRouting) {
|
|
232
259
|
errors.push('Config must contain at least one of "accounts" or "routing"');
|
|
@@ -296,6 +323,9 @@ function warnPlaintextApiKeys(accounts) {
|
|
|
296
323
|
* - `model-mappings` / `modelMappings` — array of {from, to, provider}
|
|
297
324
|
* - `fallback-chain` / `fallbackChain` — array of {provider, model}
|
|
298
325
|
* - `passthroughModels` / `passthrough-models` — array of model IDs
|
|
326
|
+
* - `quota-routing` / `quotaRouting` — quota-aware fill-first ordering
|
|
327
|
+
* - `session-soft-limit` / `sessionSoftLimit` — proactive handoff threshold
|
|
328
|
+
* - `session-reset-tolerance-ms` / `sessionResetToleranceMs` — reset bucket
|
|
299
329
|
* - `account-allowlist` / `accountAllowlist` — allowed Anthropic account IDs
|
|
300
330
|
*
|
|
301
331
|
* Accepts both camelCase and kebab-case keys for YAML-friendliness.
|
|
@@ -353,6 +383,42 @@ function parseRoutingConfig(raw) {
|
|
|
353
383
|
if (Array.isArray(rawPassthrough)) {
|
|
354
384
|
result.passthroughModels = rawPassthrough.map(String);
|
|
355
385
|
}
|
|
386
|
+
const rawQuotaRouting = raw["quota-routing"] ?? raw.quotaRouting;
|
|
387
|
+
if (rawQuotaRouting !== undefined) {
|
|
388
|
+
if (typeof rawQuotaRouting === "boolean") {
|
|
389
|
+
result.quotaRouting = rawQuotaRouting;
|
|
390
|
+
}
|
|
391
|
+
else if (typeof rawQuotaRouting === "string" &&
|
|
392
|
+
["true", "false"].includes(rawQuotaRouting.trim().toLowerCase())) {
|
|
393
|
+
result.quotaRouting = rawQuotaRouting.trim().toLowerCase() === "true";
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
logger.warn(`[proxy-config] Ignoring routing.quotaRouting: expected boolean, got ${typeof rawQuotaRouting}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const rawSessionSoftLimit = raw["session-soft-limit"] ?? raw.sessionSoftLimit;
|
|
400
|
+
if (rawSessionSoftLimit !== undefined) {
|
|
401
|
+
const sessionSoftLimit = Number(rawSessionSoftLimit);
|
|
402
|
+
if (Number.isFinite(sessionSoftLimit) &&
|
|
403
|
+
sessionSoftLimit > 0 &&
|
|
404
|
+
sessionSoftLimit <= 1) {
|
|
405
|
+
result.sessionSoftLimit = sessionSoftLimit;
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
logger.warn(`[proxy-config] Ignoring routing.sessionSoftLimit: expected number in (0, 1], got ${String(rawSessionSoftLimit)}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const rawSessionResetToleranceMs = raw["session-reset-tolerance-ms"] ?? raw.sessionResetToleranceMs;
|
|
412
|
+
if (rawSessionResetToleranceMs !== undefined) {
|
|
413
|
+
const sessionResetToleranceMs = Number(rawSessionResetToleranceMs);
|
|
414
|
+
if (Number.isInteger(sessionResetToleranceMs) &&
|
|
415
|
+
sessionResetToleranceMs > 0) {
|
|
416
|
+
result.sessionResetToleranceMs = sessionResetToleranceMs;
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
logger.warn(`[proxy-config] Ignoring routing.sessionResetToleranceMs: expected positive integer, got ${String(rawSessionResetToleranceMs)}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
356
422
|
// Primary account (accept kebab-case or camelCase). Email or label of the
|
|
357
423
|
// Anthropic account that should be tried first ("home"). Resolved to a
|
|
358
424
|
// stable key (anthropic:<email>) at proxy boot; absence preserves the
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ProxyRuntimeConfigListener, ProxyRuntimeConfigReloadListener, ProxyRuntimeConfigReloadResult, ProxyRuntimeConfigReloadSource, ProxyRuntimeConfigSnapshot, ProxyRuntimeConfigStatus, ProxyRuntimeConfigStoreOptions } from "../types/index.js";
|
|
2
|
+
/** Atomic last-known-good runtime configuration with file-triggered reloads. */
|
|
3
|
+
export declare class ProxyRuntimeConfigStore {
|
|
4
|
+
private readonly options;
|
|
5
|
+
private currentSnapshot;
|
|
6
|
+
private status;
|
|
7
|
+
private readonly listeners;
|
|
8
|
+
private readonly reloadListeners;
|
|
9
|
+
private reloadQueue;
|
|
10
|
+
private reloadTimer;
|
|
11
|
+
private configFileObserved;
|
|
12
|
+
private envFileObserved;
|
|
13
|
+
private readonly watchListener;
|
|
14
|
+
private constructor();
|
|
15
|
+
static create(options: ProxyRuntimeConfigStoreOptions): Promise<ProxyRuntimeConfigStore>;
|
|
16
|
+
getSnapshot(): ProxyRuntimeConfigSnapshot;
|
|
17
|
+
getStatus(): ProxyRuntimeConfigStatus;
|
|
18
|
+
subscribe(listener: ProxyRuntimeConfigListener): () => void;
|
|
19
|
+
subscribeReload(listener: ProxyRuntimeConfigReloadListener): () => void;
|
|
20
|
+
reload(source?: ProxyRuntimeConfigReloadSource): Promise<ProxyRuntimeConfigReloadResult>;
|
|
21
|
+
startWatching(): void;
|
|
22
|
+
stopWatching(): void;
|
|
23
|
+
private scheduleWatchReload;
|
|
24
|
+
private performReload;
|
|
25
|
+
private notifyReloadListeners;
|
|
26
|
+
}
|