@aituber-onair/chat 0.35.0 → 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 +2 -2
- package/README.md +2 -2
- 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/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 +0 -21
- package/dist/cjs/services/providers/gemini/GeminiChatService.d.ts.map +1 -1
- package/dist/cjs/services/providers/gemini/GeminiChatService.js +14 -254
- 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/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 +0 -21
- package/dist/esm/services/providers/gemini/GeminiChatService.d.ts.map +1 -1
- package/dist/esm/services/providers/gemini/GeminiChatService.js +14 -254
- 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 +551 -541
- package/dist/umd/aituber-onair-chat.min.js +10 -10
- package/package.json +1 -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,
|
|
@@ -346,6 +347,7 @@ var AITuberOnAirChat = (() => {
|
|
|
346
347
|
var MODEL_CLAUDE_4_6_SONNET = "claude-sonnet-4-6";
|
|
347
348
|
var MODEL_CLAUDE_4_6_OPUS = "claude-opus-4-6";
|
|
348
349
|
var MODEL_CLAUDE_4_7_OPUS = "claude-opus-4-7";
|
|
350
|
+
var MODEL_CLAUDE_4_8_OPUS = "claude-opus-4-8";
|
|
349
351
|
var CLAUDE_VISION_SUPPORTED_MODELS = [
|
|
350
352
|
MODEL_CLAUDE_3_HAIKU,
|
|
351
353
|
MODEL_CLAUDE_4_SONNET,
|
|
@@ -355,7 +357,8 @@ var AITuberOnAirChat = (() => {
|
|
|
355
357
|
MODEL_CLAUDE_4_5_OPUS,
|
|
356
358
|
MODEL_CLAUDE_4_6_SONNET,
|
|
357
359
|
MODEL_CLAUDE_4_6_OPUS,
|
|
358
|
-
MODEL_CLAUDE_4_7_OPUS
|
|
360
|
+
MODEL_CLAUDE_4_7_OPUS,
|
|
361
|
+
MODEL_CLAUDE_4_8_OPUS
|
|
359
362
|
];
|
|
360
363
|
|
|
361
364
|
// src/constants/openrouter.ts
|
|
@@ -1247,6 +1250,94 @@ If it's in another language, summarize in that language.
|
|
|
1247
1250
|
};
|
|
1248
1251
|
}
|
|
1249
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
|
+
|
|
1250
1341
|
// src/services/providers/claude/ClaudeChatService.ts
|
|
1251
1342
|
var ClaudeChatService = class {
|
|
1252
1343
|
/**
|
|
@@ -1353,112 +1444,6 @@ If it's in another language, summarize in that language.
|
|
|
1353
1444
|
toolErrorMessage: "processVisionChat received tool_calls. ChatProcessor must use chatOnce() loop when tools are enabled."
|
|
1354
1445
|
});
|
|
1355
1446
|
}
|
|
1356
|
-
/**
|
|
1357
|
-
* Convert AITuber OnAir messages to Claude format
|
|
1358
|
-
* @param messages Array of messages
|
|
1359
|
-
* @returns Claude formatted messages
|
|
1360
|
-
*/
|
|
1361
|
-
convertMessagesToClaudeFormat(messages) {
|
|
1362
|
-
return messages.map((msg) => {
|
|
1363
|
-
return {
|
|
1364
|
-
role: this.mapRoleToClaude(msg.role),
|
|
1365
|
-
content: msg.content
|
|
1366
|
-
};
|
|
1367
|
-
});
|
|
1368
|
-
}
|
|
1369
|
-
/**
|
|
1370
|
-
* Convert AITuber OnAir vision messages to Claude format
|
|
1371
|
-
* @param messages Array of vision messages
|
|
1372
|
-
* @returns Claude formatted vision messages
|
|
1373
|
-
*/
|
|
1374
|
-
convertVisionMessagesToClaudeFormat(messages) {
|
|
1375
|
-
return messages.map((msg) => {
|
|
1376
|
-
if (typeof msg.content === "string") {
|
|
1377
|
-
return {
|
|
1378
|
-
role: this.mapRoleToClaude(msg.role),
|
|
1379
|
-
content: [
|
|
1380
|
-
{
|
|
1381
|
-
type: "text",
|
|
1382
|
-
text: msg.content
|
|
1383
|
-
}
|
|
1384
|
-
]
|
|
1385
|
-
};
|
|
1386
|
-
}
|
|
1387
|
-
if (Array.isArray(msg.content)) {
|
|
1388
|
-
const content = msg.content.map((block) => {
|
|
1389
|
-
if (block.type === "image_url") {
|
|
1390
|
-
if (block.image_url.url.startsWith("data:")) {
|
|
1391
|
-
const m = block.image_url.url.match(
|
|
1392
|
-
/^data:([^;]+);base64,(.+)$/
|
|
1393
|
-
);
|
|
1394
|
-
if (m) {
|
|
1395
|
-
return {
|
|
1396
|
-
type: "image",
|
|
1397
|
-
source: { type: "base64", media_type: m[1], data: m[2] }
|
|
1398
|
-
};
|
|
1399
|
-
}
|
|
1400
|
-
return null;
|
|
1401
|
-
}
|
|
1402
|
-
return {
|
|
1403
|
-
type: "image",
|
|
1404
|
-
source: {
|
|
1405
|
-
type: "url",
|
|
1406
|
-
url: block.image_url.url,
|
|
1407
|
-
media_type: this.getMimeTypeFromUrl(block.image_url.url)
|
|
1408
|
-
}
|
|
1409
|
-
};
|
|
1410
|
-
}
|
|
1411
|
-
return block;
|
|
1412
|
-
}).filter((b) => b);
|
|
1413
|
-
return {
|
|
1414
|
-
role: this.mapRoleToClaude(msg.role),
|
|
1415
|
-
content
|
|
1416
|
-
};
|
|
1417
|
-
}
|
|
1418
|
-
return {
|
|
1419
|
-
role: this.mapRoleToClaude(msg.role),
|
|
1420
|
-
content: []
|
|
1421
|
-
};
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
|
-
/**
|
|
1425
|
-
* Map AITuber OnAir roles to Claude roles
|
|
1426
|
-
* @param role AITuber OnAir role
|
|
1427
|
-
* @returns Claude role
|
|
1428
|
-
*/
|
|
1429
|
-
mapRoleToClaude(role) {
|
|
1430
|
-
switch (role) {
|
|
1431
|
-
case "system":
|
|
1432
|
-
return "system";
|
|
1433
|
-
case "user":
|
|
1434
|
-
return "user";
|
|
1435
|
-
case "assistant":
|
|
1436
|
-
return "assistant";
|
|
1437
|
-
default:
|
|
1438
|
-
return "user";
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
/**
|
|
1442
|
-
* Get MIME type from URL
|
|
1443
|
-
* @param url Image URL
|
|
1444
|
-
* @returns MIME type
|
|
1445
|
-
*/
|
|
1446
|
-
getMimeTypeFromUrl(url) {
|
|
1447
|
-
const extension = url.split(".").pop()?.toLowerCase();
|
|
1448
|
-
switch (extension) {
|
|
1449
|
-
case "jpg":
|
|
1450
|
-
case "jpeg":
|
|
1451
|
-
return "image/jpeg";
|
|
1452
|
-
case "png":
|
|
1453
|
-
return "image/png";
|
|
1454
|
-
case "gif":
|
|
1455
|
-
return "image/gif";
|
|
1456
|
-
case "webp":
|
|
1457
|
-
return "image/webp";
|
|
1458
|
-
default:
|
|
1459
|
-
return "image/jpeg";
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
1447
|
/**
|
|
1463
1448
|
* Call Claude API
|
|
1464
1449
|
* @param messages Array of messages to send
|
|
@@ -1478,9 +1463,7 @@ If it's in another language, summarize in that language.
|
|
|
1478
1463
|
const body = {
|
|
1479
1464
|
model,
|
|
1480
1465
|
system,
|
|
1481
|
-
messages: hasVision ?
|
|
1482
|
-
content
|
|
1483
|
-
) : this.convertMessagesToClaudeFormat(content),
|
|
1466
|
+
messages: hasVision ? convertVisionMessagesToClaudeFormat(content) : convertMessagesToClaudeFormat(content),
|
|
1484
1467
|
stream,
|
|
1485
1468
|
max_tokens: maxTokens !== void 0 ? maxTokens : getMaxTokensForResponseLength(this.responseLength)
|
|
1486
1469
|
};
|
|
@@ -1743,6 +1726,7 @@ If it's in another language, summarize in that language.
|
|
|
1743
1726
|
MODEL_CLAUDE_4_6_SONNET,
|
|
1744
1727
|
MODEL_CLAUDE_4_6_OPUS,
|
|
1745
1728
|
MODEL_CLAUDE_4_7_OPUS,
|
|
1729
|
+
MODEL_CLAUDE_4_8_OPUS,
|
|
1746
1730
|
MODEL_CLAUDE_3_HAIKU
|
|
1747
1731
|
];
|
|
1748
1732
|
}
|
|
@@ -1934,7 +1918,49 @@ If it's in another language, summarize in that language.
|
|
|
1934
1918
|
};
|
|
1935
1919
|
}
|
|
1936
1920
|
|
|
1937
|
-
// 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
|
|
1938
1964
|
var GPT5_RESPONSE_LENGTH_MIN_TOKENS = {
|
|
1939
1965
|
[CHAT_RESPONSE_LENGTH.VERY_SHORT]: 800,
|
|
1940
1966
|
[CHAT_RESPONSE_LENGTH.SHORT]: 1200,
|
|
@@ -1956,6 +1982,177 @@ If it's in another language, summarize in that language.
|
|
|
1956
1982
|
"deepseek",
|
|
1957
1983
|
"mistral"
|
|
1958
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
|
|
1959
2156
|
var OpenAIChatService = class {
|
|
1960
2157
|
/**
|
|
1961
2158
|
* Constructor
|
|
@@ -2108,7 +2305,20 @@ If it's in another language, summarize in that language.
|
|
|
2108
2305
|
return stream ? this.parseStream(res, onPartialResponse) : this.parseOneShot(await res.json());
|
|
2109
2306
|
}
|
|
2110
2307
|
async callOpenAI(messages, model, stream = false, maxTokens) {
|
|
2111
|
-
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
|
+
});
|
|
2112
2322
|
const headers = {};
|
|
2113
2323
|
const shouldSendAuthorization = this.provider !== "openai-compatible" || this.apiKey.trim() !== "";
|
|
2114
2324
|
if (shouldSendAuthorization) {
|
|
@@ -2117,199 +2327,6 @@ If it's in another language, summarize in that language.
|
|
|
2117
2327
|
const res = await ChatServiceHttpClient.post(this.endpoint, body, headers);
|
|
2118
2328
|
return res;
|
|
2119
2329
|
}
|
|
2120
|
-
/**
|
|
2121
|
-
* Build request body based on the endpoint type
|
|
2122
|
-
*/
|
|
2123
|
-
buildRequestBody(messages, model, stream, maxTokens) {
|
|
2124
|
-
const isResponsesAPI = this.endpoint === ENDPOINT_OPENAI_RESPONSES_API;
|
|
2125
|
-
this.validateMCPCompatibility();
|
|
2126
|
-
const body = {
|
|
2127
|
-
model,
|
|
2128
|
-
stream
|
|
2129
|
-
};
|
|
2130
|
-
const tokenLimit = this.resolveTokenLimit(model, maxTokens);
|
|
2131
|
-
if (isResponsesAPI) {
|
|
2132
|
-
if (tokenLimit !== void 0) {
|
|
2133
|
-
body.max_output_tokens = tokenLimit;
|
|
2134
|
-
}
|
|
2135
|
-
} else {
|
|
2136
|
-
if (tokenLimit !== void 0) {
|
|
2137
|
-
if (this.usesCompatibleChatCompletions()) {
|
|
2138
|
-
body.max_tokens = tokenLimit;
|
|
2139
|
-
} else {
|
|
2140
|
-
body.max_completion_tokens = tokenLimit;
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
if (isResponsesAPI) {
|
|
2145
|
-
body.input = this.cleanMessagesForResponsesAPI(messages);
|
|
2146
|
-
} else {
|
|
2147
|
-
body.messages = this.provider === "mistral" ? this.cleanMessagesForMistralChatCompletions(messages) : messages;
|
|
2148
|
-
}
|
|
2149
|
-
if (isGPT5Model(model)) {
|
|
2150
|
-
if (isResponsesAPI) {
|
|
2151
|
-
if (this.reasoning_effort) {
|
|
2152
|
-
body.reasoning = {
|
|
2153
|
-
...body.reasoning,
|
|
2154
|
-
effort: this.reasoning_effort
|
|
2155
|
-
};
|
|
2156
|
-
if (this.enableReasoningSummary) {
|
|
2157
|
-
body.reasoning.summary = "auto";
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
|
-
if (this.verbosity) {
|
|
2161
|
-
body.text = {
|
|
2162
|
-
...body.text,
|
|
2163
|
-
format: { type: "text" },
|
|
2164
|
-
verbosity: this.verbosity
|
|
2165
|
-
};
|
|
2166
|
-
}
|
|
2167
|
-
} else {
|
|
2168
|
-
if (this.reasoning_effort) {
|
|
2169
|
-
body.reasoning_effort = this.reasoning_effort;
|
|
2170
|
-
}
|
|
2171
|
-
if (this.verbosity) {
|
|
2172
|
-
body.verbosity = this.verbosity;
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
if (this.provider === "mistral" && isMistralReasoningEffortModel(model) && this.reasoning_effort && isMistralReasoningEffort(this.reasoning_effort)) {
|
|
2177
|
-
body.reasoning_effort = this.reasoning_effort;
|
|
2178
|
-
}
|
|
2179
|
-
const tools = this.buildToolsDefinition();
|
|
2180
|
-
if (tools.length > 0) {
|
|
2181
|
-
body.tools = tools;
|
|
2182
|
-
if (!isResponsesAPI) {
|
|
2183
|
-
body.tool_choice = "auto";
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
2186
|
-
return body;
|
|
2187
|
-
}
|
|
2188
|
-
resolveTokenLimit(model, maxTokens) {
|
|
2189
|
-
if (maxTokens !== void 0) {
|
|
2190
|
-
return maxTokens;
|
|
2191
|
-
}
|
|
2192
|
-
const baseTokenLimit = this.usesCompatibleChatCompletions() ? this.responseLength !== void 0 ? getMaxTokensForResponseLength(this.responseLength) : void 0 : getMaxTokensForResponseLength(this.responseLength);
|
|
2193
|
-
if (this.provider !== "openai" || !isGPT5Model(model) || this.responseLength === void 0) {
|
|
2194
|
-
return baseTokenLimit;
|
|
2195
|
-
}
|
|
2196
|
-
const effectiveReasoningEffort = this.reasoning_effort ?? getDefaultReasoningEffortForGPT5Model(model);
|
|
2197
|
-
return Math.max(
|
|
2198
|
-
baseTokenLimit ?? 0,
|
|
2199
|
-
GPT5_RESPONSE_LENGTH_MIN_TOKENS[this.responseLength],
|
|
2200
|
-
GPT5_REASONING_MIN_TOKENS[effectiveReasoningEffort]
|
|
2201
|
-
);
|
|
2202
|
-
}
|
|
2203
|
-
/**
|
|
2204
|
-
* Validate MCP servers compatibility with the current endpoint
|
|
2205
|
-
*/
|
|
2206
|
-
validateMCPCompatibility() {
|
|
2207
|
-
if (this.mcpServers.length > 0 && this.endpoint === ENDPOINT_OPENAI_CHAT_COMPLETIONS_API) {
|
|
2208
|
-
throw new Error(
|
|
2209
|
-
`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.`
|
|
2210
|
-
);
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
|
-
/**
|
|
2214
|
-
* Clean messages for Responses API (remove timestamp and other extra properties)
|
|
2215
|
-
*/
|
|
2216
|
-
cleanMessagesForResponsesAPI(messages) {
|
|
2217
|
-
return messages.map((msg) => {
|
|
2218
|
-
const role = msg.role === "tool" ? "user" : msg.role;
|
|
2219
|
-
const cleanMsg = {
|
|
2220
|
-
role
|
|
2221
|
-
};
|
|
2222
|
-
if (typeof msg.content === "string") {
|
|
2223
|
-
cleanMsg.content = msg.content;
|
|
2224
|
-
} else if (Array.isArray(msg.content)) {
|
|
2225
|
-
cleanMsg.content = msg.content.map((block) => {
|
|
2226
|
-
if (block.type === "text") {
|
|
2227
|
-
return {
|
|
2228
|
-
type: "input_text",
|
|
2229
|
-
text: block.text
|
|
2230
|
-
};
|
|
2231
|
-
} else if (block.type === "image_url") {
|
|
2232
|
-
return {
|
|
2233
|
-
type: "input_image",
|
|
2234
|
-
image_url: block.image_url.url
|
|
2235
|
-
// Extract the URL string directly
|
|
2236
|
-
};
|
|
2237
|
-
}
|
|
2238
|
-
return block;
|
|
2239
|
-
});
|
|
2240
|
-
} else {
|
|
2241
|
-
cleanMsg.content = msg.content;
|
|
2242
|
-
}
|
|
2243
|
-
return cleanMsg;
|
|
2244
|
-
});
|
|
2245
|
-
}
|
|
2246
|
-
cleanMessagesForMistralChatCompletions(messages) {
|
|
2247
|
-
return messages.map((msg) => {
|
|
2248
|
-
const cleanMsg = {
|
|
2249
|
-
role: msg.role
|
|
2250
|
-
};
|
|
2251
|
-
if (!Array.isArray(msg.content)) {
|
|
2252
|
-
cleanMsg.content = msg.content;
|
|
2253
|
-
return cleanMsg;
|
|
2254
|
-
}
|
|
2255
|
-
cleanMsg.content = msg.content.map((block) => {
|
|
2256
|
-
if (block.type === "image_url" && typeof block.image_url === "object" && typeof block.image_url?.url === "string") {
|
|
2257
|
-
return {
|
|
2258
|
-
type: "image_url",
|
|
2259
|
-
image_url: block.image_url.url
|
|
2260
|
-
};
|
|
2261
|
-
}
|
|
2262
|
-
return block;
|
|
2263
|
-
});
|
|
2264
|
-
return cleanMsg;
|
|
2265
|
-
});
|
|
2266
|
-
}
|
|
2267
|
-
/**
|
|
2268
|
-
* Build tools definition based on the endpoint type
|
|
2269
|
-
*/
|
|
2270
|
-
buildToolsDefinition() {
|
|
2271
|
-
const isResponsesAPI = this.endpoint === ENDPOINT_OPENAI_RESPONSES_API;
|
|
2272
|
-
const toolDefs = [];
|
|
2273
|
-
if (this.tools.length > 0) {
|
|
2274
|
-
toolDefs.push(
|
|
2275
|
-
...buildOpenAICompatibleTools(
|
|
2276
|
-
this.tools,
|
|
2277
|
-
isResponsesAPI ? "responses" : "chat-completions"
|
|
2278
|
-
)
|
|
2279
|
-
);
|
|
2280
|
-
}
|
|
2281
|
-
if (this.mcpServers.length > 0 && isResponsesAPI) {
|
|
2282
|
-
toolDefs.push(...this.buildMCPToolsDefinition());
|
|
2283
|
-
}
|
|
2284
|
-
return toolDefs;
|
|
2285
|
-
}
|
|
2286
|
-
/**
|
|
2287
|
-
* Build MCP tools definition for Responses API
|
|
2288
|
-
*/
|
|
2289
|
-
buildMCPToolsDefinition() {
|
|
2290
|
-
return this.mcpServers.map((server) => {
|
|
2291
|
-
const mcpDef = {
|
|
2292
|
-
type: "mcp",
|
|
2293
|
-
// Using 'mcp' as indicated by the error message
|
|
2294
|
-
server_label: server.name,
|
|
2295
|
-
// Use server_label as required by API
|
|
2296
|
-
server_url: server.url
|
|
2297
|
-
// Use server_url instead of url
|
|
2298
|
-
};
|
|
2299
|
-
if (server.require_approval) {
|
|
2300
|
-
mcpDef.require_approval = server.require_approval;
|
|
2301
|
-
}
|
|
2302
|
-
if (server.tool_configuration?.allowed_tools) {
|
|
2303
|
-
mcpDef.allowed_tools = server.tool_configuration.allowed_tools;
|
|
2304
|
-
}
|
|
2305
|
-
if (server.authorization_token) {
|
|
2306
|
-
mcpDef.headers = {
|
|
2307
|
-
Authorization: `Bearer ${server.authorization_token}`
|
|
2308
|
-
};
|
|
2309
|
-
}
|
|
2310
|
-
return mcpDef;
|
|
2311
|
-
});
|
|
2312
|
-
}
|
|
2313
2330
|
async handleStream(res, onPartial) {
|
|
2314
2331
|
return parseOpenAICompatibleTextStream(res, onPartial);
|
|
2315
2332
|
}
|
|
@@ -2321,9 +2338,6 @@ If it's in another language, summarize in that language.
|
|
|
2321
2338
|
parseOneShot(data) {
|
|
2322
2339
|
return parseOpenAICompatibleOneShot(data);
|
|
2323
2340
|
}
|
|
2324
|
-
usesCompatibleChatCompletions() {
|
|
2325
|
-
return OPENAI_COMPATIBLE_CHAT_COMPLETIONS_PROVIDERS.has(this.provider);
|
|
2326
|
-
}
|
|
2327
2341
|
};
|
|
2328
2342
|
|
|
2329
2343
|
// src/services/providers/deepseek/DeepSeekChatService.ts
|
|
@@ -2495,6 +2509,221 @@ If it's in another language, summarize in that language.
|
|
|
2495
2509
|
}
|
|
2496
2510
|
};
|
|
2497
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
|
+
|
|
2498
2727
|
// src/services/providers/gemini/GeminiChatService.ts
|
|
2499
2728
|
var GeminiChatService = class {
|
|
2500
2729
|
/**
|
|
@@ -2524,21 +2753,6 @@ If it's in another language, summarize in that language.
|
|
|
2524
2753
|
this.tools = tools;
|
|
2525
2754
|
this.mcpServers = mcpServers;
|
|
2526
2755
|
}
|
|
2527
|
-
/* ────────────────────────────────── */
|
|
2528
|
-
/* Utilities */
|
|
2529
|
-
/* ────────────────────────────────── */
|
|
2530
|
-
safeJsonParse(str) {
|
|
2531
|
-
try {
|
|
2532
|
-
return JSON.parse(str);
|
|
2533
|
-
} catch {
|
|
2534
|
-
return str;
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
normalizeToolResult(val) {
|
|
2538
|
-
if (val === null) return { content: null };
|
|
2539
|
-
if (typeof val === "object") return val;
|
|
2540
|
-
return { content: val };
|
|
2541
|
-
}
|
|
2542
2756
|
isGemma4Model(model) {
|
|
2543
2757
|
return /^gemma-4-/.test(model);
|
|
2544
2758
|
}
|
|
@@ -2642,20 +2856,7 @@ If it's in another language, summarize in that language.
|
|
|
2642
2856
|
this.mcpSchemasInitialized = true;
|
|
2643
2857
|
} catch (error) {
|
|
2644
2858
|
console.warn("Failed to initialize MCP schemas, using fallback:", error);
|
|
2645
|
-
this.mcpToolSchemas = this.mcpServers
|
|
2646
|
-
name: `mcp_${server.name}_search`,
|
|
2647
|
-
description: `Search using ${server.name} MCP server (fallback)`,
|
|
2648
|
-
parameters: {
|
|
2649
|
-
type: "object",
|
|
2650
|
-
properties: {
|
|
2651
|
-
query: {
|
|
2652
|
-
type: "string",
|
|
2653
|
-
description: "Search query"
|
|
2654
|
-
}
|
|
2655
|
-
},
|
|
2656
|
-
required: ["query"]
|
|
2657
|
-
}
|
|
2658
|
-
}));
|
|
2859
|
+
this.mcpToolSchemas = createFallbackMCPToolSchemas(this.mcpServers);
|
|
2659
2860
|
this.mcpSchemasInitialized = true;
|
|
2660
2861
|
}
|
|
2661
2862
|
}
|
|
@@ -2715,64 +2916,6 @@ If it's in another language, summarize in that language.
|
|
|
2715
2916
|
}
|
|
2716
2917
|
}
|
|
2717
2918
|
/* ────────────────────────────────── */
|
|
2718
|
-
/* OpenAI → Gemini conversion */
|
|
2719
|
-
/* ────────────────────────────────── */
|
|
2720
|
-
convertMessagesToGeminiFormat(messages) {
|
|
2721
|
-
const gemini = [];
|
|
2722
|
-
let currentRole = null;
|
|
2723
|
-
let currentParts = [];
|
|
2724
|
-
const pushCurrent = () => {
|
|
2725
|
-
if (currentRole && currentParts.length) {
|
|
2726
|
-
gemini.push({ role: currentRole, parts: [...currentParts] });
|
|
2727
|
-
currentParts = [];
|
|
2728
|
-
}
|
|
2729
|
-
};
|
|
2730
|
-
for (const msg of messages) {
|
|
2731
|
-
const role = this.mapRoleToGemini(msg.role);
|
|
2732
|
-
if (msg.tool_calls) {
|
|
2733
|
-
pushCurrent();
|
|
2734
|
-
for (const call of msg.tool_calls) {
|
|
2735
|
-
this.callIdMap.set(call.id, call.function.name);
|
|
2736
|
-
gemini.push({
|
|
2737
|
-
role: "model",
|
|
2738
|
-
parts: [
|
|
2739
|
-
{
|
|
2740
|
-
functionCall: {
|
|
2741
|
-
name: call.function.name,
|
|
2742
|
-
args: JSON.parse(call.function.arguments || "{}")
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
]
|
|
2746
|
-
});
|
|
2747
|
-
}
|
|
2748
|
-
continue;
|
|
2749
|
-
}
|
|
2750
|
-
if (msg.role === "tool") {
|
|
2751
|
-
pushCurrent();
|
|
2752
|
-
const funcName = msg.name ?? this.callIdMap.get(msg.tool_call_id) ?? "result";
|
|
2753
|
-
gemini.push({
|
|
2754
|
-
role: "user",
|
|
2755
|
-
parts: [
|
|
2756
|
-
{
|
|
2757
|
-
functionResponse: {
|
|
2758
|
-
name: funcName,
|
|
2759
|
-
response: this.normalizeToolResult(
|
|
2760
|
-
this.safeJsonParse(msg.content)
|
|
2761
|
-
)
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
2764
|
-
]
|
|
2765
|
-
});
|
|
2766
|
-
continue;
|
|
2767
|
-
}
|
|
2768
|
-
if (role !== currentRole) pushCurrent();
|
|
2769
|
-
currentRole = role;
|
|
2770
|
-
currentParts.push({ text: msg.content });
|
|
2771
|
-
}
|
|
2772
|
-
pushCurrent();
|
|
2773
|
-
return gemini;
|
|
2774
|
-
}
|
|
2775
|
-
/* ────────────────────────────────── */
|
|
2776
2919
|
/* HTTP call */
|
|
2777
2920
|
/* ────────────────────────────────── */
|
|
2778
2921
|
async callGemini(messages, model, stream = false, maxTokens) {
|
|
@@ -2781,9 +2924,14 @@ If it's in another language, summarize in that language.
|
|
|
2781
2924
|
(b) => b?.type === "image_url" || b?.inlineData
|
|
2782
2925
|
)
|
|
2783
2926
|
);
|
|
2784
|
-
const contents = hasVision ? await
|
|
2785
|
-
messages
|
|
2786
|
-
|
|
2927
|
+
const contents = hasVision ? await convertVisionMessagesToGeminiFormat(
|
|
2928
|
+
messages,
|
|
2929
|
+
{
|
|
2930
|
+
callIdMap: this.callIdMap
|
|
2931
|
+
}
|
|
2932
|
+
) : convertMessagesToGeminiFormat(messages, {
|
|
2933
|
+
callIdMap: this.callIdMap
|
|
2934
|
+
});
|
|
2787
2935
|
const body = {
|
|
2788
2936
|
contents,
|
|
2789
2937
|
generationConfig: {
|
|
@@ -2796,37 +2944,18 @@ If it's in another language, summarize in that language.
|
|
|
2796
2944
|
thinkingLevel: "MINIMAL"
|
|
2797
2945
|
};
|
|
2798
2946
|
}
|
|
2799
|
-
|
|
2800
|
-
if (this.tools.length > 0) {
|
|
2801
|
-
allToolDeclarations.push(
|
|
2802
|
-
...this.tools.map((t) => ({
|
|
2803
|
-
name: t.name,
|
|
2804
|
-
description: t.description,
|
|
2805
|
-
parameters: t.parameters
|
|
2806
|
-
}))
|
|
2807
|
-
);
|
|
2808
|
-
}
|
|
2947
|
+
let activeMCPToolSchemas = [];
|
|
2809
2948
|
if (this.mcpServers.length > 0) {
|
|
2810
2949
|
try {
|
|
2811
2950
|
await this.initializeMCPSchemas();
|
|
2812
|
-
|
|
2813
|
-
...this.mcpToolSchemas.map((t) => ({
|
|
2814
|
-
name: t.name,
|
|
2815
|
-
description: t.description,
|
|
2816
|
-
parameters: t.parameters
|
|
2817
|
-
}))
|
|
2818
|
-
);
|
|
2951
|
+
activeMCPToolSchemas = this.mcpToolSchemas;
|
|
2819
2952
|
} catch (error) {
|
|
2820
2953
|
console.warn("MCP initialization failed, skipping MCP tools:", error);
|
|
2821
2954
|
}
|
|
2822
2955
|
}
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
functionDeclarations: allToolDeclarations
|
|
2827
|
-
}
|
|
2828
|
-
];
|
|
2829
|
-
body.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
|
|
2956
|
+
const toolConfig = buildGeminiToolConfig(this.tools, activeMCPToolSchemas);
|
|
2957
|
+
if (toolConfig) {
|
|
2958
|
+
Object.assign(body, toolConfig);
|
|
2830
2959
|
}
|
|
2831
2960
|
const fetchOnce = async (ver, payload) => {
|
|
2832
2961
|
const fn = stream ? "streamGenerateContent" : "generateContent";
|
|
@@ -2863,125 +2992,6 @@ If it's in another language, summarize in that language.
|
|
|
2863
2992
|
throw error;
|
|
2864
2993
|
}
|
|
2865
2994
|
}
|
|
2866
|
-
/**
|
|
2867
|
-
* Convert AITuber OnAir vision messages to Gemini format
|
|
2868
|
-
* @param messages Array of vision messages
|
|
2869
|
-
* @returns Gemini formatted vision messages
|
|
2870
|
-
*/
|
|
2871
|
-
async convertVisionMessagesToGeminiFormat(messages) {
|
|
2872
|
-
const geminiMessages = [];
|
|
2873
|
-
let currentRole = null;
|
|
2874
|
-
let currentParts = [];
|
|
2875
|
-
for (const msg of messages) {
|
|
2876
|
-
const role = this.mapRoleToGemini(msg.role);
|
|
2877
|
-
if (msg.tool_calls) {
|
|
2878
|
-
for (const call of msg.tool_calls) {
|
|
2879
|
-
geminiMessages.push({
|
|
2880
|
-
role: "model",
|
|
2881
|
-
parts: [
|
|
2882
|
-
{
|
|
2883
|
-
functionCall: {
|
|
2884
|
-
name: call.function.name,
|
|
2885
|
-
args: JSON.parse(call.function.arguments || "{}")
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
]
|
|
2889
|
-
});
|
|
2890
|
-
}
|
|
2891
|
-
continue;
|
|
2892
|
-
}
|
|
2893
|
-
if (msg.role === "tool") {
|
|
2894
|
-
const funcName = msg.name ?? this.callIdMap.get(msg.tool_call_id) ?? "result";
|
|
2895
|
-
geminiMessages.push({
|
|
2896
|
-
role: "user",
|
|
2897
|
-
parts: [
|
|
2898
|
-
{
|
|
2899
|
-
functionResponse: {
|
|
2900
|
-
name: funcName,
|
|
2901
|
-
response: this.normalizeToolResult(
|
|
2902
|
-
this.safeJsonParse(msg.content)
|
|
2903
|
-
)
|
|
2904
|
-
}
|
|
2905
|
-
}
|
|
2906
|
-
]
|
|
2907
|
-
});
|
|
2908
|
-
continue;
|
|
2909
|
-
}
|
|
2910
|
-
if (role !== currentRole && currentParts.length > 0) {
|
|
2911
|
-
geminiMessages.push({
|
|
2912
|
-
role: currentRole,
|
|
2913
|
-
parts: [...currentParts]
|
|
2914
|
-
});
|
|
2915
|
-
currentParts = [];
|
|
2916
|
-
}
|
|
2917
|
-
currentRole = role;
|
|
2918
|
-
if (typeof msg.content === "string") {
|
|
2919
|
-
currentParts.push({ text: msg.content });
|
|
2920
|
-
} else if (Array.isArray(msg.content)) {
|
|
2921
|
-
for (const block of msg.content) {
|
|
2922
|
-
if (block.type === "text") {
|
|
2923
|
-
currentParts.push({ text: block.text });
|
|
2924
|
-
} else if (block.type === "image_url") {
|
|
2925
|
-
try {
|
|
2926
|
-
const imageResponse = await ChatServiceHttpClient.get(
|
|
2927
|
-
block.image_url.url
|
|
2928
|
-
);
|
|
2929
|
-
const imageBlob = await imageResponse.blob();
|
|
2930
|
-
const base64Data = await this.blobToBase64(imageBlob);
|
|
2931
|
-
currentParts.push({
|
|
2932
|
-
inlineData: {
|
|
2933
|
-
mimeType: imageBlob.type || "image/jpeg",
|
|
2934
|
-
data: base64Data.split(",")[1]
|
|
2935
|
-
// Remove the "data:image/jpeg;base64," prefix
|
|
2936
|
-
}
|
|
2937
|
-
});
|
|
2938
|
-
} catch (error) {
|
|
2939
|
-
console.error("Error processing image:", error);
|
|
2940
|
-
throw new Error(`Failed to process image: ${error.message}`);
|
|
2941
|
-
}
|
|
2942
|
-
}
|
|
2943
|
-
}
|
|
2944
|
-
}
|
|
2945
|
-
}
|
|
2946
|
-
if (currentRole && currentParts.length > 0) {
|
|
2947
|
-
geminiMessages.push({
|
|
2948
|
-
role: currentRole,
|
|
2949
|
-
parts: [...currentParts]
|
|
2950
|
-
});
|
|
2951
|
-
}
|
|
2952
|
-
return geminiMessages;
|
|
2953
|
-
}
|
|
2954
|
-
/**
|
|
2955
|
-
* Convert Blob to Base64 string
|
|
2956
|
-
* @param blob Image blob
|
|
2957
|
-
* @returns Promise with base64 encoded string
|
|
2958
|
-
*/
|
|
2959
|
-
blobToBase64(blob) {
|
|
2960
|
-
return new Promise((resolve, reject) => {
|
|
2961
|
-
const reader = new FileReader();
|
|
2962
|
-
reader.onloadend = () => resolve(reader.result);
|
|
2963
|
-
reader.onerror = reject;
|
|
2964
|
-
reader.readAsDataURL(blob);
|
|
2965
|
-
});
|
|
2966
|
-
}
|
|
2967
|
-
/**
|
|
2968
|
-
* Map AITuber OnAir roles to Gemini roles
|
|
2969
|
-
* @param role AITuber OnAir role
|
|
2970
|
-
* @returns Gemini role
|
|
2971
|
-
*/
|
|
2972
|
-
mapRoleToGemini(role) {
|
|
2973
|
-
switch (role) {
|
|
2974
|
-
case "system":
|
|
2975
|
-
return "model";
|
|
2976
|
-
// Gemini uses 'model' for system messages
|
|
2977
|
-
case "user":
|
|
2978
|
-
return "user";
|
|
2979
|
-
case "assistant":
|
|
2980
|
-
return "model";
|
|
2981
|
-
default:
|
|
2982
|
-
return "user";
|
|
2983
|
-
}
|
|
2984
|
-
}
|
|
2985
2995
|
/* ────────────────────────────────────────────────────────── */
|
|
2986
2996
|
/* Convert NDJSON stream to common format */
|
|
2987
2997
|
/* ────────────────────────────────────────────────────────── */
|