diff --git a/crates/goose/src/conversation/message.rs b/crates/goose/src/conversation/message.rs index 419f2fb9c7f3..3e7cf4a74437 100644 --- a/crates/goose/src/conversation/message.rs +++ b/crates/goose/src/conversation/message.rs @@ -285,10 +285,8 @@ impl MessageContent { .cloned() .collect(); - if filtered_content.is_empty() { - return None; - } - + // Preserve ToolResponse even when content is empty - some providers + // (like Google) need to handle empty tool responses specially Some(MessageContent::ToolResponse(ToolResponse { id: res.id.clone(), tool_result: Ok(CallToolResult { diff --git a/crates/goose/src/providers/chatgpt_codex.rs b/crates/goose/src/providers/chatgpt_codex.rs index 8a6060531317..196c8b2d26d1 100644 --- a/crates/goose/src/providers/chatgpt_codex.rs +++ b/crates/goose/src/providers/chatgpt_codex.rs @@ -79,7 +79,8 @@ fn build_input_items(messages: &[Message]) -> Result> { let mut items = Vec::new(); for message in messages.iter().filter(|m| m.is_agent_visible()) { - let role = match message.role { + let filtered = message.agent_visible_content(); + let role = match filtered.role { Role::User => Some("user"), Role::Assistant => Some("assistant"), }; @@ -95,7 +96,7 @@ fn build_input_items(messages: &[Message]) -> Result> { } }; - for content in &message.content { + for content in &filtered.content { match content { MessageContent::Text(text) => { if !text.text.is_empty() { diff --git a/crates/goose/src/providers/cursor_agent.rs b/crates/goose/src/providers/cursor_agent.rs index 10193aa0c59f..829bd173f99c 100644 --- a/crates/goose/src/providers/cursor_agent.rs +++ b/crates/goose/src/providers/cursor_agent.rs @@ -65,13 +65,14 @@ impl CursorAgentProvider { // Add conversation history for message in messages.iter().filter(|m| m.is_agent_visible()) { - let role_prefix = match message.role { + let filtered = message.agent_visible_content(); + let role_prefix = match filtered.role { Role::User => "Human: ", Role::Assistant => "Assistant: ", }; full_prompt.push_str(role_prefix); - for content in &message.content { + for content in &filtered.content { match content { MessageContent::Text(text_content) => { full_prompt.push_str(&text_content.text); diff --git a/crates/goose/src/providers/formats/anthropic.rs b/crates/goose/src/providers/formats/anthropic.rs index 7e3dd161515a..ea6743c9841e 100644 --- a/crates/goose/src/providers/formats/anthropic.rs +++ b/crates/goose/src/providers/formats/anthropic.rs @@ -34,7 +34,13 @@ const DATA_FIELD: &str = "data"; pub fn format_messages(messages: &[Message]) -> Vec { let mut anthropic_messages = Vec::new(); - for message in messages.iter().filter(|m| m.is_agent_visible()) { + let filtered_messages: Vec = messages + .iter() + .filter(|m| m.is_agent_visible()) + .map(|m| m.agent_visible_content()) + .collect(); + + for message in &filtered_messages { let role = match message.role { Role::User => USER_ROLE, Role::Assistant => ASSISTANT_ROLE, diff --git a/crates/goose/src/providers/formats/bedrock.rs b/crates/goose/src/providers/formats/bedrock.rs index 08ee61213db2..51df50c1edf8 100644 --- a/crates/goose/src/providers/formats/bedrock.rs +++ b/crates/goose/src/providers/formats/bedrock.rs @@ -18,10 +18,12 @@ use super::super::base::Usage; use crate::conversation::message::{Message, MessageContent}; pub fn to_bedrock_message(message: &Message) -> Result { + let filtered = message.agent_visible_content(); + bedrock::Message::builder() - .role(to_bedrock_role(&message.role)) + .role(to_bedrock_role(&filtered.role)) .set_content(Some( - message + filtered .content .iter() .map(to_bedrock_message_content) @@ -90,11 +92,6 @@ pub fn to_bedrock_message_content(content: &MessageContent) -> Result>()?, ), diff --git a/crates/goose/src/providers/formats/databricks.rs b/crates/goose/src/providers/formats/databricks.rs index 31ff46616f12..3c5128e3220f 100644 --- a/crates/goose/src/providers/formats/databricks.rs +++ b/crates/goose/src/providers/formats/databricks.rs @@ -45,12 +45,7 @@ fn format_tool_response( match &response.tool_result { Ok(call_result) => { - let abridged: Vec<_> = call_result - .content - .iter() - .filter(|c| c.audience().is_none_or(|a| a.contains(&Role::Assistant))) - .map(|c| c.raw.clone()) - .collect(); + let abridged: Vec<_> = call_result.content.iter().map(|c| c.raw.clone()).collect(); let mut tool_content = Vec::new(); let mut image_messages = Vec::new(); @@ -113,9 +108,10 @@ fn format_tool_response( fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec { let mut result = Vec::new(); for message in messages.iter().filter(|m| m.is_agent_visible()) { + let filtered = message.agent_visible_content(); let mut converted = DatabricksMessage { content: Value::Null, - role: match message.role { + role: match filtered.role { Role::User => "user".to_string(), Role::Assistant => "assistant".to_string(), }, @@ -127,7 +123,7 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec { if !text.text.is_empty() { diff --git a/crates/goose/src/providers/formats/openai.rs b/crates/goose/src/providers/formats/openai.rs index b00a2f8993e4..5b033ebed237 100644 --- a/crates/goose/src/providers/formats/openai.rs +++ b/crates/goose/src/providers/formats/openai.rs @@ -60,15 +60,16 @@ struct StreamingChunk { pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec { let mut messages_spec = Vec::new(); for message in messages.iter().filter(|m| m.is_agent_visible()) { + let filtered = message.agent_visible_content(); let mut converted = json!({ - "role": message.role + "role": filtered.role }); let mut output = Vec::new(); let mut content_array = Vec::new(); let mut text_array = Vec::new(); - for content in &message.content { + for content in &filtered.content { match content { MessageContent::Text(text) => { if !text.text.is_empty() { @@ -131,17 +132,7 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec< MessageContent::ToolResponse(response) => { match &response.tool_result { Ok(result) => { - // Send only contents with no audience or with Assistant in the audience - let abridged: Vec<_> = result - .content - .iter() - .filter(|content| { - content - .audience() - .is_none_or(|audience| audience.contains(&Role::Assistant)) - }) - .cloned() - .collect(); + let abridged: Vec<_> = result.content.to_vec(); // Process all content, replacing images with placeholder text let mut tool_content = Vec::new(); diff --git a/crates/goose/src/providers/formats/openai_responses.rs b/crates/goose/src/providers/formats/openai_responses.rs index f1c692a8b358..59d85b0b9d0a 100644 --- a/crates/goose/src/providers/formats/openai_responses.rs +++ b/crates/goose/src/providers/formats/openai_responses.rs @@ -307,7 +307,8 @@ fn add_function_calls(input_items: &mut Vec, messages: &[Message]) { fn add_function_call_outputs(input_items: &mut Vec, messages: &[Message]) { for message in messages.iter().filter(|m| m.is_agent_visible()) { - for content in &message.content { + let filtered = message.agent_visible_content(); + for content in &filtered.content { if let MessageContent::ToolResponse(response) = content { match &response.tool_result { Ok(contents) => { diff --git a/crates/goose/src/providers/formats/snowflake.rs b/crates/goose/src/providers/formats/snowflake.rs index 5f7b32ade3d1..7896515011f0 100644 --- a/crates/goose/src/providers/formats/snowflake.rs +++ b/crates/goose/src/providers/formats/snowflake.rs @@ -12,8 +12,13 @@ use std::collections::HashSet; pub fn format_messages(messages: &[Message]) -> Vec { let mut snowflake_messages = Vec::new(); - // Convert messages to Snowflake format - for message in messages.iter().filter(|m| m.is_agent_visible()) { + let filtered_messages: Vec = messages + .iter() + .filter(|m| m.is_agent_visible()) + .map(|m| m.agent_visible_content()) + .collect(); + + for message in &filtered_messages { let role = match message.role { Role::User => "user", Role::Assistant => "assistant", diff --git a/crates/goose/src/providers/toolshim.rs b/crates/goose/src/providers/toolshim.rs index a5815e98519c..dc03f377b0ca 100644 --- a/crates/goose/src/providers/toolshim.rs +++ b/crates/goose/src/providers/toolshim.rs @@ -321,10 +321,11 @@ pub fn convert_tool_messages_to_text(messages: &[Message]) -> Conversation { let converted_messages: Vec = messages .iter() .map(|message| { + let filtered = message.agent_visible_content(); let mut new_content = Vec::new(); let mut has_tool_content = false; - for content in &message.content { + for content in &filtered.content { match content { MessageContent::ToolRequest(req) => { has_tool_content = true; @@ -369,9 +370,9 @@ pub fn convert_tool_messages_to_text(messages: &[Message]) -> Conversation { } if has_tool_content { - Message::new(message.role.clone(), message.created, new_content) + Message::new(filtered.role.clone(), filtered.created, new_content) } else { - message.clone() + filtered } }) .collect();