Add backend-provided websearch connectors for OpenRouter and Gemini

e9cf606c70e2cba2aade710acfdfc6b6ca28bb3b

Cohee <18619528+Cohee1207@users.noreply.github.com>

3 files changed, +52 -0Showing whitespace changes
public/index.html+11 -0
@@ -1951,6 +1951,17 @@
19511951 </span>
19521952 </div>
19531953 </div>
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>
19541965 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek">
19551966 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
19561967 <input id="openai_function_calling" type="checkbox" />
public/scripts/openai.js+12 -0
@@ -300,6 +300,7 @@ export const settingsToUpdate = {
300300 function_calling: ['#openai_function_calling', 'function_calling', true],
301301 show_thoughts: ['#openai_show_thoughts', 'show_thoughts', true],
302302 reasoning_effort: ['#openai_reasoning_effort', 'reasoning_effort', false],
303+ enable_web_search: ['#openai_enable_web_search', 'enable_web_search', true],
303304 seed: ['#seed_openai', 'seed', false],
304305 n: ['#n_openai', 'n', false],
305306 bypass_status_check: ['#openai_bypass_status_check', 'bypass_status_check', true],
@@ -380,6 +381,7 @@ const default_settings = {
380381 custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,
381382 show_thoughts: true,
382383 reasoning_effort: 'medium',
384+ enable_web_search: false,
383385 seed: -1,
384386 n: 1,
385387};
@@ -459,6 +461,7 @@ const oai_settings = {
459461 custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,
460462 show_thoughts: true,
461463 reasoning_effort: 'medium',
464+ enable_web_search: false,
462465 seed: -1,
463466 n: 1,
464467};
@@ -2000,6 +2003,7 @@ async function sendOpenAIRequest(type, messages, signal) {
20002003 'group_names': getGroupNames(),
20012004 'include_reasoning': Boolean(oai_settings.show_thoughts),
20022005 'reasoning_effort': String(oai_settings.reasoning_effort),
2006+ 'enable_web_search': Boolean(oai_settings.enable_web_search),
20032007 };
20042008
20052009 if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) {
@@ -3222,6 +3226,7 @@ function loadOpenAISettings(data, settings) {
32223226 oai_settings.bypass_status_check = settings.bypass_status_check ?? default_settings.bypass_status_check;
32233227 oai_settings.show_thoughts = settings.show_thoughts ?? default_settings.show_thoughts;
32243228 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;
32253230 oai_settings.seed = settings.seed ?? default_settings.seed;
32263231 oai_settings.n = settings.n ?? default_settings.n;
32273232
@@ -3349,6 +3354,7 @@ function loadOpenAISettings(data, settings) {
33493354 $('#seed_openai').val(oai_settings.seed);
33503355 $('#n_openai').val(oai_settings.n);
33513356 $('#openai_show_thoughts').prop('checked', oai_settings.show_thoughts);
3357+ $('#openai_enable_web_search').prop('checked', oai_settings.enable_web_search);
33523358
33533359 $('#openai_reasoning_effort').val(oai_settings.reasoning_effort);
33543360 $(`#openai_reasoning_effort option[value="${oai_settings.reasoning_effort}"]`).prop('selected', true);
@@ -3613,6 +3619,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
36133619 function_calling: settings.function_calling,
36143620 show_thoughts: settings.show_thoughts,
36153621 reasoning_effort: settings.reasoning_effort,
3622+ enable_web_search: settings.enable_web_search,
36163623 seed: settings.seed,
36173624 n: settings.n,
36183625 };
@@ -5572,6 +5579,11 @@ export function initOpenAI() {
55725579 saveSettingsDebounced();
55735580 });
55745581
5582+ $('#openai_enable_web_search').on('input', function () {
5583+ oai_settings.enable_web_search = !!$(this).prop('checked');
5584+ saveSettingsDebounced();
5585+ });
5586+
55755587 if (!CSS.supports('field-sizing', 'content')) {
55765588 $(document).on('input', '#openai_settings .autoSetHeight', function () {
55775589 resetScrollHeight($(this));
src/endpoints/backends/chat-completions.js+29 -0
@@ -99,6 +99,21 @@ function getOpenRouterTransforms(request) {
9999}
100100
101101/**
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+/**
102117 * Sends a request to Claude API.
103118 * @param {express.Request} request Express request
104119 * @param {express.Response} response Express response
@@ -323,6 +338,7 @@ async function sendMakerSuiteRequest(request, response) {
323338
324339 const model = String(request.body.model);
325340 const stream = Boolean(request.body.stream);
341+ const enableWebSearch = Boolean(request.body.enable_web_search);
326342 const isThinking = model.includes('thinking');
327343
328344 const generationConfig = {
@@ -348,6 +364,7 @@ async function sendMakerSuiteRequest(request, response) {
348364 model.startsWith('gemini-exp')
349365 ) && request.body.use_makersuite_sysprompt;
350366
367+ const tools = [];
351368 const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, getPromptNames(request));
352369 let safetySettings = GEMINI_SAFETY;
353370
@@ -361,6 +378,13 @@ async function sendMakerSuiteRequest(request, response) {
361378 }
362379 // Most of the other models allow for setting the threshold of filters, except for HARM_CATEGORY_CIVIC_INTEGRITY, to OFF.
363380
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+
364388 let body = {
365389 contents: prompt.contents,
366390 safetySettings: safetySettings,
@@ -371,6 +395,10 @@ async function sendMakerSuiteRequest(request, response) {
371395 body.systemInstruction = prompt.system_instruction;
372396 }
373397
398+ if (tools.length) {
399+ body.tools = tools;
400+ }
401+
374402 return body;
375403 }
376404
@@ -1014,6 +1042,7 @@ router.post('/generate', jsonParser, function (request, response) {
10141042 headers = { ...OPENROUTER_HEADERS };
10151043 bodyParams = {
10161044 'transforms': getOpenRouterTransforms(request),
1045+ 'plugins': getOpenRouterPlugins(request),
10171046 'include_reasoning': Boolean(request.body.include_reasoning),
10181047 };
10191048