@moraya/core 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -41
- package/dist/ai/drivers/claude.d.ts +6 -0
- package/dist/ai/drivers/claude.js +229 -0
- package/dist/ai/drivers/claude.js.map +1 -0
- package/dist/ai/drivers/gemini.d.ts +6 -0
- package/dist/ai/drivers/gemini.js +212 -0
- package/dist/ai/drivers/gemini.js.map +1 -0
- package/dist/ai/drivers/index.d.ts +14 -0
- package/dist/ai/drivers/index.js +617 -0
- package/dist/ai/drivers/index.js.map +1 -0
- package/dist/ai/drivers/ollama.d.ts +8 -0
- package/dist/ai/drivers/ollama.js +158 -0
- package/dist/ai/drivers/ollama.js.map +1 -0
- package/dist/ai/drivers/openai.d.ts +7 -0
- package/dist/ai/drivers/openai.js +225 -0
- package/dist/ai/drivers/openai.js.map +1 -0
- package/dist/ai/drivers/tool-bridge.d.ts +37 -0
- package/dist/ai/drivers/tool-bridge.js +138 -0
- package/dist/ai/drivers/tool-bridge.js.map +1 -0
- package/dist/ai/drivers/types.d.ts +2 -0
- package/dist/ai/drivers/types.js +1 -0
- package/dist/ai/drivers/types.js.map +1 -0
- package/dist/ai/drivers/util.d.ts +13 -0
- package/dist/ai/drivers/util.js +40 -0
- package/dist/ai/drivers/util.js.map +1 -0
- package/dist/ai/image.d.ts +37 -0
- package/dist/ai/image.js +36 -0
- package/dist/ai/image.js.map +1 -0
- package/dist/ai/index.d.ts +37 -0
- package/dist/ai/index.js +826 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/types.d.ts +92 -0
- package/dist/ai/types.js +1 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/ai/voice.d.ts +42 -0
- package/dist/ai/voice.js +34 -0
- package/dist/ai/voice.js.map +1 -0
- package/dist/chat-markdown/index.d.ts +82 -0
- package/dist/chat-markdown/index.js +165 -0
- package/dist/chat-markdown/index.js.map +1 -0
- package/dist/i18n/locales/ar.json +806 -732
- package/dist/i18n/locales/de.json +912 -838
- package/dist/i18n/locales/en.json +34 -5
- package/dist/i18n/locales/es.json +952 -876
- package/dist/i18n/locales/fr.json +1784 -1708
- package/dist/i18n/locales/hi.json +1808 -1734
- package/dist/i18n/locales/ja.json +839 -765
- package/dist/i18n/locales/ko.json +1783 -1709
- package/dist/i18n/locales/pt.json +894 -820
- package/dist/i18n/locales/ru.json +812 -738
- package/dist/i18n/locales/zh-CN.json +34 -5
- package/dist/i18n/locales/zh-Hant.json +1039 -965
- package/dist/types-CwM77g7u.d.ts +88 -0
- package/package.json +26 -2
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
// src/ai/drivers/tool-bridge.ts
|
|
2
|
+
var GEMINI_UNSUPPORTED_KEYS = /* @__PURE__ */ new Set([
|
|
3
|
+
"additionalProperties",
|
|
4
|
+
"$schema",
|
|
5
|
+
"$id",
|
|
6
|
+
"$ref",
|
|
7
|
+
"$defs",
|
|
8
|
+
"definitions",
|
|
9
|
+
"patternProperties",
|
|
10
|
+
"unevaluatedProperties",
|
|
11
|
+
"dependentRequired",
|
|
12
|
+
"dependentSchemas",
|
|
13
|
+
"const"
|
|
14
|
+
]);
|
|
15
|
+
function sanitizeGeminiSchema(schema) {
|
|
16
|
+
if (Array.isArray(schema)) return schema.map(sanitizeGeminiSchema);
|
|
17
|
+
if (schema && typeof schema === "object") {
|
|
18
|
+
const out = {};
|
|
19
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
20
|
+
if (GEMINI_UNSUPPORTED_KEYS.has(key)) continue;
|
|
21
|
+
out[key] = sanitizeGeminiSchema(value);
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
return schema;
|
|
26
|
+
}
|
|
27
|
+
function formatToolsForProvider(provider, tools) {
|
|
28
|
+
if (tools.length === 0) return {};
|
|
29
|
+
switch (provider) {
|
|
30
|
+
case "claude":
|
|
31
|
+
return {
|
|
32
|
+
tools: tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.input_schema }))
|
|
33
|
+
};
|
|
34
|
+
case "gemini":
|
|
35
|
+
return {
|
|
36
|
+
tools: [{
|
|
37
|
+
functionDeclarations: tools.map((t) => ({
|
|
38
|
+
name: t.name,
|
|
39
|
+
description: t.description,
|
|
40
|
+
parameters: sanitizeGeminiSchema(t.input_schema)
|
|
41
|
+
}))
|
|
42
|
+
}]
|
|
43
|
+
};
|
|
44
|
+
default:
|
|
45
|
+
return {
|
|
46
|
+
tools: tools.map((t) => ({
|
|
47
|
+
type: "function",
|
|
48
|
+
function: { name: t.name, description: t.description, parameters: t.input_schema }
|
|
49
|
+
}))
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function parseClaudeToolCalls(data) {
|
|
54
|
+
const content = data.content;
|
|
55
|
+
const stopReason = data.stop_reason || "end_turn";
|
|
56
|
+
const toolCalls = [];
|
|
57
|
+
let textContent = "";
|
|
58
|
+
if (content) {
|
|
59
|
+
for (const block of content) {
|
|
60
|
+
if (block.type === "tool_use") {
|
|
61
|
+
toolCalls.push({ id: block.id, name: block.name, arguments: block.input || {} });
|
|
62
|
+
} else if (block.type === "text") {
|
|
63
|
+
textContent += block.text;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { toolCalls, textContent, stopReason };
|
|
68
|
+
}
|
|
69
|
+
function parseOpenAIToolCalls(data) {
|
|
70
|
+
const choices = data.choices;
|
|
71
|
+
if (!choices || choices.length === 0) return { toolCalls: [], textContent: "", stopReason: "stop" };
|
|
72
|
+
const choice = choices[0];
|
|
73
|
+
const message = choice.message;
|
|
74
|
+
const finishReason = choice.finish_reason || "stop";
|
|
75
|
+
const textContent = message?.content || "";
|
|
76
|
+
const toolCalls = [];
|
|
77
|
+
const rawToolCalls = message?.tool_calls;
|
|
78
|
+
if (rawToolCalls) {
|
|
79
|
+
for (const tc of rawToolCalls) {
|
|
80
|
+
const fn = tc.function;
|
|
81
|
+
let args = {};
|
|
82
|
+
try {
|
|
83
|
+
args = JSON.parse(fn.arguments);
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
toolCalls.push({ id: tc.id, name: fn.name, arguments: args });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { toolCalls, textContent, stopReason: finishReason === "tool_calls" ? "tool_use" : finishReason };
|
|
90
|
+
}
|
|
91
|
+
function parseGeminiToolCalls(data) {
|
|
92
|
+
const candidates = data.candidates;
|
|
93
|
+
if (!candidates || candidates.length === 0) return { toolCalls: [], textContent: "", stopReason: "stop" };
|
|
94
|
+
const content = candidates[0].content;
|
|
95
|
+
const parts = content?.parts;
|
|
96
|
+
const toolCalls = [];
|
|
97
|
+
let textContent = "";
|
|
98
|
+
if (parts) {
|
|
99
|
+
for (const part of parts) {
|
|
100
|
+
if (part.functionCall) {
|
|
101
|
+
const fc = part.functionCall;
|
|
102
|
+
const thoughtSignature = part.thoughtSignature ?? fc.thoughtSignature;
|
|
103
|
+
toolCalls.push({
|
|
104
|
+
id: `gemini-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
105
|
+
name: fc.name,
|
|
106
|
+
arguments: fc.args || {},
|
|
107
|
+
...thoughtSignature ? { providerMeta: { thoughtSignature } } : {}
|
|
108
|
+
});
|
|
109
|
+
} else if (part.text) {
|
|
110
|
+
textContent += part.text;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return { toolCalls, textContent, stopReason: toolCalls.length > 0 ? "tool_use" : "stop" };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/ai/catalog.ts
|
|
118
|
+
var PROVIDER_BASE_URLS = {
|
|
119
|
+
claude: "https://api.anthropic.com",
|
|
120
|
+
openai: "https://api.openai.com",
|
|
121
|
+
gemini: "https://generativelanguage.googleapis.com",
|
|
122
|
+
deepseek: "https://api.deepseek.com",
|
|
123
|
+
ollama: "http://localhost:11434",
|
|
124
|
+
grok: "https://api.x.ai",
|
|
125
|
+
mistral: "https://api.mistral.ai",
|
|
126
|
+
glm: "https://open.bigmodel.cn/api/paas/v4",
|
|
127
|
+
minimax: "https://api.minimax.io/v1",
|
|
128
|
+
doubao: "https://ark.cn-beijing.volces.com/api/v3",
|
|
129
|
+
custom: "",
|
|
130
|
+
"local-mlx": "",
|
|
131
|
+
"local-llama": ""
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// src/ai/drivers/util.ts
|
|
135
|
+
function resolveBaseUrl(config, fallback) {
|
|
136
|
+
return config.baseUrl || PROVIDER_BASE_URLS[config.provider] || fallback;
|
|
137
|
+
}
|
|
138
|
+
function openaiEndpoint(baseUrl, path) {
|
|
139
|
+
const clean = baseUrl.replace(/\/+$/, "");
|
|
140
|
+
if (/\/v\d+$/.test(clean)) return `${clean}${path}`;
|
|
141
|
+
return `${clean}/v1${path}`;
|
|
142
|
+
}
|
|
143
|
+
var NOOP_FOLD = {
|
|
144
|
+
pushEnvelope() {
|
|
145
|
+
return void 0;
|
|
146
|
+
},
|
|
147
|
+
finish() {
|
|
148
|
+
return { stopReason: "end_turn" };
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/ai/drivers/claude.ts
|
|
153
|
+
function buildClaudeMessages(messages) {
|
|
154
|
+
const result = [];
|
|
155
|
+
for (const msg of messages) {
|
|
156
|
+
if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
157
|
+
const content = [];
|
|
158
|
+
if (msg.content) content.push({ type: "text", text: msg.content });
|
|
159
|
+
for (const tc of msg.toolCalls) content.push({ type: "tool_use", id: tc.id, name: tc.name, input: tc.arguments });
|
|
160
|
+
result.push({ role: "assistant", content });
|
|
161
|
+
} else if (msg.role === "tool") {
|
|
162
|
+
const lastMsg = result[result.length - 1];
|
|
163
|
+
const toolResultBlock = { type: "tool_result", tool_use_id: msg.toolCallId, content: msg.content, is_error: msg.isError || false };
|
|
164
|
+
if (lastMsg && lastMsg.role === "user" && Array.isArray(lastMsg.content) && lastMsg.content.every((b) => b.type === "tool_result")) {
|
|
165
|
+
lastMsg.content.push(toolResultBlock);
|
|
166
|
+
} else {
|
|
167
|
+
result.push({ role: "user", content: [toolResultBlock] });
|
|
168
|
+
}
|
|
169
|
+
} else if (msg.role === "user" && msg.images && msg.images.length > 0) {
|
|
170
|
+
const content = [];
|
|
171
|
+
for (const img of msg.images) content.push({ type: "image", source: { type: "base64", media_type: img.mimeType, data: img.base64 } });
|
|
172
|
+
if (msg.content) content.push({ type: "text", text: msg.content });
|
|
173
|
+
result.push({ role: "user", content });
|
|
174
|
+
} else {
|
|
175
|
+
result.push({ role: msg.role, content: msg.content });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
var claudeDriver = {
|
|
181
|
+
supportsStreaming: true,
|
|
182
|
+
buildChatRequest(config, request, stream) {
|
|
183
|
+
const baseUrl = resolveBaseUrl(config, "https://api.anthropic.com");
|
|
184
|
+
const systemMessages = request.messages.filter((m) => m.role === "system");
|
|
185
|
+
const chatMessages = request.messages.filter((m) => m.role !== "system");
|
|
186
|
+
const body = {
|
|
187
|
+
model: request.model || config.model,
|
|
188
|
+
max_tokens: request.maxTokens ?? config.maxTokens ?? 41920,
|
|
189
|
+
messages: buildClaudeMessages(chatMessages)
|
|
190
|
+
};
|
|
191
|
+
if (stream) body.stream = true;
|
|
192
|
+
if (systemMessages.length > 0) body.system = systemMessages.map((m) => m.content).join("\n");
|
|
193
|
+
const temperature = request.temperature ?? config.temperature;
|
|
194
|
+
if (temperature !== void 0) body.temperature = temperature;
|
|
195
|
+
const topP = request.topP ?? config.topP;
|
|
196
|
+
if (topP !== void 0) body.top_p = topP;
|
|
197
|
+
if (request.stop && request.stop.length > 0) body.stop_sequences = request.stop.slice(0, 5);
|
|
198
|
+
if (request.tools && request.tools.length > 0) Object.assign(body, formatToolsForProvider("claude", request.tools));
|
|
199
|
+
return {
|
|
200
|
+
provider: "claude",
|
|
201
|
+
configId: config.id,
|
|
202
|
+
method: "POST",
|
|
203
|
+
url: `${baseUrl}/v1/messages`,
|
|
204
|
+
headers: {
|
|
205
|
+
"anthropic-version": "2023-06-01",
|
|
206
|
+
// Required for direct browser fetch; harmless when proxied via Rust.
|
|
207
|
+
"anthropic-dangerous-direct-browser-access": "true"
|
|
208
|
+
},
|
|
209
|
+
body: JSON.stringify(body),
|
|
210
|
+
auth: { scheme: "header", headerName: "x-api-key" }
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
parseResponse(json, _config) {
|
|
214
|
+
const parsed = parseClaudeToolCalls(json);
|
|
215
|
+
const usage = json.usage;
|
|
216
|
+
return {
|
|
217
|
+
content: parsed.textContent,
|
|
218
|
+
model: json.model || _config.model,
|
|
219
|
+
usage: { inputTokens: usage?.input_tokens || 0, outputTokens: usage?.output_tokens || 0 },
|
|
220
|
+
...parsed.toolCalls.length > 0 ? { toolCalls: parsed.toolCalls } : {},
|
|
221
|
+
stopReason: parsed.stopReason
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
createStreamFold() {
|
|
225
|
+
const partials = /* @__PURE__ */ new Map();
|
|
226
|
+
const toolCalls = [];
|
|
227
|
+
let stopReason = "end_turn";
|
|
228
|
+
let inputTokens = 0;
|
|
229
|
+
let outputTokens = 0;
|
|
230
|
+
return {
|
|
231
|
+
pushEnvelope(raw) {
|
|
232
|
+
let v;
|
|
233
|
+
try {
|
|
234
|
+
v = JSON.parse(raw);
|
|
235
|
+
} catch {
|
|
236
|
+
return void 0;
|
|
237
|
+
}
|
|
238
|
+
switch (v.type) {
|
|
239
|
+
case "message_start": {
|
|
240
|
+
const u = v.message?.usage;
|
|
241
|
+
if (u?.input_tokens) inputTokens = u.input_tokens;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
case "content_block_delta": {
|
|
245
|
+
const delta = v.delta;
|
|
246
|
+
if (delta?.type === "text_delta") return delta.text || void 0;
|
|
247
|
+
if (delta?.type === "input_json_delta") {
|
|
248
|
+
const p = partials.get(v.index);
|
|
249
|
+
if (p) p.json += delta.partial_json || "";
|
|
250
|
+
}
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case "content_block_start": {
|
|
254
|
+
const block = v.content_block;
|
|
255
|
+
if (block?.type === "tool_use") partials.set(v.index, { id: block.id, name: block.name, json: "" });
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case "content_block_stop": {
|
|
259
|
+
const p = partials.get(v.index);
|
|
260
|
+
if (p) {
|
|
261
|
+
try {
|
|
262
|
+
toolCalls.push({ id: p.id, name: p.name, arguments: JSON.parse(p.json || "{}") });
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
partials.delete(v.index);
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
case "message_delta": {
|
|
270
|
+
const d = v.delta;
|
|
271
|
+
if (d?.stop_reason) stopReason = d.stop_reason === "tool_use" ? "tool_use" : d.stop_reason;
|
|
272
|
+
const u = v.usage;
|
|
273
|
+
if (u?.output_tokens) outputTokens = u.output_tokens;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return void 0;
|
|
278
|
+
},
|
|
279
|
+
finish() {
|
|
280
|
+
const usage = inputTokens || outputTokens ? { inputTokens, outputTokens } : void 0;
|
|
281
|
+
return { ...toolCalls.length > 0 ? { toolCalls } : {}, stopReason, ...usage ? { usage } : {} };
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// src/ai/drivers/openai.ts
|
|
288
|
+
function buildOpenAIMessages(messages) {
|
|
289
|
+
return messages.map((msg) => {
|
|
290
|
+
if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
291
|
+
return {
|
|
292
|
+
role: "assistant",
|
|
293
|
+
content: msg.content || null,
|
|
294
|
+
tool_calls: msg.toolCalls.map((tc) => ({
|
|
295
|
+
id: tc.id,
|
|
296
|
+
type: "function",
|
|
297
|
+
function: { name: tc.name, arguments: JSON.stringify(tc.arguments) }
|
|
298
|
+
}))
|
|
299
|
+
};
|
|
300
|
+
} else if (msg.role === "tool") {
|
|
301
|
+
return { role: "tool", tool_call_id: msg.toolCallId, content: msg.content };
|
|
302
|
+
} else if (msg.role === "user" && msg.images && msg.images.length > 0) {
|
|
303
|
+
const content = [];
|
|
304
|
+
for (const img of msg.images) content.push({ type: "image_url", image_url: { url: `data:${img.mimeType};base64,${img.base64}` } });
|
|
305
|
+
if (msg.content) content.push({ type: "text", text: msg.content });
|
|
306
|
+
return { role: "user", content };
|
|
307
|
+
}
|
|
308
|
+
return { role: msg.role, content: msg.content };
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
var openaiDriver = {
|
|
312
|
+
supportsStreaming: true,
|
|
313
|
+
buildChatRequest(config, request, stream) {
|
|
314
|
+
const baseUrl = resolveBaseUrl(config, "https://api.openai.com");
|
|
315
|
+
const body = {
|
|
316
|
+
model: request.model || config.model,
|
|
317
|
+
max_tokens: request.maxTokens ?? config.maxTokens ?? 41920,
|
|
318
|
+
temperature: request.temperature ?? config.temperature ?? 0.7,
|
|
319
|
+
messages: buildOpenAIMessages(request.messages)
|
|
320
|
+
};
|
|
321
|
+
if (stream) {
|
|
322
|
+
body.stream = true;
|
|
323
|
+
if (config.provider === "openai" || config.provider === "deepseek") {
|
|
324
|
+
body.stream_options = { include_usage: true };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const topP = request.topP ?? config.topP;
|
|
328
|
+
if (topP !== void 0) body.top_p = topP;
|
|
329
|
+
if (request.stop && request.stop.length > 0) body.stop = request.stop.slice(0, 4);
|
|
330
|
+
if (request.tools && request.tools.length > 0) Object.assign(body, formatToolsForProvider(config.provider, request.tools));
|
|
331
|
+
const proxyProvider = config.provider === "deepseek" ? "deepseek" : "openai";
|
|
332
|
+
return {
|
|
333
|
+
provider: proxyProvider,
|
|
334
|
+
configId: config.id,
|
|
335
|
+
method: "POST",
|
|
336
|
+
url: openaiEndpoint(baseUrl, "/chat/completions"),
|
|
337
|
+
headers: {},
|
|
338
|
+
body: JSON.stringify(body),
|
|
339
|
+
auth: { scheme: "bearer" }
|
|
340
|
+
};
|
|
341
|
+
},
|
|
342
|
+
parseResponse(json, _config) {
|
|
343
|
+
const parsed = parseOpenAIToolCalls(json);
|
|
344
|
+
const usage = json.usage;
|
|
345
|
+
return {
|
|
346
|
+
content: parsed.textContent,
|
|
347
|
+
model: json.model || _config.model,
|
|
348
|
+
usage: { inputTokens: usage?.prompt_tokens || 0, outputTokens: usage?.completion_tokens || 0 },
|
|
349
|
+
...parsed.toolCalls.length > 0 ? { toolCalls: parsed.toolCalls } : {},
|
|
350
|
+
stopReason: parsed.stopReason
|
|
351
|
+
};
|
|
352
|
+
},
|
|
353
|
+
createStreamFold() {
|
|
354
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
355
|
+
let stopReason = "end_turn";
|
|
356
|
+
let usage;
|
|
357
|
+
return {
|
|
358
|
+
pushEnvelope(raw) {
|
|
359
|
+
let v;
|
|
360
|
+
try {
|
|
361
|
+
v = JSON.parse(raw);
|
|
362
|
+
} catch {
|
|
363
|
+
return void 0;
|
|
364
|
+
}
|
|
365
|
+
const u = v.usage;
|
|
366
|
+
if (u) usage = { inputTokens: u.prompt_tokens || 0, outputTokens: u.completion_tokens || 0 };
|
|
367
|
+
const choices = v.choices;
|
|
368
|
+
if (!choices || choices.length === 0) return void 0;
|
|
369
|
+
const choice = choices[0];
|
|
370
|
+
const fr = choice.finish_reason;
|
|
371
|
+
if (fr) stopReason = fr === "tool_calls" ? "tool_use" : fr === "length" ? "max_tokens" : fr;
|
|
372
|
+
const delta = choice.delta;
|
|
373
|
+
const rawTC = delta?.tool_calls;
|
|
374
|
+
if (rawTC) {
|
|
375
|
+
for (const tc of rawTC) {
|
|
376
|
+
const idx = tc.index ?? 0;
|
|
377
|
+
const fn = tc.function;
|
|
378
|
+
let entry = toolMap.get(idx);
|
|
379
|
+
if (!entry) {
|
|
380
|
+
entry = { id: tc.id || "", name: "", args: "" };
|
|
381
|
+
toolMap.set(idx, entry);
|
|
382
|
+
}
|
|
383
|
+
if (tc.id) entry.id = tc.id;
|
|
384
|
+
if (fn?.name) entry.name = fn.name;
|
|
385
|
+
if (fn?.arguments) entry.args += fn.arguments;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return delta?.content || void 0;
|
|
389
|
+
},
|
|
390
|
+
finish() {
|
|
391
|
+
const toolCalls = [];
|
|
392
|
+
for (const [, entry] of [...toolMap.entries()].sort((a, b) => a[0] - b[0])) {
|
|
393
|
+
let args = {};
|
|
394
|
+
try {
|
|
395
|
+
args = JSON.parse(entry.args || "{}");
|
|
396
|
+
} catch {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
toolCalls.push({ id: entry.id, name: entry.name, arguments: args });
|
|
400
|
+
}
|
|
401
|
+
return { ...toolCalls.length > 0 ? { toolCalls } : {}, stopReason, ...usage ? { usage } : {} };
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/ai/drivers/gemini.ts
|
|
408
|
+
function buildGeminiContents(messages) {
|
|
409
|
+
return messages.map((msg) => {
|
|
410
|
+
if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
411
|
+
const parts = [];
|
|
412
|
+
if (msg.content) parts.push({ text: msg.content });
|
|
413
|
+
for (const tc of msg.toolCalls) {
|
|
414
|
+
const sig = tc.providerMeta?.thoughtSignature;
|
|
415
|
+
const part = { functionCall: { name: tc.name, args: tc.arguments } };
|
|
416
|
+
if (sig) part.thoughtSignature = sig;
|
|
417
|
+
parts.push(part);
|
|
418
|
+
}
|
|
419
|
+
return { role: "model", parts };
|
|
420
|
+
} else if (msg.role === "tool") {
|
|
421
|
+
return { role: "user", parts: [{ functionResponse: { name: msg.toolName, response: { content: msg.content } } }] };
|
|
422
|
+
}
|
|
423
|
+
if (msg.role === "user" && msg.images && msg.images.length > 0) {
|
|
424
|
+
const parts = [];
|
|
425
|
+
for (const img of msg.images) parts.push({ inlineData: { mimeType: img.mimeType, data: img.base64 } });
|
|
426
|
+
if (msg.content) parts.push({ text: msg.content });
|
|
427
|
+
return { role: "user", parts };
|
|
428
|
+
}
|
|
429
|
+
return { role: msg.role === "assistant" ? "model" : "user", parts: [{ text: msg.content }] };
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
var geminiDriver = {
|
|
433
|
+
supportsStreaming: true,
|
|
434
|
+
buildChatRequest(config, request, stream) {
|
|
435
|
+
const baseUrl = resolveBaseUrl(config, "https://generativelanguage.googleapis.com");
|
|
436
|
+
const systemMessages = request.messages.filter((m) => m.role === "system");
|
|
437
|
+
const chatMessages = request.messages.filter((m) => m.role !== "system");
|
|
438
|
+
const generationConfig = {
|
|
439
|
+
maxOutputTokens: request.maxTokens ?? config.maxTokens ?? 41920,
|
|
440
|
+
temperature: request.temperature ?? config.temperature ?? 0.7
|
|
441
|
+
};
|
|
442
|
+
const topP = request.topP ?? config.topP;
|
|
443
|
+
if (topP !== void 0) generationConfig.topP = topP;
|
|
444
|
+
if (request.stop && request.stop.length > 0) generationConfig.stopSequences = request.stop;
|
|
445
|
+
const body = { contents: buildGeminiContents(chatMessages), generationConfig };
|
|
446
|
+
if (systemMessages.length > 0) body.systemInstruction = { parts: [{ text: systemMessages.map((m) => m.content).join("\n") }] };
|
|
447
|
+
if (request.tools && request.tools.length > 0) Object.assign(body, formatToolsForProvider("gemini", request.tools));
|
|
448
|
+
const model = request.model || config.model;
|
|
449
|
+
const verb = stream ? "streamGenerateContent?alt=sse" : "generateContent";
|
|
450
|
+
return {
|
|
451
|
+
provider: "gemini",
|
|
452
|
+
configId: config.id,
|
|
453
|
+
method: "POST",
|
|
454
|
+
url: `${baseUrl}/v1beta/models/${model}:${verb}`,
|
|
455
|
+
headers: {},
|
|
456
|
+
body: JSON.stringify(body),
|
|
457
|
+
auth: { scheme: "query", queryParam: "key" }
|
|
458
|
+
};
|
|
459
|
+
},
|
|
460
|
+
parseResponse(json, config) {
|
|
461
|
+
const parsed = parseGeminiToolCalls(json);
|
|
462
|
+
const usage = json.usageMetadata;
|
|
463
|
+
return {
|
|
464
|
+
content: parsed.textContent,
|
|
465
|
+
model: config.model,
|
|
466
|
+
usage: { inputTokens: usage?.promptTokenCount || 0, outputTokens: usage?.candidatesTokenCount || 0 },
|
|
467
|
+
...parsed.toolCalls.length > 0 ? { toolCalls: parsed.toolCalls } : {},
|
|
468
|
+
stopReason: parsed.stopReason === "tool_use" ? "tool_use" : "end_turn"
|
|
469
|
+
};
|
|
470
|
+
},
|
|
471
|
+
createStreamFold() {
|
|
472
|
+
const toolCalls = [];
|
|
473
|
+
let stopReason = "end_turn";
|
|
474
|
+
let usage;
|
|
475
|
+
return {
|
|
476
|
+
pushEnvelope(raw) {
|
|
477
|
+
let v;
|
|
478
|
+
try {
|
|
479
|
+
v = JSON.parse(raw);
|
|
480
|
+
} catch {
|
|
481
|
+
return void 0;
|
|
482
|
+
}
|
|
483
|
+
const um = v.usageMetadata;
|
|
484
|
+
if (um) usage = { inputTokens: um.promptTokenCount || 0, outputTokens: um.candidatesTokenCount || 0 };
|
|
485
|
+
const candidates = v.candidates;
|
|
486
|
+
const cand = candidates?.[0];
|
|
487
|
+
if (!cand) return void 0;
|
|
488
|
+
const parts = cand.content?.parts;
|
|
489
|
+
let text = "";
|
|
490
|
+
if (parts) {
|
|
491
|
+
for (const part of parts) {
|
|
492
|
+
if (part.functionCall) {
|
|
493
|
+
const fc = part.functionCall;
|
|
494
|
+
const sig = part.thoughtSignature ?? fc.thoughtSignature;
|
|
495
|
+
toolCalls.push({
|
|
496
|
+
id: `gemini-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
497
|
+
name: fc.name,
|
|
498
|
+
arguments: fc.args || {},
|
|
499
|
+
...sig ? { providerMeta: { thoughtSignature: sig } } : {}
|
|
500
|
+
});
|
|
501
|
+
stopReason = "tool_use";
|
|
502
|
+
} else if (part.text) {
|
|
503
|
+
text += part.text;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return text || void 0;
|
|
508
|
+
},
|
|
509
|
+
finish() {
|
|
510
|
+
return { ...toolCalls.length > 0 ? { toolCalls } : {}, stopReason, ...usage ? { usage } : {} };
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
// src/ai/drivers/ollama.ts
|
|
517
|
+
function buildOllamaMessages(messages) {
|
|
518
|
+
return messages.map((msg) => {
|
|
519
|
+
if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
|
|
520
|
+
return {
|
|
521
|
+
role: "assistant",
|
|
522
|
+
content: msg.content || "",
|
|
523
|
+
tool_calls: msg.toolCalls.map((tc) => ({
|
|
524
|
+
id: tc.id,
|
|
525
|
+
type: "function",
|
|
526
|
+
function: { name: tc.name, arguments: JSON.stringify(tc.arguments) }
|
|
527
|
+
}))
|
|
528
|
+
};
|
|
529
|
+
} else if (msg.role === "tool") {
|
|
530
|
+
return { role: "tool", tool_call_id: msg.toolCallId, content: msg.content };
|
|
531
|
+
}
|
|
532
|
+
const result = { role: msg.role, content: msg.content };
|
|
533
|
+
if (msg.role === "user" && msg.images && msg.images.length > 0) result.images = msg.images.map((img) => img.base64);
|
|
534
|
+
return result;
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
var ollamaDriver = {
|
|
538
|
+
supportsStreaming: false,
|
|
539
|
+
buildChatRequest(config, request, _stream) {
|
|
540
|
+
const baseUrl = resolveBaseUrl(config, "http://localhost:11434");
|
|
541
|
+
const body = {
|
|
542
|
+
model: request.model || config.model,
|
|
543
|
+
messages: buildOllamaMessages(request.messages),
|
|
544
|
+
stream: false,
|
|
545
|
+
options: {
|
|
546
|
+
temperature: request.temperature ?? config.temperature ?? 0.7,
|
|
547
|
+
num_predict: request.maxTokens ?? config.maxTokens ?? 41920
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
if (request.tools && request.tools.length > 0) Object.assign(body, formatToolsForProvider("ollama", request.tools));
|
|
551
|
+
return {
|
|
552
|
+
provider: "ollama",
|
|
553
|
+
configId: config.id,
|
|
554
|
+
method: "POST",
|
|
555
|
+
url: `${baseUrl}/api/chat`,
|
|
556
|
+
headers: {},
|
|
557
|
+
body: JSON.stringify(body),
|
|
558
|
+
auth: { scheme: "none" }
|
|
559
|
+
};
|
|
560
|
+
},
|
|
561
|
+
parseResponse(json, _config) {
|
|
562
|
+
const message = json.message;
|
|
563
|
+
const toolCalls = [];
|
|
564
|
+
const rawToolCalls = message?.tool_calls;
|
|
565
|
+
if (rawToolCalls) {
|
|
566
|
+
for (const tc of rawToolCalls) {
|
|
567
|
+
const fn = tc.function;
|
|
568
|
+
toolCalls.push({
|
|
569
|
+
id: `ollama-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
570
|
+
name: fn.name,
|
|
571
|
+
arguments: fn.arguments || {}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return {
|
|
576
|
+
content: message?.content || "",
|
|
577
|
+
model: json.model || _config.model,
|
|
578
|
+
usage: { inputTokens: json.prompt_eval_count || 0, outputTokens: json.eval_count || 0 },
|
|
579
|
+
...toolCalls.length > 0 ? { toolCalls } : {},
|
|
580
|
+
stopReason: toolCalls.length > 0 ? "tool_use" : "end_turn"
|
|
581
|
+
};
|
|
582
|
+
},
|
|
583
|
+
createStreamFold() {
|
|
584
|
+
return NOOP_FOLD;
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
// src/ai/drivers/index.ts
|
|
589
|
+
function getDriver(provider) {
|
|
590
|
+
switch (provider) {
|
|
591
|
+
case "claude":
|
|
592
|
+
return claudeDriver;
|
|
593
|
+
case "gemini":
|
|
594
|
+
return geminiDriver;
|
|
595
|
+
case "ollama":
|
|
596
|
+
return ollamaDriver;
|
|
597
|
+
case "openai":
|
|
598
|
+
case "deepseek":
|
|
599
|
+
case "grok":
|
|
600
|
+
case "mistral":
|
|
601
|
+
case "glm":
|
|
602
|
+
case "minimax":
|
|
603
|
+
case "doubao":
|
|
604
|
+
case "custom":
|
|
605
|
+
return openaiDriver;
|
|
606
|
+
default:
|
|
607
|
+
throw new Error(`No HTTP driver for provider: ${provider}`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
export {
|
|
611
|
+
claudeDriver,
|
|
612
|
+
geminiDriver,
|
|
613
|
+
getDriver,
|
|
614
|
+
ollamaDriver,
|
|
615
|
+
openaiDriver
|
|
616
|
+
};
|
|
617
|
+
//# sourceMappingURL=index.js.map
|