From 5050c9fc962266ab9cae63984f51a73fb97b6e28 Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Tue, 27 Jan 2026 11:54:08 -0500 Subject: [PATCH] fix: remove duplicate tool_call emission from Responses API providers The openai-native and openai-codex providers were emitting tool calls twice: 1. tool_call_partial events during streaming (handled by NativeToolCallParser) 2. Complete tool_call from response.output_item.done events This caused tools to be rendered twice in the UI. Fix: Remove the tool_call emission from response.output_item.done since the streaming path already handles tool call completion via NativeToolCallParser's finalizeRawChunks() and finalizeStreamingToolCall() methods. --- src/api/providers/openai-codex.ts | 22 ++++++---------------- src/api/providers/openai-native.ts | 24 ++++++------------------ 2 files changed, 12 insertions(+), 34 deletions(-) diff --git a/src/api/providers/openai-codex.ts b/src/api/providers/openai-codex.ts index 7ca49eb5a99..bdb06c5ef5b 100644 --- a/src/api/providers/openai-codex.ts +++ b/src/api/providers/openai-codex.ts @@ -925,22 +925,12 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion } } - // Only handle tool/function calls from done events (to ensure arguments are complete) - if ( - (item.type === "function_call" || item.type === "tool_call") && - event.type === "response.output_item.done" - ) { - const callId = item.call_id || item.tool_call_id || item.id - if (callId) { - const args = item.arguments || item.function?.arguments || item.function_arguments - yield { - type: "tool_call", - id: callId, - name: item.name || item.function?.name || item.function_name || "", - arguments: typeof args === "string" ? args : "{}", - } - } - } + // Note: We intentionally do NOT emit tool_call from response.output_item.done + // for function_call/tool_call items. The streaming path handles tool calls via: + // 1. tool_call_partial events during argument deltas + // 2. NativeToolCallParser.finalizeRawChunks() at stream end emitting tool_call_end + // 3. NativeToolCallParser.finalizeStreamingToolCall() creating the final ToolUse + // Emitting tool_call here would cause duplicate tool rendering. } return } diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts index 5719995dd68..82f55c02d85 100644 --- a/src/api/providers/openai-native.ts +++ b/src/api/providers/openai-native.ts @@ -1241,24 +1241,12 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio } } - // Only handle tool/function calls from done events (to ensure arguments are complete) - if ( - (item.type === "function_call" || item.type === "tool_call") && - event.type === "response.output_item.done" - ) { - // Handle complete tool/function call item - // Emit as tool_call for backward compatibility with non-streaming tool handling - const callId = item.call_id || item.tool_call_id || item.id - if (callId) { - const args = item.arguments || item.function?.arguments || item.function_arguments - yield { - type: "tool_call", - id: callId, - name: item.name || item.function?.name || item.function_name || "", - arguments: typeof args === "string" ? args : "{}", - } - } - } + // Note: We intentionally do NOT emit tool_call from response.output_item.done + // for function_call/tool_call items. The streaming path handles tool calls via: + // 1. tool_call_partial events during argument deltas + // 2. NativeToolCallParser.finalizeRawChunks() at stream end emitting tool_call_end + // 3. NativeToolCallParser.finalizeStreamingToolCall() creating the final ToolUse + // Emitting tool_call here would cause duplicate tool rendering. } return }