@push.rocks/smartai 0.13.3 → 2.0.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.
Files changed (98) hide show
  1. package/dist_ts/00_commitinfo_data.js +3 -3
  2. package/dist_ts/index.d.ts +6 -11
  3. package/dist_ts/index.js +6 -12
  4. package/dist_ts/plugins.d.ts +10 -15
  5. package/dist_ts/plugins.js +13 -19
  6. package/dist_ts/smartai.classes.smartai.d.ts +7 -0
  7. package/dist_ts/smartai.classes.smartai.js +51 -0
  8. package/dist_ts/smartai.interfaces.d.ts +41 -0
  9. package/dist_ts/smartai.interfaces.js +2 -0
  10. package/dist_ts/smartai.middleware.anthropic.d.ts +7 -0
  11. package/dist_ts/smartai.middleware.anthropic.js +36 -0
  12. package/dist_ts/smartai.provider.ollama.d.ts +8 -0
  13. package/dist_ts/smartai.provider.ollama.js +378 -0
  14. package/dist_ts_audio/index.d.ts +9 -0
  15. package/dist_ts_audio/index.js +15 -0
  16. package/dist_ts_audio/plugins.d.ts +2 -0
  17. package/dist_ts_audio/plugins.js +3 -0
  18. package/dist_ts_document/index.d.ts +11 -0
  19. package/dist_ts_document/index.js +45 -0
  20. package/dist_ts_document/plugins.d.ts +3 -0
  21. package/dist_ts_document/plugins.js +4 -0
  22. package/dist_ts_image/index.d.ts +46 -0
  23. package/dist_ts_image/index.js +110 -0
  24. package/dist_ts_image/plugins.d.ts +3 -0
  25. package/dist_ts_image/plugins.js +4 -0
  26. package/dist_ts_research/index.d.ts +19 -0
  27. package/dist_ts_research/index.js +98 -0
  28. package/dist_ts_research/plugins.d.ts +2 -0
  29. package/dist_ts_research/plugins.js +3 -0
  30. package/dist_ts_vision/index.d.ts +8 -0
  31. package/dist_ts_vision/index.js +21 -0
  32. package/dist_ts_vision/plugins.d.ts +2 -0
  33. package/dist_ts_vision/plugins.js +3 -0
  34. package/package.json +50 -22
  35. package/readme.hints.md +34 -88
  36. package/readme.md +284 -547
  37. package/ts/00_commitinfo_data.ts +2 -2
  38. package/ts/index.ts +8 -11
  39. package/ts/plugins.ts +19 -35
  40. package/ts/smartai.classes.smartai.ts +51 -0
  41. package/ts/smartai.interfaces.ts +53 -0
  42. package/ts/smartai.middleware.anthropic.ts +38 -0
  43. package/ts/smartai.provider.ollama.ts +426 -0
  44. package/ts_audio/index.ts +24 -0
  45. package/ts_audio/plugins.ts +2 -0
  46. package/ts_document/index.ts +61 -0
  47. package/ts_document/plugins.ts +3 -0
  48. package/ts_image/index.ts +147 -0
  49. package/ts_image/plugins.ts +3 -0
  50. package/ts_research/index.ts +120 -0
  51. package/ts_research/plugins.ts +2 -0
  52. package/ts_vision/index.ts +29 -0
  53. package/ts_vision/plugins.ts +2 -0
  54. package/dist_ts/abstract.classes.multimodal.d.ts +0 -212
  55. package/dist_ts/abstract.classes.multimodal.js +0 -43
  56. package/dist_ts/classes.conversation.d.ts +0 -31
  57. package/dist_ts/classes.conversation.js +0 -150
  58. package/dist_ts/classes.smartai.d.ts +0 -59
  59. package/dist_ts/classes.smartai.js +0 -139
  60. package/dist_ts/classes.tts.d.ts +0 -6
  61. package/dist_ts/classes.tts.js +0 -10
  62. package/dist_ts/interfaces.d.ts +0 -1
  63. package/dist_ts/interfaces.js +0 -2
  64. package/dist_ts/paths.d.ts +0 -2
  65. package/dist_ts/paths.js +0 -4
  66. package/dist_ts/provider.anthropic.d.ts +0 -48
  67. package/dist_ts/provider.anthropic.js +0 -369
  68. package/dist_ts/provider.elevenlabs.d.ts +0 -43
  69. package/dist_ts/provider.elevenlabs.js +0 -64
  70. package/dist_ts/provider.exo.d.ts +0 -40
  71. package/dist_ts/provider.exo.js +0 -116
  72. package/dist_ts/provider.groq.d.ts +0 -39
  73. package/dist_ts/provider.groq.js +0 -178
  74. package/dist_ts/provider.mistral.d.ts +0 -61
  75. package/dist_ts/provider.mistral.js +0 -288
  76. package/dist_ts/provider.ollama.d.ts +0 -141
  77. package/dist_ts/provider.ollama.js +0 -529
  78. package/dist_ts/provider.openai.d.ts +0 -62
  79. package/dist_ts/provider.openai.js +0 -403
  80. package/dist_ts/provider.perplexity.d.ts +0 -37
  81. package/dist_ts/provider.perplexity.js +0 -215
  82. package/dist_ts/provider.xai.d.ts +0 -52
  83. package/dist_ts/provider.xai.js +0 -160
  84. package/ts/abstract.classes.multimodal.ts +0 -240
  85. package/ts/classes.conversation.ts +0 -176
  86. package/ts/classes.smartai.ts +0 -187
  87. package/ts/classes.tts.ts +0 -15
  88. package/ts/interfaces.ts +0 -0
  89. package/ts/paths.ts +0 -4
  90. package/ts/provider.anthropic.ts +0 -446
  91. package/ts/provider.elevenlabs.ts +0 -116
  92. package/ts/provider.exo.ts +0 -155
  93. package/ts/provider.groq.ts +0 -219
  94. package/ts/provider.mistral.ts +0 -352
  95. package/ts/provider.ollama.ts +0 -705
  96. package/ts/provider.openai.ts +0 -462
  97. package/ts/provider.perplexity.ts +0 -259
  98. package/ts/provider.xai.ts +0 -214
@@ -1,705 +0,0 @@
1
- import * as plugins from './plugins.js';
2
- import * as paths from './paths.js';
3
- import { MultiModalModel } from './abstract.classes.multimodal.js';
4
- import type {
5
- ChatOptions,
6
- ChatResponse,
7
- ChatMessage,
8
- ResearchOptions,
9
- ResearchResponse,
10
- ImageGenerateOptions,
11
- ImageEditOptions,
12
- ImageResponse,
13
- StreamingChatOptions
14
- } from './abstract.classes.multimodal.js';
15
-
16
- /**
17
- * Ollama model runtime options
18
- * @see https://github.com/ollama/ollama/blob/main/docs/modelfile.md
19
- */
20
- export interface IOllamaModelOptions {
21
- num_ctx?: number; // Context window (default: 2048)
22
- temperature?: number; // 0 = deterministic (default: 0.8)
23
- top_k?: number; // Top-k sampling (default: 40)
24
- top_p?: number; // Nucleus sampling (default: 0.9)
25
- repeat_penalty?: number;// Repeat penalty (default: 1.1)
26
- num_predict?: number; // Max tokens to predict
27
- stop?: string[]; // Stop sequences
28
- seed?: number; // Random seed for reproducibility
29
- think?: boolean; // Enable thinking/reasoning mode (for GPT-OSS, QwQ, etc.)
30
- }
31
-
32
- /**
33
- * JSON Schema tool definition for Ollama native tool calling
34
- * @see https://docs.ollama.com/capabilities/tool-calling
35
- */
36
- export interface IOllamaTool {
37
- type: 'function';
38
- function: {
39
- name: string;
40
- description: string;
41
- parameters: {
42
- type: 'object';
43
- properties: Record<string, {
44
- type: string;
45
- description?: string;
46
- enum?: string[];
47
- }>;
48
- required?: string[];
49
- };
50
- };
51
- }
52
-
53
- /**
54
- * Tool call returned by model in native tool calling mode
55
- */
56
- export interface IOllamaToolCall {
57
- function: {
58
- name: string;
59
- arguments: Record<string, unknown>;
60
- index?: number;
61
- };
62
- }
63
-
64
- export interface IOllamaProviderOptions {
65
- baseUrl?: string;
66
- model?: string;
67
- visionModel?: string; // Model to use for vision tasks (e.g. 'llava')
68
- defaultOptions?: IOllamaModelOptions; // Default model options
69
- defaultTimeout?: number; // Default timeout in ms (default: 120000)
70
- }
71
-
72
- /**
73
- * Extended chat options with Ollama-specific settings
74
- */
75
- export interface IOllamaChatOptions extends ChatOptions {
76
- options?: IOllamaModelOptions; // Per-request model options
77
- timeout?: number; // Per-request timeout in ms
78
- model?: string; // Per-request model override
79
- tools?: IOllamaTool[]; // Available tools for native function calling
80
- // images is inherited from ChatOptions
81
- }
82
-
83
- /**
84
- * Chunk emitted during streaming
85
- */
86
- export interface IOllamaStreamChunk {
87
- content: string;
88
- thinking?: string; // For models with extended thinking
89
- toolCalls?: IOllamaToolCall[]; // Tool calls in streaming mode
90
- done: boolean;
91
- stats?: {
92
- totalDuration?: number;
93
- evalCount?: number;
94
- };
95
- }
96
-
97
- /**
98
- * Extended chat response with Ollama-specific fields
99
- */
100
- export interface IOllamaChatResponse extends ChatResponse {
101
- thinking?: string;
102
- toolCalls?: IOllamaToolCall[]; // Tool calls from model (native tool calling)
103
- stats?: {
104
- totalDuration?: number;
105
- evalCount?: number;
106
- };
107
- }
108
-
109
- export class OllamaProvider extends MultiModalModel {
110
- private options: IOllamaProviderOptions;
111
- private baseUrl: string;
112
- private model: string;
113
- private visionModel: string;
114
- private defaultOptions: IOllamaModelOptions;
115
- private defaultTimeout: number;
116
-
117
- constructor(optionsArg: IOllamaProviderOptions = {}) {
118
- super();
119
- this.options = optionsArg;
120
- this.baseUrl = optionsArg.baseUrl || 'http://localhost:11434';
121
- this.model = optionsArg.model || 'llama2';
122
- this.visionModel = optionsArg.visionModel || 'llava';
123
- this.defaultOptions = optionsArg.defaultOptions || {};
124
- this.defaultTimeout = optionsArg.defaultTimeout || 120000;
125
- }
126
-
127
- async start() {
128
- await super.start();
129
- // Verify Ollama is running
130
- try {
131
- const response = await fetch(`${this.baseUrl}/api/tags`);
132
- if (!response.ok) {
133
- throw new Error('Failed to connect to Ollama server');
134
- }
135
- } catch (error) {
136
- throw new Error(`Failed to connect to Ollama server at ${this.baseUrl}: ${error.message}`);
137
- }
138
- }
139
-
140
- async stop() {
141
- await super.stop();
142
- }
143
-
144
- public async chatStream(input: ReadableStream<Uint8Array>): Promise<ReadableStream<string>> {
145
- // Create a TextDecoder to handle incoming chunks
146
- const decoder = new TextDecoder();
147
- let buffer = '';
148
- let currentMessage: { role: string; content: string; } | null = null;
149
-
150
- // Create a TransformStream to process the input
151
- const transform = new TransformStream<Uint8Array, string>({
152
- transform: async (chunk, controller) => {
153
- buffer += decoder.decode(chunk, { stream: true });
154
-
155
- // Try to parse complete JSON messages from the buffer
156
- while (true) {
157
- const newlineIndex = buffer.indexOf('\n');
158
- if (newlineIndex === -1) break;
159
-
160
- const line = buffer.slice(0, newlineIndex);
161
- buffer = buffer.slice(newlineIndex + 1);
162
-
163
- if (line.trim()) {
164
- try {
165
- const message = JSON.parse(line);
166
- currentMessage = {
167
- role: message.role || 'user',
168
- content: message.content || '',
169
- };
170
- } catch (e) {
171
- console.error('Failed to parse message:', e);
172
- }
173
- }
174
- }
175
-
176
- // If we have a complete message, send it to Ollama
177
- if (currentMessage) {
178
- const response = await fetch(`${this.baseUrl}/api/chat`, {
179
- method: 'POST',
180
- headers: {
181
- 'Content-Type': 'application/json',
182
- },
183
- body: JSON.stringify({
184
- model: this.model,
185
- messages: [{ role: currentMessage.role, content: currentMessage.content }],
186
- stream: true,
187
- }),
188
- });
189
-
190
- // Process each chunk from Ollama
191
- const reader = response.body?.getReader();
192
- if (reader) {
193
- try {
194
- while (true) {
195
- const { done, value } = await reader.read();
196
- if (done) break;
197
-
198
- const chunk = new TextDecoder().decode(value);
199
- const lines = chunk.split('\n');
200
-
201
- for (const line of lines) {
202
- if (line.trim()) {
203
- try {
204
- const parsed = JSON.parse(line);
205
- const content = parsed.message?.content;
206
- if (content) {
207
- controller.enqueue(content);
208
- }
209
- } catch (e) {
210
- console.error('Failed to parse Ollama response:', e);
211
- }
212
- }
213
- }
214
- }
215
- } finally {
216
- reader.releaseLock();
217
- }
218
- }
219
-
220
- currentMessage = null;
221
- }
222
- },
223
-
224
- flush(controller) {
225
- if (buffer) {
226
- try {
227
- const message = JSON.parse(buffer);
228
- controller.enqueue(message.content || '');
229
- } catch (e) {
230
- console.error('Failed to parse remaining buffer:', e);
231
- }
232
- }
233
- }
234
- });
235
-
236
- // Connect the input to our transform stream
237
- return input.pipeThrough(transform);
238
- }
239
-
240
- // Implementing the synchronous chat interaction
241
- public async chat(optionsArg: ChatOptions): Promise<ChatResponse> {
242
- // Format messages for Ollama
243
- const historyMessages = optionsArg.messageHistory.map((msg) => {
244
- const formatted: { role: string; content: string; images?: string[]; reasoning?: string } = {
245
- role: msg.role,
246
- content: msg.content,
247
- };
248
- if (msg.images && msg.images.length > 0) {
249
- formatted.images = msg.images;
250
- }
251
- if (msg.reasoning) {
252
- formatted.reasoning = msg.reasoning;
253
- }
254
- return formatted;
255
- });
256
-
257
- // Build user message with optional images
258
- const userMessage: { role: string; content: string; images?: string[] } = {
259
- role: 'user',
260
- content: optionsArg.userMessage,
261
- };
262
- if (optionsArg.images && optionsArg.images.length > 0) {
263
- userMessage.images = optionsArg.images;
264
- }
265
-
266
- const messages = [
267
- { role: 'system', content: optionsArg.systemMessage },
268
- ...historyMessages,
269
- userMessage,
270
- ];
271
-
272
- // Build request body - include think parameter if set
273
- const requestBody: Record<string, unknown> = {
274
- model: this.model,
275
- messages: messages,
276
- stream: false,
277
- options: this.defaultOptions,
278
- };
279
-
280
- // Add think parameter for reasoning models (GPT-OSS, QwQ, etc.)
281
- if (this.defaultOptions.think !== undefined) {
282
- requestBody.think = this.defaultOptions.think;
283
- }
284
-
285
- // Make API call to Ollama with defaultOptions and timeout
286
- const response = await fetch(`${this.baseUrl}/api/chat`, {
287
- method: 'POST',
288
- headers: {
289
- 'Content-Type': 'application/json',
290
- },
291
- body: JSON.stringify(requestBody),
292
- signal: AbortSignal.timeout(this.defaultTimeout),
293
- });
294
-
295
- if (!response.ok) {
296
- throw new Error(`Ollama API error: ${response.statusText}`);
297
- }
298
-
299
- const result = await response.json();
300
-
301
- return {
302
- role: 'assistant' as const,
303
- message: result.message.content,
304
- reasoning: result.message.thinking || result.message.reasoning,
305
- };
306
- }
307
-
308
- /**
309
- * Streaming chat with token callback (implements MultiModalModel interface)
310
- * Calls onToken for each token generated during the response
311
- */
312
- public async chatStreaming(optionsArg: StreamingChatOptions): Promise<ChatResponse> {
313
- const onToken = optionsArg.onToken;
314
-
315
- // Use existing collectStreamResponse with callback, including images
316
- const response = await this.collectStreamResponse(
317
- {
318
- systemMessage: optionsArg.systemMessage,
319
- userMessage: optionsArg.userMessage,
320
- messageHistory: optionsArg.messageHistory,
321
- images: optionsArg.images,
322
- },
323
- (chunk) => {
324
- if (onToken) {
325
- if (chunk.thinking) onToken(chunk.thinking);
326
- if (chunk.content) onToken(chunk.content);
327
- }
328
- }
329
- );
330
-
331
- return {
332
- role: 'assistant' as const,
333
- message: response.message,
334
- reasoning: response.thinking,
335
- };
336
- }
337
-
338
- /**
339
- * Streaming chat with async iteration and options support
340
- */
341
- public async chatStreamResponse(
342
- optionsArg: IOllamaChatOptions
343
- ): Promise<AsyncIterable<IOllamaStreamChunk>> {
344
- const model = optionsArg.model || this.model;
345
- const timeout = optionsArg.timeout || this.defaultTimeout;
346
- const modelOptions = { ...this.defaultOptions, ...optionsArg.options };
347
-
348
- // Format history messages with optional images, reasoning, and tool_calls
349
- const historyMessages = optionsArg.messageHistory.map((msg) => {
350
- const formatted: { role: string; content: string; images?: string[]; reasoning?: string; tool_calls?: any[] } = {
351
- role: msg.role,
352
- content: msg.content,
353
- };
354
- if (msg.images && msg.images.length > 0) {
355
- formatted.images = msg.images;
356
- }
357
- if (msg.reasoning) {
358
- formatted.reasoning = msg.reasoning;
359
- }
360
- // CRITICAL: Include tool_calls in history for native tool calling
361
- // Without this, the model doesn't know it already called a tool and may call it again
362
- if ((msg as any).tool_calls && Array.isArray((msg as any).tool_calls)) {
363
- formatted.tool_calls = (msg as any).tool_calls;
364
- }
365
- return formatted;
366
- });
367
-
368
- // Build user message with optional images
369
- const userMessage: { role: string; content: string; images?: string[] } = {
370
- role: 'user',
371
- content: optionsArg.userMessage,
372
- };
373
- if (optionsArg.images && optionsArg.images.length > 0) {
374
- userMessage.images = optionsArg.images;
375
- }
376
-
377
- const messages = [
378
- { role: 'system', content: optionsArg.systemMessage },
379
- ...historyMessages,
380
- userMessage,
381
- ];
382
-
383
- // Build request body with optional tools and think parameters
384
- const requestBody: Record<string, unknown> = {
385
- model,
386
- messages,
387
- stream: true,
388
- options: modelOptions,
389
- };
390
-
391
- // Add think parameter for reasoning models (GPT-OSS, QwQ, etc.)
392
- if (modelOptions.think !== undefined) {
393
- requestBody.think = modelOptions.think;
394
- }
395
-
396
- // Add tools for native function calling
397
- if (optionsArg.tools && optionsArg.tools.length > 0) {
398
- requestBody.tools = optionsArg.tools;
399
- }
400
-
401
- const response = await fetch(`${this.baseUrl}/api/chat`, {
402
- method: 'POST',
403
- headers: { 'Content-Type': 'application/json' },
404
- body: JSON.stringify(requestBody),
405
- signal: AbortSignal.timeout(timeout),
406
- });
407
-
408
- if (!response.ok) {
409
- throw new Error(`Ollama API error: ${response.status}`);
410
- }
411
-
412
- const reader = response.body!.getReader();
413
- const decoder = new TextDecoder();
414
-
415
- return {
416
- [Symbol.asyncIterator]: async function* () {
417
- let buffer = '';
418
- try {
419
- while (true) {
420
- const { done, value } = await reader.read();
421
- if (done) break;
422
- buffer += decoder.decode(value, { stream: true });
423
- const lines = buffer.split('\n');
424
- buffer = lines.pop() || '';
425
- for (const line of lines) {
426
- if (!line.trim()) continue;
427
- try {
428
- const json = JSON.parse(line);
429
-
430
- // Parse tool_calls from response
431
- let toolCalls: IOllamaToolCall[] | undefined;
432
- if (json.message?.tool_calls && Array.isArray(json.message.tool_calls)) {
433
- toolCalls = json.message.tool_calls.map((tc: any) => ({
434
- function: {
435
- name: tc.function?.name || '',
436
- arguments: typeof tc.function?.arguments === 'string'
437
- ? JSON.parse(tc.function.arguments)
438
- : tc.function?.arguments || {},
439
- index: tc.index,
440
- },
441
- }));
442
- }
443
-
444
- yield {
445
- content: json.message?.content || '',
446
- thinking: json.message?.thinking,
447
- toolCalls,
448
- done: json.done || false,
449
- stats: json.done ? {
450
- totalDuration: json.total_duration,
451
- evalCount: json.eval_count,
452
- } : undefined,
453
- } as IOllamaStreamChunk;
454
- } catch { /* skip malformed */ }
455
- }
456
- }
457
- } finally {
458
- reader.releaseLock();
459
- }
460
- }
461
- };
462
- }
463
-
464
- /**
465
- * Stream and collect full response with optional progress callback
466
- */
467
- public async collectStreamResponse(
468
- optionsArg: IOllamaChatOptions,
469
- onChunk?: (chunk: IOllamaStreamChunk) => void
470
- ): Promise<IOllamaChatResponse> {
471
- const stream = await this.chatStreamResponse(optionsArg);
472
- let content = '';
473
- let thinking = '';
474
- let toolCalls: IOllamaToolCall[] = [];
475
- let stats: IOllamaChatResponse['stats'];
476
-
477
- for await (const chunk of stream) {
478
- if (chunk.content) content += chunk.content;
479
- if (chunk.thinking) thinking += chunk.thinking;
480
- if (chunk.toolCalls) toolCalls = toolCalls.concat(chunk.toolCalls);
481
- if (chunk.stats) stats = chunk.stats;
482
- if (onChunk) onChunk(chunk);
483
- }
484
-
485
- return {
486
- role: 'assistant' as const,
487
- message: content,
488
- thinking: thinking || undefined,
489
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
490
- stats,
491
- };
492
- }
493
-
494
- /**
495
- * Non-streaming chat with full options support
496
- */
497
- public async chatWithOptions(optionsArg: IOllamaChatOptions): Promise<IOllamaChatResponse> {
498
- const model = optionsArg.model || this.model;
499
- const timeout = optionsArg.timeout || this.defaultTimeout;
500
- const modelOptions = { ...this.defaultOptions, ...optionsArg.options };
501
-
502
- // Format history messages with optional images, reasoning, tool_calls, and tool role
503
- const historyMessages = optionsArg.messageHistory.map((msg) => {
504
- // Handle tool result messages
505
- if ((msg as any).role === 'tool') {
506
- return {
507
- role: 'tool',
508
- content: msg.content,
509
- tool_name: (msg as any).toolName,
510
- };
511
- }
512
-
513
- const formatted: { role: string; content: string; images?: string[]; reasoning?: string; tool_calls?: any[] } = {
514
- role: msg.role,
515
- content: msg.content,
516
- };
517
- if (msg.images && msg.images.length > 0) {
518
- formatted.images = msg.images;
519
- }
520
- if (msg.reasoning) {
521
- formatted.reasoning = msg.reasoning;
522
- }
523
- // CRITICAL: Include tool_calls in history for native tool calling
524
- // Without this, the model doesn't know it already called a tool and may call it again
525
- if ((msg as any).tool_calls && Array.isArray((msg as any).tool_calls)) {
526
- formatted.tool_calls = (msg as any).tool_calls;
527
- }
528
- return formatted;
529
- });
530
-
531
- // Build user message with optional images
532
- const userMessage: { role: string; content: string; images?: string[] } = {
533
- role: 'user',
534
- content: optionsArg.userMessage,
535
- };
536
- if (optionsArg.images && optionsArg.images.length > 0) {
537
- userMessage.images = optionsArg.images;
538
- }
539
-
540
- const messages = [
541
- { role: 'system', content: optionsArg.systemMessage },
542
- ...historyMessages,
543
- userMessage,
544
- ];
545
-
546
- // Build request body with optional tools and think parameters
547
- const requestBody: Record<string, unknown> = {
548
- model,
549
- messages,
550
- stream: false,
551
- options: modelOptions,
552
- };
553
-
554
- // Add think parameter for reasoning models (GPT-OSS, QwQ, etc.)
555
- if (modelOptions.think !== undefined) {
556
- requestBody.think = modelOptions.think;
557
- }
558
-
559
- // Add tools for native function calling
560
- if (optionsArg.tools && optionsArg.tools.length > 0) {
561
- requestBody.tools = optionsArg.tools;
562
- }
563
-
564
- const response = await fetch(`${this.baseUrl}/api/chat`, {
565
- method: 'POST',
566
- headers: { 'Content-Type': 'application/json' },
567
- body: JSON.stringify(requestBody),
568
- signal: AbortSignal.timeout(timeout),
569
- });
570
-
571
- if (!response.ok) {
572
- throw new Error(`Ollama API error: ${response.statusText}`);
573
- }
574
-
575
- const result = await response.json();
576
-
577
- // Parse tool_calls from response
578
- let toolCalls: IOllamaToolCall[] | undefined;
579
- if (result.message?.tool_calls && Array.isArray(result.message.tool_calls)) {
580
- toolCalls = result.message.tool_calls.map((tc: any) => ({
581
- function: {
582
- name: tc.function?.name || '',
583
- arguments: typeof tc.function?.arguments === 'string'
584
- ? JSON.parse(tc.function.arguments)
585
- : tc.function?.arguments || {},
586
- index: tc.index,
587
- },
588
- }));
589
- }
590
-
591
- return {
592
- role: 'assistant' as const,
593
- message: result.message.content || '',
594
- thinking: result.message.thinking,
595
- toolCalls,
596
- stats: {
597
- totalDuration: result.total_duration,
598
- evalCount: result.eval_count,
599
- },
600
- };
601
- }
602
-
603
- public async audio(optionsArg: { message: string }): Promise<NodeJS.ReadableStream> {
604
- throw new Error('Audio generation is not supported by Ollama.');
605
- }
606
-
607
- public async vision(optionsArg: { image: Buffer; prompt: string }): Promise<string> {
608
- const base64Image = optionsArg.image.toString('base64');
609
-
610
- const response = await fetch(`${this.baseUrl}/api/chat`, {
611
- method: 'POST',
612
- headers: {
613
- 'Content-Type': 'application/json',
614
- },
615
- body: JSON.stringify({
616
- model: this.visionModel,
617
- messages: [{
618
- role: 'user',
619
- content: optionsArg.prompt,
620
- images: [base64Image]
621
- }],
622
- stream: false
623
- }),
624
- });
625
-
626
- if (!response.ok) {
627
- throw new Error(`Ollama API error: ${response.statusText}`);
628
- }
629
-
630
- const result = await response.json();
631
- return result.message.content;
632
- }
633
-
634
- public async document(optionsArg: {
635
- systemMessage: string;
636
- userMessage: string;
637
- pdfDocuments: Uint8Array[];
638
- messageHistory: ChatMessage[];
639
- }): Promise<{ message: any }> {
640
- // Ensure SmartPdf is initialized before processing documents
641
- await this.ensureSmartpdfReady();
642
-
643
- // Convert PDF documents to images using SmartPDF
644
- let documentImageBytesArray: Uint8Array[] = [];
645
-
646
- for (const pdfDocument of optionsArg.pdfDocuments) {
647
- const documentImageArray = await this.smartpdfInstance!.convertPDFToPngBytes(pdfDocument);
648
- documentImageBytesArray = documentImageBytesArray.concat(documentImageArray);
649
- }
650
-
651
- // Convert images to base64
652
- const base64Images = documentImageBytesArray.map(bytes => Buffer.from(bytes).toString('base64'));
653
-
654
- // Send request to Ollama with images
655
- const response = await fetch(`${this.baseUrl}/api/chat`, {
656
- method: 'POST',
657
- headers: {
658
- 'Content-Type': 'application/json',
659
- },
660
- body: JSON.stringify({
661
- model: this.visionModel,
662
- messages: [
663
- { role: 'system', content: optionsArg.systemMessage },
664
- ...optionsArg.messageHistory,
665
- {
666
- role: 'user',
667
- content: optionsArg.userMessage,
668
- images: base64Images
669
- }
670
- ],
671
- stream: false
672
- }),
673
- });
674
-
675
- if (!response.ok) {
676
- throw new Error(`Ollama API error: ${response.statusText}`);
677
- }
678
-
679
- const result = await response.json();
680
- return {
681
- message: {
682
- role: 'assistant',
683
- content: result.message.content
684
- }
685
- };
686
- }
687
-
688
- public async research(optionsArg: ResearchOptions): Promise<ResearchResponse> {
689
- throw new Error('Research capabilities are not yet supported by Ollama provider.');
690
- }
691
-
692
- /**
693
- * Image generation is not supported by Ollama
694
- */
695
- public async imageGenerate(optionsArg: ImageGenerateOptions): Promise<ImageResponse> {
696
- throw new Error('Image generation is not supported by Ollama. Please use OpenAI provider for image generation.');
697
- }
698
-
699
- /**
700
- * Image editing is not supported by Ollama
701
- */
702
- public async imageEdit(optionsArg: ImageEditOptions): Promise<ImageResponse> {
703
- throw new Error('Image editing is not supported by Ollama. Please use OpenAI provider for image editing.');
704
- }
705
- }