@aituber-onair/chat 0.34.1 → 0.36.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.ja.md +9 -3
- package/README.md +10 -4
- package/dist/cjs/constants/claude.d.ts +1 -0
- package/dist/cjs/constants/claude.d.ts.map +1 -1
- package/dist/cjs/constants/claude.js +3 -1
- package/dist/cjs/constants/claude.js.map +1 -1
- package/dist/cjs/constants/gemini.d.ts +1 -0
- package/dist/cjs/constants/gemini.d.ts.map +1 -1
- package/dist/cjs/constants/gemini.js +3 -1
- package/dist/cjs/constants/gemini.js.map +1 -1
- package/dist/cjs/services/providers/claude/ClaudeChatService.d.ts +0 -24
- package/dist/cjs/services/providers/claude/ClaudeChatService.d.ts.map +1 -1
- package/dist/cjs/services/providers/claude/ClaudeChatService.js +3 -113
- package/dist/cjs/services/providers/claude/ClaudeChatService.js.map +1 -1
- package/dist/cjs/services/providers/claude/ClaudeChatServiceProvider.d.ts.map +1 -1
- package/dist/cjs/services/providers/claude/ClaudeChatServiceProvider.js +1 -0
- package/dist/cjs/services/providers/claude/ClaudeChatServiceProvider.js.map +1 -1
- package/dist/cjs/services/providers/claude/claudeMessageConverter.d.ts +6 -0
- package/dist/cjs/services/providers/claude/claudeMessageConverter.d.ts.map +1 -0
- package/dist/cjs/services/providers/claude/claudeMessageConverter.js +95 -0
- package/dist/cjs/services/providers/claude/claudeMessageConverter.js.map +1 -0
- package/dist/cjs/services/providers/gemini/GeminiChatService.d.ts +1 -21
- package/dist/cjs/services/providers/gemini/GeminiChatService.d.ts.map +1 -1
- package/dist/cjs/services/providers/gemini/GeminiChatService.js +21 -258
- package/dist/cjs/services/providers/gemini/GeminiChatService.js.map +1 -1
- package/dist/cjs/services/providers/gemini/geminiMessageConverter.d.ts +17 -0
- package/dist/cjs/services/providers/gemini/geminiMessageConverter.d.ts.map +1 -0
- package/dist/cjs/services/providers/gemini/geminiMessageConverter.js +183 -0
- package/dist/cjs/services/providers/gemini/geminiMessageConverter.js.map +1 -0
- package/dist/cjs/services/providers/gemini/geminiToolAdapter.d.ts +23 -0
- package/dist/cjs/services/providers/gemini/geminiToolAdapter.d.ts.map +1 -0
- package/dist/cjs/services/providers/gemini/geminiToolAdapter.js +47 -0
- package/dist/cjs/services/providers/gemini/geminiToolAdapter.js.map +1 -0
- package/dist/cjs/services/providers/openai/OpenAIChatService.d.ts +3 -28
- package/dist/cjs/services/providers/openai/OpenAIChatService.d.ts.map +1 -1
- package/dist/cjs/services/providers/openai/OpenAIChatService.js +15 -250
- package/dist/cjs/services/providers/openai/OpenAIChatService.js.map +1 -1
- package/dist/cjs/services/providers/openai/openaiRequestBuilder.d.ts +33 -0
- package/dist/cjs/services/providers/openai/openaiRequestBuilder.d.ts.map +1 -0
- package/dist/cjs/services/providers/openai/openaiRequestBuilder.js +218 -0
- package/dist/cjs/services/providers/openai/openaiRequestBuilder.js.map +1 -0
- package/dist/cjs/services/providers/openai/openaiToolBuilder.d.ts +9 -0
- package/dist/cjs/services/providers/openai/openaiToolBuilder.d.ts.map +1 -0
- package/dist/cjs/services/providers/openai/openaiToolBuilder.js +36 -0
- package/dist/cjs/services/providers/openai/openaiToolBuilder.js.map +1 -0
- package/dist/esm/constants/claude.d.ts +1 -0
- package/dist/esm/constants/claude.d.ts.map +1 -1
- package/dist/esm/constants/claude.js +2 -0
- package/dist/esm/constants/claude.js.map +1 -1
- package/dist/esm/constants/gemini.d.ts +1 -0
- package/dist/esm/constants/gemini.d.ts.map +1 -1
- package/dist/esm/constants/gemini.js +2 -0
- package/dist/esm/constants/gemini.js.map +1 -1
- package/dist/esm/services/providers/claude/ClaudeChatService.d.ts +0 -24
- package/dist/esm/services/providers/claude/ClaudeChatService.d.ts.map +1 -1
- package/dist/esm/services/providers/claude/ClaudeChatService.js +3 -113
- package/dist/esm/services/providers/claude/ClaudeChatService.js.map +1 -1
- package/dist/esm/services/providers/claude/ClaudeChatServiceProvider.d.ts.map +1 -1
- package/dist/esm/services/providers/claude/ClaudeChatServiceProvider.js +2 -1
- package/dist/esm/services/providers/claude/ClaudeChatServiceProvider.js.map +1 -1
- package/dist/esm/services/providers/claude/claudeMessageConverter.d.ts +6 -0
- package/dist/esm/services/providers/claude/claudeMessageConverter.d.ts.map +1 -0
- package/dist/esm/services/providers/claude/claudeMessageConverter.js +89 -0
- package/dist/esm/services/providers/claude/claudeMessageConverter.js.map +1 -0
- package/dist/esm/services/providers/gemini/GeminiChatService.d.ts +1 -21
- package/dist/esm/services/providers/gemini/GeminiChatService.d.ts.map +1 -1
- package/dist/esm/services/providers/gemini/GeminiChatService.js +22 -259
- package/dist/esm/services/providers/gemini/GeminiChatService.js.map +1 -1
- package/dist/esm/services/providers/gemini/geminiMessageConverter.d.ts +17 -0
- package/dist/esm/services/providers/gemini/geminiMessageConverter.d.ts.map +1 -0
- package/dist/esm/services/providers/gemini/geminiMessageConverter.js +178 -0
- package/dist/esm/services/providers/gemini/geminiMessageConverter.js.map +1 -0
- package/dist/esm/services/providers/gemini/geminiToolAdapter.d.ts +23 -0
- package/dist/esm/services/providers/gemini/geminiToolAdapter.d.ts.map +1 -0
- package/dist/esm/services/providers/gemini/geminiToolAdapter.js +42 -0
- package/dist/esm/services/providers/gemini/geminiToolAdapter.js.map +1 -0
- package/dist/esm/services/providers/openai/OpenAIChatService.d.ts +3 -28
- package/dist/esm/services/providers/openai/OpenAIChatService.d.ts.map +1 -1
- package/dist/esm/services/providers/openai/OpenAIChatService.js +17 -252
- package/dist/esm/services/providers/openai/OpenAIChatService.js.map +1 -1
- package/dist/esm/services/providers/openai/openaiRequestBuilder.d.ts +33 -0
- package/dist/esm/services/providers/openai/openaiRequestBuilder.d.ts.map +1 -0
- package/dist/esm/services/providers/openai/openaiRequestBuilder.js +212 -0
- package/dist/esm/services/providers/openai/openaiRequestBuilder.js.map +1 -0
- package/dist/esm/services/providers/openai/openaiToolBuilder.d.ts +9 -0
- package/dist/esm/services/providers/openai/openaiToolBuilder.d.ts.map +1 -0
- package/dist/esm/services/providers/openai/openaiToolBuilder.js +33 -0
- package/dist/esm/services/providers/openai/openaiToolBuilder.js.map +1 -0
- package/dist/umd/aituber-onair-chat.js +561 -545
- package/dist/umd/aituber-onair-chat.min.js +10 -10
- package/package.json +3 -1
|
@@ -84,6 +84,7 @@ var AITuberOnAirChat = (() => {
|
|
|
84
84
|
MODEL_CLAUDE_4_6_OPUS: () => MODEL_CLAUDE_4_6_OPUS,
|
|
85
85
|
MODEL_CLAUDE_4_6_SONNET: () => MODEL_CLAUDE_4_6_SONNET,
|
|
86
86
|
MODEL_CLAUDE_4_7_OPUS: () => MODEL_CLAUDE_4_7_OPUS,
|
|
87
|
+
MODEL_CLAUDE_4_8_OPUS: () => MODEL_CLAUDE_4_8_OPUS,
|
|
87
88
|
MODEL_CLAUDE_4_OPUS: () => MODEL_CLAUDE_4_OPUS,
|
|
88
89
|
MODEL_CLAUDE_4_SONNET: () => MODEL_CLAUDE_4_SONNET,
|
|
89
90
|
MODEL_DEEPSEEK_CHAT: () => MODEL_DEEPSEEK_CHAT,
|
|
@@ -99,6 +100,7 @@ var AITuberOnAirChat = (() => {
|
|
|
99
100
|
MODEL_GEMINI_3_1_FLASH_LITE: () => MODEL_GEMINI_3_1_FLASH_LITE,
|
|
100
101
|
MODEL_GEMINI_3_1_FLASH_LITE_PREVIEW: () => MODEL_GEMINI_3_1_FLASH_LITE_PREVIEW,
|
|
101
102
|
MODEL_GEMINI_3_1_PRO_PREVIEW: () => MODEL_GEMINI_3_1_PRO_PREVIEW,
|
|
103
|
+
MODEL_GEMINI_3_5_FLASH: () => MODEL_GEMINI_3_5_FLASH,
|
|
102
104
|
MODEL_GEMINI_3_FLASH_PREVIEW: () => MODEL_GEMINI_3_FLASH_PREVIEW,
|
|
103
105
|
MODEL_GEMINI_3_PRO_PREVIEW: () => MODEL_GEMINI_3_PRO_PREVIEW,
|
|
104
106
|
MODEL_GEMINI_NANO: () => MODEL_GEMINI_NANO,
|
|
@@ -296,6 +298,7 @@ var AITuberOnAirChat = (() => {
|
|
|
296
298
|
var ENDPOINT_GEMINI_API = "https://generativelanguage.googleapis.com";
|
|
297
299
|
var MODEL_GEMMA_4_31B_IT = "gemma-4-31b-it";
|
|
298
300
|
var MODEL_GEMMA_4_26B_A4B_IT = "gemma-4-26b-a4b-it";
|
|
301
|
+
var MODEL_GEMINI_3_5_FLASH = "gemini-3.5-flash";
|
|
299
302
|
var MODEL_GEMINI_3_1_PRO_PREVIEW = "gemini-3.1-pro-preview";
|
|
300
303
|
var MODEL_GEMINI_3_1_FLASH_LITE = "gemini-3.1-flash-lite";
|
|
301
304
|
var MODEL_GEMINI_3_1_FLASH_LITE_PREVIEW = "gemini-3.1-flash-lite-preview";
|
|
@@ -308,6 +311,7 @@ var AITuberOnAirChat = (() => {
|
|
|
308
311
|
var MODEL_GEMINI_2_0_FLASH = "gemini-2.0-flash";
|
|
309
312
|
var MODEL_GEMINI_2_0_FLASH_LITE = "gemini-2.0-flash-lite";
|
|
310
313
|
var GEMINI_RECOMMENDED_MODELS = [
|
|
314
|
+
MODEL_GEMINI_3_5_FLASH,
|
|
311
315
|
MODEL_GEMINI_3_1_FLASH_LITE,
|
|
312
316
|
MODEL_GEMINI_3_1_PRO_PREVIEW,
|
|
313
317
|
MODEL_GEMINI_3_FLASH_PREVIEW,
|
|
@@ -343,6 +347,7 @@ var AITuberOnAirChat = (() => {
|
|
|
343
347
|
var MODEL_CLAUDE_4_6_SONNET = "claude-sonnet-4-6";
|
|
344
348
|
var MODEL_CLAUDE_4_6_OPUS = "claude-opus-4-6";
|
|
345
349
|
var MODEL_CLAUDE_4_7_OPUS = "claude-opus-4-7";
|
|
350
|
+
var MODEL_CLAUDE_4_8_OPUS = "claude-opus-4-8";
|
|
346
351
|
var CLAUDE_VISION_SUPPORTED_MODELS = [
|
|
347
352
|
MODEL_CLAUDE_3_HAIKU,
|
|
348
353
|
MODEL_CLAUDE_4_SONNET,
|
|
@@ -352,7 +357,8 @@ var AITuberOnAirChat = (() => {
|
|
|
352
357
|
MODEL_CLAUDE_4_5_OPUS,
|
|
353
358
|
MODEL_CLAUDE_4_6_SONNET,
|
|
354
359
|
MODEL_CLAUDE_4_6_OPUS,
|
|
355
|
-
MODEL_CLAUDE_4_7_OPUS
|
|
360
|
+
MODEL_CLAUDE_4_7_OPUS,
|
|
361
|
+
MODEL_CLAUDE_4_8_OPUS
|
|
356
362
|
];
|
|
357
363
|
|
|
358
364
|
// src/constants/openrouter.ts
|
|
@@ -1244,6 +1250,94 @@ If it's in another language, summarize in that language.
|
|
|
1244
1250
|
};
|
|
1245
1251
|
}
|
|
1246
1252
|
|
|
1253
|
+
// src/services/providers/claude/claudeMessageConverter.ts
|
|
1254
|
+
function convertMessagesToClaudeFormat(messages) {
|
|
1255
|
+
return messages.map((msg) => ({
|
|
1256
|
+
role: mapRoleToClaude(msg.role),
|
|
1257
|
+
content: msg.content
|
|
1258
|
+
}));
|
|
1259
|
+
}
|
|
1260
|
+
function convertVisionMessagesToClaudeFormat(messages) {
|
|
1261
|
+
return messages.map((msg) => {
|
|
1262
|
+
if (typeof msg.content === "string") {
|
|
1263
|
+
return {
|
|
1264
|
+
role: mapRoleToClaude(msg.role),
|
|
1265
|
+
content: [
|
|
1266
|
+
{
|
|
1267
|
+
type: "text",
|
|
1268
|
+
text: msg.content
|
|
1269
|
+
}
|
|
1270
|
+
]
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
if (Array.isArray(msg.content)) {
|
|
1274
|
+
const content = msg.content.map((block) => {
|
|
1275
|
+
if (block.type === "image_url") {
|
|
1276
|
+
if (block.image_url.url.startsWith("data:")) {
|
|
1277
|
+
const m = block.image_url.url.match(/^data:([^;]+);base64,(.+)$/);
|
|
1278
|
+
if (m) {
|
|
1279
|
+
return {
|
|
1280
|
+
type: "image",
|
|
1281
|
+
source: {
|
|
1282
|
+
type: "base64",
|
|
1283
|
+
media_type: m[1],
|
|
1284
|
+
data: m[2]
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
return {
|
|
1291
|
+
type: "image",
|
|
1292
|
+
source: {
|
|
1293
|
+
type: "url",
|
|
1294
|
+
url: block.image_url.url,
|
|
1295
|
+
media_type: getMimeTypeFromUrl(block.image_url.url)
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
return block;
|
|
1300
|
+
}).filter((b) => b);
|
|
1301
|
+
return {
|
|
1302
|
+
role: mapRoleToClaude(msg.role),
|
|
1303
|
+
content
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
return {
|
|
1307
|
+
role: mapRoleToClaude(msg.role),
|
|
1308
|
+
content: []
|
|
1309
|
+
};
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
function mapRoleToClaude(role) {
|
|
1313
|
+
switch (role) {
|
|
1314
|
+
case "system":
|
|
1315
|
+
return "system";
|
|
1316
|
+
case "user":
|
|
1317
|
+
return "user";
|
|
1318
|
+
case "assistant":
|
|
1319
|
+
return "assistant";
|
|
1320
|
+
default:
|
|
1321
|
+
return "user";
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
function getMimeTypeFromUrl(url) {
|
|
1325
|
+
const extension = url.split(".").pop()?.toLowerCase();
|
|
1326
|
+
switch (extension) {
|
|
1327
|
+
case "jpg":
|
|
1328
|
+
case "jpeg":
|
|
1329
|
+
return "image/jpeg";
|
|
1330
|
+
case "png":
|
|
1331
|
+
return "image/png";
|
|
1332
|
+
case "gif":
|
|
1333
|
+
return "image/gif";
|
|
1334
|
+
case "webp":
|
|
1335
|
+
return "image/webp";
|
|
1336
|
+
default:
|
|
1337
|
+
return "image/jpeg";
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1247
1341
|
// src/services/providers/claude/ClaudeChatService.ts
|
|
1248
1342
|
var ClaudeChatService = class {
|
|
1249
1343
|
/**
|
|
@@ -1350,112 +1444,6 @@ If it's in another language, summarize in that language.
|
|
|
1350
1444
|
toolErrorMessage: "processVisionChat received tool_calls. ChatProcessor must use chatOnce() loop when tools are enabled."
|
|
1351
1445
|
});
|
|
1352
1446
|
}
|
|
1353
|
-
/**
|
|
1354
|
-
* Convert AITuber OnAir messages to Claude format
|
|
1355
|
-
* @param messages Array of messages
|
|
1356
|
-
* @returns Claude formatted messages
|
|
1357
|
-
*/
|
|
1358
|
-
convertMessagesToClaudeFormat(messages) {
|
|
1359
|
-
return messages.map((msg) => {
|
|
1360
|
-
return {
|
|
1361
|
-
role: this.mapRoleToClaude(msg.role),
|
|
1362
|
-
content: msg.content
|
|
1363
|
-
};
|
|
1364
|
-
});
|
|
1365
|
-
}
|
|
1366
|
-
/**
|
|
1367
|
-
* Convert AITuber OnAir vision messages to Claude format
|
|
1368
|
-
* @param messages Array of vision messages
|
|
1369
|
-
* @returns Claude formatted vision messages
|
|
1370
|
-
*/
|
|
1371
|
-
convertVisionMessagesToClaudeFormat(messages) {
|
|
1372
|
-
return messages.map((msg) => {
|
|
1373
|
-
if (typeof msg.content === "string") {
|
|
1374
|
-
return {
|
|
1375
|
-
role: this.mapRoleToClaude(msg.role),
|
|
1376
|
-
content: [
|
|
1377
|
-
{
|
|
1378
|
-
type: "text",
|
|
1379
|
-
text: msg.content
|
|
1380
|
-
}
|
|
1381
|
-
]
|
|
1382
|
-
};
|
|
1383
|
-
}
|
|
1384
|
-
if (Array.isArray(msg.content)) {
|
|
1385
|
-
const content = msg.content.map((block) => {
|
|
1386
|
-
if (block.type === "image_url") {
|
|
1387
|
-
if (block.image_url.url.startsWith("data:")) {
|
|
1388
|
-
const m = block.image_url.url.match(
|
|
1389
|
-
/^data:([^;]+);base64,(.+)$/
|
|
1390
|
-
);
|
|
1391
|
-
if (m) {
|
|
1392
|
-
return {
|
|
1393
|
-
type: "image",
|
|
1394
|
-
source: { type: "base64", media_type: m[1], data: m[2] }
|
|
1395
|
-
};
|
|
1396
|
-
}
|
|
1397
|
-
return null;
|
|
1398
|
-
}
|
|
1399
|
-
return {
|
|
1400
|
-
type: "image",
|
|
1401
|
-
source: {
|
|
1402
|
-
type: "url",
|
|
1403
|
-
url: block.image_url.url,
|
|
1404
|
-
media_type: this.getMimeTypeFromUrl(block.image_url.url)
|
|
1405
|
-
}
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
|
-
return block;
|
|
1409
|
-
}).filter((b) => b);
|
|
1410
|
-
return {
|
|
1411
|
-
role: this.mapRoleToClaude(msg.role),
|
|
1412
|
-
content
|
|
1413
|
-
};
|
|
1414
|
-
}
|
|
1415
|
-
return {
|
|
1416
|
-
role: this.mapRoleToClaude(msg.role),
|
|
1417
|
-
content: []
|
|
1418
|
-
};
|
|
1419
|
-
});
|
|
1420
|
-
}
|
|
1421
|
-
/**
|
|
1422
|
-
* Map AITuber OnAir roles to Claude roles
|
|
1423
|
-
* @param role AITuber OnAir role
|
|
1424
|
-
* @returns Claude role
|
|
1425
|
-
*/
|
|
1426
|
-
mapRoleToClaude(role) {
|
|
1427
|
-
switch (role) {
|
|
1428
|
-
case "system":
|
|
1429
|
-
return "system";
|
|
1430
|
-
case "user":
|
|
1431
|
-
return "user";
|
|
1432
|
-
case "assistant":
|
|
1433
|
-
return "assistant";
|
|
1434
|
-
default:
|
|
1435
|
-
return "user";
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
/**
|
|
1439
|
-
* Get MIME type from URL
|
|
1440
|
-
* @param url Image URL
|
|
1441
|
-
* @returns MIME type
|
|
1442
|
-
*/
|
|
1443
|
-
getMimeTypeFromUrl(url) {
|
|
1444
|
-
const extension = url.split(".").pop()?.toLowerCase();
|
|
1445
|
-
switch (extension) {
|
|
1446
|
-
case "jpg":
|
|
1447
|
-
case "jpeg":
|
|
1448
|
-
return "image/jpeg";
|
|
1449
|
-
case "png":
|
|
1450
|
-
return "image/png";
|
|
1451
|
-
case "gif":
|
|
1452
|
-
return "image/gif";
|
|
1453
|
-
case "webp":
|
|
1454
|
-
return "image/webp";
|
|
1455
|
-
default:
|
|
1456
|
-
return "image/jpeg";
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
1447
|
/**
|
|
1460
1448
|
* Call Claude API
|
|
1461
1449
|
* @param messages Array of messages to send
|
|
@@ -1475,9 +1463,7 @@ If it's in another language, summarize in that language.
|
|
|
1475
1463
|
const body = {
|
|
1476
1464
|
model,
|
|
1477
1465
|
system,
|
|
1478
|
-
messages: hasVision ?
|
|
1479
|
-
content
|
|
1480
|
-
) : this.convertMessagesToClaudeFormat(content),
|
|
1466
|
+
messages: hasVision ? convertVisionMessagesToClaudeFormat(content) : convertMessagesToClaudeFormat(content),
|
|
1481
1467
|
stream,
|
|
1482
1468
|
max_tokens: maxTokens !== void 0 ? maxTokens : getMaxTokensForResponseLength(this.responseLength)
|
|
1483
1469
|
};
|
|
@@ -1740,6 +1726,7 @@ If it's in another language, summarize in that language.
|
|
|
1740
1726
|
MODEL_CLAUDE_4_6_SONNET,
|
|
1741
1727
|
MODEL_CLAUDE_4_6_OPUS,
|
|
1742
1728
|
MODEL_CLAUDE_4_7_OPUS,
|
|
1729
|
+
MODEL_CLAUDE_4_8_OPUS,
|
|
1743
1730
|
MODEL_CLAUDE_3_HAIKU
|
|
1744
1731
|
];
|
|
1745
1732
|
}
|
|
@@ -1931,7 +1918,49 @@ If it's in another language, summarize in that language.
|
|
|
1931
1918
|
};
|
|
1932
1919
|
}
|
|
1933
1920
|
|
|
1934
|
-
// src/services/providers/openai/
|
|
1921
|
+
// src/services/providers/openai/openaiToolBuilder.ts
|
|
1922
|
+
function buildOpenAIToolsDefinition({
|
|
1923
|
+
tools,
|
|
1924
|
+
mcpServers,
|
|
1925
|
+
isResponsesAPI
|
|
1926
|
+
}) {
|
|
1927
|
+
const toolDefs = [];
|
|
1928
|
+
if (tools.length > 0) {
|
|
1929
|
+
toolDefs.push(
|
|
1930
|
+
...buildOpenAICompatibleTools(
|
|
1931
|
+
tools,
|
|
1932
|
+
isResponsesAPI ? "responses" : "chat-completions"
|
|
1933
|
+
)
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1936
|
+
if (mcpServers.length > 0 && isResponsesAPI) {
|
|
1937
|
+
toolDefs.push(...buildOpenAIMCPToolsDefinition(mcpServers));
|
|
1938
|
+
}
|
|
1939
|
+
return toolDefs;
|
|
1940
|
+
}
|
|
1941
|
+
function buildOpenAIMCPToolsDefinition(mcpServers) {
|
|
1942
|
+
return mcpServers.map((server) => {
|
|
1943
|
+
const mcpDef = {
|
|
1944
|
+
type: "mcp",
|
|
1945
|
+
server_label: server.name,
|
|
1946
|
+
server_url: server.url
|
|
1947
|
+
};
|
|
1948
|
+
if (server.require_approval) {
|
|
1949
|
+
mcpDef.require_approval = server.require_approval;
|
|
1950
|
+
}
|
|
1951
|
+
if (server.tool_configuration?.allowed_tools) {
|
|
1952
|
+
mcpDef.allowed_tools = server.tool_configuration.allowed_tools;
|
|
1953
|
+
}
|
|
1954
|
+
if (server.authorization_token) {
|
|
1955
|
+
mcpDef.headers = {
|
|
1956
|
+
Authorization: `Bearer ${server.authorization_token}`
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
return mcpDef;
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
// src/services/providers/openai/openaiRequestBuilder.ts
|
|
1935
1964
|
var GPT5_RESPONSE_LENGTH_MIN_TOKENS = {
|
|
1936
1965
|
[CHAT_RESPONSE_LENGTH.VERY_SHORT]: 800,
|
|
1937
1966
|
[CHAT_RESPONSE_LENGTH.SHORT]: 1200,
|
|
@@ -1953,6 +1982,177 @@ If it's in another language, summarize in that language.
|
|
|
1953
1982
|
"deepseek",
|
|
1954
1983
|
"mistral"
|
|
1955
1984
|
]);
|
|
1985
|
+
function buildOpenAIRequestBody({
|
|
1986
|
+
provider,
|
|
1987
|
+
endpoint,
|
|
1988
|
+
messages,
|
|
1989
|
+
model,
|
|
1990
|
+
stream,
|
|
1991
|
+
tools,
|
|
1992
|
+
mcpServers,
|
|
1993
|
+
responseLength,
|
|
1994
|
+
verbosity,
|
|
1995
|
+
reasoning_effort,
|
|
1996
|
+
enableReasoningSummary,
|
|
1997
|
+
maxTokens
|
|
1998
|
+
}) {
|
|
1999
|
+
const isResponsesAPI = endpoint === ENDPOINT_OPENAI_RESPONSES_API;
|
|
2000
|
+
validateMCPCompatibility(endpoint, mcpServers);
|
|
2001
|
+
const body = {
|
|
2002
|
+
model,
|
|
2003
|
+
stream
|
|
2004
|
+
};
|
|
2005
|
+
const tokenLimit = resolveOpenAITokenLimit({
|
|
2006
|
+
provider,
|
|
2007
|
+
model,
|
|
2008
|
+
responseLength,
|
|
2009
|
+
reasoning_effort,
|
|
2010
|
+
maxTokens
|
|
2011
|
+
});
|
|
2012
|
+
if (isResponsesAPI) {
|
|
2013
|
+
if (tokenLimit !== void 0) {
|
|
2014
|
+
body.max_output_tokens = tokenLimit;
|
|
2015
|
+
}
|
|
2016
|
+
} else {
|
|
2017
|
+
if (tokenLimit !== void 0) {
|
|
2018
|
+
if (usesCompatibleChatCompletions(provider)) {
|
|
2019
|
+
body.max_tokens = tokenLimit;
|
|
2020
|
+
} else {
|
|
2021
|
+
body.max_completion_tokens = tokenLimit;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
if (isResponsesAPI) {
|
|
2026
|
+
body.input = cleanMessagesForResponsesAPI(messages);
|
|
2027
|
+
} else {
|
|
2028
|
+
body.messages = provider === "mistral" ? cleanMessagesForMistralChatCompletions(messages) : messages;
|
|
2029
|
+
}
|
|
2030
|
+
if (isGPT5Model(model)) {
|
|
2031
|
+
if (isResponsesAPI) {
|
|
2032
|
+
if (reasoning_effort) {
|
|
2033
|
+
body.reasoning = {
|
|
2034
|
+
...body.reasoning,
|
|
2035
|
+
effort: reasoning_effort
|
|
2036
|
+
};
|
|
2037
|
+
if (enableReasoningSummary) {
|
|
2038
|
+
body.reasoning.summary = "auto";
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
if (verbosity) {
|
|
2042
|
+
body.text = {
|
|
2043
|
+
...body.text,
|
|
2044
|
+
format: { type: "text" },
|
|
2045
|
+
verbosity
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
} else {
|
|
2049
|
+
if (reasoning_effort) {
|
|
2050
|
+
body.reasoning_effort = reasoning_effort;
|
|
2051
|
+
}
|
|
2052
|
+
if (verbosity) {
|
|
2053
|
+
body.verbosity = verbosity;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
if (provider === "mistral" && isMistralReasoningEffortModel(model) && reasoning_effort && isMistralReasoningEffort(reasoning_effort)) {
|
|
2058
|
+
body.reasoning_effort = reasoning_effort;
|
|
2059
|
+
}
|
|
2060
|
+
const toolDefinitions = buildOpenAIToolsDefinition({
|
|
2061
|
+
tools,
|
|
2062
|
+
mcpServers,
|
|
2063
|
+
isResponsesAPI
|
|
2064
|
+
});
|
|
2065
|
+
if (toolDefinitions.length > 0) {
|
|
2066
|
+
body.tools = toolDefinitions;
|
|
2067
|
+
if (!isResponsesAPI) {
|
|
2068
|
+
body.tool_choice = "auto";
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
return body;
|
|
2072
|
+
}
|
|
2073
|
+
function resolveOpenAITokenLimit({
|
|
2074
|
+
provider,
|
|
2075
|
+
model,
|
|
2076
|
+
responseLength,
|
|
2077
|
+
reasoning_effort,
|
|
2078
|
+
maxTokens
|
|
2079
|
+
}) {
|
|
2080
|
+
if (maxTokens !== void 0) {
|
|
2081
|
+
return maxTokens;
|
|
2082
|
+
}
|
|
2083
|
+
const baseTokenLimit = usesCompatibleChatCompletions(provider) ? responseLength !== void 0 ? getMaxTokensForResponseLength(responseLength) : void 0 : getMaxTokensForResponseLength(responseLength);
|
|
2084
|
+
if (provider !== "openai" || !isGPT5Model(model) || responseLength === void 0) {
|
|
2085
|
+
return baseTokenLimit;
|
|
2086
|
+
}
|
|
2087
|
+
const effectiveReasoningEffort = reasoning_effort ?? getDefaultReasoningEffortForGPT5Model(model);
|
|
2088
|
+
return Math.max(
|
|
2089
|
+
baseTokenLimit ?? 0,
|
|
2090
|
+
GPT5_RESPONSE_LENGTH_MIN_TOKENS[responseLength],
|
|
2091
|
+
GPT5_REASONING_MIN_TOKENS[effectiveReasoningEffort]
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
function validateMCPCompatibility(endpoint, mcpServers) {
|
|
2095
|
+
if (mcpServers.length > 0 && endpoint === ENDPOINT_OPENAI_CHAT_COMPLETIONS_API) {
|
|
2096
|
+
throw new Error(
|
|
2097
|
+
`MCP servers are not supported with Chat Completions API. Current endpoint: ${endpoint}. Please use OpenAI Responses API endpoint: ${ENDPOINT_OPENAI_RESPONSES_API}. MCP tools are only available in the Responses API endpoint.`
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
function cleanMessagesForResponsesAPI(messages) {
|
|
2102
|
+
return messages.map((msg) => {
|
|
2103
|
+
const role = msg.role === "tool" ? "user" : msg.role;
|
|
2104
|
+
const cleanMsg = {
|
|
2105
|
+
role
|
|
2106
|
+
};
|
|
2107
|
+
if (typeof msg.content === "string") {
|
|
2108
|
+
cleanMsg.content = msg.content;
|
|
2109
|
+
} else if (Array.isArray(msg.content)) {
|
|
2110
|
+
cleanMsg.content = msg.content.map((block) => {
|
|
2111
|
+
if (block.type === "text") {
|
|
2112
|
+
return {
|
|
2113
|
+
type: "input_text",
|
|
2114
|
+
text: block.text
|
|
2115
|
+
};
|
|
2116
|
+
} else if (block.type === "image_url") {
|
|
2117
|
+
return {
|
|
2118
|
+
type: "input_image",
|
|
2119
|
+
image_url: block.image_url.url
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
return block;
|
|
2123
|
+
});
|
|
2124
|
+
} else {
|
|
2125
|
+
cleanMsg.content = msg.content;
|
|
2126
|
+
}
|
|
2127
|
+
return cleanMsg;
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
function cleanMessagesForMistralChatCompletions(messages) {
|
|
2131
|
+
return messages.map((msg) => {
|
|
2132
|
+
const cleanMsg = {
|
|
2133
|
+
role: msg.role
|
|
2134
|
+
};
|
|
2135
|
+
if (!Array.isArray(msg.content)) {
|
|
2136
|
+
cleanMsg.content = msg.content;
|
|
2137
|
+
return cleanMsg;
|
|
2138
|
+
}
|
|
2139
|
+
cleanMsg.content = msg.content.map((block) => {
|
|
2140
|
+
if (block.type === "image_url" && typeof block.image_url === "object" && typeof block.image_url?.url === "string") {
|
|
2141
|
+
return {
|
|
2142
|
+
type: "image_url",
|
|
2143
|
+
image_url: block.image_url.url
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
return block;
|
|
2147
|
+
});
|
|
2148
|
+
return cleanMsg;
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
function usesCompatibleChatCompletions(provider) {
|
|
2152
|
+
return OPENAI_COMPATIBLE_CHAT_COMPLETIONS_PROVIDERS.has(provider);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// src/services/providers/openai/OpenAIChatService.ts
|
|
1956
2156
|
var OpenAIChatService = class {
|
|
1957
2157
|
/**
|
|
1958
2158
|
* Constructor
|
|
@@ -2105,7 +2305,20 @@ If it's in another language, summarize in that language.
|
|
|
2105
2305
|
return stream ? this.parseStream(res, onPartialResponse) : this.parseOneShot(await res.json());
|
|
2106
2306
|
}
|
|
2107
2307
|
async callOpenAI(messages, model, stream = false, maxTokens) {
|
|
2108
|
-
const body =
|
|
2308
|
+
const body = buildOpenAIRequestBody({
|
|
2309
|
+
provider: this.provider,
|
|
2310
|
+
endpoint: this.endpoint,
|
|
2311
|
+
messages,
|
|
2312
|
+
model,
|
|
2313
|
+
stream,
|
|
2314
|
+
tools: this.tools,
|
|
2315
|
+
mcpServers: this.mcpServers,
|
|
2316
|
+
responseLength: this.responseLength,
|
|
2317
|
+
verbosity: this.verbosity,
|
|
2318
|
+
reasoning_effort: this.reasoning_effort,
|
|
2319
|
+
enableReasoningSummary: this.enableReasoningSummary,
|
|
2320
|
+
maxTokens
|
|
2321
|
+
});
|
|
2109
2322
|
const headers = {};
|
|
2110
2323
|
const shouldSendAuthorization = this.provider !== "openai-compatible" || this.apiKey.trim() !== "";
|
|
2111
2324
|
if (shouldSendAuthorization) {
|
|
@@ -2114,199 +2327,6 @@ If it's in another language, summarize in that language.
|
|
|
2114
2327
|
const res = await ChatServiceHttpClient.post(this.endpoint, body, headers);
|
|
2115
2328
|
return res;
|
|
2116
2329
|
}
|
|
2117
|
-
/**
|
|
2118
|
-
* Build request body based on the endpoint type
|
|
2119
|
-
*/
|
|
2120
|
-
buildRequestBody(messages, model, stream, maxTokens) {
|
|
2121
|
-
const isResponsesAPI = this.endpoint === ENDPOINT_OPENAI_RESPONSES_API;
|
|
2122
|
-
this.validateMCPCompatibility();
|
|
2123
|
-
const body = {
|
|
2124
|
-
model,
|
|
2125
|
-
stream
|
|
2126
|
-
};
|
|
2127
|
-
const tokenLimit = this.resolveTokenLimit(model, maxTokens);
|
|
2128
|
-
if (isResponsesAPI) {
|
|
2129
|
-
if (tokenLimit !== void 0) {
|
|
2130
|
-
body.max_output_tokens = tokenLimit;
|
|
2131
|
-
}
|
|
2132
|
-
} else {
|
|
2133
|
-
if (tokenLimit !== void 0) {
|
|
2134
|
-
if (this.usesCompatibleChatCompletions()) {
|
|
2135
|
-
body.max_tokens = tokenLimit;
|
|
2136
|
-
} else {
|
|
2137
|
-
body.max_completion_tokens = tokenLimit;
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
if (isResponsesAPI) {
|
|
2142
|
-
body.input = this.cleanMessagesForResponsesAPI(messages);
|
|
2143
|
-
} else {
|
|
2144
|
-
body.messages = this.provider === "mistral" ? this.cleanMessagesForMistralChatCompletions(messages) : messages;
|
|
2145
|
-
}
|
|
2146
|
-
if (isGPT5Model(model)) {
|
|
2147
|
-
if (isResponsesAPI) {
|
|
2148
|
-
if (this.reasoning_effort) {
|
|
2149
|
-
body.reasoning = {
|
|
2150
|
-
...body.reasoning,
|
|
2151
|
-
effort: this.reasoning_effort
|
|
2152
|
-
};
|
|
2153
|
-
if (this.enableReasoningSummary) {
|
|
2154
|
-
body.reasoning.summary = "auto";
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
if (this.verbosity) {
|
|
2158
|
-
body.text = {
|
|
2159
|
-
...body.text,
|
|
2160
|
-
format: { type: "text" },
|
|
2161
|
-
verbosity: this.verbosity
|
|
2162
|
-
};
|
|
2163
|
-
}
|
|
2164
|
-
} else {
|
|
2165
|
-
if (this.reasoning_effort) {
|
|
2166
|
-
body.reasoning_effort = this.reasoning_effort;
|
|
2167
|
-
}
|
|
2168
|
-
if (this.verbosity) {
|
|
2169
|
-
body.verbosity = this.verbosity;
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
}
|
|
2173
|
-
if (this.provider === "mistral" && isMistralReasoningEffortModel(model) && this.reasoning_effort && isMistralReasoningEffort(this.reasoning_effort)) {
|
|
2174
|
-
body.reasoning_effort = this.reasoning_effort;
|
|
2175
|
-
}
|
|
2176
|
-
const tools = this.buildToolsDefinition();
|
|
2177
|
-
if (tools.length > 0) {
|
|
2178
|
-
body.tools = tools;
|
|
2179
|
-
if (!isResponsesAPI) {
|
|
2180
|
-
body.tool_choice = "auto";
|
|
2181
|
-
}
|
|
2182
|
-
}
|
|
2183
|
-
return body;
|
|
2184
|
-
}
|
|
2185
|
-
resolveTokenLimit(model, maxTokens) {
|
|
2186
|
-
if (maxTokens !== void 0) {
|
|
2187
|
-
return maxTokens;
|
|
2188
|
-
}
|
|
2189
|
-
const baseTokenLimit = this.usesCompatibleChatCompletions() ? this.responseLength !== void 0 ? getMaxTokensForResponseLength(this.responseLength) : void 0 : getMaxTokensForResponseLength(this.responseLength);
|
|
2190
|
-
if (this.provider !== "openai" || !isGPT5Model(model) || this.responseLength === void 0) {
|
|
2191
|
-
return baseTokenLimit;
|
|
2192
|
-
}
|
|
2193
|
-
const effectiveReasoningEffort = this.reasoning_effort ?? getDefaultReasoningEffortForGPT5Model(model);
|
|
2194
|
-
return Math.max(
|
|
2195
|
-
baseTokenLimit ?? 0,
|
|
2196
|
-
GPT5_RESPONSE_LENGTH_MIN_TOKENS[this.responseLength],
|
|
2197
|
-
GPT5_REASONING_MIN_TOKENS[effectiveReasoningEffort]
|
|
2198
|
-
);
|
|
2199
|
-
}
|
|
2200
|
-
/**
|
|
2201
|
-
* Validate MCP servers compatibility with the current endpoint
|
|
2202
|
-
*/
|
|
2203
|
-
validateMCPCompatibility() {
|
|
2204
|
-
if (this.mcpServers.length > 0 && this.endpoint === ENDPOINT_OPENAI_CHAT_COMPLETIONS_API) {
|
|
2205
|
-
throw new Error(
|
|
2206
|
-
`MCP servers are not supported with Chat Completions API. Current endpoint: ${this.endpoint}. Please use OpenAI Responses API endpoint: ${ENDPOINT_OPENAI_RESPONSES_API}. MCP tools are only available in the Responses API endpoint.`
|
|
2207
|
-
);
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
/**
|
|
2211
|
-
* Clean messages for Responses API (remove timestamp and other extra properties)
|
|
2212
|
-
*/
|
|
2213
|
-
cleanMessagesForResponsesAPI(messages) {
|
|
2214
|
-
return messages.map((msg) => {
|
|
2215
|
-
const role = msg.role === "tool" ? "user" : msg.role;
|
|
2216
|
-
const cleanMsg = {
|
|
2217
|
-
role
|
|
2218
|
-
};
|
|
2219
|
-
if (typeof msg.content === "string") {
|
|
2220
|
-
cleanMsg.content = msg.content;
|
|
2221
|
-
} else if (Array.isArray(msg.content)) {
|
|
2222
|
-
cleanMsg.content = msg.content.map((block) => {
|
|
2223
|
-
if (block.type === "text") {
|
|
2224
|
-
return {
|
|
2225
|
-
type: "input_text",
|
|
2226
|
-
text: block.text
|
|
2227
|
-
};
|
|
2228
|
-
} else if (block.type === "image_url") {
|
|
2229
|
-
return {
|
|
2230
|
-
type: "input_image",
|
|
2231
|
-
image_url: block.image_url.url
|
|
2232
|
-
// Extract the URL string directly
|
|
2233
|
-
};
|
|
2234
|
-
}
|
|
2235
|
-
return block;
|
|
2236
|
-
});
|
|
2237
|
-
} else {
|
|
2238
|
-
cleanMsg.content = msg.content;
|
|
2239
|
-
}
|
|
2240
|
-
return cleanMsg;
|
|
2241
|
-
});
|
|
2242
|
-
}
|
|
2243
|
-
cleanMessagesForMistralChatCompletions(messages) {
|
|
2244
|
-
return messages.map((msg) => {
|
|
2245
|
-
const cleanMsg = {
|
|
2246
|
-
role: msg.role
|
|
2247
|
-
};
|
|
2248
|
-
if (!Array.isArray(msg.content)) {
|
|
2249
|
-
cleanMsg.content = msg.content;
|
|
2250
|
-
return cleanMsg;
|
|
2251
|
-
}
|
|
2252
|
-
cleanMsg.content = msg.content.map((block) => {
|
|
2253
|
-
if (block.type === "image_url" && typeof block.image_url === "object" && typeof block.image_url?.url === "string") {
|
|
2254
|
-
return {
|
|
2255
|
-
type: "image_url",
|
|
2256
|
-
image_url: block.image_url.url
|
|
2257
|
-
};
|
|
2258
|
-
}
|
|
2259
|
-
return block;
|
|
2260
|
-
});
|
|
2261
|
-
return cleanMsg;
|
|
2262
|
-
});
|
|
2263
|
-
}
|
|
2264
|
-
/**
|
|
2265
|
-
* Build tools definition based on the endpoint type
|
|
2266
|
-
*/
|
|
2267
|
-
buildToolsDefinition() {
|
|
2268
|
-
const isResponsesAPI = this.endpoint === ENDPOINT_OPENAI_RESPONSES_API;
|
|
2269
|
-
const toolDefs = [];
|
|
2270
|
-
if (this.tools.length > 0) {
|
|
2271
|
-
toolDefs.push(
|
|
2272
|
-
...buildOpenAICompatibleTools(
|
|
2273
|
-
this.tools,
|
|
2274
|
-
isResponsesAPI ? "responses" : "chat-completions"
|
|
2275
|
-
)
|
|
2276
|
-
);
|
|
2277
|
-
}
|
|
2278
|
-
if (this.mcpServers.length > 0 && isResponsesAPI) {
|
|
2279
|
-
toolDefs.push(...this.buildMCPToolsDefinition());
|
|
2280
|
-
}
|
|
2281
|
-
return toolDefs;
|
|
2282
|
-
}
|
|
2283
|
-
/**
|
|
2284
|
-
* Build MCP tools definition for Responses API
|
|
2285
|
-
*/
|
|
2286
|
-
buildMCPToolsDefinition() {
|
|
2287
|
-
return this.mcpServers.map((server) => {
|
|
2288
|
-
const mcpDef = {
|
|
2289
|
-
type: "mcp",
|
|
2290
|
-
// Using 'mcp' as indicated by the error message
|
|
2291
|
-
server_label: server.name,
|
|
2292
|
-
// Use server_label as required by API
|
|
2293
|
-
server_url: server.url
|
|
2294
|
-
// Use server_url instead of url
|
|
2295
|
-
};
|
|
2296
|
-
if (server.require_approval) {
|
|
2297
|
-
mcpDef.require_approval = server.require_approval;
|
|
2298
|
-
}
|
|
2299
|
-
if (server.tool_configuration?.allowed_tools) {
|
|
2300
|
-
mcpDef.allowed_tools = server.tool_configuration.allowed_tools;
|
|
2301
|
-
}
|
|
2302
|
-
if (server.authorization_token) {
|
|
2303
|
-
mcpDef.headers = {
|
|
2304
|
-
Authorization: `Bearer ${server.authorization_token}`
|
|
2305
|
-
};
|
|
2306
|
-
}
|
|
2307
|
-
return mcpDef;
|
|
2308
|
-
});
|
|
2309
|
-
}
|
|
2310
2330
|
async handleStream(res, onPartial) {
|
|
2311
2331
|
return parseOpenAICompatibleTextStream(res, onPartial);
|
|
2312
2332
|
}
|
|
@@ -2318,9 +2338,6 @@ If it's in another language, summarize in that language.
|
|
|
2318
2338
|
parseOneShot(data) {
|
|
2319
2339
|
return parseOpenAICompatibleOneShot(data);
|
|
2320
2340
|
}
|
|
2321
|
-
usesCompatibleChatCompletions() {
|
|
2322
|
-
return OPENAI_COMPATIBLE_CHAT_COMPLETIONS_PROVIDERS.has(this.provider);
|
|
2323
|
-
}
|
|
2324
2341
|
};
|
|
2325
2342
|
|
|
2326
2343
|
// src/services/providers/deepseek/DeepSeekChatService.ts
|
|
@@ -2492,6 +2509,221 @@ If it's in another language, summarize in that language.
|
|
|
2492
2509
|
}
|
|
2493
2510
|
};
|
|
2494
2511
|
|
|
2512
|
+
// src/services/providers/gemini/geminiMessageConverter.ts
|
|
2513
|
+
function mapRoleToGemini(role) {
|
|
2514
|
+
switch (role) {
|
|
2515
|
+
case "system":
|
|
2516
|
+
return "model";
|
|
2517
|
+
case "user":
|
|
2518
|
+
return "user";
|
|
2519
|
+
case "assistant":
|
|
2520
|
+
return "model";
|
|
2521
|
+
default:
|
|
2522
|
+
return "user";
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
function convertMessagesToGeminiFormat(messages, options = {}) {
|
|
2526
|
+
const gemini = [];
|
|
2527
|
+
let currentRole = null;
|
|
2528
|
+
let currentParts = [];
|
|
2529
|
+
const pushCurrent = () => {
|
|
2530
|
+
if (currentRole && currentParts.length) {
|
|
2531
|
+
gemini.push({ role: currentRole, parts: [...currentParts] });
|
|
2532
|
+
currentParts = [];
|
|
2533
|
+
}
|
|
2534
|
+
};
|
|
2535
|
+
for (const msg of messages) {
|
|
2536
|
+
const role = mapRoleToGemini(msg.role);
|
|
2537
|
+
if (msg.tool_calls) {
|
|
2538
|
+
pushCurrent();
|
|
2539
|
+
for (const call of msg.tool_calls) {
|
|
2540
|
+
options.callIdMap?.set(call.id, call.function.name);
|
|
2541
|
+
gemini.push({
|
|
2542
|
+
role: "model",
|
|
2543
|
+
parts: [
|
|
2544
|
+
{
|
|
2545
|
+
functionCall: {
|
|
2546
|
+
name: call.function.name,
|
|
2547
|
+
args: JSON.parse(call.function.arguments || "{}")
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
]
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
continue;
|
|
2554
|
+
}
|
|
2555
|
+
if (msg.role === "tool") {
|
|
2556
|
+
pushCurrent();
|
|
2557
|
+
const funcName = msg.name ?? options.callIdMap?.get(msg.tool_call_id) ?? "result";
|
|
2558
|
+
gemini.push({
|
|
2559
|
+
role: "user",
|
|
2560
|
+
parts: [
|
|
2561
|
+
{
|
|
2562
|
+
functionResponse: {
|
|
2563
|
+
name: funcName,
|
|
2564
|
+
response: normalizeToolResult(safeJsonParse(msg.content))
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
]
|
|
2568
|
+
});
|
|
2569
|
+
continue;
|
|
2570
|
+
}
|
|
2571
|
+
if (role !== currentRole) pushCurrent();
|
|
2572
|
+
currentRole = role;
|
|
2573
|
+
currentParts.push({ text: msg.content });
|
|
2574
|
+
}
|
|
2575
|
+
pushCurrent();
|
|
2576
|
+
return gemini;
|
|
2577
|
+
}
|
|
2578
|
+
async function convertVisionMessagesToGeminiFormat(messages, options = {}) {
|
|
2579
|
+
const imageFetcher = options.imageFetcher ?? ChatServiceHttpClient.get;
|
|
2580
|
+
const encodeBlob = options.blobToBase64 ?? blobToBase64;
|
|
2581
|
+
const geminiMessages = [];
|
|
2582
|
+
let currentRole = null;
|
|
2583
|
+
let currentParts = [];
|
|
2584
|
+
for (const msg of messages) {
|
|
2585
|
+
const role = mapRoleToGemini(msg.role);
|
|
2586
|
+
if (msg.tool_calls) {
|
|
2587
|
+
for (const call of msg.tool_calls) {
|
|
2588
|
+
geminiMessages.push({
|
|
2589
|
+
role: "model",
|
|
2590
|
+
parts: [
|
|
2591
|
+
{
|
|
2592
|
+
functionCall: {
|
|
2593
|
+
name: call.function.name,
|
|
2594
|
+
args: JSON.parse(call.function.arguments || "{}")
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
]
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
continue;
|
|
2601
|
+
}
|
|
2602
|
+
if (msg.role === "tool") {
|
|
2603
|
+
const funcName = msg.name ?? options.callIdMap?.get(msg.tool_call_id) ?? "result";
|
|
2604
|
+
geminiMessages.push({
|
|
2605
|
+
role: "user",
|
|
2606
|
+
parts: [
|
|
2607
|
+
{
|
|
2608
|
+
functionResponse: {
|
|
2609
|
+
name: funcName,
|
|
2610
|
+
response: normalizeToolResult(
|
|
2611
|
+
safeJsonParse(msg.content)
|
|
2612
|
+
)
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
]
|
|
2616
|
+
});
|
|
2617
|
+
continue;
|
|
2618
|
+
}
|
|
2619
|
+
if (role !== currentRole && currentParts.length > 0) {
|
|
2620
|
+
geminiMessages.push({
|
|
2621
|
+
role: currentRole,
|
|
2622
|
+
parts: [...currentParts]
|
|
2623
|
+
});
|
|
2624
|
+
currentParts = [];
|
|
2625
|
+
}
|
|
2626
|
+
currentRole = role;
|
|
2627
|
+
if (typeof msg.content === "string") {
|
|
2628
|
+
currentParts.push({ text: msg.content });
|
|
2629
|
+
} else if (Array.isArray(msg.content)) {
|
|
2630
|
+
for (const block of msg.content) {
|
|
2631
|
+
if (block.type === "text") {
|
|
2632
|
+
currentParts.push({ text: block.text });
|
|
2633
|
+
} else if (block.type === "image_url") {
|
|
2634
|
+
try {
|
|
2635
|
+
const imageResponse = await imageFetcher(block.image_url.url);
|
|
2636
|
+
const imageBlob = await imageResponse.blob();
|
|
2637
|
+
const base64Data = await encodeBlob(imageBlob);
|
|
2638
|
+
currentParts.push({
|
|
2639
|
+
inlineData: {
|
|
2640
|
+
mimeType: imageBlob.type || "image/jpeg",
|
|
2641
|
+
data: base64Data.split(",")[1]
|
|
2642
|
+
}
|
|
2643
|
+
});
|
|
2644
|
+
} catch (error) {
|
|
2645
|
+
console.error("Error processing image:", error);
|
|
2646
|
+
throw new Error(`Failed to process image: ${error.message}`);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
if (currentRole && currentParts.length > 0) {
|
|
2653
|
+
geminiMessages.push({
|
|
2654
|
+
role: currentRole,
|
|
2655
|
+
parts: [...currentParts]
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
return geminiMessages;
|
|
2659
|
+
}
|
|
2660
|
+
function safeJsonParse(str) {
|
|
2661
|
+
try {
|
|
2662
|
+
return JSON.parse(str);
|
|
2663
|
+
} catch {
|
|
2664
|
+
return str;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
function normalizeToolResult(val) {
|
|
2668
|
+
if (val === null) return { content: null };
|
|
2669
|
+
if (typeof val === "object") return val;
|
|
2670
|
+
return { content: val };
|
|
2671
|
+
}
|
|
2672
|
+
function blobToBase64(blob) {
|
|
2673
|
+
return new Promise((resolve, reject) => {
|
|
2674
|
+
const reader = new FileReader();
|
|
2675
|
+
reader.onloadend = () => resolve(reader.result);
|
|
2676
|
+
reader.onerror = reject;
|
|
2677
|
+
reader.readAsDataURL(blob);
|
|
2678
|
+
});
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
// src/services/providers/gemini/geminiToolAdapter.ts
|
|
2682
|
+
function buildGeminiToolDeclarations(tools, mcpToolSchemas) {
|
|
2683
|
+
return [...tools, ...mcpToolSchemas].map((tool) => ({
|
|
2684
|
+
name: tool.name,
|
|
2685
|
+
description: tool.description,
|
|
2686
|
+
parameters: tool.parameters
|
|
2687
|
+
}));
|
|
2688
|
+
}
|
|
2689
|
+
function buildGeminiToolConfig(tools, mcpToolSchemas) {
|
|
2690
|
+
const functionDeclarations = buildGeminiToolDeclarations(
|
|
2691
|
+
tools,
|
|
2692
|
+
mcpToolSchemas
|
|
2693
|
+
);
|
|
2694
|
+
if (functionDeclarations.length === 0) {
|
|
2695
|
+
return void 0;
|
|
2696
|
+
}
|
|
2697
|
+
return {
|
|
2698
|
+
tools: [
|
|
2699
|
+
{
|
|
2700
|
+
functionDeclarations
|
|
2701
|
+
}
|
|
2702
|
+
],
|
|
2703
|
+
toolConfig: {
|
|
2704
|
+
functionCallingConfig: {
|
|
2705
|
+
mode: "AUTO"
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
function createFallbackMCPToolSchemas(servers) {
|
|
2711
|
+
return servers.map((server) => ({
|
|
2712
|
+
name: `mcp_${server.name}_search`,
|
|
2713
|
+
description: `Search using ${server.name} MCP server (fallback)`,
|
|
2714
|
+
parameters: {
|
|
2715
|
+
type: "object",
|
|
2716
|
+
properties: {
|
|
2717
|
+
query: {
|
|
2718
|
+
type: "string",
|
|
2719
|
+
description: "Search query"
|
|
2720
|
+
}
|
|
2721
|
+
},
|
|
2722
|
+
required: ["query"]
|
|
2723
|
+
}
|
|
2724
|
+
}));
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2495
2727
|
// src/services/providers/gemini/GeminiChatService.ts
|
|
2496
2728
|
var GeminiChatService = class {
|
|
2497
2729
|
/**
|
|
@@ -2521,24 +2753,12 @@ If it's in another language, summarize in that language.
|
|
|
2521
2753
|
this.tools = tools;
|
|
2522
2754
|
this.mcpServers = mcpServers;
|
|
2523
2755
|
}
|
|
2524
|
-
/* ────────────────────────────────── */
|
|
2525
|
-
/* Utilities */
|
|
2526
|
-
/* ────────────────────────────────── */
|
|
2527
|
-
safeJsonParse(str) {
|
|
2528
|
-
try {
|
|
2529
|
-
return JSON.parse(str);
|
|
2530
|
-
} catch {
|
|
2531
|
-
return str;
|
|
2532
|
-
}
|
|
2533
|
-
}
|
|
2534
|
-
normalizeToolResult(val) {
|
|
2535
|
-
if (val === null) return { content: null };
|
|
2536
|
-
if (typeof val === "object") return val;
|
|
2537
|
-
return { content: val };
|
|
2538
|
-
}
|
|
2539
2756
|
isGemma4Model(model) {
|
|
2540
2757
|
return /^gemma-4-/.test(model);
|
|
2541
2758
|
}
|
|
2759
|
+
shouldMinimizeThinking(model) {
|
|
2760
|
+
return model === MODEL_GEMINI_3_5_FLASH || this.isGemma4Model(model);
|
|
2761
|
+
}
|
|
2542
2762
|
shouldExposeTextPart(part, model) {
|
|
2543
2763
|
if (!part.text) return false;
|
|
2544
2764
|
if (this.isGemma4Model(model) && part.thought === true) {
|
|
@@ -2636,20 +2856,7 @@ If it's in another language, summarize in that language.
|
|
|
2636
2856
|
this.mcpSchemasInitialized = true;
|
|
2637
2857
|
} catch (error) {
|
|
2638
2858
|
console.warn("Failed to initialize MCP schemas, using fallback:", error);
|
|
2639
|
-
this.mcpToolSchemas = this.mcpServers
|
|
2640
|
-
name: `mcp_${server.name}_search`,
|
|
2641
|
-
description: `Search using ${server.name} MCP server (fallback)`,
|
|
2642
|
-
parameters: {
|
|
2643
|
-
type: "object",
|
|
2644
|
-
properties: {
|
|
2645
|
-
query: {
|
|
2646
|
-
type: "string",
|
|
2647
|
-
description: "Search query"
|
|
2648
|
-
}
|
|
2649
|
-
},
|
|
2650
|
-
required: ["query"]
|
|
2651
|
-
}
|
|
2652
|
-
}));
|
|
2859
|
+
this.mcpToolSchemas = createFallbackMCPToolSchemas(this.mcpServers);
|
|
2653
2860
|
this.mcpSchemasInitialized = true;
|
|
2654
2861
|
}
|
|
2655
2862
|
}
|
|
@@ -2709,64 +2916,6 @@ If it's in another language, summarize in that language.
|
|
|
2709
2916
|
}
|
|
2710
2917
|
}
|
|
2711
2918
|
/* ────────────────────────────────── */
|
|
2712
|
-
/* OpenAI → Gemini conversion */
|
|
2713
|
-
/* ────────────────────────────────── */
|
|
2714
|
-
convertMessagesToGeminiFormat(messages) {
|
|
2715
|
-
const gemini = [];
|
|
2716
|
-
let currentRole = null;
|
|
2717
|
-
let currentParts = [];
|
|
2718
|
-
const pushCurrent = () => {
|
|
2719
|
-
if (currentRole && currentParts.length) {
|
|
2720
|
-
gemini.push({ role: currentRole, parts: [...currentParts] });
|
|
2721
|
-
currentParts = [];
|
|
2722
|
-
}
|
|
2723
|
-
};
|
|
2724
|
-
for (const msg of messages) {
|
|
2725
|
-
const role = this.mapRoleToGemini(msg.role);
|
|
2726
|
-
if (msg.tool_calls) {
|
|
2727
|
-
pushCurrent();
|
|
2728
|
-
for (const call of msg.tool_calls) {
|
|
2729
|
-
this.callIdMap.set(call.id, call.function.name);
|
|
2730
|
-
gemini.push({
|
|
2731
|
-
role: "model",
|
|
2732
|
-
parts: [
|
|
2733
|
-
{
|
|
2734
|
-
functionCall: {
|
|
2735
|
-
name: call.function.name,
|
|
2736
|
-
args: JSON.parse(call.function.arguments || "{}")
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
]
|
|
2740
|
-
});
|
|
2741
|
-
}
|
|
2742
|
-
continue;
|
|
2743
|
-
}
|
|
2744
|
-
if (msg.role === "tool") {
|
|
2745
|
-
pushCurrent();
|
|
2746
|
-
const funcName = msg.name ?? this.callIdMap.get(msg.tool_call_id) ?? "result";
|
|
2747
|
-
gemini.push({
|
|
2748
|
-
role: "user",
|
|
2749
|
-
parts: [
|
|
2750
|
-
{
|
|
2751
|
-
functionResponse: {
|
|
2752
|
-
name: funcName,
|
|
2753
|
-
response: this.normalizeToolResult(
|
|
2754
|
-
this.safeJsonParse(msg.content)
|
|
2755
|
-
)
|
|
2756
|
-
}
|
|
2757
|
-
}
|
|
2758
|
-
]
|
|
2759
|
-
});
|
|
2760
|
-
continue;
|
|
2761
|
-
}
|
|
2762
|
-
if (role !== currentRole) pushCurrent();
|
|
2763
|
-
currentRole = role;
|
|
2764
|
-
currentParts.push({ text: msg.content });
|
|
2765
|
-
}
|
|
2766
|
-
pushCurrent();
|
|
2767
|
-
return gemini;
|
|
2768
|
-
}
|
|
2769
|
-
/* ────────────────────────────────── */
|
|
2770
2919
|
/* HTTP call */
|
|
2771
2920
|
/* ────────────────────────────────── */
|
|
2772
2921
|
async callGemini(messages, model, stream = false, maxTokens) {
|
|
@@ -2775,52 +2924,38 @@ If it's in another language, summarize in that language.
|
|
|
2775
2924
|
(b) => b?.type === "image_url" || b?.inlineData
|
|
2776
2925
|
)
|
|
2777
2926
|
);
|
|
2778
|
-
const contents = hasVision ? await
|
|
2779
|
-
messages
|
|
2780
|
-
|
|
2927
|
+
const contents = hasVision ? await convertVisionMessagesToGeminiFormat(
|
|
2928
|
+
messages,
|
|
2929
|
+
{
|
|
2930
|
+
callIdMap: this.callIdMap
|
|
2931
|
+
}
|
|
2932
|
+
) : convertMessagesToGeminiFormat(messages, {
|
|
2933
|
+
callIdMap: this.callIdMap
|
|
2934
|
+
});
|
|
2781
2935
|
const body = {
|
|
2782
2936
|
contents,
|
|
2783
2937
|
generationConfig: {
|
|
2784
2938
|
maxOutputTokens: maxTokens !== void 0 ? maxTokens : getMaxTokensForResponseLength(this.responseLength)
|
|
2785
2939
|
}
|
|
2786
2940
|
};
|
|
2787
|
-
if (this.
|
|
2941
|
+
if (this.shouldMinimizeThinking(model)) {
|
|
2788
2942
|
body.generationConfig.thinkingConfig = {
|
|
2789
2943
|
includeThoughts: false,
|
|
2790
|
-
thinkingLevel: "
|
|
2944
|
+
thinkingLevel: "MINIMAL"
|
|
2791
2945
|
};
|
|
2792
2946
|
}
|
|
2793
|
-
|
|
2794
|
-
if (this.tools.length > 0) {
|
|
2795
|
-
allToolDeclarations.push(
|
|
2796
|
-
...this.tools.map((t) => ({
|
|
2797
|
-
name: t.name,
|
|
2798
|
-
description: t.description,
|
|
2799
|
-
parameters: t.parameters
|
|
2800
|
-
}))
|
|
2801
|
-
);
|
|
2802
|
-
}
|
|
2947
|
+
let activeMCPToolSchemas = [];
|
|
2803
2948
|
if (this.mcpServers.length > 0) {
|
|
2804
2949
|
try {
|
|
2805
2950
|
await this.initializeMCPSchemas();
|
|
2806
|
-
|
|
2807
|
-
...this.mcpToolSchemas.map((t) => ({
|
|
2808
|
-
name: t.name,
|
|
2809
|
-
description: t.description,
|
|
2810
|
-
parameters: t.parameters
|
|
2811
|
-
}))
|
|
2812
|
-
);
|
|
2951
|
+
activeMCPToolSchemas = this.mcpToolSchemas;
|
|
2813
2952
|
} catch (error) {
|
|
2814
2953
|
console.warn("MCP initialization failed, skipping MCP tools:", error);
|
|
2815
2954
|
}
|
|
2816
2955
|
}
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
functionDeclarations: allToolDeclarations
|
|
2821
|
-
}
|
|
2822
|
-
];
|
|
2823
|
-
body.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
|
|
2956
|
+
const toolConfig = buildGeminiToolConfig(this.tools, activeMCPToolSchemas);
|
|
2957
|
+
if (toolConfig) {
|
|
2958
|
+
Object.assign(body, toolConfig);
|
|
2824
2959
|
}
|
|
2825
2960
|
const fetchOnce = async (ver, payload) => {
|
|
2826
2961
|
const fn = stream ? "streamGenerateContent" : "generateContent";
|
|
@@ -2831,8 +2966,8 @@ If it's in another language, summarize in that language.
|
|
|
2831
2966
|
const isLite = /flash[-_]lite/.test(model);
|
|
2832
2967
|
const isGemma4 = this.isGemma4Model(model);
|
|
2833
2968
|
const isGemini25 = /gemini-2\.5/.test(model);
|
|
2834
|
-
const
|
|
2835
|
-
const requiresV1beta = isLite || isGemma4 || isGemini25 ||
|
|
2969
|
+
const isGemini3 = /^gemini-3(?:\.[0-9]+)?-/.test(model);
|
|
2970
|
+
const requiresV1beta = isLite || isGemma4 || isGemini25 || isGemini3;
|
|
2836
2971
|
const firstVer = requiresV1beta ? "v1beta" : "v1";
|
|
2837
2972
|
const tryApi = async () => {
|
|
2838
2973
|
try {
|
|
@@ -2857,125 +2992,6 @@ If it's in another language, summarize in that language.
|
|
|
2857
2992
|
throw error;
|
|
2858
2993
|
}
|
|
2859
2994
|
}
|
|
2860
|
-
/**
|
|
2861
|
-
* Convert AITuber OnAir vision messages to Gemini format
|
|
2862
|
-
* @param messages Array of vision messages
|
|
2863
|
-
* @returns Gemini formatted vision messages
|
|
2864
|
-
*/
|
|
2865
|
-
async convertVisionMessagesToGeminiFormat(messages) {
|
|
2866
|
-
const geminiMessages = [];
|
|
2867
|
-
let currentRole = null;
|
|
2868
|
-
let currentParts = [];
|
|
2869
|
-
for (const msg of messages) {
|
|
2870
|
-
const role = this.mapRoleToGemini(msg.role);
|
|
2871
|
-
if (msg.tool_calls) {
|
|
2872
|
-
for (const call of msg.tool_calls) {
|
|
2873
|
-
geminiMessages.push({
|
|
2874
|
-
role: "model",
|
|
2875
|
-
parts: [
|
|
2876
|
-
{
|
|
2877
|
-
functionCall: {
|
|
2878
|
-
name: call.function.name,
|
|
2879
|
-
args: JSON.parse(call.function.arguments || "{}")
|
|
2880
|
-
}
|
|
2881
|
-
}
|
|
2882
|
-
]
|
|
2883
|
-
});
|
|
2884
|
-
}
|
|
2885
|
-
continue;
|
|
2886
|
-
}
|
|
2887
|
-
if (msg.role === "tool") {
|
|
2888
|
-
const funcName = msg.name ?? this.callIdMap.get(msg.tool_call_id) ?? "result";
|
|
2889
|
-
geminiMessages.push({
|
|
2890
|
-
role: "user",
|
|
2891
|
-
parts: [
|
|
2892
|
-
{
|
|
2893
|
-
functionResponse: {
|
|
2894
|
-
name: funcName,
|
|
2895
|
-
response: this.normalizeToolResult(
|
|
2896
|
-
this.safeJsonParse(msg.content)
|
|
2897
|
-
)
|
|
2898
|
-
}
|
|
2899
|
-
}
|
|
2900
|
-
]
|
|
2901
|
-
});
|
|
2902
|
-
continue;
|
|
2903
|
-
}
|
|
2904
|
-
if (role !== currentRole && currentParts.length > 0) {
|
|
2905
|
-
geminiMessages.push({
|
|
2906
|
-
role: currentRole,
|
|
2907
|
-
parts: [...currentParts]
|
|
2908
|
-
});
|
|
2909
|
-
currentParts = [];
|
|
2910
|
-
}
|
|
2911
|
-
currentRole = role;
|
|
2912
|
-
if (typeof msg.content === "string") {
|
|
2913
|
-
currentParts.push({ text: msg.content });
|
|
2914
|
-
} else if (Array.isArray(msg.content)) {
|
|
2915
|
-
for (const block of msg.content) {
|
|
2916
|
-
if (block.type === "text") {
|
|
2917
|
-
currentParts.push({ text: block.text });
|
|
2918
|
-
} else if (block.type === "image_url") {
|
|
2919
|
-
try {
|
|
2920
|
-
const imageResponse = await ChatServiceHttpClient.get(
|
|
2921
|
-
block.image_url.url
|
|
2922
|
-
);
|
|
2923
|
-
const imageBlob = await imageResponse.blob();
|
|
2924
|
-
const base64Data = await this.blobToBase64(imageBlob);
|
|
2925
|
-
currentParts.push({
|
|
2926
|
-
inlineData: {
|
|
2927
|
-
mimeType: imageBlob.type || "image/jpeg",
|
|
2928
|
-
data: base64Data.split(",")[1]
|
|
2929
|
-
// Remove the "data:image/jpeg;base64," prefix
|
|
2930
|
-
}
|
|
2931
|
-
});
|
|
2932
|
-
} catch (error) {
|
|
2933
|
-
console.error("Error processing image:", error);
|
|
2934
|
-
throw new Error(`Failed to process image: ${error.message}`);
|
|
2935
|
-
}
|
|
2936
|
-
}
|
|
2937
|
-
}
|
|
2938
|
-
}
|
|
2939
|
-
}
|
|
2940
|
-
if (currentRole && currentParts.length > 0) {
|
|
2941
|
-
geminiMessages.push({
|
|
2942
|
-
role: currentRole,
|
|
2943
|
-
parts: [...currentParts]
|
|
2944
|
-
});
|
|
2945
|
-
}
|
|
2946
|
-
return geminiMessages;
|
|
2947
|
-
}
|
|
2948
|
-
/**
|
|
2949
|
-
* Convert Blob to Base64 string
|
|
2950
|
-
* @param blob Image blob
|
|
2951
|
-
* @returns Promise with base64 encoded string
|
|
2952
|
-
*/
|
|
2953
|
-
blobToBase64(blob) {
|
|
2954
|
-
return new Promise((resolve, reject) => {
|
|
2955
|
-
const reader = new FileReader();
|
|
2956
|
-
reader.onloadend = () => resolve(reader.result);
|
|
2957
|
-
reader.onerror = reject;
|
|
2958
|
-
reader.readAsDataURL(blob);
|
|
2959
|
-
});
|
|
2960
|
-
}
|
|
2961
|
-
/**
|
|
2962
|
-
* Map AITuber OnAir roles to Gemini roles
|
|
2963
|
-
* @param role AITuber OnAir role
|
|
2964
|
-
* @returns Gemini role
|
|
2965
|
-
*/
|
|
2966
|
-
mapRoleToGemini(role) {
|
|
2967
|
-
switch (role) {
|
|
2968
|
-
case "system":
|
|
2969
|
-
return "model";
|
|
2970
|
-
// Gemini uses 'model' for system messages
|
|
2971
|
-
case "user":
|
|
2972
|
-
return "user";
|
|
2973
|
-
case "assistant":
|
|
2974
|
-
return "model";
|
|
2975
|
-
default:
|
|
2976
|
-
return "user";
|
|
2977
|
-
}
|
|
2978
|
-
}
|
|
2979
2995
|
/* ────────────────────────────────────────────────────────── */
|
|
2980
2996
|
/* Convert NDJSON stream to common format */
|
|
2981
2997
|
/* ────────────────────────────────────────────────────────── */
|