From edd9afdfc07923f62769038db125b6a3f6c049de Mon Sep 17 00:00:00 2001 From: stefanwalcz Date: Thu, 9 Apr 2026 14:56:26 +0200 Subject: [PATCH] fix(streaming): skip duplicate delta.tool_calls already emitted during streaming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the incremental JSON/XML parser emits tool calls during ComputeChoices (tracked by lastEmittedCount), the post-streaming default: case was iterating functionResults from index 0 and re-emitting all of them as separate name-only + args-only chunks. This produced duplicate delta.tool_calls events for the same tool call, causing streaming clients (e.g. cogito) to accumulate arguments as "{args}{args}" — invalid JSON that fails parsing on every attempt. Fix: skip indices i < lastEmittedCount in the default: loop so that only tool calls NOT already emitted during streaming are sent post-stream. Co-Authored-By: Claude Sonnet 4.6 --- core/http/endpoints/openai/chat.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/http/endpoints/openai/chat.go b/core/http/endpoints/openai/chat.go index bc66d1aefb77..0feb0400e05b 100644 --- a/core/http/endpoints/openai/chat.go +++ b/core/http/endpoints/openai/chat.go @@ -420,6 +420,15 @@ func ChatEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, evaluator default: for i, ss := range functionResults { + // Skip tool calls that were already emitted incrementally by the + // streaming JSON/XML parser during ComputeChoices (tracked by + // lastEmittedCount). Re-emitting them would produce duplicate + // delta.tool_calls events, causing clients to accumulate + // "{args}{args}" which is invalid JSON (cogito streaming bug). + if i < lastEmittedCount { + continue + } + name, args := ss.Name, ss.Arguments toolCallID := ss.ID if toolCallID == "" {