From 362ac158d6fe524be5a447dd916dbeeeb82234d4 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Mon, 15 Jun 2026 18:34:43 +0200 Subject: [PATCH] feat(tools): add get_location built-in agent tool --- api/app/clients/tools/index.js | 2 + api/app/clients/tools/manifest.json | 7 ++++ .../clients/tools/structured/GetLocation.js | 29 ++++++++++++++ .../tools/structured/GetLocation.spec.js | 40 +++++++++++++++++++ api/app/clients/tools/util/handleTools.js | 4 ++ .../api/src/tools/registry/definitions.ts | 10 +++++ 6 files changed, 92 insertions(+) create mode 100644 api/app/clients/tools/structured/GetLocation.js create mode 100644 api/app/clients/tools/structured/GetLocation.spec.js diff --git a/api/app/clients/tools/index.js b/api/app/clients/tools/index.js index bb58e81221..6865b5ccdb 100644 --- a/api/app/clients/tools/index.js +++ b/api/app/clients/tools/index.js @@ -12,6 +12,7 @@ const TraversaalSearch = require('./structured/TraversaalSearch'); const createOpenAIImageTools = require('./structured/OpenAIImageTools'); const TavilySearchResults = require('./structured/TavilySearchResults'); const createGeminiImageTool = require('./structured/GeminiImageGen'); +const createLocationTool = require('./structured/GetLocation'); module.exports = { ...manifest, @@ -27,4 +28,5 @@ module.exports = { TavilySearchResults, createOpenAIImageTools, createGeminiImageTool, + createLocationTool, }; diff --git a/api/app/clients/tools/manifest.json b/api/app/clients/tools/manifest.json index 9637c20867..b13932522c 100644 --- a/api/app/clients/tools/manifest.json +++ b/api/app/clients/tools/manifest.json @@ -166,5 +166,12 @@ "optional": true } ] + }, + { + "name": "Get Location", + "pluginKey": "get_location", + "description": "Returns the user's shared location (place, coordinates, timezone) so the assistant can tailor language, units, and regional context.", + "icon": "assets/google-search.svg", + "authConfig": [] } ] diff --git a/api/app/clients/tools/structured/GetLocation.js b/api/app/clients/tools/structured/GetLocation.js new file mode 100644 index 0000000000..4f65332ee2 --- /dev/null +++ b/api/app/clients/tools/structured/GetLocation.js @@ -0,0 +1,29 @@ +const { tool } = require('@librechat/agents/langchain/tools'); +const { formatLocationToolResult } = require('@librechat/api'); + +const locationSchema = { + type: 'object', + properties: {}, + required: [], +}; + +/** + * Factory for the `get_location` tool, bound to the current request/user. + * @param {{ userId?: string, req?: import('express').Request }} params + * @returns {Promise} + */ +module.exports = async function createLocationTool({ req } = {}) { + return tool( + async () => { + const featureEnabled = req?.config?.location?.enabled !== false; + const location = req?.user?.personalization?.location; + return formatLocationToolResult(location, { featureEnabled }); + }, + { + name: 'get_location', + description: + "Returns the user's current location (place, coordinates, timezone) when they have shared it. Use it to tailor language, regional context, units, or weather lookups.", + schema: locationSchema, + }, + ); +}; diff --git a/api/app/clients/tools/structured/GetLocation.spec.js b/api/app/clients/tools/structured/GetLocation.spec.js new file mode 100644 index 0000000000..8207557b66 --- /dev/null +++ b/api/app/clients/tools/structured/GetLocation.spec.js @@ -0,0 +1,40 @@ +const createLocationTool = require('./GetLocation'); + +const makeReq = ({ location, featureEnabled = true } = {}) => ({ + config: { location: { enabled: featureEnabled } }, + user: { id: 'user-1', personalization: location ? { location } : {} }, +}); + +describe('createLocationTool', () => { + it('returns the user location when enabled', async () => { + const tool = await createLocationTool({ + userId: 'user-1', + req: makeReq({ + location: { + enabled: true, + source: 'manual', + manual: 'Berlin, Germany', + timezone: 'Europe/Berlin', + }, + }), + }); + const result = await tool.invoke({}); + expect(result).toContain('Berlin, Germany'); + expect(result).toContain('Europe/Berlin'); + }); + + it('returns a not-shared message when the user has not opted in', async () => { + const tool = await createLocationTool({ userId: 'user-1', req: makeReq({}) }); + const result = await tool.invoke({}); + expect(result).toMatch(/has not shared/i); + }); + + it('returns a disabled message when the admin flag is off', async () => { + const tool = await createLocationTool({ + userId: 'user-1', + req: makeReq({ location: { enabled: true, manual: 'X' }, featureEnabled: false }), + }); + const result = await tool.invoke({}); + expect(result).toMatch(/disabled/i); + }); +}); diff --git a/api/app/clients/tools/util/handleTools.js b/api/app/clients/tools/util/handleTools.js index adeb9f7ca9..8cd9356085 100644 --- a/api/app/clients/tools/util/handleTools.js +++ b/api/app/clients/tools/util/handleTools.js @@ -34,6 +34,7 @@ const { TavilySearchResults, createGeminiImageTool, createOpenAIImageTools, + createLocationTool, } = require('../'); const { createMCPTool, @@ -230,6 +231,9 @@ const loadTools = async ({ fileStrategy, }); }, + get_location: async () => { + return createLocationTool({ userId: user, req: options.req }); + }, }; const requestedTools = {}; diff --git a/packages/api/src/tools/registry/definitions.ts b/packages/api/src/tools/registry/definitions.ts index 5e953ce547..9b049c3c95 100644 --- a/packages/api/src/tools/registry/definitions.ts +++ b/packages/api/src/tools/registry/definitions.ts @@ -445,6 +445,16 @@ export const toolDefinitions: Record = { toolType: 'builtin', responseFormat: geminiToolkit.gemini_image_gen.responseFormat, }, + get_location: { + name: 'get_location', + description: "Returns the user's current location (place, coordinates, timezone) when shared.", + schema: { + type: 'object', + properties: {}, + required: [], + }, + toolType: 'builtin', + }, }; /**