@yourgpt/llm-sdk 2.0.1 → 2.0.2-beta.2
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/dist/adapters/index.d.mts +27 -4
- package/dist/adapters/index.d.ts +27 -4
- package/dist/adapters/index.js +395 -25
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/index.mjs +395 -25
- package/dist/adapters/index.mjs.map +1 -1
- package/dist/index.d.mts +24 -5
- package/dist/index.d.ts +24 -5
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +20 -4
- package/dist/index.mjs.map +1 -1
- package/dist/providers/anthropic/index.d.mts +1 -2
- package/dist/providers/anthropic/index.d.ts +1 -2
- package/dist/providers/anthropic/index.js +108 -12
- package/dist/providers/anthropic/index.js.map +1 -1
- package/dist/providers/anthropic/index.mjs +108 -12
- package/dist/providers/anthropic/index.mjs.map +1 -1
- package/dist/providers/azure/index.d.mts +1 -2
- package/dist/providers/azure/index.d.ts +1 -2
- package/dist/providers/azure/index.js.map +1 -1
- package/dist/providers/azure/index.mjs.map +1 -1
- package/dist/providers/google/index.d.mts +1 -2
- package/dist/providers/google/index.d.ts +1 -2
- package/dist/providers/google/index.js +61 -2
- package/dist/providers/google/index.js.map +1 -1
- package/dist/providers/google/index.mjs +61 -2
- package/dist/providers/google/index.mjs.map +1 -1
- package/dist/providers/ollama/index.d.mts +8 -3
- package/dist/providers/ollama/index.d.ts +8 -3
- package/dist/providers/ollama/index.js +227 -17
- package/dist/providers/ollama/index.js.map +1 -1
- package/dist/providers/ollama/index.mjs +227 -17
- package/dist/providers/ollama/index.mjs.map +1 -1
- package/dist/providers/openai/index.d.mts +1 -2
- package/dist/providers/openai/index.d.ts +1 -2
- package/dist/providers/openai/index.js +57 -3
- package/dist/providers/openai/index.js.map +1 -1
- package/dist/providers/openai/index.mjs +57 -3
- package/dist/providers/openai/index.mjs.map +1 -1
- package/dist/providers/openrouter/index.d.mts +56 -3
- package/dist/providers/openrouter/index.d.ts +56 -3
- package/dist/providers/openrouter/index.js +90 -276
- package/dist/providers/openrouter/index.js.map +1 -1
- package/dist/providers/openrouter/index.mjs +89 -277
- package/dist/providers/openrouter/index.mjs.map +1 -1
- package/dist/providers/xai/index.d.mts +1 -2
- package/dist/providers/xai/index.d.ts +1 -2
- package/dist/providers/xai/index.js.map +1 -1
- package/dist/providers/xai/index.mjs.map +1 -1
- package/dist/{base-DdxolpKP.d.mts → types-C_f95PKp.d.mts} +434 -3
- package/dist/{base-DdxolpKP.d.ts → types-C_f95PKp.d.ts} +434 -3
- package/package.json +1 -1
- package/dist/types-Ck25ZYma.d.mts +0 -323
- package/dist/types-Dsz8SpdB.d.ts +0 -323
|
@@ -7,23 +7,165 @@ function generateId(prefix = "id") {
|
|
|
7
7
|
function generateMessageId() {
|
|
8
8
|
return generateId("msg");
|
|
9
9
|
}
|
|
10
|
+
function generateToolCallId() {
|
|
11
|
+
return generateId("call");
|
|
12
|
+
}
|
|
10
13
|
|
|
11
14
|
// src/adapters/base.ts
|
|
12
|
-
function
|
|
15
|
+
function parameterToJsonSchema(param) {
|
|
16
|
+
const schema = {
|
|
17
|
+
type: param.type
|
|
18
|
+
};
|
|
19
|
+
if (param.description) {
|
|
20
|
+
schema.description = param.description;
|
|
21
|
+
}
|
|
22
|
+
if (param.enum) {
|
|
23
|
+
schema.enum = param.enum;
|
|
24
|
+
}
|
|
25
|
+
if (param.type === "array" && param.items) {
|
|
26
|
+
schema.items = parameterToJsonSchema(
|
|
27
|
+
param.items
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (param.type === "object" && param.properties) {
|
|
31
|
+
schema.properties = Object.fromEntries(
|
|
32
|
+
Object.entries(param.properties).map(([key, prop]) => [
|
|
33
|
+
key,
|
|
34
|
+
parameterToJsonSchema(
|
|
35
|
+
prop
|
|
36
|
+
)
|
|
37
|
+
])
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return schema;
|
|
41
|
+
}
|
|
42
|
+
function formatTools(actions) {
|
|
43
|
+
return actions.map((action) => ({
|
|
44
|
+
type: "function",
|
|
45
|
+
function: {
|
|
46
|
+
name: action.name,
|
|
47
|
+
description: action.description,
|
|
48
|
+
parameters: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: action.parameters ? Object.fromEntries(
|
|
51
|
+
Object.entries(action.parameters).map(([key, param]) => [
|
|
52
|
+
key,
|
|
53
|
+
parameterToJsonSchema(param)
|
|
54
|
+
])
|
|
55
|
+
) : {},
|
|
56
|
+
required: action.parameters ? Object.entries(action.parameters).filter(([, param]) => param.required).map(([key]) => key) : []
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/adapters/ollama.ts
|
|
63
|
+
function extractBase64Data(data) {
|
|
64
|
+
if (data.startsWith("data:")) {
|
|
65
|
+
const commaIndex = data.indexOf(",");
|
|
66
|
+
if (commaIndex !== -1) {
|
|
67
|
+
return data.slice(commaIndex + 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return data;
|
|
71
|
+
}
|
|
72
|
+
async function downloadImageAsBase64(url) {
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetch(url);
|
|
75
|
+
if (!response.ok) return null;
|
|
76
|
+
const buffer = await response.arrayBuffer();
|
|
77
|
+
const base64 = Buffer.from(buffer).toString("base64");
|
|
78
|
+
return base64;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function extractImagesFromAttachments(attachments) {
|
|
84
|
+
if (!attachments) return [];
|
|
85
|
+
const images = [];
|
|
86
|
+
for (const attachment of attachments) {
|
|
87
|
+
if (attachment.type !== "image") continue;
|
|
88
|
+
if (attachment.data) {
|
|
89
|
+
images.push(extractBase64Data(attachment.data));
|
|
90
|
+
} else if (attachment.url) {
|
|
91
|
+
const base64 = await downloadImageAsBase64(attachment.url);
|
|
92
|
+
if (base64) {
|
|
93
|
+
images.push(base64);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return images;
|
|
98
|
+
}
|
|
99
|
+
async function formatMessagesForOllama(messages, systemPrompt) {
|
|
13
100
|
const formatted = [];
|
|
14
101
|
if (systemPrompt) {
|
|
15
102
|
formatted.push({ role: "system", content: systemPrompt });
|
|
16
103
|
}
|
|
17
104
|
for (const msg of messages) {
|
|
18
|
-
|
|
105
|
+
const baseMessage = {
|
|
19
106
|
role: msg.role,
|
|
20
107
|
content: msg.content ?? ""
|
|
21
|
-
}
|
|
108
|
+
};
|
|
109
|
+
if (msg.role === "user" && msg.metadata?.attachments) {
|
|
110
|
+
const images = await extractImagesFromAttachments(
|
|
111
|
+
msg.metadata.attachments
|
|
112
|
+
);
|
|
113
|
+
if (images.length > 0) {
|
|
114
|
+
baseMessage.images = images;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
|
|
118
|
+
baseMessage.tool_calls = msg.tool_calls.map((tc) => ({
|
|
119
|
+
id: tc.id,
|
|
120
|
+
function: {
|
|
121
|
+
name: tc.function.name,
|
|
122
|
+
arguments: JSON.parse(tc.function.arguments)
|
|
123
|
+
}
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
if (msg.role === "tool" && msg.tool_call_id) {
|
|
127
|
+
baseMessage.tool_call_id = msg.tool_call_id;
|
|
128
|
+
}
|
|
129
|
+
formatted.push(baseMessage);
|
|
22
130
|
}
|
|
23
131
|
return formatted;
|
|
24
132
|
}
|
|
25
|
-
|
|
26
|
-
|
|
133
|
+
async function processRawMessagesForOllama(rawMessages, systemPrompt) {
|
|
134
|
+
const processed = [];
|
|
135
|
+
if (systemPrompt) {
|
|
136
|
+
const hasSystem = rawMessages.some((m) => m.role === "system");
|
|
137
|
+
if (!hasSystem) {
|
|
138
|
+
processed.push({ role: "system", content: systemPrompt });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const msg of rawMessages) {
|
|
142
|
+
const baseMessage = {
|
|
143
|
+
role: msg.role,
|
|
144
|
+
content: msg.content ?? ""
|
|
145
|
+
};
|
|
146
|
+
if (msg.role === "user" && msg.attachments && Array.isArray(msg.attachments)) {
|
|
147
|
+
const attachments = msg.attachments;
|
|
148
|
+
const images = await extractImagesFromAttachments(attachments);
|
|
149
|
+
if (images.length > 0) {
|
|
150
|
+
baseMessage.images = images;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
|
|
154
|
+
baseMessage.tool_calls = msg.tool_calls.map((tc) => ({
|
|
155
|
+
id: tc.id,
|
|
156
|
+
function: {
|
|
157
|
+
name: tc.function.name,
|
|
158
|
+
arguments: typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments
|
|
159
|
+
}
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
if (msg.tool_call_id) {
|
|
163
|
+
baseMessage.tool_call_id = msg.tool_call_id;
|
|
164
|
+
}
|
|
165
|
+
processed.push(baseMessage);
|
|
166
|
+
}
|
|
167
|
+
return processed;
|
|
168
|
+
}
|
|
27
169
|
var OllamaAdapter = class {
|
|
28
170
|
constructor(config = {}) {
|
|
29
171
|
this.provider = "ollama";
|
|
@@ -32,10 +174,32 @@ var OllamaAdapter = class {
|
|
|
32
174
|
this.baseUrl = config.baseUrl || "http://localhost:11434";
|
|
33
175
|
}
|
|
34
176
|
async *stream(request) {
|
|
35
|
-
|
|
177
|
+
let messages;
|
|
178
|
+
if (request.rawMessages && request.rawMessages.length > 0) {
|
|
179
|
+
messages = await processRawMessagesForOllama(
|
|
180
|
+
request.rawMessages,
|
|
181
|
+
request.systemPrompt
|
|
182
|
+
);
|
|
183
|
+
} else {
|
|
184
|
+
messages = await formatMessagesForOllama(
|
|
185
|
+
request.messages,
|
|
186
|
+
request.systemPrompt
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const tools = request.actions?.length ? formatTools(request.actions) : void 0;
|
|
36
190
|
const messageId = generateMessageId();
|
|
37
191
|
yield { type: "message:start", id: messageId };
|
|
38
192
|
try {
|
|
193
|
+
const ollamaOptions = {
|
|
194
|
+
temperature: request.config?.temperature ?? this.config.temperature
|
|
195
|
+
};
|
|
196
|
+
const maxTokens = request.config?.maxTokens ?? this.config.maxTokens;
|
|
197
|
+
if (maxTokens !== void 0) {
|
|
198
|
+
ollamaOptions.num_predict = maxTokens;
|
|
199
|
+
}
|
|
200
|
+
if (this.config.options) {
|
|
201
|
+
Object.assign(ollamaOptions, this.config.options);
|
|
202
|
+
}
|
|
39
203
|
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
40
204
|
method: "POST",
|
|
41
205
|
headers: {
|
|
@@ -44,16 +208,15 @@ var OllamaAdapter = class {
|
|
|
44
208
|
body: JSON.stringify({
|
|
45
209
|
model: request.config?.model || this.model,
|
|
46
210
|
messages,
|
|
211
|
+
tools,
|
|
47
212
|
stream: true,
|
|
48
|
-
options:
|
|
49
|
-
temperature: request.config?.temperature ?? this.config.temperature,
|
|
50
|
-
num_predict: request.config?.maxTokens ?? this.config.maxTokens
|
|
51
|
-
}
|
|
213
|
+
options: ollamaOptions
|
|
52
214
|
}),
|
|
53
215
|
signal: request.signal
|
|
54
216
|
});
|
|
55
217
|
if (!response.ok) {
|
|
56
|
-
|
|
218
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
219
|
+
throw new Error(`Ollama API error: ${response.status} - ${errorText}`);
|
|
57
220
|
}
|
|
58
221
|
if (!response.body) {
|
|
59
222
|
throw new Error("No response body");
|
|
@@ -61,6 +224,8 @@ var OllamaAdapter = class {
|
|
|
61
224
|
const reader = response.body.getReader();
|
|
62
225
|
const decoder = new TextDecoder();
|
|
63
226
|
let buffer = "";
|
|
227
|
+
let hasEmittedToolCalls = false;
|
|
228
|
+
const toolCallsInProgress = /* @__PURE__ */ new Map();
|
|
64
229
|
while (true) {
|
|
65
230
|
const { done, value } = await reader.read();
|
|
66
231
|
if (done) break;
|
|
@@ -74,6 +239,28 @@ var OllamaAdapter = class {
|
|
|
74
239
|
if (chunk.message?.content) {
|
|
75
240
|
yield { type: "message:delta", content: chunk.message.content };
|
|
76
241
|
}
|
|
242
|
+
if (chunk.message?.tool_calls && !hasEmittedToolCalls) {
|
|
243
|
+
for (let i = 0; i < chunk.message.tool_calls.length; i++) {
|
|
244
|
+
const toolCall = chunk.message.tool_calls[i];
|
|
245
|
+
const toolCallId = toolCall.id || generateToolCallId();
|
|
246
|
+
toolCallsInProgress.set(i, {
|
|
247
|
+
id: toolCallId,
|
|
248
|
+
name: toolCall.function?.name || "",
|
|
249
|
+
arguments: toolCall.function?.arguments || {}
|
|
250
|
+
});
|
|
251
|
+
yield {
|
|
252
|
+
type: "action:start",
|
|
253
|
+
id: toolCallId,
|
|
254
|
+
name: toolCall.function?.name || ""
|
|
255
|
+
};
|
|
256
|
+
yield {
|
|
257
|
+
type: "action:args",
|
|
258
|
+
id: toolCallId,
|
|
259
|
+
args: JSON.stringify(toolCall.function?.arguments || {})
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
hasEmittedToolCalls = true;
|
|
263
|
+
}
|
|
77
264
|
if (chunk.done) {
|
|
78
265
|
break;
|
|
79
266
|
}
|
|
@@ -87,10 +274,20 @@ var OllamaAdapter = class {
|
|
|
87
274
|
if (error.name === "AbortError") {
|
|
88
275
|
yield { type: "done" };
|
|
89
276
|
} else {
|
|
277
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
278
|
+
let code = "OLLAMA_ERROR";
|
|
279
|
+
let message = errorMessage;
|
|
280
|
+
if (errorMessage.includes("ECONNREFUSED") || errorMessage.includes("fetch failed")) {
|
|
281
|
+
message = `Cannot connect to Ollama at ${this.baseUrl}. Make sure Ollama is running (ollama serve).`;
|
|
282
|
+
code = "OLLAMA_CONNECTION_ERROR";
|
|
283
|
+
} else if (errorMessage.includes("model") && errorMessage.includes("not found")) {
|
|
284
|
+
message = `Model "${this.model}" not found. Pull it with: ollama pull ${this.model}`;
|
|
285
|
+
code = "OLLAMA_MODEL_NOT_FOUND";
|
|
286
|
+
}
|
|
90
287
|
yield {
|
|
91
288
|
type: "error",
|
|
92
|
-
message
|
|
93
|
-
code
|
|
289
|
+
message,
|
|
290
|
+
code
|
|
94
291
|
};
|
|
95
292
|
}
|
|
96
293
|
}
|
|
@@ -129,15 +326,25 @@ var OLLAMA_MODELS = {
|
|
|
129
326
|
tools: true,
|
|
130
327
|
maxTokens: 8192
|
|
131
328
|
},
|
|
329
|
+
"llama3.1": {
|
|
330
|
+
vision: false,
|
|
331
|
+
tools: true,
|
|
332
|
+
maxTokens: 128e3
|
|
333
|
+
},
|
|
334
|
+
"llama3.1:70b": {
|
|
335
|
+
vision: false,
|
|
336
|
+
tools: true,
|
|
337
|
+
maxTokens: 128e3
|
|
338
|
+
},
|
|
132
339
|
"llama3.2": {
|
|
133
340
|
vision: false,
|
|
134
341
|
tools: true,
|
|
135
|
-
maxTokens:
|
|
342
|
+
maxTokens: 128e3
|
|
136
343
|
},
|
|
137
344
|
"llama3.2-vision": {
|
|
138
345
|
vision: true,
|
|
139
346
|
tools: true,
|
|
140
|
-
maxTokens:
|
|
347
|
+
maxTokens: 128e3
|
|
141
348
|
},
|
|
142
349
|
// Mistral series
|
|
143
350
|
mistral: {
|
|
@@ -219,10 +426,13 @@ var DEFAULT_MODEL_CAPS = {
|
|
|
219
426
|
};
|
|
220
427
|
function createOllama(config = {}) {
|
|
221
428
|
const baseUrl = config.baseUrl ?? "http://localhost:11434";
|
|
222
|
-
const
|
|
429
|
+
const defaultOptions = config.options;
|
|
430
|
+
const providerFn = (modelId, modelOptions) => {
|
|
223
431
|
return createOllamaAdapter({
|
|
224
432
|
model: modelId,
|
|
225
|
-
baseUrl
|
|
433
|
+
baseUrl,
|
|
434
|
+
// Merge default options with model-specific options
|
|
435
|
+
options: modelOptions ? { ...defaultOptions, ...modelOptions } : defaultOptions
|
|
226
436
|
});
|
|
227
437
|
};
|
|
228
438
|
const getCapabilities = (modelId) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/utils.ts","../../../src/adapters/base.ts","../../../src/adapters/ollama.ts","../../../src/providers/types.ts","../../../src/providers/ollama/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Utility functions for llm-sdk\n */\n\n/**\n * Generate a unique ID with optional prefix\n */\nexport function generateId(prefix = \"id\"): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Generate a message ID\n */\nexport function generateMessageId(): string {\n return generateId(\"msg\");\n}\n\n/**\n * Generate a conversation/thread ID\n */\nexport function generateThreadId(): string {\n return generateId(\"thread\");\n}\n\n/**\n * Generate a tool call ID\n */\nexport function generateToolCallId(): string {\n return generateId(\"call\");\n}\n","import type {\n Message,\n MessageAttachment,\n ActionDefinition,\n StreamEvent,\n LLMConfig,\n} from \"../core/stream-events\";\nimport type { TokenUsage } from \"../core/types\";\n\n/**\n * Request-level LLM configuration overrides\n */\nexport interface RequestLLMConfig {\n model?: string;\n temperature?: number;\n maxTokens?: number;\n}\n\n/**\n * Chat completion request\n */\nexport interface ChatCompletionRequest {\n /** Conversation messages */\n messages: Message[];\n /**\n * Raw provider-formatted messages (for agent loop with tool calls)\n * When provided, these are used instead of converting from Message[]\n * This allows passing messages with tool_calls and tool role\n */\n rawMessages?: Array<Record<string, unknown>>;\n /** Available actions/tools */\n actions?: ActionDefinition[];\n /** System prompt */\n systemPrompt?: string;\n /** LLM configuration overrides */\n config?: RequestLLMConfig;\n /** Abort signal for cancellation */\n signal?: AbortSignal;\n}\n\n/**\n * Non-streaming completion result\n */\nexport interface CompletionResult {\n /** Text content */\n content: string;\n /** Tool calls */\n toolCalls: Array<{ id: string; name: string; args: Record<string, unknown> }>;\n /** Thinking content (if extended thinking enabled) */\n thinking?: string;\n /** Token usage for billing/tracking */\n usage?: TokenUsage;\n /** Raw provider response for debugging */\n rawResponse: Record<string, unknown>;\n}\n\n/**\n * Base LLM adapter interface\n */\nexport interface LLMAdapter {\n /** Provider name */\n readonly provider: string;\n\n /** Model name */\n readonly model: string;\n\n /**\n * Stream a chat completion\n */\n stream(request: ChatCompletionRequest): AsyncGenerator<StreamEvent>;\n\n /**\n * Non-streaming chat completion (for debugging/comparison)\n */\n complete?(request: ChatCompletionRequest): Promise<CompletionResult>;\n}\n\n/**\n * Adapter factory function type\n */\nexport type AdapterFactory = (config: LLMConfig) => LLMAdapter;\n\n/**\n * Convert messages to provider format (simple text only)\n */\nexport function formatMessages(\n messages: Message[],\n systemPrompt?: string,\n): Array<{ role: string; content: string }> {\n const formatted: Array<{ role: string; content: string }> = [];\n\n // Add system prompt if provided\n if (systemPrompt) {\n formatted.push({ role: \"system\", content: systemPrompt });\n }\n\n // Add conversation messages\n for (const msg of messages) {\n formatted.push({\n role: msg.role,\n content: msg.content ?? \"\",\n });\n }\n\n return formatted;\n}\n\n/**\n * Convert ActionParameter to JSON Schema format recursively\n */\nfunction parameterToJsonSchema(param: {\n type: string;\n description?: string;\n enum?: string[];\n items?: unknown;\n properties?: Record<string, unknown>;\n}): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n type: param.type,\n };\n\n if (param.description) {\n schema.description = param.description;\n }\n\n if (param.enum) {\n schema.enum = param.enum;\n }\n\n // Handle array items\n if (param.type === \"array\" && param.items) {\n schema.items = parameterToJsonSchema(\n param.items as {\n type: string;\n description?: string;\n enum?: string[];\n items?: unknown;\n properties?: Record<string, unknown>;\n },\n );\n }\n\n // Handle nested object properties\n if (param.type === \"object\" && param.properties) {\n schema.properties = Object.fromEntries(\n Object.entries(param.properties).map(([key, prop]) => [\n key,\n parameterToJsonSchema(\n prop as {\n type: string;\n description?: string;\n enum?: string[];\n items?: unknown;\n properties?: Record<string, unknown>;\n },\n ),\n ]),\n );\n }\n\n return schema;\n}\n\n/**\n * Convert actions to OpenAI tool format\n */\nexport function formatTools(actions: ActionDefinition[]): Array<{\n type: \"function\";\n function: {\n name: string;\n description: string;\n parameters: object;\n };\n}> {\n return actions.map((action) => ({\n type: \"function\" as const,\n function: {\n name: action.name,\n description: action.description,\n parameters: {\n type: \"object\",\n properties: action.parameters\n ? Object.fromEntries(\n Object.entries(action.parameters).map(([key, param]) => [\n key,\n parameterToJsonSchema(param),\n ]),\n )\n : {},\n required: action.parameters\n ? Object.entries(action.parameters)\n .filter(([, param]) => param.required)\n .map(([key]) => key)\n : [],\n },\n },\n }));\n}\n\n// ============================================\n// Vision/Multimodal Support\n// ============================================\n\n/**\n * Content block types for multimodal messages\n */\nexport type AnthropicContentBlock =\n | { type: \"text\"; text: string }\n | {\n type: \"image\";\n source:\n | {\n type: \"base64\";\n media_type: string;\n data: string;\n }\n | {\n type: \"url\";\n url: string;\n };\n }\n | {\n type: \"document\";\n source:\n | {\n type: \"base64\";\n media_type: string;\n data: string;\n }\n | {\n type: \"url\";\n url: string;\n };\n };\n\nexport type OpenAIContentBlock =\n | { type: \"text\"; text: string }\n | {\n type: \"image_url\";\n image_url: {\n url: string;\n detail?: \"low\" | \"high\" | \"auto\";\n };\n };\n\n/**\n * Check if a message has image attachments\n * Supports both new format (metadata.attachments) and legacy (attachments)\n */\nexport function hasImageAttachments(message: Message): boolean {\n const attachments = message.metadata?.attachments;\n return attachments?.some((a) => a.type === \"image\") ?? false;\n}\n\n/**\n * Check if a message has media attachments (images or PDFs)\n */\nexport function hasMediaAttachments(message: Message): boolean {\n const attachments = message.metadata?.attachments;\n return (\n attachments?.some(\n (a) =>\n a.type === \"image\" ||\n (a.type === \"file\" && a.mimeType === \"application/pdf\"),\n ) ?? false\n );\n}\n\n/**\n * Convert MessageAttachment to Anthropic image content block\n *\n * Anthropic format:\n * {\n * type: \"image\",\n * source: {\n * type: \"base64\",\n * media_type: \"image/png\",\n * data: \"base64data...\"\n * }\n * }\n */\nexport function attachmentToAnthropicImage(\n attachment: MessageAttachment,\n): AnthropicContentBlock | null {\n if (attachment.type !== \"image\") return null;\n\n // Use URL if available (cloud storage)\n if (attachment.url) {\n return {\n type: \"image\",\n source: {\n type: \"url\",\n url: attachment.url,\n },\n };\n }\n\n // Fall back to base64 data\n if (!attachment.data) return null;\n\n // Extract base64 data (remove data URI prefix if present)\n let base64Data = attachment.data;\n if (base64Data.startsWith(\"data:\")) {\n const commaIndex = base64Data.indexOf(\",\");\n if (commaIndex !== -1) {\n base64Data = base64Data.slice(commaIndex + 1);\n }\n }\n\n return {\n type: \"image\",\n source: {\n type: \"base64\",\n media_type: attachment.mimeType || \"image/png\",\n data: base64Data,\n },\n };\n}\n\n/**\n * Convert MessageAttachment to OpenAI image_url content block\n *\n * OpenAI format:\n * {\n * type: \"image_url\",\n * image_url: {\n * url: \"data:image/png;base64,...\"\n * }\n * }\n */\nexport function attachmentToOpenAIImage(\n attachment: MessageAttachment,\n): OpenAIContentBlock | null {\n if (attachment.type !== \"image\") return null;\n\n let imageUrl: string;\n\n // Use URL if available (cloud storage)\n if (attachment.url) {\n imageUrl = attachment.url;\n } else if (attachment.data) {\n // Build data URI if not already one\n imageUrl = attachment.data.startsWith(\"data:\")\n ? attachment.data\n : `data:${attachment.mimeType || \"image/png\"};base64,${attachment.data}`;\n } else {\n return null;\n }\n\n return {\n type: \"image_url\",\n image_url: {\n url: imageUrl,\n detail: \"auto\",\n },\n };\n}\n\n/**\n * Convert MessageAttachment (PDF) to Anthropic document content block\n *\n * Anthropic format:\n * {\n * type: \"document\",\n * source: {\n * type: \"base64\",\n * media_type: \"application/pdf\",\n * data: \"base64data...\"\n * }\n * }\n */\nexport function attachmentToAnthropicDocument(\n attachment: MessageAttachment,\n): AnthropicContentBlock | null {\n // Only handle PDF files\n if (attachment.type !== \"file\" || attachment.mimeType !== \"application/pdf\") {\n return null;\n }\n\n // Use URL if available (cloud storage)\n if (attachment.url) {\n return {\n type: \"document\",\n source: {\n type: \"url\",\n url: attachment.url,\n },\n };\n }\n\n // Fall back to base64 data\n if (!attachment.data) return null;\n\n // Extract base64 data (remove data URI prefix if present)\n let base64Data = attachment.data;\n if (base64Data.startsWith(\"data:\")) {\n const commaIndex = base64Data.indexOf(\",\");\n if (commaIndex !== -1) {\n base64Data = base64Data.slice(commaIndex + 1);\n }\n }\n\n return {\n type: \"document\",\n source: {\n type: \"base64\",\n media_type: \"application/pdf\",\n data: base64Data,\n },\n };\n}\n\n/**\n * Convert a Message to Anthropic multimodal content blocks\n */\nexport function messageToAnthropicContent(\n message: Message,\n): string | AnthropicContentBlock[] {\n const attachments = message.metadata?.attachments;\n const content = message.content ?? \"\";\n\n // If no media attachments (images or PDFs), return simple string\n if (!hasMediaAttachments(message)) {\n return content;\n }\n\n // Build content blocks array\n const blocks: AnthropicContentBlock[] = [];\n\n // Add media attachments first (Claude recommends media before text)\n if (attachments) {\n for (const attachment of attachments) {\n // Try image first\n const imageBlock = attachmentToAnthropicImage(attachment);\n if (imageBlock) {\n blocks.push(imageBlock);\n continue;\n }\n // Try document (PDF)\n const docBlock = attachmentToAnthropicDocument(attachment);\n if (docBlock) {\n blocks.push(docBlock);\n }\n }\n }\n\n // Add text content\n if (content) {\n blocks.push({ type: \"text\", text: content });\n }\n\n return blocks;\n}\n\n/**\n * Convert a Message to OpenAI multimodal content blocks\n */\nexport function messageToOpenAIContent(\n message: Message,\n): string | OpenAIContentBlock[] {\n const attachments = message.metadata?.attachments;\n const content = message.content ?? \"\";\n\n // If no image attachments, return simple string\n if (!hasImageAttachments(message)) {\n return content;\n }\n\n // Build content blocks array\n const blocks: OpenAIContentBlock[] = [];\n\n // Add text content first\n if (content) {\n blocks.push({ type: \"text\", text: content });\n }\n\n // Add image attachments\n if (attachments) {\n for (const attachment of attachments) {\n const imageBlock = attachmentToOpenAIImage(attachment);\n if (imageBlock) {\n blocks.push(imageBlock);\n }\n }\n }\n\n return blocks;\n}\n\n/**\n * Anthropic content block types (extended for tools)\n */\nexport type AnthropicToolUseBlock = {\n type: \"tool_use\";\n id: string;\n name: string;\n input: Record<string, unknown>;\n};\n\nexport type AnthropicToolResultBlock = {\n type: \"tool_result\";\n tool_use_id: string;\n content: string;\n};\n\nexport type AnthropicMessageContent =\n | string\n | Array<\n AnthropicContentBlock | AnthropicToolUseBlock | AnthropicToolResultBlock\n >;\n\n/**\n * Format messages for Anthropic with full tool support\n * Handles: text, images, tool_use, and tool_result\n *\n * Key differences from OpenAI:\n * - tool_calls become tool_use blocks in assistant content\n * - tool results become tool_result blocks in user content\n */\nexport function formatMessagesForAnthropic(\n messages: Message[],\n systemPrompt?: string,\n): {\n system: string;\n messages: Array<{\n role: \"user\" | \"assistant\";\n content: AnthropicMessageContent;\n }>;\n} {\n const formatted: Array<{\n role: \"user\" | \"assistant\";\n content: AnthropicMessageContent;\n }> = [];\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n\n if (msg.role === \"system\") continue; // System handled separately\n\n if (msg.role === \"assistant\") {\n // Build content array for assistant\n const content: Array<AnthropicContentBlock | AnthropicToolUseBlock> = [];\n\n // Add text content if present\n if (msg.content) {\n content.push({ type: \"text\", text: msg.content });\n }\n\n // Convert tool_calls to tool_use blocks\n if (msg.tool_calls && msg.tool_calls.length > 0) {\n for (const tc of msg.tool_calls) {\n content.push({\n type: \"tool_use\",\n id: tc.id,\n name: tc.function.name,\n input: JSON.parse(tc.function.arguments),\n });\n }\n }\n\n formatted.push({\n role: \"assistant\",\n content:\n content.length === 1 && content[0].type === \"text\"\n ? (content[0] as { type: \"text\"; text: string }).text\n : content,\n });\n } else if (msg.role === \"tool\" && msg.tool_call_id) {\n // Tool results go in user message as tool_result blocks\n // Group consecutive tool messages together\n const toolResults: AnthropicToolResultBlock[] = [\n {\n type: \"tool_result\",\n tool_use_id: msg.tool_call_id,\n content: msg.content ?? \"\",\n },\n ];\n\n // Look ahead for more consecutive tool messages\n while (i + 1 < messages.length && messages[i + 1].role === \"tool\") {\n i++;\n const nextTool = messages[i];\n if (nextTool.tool_call_id) {\n toolResults.push({\n type: \"tool_result\",\n tool_use_id: nextTool.tool_call_id,\n content: nextTool.content ?? \"\",\n });\n }\n }\n\n formatted.push({\n role: \"user\",\n content: toolResults,\n });\n } else if (msg.role === \"user\") {\n formatted.push({\n role: \"user\",\n content: messageToAnthropicContent(msg),\n });\n }\n }\n\n return {\n system: systemPrompt || \"\",\n messages: formatted,\n };\n}\n\n/**\n * OpenAI message format with tool support\n */\nexport type OpenAIMessage =\n | { role: \"system\"; content: string }\n | { role: \"user\"; content: string | OpenAIContentBlock[] }\n | {\n role: \"assistant\";\n content: string | null;\n tool_calls?: Array<{\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }>;\n }\n | { role: \"tool\"; content: string; tool_call_id: string };\n\n/**\n * Format messages for OpenAI with full tool support\n * Handles: text, images, tool_calls, and tool results\n */\nexport function formatMessagesForOpenAI(\n messages: Message[],\n systemPrompt?: string,\n): OpenAIMessage[] {\n const formatted: OpenAIMessage[] = [];\n\n // Add system prompt if provided\n if (systemPrompt) {\n formatted.push({ role: \"system\", content: systemPrompt });\n }\n\n for (const msg of messages) {\n if (msg.role === \"system\") {\n formatted.push({ role: \"system\", content: msg.content ?? \"\" });\n } else if (msg.role === \"user\") {\n formatted.push({\n role: \"user\",\n content: messageToOpenAIContent(msg),\n });\n } else if (msg.role === \"assistant\") {\n const assistantMsg: OpenAIMessage = {\n role: \"assistant\",\n content: msg.content,\n };\n if (msg.tool_calls && msg.tool_calls.length > 0) {\n (assistantMsg as { tool_calls: typeof msg.tool_calls }).tool_calls =\n msg.tool_calls;\n }\n formatted.push(assistantMsg);\n } else if (msg.role === \"tool\" && msg.tool_call_id) {\n formatted.push({\n role: \"tool\",\n content: msg.content ?? \"\",\n tool_call_id: msg.tool_call_id,\n });\n }\n }\n\n return formatted;\n}\n","import type { LLMConfig, StreamEvent } from \"../core/stream-events\";\nimport { generateMessageId } from \"../core/utils\";\nimport type { LLMAdapter, ChatCompletionRequest } from \"./base\";\nimport { formatMessages } from \"./base\";\n\n/**\n * Ollama adapter configuration\n */\nexport interface OllamaAdapterConfig {\n model?: string;\n baseUrl?: string;\n temperature?: number;\n maxTokens?: number;\n}\n\n/**\n * Ollama LLM Adapter (Local models)\n *\n * Supports: Llama 3, Mistral, Phi, Gemma, CodeLlama, etc.\n */\nexport class OllamaAdapter implements LLMAdapter {\n readonly provider = \"ollama\";\n readonly model: string;\n\n private baseUrl: string;\n private config: OllamaAdapterConfig;\n\n constructor(config: OllamaAdapterConfig = {}) {\n this.config = config;\n this.model = config.model || \"llama3\";\n this.baseUrl = config.baseUrl || \"http://localhost:11434\";\n }\n\n async *stream(request: ChatCompletionRequest): AsyncGenerator<StreamEvent> {\n const messages = formatMessages(request.messages, request.systemPrompt);\n\n const messageId = generateMessageId();\n\n // Emit message start\n yield { type: \"message:start\", id: messageId };\n\n try {\n const response = await fetch(`${this.baseUrl}/api/chat`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model: request.config?.model || this.model,\n messages,\n stream: true,\n options: {\n temperature: request.config?.temperature ?? this.config.temperature,\n num_predict: request.config?.maxTokens ?? this.config.maxTokens,\n },\n }),\n signal: request.signal,\n });\n\n if (!response.ok) {\n throw new Error(`Ollama API error: ${response.status}`);\n }\n\n if (!response.body) {\n throw new Error(\"No response body\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const chunk = JSON.parse(line);\n\n if (chunk.message?.content) {\n yield { type: \"message:delta\", content: chunk.message.content };\n }\n\n // Ollama indicates completion with done: true\n if (chunk.done) {\n break;\n }\n } catch {\n // Skip invalid JSON\n }\n }\n }\n\n yield { type: \"message:end\" };\n yield { type: \"done\" };\n } catch (error) {\n if ((error as Error).name === \"AbortError\") {\n yield { type: \"done\" };\n } else {\n yield {\n type: \"error\",\n message: error instanceof Error ? error.message : \"Unknown error\",\n code: \"OLLAMA_ERROR\",\n };\n }\n }\n }\n}\n\n/**\n * Create Ollama adapter\n */\nexport function createOllamaAdapter(\n config?: OllamaAdapterConfig,\n): OllamaAdapter {\n return new OllamaAdapter(config);\n}\n","/**\n * Provider Types\n *\n * Defines interfaces for:\n * 1. Provider Formatters (for tool transformations in agent loop)\n * 2. Multi-provider architecture (AIProvider, capabilities, configs)\n */\n\nimport type {\n ToolDefinition,\n UnifiedToolCall,\n UnifiedToolResult,\n} from \"../core/stream-events\";\nimport type { LLMAdapter } from \"../adapters/base\";\n\n// ============================================\n// Provider Formatter Types (for agent loop)\n// ============================================\n\n/**\n * Provider formatter interface\n *\n * Each provider implements this interface to handle:\n * - Tool definition transformation\n * - Tool call parsing from responses\n * - Tool result formatting\n * - Stop reason detection\n */\nexport interface ProviderFormatter {\n /**\n * Transform unified tool definitions to provider format\n */\n transformTools(tools: ToolDefinition[]): unknown[];\n\n /**\n * Parse tool calls from provider response\n */\n parseToolCalls(response: unknown): UnifiedToolCall[];\n\n /**\n * Format tool results for provider\n */\n formatToolResults(results: UnifiedToolResult[]): unknown[];\n\n /**\n * Check if response indicates tool use is requested\n */\n isToolUseStop(response: unknown): boolean;\n\n /**\n * Check if response indicates end of turn\n */\n isEndTurnStop(response: unknown): boolean;\n\n /**\n * Get stop reason string from response\n */\n getStopReason(response: unknown): string;\n\n /**\n * Extract text content from response\n */\n extractTextContent(response: unknown): string;\n\n /**\n * Build assistant message with tool calls for conversation history\n */\n buildAssistantToolMessage(\n toolCalls: UnifiedToolCall[],\n textContent?: string,\n ): unknown;\n\n /**\n * Build user message with tool results for conversation history\n */\n buildToolResultMessage(results: UnifiedToolResult[]): unknown;\n}\n\n// ============================================\n// Anthropic Tool Types\n// ============================================\n\n/**\n * Anthropic tool definition format\n */\nexport interface AnthropicTool {\n name: string;\n description: string;\n input_schema: {\n type: \"object\";\n properties: Record<string, unknown>;\n required?: string[];\n };\n}\n\n/**\n * Anthropic tool_use block from response\n */\nexport interface AnthropicToolUse {\n type: \"tool_use\";\n id: string;\n name: string;\n input: Record<string, unknown>;\n}\n\n/**\n * Anthropic tool_result block\n */\nexport interface AnthropicToolResult {\n type: \"tool_result\";\n tool_use_id: string;\n content: string;\n}\n\n// ============================================\n// OpenAI Tool Types\n// ============================================\n\n/**\n * OpenAI tool definition format\n */\nexport interface OpenAITool {\n type: \"function\";\n function: {\n name: string;\n description: string;\n parameters: {\n type: \"object\";\n properties: Record<string, unknown>;\n required?: string[];\n };\n };\n}\n\n/**\n * OpenAI tool call from response\n */\nexport interface OpenAIToolCall {\n id: string;\n type: \"function\";\n function: {\n name: string;\n arguments: string; // JSON string\n };\n}\n\n/**\n * OpenAI tool result message\n */\nexport interface OpenAIToolResult {\n role: \"tool\";\n tool_call_id: string;\n content: string;\n}\n\n// ============================================\n// Gemini Tool Types\n// ============================================\n\n/**\n * Google Gemini function declaration\n */\nexport interface GeminiFunctionDeclaration {\n name: string;\n description: string;\n parameters?: {\n type: \"object\";\n properties: Record<string, unknown>;\n required?: string[];\n };\n}\n\n/**\n * Gemini function call from response\n */\nexport interface GeminiFunctionCall {\n name: string;\n args: Record<string, unknown>;\n}\n\n/**\n * Gemini function response\n */\nexport interface GeminiFunctionResponse {\n name: string;\n response: Record<string, unknown>;\n}\n\n// ============================================\n// Provider Capabilities (for UI feature flags)\n// ============================================\n\n/**\n * Capabilities of a model for UI feature flags\n * UI components can use this to enable/disable features\n */\nexport interface ProviderCapabilities {\n /** Supports image inputs */\n supportsVision: boolean;\n /** Supports tool/function calling */\n supportsTools: boolean;\n /** Supports extended thinking (Claude, DeepSeek) */\n supportsThinking: boolean;\n /** Supports streaming responses */\n supportsStreaming: boolean;\n /** Supports PDF document inputs */\n supportsPDF: boolean;\n /** Supports audio inputs */\n supportsAudio: boolean;\n /** Supports video inputs */\n supportsVideo: boolean;\n /** Maximum context tokens */\n maxTokens: number;\n /** Supported image MIME types */\n supportedImageTypes: string[];\n /** Supported audio MIME types */\n supportedAudioTypes?: string[];\n /** Supported video MIME types */\n supportedVideoTypes?: string[];\n /** Supports JSON mode / structured output */\n supportsJsonMode?: boolean;\n /** Supports system messages */\n supportsSystemMessages?: boolean;\n}\n\n// ============================================\n// AI Provider Interface\n// ============================================\n\n/**\n * AI Provider interface (object form)\n *\n * Wraps existing LLMAdapter with additional metadata:\n * - Supported models list\n * - Per-model capabilities\n * - Provider name\n */\nexport interface AIProviderObject {\n /** Provider name (e.g., 'openai', 'anthropic') */\n readonly name: string;\n\n /** List of supported model IDs */\n readonly supportedModels: string[];\n\n /**\n * Get a language model adapter for the given model ID\n * Returns the existing LLMAdapter interface - no breaking changes\n */\n languageModel(modelId: string): LLMAdapter;\n\n /**\n * Get capabilities for a specific model\n * UI components use this to enable/disable features\n */\n getCapabilities(modelId: string): ProviderCapabilities;\n\n /**\n * Optional: Get an embedding model (future expansion)\n */\n embeddingModel?(modelId: string): EmbeddingModel;\n}\n\n/**\n * Callable AI Provider (Vercel AI SDK style)\n *\n * A function that returns a LanguageModel when called with a model ID,\n * but also has properties for provider metadata and methods.\n *\n * @example\n * ```typescript\n * const openai = createOpenAI({ apiKey: '...' });\n *\n * // Callable - returns LanguageModel directly (Vercel AI SDK style)\n * const model = openai('gpt-4o');\n *\n * // Also supports method calls (backward compatible)\n * const model2 = openai.languageModel('gpt-4o');\n *\n * // Check capabilities\n * const caps = openai.getCapabilities('gpt-4o');\n * if (caps.supportsVision) {\n * // Show image upload button\n * }\n * ```\n */\nexport interface AIProvider extends AIProviderObject {\n /**\n * Call the provider directly with a model ID to get a LanguageModel\n * This is the Vercel AI SDK style pattern\n */\n (modelId: string): LLMAdapter;\n}\n\n/**\n * Helper to create a callable AIProvider\n * Combines a callable function with AIProvider properties\n */\nexport function createCallableProvider(\n providerFn: (modelId: string) => LLMAdapter,\n properties: Omit<AIProviderObject, \"languageModel\">,\n): AIProvider {\n // Define 'name' property using defineProperty since it's read-only on functions\n Object.defineProperty(providerFn, \"name\", {\n value: properties.name,\n writable: false,\n configurable: true,\n });\n\n // Assign other properties\n Object.assign(providerFn, {\n supportedModels: properties.supportedModels,\n languageModel: providerFn,\n getCapabilities: properties.getCapabilities,\n embeddingModel: properties.embeddingModel,\n });\n\n return providerFn as AIProvider;\n}\n\n/**\n * Embedding model interface (for future expansion)\n */\nexport interface EmbeddingModel {\n readonly provider: string;\n readonly modelId: string;\n embed(texts: string[]): Promise<number[][]>;\n}\n\n// ============================================\n// Provider-Specific Configurations\n// ============================================\n\n/**\n * Base provider configuration\n */\nexport interface BaseProviderConfig {\n /** API key (falls back to environment variable) */\n apiKey?: string;\n /** Custom base URL */\n baseUrl?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Custom headers to include */\n headers?: Record<string, string>;\n}\n\n/**\n * OpenAI provider configuration\n */\nexport interface OpenAIProviderConfig extends BaseProviderConfig {\n /** OpenAI organization ID */\n organization?: string;\n /** OpenAI project ID */\n project?: string;\n /** Vision detail level for images */\n imageDetail?: \"auto\" | \"low\" | \"high\";\n}\n\n/**\n * Anthropic provider configuration\n */\nexport interface AnthropicProviderConfig extends BaseProviderConfig {\n /** Extended thinking budget in tokens (minimum 1024) */\n thinkingBudget?: number;\n /** Enable prompt caching */\n cacheControl?: boolean;\n}\n\n/**\n * Google provider configuration\n */\nexport interface GoogleProviderConfig extends BaseProviderConfig {\n /** Safety settings */\n safetySettings?: GoogleSafetySetting[];\n /** Grounding configuration (for web search) */\n groundingConfig?: GoogleGroundingConfig;\n}\n\n/**\n * Google safety setting\n */\nexport interface GoogleSafetySetting {\n category:\n | \"HARM_CATEGORY_HARASSMENT\"\n | \"HARM_CATEGORY_HATE_SPEECH\"\n | \"HARM_CATEGORY_SEXUALLY_EXPLICIT\"\n | \"HARM_CATEGORY_DANGEROUS_CONTENT\";\n threshold:\n | \"BLOCK_NONE\"\n | \"BLOCK_LOW_AND_ABOVE\"\n | \"BLOCK_MEDIUM_AND_ABOVE\"\n | \"BLOCK_HIGH_AND_ABOVE\";\n}\n\n/**\n * Google grounding configuration\n */\nexport interface GoogleGroundingConfig {\n /** Enable Google Search grounding */\n googleSearchRetrieval?: boolean;\n}\n\n/**\n * xAI provider configuration\n */\nexport interface XAIProviderConfig extends BaseProviderConfig {\n // xAI uses OpenAI-compatible API, no extra config needed\n}\n\n/**\n * Azure OpenAI provider configuration\n */\nexport interface AzureProviderConfig extends BaseProviderConfig {\n /** Azure resource name */\n resourceName: string;\n /** Deployment name */\n deploymentName: string;\n /** API version (default: 2024-02-15-preview) */\n apiVersion?: string;\n}\n\n/**\n * Ollama provider configuration\n */\nexport interface OllamaProviderConfig extends BaseProviderConfig {\n // baseUrl defaults to http://localhost:11434\n}\n\n// ============================================\n// Model Information\n// ============================================\n\n/**\n * Model information for a provider\n */\nexport interface ModelInfo {\n /** Model ID */\n id: string;\n /** Display name */\n name: string;\n /** Description */\n description?: string;\n /** Capabilities */\n capabilities: ProviderCapabilities;\n /** Context window size */\n contextWindow: number;\n /** Pricing info (optional) */\n pricing?: {\n inputPerMillion?: number;\n outputPerMillion?: number;\n };\n}\n\n// ============================================\n// Default Capabilities\n// ============================================\n\n/**\n * Default capabilities for unknown models\n */\nexport const DEFAULT_CAPABILITIES: ProviderCapabilities = {\n supportsVision: false,\n supportsTools: true,\n supportsThinking: false,\n supportsStreaming: true,\n supportsPDF: false,\n supportsAudio: false,\n supportsVideo: false,\n maxTokens: 8192,\n supportedImageTypes: [],\n supportsJsonMode: false,\n supportsSystemMessages: true,\n};\n","/**\n * Ollama Provider\n *\n * Wraps the existing OllamaAdapter with provider interface.\n * Ollama runs models locally on your machine.\n */\n\nimport { createOllamaAdapter } from \"../../adapters/ollama\";\nimport {\n createCallableProvider,\n type AIProvider,\n type ProviderCapabilities,\n type OllamaProviderConfig,\n} from \"../types\";\n\n// ============================================\n// Model Definitions\n// ============================================\n\ninterface ModelCapabilities {\n vision: boolean;\n tools: boolean;\n maxTokens: number;\n}\n\n// Common Ollama models - users can run any model\nconst OLLAMA_MODELS: Record<string, ModelCapabilities> = {\n // Llama series\n llama3: {\n vision: false,\n tools: true,\n maxTokens: 8192,\n },\n \"llama3:70b\": {\n vision: false,\n tools: true,\n maxTokens: 8192,\n },\n \"llama3.2\": {\n vision: false,\n tools: true,\n maxTokens: 8192,\n },\n \"llama3.2-vision\": {\n vision: true,\n tools: true,\n maxTokens: 8192,\n },\n\n // Mistral series\n mistral: {\n vision: false,\n tools: true,\n maxTokens: 8192,\n },\n \"mistral-nemo\": {\n vision: false,\n tools: true,\n maxTokens: 128000,\n },\n mixtral: {\n vision: false,\n tools: true,\n maxTokens: 32768,\n },\n\n // CodeLlama\n codellama: {\n vision: false,\n tools: false,\n maxTokens: 16384,\n },\n\n // Phi series\n phi3: {\n vision: false,\n tools: true,\n maxTokens: 4096,\n },\n \"phi3:medium\": {\n vision: false,\n tools: true,\n maxTokens: 4096,\n },\n\n // Gemma series\n gemma2: {\n vision: false,\n tools: false,\n maxTokens: 8192,\n },\n \"gemma2:27b\": {\n vision: false,\n tools: false,\n maxTokens: 8192,\n },\n\n // Qwen series\n qwen2: {\n vision: false,\n tools: true,\n maxTokens: 32768,\n },\n \"qwen2.5-coder\": {\n vision: false,\n tools: true,\n maxTokens: 32768,\n },\n\n // LLaVA (vision)\n llava: {\n vision: true,\n tools: false,\n maxTokens: 4096,\n },\n\n // DeepSeek\n deepseek: {\n vision: false,\n tools: true,\n maxTokens: 16384,\n },\n \"deepseek-coder\": {\n vision: false,\n tools: false,\n maxTokens: 16384,\n },\n};\n\n// Default capabilities for unknown models\nconst DEFAULT_MODEL_CAPS: ModelCapabilities = {\n vision: false,\n tools: false,\n maxTokens: 4096,\n};\n\n// ============================================\n// Provider Implementation\n// ============================================\n\n/**\n * Create an Ollama provider (callable, Vercel AI SDK style)\n *\n * @example\n * ```typescript\n * const ollama = createOllama({ baseUrl: 'http://localhost:11434' });\n *\n * // Callable - Vercel AI SDK style\n * const model = ollama('llama3');\n *\n * // Also supports method call (backward compatible)\n * const model2 = ollama.languageModel('llama3');\n * ```\n */\nexport function createOllama(config: OllamaProviderConfig = {}): AIProvider {\n const baseUrl = config.baseUrl ?? \"http://localhost:11434\";\n\n // Create the callable function\n const providerFn = (modelId: string) => {\n return createOllamaAdapter({\n model: modelId,\n baseUrl,\n });\n };\n\n // Get capabilities helper\n const getCapabilities = (modelId: string): ProviderCapabilities => {\n const baseModelName = modelId.split(\":\")[0];\n const model =\n OLLAMA_MODELS[modelId] ??\n OLLAMA_MODELS[baseModelName] ??\n DEFAULT_MODEL_CAPS;\n\n return {\n supportsVision: model.vision,\n supportsTools: model.tools,\n supportsThinking: false,\n supportsStreaming: true,\n supportsPDF: false,\n supportsAudio: false,\n supportsVideo: false,\n maxTokens: model.maxTokens,\n supportedImageTypes: model.vision\n ? [\"image/png\", \"image/jpeg\", \"image/gif\"]\n : [],\n supportsJsonMode: false,\n supportsSystemMessages: true,\n };\n };\n\n return createCallableProvider(providerFn, {\n name: \"ollama\",\n supportedModels: Object.keys(OLLAMA_MODELS),\n getCapabilities,\n });\n}\n\n// Alias for consistency\nexport const createOllamaProvider = createOllama;\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/core/utils.ts","../../../src/adapters/base.ts","../../../src/adapters/ollama.ts","../../../src/providers/types.ts","../../../src/providers/ollama/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Utility functions for llm-sdk\n */\n\n/**\n * Generate a unique ID with optional prefix\n */\nexport function generateId(prefix = \"id\"): string {\n return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Generate a message ID\n */\nexport function generateMessageId(): string {\n return generateId(\"msg\");\n}\n\n/**\n * Generate a conversation/thread ID\n */\nexport function generateThreadId(): string {\n return generateId(\"thread\");\n}\n\n/**\n * Generate a tool call ID\n */\nexport function generateToolCallId(): string {\n return generateId(\"call\");\n}\n","import type {\n Message,\n MessageAttachment,\n ActionDefinition,\n StreamEvent,\n LLMConfig,\n WebSearchConfig,\n} from \"../core/stream-events\";\nimport type { TokenUsage } from \"../core/types\";\n\n/**\n * Request-level LLM configuration overrides\n */\nexport interface RequestLLMConfig {\n model?: string;\n temperature?: number;\n maxTokens?: number;\n}\n\n/**\n * Chat completion request\n */\nexport interface ChatCompletionRequest {\n /** Conversation messages */\n messages: Message[];\n /**\n * Raw provider-formatted messages (for agent loop with tool calls)\n * When provided, these are used instead of converting from Message[]\n * This allows passing messages with tool_calls and tool role\n */\n rawMessages?: Array<Record<string, unknown>>;\n /** Available actions/tools */\n actions?: ActionDefinition[];\n /** System prompt */\n systemPrompt?: string;\n /** LLM configuration overrides */\n config?: RequestLLMConfig;\n /** Abort signal for cancellation */\n signal?: AbortSignal;\n /**\n * Enable native web search for the provider.\n * When true or configured, the provider's native search is enabled.\n */\n webSearch?: boolean | WebSearchConfig;\n}\n\n/**\n * Non-streaming completion result\n */\nexport interface CompletionResult {\n /** Text content */\n content: string;\n /** Tool calls */\n toolCalls: Array<{ id: string; name: string; args: Record<string, unknown> }>;\n /** Thinking content (if extended thinking enabled) */\n thinking?: string;\n /** Token usage for billing/tracking */\n usage?: TokenUsage;\n /** Raw provider response for debugging */\n rawResponse: Record<string, unknown>;\n}\n\n/**\n * Base LLM adapter interface\n */\nexport interface LLMAdapter {\n /** Provider name */\n readonly provider: string;\n\n /** Model name */\n readonly model: string;\n\n /**\n * Stream a chat completion\n */\n stream(request: ChatCompletionRequest): AsyncGenerator<StreamEvent>;\n\n /**\n * Non-streaming chat completion (for debugging/comparison)\n */\n complete?(request: ChatCompletionRequest): Promise<CompletionResult>;\n}\n\n/**\n * Adapter factory function type\n */\nexport type AdapterFactory = (config: LLMConfig) => LLMAdapter;\n\n/**\n * Convert messages to provider format (simple text only)\n */\nexport function formatMessages(\n messages: Message[],\n systemPrompt?: string,\n): Array<{ role: string; content: string }> {\n const formatted: Array<{ role: string; content: string }> = [];\n\n // Add system prompt if provided\n if (systemPrompt) {\n formatted.push({ role: \"system\", content: systemPrompt });\n }\n\n // Add conversation messages\n for (const msg of messages) {\n formatted.push({\n role: msg.role,\n content: msg.content ?? \"\",\n });\n }\n\n return formatted;\n}\n\n/**\n * Convert ActionParameter to JSON Schema format recursively\n */\nfunction parameterToJsonSchema(param: {\n type: string;\n description?: string;\n enum?: string[];\n items?: unknown;\n properties?: Record<string, unknown>;\n}): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n type: param.type,\n };\n\n if (param.description) {\n schema.description = param.description;\n }\n\n if (param.enum) {\n schema.enum = param.enum;\n }\n\n // Handle array items\n if (param.type === \"array\" && param.items) {\n schema.items = parameterToJsonSchema(\n param.items as {\n type: string;\n description?: string;\n enum?: string[];\n items?: unknown;\n properties?: Record<string, unknown>;\n },\n );\n }\n\n // Handle nested object properties\n if (param.type === \"object\" && param.properties) {\n schema.properties = Object.fromEntries(\n Object.entries(param.properties).map(([key, prop]) => [\n key,\n parameterToJsonSchema(\n prop as {\n type: string;\n description?: string;\n enum?: string[];\n items?: unknown;\n properties?: Record<string, unknown>;\n },\n ),\n ]),\n );\n }\n\n return schema;\n}\n\n/**\n * Convert actions to OpenAI tool format\n */\nexport function formatTools(actions: ActionDefinition[]): Array<{\n type: \"function\";\n function: {\n name: string;\n description: string;\n parameters: object;\n };\n}> {\n return actions.map((action) => ({\n type: \"function\" as const,\n function: {\n name: action.name,\n description: action.description,\n parameters: {\n type: \"object\",\n properties: action.parameters\n ? Object.fromEntries(\n Object.entries(action.parameters).map(([key, param]) => [\n key,\n parameterToJsonSchema(param),\n ]),\n )\n : {},\n required: action.parameters\n ? Object.entries(action.parameters)\n .filter(([, param]) => param.required)\n .map(([key]) => key)\n : [],\n },\n },\n }));\n}\n\n// ============================================\n// Vision/Multimodal Support\n// ============================================\n\n/**\n * Content block types for multimodal messages\n */\nexport type AnthropicContentBlock =\n | { type: \"text\"; text: string }\n | {\n type: \"image\";\n source:\n | {\n type: \"base64\";\n media_type: string;\n data: string;\n }\n | {\n type: \"url\";\n url: string;\n };\n }\n | {\n type: \"document\";\n source:\n | {\n type: \"base64\";\n media_type: string;\n data: string;\n }\n | {\n type: \"url\";\n url: string;\n };\n };\n\nexport type OpenAIContentBlock =\n | { type: \"text\"; text: string }\n | {\n type: \"image_url\";\n image_url: {\n url: string;\n detail?: \"low\" | \"high\" | \"auto\";\n };\n };\n\n/**\n * Check if a message has image attachments\n * Supports both new format (metadata.attachments) and legacy (attachments)\n */\nexport function hasImageAttachments(message: Message): boolean {\n const attachments = message.metadata?.attachments;\n return attachments?.some((a) => a.type === \"image\") ?? false;\n}\n\n/**\n * Check if a message has media attachments (images or PDFs)\n */\nexport function hasMediaAttachments(message: Message): boolean {\n const attachments = message.metadata?.attachments;\n return (\n attachments?.some(\n (a) =>\n a.type === \"image\" ||\n (a.type === \"file\" && a.mimeType === \"application/pdf\"),\n ) ?? false\n );\n}\n\n/**\n * Convert MessageAttachment to Anthropic image content block\n *\n * Anthropic format:\n * {\n * type: \"image\",\n * source: {\n * type: \"base64\",\n * media_type: \"image/png\",\n * data: \"base64data...\"\n * }\n * }\n */\nexport function attachmentToAnthropicImage(\n attachment: MessageAttachment,\n): AnthropicContentBlock | null {\n if (attachment.type !== \"image\") return null;\n\n // Use URL if available (cloud storage)\n if (attachment.url) {\n return {\n type: \"image\",\n source: {\n type: \"url\",\n url: attachment.url,\n },\n };\n }\n\n // Fall back to base64 data\n if (!attachment.data) return null;\n\n // Extract base64 data (remove data URI prefix if present)\n let base64Data = attachment.data;\n if (base64Data.startsWith(\"data:\")) {\n const commaIndex = base64Data.indexOf(\",\");\n if (commaIndex !== -1) {\n base64Data = base64Data.slice(commaIndex + 1);\n }\n }\n\n return {\n type: \"image\",\n source: {\n type: \"base64\",\n media_type: attachment.mimeType || \"image/png\",\n data: base64Data,\n },\n };\n}\n\n/**\n * Convert MessageAttachment to OpenAI image_url content block\n *\n * OpenAI format:\n * {\n * type: \"image_url\",\n * image_url: {\n * url: \"data:image/png;base64,...\"\n * }\n * }\n */\nexport function attachmentToOpenAIImage(\n attachment: MessageAttachment,\n): OpenAIContentBlock | null {\n if (attachment.type !== \"image\") return null;\n\n let imageUrl: string;\n\n // Use URL if available (cloud storage)\n if (attachment.url) {\n imageUrl = attachment.url;\n } else if (attachment.data) {\n // Build data URI if not already one\n imageUrl = attachment.data.startsWith(\"data:\")\n ? attachment.data\n : `data:${attachment.mimeType || \"image/png\"};base64,${attachment.data}`;\n } else {\n return null;\n }\n\n return {\n type: \"image_url\",\n image_url: {\n url: imageUrl,\n detail: \"auto\",\n },\n };\n}\n\n/**\n * Convert MessageAttachment (PDF) to Anthropic document content block\n *\n * Anthropic format:\n * {\n * type: \"document\",\n * source: {\n * type: \"base64\",\n * media_type: \"application/pdf\",\n * data: \"base64data...\"\n * }\n * }\n */\nexport function attachmentToAnthropicDocument(\n attachment: MessageAttachment,\n): AnthropicContentBlock | null {\n // Only handle PDF files\n if (attachment.type !== \"file\" || attachment.mimeType !== \"application/pdf\") {\n return null;\n }\n\n // Use URL if available (cloud storage)\n if (attachment.url) {\n return {\n type: \"document\",\n source: {\n type: \"url\",\n url: attachment.url,\n },\n };\n }\n\n // Fall back to base64 data\n if (!attachment.data) return null;\n\n // Extract base64 data (remove data URI prefix if present)\n let base64Data = attachment.data;\n if (base64Data.startsWith(\"data:\")) {\n const commaIndex = base64Data.indexOf(\",\");\n if (commaIndex !== -1) {\n base64Data = base64Data.slice(commaIndex + 1);\n }\n }\n\n return {\n type: \"document\",\n source: {\n type: \"base64\",\n media_type: \"application/pdf\",\n data: base64Data,\n },\n };\n}\n\n/**\n * Convert a Message to Anthropic multimodal content blocks\n */\nexport function messageToAnthropicContent(\n message: Message,\n): string | AnthropicContentBlock[] {\n const attachments = message.metadata?.attachments;\n const content = message.content ?? \"\";\n\n // If no media attachments (images or PDFs), return simple string\n if (!hasMediaAttachments(message)) {\n return content;\n }\n\n // Build content blocks array\n const blocks: AnthropicContentBlock[] = [];\n\n // Add media attachments first (Claude recommends media before text)\n if (attachments) {\n for (const attachment of attachments) {\n // Try image first\n const imageBlock = attachmentToAnthropicImage(attachment);\n if (imageBlock) {\n blocks.push(imageBlock);\n continue;\n }\n // Try document (PDF)\n const docBlock = attachmentToAnthropicDocument(attachment);\n if (docBlock) {\n blocks.push(docBlock);\n }\n }\n }\n\n // Add text content\n if (content) {\n blocks.push({ type: \"text\", text: content });\n }\n\n return blocks;\n}\n\n/**\n * Convert a Message to OpenAI multimodal content blocks\n */\nexport function messageToOpenAIContent(\n message: Message,\n): string | OpenAIContentBlock[] {\n const attachments = message.metadata?.attachments;\n const content = message.content ?? \"\";\n\n // If no image attachments, return simple string\n if (!hasImageAttachments(message)) {\n return content;\n }\n\n // Build content blocks array\n const blocks: OpenAIContentBlock[] = [];\n\n // Add text content first\n if (content) {\n blocks.push({ type: \"text\", text: content });\n }\n\n // Add image attachments\n if (attachments) {\n for (const attachment of attachments) {\n const imageBlock = attachmentToOpenAIImage(attachment);\n if (imageBlock) {\n blocks.push(imageBlock);\n }\n }\n }\n\n return blocks;\n}\n\n/**\n * Anthropic content block types (extended for tools)\n */\nexport type AnthropicToolUseBlock = {\n type: \"tool_use\";\n id: string;\n name: string;\n input: Record<string, unknown>;\n};\n\nexport type AnthropicToolResultBlock = {\n type: \"tool_result\";\n tool_use_id: string;\n content: string;\n};\n\nexport type AnthropicMessageContent =\n | string\n | Array<\n AnthropicContentBlock | AnthropicToolUseBlock | AnthropicToolResultBlock\n >;\n\n/**\n * Format messages for Anthropic with full tool support\n * Handles: text, images, tool_use, and tool_result\n *\n * Key differences from OpenAI:\n * - tool_calls become tool_use blocks in assistant content\n * - tool results become tool_result blocks in user content\n */\nexport function formatMessagesForAnthropic(\n messages: Message[],\n systemPrompt?: string,\n): {\n system: string;\n messages: Array<{\n role: \"user\" | \"assistant\";\n content: AnthropicMessageContent;\n }>;\n} {\n const formatted: Array<{\n role: \"user\" | \"assistant\";\n content: AnthropicMessageContent;\n }> = [];\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n\n if (msg.role === \"system\") continue; // System handled separately\n\n if (msg.role === \"assistant\") {\n // Build content array for assistant\n const content: Array<AnthropicContentBlock | AnthropicToolUseBlock> = [];\n\n // Add text content if present\n if (msg.content) {\n content.push({ type: \"text\", text: msg.content });\n }\n\n // Convert tool_calls to tool_use blocks\n if (msg.tool_calls && msg.tool_calls.length > 0) {\n for (const tc of msg.tool_calls) {\n content.push({\n type: \"tool_use\",\n id: tc.id,\n name: tc.function.name,\n input: JSON.parse(tc.function.arguments),\n });\n }\n }\n\n formatted.push({\n role: \"assistant\",\n content:\n content.length === 1 && content[0].type === \"text\"\n ? (content[0] as { type: \"text\"; text: string }).text\n : content,\n });\n } else if (msg.role === \"tool\" && msg.tool_call_id) {\n // Tool results go in user message as tool_result blocks\n // Group consecutive tool messages together\n const toolResults: AnthropicToolResultBlock[] = [\n {\n type: \"tool_result\",\n tool_use_id: msg.tool_call_id,\n content: msg.content ?? \"\",\n },\n ];\n\n // Look ahead for more consecutive tool messages\n while (i + 1 < messages.length && messages[i + 1].role === \"tool\") {\n i++;\n const nextTool = messages[i];\n if (nextTool.tool_call_id) {\n toolResults.push({\n type: \"tool_result\",\n tool_use_id: nextTool.tool_call_id,\n content: nextTool.content ?? \"\",\n });\n }\n }\n\n formatted.push({\n role: \"user\",\n content: toolResults,\n });\n } else if (msg.role === \"user\") {\n formatted.push({\n role: \"user\",\n content: messageToAnthropicContent(msg),\n });\n }\n }\n\n return {\n system: systemPrompt || \"\",\n messages: formatted,\n };\n}\n\n/**\n * OpenAI message format with tool support\n */\nexport type OpenAIMessage =\n | { role: \"system\"; content: string }\n | { role: \"user\"; content: string | OpenAIContentBlock[] }\n | {\n role: \"assistant\";\n content: string | null;\n tool_calls?: Array<{\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }>;\n }\n | { role: \"tool\"; content: string; tool_call_id: string };\n\n/**\n * Format messages for OpenAI with full tool support\n * Handles: text, images, tool_calls, and tool results\n */\nexport function formatMessagesForOpenAI(\n messages: Message[],\n systemPrompt?: string,\n): OpenAIMessage[] {\n const formatted: OpenAIMessage[] = [];\n\n // Add system prompt if provided\n if (systemPrompt) {\n formatted.push({ role: \"system\", content: systemPrompt });\n }\n\n for (const msg of messages) {\n if (msg.role === \"system\") {\n formatted.push({ role: \"system\", content: msg.content ?? \"\" });\n } else if (msg.role === \"user\") {\n formatted.push({\n role: \"user\",\n content: messageToOpenAIContent(msg),\n });\n } else if (msg.role === \"assistant\") {\n const assistantMsg: OpenAIMessage = {\n role: \"assistant\",\n content: msg.content,\n };\n if (msg.tool_calls && msg.tool_calls.length > 0) {\n (assistantMsg as { tool_calls: typeof msg.tool_calls }).tool_calls =\n msg.tool_calls;\n }\n formatted.push(assistantMsg);\n } else if (msg.role === \"tool\" && msg.tool_call_id) {\n formatted.push({\n role: \"tool\",\n content: msg.content ?? \"\",\n tool_call_id: msg.tool_call_id,\n });\n }\n }\n\n return formatted;\n}\n","import type {\n StreamEvent,\n Message,\n MessageAttachment,\n} from \"../core/stream-events\";\nimport { generateMessageId, generateToolCallId } from \"../core/utils\";\nimport type { LLMAdapter, ChatCompletionRequest } from \"./base\";\nimport { formatMessages, formatTools } from \"./base\";\nimport type { OllamaModelOptions } from \"../providers/types\";\n\n/**\n * Ollama adapter configuration\n */\nexport interface OllamaAdapterConfig {\n model?: string;\n baseUrl?: string;\n temperature?: number;\n maxTokens?: number;\n /** Ollama-specific model options */\n options?: OllamaModelOptions;\n}\n\n/**\n * Ollama message format with vision support\n */\ninterface OllamaMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string;\n images?: string[];\n tool_calls?: Array<{\n id?: string;\n function: {\n name: string;\n arguments: Record<string, unknown>;\n };\n }>;\n tool_call_id?: string;\n}\n\n/**\n * Extract base64 data from attachment\n * Removes data URI prefix if present\n */\nfunction extractBase64Data(data: string): string {\n if (data.startsWith(\"data:\")) {\n const commaIndex = data.indexOf(\",\");\n if (commaIndex !== -1) {\n return data.slice(commaIndex + 1);\n }\n }\n return data;\n}\n\n/**\n * Download image from URL and convert to base64\n * Following Vercel AI SDK pattern\n */\nasync function downloadImageAsBase64(url: string): Promise<string | null> {\n try {\n const response = await fetch(url);\n if (!response.ok) return null;\n\n const buffer = await response.arrayBuffer();\n const base64 = Buffer.from(buffer).toString(\"base64\");\n return base64;\n } catch {\n return null;\n }\n}\n\n/**\n * Extract images from message attachments\n * Returns array of base64 strings (Ollama format)\n */\nasync function extractImagesFromAttachments(\n attachments?: MessageAttachment[],\n): Promise<string[]> {\n if (!attachments) return [];\n\n const images: string[] = [];\n\n for (const attachment of attachments) {\n if (attachment.type !== \"image\") continue;\n\n if (attachment.data) {\n // Use base64 data directly\n images.push(extractBase64Data(attachment.data));\n } else if (attachment.url) {\n // Download image from URL (like Vercel AI SDK does)\n const base64 = await downloadImageAsBase64(attachment.url);\n if (base64) {\n images.push(base64);\n }\n }\n }\n\n return images;\n}\n\n/**\n * Format messages for Ollama with vision and tool support\n */\nasync function formatMessagesForOllama(\n messages: Message[],\n systemPrompt?: string,\n): Promise<OllamaMessage[]> {\n const formatted: OllamaMessage[] = [];\n\n // Add system prompt if provided\n if (systemPrompt) {\n formatted.push({ role: \"system\", content: systemPrompt });\n }\n\n for (const msg of messages) {\n const baseMessage: OllamaMessage = {\n role: msg.role as OllamaMessage[\"role\"],\n content: msg.content ?? \"\",\n };\n\n // Handle image attachments (vision support)\n if (msg.role === \"user\" && msg.metadata?.attachments) {\n const images = await extractImagesFromAttachments(\n msg.metadata.attachments,\n );\n if (images.length > 0) {\n baseMessage.images = images;\n }\n }\n\n // Handle tool calls on assistant messages\n if (\n msg.role === \"assistant\" &&\n msg.tool_calls &&\n msg.tool_calls.length > 0\n ) {\n baseMessage.tool_calls = msg.tool_calls.map((tc) => ({\n id: tc.id,\n function: {\n name: tc.function.name,\n arguments: JSON.parse(tc.function.arguments),\n },\n }));\n }\n\n // Handle tool results\n if (msg.role === \"tool\" && msg.tool_call_id) {\n baseMessage.tool_call_id = msg.tool_call_id;\n }\n\n formatted.push(baseMessage);\n }\n\n return formatted;\n}\n\n/**\n * Process raw messages for Ollama\n * Handles conversion of attachments to Ollama image format\n */\nasync function processRawMessagesForOllama(\n rawMessages: Array<Record<string, unknown>>,\n systemPrompt?: string,\n): Promise<OllamaMessage[]> {\n const processed: OllamaMessage[] = [];\n\n // Add system prompt if provided and not already present\n if (systemPrompt) {\n const hasSystem = rawMessages.some((m) => m.role === \"system\");\n if (!hasSystem) {\n processed.push({ role: \"system\", content: systemPrompt });\n }\n }\n\n for (const msg of rawMessages) {\n const baseMessage: OllamaMessage = {\n role: msg.role as OllamaMessage[\"role\"],\n content: (msg.content as string) ?? \"\",\n };\n\n // Handle attachments for vision\n if (\n msg.role === \"user\" &&\n msg.attachments &&\n Array.isArray(msg.attachments)\n ) {\n const attachments = msg.attachments as MessageAttachment[];\n const images = await extractImagesFromAttachments(attachments);\n if (images.length > 0) {\n baseMessage.images = images;\n }\n }\n\n // Handle tool calls\n if (msg.tool_calls && Array.isArray(msg.tool_calls)) {\n baseMessage.tool_calls = (\n msg.tool_calls as Array<{\n id: string;\n type: string;\n function: { name: string; arguments: string };\n }>\n ).map((tc) => ({\n id: tc.id,\n function: {\n name: tc.function.name,\n arguments:\n typeof tc.function.arguments === \"string\"\n ? JSON.parse(tc.function.arguments)\n : tc.function.arguments,\n },\n }));\n }\n\n // Handle tool result\n if (msg.tool_call_id) {\n baseMessage.tool_call_id = msg.tool_call_id as string;\n }\n\n processed.push(baseMessage);\n }\n\n return processed;\n}\n\n/**\n * Ollama LLM Adapter (Local models)\n *\n * Supports: Llama 3, Mistral, Phi, Gemma, CodeLlama, LLaVA (vision), etc.\n *\n * Features:\n * - Tool/function calling (OpenAI-compatible format)\n * - Vision support (images via base64)\n * - Ollama-specific options (num_ctx, mirostat, etc.)\n * - Raw message support for agent loop\n */\nexport class OllamaAdapter implements LLMAdapter {\n readonly provider = \"ollama\";\n readonly model: string;\n\n private baseUrl: string;\n private config: OllamaAdapterConfig;\n\n constructor(config: OllamaAdapterConfig = {}) {\n this.config = config;\n this.model = config.model || \"llama3\";\n this.baseUrl = config.baseUrl || \"http://localhost:11434\";\n }\n\n async *stream(request: ChatCompletionRequest): AsyncGenerator<StreamEvent> {\n // Prepare messages - use rawMessages if provided (for agent loop), otherwise format from Message[]\n let messages: OllamaMessage[];\n\n if (request.rawMessages && request.rawMessages.length > 0) {\n messages = await processRawMessagesForOllama(\n request.rawMessages,\n request.systemPrompt,\n );\n } else {\n messages = await formatMessagesForOllama(\n request.messages,\n request.systemPrompt,\n );\n }\n\n // Format tools for Ollama (OpenAI-compatible format)\n const tools = request.actions?.length\n ? formatTools(request.actions)\n : undefined;\n\n const messageId = generateMessageId();\n\n // Emit message start\n yield { type: \"message:start\", id: messageId };\n\n try {\n // Build Ollama options\n const ollamaOptions: Record<string, unknown> = {\n temperature: request.config?.temperature ?? this.config.temperature,\n };\n\n // Add num_predict for maxTokens\n const maxTokens = request.config?.maxTokens ?? this.config.maxTokens;\n if (maxTokens !== undefined) {\n ollamaOptions.num_predict = maxTokens;\n }\n\n // Merge in Ollama-specific options\n if (this.config.options) {\n Object.assign(ollamaOptions, this.config.options);\n }\n\n const response = await fetch(`${this.baseUrl}/api/chat`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model: request.config?.model || this.model,\n messages,\n tools,\n stream: true,\n options: ollamaOptions,\n }),\n signal: request.signal,\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n throw new Error(`Ollama API error: ${response.status} - ${errorText}`);\n }\n\n if (!response.body) {\n throw new Error(\"No response body\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let hasEmittedToolCalls = false;\n\n // Track accumulated tool calls for streaming\n const toolCallsInProgress: Map<\n number,\n { id: string; name: string; arguments: Record<string, unknown> }\n > = new Map();\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const chunk = JSON.parse(line);\n\n // Handle content\n if (chunk.message?.content) {\n yield { type: \"message:delta\", content: chunk.message.content };\n }\n\n // Handle tool calls from Ollama\n if (chunk.message?.tool_calls && !hasEmittedToolCalls) {\n for (let i = 0; i < chunk.message.tool_calls.length; i++) {\n const toolCall = chunk.message.tool_calls[i];\n const toolCallId = toolCall.id || generateToolCallId();\n\n // Store the tool call\n toolCallsInProgress.set(i, {\n id: toolCallId,\n name: toolCall.function?.name || \"\",\n arguments: toolCall.function?.arguments || {},\n });\n\n // Emit action start\n yield {\n type: \"action:start\",\n id: toolCallId,\n name: toolCall.function?.name || \"\",\n };\n\n // Emit action args (Ollama sends complete args, not streamed)\n yield {\n type: \"action:args\",\n id: toolCallId,\n args: JSON.stringify(toolCall.function?.arguments || {}),\n };\n }\n hasEmittedToolCalls = true;\n }\n\n // Ollama indicates completion with done: true\n if (chunk.done) {\n break;\n }\n } catch {\n // Skip invalid JSON\n }\n }\n }\n\n yield { type: \"message:end\" };\n yield { type: \"done\" };\n } catch (error) {\n if ((error as Error).name === \"AbortError\") {\n yield { type: \"done\" };\n } else {\n const errorMessage =\n error instanceof Error ? error.message : \"Unknown error\";\n\n // Provide helpful error messages for common issues\n let code = \"OLLAMA_ERROR\";\n let message = errorMessage;\n\n if (\n errorMessage.includes(\"ECONNREFUSED\") ||\n errorMessage.includes(\"fetch failed\")\n ) {\n message = `Cannot connect to Ollama at ${this.baseUrl}. Make sure Ollama is running (ollama serve).`;\n code = \"OLLAMA_CONNECTION_ERROR\";\n } else if (\n errorMessage.includes(\"model\") &&\n errorMessage.includes(\"not found\")\n ) {\n message = `Model \"${this.model}\" not found. Pull it with: ollama pull ${this.model}`;\n code = \"OLLAMA_MODEL_NOT_FOUND\";\n }\n\n yield {\n type: \"error\",\n message,\n code,\n };\n }\n }\n }\n}\n\n/**\n * Create Ollama adapter\n */\nexport function createOllamaAdapter(\n config?: OllamaAdapterConfig,\n): OllamaAdapter {\n return new OllamaAdapter(config);\n}\n","/**\n * Provider Types\n *\n * Defines interfaces for:\n * 1. Provider Formatters (for tool transformations in agent loop)\n * 2. Multi-provider architecture (AIProvider, capabilities, configs)\n */\n\nimport type {\n ToolDefinition,\n UnifiedToolCall,\n UnifiedToolResult,\n} from \"../core/stream-events\";\nimport type { LLMAdapter } from \"../adapters/base\";\n\n// ============================================\n// Provider Formatter Types (for agent loop)\n// ============================================\n\n/**\n * Provider formatter interface\n *\n * Each provider implements this interface to handle:\n * - Tool definition transformation\n * - Tool call parsing from responses\n * - Tool result formatting\n * - Stop reason detection\n */\nexport interface ProviderFormatter {\n /**\n * Transform unified tool definitions to provider format\n */\n transformTools(tools: ToolDefinition[]): unknown[];\n\n /**\n * Parse tool calls from provider response\n */\n parseToolCalls(response: unknown): UnifiedToolCall[];\n\n /**\n * Format tool results for provider\n */\n formatToolResults(results: UnifiedToolResult[]): unknown[];\n\n /**\n * Check if response indicates tool use is requested\n */\n isToolUseStop(response: unknown): boolean;\n\n /**\n * Check if response indicates end of turn\n */\n isEndTurnStop(response: unknown): boolean;\n\n /**\n * Get stop reason string from response\n */\n getStopReason(response: unknown): string;\n\n /**\n * Extract text content from response\n */\n extractTextContent(response: unknown): string;\n\n /**\n * Build assistant message with tool calls for conversation history\n */\n buildAssistantToolMessage(\n toolCalls: UnifiedToolCall[],\n textContent?: string,\n ): unknown;\n\n /**\n * Build user message with tool results for conversation history\n */\n buildToolResultMessage(results: UnifiedToolResult[]): unknown;\n}\n\n// ============================================\n// Anthropic Tool Types\n// ============================================\n\n/**\n * Anthropic tool definition format\n */\nexport interface AnthropicTool {\n name: string;\n description: string;\n input_schema: {\n type: \"object\";\n properties: Record<string, unknown>;\n required?: string[];\n };\n}\n\n/**\n * Anthropic tool_use block from response\n */\nexport interface AnthropicToolUse {\n type: \"tool_use\";\n id: string;\n name: string;\n input: Record<string, unknown>;\n}\n\n/**\n * Anthropic tool_result block\n */\nexport interface AnthropicToolResult {\n type: \"tool_result\";\n tool_use_id: string;\n content: string;\n}\n\n// ============================================\n// OpenAI Tool Types\n// ============================================\n\n/**\n * OpenAI tool definition format\n */\nexport interface OpenAITool {\n type: \"function\";\n function: {\n name: string;\n description: string;\n parameters: {\n type: \"object\";\n properties: Record<string, unknown>;\n required?: string[];\n };\n };\n}\n\n/**\n * OpenAI tool call from response\n */\nexport interface OpenAIToolCall {\n id: string;\n type: \"function\";\n function: {\n name: string;\n arguments: string; // JSON string\n };\n}\n\n/**\n * OpenAI tool result message\n */\nexport interface OpenAIToolResult {\n role: \"tool\";\n tool_call_id: string;\n content: string;\n}\n\n// ============================================\n// Gemini Tool Types\n// ============================================\n\n/**\n * Google Gemini function declaration\n */\nexport interface GeminiFunctionDeclaration {\n name: string;\n description: string;\n parameters?: {\n type: \"object\";\n properties: Record<string, unknown>;\n required?: string[];\n };\n}\n\n/**\n * Gemini function call from response\n */\nexport interface GeminiFunctionCall {\n name: string;\n args: Record<string, unknown>;\n}\n\n/**\n * Gemini function response\n */\nexport interface GeminiFunctionResponse {\n name: string;\n response: Record<string, unknown>;\n}\n\n// ============================================\n// Provider Capabilities (for UI feature flags)\n// ============================================\n\n/**\n * Capabilities of a model for UI feature flags\n * UI components can use this to enable/disable features\n */\nexport interface ProviderCapabilities {\n /** Supports image inputs */\n supportsVision: boolean;\n /** Supports tool/function calling */\n supportsTools: boolean;\n /** Supports extended thinking (Claude, DeepSeek) */\n supportsThinking: boolean;\n /** Supports streaming responses */\n supportsStreaming: boolean;\n /** Supports PDF document inputs */\n supportsPDF: boolean;\n /** Supports audio inputs */\n supportsAudio: boolean;\n /** Supports video inputs */\n supportsVideo: boolean;\n /** Maximum context tokens */\n maxTokens: number;\n /** Supported image MIME types */\n supportedImageTypes: string[];\n /** Supported audio MIME types */\n supportedAudioTypes?: string[];\n /** Supported video MIME types */\n supportedVideoTypes?: string[];\n /** Supports JSON mode / structured output */\n supportsJsonMode?: boolean;\n /** Supports system messages */\n supportsSystemMessages?: boolean;\n}\n\n// ============================================\n// AI Provider Interface\n// ============================================\n\n/**\n * AI Provider interface (object form)\n *\n * Wraps existing LLMAdapter with additional metadata:\n * - Supported models list\n * - Per-model capabilities\n * - Provider name\n */\nexport interface AIProviderObject {\n /** Provider name (e.g., 'openai', 'anthropic') */\n readonly name: string;\n\n /** List of supported model IDs */\n readonly supportedModels: string[];\n\n /**\n * Get a language model adapter for the given model ID\n * Returns the existing LLMAdapter interface - no breaking changes\n */\n languageModel(modelId: string): LLMAdapter;\n\n /**\n * Get capabilities for a specific model\n * UI components use this to enable/disable features\n */\n getCapabilities(modelId: string): ProviderCapabilities;\n\n /**\n * Optional: Get an embedding model (future expansion)\n */\n embeddingModel?(modelId: string): EmbeddingModel;\n}\n\n/**\n * Callable AI Provider (Vercel AI SDK style)\n *\n * A function that returns a LanguageModel when called with a model ID,\n * but also has properties for provider metadata and methods.\n *\n * @example\n * ```typescript\n * const openai = createOpenAI({ apiKey: '...' });\n *\n * // Callable - returns LanguageModel directly (Vercel AI SDK style)\n * const model = openai('gpt-4o');\n *\n * // Also supports method calls (backward compatible)\n * const model2 = openai.languageModel('gpt-4o');\n *\n * // Check capabilities\n * const caps = openai.getCapabilities('gpt-4o');\n * if (caps.supportsVision) {\n * // Show image upload button\n * }\n * ```\n */\nexport interface AIProvider extends AIProviderObject {\n /**\n * Call the provider directly with a model ID to get a LanguageModel\n * This is the Vercel AI SDK style pattern\n */\n (modelId: string): LLMAdapter;\n}\n\n/**\n * Helper to create a callable AIProvider\n * Combines a callable function with AIProvider properties\n */\nexport function createCallableProvider(\n providerFn: (modelId: string) => LLMAdapter,\n properties: Omit<AIProviderObject, \"languageModel\">,\n): AIProvider {\n // Define 'name' property using defineProperty since it's read-only on functions\n Object.defineProperty(providerFn, \"name\", {\n value: properties.name,\n writable: false,\n configurable: true,\n });\n\n // Assign other properties\n Object.assign(providerFn, {\n supportedModels: properties.supportedModels,\n languageModel: providerFn,\n getCapabilities: properties.getCapabilities,\n embeddingModel: properties.embeddingModel,\n });\n\n return providerFn as AIProvider;\n}\n\n/**\n * Embedding model interface (for future expansion)\n */\nexport interface EmbeddingModel {\n readonly provider: string;\n readonly modelId: string;\n embed(texts: string[]): Promise<number[][]>;\n}\n\n// ============================================\n// Provider-Specific Configurations\n// ============================================\n\n/**\n * Base provider configuration\n */\nexport interface BaseProviderConfig {\n /** API key (falls back to environment variable) */\n apiKey?: string;\n /** Custom base URL */\n baseUrl?: string;\n /** Request timeout in milliseconds */\n timeout?: number;\n /** Custom headers to include */\n headers?: Record<string, string>;\n}\n\n/**\n * OpenAI provider configuration\n */\nexport interface OpenAIProviderConfig extends BaseProviderConfig {\n /** OpenAI organization ID */\n organization?: string;\n /** OpenAI project ID */\n project?: string;\n /** Vision detail level for images */\n imageDetail?: \"auto\" | \"low\" | \"high\";\n}\n\n/**\n * Anthropic provider configuration\n */\nexport interface AnthropicProviderConfig extends BaseProviderConfig {\n /** Extended thinking budget in tokens (minimum 1024) */\n thinkingBudget?: number;\n /** Enable prompt caching */\n cacheControl?: boolean;\n}\n\n/**\n * Google provider configuration\n */\nexport interface GoogleProviderConfig extends BaseProviderConfig {\n /** Safety settings */\n safetySettings?: GoogleSafetySetting[];\n /** Grounding configuration (for web search) */\n groundingConfig?: GoogleGroundingConfig;\n}\n\n/**\n * Google safety setting\n */\nexport interface GoogleSafetySetting {\n category:\n | \"HARM_CATEGORY_HARASSMENT\"\n | \"HARM_CATEGORY_HATE_SPEECH\"\n | \"HARM_CATEGORY_SEXUALLY_EXPLICIT\"\n | \"HARM_CATEGORY_DANGEROUS_CONTENT\";\n threshold:\n | \"BLOCK_NONE\"\n | \"BLOCK_LOW_AND_ABOVE\"\n | \"BLOCK_MEDIUM_AND_ABOVE\"\n | \"BLOCK_HIGH_AND_ABOVE\";\n}\n\n/**\n * Google grounding configuration\n */\nexport interface GoogleGroundingConfig {\n /** Enable Google Search grounding */\n googleSearchRetrieval?: boolean;\n}\n\n/**\n * xAI provider configuration\n */\nexport interface XAIProviderConfig extends BaseProviderConfig {\n // xAI uses OpenAI-compatible API, no extra config needed\n}\n\n/**\n * Azure OpenAI provider configuration\n */\nexport interface AzureProviderConfig extends BaseProviderConfig {\n /** Azure resource name */\n resourceName: string;\n /** Deployment name */\n deploymentName: string;\n /** API version (default: 2024-02-15-preview) */\n apiVersion?: string;\n}\n\n/**\n * Ollama model-specific options\n * These map to Ollama's native API options\n */\nexport interface OllamaModelOptions {\n /** Context window size (default varies by model) */\n num_ctx?: number;\n /** Max tokens to predict (-1 = infinite, -2 = fill context) */\n num_predict?: number;\n /** Mirostat sampling (0 = disabled, 1 = Mirostat, 2 = Mirostat 2.0) */\n mirostat?: 0 | 1 | 2;\n /** Mirostat learning rate (default: 0.1) */\n mirostat_eta?: number;\n /** Mirostat target entropy (default: 5.0) */\n mirostat_tau?: number;\n /** Repeat penalty (default: 1.1) */\n repeat_penalty?: number;\n /** Random seed for reproducibility (-1 = random) */\n seed?: number;\n /** Top-k sampling (default: 40) */\n top_k?: number;\n /** Top-p (nucleus) sampling (default: 0.9) */\n top_p?: number;\n /** Min-p sampling (default: 0.0) */\n min_p?: number;\n /** Stop sequences */\n stop?: string[];\n /** Temperature override (also available in config) */\n temperature?: number;\n}\n\n/**\n * Ollama provider configuration\n */\nexport interface OllamaProviderConfig extends BaseProviderConfig {\n /** Default Ollama-specific model options */\n options?: OllamaModelOptions;\n}\n\n// ============================================\n// Model Information\n// ============================================\n\n/**\n * Model information for a provider\n */\nexport interface ModelInfo {\n /** Model ID */\n id: string;\n /** Display name */\n name: string;\n /** Description */\n description?: string;\n /** Capabilities */\n capabilities: ProviderCapabilities;\n /** Context window size */\n contextWindow: number;\n /** Pricing info (optional) */\n pricing?: {\n inputPerMillion?: number;\n outputPerMillion?: number;\n };\n}\n\n// ============================================\n// Default Capabilities\n// ============================================\n\n/**\n * Default capabilities for unknown models\n */\nexport const DEFAULT_CAPABILITIES: ProviderCapabilities = {\n supportsVision: false,\n supportsTools: true,\n supportsThinking: false,\n supportsStreaming: true,\n supportsPDF: false,\n supportsAudio: false,\n supportsVideo: false,\n maxTokens: 8192,\n supportedImageTypes: [],\n supportsJsonMode: false,\n supportsSystemMessages: true,\n};\n","/**\n * Ollama Provider\n *\n * Wraps the existing OllamaAdapter with provider interface.\n * Ollama runs models locally on your machine.\n *\n * Features:\n * - Tool/function calling support\n * - Vision support (with LLaVA and other vision models)\n * - Ollama-specific options (num_ctx, mirostat, etc.)\n */\n\nimport { createOllamaAdapter } from \"../../adapters/ollama\";\nimport {\n createCallableProvider,\n type AIProvider,\n type ProviderCapabilities,\n type OllamaProviderConfig,\n type OllamaModelOptions,\n} from \"../types\";\n\n// ============================================\n// Model Definitions\n// ============================================\n\ninterface ModelCapabilities {\n vision: boolean;\n tools: boolean;\n maxTokens: number;\n}\n\n// Common Ollama models - users can run any model\nconst OLLAMA_MODELS: Record<string, ModelCapabilities> = {\n // Llama series\n llama3: {\n vision: false,\n tools: true,\n maxTokens: 8192,\n },\n \"llama3:70b\": {\n vision: false,\n tools: true,\n maxTokens: 8192,\n },\n \"llama3.1\": {\n vision: false,\n tools: true,\n maxTokens: 128000,\n },\n \"llama3.1:70b\": {\n vision: false,\n tools: true,\n maxTokens: 128000,\n },\n \"llama3.2\": {\n vision: false,\n tools: true,\n maxTokens: 128000,\n },\n \"llama3.2-vision\": {\n vision: true,\n tools: true,\n maxTokens: 128000,\n },\n\n // Mistral series\n mistral: {\n vision: false,\n tools: true,\n maxTokens: 8192,\n },\n \"mistral-nemo\": {\n vision: false,\n tools: true,\n maxTokens: 128000,\n },\n mixtral: {\n vision: false,\n tools: true,\n maxTokens: 32768,\n },\n\n // CodeLlama\n codellama: {\n vision: false,\n tools: false,\n maxTokens: 16384,\n },\n\n // Phi series\n phi3: {\n vision: false,\n tools: true,\n maxTokens: 4096,\n },\n \"phi3:medium\": {\n vision: false,\n tools: true,\n maxTokens: 4096,\n },\n\n // Gemma series\n gemma2: {\n vision: false,\n tools: false,\n maxTokens: 8192,\n },\n \"gemma2:27b\": {\n vision: false,\n tools: false,\n maxTokens: 8192,\n },\n\n // Qwen series\n qwen2: {\n vision: false,\n tools: true,\n maxTokens: 32768,\n },\n \"qwen2.5-coder\": {\n vision: false,\n tools: true,\n maxTokens: 32768,\n },\n\n // LLaVA (vision)\n llava: {\n vision: true,\n tools: false,\n maxTokens: 4096,\n },\n\n // DeepSeek\n deepseek: {\n vision: false,\n tools: true,\n maxTokens: 16384,\n },\n \"deepseek-coder\": {\n vision: false,\n tools: false,\n maxTokens: 16384,\n },\n};\n\n// Default capabilities for unknown models\nconst DEFAULT_MODEL_CAPS: ModelCapabilities = {\n vision: false,\n tools: false,\n maxTokens: 4096,\n};\n\n// ============================================\n// Provider Implementation\n// ============================================\n\n/**\n * Create an Ollama provider (callable, Vercel AI SDK style)\n *\n * @example\n * ```typescript\n * const ollama = createOllama({ baseUrl: 'http://localhost:11434' });\n *\n * // Callable - Vercel AI SDK style\n * const model = ollama('llama3');\n *\n * // Also supports method call (backward compatible)\n * const model2 = ollama.languageModel('llama3');\n * ```\n */\nexport function createOllama(config: OllamaProviderConfig = {}): AIProvider {\n const baseUrl = config.baseUrl ?? \"http://localhost:11434\";\n const defaultOptions = config.options;\n\n // Create the callable function\n const providerFn = (modelId: string, modelOptions?: OllamaModelOptions) => {\n return createOllamaAdapter({\n model: modelId,\n baseUrl,\n // Merge default options with model-specific options\n options: modelOptions\n ? { ...defaultOptions, ...modelOptions }\n : defaultOptions,\n });\n };\n\n // Get capabilities helper\n const getCapabilities = (modelId: string): ProviderCapabilities => {\n const baseModelName = modelId.split(\":\")[0];\n const model =\n OLLAMA_MODELS[modelId] ??\n OLLAMA_MODELS[baseModelName] ??\n DEFAULT_MODEL_CAPS;\n\n return {\n supportsVision: model.vision,\n supportsTools: model.tools,\n supportsThinking: false,\n supportsStreaming: true,\n supportsPDF: false,\n supportsAudio: false,\n supportsVideo: false,\n maxTokens: model.maxTokens,\n supportedImageTypes: model.vision\n ? [\"image/png\", \"image/jpeg\", \"image/gif\"]\n : [],\n supportsJsonMode: false,\n supportsSystemMessages: true,\n };\n };\n\n return createCallableProvider(providerFn, {\n name: \"ollama\",\n supportedModels: Object.keys(OLLAMA_MODELS),\n getCapabilities,\n });\n}\n\n// Alias for consistency\nexport const createOllamaProvider = createOllama;\n\n// Re-export types for convenience\nexport type { OllamaProviderConfig, OllamaModelOptions } from \"../types\";\n"]}
|