@juspay/neurolink 9.17.2 → 9.19.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/context/stages/slidingWindowTruncator.js +2 -2
  3. package/dist/core/baseProvider.d.ts +13 -0
  4. package/dist/core/baseProvider.js +21 -0
  5. package/dist/lib/context/stages/slidingWindowTruncator.js +2 -2
  6. package/dist/lib/core/baseProvider.d.ts +13 -0
  7. package/dist/lib/core/baseProvider.js +21 -0
  8. package/dist/lib/providers/amazonBedrock.d.ts +7 -0
  9. package/dist/lib/providers/amazonBedrock.js +32 -0
  10. package/dist/lib/providers/googleAiStudio.d.ts +15 -0
  11. package/dist/lib/providers/googleAiStudio.js +87 -3
  12. package/dist/lib/providers/googleNativeGemini3.d.ts +24 -1
  13. package/dist/lib/providers/googleNativeGemini3.js +117 -4
  14. package/dist/lib/providers/googleVertex.d.ts +7 -0
  15. package/dist/lib/providers/googleVertex.js +52 -5
  16. package/dist/lib/providers/openAI.d.ts +7 -0
  17. package/dist/lib/providers/openAI.js +41 -2
  18. package/dist/lib/server/routes/agentRoutes.js +60 -1
  19. package/dist/lib/server/types.d.ts +50 -0
  20. package/dist/lib/server/utils/validation.d.ts +32 -0
  21. package/dist/lib/server/utils/validation.js +19 -0
  22. package/dist/lib/types/providers.d.ts +2 -0
  23. package/dist/providers/amazonBedrock.d.ts +7 -0
  24. package/dist/providers/amazonBedrock.js +32 -0
  25. package/dist/providers/googleAiStudio.d.ts +15 -0
  26. package/dist/providers/googleAiStudio.js +87 -3
  27. package/dist/providers/googleNativeGemini3.d.ts +24 -1
  28. package/dist/providers/googleNativeGemini3.js +117 -4
  29. package/dist/providers/googleVertex.d.ts +7 -0
  30. package/dist/providers/googleVertex.js +52 -5
  31. package/dist/providers/openAI.d.ts +7 -0
  32. package/dist/providers/openAI.js +41 -2
  33. package/dist/server/routes/agentRoutes.js +60 -1
  34. package/dist/server/types.d.ts +50 -0
  35. package/dist/server/utils/validation.d.ts +32 -0
  36. package/dist/server/utils/validation.js +19 -0
  37. package/dist/types/providers.d.ts +2 -0
  38. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [9.19.0](https://github.com/juspay/neurolink/compare/v9.18.0...v9.19.0) (2026-03-07)
2
+
3
+ ### Features
4
+
5
+ - **(landing):** nervous system visualization redesign with SEO and SDK fixes ([d410a49](https://github.com/juspay/neurolink/commit/d410a49546e9d55bce319f41d2b11f955350becc))
6
+
7
+ ## [9.18.0](https://github.com/juspay/neurolink/compare/v9.17.2...v9.18.0) (2026-03-07)
8
+
9
+ ### Features
10
+
11
+ - **(sdk):** add embed() and embedMany() support across providers and server ([17243ad](https://github.com/juspay/neurolink/commit/17243ada417a192caa6e555f92df23938ddca6aa))
12
+
1
13
  ## [9.17.2](https://github.com/juspay/neurolink/compare/v9.17.1...v9.17.2) (2026-03-07)
2
14
 
3
15
  ### Bug Fixes
@@ -124,7 +124,7 @@ export function truncateWithSlidingWindow(messages, config) {
124
124
  const keptAfterTruncation = remainingMessages.slice(evenRemoveCount);
125
125
  const truncationMarker = {
126
126
  id: `truncation-${randomUUID()}`,
127
- role: "system",
127
+ role: "user",
128
128
  content: TRUNCATION_MARKER_CONTENT,
129
129
  timestamp: new Date().toISOString(),
130
130
  metadata: { isSummary: false, truncated: true },
@@ -162,7 +162,7 @@ export function truncateWithSlidingWindow(messages, config) {
162
162
  const keptMessages = remainingMessages.slice(evenMaxRemove);
163
163
  const truncationMarker = {
164
164
  id: `truncation-${randomUUID()}`,
165
- role: "system",
165
+ role: "user",
166
166
  content: TRUNCATION_MARKER_CONTENT,
167
167
  timestamp: new Date().toISOString(),
168
168
  metadata: { isSummary: false, truncated: true },
@@ -153,6 +153,19 @@ export declare abstract class BaseProvider implements AIProvider {
153
153
  * ```
154
154
  */
155
155
  embed(text: string, _modelName?: string): Promise<number[]>;
156
+ /**
157
+ * Generate embeddings for multiple texts in a single batch
158
+ *
159
+ * This is a default implementation that throws an error.
160
+ * Providers that support embeddings should override this method.
161
+ * The AI SDK's embedMany automatically handles chunking for models with batch limits.
162
+ *
163
+ * @param texts - The texts to embed
164
+ * @param _modelName - Optional embedding model name (provider-specific)
165
+ * @returns Promise resolving to an array of embedding vectors
166
+ * @throws Error if the provider does not support embeddings
167
+ */
168
+ embedMany(texts: string[], _modelName?: string): Promise<number[][]>;
156
169
  /**
157
170
  * Get the default embedding model for this provider
158
171
  *
@@ -756,6 +756,27 @@ export class BaseProvider {
756
756
  `Use an embedding model like text-embedding-3-small (OpenAI), text-embedding-004 (Vertex), ` +
757
757
  `or amazon.titan-embed-text-v2:0 (Bedrock).`);
758
758
  }
759
+ /**
760
+ * Generate embeddings for multiple texts in a single batch
761
+ *
762
+ * This is a default implementation that throws an error.
763
+ * Providers that support embeddings should override this method.
764
+ * The AI SDK's embedMany automatically handles chunking for models with batch limits.
765
+ *
766
+ * @param texts - The texts to embed
767
+ * @param _modelName - Optional embedding model name (provider-specific)
768
+ * @returns Promise resolving to an array of embedding vectors
769
+ * @throws Error if the provider does not support embeddings
770
+ */
771
+ async embedMany(texts, _modelName) {
772
+ logger.warn(`embedMany() called on ${this.providerName} which does not have a native implementation`, {
773
+ count: texts.length,
774
+ });
775
+ throw new Error(`Batch embedding generation is not supported by the ${this.providerName} provider. ` +
776
+ `Supported providers: openai, googleAiStudio, vertex/google, bedrock. ` +
777
+ `Use an embedding model like text-embedding-3-small (OpenAI), gemini-embedding-001 (Google AI), ` +
778
+ `text-embedding-004 (Vertex), or amazon.titan-embed-text-v2:0 (Bedrock).`);
779
+ }
759
780
  /**
760
781
  * Get the default embedding model for this provider
761
782
  *
@@ -124,7 +124,7 @@ export function truncateWithSlidingWindow(messages, config) {
124
124
  const keptAfterTruncation = remainingMessages.slice(evenRemoveCount);
125
125
  const truncationMarker = {
126
126
  id: `truncation-${randomUUID()}`,
127
- role: "system",
127
+ role: "user",
128
128
  content: TRUNCATION_MARKER_CONTENT,
129
129
  timestamp: new Date().toISOString(),
130
130
  metadata: { isSummary: false, truncated: true },
@@ -162,7 +162,7 @@ export function truncateWithSlidingWindow(messages, config) {
162
162
  const keptMessages = remainingMessages.slice(evenMaxRemove);
163
163
  const truncationMarker = {
164
164
  id: `truncation-${randomUUID()}`,
165
- role: "system",
165
+ role: "user",
166
166
  content: TRUNCATION_MARKER_CONTENT,
167
167
  timestamp: new Date().toISOString(),
168
168
  metadata: { isSummary: false, truncated: true },
@@ -153,6 +153,19 @@ export declare abstract class BaseProvider implements AIProvider {
153
153
  * ```
154
154
  */
155
155
  embed(text: string, _modelName?: string): Promise<number[]>;
156
+ /**
157
+ * Generate embeddings for multiple texts in a single batch
158
+ *
159
+ * This is a default implementation that throws an error.
160
+ * Providers that support embeddings should override this method.
161
+ * The AI SDK's embedMany automatically handles chunking for models with batch limits.
162
+ *
163
+ * @param texts - The texts to embed
164
+ * @param _modelName - Optional embedding model name (provider-specific)
165
+ * @returns Promise resolving to an array of embedding vectors
166
+ * @throws Error if the provider does not support embeddings
167
+ */
168
+ embedMany(texts: string[], _modelName?: string): Promise<number[][]>;
156
169
  /**
157
170
  * Get the default embedding model for this provider
158
171
  *
@@ -756,6 +756,27 @@ export class BaseProvider {
756
756
  `Use an embedding model like text-embedding-3-small (OpenAI), text-embedding-004 (Vertex), ` +
757
757
  `or amazon.titan-embed-text-v2:0 (Bedrock).`);
758
758
  }
759
+ /**
760
+ * Generate embeddings for multiple texts in a single batch
761
+ *
762
+ * This is a default implementation that throws an error.
763
+ * Providers that support embeddings should override this method.
764
+ * The AI SDK's embedMany automatically handles chunking for models with batch limits.
765
+ *
766
+ * @param texts - The texts to embed
767
+ * @param _modelName - Optional embedding model name (provider-specific)
768
+ * @returns Promise resolving to an array of embedding vectors
769
+ * @throws Error if the provider does not support embeddings
770
+ */
771
+ async embedMany(texts, _modelName) {
772
+ logger.warn(`embedMany() called on ${this.providerName} which does not have a native implementation`, {
773
+ count: texts.length,
774
+ });
775
+ throw new Error(`Batch embedding generation is not supported by the ${this.providerName} provider. ` +
776
+ `Supported providers: openai, googleAiStudio, vertex/google, bedrock. ` +
777
+ `Use an embedding model like text-embedding-3-small (OpenAI), gemini-embedding-001 (Google AI), ` +
778
+ `text-embedding-004 (Vertex), or amazon.titan-embed-text-v2:0 (Bedrock).`);
779
+ }
759
780
  /**
760
781
  * Get the default embedding model for this provider
761
782
  *
@@ -53,4 +53,11 @@ export declare class AmazonBedrockProvider extends BaseProvider {
53
53
  * @returns Promise resolving to the embedding vector
54
54
  */
55
55
  embed(text: string, modelName?: string): Promise<number[]>;
56
+ /**
57
+ * Generate embeddings for multiple texts in a single batch
58
+ * @param texts - The texts to embed
59
+ * @param modelName - The embedding model to use (default: amazon.titan-embed-text-v2:0)
60
+ * @returns Promise resolving to an array of embedding vectors
61
+ */
62
+ embedMany(texts: string[], modelName?: string): Promise<number[][]>;
56
63
  }
@@ -1470,5 +1470,37 @@ export class AmazonBedrockProvider extends BaseProvider {
1470
1470
  throw this.handleProviderError(error);
1471
1471
  }
1472
1472
  }
1473
+ /**
1474
+ * Generate embeddings for multiple texts in a single batch
1475
+ * @param texts - The texts to embed
1476
+ * @param modelName - The embedding model to use (default: amazon.titan-embed-text-v2:0)
1477
+ * @returns Promise resolving to an array of embedding vectors
1478
+ */
1479
+ async embedMany(texts, modelName) {
1480
+ const embeddingModelName = modelName || "amazon.titan-embed-text-v2:0";
1481
+ logger.debug("Generating batch embeddings", {
1482
+ provider: this.providerName,
1483
+ model: embeddingModelName,
1484
+ count: texts.length,
1485
+ });
1486
+ try {
1487
+ const embeddings = await Promise.all(texts.map((text) => this.embed(text, embeddingModelName)));
1488
+ logger.debug("Batch embeddings generated successfully", {
1489
+ provider: this.providerName,
1490
+ model: embeddingModelName,
1491
+ count: embeddings.length,
1492
+ embeddingDimension: embeddings[0]?.length,
1493
+ });
1494
+ return embeddings;
1495
+ }
1496
+ catch (error) {
1497
+ logger.error("Batch embedding generation failed", {
1498
+ error: error instanceof Error ? error.message : String(error),
1499
+ model: embeddingModelName,
1500
+ count: texts.length,
1501
+ });
1502
+ throw this.handleProviderError(error);
1503
+ }
1504
+ }
1473
1505
  }
1474
1506
  //# sourceMappingURL=amazonBedrock.js.map
@@ -75,6 +75,21 @@ export declare class GoogleAIStudioProvider extends BaseProvider {
75
75
  */
76
76
  generate(optionsOrPrompt: TextGenerationOptions | string): Promise<EnhancedGenerateResult | null>;
77
77
  private executeAudioStreamViaGeminiLive;
78
+ protected getDefaultEmbeddingModel(): string;
79
+ /**
80
+ * Generate embeddings for text using Google AI Studio embedding models
81
+ * @param text - The text to embed
82
+ * @param modelName - The embedding model to use (default: gemini-embedding-001)
83
+ * @returns Promise resolving to the embedding vector
84
+ */
85
+ embed(text: string, modelName?: string): Promise<number[]>;
86
+ /**
87
+ * Generate embeddings for multiple texts in a single batch
88
+ * @param texts - The texts to embed
89
+ * @param modelName - The embedding model to use (default: gemini-embedding-001)
90
+ * @returns Promise resolving to an array of embedding vectors
91
+ */
92
+ embedMany(texts: string[], modelName?: string): Promise<number[][]>;
78
93
  private getApiKey;
79
94
  }
80
95
  export default GoogleAIStudioProvider;
@@ -1,5 +1,5 @@
1
1
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
2
- import { streamText } from "ai";
2
+ import { embed, embedMany, streamText, } from "ai";
3
3
  import { ErrorCategory, ErrorSeverity, GoogleAIModels, } from "../constants/enums.js";
4
4
  import { estimateTokens } from "../utils/tokenEstimation.js";
5
5
  import { BaseProvider } from "../core/baseProvider.js";
@@ -11,7 +11,7 @@ import { logger } from "../utils/logger.js";
11
11
  import { isGemini3Model } from "../utils/modelDetection.js";
12
12
  import { tracers, ATTR, withClientSpan } from "../telemetry/index.js";
13
13
  import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
14
- import { buildNativeToolDeclarations, buildNativeConfig, computeMaxSteps, collectStreamChunks, extractTextFromParts, executeNativeToolCalls, handleMaxStepsTermination, pushModelResponseToHistory, } from "./googleNativeGemini3.js";
14
+ import { buildNativeToolDeclarations, buildNativeConfig, computeMaxSteps, collectStreamChunks, extractTextFromParts, executeNativeToolCalls, handleMaxStepsTermination, pushModelResponseToHistory, sanitizeToolsForGemini, } from "./googleNativeGemini3.js";
15
15
  // Google AI Live API types now imported from ../types/providerSpecific.js
16
16
  // Import proper types for multimodal message handling
17
17
  // Create Google GenAI client
@@ -444,9 +444,13 @@ export class GoogleAIStudioProvider extends BaseProvider {
444
444
  // Get tools consistently with generate method (include user-provided RAG tools)
445
445
  const shouldUseTools = !options.disableTools && this.supportsTools();
446
446
  const baseTools = shouldUseTools ? await this.getAllTools() : {};
447
- const tools = shouldUseTools
447
+ const rawTools = shouldUseTools
448
448
  ? { ...baseTools, ...(options.tools || {}) }
449
449
  : {};
450
+ // Sanitize tool schemas for Gemini proto compatibility (converts anyOf/oneOf unions to string)
451
+ const tools = Object.keys(rawTools).length > 0
452
+ ? sanitizeToolsForGemini(rawTools)
453
+ : rawTools;
450
454
  // Build message array from options with multimodal support
451
455
  // Using protected helper from BaseProvider to eliminate code duplication
452
456
  const messages = await this.buildMessagesForStream(options);
@@ -1027,6 +1031,86 @@ export class GoogleAIStudioProvider extends BaseProvider {
1027
1031
  },
1028
1032
  };
1029
1033
  }
1034
+ getDefaultEmbeddingModel() {
1035
+ return (process.env.GOOGLE_AI_EMBEDDING_MODEL ||
1036
+ process.env.GOOGLE_EMBEDDING_MODEL ||
1037
+ "gemini-embedding-001");
1038
+ }
1039
+ /**
1040
+ * Generate embeddings for text using Google AI Studio embedding models
1041
+ * @param text - The text to embed
1042
+ * @param modelName - The embedding model to use (default: gemini-embedding-001)
1043
+ * @returns Promise resolving to the embedding vector
1044
+ */
1045
+ async embed(text, modelName) {
1046
+ const embeddingModelName = modelName || this.getDefaultEmbeddingModel() || "gemini-embedding-001";
1047
+ logger.debug("Generating embedding", {
1048
+ provider: this.providerName,
1049
+ model: embeddingModelName,
1050
+ textLength: text.length,
1051
+ });
1052
+ try {
1053
+ const apiKey = this.getApiKey();
1054
+ const google = createGoogleGenerativeAI({ apiKey });
1055
+ const embeddingModel = google.textEmbeddingModel(embeddingModelName);
1056
+ const result = await embed({
1057
+ model: embeddingModel,
1058
+ value: text,
1059
+ });
1060
+ logger.debug("Embedding generated successfully", {
1061
+ provider: this.providerName,
1062
+ model: embeddingModelName,
1063
+ embeddingDimension: result.embedding.length,
1064
+ });
1065
+ return result.embedding;
1066
+ }
1067
+ catch (error) {
1068
+ logger.error("Embedding generation failed", {
1069
+ error: error instanceof Error ? error.message : String(error),
1070
+ model: embeddingModelName,
1071
+ textLength: text.length,
1072
+ });
1073
+ throw this.handleProviderError(error);
1074
+ }
1075
+ }
1076
+ /**
1077
+ * Generate embeddings for multiple texts in a single batch
1078
+ * @param texts - The texts to embed
1079
+ * @param modelName - The embedding model to use (default: gemini-embedding-001)
1080
+ * @returns Promise resolving to an array of embedding vectors
1081
+ */
1082
+ async embedMany(texts, modelName) {
1083
+ const embeddingModelName = modelName || this.getDefaultEmbeddingModel() || "gemini-embedding-001";
1084
+ logger.debug("Generating batch embeddings", {
1085
+ provider: this.providerName,
1086
+ model: embeddingModelName,
1087
+ count: texts.length,
1088
+ });
1089
+ try {
1090
+ const apiKey = this.getApiKey();
1091
+ const google = createGoogleGenerativeAI({ apiKey });
1092
+ const embeddingModel = google.textEmbeddingModel(embeddingModelName);
1093
+ const result = await embedMany({
1094
+ model: embeddingModel,
1095
+ values: texts,
1096
+ });
1097
+ logger.debug("Batch embeddings generated successfully", {
1098
+ provider: this.providerName,
1099
+ model: embeddingModelName,
1100
+ count: result.embeddings.length,
1101
+ embeddingDimension: result.embeddings[0]?.length,
1102
+ });
1103
+ return result.embeddings;
1104
+ }
1105
+ catch (error) {
1106
+ logger.error("Batch embedding generation failed", {
1107
+ error: error instanceof Error ? error.message : String(error),
1108
+ model: embeddingModelName,
1109
+ count: texts.length,
1110
+ });
1111
+ throw this.handleProviderError(error);
1112
+ }
1113
+ }
1030
1114
  getApiKey() {
1031
1115
  const apiKey = process.env.GOOGLE_AI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY;
1032
1116
  if (!apiKey) {
@@ -8,7 +8,7 @@
8
8
  * This module extracts the functions that are duplicated between the two
9
9
  * providers so they can share a single implementation.
10
10
  */
11
- import type { Tool } from "ai";
11
+ import { type Tool } from "ai";
12
12
  import type { ThinkingConfig } from "../utils/thinkingConfig.js";
13
13
  /** A single native @google/genai function declaration. */
14
14
  export type NativeFunctionDeclaration = {
@@ -44,6 +44,29 @@ export type CollectedChunkResult = {
44
44
  inputTokens: number;
45
45
  outputTokens: number;
46
46
  };
47
+ /**
48
+ * Sanitize a JSON Schema for Gemini's proto-based API.
49
+ *
50
+ * Gemini cannot handle `anyOf`/`oneOf` union types in function declarations
51
+ * because its proto format expects a single `type` field, not a list of types.
52
+ * This function recursively converts unions to `string` type (the most
53
+ * permissive primitive that can represent any value as text).
54
+ *
55
+ * Also removes `$schema`, `additionalProperties`, and `default` keys that
56
+ * Gemini's proto format doesn't support.
57
+ */
58
+ export declare function sanitizeSchemaForGemini(schema: Record<string, unknown>): Record<string, unknown>;
59
+ /**
60
+ * Sanitize Vercel AI SDK tools for Gemini compatibility.
61
+ *
62
+ * For the Vercel AI SDK path (non-native), tool parameters are Zod schemas that
63
+ * get converted to JSON Schema internally by @ai-sdk/google. This conversion
64
+ * doesn't sanitize union types (anyOf/oneOf), causing Gemini proto errors.
65
+ *
66
+ * This function pre-converts each tool's Zod parameters to sanitized JSON Schema
67
+ * and re-wraps with the Vercel AI SDK's jsonSchema() helper.
68
+ */
69
+ export declare function sanitizeToolsForGemini(tools: Record<string, Tool>): Record<string, Tool>;
47
70
  /**
48
71
  * Convert Vercel AI SDK tools to @google/genai FunctionDeclarations and an execute map.
49
72
  *
@@ -9,11 +9,127 @@
9
9
  * providers so they can share a single implementation.
10
10
  */
11
11
  import { randomUUID } from "node:crypto";
12
+ import { jsonSchema as aiJsonSchema, tool as createAISDKTool, } from "ai";
12
13
  import { DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIES, } from "../core/constants.js";
13
14
  import { logger } from "../utils/logger.js";
14
15
  import { convertZodToJsonSchema, inlineJsonSchema, isZodSchema, } from "../utils/schemaConversion.js";
15
16
  import { createNativeThinkingConfig } from "../utils/thinkingConfig.js";
16
17
  // ── Functions ──
18
+ /**
19
+ * Sanitize a JSON Schema for Gemini's proto-based API.
20
+ *
21
+ * Gemini cannot handle `anyOf`/`oneOf` union types in function declarations
22
+ * because its proto format expects a single `type` field, not a list of types.
23
+ * This function recursively converts unions to `string` type (the most
24
+ * permissive primitive that can represent any value as text).
25
+ *
26
+ * Also removes `$schema`, `additionalProperties`, and `default` keys that
27
+ * Gemini's proto format doesn't support.
28
+ */
29
+ export function sanitizeSchemaForGemini(schema) {
30
+ // If this node has anyOf/oneOf, collapse to string type
31
+ if (Array.isArray(schema.anyOf) || Array.isArray(schema.oneOf)) {
32
+ const unionKey = schema.anyOf ? "anyOf" : "oneOf";
33
+ const variants = schema[unionKey];
34
+ // Check if it's a nullable union (e.g., anyOf: [{type: "string"}, {type: "null"}])
35
+ const nonNullVariants = variants.filter((v) => v.type !== "null" && v.type !== "undefined");
36
+ if (nonNullVariants.length === 1) {
37
+ // Simple nullable — use the non-null type with nullable flag
38
+ const base = sanitizeSchemaForGemini({ ...nonNullVariants[0] });
39
+ base.nullable = true;
40
+ if (schema.description) {
41
+ base.description = schema.description;
42
+ }
43
+ return base;
44
+ }
45
+ // Multi-type union — collapse to string with description noting the original types
46
+ const types = nonNullVariants.map((v) => v.type || "unknown").join(" | ");
47
+ const result = { type: "string" };
48
+ const desc = schema.description
49
+ ? `${schema.description} (accepts: ${types})`
50
+ : `Value as string (accepts: ${types})`;
51
+ result.description = desc;
52
+ if (variants.some((v) => v.type === "null")) {
53
+ result.nullable = true;
54
+ }
55
+ return result;
56
+ }
57
+ const result = {};
58
+ for (const [key, value] of Object.entries(schema)) {
59
+ // Skip keys unsupported by Gemini proto format
60
+ if (key === "$schema" ||
61
+ key === "additionalProperties" ||
62
+ key === "default") {
63
+ continue;
64
+ }
65
+ if (key === "properties" && value && typeof value === "object") {
66
+ const properties = {};
67
+ for (const [propName, propSchema] of Object.entries(value)) {
68
+ if (propSchema && typeof propSchema === "object") {
69
+ properties[propName] = sanitizeSchemaForGemini(propSchema);
70
+ }
71
+ else {
72
+ properties[propName] = propSchema;
73
+ }
74
+ }
75
+ result[key] = properties;
76
+ }
77
+ else if (key === "items" && value && typeof value === "object") {
78
+ if (Array.isArray(value)) {
79
+ result[key] = value.map((item) => item && typeof item === "object"
80
+ ? sanitizeSchemaForGemini(item)
81
+ : item);
82
+ }
83
+ else {
84
+ result[key] = sanitizeSchemaForGemini(value);
85
+ }
86
+ }
87
+ else {
88
+ result[key] = value;
89
+ }
90
+ }
91
+ return result;
92
+ }
93
+ /**
94
+ * Sanitize Vercel AI SDK tools for Gemini compatibility.
95
+ *
96
+ * For the Vercel AI SDK path (non-native), tool parameters are Zod schemas that
97
+ * get converted to JSON Schema internally by @ai-sdk/google. This conversion
98
+ * doesn't sanitize union types (anyOf/oneOf), causing Gemini proto errors.
99
+ *
100
+ * This function pre-converts each tool's Zod parameters to sanitized JSON Schema
101
+ * and re-wraps with the Vercel AI SDK's jsonSchema() helper.
102
+ */
103
+ export function sanitizeToolsForGemini(tools) {
104
+ const sanitized = {};
105
+ for (const [name, tool] of Object.entries(tools)) {
106
+ try {
107
+ const params = tool.parameters;
108
+ if (params &&
109
+ typeof params === "object" &&
110
+ "_def" in params &&
111
+ typeof params.parse === "function") {
112
+ const rawJsonSchema = convertZodToJsonSchema(params);
113
+ const inlined = inlineJsonSchema(rawJsonSchema);
114
+ const sanitizedSchema = sanitizeSchemaForGemini(inlined);
115
+ sanitized[name] = createAISDKTool({
116
+ description: tool.description || `Tool: ${name}`,
117
+ parameters: aiJsonSchema(sanitizedSchema),
118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
+ execute: tool.execute,
120
+ });
121
+ }
122
+ else {
123
+ sanitized[name] = tool;
124
+ }
125
+ }
126
+ catch (error) {
127
+ logger.warn(`[Gemini] Failed to sanitize tool "${name}", using original`, { error: error instanceof Error ? error.message : String(error) });
128
+ sanitized[name] = tool;
129
+ }
130
+ }
131
+ return sanitized;
132
+ }
17
133
  /**
18
134
  * Convert Vercel AI SDK tools to @google/genai FunctionDeclarations and an execute map.
19
135
  *
@@ -38,10 +154,7 @@ export function buildNativeToolDeclarations(tools) {
38
154
  else {
39
155
  rawSchema = { type: "object", properties: {} };
40
156
  }
41
- decl.parametersJsonSchema = inlineJsonSchema(rawSchema);
42
- if (decl.parametersJsonSchema.$schema) {
43
- delete decl.parametersJsonSchema.$schema;
44
- }
157
+ decl.parametersJsonSchema = sanitizeSchemaForGemini(inlineJsonSchema(rawSchema));
45
158
  }
46
159
  functionDeclarations.push(decl);
47
160
  if (tool.execute) {
@@ -298,6 +298,13 @@ export declare class GoogleVertexProvider extends BaseProvider {
298
298
  * @returns Promise resolving to the embedding vector
299
299
  */
300
300
  embed(text: string, modelName?: string): Promise<number[]>;
301
+ /**
302
+ * Generate embeddings for multiple texts in a single batch
303
+ * @param texts - The texts to embed
304
+ * @param modelName - The embedding model to use (default: text-embedding-004)
305
+ * @returns Promise resolving to an array of embedding vectors
306
+ */
307
+ embedMany(texts: string[], modelName?: string): Promise<number[][]>;
301
308
  /**
302
309
  * Get model suggestions when a model is not found
303
310
  */
@@ -1,6 +1,6 @@
1
1
  import { createVertex, } from "@ai-sdk/google-vertex";
2
2
  import { createVertexAnthropic, } from "@ai-sdk/google-vertex/anthropic";
3
- import { Output, streamText, } from "ai";
3
+ import { embed, embedMany, Output, streamText, } from "ai";
4
4
  import { trace, SpanKind, SpanStatusCode } from "@opentelemetry/api";
5
5
  import dns from "node:dns";
6
6
  import fs from "fs";
@@ -22,7 +22,7 @@ import { tracers, ATTR, withClientSpan } from "../telemetry/index.js";
22
22
  import { createGoogleAuthConfig, createVertexProjectConfig, validateApiKey, } from "../utils/providerConfig.js";
23
23
  import { convertZodToJsonSchema, inlineJsonSchema, } from "../utils/schemaConversion.js";
24
24
  import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
25
- import { buildNativeToolDeclarations, buildNativeConfig, computeMaxSteps as computeMaxStepsShared, collectStreamChunks, extractTextFromParts, executeNativeToolCalls, handleMaxStepsTermination, pushModelResponseToHistory, } from "./googleNativeGemini3.js";
25
+ import { buildNativeToolDeclarations, buildNativeConfig, computeMaxSteps as computeMaxStepsShared, collectStreamChunks, extractTextFromParts, executeNativeToolCalls, handleMaxStepsTermination, pushModelResponseToHistory, sanitizeToolsForGemini, } from "./googleNativeGemini3.js";
26
26
  // Import proper types for multimodal message handling
27
27
  // Keep-alive note: Node.js native fetch and undici (used by createProxyFetch)
28
28
  // handle HTTP keep-alive internally. The fetchWithRetry wrapper in proxyFetch.ts
@@ -816,9 +816,14 @@ export class GoogleVertexProvider extends BaseProvider {
816
816
  // Get all available tools (direct + MCP + external + user-provided RAG tools) for streaming
817
817
  const shouldUseTools = !options.disableTools && this.supportsTools();
818
818
  const baseStreamTools = shouldUseTools ? await this.getAllTools() : {};
819
- const tools = shouldUseTools
819
+ const rawTools = shouldUseTools
820
820
  ? { ...baseStreamTools, ...(options.tools || {}) }
821
821
  : {};
822
+ // Only sanitize for Gemini models (not Anthropic/Claude models routed through Vertex)
823
+ const isAnthropic = isAnthropicModel(gemini3CheckModelName);
824
+ const tools = Object.keys(rawTools).length > 0 && !isAnthropic
825
+ ? sanitizeToolsForGemini(rawTools)
826
+ : rawTools;
822
827
  logger.debug(`${functionTag}: Tools for streaming`, {
823
828
  shouldUseTools,
824
829
  baseToolCount: Object.keys(baseStreamTools).length,
@@ -899,6 +904,12 @@ export class GoogleVertexProvider extends BaseProvider {
899
904
  };
900
905
  if (analysisSchema) {
901
906
  try {
907
+ // Gemini cannot use tools and JSON schema simultaneously
908
+ if (!isAnthropic) {
909
+ delete streamOptions.tools;
910
+ delete streamOptions.toolChoice;
911
+ delete streamOptions.maxSteps;
912
+ }
902
913
  streamOptions = {
903
914
  ...streamOptions,
904
915
  experimental_output: Output.object({
@@ -2691,8 +2702,6 @@ export class GoogleVertexProvider extends BaseProvider {
2691
2702
  textLength: text.length,
2692
2703
  });
2693
2704
  try {
2694
- // Create embedding model using the AI SDK
2695
- const { embed } = await import("ai");
2696
2705
  // Create the Vertex provider with current settings
2697
2706
  const vertexSettings = await createVertexSettings(this.location);
2698
2707
  const vertex = createVertex(vertexSettings);
@@ -2719,6 +2728,44 @@ export class GoogleVertexProvider extends BaseProvider {
2719
2728
  throw this.handleProviderError(error);
2720
2729
  }
2721
2730
  }
2731
+ /**
2732
+ * Generate embeddings for multiple texts in a single batch
2733
+ * @param texts - The texts to embed
2734
+ * @param modelName - The embedding model to use (default: text-embedding-004)
2735
+ * @returns Promise resolving to an array of embedding vectors
2736
+ */
2737
+ async embedMany(texts, modelName) {
2738
+ const embeddingModelName = modelName || this.getDefaultEmbeddingModel() || "text-embedding-004";
2739
+ logger.debug("Generating batch embeddings", {
2740
+ provider: this.providerName,
2741
+ model: embeddingModelName,
2742
+ count: texts.length,
2743
+ });
2744
+ try {
2745
+ const vertexSettings = await createVertexSettings(this.location);
2746
+ const vertex = createVertex(vertexSettings);
2747
+ const embeddingModel = vertex.textEmbeddingModel(embeddingModelName);
2748
+ const result = await embedMany({
2749
+ model: embeddingModel,
2750
+ values: texts,
2751
+ });
2752
+ logger.debug("Batch embeddings generated successfully", {
2753
+ provider: this.providerName,
2754
+ model: embeddingModelName,
2755
+ count: result.embeddings.length,
2756
+ embeddingDimension: result.embeddings[0]?.length,
2757
+ });
2758
+ return result.embeddings;
2759
+ }
2760
+ catch (error) {
2761
+ logger.error("Batch embedding generation failed", {
2762
+ error: error instanceof Error ? error.message : String(error),
2763
+ model: embeddingModelName,
2764
+ count: texts.length,
2765
+ });
2766
+ throw this.handleProviderError(error);
2767
+ }
2768
+ }
2722
2769
  /**
2723
2770
  * Get model suggestions when a model is not found
2724
2771
  */
@@ -59,5 +59,12 @@ export declare class OpenAIProvider extends BaseProvider {
59
59
  * @returns Promise resolving to the embedding vector
60
60
  */
61
61
  embed(text: string, modelName?: string): Promise<number[]>;
62
+ /**
63
+ * Generate embeddings for multiple texts in a single batch
64
+ * @param texts - The texts to embed
65
+ * @param modelName - The embedding model to use (default: text-embedding-3-small)
66
+ * @returns Promise resolving to an array of embedding vectors
67
+ */
68
+ embedMany(texts: string[], modelName?: string): Promise<number[][]>;
62
69
  }
63
70
  export default OpenAIProvider;