@sulala/agent 0.1.6 → 0.1.8
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/README.md +42 -27
- package/context/airtable.md +35 -0
- package/context/asana.md +37 -0
- package/context/bluesky.md +26 -91
- package/context/calendar.md +63 -0
- package/context/country-info.md +13 -0
- package/context/create-skill.md +128 -0
- package/context/discord.md +30 -0
- package/context/docs.md +29 -0
- package/context/drive.md +49 -0
- package/context/dropbox.md +39 -0
- package/context/facebook.md +47 -0
- package/context/fetch-form-api.md +16 -0
- package/context/figma.md +30 -0
- package/context/github.md +58 -0
- package/context/gmail.md +52 -0
- package/context/google.md +28 -0
- package/context/hellohub.md +29 -0
- package/context/jira.md +46 -0
- package/context/linear.md +40 -0
- package/context/notion.md +45 -0
- package/context/portal-integrations.md +42 -0
- package/context/post-to-x.md +50 -0
- package/context/sheets.md +47 -0
- package/context/slack.md +48 -0
- package/context/slides.md +35 -0
- package/context/stripe.md +38 -0
- package/context/tes.md +7 -0
- package/context/test.md +7 -0
- package/context/zoom.md +28 -0
- package/dist/agent/google/calendar.d.ts +2 -0
- package/dist/agent/google/calendar.d.ts.map +1 -0
- package/dist/agent/google/calendar.js +119 -0
- package/dist/agent/google/calendar.js.map +1 -0
- package/dist/agent/google/drive.d.ts +2 -0
- package/dist/agent/google/drive.d.ts.map +1 -0
- package/dist/agent/google/drive.js +51 -0
- package/dist/agent/google/drive.js.map +1 -0
- package/dist/agent/google/get-token.d.ts +7 -0
- package/dist/agent/google/get-token.d.ts.map +1 -0
- package/dist/agent/google/get-token.js +37 -0
- package/dist/agent/google/get-token.js.map +1 -0
- package/dist/agent/google/gmail.d.ts +2 -0
- package/dist/agent/google/gmail.d.ts.map +1 -0
- package/dist/agent/google/gmail.js +138 -0
- package/dist/agent/google/gmail.js.map +1 -0
- package/dist/agent/google/index.d.ts +2 -0
- package/dist/agent/google/index.d.ts.map +1 -0
- package/dist/agent/google/index.js +13 -0
- package/dist/agent/google/index.js.map +1 -0
- package/dist/agent/loop.d.ts +8 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +226 -40
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/memory.d.ts +21 -0
- package/dist/agent/memory.d.ts.map +1 -0
- package/dist/agent/memory.js +33 -0
- package/dist/agent/memory.js.map +1 -0
- package/dist/agent/pending-actions.d.ts +21 -0
- package/dist/agent/pending-actions.d.ts.map +1 -0
- package/dist/agent/pending-actions.js +65 -0
- package/dist/agent/pending-actions.js.map +1 -0
- package/dist/agent/pi-runner.d.ts +27 -0
- package/dist/agent/pi-runner.d.ts.map +1 -0
- package/dist/agent/pi-runner.js +300 -0
- package/dist/agent/pi-runner.js.map +1 -0
- package/dist/agent/skill-generate.d.ts +63 -0
- package/dist/agent/skill-generate.d.ts.map +1 -0
- package/dist/agent/skill-generate.js +128 -0
- package/dist/agent/skill-generate.js.map +1 -0
- package/dist/agent/skill-install.d.ts.map +1 -1
- package/dist/agent/skill-install.js +80 -31
- package/dist/agent/skill-install.js.map +1 -1
- package/dist/agent/skill-templates.d.ts +17 -0
- package/dist/agent/skill-templates.d.ts.map +1 -0
- package/dist/agent/skill-templates.js +26 -0
- package/dist/agent/skill-templates.js.map +1 -0
- package/dist/agent/skills-config.d.ts +24 -2
- package/dist/agent/skills-config.d.ts.map +1 -1
- package/dist/agent/skills-config.js +108 -9
- package/dist/agent/skills-config.js.map +1 -1
- package/dist/agent/skills-watcher.js +1 -1
- package/dist/agent/skills.d.ts +9 -3
- package/dist/agent/skills.d.ts.map +1 -1
- package/dist/agent/skills.js +104 -9
- package/dist/agent/skills.js.map +1 -1
- package/dist/agent/tools.d.ts +25 -3
- package/dist/agent/tools.d.ts.map +1 -1
- package/dist/agent/tools.integrations.test.d.ts +2 -0
- package/dist/agent/tools.integrations.test.d.ts.map +1 -0
- package/dist/agent/tools.integrations.test.js +269 -0
- package/dist/agent/tools.integrations.test.js.map +1 -0
- package/dist/agent/tools.js +692 -39
- package/dist/agent/tools.js.map +1 -1
- package/dist/ai/orchestrator.d.ts +6 -1
- package/dist/ai/orchestrator.d.ts.map +1 -1
- package/dist/ai/orchestrator.js +499 -212
- package/dist/ai/orchestrator.js.map +1 -1
- package/dist/ai/pricing.d.ts +6 -0
- package/dist/ai/pricing.d.ts.map +1 -0
- package/dist/ai/pricing.js +39 -0
- package/dist/ai/pricing.js.map +1 -0
- package/dist/channels/discord.d.ts +15 -0
- package/dist/channels/discord.d.ts.map +1 -0
- package/dist/channels/discord.js +55 -0
- package/dist/channels/discord.js.map +1 -0
- package/dist/channels/stripe.d.ts +15 -0
- package/dist/channels/stripe.d.ts.map +1 -0
- package/dist/channels/stripe.js +58 -0
- package/dist/channels/stripe.js.map +1 -0
- package/dist/channels/telegram.d.ts +60 -0
- package/dist/channels/telegram.d.ts.map +1 -0
- package/dist/channels/telegram.js +562 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/cli.js +69 -11
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +91 -2
- package/dist/config.js.map +1 -1
- package/dist/db/index.d.ts +83 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +174 -2
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema.sql +35 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +1224 -29
- package/dist/gateway/server.js.map +1 -1
- package/dist/index.js +149 -6
- package/dist/index.js.map +1 -1
- package/dist/ollama-setup.d.ts +27 -0
- package/dist/ollama-setup.d.ts.map +1 -0
- package/dist/ollama-setup.js +191 -0
- package/dist/ollama-setup.js.map +1 -0
- package/dist/onboard-env.d.ts +1 -1
- package/dist/onboard-env.d.ts.map +1 -1
- package/dist/onboard-env.js +3 -0
- package/dist/onboard-env.js.map +1 -1
- package/dist/onboard.d.ts +3 -1
- package/dist/onboard.d.ts.map +1 -1
- package/dist/onboard.js +9 -4
- package/dist/onboard.js.map +1 -1
- package/dist/plugins/index.d.ts +10 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +32 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/redact.d.ts +15 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +56 -0
- package/dist/redact.js.map +1 -0
- package/dist/scheduler/cron.d.ts +21 -0
- package/dist/scheduler/cron.d.ts.map +1 -1
- package/dist/scheduler/cron.js +60 -0
- package/dist/scheduler/cron.js.map +1 -1
- package/dist/system-capabilities.d.ts +11 -0
- package/dist/system-capabilities.d.ts.map +1 -0
- package/dist/system-capabilities.js +109 -0
- package/dist/system-capabilities.js.map +1 -0
- package/dist/types.d.ts +62 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/watcher/index.d.ts +2 -0
- package/dist/watcher/index.d.ts.map +1 -1
- package/dist/watcher/index.js +31 -1
- package/dist/watcher/index.js.map +1 -1
- package/dist/workspace-automations.d.ts +16 -0
- package/dist/workspace-automations.d.ts.map +1 -0
- package/dist/workspace-automations.js +133 -0
- package/dist/workspace-automations.js.map +1 -0
- package/package.json +19 -3
- package/registry/bluesky.md +12 -89
- package/registry/skills-registry.json +6 -0
- package/src/db/schema.sql +35 -0
- package/src/index.ts +159 -6
package/dist/ai/orchestrator.js
CHANGED
|
@@ -1,23 +1,146 @@
|
|
|
1
1
|
import { log, saveAiResult } from '../db/index.js';
|
|
2
2
|
const providers = new Map();
|
|
3
|
-
const defaultProvider = process.env.AI_DEFAULT_PROVIDER || 'openai';
|
|
4
3
|
export function registerProvider(name, adapter) {
|
|
5
4
|
providers.set(name, adapter);
|
|
6
5
|
}
|
|
7
6
|
export function getProvider(name) {
|
|
8
|
-
const key = name ??
|
|
7
|
+
const key = name ?? process.env.AI_DEFAULT_PROVIDER ?? 'ollama';
|
|
9
8
|
const p = providers.get(key);
|
|
10
9
|
if (!p)
|
|
11
10
|
throw new Error(`Unknown AI provider: ${key}. Registered: ${[...providers.keys()].join(', ')}`);
|
|
12
11
|
return p;
|
|
13
12
|
}
|
|
14
|
-
/** Stream completion (OpenAI
|
|
13
|
+
/** Stream completion (OpenAI, OpenRouter, Ollama). Falls back to non-streaming for other providers. */
|
|
15
14
|
export async function completeStream(options, onChunk) {
|
|
16
|
-
const provider = options.provider ??
|
|
15
|
+
const provider = options.provider ?? process.env.AI_DEFAULT_PROVIDER ?? 'ollama';
|
|
17
16
|
const messages = options.messages ?? [];
|
|
18
17
|
const model = options.model;
|
|
19
18
|
const max_tokens = options.max_tokens ?? 1024;
|
|
20
19
|
const tools = options.tools;
|
|
20
|
+
if (provider === 'ollama' || provider === 'llama') {
|
|
21
|
+
// Tool calling: use non-streaming so tool_calls.arguments are complete (streaming can truncate and cause JSON.parse errors).
|
|
22
|
+
if (tools?.length) {
|
|
23
|
+
const result = await complete({ ...options, provider, messages, model, max_tokens, tools, signal: options.signal });
|
|
24
|
+
onChunk({ type: 'finish', content: result.content ?? '', tool_calls: result.tool_calls, usage: result.usage });
|
|
25
|
+
return { content: result.content ?? '', tool_calls: result.tool_calls, usage: result.usage };
|
|
26
|
+
}
|
|
27
|
+
const think = options.think !== false && ollamaSupportsThinking(model);
|
|
28
|
+
const OLLAMA_BASE = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
|
|
29
|
+
const OLLAMA_DEFAULT = process.env.AI_OLLAMA_DEFAULT_MODEL || 'llama3.2';
|
|
30
|
+
const url = `${OLLAMA_BASE}/api/chat`;
|
|
31
|
+
const ollamaMessages = toOllamaMessages(messages);
|
|
32
|
+
const body = {
|
|
33
|
+
model: model || OLLAMA_DEFAULT,
|
|
34
|
+
messages: ollamaMessages,
|
|
35
|
+
stream: true,
|
|
36
|
+
think,
|
|
37
|
+
options: { num_predict: max_tokens || 1024 },
|
|
38
|
+
};
|
|
39
|
+
if (tools?.length) {
|
|
40
|
+
body.tools = tools.map((t) => ({
|
|
41
|
+
type: 'function',
|
|
42
|
+
function: { name: t.name, description: t.description, parameters: t.parameters ?? {} },
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
let res = await fetch(url, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
body: JSON.stringify(body),
|
|
49
|
+
signal: options.signal,
|
|
50
|
+
});
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
const errText = await res.text();
|
|
53
|
+
let didRetryStream = false;
|
|
54
|
+
if (res.status === 400 && errText.includes('does not support tools') && body.tools) {
|
|
55
|
+
delete body.tools;
|
|
56
|
+
console.log('[Ollama] Model does not support tools; retrying without tools');
|
|
57
|
+
res = await fetch(url, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify(body),
|
|
61
|
+
signal: options.signal,
|
|
62
|
+
});
|
|
63
|
+
didRetryStream = true;
|
|
64
|
+
}
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
const retryErrText = didRetryStream ? await res.text() : errText;
|
|
67
|
+
if (res.status === 404 && (retryErrText.includes('not found') || retryErrText.includes('model'))) {
|
|
68
|
+
const { pullOllamaModel } = await import('../ollama-setup.js');
|
|
69
|
+
pullOllamaModel(model || OLLAMA_DEFAULT);
|
|
70
|
+
throw new Error(`Model "${model || OLLAMA_DEFAULT}" not found. Pulling now; try again in 1–2 min.`);
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`Ollama: ${res.status} ${retryErrText}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const reader = res.body?.getReader();
|
|
76
|
+
if (!reader) {
|
|
77
|
+
const result = await complete({ ...options, provider, messages, model, max_tokens, tools, signal: options.signal });
|
|
78
|
+
onChunk({ type: 'finish', content: result.content ?? '', tool_calls: result.tool_calls, usage: result.usage });
|
|
79
|
+
return { content: result.content ?? '', tool_calls: result.tool_calls, usage: result.usage };
|
|
80
|
+
}
|
|
81
|
+
const dec = new TextDecoder();
|
|
82
|
+
let buffer = '';
|
|
83
|
+
let content = '';
|
|
84
|
+
let usage;
|
|
85
|
+
let tool_calls;
|
|
86
|
+
try {
|
|
87
|
+
while (true) {
|
|
88
|
+
const { done, value } = await reader.read();
|
|
89
|
+
if (done)
|
|
90
|
+
break;
|
|
91
|
+
buffer += dec.decode(value, { stream: true });
|
|
92
|
+
const lines = buffer.split('\n');
|
|
93
|
+
buffer = lines.pop() ?? '';
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
if (!line.trim())
|
|
96
|
+
continue;
|
|
97
|
+
try {
|
|
98
|
+
const obj = JSON.parse(line);
|
|
99
|
+
if (obj.message?.thinking) {
|
|
100
|
+
onChunk({ type: 'thinking', delta: obj.message.thinking });
|
|
101
|
+
}
|
|
102
|
+
if (obj.message?.content) {
|
|
103
|
+
content += obj.message.content;
|
|
104
|
+
onChunk({ type: 'delta', content: obj.message.content });
|
|
105
|
+
}
|
|
106
|
+
if (obj.message?.tool_calls?.length) {
|
|
107
|
+
tool_calls = fromOllamaToolCalls(obj.message.tool_calls);
|
|
108
|
+
}
|
|
109
|
+
if (obj.done) {
|
|
110
|
+
if (obj.eval_count != null)
|
|
111
|
+
usage = { completion_tokens: obj.eval_count };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// skip malformed line
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (buffer.trim()) {
|
|
120
|
+
try {
|
|
121
|
+
const obj = JSON.parse(buffer);
|
|
122
|
+
if (obj.message?.thinking)
|
|
123
|
+
onChunk({ type: 'thinking', delta: obj.message.thinking });
|
|
124
|
+
if (obj.message?.content) {
|
|
125
|
+
content += obj.message.content;
|
|
126
|
+
onChunk({ type: 'delta', content: obj.message.content });
|
|
127
|
+
}
|
|
128
|
+
if (obj.message?.tool_calls?.length)
|
|
129
|
+
tool_calls = fromOllamaToolCalls(obj.message.tool_calls);
|
|
130
|
+
if (obj.done && obj.eval_count != null)
|
|
131
|
+
usage = { completion_tokens: obj.eval_count };
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// ignore
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
reader.releaseLock();
|
|
140
|
+
}
|
|
141
|
+
onChunk({ type: 'finish', content, tool_calls, usage });
|
|
142
|
+
return { content, tool_calls, usage };
|
|
143
|
+
}
|
|
21
144
|
const streamClient = provider === 'openai' ? openAiStreamClient : provider === 'openrouter' ? openRouterStreamClient : null;
|
|
22
145
|
const streamDefaultModel = provider === 'openai' ? OPENAI_DEFAULT : provider === 'openrouter' ? OPENROUTER_DEFAULT : '';
|
|
23
146
|
if (!streamClient || !streamDefaultModel) {
|
|
@@ -25,6 +148,12 @@ export async function completeStream(options, onChunk) {
|
|
|
25
148
|
onChunk({ type: 'finish', content: result.content ?? '', tool_calls: result.tool_calls, usage: result.usage });
|
|
26
149
|
return { content: result.content ?? '', tool_calls: result.tool_calls, usage: result.usage };
|
|
27
150
|
}
|
|
151
|
+
// Tool calling: use non-streaming so tool_calls.arguments are complete JSON (streaming can truncate and cause JSON.parse errors).
|
|
152
|
+
if (tools?.length) {
|
|
153
|
+
const result = await complete({ ...options, provider, messages, model, max_tokens, tools, signal: options.signal });
|
|
154
|
+
onChunk({ type: 'finish', content: result.content ?? '', tool_calls: result.tool_calls, usage: result.usage });
|
|
155
|
+
return { content: result.content ?? '', tool_calls: result.tool_calls, usage: result.usage };
|
|
156
|
+
}
|
|
28
157
|
const openAiMessages = messages.map((m) => {
|
|
29
158
|
if (!m.tool_calls?.length)
|
|
30
159
|
return m;
|
|
@@ -87,7 +216,7 @@ export async function completeStream(options, onChunk) {
|
|
|
87
216
|
let openAiStreamClient = null;
|
|
88
217
|
let openRouterStreamClient = null;
|
|
89
218
|
export async function complete(options = {}) {
|
|
90
|
-
const { provider =
|
|
219
|
+
const { provider = process.env.AI_DEFAULT_PROVIDER ?? 'ollama', model, messages, max_tokens = 1024, task_id = null, tools, signal, } = options;
|
|
91
220
|
const start = Date.now();
|
|
92
221
|
const adapter = getProvider(provider);
|
|
93
222
|
const id = `ai_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
@@ -96,8 +225,14 @@ export async function complete(options = {}) {
|
|
|
96
225
|
result = await adapter.complete({ model, messages: messages ?? [], max_tokens, tools, signal });
|
|
97
226
|
}
|
|
98
227
|
catch (err) {
|
|
99
|
-
|
|
228
|
+
const errObj = err;
|
|
229
|
+
const detail = errObj?.status && errObj?.error != null
|
|
230
|
+
? `${errObj.status} ${typeof errObj.error === 'object' && errObj.error && 'message' in errObj.error ? errObj.error.message : JSON.stringify(errObj.error)}`
|
|
231
|
+
: err instanceof Error ? err.message : String(err);
|
|
232
|
+
log('ai', 'error', `Completion failed: ${detail}`, {
|
|
100
233
|
provider,
|
|
234
|
+
model: model || adapter.defaultModel,
|
|
235
|
+
status: errObj?.status,
|
|
101
236
|
stack: err instanceof Error ? err.stack : undefined,
|
|
102
237
|
});
|
|
103
238
|
throw err;
|
|
@@ -127,234 +262,386 @@ function stubAdapter(defaultModel = 'stub') {
|
|
|
127
262
|
},
|
|
128
263
|
};
|
|
129
264
|
}
|
|
265
|
+
const OLLAMA_DEFAULT = process.env.AI_OLLAMA_DEFAULT_MODEL || 'llama3.2';
|
|
266
|
+
const OLLAMA_THINKING_MODELS = ['deepseek-r1', 'qwen3', 'deepseek-v3.1', 'gpt-oss'];
|
|
267
|
+
function ollamaSupportsThinking(model) {
|
|
268
|
+
const m = (model || OLLAMA_DEFAULT).toLowerCase();
|
|
269
|
+
return OLLAMA_THINKING_MODELS.some((t) => m === t || m.startsWith(t + ':'));
|
|
270
|
+
}
|
|
271
|
+
/** Convert agent messages to Ollama format (tool_calls use index/object args; tool results use tool_name). */
|
|
272
|
+
function toOllamaMessages(messages) {
|
|
273
|
+
return messages.map((m) => {
|
|
274
|
+
const base = {
|
|
275
|
+
role: m.role,
|
|
276
|
+
content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content ?? ''),
|
|
277
|
+
};
|
|
278
|
+
if (m.role === 'assistant' && m.tool_calls?.length) {
|
|
279
|
+
base.tool_calls = m.tool_calls.map((tc, i) => ({
|
|
280
|
+
type: 'function',
|
|
281
|
+
function: {
|
|
282
|
+
index: i,
|
|
283
|
+
name: tc.name,
|
|
284
|
+
arguments: (() => {
|
|
285
|
+
try {
|
|
286
|
+
return typeof tc.arguments === 'string' ? JSON.parse(tc.arguments) : (tc.arguments ?? {});
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return {};
|
|
290
|
+
}
|
|
291
|
+
})(),
|
|
292
|
+
},
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
if (m.role === 'tool') {
|
|
296
|
+
base.tool_name = m.name ?? '';
|
|
297
|
+
}
|
|
298
|
+
return base;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/** Parse Ollama tool_calls (arguments may be object or string) into agent format. */
|
|
302
|
+
function fromOllamaToolCalls(toolCalls) {
|
|
303
|
+
if (!toolCalls?.length)
|
|
304
|
+
return undefined;
|
|
305
|
+
return toolCalls.map((tc, i) => {
|
|
306
|
+
const fn = tc.function;
|
|
307
|
+
const name = fn?.name ?? '';
|
|
308
|
+
const args = fn?.arguments;
|
|
309
|
+
const argsStr = typeof args === 'string' ? args : JSON.stringify(args ?? {});
|
|
310
|
+
return { id: `ollama_${i}_${Date.now()}`, name, arguments: argsStr };
|
|
311
|
+
});
|
|
312
|
+
}
|
|
130
313
|
const OPENAI_DEFAULT = process.env.AI_OPENAI_DEFAULT_MODEL || 'gpt-4o-mini';
|
|
131
314
|
const OPENROUTER_DEFAULT = process.env.AI_OPENROUTER_DEFAULT_MODEL || 'openai/gpt-4o-mini';
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
315
|
+
async function registerAllProviders() {
|
|
316
|
+
providers.clear();
|
|
317
|
+
registerProvider('stub', stubAdapter('stub'));
|
|
318
|
+
if (process.env.OPENAI_API_KEY) {
|
|
319
|
+
try {
|
|
320
|
+
const mod = await import('openai').catch(() => ({}));
|
|
321
|
+
const OpenAI = mod.OpenAI;
|
|
322
|
+
if (OpenAI) {
|
|
323
|
+
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
324
|
+
openAiStreamClient = client;
|
|
325
|
+
registerProvider('openai', {
|
|
326
|
+
defaultModel: OPENAI_DEFAULT,
|
|
327
|
+
async complete({ model, messages, max_tokens, tools: toolsOpt, signal }) {
|
|
328
|
+
// OpenAI expects tool_calls as { id, type: "function", function: { name, arguments } }
|
|
329
|
+
const openAiMessages = (messages ?? []).map((m) => {
|
|
330
|
+
if (!m.tool_calls?.length)
|
|
331
|
+
return m;
|
|
332
|
+
return {
|
|
333
|
+
...m,
|
|
334
|
+
tool_calls: m.tool_calls.map((tc) => ({
|
|
335
|
+
id: tc.id,
|
|
336
|
+
type: 'function',
|
|
337
|
+
function: { name: tc.name, arguments: tc.arguments ?? '' },
|
|
338
|
+
})),
|
|
339
|
+
};
|
|
340
|
+
});
|
|
341
|
+
const createOpts = {
|
|
342
|
+
model: model || OPENAI_DEFAULT,
|
|
343
|
+
messages: openAiMessages,
|
|
344
|
+
max_tokens: max_tokens || 1024,
|
|
154
345
|
};
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
346
|
+
if (toolsOpt?.length) {
|
|
347
|
+
createOpts.tools = toolsOpt.map((t) => ({
|
|
348
|
+
type: 'function',
|
|
349
|
+
function: { name: t.name, description: t.description, parameters: t.parameters ?? {} },
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
const res = await client.chat.completions.create(createOpts, signal ? { signal } : undefined);
|
|
353
|
+
const choice = res.choices?.[0];
|
|
354
|
+
const msg = choice?.message;
|
|
355
|
+
const tool_calls = msg?.tool_calls?.map((tc) => ({
|
|
356
|
+
id: tc.id,
|
|
357
|
+
name: tc.function?.name ?? '',
|
|
358
|
+
arguments: tc.function?.arguments ?? '',
|
|
165
359
|
}));
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
usage: res.usage ?? {},
|
|
178
|
-
...(tool_calls?.length ? { tool_calls } : {}),
|
|
179
|
-
};
|
|
180
|
-
},
|
|
181
|
-
});
|
|
360
|
+
return {
|
|
361
|
+
content: msg?.content ?? '',
|
|
362
|
+
usage: res.usage ?? {},
|
|
363
|
+
...(tool_calls?.length ? { tool_calls } : {}),
|
|
364
|
+
};
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
// import failed
|
|
182
371
|
}
|
|
183
372
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
373
|
+
if (process.env.OPENROUTER_API_KEY) {
|
|
374
|
+
try {
|
|
375
|
+
const mod = await import('openai').catch(() => ({}));
|
|
376
|
+
const OpenAI = mod.OpenAI;
|
|
377
|
+
if (OpenAI) {
|
|
378
|
+
const client = new OpenAI({
|
|
379
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
380
|
+
baseURL: 'https://openrouter.ai/api/v1',
|
|
381
|
+
});
|
|
382
|
+
openRouterStreamClient = client;
|
|
383
|
+
registerProvider('openrouter', {
|
|
384
|
+
defaultModel: OPENROUTER_DEFAULT,
|
|
385
|
+
async complete({ model, messages, max_tokens, tools: toolsOpt, signal }) {
|
|
386
|
+
const openAiMessages = (messages ?? []).map((m) => {
|
|
387
|
+
if (!m.tool_calls?.length)
|
|
388
|
+
return m;
|
|
389
|
+
return {
|
|
390
|
+
...m,
|
|
391
|
+
tool_calls: m.tool_calls.map((tc) => ({
|
|
392
|
+
id: tc.id,
|
|
393
|
+
type: 'function',
|
|
394
|
+
function: { name: tc.name, arguments: tc.arguments ?? '' },
|
|
395
|
+
})),
|
|
396
|
+
};
|
|
397
|
+
});
|
|
398
|
+
const createOpts = {
|
|
399
|
+
model: model || OPENROUTER_DEFAULT,
|
|
400
|
+
messages: openAiMessages,
|
|
401
|
+
max_tokens: max_tokens || 1024,
|
|
211
402
|
};
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
403
|
+
if (toolsOpt?.length) {
|
|
404
|
+
createOpts.tools = toolsOpt.map((t) => ({
|
|
405
|
+
type: 'function',
|
|
406
|
+
function: { name: t.name, description: t.description, parameters: t.parameters ?? {} },
|
|
407
|
+
}));
|
|
408
|
+
}
|
|
409
|
+
const res = await client.chat.completions.create(createOpts, signal ? { signal } : undefined);
|
|
410
|
+
const choice = res.choices?.[0];
|
|
411
|
+
const msg = choice?.message;
|
|
412
|
+
const tool_calls = msg?.tool_calls?.map((tc) => ({
|
|
413
|
+
id: tc.id,
|
|
414
|
+
name: tc.function?.name ?? '',
|
|
415
|
+
arguments: tc.function?.arguments ?? '',
|
|
222
416
|
}));
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
usage: res.usage ?? {},
|
|
235
|
-
...(tool_calls?.length ? { tool_calls } : {}),
|
|
236
|
-
};
|
|
237
|
-
},
|
|
238
|
-
});
|
|
417
|
+
return {
|
|
418
|
+
content: msg?.content ?? '',
|
|
419
|
+
usage: res.usage ?? {},
|
|
420
|
+
...(tool_calls?.length ? { tool_calls } : {}),
|
|
421
|
+
};
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// import failed
|
|
239
428
|
}
|
|
240
429
|
}
|
|
241
|
-
|
|
242
|
-
|
|
430
|
+
if (!providers.has('openai')) {
|
|
431
|
+
registerProvider('openai', stubAdapter(OPENAI_DEFAULT));
|
|
243
432
|
}
|
|
244
|
-
|
|
245
|
-
if (
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
433
|
+
const CLAUDE_DEFAULT = process.env.AI_CLAUDE_DEFAULT_MODEL || 'claude-sonnet-4-6';
|
|
434
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
435
|
+
registerProvider('claude', {
|
|
436
|
+
defaultModel: CLAUDE_DEFAULT,
|
|
437
|
+
async complete({ model, messages, max_tokens }) {
|
|
438
|
+
const key = process.env.ANTHROPIC_API_KEY;
|
|
439
|
+
const systemMsg = messages.find((m) => m.role === 'system');
|
|
440
|
+
const chatMessages = messages.filter((m) => m.role !== 'system').map((m) => ({
|
|
441
|
+
role: m.role === 'assistant' ? 'assistant' : 'user',
|
|
442
|
+
content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
|
|
443
|
+
}));
|
|
444
|
+
const body = {
|
|
445
|
+
model: model || CLAUDE_DEFAULT,
|
|
446
|
+
max_tokens: max_tokens || 1024,
|
|
447
|
+
messages: chatMessages,
|
|
448
|
+
...(systemMsg && { system: typeof systemMsg.content === 'string' ? systemMsg.content : JSON.stringify(systemMsg.content) }),
|
|
449
|
+
};
|
|
450
|
+
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
451
|
+
method: 'POST',
|
|
452
|
+
headers: {
|
|
453
|
+
'Content-Type': 'application/json',
|
|
454
|
+
'x-api-key': key,
|
|
455
|
+
'anthropic-version': '2023-06-01',
|
|
456
|
+
},
|
|
457
|
+
body: JSON.stringify(body),
|
|
458
|
+
});
|
|
459
|
+
if (!res.ok) {
|
|
460
|
+
const err = await res.text();
|
|
461
|
+
throw new Error(`Anthropic API: ${res.status} ${err}`);
|
|
462
|
+
}
|
|
463
|
+
const data = (await res.json());
|
|
464
|
+
const content = data.content?.find((c) => c.type === 'text')?.text || '';
|
|
465
|
+
const usage = data.usage || {};
|
|
466
|
+
return { content, usage: { prompt_tokens: usage.input_tokens ?? 0, completion_tokens: usage.output_tokens ?? 0 } };
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
registerProvider('claude', stubAdapter(CLAUDE_DEFAULT));
|
|
472
|
+
}
|
|
473
|
+
const GEMINI_DEFAULT = process.env.AI_GEMINI_DEFAULT_MODEL || 'gemini-2.5-flash';
|
|
474
|
+
const GEMINI_KEY = process.env.GOOGLE_GEMINI_API_KEY || process.env.GEMINI_API_KEY;
|
|
475
|
+
if (GEMINI_KEY) {
|
|
476
|
+
registerProvider('gemini', {
|
|
477
|
+
defaultModel: GEMINI_DEFAULT,
|
|
478
|
+
async complete({ model, messages, max_tokens }) {
|
|
479
|
+
const modelId = model || GEMINI_DEFAULT;
|
|
480
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelId}:generateContent?key=${GEMINI_KEY}`;
|
|
481
|
+
const contents = messages
|
|
482
|
+
.filter((m) => m.role !== 'system')
|
|
483
|
+
.map((m) => ({
|
|
484
|
+
role: (m.role === 'assistant' ? 'model' : 'user'),
|
|
485
|
+
parts: [{ text: typeof m.content === 'string' ? m.content : JSON.stringify(m.content) }],
|
|
486
|
+
}));
|
|
487
|
+
const systemInstruction = messages.find((m) => m.role === 'system');
|
|
488
|
+
const body = {
|
|
489
|
+
contents: contents.length ? contents : [{ role: 'user', parts: [{ text: '' }] }],
|
|
490
|
+
generationConfig: { maxOutputTokens: max_tokens || 1024 },
|
|
491
|
+
...(systemInstruction && {
|
|
492
|
+
systemInstruction: { parts: [{ text: typeof systemInstruction.content === 'string' ? systemInstruction.content : JSON.stringify(systemInstruction.content) }] },
|
|
493
|
+
}),
|
|
494
|
+
};
|
|
495
|
+
const res = await fetch(url, {
|
|
496
|
+
method: 'POST',
|
|
497
|
+
headers: { 'Content-Type': 'application/json' },
|
|
498
|
+
body: JSON.stringify(body),
|
|
499
|
+
});
|
|
500
|
+
if (!res.ok) {
|
|
501
|
+
const err = await res.text();
|
|
502
|
+
throw new Error(`Gemini API: ${res.status} ${err}`);
|
|
503
|
+
}
|
|
504
|
+
const data = (await res.json());
|
|
505
|
+
const text = data.candidates?.[0]?.content?.parts?.[0]?.text ?? '';
|
|
506
|
+
const usage = data.usageMetadata || {};
|
|
507
|
+
return {
|
|
508
|
+
content: text,
|
|
509
|
+
usage: { prompt_tokens: usage.promptTokenCount ?? 0, completion_tokens: usage.candidatesTokenCount ?? 0 },
|
|
510
|
+
};
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
registerProvider('gemini', stubAdapter(GEMINI_DEFAULT));
|
|
516
|
+
}
|
|
517
|
+
const OLLAMA_DEFAULT = process.env.AI_OLLAMA_DEFAULT_MODEL || 'llama3.2';
|
|
518
|
+
const OLLAMA_BASE = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
|
|
519
|
+
const OLLAMA_THINKING_MODELS = ['deepseek-r1', 'qwen3', 'deepseek-v3.1', 'gpt-oss'];
|
|
520
|
+
function ollamaSupportsThinking(model) {
|
|
521
|
+
const m = (model || OLLAMA_DEFAULT).toLowerCase();
|
|
522
|
+
return OLLAMA_THINKING_MODELS.some((t) => m === t || m.startsWith(t + ':'));
|
|
523
|
+
}
|
|
524
|
+
/** Convert agent messages to Ollama format (tool_calls use index/object args; tool results use tool_name). */
|
|
525
|
+
function toOllamaMessages(messages) {
|
|
526
|
+
return messages.map((m) => {
|
|
527
|
+
const base = {
|
|
528
|
+
role: m.role,
|
|
529
|
+
content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content ?? ''),
|
|
264
530
|
};
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
531
|
+
if (m.role === 'assistant' && m.tool_calls?.length) {
|
|
532
|
+
base.tool_calls = m.tool_calls.map((tc, i) => ({
|
|
533
|
+
type: 'function',
|
|
534
|
+
function: {
|
|
535
|
+
index: i,
|
|
536
|
+
name: tc.name,
|
|
537
|
+
arguments: (() => {
|
|
538
|
+
try {
|
|
539
|
+
return typeof tc.arguments === 'string' ? JSON.parse(tc.arguments) : (tc.arguments ?? {});
|
|
540
|
+
}
|
|
541
|
+
catch {
|
|
542
|
+
return {};
|
|
543
|
+
}
|
|
544
|
+
})(),
|
|
545
|
+
},
|
|
546
|
+
}));
|
|
277
547
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
return
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}));
|
|
302
|
-
const systemInstruction = messages.find((m) => m.role === 'system');
|
|
548
|
+
if (m.role === 'tool') {
|
|
549
|
+
base.tool_name = m.name ?? '';
|
|
550
|
+
}
|
|
551
|
+
return base;
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
/** Parse Ollama tool_calls (arguments may be object or string) into agent format. */
|
|
555
|
+
function fromOllamaToolCalls(toolCalls) {
|
|
556
|
+
if (!toolCalls?.length)
|
|
557
|
+
return undefined;
|
|
558
|
+
return toolCalls.map((tc, i) => {
|
|
559
|
+
const fn = tc.function;
|
|
560
|
+
const name = fn?.name ?? '';
|
|
561
|
+
const args = fn?.arguments;
|
|
562
|
+
const argsStr = typeof args === 'string' ? args : JSON.stringify(args ?? {});
|
|
563
|
+
return { id: `ollama_${i}_${Date.now()}`, name, arguments: argsStr };
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
registerProvider('ollama', {
|
|
567
|
+
defaultModel: OLLAMA_DEFAULT,
|
|
568
|
+
async complete({ model, messages, max_tokens, think, tools: toolsOpt }) {
|
|
569
|
+
const url = `${OLLAMA_BASE}/api/chat`;
|
|
570
|
+
const ollamaMessages = toOllamaMessages(messages ?? []);
|
|
303
571
|
const body = {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
572
|
+
model: model || OLLAMA_DEFAULT,
|
|
573
|
+
messages: ollamaMessages,
|
|
574
|
+
options: { num_predict: max_tokens || 1024 },
|
|
575
|
+
stream: false,
|
|
576
|
+
think: think !== false && ollamaSupportsThinking(model),
|
|
309
577
|
};
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
578
|
+
if (toolsOpt?.length) {
|
|
579
|
+
body.tools = toolsOpt.map((t) => ({
|
|
580
|
+
type: 'function',
|
|
581
|
+
function: { name: t.name, description: t.description, parameters: t.parameters ?? {} },
|
|
582
|
+
}));
|
|
583
|
+
}
|
|
584
|
+
console.log('[Ollama] POST', url, 'model:', model || OLLAMA_DEFAULT, toolsOpt?.length ? `tools=${toolsOpt.length}` : '');
|
|
585
|
+
let res;
|
|
586
|
+
try {
|
|
587
|
+
res = await fetch(url, {
|
|
588
|
+
method: 'POST',
|
|
589
|
+
headers: { 'Content-Type': 'application/json' },
|
|
590
|
+
body: JSON.stringify(body),
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
catch (e) {
|
|
594
|
+
console.error('[Ollama] fetch failed:', e);
|
|
595
|
+
const cause = e?.cause;
|
|
596
|
+
if (cause?.code === 'ECONNREFUSED') {
|
|
597
|
+
throw new Error('Ollama is not running. Start it with: ollama serve (or open the Ollama app).');
|
|
598
|
+
}
|
|
599
|
+
throw e;
|
|
600
|
+
}
|
|
315
601
|
if (!res.ok) {
|
|
316
|
-
const
|
|
317
|
-
|
|
602
|
+
const errText = await res.text();
|
|
603
|
+
let didRetry = false;
|
|
604
|
+
if (res.status === 400 && errText.includes('does not support tools') && body.tools) {
|
|
605
|
+
delete body.tools;
|
|
606
|
+
console.log('[Ollama] Model does not support tools; retrying without tools');
|
|
607
|
+
res = await fetch(url, {
|
|
608
|
+
method: 'POST',
|
|
609
|
+
headers: { 'Content-Type': 'application/json' },
|
|
610
|
+
body: JSON.stringify(body),
|
|
611
|
+
});
|
|
612
|
+
didRetry = true;
|
|
613
|
+
}
|
|
614
|
+
if (!res.ok) {
|
|
615
|
+
const err = didRetry ? await res.text() : errText;
|
|
616
|
+
const modelName = model || OLLAMA_DEFAULT;
|
|
617
|
+
if (res.status === 404 && (err.includes('not found') || err.includes('model'))) {
|
|
618
|
+
const { pullOllamaModel } = await import('../ollama-setup.js');
|
|
619
|
+
pullOllamaModel(modelName);
|
|
620
|
+
throw new Error(`Model "${modelName}" not found. Pulling it now (see terminal); try again in 1–2 min. Or run: ollama pull ${modelName}`);
|
|
621
|
+
}
|
|
622
|
+
console.error('[Ollama]', res.status, err);
|
|
623
|
+
throw new Error(`Ollama: ${res.status} ${err}`);
|
|
624
|
+
}
|
|
318
625
|
}
|
|
319
626
|
const data = (await res.json());
|
|
320
|
-
const
|
|
321
|
-
const
|
|
627
|
+
const msg = data.message;
|
|
628
|
+
const thinking = msg?.thinking ?? '';
|
|
629
|
+
const content = msg?.content ?? '';
|
|
630
|
+
const fullContent = thinking ? thinking + (content ? '\n\n' + content : '') : content;
|
|
631
|
+
const tool_calls = fromOllamaToolCalls(msg?.tool_calls);
|
|
322
632
|
return {
|
|
323
|
-
content:
|
|
324
|
-
usage:
|
|
633
|
+
content: fullContent,
|
|
634
|
+
usage: data.eval_count != null ? { completion_tokens: data.eval_count } : undefined,
|
|
635
|
+
...(tool_calls?.length ? { tool_calls } : {}),
|
|
325
636
|
};
|
|
326
637
|
},
|
|
327
638
|
});
|
|
639
|
+
if (!providers.has('llama'))
|
|
640
|
+
registerProvider('llama', getProvider('ollama'));
|
|
328
641
|
}
|
|
329
|
-
|
|
330
|
-
|
|
642
|
+
await registerAllProviders();
|
|
643
|
+
/** Reload AI providers from current process.env (e.g. after onboarding saves new API keys). No restart needed. */
|
|
644
|
+
export async function reloadProviders() {
|
|
645
|
+
await registerAllProviders();
|
|
331
646
|
}
|
|
332
|
-
const OLLAMA_DEFAULT = process.env.AI_OLLAMA_DEFAULT_MODEL || 'llama3.2';
|
|
333
|
-
const OLLAMA_BASE = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
|
|
334
|
-
registerProvider('ollama', {
|
|
335
|
-
defaultModel: OLLAMA_DEFAULT,
|
|
336
|
-
async complete({ model, messages, max_tokens }) {
|
|
337
|
-
const url = `${OLLAMA_BASE}/api/chat`;
|
|
338
|
-
const body = {
|
|
339
|
-
model: model || OLLAMA_DEFAULT,
|
|
340
|
-
messages: messages.map((m) => ({ role: m.role, content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content) })),
|
|
341
|
-
options: { num_predict: max_tokens || 1024 },
|
|
342
|
-
stream: false,
|
|
343
|
-
};
|
|
344
|
-
const res = await fetch(url, {
|
|
345
|
-
method: 'POST',
|
|
346
|
-
headers: { 'Content-Type': 'application/json' },
|
|
347
|
-
body: JSON.stringify(body),
|
|
348
|
-
});
|
|
349
|
-
if (!res.ok) {
|
|
350
|
-
const err = await res.text();
|
|
351
|
-
throw new Error(`Ollama: ${res.status} ${err}`);
|
|
352
|
-
}
|
|
353
|
-
const data = (await res.json());
|
|
354
|
-
const content = data.message?.content || '';
|
|
355
|
-
return { content, usage: data.eval_count != null ? { completion_tokens: data.eval_count } : undefined };
|
|
356
|
-
},
|
|
357
|
-
});
|
|
358
|
-
if (!providers.has('llama'))
|
|
359
|
-
registerProvider('llama', getProvider('ollama'));
|
|
360
647
|
//# sourceMappingURL=orchestrator.js.map
|