@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.
- package/CHANGELOG.md +6 -0
- package/dist/auth/anthropicOAuth.js +1 -1
- package/dist/cli/commands/proxy.js +18 -5
- package/dist/client/aiSdkAdapter.js +1 -1
- package/dist/client/index.js +137 -501
- package/dist/core/factory.js +0 -1
- package/dist/core/redisConversationMemoryManager.js +1 -1
- package/dist/features/ppt/slideGenerator.js +0 -1
- package/dist/features/ppt/utils.js +0 -1
- package/dist/lib/server/routes/claudeProxyRoutes.js +45 -9
- package/dist/mcp/elicitationProtocol.js +1 -1
- package/dist/mcp/servers/agent/directToolsServer.js +0 -1
- package/dist/providers/azureOpenai.js +1 -1
- package/dist/providers/huggingFace.js +0 -1
- package/dist/providers/openaiCompatible.js +0 -1
- package/dist/sdk/toolRegistration.js +0 -1
- package/dist/server/openapi/generator.js +1 -1
- package/dist/server/routes/claudeProxyRoutes.js +45 -9
- package/dist/types/configTypes.js +0 -5
- package/dist/types/modelTypes.js +0 -1
- package/dist/types/tools.js +0 -1
- package/dist/types/typeAliases.js +0 -1
- package/dist/types/utilities.js +1 -1
- package/dist/types/workflowTypes.js +0 -1
- package/dist/utils/providerRetry.js +0 -1
- package/dist/utils/providerUtils.js +0 -1
- package/package.json +2 -2
- package/dist/client/adapters/providerImageAdapter.js +0 -588
- package/dist/client/adapters/tts/googleTTSHandler.js +0 -344
- package/dist/client/adapters/video/directorPipeline.js +0 -516
- package/dist/client/adapters/video/ffmpegAdapter.js +0 -206
- package/dist/client/adapters/video/frameExtractor.js +0 -143
- package/dist/client/adapters/video/vertexVideoHandler.js +0 -763
- package/dist/client/adapters/video/videoAnalyzer.js +0 -238
- package/dist/client/adapters/video/videoMerger.js +0 -171
- package/dist/client/agent/directTools.js +0 -840
- package/dist/client/auth/AuthProviderFactory.js +0 -111
- package/dist/client/auth/AuthProviderRegistry.js +0 -190
- package/dist/client/auth/RequestContext.js +0 -78
- package/dist/client/auth/accountPool.js +0 -178
- package/dist/client/auth/anthropicOAuth.js +0 -974
- package/dist/client/auth/authContext.js +0 -314
- package/dist/client/auth/errors.js +0 -39
- package/dist/client/auth/index.js +0 -61
- package/dist/client/auth/middleware/AuthMiddleware.js +0 -519
- package/dist/client/auth/middleware/rateLimitByUser.js +0 -554
- package/dist/client/auth/providers/BaseAuthProvider.js +0 -723
- package/dist/client/auth/providers/CognitoProvider.js +0 -304
- package/dist/client/auth/providers/KeycloakProvider.js +0 -393
- package/dist/client/auth/providers/auth0.js +0 -274
- package/dist/client/auth/providers/betterAuth.js +0 -182
- package/dist/client/auth/providers/clerk.js +0 -317
- package/dist/client/auth/providers/custom.js +0 -112
- package/dist/client/auth/providers/firebase.js +0 -226
- package/dist/client/auth/providers/jwt.js +0 -212
- package/dist/client/auth/providers/oauth2.js +0 -303
- package/dist/client/auth/providers/supabase.js +0 -259
- package/dist/client/auth/providers/workos.js +0 -284
- package/dist/client/auth/serverBridge.js +0 -25
- package/dist/client/auth/sessionManager.js +0 -437
- package/dist/client/auth/tokenStore.js +0 -799
- package/dist/client/client/aiSdkAdapter.js +0 -487
- package/dist/client/client/auth.js +0 -473
- package/dist/client/client/errors.js +0 -552
- package/dist/client/client/httpClient.js +0 -837
- package/dist/client/client/index.js +0 -172
- package/dist/client/client/interceptors.js +0 -601
- package/dist/client/client/sseClient.js +0 -545
- package/dist/client/client/streamingClient.js +0 -917
- package/dist/client/client/wsClient.js +0 -369
- package/dist/client/config/configManager.js +0 -303
- package/dist/client/config/conversationMemory.js +0 -86
- package/dist/client/config/taskClassificationConfig.js +0 -148
- package/dist/client/constants/contextWindows.js +0 -295
- package/dist/client/constants/enums.js +0 -853
- package/dist/client/constants/index.js +0 -207
- package/dist/client/constants/performance.js +0 -389
- package/dist/client/constants/retry.js +0 -266
- package/dist/client/constants/timeouts.js +0 -182
- package/dist/client/constants/tokens.js +0 -380
- package/dist/client/constants/videoErrors.js +0 -46
- package/dist/client/context/budgetChecker.js +0 -98
- package/dist/client/context/contextCompactor.js +0 -205
- package/dist/client/context/emergencyTruncation.js +0 -88
- package/dist/client/context/errorDetection.js +0 -171
- package/dist/client/context/errors.js +0 -21
- package/dist/client/context/fileTokenBudget.js +0 -127
- package/dist/client/context/prompts/summarizationPrompt.js +0 -117
- package/dist/client/context/stages/fileReadDeduplicator.js +0 -66
- package/dist/client/context/stages/slidingWindowTruncator.js +0 -190
- package/dist/client/context/stages/structuredSummarizer.js +0 -99
- package/dist/client/context/stages/toolOutputPruner.js +0 -52
- package/dist/client/context/summarizationEngine.js +0 -136
- package/dist/client/context/toolOutputLimits.js +0 -78
- package/dist/client/context/toolPairRepair.js +0 -66
- package/dist/client/core/analytics.js +0 -88
- package/dist/client/core/baseProvider.js +0 -1385
- package/dist/client/core/constants.js +0 -140
- package/dist/client/core/conversationMemoryFactory.js +0 -141
- package/dist/client/core/conversationMemoryInitializer.js +0 -128
- package/dist/client/core/conversationMemoryManager.js +0 -344
- package/dist/client/core/dynamicModels.js +0 -358
- package/dist/client/core/evaluation.js +0 -309
- package/dist/client/core/evaluationProviders.js +0 -248
- package/dist/client/core/factory.js +0 -412
- package/dist/client/core/infrastructure/baseError.js +0 -22
- package/dist/client/core/infrastructure/baseFactory.js +0 -54
- package/dist/client/core/infrastructure/baseRegistry.js +0 -53
- package/dist/client/core/infrastructure/index.js +0 -5
- package/dist/client/core/infrastructure/retry.js +0 -20
- package/dist/client/core/infrastructure/typedEventEmitter.js +0 -23
- package/dist/client/core/modelConfiguration.js +0 -851
- package/dist/client/core/modules/GenerationHandler.js +0 -588
- package/dist/client/core/modules/MessageBuilder.js +0 -273
- package/dist/client/core/modules/StreamHandler.js +0 -185
- package/dist/client/core/modules/TelemetryHandler.js +0 -203
- package/dist/client/core/modules/ToolsManager.js +0 -499
- package/dist/client/core/modules/Utilities.js +0 -331
- package/dist/client/core/redisConversationMemoryManager.js +0 -1435
- package/dist/client/core/streamAnalytics.js +0 -131
- package/dist/client/evaluation/contextBuilder.js +0 -134
- package/dist/client/evaluation/index.js +0 -61
- package/dist/client/evaluation/prompts.js +0 -73
- package/dist/client/evaluation/ragasEvaluator.js +0 -110
- package/dist/client/evaluation/retryManager.js +0 -78
- package/dist/client/evaluation/scoring.js +0 -61
- package/dist/client/factories/providerFactory.js +0 -166
- package/dist/client/factories/providerRegistry.js +0 -166
- package/dist/client/features/ppt/constants.js +0 -896
- package/dist/client/features/ppt/contentPlanner.js +0 -529
- package/dist/client/features/ppt/presentationOrchestrator.js +0 -236
- package/dist/client/features/ppt/slideGenerator.js +0 -532
- package/dist/client/features/ppt/slideRenderers.js +0 -2383
- package/dist/client/features/ppt/slideTypeInference.js +0 -405
- package/dist/client/features/ppt/types.js +0 -13
- package/dist/client/features/ppt/utils.js +0 -443
- package/dist/client/files/fileReferenceRegistry.js +0 -1543
- package/dist/client/files/fileTools.js +0 -450
- package/dist/client/files/streamingReader.js +0 -321
- package/dist/client/files/types.js +0 -23
- package/dist/client/hitl/hitlErrors.js +0 -54
- package/dist/client/hitl/hitlManager.js +0 -460
- package/dist/client/mcp/agentExposure.js +0 -356
- package/dist/client/mcp/auth/index.js +0 -11
- package/dist/client/mcp/auth/oauthClientProvider.js +0 -325
- package/dist/client/mcp/auth/tokenStorage.js +0 -134
- package/dist/client/mcp/batching/index.js +0 -10
- package/dist/client/mcp/batching/requestBatcher.js +0 -441
- package/dist/client/mcp/caching/index.js +0 -10
- package/dist/client/mcp/caching/toolCache.js +0 -433
- package/dist/client/mcp/elicitation/elicitationManager.js +0 -376
- package/dist/client/mcp/elicitation/index.js +0 -11
- package/dist/client/mcp/elicitation/types.js +0 -10
- package/dist/client/mcp/elicitationProtocol.js +0 -375
- package/dist/client/mcp/enhancedToolDiscovery.js +0 -481
- package/dist/client/mcp/externalServerManager.js +0 -1478
- package/dist/client/mcp/factory.js +0 -161
- package/dist/client/mcp/flexibleToolValidator.js +0 -161
- package/dist/client/mcp/httpRateLimiter.js +0 -391
- package/dist/client/mcp/httpRetryHandler.js +0 -178
- package/dist/client/mcp/index.js +0 -74
- package/dist/client/mcp/mcpCircuitBreaker.js +0 -427
- package/dist/client/mcp/mcpClientFactory.js +0 -708
- package/dist/client/mcp/mcpRegistryClient.js +0 -488
- package/dist/client/mcp/mcpServerBase.js +0 -373
- package/dist/client/mcp/multiServerManager.js +0 -579
- package/dist/client/mcp/registry.js +0 -158
- package/dist/client/mcp/routing/index.js +0 -10
- package/dist/client/mcp/routing/toolRouter.js +0 -416
- package/dist/client/mcp/serverCapabilities.js +0 -502
- package/dist/client/mcp/servers/agent/directToolsServer.js +0 -150
- package/dist/client/mcp/toolAnnotations.js +0 -239
- package/dist/client/mcp/toolConverter.js +0 -258
- package/dist/client/mcp/toolDiscoveryService.js +0 -798
- package/dist/client/mcp/toolIntegration.js +0 -334
- package/dist/client/mcp/toolRegistry.js +0 -729
- package/dist/client/memory/hippocampusInitializer.js +0 -19
- package/dist/client/memory/memoryRetrievalTools.js +0 -166
- package/dist/client/middleware/builtin/analytics.js +0 -132
- package/dist/client/middleware/builtin/autoEvaluation.js +0 -203
- package/dist/client/middleware/builtin/guardrails.js +0 -109
- package/dist/client/middleware/builtin/lifecycle.js +0 -168
- package/dist/client/middleware/factory.js +0 -327
- package/dist/client/middleware/registry.js +0 -295
- package/dist/client/middleware/utils/guardrailsUtils.js +0 -396
- package/dist/client/models/anthropicModels.js +0 -527
- package/dist/client/neurolink.js +0 -8233
- package/dist/client/observability/exporterRegistry.js +0 -413
- package/dist/client/observability/exporters/arizeExporter.js +0 -138
- package/dist/client/observability/exporters/baseExporter.js +0 -190
- package/dist/client/observability/exporters/braintrustExporter.js +0 -154
- package/dist/client/observability/exporters/datadogExporter.js +0 -196
- package/dist/client/observability/exporters/laminarExporter.js +0 -302
- package/dist/client/observability/exporters/langfuseExporter.js +0 -209
- package/dist/client/observability/exporters/langsmithExporter.js +0 -143
- package/dist/client/observability/exporters/otelExporter.js +0 -164
- package/dist/client/observability/exporters/posthogExporter.js +0 -287
- package/dist/client/observability/exporters/sentryExporter.js +0 -165
- package/dist/client/observability/index.js +0 -31
- package/dist/client/observability/metricsAggregator.js +0 -556
- package/dist/client/observability/otelBridge.js +0 -131
- package/dist/client/observability/retryPolicy.js +0 -383
- package/dist/client/observability/sampling/samplers.js +0 -216
- package/dist/client/observability/spanProcessor.js +0 -303
- package/dist/client/observability/tokenTracker.js +0 -413
- package/dist/client/observability/types/exporterTypes.js +0 -5
- package/dist/client/observability/types/index.js +0 -4
- package/dist/client/observability/types/spanTypes.js +0 -92
- package/dist/client/observability/utils/safeMetadata.js +0 -25
- package/dist/client/observability/utils/spanSerializer.js +0 -292
- package/dist/client/processors/archive/ArchiveProcessor.js +0 -1308
- package/dist/client/processors/base/BaseFileProcessor.js +0 -614
- package/dist/client/processors/base/types.js +0 -82
- package/dist/client/processors/config/fileTypes.js +0 -520
- package/dist/client/processors/config/index.js +0 -92
- package/dist/client/processors/config/languageMap.js +0 -410
- package/dist/client/processors/config/mimeTypes.js +0 -363
- package/dist/client/processors/config/sizeLimits.js +0 -258
- package/dist/client/processors/document/ExcelProcessor.js +0 -590
- package/dist/client/processors/document/OpenDocumentProcessor.js +0 -212
- package/dist/client/processors/document/PptxProcessor.js +0 -157
- package/dist/client/processors/document/RtfProcessor.js +0 -361
- package/dist/client/processors/document/WordProcessor.js +0 -353
- package/dist/client/processors/errors/FileErrorCode.js +0 -255
- package/dist/client/processors/errors/errorHelpers.js +0 -386
- package/dist/client/processors/errors/errorSerializer.js +0 -507
- package/dist/client/processors/errors/index.js +0 -49
- package/dist/client/processors/markup/SvgProcessor.js +0 -240
- package/dist/client/processors/media/AudioProcessor.js +0 -707
- package/dist/client/processors/media/VideoProcessor.js +0 -1045
- package/dist/client/providers/amazonBedrock.js +0 -1512
- package/dist/client/providers/amazonSagemaker.js +0 -162
- package/dist/client/providers/anthropic.js +0 -831
- package/dist/client/providers/azureOpenai.js +0 -143
- package/dist/client/providers/googleAiStudio.js +0 -1200
- package/dist/client/providers/googleNativeGemini3.js +0 -543
- package/dist/client/providers/googleVertex.js +0 -2936
- package/dist/client/providers/huggingFace.js +0 -315
- package/dist/client/providers/litellm.js +0 -488
- package/dist/client/providers/mistral.js +0 -157
- package/dist/client/providers/ollama.js +0 -1579
- package/dist/client/providers/openAI.js +0 -627
- package/dist/client/providers/openRouter.js +0 -543
- package/dist/client/providers/openaiCompatible.js +0 -290
- package/dist/client/providers/providerTypeUtils.js +0 -46
- package/dist/client/providers/sagemaker/adaptive-semaphore.js +0 -215
- package/dist/client/providers/sagemaker/client.js +0 -472
- package/dist/client/providers/sagemaker/config.js +0 -317
- package/dist/client/providers/sagemaker/detection.js +0 -606
- package/dist/client/providers/sagemaker/error-constants.js +0 -227
- package/dist/client/providers/sagemaker/errors.js +0 -299
- package/dist/client/providers/sagemaker/language-model.js +0 -775
- package/dist/client/providers/sagemaker/parsers.js +0 -634
- package/dist/client/providers/sagemaker/streaming.js +0 -331
- package/dist/client/providers/sagemaker/structured-parser.js +0 -625
- package/dist/client/proxy/accountQuota.js +0 -162
- package/dist/client/proxy/claudeFormat.js +0 -595
- package/dist/client/proxy/modelRouter.js +0 -29
- package/dist/client/proxy/oauthFetch.js +0 -367
- package/dist/client/proxy/proxyFetch.js +0 -586
- package/dist/client/proxy/requestLogger.js +0 -207
- package/dist/client/proxy/tokenRefresh.js +0 -124
- package/dist/client/proxy/usageStats.js +0 -74
- package/dist/client/proxy/utils/noProxyUtils.js +0 -149
- package/dist/client/rag/ChunkerFactory.js +0 -320
- package/dist/client/rag/ChunkerRegistry.js +0 -421
- package/dist/client/rag/chunkers/BaseChunker.js +0 -143
- package/dist/client/rag/chunkers/CharacterChunker.js +0 -28
- package/dist/client/rag/chunkers/HTMLChunker.js +0 -38
- package/dist/client/rag/chunkers/JSONChunker.js +0 -68
- package/dist/client/rag/chunkers/LaTeXChunker.js +0 -63
- package/dist/client/rag/chunkers/MarkdownChunker.js +0 -306
- package/dist/client/rag/chunkers/RecursiveChunker.js +0 -139
- package/dist/client/rag/chunkers/SemanticMarkdownChunker.js +0 -138
- package/dist/client/rag/chunkers/SentenceChunker.js +0 -66
- package/dist/client/rag/chunkers/TokenChunker.js +0 -61
- package/dist/client/rag/chunkers/index.js +0 -15
- package/dist/client/rag/chunking/characterChunker.js +0 -142
- package/dist/client/rag/chunking/chunkerRegistry.js +0 -194
- package/dist/client/rag/chunking/htmlChunker.js +0 -247
- package/dist/client/rag/chunking/index.js +0 -17
- package/dist/client/rag/chunking/jsonChunker.js +0 -281
- package/dist/client/rag/chunking/latexChunker.js +0 -251
- package/dist/client/rag/chunking/markdownChunker.js +0 -373
- package/dist/client/rag/chunking/recursiveChunker.js +0 -148
- package/dist/client/rag/chunking/semanticChunker.js +0 -306
- package/dist/client/rag/chunking/sentenceChunker.js +0 -230
- package/dist/client/rag/chunking/tokenChunker.js +0 -183
- package/dist/client/rag/document/MDocument.js +0 -392
- package/dist/client/rag/document/index.js +0 -5
- package/dist/client/rag/document/loaders.js +0 -500
- package/dist/client/rag/errors/RAGError.js +0 -274
- package/dist/client/rag/errors/index.js +0 -6
- package/dist/client/rag/graphRag/graphRAG.js +0 -401
- package/dist/client/rag/graphRag/index.js +0 -4
- package/dist/client/rag/index.js +0 -141
- package/dist/client/rag/metadata/MetadataExtractorFactory.js +0 -418
- package/dist/client/rag/metadata/MetadataExtractorRegistry.js +0 -362
- package/dist/client/rag/metadata/index.js +0 -9
- package/dist/client/rag/metadata/metadataExtractor.js +0 -280
- package/dist/client/rag/pipeline/RAGPipeline.js +0 -436
- package/dist/client/rag/pipeline/contextAssembly.js +0 -341
- package/dist/client/rag/pipeline/index.js +0 -5
- package/dist/client/rag/ragIntegration.js +0 -321
- package/dist/client/rag/reranker/RerankerFactory.js +0 -430
- package/dist/client/rag/reranker/RerankerRegistry.js +0 -402
- package/dist/client/rag/reranker/index.js +0 -9
- package/dist/client/rag/reranker/reranker.js +0 -277
- package/dist/client/rag/resilience/CircuitBreaker.js +0 -431
- package/dist/client/rag/resilience/RetryHandler.js +0 -304
- package/dist/client/rag/resilience/index.js +0 -7
- package/dist/client/rag/retrieval/hybridSearch.js +0 -335
- package/dist/client/rag/retrieval/index.js +0 -5
- package/dist/client/rag/retrieval/vectorQueryTool.js +0 -307
- package/dist/client/rag/types.js +0 -8
- package/dist/client/sdk/toolRegistration.js +0 -377
- package/dist/client/server/abstract/baseServerAdapter.js +0 -575
- package/dist/client/server/adapters/expressAdapter.js +0 -486
- package/dist/client/server/adapters/fastifyAdapter.js +0 -472
- package/dist/client/server/adapters/honoAdapter.js +0 -632
- package/dist/client/server/adapters/koaAdapter.js +0 -510
- package/dist/client/server/errors.js +0 -486
- package/dist/client/server/factory/serverAdapterFactory.js +0 -160
- package/dist/client/server/index.js +0 -108
- package/dist/client/server/middleware/abortSignal.js +0 -111
- package/dist/client/server/middleware/auth.js +0 -388
- package/dist/client/server/middleware/cache.js +0 -359
- package/dist/client/server/middleware/common.js +0 -281
- package/dist/client/server/middleware/deprecation.js +0 -190
- package/dist/client/server/middleware/mcpBodyAttachment.js +0 -63
- package/dist/client/server/middleware/rateLimit.js +0 -227
- package/dist/client/server/middleware/validation.js +0 -388
- package/dist/client/server/openapi/generator.js +0 -398
- package/dist/client/server/openapi/index.js +0 -36
- package/dist/client/server/openapi/schemas.js +0 -695
- package/dist/client/server/openapi/templates.js +0 -374
- package/dist/client/server/routes/agentRoutes.js +0 -189
- package/dist/client/server/routes/claudeProxyRoutes.js +0 -1600
- package/dist/client/server/routes/healthRoutes.js +0 -187
- package/dist/client/server/routes/index.js +0 -57
- package/dist/client/server/routes/mcpRoutes.js +0 -342
- package/dist/client/server/routes/memoryRoutes.js +0 -350
- package/dist/client/server/routes/openApiRoutes.js +0 -126
- package/dist/client/server/routes/toolRoutes.js +0 -199
- package/dist/client/server/streaming/dataStream.js +0 -486
- package/dist/client/server/streaming/index.js +0 -11
- package/dist/client/server/types.js +0 -67
- package/dist/client/server/utils/redaction.js +0 -334
- package/dist/client/server/utils/validation.js +0 -243
- package/dist/client/server/websocket/WebSocketHandler.js +0 -383
- package/dist/client/server/websocket/index.js +0 -4
- package/dist/client/services/server/ai/observability/instrumentation.js +0 -808
- package/dist/client/telemetry/attributes.js +0 -100
- package/dist/client/telemetry/index.js +0 -26
- package/dist/client/telemetry/telemetryService.js +0 -308
- package/dist/client/telemetry/tracers.js +0 -17
- package/dist/client/telemetry/withSpan.js +0 -34
- package/dist/client/types/actionTypes.js +0 -6
- package/dist/client/types/analytics.js +0 -5
- package/dist/client/types/authTypes.js +0 -9
- package/dist/client/types/circuitBreakerErrors.js +0 -34
- package/dist/client/types/cli.js +0 -21
- package/dist/client/types/clientTypes.js +0 -10
- package/dist/client/types/common.js +0 -51
- package/dist/client/types/configTypes.js +0 -49
- package/dist/client/types/content.js +0 -19
- package/dist/client/types/contextTypes.js +0 -400
- package/dist/client/types/conversation.js +0 -47
- package/dist/client/types/conversationMemoryInterface.js +0 -6
- package/dist/client/types/domainTypes.js +0 -5
- package/dist/client/types/errors.js +0 -167
- package/dist/client/types/evaluation.js +0 -5
- package/dist/client/types/evaluationProviders.js +0 -5
- package/dist/client/types/evaluationTypes.js +0 -1
- package/dist/client/types/externalMcp.js +0 -6
- package/dist/client/types/fileReferenceTypes.js +0 -8
- package/dist/client/types/fileTypes.js +0 -4
- package/dist/client/types/generateTypes.js +0 -1
- package/dist/client/types/guardrails.js +0 -1
- package/dist/client/types/hitlTypes.js +0 -8
- package/dist/client/types/index.js +0 -57
- package/dist/client/types/mcpTypes.js +0 -5
- package/dist/client/types/middlewareTypes.js +0 -1
- package/dist/client/types/modelTypes.js +0 -30
- package/dist/client/types/multimodal.js +0 -135
- package/dist/client/types/observability.js +0 -6
- package/dist/client/types/pptTypes.js +0 -82
- package/dist/client/types/providers.js +0 -111
- package/dist/client/types/proxyTypes.js +0 -16
- package/dist/client/types/ragTypes.js +0 -7
- package/dist/client/types/sdkTypes.js +0 -8
- package/dist/client/types/serviceTypes.js +0 -5
- package/dist/client/types/streamTypes.js +0 -1
- package/dist/client/types/subscriptionTypes.js +0 -9
- package/dist/client/types/taskClassificationTypes.js +0 -5
- package/dist/client/types/tools.js +0 -24
- package/dist/client/types/ttsTypes.js +0 -57
- package/dist/client/types/typeAliases.js +0 -48
- package/dist/client/types/utilities.js +0 -4
- package/dist/client/types/workflowTypes.js +0 -30
- package/dist/client/utils/async/withTimeout.js +0 -98
- package/dist/client/utils/asyncMutex.js +0 -60
- package/dist/client/utils/conversationMemory.js +0 -431
- package/dist/client/utils/csvProcessor.js +0 -846
- package/dist/client/utils/errorHandling.js +0 -936
- package/dist/client/utils/evaluationUtils.js +0 -131
- package/dist/client/utils/factoryProcessing.js +0 -589
- package/dist/client/utils/fileDetector.js +0 -2161
- package/dist/client/utils/imageCache.js +0 -376
- package/dist/client/utils/imageProcessor.js +0 -704
- package/dist/client/utils/logger.js +0 -491
- package/dist/client/utils/mcpDefaults.js +0 -134
- package/dist/client/utils/messageBuilder.js +0 -1653
- package/dist/client/utils/modelAliasResolver.js +0 -54
- package/dist/client/utils/modelDetection.js +0 -80
- package/dist/client/utils/modelRouter.js +0 -292
- package/dist/client/utils/multimodalOptionsBuilder.js +0 -65
- package/dist/client/utils/observabilityHelpers.js +0 -47
- package/dist/client/utils/parameterValidation.js +0 -966
- package/dist/client/utils/pdfProcessor.js +0 -410
- package/dist/client/utils/performance.js +0 -222
- package/dist/client/utils/pricing.js +0 -340
- package/dist/client/utils/promptRedaction.js +0 -62
- package/dist/client/utils/providerConfig.js +0 -1009
- package/dist/client/utils/providerHealth.js +0 -1237
- package/dist/client/utils/providerRetry.js +0 -112
- package/dist/client/utils/providerUtils.js +0 -434
- package/dist/client/utils/rateLimiter.js +0 -200
- package/dist/client/utils/redis.js +0 -368
- package/dist/client/utils/retryHandler.js +0 -269
- package/dist/client/utils/retryability.js +0 -22
- package/dist/client/utils/sanitizers/svg.js +0 -481
- package/dist/client/utils/schemaConversion.js +0 -255
- package/dist/client/utils/taskClassificationUtils.js +0 -149
- package/dist/client/utils/taskClassifier.js +0 -94
- package/dist/client/utils/thinkingConfig.js +0 -104
- package/dist/client/utils/timeout.js +0 -359
- package/dist/client/utils/tokenEstimation.js +0 -142
- package/dist/client/utils/tokenLimits.js +0 -125
- package/dist/client/utils/tokenUtils.js +0 -239
- package/dist/client/utils/toolUtils.js +0 -75
- package/dist/client/utils/transformationUtils.js +0 -554
- package/dist/client/utils/ttsProcessor.js +0 -286
- package/dist/client/utils/typeUtils.js +0 -97
- package/dist/client/utils/videoAnalysisProcessor.js +0 -67
- package/dist/client/workflow/config.js +0 -398
- package/dist/client/workflow/core/ensembleExecutor.js +0 -407
- package/dist/client/workflow/core/judgeScorer.js +0 -544
- package/dist/client/workflow/core/responseConditioner.js +0 -225
- package/dist/client/workflow/core/types/conditionerTypes.js +0 -7
- package/dist/client/workflow/core/types/ensembleTypes.js +0 -7
- package/dist/client/workflow/core/types/index.js +0 -7
- package/dist/client/workflow/core/types/judgeTypes.js +0 -7
- package/dist/client/workflow/core/types/layerTypes.js +0 -7
- package/dist/client/workflow/core/types/registryTypes.js +0 -7
- package/dist/client/workflow/core/workflowRegistry.js +0 -304
- package/dist/client/workflow/core/workflowRunner.js +0 -586
- package/dist/client/workflow/index.js +0 -50
- package/dist/client/workflow/types.js +0 -9
- package/dist/client/workflow/utils/types/index.js +0 -7
- package/dist/client/workflow/utils/workflowMetrics.js +0 -311
- package/dist/client/workflow/utils/workflowValidation.js +0 -420
- package/dist/client/workflow/workflows/adaptiveWorkflow.js +0 -366
- package/dist/client/workflow/workflows/consensusWorkflow.js +0 -192
- package/dist/client/workflow/workflows/fallbackWorkflow.js +0 -225
- package/dist/client/workflow/workflows/multiJudgeWorkflow.js +0 -351
- /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 };
|