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