diff --git a/src/crates/assembly/core/src/agentic/core/message.rs b/src/crates/assembly/core/src/agentic/core/message.rs index bbdff6cc5e..f4abdc2622 100644 --- a/src/crates/assembly/core/src/agentic/core/message.rs +++ b/src/crates/assembly/core/src/agentic/core/message.rs @@ -268,15 +268,12 @@ impl From 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) }; @@ -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(