From 5e5093195396a0fabdfd091348f3a39f159309ed Mon Sep 17 00:00:00 2001 From: Dominik Hayon Date: Wed, 18 Mar 2026 16:35:05 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20feat:=20Add=20Gemini=20`url=5Fc?= =?UTF-8?q?ontext`=20parameter=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a toggleable URL Context setting for the Google endpoint, allowing Gemini models to fetch and use content from URLs in the prompt. Mirrors the existing `web_search` / `googleSearch` pattern across schemas, types, parameter settings, backend handler, and tests. Co-Authored-By: Claude Opus 4.6 --- client/src/locales/en/translation.json | 2 + packages/api/src/endpoints/google/llm.spec.ts | 50 +++++++++++++++++++ packages/api/src/endpoints/google/llm.ts | 27 ++++++++++ .../data-provider/src/parameterSettings.ts | 15 ++++++ packages/data-provider/src/schemas.ts | 6 +++ packages/data-schemas/src/schema/defaults.ts | 4 ++ packages/data-schemas/src/schema/preset.ts | 1 + packages/data-schemas/src/types/convo.ts | 1 + 8 files changed, 106 insertions(+) diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index afd1072b61..677b9b4b31 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -353,6 +353,8 @@ "com_endpoint_use_active_assistant": "Use Active Assistant", "com_endpoint_use_responses_api": "Use Responses API", "com_endpoint_use_search_grounding": "Grounding with Google Search", + "com_endpoint_use_url_context": "URL Context", + "com_endpoint_google_use_url_context": "Use Gemini's URL context feature to fetch and use content from URLs in your prompt. Supports up to 20 URLs per request.", "com_endpoint_verbosity": "Verbosity", "com_error_endpoint_models_not_loaded": "Models for {{0}} could not be loaded. Please refresh the page and try again.", "com_error_expired_user_key": "Provided key for {{0}} expired at {{1}}. Please provide a new key and try again.", diff --git a/packages/api/src/endpoints/google/llm.spec.ts b/packages/api/src/endpoints/google/llm.spec.ts index 6e2a8ddb25..4b23c26238 100644 --- a/packages/api/src/endpoints/google/llm.spec.ts +++ b/packages/api/src/endpoints/google/llm.spec.ts @@ -634,6 +634,56 @@ describe('getGoogleConfig', () => { }); }); + describe('URL Context Functionality', () => { + it('should enable url_context when url_context is true', () => { + const credentials = { + [AuthKeys.GOOGLE_API_KEY]: 'test-api-key', + }; + + const result = getGoogleConfig(credentials, { + modelOptions: { + model: 'gemini-2.0-flash', + url_context: true, + }, + }); + + expect(result.tools).toContainEqual({ urlContext: {} }); + }); + + it('should not enable url_context when url_context is false', () => { + const credentials = { + [AuthKeys.GOOGLE_API_KEY]: 'test-api-key', + }; + + const result = getGoogleConfig(credentials, { + modelOptions: { + model: 'gemini-2.0-flash', + url_context: false, + }, + }); + + expect(result.tools).not.toContainEqual({ urlContext: {} }); + }); + + it('should handle url_context with web_search together', () => { + const credentials = { + [AuthKeys.GOOGLE_API_KEY]: 'test-api-key', + }; + + const result = getGoogleConfig(credentials, { + modelOptions: { + model: 'gemini-2.0-flash', + web_search: true, + url_context: true, + }, + }); + + expect(result.tools).toContainEqual({ googleSearch: {} }); + expect(result.tools).toContainEqual({ urlContext: {} }); + expect(result.tools).toHaveLength(2); + }); + }); + describe('Default and Add Parameters', () => { it('should apply default parameters when fields are undefined', () => { const credentials = { diff --git a/packages/api/src/endpoints/google/llm.ts b/packages/api/src/endpoints/google/llm.ts index 83951f9e0c..165a1cc0b0 100644 --- a/packages/api/src/endpoints/google/llm.ts +++ b/packages/api/src/endpoints/google/llm.ts @@ -150,6 +150,7 @@ export function getGoogleConfig( const { web_search, + url_context, thinkingLevel, thinking = googleSettings.thinking.default, thinkingBudget = googleSettings.thinkingBudget.default, @@ -157,6 +158,7 @@ export function getGoogleConfig( } = options.modelOptions || {}; let enableWebSearch = web_search; + let enableUrlContext = url_context; const llmConfig: GoogleClientOptions | VertexAIClientOptions = removeNullishValues( { @@ -282,6 +284,14 @@ export function getGoogleConfig( continue; } + /** Handle url_context separately - don't add to config */ + if (key === 'url_context') { + if (enableUrlContext === undefined && typeof value === 'boolean') { + enableUrlContext = value; + } + continue; + } + if (knownGoogleParams.has(key)) { /** Route known Google params to llmConfig only if undefined */ applyDefaultParams(llmConfig as Record, { [key]: value }); @@ -301,6 +311,14 @@ export function getGoogleConfig( continue; } + /** Handle url_context separately - don't add to config */ + if (key === 'url_context') { + if (typeof value === 'boolean') { + enableUrlContext = value; + } + continue; + } + if (knownGoogleParams.has(key)) { /** Route known Google params to llmConfig */ (llmConfig as Record)[key] = value; @@ -317,6 +335,11 @@ export function getGoogleConfig( return; } + if (param === 'url_context') { + enableUrlContext = false; + return; + } + if (param in llmConfig) { delete (llmConfig as Record)[param]; } @@ -329,6 +352,10 @@ export function getGoogleConfig( tools.push({ googleSearch: {} }); } + if (enableUrlContext) { + tools.push({ urlContext: {} }); + } + // Return the final shape return { /** @type {GoogleAIToolType[]} */ diff --git a/packages/data-provider/src/parameterSettings.ts b/packages/data-provider/src/parameterSettings.ts index d0cfdf210f..7578bee70e 100644 --- a/packages/data-provider/src/parameterSettings.ts +++ b/packages/data-provider/src/parameterSettings.ts @@ -712,6 +712,19 @@ const google: Record = { showDefault: false, columnSpan: 2, }, + url_context: { + key: 'url_context', + label: 'com_endpoint_use_url_context', + labelCode: true, + description: 'com_endpoint_google_use_url_context', + descriptionCode: true, + type: 'boolean', + default: false, + component: 'switch', + optionType: 'model', + showDefault: false, + columnSpan: 2, + }, }; const googleConfig: SettingsConfiguration = [ @@ -727,6 +740,7 @@ const googleConfig: SettingsConfiguration = [ google.thinkingBudget, google.thinkingLevel, google.web_search, + google.url_context, librechat.fileTokenLimit, ]; @@ -747,6 +761,7 @@ const googleCol2: SettingsConfiguration = [ google.thinkingBudget, google.thinkingLevel, google.web_search, + google.url_context, librechat.fileTokenLimit, ]; diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts index 63a7ed574e..0c6588f8a7 100644 --- a/packages/data-provider/src/schemas.ts +++ b/packages/data-provider/src/schemas.ts @@ -761,6 +761,8 @@ export const tConversationSchema = z.object({ effort: eAnthropicEffortSchema.optional().nullable(), /* OpenAI Responses API / Anthropic API / Google API */ web_search: z.boolean().optional(), + /* Google API */ + url_context: z.boolean().optional(), /* disable streaming */ disableStreaming: z.boolean().optional(), /* assistant */ @@ -869,6 +871,8 @@ export const tQueryParamsSchema = tConversationSchema useResponsesApi: true, /** @endpoints openAI, anthropic, google */ web_search: true, + /** @endpoints google */ + url_context: true, /** @endpoints openAI, custom, azureOpenAI */ disableStreaming: true, /** @endpoints google, anthropic, bedrock */ @@ -959,6 +963,7 @@ export const googleBaseSchema = tConversationSchema.pick({ thinkingBudget: true, thinkingLevel: true, web_search: true, + url_context: true, fileTokenLimit: true, iconURL: true, greeting: true, @@ -993,6 +998,7 @@ export const googleGenConfigSchema = z }) .optional(), web_search: z.boolean().optional(), + url_context: z.boolean().optional(), }) .strip() .optional(); diff --git a/packages/data-schemas/src/schema/defaults.ts b/packages/data-schemas/src/schema/defaults.ts index 9b50bceb1d..ca225ae354 100644 --- a/packages/data-schemas/src/schema/defaults.ts +++ b/packages/data-schemas/src/schema/defaults.ts @@ -143,6 +143,10 @@ export const conversationPreset = { web_search: { type: Boolean, }, + /** Google API */ + url_context: { + type: Boolean, + }, disableStreaming: { type: Boolean, }, diff --git a/packages/data-schemas/src/schema/preset.ts b/packages/data-schemas/src/schema/preset.ts index fc23d86c0b..6a6e57fc92 100644 --- a/packages/data-schemas/src/schema/preset.ts +++ b/packages/data-schemas/src/schema/preset.ts @@ -51,6 +51,7 @@ export interface IPreset extends Document { verbosity?: string; useResponsesApi?: boolean; web_search?: boolean; + url_context?: boolean; disableStreaming?: boolean; fileTokenLimit?: number; } diff --git a/packages/data-schemas/src/types/convo.ts b/packages/data-schemas/src/types/convo.ts index 43965a5827..8355e13e45 100644 --- a/packages/data-schemas/src/types/convo.ts +++ b/packages/data-schemas/src/types/convo.ts @@ -49,6 +49,7 @@ export interface IConversation extends Document { verbosity?: string; useResponsesApi?: boolean; web_search?: boolean; + url_context?: boolean; disableStreaming?: boolean; fileTokenLimit?: number; // Additional fields From 8a0307a3ec05600a8e80150b071addf231f75537 Mon Sep 17 00:00:00 2001 From: Dominik Hayon Date: Wed, 18 Mar 2026 16:51:38 +0100 Subject: [PATCH 2/4] update test model --- packages/api/src/endpoints/google/llm.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/api/src/endpoints/google/llm.spec.ts b/packages/api/src/endpoints/google/llm.spec.ts index 4b23c26238..edb6e280e4 100644 --- a/packages/api/src/endpoints/google/llm.spec.ts +++ b/packages/api/src/endpoints/google/llm.spec.ts @@ -642,7 +642,7 @@ describe('getGoogleConfig', () => { const result = getGoogleConfig(credentials, { modelOptions: { - model: 'gemini-2.0-flash', + model: 'gemini-2.5-flash', url_context: true, }, }); @@ -657,7 +657,7 @@ describe('getGoogleConfig', () => { const result = getGoogleConfig(credentials, { modelOptions: { - model: 'gemini-2.0-flash', + model: 'gemini-2.5-flash', url_context: false, }, }); @@ -672,7 +672,7 @@ describe('getGoogleConfig', () => { const result = getGoogleConfig(credentials, { modelOptions: { - model: 'gemini-2.0-flash', + model: 'gemini-2.5-flash', web_search: true, url_context: true, }, From f77fda94038961ba29c64cd4e1b14e15bf131dea Mon Sep 17 00:00:00 2001 From: Dominik Hayon Date: Wed, 18 Mar 2026 17:02:04 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=94=A7=20fix:=20Filter=20`urlContext`?= =?UTF-8?q?=20in=20OpenAI=20transform=20and=20add=20param=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `urlContext` to the Google-specific tool filter set in `transformToOpenAIConfig` so it doesn't leak into OpenAI-compatible flows. Also adds tests for url_context via defaultParams, addParams, and dropParams. Co-Authored-By: Claude Opus 4.6 --- packages/api/src/endpoints/google/llm.spec.ts | 51 +++++++++++++++++++ .../api/src/endpoints/openai/transform.ts | 23 ++++++--- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/packages/api/src/endpoints/google/llm.spec.ts b/packages/api/src/endpoints/google/llm.spec.ts index edb6e280e4..15a4d47fa5 100644 --- a/packages/api/src/endpoints/google/llm.spec.ts +++ b/packages/api/src/endpoints/google/llm.spec.ts @@ -682,6 +682,57 @@ describe('getGoogleConfig', () => { expect(result.tools).toContainEqual({ urlContext: {} }); expect(result.tools).toHaveLength(2); }); + + it('should enable url_context via defaultParams', () => { + const credentials = { + [AuthKeys.GOOGLE_API_KEY]: 'test-api-key', + }; + + const result = getGoogleConfig(credentials, { + modelOptions: { + model: 'gemini-2.5-flash', + }, + defaultParams: { + url_context: true, + }, + }); + + expect(result.tools).toContainEqual({ urlContext: {} }); + }); + + it('should enable url_context via addParams when initially disabled', () => { + const credentials = { + [AuthKeys.GOOGLE_API_KEY]: 'test-api-key', + }; + + const result = getGoogleConfig(credentials, { + modelOptions: { + model: 'gemini-2.5-flash', + url_context: false, + }, + addParams: { + url_context: true, + }, + }); + + expect(result.tools).toContainEqual({ urlContext: {} }); + }); + + it('should disable url_context via dropParams', () => { + const credentials = { + [AuthKeys.GOOGLE_API_KEY]: 'test-api-key', + }; + + const result = getGoogleConfig(credentials, { + modelOptions: { + model: 'gemini-2.5-flash', + url_context: true, + }, + dropParams: ['url_context'], + }); + + expect(result.tools).not.toContainEqual({ urlContext: {} }); + }); }); describe('Default and Add Parameters', () => { diff --git a/packages/api/src/endpoints/openai/transform.ts b/packages/api/src/endpoints/openai/transform.ts index c65e2cd6f5..3573823c47 100644 --- a/packages/api/src/endpoints/openai/transform.ts +++ b/packages/api/src/endpoints/openai/transform.ts @@ -16,7 +16,7 @@ const googleExcludeParams = new Set([ ]); /** Google-specific tool types that have no OpenAI-compatible equivalent */ -const googleToolsToFilter = new Set(['googleSearch']); +const googleToolsToFilter = new Set(['googleSearch', 'urlContext']); export type ConfigTools = Array> | Array; @@ -134,11 +134,13 @@ export function transformToOpenAIConfig({ /** * Filter out provider-specific tools that have no OpenAI equivalent. - * Exception: If web_search was explicitly enabled via addParams or defaultParams, - * preserve googleSearch tools (pass through in Google-native format). + * Exception: If web_search or url_context was explicitly enabled via addParams or defaultParams, + * preserve the corresponding Google-native tools (googleSearch, urlContext). */ const webSearchExplicitlyEnabled = addParams?.web_search === true || defaultParams?.web_search === true; + const urlContextExplicitlyEnabled = + addParams?.url_context === true || defaultParams?.url_context === true; const filterGoogleTool = (tool: unknown): boolean => { if (!isGoogle) { @@ -149,11 +151,18 @@ export function transformToOpenAIConfig({ } const toolKeys = Object.keys(tool as Record); const isGoogleSpecificTool = toolKeys.some((key) => googleToolsToFilter.has(key)); - /** Preserve googleSearch if web_search was explicitly enabled */ - if (isGoogleSpecificTool && webSearchExplicitlyEnabled) { - return true; + if (isGoogleSpecificTool) { + const hasGoogleSearch = toolKeys.includes('googleSearch'); + const hasUrlContext = toolKeys.includes('urlContext'); + if (hasGoogleSearch && webSearchExplicitlyEnabled) { + return true; + } + if (hasUrlContext && urlContextExplicitlyEnabled) { + return true; + } + return false; } - return !isGoogleSpecificTool; + return true; }; const filteredTools = Array.isArray(tools) ? tools.filter(filterGoogleTool) : []; From dac0667bda84afd1dee54d564c751784df749e25 Mon Sep 17 00:00:00 2001 From: Dominik Hayon Date: Wed, 18 Mar 2026 17:12:04 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=94=A7=20fix:=20Skip=20`url=5Fcontext?= =?UTF-8?q?`=20in=20OpenAI=20transform=20param=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevent url_context from leaking into modelKwargs during addParams/ dropParams processing — it should only influence tool selection. Co-Authored-By: Claude Opus 4.6 --- packages/api/src/endpoints/openai/transform.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/api/src/endpoints/openai/transform.ts b/packages/api/src/endpoints/openai/transform.ts index 3573823c47..2cbd1b6e3d 100644 --- a/packages/api/src/endpoints/openai/transform.ts +++ b/packages/api/src/endpoints/openai/transform.ts @@ -93,8 +93,8 @@ export function transformToOpenAIConfig({ if (addParams && typeof addParams === 'object') { for (const [key, value] of Object.entries(addParams)) { - /** Skip web_search - it's handled separately as a tool */ - if (key === 'web_search') { + /** Skip web_search and url_context - they're handled separately as tools */ + if (key === 'web_search' || key === 'url_context') { continue; } @@ -113,8 +113,8 @@ export function transformToOpenAIConfig({ if (dropParams && Array.isArray(dropParams)) { dropParams.forEach((param) => { - /** Skip web_search - handled separately */ - if (param === 'web_search') { + /** Skip web_search and url_context - handled separately as tools */ + if (param === 'web_search' || param === 'url_context') { return; }