From a3f838eb431e4668807f04f86d07204a236d892c Mon Sep 17 00:00:00 2001 From: shatfield4 Date: Fri, 16 Jan 2026 17:40:26 -0800 Subject: [PATCH] handle 0 cooldown in dedupe + fix anthropic dedupe message structure --- .../agents/aibitat/providers/ai-provider.js | 2 +- .../agents/aibitat/providers/anthropic.js | 41 ++++++++++++++++++- server/utils/agents/aibitat/utils/dedupe.js | 4 +- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/server/utils/agents/aibitat/providers/ai-provider.js b/server/utils/agents/aibitat/providers/ai-provider.js index b917feff8..148e7cfdf 100644 --- a/server/utils/agents/aibitat/providers/ai-provider.js +++ b/server/utils/agents/aibitat/providers/ai-provider.js @@ -97,7 +97,7 @@ class Provider { (def) => def?.name?.toLowerCase() === functionCall.name?.toLowerCase() ); if (!foundFunc || !foundFunc.isMCPTool) return 0; - return foundFunc.mcpCooldownMs || DEFAULT_COOLDOWN_MS; + return foundFunc.mcpCooldownMs ?? DEFAULT_COOLDOWN_MS; } /** diff --git a/server/utils/agents/aibitat/providers/anthropic.js b/server/utils/agents/aibitat/providers/anthropic.js index 86a35bb9a..fbb2ea170 100644 --- a/server/utils/agents/aibitat/providers/anthropic.js +++ b/server/utils/agents/aibitat/providers/anthropic.js @@ -278,7 +278,39 @@ class AnthropicProvider extends Provider { "The model tried to call a function with the same arguments as a previous call - it was ignored.", }); - return await this.stream(messages, [], eventHandler); + // Strip out all tool calling to prevent Anthropic from trying to call the tool again + const cleanMessages = []; + for (const msg of messages) { + if (msg.role === "system") { + cleanMessages.push({ role: "system", content: msg.content }); + } else if (msg.role === "user") { + cleanMessages.push({ role: "user", content: msg.content }); + } else if (msg.role === "assistant") { + // Extract text content only from assistant messages + const text = + typeof msg.content === "string" + ? msg.content + : Array.isArray(msg.content) + ? msg.content.find((c) => c.type === "text")?.text || "" + : ""; + if (text) + cleanMessages.push({ role: "assistant", content: text }); + } else if (msg.role === "function") { + // Convert function results to user messages so Anthropic sees them + cleanMessages.push({ + role: "user", + content: `Tool result: ${msg.content}`, + }); + } + } + + cleanMessages.push({ + role: "user", + content: + "Please provide your final response based on the information above.", + }); + + return await this.stream(cleanMessages, [], eventHandler); } else { messages.push({ role: "assistant", @@ -404,7 +436,12 @@ class AnthropicProvider extends Provider { `Cannot call ${toolCallObj.name} again because ${reason}.` ); - return await this.complete(messages, []); + this.deduplicator.reset("runs"); + return { + textResponse: "", + functionCall: null, + cost: 0, + }; } else { this.deduplicator.trackRun(toolCallObj.name, toolCallObj.arguments, { cooldown: this.isMCPTool(toolCallObj, functions), diff --git a/server/utils/agents/aibitat/utils/dedupe.js b/server/utils/agents/aibitat/utils/dedupe.js index fa3818004..dba3762cc 100644 --- a/server/utils/agents/aibitat/utils/dedupe.js +++ b/server/utils/agents/aibitat/utils/dedupe.js @@ -40,7 +40,7 @@ class Deduplicator { .update(JSON.stringify({ key, params })) .digest("hex"); this.#hashes[hash] = Number(new Date()); - if (options.cooldown) + if (options.cooldown && options.cooldownInMs > 0) this.startCooldown(key, { cooldownInMs: options.cooldownInMs }); if (options.markUnique) this.markUnique(key); } @@ -108,7 +108,7 @@ class Deduplicator { cooldownInMs: DEFAULT_COOLDOWN_MS, } ) { - const cooldownDelay = parameters.cooldownInMs || DEFAULT_COOLDOWN_MS; + const cooldownDelay = parameters.cooldownInMs ?? DEFAULT_COOLDOWN_MS; this.log(`Starting cooldown for ${key} for ${cooldownDelay}ms`); this.#cooldowns[key] = Number(new Date()) + Number(cooldownDelay); }