feat: Add Gemini url_context parameter support

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 <noreply@anthropic.com>
This commit is contained in:
Dominik Hayon 2026-03-18 16:35:05 +01:00
parent b5a55b23a4
commit 5e50931953
8 changed files with 106 additions and 0 deletions

View file

@ -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.",

View file

@ -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 = {

View file

@ -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<string, unknown>, { [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<string, unknown>)[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<string, unknown>)[param];
}
@ -329,6 +352,10 @@ export function getGoogleConfig(
tools.push({ googleSearch: {} });
}
if (enableUrlContext) {
tools.push({ urlContext: {} });
}
// Return the final shape
return {
/** @type {GoogleAIToolType[]} */

View file

@ -712,6 +712,19 @@ const google: Record<string, SettingDefinition> = {
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,
];

View file

@ -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();

View file

@ -143,6 +143,10 @@ export const conversationPreset = {
web_search: {
type: Boolean,
},
/** Google API */
url_context: {
type: Boolean,
},
disableStreaming: {
type: Boolean,
},

View file

@ -51,6 +51,7 @@ export interface IPreset extends Document {
verbosity?: string;
useResponsesApi?: boolean;
web_search?: boolean;
url_context?: boolean;
disableStreaming?: boolean;
fileTokenLimit?: number;
}

View file

@ -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