From 23109b113ddef49de61a75d7cbc12a8ad9bb06f5 Mon Sep 17 00:00:00 2001 From: Geraldo Dutra Neto Date: Tue, 10 Mar 2026 12:04:11 -0300 Subject: [PATCH] fix: send assistant content as string in openai-completions provider (#2008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OpenAI Chat Completions API standard format for assistant message content is a plain string. Sending it as an array of {type:"text", text:"..."} objects causes some models (notably DeepSeek V3.2 via NVIDIA NIM) to mirror the content-block structure literally in their output. This produces recursive nesting where each turn wraps the previous content blocks deeper: [{'type':'text','text':'[{\'type\':\'text\',\'text\':...}]'}] The fix unifies the assistant content serialization to always use a joined string — the same approach already used for the github-copilot provider — for all openai-completions backends. Affected models observed: deepseek-ai/deepseek-v3.2 (nvidia provider). Models like GLM-5, GPT-4, Claude were unaffected as they tolerate array content, but sending a standard string is safer for all. Co-authored-by: geraldoaax --- packages/ai/src/providers/openai-completions.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/ai/src/providers/openai-completions.ts b/packages/ai/src/providers/openai-completions.ts index 2d28bf4e..437dea52 100644 --- a/packages/ai/src/providers/openai-completions.ts +++ b/packages/ai/src/providers/openai-completions.ts @@ -559,15 +559,12 @@ export function convertMessages( // Filter out empty text blocks to avoid API validation errors const nonEmptyTextBlocks = textBlocks.filter((b) => b.text && b.text.trim().length > 0); if (nonEmptyTextBlocks.length > 0) { - // GitHub Copilot requires assistant content as a string, not an array. - // Sending as array causes Claude models to re-answer all previous prompts. - if (model.provider === "github-copilot") { - assistantMsg.content = nonEmptyTextBlocks.map((b) => sanitizeSurrogates(b.text)).join(""); - } else { - assistantMsg.content = nonEmptyTextBlocks.map((b) => { - return { type: "text", text: sanitizeSurrogates(b.text) }; - }); - } + // Always send assistant content as a plain string (OpenAI Chat Completions + // API standard format). Sending as an array of {type:"text", text:"..."} + // objects is non-standard and causes some models (e.g. DeepSeek V3.2 via + // NVIDIA NIM) to mirror the content-block structure literally in their + // output, producing recursive nesting like [{'type':'text','text':'[{...}]'}]. + assistantMsg.content = nonEmptyTextBlocks.map((b) => sanitizeSurrogates(b.text)).join(""); } // Handle thinking blocks