Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
6695085
test: reproduce empty text filtering breaking thinking block signatur…
altendky Mar 9, 2026
bae2f84
fix(provider): skip empty-text filtering for assistant messages in no…
altendky Mar 9, 2026
2ea031a
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 9, 2026
4b4f2ce
fix: filter empty text from assistant messages without reasoning blocks
altendky Mar 9, 2026
e750dad
revert: drop removePart for empty text in processor to avoid create-d…
altendky Mar 9, 2026
83362bd
fix: prevent cache_control on empty text blocks from aborted messages
altendky Mar 10, 2026
c7bc348
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 10, 2026
2631e6c
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 11, 2026
a95c4ea
fix: skip empty text parts in applyCaching to prevent cache_control r…
altendky Mar 11, 2026
2a020c1
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 12, 2026
5d9d5fc
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 16, 2026
aee46ec
Merge remote-tracking branch 'origin/test/thinking-signature-empty-te…
altendky Mar 16, 2026
b09f0b5
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 17, 2026
46ac5b0
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 18, 2026
88d27ff
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 20, 2026
ace4d43
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 20, 2026
352a501
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 21, 2026
389a2d4
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 21, 2026
1c02bf1
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 23, 2026
92bcb76
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 29, 2026
d73628e
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 30, 2026
422aa99
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Mar 31, 2026
0dcdcfb
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 1, 2026
3dcf016
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 3, 2026
5369b91
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 6, 2026
689e88b
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 7, 2026
dd48de9
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 8, 2026
c984686
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 8, 2026
58ad0ab
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 9, 2026
8cd68b3
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 12, 2026
7d1cd5a
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 13, 2026
a89b804
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 16, 2026
b55eb96
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 16, 2026
9cb92fe
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 17, 2026
3a1e649
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 17, 2026
a393e88
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 18, 2026
a0c42ce
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 19, 2026
97f3c74
feat: update codex plugin to support 5.5 (#23789)
rekram1-node Apr 22, 2026
69e2f3b
chore: bump Bun to 1.3.13 (#23791)
Hona Apr 22, 2026
a45d9a9
fix(app): improve icon override handling in project edit dialog (#23768)
Brendonovich Apr 22, 2026
ed3d364
chore: update nix node_modules hashes
opencode-agent[bot] Apr 22, 2026
bb69648
fix: preserve BOM in text tool round-trips (#23797)
Hona Apr 22, 2026
bfb954e
chore: generate
opencode-agent[bot] Apr 22, 2026
0595c28
test: fix cross-spawn stderr race on Windows CI (#23808)
Hona Apr 22, 2026
6aa475f
chore: generate
opencode-agent[bot] Apr 22, 2026
88c5f6b
fix: consolidate project avatar source logic (#23819)
Brendonovich Apr 22, 2026
2a480a9
fix(tui): fail fast on invalid session startup (#23837)
nexxeln Apr 22, 2026
266e965
chore: generate
opencode-agent[bot] Apr 22, 2026
b60f27b
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 22, 2026
d943e72
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 23, 2026
b718398
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 24, 2026
01ddb73
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 27, 2026
0f9547a
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 29, 2026
731e856
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 29, 2026
ef77a24
Merge branch 'dev' into test/thinking-signature-empty-text
altendky Apr 30, 2026
9aecdd4
Merge branch 'dev' into test/thinking-signature-empty-text
altendky May 1, 2026
3255f80
Merge branch 'dev' into test/thinking-signature-empty-text
altendky May 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 50 additions & 32 deletions packages/opencode/src/provider/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,40 +51,46 @@ function normalizeMessages(
_options: Record<string, unknown>,
): ModelMessage[] {
// Anthropic rejects messages with empty content - filter out empty string messages
// and remove empty text/reasoning parts from array content
if (model.api.npm === "@ai-sdk/anthropic") {
// and remove empty text/reasoning parts from array content.
// Assistant messages with reasoning blocks are excluded: their content must be
// replayed verbatim because thinking block signatures encode positional context.
// Removing an empty text part between two reasoning blocks changes the block
// arrangement and invalidates the cryptographic signatures, causing the API to
// reject the request. Assistant messages without reasoning blocks are filtered
// normally since there are no signatures to preserve.
if (model.api.npm === "@ai-sdk/anthropic" || model.api.npm === "@ai-sdk/amazon-bedrock") {
msgs = msgs
.map((msg) => {
if (typeof msg.content === "string") {
if (msg.content === "") return undefined
return msg
}
if (!Array.isArray(msg.content)) return msg
const filtered = msg.content.filter((part) => {
if (part.type === "text" || part.type === "reasoning") {
return part.text !== ""
if (msg.role === "assistant") {
if (!Array.isArray(msg.content)) return msg
if (msg.content.some((part) => part.type === "reasoning")) {
// Strip trailing empty text parts — only interstitial ones
// (between reasoning blocks) affect thinking-block signature
// positions. A trailing empty text can receive cache_control
// from applyCaching, which Anthropic rejects with:
// "cache_control cannot be set for empty text blocks"
let end = msg.content.length
while (end > 0) {
const part = msg.content[end - 1]
if (part.type === "text" && part.text === "") {
end--
continue
}
break
}
if (end === 0) return undefined
if (end < msg.content.length) return { ...msg, content: msg.content.slice(0, end) }
return msg
}
return true
})
if (filtered.length === 0) return undefined
return { ...msg, content: filtered }
})
.filter((msg): msg is ModelMessage => msg !== undefined && msg.content !== "")
}

// Bedrock specific transforms
if (model.api.npm === "@ai-sdk/amazon-bedrock") {
msgs = msgs
.map((msg) => {
// No reasoning blocks — safe to filter empty text
}
if (typeof msg.content === "string") {
if (msg.content === "") return undefined
return msg
}
if (!Array.isArray(msg.content)) return msg
const filtered = msg.content.filter((part) => {
if (part.type === "text" || part.type === "reasoning") {
return part.text !== ""
}
if (part.type === "text") return part.text !== ""
return true
})
if (filtered.length === 0) return undefined
Expand Down Expand Up @@ -283,15 +289,27 @@ function applyCaching(msgs: ModelMessage[], model: Provider.Model): ModelMessage
model.api.npm === "@ai-sdk/amazon-bedrock"
const shouldUseContentOptions = !useMessageLevelOptions && Array.isArray(msg.content) && msg.content.length > 0

if (shouldUseContentOptions) {
const lastContent = msg.content[msg.content.length - 1]
if (shouldUseContentOptions && Array.isArray(msg.content)) {
// Walk backwards to skip empty text parts — Anthropic rejects
// cache_control on empty text blocks.
const target = msg.content.findLast(
(part: unknown) =>
!(
typeof part === "object" &&
part &&
"type" in part &&
part.type === "text" &&
"text" in part &&
part.text === ""
),
)
if (
lastContent &&
typeof lastContent === "object" &&
lastContent.type !== "tool-approval-request" &&
lastContent.type !== "tool-approval-response"
target &&
typeof target === "object" &&
target.type !== "tool-approval-request" &&
target.type !== "tool-approval-response"
) {
lastContent.providerOptions = mergeDeep(lastContent.providerOptions ?? {}, providerOptions)
target.providerOptions = mergeDeep(target.providerOptions ?? {}, providerOptions)
continue
}
}
Expand Down
13 changes: 11 additions & 2 deletions packages/opencode/src/session/message-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,10 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* (
msg.info.error &&
!(
AbortedError.isInstance(msg.info.error) &&
msg.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning")
msg.parts.some(
(part) =>
part.type !== "step-start" && part.type !== "reasoning" && !(part.type === "text" && part.text === ""),
)
)
) {
continue
Expand All @@ -859,7 +862,13 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* (
assistantMessage.parts.push({
type: "text",
text: part.text,
...(differentModel ? {} : { providerMetadata: part.metadata }),
// Empty text parts between reasoning blocks must survive the AI SDK's
// internal convertToLanguageModelPrompt filter, which strips text parts
// where text==="" and providerOptions==null. Fall back to {} so the
// downstream filter sees a non-null value and preserves the part.
// Removing an empty text part shifts thinking block positions and
// invalidates cryptographic signatures.
...(differentModel ? {} : { providerMetadata: part.text === "" && !part.metadata ? {} : part.metadata }),
})
if (part.type === "step-start")
assistantMessage.parts.push({
Expand Down
Loading
Loading