@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.
@@ -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
- // Initialize AWS SDK client with configuration
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.client;
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.client;
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
+ }