diff --git a/crates/goose/src/providers/formats/google.rs b/crates/goose/src/providers/formats/google.rs index 0ebd134c66..ce652db9d0 100644 --- a/crates/goose/src/providers/formats/google.rs +++ b/crates/goose/src/providers/formats/google.rs @@ -31,7 +31,7 @@ pub fn get_thought_signature(metadata: &Option) -> Option<&str /// Convert internal Message format to Google's API message specification pub fn format_messages(messages: &[Message]) -> Vec { - messages + let filtered: Vec<_> = messages .iter() .filter(|m| m.is_agent_visible()) .filter(|message| { @@ -42,12 +42,28 @@ pub fn format_messages(messages: &[Message]) -> Vec { ) }) }) - .map(|message| { + .collect(); + + let last_assistant_idx = filtered + .iter() + .enumerate() + .filter(|(_, m)| m.role != Role::User) + .map(|(i, _)| i) + .next_back(); + + filtered + .iter() + .enumerate() + .map(|(idx, message)| { let role = if message.role == Role::User { "user" } else { "model" }; + let include_signature = match last_assistant_idx { + Some(last_idx) => idx >= last_idx, + None => false, + }; let mut parts = Vec::new(); for message_content in message.content.iter() { match message_content { @@ -74,8 +90,13 @@ pub fn format_messages(messages: &[Message]) -> Vec { let mut part = Map::new(); part.insert("functionCall".to_string(), json!(function_call_part)); - if let Some(signature) = get_thought_signature(&request.metadata) { - part.insert(THOUGHT_SIGNATURE_KEY.to_string(), json!(signature)); + if include_signature { + if let Some(signature) = get_thought_signature(&request.metadata) { + part.insert( + THOUGHT_SIGNATURE_KEY.to_string(), + json!(signature), + ); + } } parts.push(json!(part)); @@ -144,11 +165,15 @@ pub fn format_messages(messages: &[Message]) -> Vec { "functionResponse".to_string(), json!(function_response), ); - if let Some(signature) = get_thought_signature(&response.metadata) { - part.insert( - THOUGHT_SIGNATURE_KEY.to_string(), - json!(signature), - ); + if include_signature { + if let Some(signature) = + get_thought_signature(&response.metadata) + { + part.insert( + THOUGHT_SIGNATURE_KEY.to_string(), + json!(signature), + ); + } } parts.push(json!(part)); } @@ -164,11 +189,15 @@ pub fn format_messages(messages: &[Message]) -> Vec { "functionResponse".to_string(), json!(function_response), ); - if let Some(signature) = get_thought_signature(&response.metadata) { - part.insert( - THOUGHT_SIGNATURE_KEY.to_string(), - json!(signature), - ); + if include_signature { + if let Some(signature) = + get_thought_signature(&response.metadata) + { + part.insert( + THOUGHT_SIGNATURE_KEY.to_string(), + json!(signature), + ); + } } parts.push(json!(part)); } @@ -177,7 +206,9 @@ pub fn format_messages(messages: &[Message]) -> Vec { MessageContent::Thinking(thinking) => { let mut part = Map::new(); part.insert("text".to_string(), json!(thinking.thinking)); - part.insert("thoughtSignature".to_string(), json!(thinking.signature)); + if include_signature { + part.insert("thoughtSignature".to_string(), json!(thinking.signature)); + } parts.push(json!(part)); } @@ -1230,10 +1261,21 @@ mod tests { Ok(tool_result("output")), req1.metadata.as_ref(), ); - let google_out = format_messages(&[native, tool_response]); + let google_out = format_messages(&[native.clone(), tool_response.clone()]); assert_eq!(google_out[0]["parts"][0]["thoughtSignature"], SIG); assert_eq!(google_out[1]["parts"][0]["thoughtSignature"], SIG); + let second_assistant = + Message::assistant().with_thinking("More thinking".to_string(), "sig_456".to_string()); + let google_multi = format_messages(&[native, tool_response, second_assistant]); + assert!(google_multi[0]["parts"][0] + .get("thoughtSignature") + .is_none()); + assert!(google_multi[1]["parts"][0] + .get("thoughtSignature") + .is_none()); + assert_eq!(google_multi[2]["parts"][0]["thoughtSignature"], "sig_456"); + // Text-only response WITH signature but WITHOUT function calls should be regular text // (per original behavior: thinking is only when reasoning before tool calls) let final_response_with_sig =