From 7e02b445b2d9e2bdfa9b8dd0e9ec9477e4f7d339 Mon Sep 17 00:00:00 2001 From: oyi77 Date: Tue, 19 May 2026 04:22:23 +0700 Subject: [PATCH] fix(gemini-web): address code review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fixToolAdjacency: handle content and tool_calls independently - Guard fixToolAdjacency to Claude-only (this.provider === "claude" or isClaudeCodeCompatible). OpenAI allows results spread across multiple subsequent messages — adjacency guard would break multi-tool calls. Co-Authored-By: OpenClaude (mimo-v2.5-pro) --- open-sse/executors/base.ts | 7 ++- open-sse/services/contextManager.ts | 78 +++++++++++++++++------------ 2 files changed, 52 insertions(+), 33 deletions(-) diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index 63a10b6888..379cb4ffbf 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -878,7 +878,12 @@ export class BaseExecutor { const tb = transformedBody as Record; if (Array.isArray(tb?.messages)) { const fixed = fixToolPairs(tb.messages as Record[]); - const adjacent = fixToolAdjacency(fixed); + // fixToolAdjacency enforces Claude's strict adjacency rule + // (tool_result must be in immediately next message). + // Only apply for Claude/Claude-compatible — OpenAI allows results + // spread across multiple subsequent messages. + const isClaude = this.provider === "claude" || isClaudeCodeCompatible(this.provider); + const adjacent = isClaude ? fixToolAdjacency(fixed) : fixed; tb.messages = stripTrailingAssistantOrphanToolUse(adjacent); } } diff --git a/open-sse/services/contextManager.ts b/open-sse/services/contextManager.ts index 2834eaa26a..8c203ddf48 100644 --- a/open-sse/services/contextManager.ts +++ b/open-sse/services/contextManager.ts @@ -422,44 +422,58 @@ export function fixToolAdjacency(messages: Record[]): Record(); - if (nextMsg.role === "tool" && nextMsg.tool_call_id) { - nextToolResultIds.add(String(nextMsg.tool_call_id)); - } - if (nextMsg.role === "user" && Array.isArray(nextMsg.content)) { - for (const block of nextMsg.content as Record[]) { - if (block.type === "tool_result" && block.tool_use_id) { - nextToolResultIds.add(String(block.tool_use_id)); - } + if (msg.role !== "assistant" || !nextMsg) { + result.push(msg); + continue; + } + + // Collect tool_result IDs from the NEXT message only + const nextToolResultIds = new Set(); + if (nextMsg.role === "tool" && nextMsg.tool_call_id) { + nextToolResultIds.add(String(nextMsg.tool_call_id)); + } + if (nextMsg.role === "user" && Array.isArray(nextMsg.content)) { + for (const block of nextMsg.content as Record[]) { + if (block.type === "tool_result" && block.tool_use_id) { + nextToolResultIds.add(String(block.tool_use_id)); } } + } - // Filter tool_use blocks: only keep if next message has matching tool_result - const filteredContent = (msg.content as Record[]).filter( + let modified = false; + const newMsg: Record = { ...msg }; + + // Filter tool_use blocks in content array (Claude format) + if (Array.isArray(newMsg.content)) { + const filteredContent = (newMsg.content as Record[]).filter( (block) => block.type !== "tool_use" || !block.id || nextToolResultIds.has(String(block.id)) ); - - if (filteredContent.length !== (msg.content as unknown[]).length) { - // Also filter tool_calls array if present - const newMsg: Record = { ...msg, content: filteredContent }; - if (Array.isArray(newMsg.tool_calls)) { - newMsg.tool_calls = (newMsg.tool_calls as Record[]).filter( - (tc: Record) => !tc.id || nextToolResultIds.has(String(tc.id)) - ); - } - // Drop assistant message if it became empty - const hasContent = - typeof newMsg.content === "string" - ? (newMsg.content as string).trim().length > 0 - : Array.isArray(newMsg.content) && (newMsg.content as unknown[]).length > 0; - const hasToolCalls = Array.isArray(newMsg.tool_calls) && newMsg.tool_calls.length > 0; - if (!hasContent && !hasToolCalls) continue; - result.push(newMsg); - } else { - result.push(msg); + if (filteredContent.length !== (newMsg.content as unknown[]).length) { + newMsg.content = filteredContent; + modified = true; } + } + + // Filter tool_calls array (OpenAI format) — independently of content + if (Array.isArray(newMsg.tool_calls)) { + const filteredToolCalls = (newMsg.tool_calls as Record[]).filter( + (tc: Record) => !tc.id || nextToolResultIds.has(String(tc.id)) + ); + if (filteredToolCalls.length !== (newMsg.tool_calls as unknown[]).length) { + newMsg.tool_calls = filteredToolCalls; + modified = true; + } + } + + if (modified) { + // Drop assistant message if it became empty + const hasContent = + typeof newMsg.content === "string" + ? (newMsg.content as string).trim().length > 0 + : Array.isArray(newMsg.content) && (newMsg.content as unknown[]).length > 0; + const hasToolCalls = Array.isArray(newMsg.tool_calls) && newMsg.tool_calls.length > 0; + if (!hasContent && !hasToolCalls) continue; + result.push(newMsg); } else { result.push(msg); }