fix(tools): register get_location in availableTools and refine location UX

This commit is contained in:
Marco Beretta 2026-06-15 19:19:09 +02:00
parent 398661d340
commit f2379f7327
No known key found for this signature in database
GPG key ID: D918033D8E74CC11
9 changed files with 110 additions and 36 deletions

View file

@ -12,7 +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');
const GetLocation = require('./structured/GetLocation');
module.exports = {
...manifest,
@ -28,5 +28,5 @@ module.exports = {
TavilySearchResults,
createOpenAIImageTools,
createGeminiImageTool,
createLocationTool,
GetLocation,
};

View file

@ -171,7 +171,7 @@
"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",
"icon": "assets/logo.svg",
"authConfig": []
}
]

View file

@ -1,4 +1,4 @@
const { tool } = require('@librechat/agents/langchain/tools');
const { Tool } = require('@librechat/agents/langchain/tools');
const { formatLocationToolResult } = require('@librechat/api');
const locationSchema = {
@ -8,22 +8,31 @@ const locationSchema = {
};
/**
* Factory for the `get_location` tool, bound to the current request/user.
* @param {{ userId?: string, req?: import('express').Request }} params
* @returns {Promise<import('@librechat/agents/langchain/tools').DynamicStructuredTool>}
* GetLocation - returns the user's shared location (place, coordinates, timezone).
* Reads the resolved app config (admin feature flag) and the user's stored
* `personalization.location` from the request, and delegates formatting to
* `formatLocationToolResult`. Gracefully reports when disabled or not shared.
*/
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,
},
);
};
class GetLocation extends Tool {
constructor(fields = {}) {
super();
/** @type {boolean} Used to initialize the Tool without request context. */
this.override = fields.override ?? false;
this.req = fields.req;
this.userId = fields.userId;
this.name = 'get_location';
this.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.";
this.schema = locationSchema;
}
async _call() {
const featureEnabled = this.req?.config?.location?.enabled !== false;
const location = this.req?.user?.personalization?.location;
return formatLocationToolResult(location, { featureEnabled });
}
}
module.exports = GetLocation;

View file

@ -1,14 +1,13 @@
const createLocationTool = require('./GetLocation');
const GetLocation = require('./GetLocation');
const makeReq = ({ location, featureEnabled = true } = {}) => ({
config: { location: { enabled: featureEnabled } },
user: { id: 'user-1', personalization: location ? { location } : {} },
});
describe('createLocationTool', () => {
describe('GetLocation tool', () => {
it('returns the user location when enabled', async () => {
const tool = await createLocationTool({
userId: 'user-1',
const tool = new GetLocation({
req: makeReq({
location: {
enabled: true,
@ -24,17 +23,22 @@ describe('createLocationTool', () => {
});
it('returns a not-shared message when the user has not opted in', async () => {
const tool = await createLocationTool({ userId: 'user-1', req: makeReq({}) });
const tool = new GetLocation({ 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',
const tool = new GetLocation({
req: makeReq({ location: { enabled: true, manual: 'X' }, featureEnabled: false }),
});
const result = await tool.invoke({});
expect(result).toMatch(/disabled/i);
});
it('can be constructed without request context (override) for tool discovery', () => {
const tool = new GetLocation({ override: true });
expect(tool.name).toBe('get_location');
expect(tool.schema).toBeDefined();
});
});

View file

@ -0,0 +1,19 @@
const { Tool } = require('@librechat/agents/langchain/tools');
const GetLocation = require('../GetLocation');
describe('get_location tool registration', () => {
it('GetLocation is a Tool subclass so loadAndFormatTools discovers it', () => {
expect(GetLocation.prototype instanceof Tool).toBe(true);
});
it('can be instantiated with override for discovery without request context', () => {
const tool = new GetLocation({ override: true });
expect(tool.name).toBe('get_location');
expect(tool.schema).toBeDefined();
});
it('has a plain-object schema compatible with loadAndFormatTools (non-Zod)', () => {
const tool = new GetLocation({ override: true });
expect(tool.schema).toEqual({ type: 'object', properties: {}, required: [] });
});
});

View file

@ -34,7 +34,7 @@ const {
TavilySearchResults,
createGeminiImageTool,
createOpenAIImageTools,
createLocationTool,
GetLocation,
} = require('../');
const {
createMCPTool,
@ -182,6 +182,7 @@ const loadTools = async ({
google: GoogleSearchAPI,
open_weather: OpenWeather,
wolfram: StructuredWolfram,
get_location: GetLocation,
'stable-diffusion': StructuredSD,
'azure-ai-search': StructuredACS,
traversaal_search: TraversaalSearch,
@ -231,9 +232,6 @@ const loadTools = async ({
fileStrategy,
});
},
get_location: async () => {
return createLocationTool({ userId: user, req: options.req });
},
};
const requestedTools = {};
@ -264,6 +262,7 @@ const loadTools = async ({
dalle: imageGenOptions,
'stable-diffusion': imageGenOptions,
gemini_image_gen: imageGenOptions,
get_location: { req: options.req },
};
/** @type {Record<string, string>} */