Merge pull request #3625 from SillyTavern/gemini-search Add backend-provided websearch connectors for OpenRouter and Gemini
Signed| @@ -1951,7 +1951,18 @@ | |||
| 1951 | </span> | 1951 | </span> |
| 1952 | </div> | 1952 | </div> |
| 1953 | </div> | 1953 | </div> |
| 1954 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek"> | 1954 | <div class="range-block" data-source="makersuite,openrouter"> |
| 1955 | <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand"> | ||
| 1956 | <input id="openai_enable_web_search" type="checkbox" /> | ||
| 1957 | <span data-i18n="Enable web search">Enable web search</span> | ||
| 1958 | </label> | ||
| 1959 | <div class="flexBasis100p toggle-description justifyLeft"> | ||
| 1960 | <span> | ||
| 1961 | Use search capabilities provided by the backend. | ||
| 1962 | </span> | ||
| 1963 | </div> | ||
| 1964 | </div> | ||
| 1965 | <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite"> | ||
| 1955 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> | 1966 | <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand"> |
| 1956 | <input id="openai_function_calling" type="checkbox" /> | 1967 | <input id="openai_function_calling" type="checkbox" /> |
| 1957 | <span data-i18n="Enable function calling">Enable function calling</span> | 1968 | <span data-i18n="Enable function calling">Enable function calling</span> |
| @@ -300,6 +300,7 @@ export const settingsToUpdate = { | |||
| 300 | function_calling: ['#openai_function_calling', 'function_calling', true], | 300 | function_calling: ['#openai_function_calling', 'function_calling', true], |
| 301 | show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true], | 301 | show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true], |
| 302 | reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false], | 302 | reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false], |
| 303 | enable_web_search: ['#openai_enable_web_search', 'enable_web_search', true], | ||
| 303 | seed: ['#seed_openai', 'seed', false], | 304 | seed: ['#seed_openai', 'seed', false], |
| 304 | n: ['#n_openai', 'n', false], | 305 | n: ['#n_openai', 'n', false], |
| 305 | bypass_status_check: ['#openai_bypass_status_check', 'bypass_status_check', true], | 306 | bypass_status_check: ['#openai_bypass_status_check', 'bypass_status_check', true], |
| @@ -380,6 +381,7 @@ const default_settings = { | |||
| 380 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, | 381 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, |
| 381 | show_thoughts: true, | 382 | show_thoughts: true, |
| 382 | reasoning_effort: 'medium', | 383 | reasoning_effort: 'medium', |
| 384 | enable_web_search: false, | ||
| 383 | seed: -1, | 385 | seed: -1, |
| 384 | n: 1, | 386 | n: 1, |
| 385 | }; | 387 | }; |
| @@ -459,6 +461,7 @@ const oai_settings = { | |||
| 459 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, | 461 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, |
| 460 | show_thoughts: true, | 462 | show_thoughts: true, |
| 461 | reasoning_effort: 'medium', | 463 | reasoning_effort: 'medium', |
| 464 | enable_web_search: false, | ||
| 462 | seed: -1, | 465 | seed: -1, |
| 463 | n: 1, | 466 | n: 1, |
| 464 | }; | 467 | }; |
| @@ -2000,6 +2003,7 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 2000 | 'group_names': getGroupNames(), | 2003 | 'group_names': getGroupNames(), |
| 2001 | 'include_reasoning': Boolean(oai_settings.show_thoughts), | 2004 | 'include_reasoning': Boolean(oai_settings.show_thoughts), |
| 2002 | 'reasoning_effort': String(oai_settings.reasoning_effort), | 2005 | 'reasoning_effort': String(oai_settings.reasoning_effort), |
| 2006 | 'enable_web_search': Boolean(oai_settings.enable_web_search), | ||
| 2003 | }; | 2007 | }; |
| 2004 | 2008 | ||
| 2005 | if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) { | 2009 | if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) { |
| @@ -3222,6 +3226,7 @@ function loadOpenAISettings(data, settings) { | |||
| 3222 | oai_settings.bypass_status_check = settings.bypass_status_check ?? default_settings.bypass_status_check; | 3226 | oai_settings.bypass_status_check = settings.bypass_status_check ?? default_settings.bypass_status_check; |
| 3223 | oai_settings.show_thoughts = settings.show_thoughts ?? default_settings.show_thoughts; | 3227 | oai_settings.show_thoughts = settings.show_thoughts ?? default_settings.show_thoughts; |
| 3224 | oai_settings.reasoning_effort = settings.reasoning_effort ?? default_settings.reasoning_effort; | 3228 | oai_settings.reasoning_effort = settings.reasoning_effort ?? default_settings.reasoning_effort; |
| 3229 | oai_settings.enable_web_search = settings.enable_web_search ?? default_settings.enable_web_search; | ||
| 3225 | oai_settings.seed = settings.seed ?? default_settings.seed; | 3230 | oai_settings.seed = settings.seed ?? default_settings.seed; |
| 3226 | oai_settings.n = settings.n ?? default_settings.n; | 3231 | oai_settings.n = settings.n ?? default_settings.n; |
| 3227 | 3232 | ||
| @@ -3349,6 +3354,7 @@ function loadOpenAISettings(data, settings) { | |||
| 3349 | $('#seed_openai').val(oai_settings.seed); | 3354 | $('#seed_openai').val(oai_settings.seed); |
| 3350 | $('#n_openai').val(oai_settings.n); | 3355 | $('#n_openai').val(oai_settings.n); |
| 3351 | $('#openai_show_thoughts').prop('checked', oai_settings.show_thoughts); | 3356 | $('#openai_show_thoughts').prop('checked', oai_settings.show_thoughts); |
| 3357 | $('#openai_enable_web_search').prop('checked', oai_settings.enable_web_search); | ||
| 3352 | 3358 | ||
| 3353 | $('#openai_reasoning_effort').val(oai_settings.reasoning_effort); | 3359 | $('#openai_reasoning_effort').val(oai_settings.reasoning_effort); |
| 3354 | $(`#openai_reasoning_effort option[value="${oai_settings.reasoning_effort}"]`).prop('selected', true); | 3360 | $(`#openai_reasoning_effort option[value="${oai_settings.reasoning_effort}"]`).prop('selected', true); |
| @@ -3613,6 +3619,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) { | |||
| 3613 | function_calling: settings.function_calling, | 3619 | function_calling: settings.function_calling, |
| 3614 | show_thoughts: settings.show_thoughts, | 3620 | show_thoughts: settings.show_thoughts, |
| 3615 | reasoning_effort: settings.reasoning_effort, | 3621 | reasoning_effort: settings.reasoning_effort, |
| 3622 | enable_web_search: settings.enable_web_search, | ||
| 3616 | seed: settings.seed, | 3623 | seed: settings.seed, |
| 3617 | n: settings.n, | 3624 | n: settings.n, |
| 3618 | }; | 3625 | }; |
| @@ -5572,6 +5579,11 @@ export function initOpenAI() { | |||
| 5572 | saveSettingsDebounced(); | 5579 | saveSettingsDebounced(); |
| 5573 | }); | 5580 | }); |
| 5574 | 5581 | ||
| 5582 | $('#openai_enable_web_search').on('input', function () { | ||
| 5583 | oai_settings.enable_web_search = !!$(this).prop('checked'); | ||
| 5584 | saveSettingsDebounced(); | ||
| 5585 | }); | ||
| 5586 | |||
| 5575 | if (!CSS.supports('field-sizing', 'content')) { | 5587 | if (!CSS.supports('field-sizing', 'content')) { |
| 5576 | $(document).on('input', '#openai_settings .autoSetHeight', function () { | 5588 | $(document).on('input', '#openai_settings .autoSetHeight', function () { |
| 5577 | resetScrollHeight($(this)); | 5589 | resetScrollHeight($(this)); |
| @@ -137,9 +137,14 @@ async function* parseStreamData(json) { | |||
| 137 | else if (Array.isArray(json.candidates)) { | 137 | else if (Array.isArray(json.candidates)) { |
| 138 | for (let i = 0; i < json.candidates.length; i++) { | 138 | for (let i = 0; i < json.candidates.length; i++) { |
| 139 | const isNotPrimary = json.candidates?.[0]?.index > 0; | 139 | const isNotPrimary = json.candidates?.[0]?.index > 0; |
| 140 | const hasToolCalls = json?.candidates?.[0]?.content?.parts?.some(p => p?.functionCall); | ||
| 140 | if (isNotPrimary || json.candidates.length === 0) { | 141 | if (isNotPrimary || json.candidates.length === 0) { |
| 141 | return null; | 142 | return null; |
| 142 | } | 143 | } |
| 144 | if (hasToolCalls) { | ||
| 145 | yield { data: json, chunk: '' }; | ||
| 146 | return; | ||
| 147 | } | ||
| 143 | if (typeof json.candidates[0].content === 'object' && Array.isArray(json.candidates[i].content.parts)) { | 148 | if (typeof json.candidates[0].content === 'object' && Array.isArray(json.candidates[i].content.parts)) { |
| 144 | for (let j = 0; j < json.candidates[i].content.parts.length; j++) { | 149 | for (let j = 0; j < json.candidates[i].content.parts.length; j++) { |
| 145 | if (typeof json.candidates[i].content.parts[j].text === 'string') { | 150 | if (typeof json.candidates[i].content.parts[j].text === 'string') { |
| @@ -506,6 +506,26 @@ export class ToolManager { | |||
| 506 | } | 506 | } |
| 507 | } | 507 | } |
| 508 | } | 508 | } |
| 509 | if (Array.isArray(parsed?.candidates)) { | ||
| 510 | for (let choiceIndex = 0; choiceIndex < parsed.candidates.length; choiceIndex++) { | ||
| 511 | const candidate = parsed.candidates[choiceIndex]; | ||
| 512 | if (Array.isArray(candidate?.content?.parts)) { | ||
| 513 | for (let toolCallIndex = 0; toolCallIndex < candidate.content.parts.length; toolCallIndex++) { | ||
| 514 | const part = candidate.content.parts[toolCallIndex]; | ||
| 515 | if (part.functionCall) { | ||
| 516 | if (!Array.isArray(toolCalls[choiceIndex])) { | ||
| 517 | toolCalls[choiceIndex] = []; | ||
| 518 | } | ||
| 519 | if (toolCalls[choiceIndex][toolCallIndex] === undefined) { | ||
| 520 | toolCalls[choiceIndex][toolCallIndex] = {}; | ||
| 521 | } | ||
| 522 | const targetToolCall = toolCalls[choiceIndex][toolCallIndex]; | ||
| 523 | ToolManager.#applyToolCallDelta(targetToolCall, part.functionCall); | ||
| 524 | } | ||
| 525 | } | ||
| 526 | } | ||
| 527 | } | ||
| 528 | } | ||
| 509 | } | 529 | } |
| 510 | 530 | ||
| 511 | /** | 531 | /** |
| @@ -564,6 +584,7 @@ export class ToolManager { | |||
| 564 | chat_completion_sources.GROQ, | 584 | chat_completion_sources.GROQ, |
| 565 | chat_completion_sources.COHERE, | 585 | chat_completion_sources.COHERE, |
| 566 | chat_completion_sources.DEEPSEEK, | 586 | chat_completion_sources.DEEPSEEK, |
| 587 | chat_completion_sources.MAKERSUITE, | ||
| 567 | ]; | 588 | ]; |
| 568 | return supportedSources.includes(oai_settings.chat_completion_source); | 589 | return supportedSources.includes(oai_settings.chat_completion_source); |
| 569 | } | 590 | } |
| @@ -585,8 +606,11 @@ export class ToolManager { | |||
| 585 | * @returns {any[]} Tool calls from the response data | 606 | * @returns {any[]} Tool calls from the response data |
| 586 | */ | 607 | */ |
| 587 | static #getToolCallsFromData(data) { | 608 | static #getToolCallsFromData(data) { |
| 609 | const getRandomId = () => Math.random().toString(36).substring(2); | ||
| 588 | const isClaudeToolCall = c => Array.isArray(c) ? c.filter(x => x).every(isClaudeToolCall) : c?.input && c?.name && c?.id; | 610 | const isClaudeToolCall = c => Array.isArray(c) ? c.filter(x => x).every(isClaudeToolCall) : c?.input && c?.name && c?.id; |
| 611 | const isGoogleToolCall = c => Array.isArray(c) ? c.filter(x => x).every(isGoogleToolCall) : c?.name && c?.args; | ||
| 589 | const convertClaudeToolCall = c => ({ id: c.id, function: { name: c.name, arguments: c.input } }); | 612 | const convertClaudeToolCall = c => ({ id: c.id, function: { name: c.name, arguments: c.input } }); |
| 613 | const convertGoogleToolCall = (c) => ({ id: getRandomId(), function: { name: c.name, arguments: c.args } }); | ||
| 590 | 614 | ||
| 591 | // Parsed tool calls from streaming data | 615 | // Parsed tool calls from streaming data |
| 592 | if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0])) { | 616 | if (Array.isArray(data) && data.length > 0 && Array.isArray(data[0])) { |
| @@ -594,6 +618,10 @@ export class ToolManager { | |||
| 594 | return data[0].filter(x => x).map(convertClaudeToolCall); | 618 | return data[0].filter(x => x).map(convertClaudeToolCall); |
| 595 | } | 619 | } |
| 596 | 620 | ||
| 621 | if (isGoogleToolCall(data[0])) { | ||
| 622 | return data[0].filter(x => x).map(convertGoogleToolCall); | ||
| 623 | } | ||
| 624 | |||
| 597 | if (typeof data[0]?.[0]?.tool_calls === 'object') { | 625 | if (typeof data[0]?.[0]?.tool_calls === 'object') { |
| 598 | return Array.isArray(data[0]?.[0]?.tool_calls) ? data[0][0].tool_calls : [data[0][0].tool_calls]; | 626 | return Array.isArray(data[0]?.[0]?.tool_calls) ? data[0][0].tool_calls : [data[0][0].tool_calls]; |
| 599 | } | 627 | } |
| @@ -601,6 +629,11 @@ export class ToolManager { | |||
| 601 | return data[0]; | 629 | return data[0]; |
| 602 | } | 630 | } |
| 603 | 631 | ||
| 632 | // Google AI Studio tool calls | ||
| 633 | if (Array.isArray(data?.responseContent?.parts)) { | ||
| 634 | return data.responseContent.parts.filter(p => p.functionCall).map(p => convertGoogleToolCall(p.functionCall)); | ||
| 635 | } | ||
| 636 | |||
| 604 | // Parsed tool calls from non-streaming data | 637 | // Parsed tool calls from non-streaming data |
| 605 | if (Array.isArray(data?.choices)) { | 638 | if (Array.isArray(data?.choices)) { |
| 606 | // Find a choice with 0-index | 639 | // Find a choice with 0-index |
| @@ -99,6 +99,21 @@ function getOpenRouterTransforms(request) { | |||
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | /** | 101 | /** |
| 102 | * Gets OpenRouter plugins based on the request. | ||
| 103 | * @param {import('express').Request} request | ||
| 104 | * @returns {any[]} OpenRouter plugins | ||
| 105 | */ | ||
| 106 | function getOpenRouterPlugins(request) { | ||
| 107 | const plugins = []; | ||
| 108 | |||
| 109 | if (request.body.enable_web_search) { | ||
| 110 | plugins.push({ 'id': 'web' }); | ||
| 111 | } | ||
| 112 | |||
| 113 | return plugins; | ||
| 114 | } | ||
| 115 | |||
| 116 | /** | ||
| 102 | * Sends a request to Claude API. | 117 | * Sends a request to Claude API. |
| 103 | * @param {express.Request} request Express request | 118 | * @param {express.Request} request Express request |
| 104 | * @param {express.Response} response Express response | 119 | * @param {express.Response} response Express response |
| @@ -323,6 +338,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 323 | 338 | ||
| 324 | const model = String(request.body.model); | 339 | const model = String(request.body.model); |
| 325 | const stream = Boolean(request.body.stream); | 340 | const stream = Boolean(request.body.stream); |
| 341 | const enableWebSearch = Boolean(request.body.enable_web_search); | ||
| 326 | const isThinking = model.includes('thinking'); | 342 | const isThinking = model.includes('thinking'); |
| 327 | 343 | ||
| 328 | const generationConfig = { | 344 | const generationConfig = { |
| @@ -348,6 +364,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 348 | model.startsWith('gemini-exp') | 364 | model.startsWith('gemini-exp') |
| 349 | ) && request.body.use_makersuite_sysprompt; | 365 | ) && request.body.use_makersuite_sysprompt; |
| 350 | 366 | ||
| 367 | const tools = []; | ||
| 351 | const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, getPromptNames(request)); | 368 | const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, getPromptNames(request)); |
| 352 | let safetySettings = GEMINI_SAFETY; | 369 | let safetySettings = GEMINI_SAFETY; |
| 353 | 370 | ||
| @@ -361,6 +378,26 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 361 | } | 378 | } |
| 362 | // Most of the other models allow for setting the threshold of filters, except for HARM_CATEGORY_CIVIC_INTEGRITY, to OFF. | 379 | // Most of the other models allow for setting the threshold of filters, except for HARM_CATEGORY_CIVIC_INTEGRITY, to OFF. |
| 363 | 380 | ||
| 381 | if (enableWebSearch) { | ||
| 382 | const searchTool = model.includes('1.5') || model.includes('1.0') | ||
| 383 | ? ({ google_search_retrieval: {} }) | ||
| 384 | : ({ google_search: {} }); | ||
| 385 | tools.push(searchTool); | ||
| 386 | } | ||
| 387 | |||
| 388 | if (Array.isArray(request.body.tools) && request.body.tools.length > 0) { | ||
| 389 | const functionDeclarations = []; | ||
| 390 | for (const tool of request.body.tools) { | ||
| 391 | if (tool.type === 'function') { | ||
| 392 | if (tool.function.parameters?.$schema) { | ||
| 393 | delete tool.function.parameters.$schema; | ||
| 394 | } | ||
| 395 | functionDeclarations.push(tool.function); | ||
| 396 | } | ||
| 397 | } | ||
| 398 | tools.push({ function_declarations: functionDeclarations }); | ||
| 399 | } | ||
| 400 | |||
| 364 | let body = { | 401 | let body = { |
| 365 | contents: prompt.contents, | 402 | contents: prompt.contents, |
| 366 | safetySettings: safetySettings, | 403 | safetySettings: safetySettings, |
| @@ -371,6 +408,10 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 371 | body.systemInstruction = prompt.system_instruction; | 408 | body.systemInstruction = prompt.system_instruction; |
| 372 | } | 409 | } |
| 373 | 410 | ||
| 411 | if (tools.length) { | ||
| 412 | body.tools = tools; | ||
| 413 | } | ||
| 414 | |||
| 374 | return body; | 415 | return body; |
| 375 | } | 416 | } |
| 376 | 417 | ||
| @@ -426,10 +467,11 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 426 | } | 467 | } |
| 427 | 468 | ||
| 428 | const responseContent = candidates[0].content ?? candidates[0].output; | 469 | const responseContent = candidates[0].content ?? candidates[0].output; |
| 470 | const functionCall = (candidates?.[0]?.content?.parts ?? []).some(part => part.functionCall); | ||
| 429 | console.warn('Google AI Studio response:', responseContent); | 471 | console.warn('Google AI Studio response:', responseContent); |
| 430 | 472 | ||
| 431 | const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.filter(part => !part.thought)?.map(part => part.text)?.join('\n\n'); | 473 | const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.filter(part => !part.thought)?.map(part => part.text)?.join('\n\n'); |
| 432 | if (!responseText) { | 474 | if (!responseText && !functionCall) { |
| 433 | let message = 'Google AI Studio Candidate text empty'; | 475 | let message = 'Google AI Studio Candidate text empty'; |
| 434 | console.warn(message, generateResponseJson); | 476 | console.warn(message, generateResponseJson); |
| 435 | return response.send({ error: { message } }); | 477 | return response.send({ error: { message } }); |
| @@ -1014,6 +1056,7 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 1014 | headers = { ...OPENROUTER_HEADERS }; | 1056 | headers = { ...OPENROUTER_HEADERS }; |
| 1015 | bodyParams = { | 1057 | bodyParams = { |
| 1016 | 'transforms': getOpenRouterTransforms(request), | 1058 | 'transforms': getOpenRouterTransforms(request), |
| 1059 | 'plugins': getOpenRouterPlugins(request), | ||
| 1017 | 'include_reasoning': Boolean(request.body.include_reasoning), | 1060 | 'include_reasoning': Boolean(request.body.include_reasoning), |
| 1018 | }; | 1061 | }; |
| 1019 | 1062 | ||
| @@ -1,5 +1,5 @@ | |||
| 1 | import crypto from 'node:crypto'; | 1 | import crypto from 'node:crypto'; |
| 2 | import { getConfigValue } from './util.js'; | 2 | import { getConfigValue, tryParse } from './util.js'; |
| 3 | 3 | ||
| 4 | const PROMPT_PLACEHOLDER = getConfigValue('promptPlaceholder', 'Let\'s get started.'); | 4 | const PROMPT_PLACEHOLDER = getConfigValue('promptPlaceholder', 'Let\'s get started.'); |
| 5 | 5 | ||
| @@ -411,11 +411,12 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) { | |||
| 411 | } | 411 | } |
| 412 | 412 | ||
| 413 | const system_instruction = { parts: { text: sys_prompt.trim() } }; | 413 | const system_instruction = { parts: { text: sys_prompt.trim() } }; |
| 414 | const toolNameMap = {}; | ||
| 414 | 415 | ||
| 415 | const contents = []; | 416 | const contents = []; |
| 416 | messages.forEach((message, index) => { | 417 | messages.forEach((message, index) => { |
| 417 | // fix the roles | 418 | // fix the roles |
| 418 | if (message.role === 'system') { | 419 | if (message.role === 'system' || message.role === 'tool') { |
| 419 | message.role = 'user'; | 420 | message.role = 'user'; |
| 420 | } else if (message.role === 'assistant') { | 421 | } else if (message.role === 'assistant') { |
| 421 | message.role = 'model'; | 422 | message.role = 'model'; |
| @@ -423,7 +424,21 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) { | |||
| 423 | 424 | ||
| 424 | // Convert the content to an array of parts | 425 | // Convert the content to an array of parts |
| 425 | if (!Array.isArray(message.content)) { | 426 | if (!Array.isArray(message.content)) { |
| 426 | message.content = [{ type: 'text', text: String(message.content ?? '') }]; | 427 | const content = (() => { |
| 428 | const hasToolCalls = Array.isArray(message.tool_calls) && message.tool_calls.length > 0; | ||
| 429 | const hasToolCallId = typeof message.tool_call_id === 'string' && message.tool_call_id.length > 0; | ||
| 430 | |||
| 431 | if (hasToolCalls) { | ||
| 432 | return { type: 'tool_calls', tool_calls: message.tool_calls }; | ||
| 433 | } | ||
| 434 | |||
| 435 | if (hasToolCallId) { | ||
| 436 | return { type: 'tool_call_id', tool_call_id: message.tool_call_id, content: String(message.content ?? '') }; | ||
| 437 | } | ||
| 438 | |||
| 439 | return { type: 'text', text: String(message.content ?? '') }; | ||
| 440 | })(); | ||
| 441 | message.content = [content]; | ||
| 427 | } | 442 | } |
| 428 | 443 | ||
| 429 | // similar story as claude | 444 | // similar story as claude |
| @@ -455,6 +470,25 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) { | |||
| 455 | message.content.forEach((part) => { | 470 | message.content.forEach((part) => { |
| 456 | if (part.type === 'text') { | 471 | if (part.type === 'text') { |
| 457 | parts.push({ text: part.text }); | 472 | parts.push({ text: part.text }); |
| 473 | } else if (part.type === 'tool_call_id') { | ||
| 474 | const name = toolNameMap[part.tool_call_id] ?? 'unknown'; | ||
| 475 | parts.push({ | ||
| 476 | functionResponse: { | ||
| 477 | name: name, | ||
| 478 | response: { name: name, content: part.content }, | ||
| 479 | }, | ||
| 480 | }); | ||
| 481 | } else if (part.type === 'tool_calls') { | ||
| 482 | part.tool_calls.forEach((toolCall) => { | ||
| 483 | parts.push({ | ||
| 484 | functionCall: { | ||
| 485 | name: toolCall.function.name, | ||
| 486 | args: tryParse(toolCall.function.arguments) ?? toolCall.function.arguments, | ||
| 487 | }, | ||
| 488 | }); | ||
| 489 | |||
| 490 | toolNameMap[toolCall.id] = toolCall.function.name; | ||
| 491 | }); | ||
| 458 | } else if (part.type === 'image_url' && isMultimodal) { | 492 | } else if (part.type === 'image_url' && isMultimodal) { |
| 459 | const mimeType = part.image_url.url.split(';')[0].split(':')[1]; | 493 | const mimeType = part.image_url.url.split(';')[0].split(':')[1]; |
| 460 | const base64Data = part.image_url.url.split(',')[1]; | 494 | const base64Data = part.image_url.url.split(',')[1]; |
| @@ -473,7 +507,7 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) { | |||
| 473 | if (part.text) { | 507 | if (part.text) { |
| 474 | contents[contents.length - 1].parts[0].text += '\n\n' + part.text; | 508 | contents[contents.length - 1].parts[0].text += '\n\n' + part.text; |
| 475 | } | 509 | } |
| 476 | if (part.inlineData) { | 510 | if (part.inlineData || part.functionCall || part.functionResponse) { |
| 477 | contents[contents.length - 1].parts.push(part); | 511 | contents[contents.length - 1].parts.push(part); |
| 478 | } | 512 | } |
| 479 | }); | 513 | }); |