fix(provider): add universal empty-content guard to message pipeline#23259
fix(provider): add universal empty-content guard to message pipeline#23259jpvelasco wants to merge 1 commit intoanomalyco:devfrom
Conversation
Multiple transformation passes (Pass 3 Anthropic tool reorder, Pass 5 interleaved reasoning filter, unsupportedParts stripping) can each produce messages with content:[] or content:"" that providers reject with hard validation errors. Bedrock's ConverseAPI crashes the session with a ValidationException on empty content. Adds a single O(n) filter at the end of message() that drops any message with empty string or empty array content, regardless of which pass produced it. This is additive -- existing pass-local guards remain as defense-in-depth. Relates to: anomalyco#15715, anomalyco#16332, anomalyco#22364, anomalyco#17705
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
|
The following comment was made by an LLM, it may be inaccurate: Potential Duplicate PRs FoundThe following open/related PRs address the same or overlapping issues:
Why they're related: All address the same root problem of empty content ( |
|
This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window. Feel free to open a new pull request that follows our guidelines. |
Fixes #23260
Problem
The
message()pipeline inpackages/opencode/src/provider/transform.tsapplies a sequential series of transforms, but has no final validation step. Three passes can each produce messages withcontent: []orcontent: ""that providers reject with hard errors:transform.ts:55-73flatMap)transform.ts:115-126content: []if the non-tool split half was all-emptytransform.ts:178-211content: []when message was reasoning-onlyunsupportedParts()transform.ts:267-303content: []if all parts were unsupported modalityBedrock's ConverseAPI rejects empty content with a fatal
ValidationExceptionthat permanently breaks the active session. Other providers fail in their own ways.This architectural gap has surfaced in multiple independent reports:
ValidationException: messages: text content blocks must be non-emptycontent: [](also targeted by fix(provider): drop empty content messages after interleaved reasoning filter #17712)Fix
Add a single O(n) filter at the very end of
message(), after all transformation passes and beforereturn msgs:This is additive -- existing pass-local guards (Pass 1) remain in place as defense-in-depth. Any future transformation pass that produces empty content is automatically caught without needing its own guard.
Tests
Added a new
describe("ProviderTransform.message - universal empty-content guard")block totest/provider/transform.test.tswith 12 test cases:content: ""andcontent: []at assistant/user/tool/system roles (non-Anthropic)content: [{text:""}]is preserved (not our job to touch array internals outside Pass 1)content:[{type:"reasoning"}]) is droppedAlso updated one existing test that was asserting the previous pass-through behavior for empty-string content on non-Anthropic providers -- the description now accurately reflects that empty-string is dropped universally by the guard.
Compatibility with related PRs
devand includes a comprehensive regression suite.Verification