@juspay/neurolink 9.18.0 → 9.19.1
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 +12 -0
- package/dist/context/stages/slidingWindowTruncator.js +2 -2
- package/dist/lib/context/stages/slidingWindowTruncator.js +2 -2
- package/dist/lib/providers/googleAiStudio.js +6 -2
- package/dist/lib/providers/googleNativeGemini3.d.ts +24 -1
- package/dist/lib/providers/googleNativeGemini3.js +117 -4
- package/dist/lib/providers/googleVertex.js +13 -2
- package/dist/providers/googleAiStudio.js +6 -2
- package/dist/providers/googleNativeGemini3.d.ts +24 -1
- package/dist/providers/googleNativeGemini3.js +117 -4
- package/dist/providers/googleVertex.js +13 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [9.19.1](https://github.com/juspay/neurolink/compare/v9.19.0...v9.19.1) (2026-03-07)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
- **(docs):** fix search results overlap by enabling dynamic row measurement ([8117541](https://github.com/juspay/neurolink/commit/81175416db780da9de7157f3f057c5b0c78dd7b5))
|
|
6
|
+
|
|
7
|
+
## [9.19.0](https://github.com/juspay/neurolink/compare/v9.18.0...v9.19.0) (2026-03-07)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **(landing):** nervous system visualization redesign with SEO and SDK fixes ([d410a49](https://github.com/juspay/neurolink/commit/d410a49546e9d55bce319f41d2b11f955350becc))
|
|
12
|
+
|
|
1
13
|
## [9.18.0](https://github.com/juspay/neurolink/compare/v9.17.2...v9.18.0) (2026-03-07)
|
|
2
14
|
|
|
3
15
|
### Features
|
|
@@ -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: "
|
|
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: "
|
|
165
|
+
role: "user",
|
|
166
166
|
content: TRUNCATION_MARKER_CONTENT,
|
|
167
167
|
timestamp: new Date().toISOString(),
|
|
168
168
|
metadata: { isSummary: false, truncated: true },
|
|
@@ -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: "
|
|
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: "
|
|
165
|
+
role: "user",
|
|
166
166
|
content: TRUNCATION_MARKER_CONTENT,
|
|
167
167
|
timestamp: new Date().toISOString(),
|
|
168
168
|
metadata: { isSummary: false, truncated: true },
|
|
@@ -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
|
|
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);
|
|
@@ -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
|
|
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) {
|
|
@@ -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
|
|
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({
|
|
@@ -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
|
|
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);
|
|
@@ -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
|
|
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) {
|
|
@@ -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
|
|
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({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.19.1",
|
|
4
4
|
"description": "Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 13 providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Juspay Technologies",
|