@juspay/neurolink 12.49.4 → 12.49.5

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.
@@ -56,6 +56,17 @@ export declare function getRuntimeOutputCeiling(provider: string, model?: string
56
56
  export declare function clearRuntimeOutputCeiling(provider: string, model: string): void;
57
57
  /** Test hook: clear runtime-discovered output ceilings (state is module-global). */
58
58
  export declare function clearRuntimeOutputCeilings(): void;
59
+ /**
60
+ * `us.anthropic.claude-sonnet-4-6` -> `anthropic.claude-sonnet-4-6`.
61
+ *
62
+ * Undefined for any other shape, so an id that merely starts with `us.` is
63
+ * never rewritten. Removes one geography at most: no vendor is a geography.
64
+ *
65
+ * Every Bedrock table keyed by bare ids (context windows here, output ceilings
66
+ * in `getSafeMaxTokens`) goes through this one function, so they cannot
67
+ * disagree about which ids are cross-region profile ids.
68
+ */
69
+ export declare function stripBedrockGeoPrefix(model: string): string | undefined;
59
70
  /**
60
71
  * Resolve context window size for a provider/model combination.
61
72
  *
@@ -507,8 +507,12 @@ const BEDROCK_VENDORS = new Set(Object.values(BedrockModels)
507
507
  *
508
508
  * Undefined for any other shape, so an id that merely starts with `us.` is
509
509
  * never rewritten. Removes one geography at most: no vendor is a geography.
510
+ *
511
+ * Every Bedrock table keyed by bare ids (context windows here, output ceilings
512
+ * in `getSafeMaxTokens`) goes through this one function, so they cannot
513
+ * disagree about which ids are cross-region profile ids.
510
514
  */
511
- function stripBedrockGeoPrefix(model) {
515
+ export function stripBedrockGeoPrefix(model) {
512
516
  const [geo, vendor, ...rest] = model.split(".");
513
517
  if (rest.length === 0 ||
514
518
  !BEDROCK_GEO_PREFIXES.has(geo) ||
@@ -92,5 +92,10 @@ export declare class LiteLLMProvider extends OpenAIChatCompletionsProvider {
92
92
  * Generate embeddings for multiple text inputs via native /v1/embeddings.
93
93
  */
94
94
  embedMany(texts: string[], modelName?: string): Promise<number[][]>;
95
+ /**
96
+ * One non-empty vector per input, or fail: a dropped or empty row would
97
+ * pair later texts with the wrong vectors, or score 0 downstream.
98
+ */
99
+ private requireEmbeddings;
95
100
  private callEmbeddings;
96
101
  }
@@ -474,7 +474,8 @@ export class LiteLLMProvider extends OpenAIChatCompletionsProvider {
474
474
  const embeddingModelName = modelName ||
475
475
  process.env.LITELLM_EMBEDDING_MODEL ||
476
476
  "gemini-embedding-001";
477
- const [embedding] = await this.callEmbeddings(embeddingModelName, [text], "embed");
477
+ const rows = await this.callEmbeddings(embeddingModelName, [text], "embed");
478
+ const [embedding] = this.requireEmbeddings(rows, 1, embeddingModelName);
478
479
  return embedding;
479
480
  }
480
481
  /**
@@ -484,7 +485,34 @@ export class LiteLLMProvider extends OpenAIChatCompletionsProvider {
484
485
  const embeddingModelName = modelName ||
485
486
  process.env.LITELLM_EMBEDDING_MODEL ||
486
487
  "gemini-embedding-001";
487
- return this.callEmbeddings(embeddingModelName, texts, "embedMany");
488
+ const rows = await this.callEmbeddings(embeddingModelName, texts, "embedMany");
489
+ return this.requireEmbeddings(rows, texts.length, embeddingModelName);
490
+ }
491
+ /**
492
+ * One non-empty vector per input, or fail: a dropped or empty row would
493
+ * pair later texts with the wrong vectors, or score 0 downstream.
494
+ */
495
+ requireEmbeddings(rows, inputCount, modelName) {
496
+ if (rows.length !== inputCount) {
497
+ logger.error("Embedding response held the wrong number of vectors", {
498
+ provider: this.providerName,
499
+ model: modelName,
500
+ expected: inputCount,
501
+ received: rows.length,
502
+ });
503
+ throw new ProviderError(`Embedding response held ${rows.length} vectors for ${inputCount} texts`, this.providerName);
504
+ }
505
+ return rows.map((values, index) => {
506
+ if (!values || values.length === 0) {
507
+ logger.error("Embedding returned no values", {
508
+ provider: this.providerName,
509
+ model: modelName,
510
+ index,
511
+ });
512
+ throw new ProviderError(`Embedding for the text at index ${index} came back without values`, this.providerName);
513
+ }
514
+ return values;
515
+ });
488
516
  }
489
517
  async callEmbeddings(modelName, input, operation) {
490
518
  const url = `${stripTrailingSlash(this.config.baseURL)}/embeddings`;
@@ -514,13 +542,11 @@ export class LiteLLMProvider extends OpenAIChatCompletionsProvider {
514
542
  `LiteLLM ${operation} failed with status ${res.status}`));
515
543
  }
516
544
  const json = (await res.json());
517
- const embeddings = (json.data ?? [])
518
- .map((row) => row.embedding)
519
- .filter((e) => Array.isArray(e));
520
- if (embeddings.length === 0) {
545
+ const rows = (json.data ?? []).map((row) => Array.isArray(row.embedding) ? row.embedding : undefined);
546
+ if (rows.length === 0) {
521
547
  throw new ProviderError(`LiteLLM ${operation} returned no embeddings`, this.providerName);
522
548
  }
523
- return embeddings;
549
+ return rows;
524
550
  }
525
551
  finally {
526
552
  timeoutController?.cleanup();
@@ -67,5 +67,10 @@ export declare class OllamaProvider extends OpenAIChatCompletionsProvider {
67
67
  * Generate embeddings for multiple text inputs via native /v1/embeddings.
68
68
  */
69
69
  embedMany(texts: string[], modelName?: string): Promise<number[][]>;
70
+ /**
71
+ * One non-empty vector per input, or fail: a dropped or empty row would
72
+ * pair later texts with the wrong vectors, or score 0 downstream.
73
+ */
74
+ private requireEmbeddings;
70
75
  private callEmbeddings;
71
76
  }
@@ -216,7 +216,8 @@ export class OllamaProvider extends OpenAIChatCompletionsProvider {
216
216
  const embeddingModel = modelName ||
217
217
  process.env.OLLAMA_EMBEDDING_MODEL ||
218
218
  DEFAULT_EMBEDDING_MODEL;
219
- const [embedding] = await this.callEmbeddings(embeddingModel, [text], "embed");
219
+ const rows = await this.callEmbeddings(embeddingModel, [text], "embed");
220
+ const [embedding] = this.requireEmbeddings(rows, 1, embeddingModel);
220
221
  return embedding;
221
222
  }
222
223
  /**
@@ -226,7 +227,34 @@ export class OllamaProvider extends OpenAIChatCompletionsProvider {
226
227
  const embeddingModel = modelName ||
227
228
  process.env.OLLAMA_EMBEDDING_MODEL ||
228
229
  DEFAULT_EMBEDDING_MODEL;
229
- return this.callEmbeddings(embeddingModel, texts, "embedMany");
230
+ const rows = await this.callEmbeddings(embeddingModel, texts, "embedMany");
231
+ return this.requireEmbeddings(rows, texts.length, embeddingModel);
232
+ }
233
+ /**
234
+ * One non-empty vector per input, or fail: a dropped or empty row would
235
+ * pair later texts with the wrong vectors, or score 0 downstream.
236
+ */
237
+ requireEmbeddings(rows, inputCount, modelName) {
238
+ if (rows.length !== inputCount) {
239
+ logger.error("Embedding response held the wrong number of vectors", {
240
+ provider: this.providerName,
241
+ model: modelName,
242
+ expected: inputCount,
243
+ received: rows.length,
244
+ });
245
+ throw new ProviderError(`Embedding response held ${rows.length} vectors for ${inputCount} texts`, this.providerName);
246
+ }
247
+ return rows.map((values, index) => {
248
+ if (!values || values.length === 0) {
249
+ logger.error("Embedding returned no values", {
250
+ provider: this.providerName,
251
+ model: modelName,
252
+ index,
253
+ });
254
+ throw new ProviderError(`Embedding for the text at index ${index} came back without values`, this.providerName);
255
+ }
256
+ return values;
257
+ });
230
258
  }
231
259
  async callEmbeddings(modelName, input, operation) {
232
260
  const url = `${stripTrailingSlash(this.config.baseURL)}/embeddings`;
@@ -265,13 +293,11 @@ export class OllamaProvider extends OpenAIChatCompletionsProvider {
265
293
  `Ollama ${operation} failed with status ${res.status}`));
266
294
  }
267
295
  const json = (await res.json());
268
- const embeddings = (json.data ?? [])
269
- .map((row) => row.embedding)
270
- .filter((e) => Array.isArray(e));
271
- if (embeddings.length === 0) {
296
+ const rows = (json.data ?? []).map((row) => Array.isArray(row.embedding) ? row.embedding : undefined);
297
+ if (rows.length === 0) {
272
298
  throw new ProviderError(`Ollama ${operation} returned no embeddings`, "ollama");
273
299
  }
274
- return embeddings;
300
+ return rows;
275
301
  }
276
302
  finally {
277
303
  timeoutController?.cleanup();
@@ -17,6 +17,8 @@
17
17
  * - "192.168.1.10", "::1", "[::1]:8080" - an IP literal, exactly
18
18
  * - "192.168.1.0/24" - an IPv4 CIDR range, for IPv4 literal targets (IPv6 CIDR
19
19
  * ranges are not supported)
20
+ * - "." and ".." - an entry made only of dots names no host, so it matches
21
+ * nothing, as in curl
20
22
  *
21
23
  * @param targetUrl - The URL to check for proxy bypass
22
24
  * @param noProxyEnv - Optional NO_PROXY environment variable value (if not provided, reads from process.env)
@@ -37,6 +39,8 @@ export declare function getNoProxyEnv(): string | undefined;
37
39
  * - "example.com" - exact match
38
40
  * - ".example.com" - domain suffix match
39
41
  *
42
+ * An entry made only of dots names no host and matches nothing.
43
+ *
40
44
  * @param targetUrl - The URL to check
41
45
  * @param noProxyValue - The NO_PROXY value to check against
42
46
  * @returns true if should bypass proxy
@@ -44,6 +44,10 @@ function canonicalIpv6Host(address) {
44
44
  function withoutTrailingDot(name) {
45
45
  return name.endsWith(".") ? name.slice(0, -1) : name;
46
46
  }
47
+ /** An entry made only of dots (`.`, `..`) names no host. */
48
+ function isDotsOnly(entry) {
49
+ return /^\.+$/.test(entry);
50
+ }
47
51
  /**
48
52
  * curl's rules for one list entry: an optional leading `.` or `*.` is ignored;
49
53
  * the entry names that host and every subdomain of it, an IP literal only
@@ -106,13 +110,20 @@ function matchesNoProxyEntry(entry, hostname, port) {
106
110
  return name === domain || name.endsWith(`.${domain}`);
107
111
  }
108
112
  /**
109
- * The rules this function applied before it followed curl, unchanged and still
110
- * consulted, so that a configuration that bypassed the proxy through them keeps
111
- * doing so. That includes where they are looser than curl (`.example.com` also
112
- * matches `notexample.com`) and an IPv4 CIDR range such as `192.168.1.0/24`,
113
- * which curl-style entries do not express.
113
+ * The rules this function applied before it followed curl, still consulted so
114
+ * that a configuration that bypassed the proxy through them keeps doing so.
115
+ * That includes where they are looser than curl (`.example.com` also matches
116
+ * `notexample.com`) and an IPv4 CIDR range such as `192.168.1.0/24`, which
117
+ * curl-style entries do not express. They are unchanged except that an entry
118
+ * made only of dots names no host, as in the curl-style rules.
114
119
  */
115
120
  function matchesLegacyNoProxyEntry(lowerPattern, hostname, port) {
121
+ // Taken as a suffix, "." leaves the empty string, which every host name ends
122
+ // with, and ".." leaves ".", which every host written with a trailing dot
123
+ // ends with.
124
+ if (isDotsOnly(lowerPattern)) {
125
+ return false;
126
+ }
116
127
  // Domain suffix match (.example.com)
117
128
  if (lowerPattern.startsWith(".")) {
118
129
  const suffix = lowerPattern.slice(1);
@@ -145,6 +156,8 @@ function matchesLegacyNoProxyEntry(lowerPattern, hostname, port) {
145
156
  * - "192.168.1.10", "::1", "[::1]:8080" - an IP literal, exactly
146
157
  * - "192.168.1.0/24" - an IPv4 CIDR range, for IPv4 literal targets (IPv6 CIDR
147
158
  * ranges are not supported)
159
+ * - "." and ".." - an entry made only of dots names no host, so it matches
160
+ * nothing, as in curl
148
161
  *
149
162
  * @param targetUrl - The URL to check for proxy bypass
150
163
  * @param noProxyEnv - Optional NO_PROXY environment variable value (if not provided, reads from process.env)
@@ -197,6 +210,8 @@ export function getNoProxyEnv() {
197
210
  * - "example.com" - exact match
198
211
  * - ".example.com" - domain suffix match
199
212
  *
213
+ * An entry made only of dots names no host and matches nothing.
214
+ *
200
215
  * @param targetUrl - The URL to check
201
216
  * @param noProxyValue - The NO_PROXY value to check against
202
217
  * @returns true if should bypass proxy
@@ -211,6 +226,10 @@ export function shouldBypassProxySimple(targetUrl, noProxyValue) {
211
226
  if (!pattern) {
212
227
  continue;
213
228
  }
229
+ // As a suffix, "." would match every host written with a trailing dot.
230
+ if (isDotsOnly(pattern)) {
231
+ continue;
232
+ }
214
233
  // Exact match
215
234
  if (hostname === pattern) {
216
235
  return true;
@@ -2,7 +2,7 @@
2
2
  * Provider-specific token limit utilities
3
3
  * Provides safe maxTokens values based on provider and model capabilities
4
4
  */
5
- import { getRuntimeOutputCeiling } from "../constants/contextWindows.js";
5
+ import { getRuntimeOutputCeiling, stripBedrockGeoPrefix, } from "../constants/contextWindows.js";
6
6
  import { PROVIDER_MAX_TOKENS } from "../core/constants.js";
7
7
  import { logger } from "./logger.js";
8
8
  import { hasRestrictedOutputLimit, RESTRICTED_OUTPUT_TOKEN_LIMIT, } from "./modelDetection.js";
@@ -67,6 +67,11 @@ export function getSafeMaxTokens(provider, model, requestedMaxTokens) {
67
67
  }
68
68
  return requestedMaxTokens;
69
69
  }
70
+ // The Bedrock rows are keyed by bare id, but for many models the cross-region
71
+ // profile id (us.amazon.nova-pro-v1:0) is the only one a caller can invoke.
72
+ // The bare id is tried only after the id as given misses, so an explicit row
73
+ // for the prefixed id still wins.
74
+ const bareModel = provider === "bedrock" && model ? stripBedrockGeoPrefix(model) : undefined;
70
75
  // Get model-specific limit or provider default
71
76
  let maxLimit;
72
77
  if (model &&
@@ -74,6 +79,11 @@ export function getSafeMaxTokens(provider, model, requestedMaxTokens) {
74
79
  providerLimits[model]) {
75
80
  maxLimit = providerLimits[model];
76
81
  }
82
+ else if (bareModel !== undefined &&
83
+ typeof providerLimits === "object" &&
84
+ providerLimits[bareModel]) {
85
+ maxLimit = providerLimits[bareModel];
86
+ }
77
87
  else if (typeof providerLimits === "object" &&
78
88
  providerLimits.default) {
79
89
  maxLimit = providerLimits.default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.49.4",
3
+ "version": "12.49.5",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
6
6
  "author": {