fix(opencode): filter empty text content blocks for all providers#17742
fix(opencode): filter empty text content blocks for all providers#17742RhoninSeiei wants to merge 3 commits intoanomalyco:devfrom
Conversation
Many providers (Anthropic, Bedrock, and proxies like openai-compatible forwarding to Bedrock) reject messages with empty text content blocks. The existing filter only applied to @ai-sdk/anthropic and @ai-sdk/amazon-bedrock, but users connecting through @ai-sdk/openai-compatible (e.g. custom Bedrock proxies, Databricks) hit the same ValidationException in multi-turn conversations. Changes: - normalizeMessages: apply empty text/reasoning filtering universally instead of only for Anthropic/Bedrock providers. Also use .trim() to catch whitespace-only content. - message-v2.ts: skip empty text and reasoning parts at the source when constructing UIMessages from stored parts. - Update test to verify universal filtering for openai-compatible. Fixes anomalyco#15715 Fixes anomalyco#5028 Refs anomalyco#2655
|
The following comment was made by an LLM, it may be inaccurate: Based on the search results, I found the following potentially related PRs: Related/Duplicate PRs Found:
Most likely duplicates: PR #17396 and PR #17712 appear to be addressing the same or very closely related issues. You should verify if this PR (#17742) is redundant with those existing work items. |
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
|
👋 Thanks for this PR @RhoninSeiei! This directly addresses an issue I've been experiencing. Real-world impactI encountered this exact problem using AWS Bedrock via LiteLLM proxy ( Database analysis results:
Evidence this affects openai-compatible:The existing filter in if (model.api.npm === "@ai-sdk/anthropic" || model.api.npm === "@ai-sdk/amazon-bedrock")But my setup uses Why this PR is importantYour two-part fix is exactly right:
The Recovery aspectI've created a Python repair script to fix already-corrupted databases and opened issue #19309 to track the recovery/repair aspect. This PR prevents future corruption but doesn't fix existing corrupted databases. Both solutions are needed for complete coverage. Offer to helpI'm happy to:
Let me know if there's anything I can do to help get this merged! Environment:
|
…tabase Empty text and reasoning parts with blank or whitespace-only text can be stored in the database during streaming (from text-start events that receive no deltas, or when streams are interrupted). These empty parts cause permanent ValidationException errors with providers like AWS Bedrock, especially when using LiteLLM proxy (@ai-sdk/openai-compatible). This fix adds defensive filtering at two levels: 1. When hydrating parts from database (filters on load) 2. When converting to model messages (filters during conversion) Both filters use .trim() to catch whitespace-only content. Fixes anomalyco#19309 Complements anomalyco#17742 (prevention) with recovery for existing corruption
…tabase Empty text and reasoning parts with blank or whitespace-only text can be stored in the database during streaming (from text-start events that receive no deltas, or when streams are interrupted). These empty parts cause permanent ValidationException errors with providers like AWS Bedrock, especially when using LiteLLM proxy (@ai-sdk/openai-compatible). This fix adds defensive filtering at two levels: 1. When hydrating parts from database (filters on load) 2. When converting to model messages (filters during conversion) Both filters use .trim() to catch whitespace-only content. Fixes anomalyco#19309 Complements anomalyco#17742 (prevention) with recovery for existing corruption
|
Hey @RhoninSeiei — we've been working on the same bug independently (our closed PR was #17565) and wanted to flag something we discovered. Your The problem: Anthropic's adaptive thinking emits whitespace-only text parts between reasoning blocks with cryptographic signatures. The signatures are positionally sensitive — removing the empty/whitespace text part changes the block arrangement and invalidates them. The API then rejects with:
The fix we landed on: Skip the empty/whitespace filter for assistant messages that contain reasoning parts: ```typescript This preserves the signature-sensitive block arrangement while still filtering empty content from user, tool, system, and assistant-without-reasoning messages. PR #16750 independently arrived at the same approach from the #16748 investigation. You might want to coordinate with that PR to avoid conflicting fixes. Hope this helps — your root cause analysis in the PR description is excellent and matches what we found. |
|
@erichasinternet Thanks for the concrete Bedrock-via-LiteLLM confirmation. That proxy path was one of the main targets for this PR, so the real-world validation is helpful. |
|
@robinmordasiewicz Good catch. The adaptive-thinking replay case from #16748 does apply here, so I pushed ef378fa to preserve assistant reasoning separators for Anthropic 4.6-style messages and added a regression test for it. Checks are rerunning now. |
|
Built and tested this on 1.3.13 (dev branch + cherry-pick). Working fix — sessions no longer break with the whitespace text error. I had AI clean up old sessions in the DB to fix resume. Here's a gist with AI step-by-step build-from-source instructions if anyone else needs to apply this before it's released: https://gist.github.com/glassdimly/0988483d5de6a09b20e34df7d1d71afe |
Issue for this PR
Fixes #15715
Fixes #5028
Refs #2655
Type of change
What does this PR do?
The existing empty-content filter in
normalizeMessages(transform.ts) only runs for@ai-sdk/anthropicand@ai-sdk/amazon-bedrock. Users who connect through@ai-sdk/openai-compatible(custom Bedrock proxies, Databricks-hosted Claude, etc.) hit the same BedrockValidationException: messages: text content blocks must be non-emptybecause the filter does not cover their provider.The root cause: during multi-turn conversations with tool calls, the streaming processor can create text parts with empty strings (from
text-startevents that receive no deltas). These empty parts propagate through the message pipeline and eventually reach the SDK converter, which sendscontent: ""to Bedrock.This PR makes three changes:
transform.ts- Apply empty text/reasoning filtering universally to all providers (not just Anthropic/Bedrock). Uses.trim()to also catch whitespace-only content. Empty text blocks are never useful for any provider.message-v2.ts- Skip empty/whitespace-only text and reasoning parts at the source when constructing UIMessages, preventing them from entering the pipeline at all.transform.test.ts- Replace the old "does not filter for non-anthropic providers" test with a new test that verifies universal filtering works for@ai-sdk/openai-compatibleproviders.Note: the SDK itself also has a related issue where
convertToOpenAICompatibleChatMessagesoutputscontent: ""instead ofcontent: nullfor assistant messages with only tool calls. A separate issue has been filed at vercel/ai#13466.How did you verify your code works?
bun test test/provider/transform.test.ts) - all 115 tests pass with 220 expect() calls.@ai-sdk/openai-compatible. TheValidationExceptionno longer occurs after 5+ rounds of tool-call-heavy conversation.Screenshots / recordings
N/A - backend logic change, no UI impact.
Checklist