From abcbc40b8eff1e48a689c87a4b53961d803efeba Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 15 Jul 2026 13:41:55 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=99=8B=20fix:=20Await=20`ask=5Fuser=5Fque?= =?UTF-8?q?stion`=20Tool=20Factory=20in=20`loadTools`=20(#14291)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `loadTools` contracts every `requestedTools` entry as `() => Promise` and the loader relies on it via `validTool().catch(...)`. The `ask_user_question` factory was registered synchronously, and `createAskUserQuestionTool()` returns a `DynamicStructuredTool` directly, so the call returned a tool with no `.catch`, throwing `TypeError: validTool(...).catch is not a function`. The throw happens inside the loop before `Promise.all`, so it aborted the entire tool load for the turn, not just this tool: agents with `ask_user_question` attached ran with no tools at all. --- api/app/clients/tools/util/handleTools.js | 2 +- api/app/clients/tools/util/handleTools.test.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/api/app/clients/tools/util/handleTools.js b/api/app/clients/tools/util/handleTools.js index 01b523a9a5..56eb345b8f 100644 --- a/api/app/clients/tools/util/handleTools.js +++ b/api/app/clients/tools/util/handleTools.js @@ -372,7 +372,7 @@ const loadTools = async ({ }; continue; } else if (tool === ASK_USER_QUESTION_TOOL_NAME) { - requestedTools[tool] = () => createAskUserQuestionTool(); + requestedTools[tool] = async () => createAskUserQuestionTool(); continue; } else if (tool === SET_MEMORY_TOOL_NAME || tool === DELETE_MEMORY_TOOL_NAME) { requestedTools[tool] = () => diff --git a/api/app/clients/tools/util/handleTools.test.js b/api/app/clients/tools/util/handleTools.test.js index 697649e3bd..a812443faa 100644 --- a/api/app/clients/tools/util/handleTools.test.js +++ b/api/app/clients/tools/util/handleTools.test.js @@ -52,6 +52,7 @@ jest.mock('~/config', () => ({ const { Calculator } = require('@librechat/agents'); const { Constants } = require('librechat-data-provider'); +const { ASK_USER_QUESTION_TOOL_NAME } = require('@librechat/api'); const { User } = require('~/db/models'); const PluginService = require('~/server/services/PluginService'); @@ -304,6 +305,16 @@ describe('Tool Handlers', () => { delete process.env.SD_WEBUI_URL; }); + it('loads the ask_user_question tool when not returning a map', async () => { + const { loadedTools } = await loadTools({ + user: fakeUser._id, + tools: [ASK_USER_QUESTION_TOOL_NAME], + useSpecs: true, + }); + expect(loadedTools).toHaveLength(1); + expect(loadedTools[0].name).toBe(ASK_USER_QUESTION_TOOL_NAME); + }); + it('passes request body to chat MCP tool creation and skips stale cache for BODY-scoped servers', async () => { const serverName = 'body-scoped'; const toolKey = `search${Constants.mcp_delimiter}${serverName}`;