From b4e52d0c9e64847214371ae2686c3375c9afb84c Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Tue, 12 May 2026 16:28:34 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=8B=A6=E6=88=AA=20ExecuteExtraTool=20?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E8=B0=83=E7=94=A8=E6=9C=AA=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E7=9A=84=E5=BB=B6=E8=BF=9F=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 模型在未通过 SearchExtraTools 发现工具的情况下直接调用 ExecuteExtraTool, 因不知道工具 schema 导致参数错误(如 libraryName: undefined)。 双重修复: 1. ExecuteTool.call() 添加服务端拦截:检查目标 deferred 工具是否已被发现 2. 更新 系统提示:要求先搜索再执行 Co-Authored-By: glm-5-turbo --- .../src/tools/ExecuteTool/ExecuteTool.ts | 32 +++++++++++++++++++ .../__tests__/ExecuteTool.runner.ts | 24 ++++++++++++-- src/services/api/claude.ts | 2 +- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/packages/builtin-tools/src/tools/ExecuteTool/ExecuteTool.ts b/packages/builtin-tools/src/tools/ExecuteTool/ExecuteTool.ts index 52103bb1b..1ba030e98 100644 --- a/packages/builtin-tools/src/tools/ExecuteTool/ExecuteTool.ts +++ b/packages/builtin-tools/src/tools/ExecuteTool/ExecuteTool.ts @@ -10,8 +10,14 @@ import { } from 'src/Tool.js' import { lazySchema } from 'src/utils/lazySchema.js' import { createUserMessage } from 'src/utils/messages.js' +import { + extractDiscoveredToolNames, + isSearchExtraToolsEnabledOptimistic, + isSearchExtraToolsToolAvailable, +} from 'src/utils/searchExtraTools.js' import { DESCRIPTION, getPrompt } from './prompt.js' import { EXECUTE_TOOL_NAME } from './constants.js' +import { isDeferredTool } from '../SearchExtraToolsTool/prompt.js' export const inputSchema = lazySchema(() => z.object({ @@ -74,6 +80,32 @@ export const ExecuteTool = buildTool({ } } + // Guard: block execution of undiscovered deferred tools. + // When tool search is active, deferred tools must be discovered via + // SearchExtraTools first so the model has seen their schemas and knows + // the correct parameters. Executing an undiscovered tool almost always + // fails with parameter validation errors. + if ( + isSearchExtraToolsEnabledOptimistic() && + isSearchExtraToolsToolAvailable(tools) && + isDeferredTool(targetTool) + ) { + const discovered = extractDiscoveredToolNames(context.messages) + if (!discovered.has(input.tool_name)) { + return { + data: { + result: null, + tool_name: input.tool_name, + }, + newMessages: [ + createUserMessage({ + content: `Tool "${input.tool_name}" has not been discovered yet. You must first use SearchExtraTools to discover this tool before executing it.\n\nUsage: SearchExtraTools("select:${input.tool_name}")`, + }), + ], + } + } + } + // Check if the target tool is currently enabled if (!targetTool.isEnabled()) { return { diff --git a/packages/builtin-tools/src/tools/ExecuteTool/__tests__/ExecuteTool.runner.ts b/packages/builtin-tools/src/tools/ExecuteTool/__tests__/ExecuteTool.runner.ts index eb10facf1..c8ab3c8f8 100644 --- a/packages/builtin-tools/src/tools/ExecuteTool/__tests__/ExecuteTool.runner.ts +++ b/packages/builtin-tools/src/tools/ExecuteTool/__tests__/ExecuteTool.runner.ts @@ -33,10 +33,10 @@ mock.module('src/utils/searchExtraTools.js', () => ({ isSearchExtraToolsEnabledOptimistic: () => true, getAutoSearchExtraToolsCharThreshold: () => 100, getSearchExtraToolsMode: () => 'tst' as const, - isSearchExtraToolsToolAvailable: async () => true, + isSearchExtraToolsToolAvailable: () => true, isSearchExtraToolsEnabled: async () => true, isToolReferenceBlock: () => false, - extractDiscoveredToolNames: () => new Set(), + extractDiscoveredToolNames: () => new Set(['TestTool', 'SecretTool']), isDeferredToolsDeltaEnabled: () => false, getDeferredToolsDelta: () => null, })) @@ -154,6 +154,26 @@ describe('ExecuteTool', () => { expect(result.newMessages).toBeDefined() }) + test('returns error when deferred tool has not been discovered via SearchExtraTools', async () => { + const mockTarget = makeMockTool('UndiscoveredTool', 'result') + const ctx = makeContext([mockTarget]) + + const result = await ExecuteTool.call( + { tool_name: 'UndiscoveredTool', params: {} }, + ctx, + async () => ({ behavior: 'allow' }), + { type: 'assistant', content: [], uuid: 'msg1' } as never, + undefined, + ) + + expect(result.data).toEqual({ + result: null, + tool_name: 'UndiscoveredTool', + }) + expect(result.newMessages).toBeDefined() + expect(result.newMessages![0].content).toContain('has not been discovered') + }) + test('has correct name', () => { expect(ExecuteTool.name).toBe(EXECUTE_TOOL_NAME) }) diff --git a/src/services/api/claude.ts b/src/services/api/claude.ts index a5116855f..1d4b4435d 100644 --- a/src/services/api/claude.ts +++ b/src/services/api/claude.ts @@ -1396,7 +1396,7 @@ async function* queryModel( messagesForAPI = [ ...messagesForAPI, createUserMessage({ - content: `\n\n${deferredToolList}\n\nTo invoke any tool listed above, use ExecuteExtraTool with {"tool_name": "", "params": {...}}. This is the ONLY way to call deferred tools — do not read source code or analyze implementation, just call ExecuteExtraTool directly.\n`, + content: `\n\n${deferredToolList}\n\nIMPORTANT: These tools are deferred-loading. You MUST first discover a tool via SearchExtraTools before invoking it with ExecuteExtraTool. Do NOT call ExecuteExtraTool directly — it will fail if the tool has not been discovered.\n\nSteps:\n1. SearchExtraTools("select:") — discover the tool and its schema\n2. ExecuteExtraTool({"tool_name": "", "params": {...}}) — invoke it with correct parameters\n`, isMeta: true, }), ]