@juspay/neurolink 12.49.2 → 12.49.4

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.
@@ -81,6 +81,10 @@ export declare function clearRuntimeOutputCeilings(): void;
81
81
  * MODEL_CONTEXT_WINDOWS.mistral values).
82
82
  * 2. Exact model match under provider in static registry
83
83
  * 3. Prefix match under provider in static registry
84
+ * 3.5 Bedrock only: an id shaped `<geography>.<vendor>.<model>` (a
85
+ * cross-region inference profile) is resolved again from step 0 without
86
+ * the geography. Every step above saw the id as given, so an explicit row
87
+ * or discovered window for the prefixed id still wins.
84
88
  * 4. Provider's _default in static registry
85
89
  * 5. Global DEFAULT_CONTEXT_WINDOW
86
90
  */
@@ -14,6 +14,7 @@ import { DynamicModelProvider } from "../core/dynamicModels.js";
14
14
  import { logger } from "../utils/logger.js";
15
15
  import { resolveManifestEntryStrict } from "../models/manifestRegistry.js";
16
16
  import { getCatalogJsonEntries } from "../providers/catalog/loader.js";
17
+ import { BedrockModels } from "./enums.js";
17
18
  /**
18
19
  * Per-provider context window blocks for the 9 JSON-catalog providers
19
20
  * (cerebras, cloudflare, fireworks, groq, mistral, perplexity, sambanova,
@@ -478,6 +479,44 @@ export function clearRuntimeOutputCeiling(provider, model) {
478
479
  export function clearRuntimeOutputCeilings() {
479
480
  RUNTIME_OUTPUT_CEILINGS.clear();
480
481
  }
482
+ /**
483
+ * Geographies AWS puts in front of a model id to name a cross-region inference
484
+ * profile (`us.anthropic.claude-sonnet-4-6`). For many models that prefixed id
485
+ * is the only one a caller can invoke, but the Bedrock table above is keyed by
486
+ * bare ids, apart from one explicit `us.` row. AWS lists the prefixes on each
487
+ * model card, not in one place, and the set differs by model, so it is closed
488
+ * on purpose: a geography added later keeps taking the Bedrock default instead
489
+ * of being guessed at.
490
+ */
491
+ const BEDROCK_GEO_PREFIXES = new Set([
492
+ "us",
493
+ "eu",
494
+ "apac",
495
+ "jp",
496
+ "au",
497
+ "in",
498
+ "us-gov",
499
+ "global",
500
+ ]);
501
+ /** Vendor segment of every id `BedrockModels` ships: `anthropic`, `amazon`, `meta`, ... */
502
+ const BEDROCK_VENDORS = new Set(Object.values(BedrockModels)
503
+ .map((id) => id.split(".")[0])
504
+ .filter((vendor) => !BEDROCK_GEO_PREFIXES.has(vendor)));
505
+ /**
506
+ * `us.anthropic.claude-sonnet-4-6` -> `anthropic.claude-sonnet-4-6`.
507
+ *
508
+ * Undefined for any other shape, so an id that merely starts with `us.` is
509
+ * never rewritten. Removes one geography at most: no vendor is a geography.
510
+ */
511
+ function stripBedrockGeoPrefix(model) {
512
+ const [geo, vendor, ...rest] = model.split(".");
513
+ if (rest.length === 0 ||
514
+ !BEDROCK_GEO_PREFIXES.has(geo) ||
515
+ !BEDROCK_VENDORS.has(vendor)) {
516
+ return undefined;
517
+ }
518
+ return model.slice(geo.length + 1);
519
+ }
481
520
  /**
482
521
  * Resolve context window size for a provider/model combination.
483
522
  *
@@ -503,6 +542,10 @@ export function clearRuntimeOutputCeilings() {
503
542
  * MODEL_CONTEXT_WINDOWS.mistral values).
504
543
  * 2. Exact model match under provider in static registry
505
544
  * 3. Prefix match under provider in static registry
545
+ * 3.5 Bedrock only: an id shaped `<geography>.<vendor>.<model>` (a
546
+ * cross-region inference profile) is resolved again from step 0 without
547
+ * the geography. Every step above saw the id as given, so an explicit row
548
+ * or discovered window for the prefixed id still wins.
506
549
  * 4. Provider's _default in static registry
507
550
  * 5. Global DEFAULT_CONTEXT_WINDOW
508
551
  */
@@ -565,6 +608,10 @@ export function getContextWindowSize(provider, model) {
565
608
  }
566
609
  }
567
610
  }
611
+ const bareModel = canonical === "bedrock" && model ? stripBedrockGeoPrefix(model) : undefined;
612
+ if (bareModel !== undefined) {
613
+ return getContextWindowSize(provider, bareModel);
614
+ }
568
615
  return providerWindows._default ?? DEFAULT_CONTEXT_WINDOW;
569
616
  }
570
617
  /**
@@ -112,6 +112,14 @@ export declare class AmazonBedrockProvider extends BaseProvider {
112
112
  * @returns Promise resolving to the embedding vector
113
113
  */
114
114
  embed(input: string | EmbedInput, modelName?: string): Promise<number[]>;
115
+ /**
116
+ * One InvokeModel round trip. Resolves to the vector the model returned
117
+ * (empty or not), or `undefined` when the answer carries no embedding array.
118
+ * What that means is for `embed` and `embedMany` to decide, outside this
119
+ * try: inside it, handleProviderError would wrap the message in a second
120
+ * provider error and run it through its keyword rules.
121
+ */
122
+ private requestEmbedding;
115
123
  /**
116
124
  * Generate embeddings for multiple texts in a single batch
117
125
  * @param texts - The texts to embed
@@ -1542,9 +1542,28 @@ export class AmazonBedrockProvider extends BaseProvider {
1542
1542
  * @returns Promise resolving to the embedding vector
1543
1543
  */
1544
1544
  async embed(input, modelName) {
1545
+ const embeddingModelName = modelName || this.getDefaultEmbeddingModel();
1546
+ const embedding = await this.requestEmbedding(input, embeddingModelName);
1547
+ if (!embedding || embedding.length === 0) {
1548
+ throw new ProviderError("No embedding returned from Bedrock", this.providerName);
1549
+ }
1550
+ logger.debug("Embedding generated successfully", {
1551
+ provider: this.providerName,
1552
+ model: embeddingModelName,
1553
+ embeddingDimension: embedding.length,
1554
+ });
1555
+ return embedding;
1556
+ }
1557
+ /**
1558
+ * One InvokeModel round trip. Resolves to the vector the model returned
1559
+ * (empty or not), or `undefined` when the answer carries no embedding array.
1560
+ * What that means is for `embed` and `embedMany` to decide, outside this
1561
+ * try: inside it, handleProviderError would wrap the message in a second
1562
+ * provider error and run it through its keyword rules.
1563
+ */
1564
+ async requestEmbedding(input, embeddingModelName) {
1545
1565
  // Normalize input to EmbedInput shape
1546
1566
  const embedInput = typeof input === "string" ? { text: input } : input;
1547
- const embeddingModelName = modelName || this.getDefaultEmbeddingModel();
1548
1567
  const isNovaModel = embeddingModelName.includes("nova") &&
1549
1568
  embeddingModelName.includes("multimodal");
1550
1569
  const isMultiModalModel = embeddingModelName.includes("image") ||
@@ -1639,15 +1658,7 @@ export class AmazonBedrockProvider extends BaseProvider {
1639
1658
  const embedding = isNovaModel
1640
1659
  ? responseBody.embeddings?.at(0)?.embedding
1641
1660
  : responseBody.embedding;
1642
- if (!Array.isArray(embedding)) {
1643
- throw new Error("Invalid embedding response from Bedrock");
1644
- }
1645
- logger.debug("Embedding generated successfully", {
1646
- provider: this.providerName,
1647
- model: embeddingModelName,
1648
- embeddingDimension: embedding.length,
1649
- });
1650
- return embedding;
1661
+ return Array.isArray(embedding) ? embedding : undefined;
1651
1662
  }
1652
1663
  catch (error) {
1653
1664
  logger.error("Embedding generation failed", {
@@ -1670,15 +1681,9 @@ export class AmazonBedrockProvider extends BaseProvider {
1670
1681
  model: embeddingModelName,
1671
1682
  count: texts.length,
1672
1683
  });
1684
+ let rows;
1673
1685
  try {
1674
- const embeddings = await Promise.all(texts.map((text) => this.embed(text, embeddingModelName)));
1675
- logger.debug("Batch embeddings generated successfully", {
1676
- provider: this.providerName,
1677
- model: embeddingModelName,
1678
- count: embeddings.length,
1679
- embeddingDimension: embeddings[0]?.length,
1680
- });
1681
- return embeddings;
1686
+ rows = await Promise.all(texts.map((text) => this.requestEmbedding(text, embeddingModelName)));
1682
1687
  }
1683
1688
  catch (error) {
1684
1689
  logger.error("Batch embedding generation failed", {
@@ -1688,5 +1693,25 @@ export class AmazonBedrockProvider extends BaseProvider {
1688
1693
  });
1689
1694
  throw this.handleProviderError(error);
1690
1695
  }
1696
+ // Checked after the try on purpose: inside it, handleProviderError would
1697
+ // wrap this message in a second provider error and run it through its
1698
+ // keyword rules.
1699
+ const embeddings = rows.map((values, index) => {
1700
+ if (!values || values.length === 0) {
1701
+ logger.error("Batch embedding returned no values", {
1702
+ model: embeddingModelName,
1703
+ index,
1704
+ });
1705
+ throw new ProviderError(`Embedding for the text at index ${index} came back without values`, this.providerName);
1706
+ }
1707
+ return values;
1708
+ });
1709
+ logger.debug("Batch embeddings generated successfully", {
1710
+ provider: this.providerName,
1711
+ model: embeddingModelName,
1712
+ count: embeddings.length,
1713
+ embeddingDimension: embeddings[0]?.length,
1714
+ });
1715
+ return embeddings;
1691
1716
  }
1692
1717
  }
@@ -7292,6 +7292,7 @@ export class GoogleVertexProvider extends BaseProvider {
7292
7292
  model: embeddingModelName,
7293
7293
  count: texts.length,
7294
7294
  });
7295
+ let rows;
7295
7296
  try {
7296
7297
  const effectiveLocation = resolveVertexRegionForModel(embeddingModelName, undefined);
7297
7298
  const client = await this.createVertexGenAIClient(effectiveLocation);
@@ -7299,14 +7300,7 @@ export class GoogleVertexProvider extends BaseProvider {
7299
7300
  model: embeddingModelName,
7300
7301
  contents: texts,
7301
7302
  });
7302
- const embeddings = (result.embeddings || []).map((e) => e.values || []);
7303
- logger.debug("Batch embeddings generated successfully", {
7304
- provider: this.providerName,
7305
- model: embeddingModelName,
7306
- count: embeddings.length,
7307
- embeddingDimension: embeddings[0]?.length,
7308
- });
7309
- return embeddings;
7303
+ rows = (result.embeddings || []).map((e) => e.values);
7310
7304
  }
7311
7305
  catch (error) {
7312
7306
  logger.error("Batch embedding generation failed", {
@@ -7316,6 +7310,35 @@ export class GoogleVertexProvider extends BaseProvider {
7316
7310
  });
7317
7311
  throw this.handleProviderError(error);
7318
7312
  }
7313
+ // One vector per text, or fail: a dropped or empty row would pair later
7314
+ // texts with the wrong vectors, or score 0 downstream. Checked after the
7315
+ // try on purpose: inside it, handleProviderError would wrap this message
7316
+ // in a second provider error and run it through its keyword rules.
7317
+ if (rows.length !== texts.length) {
7318
+ logger.error("Batch embedding returned the wrong number of vectors", {
7319
+ model: embeddingModelName,
7320
+ expected: texts.length,
7321
+ received: rows.length,
7322
+ });
7323
+ throw new ProviderError(`Embedding response held ${rows.length} vectors for ${texts.length} texts`, this.providerName);
7324
+ }
7325
+ const embeddings = rows.map((values, index) => {
7326
+ if (!values || values.length === 0) {
7327
+ logger.error("Batch embedding returned no values", {
7328
+ model: embeddingModelName,
7329
+ index,
7330
+ });
7331
+ throw new ProviderError(`Embedding for the text at index ${index} came back without values`, this.providerName);
7332
+ }
7333
+ return values;
7334
+ });
7335
+ logger.debug("Batch embeddings generated successfully", {
7336
+ provider: this.providerName,
7337
+ model: embeddingModelName,
7338
+ count: embeddings.length,
7339
+ embeddingDimension: embeddings[0]?.length,
7340
+ });
7341
+ return embeddings;
7319
7342
  }
7320
7343
  }
7321
7344
  export default GoogleVertexProvider;
@@ -51,6 +51,15 @@ export declare class OpenAIProvider extends OpenAIChatCompletionsProvider {
51
51
  * @returns Promise resolving to an array of embedding vectors
52
52
  */
53
53
  embedMany(texts: string[], modelName?: string): Promise<number[][]>;
54
+ /**
55
+ * One non-empty vector per input, or fail: a dropped or empty row would
56
+ * pair later texts with the wrong vectors, or score 0 downstream.
57
+ *
58
+ * Called after the request's try on purpose: inside it, handleProviderError
59
+ * would wrap this message in a second provider error and run it through its
60
+ * keyword rules.
61
+ */
62
+ private requireEmbeddings;
54
63
  private callEmbeddings;
55
64
  /**
56
65
  * Image generation via the OpenAI Images API (`/v1/images/generations`).
@@ -234,14 +234,9 @@ export class OpenAIProvider extends OpenAIChatCompletionsProvider {
234
234
  model: embeddingModelName,
235
235
  textLength: text.length,
236
236
  });
237
+ let rows;
237
238
  try {
238
- const [embedding] = await this.callEmbeddings(embeddingModelName, [text], "embed");
239
- logger.debug("Embedding generated successfully", {
240
- provider: this.providerName,
241
- model: embeddingModelName,
242
- embeddingDimension: embedding.length,
243
- });
244
- return embedding;
239
+ rows = await this.callEmbeddings(embeddingModelName, [text], "embed");
245
240
  }
246
241
  catch (error) {
247
242
  logger.error("Embedding generation failed", {
@@ -251,6 +246,13 @@ export class OpenAIProvider extends OpenAIChatCompletionsProvider {
251
246
  });
252
247
  throw this.handleProviderError(error);
253
248
  }
249
+ const [embedding] = this.requireEmbeddings(rows, 1, embeddingModelName);
250
+ logger.debug("Embedding generated successfully", {
251
+ provider: this.providerName,
252
+ model: embeddingModelName,
253
+ embeddingDimension: embedding.length,
254
+ });
255
+ return embedding;
254
256
  }
255
257
  /**
256
258
  * Generate embeddings for multiple texts in a single batch via native /v1/embeddings.
@@ -266,15 +268,9 @@ export class OpenAIProvider extends OpenAIChatCompletionsProvider {
266
268
  model: embeddingModelName,
267
269
  count: texts.length,
268
270
  });
271
+ let rows;
269
272
  try {
270
- const embeddings = await this.callEmbeddings(embeddingModelName, texts, "embedMany");
271
- logger.debug("Batch embeddings generated successfully", {
272
- provider: this.providerName,
273
- model: embeddingModelName,
274
- count: embeddings.length,
275
- embeddingDimension: embeddings[0]?.length,
276
- });
277
- return embeddings;
273
+ rows = await this.callEmbeddings(embeddingModelName, texts, "embedMany");
278
274
  }
279
275
  catch (error) {
280
276
  logger.error("Batch embedding generation failed", {
@@ -284,6 +280,42 @@ export class OpenAIProvider extends OpenAIChatCompletionsProvider {
284
280
  });
285
281
  throw this.handleProviderError(error);
286
282
  }
283
+ const embeddings = this.requireEmbeddings(rows, texts.length, embeddingModelName);
284
+ logger.debug("Batch embeddings generated successfully", {
285
+ provider: this.providerName,
286
+ model: embeddingModelName,
287
+ count: embeddings.length,
288
+ embeddingDimension: embeddings[0]?.length,
289
+ });
290
+ return embeddings;
291
+ }
292
+ /**
293
+ * One non-empty vector per input, or fail: a dropped or empty row would
294
+ * pair later texts with the wrong vectors, or score 0 downstream.
295
+ *
296
+ * Called after the request's try on purpose: inside it, handleProviderError
297
+ * would wrap this message in a second provider error and run it through its
298
+ * keyword rules.
299
+ */
300
+ requireEmbeddings(rows, inputCount, modelName) {
301
+ if (rows.length !== inputCount) {
302
+ logger.error("Embedding response held the wrong number of vectors", {
303
+ model: modelName,
304
+ expected: inputCount,
305
+ received: rows.length,
306
+ });
307
+ throw new ProviderError(`Embedding response held ${rows.length} vectors for ${inputCount} texts`, this.providerName);
308
+ }
309
+ return rows.map((values, index) => {
310
+ if (!values || values.length === 0) {
311
+ logger.error("Embedding returned no values", {
312
+ model: modelName,
313
+ index,
314
+ });
315
+ throw new ProviderError(`Embedding for the text at index ${index} came back without values`, this.providerName);
316
+ }
317
+ return values;
318
+ });
287
319
  }
288
320
  async callEmbeddings(modelName, input, operation) {
289
321
  const url = `${stripTrailingSlash(this.config.baseURL)}/embeddings`;
@@ -329,13 +361,11 @@ export class OpenAIProvider extends OpenAIChatCompletionsProvider {
329
361
  });
330
362
  }
331
363
  const json = (await res.json());
332
- const embeddings = (json.data ?? [])
333
- .map((row) => row.embedding)
334
- .filter((e) => Array.isArray(e));
335
- if (embeddings.length === 0) {
364
+ const rows = (json.data ?? []).map((row) => Array.isArray(row.embedding) ? row.embedding : undefined);
365
+ if (rows.length === 0) {
336
366
  throw new ProviderError(`OpenAI ${operation} returned no embeddings`, this.providerName);
337
367
  }
338
- return embeddings;
368
+ return rows;
339
369
  }
340
370
  finally {
341
371
  timeoutController?.cleanup();
@@ -4,14 +4,19 @@
4
4
  * Supports comprehensive NO_PROXY pattern matching including wildcards, domains, ports, and CIDR
5
5
  */
6
6
  /**
7
- * Comprehensive NO_PROXY bypass check supporting multiple pattern types
7
+ * NO_PROXY bypass check. The value is a comma- or space-separated list; each
8
+ * entry is matched case-insensitively.
8
9
  *
9
- * Supported patterns:
10
+ * Supported entries:
10
11
  * - "*" - bypass all requests
11
- * - "example.com" - exact hostname match
12
- * - ".example.com" - domain suffix match (matches example.com and subdomains)
13
- * - "localhost:8080" - hostname with specific port
14
- * - "192.168.1.0/24" - CIDR notation for IP ranges
12
+ * - "example.com", ".example.com", "*.example.com" - the host and any subdomain
13
+ * - "example.com:8443" - the same, on that port only; a URL without a port is
14
+ * on the scheme's default (80 for http, 443 for https)
15
+ * - "example.com." - the same as "example.com": one trailing dot is ignored on
16
+ * an entry and on the URL's host name, so "https://api.example.com./" matches
17
+ * - "192.168.1.10", "::1", "[::1]:8080" - an IP literal, exactly
18
+ * - "192.168.1.0/24" - an IPv4 CIDR range, for IPv4 literal targets (IPv6 CIDR
19
+ * ranges are not supported)
15
20
  *
16
21
  * @param targetUrl - The URL to check for proxy bypass
17
22
  * @param noProxyEnv - Optional NO_PROXY environment variable value (if not provided, reads from process.env)
@@ -27,15 +27,124 @@ function isIpInCIDR(ip, cidr) {
27
27
  return false;
28
28
  }
29
29
  }
30
+ const IPV4_LITERAL = /^\d{1,3}(\.\d{1,3}){3}$/;
30
31
  /**
31
- * Comprehensive NO_PROXY bypass check supporting multiple pattern types
32
+ * The form a URL gives an IPv6 literal (`[::1]`, lower-case, compressed), so
33
+ * `[0:0:0:0:0:0:0:1]` and `[::1]` compare equal. `null` when it is not one.
34
+ */
35
+ function canonicalIpv6Host(address) {
36
+ try {
37
+ return new URL(`http://[${address}]`).hostname;
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ /** A fully qualified name's trailing dot, which a URL keeps and curl ignores. */
44
+ function withoutTrailingDot(name) {
45
+ return name.endsWith(".") ? name.slice(0, -1) : name;
46
+ }
47
+ /**
48
+ * curl's rules for one list entry: an optional leading `.` or `*.` is ignored;
49
+ * the entry names that host and every subdomain of it, an IP literal only
50
+ * itself; and a `:port` restricts it to that port. IPv6 is written bare (`::1`)
51
+ * or bracketed (`[::1]:8080`), and only the bracketed form can carry a port.
52
+ * One trailing dot is ignored on the entry's domain and on the URL's host name,
53
+ * so `example.com.` and `example.com` name the same host; an entry made only of
54
+ * dots names none.
55
+ */
56
+ function matchesNoProxyEntry(entry, hostname, port) {
57
+ let host = entry;
58
+ let entryPort;
59
+ if (entry.startsWith("[")) {
60
+ const close = entry.indexOf("]");
61
+ if (close === -1) {
62
+ return false;
63
+ }
64
+ host = entry.slice(0, close + 1);
65
+ const rest = entry.slice(close + 1);
66
+ if (rest !== "") {
67
+ if (!/^:\d+$/.test(rest)) {
68
+ return false;
69
+ }
70
+ entryPort = rest.slice(1);
71
+ }
72
+ }
73
+ else {
74
+ const colons = entry.split(":").length - 1;
75
+ if (colons === 1) {
76
+ const separator = entry.indexOf(":");
77
+ host = entry.slice(0, separator);
78
+ entryPort = entry.slice(separator + 1);
79
+ if (!/^\d+$/.test(entryPort)) {
80
+ return false;
81
+ }
82
+ }
83
+ else if (colons > 1) {
84
+ host = `[${entry}]`;
85
+ }
86
+ }
87
+ if (entryPort !== undefined && entryPort !== port) {
88
+ return false;
89
+ }
90
+ if (host.startsWith("[")) {
91
+ return canonicalIpv6Host(host.slice(1, -1)) === hostname;
92
+ }
93
+ host = host.replace(/^(\*\.|\.)/, "");
94
+ if (/^\.*$/.test(host) || /[\s*/]/.test(host)) {
95
+ return false;
96
+ }
97
+ if (IPV4_LITERAL.test(host)) {
98
+ return hostname === host;
99
+ }
100
+ const domain = withoutTrailingDot(host);
101
+ // curl does not take "127.0.0.1." for an address, so it names nothing.
102
+ if (IPV4_LITERAL.test(domain)) {
103
+ return false;
104
+ }
105
+ const name = withoutTrailingDot(hostname);
106
+ return name === domain || name.endsWith(`.${domain}`);
107
+ }
108
+ /**
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.
114
+ */
115
+ function matchesLegacyNoProxyEntry(lowerPattern, hostname, port) {
116
+ // Domain suffix match (.example.com)
117
+ if (lowerPattern.startsWith(".")) {
118
+ const suffix = lowerPattern.slice(1);
119
+ return hostname.endsWith(suffix) || hostname === suffix;
120
+ }
121
+ // Port-specific match (hostname:port)
122
+ if (lowerPattern.includes(":")) {
123
+ const [patternHost, patternPort] = lowerPattern.split(":");
124
+ return hostname === patternHost && port === patternPort;
125
+ }
126
+ // CIDR notation (192.168.1.0/24) - only for IP addresses
127
+ if (lowerPattern.includes("/")) {
128
+ // Only apply CIDR when target is an IP literal
129
+ return IPV4_LITERAL.test(hostname) && isIpInCIDR(hostname, lowerPattern);
130
+ }
131
+ // Exact hostname match
132
+ return hostname === lowerPattern;
133
+ }
134
+ /**
135
+ * NO_PROXY bypass check. The value is a comma- or space-separated list; each
136
+ * entry is matched case-insensitively.
32
137
  *
33
- * Supported patterns:
138
+ * Supported entries:
34
139
  * - "*" - bypass all requests
35
- * - "example.com" - exact hostname match
36
- * - ".example.com" - domain suffix match (matches example.com and subdomains)
37
- * - "localhost:8080" - hostname with specific port
38
- * - "192.168.1.0/24" - CIDR notation for IP ranges
140
+ * - "example.com", ".example.com", "*.example.com" - the host and any subdomain
141
+ * - "example.com:8443" - the same, on that port only; a URL without a port is
142
+ * on the scheme's default (80 for http, 443 for https)
143
+ * - "example.com." - the same as "example.com": one trailing dot is ignored on
144
+ * an entry and on the URL's host name, so "https://api.example.com./" matches
145
+ * - "192.168.1.10", "::1", "[::1]:8080" - an IP literal, exactly
146
+ * - "192.168.1.0/24" - an IPv4 CIDR range, for IPv4 literal targets (IPv6 CIDR
147
+ * ranges are not supported)
39
148
  *
40
149
  * @param targetUrl - The URL to check for proxy bypass
41
150
  * @param noProxyEnv - Optional NO_PROXY environment variable value (if not provided, reads from process.env)
@@ -51,7 +160,7 @@ export function shouldBypassProxy(targetUrl, noProxyEnv) {
51
160
  const hostname = url.hostname.toLowerCase();
52
161
  const port = url.port || (url.protocol === "https:" ? "443" : "80");
53
162
  const patterns = noProxy
54
- .split(",")
163
+ .split(/[,\s]+/)
55
164
  .map((p) => p.trim())
56
165
  .filter(Boolean);
57
166
  for (const pattern of patterns) {
@@ -60,30 +169,8 @@ export function shouldBypassProxy(targetUrl, noProxyEnv) {
60
169
  if (lowerPattern === "*") {
61
170
  return true;
62
171
  }
63
- // Domain suffix match (.example.com)
64
- if (lowerPattern.startsWith(".")) {
65
- const suffix = lowerPattern.slice(1);
66
- if (hostname.endsWith(suffix) || hostname === suffix) {
67
- return true;
68
- }
69
- }
70
- // Port-specific match (hostname:port)
71
- else if (lowerPattern.includes(":")) {
72
- const [patternHost, patternPort] = lowerPattern.split(":");
73
- if (hostname === patternHost && port === patternPort) {
74
- return true;
75
- }
76
- }
77
- // CIDR notation (192.168.1.0/24) - only for IP addresses
78
- else if (lowerPattern.includes("/")) {
79
- // Only apply CIDR when target is an IP literal
80
- if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) &&
81
- isIpInCIDR(hostname, lowerPattern)) {
82
- return true;
83
- }
84
- }
85
- // Exact hostname match
86
- else if (hostname === lowerPattern) {
172
+ if (matchesNoProxyEntry(lowerPattern, hostname, port) ||
173
+ matchesLegacyNoProxyEntry(lowerPattern, hostname, port)) {
87
174
  return true;
88
175
  }
89
176
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.49.2",
3
+ "version": "12.49.4",
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": {