diff --git a/api/server/routes/config.js b/api/server/routes/config.js index 79f36552ca..7e352959f4 100644 --- a/api/server/routes/config.js +++ b/api/server/routes/config.js @@ -181,6 +181,18 @@ function buildWebSearchConfig(appConfig) { }; } +function buildLocationStartupConfig(appConfig) { + const loc = appConfig?.location; + if (!loc || loc.enabled === false) { + return undefined; + } + const out = { enabled: true }; + if (loc.geocoder?.endpoint) { + out.geocoder = { endpoint: loc.geocoder.endpoint }; + } + return out; +} + function buildCloudFrontStartupConfig() { const config = getCloudFrontConfig(); if ( @@ -284,6 +296,11 @@ router.get('/', async function (req, res) { payload.webSearch = webSearch; } + const location = buildLocationStartupConfig(appConfig); + if (location) { + payload.location = location; + } + const buildInfo = buildBuildInfoPayload(appConfig?.interfaceConfig); if (buildInfo) { payload.buildInfo = buildInfo; diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts index 8edbd8d8fe..06bafeacc5 100644 --- a/packages/data-provider/src/config.ts +++ b/packages/data-provider/src/config.ts @@ -1409,6 +1409,12 @@ export type TStartupConfig = { scraperProvider?: ScraperProviders; rerankerType?: RerankerTypes; }; + location?: { + enabled: boolean; + geocoder?: { + endpoint?: string; + }; + }; cloudFront?: { cookieRefresh?: { endpoint: string; @@ -1561,6 +1567,17 @@ export const webSearchSchema = z.object({ export type TWebSearchConfig = DeepPartial>; +export const locationSchema = z.object({ + enabled: z.boolean().default(true), + geocoder: z + .object({ + endpoint: z.string().url().optional(), + }) + .optional(), +}); + +export type TLocationConfig = z.infer; + export const ocrSchema = z.object({ mistralModel: z.string().optional(), apiKey: z.string().optional().default('${OCR_API_KEY}'), @@ -1687,6 +1704,7 @@ export const configSchema = z.object({ cache: z.boolean().default(true), ocr: ocrSchema.optional(), webSearch: webSearchSchema.optional(), + location: locationSchema.optional(), memory: memorySchema.optional(), summarization: summarizationConfigSchema.optional(), skillSync: skillSyncConfigSchema, diff --git a/packages/data-schemas/src/app/location.spec.ts b/packages/data-schemas/src/app/location.spec.ts new file mode 100644 index 0000000000..6fa7f9028f --- /dev/null +++ b/packages/data-schemas/src/app/location.spec.ts @@ -0,0 +1,19 @@ +import { loadLocationConfig } from './location'; + +describe('loadLocationConfig', () => { + it('defaults to enabled when unset', () => { + expect(loadLocationConfig(undefined)).toEqual({ enabled: true }); + }); + + it('respects an explicit disable', () => { + expect(loadLocationConfig({ enabled: false })).toEqual({ enabled: false }); + }); + + it('passes through a geocoder endpoint', () => { + const result = loadLocationConfig({ + enabled: true, + geocoder: { endpoint: 'https://example.com/geo' }, + }); + expect(result.geocoder?.endpoint).toBe('https://example.com/geo'); + }); +}); diff --git a/packages/data-schemas/src/app/location.ts b/packages/data-schemas/src/app/location.ts new file mode 100644 index 0000000000..96b63c813b --- /dev/null +++ b/packages/data-schemas/src/app/location.ts @@ -0,0 +1,15 @@ +import type { TCustomConfig, TLocationConfig } from 'librechat-data-provider'; + +/** + * Resolves the `location` section of librechat.yaml into the runtime config. + * Defaults to enabled when the section is omitted. + */ +export function loadLocationConfig(location: TCustomConfig['location']): TLocationConfig { + if (!location) { + return { enabled: true }; + } + return { + enabled: location.enabled ?? true, + ...(location.geocoder?.endpoint ? { geocoder: { endpoint: location.geocoder.endpoint } } : {}), + }; +} diff --git a/packages/data-schemas/src/app/service.ts b/packages/data-schemas/src/app/service.ts index fe3ae1b007..429d27eace 100644 --- a/packages/data-schemas/src/app/service.ts +++ b/packages/data-schemas/src/app/service.ts @@ -8,6 +8,7 @@ import type { TCustomConfig, FileSources, DeepPartial } from 'librechat-data-pro import type { AppConfig, FunctionTool } from '~/types/app'; import { loadDefaultInterface } from './interface'; import { loadTurnstileConfig } from './turnstile'; +import { loadLocationConfig } from './location'; import { agentsConfigSetup } from './agents'; import { loadWebSearchConfig } from './web'; import { processModelSpecs } from './specs'; @@ -97,6 +98,7 @@ export const AppService = async (params?: { const ocr = loadOCRConfig(config.ocr); const webSearch = loadWebSearchConfig(config.webSearch); + const location = loadLocationConfig(config.location); const memory = loadMemoryConfig(config.memory); const summarization = loadSummarizationConfig(config); const skillSync = loadSkillSyncConfig(config); @@ -139,6 +141,7 @@ export const AppService = async (params?: { balance, skillSync, webSearch, + location, mcpSettings, fileStrategy, registration, diff --git a/packages/data-schemas/src/types/app.ts b/packages/data-schemas/src/types/app.ts index 0eb0f4d60b..2ffd851168 100644 --- a/packages/data-schemas/src/types/app.ts +++ b/packages/data-schemas/src/types/app.ts @@ -63,6 +63,8 @@ export interface AppConfig { summarization?: SummarizationConfig; /** Web search configuration */ webSearch?: TCustomConfig['webSearch']; + /** Location feature configuration */ + location?: TCustomConfig['location']; /** Message filter configuration (PII and future filter types) */ messageFilter?: TCustomConfig['messageFilter']; /** Skill sync configuration */