From cd2e0352e7c694ce5e786a9e564b91ea19244807 Mon Sep 17 00:00:00 2001 From: 01luyicheng Date: Sun, 1 Feb 2026 17:15:53 +0800 Subject: [PATCH] fix: strip reasoning parts from messages when switching to non-thinking models (#11571) When switching from a model with extended thinking (e.g., Claude Opus) to a model without thinking support (e.g., GPT 5.2, Claude Sonnet without thinking) mid-session, reasoning parts from previous messages were still being sent to the API, causing validation errors. This fix ensures that reasoning parts are only included in providerOptions when the target model supports interleaved reasoning (capabilities.interleaved is an object with a field). For models that don't support interleaved reasoning, the reasoning parts are filtered out from the content array but not added to providerOptions, preventing the API error. Changes: - Modified normalizeMessages() in transform.ts to check if model supports interleaved reasoning before including reasoning in providerOptions - Reasoning parts are now correctly filtered for both thinking and non-thinking models --- packages/opencode/src/provider/transform.ts | 51 ++++++++++----------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index f5fe419db9..c038c13f15 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -129,40 +129,39 @@ export namespace ProviderTransform { return result } - if (typeof model.capabilities.interleaved === "object" && model.capabilities.interleaved.field) { - const field = model.capabilities.interleaved.field - return msgs.map((msg) => { - if (msg.role === "assistant" && Array.isArray(msg.content)) { - const reasoningParts = msg.content.filter((part: any) => part.type === "reasoning") - const reasoningText = reasoningParts.map((part: any) => part.text).join("") + const supportsInterleaved = typeof model.capabilities.interleaved === "object" && model.capabilities.interleaved.field + const field = supportsInterleaved ? model.capabilities.interleaved.field : undefined - // Filter out reasoning parts from content - const filteredContent = msg.content.filter((part: any) => part.type !== "reasoning") - - // Include reasoning_content | reasoning_details directly on the message for all assistant messages - if (reasoningText) { - return { - ...msg, - content: filteredContent, - providerOptions: { - ...msg.providerOptions, - openaiCompatible: { - ...(msg.providerOptions as any)?.openaiCompatible, - [field]: reasoningText, - }, - }, - } - } + return msgs.map((msg) => { + if (msg.role === "assistant" && Array.isArray(msg.content)) { + const reasoningParts = msg.content.filter((part: any) => part.type === "reasoning") + const reasoningText = reasoningParts.map((part: any) => part.text).join("") + // Filter out reasoning parts from content + const filteredContent = msg.content.filter((part: any) => part.type !== "reasoning") + // Include reasoning_content | reasoning_details directly on message for models that support interleaved reasoning + if (reasoningText && supportsInterleaved && field) { return { ...msg, content: filteredContent, + providerOptions: { + ...msg.providerOptions, + openaiCompatible: { + ...(msg.providerOptions as any)?.openaiCompatible, + [field]: reasoningText, + }, + }, } } - return msg - }) - } + return { + ...msg, + content: filteredContent, + } + } + + return msg + }) return msgs }