Problem
An empty multimodal message (no text and no images) built with
Message::user_multimodal("", vec![]) is converted through
impl From<Message> for AIMessage into content: Some("") - a bare empty
string sent straight to the provider. A strict API rejects it with a 400, and
a permissive gateway silently bills an empty request round.
Root cause
The Multimodal conversion arm has no empty-content guard. It starts content
from the text value and only appends attached-image markers when images are
present, then always wraps the result in Some(content). The Text and Mixed
arms both guard empty content (the Mixed arm returns None for empty text),
so the Multimodal arm is the only one without defence. The user_multimodal
constructor also does no validation, and serde deserialization rebuilds the
fields directly, so the conversion arm is the correct chokepoint to defend.
Repro
- Convert
Message::user_multimodal(String::new(), vec![]) via AIMessage::from(...).
- Observe the resulting
content is Some("") instead of None.
Impact
Empty request bodies reach the provider, causing 400 rejections on strict
providers or silent billing of empty rounds on permissive gateways.
Problem
An empty multimodal message (no text and no images) built with
Message::user_multimodal("", vec![])is converted throughimpl From<Message> for AIMessageintocontent: Some("")- a bare emptystring sent straight to the provider. A strict API rejects it with a 400, and
a permissive gateway silently bills an empty request round.
Root cause
The Multimodal conversion arm has no empty-content guard. It starts content
from the text value and only appends attached-image markers when images are
present, then always wraps the result in
Some(content). The Text and Mixedarms both guard empty content (the Mixed arm returns
Nonefor empty text),so the Multimodal arm is the only one without defence. The
user_multimodalconstructor also does no validation, and serde deserialization rebuilds the
fields directly, so the conversion arm is the correct chokepoint to defend.
Repro
Message::user_multimodal(String::new(), vec![])viaAIMessage::from(...).contentisSome("")instead ofNone.Impact
Empty request bodies reach the provider, causing 400 rejections on strict
providers or silent billing of empty rounds on permissive gateways.