Summary
There is an inconsistency in how user message content is trimmed across providers when converting chat messages to provider API payloads. The desired behavior is:
- Pruning: Use
strings.TrimSpace() to detect and skip purely whitespace messages/parts (i.e. the trim result is only used as a guard).
- Content: Always send the original, untrimmed content to the API.
Currently, the Anthropic provider (both standard and Beta) deviates from this by sending the trimmed value rather than the original. Additionally, some providers have no guard at all, allowing empty/whitespace-only parts to be forwarded.
Current behavior by provider
Single-part user messages
| Provider |
Empty check |
Content sent to API |
| Anthropic (standard) |
TrimSpace != "" |
✂️ Trimmed value |
| Anthropic (Beta API) |
TrimSpace != "" |
✂️ Trimmed value |
| OpenAI Chat Completions |
None for user msgs |
Original msg.Content |
| OpenAI Responses API |
None for user msgs |
Original msg.Content |
| DMR / Ollama |
None for user msgs |
Original msg.Content |
| AWS Bedrock |
TrimSpace guard only |
Original msg.Content |
| Google Gemini |
!= "" (no trim) |
Original msg.Content |
Multi-part user messages (per text part)
| Provider |
Guard on text part |
Content sent to API |
Whitespace part |
| Anthropic (standard) |
TrimSpace != "" |
✂️ Trimmed value |
Dropped |
| Anthropic (Beta API) |
TrimSpace != "" |
✂️ Trimmed value |
Dropped |
| OpenAI Chat / oaistream |
None |
Original part.Text |
Forwarded as-is |
| OpenAI Responses API |
None |
Original part.Text |
Forwarded as-is |
| DMR / Ollama |
None |
Original part.Text |
Forwarded as-is |
| AWS Bedrock |
TrimSpace guard only |
Original part.Text |
Dropped |
| Google Gemini |
None |
Original part.Text |
Forwarded as-is ⚠️ |
Issues identified
-
Anthropic (standard + Beta) — single-part and multi-part text parts: sends the trimmed value (txt) instead of the original msg.Content / part.Text.
pkg/model/provider/anthropic/client.go lines 308–311 and 485–488
pkg/model/provider/anthropic/beta_converter.go lines 46–53 and 169–174
- Also affects tool-result content:
client.go lines 404–445, beta_converter.go lines 139–162
-
OpenAI Chat / oaistream / DMR / Responses API — multi-part text parts: no whitespace guard at all, so empty or whitespace-only text parts are forwarded to the API.
pkg/model/provider/oaistream/messages.go lines 27–42 (ConvertMultiContent)
pkg/model/provider/openai/client.go lines 571–604
-
Google Gemini — single-part: uses != "" instead of TrimSpace != "", so a whitespace-only message (e.g. " ") passes the guard and is forwarded.
pkg/model/provider/gemini/client.go lines 264–267
-
Google Gemini — multi-part: no guard at all (not even != ""), making it more permissive than its own single-part path.
pkg/model/provider/gemini/client.go lines 290–293
Desired behavior (target)
For every provider, in both single-part and multi-part paths:
// Guard: use TrimSpace to decide whether to skip
if strings.TrimSpace(content) == "" {
continue // or skip
}
// Send: use the original, untrimmed value
api.Send(content)
Concretely:
- Anthropic: change
anthropic.NewTextBlock(txt) → anthropic.NewTextBlock(msg.Content) (and equivalent in Beta/tool-result paths), keeping txt only for the emptiness check.
- OpenAI/oaistream multi-part: add
if strings.TrimSpace(part.Text) == "" { continue } guard before appending text parts.
- OpenAI Responses API multi-part: same guard.
- Gemini single-part: change
msg.Content != "" → strings.TrimSpace(msg.Content) != "".
- Gemini multi-part: add
if strings.TrimSpace(part.Text) == "" { continue } guard.
Summary
There is an inconsistency in how user message content is trimmed across providers when converting chat messages to provider API payloads. The desired behavior is:
strings.TrimSpace()to detect and skip purely whitespace messages/parts (i.e. the trim result is only used as a guard).Currently, the Anthropic provider (both standard and Beta) deviates from this by sending the trimmed value rather than the original. Additionally, some providers have no guard at all, allowing empty/whitespace-only parts to be forwarded.
Current behavior by provider
Single-part user messages
TrimSpace != ""TrimSpace != ""msg.Contentmsg.Contentmsg.ContentTrimSpaceguard onlymsg.Content!= ""(no trim)msg.ContentMulti-part user messages (per text part)
TrimSpace != ""TrimSpace != ""part.Textpart.Textpart.TextTrimSpaceguard onlypart.Textpart.TextIssues identified
Anthropic (standard + Beta) — single-part and multi-part text parts: sends the trimmed value (
txt) instead of the originalmsg.Content/part.Text.pkg/model/provider/anthropic/client.golines 308–311 and 485–488pkg/model/provider/anthropic/beta_converter.golines 46–53 and 169–174client.golines 404–445,beta_converter.golines 139–162OpenAI Chat / oaistream / DMR / Responses API — multi-part text parts: no whitespace guard at all, so empty or whitespace-only text parts are forwarded to the API.
pkg/model/provider/oaistream/messages.golines 27–42 (ConvertMultiContent)pkg/model/provider/openai/client.golines 571–604Google Gemini — single-part: uses
!= ""instead ofTrimSpace != "", so a whitespace-only message (e.g." ") passes the guard and is forwarded.pkg/model/provider/gemini/client.golines 264–267Google Gemini — multi-part: no guard at all (not even
!= ""), making it more permissive than its own single-part path.pkg/model/provider/gemini/client.golines 290–293Desired behavior (target)
For every provider, in both single-part and multi-part paths:
Concretely:
anthropic.NewTextBlock(txt)→anthropic.NewTextBlock(msg.Content)(and equivalent in Beta/tool-result paths), keepingtxtonly for the emptiness check.if strings.TrimSpace(part.Text) == "" { continue }guard before appending text parts.msg.Content != ""→strings.TrimSpace(msg.Content) != "".if strings.TrimSpace(part.Text) == "" { continue }guard.