Skip to content

Commit c4dd45f

Browse files
fix: 防止 <available-deferred-tools> 在每轮 API 调用中重复注入
使用模块级 Set 缓存已注入的 deferred tool 列表,diff 后仅在有 新增工具时重新注入。根因:注入消息追加到 queryModel 的局部变量 messagesForAPI,不写入消息历史,所以每次调用都是首次。
1 parent b5beafb commit c4dd45f

1 file changed

Lines changed: 31 additions & 9 deletions

File tree

src/services/api/claude.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,16 @@ export function stripExcessMediaItems(
10361036
}) as (UserMessage | AssistantMessage)[]
10371037
}
10381038

1039+
/**
1040+
* Module-level cache of deferred-tool lines that have already been announced
1041+
* via <available-deferred-tools>. Because the injection is ephemeral (appended
1042+
* to a local `messagesForAPI` that is never persisted back into the caller's
1043+
* message history), we cannot scan history to detect prior injections — the
1044+
* injected message is gone after each API call. Instead we keep this Set so we
1045+
* only re-inject when new deferred tools appear (e.g. MCP server connects).
1046+
*/
1047+
const lastAnnouncedDeferredTools = new Set<string>()
1048+
10391049
async function* queryModel(
10401050
messages: Message[],
10411051
systemPrompt: SystemPrompt,
@@ -1385,21 +1395,33 @@ async function* queryModel(
13851395
// via persisted deferred_tools_delta attachments instead of this
13861396
// ephemeral prepend (which busts cache whenever the pool changes).
13871397
if (useSearchExtraTools && !isDeferredToolsDeltaEnabled()) {
1398+
// Diff current deferred tools against what's already been announced in
1399+
// prior <available-deferred-tools> injections. Only re-inject when new
1400+
// tools appear (e.g. MCP server connects mid-session).
13881401
const deferredToolList = tools
13891402
.filter(t => deferredToolNames.has(t.name))
13901403
.map(formatDeferredToolLine)
13911404
.sort()
13921405
.join('\n')
1406+
13931407
if (deferredToolList) {
1394-
// Append to the end of the messages array (not prepend) so it
1395-
// never抢占 <project-instructions> (CLAUDE.md) at the front.
1396-
messagesForAPI = [
1397-
...messagesForAPI,
1398-
createUserMessage({
1399-
content: `<system-reminder>\n<available-deferred-tools>\n${deferredToolList}\n</available-deferred-tools>\nIMPORTANT: The tools listed above are deferred-loading — they are NOT in your tool list. To use them, you MUST first discover a tool via SearchExtraTools, then invoke it with ExecuteExtraTool.\n\nSearchExtraTools and ExecuteExtraTool are core tools already in your tool list right now — call them directly, do NOT use Bash/Glob to find them.\n\nSteps:\n1. SearchExtraTools({"query": "select:<tool_name>"}) — discover the tool and its schema\n2. ExecuteExtraTool({"tool_name": "<name>", "params": {...}}) — invoke it with correct parameters\n</system-reminder>`,
1400-
isMeta: true,
1401-
}),
1402-
]
1408+
const currentTools = new Set(deferredToolList.split('\n'))
1409+
const hasNewTools = [...currentTools].some(
1410+
t => !lastAnnouncedDeferredTools.has(t),
1411+
)
1412+
1413+
if (hasNewTools) {
1414+
lastAnnouncedDeferredTools.clear()
1415+
for (const t of currentTools) lastAnnouncedDeferredTools.add(t)
1416+
1417+
messagesForAPI = [
1418+
...messagesForAPI,
1419+
createUserMessage({
1420+
content: `<system-reminder>\n<available-deferred-tools>\n${deferredToolList}\n</available-deferred-tools>\nIMPORTANT: The tools listed above are deferred-loading — they are NOT in your tool list. To use them, you MUST first discover a tool via SearchExtraTools, then invoke it with ExecuteExtraTool.\n\nSearchExtraTools and ExecuteExtraTool are core tools already in your tool list right now — call them directly, do NOT use Bash/Glob to find them.\n\nSteps:\n1. SearchExtraTools({"query": "select:<tool_name>"}) — discover the tool and its schema\n2. ExecuteExtraTool({"tool_name": "<name>", "params": {...}}) — invoke it with correct parameters\n</system-reminder>`,
1421+
isMeta: true,
1422+
}),
1423+
]
1424+
}
14031425
}
14041426
}
14051427

0 commit comments

Comments
 (0)