Skip to content

Commit 6652498

Browse files
committed
fix: replace empty text in reasoning messages to preserve positions
normalizeMessages removes empty text parts, which shifts thinking block positions and invalidates signatures. Simple preservation does not work because the AI SDK has a second filter and the API rejects empty text. In assistant messages with signed reasoning, replace empty text with a placeholder instead of removing it. This preserves array positions through all filtering layers.
1 parent ae20309 commit 6652498

1 file changed

Lines changed: 20 additions & 6 deletions

File tree

packages/opencode/src/provider/transform.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,27 @@ export namespace ProviderTransform {
6161
return msg
6262
}
6363
if (!Array.isArray(msg.content)) return msg
64-
const filtered = msg.content.filter((part) => {
65-
if (part.type === "text" || part.type === "reasoning") {
66-
return part.text !== ""
67-
}
68-
return true
69-
})
64+
const hasReasoning = msg.role === "assistant" && msg.content.some((p) => p.type === "reasoning" && p.providerOptions !== undefined)
65+
const filtered = msg.content
66+
.filter((part) => {
67+
if (part.type === "reasoning") {
68+
return part.text !== "" || part.providerOptions !== undefined
69+
}
70+
if (part.type === "text" && !hasReasoning) {
71+
return part.text !== ""
72+
}
73+
return true
74+
})
75+
.map((part) => {
76+
if (hasReasoning && part.type === "text" && part.text === "") {
77+
return { ...part, text: "..." } as typeof part
78+
}
79+
return part
80+
})
7081
if (filtered.length === 0) return undefined
82+
if (hasReasoning && filtered.length > 0 && filtered[filtered.length - 1].type === "reasoning") {
83+
filtered.push({ type: "text", text: "..." } as (typeof filtered)[number])
84+
}
7185
return { ...msg, content: filtered }
7286
})
7387
.filter((msg): msg is ModelMessage => msg !== undefined && msg.content !== "")

0 commit comments

Comments
 (0)