@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,1579 +0,0 @@
|
|
|
1
|
-
import { createAnalytics } from "../core/analytics.js";
|
|
2
|
-
import { BaseProvider } from "../core/baseProvider.js";
|
|
3
|
-
import { DEFAULT_MAX_STEPS } from "../core/constants.js";
|
|
4
|
-
import { modelConfig } from "../core/modelConfiguration.js";
|
|
5
|
-
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
6
|
-
import { logger } from "../utils/logger.js";
|
|
7
|
-
import { buildMultimodalMessagesArray } from "../utils/messageBuilder.js";
|
|
8
|
-
import { buildMultimodalOptions } from "../utils/multimodalOptionsBuilder.js";
|
|
9
|
-
import { estimateTokens } from "../utils/tokenEstimation.js";
|
|
10
|
-
import { InvalidModelError, NetworkError, ProviderError, } from "../types/errors.js";
|
|
11
|
-
import { tracers, ATTR, withClientSpan } from "../telemetry/index.js";
|
|
12
|
-
import { TimeoutError } from "../utils/timeout.js";
|
|
13
|
-
// Model version constants (configurable via environment)
|
|
14
|
-
const DEFAULT_OLLAMA_MODEL = "llama3.1:8b";
|
|
15
|
-
const FALLBACK_OLLAMA_MODEL = "llama3.2:latest"; // Used when primary model fails
|
|
16
|
-
// Configuration helpers
|
|
17
|
-
const getOllamaBaseUrl = () => {
|
|
18
|
-
return process.env.OLLAMA_BASE_URL || "http://localhost:11434";
|
|
19
|
-
};
|
|
20
|
-
const isOpenAICompatibleMode = () => {
|
|
21
|
-
// Enable OpenAI-compatible API mode (/v1/chat/completions) instead of native Ollama API (/api/generate)
|
|
22
|
-
// Useful for Ollama deployments that only support OpenAI-compatible routes (e.g., breezehq.dev)
|
|
23
|
-
return process.env.OLLAMA_OPENAI_COMPATIBLE === "true";
|
|
24
|
-
};
|
|
25
|
-
// Create AbortController with timeout for better compatibility
|
|
26
|
-
const createAbortSignalWithTimeout = (timeoutMs) => {
|
|
27
|
-
const controller = new AbortController();
|
|
28
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
29
|
-
// Clear timeout if signal is aborted through other means
|
|
30
|
-
controller.signal.addEventListener("abort", () => {
|
|
31
|
-
clearTimeout(timeoutId);
|
|
32
|
-
});
|
|
33
|
-
return controller.signal;
|
|
34
|
-
};
|
|
35
|
-
const getDefaultOllamaModel = () => {
|
|
36
|
-
return process.env.OLLAMA_MODEL || DEFAULT_OLLAMA_MODEL;
|
|
37
|
-
};
|
|
38
|
-
const getOllamaTimeout = () => {
|
|
39
|
-
// Increased default timeout to 240000ms (4 minutes) to support slower native API responses
|
|
40
|
-
// especially for larger models like aliafshar/gemma3-it-qat-tools:latest (12.2B parameters)
|
|
41
|
-
return parseInt(process.env.OLLAMA_TIMEOUT || "240000", 10);
|
|
42
|
-
};
|
|
43
|
-
// Create proxy-aware fetch instance
|
|
44
|
-
const proxyFetch = createProxyFetch();
|
|
45
|
-
// Custom LanguageModel implementation for Ollama
|
|
46
|
-
class OllamaLanguageModel {
|
|
47
|
-
/**
|
|
48
|
-
* Specification version for the AI SDK LanguageModel interface.
|
|
49
|
-
* Uses "v2" for structural compatibility with AI SDK v6's `LanguageModelV2`.
|
|
50
|
-
* The AI SDK checks this field to determine which interface version to use.
|
|
51
|
-
*/
|
|
52
|
-
specificationVersion = "v2";
|
|
53
|
-
provider = "ollama";
|
|
54
|
-
modelId;
|
|
55
|
-
maxTokens;
|
|
56
|
-
supportsStreaming = true;
|
|
57
|
-
defaultObjectGenerationMode = "json";
|
|
58
|
-
/**
|
|
59
|
-
* Supported URL patterns by media type.
|
|
60
|
-
* Ollama runs locally and does not natively download URLs, so this is empty.
|
|
61
|
-
* Required by the LanguageModelV2 interface.
|
|
62
|
-
*/
|
|
63
|
-
supportedUrls = {};
|
|
64
|
-
baseUrl;
|
|
65
|
-
timeout;
|
|
66
|
-
constructor(modelId, baseUrl, timeout) {
|
|
67
|
-
this.modelId = modelId;
|
|
68
|
-
this.baseUrl = baseUrl;
|
|
69
|
-
this.timeout = timeout;
|
|
70
|
-
}
|
|
71
|
-
estimateTokenCount(text) {
|
|
72
|
-
return estimateTokens(text, "ollama");
|
|
73
|
-
}
|
|
74
|
-
convertMessagesToPrompt(messages) {
|
|
75
|
-
return messages
|
|
76
|
-
.map((msg) => {
|
|
77
|
-
if (typeof msg.content === "string") {
|
|
78
|
-
return `${msg.role}: ${msg.content}`;
|
|
79
|
-
}
|
|
80
|
-
return `${msg.role}: ${JSON.stringify(msg.content)}`;
|
|
81
|
-
})
|
|
82
|
-
.join("\n");
|
|
83
|
-
}
|
|
84
|
-
async doGenerate(options) {
|
|
85
|
-
// Vercel AI SDK passes messages via options.messages (same as stream mode)
|
|
86
|
-
// Check options.messages first, then fall back to options.prompt for backward compatibility
|
|
87
|
-
const messages = options
|
|
88
|
-
.messages ||
|
|
89
|
-
options
|
|
90
|
-
.prompt ||
|
|
91
|
-
[];
|
|
92
|
-
// Check if we should use OpenAI-compatible API
|
|
93
|
-
const useOpenAIMode = isOpenAICompatibleMode();
|
|
94
|
-
if (useOpenAIMode) {
|
|
95
|
-
// OpenAI-compatible mode: Use /v1/chat/completions
|
|
96
|
-
const requestBody = {
|
|
97
|
-
model: this.modelId,
|
|
98
|
-
messages,
|
|
99
|
-
temperature: options.temperature,
|
|
100
|
-
max_tokens: options.maxTokens,
|
|
101
|
-
stream: false,
|
|
102
|
-
};
|
|
103
|
-
if (logger.shouldLog("debug")) {
|
|
104
|
-
logger.debug("[OllamaLanguageModel] Using OpenAI-compatible API with messages:", JSON.stringify(messages, null, 2));
|
|
105
|
-
}
|
|
106
|
-
const response = await proxyFetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
107
|
-
method: "POST",
|
|
108
|
-
headers: { "Content-Type": "application/json" },
|
|
109
|
-
body: JSON.stringify(requestBody),
|
|
110
|
-
signal: createAbortSignalWithTimeout(this.timeout),
|
|
111
|
-
});
|
|
112
|
-
if (!response.ok) {
|
|
113
|
-
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
|
|
114
|
-
}
|
|
115
|
-
const data = await response.json();
|
|
116
|
-
logger.debug("[OllamaLanguageModel] OpenAI API Response:", JSON.stringify(data, null, 2));
|
|
117
|
-
const text = data.choices?.[0]?.message?.content || "";
|
|
118
|
-
const usage = data.usage || {};
|
|
119
|
-
return {
|
|
120
|
-
text,
|
|
121
|
-
usage: {
|
|
122
|
-
promptTokens: usage.prompt_tokens ??
|
|
123
|
-
this.estimateTokenCount(JSON.stringify(messages)),
|
|
124
|
-
completionTokens: usage.completion_tokens ?? this.estimateTokenCount(text),
|
|
125
|
-
totalTokens: usage.total_tokens,
|
|
126
|
-
},
|
|
127
|
-
finishReason: "stop",
|
|
128
|
-
rawCall: {
|
|
129
|
-
rawPrompt: messages,
|
|
130
|
-
rawSettings: {
|
|
131
|
-
model: this.modelId,
|
|
132
|
-
temperature: options.temperature,
|
|
133
|
-
max_tokens: options.maxTokens,
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
rawResponse: {
|
|
137
|
-
headers: {},
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
// Native Ollama mode: Use /api/generate
|
|
143
|
-
const prompt = this.convertMessagesToPrompt(messages);
|
|
144
|
-
logger.debug("[OllamaLanguageModel] Using native API with prompt:", JSON.stringify(prompt));
|
|
145
|
-
const response = await proxyFetch(`${this.baseUrl}/api/generate`, {
|
|
146
|
-
method: "POST",
|
|
147
|
-
headers: { "Content-Type": "application/json" },
|
|
148
|
-
body: JSON.stringify({
|
|
149
|
-
model: this.modelId,
|
|
150
|
-
prompt,
|
|
151
|
-
stream: false,
|
|
152
|
-
system: messages.find((m) => m.role === "system")?.content,
|
|
153
|
-
options: {
|
|
154
|
-
temperature: options.temperature,
|
|
155
|
-
num_predict: options.maxTokens,
|
|
156
|
-
},
|
|
157
|
-
}),
|
|
158
|
-
signal: createAbortSignalWithTimeout(this.timeout),
|
|
159
|
-
});
|
|
160
|
-
if (!response.ok) {
|
|
161
|
-
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
|
|
162
|
-
}
|
|
163
|
-
const data = await response.json();
|
|
164
|
-
logger.debug("[OllamaLanguageModel] Native API Response:", JSON.stringify(data, null, 2));
|
|
165
|
-
return {
|
|
166
|
-
text: data.response,
|
|
167
|
-
usage: {
|
|
168
|
-
promptTokens: data.prompt_eval_count ?? this.estimateTokenCount(prompt),
|
|
169
|
-
completionTokens: data.eval_count ??
|
|
170
|
-
this.estimateTokenCount(String(data.response ?? "")),
|
|
171
|
-
totalTokens: (data.prompt_eval_count ?? this.estimateTokenCount(prompt)) +
|
|
172
|
-
(data.eval_count ??
|
|
173
|
-
this.estimateTokenCount(String(data.response ?? ""))),
|
|
174
|
-
},
|
|
175
|
-
finishReason: "stop",
|
|
176
|
-
rawCall: {
|
|
177
|
-
rawPrompt: prompt,
|
|
178
|
-
rawSettings: {
|
|
179
|
-
model: this.modelId,
|
|
180
|
-
temperature: options.temperature,
|
|
181
|
-
num_predict: options.maxTokens,
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
rawResponse: {
|
|
185
|
-
headers: {},
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
async doStream(options) {
|
|
191
|
-
const messages = options
|
|
192
|
-
.messages || [];
|
|
193
|
-
// Check if we should use OpenAI-compatible API
|
|
194
|
-
const useOpenAIMode = isOpenAICompatibleMode();
|
|
195
|
-
if (useOpenAIMode) {
|
|
196
|
-
// OpenAI-compatible mode: Use /v1/chat/completions
|
|
197
|
-
const requestUrl = `${this.baseUrl}/v1/chat/completions`;
|
|
198
|
-
const requestBody = {
|
|
199
|
-
model: this.modelId,
|
|
200
|
-
messages,
|
|
201
|
-
temperature: options.temperature,
|
|
202
|
-
max_tokens: options.maxTokens,
|
|
203
|
-
stream: true,
|
|
204
|
-
};
|
|
205
|
-
logger.debug("[OllamaLanguageModel] doStream: Using OpenAI-compatible API", {
|
|
206
|
-
url: requestUrl,
|
|
207
|
-
baseUrl: this.baseUrl,
|
|
208
|
-
modelId: this.modelId,
|
|
209
|
-
requestBody: JSON.stringify(requestBody),
|
|
210
|
-
});
|
|
211
|
-
const response = await proxyFetch(requestUrl, {
|
|
212
|
-
method: "POST",
|
|
213
|
-
headers: { "Content-Type": "application/json" },
|
|
214
|
-
body: JSON.stringify(requestBody),
|
|
215
|
-
signal: createAbortSignalWithTimeout(this.timeout),
|
|
216
|
-
});
|
|
217
|
-
logger.debug("[OllamaLanguageModel] doStream: Response received", {
|
|
218
|
-
status: response.status,
|
|
219
|
-
statusText: response.statusText,
|
|
220
|
-
ok: response.ok,
|
|
221
|
-
});
|
|
222
|
-
if (!response.ok) {
|
|
223
|
-
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
|
|
224
|
-
}
|
|
225
|
-
const self = this;
|
|
226
|
-
return {
|
|
227
|
-
stream: new ReadableStream({
|
|
228
|
-
async start(controller) {
|
|
229
|
-
try {
|
|
230
|
-
for await (const chunk of self.parseOpenAIStreamResponse(response, messages)) {
|
|
231
|
-
controller.enqueue(chunk);
|
|
232
|
-
}
|
|
233
|
-
controller.close();
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
controller.error(error);
|
|
237
|
-
}
|
|
238
|
-
},
|
|
239
|
-
}),
|
|
240
|
-
rawCall: {
|
|
241
|
-
rawPrompt: messages,
|
|
242
|
-
rawSettings: {
|
|
243
|
-
model: this.modelId,
|
|
244
|
-
temperature: options.temperature,
|
|
245
|
-
max_tokens: options.maxTokens,
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
rawResponse: {
|
|
249
|
-
headers: {},
|
|
250
|
-
},
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
// Native Ollama mode: Use /api/generate
|
|
255
|
-
const prompt = this.convertMessagesToPrompt(messages);
|
|
256
|
-
const requestUrl = `${this.baseUrl}/api/generate`;
|
|
257
|
-
const requestBody = {
|
|
258
|
-
model: this.modelId,
|
|
259
|
-
prompt,
|
|
260
|
-
stream: true,
|
|
261
|
-
system: messages.find((m) => m.role === "system")?.content,
|
|
262
|
-
options: {
|
|
263
|
-
temperature: options.temperature,
|
|
264
|
-
num_predict: options.maxTokens,
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
logger.debug("[OllamaLanguageModel] doStream: Using native API", {
|
|
268
|
-
url: requestUrl,
|
|
269
|
-
baseUrl: this.baseUrl,
|
|
270
|
-
modelId: this.modelId,
|
|
271
|
-
requestBody: JSON.stringify(requestBody),
|
|
272
|
-
});
|
|
273
|
-
const response = await proxyFetch(requestUrl, {
|
|
274
|
-
method: "POST",
|
|
275
|
-
headers: { "Content-Type": "application/json" },
|
|
276
|
-
body: JSON.stringify(requestBody),
|
|
277
|
-
signal: createAbortSignalWithTimeout(this.timeout),
|
|
278
|
-
});
|
|
279
|
-
logger.debug("[OllamaLanguageModel] doStream: Response received", {
|
|
280
|
-
status: response.status,
|
|
281
|
-
statusText: response.statusText,
|
|
282
|
-
ok: response.ok,
|
|
283
|
-
});
|
|
284
|
-
if (!response.ok) {
|
|
285
|
-
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
|
|
286
|
-
}
|
|
287
|
-
const self = this;
|
|
288
|
-
return {
|
|
289
|
-
stream: new ReadableStream({
|
|
290
|
-
async start(controller) {
|
|
291
|
-
try {
|
|
292
|
-
for await (const chunk of self.parseStreamResponse(response)) {
|
|
293
|
-
controller.enqueue(chunk);
|
|
294
|
-
}
|
|
295
|
-
controller.close();
|
|
296
|
-
}
|
|
297
|
-
catch (error) {
|
|
298
|
-
controller.error(error);
|
|
299
|
-
}
|
|
300
|
-
},
|
|
301
|
-
}),
|
|
302
|
-
rawCall: {
|
|
303
|
-
rawPrompt: messages,
|
|
304
|
-
rawSettings: {
|
|
305
|
-
model: this.modelId,
|
|
306
|
-
temperature: options.temperature,
|
|
307
|
-
num_predict: options.maxTokens,
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
rawResponse: {
|
|
311
|
-
headers: {},
|
|
312
|
-
},
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
async *parseStreamResponse(response) {
|
|
317
|
-
const reader = response.body?.getReader();
|
|
318
|
-
if (!reader) {
|
|
319
|
-
throw new Error("No response body");
|
|
320
|
-
}
|
|
321
|
-
const decoder = new TextDecoder();
|
|
322
|
-
let buffer = "";
|
|
323
|
-
try {
|
|
324
|
-
while (true) {
|
|
325
|
-
const { done, value } = await reader.read();
|
|
326
|
-
if (done) {
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
buffer += decoder.decode(value, { stream: true });
|
|
330
|
-
const lines = buffer.split("\n");
|
|
331
|
-
buffer = lines.pop() || "";
|
|
332
|
-
for (const line of lines) {
|
|
333
|
-
if (line.trim()) {
|
|
334
|
-
try {
|
|
335
|
-
const data = JSON.parse(line);
|
|
336
|
-
if (data.response) {
|
|
337
|
-
yield {
|
|
338
|
-
type: "text-delta",
|
|
339
|
-
textDelta: data.response,
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
if (data.done) {
|
|
343
|
-
yield {
|
|
344
|
-
type: "finish",
|
|
345
|
-
finishReason: "stop",
|
|
346
|
-
usage: {
|
|
347
|
-
promptTokens: data.prompt_eval_count ||
|
|
348
|
-
this.estimateTokenCount(data.context || ""),
|
|
349
|
-
completionTokens: data.eval_count || 0,
|
|
350
|
-
},
|
|
351
|
-
};
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
catch (error) {
|
|
356
|
-
logger.error("Error parsing Ollama stream response", {
|
|
357
|
-
error,
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
finally {
|
|
365
|
-
reader.releaseLock();
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
async *parseOpenAIStreamResponse(response, messages) {
|
|
369
|
-
const reader = response.body?.getReader();
|
|
370
|
-
if (!reader) {
|
|
371
|
-
throw new Error("No response body");
|
|
372
|
-
}
|
|
373
|
-
const decoder = new TextDecoder();
|
|
374
|
-
let buffer = "";
|
|
375
|
-
// Estimate prompt tokens from messages (matches non-streaming behavior)
|
|
376
|
-
const totalPromptTokens = this.estimateTokenCount(JSON.stringify(messages));
|
|
377
|
-
// Accumulate full completion text; estimate tokens once at the end to avoid
|
|
378
|
-
// per-chunk rounding inflation that occurs when estimateTokenCount is called
|
|
379
|
-
// on every delta and the results are summed.
|
|
380
|
-
let completionText = "";
|
|
381
|
-
try {
|
|
382
|
-
while (true) {
|
|
383
|
-
const { done, value } = await reader.read();
|
|
384
|
-
if (done) {
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
387
|
-
buffer += decoder.decode(value, { stream: true });
|
|
388
|
-
const lines = buffer.split("\n");
|
|
389
|
-
buffer = lines.pop() || "";
|
|
390
|
-
for (const line of lines) {
|
|
391
|
-
const trimmed = line.trim();
|
|
392
|
-
if (trimmed === "" || trimmed === "data: [DONE]") {
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
if (trimmed.startsWith("data: ")) {
|
|
396
|
-
try {
|
|
397
|
-
const jsonStr = trimmed.slice(6); // Remove "data: " prefix
|
|
398
|
-
const data = JSON.parse(jsonStr);
|
|
399
|
-
// Extract content delta
|
|
400
|
-
const content = data.choices?.[0]?.delta?.content;
|
|
401
|
-
if (content) {
|
|
402
|
-
yield {
|
|
403
|
-
type: "text-delta",
|
|
404
|
-
textDelta: content,
|
|
405
|
-
};
|
|
406
|
-
completionText += content;
|
|
407
|
-
}
|
|
408
|
-
// Check for finish
|
|
409
|
-
const finishReason = data.choices?.[0]?.finish_reason;
|
|
410
|
-
if (finishReason === "stop") {
|
|
411
|
-
// Prefer server-reported usage; fall back to a single estimate over
|
|
412
|
-
// the full accumulated text (avoids per-chunk rounding inflation).
|
|
413
|
-
const promptTokens = data.usage?.prompt_tokens || totalPromptTokens;
|
|
414
|
-
const completionTokens = data.usage?.completion_tokens ||
|
|
415
|
-
this.estimateTokenCount(completionText);
|
|
416
|
-
yield {
|
|
417
|
-
type: "finish",
|
|
418
|
-
finishReason: "stop",
|
|
419
|
-
usage: {
|
|
420
|
-
promptTokens,
|
|
421
|
-
completionTokens,
|
|
422
|
-
},
|
|
423
|
-
};
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
catch (error) {
|
|
428
|
-
logger.error("Error parsing OpenAI stream response", {
|
|
429
|
-
error,
|
|
430
|
-
line: trimmed,
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
// If loop exits without explicit finish, yield final finish
|
|
437
|
-
yield {
|
|
438
|
-
type: "finish",
|
|
439
|
-
finishReason: "stop",
|
|
440
|
-
usage: {
|
|
441
|
-
promptTokens: totalPromptTokens,
|
|
442
|
-
completionTokens: this.estimateTokenCount(completionText),
|
|
443
|
-
},
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
finally {
|
|
447
|
-
reader.releaseLock();
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Ollama Provider v2 - BaseProvider Implementation
|
|
453
|
-
*
|
|
454
|
-
* PHASE 3.7: BaseProvider wrap around existing custom Ollama implementation
|
|
455
|
-
*
|
|
456
|
-
* Features:
|
|
457
|
-
* - Extends BaseProvider for shared functionality
|
|
458
|
-
* - Preserves custom OllamaLanguageModel implementation
|
|
459
|
-
* - Local model management and health checking
|
|
460
|
-
* - Enhanced error handling with Ollama-specific guidance
|
|
461
|
-
*/
|
|
462
|
-
export class OllamaProvider extends BaseProvider {
|
|
463
|
-
ollamaModel;
|
|
464
|
-
baseUrl;
|
|
465
|
-
timeout;
|
|
466
|
-
constructor(modelName) {
|
|
467
|
-
super(modelName, "ollama");
|
|
468
|
-
this.baseUrl = getOllamaBaseUrl();
|
|
469
|
-
this.timeout = getOllamaTimeout();
|
|
470
|
-
// Initialize Ollama model
|
|
471
|
-
this.ollamaModel = new OllamaLanguageModel(this.modelName || getDefaultOllamaModel(), this.baseUrl, this.timeout);
|
|
472
|
-
logger.debug("Ollama BaseProvider v2 initialized", {
|
|
473
|
-
modelName: this.modelName,
|
|
474
|
-
baseUrl: this.baseUrl,
|
|
475
|
-
timeout: this.timeout,
|
|
476
|
-
provider: this.providerName,
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
getProviderName() {
|
|
480
|
-
return "ollama";
|
|
481
|
-
}
|
|
482
|
-
getDefaultModel() {
|
|
483
|
-
return getDefaultOllamaModel();
|
|
484
|
-
}
|
|
485
|
-
/**
|
|
486
|
-
* Returns the Vercel AI SDK model instance for Ollama.
|
|
487
|
-
*
|
|
488
|
-
* OllamaLanguageModel implements OllamaAsLanguageModel which is structurally
|
|
489
|
-
* compatible with LanguageModelV2 (specificationVersion "v2", modelId, provider,
|
|
490
|
-
* supportedUrls, doGenerate, doStream).
|
|
491
|
-
*/
|
|
492
|
-
getAISDKModel() {
|
|
493
|
-
const model = this.ollamaModel;
|
|
494
|
-
return model;
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
|
-
* Ollama Tool Calling Support (Enhanced 2025)
|
|
498
|
-
*
|
|
499
|
-
* Uses configurable model list from ModelConfiguration instead of hardcoded values.
|
|
500
|
-
* Tool-capable models can be configured via OLLAMA_TOOL_CAPABLE_MODELS environment variable.
|
|
501
|
-
*
|
|
502
|
-
* **Configuration Options:**
|
|
503
|
-
* - Environment variable: OLLAMA_TOOL_CAPABLE_MODELS (comma-separated list)
|
|
504
|
-
* - Configuration file: providers.ollama.modelBehavior.toolCapableModels
|
|
505
|
-
* - Fallback: Default list of known tool-capable models
|
|
506
|
-
*
|
|
507
|
-
* **Implementation Features:**
|
|
508
|
-
* - Direct Ollama API integration (/v1/chat/completions)
|
|
509
|
-
* - Automatic tool schema conversion to Ollama format
|
|
510
|
-
* - Streaming tool calls with incremental response parsing
|
|
511
|
-
* - Model compatibility validation and fallback handling
|
|
512
|
-
*
|
|
513
|
-
* @returns true for supported models, false for unsupported models
|
|
514
|
-
*/
|
|
515
|
-
supportsTools() {
|
|
516
|
-
const modelName = (this.modelName ?? getDefaultOllamaModel()).toLowerCase();
|
|
517
|
-
// Get tool-capable models from configuration
|
|
518
|
-
const ollamaConfig = modelConfig.getProviderConfiguration("ollama");
|
|
519
|
-
const toolCapableModels = ollamaConfig?.modelBehavior?.toolCapableModels || [];
|
|
520
|
-
// Only disable tools if we have positive evidence the model doesn't support them
|
|
521
|
-
// If toolCapableModels config is empty, assume tools are supported (don't make assumptions)
|
|
522
|
-
if (toolCapableModels.length === 0) {
|
|
523
|
-
logger.debug("Ollama tool calling enabled", {
|
|
524
|
-
model: this.modelName,
|
|
525
|
-
reason: "No tool-capable config defined, assuming tools supported",
|
|
526
|
-
baseUrl: this.baseUrl,
|
|
527
|
-
});
|
|
528
|
-
return true;
|
|
529
|
-
}
|
|
530
|
-
// Config exists - check if current model matches tool-capable model patterns
|
|
531
|
-
const isToolCapable = toolCapableModels.some((capableModel) => modelName.includes(capableModel.toLowerCase()));
|
|
532
|
-
if (isToolCapable) {
|
|
533
|
-
logger.debug("Ollama tool calling enabled", {
|
|
534
|
-
model: this.modelName,
|
|
535
|
-
reason: "Model in tool-capable list",
|
|
536
|
-
baseUrl: this.baseUrl,
|
|
537
|
-
configuredModels: toolCapableModels.length,
|
|
538
|
-
});
|
|
539
|
-
return true;
|
|
540
|
-
}
|
|
541
|
-
// Config exists and model is NOT in list - disable tools
|
|
542
|
-
logger.debug("Ollama tool calling disabled", {
|
|
543
|
-
model: this.modelName,
|
|
544
|
-
reason: "Model not in tool-capable list",
|
|
545
|
-
suggestion: "Consider using llama3.1:8b-instruct, mistral:7b-instruct, or hermes3:8b for tool calling",
|
|
546
|
-
availableToolModels: toolCapableModels.slice(0, 3), // Show first 3 for brevity
|
|
547
|
-
});
|
|
548
|
-
return false;
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* Extract images from multimodal messages for Ollama API
|
|
552
|
-
* Returns array of base64-encoded images
|
|
553
|
-
*/
|
|
554
|
-
extractImagesFromMessages(messages) {
|
|
555
|
-
const images = [];
|
|
556
|
-
for (const msg of messages) {
|
|
557
|
-
if (Array.isArray(msg.content)) {
|
|
558
|
-
for (const content of msg.content) {
|
|
559
|
-
const typedContent = content;
|
|
560
|
-
if (typedContent.type === "image" && typedContent.image) {
|
|
561
|
-
const imageData = typeof typedContent.image === "string"
|
|
562
|
-
? typedContent.image.replace(/^data:image\/\w+;base64,/, "")
|
|
563
|
-
: Buffer.from(typedContent.image).toString("base64");
|
|
564
|
-
images.push(imageData);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
return images;
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Convert multimodal messages to Ollama chat format
|
|
573
|
-
* Extracts text content and handles images separately
|
|
574
|
-
*/
|
|
575
|
-
convertToOllamaMessages(messages) {
|
|
576
|
-
return messages.map((msg) => {
|
|
577
|
-
let textContent = "";
|
|
578
|
-
const images = [];
|
|
579
|
-
if (typeof msg.content === "string") {
|
|
580
|
-
textContent = msg.content;
|
|
581
|
-
}
|
|
582
|
-
else if (Array.isArray(msg.content)) {
|
|
583
|
-
for (const content of msg.content) {
|
|
584
|
-
const typedContent = content;
|
|
585
|
-
if (typedContent.type === "text" && typedContent.text) {
|
|
586
|
-
textContent += typedContent.text;
|
|
587
|
-
}
|
|
588
|
-
else if (typedContent.type === "image" && typedContent.image) {
|
|
589
|
-
const imageData = typeof typedContent.image === "string"
|
|
590
|
-
? typedContent.image.replace(/^data:image\/\w+;base64,/, "")
|
|
591
|
-
: Buffer.from(typedContent.image).toString("base64");
|
|
592
|
-
images.push(imageData);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
const ollamaMsg = {
|
|
597
|
-
role: (msg.role === "system" ? "system" : msg.role),
|
|
598
|
-
content: textContent,
|
|
599
|
-
};
|
|
600
|
-
if (images.length > 0) {
|
|
601
|
-
ollamaMsg.images = images;
|
|
602
|
-
}
|
|
603
|
-
return ollamaMsg;
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
// executeGenerate removed - BaseProvider handles all generation with tools
|
|
607
|
-
async executeStream(options, analysisSchema) {
|
|
608
|
-
try {
|
|
609
|
-
this.validateStreamOptions(options);
|
|
610
|
-
await this.checkOllamaHealth();
|
|
611
|
-
// Check if tools are supported and provided
|
|
612
|
-
const modelSupportsTools = this.supportsTools();
|
|
613
|
-
const hasTools = options.tools && Object.keys(options.tools).length > 0;
|
|
614
|
-
if (modelSupportsTools && hasTools) {
|
|
615
|
-
// Use chat API with tools for tool-capable models
|
|
616
|
-
return this.executeStreamWithTools(options, analysisSchema);
|
|
617
|
-
}
|
|
618
|
-
else {
|
|
619
|
-
// Use generate API for non-tool scenarios
|
|
620
|
-
return this.executeStreamWithoutTools(options, analysisSchema);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
catch (error) {
|
|
624
|
-
throw this.handleProviderError(error);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
/**
|
|
628
|
-
* Execute streaming with Ollama's function calling support
|
|
629
|
-
* Uses conversation loop to handle multi-step tool execution
|
|
630
|
-
*/
|
|
631
|
-
async executeStreamWithTools(options, _analysisSchema) {
|
|
632
|
-
return withClientSpan({
|
|
633
|
-
name: "neurolink.provider.stream",
|
|
634
|
-
tracer: tracers.provider,
|
|
635
|
-
attributes: {
|
|
636
|
-
[ATTR.GEN_AI_SYSTEM]: "ollama",
|
|
637
|
-
[ATTR.GEN_AI_MODEL]: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
638
|
-
[ATTR.GEN_AI_OPERATION]: "stream",
|
|
639
|
-
[ATTR.NL_HAS_TOOLS]: true,
|
|
640
|
-
[ATTR.NL_STREAM_MODE]: true,
|
|
641
|
-
},
|
|
642
|
-
}, async (span) => {
|
|
643
|
-
const startTime = Date.now();
|
|
644
|
-
const maxIterations = options.maxSteps || DEFAULT_MAX_STEPS;
|
|
645
|
-
let iteration = 0;
|
|
646
|
-
// Get all available tools (direct + MCP + external)
|
|
647
|
-
// BaseProvider.stream() pre-merges base tools + external tools into options.tools
|
|
648
|
-
const allTools = options.tools ||
|
|
649
|
-
(await this.getAllTools());
|
|
650
|
-
// Convert tools to Ollama format
|
|
651
|
-
const ollamaTools = this.convertToolsToOllamaFormat(allTools);
|
|
652
|
-
span.setAttribute(ATTR.NL_TOOL_COUNT, ollamaTools.length);
|
|
653
|
-
// Validate that PDFs are not provided
|
|
654
|
-
if (options.input?.pdfFiles && options.input.pdfFiles.length > 0) {
|
|
655
|
-
throw this.handleProviderError(new Error("PDF inputs are not supported by OllamaProvider. " +
|
|
656
|
-
"Please remove PDFs or use a supported provider (OpenAI, Anthropic, Google Vertex AI, etc.)."));
|
|
657
|
-
}
|
|
658
|
-
// Initialize conversation history
|
|
659
|
-
const conversationHistory = [];
|
|
660
|
-
// Build initial messages
|
|
661
|
-
const hasMultimodalInput = !!(options.input?.images?.length ||
|
|
662
|
-
options.input?.content?.length ||
|
|
663
|
-
options.input?.files?.length ||
|
|
664
|
-
options.input?.csvFiles?.length);
|
|
665
|
-
if (hasMultimodalInput) {
|
|
666
|
-
logger.debug(`Ollama: Detected multimodal input, using multimodal message builder`, {
|
|
667
|
-
hasImages: !!options.input?.images?.length,
|
|
668
|
-
imageCount: options.input?.images?.length || 0,
|
|
669
|
-
});
|
|
670
|
-
const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
|
|
671
|
-
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
672
|
-
conversationHistory.push(...this.convertToOllamaMessages(multimodalMessages));
|
|
673
|
-
}
|
|
674
|
-
else {
|
|
675
|
-
if (options.systemPrompt) {
|
|
676
|
-
conversationHistory.push({
|
|
677
|
-
role: "system",
|
|
678
|
-
content: options.systemPrompt,
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
conversationHistory.push({
|
|
682
|
-
role: "user",
|
|
683
|
-
content: options.input.text,
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
// Conversation loop for multi-step tool execution
|
|
687
|
-
const stream = new ReadableStream({
|
|
688
|
-
start: async (controller) => {
|
|
689
|
-
try {
|
|
690
|
-
while (iteration < maxIterations) {
|
|
691
|
-
logger.debug(`[OllamaProvider] Conversation iteration ${iteration + 1}/${maxIterations}`);
|
|
692
|
-
// Make API request
|
|
693
|
-
const response = await proxyFetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
694
|
-
method: "POST",
|
|
695
|
-
headers: { "Content-Type": "application/json" },
|
|
696
|
-
body: JSON.stringify({
|
|
697
|
-
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
698
|
-
messages: conversationHistory,
|
|
699
|
-
tools: ollamaTools,
|
|
700
|
-
tool_choice: "auto",
|
|
701
|
-
stream: true,
|
|
702
|
-
temperature: options.temperature,
|
|
703
|
-
max_tokens: options.maxTokens,
|
|
704
|
-
}),
|
|
705
|
-
signal: createAbortSignalWithTimeout(this.timeout),
|
|
706
|
-
});
|
|
707
|
-
if (!response.ok) {
|
|
708
|
-
throw this.handleProviderError(new Error(`Ollama API error: ${response.status} ${response.statusText}`));
|
|
709
|
-
}
|
|
710
|
-
// Process response stream
|
|
711
|
-
const { content, toolCalls, finishReason } = await this.processOllamaResponse(response, controller);
|
|
712
|
-
// Add assistant message to history
|
|
713
|
-
const assistantMessage = {
|
|
714
|
-
role: "assistant",
|
|
715
|
-
content: content || "",
|
|
716
|
-
};
|
|
717
|
-
if (toolCalls && toolCalls.length > 0) {
|
|
718
|
-
assistantMessage.tool_calls = toolCalls;
|
|
719
|
-
}
|
|
720
|
-
conversationHistory.push(assistantMessage);
|
|
721
|
-
// Check finish reason
|
|
722
|
-
if (finishReason === "stop" || !finishReason) {
|
|
723
|
-
// Conversation complete
|
|
724
|
-
span.setAttribute(ATTR.GEN_AI_FINISH_REASON, finishReason || "stop");
|
|
725
|
-
controller.close();
|
|
726
|
-
break;
|
|
727
|
-
}
|
|
728
|
-
else if (finishReason === "tool_calls" &&
|
|
729
|
-
toolCalls &&
|
|
730
|
-
toolCalls.length > 0) {
|
|
731
|
-
// Execute tools
|
|
732
|
-
logger.debug(`[OllamaProvider] Executing ${toolCalls.length} tools`);
|
|
733
|
-
for (const tc of toolCalls) {
|
|
734
|
-
span.addEvent("tool_call", {
|
|
735
|
-
[ATTR.GEN_AI_TOOL_NAME]: tc.function.name,
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
const toolResults = await this.executeOllamaTools(toolCalls, options);
|
|
739
|
-
// Add tool results to conversation
|
|
740
|
-
const toolMessage = {
|
|
741
|
-
role: "tool",
|
|
742
|
-
content: JSON.stringify(toolResults),
|
|
743
|
-
};
|
|
744
|
-
conversationHistory.push(toolMessage);
|
|
745
|
-
iteration++;
|
|
746
|
-
}
|
|
747
|
-
else if (finishReason === "length") {
|
|
748
|
-
// Max tokens reached, continue conversation
|
|
749
|
-
logger.debug(`[OllamaProvider] Max tokens reached, continuing`);
|
|
750
|
-
conversationHistory.push({
|
|
751
|
-
role: "user",
|
|
752
|
-
content: "Please continue.",
|
|
753
|
-
});
|
|
754
|
-
iteration++;
|
|
755
|
-
}
|
|
756
|
-
else {
|
|
757
|
-
// Unknown finish reason, end conversation
|
|
758
|
-
logger.warn(`[OllamaProvider] Unknown finish reason: ${finishReason}`);
|
|
759
|
-
span.setAttribute(ATTR.GEN_AI_FINISH_REASON, finishReason);
|
|
760
|
-
controller.close();
|
|
761
|
-
break;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
if (iteration >= maxIterations) {
|
|
765
|
-
controller.error(new Error(`Ollama conversation exceeded maximum iterations (${maxIterations})`));
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
catch (error) {
|
|
769
|
-
controller.error(error);
|
|
770
|
-
}
|
|
771
|
-
finally {
|
|
772
|
-
// Resolve analytics with final values now that the loop has completed.
|
|
773
|
-
resolveAnalytics(createAnalytics(this.providerName, this.modelName || FALLBACK_OLLAMA_MODEL, { usage: { input: 0, output: 0, total: 0 } }, Date.now() - startTime, {
|
|
774
|
-
requestId: `ollama-stream-${Date.now()}`,
|
|
775
|
-
streamingMode: true,
|
|
776
|
-
iterations: iteration,
|
|
777
|
-
note: "Token usage not available from Ollama streaming responses",
|
|
778
|
-
}));
|
|
779
|
-
}
|
|
780
|
-
},
|
|
781
|
-
});
|
|
782
|
-
// Defer analytics resolution until the stream's start callback finishes.
|
|
783
|
-
// This ensures responseTime and iteration reflect the actual completed values
|
|
784
|
-
// rather than values captured before the tool-loop executes.
|
|
785
|
-
let resolveAnalytics;
|
|
786
|
-
const analyticsPromise = new Promise((resolve) => {
|
|
787
|
-
resolveAnalytics = resolve;
|
|
788
|
-
});
|
|
789
|
-
return {
|
|
790
|
-
stream: this.convertToAsyncIterable(stream),
|
|
791
|
-
provider: this.providerName,
|
|
792
|
-
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
793
|
-
analytics: analyticsPromise,
|
|
794
|
-
metadata: {
|
|
795
|
-
startTime,
|
|
796
|
-
streamId: `ollama-${Date.now()}`,
|
|
797
|
-
},
|
|
798
|
-
};
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
/**
|
|
802
|
-
* Execute streaming without tools using the generate API
|
|
803
|
-
* Fallback for non-tool scenarios or when chat API is unavailable
|
|
804
|
-
*/
|
|
805
|
-
async executeStreamWithoutTools(options, _analysisSchema) {
|
|
806
|
-
return withClientSpan({
|
|
807
|
-
name: "neurolink.provider.stream",
|
|
808
|
-
tracer: tracers.provider,
|
|
809
|
-
attributes: {
|
|
810
|
-
[ATTR.GEN_AI_SYSTEM]: "ollama",
|
|
811
|
-
[ATTR.GEN_AI_MODEL]: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
812
|
-
[ATTR.GEN_AI_OPERATION]: "stream",
|
|
813
|
-
[ATTR.NL_HAS_TOOLS]: false,
|
|
814
|
-
[ATTR.NL_STREAM_MODE]: true,
|
|
815
|
-
},
|
|
816
|
-
}, async () => {
|
|
817
|
-
// Validate that PDFs are not provided
|
|
818
|
-
if (options.input?.pdfFiles && options.input.pdfFiles.length > 0) {
|
|
819
|
-
throw this.handleProviderError(new Error("PDF inputs are not supported by OllamaProvider. " +
|
|
820
|
-
"Please remove PDFs or use a supported provider (OpenAI, Anthropic, Google Vertex AI, etc.)."));
|
|
821
|
-
}
|
|
822
|
-
// Check for multimodal input
|
|
823
|
-
const hasMultimodalInput = !!(options.input?.images?.length ||
|
|
824
|
-
options.input?.content?.length ||
|
|
825
|
-
options.input?.files?.length ||
|
|
826
|
-
options.input?.csvFiles?.length);
|
|
827
|
-
const useOpenAIMode = isOpenAICompatibleMode();
|
|
828
|
-
if (useOpenAIMode) {
|
|
829
|
-
// OpenAI-compatible mode: Use /v1/chat/completions with messages
|
|
830
|
-
logger.debug(`Ollama (OpenAI mode): Building messages for streaming`);
|
|
831
|
-
const messages = [];
|
|
832
|
-
if (options.systemPrompt) {
|
|
833
|
-
messages.push({ role: "system", content: options.systemPrompt });
|
|
834
|
-
}
|
|
835
|
-
if (hasMultimodalInput) {
|
|
836
|
-
const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
|
|
837
|
-
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
838
|
-
// Convert multimodal messages to text (OpenAI-compatible mode doesn't support images in /v1/chat/completions for Ollama)
|
|
839
|
-
const content = multimodalMessages
|
|
840
|
-
.map((msg) => typeof msg.content === "string" ? msg.content : "")
|
|
841
|
-
.join("\n");
|
|
842
|
-
messages.push({ role: "user", content });
|
|
843
|
-
}
|
|
844
|
-
else {
|
|
845
|
-
messages.push({ role: "user", content: options.input.text });
|
|
846
|
-
}
|
|
847
|
-
const requestUrl = `${this.baseUrl}/v1/chat/completions`;
|
|
848
|
-
const requestBody = {
|
|
849
|
-
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
850
|
-
messages,
|
|
851
|
-
temperature: options.temperature,
|
|
852
|
-
max_tokens: options.maxTokens,
|
|
853
|
-
stream: true,
|
|
854
|
-
};
|
|
855
|
-
logger.debug(`[Ollama OpenAI Mode] About to fetch:`, {
|
|
856
|
-
url: requestUrl,
|
|
857
|
-
baseUrl: this.baseUrl,
|
|
858
|
-
modelName: this.modelName,
|
|
859
|
-
requestBody: JSON.stringify(requestBody),
|
|
860
|
-
});
|
|
861
|
-
const response = await proxyFetch(requestUrl, {
|
|
862
|
-
method: "POST",
|
|
863
|
-
headers: { "Content-Type": "application/json" },
|
|
864
|
-
body: JSON.stringify(requestBody),
|
|
865
|
-
signal: createAbortSignalWithTimeout(this.timeout),
|
|
866
|
-
});
|
|
867
|
-
logger.debug(`[Ollama OpenAI Mode] Response received:`, {
|
|
868
|
-
status: response.status,
|
|
869
|
-
statusText: response.statusText,
|
|
870
|
-
ok: response.ok,
|
|
871
|
-
});
|
|
872
|
-
if (!response.ok) {
|
|
873
|
-
throw this.handleProviderError(new Error(`Ollama API error: ${response.status} ${response.statusText}`));
|
|
874
|
-
}
|
|
875
|
-
// Transform to async generator for OpenAI-compatible format
|
|
876
|
-
const self = this;
|
|
877
|
-
const transformedStream = async function* () {
|
|
878
|
-
const generator = self.createOpenAIStream(response);
|
|
879
|
-
for await (const chunk of generator) {
|
|
880
|
-
yield chunk;
|
|
881
|
-
}
|
|
882
|
-
};
|
|
883
|
-
return {
|
|
884
|
-
stream: transformedStream(),
|
|
885
|
-
provider: self.providerName,
|
|
886
|
-
model: self.modelName,
|
|
887
|
-
};
|
|
888
|
-
}
|
|
889
|
-
else {
|
|
890
|
-
// Native Ollama mode: Use /api/generate
|
|
891
|
-
let prompt = options.input.text;
|
|
892
|
-
let images;
|
|
893
|
-
if (hasMultimodalInput) {
|
|
894
|
-
logger.debug(`Ollama (native mode): Detected multimodal input`, {
|
|
895
|
-
hasImages: !!options.input?.images?.length,
|
|
896
|
-
imageCount: options.input?.images?.length || 0,
|
|
897
|
-
});
|
|
898
|
-
const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
|
|
899
|
-
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
900
|
-
// Extract text from messages for prompt
|
|
901
|
-
prompt = multimodalMessages
|
|
902
|
-
.map((msg) => typeof msg.content === "string" ? msg.content : "")
|
|
903
|
-
.join("\n");
|
|
904
|
-
// Extract images
|
|
905
|
-
images = this.extractImagesFromMessages(multimodalMessages);
|
|
906
|
-
}
|
|
907
|
-
const requestBody = {
|
|
908
|
-
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
909
|
-
prompt,
|
|
910
|
-
system: options.systemPrompt,
|
|
911
|
-
stream: true,
|
|
912
|
-
options: {
|
|
913
|
-
temperature: options.temperature,
|
|
914
|
-
num_predict: options.maxTokens,
|
|
915
|
-
},
|
|
916
|
-
};
|
|
917
|
-
if (images && images.length > 0) {
|
|
918
|
-
requestBody.images = images;
|
|
919
|
-
}
|
|
920
|
-
const requestUrl = `${this.baseUrl}/api/generate`;
|
|
921
|
-
logger.debug(`[Ollama Native Mode] About to fetch:`, {
|
|
922
|
-
url: requestUrl,
|
|
923
|
-
baseUrl: this.baseUrl,
|
|
924
|
-
modelName: this.modelName,
|
|
925
|
-
requestBody: JSON.stringify(requestBody),
|
|
926
|
-
});
|
|
927
|
-
const response = await proxyFetch(requestUrl, {
|
|
928
|
-
method: "POST",
|
|
929
|
-
headers: { "Content-Type": "application/json" },
|
|
930
|
-
body: JSON.stringify(requestBody),
|
|
931
|
-
signal: createAbortSignalWithTimeout(this.timeout),
|
|
932
|
-
});
|
|
933
|
-
logger.debug(`[Ollama Native Mode] Response received:`, {
|
|
934
|
-
status: response.status,
|
|
935
|
-
statusText: response.statusText,
|
|
936
|
-
ok: response.ok,
|
|
937
|
-
});
|
|
938
|
-
if (!response.ok) {
|
|
939
|
-
throw this.handleProviderError(new Error(`Ollama API error: ${response.status} ${response.statusText}`));
|
|
940
|
-
}
|
|
941
|
-
// Transform to async generator to match other providers
|
|
942
|
-
const self = this;
|
|
943
|
-
const transformedStream = async function* () {
|
|
944
|
-
const generator = self.createOllamaStream(response);
|
|
945
|
-
for await (const chunk of generator) {
|
|
946
|
-
yield chunk;
|
|
947
|
-
}
|
|
948
|
-
};
|
|
949
|
-
return {
|
|
950
|
-
stream: transformedStream(),
|
|
951
|
-
provider: this.providerName,
|
|
952
|
-
model: this.modelName,
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
});
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
|
-
* Convert AI SDK tools format to Ollama's function calling format
|
|
959
|
-
*/
|
|
960
|
-
convertToolsToOllamaFormat(tools) {
|
|
961
|
-
if (!tools || typeof tools !== "object") {
|
|
962
|
-
return [];
|
|
963
|
-
}
|
|
964
|
-
const toolsArray = Array.isArray(tools) ? tools : Object.values(tools);
|
|
965
|
-
return toolsArray.map((tool) => ({
|
|
966
|
-
type: "function",
|
|
967
|
-
function: {
|
|
968
|
-
name: tool.name || tool.function?.name,
|
|
969
|
-
description: tool.description || tool.function?.description,
|
|
970
|
-
parameters: tool.parameters ||
|
|
971
|
-
tool.function?.parameters || {
|
|
972
|
-
type: "object",
|
|
973
|
-
properties: {},
|
|
974
|
-
required: [],
|
|
975
|
-
},
|
|
976
|
-
},
|
|
977
|
-
}));
|
|
978
|
-
}
|
|
979
|
-
/**
|
|
980
|
-
* Parse tool calls from Ollama API response
|
|
981
|
-
*/
|
|
982
|
-
parseToolCalls(rawToolCalls) {
|
|
983
|
-
if (!Array.isArray(rawToolCalls)) {
|
|
984
|
-
return [];
|
|
985
|
-
}
|
|
986
|
-
return rawToolCalls
|
|
987
|
-
.map((call) => {
|
|
988
|
-
const callObj = call;
|
|
989
|
-
if (!callObj.function?.name) {
|
|
990
|
-
return null;
|
|
991
|
-
}
|
|
992
|
-
return {
|
|
993
|
-
id: callObj.id ||
|
|
994
|
-
`tool_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
995
|
-
type: "function",
|
|
996
|
-
function: {
|
|
997
|
-
name: callObj.function.name,
|
|
998
|
-
arguments: callObj.function.arguments || "{}",
|
|
999
|
-
},
|
|
1000
|
-
};
|
|
1001
|
-
})
|
|
1002
|
-
.filter((call) => call !== null);
|
|
1003
|
-
}
|
|
1004
|
-
/**
|
|
1005
|
-
* Process Ollama streaming response and stream content to controller
|
|
1006
|
-
* Returns aggregated content, tool calls, and finish reason
|
|
1007
|
-
*/
|
|
1008
|
-
async processOllamaResponse(response, controller) {
|
|
1009
|
-
const reader = response.body?.getReader();
|
|
1010
|
-
if (!reader) {
|
|
1011
|
-
throw new Error("No response body from Ollama");
|
|
1012
|
-
}
|
|
1013
|
-
const decoder = new TextDecoder();
|
|
1014
|
-
let buffer = "";
|
|
1015
|
-
let aggregatedContent = "";
|
|
1016
|
-
let aggregatedToolCalls = [];
|
|
1017
|
-
let finalFinishReason;
|
|
1018
|
-
try {
|
|
1019
|
-
while (true) {
|
|
1020
|
-
const { done, value } = await reader.read();
|
|
1021
|
-
if (done) {
|
|
1022
|
-
break;
|
|
1023
|
-
}
|
|
1024
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1025
|
-
const lines = buffer.split("\n");
|
|
1026
|
-
buffer = lines.pop() || "";
|
|
1027
|
-
for (const line of lines) {
|
|
1028
|
-
if (line.trim() && line.startsWith("data: ")) {
|
|
1029
|
-
const dataLine = line.slice(6); // Remove "data: " prefix
|
|
1030
|
-
if (dataLine === "[DONE]") {
|
|
1031
|
-
break;
|
|
1032
|
-
}
|
|
1033
|
-
try {
|
|
1034
|
-
const parsed = JSON.parse(dataLine);
|
|
1035
|
-
const processed = this.processOllamaStreamData(parsed);
|
|
1036
|
-
if (!processed) {
|
|
1037
|
-
continue;
|
|
1038
|
-
}
|
|
1039
|
-
// Stream content to controller
|
|
1040
|
-
if (processed.content) {
|
|
1041
|
-
aggregatedContent += processed.content;
|
|
1042
|
-
controller.enqueue({
|
|
1043
|
-
content: processed.content,
|
|
1044
|
-
});
|
|
1045
|
-
}
|
|
1046
|
-
// Collect tool calls
|
|
1047
|
-
if (processed.toolCalls && processed.toolCalls.length > 0) {
|
|
1048
|
-
aggregatedToolCalls = [
|
|
1049
|
-
...aggregatedToolCalls,
|
|
1050
|
-
...processed.toolCalls,
|
|
1051
|
-
];
|
|
1052
|
-
}
|
|
1053
|
-
// Update finish reason
|
|
1054
|
-
if (processed.finishReason) {
|
|
1055
|
-
finalFinishReason = processed.finishReason;
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
catch (parseError) {
|
|
1059
|
-
logger.warn(`[OllamaProvider] Failed to parse stream chunk: ${dataLine}`, { error: parseError });
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
finally {
|
|
1066
|
-
reader.releaseLock();
|
|
1067
|
-
}
|
|
1068
|
-
return {
|
|
1069
|
-
content: aggregatedContent || undefined,
|
|
1070
|
-
toolCalls: aggregatedToolCalls.length > 0 ? aggregatedToolCalls : undefined,
|
|
1071
|
-
finishReason: finalFinishReason,
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
/**
|
|
1075
|
-
* Process individual stream data chunk from Ollama
|
|
1076
|
-
*/
|
|
1077
|
-
processOllamaStreamData(data) {
|
|
1078
|
-
const dataRecord = data;
|
|
1079
|
-
const choices = dataRecord.choices;
|
|
1080
|
-
const delta = choices?.[0]?.delta;
|
|
1081
|
-
const finishReason = choices?.[0]?.finish_reason;
|
|
1082
|
-
let content = "";
|
|
1083
|
-
if (delta?.content && typeof delta.content === "string") {
|
|
1084
|
-
content += delta.content;
|
|
1085
|
-
}
|
|
1086
|
-
// Return tool calls for execution instead of formatting as text
|
|
1087
|
-
if (delta?.tool_calls) {
|
|
1088
|
-
const toolCalls = this.parseToolCalls(delta.tool_calls);
|
|
1089
|
-
return {
|
|
1090
|
-
toolCalls,
|
|
1091
|
-
finishReason,
|
|
1092
|
-
shouldReturn: !!finishReason,
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
// Also check for tool calls in the message field (some responses include it there)
|
|
1096
|
-
if (choices?.[0]?.message?.tool_calls) {
|
|
1097
|
-
const toolCalls = this.parseToolCalls(choices[0].message.tool_calls);
|
|
1098
|
-
return {
|
|
1099
|
-
toolCalls,
|
|
1100
|
-
finishReason,
|
|
1101
|
-
shouldReturn: !!finishReason,
|
|
1102
|
-
};
|
|
1103
|
-
}
|
|
1104
|
-
const shouldReturn = !!finishReason;
|
|
1105
|
-
return content
|
|
1106
|
-
? { content, finishReason, shouldReturn }
|
|
1107
|
-
: { finishReason, shouldReturn };
|
|
1108
|
-
}
|
|
1109
|
-
/**
|
|
1110
|
-
* Create stream generator for Ollama chat API with tool call support
|
|
1111
|
-
*/
|
|
1112
|
-
async *createOllamaChatStream(response, _tools) {
|
|
1113
|
-
const reader = response.body?.getReader();
|
|
1114
|
-
if (!reader) {
|
|
1115
|
-
throw new Error("No response body");
|
|
1116
|
-
}
|
|
1117
|
-
const decoder = new TextDecoder();
|
|
1118
|
-
let buffer = "";
|
|
1119
|
-
try {
|
|
1120
|
-
while (true) {
|
|
1121
|
-
const { done, value } = await reader.read();
|
|
1122
|
-
if (done) {
|
|
1123
|
-
break;
|
|
1124
|
-
}
|
|
1125
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1126
|
-
const lines = buffer.split("\n");
|
|
1127
|
-
buffer = lines.pop() || "";
|
|
1128
|
-
for (const line of lines) {
|
|
1129
|
-
if (line.trim() && line.startsWith("data: ")) {
|
|
1130
|
-
const dataLine = line.slice(6); // Remove "data: " prefix
|
|
1131
|
-
if (dataLine === "[DONE]") {
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
try {
|
|
1135
|
-
const data = JSON.parse(dataLine);
|
|
1136
|
-
const result = this.processOllamaStreamData(data);
|
|
1137
|
-
if (result?.content) {
|
|
1138
|
-
yield { content: result.content };
|
|
1139
|
-
}
|
|
1140
|
-
if (result?.shouldReturn) {
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
catch (error) {
|
|
1145
|
-
logger.error("Error parsing Ollama stream response", {
|
|
1146
|
-
error,
|
|
1147
|
-
});
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
finally {
|
|
1154
|
-
reader.releaseLock();
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
/**
|
|
1158
|
-
* Format tool calls for display when tools aren't executed directly
|
|
1159
|
-
*/
|
|
1160
|
-
formatToolCallForDisplay(toolCalls) {
|
|
1161
|
-
if (!toolCalls || toolCalls.length === 0) {
|
|
1162
|
-
return "";
|
|
1163
|
-
}
|
|
1164
|
-
const descriptions = toolCalls.map((call) => {
|
|
1165
|
-
const functionName = call.function?.name || "unknown_function";
|
|
1166
|
-
let args = {};
|
|
1167
|
-
if (call.function?.arguments) {
|
|
1168
|
-
try {
|
|
1169
|
-
args = JSON.parse(call.function.arguments);
|
|
1170
|
-
}
|
|
1171
|
-
catch (error) {
|
|
1172
|
-
// If arguments are malformed, preserve for debugging while marking as invalid
|
|
1173
|
-
logger.warn?.("Malformed tool call arguments: " + call.function.arguments);
|
|
1174
|
-
args = {
|
|
1175
|
-
_malformed: true,
|
|
1176
|
-
_originalArguments: call.function.arguments,
|
|
1177
|
-
_error: error instanceof Error ? error.message : String(error),
|
|
1178
|
-
};
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
return `\n[Tool Call: ${functionName}(${JSON.stringify(args)})]`;
|
|
1182
|
-
});
|
|
1183
|
-
return descriptions.join("");
|
|
1184
|
-
}
|
|
1185
|
-
/**
|
|
1186
|
-
* Convert AI SDK tools to ToolDefinition format
|
|
1187
|
-
*/
|
|
1188
|
-
convertAISDKToolsToToolDefinitions(aiTools) {
|
|
1189
|
-
const result = {};
|
|
1190
|
-
for (const [name, tool] of Object.entries(aiTools)) {
|
|
1191
|
-
if ("description" in tool && tool.description) {
|
|
1192
|
-
// AI SDK v6 uses `inputSchema`; legacy tools may still use `parameters`
|
|
1193
|
-
const toolSchema = "inputSchema" in tool
|
|
1194
|
-
? tool.inputSchema
|
|
1195
|
-
: "parameters" in tool
|
|
1196
|
-
? tool
|
|
1197
|
-
.parameters
|
|
1198
|
-
: undefined;
|
|
1199
|
-
result[name] = {
|
|
1200
|
-
description: tool.description,
|
|
1201
|
-
parameters: toolSchema,
|
|
1202
|
-
execute: async (params) => {
|
|
1203
|
-
if ("execute" in tool && tool.execute) {
|
|
1204
|
-
const result = await tool.execute(params, {
|
|
1205
|
-
toolCallId: `tool_${Date.now()}`,
|
|
1206
|
-
messages: [],
|
|
1207
|
-
});
|
|
1208
|
-
return {
|
|
1209
|
-
success: true,
|
|
1210
|
-
data: result,
|
|
1211
|
-
};
|
|
1212
|
-
}
|
|
1213
|
-
throw new Error(`Tool ${name} has no execute method`);
|
|
1214
|
-
},
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
return result;
|
|
1219
|
-
}
|
|
1220
|
-
/**
|
|
1221
|
-
* Execute a single tool and return the result
|
|
1222
|
-
*/
|
|
1223
|
-
async executeSingleTool(toolName, args, _toolCallId) {
|
|
1224
|
-
logger.debug(`[OllamaProvider] Executing single tool: ${toolName}`, {
|
|
1225
|
-
args,
|
|
1226
|
-
});
|
|
1227
|
-
try {
|
|
1228
|
-
// Use BaseProvider's tool execution mechanism
|
|
1229
|
-
const aiTools = await this.getAllTools();
|
|
1230
|
-
const tools = this.convertAISDKToolsToToolDefinitions(aiTools);
|
|
1231
|
-
if (!tools[toolName]) {
|
|
1232
|
-
throw new Error(`Tool not found: ${toolName}`);
|
|
1233
|
-
}
|
|
1234
|
-
const tool = tools[toolName];
|
|
1235
|
-
if (!tool || !tool.execute) {
|
|
1236
|
-
throw new Error(`Tool ${toolName} does not have execute method`);
|
|
1237
|
-
}
|
|
1238
|
-
const toolInput = args || {};
|
|
1239
|
-
// Convert Record<string, unknown> to ToolArgs by filtering out non-JsonValue types
|
|
1240
|
-
const toolArgs = {};
|
|
1241
|
-
for (const [key, value] of Object.entries(toolInput)) {
|
|
1242
|
-
// Only include values that are JsonValue compatible
|
|
1243
|
-
if (value === null ||
|
|
1244
|
-
typeof value === "string" ||
|
|
1245
|
-
typeof value === "number" ||
|
|
1246
|
-
typeof value === "boolean" ||
|
|
1247
|
-
(typeof value === "object" && value !== null)) {
|
|
1248
|
-
toolArgs[key] = value;
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
const result = await tool.execute(toolArgs);
|
|
1252
|
-
logger.debug(`[OllamaProvider] Tool execution result:`, {
|
|
1253
|
-
toolName,
|
|
1254
|
-
result,
|
|
1255
|
-
});
|
|
1256
|
-
// Handle ToolResult type
|
|
1257
|
-
if (result && typeof result === "object" && "success" in result) {
|
|
1258
|
-
if (result.success && result.data !== undefined) {
|
|
1259
|
-
if (typeof result.data === "string") {
|
|
1260
|
-
return result.data;
|
|
1261
|
-
}
|
|
1262
|
-
else if (typeof result.data === "object") {
|
|
1263
|
-
return JSON.stringify(result.data, null, 2);
|
|
1264
|
-
}
|
|
1265
|
-
else {
|
|
1266
|
-
return String(result.data);
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
else if (result.error) {
|
|
1270
|
-
const errorMessage = typeof result.error === "string"
|
|
1271
|
-
? result.error
|
|
1272
|
-
: result.error.message || "Tool execution failed";
|
|
1273
|
-
throw new Error(errorMessage);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
// Fallback for non-ToolResult return types
|
|
1277
|
-
if (typeof result === "string") {
|
|
1278
|
-
return result;
|
|
1279
|
-
}
|
|
1280
|
-
else if (typeof result === "object") {
|
|
1281
|
-
return JSON.stringify(result, null, 2);
|
|
1282
|
-
}
|
|
1283
|
-
else {
|
|
1284
|
-
return String(result);
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
catch (error) {
|
|
1288
|
-
logger.error(`[OllamaProvider] Tool execution error:`, {
|
|
1289
|
-
toolName,
|
|
1290
|
-
error,
|
|
1291
|
-
});
|
|
1292
|
-
throw error;
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
/**
|
|
1296
|
-
* Execute tools and format results for Ollama API
|
|
1297
|
-
* Similar to Bedrock's executeStreamTools but for Ollama format
|
|
1298
|
-
*/
|
|
1299
|
-
async executeOllamaTools(toolCalls, options) {
|
|
1300
|
-
const toolResults = [];
|
|
1301
|
-
const toolCallsForStorage = [];
|
|
1302
|
-
const toolResultsForStorage = [];
|
|
1303
|
-
logger.debug(`[OllamaProvider] Executing ${toolCalls.length} tool calls`);
|
|
1304
|
-
for (const call of toolCalls) {
|
|
1305
|
-
logger.debug(`[OllamaProvider] Executing tool: ${call.function.name}`);
|
|
1306
|
-
// Parse arguments
|
|
1307
|
-
let args;
|
|
1308
|
-
try {
|
|
1309
|
-
args = JSON.parse(call.function.arguments);
|
|
1310
|
-
}
|
|
1311
|
-
catch (error) {
|
|
1312
|
-
logger.error(`[OllamaProvider] Failed to parse tool arguments: ${call.function.arguments}`, { error });
|
|
1313
|
-
args = {};
|
|
1314
|
-
}
|
|
1315
|
-
// Track tool call for storage
|
|
1316
|
-
toolCallsForStorage.push({
|
|
1317
|
-
type: "tool-call",
|
|
1318
|
-
toolCallId: call.id,
|
|
1319
|
-
toolName: call.function.name,
|
|
1320
|
-
args,
|
|
1321
|
-
});
|
|
1322
|
-
try {
|
|
1323
|
-
// Execute tool using existing tool framework
|
|
1324
|
-
const result = await this.executeSingleTool(call.function.name, args, call.id);
|
|
1325
|
-
logger.debug(`[OllamaProvider] Tool execution successful: ${call.function.name}`);
|
|
1326
|
-
// Track result for storage
|
|
1327
|
-
toolResultsForStorage.push({
|
|
1328
|
-
type: "tool-result",
|
|
1329
|
-
toolCallId: call.id,
|
|
1330
|
-
toolName: call.function.name,
|
|
1331
|
-
result,
|
|
1332
|
-
});
|
|
1333
|
-
// Format for Ollama API
|
|
1334
|
-
toolResults.push({
|
|
1335
|
-
tool_call_id: call.id,
|
|
1336
|
-
content: JSON.stringify(result),
|
|
1337
|
-
});
|
|
1338
|
-
}
|
|
1339
|
-
catch (error) {
|
|
1340
|
-
logger.error(`[OllamaProvider] Tool execution failed: ${call.function.name}`, { error });
|
|
1341
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1342
|
-
// Track failed result
|
|
1343
|
-
toolResultsForStorage.push({
|
|
1344
|
-
type: "tool-result",
|
|
1345
|
-
toolCallId: call.id,
|
|
1346
|
-
toolName: call.function.name,
|
|
1347
|
-
result: { error: errorMessage },
|
|
1348
|
-
});
|
|
1349
|
-
// Format error for Ollama API
|
|
1350
|
-
toolResults.push({
|
|
1351
|
-
tool_call_id: call.id,
|
|
1352
|
-
content: JSON.stringify({ error: errorMessage }),
|
|
1353
|
-
});
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
// Store tool executions (similar to Bedrock)
|
|
1357
|
-
this.handleToolExecutionStorage(toolCallsForStorage, toolResultsForStorage, options, new Date()).catch((error) => {
|
|
1358
|
-
logger.warn("[OllamaProvider] Failed to store tool executions", {
|
|
1359
|
-
provider: this.providerName,
|
|
1360
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1361
|
-
});
|
|
1362
|
-
});
|
|
1363
|
-
return toolResults;
|
|
1364
|
-
}
|
|
1365
|
-
/**
|
|
1366
|
-
* Convert ReadableStream to AsyncIterable for compatibility with StreamResult interface
|
|
1367
|
-
*/
|
|
1368
|
-
convertToAsyncIterable(stream) {
|
|
1369
|
-
return {
|
|
1370
|
-
async *[Symbol.asyncIterator]() {
|
|
1371
|
-
const reader = stream.getReader();
|
|
1372
|
-
try {
|
|
1373
|
-
while (true) {
|
|
1374
|
-
const { done, value } = await reader.read();
|
|
1375
|
-
if (done) {
|
|
1376
|
-
break;
|
|
1377
|
-
}
|
|
1378
|
-
yield value;
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
finally {
|
|
1382
|
-
reader.releaseLock();
|
|
1383
|
-
}
|
|
1384
|
-
},
|
|
1385
|
-
};
|
|
1386
|
-
}
|
|
1387
|
-
/**
|
|
1388
|
-
* Create stream generator for Ollama generate API (non-tool mode)
|
|
1389
|
-
*/
|
|
1390
|
-
async *createOllamaStream(response) {
|
|
1391
|
-
const reader = response.body?.getReader();
|
|
1392
|
-
if (!reader) {
|
|
1393
|
-
throw new Error("No response body");
|
|
1394
|
-
}
|
|
1395
|
-
const decoder = new TextDecoder();
|
|
1396
|
-
let buffer = "";
|
|
1397
|
-
try {
|
|
1398
|
-
while (true) {
|
|
1399
|
-
const { done, value } = await reader.read();
|
|
1400
|
-
if (done) {
|
|
1401
|
-
break;
|
|
1402
|
-
}
|
|
1403
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1404
|
-
const lines = buffer.split("\n");
|
|
1405
|
-
buffer = lines.pop() || "";
|
|
1406
|
-
for (const line of lines) {
|
|
1407
|
-
if (line.trim()) {
|
|
1408
|
-
try {
|
|
1409
|
-
const data = JSON.parse(line);
|
|
1410
|
-
if (data.response) {
|
|
1411
|
-
yield { content: data.response };
|
|
1412
|
-
}
|
|
1413
|
-
if (data.done) {
|
|
1414
|
-
return;
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
catch (error) {
|
|
1418
|
-
logger.error("Error parsing Ollama stream response", {
|
|
1419
|
-
error,
|
|
1420
|
-
});
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
finally {
|
|
1427
|
-
reader.releaseLock();
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
async *createOpenAIStream(response) {
|
|
1431
|
-
const reader = response.body?.getReader();
|
|
1432
|
-
if (!reader) {
|
|
1433
|
-
throw new Error("No response body");
|
|
1434
|
-
}
|
|
1435
|
-
const decoder = new TextDecoder();
|
|
1436
|
-
let buffer = "";
|
|
1437
|
-
try {
|
|
1438
|
-
while (true) {
|
|
1439
|
-
const { done, value } = await reader.read();
|
|
1440
|
-
if (done) {
|
|
1441
|
-
break;
|
|
1442
|
-
}
|
|
1443
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1444
|
-
const lines = buffer.split("\n");
|
|
1445
|
-
buffer = lines.pop() || "";
|
|
1446
|
-
for (const line of lines) {
|
|
1447
|
-
const trimmedLine = line.trim();
|
|
1448
|
-
if (!trimmedLine || trimmedLine === "data: [DONE]") {
|
|
1449
|
-
continue;
|
|
1450
|
-
}
|
|
1451
|
-
if (trimmedLine.startsWith("data: ")) {
|
|
1452
|
-
try {
|
|
1453
|
-
const jsonStr = trimmedLine.slice(6); // Remove "data: " prefix
|
|
1454
|
-
const data = JSON.parse(jsonStr);
|
|
1455
|
-
const content = data.choices?.[0]?.delta?.content;
|
|
1456
|
-
if (content) {
|
|
1457
|
-
yield { content };
|
|
1458
|
-
}
|
|
1459
|
-
if (data.choices?.[0]?.finish_reason) {
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
catch (error) {
|
|
1464
|
-
logger.error("Error parsing OpenAI stream response", {
|
|
1465
|
-
error,
|
|
1466
|
-
line: trimmedLine,
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
finally {
|
|
1474
|
-
reader.releaseLock();
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
formatProviderError(error) {
|
|
1478
|
-
if (error instanceof TimeoutError) {
|
|
1479
|
-
return new TimeoutError(`Ollama request timed out. The model might be loading or the request is too complex.`, this.timeout);
|
|
1480
|
-
}
|
|
1481
|
-
if (error.message?.includes("ECONNREFUSED") ||
|
|
1482
|
-
error.message?.includes("fetch failed")) {
|
|
1483
|
-
return new NetworkError(`ā Ollama Service Not Running\n\nCannot connect to Ollama at ${this.baseUrl}\n\nš§ Steps to Fix:\n1. Install Ollama: https://ollama.ai/\n2. Start Ollama service: 'ollama serve'\n3. Verify it's running: 'curl ${this.baseUrl}/api/version'\n4. Try again`, this.providerName);
|
|
1484
|
-
}
|
|
1485
|
-
if (error.message?.includes("model") &&
|
|
1486
|
-
error.message?.includes("not found")) {
|
|
1487
|
-
return new InvalidModelError(`ā Ollama Model Not Found\n\nModel '${this.modelName}' is not available locally.\n\nš§ Install Model:\n1. Run: ollama pull ${this.modelName}\n2. Or try a different model:\n - ollama pull ${FALLBACK_OLLAMA_MODEL}\n - ollama pull mistral:latest\n - ollama pull codellama:latest\n\nš§ List Available Models:\nollama list`, this.providerName);
|
|
1488
|
-
}
|
|
1489
|
-
if (error.message?.includes("404")) {
|
|
1490
|
-
return new NetworkError(`ā Ollama API Endpoint Not Found\n\nThe API endpoint might have changed or Ollama version is incompatible.\n\nš§ Check:\n1. Ollama version: 'ollama --version'\n2. Update Ollama to latest version\n3. Verify API is available: 'curl ${this.baseUrl}/api/version'`, this.providerName);
|
|
1491
|
-
}
|
|
1492
|
-
return new ProviderError(`ā Ollama Provider Error\n\n${error.message || "Unknown error occurred"}\n\nš§ Troubleshooting:\n1. Check if Ollama service is running\n2. Verify model is installed: 'ollama list'\n3. Check network connectivity to ${this.baseUrl}\n4. Review Ollama logs for details`, this.providerName);
|
|
1493
|
-
}
|
|
1494
|
-
/**
|
|
1495
|
-
* Check if Ollama service is healthy and accessible
|
|
1496
|
-
*/
|
|
1497
|
-
async checkOllamaHealth() {
|
|
1498
|
-
try {
|
|
1499
|
-
// Use traditional AbortController for better compatibility
|
|
1500
|
-
const controller = new AbortController();
|
|
1501
|
-
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
1502
|
-
const response = await proxyFetch(`${this.baseUrl}/api/version`, {
|
|
1503
|
-
method: "GET",
|
|
1504
|
-
signal: controller.signal,
|
|
1505
|
-
});
|
|
1506
|
-
clearTimeout(timeoutId);
|
|
1507
|
-
if (!response.ok) {
|
|
1508
|
-
throw new Error(`Ollama health check failed: ${response.status}`);
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
catch (error) {
|
|
1512
|
-
if (error instanceof Error && error.message.includes("ECONNREFUSED")) {
|
|
1513
|
-
throw new Error(`ā Ollama Service Not Running\n\nCannot connect to Ollama service.\n\nš§ Start Ollama:\n1. Run: ollama serve\n2. Or start Ollama app\n3. Verify: curl ${this.baseUrl}/api/version`, { cause: error });
|
|
1514
|
-
}
|
|
1515
|
-
throw error;
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
/**
|
|
1519
|
-
* Get available models from Ollama
|
|
1520
|
-
*/
|
|
1521
|
-
async getAvailableModels() {
|
|
1522
|
-
try {
|
|
1523
|
-
const response = await proxyFetch(`${this.baseUrl}/api/tags`);
|
|
1524
|
-
if (!response.ok) {
|
|
1525
|
-
throw new Error(`Failed to fetch models: ${response.status}`);
|
|
1526
|
-
}
|
|
1527
|
-
const data = await response.json();
|
|
1528
|
-
return data.models?.map((model) => model.name) || [];
|
|
1529
|
-
}
|
|
1530
|
-
catch (error) {
|
|
1531
|
-
logger.warn("Failed to fetch Ollama models:", error);
|
|
1532
|
-
return [];
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
/**
|
|
1536
|
-
* Check if a specific model is available
|
|
1537
|
-
*/
|
|
1538
|
-
async isModelAvailable(modelName) {
|
|
1539
|
-
const models = await this.getAvailableModels();
|
|
1540
|
-
return models.includes(modelName);
|
|
1541
|
-
}
|
|
1542
|
-
/**
|
|
1543
|
-
* Get recommendations for tool-calling capable Ollama models
|
|
1544
|
-
* Provides guidance for users who want to use function calling locally
|
|
1545
|
-
*/
|
|
1546
|
-
static getToolCallingRecommendations() {
|
|
1547
|
-
return {
|
|
1548
|
-
recommended: [
|
|
1549
|
-
"llama3.1:8b-instruct",
|
|
1550
|
-
"mistral:7b-instruct-v0.3",
|
|
1551
|
-
"hermes3:8b-llama3.1",
|
|
1552
|
-
"codellama:34b-instruct",
|
|
1553
|
-
"firefunction-v2:70b",
|
|
1554
|
-
],
|
|
1555
|
-
performance: {
|
|
1556
|
-
"llama3.1:8b-instruct": { speed: 3, quality: 3, size: "4.6GB" },
|
|
1557
|
-
"mistral:7b-instruct-v0.3": { speed: 3, quality: 2, size: "4.1GB" },
|
|
1558
|
-
"hermes3:8b-llama3.1": { speed: 3, quality: 3, size: "4.6GB" },
|
|
1559
|
-
"codellama:34b-instruct": { speed: 1, quality: 3, size: "19GB" },
|
|
1560
|
-
"firefunction-v2:70b": { speed: 1, quality: 3, size: "40GB" },
|
|
1561
|
-
},
|
|
1562
|
-
notes: {
|
|
1563
|
-
"llama3.1:8b-instruct": "Best balance of speed, quality, and tool calling capability",
|
|
1564
|
-
"mistral:7b-instruct-v0.3": "Lightweight with reliable function calling",
|
|
1565
|
-
"hermes3:8b-llama3.1": "Specialized for tool execution and reasoning",
|
|
1566
|
-
"codellama:34b-instruct": "Excellent for code-related tool calling, requires more resources",
|
|
1567
|
-
"firefunction-v2:70b": "Optimized specifically for function calling, requires high-end hardware",
|
|
1568
|
-
},
|
|
1569
|
-
installation: {
|
|
1570
|
-
"llama3.1:8b-instruct": "ollama pull llama3.1:8b-instruct",
|
|
1571
|
-
"mistral:7b-instruct-v0.3": "ollama pull mistral:7b-instruct-v0.3",
|
|
1572
|
-
"hermes3:8b-llama3.1": "ollama pull hermes3:8b-llama3.1",
|
|
1573
|
-
"codellama:34b-instruct": "ollama pull codellama:34b-instruct",
|
|
1574
|
-
"firefunction-v2:70b": "ollama pull firefunction-v2:70b",
|
|
1575
|
-
},
|
|
1576
|
-
};
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
export default OllamaProvider;
|