From 83608c352184f45ccb42f8139348e25e25a46933 Mon Sep 17 00:00:00 2001 From: Ruichen Zhou Date: Fri, 6 Mar 2026 11:28:07 +0100 Subject: [PATCH] fix(chatFormats): suppress content when it duplicates reasoning_content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some upstream providers (e.g. certain OpenAI-compatible aggregators) emit thinking/reasoning tokens with the same text written to both delta.content and delta.reasoning_content simultaneously. When metapi forwarded these chunks as-is, downstream clients would read delta.content and display the internal reasoning text to end users — appearing as word-by-word "thinking narration" in chat interfaces. Fix: after resolving both contentDelta and reasoningDelta, suppress contentDelta when it is non-empty and identical to reasoningDelta. This covers the duplicate-write pattern without affecting any legitimate case (a provider would never intentionally set content == reasoning). --- src/server/routes/proxy/chatFormats.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/server/routes/proxy/chatFormats.ts b/src/server/routes/proxy/chatFormats.ts index 1d16ea5..61b22b8 100644 --- a/src/server/routes/proxy/chatFormats.ts +++ b/src/server/routes/proxy/chatFormats.ts @@ -736,7 +736,7 @@ export function normalizeUpstreamStreamEvent( const delta = isRecord(choice?.delta) ? choice.delta : {}; const deltaParsed = extractTextAndReasoning(delta.content ?? delta); - const contentDelta = + const rawContentDelta = deltaParsed.content || (typeof choice?.message?.content === 'string' ? choice.message.content : '') || ''; @@ -747,6 +747,14 @@ export function normalizeUpstreamStreamEvent( || deltaParsed.reasoning || ''; + // Some upstream providers (e.g. certain OpenAI-compatible aggregators) emit thinking + // tokens with the same text duplicated in both delta.content and delta.reasoning_content. + // When the two values are identical it means content is just echoing the reasoning — + // suppress it so internal thinking is never leaked to downstream consumers. + const contentDelta = (reasoningDelta && rawContentDelta === reasoningDelta) + ? '' + : rawContentDelta; + const rawToolCalls = Array.isArray((delta as any).tool_calls) ? ((delta as any).tool_calls as unknown[]) : [];