@juspay/neurolink 8.26.0 → 8.27.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 (101) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +47 -25
  3. package/dist/adapters/providerImageAdapter.js +11 -0
  4. package/dist/cli/commands/config.js +16 -23
  5. package/dist/cli/commands/setup-anthropic.js +3 -26
  6. package/dist/cli/commands/setup-azure.js +3 -22
  7. package/dist/cli/commands/setup-bedrock.js +3 -26
  8. package/dist/cli/commands/setup-google-ai.js +3 -22
  9. package/dist/cli/commands/setup-mistral.js +3 -31
  10. package/dist/cli/commands/setup-openai.js +3 -22
  11. package/dist/cli/factories/commandFactory.js +32 -0
  12. package/dist/cli/factories/ollamaCommandFactory.js +5 -17
  13. package/dist/cli/loop/optionsSchema.d.ts +1 -1
  14. package/dist/cli/loop/optionsSchema.js +13 -0
  15. package/dist/config/modelSpecificPrompts.d.ts +9 -0
  16. package/dist/config/modelSpecificPrompts.js +38 -0
  17. package/dist/constants/enums.d.ts +8 -0
  18. package/dist/constants/enums.js +8 -0
  19. package/dist/constants/tokens.d.ts +25 -0
  20. package/dist/constants/tokens.js +18 -0
  21. package/dist/core/analytics.js +7 -28
  22. package/dist/core/baseProvider.js +1 -0
  23. package/dist/core/constants.d.ts +1 -0
  24. package/dist/core/constants.js +1 -0
  25. package/dist/core/modules/GenerationHandler.js +43 -5
  26. package/dist/core/streamAnalytics.d.ts +1 -0
  27. package/dist/core/streamAnalytics.js +8 -16
  28. package/dist/lib/adapters/providerImageAdapter.js +11 -0
  29. package/dist/lib/config/modelSpecificPrompts.d.ts +9 -0
  30. package/dist/lib/config/modelSpecificPrompts.js +39 -0
  31. package/dist/lib/constants/enums.d.ts +8 -0
  32. package/dist/lib/constants/enums.js +8 -0
  33. package/dist/lib/constants/tokens.d.ts +25 -0
  34. package/dist/lib/constants/tokens.js +18 -0
  35. package/dist/lib/core/analytics.js +7 -28
  36. package/dist/lib/core/baseProvider.js +1 -0
  37. package/dist/lib/core/constants.d.ts +1 -0
  38. package/dist/lib/core/constants.js +1 -0
  39. package/dist/lib/core/modules/GenerationHandler.js +43 -5
  40. package/dist/lib/core/streamAnalytics.d.ts +1 -0
  41. package/dist/lib/core/streamAnalytics.js +8 -16
  42. package/dist/lib/providers/googleAiStudio.d.ts +15 -0
  43. package/dist/lib/providers/googleAiStudio.js +659 -3
  44. package/dist/lib/providers/googleVertex.d.ts +25 -0
  45. package/dist/lib/providers/googleVertex.js +978 -3
  46. package/dist/lib/providers/sagemaker/language-model.d.ts +2 -2
  47. package/dist/lib/types/analytics.d.ts +4 -0
  48. package/dist/lib/types/cli.d.ts +16 -0
  49. package/dist/lib/types/conversation.d.ts +72 -4
  50. package/dist/lib/types/conversation.js +30 -0
  51. package/dist/lib/types/generateTypes.d.ts +135 -0
  52. package/dist/lib/types/groundingTypes.d.ts +231 -0
  53. package/dist/lib/types/groundingTypes.js +12 -0
  54. package/dist/lib/types/providers.d.ts +29 -0
  55. package/dist/lib/types/streamTypes.d.ts +54 -0
  56. package/dist/lib/utils/analyticsUtils.js +22 -2
  57. package/dist/lib/utils/errorHandling.d.ts +65 -0
  58. package/dist/lib/utils/errorHandling.js +268 -0
  59. package/dist/lib/utils/modelChoices.d.ts +82 -0
  60. package/dist/lib/utils/modelChoices.js +402 -0
  61. package/dist/lib/utils/modelDetection.d.ts +9 -0
  62. package/dist/lib/utils/modelDetection.js +81 -0
  63. package/dist/lib/utils/parameterValidation.d.ts +59 -1
  64. package/dist/lib/utils/parameterValidation.js +196 -0
  65. package/dist/lib/utils/schemaConversion.d.ts +12 -0
  66. package/dist/lib/utils/schemaConversion.js +90 -0
  67. package/dist/lib/utils/thinkingConfig.d.ts +108 -0
  68. package/dist/lib/utils/thinkingConfig.js +105 -0
  69. package/dist/lib/utils/tokenUtils.d.ts +124 -0
  70. package/dist/lib/utils/tokenUtils.js +240 -0
  71. package/dist/lib/utils/transformationUtils.js +15 -26
  72. package/dist/providers/googleAiStudio.d.ts +15 -0
  73. package/dist/providers/googleAiStudio.js +659 -3
  74. package/dist/providers/googleVertex.d.ts +25 -0
  75. package/dist/providers/googleVertex.js +978 -3
  76. package/dist/types/analytics.d.ts +4 -0
  77. package/dist/types/cli.d.ts +16 -0
  78. package/dist/types/conversation.d.ts +72 -4
  79. package/dist/types/conversation.js +30 -0
  80. package/dist/types/generateTypes.d.ts +135 -0
  81. package/dist/types/groundingTypes.d.ts +231 -0
  82. package/dist/types/groundingTypes.js +11 -0
  83. package/dist/types/providers.d.ts +29 -0
  84. package/dist/types/streamTypes.d.ts +54 -0
  85. package/dist/utils/analyticsUtils.js +22 -2
  86. package/dist/utils/errorHandling.d.ts +65 -0
  87. package/dist/utils/errorHandling.js +268 -0
  88. package/dist/utils/modelChoices.d.ts +82 -0
  89. package/dist/utils/modelChoices.js +401 -0
  90. package/dist/utils/modelDetection.d.ts +9 -0
  91. package/dist/utils/modelDetection.js +80 -0
  92. package/dist/utils/parameterValidation.d.ts +59 -1
  93. package/dist/utils/parameterValidation.js +196 -0
  94. package/dist/utils/schemaConversion.d.ts +12 -0
  95. package/dist/utils/schemaConversion.js +90 -0
  96. package/dist/utils/thinkingConfig.d.ts +108 -0
  97. package/dist/utils/thinkingConfig.js +104 -0
  98. package/dist/utils/tokenUtils.d.ts +124 -0
  99. package/dist/utils/tokenUtils.js +239 -0
  100. package/dist/utils/transformationUtils.js +15 -26
  101. package/package.json +4 -3
@@ -1,18 +1,23 @@
1
1
  import { createVertex, } from "@ai-sdk/google-vertex";
2
2
  import { createVertexAnthropic, } from "@ai-sdk/google-vertex/anthropic";
3
3
  import { streamText, Output, } from "ai";
4
- import { AIProviderName } from "../constants/enums.js";
4
+ import { AIProviderName, ErrorCategory, ErrorSeverity, } from "../constants/enums.js";
5
+ import { NeuroLinkError, ERROR_CODES } from "../utils/errorHandling.js";
5
6
  import { BaseProvider } from "../core/baseProvider.js";
6
7
  import { logger } from "../utils/logger.js";
7
8
  import { createTimeoutController, TimeoutError } from "../utils/timeout.js";
8
- import { DEFAULT_MAX_STEPS } from "../core/constants.js";
9
+ import { DEFAULT_MAX_STEPS, DEFAULT_TOOL_MAX_RETRIES, } from "../core/constants.js";
9
10
  import { ModelConfigurationManager } from "../core/modelConfiguration.js";
10
11
  import { validateApiKey, createVertexProjectConfig, createGoogleAuthConfig, } from "../utils/providerConfig.js";
12
+ import { isGemini3Model } from "../utils/modelDetection.js";
13
+ import { convertZodToJsonSchema, inlineJsonSchema, } from "../utils/schemaConversion.js";
14
+ import { createNativeThinkingConfig } from "../utils/thinkingConfig.js";
11
15
  import fs from "fs";
12
16
  import path from "path";
13
17
  import os from "os";
14
18
  import dns from "dns";
15
19
  import { createProxyFetch } from "../proxy/proxyFetch.js";
20
+ import { FileDetector } from "../utils/fileDetector.js";
16
21
  // Import proper types for multimodal message handling
17
22
  // Enhanced Anthropic support with direct imports
18
23
  // Using the dual provider architecture from Vercel AI SDK
@@ -60,6 +65,11 @@ const createVertexSettings = async (region) => {
60
65
  // instead of {region}-aiplatform.googleapis.com
61
66
  if (location === "global") {
62
67
  baseSettings.baseURL = `https://aiplatform.googleapis.com/v1/projects/${project}/locations/global/publishers/google`;
68
+ logger.debug("[GoogleVertexProvider] Using global endpoint", {
69
+ baseURL: baseSettings.baseURL,
70
+ location,
71
+ project,
72
+ });
63
73
  }
64
74
  // 🎯 OPTION 2: Create credentials file from environment variables at runtime
65
75
  // This solves the problem where GOOGLE_APPLICATION_CREDENTIALS exists in ZSHRC locally
@@ -293,6 +303,11 @@ export class GoogleVertexProvider extends BaseProvider {
293
303
  // Initialize Google Cloud configuration
294
304
  this.projectId = getVertexProjectId();
295
305
  this.location = region || getVertexLocation();
306
+ logger.debug("[GoogleVertexProvider] Constructor initialized", {
307
+ regionParam: region,
308
+ resolvedLocation: this.location,
309
+ projectId: this.projectId,
310
+ });
296
311
  logger.debug("Google Vertex AI BaseProvider v2 initialized", {
297
312
  modelName: this.modelName,
298
313
  projectId: this.projectId,
@@ -629,6 +644,31 @@ export class GoogleVertexProvider extends BaseProvider {
629
644
  this.validateStreamOptions(options);
630
645
  }
631
646
  async executeStream(options, analysisSchema) {
647
+ // Check if this is a Gemini 3 model with tools - use native SDK for thought_signature
648
+ const gemini3CheckModelName = options.model || this.modelName || getDefaultVertexModel();
649
+ // Check for tools from options AND from SDK (MCP tools)
650
+ // Need to check early if we should route to native SDK
651
+ const gemini3CheckShouldUseTools = !options.disableTools && this.supportsTools();
652
+ const optionTools = options.tools || {};
653
+ const sdkTools = gemini3CheckShouldUseTools ? await this.getAllTools() : {};
654
+ const combinedToolCount = Object.keys(optionTools).length + Object.keys(sdkTools).length;
655
+ const hasTools = gemini3CheckShouldUseTools && combinedToolCount > 0;
656
+ if (isGemini3Model(gemini3CheckModelName) && hasTools) {
657
+ // Process CSV files before routing to native SDK (bypasses normal message builder)
658
+ const processedOptions = await this.processCSVFilesForNativeSDK(options);
659
+ // Merge SDK tools into options for native SDK path
660
+ const mergedOptions = {
661
+ ...processedOptions,
662
+ tools: { ...sdkTools, ...optionTools },
663
+ };
664
+ logger.info("[GoogleVertex] Routing Gemini 3 to native SDK for tool calling", {
665
+ model: gemini3CheckModelName,
666
+ optionToolCount: Object.keys(optionTools).length,
667
+ sdkToolCount: Object.keys(sdkTools).length,
668
+ totalToolCount: combinedToolCount,
669
+ });
670
+ return this.executeNativeGemini3Stream(mergedOptions);
671
+ }
632
672
  // Initialize stream execution tracking
633
673
  const functionTag = "GoogleVertexProvider.executeStream";
634
674
  let chunkCount = 0;
@@ -672,6 +712,24 @@ export class GoogleVertexProvider extends BaseProvider {
672
712
  }),
673
713
  abortSignal: timeoutController?.controller.signal,
674
714
  experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
715
+ // Gemini 3: use thinkingLevel via providerOptions (Vertex AI)
716
+ // Gemini 2.5: use thinkingBudget via providerOptions
717
+ ...(options.thinkingConfig?.enabled && {
718
+ providerOptions: {
719
+ vertex: {
720
+ thinkingConfig: {
721
+ ...(options.thinkingConfig.thinkingLevel && {
722
+ thinkingLevel: options.thinkingConfig.thinkingLevel,
723
+ }),
724
+ ...(options.thinkingConfig.budgetTokens &&
725
+ !options.thinkingConfig.thinkingLevel && {
726
+ thinkingBudget: options.thinkingConfig.budgetTokens,
727
+ }),
728
+ includeThoughts: true,
729
+ },
730
+ },
731
+ },
732
+ }),
675
733
  onError: (event) => {
676
734
  const error = event.error;
677
735
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -745,6 +803,893 @@ export class GoogleVertexProvider extends BaseProvider {
745
803
  throw this.handleProviderError(error);
746
804
  }
747
805
  }
806
+ /**
807
+ * Create @google/genai client configured for Vertex AI
808
+ */
809
+ async createVertexGenAIClient(regionOverride) {
810
+ const project = getVertexProjectId();
811
+ const location = regionOverride || this.location || getVertexLocation();
812
+ const mod = await import("@google/genai");
813
+ const ctor = mod.GoogleGenAI;
814
+ if (!ctor) {
815
+ throw new NeuroLinkError({
816
+ code: ERROR_CODES.INVALID_CONFIGURATION,
817
+ message: "@google/genai does not export GoogleGenAI",
818
+ category: ErrorCategory.CONFIGURATION,
819
+ severity: ErrorSeverity.CRITICAL,
820
+ retriable: false,
821
+ context: { module: "@google/genai", expectedExport: "GoogleGenAI" },
822
+ });
823
+ }
824
+ const Ctor = ctor;
825
+ // Use vertexai mode with project and location
826
+ return new Ctor({
827
+ vertexai: true,
828
+ project,
829
+ location,
830
+ });
831
+ }
832
+ /**
833
+ * Execute stream using native @google/genai SDK for Gemini 3 models on Vertex AI
834
+ * This bypasses @ai-sdk/google-vertex to properly handle thought_signature
835
+ */
836
+ async executeNativeGemini3Stream(options) {
837
+ const client = await this.createVertexGenAIClient(options.region);
838
+ const modelName = options.model || this.modelName || getDefaultVertexModel();
839
+ const effectiveLocation = options.region || this.location || getVertexLocation();
840
+ logger.debug("[GoogleVertex] Using native @google/genai for Gemini 3", {
841
+ model: modelName,
842
+ hasTools: !!options.tools && Object.keys(options.tools).length > 0,
843
+ project: this.projectId,
844
+ location: effectiveLocation,
845
+ });
846
+ const contents = [];
847
+ // Build user message parts - start with text
848
+ const userParts = [{ text: options.input.text }];
849
+ // Add PDF files as inlineData parts if present
850
+ // Cast input to access multimodal properties that may exist at runtime
851
+ const multimodalInput = options.input;
852
+ if (multimodalInput?.pdfFiles && multimodalInput.pdfFiles.length > 0) {
853
+ logger.debug(`[GoogleVertex] Processing ${multimodalInput.pdfFiles.length} PDF file(s) for native stream`);
854
+ for (const pdfFile of multimodalInput.pdfFiles) {
855
+ let pdfBuffer;
856
+ if (typeof pdfFile === "string") {
857
+ // Check if it's a file path
858
+ if (fs.existsSync(pdfFile)) {
859
+ pdfBuffer = fs.readFileSync(pdfFile);
860
+ }
861
+ else {
862
+ // Assume it's already base64 encoded
863
+ pdfBuffer = Buffer.from(pdfFile, "base64");
864
+ }
865
+ }
866
+ else {
867
+ pdfBuffer = pdfFile;
868
+ }
869
+ // Convert to base64 for the native SDK
870
+ const base64Data = pdfBuffer.toString("base64");
871
+ userParts.push({
872
+ inlineData: {
873
+ mimeType: "application/pdf",
874
+ data: base64Data,
875
+ },
876
+ });
877
+ }
878
+ }
879
+ // Add images as inlineData parts if present
880
+ if (multimodalInput?.images && multimodalInput.images.length > 0) {
881
+ logger.debug(`[GoogleVertex] Processing ${multimodalInput.images.length} image(s) for native stream`);
882
+ for (const image of multimodalInput.images) {
883
+ let imageBuffer;
884
+ let mimeType = "image/jpeg"; // Default
885
+ if (typeof image === "string") {
886
+ if (fs.existsSync(image)) {
887
+ imageBuffer = fs.readFileSync(image);
888
+ // Detect mime type from extension
889
+ const ext = path.extname(image).toLowerCase();
890
+ if (ext === ".png") {
891
+ mimeType = "image/png";
892
+ }
893
+ else if (ext === ".gif") {
894
+ mimeType = "image/gif";
895
+ }
896
+ else if (ext === ".webp") {
897
+ mimeType = "image/webp";
898
+ }
899
+ }
900
+ else if (image.startsWith("data:")) {
901
+ // Handle data URL
902
+ const matches = image.match(/^data:([^;]+);base64,(.+)$/);
903
+ if (matches) {
904
+ mimeType = matches[1];
905
+ imageBuffer = Buffer.from(matches[2], "base64");
906
+ }
907
+ else {
908
+ continue; // Skip invalid data URL
909
+ }
910
+ }
911
+ else {
912
+ // Assume base64 string
913
+ imageBuffer = Buffer.from(image, "base64");
914
+ }
915
+ }
916
+ else {
917
+ imageBuffer = image;
918
+ }
919
+ const base64Data = imageBuffer.toString("base64");
920
+ userParts.push({
921
+ inlineData: {
922
+ mimeType,
923
+ data: base64Data,
924
+ },
925
+ });
926
+ }
927
+ }
928
+ contents.push({
929
+ role: "user",
930
+ parts: userParts,
931
+ });
932
+ let tools;
933
+ const executeMap = new Map();
934
+ if (options.tools &&
935
+ Object.keys(options.tools).length > 0 &&
936
+ !options.disableTools) {
937
+ const functionDeclarations = [];
938
+ for (const [name, tool] of Object.entries(options.tools)) {
939
+ const decl = {
940
+ name,
941
+ description: tool.description || `Tool: ${name}`,
942
+ };
943
+ if (tool.parameters) {
944
+ // Convert and inline schema to resolve $ref/definitions
945
+ const rawSchema = convertZodToJsonSchema(tool.parameters);
946
+ decl.parametersJsonSchema = inlineJsonSchema(rawSchema);
947
+ // Remove $schema if present - @google/genai doesn't need it
948
+ if (decl.parametersJsonSchema.$schema) {
949
+ delete decl.parametersJsonSchema.$schema;
950
+ }
951
+ }
952
+ functionDeclarations.push(decl);
953
+ if (tool.execute) {
954
+ executeMap.set(name, tool.execute);
955
+ }
956
+ }
957
+ tools = [{ functionDeclarations }];
958
+ logger.debug("[GoogleVertex] Converted tools for native SDK", {
959
+ toolCount: functionDeclarations.length,
960
+ toolNames: functionDeclarations.map((t) => t.name),
961
+ });
962
+ }
963
+ // Build config
964
+ const config = {
965
+ temperature: options.temperature ?? 1.0, // Gemini 3 requires 1.0 for tool calling
966
+ maxOutputTokens: options.maxTokens,
967
+ };
968
+ if (tools) {
969
+ config.tools = tools;
970
+ }
971
+ if (options.systemPrompt) {
972
+ config.systemInstruction = options.systemPrompt;
973
+ }
974
+ // Add thinking config for Gemini 3
975
+ const nativeThinkingConfig = createNativeThinkingConfig(options.thinkingConfig);
976
+ if (nativeThinkingConfig) {
977
+ config.thinkingConfig = nativeThinkingConfig;
978
+ }
979
+ // Add JSON output format support for native SDK stream
980
+ // Note: Combining tools + schema may have limitations with Gemini models
981
+ const streamOptions = options;
982
+ if (streamOptions.output?.format === "json" || streamOptions.schema) {
983
+ config.responseMimeType = "application/json";
984
+ // Convert schema to JSON schema format for the native SDK
985
+ if (streamOptions.schema) {
986
+ const rawSchema = convertZodToJsonSchema(streamOptions.schema);
987
+ const inlinedSchema = inlineJsonSchema(rawSchema);
988
+ // Remove $schema if present - @google/genai doesn't need it
989
+ if (inlinedSchema.$schema) {
990
+ delete inlinedSchema.$schema;
991
+ }
992
+ config.responseSchema = inlinedSchema;
993
+ logger.debug("[GoogleVertex] Added responseSchema for JSON output (stream)", {
994
+ schemaKeys: Object.keys(inlinedSchema),
995
+ });
996
+ }
997
+ }
998
+ const startTime = Date.now();
999
+ // Ensure maxSteps is a valid positive integer to prevent infinite loops
1000
+ const rawMaxSteps = options.maxSteps || DEFAULT_MAX_STEPS;
1001
+ const maxSteps = Number.isFinite(rawMaxSteps) && rawMaxSteps > 0
1002
+ ? Math.min(Math.floor(rawMaxSteps), 100) // Cap at 100 for safety
1003
+ : Math.min(DEFAULT_MAX_STEPS, 100);
1004
+ const currentContents = [...contents];
1005
+ let finalText = "";
1006
+ let lastStepText = ""; // Track text from last step for maxSteps termination
1007
+ const allToolCalls = [];
1008
+ let step = 0;
1009
+ // Track failed tools to prevent infinite retry loops
1010
+ // Key: tool name, Value: { count: retry attempts, lastError: error message }
1011
+ const failedTools = new Map();
1012
+ // Track token usage across all steps
1013
+ // promptTokenCount is typically in the final chunk, candidatesTokenCount accumulates
1014
+ let totalInputTokens = 0;
1015
+ let totalOutputTokens = 0;
1016
+ // Agentic loop for tool calling
1017
+ while (step < maxSteps) {
1018
+ step++;
1019
+ logger.debug(`[GoogleVertex] Native SDK step ${step}/${maxSteps}`);
1020
+ try {
1021
+ const stream = await client.models.generateContentStream({
1022
+ model: modelName,
1023
+ contents: currentContents,
1024
+ config,
1025
+ });
1026
+ const stepFunctionCalls = [];
1027
+ // Capture raw response parts including thoughtSignature
1028
+ const rawResponseParts = [];
1029
+ for await (const chunk of stream) {
1030
+ // Extract raw parts from candidates FIRST
1031
+ // This avoids using chunk.text which triggers SDK warning when
1032
+ // non-text parts (thoughtSignature, functionCall) are present
1033
+ const chunkRecord = chunk;
1034
+ const candidates = chunkRecord.candidates;
1035
+ const firstCandidate = candidates?.[0];
1036
+ const chunkContent = firstCandidate?.content;
1037
+ if (chunkContent && Array.isArray(chunkContent.parts)) {
1038
+ rawResponseParts.push(...chunkContent.parts);
1039
+ }
1040
+ if (chunk.functionCalls) {
1041
+ stepFunctionCalls.push(...chunk.functionCalls);
1042
+ }
1043
+ // Extract usage metadata from chunk
1044
+ // promptTokenCount is typically in the final chunk, candidatesTokenCount accumulates
1045
+ const usageMetadata = chunkRecord.usageMetadata;
1046
+ if (usageMetadata) {
1047
+ // Take the latest promptTokenCount (usually only in final chunk)
1048
+ if (usageMetadata.promptTokenCount !== undefined &&
1049
+ usageMetadata.promptTokenCount > 0) {
1050
+ totalInputTokens = usageMetadata.promptTokenCount;
1051
+ }
1052
+ // Take the latest candidatesTokenCount (accumulates through chunks)
1053
+ if (usageMetadata.candidatesTokenCount !== undefined &&
1054
+ usageMetadata.candidatesTokenCount > 0) {
1055
+ totalOutputTokens = usageMetadata.candidatesTokenCount;
1056
+ }
1057
+ }
1058
+ }
1059
+ // Extract text from raw parts after stream completes
1060
+ // This avoids SDK warning about non-text parts (thoughtSignature, functionCall)
1061
+ const stepText = rawResponseParts
1062
+ .filter((part) => typeof part.text === "string")
1063
+ .map((part) => part.text)
1064
+ .join("");
1065
+ // If no function calls, we're done
1066
+ if (stepFunctionCalls.length === 0) {
1067
+ finalText = stepText;
1068
+ break;
1069
+ }
1070
+ // Track the last step text for maxSteps termination
1071
+ lastStepText = stepText;
1072
+ // Execute function calls
1073
+ logger.debug(`[GoogleVertex] Executing ${stepFunctionCalls.length} function calls`);
1074
+ // Add model response with ALL parts (including thoughtSignature) to history
1075
+ // This preserves the thought_signature which is required for Gemini 3 multi-turn tool calling
1076
+ currentContents.push({
1077
+ role: "model",
1078
+ parts: rawResponseParts.length > 0
1079
+ ? rawResponseParts
1080
+ : stepFunctionCalls.map((fc) => ({
1081
+ functionCall: fc,
1082
+ })),
1083
+ });
1084
+ // Execute each function and collect responses
1085
+ const functionResponses = [];
1086
+ for (const call of stepFunctionCalls) {
1087
+ allToolCalls.push({ toolName: call.name, args: call.args });
1088
+ // Check if this tool has already exceeded retry limit
1089
+ const failedInfo = failedTools.get(call.name);
1090
+ if (failedInfo && failedInfo.count >= DEFAULT_TOOL_MAX_RETRIES) {
1091
+ logger.warn(`[GoogleVertex] Tool "${call.name}" has exceeded retry limit (${DEFAULT_TOOL_MAX_RETRIES}), skipping execution`);
1092
+ functionResponses.push({
1093
+ functionResponse: {
1094
+ name: call.name,
1095
+ response: {
1096
+ error: `TOOL_PERMANENTLY_FAILED: The tool "${call.name}" has failed ${failedInfo.count} times and will not be retried. Last error: ${failedInfo.lastError}. Please proceed without using this tool or inform the user that this functionality is unavailable.`,
1097
+ status: "permanently_failed",
1098
+ do_not_retry: true,
1099
+ },
1100
+ },
1101
+ });
1102
+ continue;
1103
+ }
1104
+ const execute = executeMap.get(call.name);
1105
+ if (execute) {
1106
+ try {
1107
+ // AI SDK Tool execute requires (args, options) - provide minimal options
1108
+ const toolOptions = {
1109
+ toolCallId: `${call.name}-${Date.now()}`,
1110
+ messages: [],
1111
+ abortSignal: undefined,
1112
+ };
1113
+ const result = await execute(call.args, toolOptions);
1114
+ functionResponses.push({
1115
+ functionResponse: { name: call.name, response: { result } },
1116
+ });
1117
+ }
1118
+ catch (error) {
1119
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1120
+ // Track this failure
1121
+ const currentFailInfo = failedTools.get(call.name) || {
1122
+ count: 0,
1123
+ lastError: "",
1124
+ };
1125
+ currentFailInfo.count++;
1126
+ currentFailInfo.lastError = errorMessage;
1127
+ failedTools.set(call.name, currentFailInfo);
1128
+ logger.warn(`[GoogleVertex] Tool "${call.name}" failed (attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}): ${errorMessage}`);
1129
+ // Determine if this is a permanent failure
1130
+ const isPermanentFailure = currentFailInfo.count >= DEFAULT_TOOL_MAX_RETRIES;
1131
+ functionResponses.push({
1132
+ functionResponse: {
1133
+ name: call.name,
1134
+ response: {
1135
+ error: isPermanentFailure
1136
+ ? `TOOL_PERMANENTLY_FAILED: The tool "${call.name}" has failed ${currentFailInfo.count} times with error: ${errorMessage}. This tool will not be retried. Please proceed without using this tool or inform the user that this functionality is unavailable.`
1137
+ : `TOOL_EXECUTION_ERROR: ${errorMessage}. Retry attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}.`,
1138
+ status: isPermanentFailure
1139
+ ? "permanently_failed"
1140
+ : "failed",
1141
+ do_not_retry: isPermanentFailure,
1142
+ retry_count: currentFailInfo.count,
1143
+ max_retries: DEFAULT_TOOL_MAX_RETRIES,
1144
+ },
1145
+ },
1146
+ });
1147
+ }
1148
+ }
1149
+ else {
1150
+ // Tool not found is a permanent error
1151
+ functionResponses.push({
1152
+ functionResponse: {
1153
+ name: call.name,
1154
+ response: {
1155
+ error: `TOOL_NOT_FOUND: The tool "${call.name}" does not exist. Do not attempt to call this tool again.`,
1156
+ status: "permanently_failed",
1157
+ do_not_retry: true,
1158
+ },
1159
+ },
1160
+ });
1161
+ }
1162
+ }
1163
+ // Add function responses to history
1164
+ currentContents.push({
1165
+ role: "function",
1166
+ parts: functionResponses,
1167
+ });
1168
+ }
1169
+ catch (error) {
1170
+ logger.error("[GoogleVertex] Native SDK error", error);
1171
+ throw this.handleProviderError(error);
1172
+ }
1173
+ }
1174
+ // Handle maxSteps termination - if we exited the loop due to maxSteps being reached
1175
+ if (step >= maxSteps && !finalText) {
1176
+ logger.warn(`[GoogleVertex] Tool call loop terminated after reaching maxSteps (${maxSteps}). ` +
1177
+ `Model was still calling tools. Using accumulated text from last step.`);
1178
+ finalText =
1179
+ lastStepText ||
1180
+ `[Tool execution limit reached after ${maxSteps} steps. The model continued requesting tool calls beyond the limit.]`;
1181
+ }
1182
+ const responseTime = Date.now() - startTime;
1183
+ // Create async iterable for streaming result
1184
+ async function* createTextStream() {
1185
+ yield { content: finalText };
1186
+ }
1187
+ return {
1188
+ stream: createTextStream(),
1189
+ provider: this.providerName,
1190
+ model: modelName,
1191
+ usage: {
1192
+ input: totalInputTokens,
1193
+ output: totalOutputTokens,
1194
+ total: totalInputTokens + totalOutputTokens,
1195
+ },
1196
+ toolCalls: allToolCalls.map((tc) => ({
1197
+ toolName: tc.toolName,
1198
+ args: tc.args,
1199
+ })),
1200
+ metadata: {
1201
+ streamId: `native-vertex-${Date.now()}`,
1202
+ startTime,
1203
+ responseTime,
1204
+ totalToolExecutions: allToolCalls.length,
1205
+ },
1206
+ };
1207
+ }
1208
+ /**
1209
+ * Execute generate using native @google/genai SDK for Gemini 3 models on Vertex AI
1210
+ * This bypasses @ai-sdk/google-vertex to properly handle thought_signature
1211
+ */
1212
+ async executeNativeGemini3Generate(options) {
1213
+ const client = await this.createVertexGenAIClient(options.region);
1214
+ const modelName = options.model || this.modelName || getDefaultVertexModel();
1215
+ const effectiveLocation = options.region || this.location || getVertexLocation();
1216
+ logger.debug("[GoogleVertex] Using native @google/genai for Gemini 3 generate", {
1217
+ model: modelName,
1218
+ project: this.projectId,
1219
+ location: effectiveLocation,
1220
+ });
1221
+ // Build contents from input with multimodal support
1222
+ const inputText = options.prompt || options.input?.text || "Please respond.";
1223
+ const contents = [];
1224
+ // Build user message parts - start with text
1225
+ const userParts = [{ text: inputText }];
1226
+ // Add PDF files as inlineData parts if present
1227
+ // Cast input to access multimodal properties that may exist at runtime
1228
+ const multimodalInput = options.input;
1229
+ if (multimodalInput?.pdfFiles && multimodalInput.pdfFiles.length > 0) {
1230
+ logger.debug(`[GoogleVertex] Processing ${multimodalInput.pdfFiles.length} PDF file(s) for native generate`);
1231
+ for (const pdfFile of multimodalInput.pdfFiles) {
1232
+ let pdfBuffer;
1233
+ if (typeof pdfFile === "string") {
1234
+ // Check if it's a file path
1235
+ if (fs.existsSync(pdfFile)) {
1236
+ pdfBuffer = fs.readFileSync(pdfFile);
1237
+ }
1238
+ else {
1239
+ // Assume it's already base64 encoded
1240
+ pdfBuffer = Buffer.from(pdfFile, "base64");
1241
+ }
1242
+ }
1243
+ else {
1244
+ pdfBuffer = pdfFile;
1245
+ }
1246
+ // Convert to base64 for the native SDK
1247
+ const base64Data = pdfBuffer.toString("base64");
1248
+ userParts.push({
1249
+ inlineData: {
1250
+ mimeType: "application/pdf",
1251
+ data: base64Data,
1252
+ },
1253
+ });
1254
+ }
1255
+ }
1256
+ // Add images as inlineData parts if present
1257
+ if (multimodalInput?.images && multimodalInput.images.length > 0) {
1258
+ logger.debug(`[GoogleVertex] Processing ${multimodalInput.images.length} image(s) for native generate`);
1259
+ for (const image of multimodalInput.images) {
1260
+ let imageBuffer;
1261
+ let mimeType = "image/jpeg"; // Default
1262
+ if (typeof image === "string") {
1263
+ if (fs.existsSync(image)) {
1264
+ imageBuffer = fs.readFileSync(image);
1265
+ // Detect mime type from extension
1266
+ const ext = path.extname(image).toLowerCase();
1267
+ if (ext === ".png") {
1268
+ mimeType = "image/png";
1269
+ }
1270
+ else if (ext === ".gif") {
1271
+ mimeType = "image/gif";
1272
+ }
1273
+ else if (ext === ".webp") {
1274
+ mimeType = "image/webp";
1275
+ }
1276
+ }
1277
+ else if (image.startsWith("data:")) {
1278
+ // Handle data URL
1279
+ const matches = image.match(/^data:([^;]+);base64,(.+)$/);
1280
+ if (matches) {
1281
+ mimeType = matches[1];
1282
+ imageBuffer = Buffer.from(matches[2], "base64");
1283
+ }
1284
+ else {
1285
+ continue; // Skip invalid data URL
1286
+ }
1287
+ }
1288
+ else {
1289
+ // Assume base64 string
1290
+ imageBuffer = Buffer.from(image, "base64");
1291
+ }
1292
+ }
1293
+ else {
1294
+ imageBuffer = image;
1295
+ }
1296
+ const base64Data = imageBuffer.toString("base64");
1297
+ userParts.push({
1298
+ inlineData: {
1299
+ mimeType,
1300
+ data: base64Data,
1301
+ },
1302
+ });
1303
+ }
1304
+ }
1305
+ contents.push({
1306
+ role: "user",
1307
+ parts: userParts,
1308
+ });
1309
+ // Get tools from SDK and options
1310
+ const shouldUseTools = !options.disableTools && this.supportsTools();
1311
+ const sdkTools = shouldUseTools ? await this.getAllTools() : {};
1312
+ const combinedTools = { ...sdkTools, ...(options.tools || {}) };
1313
+ let tools;
1314
+ const executeMap = new Map();
1315
+ if (Object.keys(combinedTools).length > 0) {
1316
+ const functionDeclarations = [];
1317
+ for (const [name, tool] of Object.entries(combinedTools)) {
1318
+ const decl = {
1319
+ name,
1320
+ description: tool.description || `Tool: ${name}`,
1321
+ };
1322
+ if (tool.parameters) {
1323
+ // Convert and inline schema to resolve $ref/definitions
1324
+ const rawSchema = convertZodToJsonSchema(tool.parameters);
1325
+ decl.parametersJsonSchema = inlineJsonSchema(rawSchema);
1326
+ // Remove $schema if present - @google/genai doesn't need it
1327
+ if (decl.parametersJsonSchema.$schema) {
1328
+ delete decl.parametersJsonSchema.$schema;
1329
+ }
1330
+ }
1331
+ functionDeclarations.push(decl);
1332
+ if (tool.execute) {
1333
+ executeMap.set(name, tool.execute);
1334
+ }
1335
+ }
1336
+ tools = [{ functionDeclarations }];
1337
+ logger.debug("[GoogleVertex] Converted tools for native SDK generate", {
1338
+ toolCount: functionDeclarations.length,
1339
+ toolNames: functionDeclarations.map((t) => t.name),
1340
+ });
1341
+ }
1342
+ // Build config
1343
+ const config = {
1344
+ temperature: options.temperature ?? 1.0, // Gemini 3 requires 1.0 for tool calling
1345
+ maxOutputTokens: options.maxTokens,
1346
+ };
1347
+ if (tools) {
1348
+ config.tools = tools;
1349
+ }
1350
+ if (options.systemPrompt) {
1351
+ config.systemInstruction = options.systemPrompt;
1352
+ }
1353
+ // Add thinking config for Gemini 3
1354
+ const nativeThinkingConfig2 = createNativeThinkingConfig(options.thinkingConfig);
1355
+ if (nativeThinkingConfig2) {
1356
+ config.thinkingConfig = nativeThinkingConfig2;
1357
+ }
1358
+ // Note: Schema/JSON output for Gemini 3 native SDK is complex due to $ref resolution issues
1359
+ // For now, schemas are handled via the AI SDK fallback path, not native SDK
1360
+ // TODO: Implement proper $ref resolution for complex nested schemas
1361
+ const startTime = Date.now();
1362
+ // Ensure maxSteps is a valid positive integer to prevent infinite loops
1363
+ const rawMaxSteps = options.maxSteps || DEFAULT_MAX_STEPS;
1364
+ const maxSteps = Number.isFinite(rawMaxSteps) && rawMaxSteps > 0
1365
+ ? Math.min(Math.floor(rawMaxSteps), 100) // Cap at 100 for safety
1366
+ : Math.min(DEFAULT_MAX_STEPS, 100);
1367
+ const currentContents = [...contents];
1368
+ let finalText = "";
1369
+ let lastStepText = ""; // Track text from last step for maxSteps termination
1370
+ const allToolCalls = [];
1371
+ const toolExecutions = [];
1372
+ let step = 0;
1373
+ // Track failed tools to prevent infinite retry loops
1374
+ // Key: tool name, Value: { count: retry attempts, lastError: error message }
1375
+ const failedTools = new Map();
1376
+ // Track token usage across all steps
1377
+ // promptTokenCount is typically in the final chunk, candidatesTokenCount accumulates
1378
+ let totalInputTokens = 0;
1379
+ let totalOutputTokens = 0;
1380
+ // Agentic loop for tool calling
1381
+ while (step < maxSteps) {
1382
+ step++;
1383
+ logger.debug(`[GoogleVertex] Native SDK generate step ${step}/${maxSteps}`);
1384
+ try {
1385
+ // Use generateContentStream and collect all chunks (same as GoogleAIStudio)
1386
+ const stream = await client.models.generateContentStream({
1387
+ model: modelName,
1388
+ contents: currentContents,
1389
+ config,
1390
+ });
1391
+ const stepFunctionCalls = [];
1392
+ // Capture raw response parts including thoughtSignature
1393
+ const rawResponseParts = [];
1394
+ // Collect all chunks from stream
1395
+ for await (const chunk of stream) {
1396
+ // Extract raw parts from candidates FIRST
1397
+ // This avoids using chunk.text which triggers SDK warning when
1398
+ // non-text parts (thoughtSignature, functionCall) are present
1399
+ const chunkRecord = chunk;
1400
+ const candidates = chunkRecord.candidates;
1401
+ const firstCandidate = candidates?.[0];
1402
+ const chunkContent = firstCandidate?.content;
1403
+ if (chunkContent && Array.isArray(chunkContent.parts)) {
1404
+ rawResponseParts.push(...chunkContent.parts);
1405
+ }
1406
+ if (chunk.functionCalls) {
1407
+ stepFunctionCalls.push(...chunk.functionCalls);
1408
+ }
1409
+ // Extract usage metadata from chunk
1410
+ // promptTokenCount is typically in the final chunk, candidatesTokenCount accumulates
1411
+ const usageMetadata = chunkRecord.usageMetadata;
1412
+ if (usageMetadata) {
1413
+ // Take the latest promptTokenCount (usually only in final chunk)
1414
+ if (usageMetadata.promptTokenCount !== undefined &&
1415
+ usageMetadata.promptTokenCount > 0) {
1416
+ totalInputTokens = usageMetadata.promptTokenCount;
1417
+ }
1418
+ // Take the latest candidatesTokenCount (accumulates through chunks)
1419
+ if (usageMetadata.candidatesTokenCount !== undefined &&
1420
+ usageMetadata.candidatesTokenCount > 0) {
1421
+ totalOutputTokens = usageMetadata.candidatesTokenCount;
1422
+ }
1423
+ }
1424
+ }
1425
+ // Extract text from raw parts after stream completes
1426
+ // This avoids SDK warning about non-text parts (thoughtSignature, functionCall)
1427
+ const stepText = rawResponseParts
1428
+ .filter((part) => typeof part.text === "string")
1429
+ .map((part) => part.text)
1430
+ .join("");
1431
+ // If no function calls, we're done
1432
+ if (stepFunctionCalls.length === 0) {
1433
+ finalText = stepText;
1434
+ break;
1435
+ }
1436
+ // Track the last step text for maxSteps termination
1437
+ lastStepText = stepText;
1438
+ // Execute function calls
1439
+ logger.debug(`[GoogleVertex] Generate executing ${stepFunctionCalls.length} function calls`);
1440
+ // Add model response with ALL parts (including thoughtSignature) to history
1441
+ // This preserves the thought_signature which is required for Gemini 3 multi-turn tool calling
1442
+ currentContents.push({
1443
+ role: "model",
1444
+ parts: rawResponseParts.length > 0
1445
+ ? rawResponseParts
1446
+ : stepFunctionCalls.map((fc) => ({
1447
+ functionCall: fc,
1448
+ })),
1449
+ });
1450
+ // Execute each function and collect responses
1451
+ const functionResponses = [];
1452
+ for (const call of stepFunctionCalls) {
1453
+ allToolCalls.push({ toolName: call.name, args: call.args });
1454
+ // Check if this tool has already exceeded retry limit
1455
+ const failedInfo = failedTools.get(call.name);
1456
+ if (failedInfo && failedInfo.count >= DEFAULT_TOOL_MAX_RETRIES) {
1457
+ logger.warn(`[GoogleVertex] Tool "${call.name}" has exceeded retry limit (${DEFAULT_TOOL_MAX_RETRIES}), skipping execution`);
1458
+ const errorOutput = {
1459
+ error: `TOOL_PERMANENTLY_FAILED: The tool "${call.name}" has failed ${failedInfo.count} times and will not be retried. Last error: ${failedInfo.lastError}. Please proceed without using this tool or inform the user that this functionality is unavailable.`,
1460
+ status: "permanently_failed",
1461
+ do_not_retry: true,
1462
+ };
1463
+ toolExecutions.push({
1464
+ name: call.name,
1465
+ input: call.args,
1466
+ output: errorOutput,
1467
+ });
1468
+ functionResponses.push({
1469
+ functionResponse: {
1470
+ name: call.name,
1471
+ response: errorOutput,
1472
+ },
1473
+ });
1474
+ continue;
1475
+ }
1476
+ const execute = executeMap.get(call.name);
1477
+ if (execute) {
1478
+ try {
1479
+ // AI SDK Tool execute requires (args, options) - provide minimal options
1480
+ const toolOptions = {
1481
+ toolCallId: `${call.name}-${Date.now()}`,
1482
+ messages: [],
1483
+ abortSignal: undefined,
1484
+ };
1485
+ const execResult = await execute(call.args, toolOptions);
1486
+ // Track execution
1487
+ toolExecutions.push({
1488
+ name: call.name,
1489
+ input: call.args,
1490
+ output: execResult,
1491
+ });
1492
+ functionResponses.push({
1493
+ functionResponse: {
1494
+ name: call.name,
1495
+ response: { result: execResult },
1496
+ },
1497
+ });
1498
+ }
1499
+ catch (error) {
1500
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1501
+ // Track this failure
1502
+ const currentFailInfo = failedTools.get(call.name) || {
1503
+ count: 0,
1504
+ lastError: "",
1505
+ };
1506
+ currentFailInfo.count++;
1507
+ currentFailInfo.lastError = errorMessage;
1508
+ failedTools.set(call.name, currentFailInfo);
1509
+ logger.warn(`[GoogleVertex] Tool "${call.name}" failed (attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}): ${errorMessage}`);
1510
+ // Determine if this is a permanent failure
1511
+ const isPermanentFailure = currentFailInfo.count >= DEFAULT_TOOL_MAX_RETRIES;
1512
+ const errorOutput = {
1513
+ error: isPermanentFailure
1514
+ ? `TOOL_PERMANENTLY_FAILED: The tool "${call.name}" has failed ${currentFailInfo.count} times with error: ${errorMessage}. This tool will not be retried. Please proceed without using this tool or inform the user that this functionality is unavailable.`
1515
+ : `TOOL_EXECUTION_ERROR: ${errorMessage}. Retry attempt ${currentFailInfo.count}/${DEFAULT_TOOL_MAX_RETRIES}.`,
1516
+ status: isPermanentFailure ? "permanently_failed" : "failed",
1517
+ do_not_retry: isPermanentFailure,
1518
+ retry_count: currentFailInfo.count,
1519
+ max_retries: DEFAULT_TOOL_MAX_RETRIES,
1520
+ };
1521
+ toolExecutions.push({
1522
+ name: call.name,
1523
+ input: call.args,
1524
+ output: errorOutput,
1525
+ });
1526
+ functionResponses.push({
1527
+ functionResponse: {
1528
+ name: call.name,
1529
+ response: errorOutput,
1530
+ },
1531
+ });
1532
+ }
1533
+ }
1534
+ else {
1535
+ // Tool not found is a permanent error
1536
+ const errorOutput = {
1537
+ error: `TOOL_NOT_FOUND: The tool "${call.name}" does not exist. Do not attempt to call this tool again.`,
1538
+ status: "permanently_failed",
1539
+ do_not_retry: true,
1540
+ };
1541
+ toolExecutions.push({
1542
+ name: call.name,
1543
+ input: call.args,
1544
+ output: errorOutput,
1545
+ });
1546
+ functionResponses.push({
1547
+ functionResponse: {
1548
+ name: call.name,
1549
+ response: errorOutput,
1550
+ },
1551
+ });
1552
+ }
1553
+ }
1554
+ // Add function responses to history
1555
+ currentContents.push({
1556
+ role: "function",
1557
+ parts: functionResponses,
1558
+ });
1559
+ }
1560
+ catch (error) {
1561
+ logger.error("[GoogleVertex] Native SDK generate error", error);
1562
+ throw this.handleProviderError(error);
1563
+ }
1564
+ }
1565
+ // Handle maxSteps termination - if we exited the loop due to maxSteps being reached
1566
+ if (step >= maxSteps && !finalText) {
1567
+ logger.warn(`[GoogleVertex] Generate tool call loop terminated after reaching maxSteps (${maxSteps}). ` +
1568
+ `Model was still calling tools. Using accumulated text from last step.`);
1569
+ finalText =
1570
+ lastStepText ||
1571
+ `[Tool execution limit reached after ${maxSteps} steps. The model continued requesting tool calls beyond the limit.]`;
1572
+ }
1573
+ const responseTime = Date.now() - startTime;
1574
+ // Build EnhancedGenerateResult
1575
+ return {
1576
+ content: finalText,
1577
+ provider: this.providerName,
1578
+ model: modelName,
1579
+ usage: {
1580
+ input: totalInputTokens,
1581
+ output: totalOutputTokens,
1582
+ total: totalInputTokens + totalOutputTokens,
1583
+ },
1584
+ responseTime,
1585
+ toolsUsed: allToolCalls.map((tc) => tc.toolName),
1586
+ toolExecutions: toolExecutions,
1587
+ enhancedWithTools: allToolCalls.length > 0,
1588
+ };
1589
+ }
1590
+ /**
1591
+ * Process CSV files and append content to options.input.text
1592
+ * This ensures CSV data is available in the prompt for native Gemini 3 SDK calls
1593
+ * Returns a new options object with modified input (immutable pattern)
1594
+ */
1595
+ async processCSVFilesForNativeSDK(options) {
1596
+ const input = options.input;
1597
+ if (!input?.csvFiles || input.csvFiles.length === 0) {
1598
+ return options;
1599
+ }
1600
+ logger.info(`[GoogleVertex] Processing ${input.csvFiles.length} CSV file(s) for native Gemini 3 SDK`);
1601
+ let modifiedText = input.text || "";
1602
+ for (let i = 0; i < input.csvFiles.length; i++) {
1603
+ const csvFile = input.csvFiles[i];
1604
+ try {
1605
+ const result = await FileDetector.detectAndProcess(csvFile, {
1606
+ allowedTypes: ["csv"],
1607
+ csvOptions: "csvOptions" in options
1608
+ ? options.csvOptions
1609
+ : undefined,
1610
+ });
1611
+ // Extract filename for display
1612
+ const filename = typeof csvFile === "string"
1613
+ ? path.basename(csvFile)
1614
+ : `csv_file_${i + 1}.csv`;
1615
+ let csvSection = `\n\n## CSV Data from "${filename}":\n`;
1616
+ // Add metadata if available
1617
+ if (result.metadata) {
1618
+ const meta = result.metadata;
1619
+ if (meta.rowCount || meta.columnCount || meta.columnNames) {
1620
+ csvSection += `**File Info:**\n`;
1621
+ if (meta.rowCount) {
1622
+ csvSection += `- Rows: ${meta.rowCount}\n`;
1623
+ }
1624
+ if (meta.columnCount) {
1625
+ csvSection += `- Columns: ${meta.columnCount}\n`;
1626
+ }
1627
+ if (meta.columnNames && Array.isArray(meta.columnNames)) {
1628
+ csvSection += `- Column Names: ${meta.columnNames.join(", ")}\n`;
1629
+ }
1630
+ csvSection += "\n";
1631
+ }
1632
+ }
1633
+ // Add strong instructions to use the CSV data directly
1634
+ csvSection += `\n**CRITICAL INSTRUCTION**: The complete CSV data is included below. You MUST use this data directly from this prompt.\n`;
1635
+ csvSection += `DO NOT use any external tools (github, search_code, get_file_contents, etc.) to access this data.\n`;
1636
+ csvSection += `The data you need is right here in this message - read it carefully and answer based on it.\n\n`;
1637
+ csvSection += result.content;
1638
+ // Prepend CSV to ensure data appears before user's question
1639
+ modifiedText =
1640
+ csvSection + "\n\n---\n\n**USER QUESTION:**\n" + modifiedText;
1641
+ logger.info(`[GoogleVertex] ✅ Processed CSV: ${filename}`);
1642
+ }
1643
+ catch (error) {
1644
+ logger.error(`[GoogleVertex] ❌ Failed to process CSV file ${i + 1}:`, error);
1645
+ const filename = typeof csvFile === "string"
1646
+ ? path.basename(csvFile)
1647
+ : `csv_file_${i + 1}.csv`;
1648
+ modifiedText += `\n\n## CSV Data Error: Failed to process "${filename}"\nReason: ${error instanceof Error ? error.message : "Unknown error"}`;
1649
+ }
1650
+ }
1651
+ // Return new options with modified input (immutable pattern)
1652
+ // Preserve the full type of options.input by spreading options.input directly
1653
+ return {
1654
+ ...options,
1655
+ input: { ...options.input, text: modifiedText },
1656
+ };
1657
+ }
1658
+ /**
1659
+ * Override generate to route Gemini 3 models with tools to native SDK
1660
+ */
1661
+ async generate(optionsOrPrompt) {
1662
+ // Normalize options
1663
+ const options = typeof optionsOrPrompt === "string"
1664
+ ? { prompt: optionsOrPrompt }
1665
+ : optionsOrPrompt;
1666
+ const modelName = options.model || this.modelName || getDefaultVertexModel();
1667
+ // Check if we should use native SDK for Gemini 3 with tools
1668
+ const shouldUseTools = !options.disableTools && this.supportsTools();
1669
+ const sdkTools = shouldUseTools ? await this.getAllTools() : {};
1670
+ const hasTools = shouldUseTools &&
1671
+ (Object.keys(sdkTools).length > 0 ||
1672
+ (options.tools && Object.keys(options.tools).length > 0));
1673
+ if (isGemini3Model(modelName) && hasTools) {
1674
+ // Process CSV files before routing to native SDK (bypasses normal message builder)
1675
+ const processedOptions = await this.processCSVFilesForNativeSDK(options);
1676
+ // Merge SDK tools into options for native SDK path
1677
+ const mergedOptions = {
1678
+ ...processedOptions,
1679
+ tools: { ...sdkTools, ...(processedOptions.tools || {}) },
1680
+ };
1681
+ logger.info("[GoogleVertex] Routing Gemini 3 generate to native SDK for tool calling", {
1682
+ model: modelName,
1683
+ sdkToolCount: Object.keys(sdkTools).length,
1684
+ optionToolCount: Object.keys(processedOptions.tools || {}).length,
1685
+ totalToolCount: Object.keys(sdkTools).length +
1686
+ Object.keys(processedOptions.tools || {}).length,
1687
+ });
1688
+ return this.executeNativeGemini3Generate(mergedOptions);
1689
+ }
1690
+ // Fall back to BaseProvider implementation
1691
+ return super.generate(optionsOrPrompt);
1692
+ }
748
1693
  handleProviderError(error) {
749
1694
  const errorRecord = error;
750
1695
  if (typeof errorRecord?.name === "string" &&
@@ -1150,15 +2095,45 @@ export class GoogleVertexProvider extends BaseProvider {
1150
2095
  async checkVertexRegionalSupport(region = "us-central1") {
1151
2096
  // Based on Google Cloud documentation, these regions support Anthropic models
1152
2097
  const supportedRegions = [
2098
+ // North America
1153
2099
  "us-central1",
2100
+ "us-east1",
1154
2101
  "us-east4",
1155
2102
  "us-east5",
2103
+ "us-south1",
1156
2104
  "us-west1",
1157
2105
  "us-west4",
2106
+ "northamerica-northeast1",
2107
+ "northamerica-northeast2",
2108
+ // Europe
1158
2109
  "europe-west1",
2110
+ "europe-west2",
2111
+ "europe-west3",
1159
2112
  "europe-west4",
1160
- "asia-southeast1",
2113
+ "europe-west6",
2114
+ "europe-west8",
2115
+ "europe-west9",
2116
+ "europe-north1",
2117
+ "europe-central2",
2118
+ "europe-southwest1",
2119
+ // Asia Pacific
2120
+ "asia-east1",
2121
+ "asia-east2",
1161
2122
  "asia-northeast1",
2123
+ "asia-northeast2",
2124
+ "asia-northeast3",
2125
+ "asia-south1",
2126
+ "asia-southeast1",
2127
+ "asia-southeast2",
2128
+ "australia-southeast1",
2129
+ "australia-southeast2",
2130
+ // Middle East & Africa
2131
+ "me-west1",
2132
+ "me-central1",
2133
+ "africa-south1",
2134
+ // South America
2135
+ "southamerica-east1",
2136
+ "southamerica-west1",
1162
2137
  ];
1163
2138
  return supportedRegions.includes(region);
1164
2139
  }