@juspay/neurolink 9.32.0 → 9.32.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.
Files changed (467) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/auth/anthropicOAuth.js +1 -1
  3. package/dist/cli/commands/proxy.js +18 -5
  4. package/dist/client/aiSdkAdapter.js +1 -1
  5. package/dist/client/index.js +137 -501
  6. package/dist/core/factory.js +0 -1
  7. package/dist/core/redisConversationMemoryManager.js +1 -1
  8. package/dist/features/ppt/slideGenerator.js +0 -1
  9. package/dist/features/ppt/utils.js +0 -1
  10. package/dist/lib/server/routes/claudeProxyRoutes.js +45 -9
  11. package/dist/mcp/elicitationProtocol.js +1 -1
  12. package/dist/mcp/servers/agent/directToolsServer.js +0 -1
  13. package/dist/providers/azureOpenai.js +1 -1
  14. package/dist/providers/huggingFace.js +0 -1
  15. package/dist/providers/openaiCompatible.js +0 -1
  16. package/dist/sdk/toolRegistration.js +0 -1
  17. package/dist/server/openapi/generator.js +1 -1
  18. package/dist/server/routes/claudeProxyRoutes.js +45 -9
  19. package/dist/types/configTypes.js +0 -5
  20. package/dist/types/modelTypes.js +0 -1
  21. package/dist/types/tools.js +0 -1
  22. package/dist/types/typeAliases.js +0 -1
  23. package/dist/types/utilities.js +1 -1
  24. package/dist/types/workflowTypes.js +0 -1
  25. package/dist/utils/providerRetry.js +0 -1
  26. package/dist/utils/providerUtils.js +0 -1
  27. package/package.json +2 -2
  28. package/dist/client/adapters/providerImageAdapter.js +0 -588
  29. package/dist/client/adapters/tts/googleTTSHandler.js +0 -344
  30. package/dist/client/adapters/video/directorPipeline.js +0 -516
  31. package/dist/client/adapters/video/ffmpegAdapter.js +0 -206
  32. package/dist/client/adapters/video/frameExtractor.js +0 -143
  33. package/dist/client/adapters/video/vertexVideoHandler.js +0 -763
  34. package/dist/client/adapters/video/videoAnalyzer.js +0 -238
  35. package/dist/client/adapters/video/videoMerger.js +0 -171
  36. package/dist/client/agent/directTools.js +0 -840
  37. package/dist/client/auth/AuthProviderFactory.js +0 -111
  38. package/dist/client/auth/AuthProviderRegistry.js +0 -190
  39. package/dist/client/auth/RequestContext.js +0 -78
  40. package/dist/client/auth/accountPool.js +0 -178
  41. package/dist/client/auth/anthropicOAuth.js +0 -974
  42. package/dist/client/auth/authContext.js +0 -314
  43. package/dist/client/auth/errors.js +0 -39
  44. package/dist/client/auth/index.js +0 -61
  45. package/dist/client/auth/middleware/AuthMiddleware.js +0 -519
  46. package/dist/client/auth/middleware/rateLimitByUser.js +0 -554
  47. package/dist/client/auth/providers/BaseAuthProvider.js +0 -723
  48. package/dist/client/auth/providers/CognitoProvider.js +0 -304
  49. package/dist/client/auth/providers/KeycloakProvider.js +0 -393
  50. package/dist/client/auth/providers/auth0.js +0 -274
  51. package/dist/client/auth/providers/betterAuth.js +0 -182
  52. package/dist/client/auth/providers/clerk.js +0 -317
  53. package/dist/client/auth/providers/custom.js +0 -112
  54. package/dist/client/auth/providers/firebase.js +0 -226
  55. package/dist/client/auth/providers/jwt.js +0 -212
  56. package/dist/client/auth/providers/oauth2.js +0 -303
  57. package/dist/client/auth/providers/supabase.js +0 -259
  58. package/dist/client/auth/providers/workos.js +0 -284
  59. package/dist/client/auth/serverBridge.js +0 -25
  60. package/dist/client/auth/sessionManager.js +0 -437
  61. package/dist/client/auth/tokenStore.js +0 -799
  62. package/dist/client/client/aiSdkAdapter.js +0 -487
  63. package/dist/client/client/auth.js +0 -473
  64. package/dist/client/client/errors.js +0 -552
  65. package/dist/client/client/httpClient.js +0 -837
  66. package/dist/client/client/index.js +0 -172
  67. package/dist/client/client/interceptors.js +0 -601
  68. package/dist/client/client/sseClient.js +0 -545
  69. package/dist/client/client/streamingClient.js +0 -917
  70. package/dist/client/client/wsClient.js +0 -369
  71. package/dist/client/config/configManager.js +0 -303
  72. package/dist/client/config/conversationMemory.js +0 -86
  73. package/dist/client/config/taskClassificationConfig.js +0 -148
  74. package/dist/client/constants/contextWindows.js +0 -295
  75. package/dist/client/constants/enums.js +0 -853
  76. package/dist/client/constants/index.js +0 -207
  77. package/dist/client/constants/performance.js +0 -389
  78. package/dist/client/constants/retry.js +0 -266
  79. package/dist/client/constants/timeouts.js +0 -182
  80. package/dist/client/constants/tokens.js +0 -380
  81. package/dist/client/constants/videoErrors.js +0 -46
  82. package/dist/client/context/budgetChecker.js +0 -98
  83. package/dist/client/context/contextCompactor.js +0 -205
  84. package/dist/client/context/emergencyTruncation.js +0 -88
  85. package/dist/client/context/errorDetection.js +0 -171
  86. package/dist/client/context/errors.js +0 -21
  87. package/dist/client/context/fileTokenBudget.js +0 -127
  88. package/dist/client/context/prompts/summarizationPrompt.js +0 -117
  89. package/dist/client/context/stages/fileReadDeduplicator.js +0 -66
  90. package/dist/client/context/stages/slidingWindowTruncator.js +0 -190
  91. package/dist/client/context/stages/structuredSummarizer.js +0 -99
  92. package/dist/client/context/stages/toolOutputPruner.js +0 -52
  93. package/dist/client/context/summarizationEngine.js +0 -136
  94. package/dist/client/context/toolOutputLimits.js +0 -78
  95. package/dist/client/context/toolPairRepair.js +0 -66
  96. package/dist/client/core/analytics.js +0 -88
  97. package/dist/client/core/baseProvider.js +0 -1385
  98. package/dist/client/core/constants.js +0 -140
  99. package/dist/client/core/conversationMemoryFactory.js +0 -141
  100. package/dist/client/core/conversationMemoryInitializer.js +0 -128
  101. package/dist/client/core/conversationMemoryManager.js +0 -344
  102. package/dist/client/core/dynamicModels.js +0 -358
  103. package/dist/client/core/evaluation.js +0 -309
  104. package/dist/client/core/evaluationProviders.js +0 -248
  105. package/dist/client/core/factory.js +0 -412
  106. package/dist/client/core/infrastructure/baseError.js +0 -22
  107. package/dist/client/core/infrastructure/baseFactory.js +0 -54
  108. package/dist/client/core/infrastructure/baseRegistry.js +0 -53
  109. package/dist/client/core/infrastructure/index.js +0 -5
  110. package/dist/client/core/infrastructure/retry.js +0 -20
  111. package/dist/client/core/infrastructure/typedEventEmitter.js +0 -23
  112. package/dist/client/core/modelConfiguration.js +0 -851
  113. package/dist/client/core/modules/GenerationHandler.js +0 -588
  114. package/dist/client/core/modules/MessageBuilder.js +0 -273
  115. package/dist/client/core/modules/StreamHandler.js +0 -185
  116. package/dist/client/core/modules/TelemetryHandler.js +0 -203
  117. package/dist/client/core/modules/ToolsManager.js +0 -499
  118. package/dist/client/core/modules/Utilities.js +0 -331
  119. package/dist/client/core/redisConversationMemoryManager.js +0 -1435
  120. package/dist/client/core/streamAnalytics.js +0 -131
  121. package/dist/client/evaluation/contextBuilder.js +0 -134
  122. package/dist/client/evaluation/index.js +0 -61
  123. package/dist/client/evaluation/prompts.js +0 -73
  124. package/dist/client/evaluation/ragasEvaluator.js +0 -110
  125. package/dist/client/evaluation/retryManager.js +0 -78
  126. package/dist/client/evaluation/scoring.js +0 -61
  127. package/dist/client/factories/providerFactory.js +0 -166
  128. package/dist/client/factories/providerRegistry.js +0 -166
  129. package/dist/client/features/ppt/constants.js +0 -896
  130. package/dist/client/features/ppt/contentPlanner.js +0 -529
  131. package/dist/client/features/ppt/presentationOrchestrator.js +0 -236
  132. package/dist/client/features/ppt/slideGenerator.js +0 -532
  133. package/dist/client/features/ppt/slideRenderers.js +0 -2383
  134. package/dist/client/features/ppt/slideTypeInference.js +0 -405
  135. package/dist/client/features/ppt/types.js +0 -13
  136. package/dist/client/features/ppt/utils.js +0 -443
  137. package/dist/client/files/fileReferenceRegistry.js +0 -1543
  138. package/dist/client/files/fileTools.js +0 -450
  139. package/dist/client/files/streamingReader.js +0 -321
  140. package/dist/client/files/types.js +0 -23
  141. package/dist/client/hitl/hitlErrors.js +0 -54
  142. package/dist/client/hitl/hitlManager.js +0 -460
  143. package/dist/client/mcp/agentExposure.js +0 -356
  144. package/dist/client/mcp/auth/index.js +0 -11
  145. package/dist/client/mcp/auth/oauthClientProvider.js +0 -325
  146. package/dist/client/mcp/auth/tokenStorage.js +0 -134
  147. package/dist/client/mcp/batching/index.js +0 -10
  148. package/dist/client/mcp/batching/requestBatcher.js +0 -441
  149. package/dist/client/mcp/caching/index.js +0 -10
  150. package/dist/client/mcp/caching/toolCache.js +0 -433
  151. package/dist/client/mcp/elicitation/elicitationManager.js +0 -376
  152. package/dist/client/mcp/elicitation/index.js +0 -11
  153. package/dist/client/mcp/elicitation/types.js +0 -10
  154. package/dist/client/mcp/elicitationProtocol.js +0 -375
  155. package/dist/client/mcp/enhancedToolDiscovery.js +0 -481
  156. package/dist/client/mcp/externalServerManager.js +0 -1478
  157. package/dist/client/mcp/factory.js +0 -161
  158. package/dist/client/mcp/flexibleToolValidator.js +0 -161
  159. package/dist/client/mcp/httpRateLimiter.js +0 -391
  160. package/dist/client/mcp/httpRetryHandler.js +0 -178
  161. package/dist/client/mcp/index.js +0 -74
  162. package/dist/client/mcp/mcpCircuitBreaker.js +0 -427
  163. package/dist/client/mcp/mcpClientFactory.js +0 -708
  164. package/dist/client/mcp/mcpRegistryClient.js +0 -488
  165. package/dist/client/mcp/mcpServerBase.js +0 -373
  166. package/dist/client/mcp/multiServerManager.js +0 -579
  167. package/dist/client/mcp/registry.js +0 -158
  168. package/dist/client/mcp/routing/index.js +0 -10
  169. package/dist/client/mcp/routing/toolRouter.js +0 -416
  170. package/dist/client/mcp/serverCapabilities.js +0 -502
  171. package/dist/client/mcp/servers/agent/directToolsServer.js +0 -150
  172. package/dist/client/mcp/toolAnnotations.js +0 -239
  173. package/dist/client/mcp/toolConverter.js +0 -258
  174. package/dist/client/mcp/toolDiscoveryService.js +0 -798
  175. package/dist/client/mcp/toolIntegration.js +0 -334
  176. package/dist/client/mcp/toolRegistry.js +0 -729
  177. package/dist/client/memory/hippocampusInitializer.js +0 -19
  178. package/dist/client/memory/memoryRetrievalTools.js +0 -166
  179. package/dist/client/middleware/builtin/analytics.js +0 -132
  180. package/dist/client/middleware/builtin/autoEvaluation.js +0 -203
  181. package/dist/client/middleware/builtin/guardrails.js +0 -109
  182. package/dist/client/middleware/builtin/lifecycle.js +0 -168
  183. package/dist/client/middleware/factory.js +0 -327
  184. package/dist/client/middleware/registry.js +0 -295
  185. package/dist/client/middleware/utils/guardrailsUtils.js +0 -396
  186. package/dist/client/models/anthropicModels.js +0 -527
  187. package/dist/client/neurolink.js +0 -8233
  188. package/dist/client/observability/exporterRegistry.js +0 -413
  189. package/dist/client/observability/exporters/arizeExporter.js +0 -138
  190. package/dist/client/observability/exporters/baseExporter.js +0 -190
  191. package/dist/client/observability/exporters/braintrustExporter.js +0 -154
  192. package/dist/client/observability/exporters/datadogExporter.js +0 -196
  193. package/dist/client/observability/exporters/laminarExporter.js +0 -302
  194. package/dist/client/observability/exporters/langfuseExporter.js +0 -209
  195. package/dist/client/observability/exporters/langsmithExporter.js +0 -143
  196. package/dist/client/observability/exporters/otelExporter.js +0 -164
  197. package/dist/client/observability/exporters/posthogExporter.js +0 -287
  198. package/dist/client/observability/exporters/sentryExporter.js +0 -165
  199. package/dist/client/observability/index.js +0 -31
  200. package/dist/client/observability/metricsAggregator.js +0 -556
  201. package/dist/client/observability/otelBridge.js +0 -131
  202. package/dist/client/observability/retryPolicy.js +0 -383
  203. package/dist/client/observability/sampling/samplers.js +0 -216
  204. package/dist/client/observability/spanProcessor.js +0 -303
  205. package/dist/client/observability/tokenTracker.js +0 -413
  206. package/dist/client/observability/types/exporterTypes.js +0 -5
  207. package/dist/client/observability/types/index.js +0 -4
  208. package/dist/client/observability/types/spanTypes.js +0 -92
  209. package/dist/client/observability/utils/safeMetadata.js +0 -25
  210. package/dist/client/observability/utils/spanSerializer.js +0 -292
  211. package/dist/client/processors/archive/ArchiveProcessor.js +0 -1308
  212. package/dist/client/processors/base/BaseFileProcessor.js +0 -614
  213. package/dist/client/processors/base/types.js +0 -82
  214. package/dist/client/processors/config/fileTypes.js +0 -520
  215. package/dist/client/processors/config/index.js +0 -92
  216. package/dist/client/processors/config/languageMap.js +0 -410
  217. package/dist/client/processors/config/mimeTypes.js +0 -363
  218. package/dist/client/processors/config/sizeLimits.js +0 -258
  219. package/dist/client/processors/document/ExcelProcessor.js +0 -590
  220. package/dist/client/processors/document/OpenDocumentProcessor.js +0 -212
  221. package/dist/client/processors/document/PptxProcessor.js +0 -157
  222. package/dist/client/processors/document/RtfProcessor.js +0 -361
  223. package/dist/client/processors/document/WordProcessor.js +0 -353
  224. package/dist/client/processors/errors/FileErrorCode.js +0 -255
  225. package/dist/client/processors/errors/errorHelpers.js +0 -386
  226. package/dist/client/processors/errors/errorSerializer.js +0 -507
  227. package/dist/client/processors/errors/index.js +0 -49
  228. package/dist/client/processors/markup/SvgProcessor.js +0 -240
  229. package/dist/client/processors/media/AudioProcessor.js +0 -707
  230. package/dist/client/processors/media/VideoProcessor.js +0 -1045
  231. package/dist/client/providers/amazonBedrock.js +0 -1512
  232. package/dist/client/providers/amazonSagemaker.js +0 -162
  233. package/dist/client/providers/anthropic.js +0 -831
  234. package/dist/client/providers/azureOpenai.js +0 -143
  235. package/dist/client/providers/googleAiStudio.js +0 -1200
  236. package/dist/client/providers/googleNativeGemini3.js +0 -543
  237. package/dist/client/providers/googleVertex.js +0 -2936
  238. package/dist/client/providers/huggingFace.js +0 -315
  239. package/dist/client/providers/litellm.js +0 -488
  240. package/dist/client/providers/mistral.js +0 -157
  241. package/dist/client/providers/ollama.js +0 -1579
  242. package/dist/client/providers/openAI.js +0 -627
  243. package/dist/client/providers/openRouter.js +0 -543
  244. package/dist/client/providers/openaiCompatible.js +0 -290
  245. package/dist/client/providers/providerTypeUtils.js +0 -46
  246. package/dist/client/providers/sagemaker/adaptive-semaphore.js +0 -215
  247. package/dist/client/providers/sagemaker/client.js +0 -472
  248. package/dist/client/providers/sagemaker/config.js +0 -317
  249. package/dist/client/providers/sagemaker/detection.js +0 -606
  250. package/dist/client/providers/sagemaker/error-constants.js +0 -227
  251. package/dist/client/providers/sagemaker/errors.js +0 -299
  252. package/dist/client/providers/sagemaker/language-model.js +0 -775
  253. package/dist/client/providers/sagemaker/parsers.js +0 -634
  254. package/dist/client/providers/sagemaker/streaming.js +0 -331
  255. package/dist/client/providers/sagemaker/structured-parser.js +0 -625
  256. package/dist/client/proxy/accountQuota.js +0 -162
  257. package/dist/client/proxy/claudeFormat.js +0 -595
  258. package/dist/client/proxy/modelRouter.js +0 -29
  259. package/dist/client/proxy/oauthFetch.js +0 -367
  260. package/dist/client/proxy/proxyFetch.js +0 -586
  261. package/dist/client/proxy/requestLogger.js +0 -207
  262. package/dist/client/proxy/tokenRefresh.js +0 -124
  263. package/dist/client/proxy/usageStats.js +0 -74
  264. package/dist/client/proxy/utils/noProxyUtils.js +0 -149
  265. package/dist/client/rag/ChunkerFactory.js +0 -320
  266. package/dist/client/rag/ChunkerRegistry.js +0 -421
  267. package/dist/client/rag/chunkers/BaseChunker.js +0 -143
  268. package/dist/client/rag/chunkers/CharacterChunker.js +0 -28
  269. package/dist/client/rag/chunkers/HTMLChunker.js +0 -38
  270. package/dist/client/rag/chunkers/JSONChunker.js +0 -68
  271. package/dist/client/rag/chunkers/LaTeXChunker.js +0 -63
  272. package/dist/client/rag/chunkers/MarkdownChunker.js +0 -306
  273. package/dist/client/rag/chunkers/RecursiveChunker.js +0 -139
  274. package/dist/client/rag/chunkers/SemanticMarkdownChunker.js +0 -138
  275. package/dist/client/rag/chunkers/SentenceChunker.js +0 -66
  276. package/dist/client/rag/chunkers/TokenChunker.js +0 -61
  277. package/dist/client/rag/chunkers/index.js +0 -15
  278. package/dist/client/rag/chunking/characterChunker.js +0 -142
  279. package/dist/client/rag/chunking/chunkerRegistry.js +0 -194
  280. package/dist/client/rag/chunking/htmlChunker.js +0 -247
  281. package/dist/client/rag/chunking/index.js +0 -17
  282. package/dist/client/rag/chunking/jsonChunker.js +0 -281
  283. package/dist/client/rag/chunking/latexChunker.js +0 -251
  284. package/dist/client/rag/chunking/markdownChunker.js +0 -373
  285. package/dist/client/rag/chunking/recursiveChunker.js +0 -148
  286. package/dist/client/rag/chunking/semanticChunker.js +0 -306
  287. package/dist/client/rag/chunking/sentenceChunker.js +0 -230
  288. package/dist/client/rag/chunking/tokenChunker.js +0 -183
  289. package/dist/client/rag/document/MDocument.js +0 -392
  290. package/dist/client/rag/document/index.js +0 -5
  291. package/dist/client/rag/document/loaders.js +0 -500
  292. package/dist/client/rag/errors/RAGError.js +0 -274
  293. package/dist/client/rag/errors/index.js +0 -6
  294. package/dist/client/rag/graphRag/graphRAG.js +0 -401
  295. package/dist/client/rag/graphRag/index.js +0 -4
  296. package/dist/client/rag/index.js +0 -141
  297. package/dist/client/rag/metadata/MetadataExtractorFactory.js +0 -418
  298. package/dist/client/rag/metadata/MetadataExtractorRegistry.js +0 -362
  299. package/dist/client/rag/metadata/index.js +0 -9
  300. package/dist/client/rag/metadata/metadataExtractor.js +0 -280
  301. package/dist/client/rag/pipeline/RAGPipeline.js +0 -436
  302. package/dist/client/rag/pipeline/contextAssembly.js +0 -341
  303. package/dist/client/rag/pipeline/index.js +0 -5
  304. package/dist/client/rag/ragIntegration.js +0 -321
  305. package/dist/client/rag/reranker/RerankerFactory.js +0 -430
  306. package/dist/client/rag/reranker/RerankerRegistry.js +0 -402
  307. package/dist/client/rag/reranker/index.js +0 -9
  308. package/dist/client/rag/reranker/reranker.js +0 -277
  309. package/dist/client/rag/resilience/CircuitBreaker.js +0 -431
  310. package/dist/client/rag/resilience/RetryHandler.js +0 -304
  311. package/dist/client/rag/resilience/index.js +0 -7
  312. package/dist/client/rag/retrieval/hybridSearch.js +0 -335
  313. package/dist/client/rag/retrieval/index.js +0 -5
  314. package/dist/client/rag/retrieval/vectorQueryTool.js +0 -307
  315. package/dist/client/rag/types.js +0 -8
  316. package/dist/client/sdk/toolRegistration.js +0 -377
  317. package/dist/client/server/abstract/baseServerAdapter.js +0 -575
  318. package/dist/client/server/adapters/expressAdapter.js +0 -486
  319. package/dist/client/server/adapters/fastifyAdapter.js +0 -472
  320. package/dist/client/server/adapters/honoAdapter.js +0 -632
  321. package/dist/client/server/adapters/koaAdapter.js +0 -510
  322. package/dist/client/server/errors.js +0 -486
  323. package/dist/client/server/factory/serverAdapterFactory.js +0 -160
  324. package/dist/client/server/index.js +0 -108
  325. package/dist/client/server/middleware/abortSignal.js +0 -111
  326. package/dist/client/server/middleware/auth.js +0 -388
  327. package/dist/client/server/middleware/cache.js +0 -359
  328. package/dist/client/server/middleware/common.js +0 -281
  329. package/dist/client/server/middleware/deprecation.js +0 -190
  330. package/dist/client/server/middleware/mcpBodyAttachment.js +0 -63
  331. package/dist/client/server/middleware/rateLimit.js +0 -227
  332. package/dist/client/server/middleware/validation.js +0 -388
  333. package/dist/client/server/openapi/generator.js +0 -398
  334. package/dist/client/server/openapi/index.js +0 -36
  335. package/dist/client/server/openapi/schemas.js +0 -695
  336. package/dist/client/server/openapi/templates.js +0 -374
  337. package/dist/client/server/routes/agentRoutes.js +0 -189
  338. package/dist/client/server/routes/claudeProxyRoutes.js +0 -1600
  339. package/dist/client/server/routes/healthRoutes.js +0 -187
  340. package/dist/client/server/routes/index.js +0 -57
  341. package/dist/client/server/routes/mcpRoutes.js +0 -342
  342. package/dist/client/server/routes/memoryRoutes.js +0 -350
  343. package/dist/client/server/routes/openApiRoutes.js +0 -126
  344. package/dist/client/server/routes/toolRoutes.js +0 -199
  345. package/dist/client/server/streaming/dataStream.js +0 -486
  346. package/dist/client/server/streaming/index.js +0 -11
  347. package/dist/client/server/types.js +0 -67
  348. package/dist/client/server/utils/redaction.js +0 -334
  349. package/dist/client/server/utils/validation.js +0 -243
  350. package/dist/client/server/websocket/WebSocketHandler.js +0 -383
  351. package/dist/client/server/websocket/index.js +0 -4
  352. package/dist/client/services/server/ai/observability/instrumentation.js +0 -808
  353. package/dist/client/telemetry/attributes.js +0 -100
  354. package/dist/client/telemetry/index.js +0 -26
  355. package/dist/client/telemetry/telemetryService.js +0 -308
  356. package/dist/client/telemetry/tracers.js +0 -17
  357. package/dist/client/telemetry/withSpan.js +0 -34
  358. package/dist/client/types/actionTypes.js +0 -6
  359. package/dist/client/types/analytics.js +0 -5
  360. package/dist/client/types/authTypes.js +0 -9
  361. package/dist/client/types/circuitBreakerErrors.js +0 -34
  362. package/dist/client/types/cli.js +0 -21
  363. package/dist/client/types/clientTypes.js +0 -10
  364. package/dist/client/types/common.js +0 -51
  365. package/dist/client/types/configTypes.js +0 -49
  366. package/dist/client/types/content.js +0 -19
  367. package/dist/client/types/contextTypes.js +0 -400
  368. package/dist/client/types/conversation.js +0 -47
  369. package/dist/client/types/conversationMemoryInterface.js +0 -6
  370. package/dist/client/types/domainTypes.js +0 -5
  371. package/dist/client/types/errors.js +0 -167
  372. package/dist/client/types/evaluation.js +0 -5
  373. package/dist/client/types/evaluationProviders.js +0 -5
  374. package/dist/client/types/evaluationTypes.js +0 -1
  375. package/dist/client/types/externalMcp.js +0 -6
  376. package/dist/client/types/fileReferenceTypes.js +0 -8
  377. package/dist/client/types/fileTypes.js +0 -4
  378. package/dist/client/types/generateTypes.js +0 -1
  379. package/dist/client/types/guardrails.js +0 -1
  380. package/dist/client/types/hitlTypes.js +0 -8
  381. package/dist/client/types/index.js +0 -57
  382. package/dist/client/types/mcpTypes.js +0 -5
  383. package/dist/client/types/middlewareTypes.js +0 -1
  384. package/dist/client/types/modelTypes.js +0 -30
  385. package/dist/client/types/multimodal.js +0 -135
  386. package/dist/client/types/observability.js +0 -6
  387. package/dist/client/types/pptTypes.js +0 -82
  388. package/dist/client/types/providers.js +0 -111
  389. package/dist/client/types/proxyTypes.js +0 -16
  390. package/dist/client/types/ragTypes.js +0 -7
  391. package/dist/client/types/sdkTypes.js +0 -8
  392. package/dist/client/types/serviceTypes.js +0 -5
  393. package/dist/client/types/streamTypes.js +0 -1
  394. package/dist/client/types/subscriptionTypes.js +0 -9
  395. package/dist/client/types/taskClassificationTypes.js +0 -5
  396. package/dist/client/types/tools.js +0 -24
  397. package/dist/client/types/ttsTypes.js +0 -57
  398. package/dist/client/types/typeAliases.js +0 -48
  399. package/dist/client/types/utilities.js +0 -4
  400. package/dist/client/types/workflowTypes.js +0 -30
  401. package/dist/client/utils/async/withTimeout.js +0 -98
  402. package/dist/client/utils/asyncMutex.js +0 -60
  403. package/dist/client/utils/conversationMemory.js +0 -431
  404. package/dist/client/utils/csvProcessor.js +0 -846
  405. package/dist/client/utils/errorHandling.js +0 -936
  406. package/dist/client/utils/evaluationUtils.js +0 -131
  407. package/dist/client/utils/factoryProcessing.js +0 -589
  408. package/dist/client/utils/fileDetector.js +0 -2161
  409. package/dist/client/utils/imageCache.js +0 -376
  410. package/dist/client/utils/imageProcessor.js +0 -704
  411. package/dist/client/utils/logger.js +0 -491
  412. package/dist/client/utils/mcpDefaults.js +0 -134
  413. package/dist/client/utils/messageBuilder.js +0 -1653
  414. package/dist/client/utils/modelAliasResolver.js +0 -54
  415. package/dist/client/utils/modelDetection.js +0 -80
  416. package/dist/client/utils/modelRouter.js +0 -292
  417. package/dist/client/utils/multimodalOptionsBuilder.js +0 -65
  418. package/dist/client/utils/observabilityHelpers.js +0 -47
  419. package/dist/client/utils/parameterValidation.js +0 -966
  420. package/dist/client/utils/pdfProcessor.js +0 -410
  421. package/dist/client/utils/performance.js +0 -222
  422. package/dist/client/utils/pricing.js +0 -340
  423. package/dist/client/utils/promptRedaction.js +0 -62
  424. package/dist/client/utils/providerConfig.js +0 -1009
  425. package/dist/client/utils/providerHealth.js +0 -1237
  426. package/dist/client/utils/providerRetry.js +0 -112
  427. package/dist/client/utils/providerUtils.js +0 -434
  428. package/dist/client/utils/rateLimiter.js +0 -200
  429. package/dist/client/utils/redis.js +0 -368
  430. package/dist/client/utils/retryHandler.js +0 -269
  431. package/dist/client/utils/retryability.js +0 -22
  432. package/dist/client/utils/sanitizers/svg.js +0 -481
  433. package/dist/client/utils/schemaConversion.js +0 -255
  434. package/dist/client/utils/taskClassificationUtils.js +0 -149
  435. package/dist/client/utils/taskClassifier.js +0 -94
  436. package/dist/client/utils/thinkingConfig.js +0 -104
  437. package/dist/client/utils/timeout.js +0 -359
  438. package/dist/client/utils/tokenEstimation.js +0 -142
  439. package/dist/client/utils/tokenLimits.js +0 -125
  440. package/dist/client/utils/tokenUtils.js +0 -239
  441. package/dist/client/utils/toolUtils.js +0 -75
  442. package/dist/client/utils/transformationUtils.js +0 -554
  443. package/dist/client/utils/ttsProcessor.js +0 -286
  444. package/dist/client/utils/typeUtils.js +0 -97
  445. package/dist/client/utils/videoAnalysisProcessor.js +0 -67
  446. package/dist/client/workflow/config.js +0 -398
  447. package/dist/client/workflow/core/ensembleExecutor.js +0 -407
  448. package/dist/client/workflow/core/judgeScorer.js +0 -544
  449. package/dist/client/workflow/core/responseConditioner.js +0 -225
  450. package/dist/client/workflow/core/types/conditionerTypes.js +0 -7
  451. package/dist/client/workflow/core/types/ensembleTypes.js +0 -7
  452. package/dist/client/workflow/core/types/index.js +0 -7
  453. package/dist/client/workflow/core/types/judgeTypes.js +0 -7
  454. package/dist/client/workflow/core/types/layerTypes.js +0 -7
  455. package/dist/client/workflow/core/types/registryTypes.js +0 -7
  456. package/dist/client/workflow/core/workflowRegistry.js +0 -304
  457. package/dist/client/workflow/core/workflowRunner.js +0 -586
  458. package/dist/client/workflow/index.js +0 -50
  459. package/dist/client/workflow/types.js +0 -9
  460. package/dist/client/workflow/utils/types/index.js +0 -7
  461. package/dist/client/workflow/utils/workflowMetrics.js +0 -311
  462. package/dist/client/workflow/utils/workflowValidation.js +0 -420
  463. package/dist/client/workflow/workflows/adaptiveWorkflow.js +0 -366
  464. package/dist/client/workflow/workflows/consensusWorkflow.js +0 -192
  465. package/dist/client/workflow/workflows/fallbackWorkflow.js +0 -225
  466. package/dist/client/workflow/workflows/multiJudgeWorkflow.js +0 -351
  467. /package/dist/client/{client/reactHooks.js → reactHooks.js} +0 -0
@@ -1,2936 +0,0 @@
1
- import dns from "node:dns";
2
- import { createVertex, } from "@ai-sdk/google-vertex";
3
- import { createVertexAnthropic, } from "@ai-sdk/google-vertex/anthropic";
4
- import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
5
- import { embed, embedMany, Output, stepCountIs, streamText, } from "ai";
6
- import fs from "fs";
7
- import os from "os";
8
- import path from "path";
9
- import { ErrorCategory, ErrorSeverity, } from "../constants/enums.js";
10
- import { BaseProvider } from "../core/baseProvider.js";
11
- import { DEFAULT_MAX_STEPS, GLOBAL_LOCATION_MODELS, } from "../core/constants.js";
12
- import { ModelConfigurationManager } from "../core/modelConfiguration.js";
13
- import { createProxyFetch } from "../proxy/proxyFetch.js";
14
- import { ATTR, tracers, withClientSpan } from "../telemetry/index.js";
15
- import { AuthenticationError, InvalidModelError, NetworkError, ProviderError, RateLimitError, } from "../types/errors.js";
16
- import { ERROR_CODES, NeuroLinkError } from "../utils/errorHandling.js";
17
- import { FileDetector } from "../utils/fileDetector.js";
18
- import { logger } from "../utils/logger.js";
19
- import { isGemini3Model } from "../utils/modelDetection.js";
20
- import { calculateCost } from "../utils/pricing.js";
21
- import { createGoogleAuthConfig, createVertexProjectConfig, validateApiKey, } from "../utils/providerConfig.js";
22
- import { convertZodToJsonSchema, inlineJsonSchema, } from "../utils/schemaConversion.js";
23
- import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
24
- import { estimateTokens } from "../utils/tokenEstimation.js";
25
- import { buildNativeConfig, buildNativeToolDeclarations, collectStreamChunks, collectStreamChunksIncremental, computeMaxSteps as computeMaxStepsShared, createTextChannel, executeNativeToolCalls, extractTextFromParts, handleMaxStepsTermination, pushModelResponseToHistory, sanitizeToolsForGemini, } from "./googleNativeGemini3.js";
26
- import { getModelId } from "./providerTypeUtils.js";
27
- // Import proper types for multimodal message handling
28
- // Keep-alive note: Node.js native fetch and undici (used by createProxyFetch)
29
- // handle HTTP keep-alive internally. The fetchWithRetry wrapper in proxyFetch.ts
30
- // provides retry protection for transient ECONNRESET/ETIMEDOUT errors.
31
- //
32
- // Auth isolation note: @ai-sdk/google-vertex resolves auth tokens (via
33
- // generateAuthToken → google-auth-library) BEFORE applying the user AbortSignal
34
- // to the main API call. Auth token refresh uses gaxios internally, not our
35
- // custom fetch, so it is inherently isolated from user cancellation signals.
36
- // The image generation path (getImageGenerationAccessToken) has an additional
37
- // explicit 15s timeout per attempt for direct REST API calls.
38
- /** Check whether an IP address belongs to a private, loopback, or link-local range. */
39
- function isPrivateOrLoopbackAddress(address) {
40
- const lower = address.toLowerCase();
41
- // IPv4 loopback, unspecified, and private ranges
42
- if (address.startsWith("127.") || address === "0.0.0.0") {
43
- return true;
44
- }
45
- if (address.startsWith("10.") || address.startsWith("192.168.")) {
46
- return true;
47
- }
48
- if (/^172\.(1[6-9]|2\d|3[01])\./.test(address)) {
49
- return true;
50
- }
51
- // IPv6 loopback, link-local, unique-local
52
- if (address === "::1" ||
53
- lower.startsWith("fe80:") ||
54
- lower.startsWith("fc00:") ||
55
- lower.startsWith("fd00:")) {
56
- return true;
57
- }
58
- return false;
59
- }
60
- const MAX_IMAGE_DOWNLOAD_BYTES = 10 * 1024 * 1024; // 10 MB
61
- const streamTracer = trace.getTracer("neurolink.provider.vertex");
62
- // Enhanced Anthropic support with direct imports
63
- // Using the dual provider architecture from Vercel AI SDK
64
- const hasAnthropicSupport = () => {
65
- try {
66
- // Verify the anthropic module is available
67
- return typeof createVertexAnthropic === "function";
68
- }
69
- catch {
70
- return false;
71
- }
72
- };
73
- // Configuration helpers - now using consolidated utility
74
- const getVertexProjectId = () => {
75
- return validateApiKey(createVertexProjectConfig());
76
- };
77
- const getVertexLocation = () => {
78
- return (process.env.GOOGLE_CLOUD_LOCATION ||
79
- process.env.VERTEX_LOCATION ||
80
- process.env.GOOGLE_VERTEX_LOCATION ||
81
- "us-central1");
82
- };
83
- const getDefaultVertexModel = () => {
84
- // Use gemini-2.5-flash as default - latest and best price-performance model
85
- // Override with VERTEX_MODEL environment variable if needed
86
- return process.env.VERTEX_MODEL || "gemini-2.5-flash";
87
- };
88
- const hasGoogleCredentials = () => {
89
- return !!(process.env.GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK ||
90
- process.env.GOOGLE_APPLICATION_CREDENTIALS ||
91
- process.env.GOOGLE_SERVICE_ACCOUNT_KEY ||
92
- (process.env.GOOGLE_AUTH_CLIENT_EMAIL &&
93
- process.env.GOOGLE_AUTH_PRIVATE_KEY));
94
- };
95
- // Module-level cache for runtime-created credentials file to avoid per-request writes
96
- let cachedCredentialsPath = null;
97
- // Enhanced Vertex settings creation with authentication fallback and proxy support
98
- const createVertexSettings = async (region) => {
99
- const location = region || getVertexLocation();
100
- const project = getVertexProjectId();
101
- const baseSettings = {
102
- project,
103
- location,
104
- fetch: createProxyFetch(),
105
- };
106
- // Special handling for global endpoint
107
- // Google's global endpoint uses aiplatform.googleapis.com (no region prefix)
108
- // instead of {region}-aiplatform.googleapis.com
109
- if (location === "global") {
110
- baseSettings.baseURL = `https://aiplatform.googleapis.com/v1/projects/${project}/locations/global/publishers/google`;
111
- logger.debug("[GoogleVertexProvider] Using global endpoint", {
112
- baseURL: baseSettings.baseURL,
113
- location,
114
- project,
115
- });
116
- }
117
- // šŸŽÆ OPTION 2: Create credentials file from environment variables at runtime
118
- // This solves the problem where GOOGLE_APPLICATION_CREDENTIALS exists in ZSHRC locally
119
- // but the file doesn't exist on production servers
120
- // First, try to create credentials file from individual environment variables
121
- const requiredEnvVarsForFile = {
122
- type: process.env.GOOGLE_AUTH_TYPE,
123
- project_id: process.env.GOOGLE_AUTH_BREEZE_PROJECT_ID,
124
- private_key: process.env.GOOGLE_AUTH_PRIVATE_KEY,
125
- client_email: process.env.GOOGLE_AUTH_CLIENT_EMAIL,
126
- client_id: process.env.GOOGLE_AUTH_CLIENT_ID,
127
- auth_uri: process.env.GOOGLE_AUTH_AUTH_URI,
128
- token_uri: process.env.GOOGLE_AUTH_TOKEN_URI,
129
- auth_provider_x509_cert_url: process.env.GOOGLE_AUTH_AUTH_PROVIDER_CERT_URL,
130
- client_x509_cert_url: process.env.GOOGLE_AUTH_CLIENT_CERT_URL,
131
- universe_domain: process.env.GOOGLE_AUTH_UNIVERSE_DOMAIN,
132
- };
133
- // If we have the essential fields, create a runtime credentials file (cached)
134
- if (requiredEnvVarsForFile.client_email &&
135
- requiredEnvVarsForFile.private_key) {
136
- // Return cached path if already written and still exists on disk
137
- if (cachedCredentialsPath && fs.existsSync(cachedCredentialsPath)) {
138
- process.env.GOOGLE_APPLICATION_CREDENTIALS = cachedCredentialsPath;
139
- return baseSettings;
140
- }
141
- try {
142
- // Build complete service account credentials object
143
- const serviceAccountCredentials = {
144
- type: requiredEnvVarsForFile.type || "service_account",
145
- project_id: requiredEnvVarsForFile.project_id || getVertexProjectId(),
146
- private_key: requiredEnvVarsForFile.private_key.replace(/\\n/g, "\n"),
147
- client_email: requiredEnvVarsForFile.client_email,
148
- client_id: requiredEnvVarsForFile.client_id || "",
149
- auth_uri: requiredEnvVarsForFile.auth_uri ||
150
- "https://accounts.google.com/o/oauth2/auth",
151
- token_uri: requiredEnvVarsForFile.token_uri ||
152
- "https://oauth2.googleapis.com/token",
153
- auth_provider_x509_cert_url: requiredEnvVarsForFile.auth_provider_x509_cert_url ||
154
- "https://www.googleapis.com/oauth2/v1/certs",
155
- client_x509_cert_url: requiredEnvVarsForFile.client_x509_cert_url || "",
156
- universe_domain: requiredEnvVarsForFile.universe_domain || "googleapis.com",
157
- };
158
- // Create temporary credentials file with restricted permissions
159
- const tmpDir = os.tmpdir();
160
- const credentialsFileName = `google-credentials-${Date.now()}-${Math.random().toString(36).substring(2, 11)}.json`;
161
- const credentialsFilePath = path.join(tmpDir, credentialsFileName);
162
- fs.writeFileSync(credentialsFilePath, JSON.stringify(serviceAccountCredentials, null, 2), { mode: 0o600 });
163
- cachedCredentialsPath = credentialsFilePath;
164
- // Register cleanup on process exit to remove the credentials file
165
- process.once("exit", () => {
166
- try {
167
- if (cachedCredentialsPath && fs.existsSync(cachedCredentialsPath)) {
168
- fs.unlinkSync(cachedCredentialsPath);
169
- }
170
- }
171
- catch {
172
- /* ignore cleanup errors */
173
- }
174
- });
175
- // Set the environment variable to point to our runtime-created file
176
- process.env.GOOGLE_APPLICATION_CREDENTIALS = credentialsFilePath;
177
- return baseSettings;
178
- }
179
- catch {
180
- // Silent error handling for runtime credentials file creation
181
- }
182
- }
183
- // šŸŽÆ OPTION 1: Check for principal account authentication (Accept any valid GOOGLE_APPLICATION_CREDENTIALS file (service account OR ADC))
184
- if (process.env.GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK) {
185
- const credentialsPath = process.env.GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK;
186
- // Check if the credentials file exists
187
- let fileExists;
188
- try {
189
- fileExists = fs.existsSync(credentialsPath);
190
- }
191
- catch {
192
- fileExists = false;
193
- }
194
- if (fileExists) {
195
- return baseSettings;
196
- }
197
- }
198
- else {
199
- if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
200
- const credentialsPath = process.env.GOOGLE_APPLICATION_CREDENTIALS;
201
- // Check if the credentials file exists
202
- let fileExists;
203
- try {
204
- fileExists = fs.existsSync(credentialsPath);
205
- }
206
- catch {
207
- fileExists = false;
208
- }
209
- if (fileExists) {
210
- return baseSettings;
211
- }
212
- }
213
- }
214
- // Fallback to explicit credentials for development and production
215
- // Enhanced to check ALL required fields from the .env file configuration
216
- const requiredEnvVars = {
217
- type: process.env.GOOGLE_AUTH_TYPE,
218
- project_id: process.env.GOOGLE_AUTH_BREEZE_PROJECT_ID,
219
- private_key: process.env.GOOGLE_AUTH_PRIVATE_KEY,
220
- client_email: process.env.GOOGLE_AUTH_CLIENT_EMAIL,
221
- client_id: process.env.GOOGLE_AUTH_CLIENT_ID,
222
- auth_uri: process.env.GOOGLE_AUTH_AUTH_URI,
223
- token_uri: process.env.GOOGLE_AUTH_TOKEN_URI,
224
- auth_provider_x509_cert_url: process.env.GOOGLE_AUTH_AUTH_PROVIDER_CERT_URL,
225
- client_x509_cert_url: process.env.GOOGLE_AUTH_CLIENT_CERT_URL,
226
- universe_domain: process.env.GOOGLE_AUTH_UNIVERSE_DOMAIN,
227
- };
228
- // Check if we have the minimal required fields (client_email and private_key are essential)
229
- if (requiredEnvVars.client_email && requiredEnvVars.private_key) {
230
- logger.debug("Using explicit service account credentials authentication", {
231
- authMethod: "explicit_service_account_credentials",
232
- hasType: !!requiredEnvVars.type,
233
- hasProjectId: !!requiredEnvVars.project_id,
234
- hasClientEmail: !!requiredEnvVars.client_email,
235
- hasPrivateKey: !!requiredEnvVars.private_key,
236
- hasClientId: !!requiredEnvVars.client_id,
237
- hasAuthUri: !!requiredEnvVars.auth_uri,
238
- hasTokenUri: !!requiredEnvVars.token_uri,
239
- hasAuthProviderCertUrl: !!requiredEnvVars.auth_provider_x509_cert_url,
240
- hasClientCertUrl: !!requiredEnvVars.client_x509_cert_url,
241
- hasUniverseDomain: !!requiredEnvVars.universe_domain,
242
- credentialsCompleteness: "using_individual_env_vars_as_fallback",
243
- });
244
- // Build complete service account credentials object
245
- const serviceAccountCredentials = {
246
- type: requiredEnvVars.type || "service_account",
247
- project_id: requiredEnvVars.project_id || getVertexProjectId(),
248
- private_key: requiredEnvVars.private_key.replace(/\\n/g, "\n"),
249
- client_email: requiredEnvVars.client_email,
250
- client_id: requiredEnvVars.client_id || "",
251
- auth_uri: requiredEnvVars.auth_uri || "https://accounts.google.com/o/oauth2/auth",
252
- token_uri: requiredEnvVars.token_uri || "https://oauth2.googleapis.com/token",
253
- auth_provider_x509_cert_url: requiredEnvVars.auth_provider_x509_cert_url ||
254
- "https://www.googleapis.com/oauth2/v1/certs",
255
- client_x509_cert_url: requiredEnvVars.client_x509_cert_url || "",
256
- universe_domain: requiredEnvVars.universe_domain || "googleapis.com",
257
- };
258
- return {
259
- ...baseSettings,
260
- googleAuthOptions: {
261
- credentials: serviceAccountCredentials,
262
- },
263
- };
264
- }
265
- // Log comprehensive warning if no valid authentication is available
266
- logger.warn("No valid authentication found for Google Vertex AI", {
267
- authMethod: "none",
268
- authenticationAttempts: {
269
- principalAccountFile: {
270
- envVarSet: !!process.env.GOOGLE_APPLICATION_CREDENTIALS,
271
- filePath: process.env.GOOGLE_APPLICATION_CREDENTIALS || "NOT_SET",
272
- fileExists: false, // We already checked above
273
- },
274
- explicitCredentials: {
275
- hasClientEmail: !!requiredEnvVars.client_email,
276
- hasPrivateKey: !!requiredEnvVars.private_key,
277
- hasProjectId: !!requiredEnvVars.project_id,
278
- hasType: !!requiredEnvVars.type,
279
- missingFields: Object.entries(requiredEnvVars)
280
- .filter(([_key, value]) => !value)
281
- .map(([key]) => key),
282
- },
283
- },
284
- troubleshooting: [
285
- "1. Ensure GOOGLE_APPLICATION_CREDENTIALS points to an existing file, OR",
286
- "2. Set individual environment variables: GOOGLE_AUTH_CLIENT_EMAIL and GOOGLE_AUTH_PRIVATE_KEY",
287
- ],
288
- });
289
- return baseSettings;
290
- };
291
- // Create Anthropic-specific Vertex settings with the same authentication and proxy support
292
- const createVertexAnthropicSettings = async (region) => {
293
- // The @ai-sdk/google-vertex SDK constructs Anthropic URLs as:
294
- // https://{location}-aiplatform.googleapis.com/...
295
- // When location is "global", this creates "https://global-aiplatform.googleapis.com"
296
- // which is invalid. The correct global endpoint omits the region prefix entirely.
297
- // Since the SDK doesn't handle this, redirect "global" to "us-east5" for Anthropic.
298
- const anthropicRegion = !region || region === "global" ? "us-east5" : region;
299
- const baseVertexSettings = await createVertexSettings(anthropicRegion);
300
- // GoogleVertexAnthropicProviderSettings extends GoogleVertexProviderSettings
301
- // so we can use the same settings with proper typing
302
- return {
303
- project: baseVertexSettings.project,
304
- location: baseVertexSettings.location,
305
- fetch: baseVertexSettings.fetch,
306
- ...(baseVertexSettings.googleAuthOptions && {
307
- googleAuthOptions: baseVertexSettings.googleAuthOptions,
308
- }),
309
- };
310
- };
311
- // Helper function to determine if a model is an Anthropic model
312
- const isAnthropicModel = (modelName) => {
313
- return modelName.toLowerCase().includes("claude");
314
- };
315
- /**
316
- * Vertex Model Aliases
317
- *
318
- * Maps shorthand model names to their full versioned IDs required by the
319
- * Vertex AI API. This allows users to pass convenient names like
320
- * "claude-sonnet-4-5" instead of "claude-sonnet-4-5@20250929".
321
- *
322
- * Alias resolution runs at the very start of getModel() so that all
323
- * downstream code (isAnthropicModel, validateAnthropicModelName, etc.)
324
- * sees the canonical versioned name.
325
- *
326
- * To add a new model: simply add an entry mapping the shorthand to the
327
- * full versioned string. No other changes are needed.
328
- */
329
- export const VERTEX_MODEL_ALIASES = {
330
- // Claude 4.x shorthand aliases → versioned names
331
- "claude-sonnet-4-5": "claude-sonnet-4-5@20250929",
332
- "claude-opus-4-5": "claude-opus-4-5@20251124",
333
- "claude-haiku-4-5": "claude-haiku-4-5@20251001",
334
- "claude-sonnet-4": "claude-sonnet-4@20250514",
335
- "claude-opus-4": "claude-opus-4@20250514",
336
- "claude-opus-4-1": "claude-opus-4-1@20250805",
337
- // Claude 3.x shorthand aliases → versioned names
338
- "claude-3-7-sonnet": "claude-3-7-sonnet@20250219",
339
- "claude-3-5-sonnet": "claude-3-5-sonnet-20241022",
340
- "claude-3-5-haiku": "claude-3-5-haiku-20241022",
341
- "claude-3-opus": "claude-3-opus-20240229",
342
- "claude-3-sonnet": "claude-3-sonnet-20240229",
343
- "claude-3-haiku": "claude-3-haiku-20240307",
344
- // Gemini shorthand aliases
345
- "gemini-3-pro": "gemini-3.1-pro-preview",
346
- "gemini-3-flash": "gemini-3-flash-preview",
347
- };
348
- /**
349
- * Google Vertex AI Provider v2 - BaseProvider Implementation
350
- *
351
- * Features:
352
- * - Extends BaseProvider for shared functionality
353
- * - Preserves existing Google Cloud authentication
354
- * - Maintains Anthropic model support via dynamic imports
355
- * - Fresh model creation for each request
356
- * - Enhanced error handling with setup guidance
357
- * - Tool registration and context management
358
- *
359
- * @important Structured Output Limitation (Gemini Models Only)
360
- * Google Gemini models on Vertex AI cannot combine function calling (tools) with
361
- * structured output (JSON schema). When using schemas, you MUST set disableTools: true.
362
- *
363
- * Error without disableTools:
364
- * "Function calling with a response mime type: 'application/json' is unsupported"
365
- *
366
- * This limitation ONLY affects Gemini models. Anthropic Claude models via Vertex
367
- * AI do NOT have this limitation and support both tools + schemas simultaneously.
368
- *
369
- * @example Gemini models with schemas
370
- * ```typescript
371
- * const provider = new GoogleVertexProvider("gemini-2.5-flash");
372
- * const result = await provider.generate({
373
- * input: { text: "Analyze data" },
374
- * schema: MySchema,
375
- * output: { format: "json" },
376
- * disableTools: true // Required for Gemini models
377
- * });
378
- * ```
379
- *
380
- * @example Claude models (no limitation)
381
- * ```typescript
382
- * const provider = new GoogleVertexProvider("claude-3-5-sonnet-20241022");
383
- * const result = await provider.generate({
384
- * input: { text: "Analyze data" },
385
- * schema: MySchema,
386
- * output: { format: "json" }
387
- * // No disableTools needed - Claude supports both
388
- * });
389
- * ```
390
- *
391
- * @note Gemini 3 Pro Preview (November 2025) will support combining tools + schemas
392
- * @see https://cloud.google.com/vertex-ai/docs/generative-ai/learn/models
393
- */
394
- export class GoogleVertexProvider extends BaseProvider {
395
- projectId;
396
- location;
397
- registeredTools = new Map();
398
- toolContext = {};
399
- // Memory-managed cache for model configuration lookups to avoid repeated calls
400
- // Uses WeakMap for automatic cleanup and bounded LRU for recently used models
401
- static modelConfigCache = new Map();
402
- static modelConfigCacheTime = 0;
403
- static CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
404
- static MAX_CACHE_SIZE = 50; // Prevent memory leaks by limiting cache size
405
- // Memory-managed cache for maxTokens handling decisions to optimize streaming performance
406
- static maxTokensCache = new Map();
407
- static maxTokensCacheTime = 0;
408
- constructor(modelName, _providerName, sdk, region) {
409
- super(modelName, "vertex", sdk);
410
- // Validate Google Cloud credentials - now using consolidated utility
411
- if (!hasGoogleCredentials()) {
412
- validateApiKey(createGoogleAuthConfig());
413
- }
414
- // Initialize Google Cloud configuration
415
- this.projectId = getVertexProjectId();
416
- this.location = region || getVertexLocation();
417
- logger.debug("[GoogleVertexProvider] Constructor initialized", {
418
- regionParam: region,
419
- resolvedLocation: this.location,
420
- projectId: this.projectId,
421
- });
422
- logger.debug("Google Vertex AI BaseProvider v2 initialized", {
423
- modelName: this.modelName,
424
- projectId: this.projectId,
425
- location: this.location,
426
- provider: this.providerName,
427
- });
428
- }
429
- getProviderName() {
430
- return "vertex";
431
- }
432
- getDefaultModel() {
433
- return getDefaultVertexModel();
434
- }
435
- /**
436
- * Get the default embedding model for Google Vertex
437
- * @returns The default Vertex AI embedding model name
438
- */
439
- getDefaultEmbeddingModel() {
440
- return (process.env.VERTEX_EMBEDDING_MODEL ||
441
- process.env.GOOGLE_EMBEDDING_MODEL ||
442
- "text-embedding-004");
443
- }
444
- /**
445
- * Returns the Vercel AI SDK model instance for Google Vertex
446
- * Creates fresh model instances for each request
447
- */
448
- async getAISDKModel() {
449
- const model = await this.getModel();
450
- return model;
451
- }
452
- /**
453
- * Resolve a raw model name through the alias map.
454
- * Used internally to normalize model names before any API calls.
455
- */
456
- resolveAlias(modelName) {
457
- return VERTEX_MODEL_ALIASES[modelName] ?? modelName;
458
- }
459
- /**
460
- * Initialize model creation tracking
461
- */
462
- initializeModelCreationLogging() {
463
- const modelCreationId = `vertex-model-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
464
- const modelCreationStartTime = Date.now();
465
- const modelCreationHrTimeStart = process.hrtime.bigint();
466
- // Resolve shorthand model aliases (e.g. "claude-sonnet-4-5" → "claude-sonnet-4-5@20250929")
467
- // before any downstream logic that depends on the versioned name.
468
- const rawModelName = this.modelName || getDefaultVertexModel();
469
- const modelName = VERTEX_MODEL_ALIASES[rawModelName] ?? rawModelName;
470
- return {
471
- modelCreationId,
472
- modelCreationStartTime,
473
- modelCreationHrTimeStart,
474
- modelName,
475
- };
476
- }
477
- /**
478
- * Check if model is Anthropic-based and attempt creation
479
- */
480
- async attemptAnthropicModelCreation(modelName, modelCreationId, modelCreationStartTime, modelCreationHrTimeStart) {
481
- const isAnthropic = isAnthropicModel(modelName);
482
- if (!isAnthropic) {
483
- return null;
484
- }
485
- logger.debug("Creating Anthropic model using vertexAnthropic provider", {
486
- modelName,
487
- });
488
- if (!hasAnthropicSupport()) {
489
- logger.warn(`[GoogleVertexProvider] Anthropic support not available, falling back to Google model`);
490
- return null;
491
- }
492
- try {
493
- const anthropicModel = await this.createAnthropicModel(modelName);
494
- if (anthropicModel) {
495
- return anthropicModel;
496
- }
497
- // Anthropic model creation returned null, falling back to Google model
498
- }
499
- catch (error) {
500
- logger.error(`[GoogleVertexProvider] āŒ LOG_POINT_V006_ANTHROPIC_MODEL_ERROR`, {
501
- logPoint: "V006_ANTHROPIC_MODEL_ERROR",
502
- modelCreationId,
503
- timestamp: new Date().toISOString(),
504
- elapsedMs: Date.now() - modelCreationStartTime,
505
- elapsedNs: (process.hrtime.bigint() - modelCreationHrTimeStart).toString(),
506
- modelName,
507
- error: error instanceof Error ? error.message : String(error),
508
- errorName: error instanceof Error ? error.name : "UnknownError",
509
- errorStack: error instanceof Error ? error.stack : undefined,
510
- fallbackToGoogle: true,
511
- message: "Anthropic model creation failed - falling back to Google model",
512
- });
513
- }
514
- // Fall back to regular model if Anthropic not available
515
- logger.warn(`Anthropic model ${modelName} requested but not available, falling back to Google model`);
516
- return null;
517
- }
518
- /**
519
- * Create Google Vertex model with comprehensive logging and error handling
520
- */
521
- async createGoogleVertexModel(modelName, modelCreationId, modelCreationStartTime, modelCreationHrTimeStart) {
522
- logger.debug("Creating Google Vertex model", {
523
- modelName,
524
- project: this.projectId,
525
- location: this.location,
526
- });
527
- const vertexSettingsStartTime = process.hrtime.bigint();
528
- logger.debug(`[GoogleVertexProvider] āš™ļø LOG_POINT_V008_VERTEX_SETTINGS_START`, {
529
- logPoint: "V008_VERTEX_SETTINGS_START",
530
- modelCreationId,
531
- timestamp: new Date().toISOString(),
532
- elapsedMs: Date.now() - modelCreationStartTime,
533
- elapsedNs: (process.hrtime.bigint() - modelCreationHrTimeStart).toString(),
534
- vertexSettingsStartTimeNs: vertexSettingsStartTime.toString(),
535
- // Network configuration analysis
536
- networkConfig: {
537
- projectId: this.projectId,
538
- location: this.location,
539
- expectedEndpoint: `https://${this.location}-aiplatform.googleapis.com`,
540
- httpProxy: process.env.HTTP_PROXY || process.env.http_proxy,
541
- httpsProxy: process.env.HTTPS_PROXY || process.env.https_proxy,
542
- noProxy: process.env.NO_PROXY || process.env.no_proxy,
543
- proxyConfigured: !!(process.env.HTTP_PROXY ||
544
- process.env.HTTPS_PROXY ||
545
- process.env.http_proxy ||
546
- process.env.https_proxy),
547
- },
548
- message: "Starting Vertex settings creation with network configuration analysis",
549
- });
550
- try {
551
- const vertexSettings = await createVertexSettings(this.location);
552
- const vertexSettingsEndTime = process.hrtime.bigint();
553
- const vertexSettingsDurationNs = vertexSettingsEndTime - vertexSettingsStartTime;
554
- logger.debug(`[GoogleVertexProvider] āœ… LOG_POINT_V009_VERTEX_SETTINGS_SUCCESS`, {
555
- logPoint: "V009_VERTEX_SETTINGS_SUCCESS",
556
- modelCreationId,
557
- timestamp: new Date().toISOString(),
558
- elapsedMs: Date.now() - modelCreationStartTime,
559
- elapsedNs: (process.hrtime.bigint() - modelCreationHrTimeStart).toString(),
560
- vertexSettingsDurationNs: vertexSettingsDurationNs.toString(),
561
- vertexSettingsDurationMs: Number(vertexSettingsDurationNs) / 1000000,
562
- // Settings analysis
563
- vertexSettingsAnalysis: {
564
- hasSettings: !!vertexSettings,
565
- settingsType: typeof vertexSettings,
566
- settingsKeys: vertexSettings ? Object.keys(vertexSettings) : [],
567
- projectId: vertexSettings?.project,
568
- location: vertexSettings?.location,
569
- hasFetch: !!vertexSettings?.fetch,
570
- hasGoogleAuthOptions: !!vertexSettings?.googleAuthOptions,
571
- settingsSize: vertexSettings
572
- ? JSON.stringify(vertexSettings).length
573
- : 0,
574
- },
575
- message: "Vertex settings created successfully",
576
- });
577
- return await this.createVertexInstance(vertexSettings, modelName, modelCreationId, modelCreationStartTime, modelCreationHrTimeStart);
578
- }
579
- catch (error) {
580
- const vertexSettingsErrorTime = process.hrtime.bigint();
581
- const vertexSettingsDurationNs = vertexSettingsErrorTime - vertexSettingsStartTime;
582
- const totalErrorDurationNs = vertexSettingsErrorTime - modelCreationHrTimeStart;
583
- logger.error(`[GoogleVertexProvider] āŒ LOG_POINT_V014_VERTEX_SETTINGS_ERROR`, {
584
- logPoint: "V014_VERTEX_SETTINGS_ERROR",
585
- modelCreationId,
586
- timestamp: new Date().toISOString(),
587
- totalElapsedMs: Date.now() - modelCreationStartTime,
588
- totalElapsedNs: totalErrorDurationNs.toString(),
589
- totalErrorDurationMs: Number(totalErrorDurationNs) / 1000000,
590
- vertexSettingsDurationNs: vertexSettingsDurationNs.toString(),
591
- vertexSettingsDurationMs: Number(vertexSettingsDurationNs) / 1000000,
592
- // Comprehensive error analysis
593
- error: error instanceof Error ? error.message : String(error),
594
- errorName: error instanceof Error ? error.name : "UnknownError",
595
- errorStack: error instanceof Error ? error.stack : undefined,
596
- // Network diagnostic information
597
- networkDiagnostics: {
598
- errorCode: error?.code || "UNKNOWN",
599
- errorErrno: error?.errno || "UNKNOWN",
600
- errorAddress: error?.address || "UNKNOWN",
601
- errorPort: error?.port || "UNKNOWN",
602
- errorSyscall: error?.syscall || "UNKNOWN",
603
- errorHostname: error?.hostname || "UNKNOWN",
604
- isTimeoutError: error instanceof Error &&
605
- (error.message.includes("timeout") ||
606
- error.message.includes("ETIMEDOUT")),
607
- isNetworkError: error instanceof Error &&
608
- (error.message.includes("ENOTFOUND") ||
609
- error.message.includes("ECONNREFUSED") ||
610
- error.message.includes("ETIMEDOUT")),
611
- isAuthError: error instanceof Error &&
612
- (error.message.includes("PERMISSION_DENIED") ||
613
- error.message.includes("401") ||
614
- error.message.includes("403")),
615
- infrastructureIssue: error instanceof Error &&
616
- error.message.includes("ETIMEDOUT") &&
617
- error.message.includes("aiplatform.googleapis.com"),
618
- },
619
- // Environment at error time
620
- errorEnvironment: {
621
- httpProxy: process.env.HTTP_PROXY || process.env.http_proxy,
622
- httpsProxy: process.env.HTTPS_PROXY || process.env.https_proxy,
623
- googleAppCreds: process.env.GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK ||
624
- process.env.GOOGLE_APPLICATION_CREDENTIALS ||
625
- "NOT_SET",
626
- hasGoogleServiceKey: !!process.env.GOOGLE_SERVICE_ACCOUNT_KEY,
627
- nodeVersion: process.version,
628
- memoryUsage: process.memoryUsage(),
629
- uptime: process.uptime(),
630
- },
631
- message: "Vertex settings creation failed - critical network/authentication error",
632
- });
633
- throw error;
634
- }
635
- }
636
- /**
637
- * Create Vertex AI instance and model with comprehensive logging
638
- */
639
- async createVertexInstance(vertexSettings, modelName, modelCreationId, modelCreationStartTime, modelCreationHrTimeStart) {
640
- const vertexInstanceStartTime = process.hrtime.bigint();
641
- logger.debug(`[GoogleVertexProvider] šŸ—ļø LOG_POINT_V010_VERTEX_INSTANCE_START`, {
642
- logPoint: "V010_VERTEX_INSTANCE_START",
643
- modelCreationId,
644
- timestamp: new Date().toISOString(),
645
- elapsedMs: Date.now() - modelCreationStartTime,
646
- elapsedNs: (process.hrtime.bigint() - modelCreationHrTimeStart).toString(),
647
- vertexInstanceStartTimeNs: vertexInstanceStartTime.toString(),
648
- // Pre-creation network environment
649
- networkEnvironment: {
650
- dnsServers: (() => {
651
- try {
652
- return dns.getServers ? dns.getServers() : "NOT_AVAILABLE";
653
- }
654
- catch {
655
- return "NOT_AVAILABLE";
656
- }
657
- })(),
658
- networkInterfaces: (() => {
659
- try {
660
- return Object.keys(os.networkInterfaces());
661
- }
662
- catch {
663
- return [];
664
- }
665
- })(),
666
- hostname: (() => {
667
- try {
668
- return os.hostname();
669
- }
670
- catch {
671
- return "UNKNOWN";
672
- }
673
- })(),
674
- platform: (() => {
675
- try {
676
- return os.platform();
677
- }
678
- catch {
679
- return "UNKNOWN";
680
- }
681
- })(),
682
- release: (() => {
683
- try {
684
- return os.release();
685
- }
686
- catch {
687
- return "UNKNOWN";
688
- }
689
- })(),
690
- },
691
- message: "Creating Vertex AI instance",
692
- });
693
- const vertex = createVertex(vertexSettings);
694
- const vertexInstanceEndTime = process.hrtime.bigint();
695
- const vertexInstanceDurationNs = vertexInstanceEndTime - vertexInstanceStartTime;
696
- logger.debug(`[GoogleVertexProvider] āœ… LOG_POINT_V011_VERTEX_INSTANCE_SUCCESS`, {
697
- logPoint: "V011_VERTEX_INSTANCE_SUCCESS",
698
- modelCreationId,
699
- timestamp: new Date().toISOString(),
700
- elapsedMs: Date.now() - modelCreationStartTime,
701
- elapsedNs: (process.hrtime.bigint() - modelCreationHrTimeStart).toString(),
702
- vertexInstanceDurationNs: vertexInstanceDurationNs.toString(),
703
- vertexInstanceDurationMs: Number(vertexInstanceDurationNs) / 1000000,
704
- hasVertexInstance: !!vertex,
705
- vertexInstanceType: typeof vertex,
706
- message: "Vertex AI instance created successfully",
707
- });
708
- const modelInstanceStartTime = process.hrtime.bigint();
709
- logger.debug(`[GoogleVertexProvider] šŸŽÆ LOG_POINT_V012_MODEL_INSTANCE_START`, {
710
- logPoint: "V012_MODEL_INSTANCE_START",
711
- modelCreationId,
712
- timestamp: new Date().toISOString(),
713
- elapsedMs: Date.now() - modelCreationStartTime,
714
- elapsedNs: (process.hrtime.bigint() - modelCreationHrTimeStart).toString(),
715
- modelInstanceStartTimeNs: modelInstanceStartTime.toString(),
716
- modelName,
717
- hasVertexInstance: !!vertex,
718
- message: "Creating model instance from Vertex AI instance",
719
- });
720
- const model = vertex(modelName);
721
- const modelInstanceEndTime = process.hrtime.bigint();
722
- const modelInstanceDurationNs = modelInstanceEndTime - modelInstanceStartTime;
723
- const totalModelCreationDurationNs = modelInstanceEndTime - modelCreationHrTimeStart;
724
- logger.info(`[GoogleVertexProvider] šŸ LOG_POINT_V013_MODEL_CREATION_COMPLETE`, {
725
- logPoint: "V013_MODEL_CREATION_COMPLETE",
726
- modelCreationId,
727
- timestamp: new Date().toISOString(),
728
- totalElapsedMs: Date.now() - modelCreationStartTime,
729
- totalElapsedNs: totalModelCreationDurationNs.toString(),
730
- totalDurationMs: Number(totalModelCreationDurationNs) / 1000000,
731
- modelInstanceDurationNs: modelInstanceDurationNs.toString(),
732
- modelInstanceDurationMs: Number(modelInstanceDurationNs) / 1000000,
733
- // Final model analysis
734
- finalModel: {
735
- hasModel: !!model,
736
- modelType: typeof model,
737
- modelName,
738
- isAnthropicModel: isAnthropicModel(modelName),
739
- projectId: this.projectId,
740
- location: this.location,
741
- },
742
- // Performance summary
743
- performanceSummary: {
744
- vertexSettingsDurationMs: Number(vertexInstanceDurationNs) / 1000000,
745
- vertexInstanceDurationMs: Number(vertexInstanceDurationNs) / 1000000,
746
- modelInstanceDurationMs: Number(modelInstanceDurationNs) / 1000000,
747
- totalDurationMs: Number(totalModelCreationDurationNs) / 1000000,
748
- },
749
- // Memory usage
750
- finalMemoryUsage: process.memoryUsage(),
751
- message: "Model creation completed successfully - ready for API calls",
752
- });
753
- return model;
754
- }
755
- /**
756
- * Gets the appropriate model instance (Google or Anthropic)
757
- * Uses dual provider architecture for proper model routing
758
- * Creates fresh instances for each request to ensure proper authentication
759
- */
760
- async getModel() {
761
- // Initialize logging and setup (alias resolution happens inside)
762
- const { modelCreationId, modelCreationStartTime, modelCreationHrTimeStart, modelName, } = this.initializeModelCreationLogging();
763
- // Check if this is an Anthropic model and attempt creation
764
- const anthropicModel = await this.attemptAnthropicModelCreation(modelName, modelCreationId, modelCreationStartTime, modelCreationHrTimeStart);
765
- if (anthropicModel) {
766
- return anthropicModel;
767
- }
768
- // Fall back to Google Vertex model creation
769
- return await this.createGoogleVertexModel(modelName, modelCreationId, modelCreationStartTime, modelCreationHrTimeStart);
770
- }
771
- // executeGenerate removed - BaseProvider handles all generation with tools
772
- /**
773
- * Validate stream options
774
- */
775
- validateStreamOptionsOnly(options) {
776
- this.validateStreamOptions(options);
777
- }
778
- async executeStream(options, analysisSchema) {
779
- // Check if this is a Gemini 3 model with tools - use native SDK for thought_signature
780
- const gemini3CheckModelName = this.resolveAlias(options.model || this.modelName || getDefaultVertexModel());
781
- // Structured output (analysisSchema, JSON format, or schema) is incompatible with tools on Gemini.
782
- // Compute once and reuse in both the native Gemini 3 gate and the streamText fallback path.
783
- const wantsStructuredOutput = analysisSchema || options.output?.format === "json" || options.schema;
784
- // Check for tools from options AND from SDK (MCP tools)
785
- // Need to check early if we should route to native SDK
786
- const gemini3CheckShouldUseTools = !options.disableTools && this.supportsTools() && !wantsStructuredOutput;
787
- const optionTools = options.tools || {};
788
- const sdkTools = gemini3CheckShouldUseTools ? await this.getAllTools() : {};
789
- const combinedToolCount = Object.keys(optionTools).length + Object.keys(sdkTools).length;
790
- const hasTools = gemini3CheckShouldUseTools && combinedToolCount > 0;
791
- if (isGemini3Model(gemini3CheckModelName) && hasTools) {
792
- // Process CSV files before routing to native SDK (bypasses normal message builder)
793
- const processedOptions = await this.processCSVFilesForNativeSDK(options);
794
- // Merge SDK tools into options for native SDK path
795
- const mergedOptions = {
796
- ...processedOptions,
797
- tools: { ...sdkTools, ...optionTools },
798
- };
799
- logger.info("[GoogleVertex] Routing Gemini 3 to native SDK for tool calling", {
800
- model: gemini3CheckModelName,
801
- optionToolCount: Object.keys(optionTools).length,
802
- sdkToolCount: Object.keys(sdkTools).length,
803
- totalToolCount: combinedToolCount,
804
- });
805
- return this.executeNativeGemini3Stream(mergedOptions);
806
- }
807
- // Initialize stream execution tracking
808
- const functionTag = "GoogleVertexProvider.executeStream";
809
- let chunkCount = 0;
810
- // Setup timeout controller
811
- const timeout = this.getTimeout(options);
812
- const timeoutController = createTimeoutController(timeout, this.providerName, "stream");
813
- try {
814
- // Validate stream options
815
- this.validateStreamOptionsOnly(options);
816
- // Build message array from options with multimodal support
817
- // Using protected helper from BaseProvider to eliminate code duplication
818
- const messages = await this.buildMessagesForStream(options);
819
- const model = await this.getAISDKModelWithMiddleware(options); // This is where network connection happens!
820
- // Get all available tools (direct + MCP + external + user-provided RAG tools) for streaming
821
- const shouldUseTools = !options.disableTools && this.supportsTools();
822
- const baseStreamTools = shouldUseTools ? await this.getAllTools() : {};
823
- const rawTools = shouldUseTools
824
- ? { ...baseStreamTools, ...(options.tools || {}) }
825
- : {};
826
- // Only sanitize for Gemini models (not Anthropic/Claude models routed through Vertex)
827
- const isAnthropic = isAnthropicModel(gemini3CheckModelName);
828
- let tools;
829
- if (Object.keys(rawTools).length > 0 && !isAnthropic) {
830
- const sanitized = sanitizeToolsForGemini(rawTools);
831
- if (sanitized.dropped.length > 0) {
832
- logger.warn(`[GoogleVertex] Dropped ${sanitized.dropped.length} incompatible tool(s): ${sanitized.dropped.join(", ")}`);
833
- }
834
- tools =
835
- Object.keys(sanitized.tools).length > 0 ? sanitized.tools : undefined;
836
- }
837
- else if (isAnthropic && Object.keys(rawTools).length > 0) {
838
- // Anthropic models don't need Gemini sanitization — pass tools through
839
- tools = rawTools;
840
- }
841
- else {
842
- tools = undefined;
843
- }
844
- logger.debug(`${functionTag}: Tools for streaming`, {
845
- shouldUseTools,
846
- baseToolCount: Object.keys(baseStreamTools).length,
847
- externalToolCount: Object.keys(options.tools || {}).length,
848
- toolCount: Object.keys(tools ?? {}).length,
849
- toolNames: Object.keys(tools ?? {}),
850
- });
851
- // Model-specific maxTokens handling
852
- const modelName = this.resolveAlias(options.model || this.modelName || getDefaultVertexModel());
853
- // Use cached model configuration to determine maxTokens handling for streaming performance
854
- // This avoids hardcoded model-specific logic and repeated config lookups
855
- const shouldSetMaxTokens = this.shouldSetMaxTokensCached(modelName);
856
- const maxTokens = shouldSetMaxTokens
857
- ? options.maxTokens // No default limit
858
- : undefined;
859
- // Build complete stream options with proper typing
860
- let streamOptions = {
861
- model: model,
862
- messages: messages,
863
- temperature: options.temperature,
864
- ...(maxTokens && { maxTokens }),
865
- maxRetries: 0, // NL11: Disable AI SDK's invisible internal retries; we handle retries with OTel instrumentation
866
- ...(shouldUseTools &&
867
- tools &&
868
- Object.keys(tools).length > 0 && {
869
- tools,
870
- toolChoice: "auto",
871
- stopWhen: stepCountIs(options.maxSteps || DEFAULT_MAX_STEPS),
872
- }),
873
- abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
874
- experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
875
- // Gemini 3: use thinkingLevel via providerOptions (Vertex AI)
876
- // Gemini 2.5: use thinkingBudget via providerOptions
877
- ...(options.thinkingConfig?.enabled && {
878
- providerOptions: {
879
- vertex: {
880
- thinkingConfig: {
881
- ...(options.thinkingConfig.thinkingLevel && {
882
- thinkingLevel: options.thinkingConfig.thinkingLevel,
883
- }),
884
- ...(options.thinkingConfig.budgetTokens &&
885
- !options.thinkingConfig.thinkingLevel && {
886
- thinkingBudget: options.thinkingConfig.budgetTokens,
887
- }),
888
- includeThoughts: true,
889
- },
890
- },
891
- },
892
- }),
893
- onError: (event) => {
894
- const error = event.error;
895
- const errorMessage = error instanceof Error ? error.message : String(error);
896
- logger.error(`${functionTag}: Stream error`, {
897
- provider: this.providerName,
898
- modelName: this.modelName,
899
- error: errorMessage,
900
- chunkCount,
901
- });
902
- },
903
- onFinish: (event) => {
904
- logger.debug(`${functionTag}: Stream finished`, {
905
- finishReason: event.finishReason,
906
- totalChunks: chunkCount,
907
- });
908
- },
909
- onChunk: () => {
910
- chunkCount++;
911
- },
912
- onStepFinish: ({ toolCalls, toolResults }) => {
913
- logger.info("Tool execution completed", { toolResults, toolCalls });
914
- // Handle tool execution storage
915
- this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
916
- logger.warn("[GoogleVertexProvider] Failed to store tool executions", {
917
- provider: this.providerName,
918
- error: error instanceof Error ? error.message : String(error),
919
- });
920
- });
921
- },
922
- };
923
- if (analysisSchema) {
924
- try {
925
- // Gemini cannot use tools and JSON schema simultaneously
926
- if (!isAnthropic) {
927
- delete streamOptions.tools;
928
- delete streamOptions.toolChoice;
929
- delete streamOptions.stopWhen;
930
- }
931
- streamOptions = {
932
- ...streamOptions,
933
- experimental_output: Output.object({
934
- schema: analysisSchema,
935
- }),
936
- };
937
- }
938
- catch (error) {
939
- logger.warn("Schema application failed, continuing without schema", {
940
- error: String(error),
941
- });
942
- }
943
- }
944
- // Wrap streamText in an OTel span to capture provider-level latency and token usage
945
- const streamSpan = streamTracer.startSpan("neurolink.provider.streamText", {
946
- kind: SpanKind.CLIENT,
947
- attributes: {
948
- "gen_ai.system": "vertex",
949
- "gen_ai.request.model": getModelId(model, this.modelName || "unknown"),
950
- },
951
- });
952
- let result;
953
- try {
954
- result = streamText(streamOptions);
955
- }
956
- catch (err) {
957
- streamSpan.recordException(err instanceof Error ? err : new Error(String(err)));
958
- streamSpan.setStatus({
959
- code: SpanStatusCode.ERROR,
960
- message: err instanceof Error ? err.message : String(err),
961
- });
962
- streamSpan.end();
963
- throw err;
964
- }
965
- // Collect token usage and finish reason asynchronously when the stream completes,
966
- // then end the span. This avoids blocking the stream consumer.
967
- Promise.resolve(result.usage)
968
- .then((usage) => {
969
- streamSpan.setAttribute("gen_ai.usage.input_tokens", usage.inputTokens || 0);
970
- streamSpan.setAttribute("gen_ai.usage.output_tokens", usage.outputTokens || 0);
971
- const effectiveModel = options.model ||
972
- getModelId(model, this.modelName || getDefaultVertexModel());
973
- const cost = calculateCost(this.providerName, effectiveModel, {
974
- input: usage.inputTokens || 0,
975
- output: usage.outputTokens || 0,
976
- total: (usage.inputTokens || 0) + (usage.outputTokens || 0),
977
- });
978
- if (cost && cost > 0) {
979
- streamSpan.setAttribute("neurolink.cost", cost);
980
- }
981
- })
982
- .catch(() => {
983
- // Usage may not be available if the stream is aborted
984
- });
985
- Promise.resolve(result.finishReason)
986
- .then((reason) => {
987
- streamSpan.setAttribute("gen_ai.response.finish_reason", reason || "unknown");
988
- })
989
- .catch(() => {
990
- // Finish reason may not be available if the stream is aborted
991
- });
992
- Promise.resolve(result.text)
993
- .then(() => {
994
- streamSpan.end();
995
- })
996
- .catch((err) => {
997
- streamSpan.setStatus({
998
- code: SpanStatusCode.ERROR,
999
- message: err instanceof Error ? err.message : String(err),
1000
- });
1001
- streamSpan.end();
1002
- });
1003
- // Defer timeout cleanup until the stream completes or errors.
1004
- // Guard against NoOutputGeneratedError becoming an unhandled rejection.
1005
- Promise.resolve(result.text)
1006
- .catch((err) => {
1007
- logger.debug("Stream text promise rejected (expected for empty streams)", {
1008
- error: err instanceof Error ? err.message : String(err),
1009
- });
1010
- })
1011
- .finally(() => timeoutController?.cleanup());
1012
- // Transform string stream to content object stream using BaseProvider method
1013
- const transformedStream = this.createTextStream(result);
1014
- // Track tool calls and results for streaming
1015
- const toolCalls = [];
1016
- const toolResults = [];
1017
- return {
1018
- stream: transformedStream,
1019
- provider: this.providerName,
1020
- model: this.modelName,
1021
- ...(shouldUseTools && {
1022
- toolCalls,
1023
- toolResults,
1024
- }),
1025
- };
1026
- }
1027
- catch (error) {
1028
- timeoutController?.cleanup();
1029
- logger.error(`${functionTag}: Exception`, {
1030
- provider: this.providerName,
1031
- modelName: this.modelName,
1032
- error: String(error),
1033
- chunkCount,
1034
- });
1035
- throw this.handleProviderError(error);
1036
- }
1037
- }
1038
- /**
1039
- * Create @google/genai client configured for Vertex AI
1040
- */
1041
- async createVertexGenAIClient(regionOverride) {
1042
- const project = getVertexProjectId();
1043
- const location = regionOverride || this.location || getVertexLocation();
1044
- const mod = await import("@google/genai");
1045
- const ctor = mod.GoogleGenAI;
1046
- if (!ctor) {
1047
- throw new NeuroLinkError({
1048
- code: ERROR_CODES.INVALID_CONFIGURATION,
1049
- message: "@google/genai does not export GoogleGenAI",
1050
- category: ErrorCategory.CONFIGURATION,
1051
- severity: ErrorSeverity.CRITICAL,
1052
- retriable: false,
1053
- context: { module: "@google/genai", expectedExport: "GoogleGenAI" },
1054
- });
1055
- }
1056
- const Ctor = ctor;
1057
- // Use vertexai mode with project and location
1058
- return new Ctor({
1059
- vertexai: true,
1060
- project,
1061
- location,
1062
- });
1063
- }
1064
- // ── Shared helpers for native Gemini 3 SDK methods ──
1065
- /**
1066
- * Build multimodal content parts (user message) from input text, PDFs, and images.
1067
- * Shared by both stream and generate native Gemini 3 paths.
1068
- */
1069
- buildNativeContentParts(inputText, multimodalInput, logLabel) {
1070
- const userParts = [{ text: inputText }];
1071
- // Add PDF files as inlineData parts if present
1072
- if (multimodalInput?.pdfFiles && multimodalInput.pdfFiles.length > 0) {
1073
- logger.debug(`[GoogleVertex] Processing ${multimodalInput.pdfFiles.length} PDF file(s) for ${logLabel}`);
1074
- for (const pdfFile of multimodalInput.pdfFiles) {
1075
- let pdfBuffer;
1076
- if (typeof pdfFile === "string") {
1077
- if (fs.existsSync(pdfFile)) {
1078
- pdfBuffer = fs.readFileSync(pdfFile);
1079
- }
1080
- else {
1081
- pdfBuffer = Buffer.from(pdfFile, "base64");
1082
- }
1083
- }
1084
- else {
1085
- pdfBuffer = pdfFile;
1086
- }
1087
- const base64Data = pdfBuffer.toString("base64");
1088
- userParts.push({
1089
- inlineData: {
1090
- mimeType: "application/pdf",
1091
- data: base64Data,
1092
- },
1093
- });
1094
- }
1095
- }
1096
- // Add images as inlineData parts if present
1097
- if (multimodalInput?.images && multimodalInput.images.length > 0) {
1098
- logger.debug(`[GoogleVertex] Processing ${multimodalInput.images.length} image(s) for ${logLabel}`);
1099
- for (const image of multimodalInput.images) {
1100
- let imageBuffer;
1101
- let mimeType = "image/jpeg"; // Default
1102
- if (typeof image === "string") {
1103
- if (fs.existsSync(image)) {
1104
- imageBuffer = fs.readFileSync(image);
1105
- const ext = path.extname(image).toLowerCase();
1106
- if (ext === ".png") {
1107
- mimeType = "image/png";
1108
- }
1109
- else if (ext === ".gif") {
1110
- mimeType = "image/gif";
1111
- }
1112
- else if (ext === ".webp") {
1113
- mimeType = "image/webp";
1114
- }
1115
- }
1116
- else if (image.startsWith("data:")) {
1117
- const matches = image.match(/^data:([^;]+);base64,(.+)$/);
1118
- if (matches) {
1119
- mimeType = matches[1];
1120
- imageBuffer = Buffer.from(matches[2], "base64");
1121
- }
1122
- else {
1123
- continue; // Skip invalid data URL
1124
- }
1125
- }
1126
- else {
1127
- imageBuffer = Buffer.from(image, "base64");
1128
- }
1129
- }
1130
- else {
1131
- imageBuffer = image;
1132
- }
1133
- const base64Data = imageBuffer.toString("base64");
1134
- userParts.push({
1135
- inlineData: {
1136
- mimeType,
1137
- data: base64Data,
1138
- },
1139
- });
1140
- }
1141
- }
1142
- return [
1143
- {
1144
- role: "user",
1145
- parts: userParts,
1146
- },
1147
- ];
1148
- }
1149
- /**
1150
- * Convert conversationMessages from NeuroLink's ChatMessage format into
1151
- * the @google/genai contents format and prepend them before the current
1152
- * user message. This gives the native Gemini 3 path multi-turn context
1153
- * that was previously dropped (only the current prompt was sent).
1154
- */
1155
- prependConversationHistory(currentContents, conversationMessages) {
1156
- if (!conversationMessages || conversationMessages.length === 0) {
1157
- return currentContents;
1158
- }
1159
- const history = [];
1160
- for (const msg of conversationMessages) {
1161
- // @google/genai only accepts "user" and "model" roles in contents.
1162
- // Skip system messages (handled via config.systemInstruction).
1163
- // Map "assistant" → "model" (Gemini convention).
1164
- const role = msg.role === "assistant" ? "model" : msg.role;
1165
- if (role !== "user" && role !== "model") {
1166
- continue;
1167
- }
1168
- if (!msg.content || msg.content.trim().length === 0) {
1169
- continue;
1170
- }
1171
- history.push({ role, parts: [{ text: msg.content }] });
1172
- }
1173
- // Prepend history before current user message
1174
- return [...history, ...currentContents];
1175
- }
1176
- // ── Shared Gemini 3 helpers are now in ./googleNativeGemini3.ts ──
1177
- /**
1178
- * Execute stream using native @google/genai SDK for Gemini 3 models on Vertex AI
1179
- * This bypasses @ai-sdk/google-vertex to properly handle thought_signature
1180
- */
1181
- async executeNativeGemini3Stream(options) {
1182
- const modelName = this.resolveAlias(options.model || this.modelName || getDefaultVertexModel());
1183
- return withClientSpan({
1184
- name: "neurolink.provider.stream",
1185
- tracer: tracers.provider,
1186
- attributes: {
1187
- [ATTR.GEN_AI_SYSTEM]: "vertex",
1188
- [ATTR.GEN_AI_MODEL]: modelName,
1189
- [ATTR.GEN_AI_OPERATION]: "stream",
1190
- [ATTR.NL_PROVIDER]: this.providerName,
1191
- },
1192
- }, async (span) => {
1193
- const client = await this.createVertexGenAIClient(options.region);
1194
- const effectiveLocation = options.region || this.location || getVertexLocation();
1195
- logger.debug("[GoogleVertex] Using native @google/genai for Gemini 3", {
1196
- model: modelName,
1197
- hasTools: !!options.tools && Object.keys(options.tools).length > 0,
1198
- project: this.projectId,
1199
- location: effectiveLocation,
1200
- });
1201
- // Build contents from input with multimodal support
1202
- const multimodalInput = options.input;
1203
- const contents = this.buildNativeContentParts(options.input.text, multimodalInput, "native stream");
1204
- // Convert tools to native format
1205
- let hasToolsInput = options.tools &&
1206
- Object.keys(options.tools).length > 0 &&
1207
- !options.disableTools;
1208
- // Guard: Gemini cannot use tools + JSON schema simultaneously
1209
- const streamOptions = options;
1210
- const wantsJsonOutput = streamOptions.output?.format === "json" || streamOptions.schema;
1211
- if (wantsJsonOutput && hasToolsInput) {
1212
- logger.warn("[GoogleVertex] Gemini does not support tools and JSON schema output simultaneously. Disabling tools for this request.");
1213
- hasToolsInput = false;
1214
- }
1215
- let toolsConfig;
1216
- let executeMap = new Map();
1217
- if (hasToolsInput) {
1218
- const result = buildNativeToolDeclarations(options.tools);
1219
- toolsConfig = result.toolsConfig;
1220
- executeMap = result.executeMap;
1221
- logger.debug("[GoogleVertex] Converted tools for native SDK", {
1222
- toolCount: toolsConfig[0].functionDeclarations.length,
1223
- toolNames: toolsConfig[0].functionDeclarations.map((t) => t.name),
1224
- });
1225
- }
1226
- // Build config — systemInstruction stays in config for Gemini 3.x.
1227
- // The @google/genai SDK maps config.systemInstruction to the HTTP-level
1228
- // system_instruction field, which is the correct mechanism for all
1229
- // Gemini 3.x models (including global endpoint). Older workaround
1230
- // that moved systemInstruction into user/model content messages caused
1231
- // "Please use a valid role: user, model" on Gemini 3.1+ preview models.
1232
- const config = buildNativeConfig(options, toolsConfig);
1233
- // Add JSON output format support for native SDK stream
1234
- if (streamOptions.output?.format === "json" || streamOptions.schema) {
1235
- config.responseMimeType = "application/json";
1236
- if (streamOptions.schema) {
1237
- const rawSchema = convertZodToJsonSchema(streamOptions.schema);
1238
- const inlinedSchema = inlineJsonSchema(rawSchema);
1239
- if (inlinedSchema.$schema) {
1240
- delete inlinedSchema.$schema;
1241
- }
1242
- config.responseSchema = inlinedSchema;
1243
- logger.debug("[GoogleVertex] Added responseSchema for JSON output (stream)", {
1244
- schemaKeys: Object.keys(inlinedSchema),
1245
- });
1246
- }
1247
- }
1248
- const startTime = Date.now();
1249
- const timeout = this.getTimeout(options);
1250
- const timeoutController = createTimeoutController(timeout, this.providerName, "stream");
1251
- const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
1252
- const maxSteps = computeMaxStepsShared(options.maxSteps);
1253
- // Inject conversation history so the native path has multi-turn context
1254
- const currentContents = this.prependConversationHistory([...contents], options.conversationMessages);
1255
- // Create a push-based text channel so the caller receives tokens as
1256
- // they arrive from the network rather than after full buffering.
1257
- const channel = createTextChannel();
1258
- // Shared mutable state updated by the background agentic loop.
1259
- const allToolCalls = [];
1260
- // Shared metadata object mutated by the background loop so that
1261
- // responseTime and totalToolExecutions reflect final values.
1262
- const metadata = {
1263
- streamId: `native-vertex-${Date.now()}`,
1264
- startTime,
1265
- responseTime: 0,
1266
- totalToolExecutions: 0,
1267
- };
1268
- // analyticsResolvers lets the background loop settle the analytics
1269
- // promise once token counts are known (after the loop completes).
1270
- let analyticsResolve;
1271
- let analyticsReject;
1272
- const analyticsPromise = new Promise((res, rej) => {
1273
- analyticsResolve = res;
1274
- analyticsReject = rej;
1275
- });
1276
- // Run the agentic loop in the background without awaiting it here,
1277
- // so we can return the StreamResult (with channel.iterable) immediately.
1278
- const loopPromise = (async () => {
1279
- let lastStepText = "";
1280
- let totalInputTokens = 0;
1281
- let totalOutputTokens = 0;
1282
- let step = 0;
1283
- let completedWithFinalAnswer = false;
1284
- const failedTools = new Map();
1285
- try {
1286
- // Agentic loop for tool calling
1287
- while (step < maxSteps) {
1288
- if (composedSignal?.aborted) {
1289
- throw composedSignal.reason instanceof Error
1290
- ? composedSignal.reason
1291
- : new Error("Request aborted");
1292
- }
1293
- step++;
1294
- logger.debug(`[GoogleVertex] Native SDK step ${step}/${maxSteps}`);
1295
- try {
1296
- const rawStream = await client.models.generateContentStream({
1297
- model: modelName,
1298
- contents: currentContents,
1299
- config,
1300
- ...(composedSignal
1301
- ? { httpOptions: { signal: composedSignal } }
1302
- : {}),
1303
- });
1304
- // For every step, use incremental collection so text parts
1305
- // are pushed to the channel as they arrive. For intermediate
1306
- // steps (those that produce function calls) we still need the
1307
- // complete rawResponseParts for pushModelResponseToHistory,
1308
- // which collectStreamChunksIncremental provides at stream end.
1309
- const chunkResult = await collectStreamChunksIncremental(rawStream, channel);
1310
- totalInputTokens += chunkResult.inputTokens;
1311
- totalOutputTokens += chunkResult.outputTokens;
1312
- const stepText = extractTextFromParts(chunkResult.rawResponseParts);
1313
- // If no function calls, this was the final step — channel
1314
- // already received all text parts incrementally.
1315
- if (chunkResult.stepFunctionCalls.length === 0) {
1316
- completedWithFinalAnswer = true;
1317
- break;
1318
- }
1319
- lastStepText = stepText;
1320
- // Record tool call events on the span
1321
- for (const fc of chunkResult.stepFunctionCalls) {
1322
- span.addEvent("gen_ai.tool_call", {
1323
- "tool.name": fc.name,
1324
- "tool.step": step,
1325
- });
1326
- }
1327
- logger.debug(`[GoogleVertex] Executing ${chunkResult.stepFunctionCalls.length} function calls`);
1328
- pushModelResponseToHistory(currentContents, chunkResult.rawResponseParts, chunkResult.stepFunctionCalls);
1329
- const functionResponses = await executeNativeToolCalls("[GoogleVertex]", chunkResult.stepFunctionCalls, executeMap, failedTools, allToolCalls, { abortSignal: composedSignal });
1330
- // Function/tool responses must use role: "user" — the
1331
- // @google/genai SDK's validateHistory() only accepts "user"
1332
- // and "model" roles (matching automaticFunctionCalling).
1333
- currentContents.push({
1334
- role: "user",
1335
- parts: functionResponses,
1336
- });
1337
- }
1338
- catch (error) {
1339
- logger.error("[GoogleVertex] Native SDK error", error);
1340
- throw this.handleProviderError(error);
1341
- }
1342
- }
1343
- // Handle max-steps termination: if the model was still calling
1344
- // tools when we hit the limit, push a synthetic final message.
1345
- if (step >= maxSteps && !completedWithFinalAnswer) {
1346
- const fallback = handleMaxStepsTermination("[GoogleVertex]", step, maxSteps, "", // finalText is empty — model didn't stop on its own
1347
- lastStepText);
1348
- if (fallback) {
1349
- channel.push(fallback);
1350
- }
1351
- }
1352
- const responseTime = Date.now() - startTime;
1353
- // Propagate final values to the shared metadata object so that
1354
- // the already-returned StreamResult reflects accurate telemetry.
1355
- metadata.responseTime = responseTime;
1356
- metadata.totalToolExecutions = allToolCalls.length;
1357
- // Set token usage and finish reason on the span
1358
- span.setAttribute(ATTR.GEN_AI_INPUT_TOKENS, totalInputTokens);
1359
- span.setAttribute(ATTR.GEN_AI_OUTPUT_TOKENS, totalOutputTokens);
1360
- span.setAttribute(ATTR.GEN_AI_FINISH_REASON, step >= maxSteps && !completedWithFinalAnswer
1361
- ? "max_steps"
1362
- : "stop");
1363
- analyticsResolve({
1364
- provider: this.providerName,
1365
- model: modelName,
1366
- tokenUsage: {
1367
- input: totalInputTokens,
1368
- output: totalOutputTokens,
1369
- total: totalInputTokens + totalOutputTokens,
1370
- },
1371
- requestDuration: responseTime,
1372
- timestamp: new Date().toISOString(),
1373
- });
1374
- channel.close();
1375
- }
1376
- catch (err) {
1377
- channel.error(err);
1378
- analyticsReject(err);
1379
- }
1380
- finally {
1381
- timeoutController?.cleanup();
1382
- }
1383
- })();
1384
- // Suppress unhandled-rejection warnings on loopPromise — errors are
1385
- // forwarded to the channel and will surface when the caller iterates.
1386
- loopPromise.catch(() => undefined);
1387
- return {
1388
- stream: channel.iterable,
1389
- provider: this.providerName,
1390
- model: modelName,
1391
- toolCalls: allToolCalls,
1392
- analytics: analyticsPromise,
1393
- metadata,
1394
- };
1395
- });
1396
- }
1397
- /**
1398
- * Execute generate using native @google/genai SDK for Gemini 3 models on Vertex AI
1399
- * This bypasses @ai-sdk/google-vertex to properly handle thought_signature
1400
- */
1401
- async executeNativeGemini3Generate(options) {
1402
- const modelName = this.resolveAlias(options.model || this.modelName || getDefaultVertexModel());
1403
- return withClientSpan({
1404
- name: "neurolink.provider.generate",
1405
- tracer: tracers.provider,
1406
- attributes: {
1407
- [ATTR.GEN_AI_SYSTEM]: "vertex",
1408
- [ATTR.GEN_AI_MODEL]: modelName,
1409
- [ATTR.GEN_AI_OPERATION]: "generate",
1410
- [ATTR.NL_PROVIDER]: this.providerName,
1411
- },
1412
- }, async (span) => {
1413
- const client = await this.createVertexGenAIClient(options.region);
1414
- const effectiveLocation = options.region || this.location || getVertexLocation();
1415
- logger.debug("[GoogleVertex] Using native @google/genai for Gemini 3 generate", {
1416
- model: modelName,
1417
- project: this.projectId,
1418
- location: effectiveLocation,
1419
- });
1420
- // Build contents from input with multimodal support
1421
- // Prefer input.text over prompt — processCSVFilesForNativeSDK enriches
1422
- // input.text with inlined CSV data, so using prompt first would discard it.
1423
- const inputText = options.input?.text || options.prompt || "Please respond.";
1424
- const multimodalInput = options.input;
1425
- const contents = this.buildNativeContentParts(inputText, multimodalInput, "native generate");
1426
- // Get tools from SDK and options
1427
- let shouldUseTools = !options.disableTools && this.supportsTools();
1428
- // Guard: Gemini cannot use tools + JSON schema simultaneously
1429
- const wantsJsonOutputGen = options.output?.format === "json" || options.schema;
1430
- if (wantsJsonOutputGen && shouldUseTools) {
1431
- logger.warn("[GoogleVertex] Gemini does not support tools and JSON schema output simultaneously. Disabling tools for this request.");
1432
- shouldUseTools = false;
1433
- }
1434
- const sdkTools = shouldUseTools ? await this.getAllTools() : {};
1435
- const combinedTools = shouldUseTools
1436
- ? { ...sdkTools, ...(options.tools || {}) }
1437
- : {};
1438
- let toolsConfig;
1439
- let executeMap = new Map();
1440
- if (Object.keys(combinedTools).length > 0) {
1441
- const result = buildNativeToolDeclarations(combinedTools);
1442
- toolsConfig = result.toolsConfig;
1443
- executeMap = result.executeMap;
1444
- logger.debug("[GoogleVertex] Converted tools for native SDK generate", {
1445
- toolCount: toolsConfig[0].functionDeclarations.length,
1446
- toolNames: toolsConfig[0].functionDeclarations.map((t) => t.name),
1447
- });
1448
- }
1449
- // Build config — systemInstruction stays in config for Gemini 3.x.
1450
- // See stream path comment for rationale.
1451
- const config = buildNativeConfig(options, toolsConfig);
1452
- // Note: Schema/JSON output for Gemini 3 native SDK is complex due to $ref resolution issues
1453
- // For now, schemas are handled via the AI SDK fallback path, not native SDK
1454
- // TODO: Implement proper $ref resolution for complex nested schemas
1455
- const startTime = Date.now();
1456
- const timeout = this.getTimeout(options);
1457
- const timeoutController = createTimeoutController(timeout, this.providerName, "generate");
1458
- const composedSignal = composeAbortSignals(options.abortSignal, timeoutController?.controller.signal);
1459
- const maxSteps = computeMaxStepsShared(options.maxSteps);
1460
- // Inject conversation history so the native path has multi-turn context
1461
- const currentContents = this.prependConversationHistory([...contents], options.conversationMessages);
1462
- let finalText = "";
1463
- let lastStepText = "";
1464
- let totalInputTokens = 0;
1465
- let totalOutputTokens = 0;
1466
- const allToolCalls = [];
1467
- const toolExecutions = [];
1468
- let step = 0;
1469
- const failedTools = new Map();
1470
- try {
1471
- // Agentic loop for tool calling
1472
- while (step < maxSteps) {
1473
- if (composedSignal?.aborted) {
1474
- throw composedSignal.reason instanceof Error
1475
- ? composedSignal.reason
1476
- : new Error("Request aborted");
1477
- }
1478
- step++;
1479
- logger.debug(`[GoogleVertex] Native SDK generate step ${step}/${maxSteps}`);
1480
- try {
1481
- // Use generateContentStream and collect all chunks (same as GoogleAIStudio)
1482
- const stream = await client.models.generateContentStream({
1483
- model: modelName,
1484
- contents: currentContents,
1485
- config,
1486
- ...(composedSignal
1487
- ? { httpOptions: { signal: composedSignal } }
1488
- : {}),
1489
- });
1490
- const chunkResult = await collectStreamChunks(stream);
1491
- totalInputTokens += chunkResult.inputTokens;
1492
- totalOutputTokens += chunkResult.outputTokens;
1493
- const stepText = extractTextFromParts(chunkResult.rawResponseParts);
1494
- if (chunkResult.stepFunctionCalls.length === 0) {
1495
- finalText = stepText;
1496
- break;
1497
- }
1498
- lastStepText = stepText;
1499
- // Record tool call events on the span
1500
- for (const fc of chunkResult.stepFunctionCalls) {
1501
- span.addEvent("gen_ai.tool_call", {
1502
- "tool.name": fc.name,
1503
- "tool.step": step,
1504
- });
1505
- }
1506
- logger.debug(`[GoogleVertex] Generate executing ${chunkResult.stepFunctionCalls.length} function calls`);
1507
- pushModelResponseToHistory(currentContents, chunkResult.rawResponseParts, chunkResult.stepFunctionCalls);
1508
- const functionResponses = await executeNativeToolCalls("[GoogleVertex]", chunkResult.stepFunctionCalls, executeMap, failedTools, allToolCalls, { toolExecutions, abortSignal: composedSignal });
1509
- // Function/tool responses must use role: "user" — the
1510
- // @google/genai SDK's validateHistory() only accepts "user"
1511
- // and "model" roles (matching automaticFunctionCalling).
1512
- currentContents.push({
1513
- role: "user",
1514
- parts: functionResponses,
1515
- });
1516
- }
1517
- catch (error) {
1518
- logger.error("[GoogleVertex] Native SDK generate error", error);
1519
- throw this.handleProviderError(error);
1520
- }
1521
- }
1522
- }
1523
- finally {
1524
- timeoutController?.cleanup();
1525
- }
1526
- finalText = handleMaxStepsTermination("[GoogleVertex]", step, maxSteps, finalText, lastStepText);
1527
- const responseTime = Date.now() - startTime;
1528
- // Set token usage and finish reason on the span
1529
- span.setAttribute(ATTR.GEN_AI_INPUT_TOKENS, totalInputTokens);
1530
- span.setAttribute(ATTR.GEN_AI_OUTPUT_TOKENS, totalOutputTokens);
1531
- span.setAttribute(ATTR.GEN_AI_FINISH_REASON, step >= maxSteps ? "max_steps" : "stop");
1532
- // Build EnhancedGenerateResult
1533
- return {
1534
- content: finalText,
1535
- provider: this.providerName,
1536
- model: modelName,
1537
- usage: {
1538
- input: totalInputTokens,
1539
- output: totalOutputTokens,
1540
- total: totalInputTokens + totalOutputTokens,
1541
- },
1542
- responseTime,
1543
- toolsUsed: allToolCalls.map((tc) => tc.toolName),
1544
- toolExecutions: toolExecutions,
1545
- enhancedWithTools: allToolCalls.length > 0,
1546
- };
1547
- });
1548
- }
1549
- /**
1550
- * Process CSV files and append content to options.input.text
1551
- * This ensures CSV data is available in the prompt for native Gemini 3 SDK calls
1552
- * Returns a new options object with modified input (immutable pattern)
1553
- */
1554
- async processCSVFilesForNativeSDK(options) {
1555
- const input = options.input;
1556
- if (!input?.csvFiles || input.csvFiles.length === 0) {
1557
- return options;
1558
- }
1559
- logger.info(`[GoogleVertex] Processing ${input.csvFiles.length} CSV file(s) for native Gemini 3 SDK`);
1560
- let modifiedText = input.text || "";
1561
- for (let i = 0; i < input.csvFiles.length; i++) {
1562
- const csvFile = input.csvFiles[i];
1563
- try {
1564
- const result = await FileDetector.detectAndProcess(csvFile, {
1565
- allowedTypes: ["csv"],
1566
- csvOptions: "csvOptions" in options
1567
- ? options.csvOptions
1568
- : undefined,
1569
- });
1570
- // Extract filename for display
1571
- const filename = typeof csvFile === "string"
1572
- ? path.basename(csvFile)
1573
- : `csv_file_${i + 1}.csv`;
1574
- let csvSection = `\n\n## CSV Data from "${filename}":\n`;
1575
- // Add metadata if available
1576
- if (result.metadata) {
1577
- const meta = result.metadata;
1578
- if (meta.rowCount || meta.columnCount || meta.columnNames) {
1579
- csvSection += `**File Info:**\n`;
1580
- if (meta.rowCount) {
1581
- csvSection += `- Rows: ${meta.rowCount}\n`;
1582
- }
1583
- if (meta.columnCount) {
1584
- csvSection += `- Columns: ${meta.columnCount}\n`;
1585
- }
1586
- if (meta.columnNames && Array.isArray(meta.columnNames)) {
1587
- csvSection += `- Column Names: ${meta.columnNames.join(", ")}\n`;
1588
- }
1589
- csvSection += "\n";
1590
- }
1591
- }
1592
- // Add strong instructions to use the CSV data directly
1593
- csvSection += `\n**CRITICAL INSTRUCTION**: The complete CSV data is included below. You MUST use this data directly from this prompt.\n`;
1594
- csvSection += `DO NOT use any external tools (github, search_code, get_file_contents, etc.) to access this data.\n`;
1595
- csvSection += `The data you need is right here in this message - read it carefully and answer based on it.\n\n`;
1596
- csvSection += result.content;
1597
- // Prepend CSV to ensure data appears before user's question
1598
- modifiedText =
1599
- csvSection + "\n\n---\n\n**USER QUESTION:**\n" + modifiedText;
1600
- logger.info(`[GoogleVertex] āœ… Processed CSV: ${filename}`);
1601
- }
1602
- catch (error) {
1603
- logger.error(`[GoogleVertex] āŒ Failed to process CSV file ${i + 1}:`, error);
1604
- const filename = typeof csvFile === "string"
1605
- ? path.basename(csvFile)
1606
- : `csv_file_${i + 1}.csv`;
1607
- modifiedText += `\n\n## CSV Data Error: Failed to process "${filename}"\nReason: ${error instanceof Error ? error.message : "Unknown error"}`;
1608
- }
1609
- }
1610
- // Return new options with modified input (immutable pattern)
1611
- // Preserve the full type of options.input by spreading options.input directly
1612
- return {
1613
- ...options,
1614
- input: { ...options.input, text: modifiedText },
1615
- };
1616
- }
1617
- /**
1618
- * Override generate to route Gemini 3 models with tools to native SDK
1619
- */
1620
- async generate(optionsOrPrompt) {
1621
- // Normalize options
1622
- const options = typeof optionsOrPrompt === "string"
1623
- ? { prompt: optionsOrPrompt }
1624
- : optionsOrPrompt;
1625
- const modelName = this.resolveAlias(options.model || this.modelName || getDefaultVertexModel());
1626
- // Structured output (JSON format or schema) is incompatible with tools on Gemini.
1627
- // Mirror the stream path pattern to prevent silent downgrade on the generate path.
1628
- const wantsStructuredOutput = options.output?.format === "json" || !!options.schema;
1629
- // Check if we should use native SDK for Gemini 3 with tools
1630
- const shouldUseTools = !options.disableTools && this.supportsTools() && !wantsStructuredOutput;
1631
- const sdkTools = shouldUseTools ? await this.getAllTools() : {};
1632
- const hasTools = shouldUseTools &&
1633
- (Object.keys(sdkTools).length > 0 ||
1634
- (options.tools && Object.keys(options.tools).length > 0));
1635
- if (isGemini3Model(modelName) && hasTools && !wantsStructuredOutput) {
1636
- // Process CSV files before routing to native SDK (bypasses normal message builder)
1637
- const processedOptions = await this.processCSVFilesForNativeSDK(options);
1638
- // Merge SDK tools into options for native SDK path
1639
- const mergedOptions = {
1640
- ...processedOptions,
1641
- tools: { ...sdkTools, ...(processedOptions.tools || {}) },
1642
- };
1643
- logger.info("[GoogleVertex] Routing Gemini 3 generate to native SDK for tool calling", {
1644
- model: modelName,
1645
- sdkToolCount: Object.keys(sdkTools).length,
1646
- optionToolCount: Object.keys(processedOptions.tools || {}).length,
1647
- totalToolCount: Object.keys(sdkTools).length +
1648
- Object.keys(processedOptions.tools || {}).length,
1649
- });
1650
- return this.executeNativeGemini3Generate(mergedOptions);
1651
- }
1652
- // Fall back to BaseProvider implementation
1653
- return super.generate(optionsOrPrompt);
1654
- }
1655
- formatProviderError(error) {
1656
- // Pass through AbortError as-is so callers can detect cancellation
1657
- const errorRecord = error;
1658
- if (typeof errorRecord?.name === "string" &&
1659
- errorRecord.name === "AbortError") {
1660
- return error;
1661
- }
1662
- if (typeof errorRecord?.name === "string" &&
1663
- errorRecord.name === "TimeoutError") {
1664
- return new NetworkError(`Google Vertex AI request timed out. Consider increasing timeout or using a lighter model.`, this.providerName);
1665
- }
1666
- const message = typeof errorRecord?.message === "string"
1667
- ? errorRecord.message
1668
- : "Unknown error occurred";
1669
- const code = typeof errorRecord?.code === "string" ? errorRecord.code : undefined;
1670
- if (code === "ECONNRESET" ||
1671
- code === "ENOTFOUND" ||
1672
- code === "ECONNREFUSED" ||
1673
- message.includes("ECONNRESET") ||
1674
- message.includes("ENOTFOUND") ||
1675
- message.includes("ECONNREFUSED") ||
1676
- message.includes("connect ETIMEDOUT")) {
1677
- return new NetworkError(`Google Vertex AI network error: ${message}`, this.providerName);
1678
- }
1679
- if (message.includes("PERMISSION_DENIED")) {
1680
- return new AuthenticationError(`āŒ Google Vertex AI Permission Denied\n\nYour Google Cloud credentials don't have permission to access Vertex AI.\n\nRequired Steps:\n1. Ensure your service account has Vertex AI User role\n2. Check if Vertex AI API is enabled in your project\n3. Verify your project ID is correct\n4. Confirm your location/region has Vertex AI available`, this.providerName);
1681
- }
1682
- if (message.includes("NOT_FOUND")) {
1683
- const modelSuggestions = this.getModelSuggestions(this.modelName);
1684
- return new InvalidModelError(`āŒ Google Vertex AI Model Not Found\n\n${message}\n\nModel '${this.modelName}' is not available.\n\nSuggested alternatives:\n${modelSuggestions}\n\nTroubleshooting:\n1. Check model name spelling and format\n2. Verify model is available in your region (${this.location})\n3. Ensure your project has access to the model\n4. For Claude models, enable Anthropic integration in Google Cloud Console`, this.providerName);
1685
- }
1686
- if (message.includes("QUOTA_EXCEEDED")) {
1687
- return new RateLimitError(`āŒ Google Vertex AI Quota Exceeded\n\n${message}\n\nSolutions:\n1. Check your Vertex AI quotas in Google Cloud Console\n2. Request quota increase if needed\n3. Try a different model or reduce request frequency\n4. Consider using a different region`, this.providerName);
1688
- }
1689
- if (message.includes("INVALID_ARGUMENT")) {
1690
- return new ProviderError(`āŒ Google Vertex AI Invalid Request\n\n${message}\n\nCheck:\n1. Request parameters are within model limits\n2. Input text is properly formatted\n3. Temperature and other settings are valid\n4. Model supports your request type`, this.providerName);
1691
- }
1692
- return new ProviderError(`āŒ Google Vertex AI Provider Error\n\n${message}\n\nTroubleshooting:\n1. Check Google Cloud credentials and permissions\n2. Verify project ID and location settings\n3. Ensure Vertex AI API is enabled\n4. Check network connectivity`, this.providerName);
1693
- }
1694
- /**
1695
- * Memory-safe cache management for model configurations
1696
- * Implements LRU eviction to prevent memory leaks in long-running processes
1697
- */
1698
- static evictLRUCacheEntries(cache) {
1699
- if (cache.size <= GoogleVertexProvider.MAX_CACHE_SIZE) {
1700
- return;
1701
- }
1702
- // Evict oldest entries (first entries in Map are oldest in insertion order)
1703
- const entriesToRemove = cache.size - GoogleVertexProvider.MAX_CACHE_SIZE + 5; // Remove extra to avoid frequent evictions
1704
- let removed = 0;
1705
- for (const key of cache.keys()) {
1706
- if (removed >= entriesToRemove) {
1707
- break;
1708
- }
1709
- cache.delete(key);
1710
- removed++;
1711
- }
1712
- logger.debug("GoogleVertexProvider: Evicted LRU cache entries", {
1713
- entriesRemoved: removed,
1714
- currentCacheSize: cache.size,
1715
- });
1716
- }
1717
- /**
1718
- * Access and refresh cache entry (moves to end for LRU)
1719
- */
1720
- static accessCacheEntry(cache, key) {
1721
- const value = cache.get(key);
1722
- if (value !== undefined) {
1723
- // Move to end (most recently used)
1724
- cache.delete(key);
1725
- cache.set(key, value);
1726
- }
1727
- return value;
1728
- }
1729
- /**
1730
- * Memory-safe cached check for whether maxTokens should be set for the given model
1731
- * Optimized for streaming performance with LRU eviction to prevent memory leaks
1732
- */
1733
- shouldSetMaxTokensCached(modelName) {
1734
- const now = Date.now();
1735
- // Check if cache is valid (within 5 minutes)
1736
- if (now - GoogleVertexProvider.maxTokensCacheTime >
1737
- GoogleVertexProvider.CACHE_DURATION) {
1738
- // Cache expired, refresh all cached results
1739
- GoogleVertexProvider.maxTokensCache.clear();
1740
- GoogleVertexProvider.maxTokensCacheTime = now;
1741
- }
1742
- // Check if we have cached result for this model (with LRU access)
1743
- const cachedResult = GoogleVertexProvider.accessCacheEntry(GoogleVertexProvider.maxTokensCache, modelName);
1744
- if (cachedResult !== undefined) {
1745
- return cachedResult;
1746
- }
1747
- // Calculate and cache the result with memory management
1748
- const shouldSet = !this.modelHasMaxTokensIssues(modelName);
1749
- GoogleVertexProvider.maxTokensCache.set(modelName, shouldSet);
1750
- // Prevent memory leaks by evicting old entries if cache grows too large
1751
- GoogleVertexProvider.evictLRUCacheEntries(GoogleVertexProvider.maxTokensCache);
1752
- return shouldSet;
1753
- }
1754
- /**
1755
- * Memory-safe check if model has maxTokens issues using configuration-based approach
1756
- * This replaces hardcoded model-specific logic with configurable behavior
1757
- * Includes LRU caching to avoid repeated configuration lookups during streaming
1758
- */
1759
- modelHasMaxTokensIssues(modelName) {
1760
- const now = Date.now();
1761
- const cacheKey = "google-vertex-config";
1762
- // Check if cache is valid (within 5 minutes)
1763
- if (now - GoogleVertexProvider.modelConfigCacheTime >
1764
- GoogleVertexProvider.CACHE_DURATION) {
1765
- // Cache expired, refresh it with memory management
1766
- GoogleVertexProvider.modelConfigCache.clear();
1767
- const config = ModelConfigurationManager.getInstance();
1768
- const vertexConfig = config.getProviderConfiguration("google-vertex");
1769
- GoogleVertexProvider.modelConfigCache.set(cacheKey, vertexConfig);
1770
- GoogleVertexProvider.modelConfigCacheTime = now;
1771
- }
1772
- // Access cached config with LRU behavior
1773
- const vertexConfig = GoogleVertexProvider.accessCacheEntry(GoogleVertexProvider.modelConfigCache, cacheKey);
1774
- // Check if model is in the list of models with maxTokens issues
1775
- const modelsWithIssues = vertexConfig?.modelBehavior?.maxTokensIssues || [
1776
- "gemini-2.5-flash",
1777
- "gemini-2.5-pro",
1778
- ];
1779
- return modelsWithIssues.some((problematicModel) => modelName.includes(problematicModel));
1780
- }
1781
- /**
1782
- * Check if Anthropic models are available
1783
- * @returns Promise<boolean> indicating if Anthropic support is available
1784
- */
1785
- async hasAnthropicSupport() {
1786
- return hasAnthropicSupport();
1787
- }
1788
- /**
1789
- * Resolve a shorthand model name to its full versioned Vertex AI identifier.
1790
- * Returns the original name unchanged if no alias exists.
1791
- *
1792
- * @param modelName - A model name, possibly a shorthand alias
1793
- * @returns The resolved full versioned model name
1794
- *
1795
- * @example
1796
- * ```typescript
1797
- * provider.resolveModelAlias("claude-sonnet-4-5"); // "claude-sonnet-4-5@20250929"
1798
- * provider.resolveModelAlias("gemini-3-pro"); // "gemini-3.1-pro-preview"
1799
- * provider.resolveModelAlias("gemini-2.5-flash"); // "gemini-2.5-flash" (unchanged)
1800
- * ```
1801
- */
1802
- resolveModelAlias(modelName) {
1803
- return VERTEX_MODEL_ALIASES[modelName] ?? modelName;
1804
- }
1805
- /**
1806
- * Create an Anthropic model instance using vertexAnthropic provider
1807
- * Uses fresh vertex settings for each request with comprehensive validation
1808
- * @param modelName Anthropic model name (e.g., 'claude-3-sonnet@20240229')
1809
- * @returns LanguageModel instance or null if not available
1810
- */
1811
- async createAnthropicModel(modelName) {
1812
- const validationId = `anthropic-validation-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
1813
- logger.debug("[GoogleVertexProvider] 🧠 Starting comprehensive Anthropic model validation", {
1814
- validationId,
1815
- modelName,
1816
- timestamp: new Date().toISOString(),
1817
- });
1818
- // 1. SDK Module Availability Validation
1819
- if (!hasAnthropicSupport()) {
1820
- logger.error("[GoogleVertexProvider] āŒ SDK module validation failed", {
1821
- validationId,
1822
- issue: "createVertexAnthropic function not available",
1823
- solution: "Update @ai-sdk/google-vertex to latest version with Anthropic support",
1824
- command: "npm install @ai-sdk/google-vertex@latest",
1825
- documentation: "https://sdk.vercel.ai/providers/ai-sdk-providers/google-vertex#anthropic-models",
1826
- });
1827
- return null;
1828
- }
1829
- // 2. Authentication Validation
1830
- const authValidation = await this.validateVertexAuthentication();
1831
- if (!authValidation.isValid) {
1832
- logger.error("[GoogleVertexProvider] āŒ Authentication validation failed", {
1833
- validationId,
1834
- method: authValidation.method,
1835
- issues: authValidation.issues,
1836
- solutions: [
1837
- "Option 1: Set GOOGLE_APPLICATION_CREDENTIALS to valid service account OR ADC file",
1838
- "Option 2: Set individual env vars: GOOGLE_AUTH_CLIENT_EMAIL, GOOGLE_AUTH_PRIVATE_KEY",
1839
- "Option 3: Use gcloud auth application-default login for ADC",
1840
- "Documentation: https://cloud.google.com/docs/authentication/provide-credentials-adc",
1841
- ],
1842
- });
1843
- return null;
1844
- }
1845
- // 3. Project Configuration Validation
1846
- const projectValidation = await this.validateVertexProjectConfiguration();
1847
- if (!projectValidation.isValid) {
1848
- logger.error("[GoogleVertexProvider] āŒ Project configuration validation failed", {
1849
- validationId,
1850
- projectId: projectValidation.projectId,
1851
- region: projectValidation.region,
1852
- issues: projectValidation.issues,
1853
- solutions: [
1854
- "Set GOOGLE_VERTEX_PROJECT or GOOGLE_CLOUD_PROJECT environment variable",
1855
- "Ensure project exists and has Vertex AI API enabled",
1856
- "Check: https://console.cloud.google.com/apis/library/aiplatform.googleapis.com",
1857
- ],
1858
- });
1859
- return null;
1860
- }
1861
- // 4. Regional Support Validation
1862
- const regionSupported = await this.checkVertexRegionalSupport(projectValidation.region);
1863
- if (!regionSupported) {
1864
- logger.warn("[GoogleVertexProvider] āš ļø Regional support warning", {
1865
- validationId,
1866
- region: projectValidation.region,
1867
- warning: "Anthropic models may not be available in this region",
1868
- supportedRegions: [
1869
- "us-central1",
1870
- "us-east4",
1871
- "us-east5",
1872
- "us-west1",
1873
- "us-west4",
1874
- "europe-west1",
1875
- "europe-west4",
1876
- "asia-southeast1",
1877
- "asia-northeast1",
1878
- ],
1879
- solution: "Set GOOGLE_CLOUD_LOCATION to a supported region",
1880
- });
1881
- // Continue with warning, don't block
1882
- }
1883
- // 5. Model Name Validation
1884
- const modelValidation = this.validateAnthropicModelName(modelName);
1885
- if (!modelValidation.isValid) {
1886
- logger.error("[GoogleVertexProvider] āŒ Model name validation failed", {
1887
- validationId,
1888
- modelName,
1889
- issue: modelValidation.issue,
1890
- recommendedModels: [
1891
- "claude-sonnet-4-6",
1892
- "claude-opus-4-6",
1893
- "claude-sonnet-4-5@20250929",
1894
- "claude-opus-4@20250514",
1895
- "claude-3-5-sonnet-20241022",
1896
- ],
1897
- });
1898
- return null;
1899
- }
1900
- try {
1901
- // 6. Settings Creation with Enhanced Error Handling
1902
- logger.debug("[GoogleVertexProvider] šŸ”§ Creating vertexAnthropic settings", {
1903
- validationId,
1904
- authMethod: authValidation.method,
1905
- projectId: projectValidation.projectId,
1906
- region: projectValidation.region,
1907
- });
1908
- const vertexAnthropicSettings = await createVertexAnthropicSettings(this.location);
1909
- // 7. Settings Validation
1910
- if (!vertexAnthropicSettings.project ||
1911
- !vertexAnthropicSettings.location) {
1912
- logger.error("[GoogleVertexProvider] āŒ Settings validation failed", {
1913
- validationId,
1914
- hasProject: !!vertexAnthropicSettings.project,
1915
- hasLocation: !!vertexAnthropicSettings.location,
1916
- hasProxy: !!vertexAnthropicSettings.fetch,
1917
- hasAuth: !!vertexAnthropicSettings.googleAuthOptions,
1918
- });
1919
- return null;
1920
- }
1921
- logger.debug("[GoogleVertexProvider] āœ… Creating vertexAnthropic instance", {
1922
- validationId,
1923
- modelName,
1924
- project: vertexAnthropicSettings.project,
1925
- location: vertexAnthropicSettings.location,
1926
- hasProxy: !!vertexAnthropicSettings.fetch,
1927
- hasAuth: !!vertexAnthropicSettings.googleAuthOptions,
1928
- });
1929
- // 8. Provider Instance Creation
1930
- const vertexAnthropicInstance = createVertexAnthropic(vertexAnthropicSettings);
1931
- // 9. Model Instance Creation with Network Error Handling
1932
- const model = vertexAnthropicInstance(modelName);
1933
- logger.info("[GoogleVertexProvider] āœ… Anthropic model created successfully", {
1934
- validationId,
1935
- modelName,
1936
- hasModel: !!model,
1937
- modelType: typeof model,
1938
- authMethod: authValidation.method,
1939
- projectId: projectValidation.projectId,
1940
- region: projectValidation.region,
1941
- validationsPassed: [
1942
- "SDK_MODULE_AVAILABLE",
1943
- "AUTHENTICATION_VALID",
1944
- "PROJECT_CONFIGURED",
1945
- "MODEL_NAME_VALID",
1946
- "SETTINGS_CREATED",
1947
- "PROVIDER_INSTANCE_CREATED",
1948
- "MODEL_INSTANCE_CREATED",
1949
- ],
1950
- });
1951
- return model;
1952
- }
1953
- catch (error) {
1954
- // Enhanced error analysis and reporting
1955
- const errorAnalysis = this.analyzeAnthropicCreationError(error, {
1956
- validationId,
1957
- modelName,
1958
- projectId: projectValidation.projectId,
1959
- region: projectValidation.region,
1960
- authMethod: authValidation.method,
1961
- });
1962
- logger.error("[GoogleVertexProvider] āŒ Anthropic model creation failed", {
1963
- validationId,
1964
- modelName,
1965
- ...errorAnalysis,
1966
- detailedTroubleshooting: this.getAnthropicTroubleshootingSteps(errorAnalysis),
1967
- });
1968
- return null;
1969
- }
1970
- }
1971
- /**
1972
- * Validate Vertex AI authentication configuration
1973
- */
1974
- async validateVertexAuthentication() {
1975
- const result = {
1976
- isValid: false,
1977
- method: "none",
1978
- issues: [],
1979
- };
1980
- try {
1981
- // Check for service account file authentication (preferred)
1982
- if (process.env.GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK ||
1983
- process.env.GOOGLE_APPLICATION_CREDENTIALS) {
1984
- const credentialsPath = process.env
1985
- .GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK
1986
- ? process.env.GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK
1987
- : process.env.GOOGLE_APPLICATION_CREDENTIALS || "";
1988
- try {
1989
- if (fs.existsSync(credentialsPath)) {
1990
- // Validate JSON structure
1991
- const credentialsContent = fs.readFileSync(credentialsPath, "utf8");
1992
- const credentials = JSON.parse(credentialsContent);
1993
- if (credentials.type === "service_account" &&
1994
- credentials.project_id &&
1995
- credentials.client_email &&
1996
- credentials.private_key) {
1997
- result.isValid = true;
1998
- result.method = "service_account_file";
1999
- return result;
2000
- }
2001
- else if (credentials.client_id &&
2002
- credentials.client_secret &&
2003
- credentials.refresh_token &&
2004
- credentials.type !== "service_account") {
2005
- result.isValid = true;
2006
- result.method = "application_default_credentials";
2007
- return result;
2008
- }
2009
- else {
2010
- result.issues.push("Credentials file missing required fields (not service account or ADC format)");
2011
- }
2012
- }
2013
- else {
2014
- result.issues.push(`Service account file not found: ${credentialsPath}`);
2015
- }
2016
- }
2017
- catch (fileError) {
2018
- result.issues.push(`Service account file validation failed: ${fileError}`);
2019
- }
2020
- }
2021
- // Check for individual environment variables
2022
- if (process.env.GOOGLE_AUTH_CLIENT_EMAIL &&
2023
- process.env.GOOGLE_AUTH_PRIVATE_KEY) {
2024
- const email = process.env.GOOGLE_AUTH_CLIENT_EMAIL;
2025
- const privateKey = process.env.GOOGLE_AUTH_PRIVATE_KEY;
2026
- if (email.includes("@") && privateKey.includes("BEGIN PRIVATE KEY")) {
2027
- result.isValid = true;
2028
- result.method = "environment_variables";
2029
- return result;
2030
- }
2031
- else {
2032
- result.issues.push("Individual credentials format validation failed");
2033
- }
2034
- }
2035
- else {
2036
- result.issues.push("Missing individual credential environment variables");
2037
- }
2038
- if (!result.isValid) {
2039
- result.issues.push("No valid authentication method found");
2040
- }
2041
- }
2042
- catch (error) {
2043
- result.issues.push(`Authentication validation error: ${error}`);
2044
- }
2045
- return result;
2046
- }
2047
- /**
2048
- * Validate Vertex AI project configuration
2049
- */
2050
- async validateVertexProjectConfiguration() {
2051
- const result = {
2052
- isValid: false,
2053
- projectId: undefined,
2054
- region: undefined,
2055
- issues: [],
2056
- };
2057
- // Check project ID
2058
- const projectId = process.env.GOOGLE_VERTEX_PROJECT ||
2059
- process.env.GOOGLE_CLOUD_PROJECT_ID ||
2060
- process.env.GOOGLE_PROJECT_ID ||
2061
- process.env.GOOGLE_CLOUD_PROJECT;
2062
- if (projectId) {
2063
- result.projectId = projectId;
2064
- // Validate project ID format
2065
- const projectIdPattern = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
2066
- if (projectIdPattern.test(projectId)) {
2067
- result.isValid = true;
2068
- }
2069
- else {
2070
- result.issues.push(`Invalid project ID format: ${projectId}`);
2071
- }
2072
- }
2073
- else {
2074
- result.issues.push("No project ID configured");
2075
- }
2076
- // Check region/location
2077
- const region = process.env.GOOGLE_CLOUD_LOCATION ||
2078
- process.env.VERTEX_LOCATION ||
2079
- process.env.GOOGLE_VERTEX_LOCATION ||
2080
- "us-central1";
2081
- result.region = region;
2082
- // Validate region format (regional format like us-central1 or global endpoint)
2083
- const regionPattern = /^([a-z]+-[a-z]+\d+|global)$/;
2084
- if (!regionPattern.test(region)) {
2085
- result.issues.push(`Invalid region format: ${region} (expected format: 'us-central1' or 'global')`);
2086
- result.isValid = false;
2087
- }
2088
- return result;
2089
- }
2090
- /**
2091
- * Check if the specified region supports Anthropic models
2092
- */
2093
- async checkVertexRegionalSupport(region = "us-central1") {
2094
- // Based on Google Cloud documentation, these regions support Anthropic models
2095
- const supportedRegions = [
2096
- // Global endpoint (routed automatically)
2097
- "global",
2098
- // North America
2099
- "us-central1",
2100
- "us-east1",
2101
- "us-east4",
2102
- "us-east5",
2103
- "us-south1",
2104
- "us-west1",
2105
- "us-west4",
2106
- "northamerica-northeast1",
2107
- "northamerica-northeast2",
2108
- // Europe
2109
- "europe-west1",
2110
- "europe-west2",
2111
- "europe-west3",
2112
- "europe-west4",
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",
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",
2137
- ];
2138
- return supportedRegions.includes(region);
2139
- }
2140
- /**
2141
- * Validate Anthropic model name format and availability
2142
- */
2143
- validateAnthropicModelName(modelName) {
2144
- if (!modelName || typeof modelName !== "string") {
2145
- return {
2146
- isValid: false,
2147
- issue: "Model name is required and must be a string",
2148
- };
2149
- }
2150
- // Check if it's a Claude model
2151
- if (!modelName.toLowerCase().includes("claude")) {
2152
- return {
2153
- isValid: false,
2154
- issue: 'Model name must be a Claude model (should contain "claude")',
2155
- };
2156
- }
2157
- // Validate against known Claude model patterns
2158
- const validPatterns = [
2159
- // Claude 4.6 — versionless IDs (no @YYYYMMDD suffix)
2160
- /^claude-opus-4-6$/,
2161
- /^claude-sonnet-4-6$/,
2162
- // Claude 4.x versioned
2163
- /^claude-sonnet-4@\d{8}$/,
2164
- /^claude-sonnet-4-5@\d{8}$/,
2165
- /^claude-opus-4@\d{8}$/,
2166
- /^claude-opus-4-1@\d{8}$/,
2167
- /^claude-opus-4-5@\d{8}$/,
2168
- /^claude-haiku-4-5@\d{8}$/,
2169
- // Claude 3.x
2170
- /^claude-3-7-sonnet@\d{8}$/,
2171
- /^claude-3-5-sonnet-\d{8}$/,
2172
- /^claude-3-5-haiku-\d{8}$/,
2173
- /^claude-3-sonnet-\d{8}$/,
2174
- /^claude-3-haiku-\d{8}$/,
2175
- /^claude-3-opus-\d{8}$/,
2176
- ];
2177
- const isValidFormat = validPatterns.some((pattern) => pattern.test(modelName));
2178
- if (!isValidFormat) {
2179
- return {
2180
- isValid: false,
2181
- issue: `Model name format not recognized. Expected formats like "claude-3-5-sonnet-20241022" or "claude-sonnet-4@20250514"`,
2182
- };
2183
- }
2184
- return { isValid: true };
2185
- }
2186
- /**
2187
- * Analyze Anthropic model creation errors for detailed troubleshooting
2188
- */
2189
- analyzeAnthropicCreationError(error, context) {
2190
- const errorMessage = error instanceof Error ? error.message : String(error);
2191
- const errorName = error instanceof Error ? error.name : "UnknownError";
2192
- const analysis = {
2193
- error: errorMessage,
2194
- errorName,
2195
- errorType: "UNKNOWN",
2196
- isNetworkError: false,
2197
- isAuthError: false,
2198
- isConfigurationError: false,
2199
- isModelError: false,
2200
- isRegionalError: false,
2201
- specificIssue: "Unknown error occurred",
2202
- errorStack: error instanceof Error ? error.stack : undefined,
2203
- };
2204
- // Network-related errors
2205
- if (errorMessage.includes("ETIMEDOUT") ||
2206
- errorMessage.includes("ECONNREFUSED") ||
2207
- errorMessage.includes("ENOTFOUND") ||
2208
- errorMessage.includes("timeout")) {
2209
- analysis.errorType = "NETWORK";
2210
- analysis.isNetworkError = true;
2211
- analysis.specificIssue =
2212
- "Network connectivity issue - cannot reach Google Cloud endpoints";
2213
- }
2214
- // Authentication errors
2215
- else if (errorMessage.includes("PERMISSION_DENIED") ||
2216
- errorMessage.includes("401") ||
2217
- errorMessage.includes("403") ||
2218
- errorMessage.includes("Unauthorized") ||
2219
- errorMessage.includes("Forbidden")) {
2220
- analysis.errorType = "AUTHENTICATION";
2221
- analysis.isAuthError = true;
2222
- analysis.specificIssue =
2223
- "Authentication failed - invalid credentials or insufficient permissions";
2224
- }
2225
- // Model availability errors
2226
- else if (errorMessage.includes("NOT_FOUND") ||
2227
- errorMessage.includes("404") ||
2228
- (errorMessage.includes("model") && errorMessage.includes("not available"))) {
2229
- analysis.errorType = "MODEL_AVAILABILITY";
2230
- analysis.isModelError = true;
2231
- analysis.specificIssue = `Model "${context.modelName}" not available in region "${context.region}"`;
2232
- }
2233
- // Regional/quota errors
2234
- else if (errorMessage.includes("QUOTA_EXCEEDED") ||
2235
- errorMessage.includes("quota") ||
2236
- errorMessage.includes("limit")) {
2237
- analysis.errorType = "QUOTA";
2238
- analysis.isRegionalError = true;
2239
- analysis.specificIssue = "Quota exceeded or rate limit reached";
2240
- }
2241
- // Configuration errors
2242
- else if (errorMessage.includes("INVALID_ARGUMENT") ||
2243
- errorMessage.includes("BadRequest") ||
2244
- errorMessage.includes("400")) {
2245
- analysis.errorType = "CONFIGURATION";
2246
- analysis.isConfigurationError = true;
2247
- analysis.specificIssue = "Invalid configuration or request parameters";
2248
- }
2249
- return analysis;
2250
- }
2251
- /**
2252
- * Get detailed troubleshooting steps based on error analysis
2253
- */
2254
- getAnthropicTroubleshootingSteps(errorAnalysis) {
2255
- const steps = [];
2256
- switch (errorAnalysis.errorType) {
2257
- case "NETWORK":
2258
- steps.push("🌐 Network Troubleshooting:", "1. Check internet connectivity", "2. Verify proxy configuration if behind corporate firewall", "3. Ensure firewall allows HTTPS to *.googleapis.com", "4. Try different network or wait for network issues to resolve", "5. Check if using VPN that might block Google Cloud endpoints");
2259
- break;
2260
- case "AUTHENTICATION":
2261
- steps.push("šŸ” Authentication Troubleshooting:", "1. Verify GOOGLE_APPLICATION_CREDENTIALS file exists and is valid", "2. Check individual credentials: GOOGLE_AUTH_CLIENT_EMAIL, GOOGLE_AUTH_PRIVATE_KEY", '3. Ensure service account has "Vertex AI User" role', "4. Verify project ID matches the one in your credentials", "5. Enable Vertex AI API: https://console.cloud.google.com/apis/library/aiplatform.googleapis.com");
2262
- break;
2263
- case "MODEL_AVAILABILITY":
2264
- steps.push("šŸ¤– Model Availability Troubleshooting:", "1. Verify model name format and spelling", "2. Check if Anthropic integration is enabled in your project", "3. Enable Claude models: https://console.cloud.google.com/vertex-ai/publishers/anthropic", "4. Try a different region if current region lacks Anthropic support", "5. Accept Anthropic terms and conditions in Google Cloud Console");
2265
- break;
2266
- case "QUOTA":
2267
- steps.push("šŸ“Š Quota Troubleshooting:", "1. Check Vertex AI quotas in Google Cloud Console", "2. Request quota increase if needed", "3. Try a different model with lower resource requirements", "4. Wait before retrying if rate limited", "5. Consider using a different region with available quota");
2268
- break;
2269
- case "CONFIGURATION":
2270
- steps.push("āš™ļø Configuration Troubleshooting:", "1. Verify all required environment variables are set", "2. Check project ID and region format", "3. Ensure model name follows correct format", "4. Verify request parameters are within model limits", "5. Update @ai-sdk/google-vertex to latest version");
2271
- break;
2272
- default:
2273
- steps.push("šŸ”§ General Troubleshooting:", "1. Update @ai-sdk/google-vertex to latest version", "2. Check Google Cloud service status", "3. Verify all authentication and configuration", "4. Try with a simple Claude model like claude-3-haiku-20240307", "5. Enable debug logging with NEUROLINK_DEBUG=true");
2274
- }
2275
- return steps;
2276
- }
2277
- /**
2278
- * Register a tool with the AI provider
2279
- * @param name The name of the tool
2280
- * @param schema The Zod schema defining the tool's parameters
2281
- * @param description A description of what the tool does
2282
- * @param handler The function to execute when the tool is called
2283
- */
2284
- registerTool(name, schema, description, handler) {
2285
- const functionTag = "GoogleVertexProvider.registerTool";
2286
- try {
2287
- const tool = {
2288
- description,
2289
- parameters: schema,
2290
- execute: async (params) => {
2291
- try {
2292
- const contextEnrichedParams = {
2293
- ...params,
2294
- __context: this.toolContext,
2295
- };
2296
- return await handler(contextEnrichedParams);
2297
- }
2298
- catch (error) {
2299
- logger.error(`${functionTag}: Tool execution error`, {
2300
- toolName: name,
2301
- error: error instanceof Error ? error.message : String(error),
2302
- });
2303
- throw error;
2304
- }
2305
- },
2306
- };
2307
- this.registeredTools.set(name, tool);
2308
- logger.debug(`${functionTag}: Tool registered`, {
2309
- toolName: name,
2310
- modelName: this.modelName,
2311
- });
2312
- }
2313
- catch (error) {
2314
- logger.error(`${functionTag}: Tool registration error`, {
2315
- toolName: name,
2316
- error: error instanceof Error ? error.message : String(error),
2317
- });
2318
- throw error;
2319
- }
2320
- }
2321
- /**
2322
- * Set the context for tool execution
2323
- * @param context The context to use for tool execution
2324
- */
2325
- setToolContext(context) {
2326
- this.toolContext = { ...this.toolContext, ...context };
2327
- logger.debug("GoogleVertexProvider.setToolContext: Tool context set", {
2328
- contextKeys: Object.keys(context),
2329
- });
2330
- }
2331
- /**
2332
- * Get the current tool execution context
2333
- * @returns The current tool execution context
2334
- */
2335
- getToolContext() {
2336
- return { ...this.toolContext };
2337
- }
2338
- /**
2339
- * Set the tool executor function for custom tool execution
2340
- * This method is called by BaseProvider.setupToolExecutor()
2341
- * @param executor Function to execute tools by name
2342
- */
2343
- setToolExecutor(executor) {
2344
- this.toolExecutor = executor;
2345
- logger.debug("GoogleVertexProvider.setToolExecutor: Tool executor set", {
2346
- hasExecutor: typeof executor === "function",
2347
- });
2348
- }
2349
- /**
2350
- * Clear all static caches - useful for testing and memory cleanup
2351
- * Public method to allow external cache management
2352
- */
2353
- static clearCaches() {
2354
- GoogleVertexProvider.modelConfigCache.clear();
2355
- GoogleVertexProvider.maxTokensCache.clear();
2356
- GoogleVertexProvider.modelConfigCacheTime = 0;
2357
- GoogleVertexProvider.maxTokensCacheTime = 0;
2358
- logger.debug("GoogleVertexProvider: All caches cleared", {
2359
- clearedAt: Date.now(),
2360
- });
2361
- }
2362
- /**
2363
- * Get cache statistics for monitoring and debugging
2364
- */
2365
- static getCacheStats() {
2366
- const now = Date.now();
2367
- return {
2368
- modelConfigCacheSize: GoogleVertexProvider.modelConfigCache.size,
2369
- maxTokensCacheSize: GoogleVertexProvider.maxTokensCache.size,
2370
- maxCacheSize: GoogleVertexProvider.MAX_CACHE_SIZE,
2371
- cacheAge: {
2372
- modelConfig: now - GoogleVertexProvider.modelConfigCacheTime,
2373
- maxTokens: now - GoogleVertexProvider.maxTokensCacheTime,
2374
- },
2375
- };
2376
- }
2377
- /**
2378
- * Detect image MIME type from buffer
2379
- */
2380
- detectImageType(buffer) {
2381
- // Check PNG signature
2382
- if (buffer.length >= 8 &&
2383
- buffer[0] === 0x89 &&
2384
- buffer[1] === 0x50 &&
2385
- buffer[2] === 0x4e &&
2386
- buffer[3] === 0x47) {
2387
- return "image/png";
2388
- }
2389
- // Check JPEG signature
2390
- if (buffer.length >= 3 &&
2391
- buffer[0] === 0xff &&
2392
- buffer[1] === 0xd8 &&
2393
- buffer[2] === 0xff) {
2394
- return "image/jpeg";
2395
- }
2396
- // Check WebP signature
2397
- if (buffer.length >= 12 &&
2398
- buffer[0] === 0x52 &&
2399
- buffer[1] === 0x49 &&
2400
- buffer[2] === 0x46 &&
2401
- buffer[3] === 0x46 &&
2402
- buffer[8] === 0x57 &&
2403
- buffer[9] === 0x45 &&
2404
- buffer[10] === 0x42 &&
2405
- buffer[11] === 0x50) {
2406
- return "image/webp";
2407
- }
2408
- // Check GIF signature
2409
- if (buffer.length >= 6 &&
2410
- buffer[0] === 0x47 &&
2411
- buffer[1] === 0x49 &&
2412
- buffer[2] === 0x46) {
2413
- return "image/gif";
2414
- }
2415
- // Default to PNG if unknown
2416
- return "image/png";
2417
- }
2418
- /**
2419
- * Estimate token count from text using centralized estimation with provider multipliers
2420
- */
2421
- estimateTokenCount(text) {
2422
- return estimateTokens(text, "vertex");
2423
- }
2424
- /**
2425
- * Obtain a Google Auth access token for Vertex AI REST API calls.
2426
- */
2427
- async getImageGenerationAccessToken() {
2428
- const maxRetries = 3;
2429
- const baseDelay = 500;
2430
- const authTimeoutMs = 15000;
2431
- const { GoogleAuth } = await import("google-auth-library");
2432
- // Priority: GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK > GOOGLE_APPLICATION_CREDENTIALS
2433
- const credentialsPath = process.env.GOOGLE_APPLICATION_CREDENTIALS_NEUROLINK ||
2434
- process.env.GOOGLE_APPLICATION_CREDENTIALS;
2435
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
2436
- // Enforce per-attempt timeout with AbortController
2437
- const controller = new AbortController();
2438
- const authTimer = setTimeout(() => {
2439
- controller.abort();
2440
- logger.warn(`[GoogleVertexProvider] Auth token refresh exceeded ${authTimeoutMs}ms timeout (attempt ${attempt}/${maxRetries})`);
2441
- }, authTimeoutMs);
2442
- try {
2443
- const auth = new GoogleAuth({
2444
- ...(credentialsPath && { keyFilename: credentialsPath }),
2445
- scopes: ["https://www.googleapis.com/auth/cloud-platform"],
2446
- });
2447
- const client = await auth.getClient();
2448
- const accessToken = await client.getAccessToken();
2449
- if (!accessToken.token) {
2450
- throw new AuthenticationError("Failed to obtain access token from Google Auth", this.providerName);
2451
- }
2452
- return accessToken.token;
2453
- }
2454
- catch (error) {
2455
- const err = error;
2456
- const isRetryable = controller.signal.aborted ||
2457
- err?.code === "ECONNRESET" ||
2458
- err?.code === "ETIMEDOUT" ||
2459
- err?.code === "ENOTFOUND" ||
2460
- err?.message?.includes("socket hang up") ||
2461
- err?.message?.includes("network socket disconnected");
2462
- if (!isRetryable || attempt === maxRetries) {
2463
- throw error;
2464
- }
2465
- const delay = baseDelay * 2 ** (attempt - 1);
2466
- logger.warn(`[GoogleVertexProvider] Auth token transient error (${err?.code || err?.message}), retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`);
2467
- await new Promise((r) => setTimeout(r, delay));
2468
- }
2469
- finally {
2470
- clearTimeout(authTimer);
2471
- }
2472
- }
2473
- throw new AuthenticationError("Failed to obtain access token after retries", this.providerName);
2474
- }
2475
- /**
2476
- * Build request parts for image generation from prompt, PDFs, and images.
2477
- */
2478
- async buildImageGenerationParts(prompt, pdfFiles, inputImages) {
2479
- const parts = [];
2480
- if (prompt) {
2481
- parts.push({ text: prompt });
2482
- }
2483
- // Add PDF files as inline data
2484
- for (const pdfFile of pdfFiles) {
2485
- let pdfBase64;
2486
- if (Buffer.isBuffer(pdfFile)) {
2487
- pdfBase64 = pdfFile.toString("base64");
2488
- }
2489
- else if (typeof pdfFile === "string") {
2490
- const isFilePath = pdfFile.startsWith("/") ||
2491
- /^[a-zA-Z]:\\/.test(pdfFile) ||
2492
- pdfFile.startsWith("./") ||
2493
- pdfFile.startsWith("../") ||
2494
- pdfFile.startsWith("..\\") ||
2495
- pdfFile.startsWith(".\\");
2496
- if (isFilePath) {
2497
- const normalizedPath = path.resolve(pdfFile);
2498
- const cwd = process.cwd();
2499
- if (!normalizedPath.startsWith(cwd + path.sep) &&
2500
- normalizedPath !== cwd) {
2501
- throw new ProviderError(`PDF file path must be within current directory for security`, this.providerName);
2502
- }
2503
- if (!fs.existsSync(normalizedPath)) {
2504
- throw new ProviderError(`PDF file not found: ${normalizedPath}`, this.providerName);
2505
- }
2506
- const pdfBuffer = fs.readFileSync(normalizedPath);
2507
- pdfBase64 = pdfBuffer.toString("base64");
2508
- }
2509
- else {
2510
- pdfBase64 = pdfFile;
2511
- }
2512
- }
2513
- else {
2514
- logger.warn("Invalid PDF file format, skipping", {
2515
- type: typeof pdfFile,
2516
- });
2517
- continue;
2518
- }
2519
- parts.push({
2520
- inlineData: {
2521
- mimeType: "application/pdf",
2522
- data: pdfBase64,
2523
- },
2524
- });
2525
- logger.debug("Added PDF file to request", {
2526
- dataLength: pdfBase64.length,
2527
- });
2528
- }
2529
- // Add images (including those converted from PDF by baseProvider)
2530
- for (let i = 0; i < inputImages.length; i++) {
2531
- const image = inputImages[i];
2532
- let imageBase64;
2533
- let mimeType;
2534
- if (Buffer.isBuffer(image)) {
2535
- imageBase64 = image.toString("base64");
2536
- mimeType = this.detectImageType(image);
2537
- }
2538
- else if (typeof image === "string") {
2539
- const isFilePath = image.startsWith("/") ||
2540
- /^[a-zA-Z]:\\/.test(image) ||
2541
- image.startsWith("./") ||
2542
- image.startsWith("../") ||
2543
- image.startsWith("..\\") ||
2544
- image.startsWith(".\\");
2545
- if (isFilePath) {
2546
- const normalizedPath = path.resolve(image);
2547
- if (!fs.existsSync(normalizedPath)) {
2548
- logger.warn(`Image file not found: ${normalizedPath}, skipping`);
2549
- continue;
2550
- }
2551
- const imageBuffer = fs.readFileSync(normalizedPath);
2552
- imageBase64 = imageBuffer.toString("base64");
2553
- mimeType = this.detectImageType(imageBuffer);
2554
- }
2555
- else if (image.startsWith("data:")) {
2556
- const matches = image.match(/^data:([^;]+);base64,(.+)$/);
2557
- if (matches) {
2558
- mimeType = matches[1];
2559
- imageBase64 = matches[2];
2560
- }
2561
- else {
2562
- logger.warn("Invalid data URL format, skipping image", {
2563
- index: i,
2564
- });
2565
- continue;
2566
- }
2567
- }
2568
- else if (image.startsWith("http://") ||
2569
- image.startsWith("https://")) {
2570
- // Download URL image and convert to base64
2571
- try {
2572
- // Validate URL to prevent SSRF attacks
2573
- const parsedUrl = new URL(image);
2574
- const hostname = parsedUrl.hostname;
2575
- const blockedHosts = ["localhost", "127.0.0.1", "0.0.0.0", "[::1]"];
2576
- if (blockedHosts.some((h) => hostname === h) ||
2577
- /^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.)/.test(hostname)) {
2578
- logger.warn(`[GoogleVertexProvider] Blocked fetch to private/local URL: ${hostname}`, { index: i });
2579
- continue;
2580
- }
2581
- // DNS resolution check — verify resolved IPs are not private/loopback
2582
- try {
2583
- const { resolve4, resolve6 } = dns.promises;
2584
- const addresses = [];
2585
- try {
2586
- addresses.push(...(await resolve4(hostname)));
2587
- }
2588
- catch {
2589
- /* hostname may not have A records */
2590
- }
2591
- try {
2592
- addresses.push(...(await resolve6(hostname)));
2593
- }
2594
- catch {
2595
- /* hostname may not have AAAA records */
2596
- }
2597
- if (addresses.length > 0 &&
2598
- addresses.every((addr) => isPrivateOrLoopbackAddress(addr))) {
2599
- logger.warn(`[GoogleVertexProvider] Blocked fetch: hostname ${hostname} resolves to private/loopback address`, { index: i, addresses });
2600
- continue;
2601
- }
2602
- }
2603
- catch (dnsError) {
2604
- logger.warn(`[GoogleVertexProvider] DNS resolution failed for ${hostname}, blocking fetch`, {
2605
- index: i,
2606
- error: dnsError instanceof Error
2607
- ? dnsError.message
2608
- : String(dnsError),
2609
- });
2610
- continue;
2611
- }
2612
- const response = await fetch(image, {
2613
- signal: AbortSignal.timeout(15_000),
2614
- });
2615
- if (!response.ok) {
2616
- logger.warn(`Failed to fetch image URL (${response.status}), skipping`, { index: i, url: image });
2617
- continue;
2618
- }
2619
- // Size guard — reject downloads exceeding 10 MB
2620
- const contentLength = response.headers.get("content-length");
2621
- if (contentLength &&
2622
- Number(contentLength) > MAX_IMAGE_DOWNLOAD_BYTES) {
2623
- logger.warn(`[GoogleVertexProvider] Image URL exceeds ${MAX_IMAGE_DOWNLOAD_BYTES} byte limit (Content-Length: ${contentLength}), skipping`, { index: i, url: image });
2624
- continue;
2625
- }
2626
- const buffer = Buffer.from(await response.arrayBuffer());
2627
- if (buffer.byteLength > MAX_IMAGE_DOWNLOAD_BYTES) {
2628
- logger.warn(`[GoogleVertexProvider] Downloaded image exceeds ${MAX_IMAGE_DOWNLOAD_BYTES} byte limit (${buffer.byteLength} bytes), skipping`, { index: i, url: image });
2629
- continue;
2630
- }
2631
- imageBase64 = buffer.toString("base64");
2632
- mimeType = this.detectImageType(buffer);
2633
- }
2634
- catch (fetchError) {
2635
- logger.warn(`Failed to download image from URL, skipping: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`, { index: i, url: image });
2636
- continue;
2637
- }
2638
- }
2639
- else {
2640
- imageBase64 = image;
2641
- const decodedBuffer = Buffer.from(imageBase64, "base64");
2642
- mimeType = this.detectImageType(decodedBuffer);
2643
- }
2644
- }
2645
- else {
2646
- logger.warn("Invalid image format, skipping", {
2647
- type: typeof image,
2648
- index: i,
2649
- });
2650
- continue;
2651
- }
2652
- parts.push({
2653
- inlineData: {
2654
- mimeType: mimeType,
2655
- data: imageBase64,
2656
- },
2657
- });
2658
- logger.debug("Added image to request", {
2659
- index: i,
2660
- mimeType,
2661
- dataLength: imageBase64.length,
2662
- });
2663
- }
2664
- return parts;
2665
- }
2666
- /**
2667
- * Parse the Vertex AI image generation REST API response and extract image data.
2668
- */
2669
- parseImageGenerationResponse(data, imageModelName) {
2670
- const candidate = data.candidates?.[0];
2671
- if (!candidate?.content?.parts) {
2672
- throw new ProviderError("No content parts in Vertex AI response", this.providerName);
2673
- }
2674
- // Find image part (check both camelCase and snake_case)
2675
- const imagePart = candidate.content.parts.find((part) => (part.inlineData || part.inline_data) &&
2676
- ((part.inlineData && part.inlineData.mimeType) ||
2677
- (part.inline_data && part.inline_data.mime_type)) &&
2678
- ((part.inlineData && part.inlineData.mimeType?.startsWith("image/")) ||
2679
- (part.inline_data &&
2680
- part.inline_data.mime_type?.startsWith("image/"))));
2681
- if (!imagePart) {
2682
- const hasTextContent = candidate.content.parts.some((part) => part.text);
2683
- throw new ProviderError(hasTextContent
2684
- ? `Image generation completed but model returned text instead of image data. Model: ${imageModelName}`
2685
- : `Image generation completed but no image data was returned. Model: ${imageModelName}`, this.providerName);
2686
- }
2687
- const imageData = imagePart.inlineData?.data || imagePart.inline_data?.data;
2688
- const mimeType = imagePart.inlineData?.mimeType ||
2689
- imagePart.inline_data?.mime_type ||
2690
- "image/png";
2691
- if (!imageData) {
2692
- throw new ProviderError("Image part found but no data available", this.providerName);
2693
- }
2694
- return { imageData, mimeType };
2695
- }
2696
- /**
2697
- * Overrides the BaseProvider's image generation method to implement it for Vertex AI.
2698
- * Uses REST API approach with google-auth-library for authentication.
2699
- * Supports PDF input for image generation with gemini-3-pro-image-preview (Nano Banana Pro).
2700
- * @param options The generation options containing the prompt and optional PDF files.
2701
- * @returns A promise that resolves to the generation result, including the image data.
2702
- */
2703
- async executeImageGeneration(options) {
2704
- const prompt = options.prompt || options.input?.text || "";
2705
- const pdfFiles = options.input?.pdfFiles || [];
2706
- const inputImages = options.input?.images || [];
2707
- const hasPdfInput = pdfFiles.length > 0;
2708
- const hasImageInput = inputImages.length > 0;
2709
- if (!prompt.trim() && !hasPdfInput && !hasImageInput) {
2710
- throw new ProviderError("Image generation requires either a prompt, PDF file, or image as input", this.providerName);
2711
- }
2712
- // Select appropriate model
2713
- let imageModelName = options.model || this.modelName || "gemini-3-pro-image-preview";
2714
- if (hasPdfInput && !imageModelName.includes("gemini-3-pro-image")) {
2715
- imageModelName = "gemini-3-pro-image-preview";
2716
- }
2717
- // Determine location - some image models require 'global' location
2718
- const imageLocation = process.env.GOOGLE_VERTEX_IMAGE_LOCATION || "global";
2719
- const requiresGlobalLocation = GLOBAL_LOCATION_MODELS.some((model) => imageModelName.includes(model) || model.includes(imageModelName));
2720
- const location = requiresGlobalLocation ? imageLocation : this.location;
2721
- const startTime = Date.now();
2722
- logger.info("šŸŽØ Starting Vertex AI image generation (REST API)", {
2723
- model: imageModelName,
2724
- prompt: prompt.substring(0, 100),
2725
- provider: this.providerName,
2726
- projectId: this.projectId,
2727
- location: location,
2728
- hasPdfInput,
2729
- pdfCount: pdfFiles.length,
2730
- hasImageInput,
2731
- imageCount: inputImages.length,
2732
- });
2733
- try {
2734
- const token = await this.getImageGenerationAccessToken();
2735
- const parts = await this.buildImageGenerationParts(prompt, pdfFiles, inputImages);
2736
- // Build request body with CRITICAL response_modalities setting
2737
- const requestBody = {
2738
- contents: [{ role: "user", parts }],
2739
- generation_config: {
2740
- response_modalities: ["TEXT", "IMAGE"],
2741
- temperature: options.temperature || 0.7,
2742
- candidate_count: 1,
2743
- },
2744
- };
2745
- // Construct Vertex AI endpoint
2746
- let url;
2747
- if (location === "global") {
2748
- url = `https://aiplatform.googleapis.com/v1/projects/${this.projectId}/locations/global/publishers/google/models/${imageModelName}:generateContent`;
2749
- }
2750
- else {
2751
- url = `https://${location}-aiplatform.googleapis.com/v1/projects/${this.projectId}/locations/${location}/publishers/google/models/${imageModelName}:generateContent`;
2752
- }
2753
- logger.debug("Making REST API call to Vertex AI", {
2754
- url,
2755
- model: imageModelName,
2756
- hasAccessToken: true,
2757
- });
2758
- // Add timeout protection (120 seconds for image generation)
2759
- const timeoutMs = 120000;
2760
- const fetchPromise = fetch(url, {
2761
- method: "POST",
2762
- headers: {
2763
- Authorization: `Bearer ${token}`,
2764
- "Content-Type": "application/json",
2765
- },
2766
- body: JSON.stringify(requestBody),
2767
- });
2768
- const timeoutPromise = new Promise((_, reject) => {
2769
- setTimeout(() => {
2770
- reject(new TimeoutError(`Vertex AI image generation timed out after ${timeoutMs}ms`, timeoutMs));
2771
- }, timeoutMs);
2772
- });
2773
- const response = await Promise.race([fetchPromise, timeoutPromise]);
2774
- if (!response.ok) {
2775
- const errorText = await response.text();
2776
- throw new ProviderError(`Vertex AI API error (${response.status}): ${errorText}`, this.providerName);
2777
- }
2778
- const data = (await response.json());
2779
- const { imageData, mimeType } = this.parseImageGenerationResponse(data, imageModelName);
2780
- logger.info("Image generation successful", {
2781
- model: imageModelName,
2782
- mimeType,
2783
- dataLength: imageData.length,
2784
- responseTime: Date.now() - startTime,
2785
- });
2786
- const result = {
2787
- content: `Generated image using ${imageModelName} (${mimeType})`,
2788
- imageOutput: {
2789
- base64: imageData,
2790
- },
2791
- provider: this.providerName,
2792
- model: imageModelName,
2793
- usage: {
2794
- input: this.estimateTokenCount(prompt),
2795
- output: 0,
2796
- total: this.estimateTokenCount(prompt),
2797
- },
2798
- };
2799
- return await this.enhanceResult(result, options, startTime);
2800
- }
2801
- catch (error) {
2802
- logger.error("Image generation failed", {
2803
- error: error instanceof Error ? error.message : String(error),
2804
- model: imageModelName,
2805
- prompt: prompt.substring(0, 100),
2806
- });
2807
- throw this.handleProviderError(error);
2808
- }
2809
- }
2810
- /**
2811
- * Generate embeddings for text using Google Vertex AI text-embedding models
2812
- * @param text - The text to embed
2813
- * @param modelName - The embedding model to use (default: text-embedding-004)
2814
- * @returns Promise resolving to the embedding vector
2815
- */
2816
- async embed(text, modelName) {
2817
- const embeddingModelName = modelName || "text-embedding-004";
2818
- logger.debug("Generating embedding", {
2819
- provider: this.providerName,
2820
- model: embeddingModelName,
2821
- textLength: text.length,
2822
- });
2823
- try {
2824
- // Create the Vertex provider with current settings
2825
- const vertexSettings = await createVertexSettings(this.location);
2826
- const vertex = createVertex(vertexSettings);
2827
- // Get the text embedding model
2828
- const embeddingModel = vertex.textEmbeddingModel(embeddingModelName);
2829
- // Generate the embedding
2830
- const result = await embed({
2831
- model: embeddingModel,
2832
- value: text,
2833
- });
2834
- logger.debug("Embedding generated successfully", {
2835
- provider: this.providerName,
2836
- model: embeddingModelName,
2837
- embeddingDimension: result.embedding.length,
2838
- });
2839
- return result.embedding;
2840
- }
2841
- catch (error) {
2842
- logger.error("Embedding generation failed", {
2843
- error: error instanceof Error ? error.message : String(error),
2844
- model: embeddingModelName,
2845
- textLength: text.length,
2846
- });
2847
- throw this.handleProviderError(error);
2848
- }
2849
- }
2850
- /**
2851
- * Generate embeddings for multiple texts in a single batch
2852
- * @param texts - The texts to embed
2853
- * @param modelName - The embedding model to use (default: text-embedding-004)
2854
- * @returns Promise resolving to an array of embedding vectors
2855
- */
2856
- async embedMany(texts, modelName) {
2857
- const embeddingModelName = modelName || this.getDefaultEmbeddingModel() || "text-embedding-004";
2858
- logger.debug("Generating batch embeddings", {
2859
- provider: this.providerName,
2860
- model: embeddingModelName,
2861
- count: texts.length,
2862
- });
2863
- try {
2864
- const vertexSettings = await createVertexSettings(this.location);
2865
- const vertex = createVertex(vertexSettings);
2866
- const embeddingModel = vertex.textEmbeddingModel(embeddingModelName);
2867
- const result = await embedMany({
2868
- model: embeddingModel,
2869
- values: texts,
2870
- });
2871
- logger.debug("Batch embeddings generated successfully", {
2872
- provider: this.providerName,
2873
- model: embeddingModelName,
2874
- count: result.embeddings.length,
2875
- embeddingDimension: result.embeddings[0]?.length,
2876
- });
2877
- return result.embeddings;
2878
- }
2879
- catch (error) {
2880
- logger.error("Batch embedding generation failed", {
2881
- error: error instanceof Error ? error.message : String(error),
2882
- model: embeddingModelName,
2883
- count: texts.length,
2884
- });
2885
- throw this.handleProviderError(error);
2886
- }
2887
- }
2888
- /**
2889
- * Get model suggestions when a model is not found
2890
- */
2891
- getModelSuggestions(requestedModel) {
2892
- const availableModels = {
2893
- google: [
2894
- "gemini-3.1-pro-preview",
2895
- "gemini-3.1-flash-lite-preview",
2896
- "gemini-3-flash-preview",
2897
- "gemini-2.5-pro",
2898
- "gemini-2.5-flash",
2899
- "gemini-2.5-flash-lite",
2900
- "gemini-2.0-flash-001",
2901
- "gemini-2.0-flash-lite",
2902
- "gemini-1.5-pro",
2903
- "gemini-1.5-flash",
2904
- ],
2905
- claude: [
2906
- "claude-sonnet-4-5@20250929",
2907
- "claude-sonnet-4@20250514",
2908
- "claude-opus-4@20250514",
2909
- "claude-3-5-sonnet-20241022",
2910
- "claude-3-5-haiku-20241022",
2911
- "claude-3-sonnet-20240229",
2912
- "claude-3-haiku-20240307",
2913
- "claude-3-opus-20240229",
2914
- ],
2915
- };
2916
- let suggestions = "\nšŸ¤– Google Models (always available):\n";
2917
- availableModels.google.forEach((model) => {
2918
- suggestions += ` • ${model}\n`;
2919
- });
2920
- suggestions += "\n🧠 Claude Models (requires Anthropic integration):\n";
2921
- availableModels.claude.forEach((model) => {
2922
- suggestions += ` • ${model}\n`;
2923
- });
2924
- // If the requested model looks like a Claude model, provide specific guidance
2925
- if (requestedModel && requestedModel.toLowerCase().includes("claude")) {
2926
- suggestions += `\nšŸ’” Tip: "${requestedModel}" appears to be a Claude model.\n`;
2927
- suggestions +=
2928
- "Ensure Anthropic integration is enabled in your Google Cloud project.\n";
2929
- suggestions += "Try using an available Claude model from the list above.";
2930
- }
2931
- return suggestions;
2932
- }
2933
- }
2934
- export default GoogleVertexProvider;
2935
- // Re-export for compatibility
2936
- export { GoogleVertexProvider as GoogleVertexAI };