Skip to content
Open
Changes from all commits
Commits
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
46 changes: 38 additions & 8 deletions src/crates/assembly/core/src/agentic/core/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,15 +268,12 @@ impl From<Message> for AIMessage {
MessageContent::Text(text) => {
// Check if text is empty to avoid sending empty content to API
let content = if text.trim().is_empty() {
// Should not have empty text messages, but provide default value for defensive programming
// Empty text is a degenerate input; do not fabricate a placeholder.
// Return None so downstream converters apply their existing
// empty-content semantics (skip/drop) instead of treating a fake
// token as a real user/assistant utterance or a system directive.
warn!("Empty text message detected: role={}", role);
if role == "user" {
Some("(empty message)".to_string())
} else if role == "system" {
Some("You are a helpful assistant.".to_string())
} else {
Some(" ".to_string()) // Minimum valid value
}
None
} else {
Some(text)
};
Expand Down Expand Up @@ -788,6 +785,39 @@ mod tests {
assert_eq!(ai_msg.thinking_signature.as_deref(), Some("sig_1"));
}

#[test]
fn empty_text_user_becomes_none_content() {
let ai_msg = AIMessage::from(Message::user(String::new()));

// Empty user text must not be turned into a fabricated "(empty message)"
// token that the model would treat as a real user utterance.
assert_eq!(ai_msg.content.as_deref(), None);
}

#[test]
fn empty_text_system_becomes_none_content() {
let ai_msg = AIMessage::from(Message::system(String::new()));

// Empty system text must not be turned into a fabricated system directive.
assert_eq!(ai_msg.content.as_deref(), None);
}

#[test]
fn empty_text_assistant_becomes_none_content() {
let ai_msg = AIMessage::from(Message::assistant(String::new()));

// Empty assistant text must not be replaced with a whitespace placeholder.
assert_eq!(ai_msg.content.as_deref(), None);
}

#[test]
fn non_empty_text_preserves_content() {
let ai_msg = AIMessage::from(Message::user("hi".to_string()));

// Normal non-empty text is unaffected by the empty-text handling.
assert_eq!(ai_msg.content.as_deref(), Some("hi"));
}

#[test]
fn persists_and_restores_model_response_replay() {
let message = Message::assistant("done".to_string()).with_model_response_replay(Some(
Expand Down