@juspay/neurolink 9.14.0 → 9.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +15 -15
- package/dist/adapters/video/videoAnalyzer.d.ts +1 -1
- package/dist/adapters/video/videoAnalyzer.js +10 -8
- package/dist/auth/anthropicOAuth.d.ts +377 -0
- package/dist/auth/anthropicOAuth.js +914 -0
- package/dist/auth/index.d.ts +20 -0
- package/dist/auth/index.js +29 -0
- package/dist/auth/tokenStore.d.ts +225 -0
- package/dist/auth/tokenStore.js +521 -0
- package/dist/cli/commands/auth.d.ts +50 -0
- package/dist/cli/commands/auth.js +1115 -0
- package/dist/cli/commands/setup-anthropic.js +1 -14
- package/dist/cli/commands/setup-azure.js +1 -12
- package/dist/cli/commands/setup-bedrock.js +1 -9
- package/dist/cli/commands/setup-google-ai.js +1 -12
- package/dist/cli/commands/setup-openai.js +1 -14
- package/dist/cli/commands/workflow.d.ts +27 -0
- package/dist/cli/commands/workflow.js +216 -0
- package/dist/cli/factories/authCommandFactory.d.ts +52 -0
- package/dist/cli/factories/authCommandFactory.js +146 -0
- package/dist/cli/factories/commandFactory.d.ts +6 -0
- package/dist/cli/factories/commandFactory.js +171 -22
- package/dist/cli/index.js +0 -1
- package/dist/cli/parser.js +14 -2
- package/dist/cli/utils/maskCredential.d.ts +11 -0
- package/dist/cli/utils/maskCredential.js +23 -0
- package/dist/constants/contextWindows.js +107 -16
- package/dist/constants/enums.d.ts +119 -15
- package/dist/constants/enums.js +182 -22
- package/dist/constants/index.d.ts +3 -1
- package/dist/constants/index.js +11 -1
- package/dist/context/budgetChecker.js +1 -1
- package/dist/context/contextCompactor.js +31 -4
- package/dist/context/emergencyTruncation.d.ts +21 -0
- package/dist/context/emergencyTruncation.js +88 -0
- package/dist/context/errorDetection.d.ts +16 -0
- package/dist/context/errorDetection.js +48 -1
- package/dist/context/errors.d.ts +19 -0
- package/dist/context/errors.js +21 -0
- package/dist/context/stages/slidingWindowTruncator.d.ts +6 -0
- package/dist/context/stages/slidingWindowTruncator.js +159 -24
- package/dist/core/baseProvider.js +306 -200
- package/dist/core/conversationMemoryManager.js +104 -61
- package/dist/core/evaluationProviders.js +16 -33
- package/dist/core/factory.js +237 -164
- package/dist/core/modules/GenerationHandler.js +175 -116
- package/dist/core/modules/MessageBuilder.js +222 -170
- package/dist/core/modules/StreamHandler.d.ts +1 -0
- package/dist/core/modules/StreamHandler.js +95 -27
- package/dist/core/modules/TelemetryHandler.d.ts +10 -1
- package/dist/core/modules/TelemetryHandler.js +25 -7
- package/dist/core/modules/ToolsManager.js +115 -191
- package/dist/core/redisConversationMemoryManager.js +418 -282
- package/dist/factories/providerRegistry.d.ts +5 -0
- package/dist/factories/providerRegistry.js +20 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -2
- package/dist/lib/adapters/video/videoAnalyzer.d.ts +1 -1
- package/dist/lib/adapters/video/videoAnalyzer.js +10 -8
- package/dist/lib/auth/anthropicOAuth.d.ts +377 -0
- package/dist/lib/auth/anthropicOAuth.js +915 -0
- package/dist/lib/auth/index.d.ts +20 -0
- package/dist/lib/auth/index.js +30 -0
- package/dist/lib/auth/tokenStore.d.ts +225 -0
- package/dist/lib/auth/tokenStore.js +522 -0
- package/dist/lib/constants/contextWindows.js +107 -16
- package/dist/lib/constants/enums.d.ts +119 -15
- package/dist/lib/constants/enums.js +182 -22
- package/dist/lib/constants/index.d.ts +3 -1
- package/dist/lib/constants/index.js +11 -1
- package/dist/lib/context/budgetChecker.js +1 -1
- package/dist/lib/context/contextCompactor.js +31 -4
- package/dist/lib/context/emergencyTruncation.d.ts +21 -0
- package/dist/lib/context/emergencyTruncation.js +89 -0
- package/dist/lib/context/errorDetection.d.ts +16 -0
- package/dist/lib/context/errorDetection.js +48 -1
- package/dist/lib/context/errors.d.ts +19 -0
- package/dist/lib/context/errors.js +22 -0
- package/dist/lib/context/stages/slidingWindowTruncator.d.ts +6 -0
- package/dist/lib/context/stages/slidingWindowTruncator.js +159 -24
- package/dist/lib/core/baseProvider.js +306 -200
- package/dist/lib/core/conversationMemoryManager.js +104 -61
- package/dist/lib/core/evaluationProviders.js +16 -33
- package/dist/lib/core/factory.js +237 -164
- package/dist/lib/core/modules/GenerationHandler.js +175 -116
- package/dist/lib/core/modules/MessageBuilder.js +222 -170
- package/dist/lib/core/modules/StreamHandler.d.ts +1 -0
- package/dist/lib/core/modules/StreamHandler.js +95 -27
- package/dist/lib/core/modules/TelemetryHandler.d.ts +10 -1
- package/dist/lib/core/modules/TelemetryHandler.js +25 -7
- package/dist/lib/core/modules/ToolsManager.js +115 -191
- package/dist/lib/core/redisConversationMemoryManager.js +418 -282
- package/dist/lib/factories/providerRegistry.d.ts +5 -0
- package/dist/lib/factories/providerRegistry.js +20 -2
- package/dist/lib/index.d.ts +3 -3
- package/dist/lib/index.js +4 -2
- package/dist/lib/mcp/externalServerManager.js +66 -0
- package/dist/lib/mcp/mcpCircuitBreaker.js +24 -0
- package/dist/lib/mcp/mcpClientFactory.js +16 -0
- package/dist/lib/mcp/toolDiscoveryService.js +32 -6
- package/dist/lib/mcp/toolRegistry.js +193 -123
- package/dist/lib/models/anthropicModels.d.ts +267 -0
- package/dist/lib/models/anthropicModels.js +528 -0
- package/dist/lib/neurolink.d.ts +6 -0
- package/dist/lib/neurolink.js +1162 -646
- package/dist/lib/providers/amazonBedrock.d.ts +1 -1
- package/dist/lib/providers/amazonBedrock.js +521 -319
- package/dist/lib/providers/anthropic.d.ts +123 -2
- package/dist/lib/providers/anthropic.js +873 -27
- package/dist/lib/providers/anthropicBaseProvider.js +77 -17
- package/dist/lib/providers/googleAiStudio.d.ts +1 -1
- package/dist/lib/providers/googleAiStudio.js +292 -227
- package/dist/lib/providers/googleVertex.d.ts +36 -1
- package/dist/lib/providers/googleVertex.js +553 -260
- package/dist/lib/providers/ollama.js +329 -278
- package/dist/lib/providers/openAI.js +77 -19
- package/dist/lib/providers/sagemaker/parsers.js +3 -3
- package/dist/lib/providers/sagemaker/streaming.js +3 -3
- package/dist/lib/proxy/proxyFetch.js +81 -48
- package/dist/lib/rag/ChunkerFactory.js +1 -1
- package/dist/lib/rag/chunkers/MarkdownChunker.d.ts +22 -0
- package/dist/lib/rag/chunkers/MarkdownChunker.js +213 -9
- package/dist/lib/rag/chunking/markdownChunker.d.ts +16 -0
- package/dist/lib/rag/chunking/markdownChunker.js +174 -2
- package/dist/lib/rag/pipeline/contextAssembly.js +2 -1
- package/dist/lib/rag/ragIntegration.d.ts +18 -1
- package/dist/lib/rag/ragIntegration.js +94 -14
- package/dist/lib/rag/retrieval/vectorQueryTool.js +21 -4
- package/dist/lib/server/abstract/baseServerAdapter.js +4 -1
- package/dist/lib/server/adapters/fastifyAdapter.js +35 -30
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +32 -0
- package/dist/lib/services/server/ai/observability/instrumentation.js +39 -0
- package/dist/lib/telemetry/attributes.d.ts +52 -0
- package/dist/lib/telemetry/attributes.js +61 -0
- package/dist/lib/telemetry/index.d.ts +3 -0
- package/dist/lib/telemetry/index.js +3 -0
- package/dist/lib/telemetry/telemetryService.d.ts +6 -0
- package/dist/lib/telemetry/telemetryService.js +6 -0
- package/dist/lib/telemetry/tracers.d.ts +15 -0
- package/dist/lib/telemetry/tracers.js +17 -0
- package/dist/lib/telemetry/withSpan.d.ts +9 -0
- package/dist/lib/telemetry/withSpan.js +35 -0
- package/dist/lib/types/contextTypes.d.ts +10 -0
- package/dist/lib/types/errors.d.ts +62 -0
- package/dist/lib/types/errors.js +107 -0
- package/dist/lib/types/index.d.ts +2 -1
- package/dist/lib/types/index.js +2 -0
- package/dist/lib/types/providers.d.ts +107 -0
- package/dist/lib/types/providers.js +69 -0
- package/dist/lib/types/streamTypes.d.ts +14 -0
- package/dist/lib/types/subscriptionTypes.d.ts +893 -0
- package/dist/lib/types/subscriptionTypes.js +8 -0
- package/dist/lib/utils/conversationMemory.js +121 -82
- package/dist/lib/utils/logger.d.ts +5 -0
- package/dist/lib/utils/logger.js +50 -2
- package/dist/lib/utils/messageBuilder.js +22 -42
- package/dist/lib/utils/modelDetection.js +3 -3
- package/dist/lib/utils/providerConfig.d.ts +167 -0
- package/dist/lib/utils/providerConfig.js +619 -9
- package/dist/lib/utils/providerRetry.d.ts +41 -0
- package/dist/lib/utils/providerRetry.js +114 -0
- package/dist/lib/utils/retryability.d.ts +14 -0
- package/dist/lib/utils/retryability.js +23 -0
- package/dist/lib/utils/sanitizers/svg.js +4 -5
- package/dist/lib/utils/tokenEstimation.d.ts +11 -1
- package/dist/lib/utils/tokenEstimation.js +19 -4
- package/dist/lib/utils/videoAnalysisProcessor.js +7 -3
- package/dist/mcp/externalServerManager.js +66 -0
- package/dist/mcp/mcpCircuitBreaker.js +24 -0
- package/dist/mcp/mcpClientFactory.js +16 -0
- package/dist/mcp/toolDiscoveryService.js +32 -6
- package/dist/mcp/toolRegistry.js +193 -123
- package/dist/models/anthropicModels.d.ts +267 -0
- package/dist/models/anthropicModels.js +527 -0
- package/dist/neurolink.d.ts +6 -0
- package/dist/neurolink.js +1162 -646
- package/dist/providers/amazonBedrock.d.ts +1 -1
- package/dist/providers/amazonBedrock.js +521 -319
- package/dist/providers/anthropic.d.ts +123 -2
- package/dist/providers/anthropic.js +873 -27
- package/dist/providers/anthropicBaseProvider.js +77 -17
- package/dist/providers/googleAiStudio.d.ts +1 -1
- package/dist/providers/googleAiStudio.js +292 -227
- package/dist/providers/googleVertex.d.ts +36 -1
- package/dist/providers/googleVertex.js +553 -260
- package/dist/providers/ollama.js +329 -278
- package/dist/providers/openAI.js +77 -19
- package/dist/providers/sagemaker/parsers.js +3 -3
- package/dist/providers/sagemaker/streaming.js +3 -3
- package/dist/proxy/proxyFetch.js +81 -48
- package/dist/rag/ChunkerFactory.js +1 -1
- package/dist/rag/chunkers/MarkdownChunker.d.ts +22 -0
- package/dist/rag/chunkers/MarkdownChunker.js +213 -9
- package/dist/rag/chunking/markdownChunker.d.ts +16 -0
- package/dist/rag/chunking/markdownChunker.js +174 -2
- package/dist/rag/pipeline/contextAssembly.js +2 -1
- package/dist/rag/ragIntegration.d.ts +18 -1
- package/dist/rag/ragIntegration.js +94 -14
- package/dist/rag/retrieval/vectorQueryTool.js +21 -4
- package/dist/server/abstract/baseServerAdapter.js +4 -1
- package/dist/server/adapters/fastifyAdapter.js +35 -30
- package/dist/services/server/ai/observability/instrumentation.d.ts +32 -0
- package/dist/services/server/ai/observability/instrumentation.js +39 -0
- package/dist/telemetry/attributes.d.ts +52 -0
- package/dist/telemetry/attributes.js +60 -0
- package/dist/telemetry/index.d.ts +3 -0
- package/dist/telemetry/index.js +3 -0
- package/dist/telemetry/telemetryService.d.ts +6 -0
- package/dist/telemetry/telemetryService.js +6 -0
- package/dist/telemetry/tracers.d.ts +15 -0
- package/dist/telemetry/tracers.js +16 -0
- package/dist/telemetry/withSpan.d.ts +9 -0
- package/dist/telemetry/withSpan.js +34 -0
- package/dist/types/contextTypes.d.ts +10 -0
- package/dist/types/errors.d.ts +62 -0
- package/dist/types/errors.js +107 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +2 -0
- package/dist/types/providers.d.ts +107 -0
- package/dist/types/providers.js +69 -0
- package/dist/types/streamTypes.d.ts +14 -0
- package/dist/types/subscriptionTypes.d.ts +893 -0
- package/dist/types/subscriptionTypes.js +7 -0
- package/dist/utils/conversationMemory.js +121 -82
- package/dist/utils/logger.d.ts +5 -0
- package/dist/utils/logger.js +50 -2
- package/dist/utils/messageBuilder.js +22 -42
- package/dist/utils/modelDetection.js +3 -3
- package/dist/utils/providerConfig.d.ts +167 -0
- package/dist/utils/providerConfig.js +619 -9
- package/dist/utils/providerRetry.d.ts +41 -0
- package/dist/utils/providerRetry.js +113 -0
- package/dist/utils/retryability.d.ts +14 -0
- package/dist/utils/retryability.js +22 -0
- package/dist/utils/sanitizers/svg.js +4 -5
- package/dist/utils/tokenEstimation.d.ts +11 -1
- package/dist/utils/tokenEstimation.js +19 -4
- package/dist/utils/videoAnalysisProcessor.js +7 -3
- package/dist/workflow/config.d.ts +26 -26
- package/package.json +2 -1
|
@@ -6,7 +6,9 @@ import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
|
6
6
|
import { logger } from "../utils/logger.js";
|
|
7
7
|
import { buildMultimodalMessagesArray } from "../utils/messageBuilder.js";
|
|
8
8
|
import { buildMultimodalOptions } from "../utils/multimodalOptionsBuilder.js";
|
|
9
|
+
import { estimateTokens } from "../utils/tokenEstimation.js";
|
|
9
10
|
import { InvalidModelError, NetworkError, ProviderError, } from "../types/errors.js";
|
|
11
|
+
import { tracers, ATTR, withClientSpan } from "../telemetry/index.js";
|
|
10
12
|
import { TimeoutError } from "../utils/timeout.js";
|
|
11
13
|
// Model version constants (configurable via environment)
|
|
12
14
|
const DEFAULT_OLLAMA_MODEL = "llama3.1:8b";
|
|
@@ -55,8 +57,8 @@ class OllamaLanguageModel {
|
|
|
55
57
|
this.baseUrl = baseUrl;
|
|
56
58
|
this.timeout = timeout;
|
|
57
59
|
}
|
|
58
|
-
|
|
59
|
-
return
|
|
60
|
+
estimateTokenCount(text) {
|
|
61
|
+
return estimateTokens(text, "ollama");
|
|
60
62
|
}
|
|
61
63
|
convertMessagesToPrompt(messages) {
|
|
62
64
|
return messages
|
|
@@ -87,7 +89,9 @@ class OllamaLanguageModel {
|
|
|
87
89
|
max_tokens: options.maxTokens,
|
|
88
90
|
stream: false,
|
|
89
91
|
};
|
|
90
|
-
logger.
|
|
92
|
+
if (logger.shouldLog("debug")) {
|
|
93
|
+
logger.debug("[OllamaLanguageModel] Using OpenAI-compatible API with messages:", JSON.stringify(messages, null, 2));
|
|
94
|
+
}
|
|
91
95
|
const response = await proxyFetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
92
96
|
method: "POST",
|
|
93
97
|
headers: { "Content-Type": "application/json" },
|
|
@@ -105,8 +109,8 @@ class OllamaLanguageModel {
|
|
|
105
109
|
text,
|
|
106
110
|
usage: {
|
|
107
111
|
promptTokens: usage.prompt_tokens ??
|
|
108
|
-
this.
|
|
109
|
-
completionTokens: usage.completion_tokens ?? this.
|
|
112
|
+
this.estimateTokenCount(JSON.stringify(messages)),
|
|
113
|
+
completionTokens: usage.completion_tokens ?? this.estimateTokenCount(text),
|
|
110
114
|
totalTokens: usage.total_tokens,
|
|
111
115
|
},
|
|
112
116
|
finishReason: "stop",
|
|
@@ -150,11 +154,12 @@ class OllamaLanguageModel {
|
|
|
150
154
|
return {
|
|
151
155
|
text: data.response,
|
|
152
156
|
usage: {
|
|
153
|
-
promptTokens: data.prompt_eval_count ?? this.
|
|
154
|
-
completionTokens: data.eval_count ??
|
|
155
|
-
|
|
157
|
+
promptTokens: data.prompt_eval_count ?? this.estimateTokenCount(prompt),
|
|
158
|
+
completionTokens: data.eval_count ??
|
|
159
|
+
this.estimateTokenCount(String(data.response ?? "")),
|
|
160
|
+
totalTokens: (data.prompt_eval_count ?? this.estimateTokenCount(prompt)) +
|
|
156
161
|
(data.eval_count ??
|
|
157
|
-
this.
|
|
162
|
+
this.estimateTokenCount(String(data.response ?? ""))),
|
|
158
163
|
},
|
|
159
164
|
finishReason: "stop",
|
|
160
165
|
rawCall: {
|
|
@@ -329,7 +334,7 @@ class OllamaLanguageModel {
|
|
|
329
334
|
finishReason: "stop",
|
|
330
335
|
usage: {
|
|
331
336
|
promptTokens: data.prompt_eval_count ||
|
|
332
|
-
this.
|
|
337
|
+
this.estimateTokenCount(data.context || ""),
|
|
333
338
|
completionTokens: data.eval_count || 0,
|
|
334
339
|
},
|
|
335
340
|
};
|
|
@@ -357,8 +362,11 @@ class OllamaLanguageModel {
|
|
|
357
362
|
const decoder = new TextDecoder();
|
|
358
363
|
let buffer = "";
|
|
359
364
|
// Estimate prompt tokens from messages (matches non-streaming behavior)
|
|
360
|
-
const totalPromptTokens = this.
|
|
361
|
-
|
|
365
|
+
const totalPromptTokens = this.estimateTokenCount(JSON.stringify(messages));
|
|
366
|
+
// Accumulate full completion text; estimate tokens once at the end to avoid
|
|
367
|
+
// per-chunk rounding inflation that occurs when estimateTokenCount is called
|
|
368
|
+
// on every delta and the results are summed.
|
|
369
|
+
let completionText = "";
|
|
362
370
|
try {
|
|
363
371
|
while (true) {
|
|
364
372
|
const { done, value } = await reader.read();
|
|
@@ -384,14 +392,16 @@ class OllamaLanguageModel {
|
|
|
384
392
|
type: "text-delta",
|
|
385
393
|
textDelta: content,
|
|
386
394
|
};
|
|
387
|
-
|
|
395
|
+
completionText += content;
|
|
388
396
|
}
|
|
389
397
|
// Check for finish
|
|
390
398
|
const finishReason = data.choices?.[0]?.finish_reason;
|
|
391
399
|
if (finishReason === "stop") {
|
|
392
|
-
//
|
|
400
|
+
// Prefer server-reported usage; fall back to a single estimate over
|
|
401
|
+
// the full accumulated text (avoids per-chunk rounding inflation).
|
|
393
402
|
const promptTokens = data.usage?.prompt_tokens || totalPromptTokens;
|
|
394
|
-
const completionTokens = data.usage?.completion_tokens ||
|
|
403
|
+
const completionTokens = data.usage?.completion_tokens ||
|
|
404
|
+
this.estimateTokenCount(completionText);
|
|
395
405
|
yield {
|
|
396
406
|
type: "finish",
|
|
397
407
|
finishReason: "stop",
|
|
@@ -418,7 +428,7 @@ class OllamaLanguageModel {
|
|
|
418
428
|
finishReason: "stop",
|
|
419
429
|
usage: {
|
|
420
430
|
promptTokens: totalPromptTokens,
|
|
421
|
-
completionTokens:
|
|
431
|
+
completionTokens: this.estimateTokenCount(completionText),
|
|
422
432
|
},
|
|
423
433
|
};
|
|
424
434
|
}
|
|
@@ -604,289 +614,330 @@ export class OllamaProvider extends BaseProvider {
|
|
|
604
614
|
* Uses conversation loop to handle multi-step tool execution
|
|
605
615
|
*/
|
|
606
616
|
async executeStreamWithTools(options, _analysisSchema) {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
617
|
+
return withClientSpan({
|
|
618
|
+
name: "neurolink.provider.stream",
|
|
619
|
+
tracer: tracers.provider,
|
|
620
|
+
attributes: {
|
|
621
|
+
[ATTR.GEN_AI_SYSTEM]: "ollama",
|
|
622
|
+
[ATTR.GEN_AI_MODEL]: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
623
|
+
[ATTR.GEN_AI_OPERATION]: "stream",
|
|
624
|
+
[ATTR.NL_HAS_TOOLS]: true,
|
|
625
|
+
[ATTR.NL_STREAM_MODE]: true,
|
|
626
|
+
},
|
|
627
|
+
}, async (span) => {
|
|
628
|
+
const startTime = Date.now();
|
|
629
|
+
const maxIterations = options.maxSteps || DEFAULT_MAX_STEPS;
|
|
630
|
+
let iteration = 0;
|
|
631
|
+
// Get all available tools (direct + MCP + external)
|
|
632
|
+
// BaseProvider.stream() pre-merges base tools + external tools into options.tools
|
|
633
|
+
const allTools = options.tools ||
|
|
634
|
+
(await this.getAllTools());
|
|
635
|
+
// Convert tools to Ollama format
|
|
636
|
+
const ollamaTools = this.convertToolsToOllamaFormat(allTools);
|
|
637
|
+
span.setAttribute(ATTR.NL_TOOL_COUNT, ollamaTools.length);
|
|
638
|
+
// Validate that PDFs are not provided
|
|
639
|
+
if (options.input?.pdfFiles && options.input.pdfFiles.length > 0) {
|
|
640
|
+
throw this.handleProviderError(new Error("PDF inputs are not supported by OllamaProvider. " +
|
|
641
|
+
"Please remove PDFs or use a supported provider (OpenAI, Anthropic, Google Vertex AI, etc.)."));
|
|
642
|
+
}
|
|
643
|
+
// Initialize conversation history
|
|
644
|
+
const conversationHistory = [];
|
|
645
|
+
// Build initial messages
|
|
646
|
+
const hasMultimodalInput = !!(options.input?.images?.length ||
|
|
647
|
+
options.input?.content?.length ||
|
|
648
|
+
options.input?.files?.length ||
|
|
649
|
+
options.input?.csvFiles?.length);
|
|
650
|
+
if (hasMultimodalInput) {
|
|
651
|
+
logger.debug(`Ollama: Detected multimodal input, using multimodal message builder`, {
|
|
652
|
+
hasImages: !!options.input?.images?.length,
|
|
653
|
+
imageCount: options.input?.images?.length || 0,
|
|
654
|
+
});
|
|
655
|
+
const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
|
|
656
|
+
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
657
|
+
conversationHistory.push(...this.convertToOllamaMessages(multimodalMessages));
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
if (options.systemPrompt) {
|
|
661
|
+
conversationHistory.push({
|
|
662
|
+
role: "system",
|
|
663
|
+
content: options.systemPrompt,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
639
666
|
conversationHistory.push({
|
|
640
|
-
role: "
|
|
641
|
-
content: options.
|
|
667
|
+
role: "user",
|
|
668
|
+
content: options.input.text,
|
|
642
669
|
});
|
|
643
670
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
stream: true,
|
|
665
|
-
temperature: options.temperature,
|
|
666
|
-
max_tokens: options.maxTokens,
|
|
667
|
-
}),
|
|
668
|
-
signal: createAbortSignalWithTimeout(this.timeout),
|
|
669
|
-
});
|
|
670
|
-
if (!response.ok) {
|
|
671
|
-
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
|
|
672
|
-
}
|
|
673
|
-
// Process response stream
|
|
674
|
-
const { content, toolCalls, finishReason } = await this.processOllamaResponse(response, controller);
|
|
675
|
-
// Add assistant message to history
|
|
676
|
-
const assistantMessage = {
|
|
677
|
-
role: "assistant",
|
|
678
|
-
content: content || "",
|
|
679
|
-
};
|
|
680
|
-
if (toolCalls && toolCalls.length > 0) {
|
|
681
|
-
assistantMessage.tool_calls = toolCalls;
|
|
682
|
-
}
|
|
683
|
-
conversationHistory.push(assistantMessage);
|
|
684
|
-
// Check finish reason
|
|
685
|
-
if (finishReason === "stop" || !finishReason) {
|
|
686
|
-
// Conversation complete
|
|
687
|
-
controller.close();
|
|
688
|
-
break;
|
|
689
|
-
}
|
|
690
|
-
else if (finishReason === "tool_calls" &&
|
|
691
|
-
toolCalls &&
|
|
692
|
-
toolCalls.length > 0) {
|
|
693
|
-
// Execute tools
|
|
694
|
-
logger.debug(`[OllamaProvider] Executing ${toolCalls.length} tools`);
|
|
695
|
-
const toolResults = await this.executeOllamaTools(toolCalls, options);
|
|
696
|
-
// Add tool results to conversation
|
|
697
|
-
const toolMessage = {
|
|
698
|
-
role: "tool",
|
|
699
|
-
content: JSON.stringify(toolResults),
|
|
700
|
-
};
|
|
701
|
-
conversationHistory.push(toolMessage);
|
|
702
|
-
iteration++;
|
|
703
|
-
}
|
|
704
|
-
else if (finishReason === "length") {
|
|
705
|
-
// Max tokens reached, continue conversation
|
|
706
|
-
logger.debug(`[OllamaProvider] Max tokens reached, continuing`);
|
|
707
|
-
conversationHistory.push({
|
|
708
|
-
role: "user",
|
|
709
|
-
content: "Please continue.",
|
|
671
|
+
// Conversation loop for multi-step tool execution
|
|
672
|
+
const stream = new ReadableStream({
|
|
673
|
+
start: async (controller) => {
|
|
674
|
+
try {
|
|
675
|
+
while (iteration < maxIterations) {
|
|
676
|
+
logger.debug(`[OllamaProvider] Conversation iteration ${iteration + 1}/${maxIterations}`);
|
|
677
|
+
// Make API request
|
|
678
|
+
const response = await proxyFetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
679
|
+
method: "POST",
|
|
680
|
+
headers: { "Content-Type": "application/json" },
|
|
681
|
+
body: JSON.stringify({
|
|
682
|
+
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
683
|
+
messages: conversationHistory,
|
|
684
|
+
tools: ollamaTools,
|
|
685
|
+
tool_choice: "auto",
|
|
686
|
+
stream: true,
|
|
687
|
+
temperature: options.temperature,
|
|
688
|
+
max_tokens: options.maxTokens,
|
|
689
|
+
}),
|
|
690
|
+
signal: createAbortSignalWithTimeout(this.timeout),
|
|
710
691
|
});
|
|
711
|
-
|
|
692
|
+
if (!response.ok) {
|
|
693
|
+
throw this.handleProviderError(new Error(`Ollama API error: ${response.status} ${response.statusText}`));
|
|
694
|
+
}
|
|
695
|
+
// Process response stream
|
|
696
|
+
const { content, toolCalls, finishReason } = await this.processOllamaResponse(response, controller);
|
|
697
|
+
// Add assistant message to history
|
|
698
|
+
const assistantMessage = {
|
|
699
|
+
role: "assistant",
|
|
700
|
+
content: content || "",
|
|
701
|
+
};
|
|
702
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
703
|
+
assistantMessage.tool_calls = toolCalls;
|
|
704
|
+
}
|
|
705
|
+
conversationHistory.push(assistantMessage);
|
|
706
|
+
// Check finish reason
|
|
707
|
+
if (finishReason === "stop" || !finishReason) {
|
|
708
|
+
// Conversation complete
|
|
709
|
+
span.setAttribute(ATTR.GEN_AI_FINISH_REASON, finishReason || "stop");
|
|
710
|
+
controller.close();
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
else if (finishReason === "tool_calls" &&
|
|
714
|
+
toolCalls &&
|
|
715
|
+
toolCalls.length > 0) {
|
|
716
|
+
// Execute tools
|
|
717
|
+
logger.debug(`[OllamaProvider] Executing ${toolCalls.length} tools`);
|
|
718
|
+
for (const tc of toolCalls) {
|
|
719
|
+
span.addEvent("tool_call", {
|
|
720
|
+
[ATTR.GEN_AI_TOOL_NAME]: tc.function.name,
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
const toolResults = await this.executeOllamaTools(toolCalls, options);
|
|
724
|
+
// Add tool results to conversation
|
|
725
|
+
const toolMessage = {
|
|
726
|
+
role: "tool",
|
|
727
|
+
content: JSON.stringify(toolResults),
|
|
728
|
+
};
|
|
729
|
+
conversationHistory.push(toolMessage);
|
|
730
|
+
iteration++;
|
|
731
|
+
}
|
|
732
|
+
else if (finishReason === "length") {
|
|
733
|
+
// Max tokens reached, continue conversation
|
|
734
|
+
logger.debug(`[OllamaProvider] Max tokens reached, continuing`);
|
|
735
|
+
conversationHistory.push({
|
|
736
|
+
role: "user",
|
|
737
|
+
content: "Please continue.",
|
|
738
|
+
});
|
|
739
|
+
iteration++;
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
// Unknown finish reason, end conversation
|
|
743
|
+
logger.warn(`[OllamaProvider] Unknown finish reason: ${finishReason}`);
|
|
744
|
+
span.setAttribute(ATTR.GEN_AI_FINISH_REASON, finishReason);
|
|
745
|
+
controller.close();
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
712
748
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
logger.warn(`[OllamaProvider] Unknown finish reason: ${finishReason}`);
|
|
716
|
-
controller.close();
|
|
717
|
-
break;
|
|
749
|
+
if (iteration >= maxIterations) {
|
|
750
|
+
controller.error(new Error(`Ollama conversation exceeded maximum iterations (${maxIterations})`));
|
|
718
751
|
}
|
|
719
752
|
}
|
|
720
|
-
|
|
721
|
-
controller.error(
|
|
753
|
+
catch (error) {
|
|
754
|
+
controller.error(error);
|
|
722
755
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
756
|
+
finally {
|
|
757
|
+
// Resolve analytics with final values now that the loop has completed.
|
|
758
|
+
resolveAnalytics(createAnalytics(this.providerName, this.modelName || FALLBACK_OLLAMA_MODEL, { usage: { input: 0, output: 0, total: 0 } }, Date.now() - startTime, {
|
|
759
|
+
requestId: `ollama-stream-${Date.now()}`,
|
|
760
|
+
streamingMode: true,
|
|
761
|
+
iterations: iteration,
|
|
762
|
+
note: "Token usage not available from Ollama streaming responses",
|
|
763
|
+
}));
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
});
|
|
767
|
+
// Defer analytics resolution until the stream's start callback finishes.
|
|
768
|
+
// This ensures responseTime and iteration reflect the actual completed values
|
|
769
|
+
// rather than values captured before the tool-loop executes.
|
|
770
|
+
let resolveAnalytics;
|
|
771
|
+
const analyticsPromise = new Promise((resolve) => {
|
|
772
|
+
resolveAnalytics = resolve;
|
|
773
|
+
});
|
|
774
|
+
return {
|
|
775
|
+
stream: this.convertToAsyncIterable(stream),
|
|
776
|
+
provider: this.providerName,
|
|
777
|
+
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
778
|
+
analytics: analyticsPromise,
|
|
779
|
+
metadata: {
|
|
780
|
+
startTime,
|
|
781
|
+
streamId: `ollama-${Date.now()}`,
|
|
782
|
+
},
|
|
783
|
+
};
|
|
728
784
|
});
|
|
729
|
-
// Create analytics promise
|
|
730
|
-
const analyticsPromise = Promise.resolve(createAnalytics(this.providerName, this.modelName || FALLBACK_OLLAMA_MODEL, { usage: { input: 0, output: 0, total: 0 } }, Date.now() - startTime, {
|
|
731
|
-
requestId: `ollama-stream-${Date.now()}`,
|
|
732
|
-
streamingMode: true,
|
|
733
|
-
iterations: iteration,
|
|
734
|
-
note: "Token usage not available from Ollama streaming responses",
|
|
735
|
-
}));
|
|
736
|
-
return {
|
|
737
|
-
stream: this.convertToAsyncIterable(stream),
|
|
738
|
-
provider: this.providerName,
|
|
739
|
-
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
740
|
-
analytics: analyticsPromise,
|
|
741
|
-
metadata: {
|
|
742
|
-
startTime,
|
|
743
|
-
streamId: `ollama-${Date.now()}`,
|
|
744
|
-
},
|
|
745
|
-
};
|
|
746
785
|
}
|
|
747
786
|
/**
|
|
748
787
|
* Execute streaming without tools using the generate API
|
|
749
788
|
* Fallback for non-tool scenarios or when chat API is unavailable
|
|
750
789
|
*/
|
|
751
790
|
async executeStreamWithoutTools(options, _analysisSchema) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
if (options.systemPrompt) {
|
|
768
|
-
messages.push({ role: "system", content: options.systemPrompt });
|
|
791
|
+
return withClientSpan({
|
|
792
|
+
name: "neurolink.provider.stream",
|
|
793
|
+
tracer: tracers.provider,
|
|
794
|
+
attributes: {
|
|
795
|
+
[ATTR.GEN_AI_SYSTEM]: "ollama",
|
|
796
|
+
[ATTR.GEN_AI_MODEL]: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
797
|
+
[ATTR.GEN_AI_OPERATION]: "stream",
|
|
798
|
+
[ATTR.NL_HAS_TOOLS]: false,
|
|
799
|
+
[ATTR.NL_STREAM_MODE]: true,
|
|
800
|
+
},
|
|
801
|
+
}, async () => {
|
|
802
|
+
// Validate that PDFs are not provided
|
|
803
|
+
if (options.input?.pdfFiles && options.input.pdfFiles.length > 0) {
|
|
804
|
+
throw this.handleProviderError(new Error("PDF inputs are not supported by OllamaProvider. " +
|
|
805
|
+
"Please remove PDFs or use a supported provider (OpenAI, Anthropic, Google Vertex AI, etc.)."));
|
|
769
806
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
807
|
+
// Check for multimodal input
|
|
808
|
+
const hasMultimodalInput = !!(options.input?.images?.length ||
|
|
809
|
+
options.input?.content?.length ||
|
|
810
|
+
options.input?.files?.length ||
|
|
811
|
+
options.input?.csvFiles?.length);
|
|
812
|
+
const useOpenAIMode = isOpenAICompatibleMode();
|
|
813
|
+
if (useOpenAIMode) {
|
|
814
|
+
// OpenAI-compatible mode: Use /v1/chat/completions with messages
|
|
815
|
+
logger.debug(`Ollama (OpenAI mode): Building messages for streaming`);
|
|
816
|
+
const messages = [];
|
|
817
|
+
if (options.systemPrompt) {
|
|
818
|
+
messages.push({ role: "system", content: options.systemPrompt });
|
|
819
|
+
}
|
|
820
|
+
if (hasMultimodalInput) {
|
|
821
|
+
const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
|
|
822
|
+
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
823
|
+
// Convert multimodal messages to text (OpenAI-compatible mode doesn't support images in /v1/chat/completions for Ollama)
|
|
824
|
+
const content = multimodalMessages
|
|
825
|
+
.map((msg) => typeof msg.content === "string" ? msg.content : "")
|
|
826
|
+
.join("\n");
|
|
827
|
+
messages.push({ role: "user", content });
|
|
828
|
+
}
|
|
829
|
+
else {
|
|
830
|
+
messages.push({ role: "user", content: options.input.text });
|
|
831
|
+
}
|
|
832
|
+
const requestUrl = `${this.baseUrl}/v1/chat/completions`;
|
|
833
|
+
const requestBody = {
|
|
834
|
+
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
835
|
+
messages,
|
|
836
|
+
temperature: options.temperature,
|
|
837
|
+
max_tokens: options.maxTokens,
|
|
838
|
+
stream: true,
|
|
839
|
+
};
|
|
840
|
+
logger.debug(`[Ollama OpenAI Mode] About to fetch:`, {
|
|
841
|
+
url: requestUrl,
|
|
842
|
+
baseUrl: this.baseUrl,
|
|
843
|
+
modelName: this.modelName,
|
|
844
|
+
requestBody: JSON.stringify(requestBody),
|
|
845
|
+
});
|
|
846
|
+
const response = await proxyFetch(requestUrl, {
|
|
847
|
+
method: "POST",
|
|
848
|
+
headers: { "Content-Type": "application/json" },
|
|
849
|
+
body: JSON.stringify(requestBody),
|
|
850
|
+
signal: createAbortSignalWithTimeout(this.timeout),
|
|
851
|
+
});
|
|
852
|
+
logger.debug(`[Ollama OpenAI Mode] Response received:`, {
|
|
853
|
+
status: response.status,
|
|
854
|
+
statusText: response.statusText,
|
|
855
|
+
ok: response.ok,
|
|
856
|
+
});
|
|
857
|
+
if (!response.ok) {
|
|
858
|
+
throw this.handleProviderError(new Error(`Ollama API error: ${response.status} ${response.statusText}`));
|
|
859
|
+
}
|
|
860
|
+
// Transform to async generator for OpenAI-compatible format
|
|
861
|
+
const self = this;
|
|
862
|
+
const transformedStream = async function* () {
|
|
863
|
+
const generator = self.createOpenAIStream(response);
|
|
864
|
+
for await (const chunk of generator) {
|
|
865
|
+
yield chunk;
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
return {
|
|
869
|
+
stream: transformedStream(),
|
|
870
|
+
provider: self.providerName,
|
|
871
|
+
model: self.modelName,
|
|
872
|
+
};
|
|
778
873
|
}
|
|
779
874
|
else {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const response = await proxyFetch(requestUrl, {
|
|
797
|
-
method: "POST",
|
|
798
|
-
headers: { "Content-Type": "application/json" },
|
|
799
|
-
body: JSON.stringify(requestBody),
|
|
800
|
-
signal: createAbortSignalWithTimeout(this.timeout),
|
|
801
|
-
});
|
|
802
|
-
logger.debug(`[Ollama OpenAI Mode] Response received:`, {
|
|
803
|
-
status: response.status,
|
|
804
|
-
statusText: response.statusText,
|
|
805
|
-
ok: response.ok,
|
|
806
|
-
});
|
|
807
|
-
if (!response.ok) {
|
|
808
|
-
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
|
|
809
|
-
}
|
|
810
|
-
// Transform to async generator for OpenAI-compatible format
|
|
811
|
-
const self = this;
|
|
812
|
-
const transformedStream = async function* () {
|
|
813
|
-
const generator = self.createOpenAIStream(response);
|
|
814
|
-
for await (const chunk of generator) {
|
|
815
|
-
yield chunk;
|
|
875
|
+
// Native Ollama mode: Use /api/generate
|
|
876
|
+
let prompt = options.input.text;
|
|
877
|
+
let images;
|
|
878
|
+
if (hasMultimodalInput) {
|
|
879
|
+
logger.debug(`Ollama (native mode): Detected multimodal input`, {
|
|
880
|
+
hasImages: !!options.input?.images?.length,
|
|
881
|
+
imageCount: options.input?.images?.length || 0,
|
|
882
|
+
});
|
|
883
|
+
const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
|
|
884
|
+
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
885
|
+
// Extract text from messages for prompt
|
|
886
|
+
prompt = multimodalMessages
|
|
887
|
+
.map((msg) => typeof msg.content === "string" ? msg.content : "")
|
|
888
|
+
.join("\n");
|
|
889
|
+
// Extract images
|
|
890
|
+
images = this.extractImagesFromMessages(multimodalMessages);
|
|
816
891
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
892
|
+
const requestBody = {
|
|
893
|
+
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
894
|
+
prompt,
|
|
895
|
+
system: options.systemPrompt,
|
|
896
|
+
stream: true,
|
|
897
|
+
options: {
|
|
898
|
+
temperature: options.temperature,
|
|
899
|
+
num_predict: options.maxTokens,
|
|
900
|
+
},
|
|
901
|
+
};
|
|
902
|
+
if (images && images.length > 0) {
|
|
903
|
+
requestBody.images = images;
|
|
904
|
+
}
|
|
905
|
+
const requestUrl = `${this.baseUrl}/api/generate`;
|
|
906
|
+
logger.debug(`[Ollama Native Mode] About to fetch:`, {
|
|
907
|
+
url: requestUrl,
|
|
908
|
+
baseUrl: this.baseUrl,
|
|
909
|
+
modelName: this.modelName,
|
|
910
|
+
requestBody: JSON.stringify(requestBody),
|
|
832
911
|
});
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
stream: true,
|
|
847
|
-
options: {
|
|
848
|
-
temperature: options.temperature,
|
|
849
|
-
num_predict: options.maxTokens,
|
|
850
|
-
},
|
|
851
|
-
};
|
|
852
|
-
if (images && images.length > 0) {
|
|
853
|
-
requestBody.images = images;
|
|
854
|
-
}
|
|
855
|
-
const requestUrl = `${this.baseUrl}/api/generate`;
|
|
856
|
-
logger.debug(`[Ollama Native Mode] About to fetch:`, {
|
|
857
|
-
url: requestUrl,
|
|
858
|
-
baseUrl: this.baseUrl,
|
|
859
|
-
modelName: this.modelName,
|
|
860
|
-
requestBody: JSON.stringify(requestBody),
|
|
861
|
-
});
|
|
862
|
-
const response = await proxyFetch(requestUrl, {
|
|
863
|
-
method: "POST",
|
|
864
|
-
headers: { "Content-Type": "application/json" },
|
|
865
|
-
body: JSON.stringify(requestBody),
|
|
866
|
-
signal: createAbortSignalWithTimeout(this.timeout),
|
|
867
|
-
});
|
|
868
|
-
logger.debug(`[Ollama Native Mode] Response received:`, {
|
|
869
|
-
status: response.status,
|
|
870
|
-
statusText: response.statusText,
|
|
871
|
-
ok: response.ok,
|
|
872
|
-
});
|
|
873
|
-
if (!response.ok) {
|
|
874
|
-
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
|
|
875
|
-
}
|
|
876
|
-
// Transform to async generator to match other providers
|
|
877
|
-
const self = this;
|
|
878
|
-
const transformedStream = async function* () {
|
|
879
|
-
const generator = self.createOllamaStream(response);
|
|
880
|
-
for await (const chunk of generator) {
|
|
881
|
-
yield chunk;
|
|
912
|
+
const response = await proxyFetch(requestUrl, {
|
|
913
|
+
method: "POST",
|
|
914
|
+
headers: { "Content-Type": "application/json" },
|
|
915
|
+
body: JSON.stringify(requestBody),
|
|
916
|
+
signal: createAbortSignalWithTimeout(this.timeout),
|
|
917
|
+
});
|
|
918
|
+
logger.debug(`[Ollama Native Mode] Response received:`, {
|
|
919
|
+
status: response.status,
|
|
920
|
+
statusText: response.statusText,
|
|
921
|
+
ok: response.ok,
|
|
922
|
+
});
|
|
923
|
+
if (!response.ok) {
|
|
924
|
+
throw this.handleProviderError(new Error(`Ollama API error: ${response.status} ${response.statusText}`));
|
|
882
925
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
926
|
+
// Transform to async generator to match other providers
|
|
927
|
+
const self = this;
|
|
928
|
+
const transformedStream = async function* () {
|
|
929
|
+
const generator = self.createOllamaStream(response);
|
|
930
|
+
for await (const chunk of generator) {
|
|
931
|
+
yield chunk;
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
return {
|
|
935
|
+
stream: transformedStream(),
|
|
936
|
+
provider: this.providerName,
|
|
937
|
+
model: this.modelName,
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
});
|
|
890
941
|
}
|
|
891
942
|
/**
|
|
892
943
|
* Convert AI SDK tools format to Ollama's function calling format
|