@juspay/neurolink 12.49.3 → 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.
- package/CHANGELOG.md +4 -2
- package/dist/browser/neurolink.min.js +410 -410
- package/dist/constants/contextWindows.d.ts +15 -0
- package/dist/constants/contextWindows.js +51 -0
- package/dist/providers/amazonBedrock/client.d.ts +8 -0
- package/dist/providers/amazonBedrock/client.js +43 -18
- package/dist/providers/googleVertex/client.js +31 -8
- package/dist/providers/litellm/client.d.ts +5 -0
- package/dist/providers/litellm/client.js +33 -7
- package/dist/providers/ollama/client.d.ts +5 -0
- package/dist/providers/ollama/client.js +33 -7
- package/dist/providers/openAI/client.d.ts +9 -0
- package/dist/providers/openAI/client.js +50 -20
- package/dist/proxy/utils/noProxyUtils.d.ts +4 -0
- package/dist/proxy/utils/noProxyUtils.js +24 -5
- package/dist/utils/tokenLimits.js +11 -1
- package/package.json +1 -1
|
@@ -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
|
*
|
|
@@ -81,6 +92,10 @@ export declare function clearRuntimeOutputCeilings(): void;
|
|
|
81
92
|
* MODEL_CONTEXT_WINDOWS.mistral values).
|
|
82
93
|
* 2. Exact model match under provider in static registry
|
|
83
94
|
* 3. Prefix match under provider in static registry
|
|
95
|
+
* 3.5 Bedrock only: an id shaped `<geography>.<vendor>.<model>` (a
|
|
96
|
+
* cross-region inference profile) is resolved again from step 0 without
|
|
97
|
+
* the geography. Every step above saw the id as given, so an explicit row
|
|
98
|
+
* or discovered window for the prefixed id still wins.
|
|
84
99
|
* 4. Provider's _default in static registry
|
|
85
100
|
* 5. Global DEFAULT_CONTEXT_WINDOW
|
|
86
101
|
*/
|
|
@@ -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,48 @@ 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
|
+
* 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.
|
|
514
|
+
*/
|
|
515
|
+
export function stripBedrockGeoPrefix(model) {
|
|
516
|
+
const [geo, vendor, ...rest] = model.split(".");
|
|
517
|
+
if (rest.length === 0 ||
|
|
518
|
+
!BEDROCK_GEO_PREFIXES.has(geo) ||
|
|
519
|
+
!BEDROCK_VENDORS.has(vendor)) {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
return model.slice(geo.length + 1);
|
|
523
|
+
}
|
|
481
524
|
/**
|
|
482
525
|
* Resolve context window size for a provider/model combination.
|
|
483
526
|
*
|
|
@@ -503,6 +546,10 @@ export function clearRuntimeOutputCeilings() {
|
|
|
503
546
|
* MODEL_CONTEXT_WINDOWS.mistral values).
|
|
504
547
|
* 2. Exact model match under provider in static registry
|
|
505
548
|
* 3. Prefix match under provider in static registry
|
|
549
|
+
* 3.5 Bedrock only: an id shaped `<geography>.<vendor>.<model>` (a
|
|
550
|
+
* cross-region inference profile) is resolved again from step 0 without
|
|
551
|
+
* the geography. Every step above saw the id as given, so an explicit row
|
|
552
|
+
* or discovered window for the prefixed id still wins.
|
|
506
553
|
* 4. Provider's _default in static registry
|
|
507
554
|
* 5. Global DEFAULT_CONTEXT_WINDOW
|
|
508
555
|
*/
|
|
@@ -565,6 +612,10 @@ export function getContextWindowSize(provider, model) {
|
|
|
565
612
|
}
|
|
566
613
|
}
|
|
567
614
|
}
|
|
615
|
+
const bareModel = canonical === "bedrock" && model ? stripBedrockGeoPrefix(model) : undefined;
|
|
616
|
+
if (bareModel !== undefined) {
|
|
617
|
+
return getContextWindowSize(provider, bareModel);
|
|
618
|
+
}
|
|
568
619
|
return providerWindows._default ?? DEFAULT_CONTEXT_WINDOW;
|
|
569
620
|
}
|
|
570
621
|
/**
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
518
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
269
|
-
|
|
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
|
|
300
|
+
return rows;
|
|
275
301
|
}
|
|
276
302
|
finally {
|
|
277
303
|
timeoutController?.cleanup();
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
333
|
-
|
|
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
|
|
368
|
+
return rows;
|
|
339
369
|
}
|
|
340
370
|
finally {
|
|
341
371
|
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,
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
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;
|