@juspay/neurolink 7.50.0 → 7.51.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +12 -9
  3. package/dist/adapters/providerImageAdapter.js +82 -10
  4. package/dist/agent/directTools.d.ts +10 -10
  5. package/dist/agent/directTools.js +5 -3
  6. package/dist/cli/commands/config.js +1 -0
  7. package/dist/cli/commands/mcp.js +1 -0
  8. package/dist/cli/commands/models.js +1 -0
  9. package/dist/cli/commands/ollama.js +1 -0
  10. package/dist/cli/commands/setup-anthropic.js +1 -0
  11. package/dist/cli/commands/setup-azure.js +1 -0
  12. package/dist/cli/commands/setup-bedrock.js +1 -0
  13. package/dist/cli/commands/setup-gcp.js +1 -0
  14. package/dist/cli/commands/setup-google-ai.js +1 -0
  15. package/dist/cli/commands/setup-huggingface.js +1 -0
  16. package/dist/cli/commands/setup-mistral.js +1 -0
  17. package/dist/cli/commands/setup-openai.js +1 -0
  18. package/dist/cli/commands/setup.js +1 -0
  19. package/dist/cli/errorHandler.js +1 -0
  20. package/dist/cli/factories/commandFactory.d.ts +1 -0
  21. package/dist/cli/factories/commandFactory.js +23 -6
  22. package/dist/cli/factories/ollamaCommandFactory.js +1 -0
  23. package/dist/cli/factories/sagemakerCommandFactory.js +1 -0
  24. package/dist/cli/factories/setupCommandFactory.js +1 -0
  25. package/dist/cli/index.js +1 -0
  26. package/dist/cli/loop/conversationSelector.js +1 -0
  27. package/dist/cli/loop/optionsSchema.js +1 -0
  28. package/dist/cli/loop/session.js +1 -0
  29. package/dist/cli/parser.js +1 -0
  30. package/dist/cli/utils/completeSetup.js +1 -0
  31. package/dist/cli/utils/envManager.js +1 -0
  32. package/dist/cli/utils/interactiveSetup.js +1 -0
  33. package/dist/cli/utils/ollamaUtils.js +1 -0
  34. package/dist/constants/index.js +1 -1
  35. package/dist/core/baseProvider.d.ts +5 -0
  36. package/dist/core/baseProvider.js +70 -20
  37. package/dist/index.d.ts +3 -3
  38. package/dist/lib/adapters/providerImageAdapter.js +83 -10
  39. package/dist/lib/agent/directTools.d.ts +10 -10
  40. package/dist/lib/agent/directTools.js +6 -3
  41. package/dist/lib/config/configManager.js +1 -0
  42. package/dist/lib/config/conversationMemory.js +1 -0
  43. package/dist/lib/config/taskClassificationConfig.js +1 -0
  44. package/dist/lib/constants/index.js +2 -1
  45. package/dist/lib/constants/performance.js +1 -0
  46. package/dist/lib/constants/retry.js +1 -0
  47. package/dist/lib/constants/timeouts.js +1 -0
  48. package/dist/lib/constants/tokens.js +1 -0
  49. package/dist/lib/core/analytics.js +1 -0
  50. package/dist/lib/core/baseProvider.d.ts +5 -0
  51. package/dist/lib/core/baseProvider.js +71 -20
  52. package/dist/lib/core/constants.js +1 -0
  53. package/dist/lib/core/conversationMemoryFactory.js +1 -0
  54. package/dist/lib/core/conversationMemoryInitializer.js +1 -0
  55. package/dist/lib/core/conversationMemoryManager.js +1 -0
  56. package/dist/lib/core/dynamicModels.js +1 -0
  57. package/dist/lib/core/evaluation.js +1 -0
  58. package/dist/lib/core/evaluationProviders.js +1 -0
  59. package/dist/lib/core/factory.js +1 -0
  60. package/dist/lib/core/modelConfiguration.js +1 -0
  61. package/dist/lib/core/redisConversationMemoryManager.js +1 -0
  62. package/dist/lib/core/serviceRegistry.js +1 -0
  63. package/dist/lib/core/streamAnalytics.js +1 -0
  64. package/dist/lib/evaluation/contextBuilder.js +1 -0
  65. package/dist/lib/evaluation/index.js +1 -0
  66. package/dist/lib/evaluation/prompts.js +1 -0
  67. package/dist/lib/evaluation/ragasEvaluator.js +1 -0
  68. package/dist/lib/evaluation/retryManager.js +1 -0
  69. package/dist/lib/evaluation/scoring.js +1 -0
  70. package/dist/lib/factories/providerFactory.js +1 -0
  71. package/dist/lib/factories/providerRegistry.js +1 -0
  72. package/dist/lib/hitl/hitlErrors.js +1 -0
  73. package/dist/lib/hitl/hitlManager.js +1 -0
  74. package/dist/lib/hitl/index.js +1 -0
  75. package/dist/lib/hitl/types.js +1 -0
  76. package/dist/lib/index.d.ts +3 -3
  77. package/dist/lib/index.js +1 -0
  78. package/dist/lib/mcp/externalServerManager.js +1 -0
  79. package/dist/lib/mcp/factory.js +1 -0
  80. package/dist/lib/mcp/flexibleToolValidator.js +1 -0
  81. package/dist/lib/mcp/index.js +1 -0
  82. package/dist/lib/mcp/mcpCircuitBreaker.js +1 -0
  83. package/dist/lib/mcp/mcpClientFactory.js +2 -1
  84. package/dist/lib/mcp/registry.js +1 -0
  85. package/dist/lib/mcp/servers/agent/directToolsServer.js +2 -0
  86. package/dist/lib/mcp/servers/aiProviders/aiAnalysisTools.js +1 -0
  87. package/dist/lib/mcp/servers/aiProviders/aiCoreServer.js +1 -0
  88. package/dist/lib/mcp/servers/aiProviders/aiWorkflowTools.js +1 -0
  89. package/dist/lib/mcp/servers/utilities/utilityServer.js +1 -0
  90. package/dist/lib/mcp/toolDiscoveryService.js +1 -0
  91. package/dist/lib/mcp/toolRegistry.js +1 -0
  92. package/dist/lib/memory/mem0Initializer.js +1 -0
  93. package/dist/lib/middleware/builtin/analytics.js +1 -0
  94. package/dist/lib/middleware/builtin/autoEvaluation.js +1 -0
  95. package/dist/lib/middleware/builtin/guardrails.js +1 -0
  96. package/dist/lib/middleware/factory.js +1 -0
  97. package/dist/lib/middleware/index.js +1 -0
  98. package/dist/lib/middleware/registry.js +1 -0
  99. package/dist/lib/middleware/utils/guardrailsUtils.js +1 -0
  100. package/dist/lib/models/modelRegistry.js +1 -0
  101. package/dist/lib/models/modelResolver.js +2 -0
  102. package/dist/lib/neurolink.d.ts +6 -0
  103. package/dist/lib/neurolink.js +135 -5
  104. package/dist/lib/providers/amazonBedrock.d.ts +1 -0
  105. package/dist/lib/providers/amazonBedrock.js +166 -14
  106. package/dist/lib/providers/amazonSagemaker.js +1 -0
  107. package/dist/lib/providers/anthropic.js +7 -21
  108. package/dist/lib/providers/anthropicBaseProvider.js +1 -0
  109. package/dist/lib/providers/azureOpenai.js +5 -21
  110. package/dist/lib/providers/googleAiStudio.js +5 -21
  111. package/dist/lib/providers/googleVertex.js +8 -1
  112. package/dist/lib/providers/huggingFace.js +34 -3
  113. package/dist/lib/providers/index.js +1 -0
  114. package/dist/lib/providers/litellm.js +34 -3
  115. package/dist/lib/providers/mistral.js +32 -2
  116. package/dist/lib/providers/ollama.d.ts +37 -1
  117. package/dist/lib/providers/ollama.js +544 -58
  118. package/dist/lib/providers/openAI.js +5 -21
  119. package/dist/lib/providers/openaiCompatible.js +41 -4
  120. package/dist/lib/providers/sagemaker/adaptive-semaphore.js +1 -0
  121. package/dist/lib/providers/sagemaker/client.js +1 -0
  122. package/dist/lib/providers/sagemaker/config.js +1 -0
  123. package/dist/lib/providers/sagemaker/detection.js +1 -0
  124. package/dist/lib/providers/sagemaker/diagnostics.js +1 -0
  125. package/dist/lib/providers/sagemaker/error-constants.js +1 -0
  126. package/dist/lib/providers/sagemaker/errors.js +1 -0
  127. package/dist/lib/providers/sagemaker/index.js +1 -0
  128. package/dist/lib/providers/sagemaker/language-model.js +1 -0
  129. package/dist/lib/providers/sagemaker/parsers.js +1 -0
  130. package/dist/lib/providers/sagemaker/streaming.js +1 -0
  131. package/dist/lib/providers/sagemaker/structured-parser.js +1 -0
  132. package/dist/lib/proxy/awsProxyIntegration.js +1 -0
  133. package/dist/lib/proxy/proxyFetch.js +1 -0
  134. package/dist/lib/proxy/utils/noProxyUtils.js +1 -0
  135. package/dist/lib/sdk/toolRegistration.js +2 -0
  136. package/dist/lib/services/server/ai/observability/instrumentation.js +1 -0
  137. package/dist/lib/session/globalSessionState.js +1 -0
  138. package/dist/lib/telemetry/index.js +1 -0
  139. package/dist/lib/telemetry/telemetryService.js +1 -0
  140. package/dist/lib/types/analytics.js +1 -0
  141. package/dist/lib/types/cli.js +1 -0
  142. package/dist/lib/types/common.js +1 -0
  143. package/dist/lib/types/configTypes.js +1 -0
  144. package/dist/lib/types/content.d.ts +14 -1
  145. package/dist/lib/types/content.js +1 -0
  146. package/dist/lib/types/contextTypes.js +1 -0
  147. package/dist/lib/types/conversation.js +1 -0
  148. package/dist/lib/types/domainTypes.js +1 -0
  149. package/dist/lib/types/errors.js +1 -0
  150. package/dist/lib/types/evaluation.js +1 -0
  151. package/dist/lib/types/evaluationProviders.js +1 -0
  152. package/dist/lib/types/evaluationTypes.js +1 -0
  153. package/dist/lib/types/externalMcp.js +1 -0
  154. package/dist/lib/types/fileTypes.d.ts +44 -0
  155. package/dist/lib/types/fileTypes.js +1 -0
  156. package/dist/lib/types/generateTypes.d.ts +1 -0
  157. package/dist/lib/types/generateTypes.js +1 -0
  158. package/dist/lib/types/guardrails.js +1 -0
  159. package/dist/lib/types/index.js +1 -0
  160. package/dist/lib/types/mcpTypes.js +1 -0
  161. package/dist/lib/types/middlewareTypes.js +1 -0
  162. package/dist/lib/types/modelTypes.js +1 -0
  163. package/dist/lib/types/observability.js +1 -0
  164. package/dist/lib/types/providers.d.ts +44 -0
  165. package/dist/lib/types/providers.js +1 -0
  166. package/dist/lib/types/sdkTypes.js +1 -0
  167. package/dist/lib/types/serviceTypes.js +1 -0
  168. package/dist/lib/types/streamTypes.d.ts +1 -0
  169. package/dist/lib/types/streamTypes.js +1 -0
  170. package/dist/lib/types/taskClassificationTypes.js +1 -0
  171. package/dist/lib/types/tools.js +2 -0
  172. package/dist/lib/types/typeAliases.js +1 -0
  173. package/dist/lib/types/universalProviderOptions.js +1 -0
  174. package/dist/lib/utils/analyticsUtils.js +1 -0
  175. package/dist/lib/utils/conversationMemory.js +1 -0
  176. package/dist/lib/utils/conversationMemoryUtils.js +1 -0
  177. package/dist/lib/utils/csvProcessor.js +1 -0
  178. package/dist/lib/utils/errorHandling.js +1 -0
  179. package/dist/lib/utils/evaluationUtils.js +1 -0
  180. package/dist/lib/utils/factoryProcessing.js +1 -0
  181. package/dist/lib/utils/fileDetector.js +7 -3
  182. package/dist/lib/utils/imageProcessor.js +1 -0
  183. package/dist/lib/utils/logger.js +1 -0
  184. package/dist/lib/utils/loopUtils.js +1 -0
  185. package/dist/lib/utils/mcpDefaults.js +1 -0
  186. package/dist/lib/utils/messageBuilder.js +96 -9
  187. package/dist/lib/utils/modelRouter.js +1 -0
  188. package/dist/lib/utils/multimodalOptionsBuilder.d.ts +67 -0
  189. package/dist/lib/utils/multimodalOptionsBuilder.js +65 -0
  190. package/dist/lib/utils/optionsConversion.js +1 -0
  191. package/dist/lib/utils/optionsUtils.js +1 -0
  192. package/dist/lib/utils/parameterValidation.js +1 -0
  193. package/dist/lib/utils/pdfProcessor.d.ts +10 -0
  194. package/dist/lib/utils/pdfProcessor.js +199 -0
  195. package/dist/lib/utils/performance.js +1 -0
  196. package/dist/lib/utils/promptRedaction.js +1 -0
  197. package/dist/lib/utils/providerConfig.js +1 -0
  198. package/dist/lib/utils/providerHealth.js +1 -0
  199. package/dist/lib/utils/providerSetupMessages.js +1 -0
  200. package/dist/lib/utils/providerUtils.js +1 -0
  201. package/dist/lib/utils/redis.js +1 -0
  202. package/dist/lib/utils/retryHandler.js +1 -0
  203. package/dist/lib/utils/schemaConversion.js +1 -0
  204. package/dist/lib/utils/taskClassificationUtils.js +1 -0
  205. package/dist/lib/utils/taskClassifier.js +1 -0
  206. package/dist/lib/utils/timeout.js +1 -0
  207. package/dist/lib/utils/tokenLimits.js +1 -0
  208. package/dist/lib/utils/toolUtils.js +1 -0
  209. package/dist/lib/utils/transformationUtils.js +1 -0
  210. package/dist/lib/utils/typeUtils.js +1 -0
  211. package/dist/mcp/mcpClientFactory.js +1 -1
  212. package/dist/mcp/servers/agent/directToolsServer.js +1 -0
  213. package/dist/models/modelResolver.js +1 -0
  214. package/dist/neurolink.d.ts +6 -0
  215. package/dist/neurolink.js +134 -5
  216. package/dist/providers/amazonBedrock.d.ts +1 -0
  217. package/dist/providers/amazonBedrock.js +165 -14
  218. package/dist/providers/anthropic.js +6 -21
  219. package/dist/providers/azureOpenai.js +4 -21
  220. package/dist/providers/googleAiStudio.js +4 -21
  221. package/dist/providers/googleVertex.js +7 -1
  222. package/dist/providers/huggingFace.js +33 -3
  223. package/dist/providers/litellm.js +33 -3
  224. package/dist/providers/mistral.js +31 -2
  225. package/dist/providers/ollama.d.ts +37 -1
  226. package/dist/providers/ollama.js +543 -58
  227. package/dist/providers/openAI.js +4 -21
  228. package/dist/providers/openaiCompatible.js +40 -4
  229. package/dist/sdk/toolRegistration.js +1 -0
  230. package/dist/types/content.d.ts +14 -1
  231. package/dist/types/fileTypes.d.ts +44 -0
  232. package/dist/types/generateTypes.d.ts +1 -0
  233. package/dist/types/providers.d.ts +44 -0
  234. package/dist/types/streamTypes.d.ts +1 -0
  235. package/dist/types/tools.js +1 -0
  236. package/dist/utils/fileDetector.js +6 -3
  237. package/dist/utils/messageBuilder.js +95 -9
  238. package/dist/utils/multimodalOptionsBuilder.d.ts +67 -0
  239. package/dist/utils/multimodalOptionsBuilder.js +64 -0
  240. package/dist/utils/pdfProcessor.d.ts +10 -0
  241. package/dist/utils/pdfProcessor.js +198 -0
  242. package/package.json +11 -20
@@ -3,6 +3,10 @@ import { logger } from "../utils/logger.js";
3
3
  import { modelConfig } from "../core/modelConfiguration.js";
4
4
  import { createProxyFetch } from "../proxy/proxyFetch.js";
5
5
  import { TimeoutError } from "../utils/timeout.js";
6
+ import { buildMultimodalMessagesArray } from "../utils/messageBuilder.js";
7
+ import { buildMultimodalOptions } from "../utils/multimodalOptionsBuilder.js";
8
+ import { DEFAULT_MAX_STEPS } from "../core/constants.js";
9
+ import { createAnalytics } from "../core/analytics.js";
6
10
  // Model version constants (configurable via environment)
7
11
  const DEFAULT_OLLAMA_MODEL = "llama3.1:8b";
8
12
  const FALLBACK_OLLAMA_MODEL = "llama3.2:latest"; // Used when primary model fails
@@ -294,6 +298,62 @@ export class OllamaProvider extends BaseProvider {
294
298
  });
295
299
  return false;
296
300
  }
301
+ /**
302
+ * Extract images from multimodal messages for Ollama API
303
+ * Returns array of base64-encoded images
304
+ */
305
+ extractImagesFromMessages(messages) {
306
+ const images = [];
307
+ for (const msg of messages) {
308
+ if (Array.isArray(msg.content)) {
309
+ for (const content of msg.content) {
310
+ const typedContent = content;
311
+ if (typedContent.type === "image" && typedContent.image) {
312
+ const imageData = typeof typedContent.image === "string"
313
+ ? typedContent.image.replace(/^data:image\/\w+;base64,/, "")
314
+ : Buffer.from(typedContent.image).toString("base64");
315
+ images.push(imageData);
316
+ }
317
+ }
318
+ }
319
+ }
320
+ return images;
321
+ }
322
+ /**
323
+ * Convert multimodal messages to Ollama chat format
324
+ * Extracts text content and handles images separately
325
+ */
326
+ convertToOllamaMessages(messages) {
327
+ return messages.map((msg) => {
328
+ let textContent = "";
329
+ const images = [];
330
+ if (typeof msg.content === "string") {
331
+ textContent = msg.content;
332
+ }
333
+ else if (Array.isArray(msg.content)) {
334
+ for (const content of msg.content) {
335
+ const typedContent = content;
336
+ if (typedContent.type === "text" && typedContent.text) {
337
+ textContent += typedContent.text;
338
+ }
339
+ else if (typedContent.type === "image" && typedContent.image) {
340
+ const imageData = typeof typedContent.image === "string"
341
+ ? typedContent.image.replace(/^data:image\/\w+;base64,/, "")
342
+ : Buffer.from(typedContent.image).toString("base64");
343
+ images.push(imageData);
344
+ }
345
+ }
346
+ }
347
+ const ollamaMsg = {
348
+ role: (msg.role === "system" ? "system" : msg.role),
349
+ content: textContent,
350
+ };
351
+ if (images.length > 0) {
352
+ ollamaMsg.images = images;
353
+ }
354
+ return ollamaMsg;
355
+ });
356
+ }
297
357
  // executeGenerate removed - BaseProvider handles all generation with tools
298
358
  async executeStream(options, analysisSchema) {
299
359
  try {
@@ -317,52 +377,147 @@ export class OllamaProvider extends BaseProvider {
317
377
  }
318
378
  /**
319
379
  * Execute streaming with Ollama's function calling support
320
- * Uses the /v1/chat/completions endpoint with tools parameter
380
+ * Uses conversation loop to handle multi-step tool execution
321
381
  */
322
- async executeStreamWithTools(options, analysisSchema) {
382
+ async executeStreamWithTools(options, _analysisSchema) {
383
+ const startTime = Date.now();
384
+ const maxIterations = options.maxSteps || DEFAULT_MAX_STEPS;
385
+ let iteration = 0;
386
+ // Get all available tools (direct + MCP + external)
387
+ const allTools = await this.getAllTools();
323
388
  // Convert tools to Ollama format
324
- const ollamaTools = this.convertToolsToOllamaFormat(options.tools);
325
- // Prepare messages in Ollama chat format
326
- const messages = [
327
- ...(options.systemPrompt
328
- ? [{ role: "system", content: options.systemPrompt }]
329
- : []),
330
- { role: "user", content: options.input.text },
331
- ];
332
- const response = await proxyFetch(`${this.baseUrl}/v1/chat/completions`, {
333
- method: "POST",
334
- headers: { "Content-Type": "application/json" },
335
- body: JSON.stringify({
336
- model: this.modelName || FALLBACK_OLLAMA_MODEL,
337
- messages,
338
- tools: ollamaTools,
339
- tool_choice: "auto",
340
- stream: true,
341
- temperature: options.temperature,
342
- max_tokens: options.maxTokens,
343
- }),
344
- signal: createAbortSignalWithTimeout(this.timeout),
345
- });
346
- if (!response.ok) {
347
- // Fallback to non-tool mode if chat API fails
348
- logger.warn("Ollama chat API failed, falling back to generate API", {
349
- status: response.status,
350
- statusText: response.statusText,
389
+ const ollamaTools = this.convertToolsToOllamaFormat(allTools);
390
+ // Validate that PDFs are not provided
391
+ if (options.input?.pdfFiles && options.input.pdfFiles.length > 0) {
392
+ throw new Error("PDF inputs are not supported by OllamaProvider. " +
393
+ "Please remove PDFs or use a supported provider (OpenAI, Anthropic, Google Vertex AI, etc.).");
394
+ }
395
+ // Initialize conversation history
396
+ const conversationHistory = [];
397
+ // Build initial messages
398
+ const hasMultimodalInput = !!(options.input?.images?.length ||
399
+ options.input?.content?.length ||
400
+ options.input?.files?.length ||
401
+ options.input?.csvFiles?.length);
402
+ if (hasMultimodalInput) {
403
+ logger.debug(`Ollama: Detected multimodal input, using multimodal message builder`, {
404
+ hasImages: !!options.input?.images?.length,
405
+ imageCount: options.input?.images?.length || 0,
351
406
  });
352
- return this.executeStreamWithoutTools(options, analysisSchema);
407
+ const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
408
+ const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
409
+ conversationHistory.push(...this.convertToOllamaMessages(multimodalMessages));
353
410
  }
354
- // Transform to async generator with tool call handling
355
- const self = this;
356
- const transformedStream = async function* () {
357
- const generator = self.createOllamaChatStream(response, options.tools);
358
- for await (const chunk of generator) {
359
- yield chunk;
411
+ else {
412
+ if (options.systemPrompt) {
413
+ conversationHistory.push({
414
+ role: "system",
415
+ content: options.systemPrompt,
416
+ });
360
417
  }
361
- };
418
+ conversationHistory.push({
419
+ role: "user",
420
+ content: options.input.text,
421
+ });
422
+ }
423
+ // Conversation loop for multi-step tool execution
424
+ const stream = new ReadableStream({
425
+ start: async (controller) => {
426
+ try {
427
+ while (iteration < maxIterations) {
428
+ logger.debug(`[OllamaProvider] Conversation iteration ${iteration + 1}/${maxIterations}`);
429
+ // Make API request
430
+ const response = await proxyFetch(`${this.baseUrl}/v1/chat/completions`, {
431
+ method: "POST",
432
+ headers: { "Content-Type": "application/json" },
433
+ body: JSON.stringify({
434
+ model: this.modelName || FALLBACK_OLLAMA_MODEL,
435
+ messages: conversationHistory,
436
+ tools: ollamaTools,
437
+ tool_choice: "auto",
438
+ stream: true,
439
+ temperature: options.temperature,
440
+ max_tokens: options.maxTokens,
441
+ }),
442
+ signal: createAbortSignalWithTimeout(this.timeout),
443
+ });
444
+ if (!response.ok) {
445
+ throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
446
+ }
447
+ // Process response stream
448
+ const { content, toolCalls, finishReason } = await this.processOllamaResponse(response, controller);
449
+ // Add assistant message to history
450
+ const assistantMessage = {
451
+ role: "assistant",
452
+ content: content || "",
453
+ };
454
+ if (toolCalls && toolCalls.length > 0) {
455
+ assistantMessage.tool_calls = toolCalls;
456
+ }
457
+ conversationHistory.push(assistantMessage);
458
+ // Check finish reason
459
+ if (finishReason === "stop" || !finishReason) {
460
+ // Conversation complete
461
+ controller.close();
462
+ break;
463
+ }
464
+ else if (finishReason === "tool_calls" &&
465
+ toolCalls &&
466
+ toolCalls.length > 0) {
467
+ // Execute tools
468
+ logger.debug(`[OllamaProvider] Executing ${toolCalls.length} tools`);
469
+ const toolResults = await this.executeOllamaTools(toolCalls, options);
470
+ // Add tool results to conversation
471
+ const toolMessage = {
472
+ role: "tool",
473
+ content: JSON.stringify(toolResults),
474
+ };
475
+ conversationHistory.push(toolMessage);
476
+ iteration++;
477
+ continue; // Next iteration
478
+ }
479
+ else if (finishReason === "length") {
480
+ // Max tokens reached, continue conversation
481
+ logger.debug(`[OllamaProvider] Max tokens reached, continuing`);
482
+ conversationHistory.push({
483
+ role: "user",
484
+ content: "Please continue.",
485
+ });
486
+ iteration++;
487
+ continue;
488
+ }
489
+ else {
490
+ // Unknown finish reason, end conversation
491
+ logger.warn(`[OllamaProvider] Unknown finish reason: ${finishReason}`);
492
+ controller.close();
493
+ break;
494
+ }
495
+ }
496
+ if (iteration >= maxIterations) {
497
+ controller.error(new Error(`Ollama conversation exceeded maximum iterations (${maxIterations})`));
498
+ }
499
+ }
500
+ catch (error) {
501
+ controller.error(error);
502
+ }
503
+ },
504
+ });
505
+ // Create analytics promise
506
+ const analyticsPromise = Promise.resolve(createAnalytics(this.providerName, this.modelName || FALLBACK_OLLAMA_MODEL, { usage: { input: 0, output: 0, total: 0 } }, Date.now() - startTime, {
507
+ requestId: `ollama-stream-${Date.now()}`,
508
+ streamingMode: true,
509
+ iterations: iteration,
510
+ note: "Token usage not available from Ollama streaming responses",
511
+ }));
362
512
  return {
363
- stream: transformedStream(),
364
- provider: self.providerName,
365
- model: self.modelName,
513
+ stream: this.convertToAsyncIterable(stream),
514
+ provider: this.providerName,
515
+ model: this.modelName || FALLBACK_OLLAMA_MODEL,
516
+ analytics: analyticsPromise,
517
+ metadata: {
518
+ startTime,
519
+ streamId: `ollama-${Date.now()}`,
520
+ },
366
521
  };
367
522
  }
368
523
  /**
@@ -370,19 +525,49 @@ export class OllamaProvider extends BaseProvider {
370
525
  * Fallback for non-tool scenarios or when chat API is unavailable
371
526
  */
372
527
  async executeStreamWithoutTools(options, _analysisSchema) {
528
+ // Validate that PDFs are not provided
529
+ if (options.input?.pdfFiles && options.input.pdfFiles.length > 0) {
530
+ throw new Error("PDF inputs are not supported by OllamaProvider. " +
531
+ "Please remove PDFs or use a supported provider (OpenAI, Anthropic, Google Vertex AI, etc.).");
532
+ }
533
+ // Check for multimodal input
534
+ const hasMultimodalInput = !!(options.input?.images?.length ||
535
+ options.input?.content?.length ||
536
+ options.input?.files?.length ||
537
+ options.input?.csvFiles?.length);
538
+ let prompt = options.input.text;
539
+ let images;
540
+ if (hasMultimodalInput) {
541
+ logger.debug(`Ollama (generate API): Detected multimodal input`, {
542
+ hasImages: !!options.input?.images?.length,
543
+ imageCount: options.input?.images?.length || 0,
544
+ });
545
+ const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
546
+ const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
547
+ // Extract text from messages for prompt
548
+ prompt = multimodalMessages
549
+ .map((msg) => (typeof msg.content === "string" ? msg.content : ""))
550
+ .join("\n");
551
+ // Extract images
552
+ images = this.extractImagesFromMessages(multimodalMessages);
553
+ }
554
+ const requestBody = {
555
+ model: this.modelName || FALLBACK_OLLAMA_MODEL,
556
+ prompt,
557
+ system: options.systemPrompt,
558
+ stream: true,
559
+ options: {
560
+ temperature: options.temperature,
561
+ num_predict: options.maxTokens,
562
+ },
563
+ };
564
+ if (images && images.length > 0) {
565
+ requestBody.images = images;
566
+ }
373
567
  const response = await proxyFetch(`${this.baseUrl}/api/generate`, {
374
568
  method: "POST",
375
569
  headers: { "Content-Type": "application/json" },
376
- body: JSON.stringify({
377
- model: this.modelName || FALLBACK_OLLAMA_MODEL,
378
- prompt: options.input.text,
379
- system: options.systemPrompt,
380
- stream: true,
381
- options: {
382
- temperature: options.temperature,
383
- num_predict: options.maxTokens,
384
- },
385
- }),
570
+ body: JSON.stringify(requestBody),
386
571
  signal: createAbortSignalWithTimeout(this.timeout),
387
572
  });
388
573
  if (!response.ok) {
@@ -424,6 +609,101 @@ export class OllamaProvider extends BaseProvider {
424
609
  },
425
610
  }));
426
611
  }
612
+ /**
613
+ * Parse tool calls from Ollama API response
614
+ */
615
+ parseToolCalls(rawToolCalls) {
616
+ if (!Array.isArray(rawToolCalls)) {
617
+ return [];
618
+ }
619
+ return rawToolCalls
620
+ .map((call) => {
621
+ const callObj = call;
622
+ if (!callObj.function?.name) {
623
+ return null;
624
+ }
625
+ return {
626
+ id: callObj.id ||
627
+ `tool_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
628
+ type: "function",
629
+ function: {
630
+ name: callObj.function.name,
631
+ arguments: callObj.function.arguments || "{}",
632
+ },
633
+ };
634
+ })
635
+ .filter((call) => call !== null);
636
+ }
637
+ /**
638
+ * Process Ollama streaming response and stream content to controller
639
+ * Returns aggregated content, tool calls, and finish reason
640
+ */
641
+ async processOllamaResponse(response, controller) {
642
+ const reader = response.body?.getReader();
643
+ if (!reader) {
644
+ throw new Error("No response body from Ollama");
645
+ }
646
+ const decoder = new TextDecoder();
647
+ let buffer = "";
648
+ let aggregatedContent = "";
649
+ let aggregatedToolCalls = [];
650
+ let finalFinishReason;
651
+ try {
652
+ while (true) {
653
+ const { done, value } = await reader.read();
654
+ if (done) {
655
+ break;
656
+ }
657
+ buffer += decoder.decode(value, { stream: true });
658
+ const lines = buffer.split("\n");
659
+ buffer = lines.pop() || "";
660
+ for (const line of lines) {
661
+ if (line.trim() && line.startsWith("data: ")) {
662
+ const dataLine = line.slice(6); // Remove "data: " prefix
663
+ if (dataLine === "[DONE]") {
664
+ break;
665
+ }
666
+ try {
667
+ const parsed = JSON.parse(dataLine);
668
+ const processed = this.processOllamaStreamData(parsed);
669
+ if (!processed) {
670
+ continue;
671
+ }
672
+ // Stream content to controller
673
+ if (processed.content) {
674
+ aggregatedContent += processed.content;
675
+ controller.enqueue({
676
+ content: processed.content,
677
+ });
678
+ }
679
+ // Collect tool calls
680
+ if (processed.toolCalls && processed.toolCalls.length > 0) {
681
+ aggregatedToolCalls = [
682
+ ...aggregatedToolCalls,
683
+ ...processed.toolCalls,
684
+ ];
685
+ }
686
+ // Update finish reason
687
+ if (processed.finishReason) {
688
+ finalFinishReason = processed.finishReason;
689
+ }
690
+ }
691
+ catch (parseError) {
692
+ logger.warn(`[OllamaProvider] Failed to parse stream chunk: ${dataLine}`, { error: parseError });
693
+ }
694
+ }
695
+ }
696
+ }
697
+ }
698
+ finally {
699
+ reader.releaseLock();
700
+ }
701
+ return {
702
+ content: aggregatedContent || undefined,
703
+ toolCalls: aggregatedToolCalls.length > 0 ? aggregatedToolCalls : undefined,
704
+ finishReason: finalFinishReason,
705
+ };
706
+ }
427
707
  /**
428
708
  * Process individual stream data chunk from Ollama
429
709
  */
@@ -431,20 +711,33 @@ export class OllamaProvider extends BaseProvider {
431
711
  const dataRecord = data;
432
712
  const choices = dataRecord.choices;
433
713
  const delta = choices?.[0]?.delta;
714
+ const finishReason = choices?.[0]?.finish_reason;
434
715
  let content = "";
435
716
  if (delta?.content && typeof delta.content === "string") {
436
717
  content += delta.content;
437
718
  }
719
+ // Return tool calls for execution instead of formatting as text
438
720
  if (delta?.tool_calls) {
439
- // Handle tool calls - for now, we'll include them as content
440
- // Future enhancement: Execute tools and return results
441
- const toolCallDescription = this.formatToolCallForDisplay(delta.tool_calls);
442
- if (toolCallDescription) {
443
- content += toolCallDescription;
444
- }
721
+ const toolCalls = this.parseToolCalls(delta.tool_calls);
722
+ return {
723
+ toolCalls,
724
+ finishReason,
725
+ shouldReturn: !!finishReason,
726
+ };
727
+ }
728
+ // Also check for tool calls in the message field (some responses include it there)
729
+ if (choices?.[0]?.message?.tool_calls) {
730
+ const toolCalls = this.parseToolCalls(choices[0].message.tool_calls);
731
+ return {
732
+ toolCalls,
733
+ finishReason,
734
+ shouldReturn: !!finishReason,
735
+ };
445
736
  }
446
- const shouldReturn = !!choices?.[0]?.finish_reason;
447
- return content ? { content, shouldReturn } : { shouldReturn };
737
+ const shouldReturn = !!finishReason;
738
+ return content
739
+ ? { content, finishReason, shouldReturn }
740
+ : { finishReason, shouldReturn };
448
741
  }
449
742
  /**
450
743
  * Create stream generator for Ollama chat API with tool call support
@@ -522,6 +815,198 @@ export class OllamaProvider extends BaseProvider {
522
815
  });
523
816
  return descriptions.join("");
524
817
  }
818
+ /**
819
+ * Convert AI SDK tools to ToolDefinition format
820
+ */
821
+ convertAISDKToolsToToolDefinitions(aiTools) {
822
+ const result = {};
823
+ for (const [name, tool] of Object.entries(aiTools)) {
824
+ if ("description" in tool && tool.description) {
825
+ result[name] = {
826
+ description: tool.description,
827
+ parameters: "parameters" in tool ? tool.parameters : undefined,
828
+ execute: async (params) => {
829
+ if ("execute" in tool && tool.execute) {
830
+ const result = await tool.execute(params, {
831
+ toolCallId: `tool_${Date.now()}`,
832
+ messages: [],
833
+ });
834
+ return {
835
+ success: true,
836
+ data: result,
837
+ };
838
+ }
839
+ throw new Error(`Tool ${name} has no execute method`);
840
+ },
841
+ };
842
+ }
843
+ }
844
+ return result;
845
+ }
846
+ /**
847
+ * Execute a single tool and return the result
848
+ */
849
+ async executeSingleTool(toolName, args, _toolCallId) {
850
+ logger.debug(`[OllamaProvider] Executing single tool: ${toolName}`, {
851
+ args,
852
+ });
853
+ try {
854
+ // Use BaseProvider's tool execution mechanism
855
+ const aiTools = await this.getAllTools();
856
+ const tools = this.convertAISDKToolsToToolDefinitions(aiTools);
857
+ if (!tools[toolName]) {
858
+ throw new Error(`Tool not found: ${toolName}`);
859
+ }
860
+ const tool = tools[toolName];
861
+ if (!tool || !tool.execute) {
862
+ throw new Error(`Tool ${toolName} does not have execute method`);
863
+ }
864
+ const toolInput = args || {};
865
+ // Convert Record<string, unknown> to ToolArgs by filtering out non-JsonValue types
866
+ const toolArgs = {};
867
+ for (const [key, value] of Object.entries(toolInput)) {
868
+ // Only include values that are JsonValue compatible
869
+ if (value === null ||
870
+ typeof value === "string" ||
871
+ typeof value === "number" ||
872
+ typeof value === "boolean" ||
873
+ (typeof value === "object" && value !== null)) {
874
+ toolArgs[key] = value;
875
+ }
876
+ }
877
+ const result = await tool.execute(toolArgs);
878
+ logger.debug(`[OllamaProvider] Tool execution result:`, {
879
+ toolName,
880
+ result,
881
+ });
882
+ // Handle ToolResult type
883
+ if (result && typeof result === "object" && "success" in result) {
884
+ if (result.success && result.data !== undefined) {
885
+ if (typeof result.data === "string") {
886
+ return result.data;
887
+ }
888
+ else if (typeof result.data === "object") {
889
+ return JSON.stringify(result.data, null, 2);
890
+ }
891
+ else {
892
+ return String(result.data);
893
+ }
894
+ }
895
+ else if (result.error) {
896
+ throw new Error(result.error.message || "Tool execution failed");
897
+ }
898
+ }
899
+ // Fallback for non-ToolResult return types
900
+ if (typeof result === "string") {
901
+ return result;
902
+ }
903
+ else if (typeof result === "object") {
904
+ return JSON.stringify(result, null, 2);
905
+ }
906
+ else {
907
+ return String(result);
908
+ }
909
+ }
910
+ catch (error) {
911
+ logger.error(`[OllamaProvider] Tool execution error:`, {
912
+ toolName,
913
+ error,
914
+ });
915
+ throw error;
916
+ }
917
+ }
918
+ /**
919
+ * Execute tools and format results for Ollama API
920
+ * Similar to Bedrock's executeStreamTools but for Ollama format
921
+ */
922
+ async executeOllamaTools(toolCalls, options) {
923
+ const toolResults = [];
924
+ const toolCallsForStorage = [];
925
+ const toolResultsForStorage = [];
926
+ logger.debug(`[OllamaProvider] Executing ${toolCalls.length} tool calls`);
927
+ for (const call of toolCalls) {
928
+ logger.debug(`[OllamaProvider] Executing tool: ${call.function.name}`);
929
+ // Parse arguments
930
+ let args = {};
931
+ try {
932
+ args = JSON.parse(call.function.arguments);
933
+ }
934
+ catch (error) {
935
+ logger.error(`[OllamaProvider] Failed to parse tool arguments: ${call.function.arguments}`, { error });
936
+ args = {};
937
+ }
938
+ // Track tool call for storage
939
+ toolCallsForStorage.push({
940
+ type: "tool-call",
941
+ toolCallId: call.id,
942
+ toolName: call.function.name,
943
+ args,
944
+ });
945
+ try {
946
+ // Execute tool using existing tool framework
947
+ const result = await this.executeSingleTool(call.function.name, args, call.id);
948
+ logger.debug(`[OllamaProvider] Tool execution successful: ${call.function.name}`);
949
+ // Track result for storage
950
+ toolResultsForStorage.push({
951
+ type: "tool-result",
952
+ toolCallId: call.id,
953
+ toolName: call.function.name,
954
+ result,
955
+ });
956
+ // Format for Ollama API
957
+ toolResults.push({
958
+ tool_call_id: call.id,
959
+ content: JSON.stringify(result),
960
+ });
961
+ }
962
+ catch (error) {
963
+ logger.error(`[OllamaProvider] Tool execution failed: ${call.function.name}`, { error });
964
+ const errorMessage = error instanceof Error ? error.message : String(error);
965
+ // Track failed result
966
+ toolResultsForStorage.push({
967
+ type: "tool-result",
968
+ toolCallId: call.id,
969
+ toolName: call.function.name,
970
+ result: { error: errorMessage },
971
+ });
972
+ // Format error for Ollama API
973
+ toolResults.push({
974
+ tool_call_id: call.id,
975
+ content: JSON.stringify({ error: errorMessage }),
976
+ });
977
+ }
978
+ }
979
+ // Store tool executions (similar to Bedrock)
980
+ this.handleToolExecutionStorage(toolCallsForStorage, toolResultsForStorage, options, new Date()).catch((error) => {
981
+ logger.warn("[OllamaProvider] Failed to store tool executions", {
982
+ provider: this.providerName,
983
+ error: error instanceof Error ? error.message : String(error),
984
+ });
985
+ });
986
+ return toolResults;
987
+ }
988
+ /**
989
+ * Convert ReadableStream to AsyncIterable for compatibility with StreamResult interface
990
+ */
991
+ convertToAsyncIterable(stream) {
992
+ return {
993
+ async *[Symbol.asyncIterator]() {
994
+ const reader = stream.getReader();
995
+ try {
996
+ while (true) {
997
+ const { done, value } = await reader.read();
998
+ if (done) {
999
+ break;
1000
+ }
1001
+ yield value;
1002
+ }
1003
+ }
1004
+ finally {
1005
+ reader.releaseLock();
1006
+ }
1007
+ },
1008
+ };
1009
+ }
525
1010
  /**
526
1011
  * Create stream generator for Ollama generate API (non-tool mode)
527
1012
  */