Skip to content
Merged
Show file tree
Hide file tree
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
10 changes: 8 additions & 2 deletions crates/goose/src/agents/reply_parts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,17 @@ impl Agent {
) -> Result<MessageStream, ProviderError> {
let config = provider.get_model_config();

let filtered_messages: Vec<Message> = messages
.iter()
.filter(|m| m.is_agent_visible())
.map(|m| m.agent_visible_content())
.collect();

// Convert tool messages to text if toolshim is enabled
let messages_for_provider = if config.toolshim {
convert_tool_messages_to_text(messages)
convert_tool_messages_to_text(&filtered_messages)
} else {
Conversation::new_unvalidated(messages.to_vec())
Conversation::new_unvalidated(filtered_messages)
};

// Clone owned data to move into the async stream
Expand Down
7 changes: 3 additions & 4 deletions crates/goose/src/providers/chatgpt_codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,8 @@ static CHATGPT_CODEX_AUTH_STATE: LazyLock<Arc<ChatGptCodexAuthState>> =
fn build_input_items(messages: &[Message]) -> Result<Vec<Value>> {
let mut items = Vec::new();

for message in messages.iter().filter(|m| m.is_agent_visible()) {
let filtered = message.agent_visible_content();
let role = match filtered.role {
for message in messages {
let role = match message.role {
Role::User => Some("user"),
Role::Assistant => Some("assistant"),
};
Expand All @@ -96,7 +95,7 @@ fn build_input_items(messages: &[Message]) -> Result<Vec<Value>> {
}
};

for content in &filtered.content {
for content in &message.content {
match content {
MessageContent::Text(text) => {
if !text.text.is_empty() {
Expand Down
7 changes: 3 additions & 4 deletions crates/goose/src/providers/cursor_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,14 @@ impl CursorAgentProvider {
full_prompt.push_str("\n\n");

// Add conversation history
for message in messages.iter().filter(|m| m.is_agent_visible()) {
let filtered = message.agent_visible_content();
let role_prefix = match filtered.role {
for message in messages {
let role_prefix = match message.role {
Role::User => "Human: ",
Role::Assistant => "Assistant: ",
};
full_prompt.push_str(role_prefix);

for content in &filtered.content {
for content in &message.content {
match content {
MessageContent::Text(text_content) => {
full_prompt.push_str(&text_content.text);
Expand Down
8 changes: 1 addition & 7 deletions crates/goose/src/providers/formats/anthropic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,7 @@ const DATA_FIELD: &str = "data";
pub fn format_messages(messages: &[Message]) -> Vec<Value> {
let mut anthropic_messages = Vec::new();

let filtered_messages: Vec<Message> = messages
.iter()
.filter(|m| m.is_agent_visible())
.map(|m| m.agent_visible_content())
.collect();

for message in &filtered_messages {
for message in messages {
let role = match message.role {
Role::User => USER_ROLE,
Role::Assistant => ASSISTANT_ROLE,
Expand Down
6 changes: 2 additions & 4 deletions crates/goose/src/providers/formats/bedrock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ use super::super::base::Usage;
use crate::conversation::message::{Message, MessageContent};

pub fn to_bedrock_message(message: &Message) -> Result<bedrock::Message> {
let filtered = message.agent_visible_content();

bedrock::Message::builder()
.role(to_bedrock_role(&filtered.role))
.role(to_bedrock_role(&message.role))
.set_content(Some(
filtered
message
.content
.iter()
.map(to_bedrock_message_content)
Expand Down
7 changes: 3 additions & 4 deletions crates/goose/src/providers/formats/databricks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,10 @@ fn format_tool_response(
/// even though the message structure is otherwise following openai, the enum switches this
fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<DatabricksMessage> {
let mut result = Vec::new();
for message in messages.iter().filter(|m| m.is_agent_visible()) {
let filtered = message.agent_visible_content();
for message in messages {
let mut converted = DatabricksMessage {
content: Value::Null,
role: match filtered.role {
role: match message.role {
Role::User => "user".to_string(),
Role::Assistant => "assistant".to_string(),
},
Expand All @@ -122,7 +121,7 @@ fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<Data
let mut has_tool_calls = false;
let mut has_multiple_content = false;

for content in &filtered.content {
for content in &message.content {
match content {
MessageContent::Text(text) => {
if !text.text.is_empty() {
Expand Down
153 changes: 62 additions & 91 deletions crates/goose/src/providers/formats/google.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,104 +105,75 @@ pub fn format_messages(messages: &[Message]) -> Vec<Value> {
parts.push(json!({"text":format!("Error: {}", e)}));
}
},
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)
})
})
.map(|content| content.raw.clone())
.collect();

let mut tool_content = Vec::new();
for content in abridged {
match content {
RawContent::Image(image) => {
parts.push(json!({
"inline_data": {
"mime_type": image.mime_type,
"data": image.data,
}
}));
}
_ => {
tool_content.push(content.no_annotation());
}
MessageContent::ToolResponse(response) => match &response.tool_result {
Ok(result) => {
let mut tool_content = Vec::new();
for content in result.content.iter().map(|c| c.raw.clone()) {
match content {
RawContent::Image(image) => {
parts.push(json!({
"inline_data": {
"mime_type": image.mime_type,
"data": image.data,
}
}));
}
}
let mut text = tool_content
.iter()
.filter_map(|c| match c.deref() {
RawContent::Text(t) => Some(t.text.clone()),
RawContent::Resource(raw_embedded_resource) => Some(
raw_embedded_resource
.clone()
.no_annotation()
.get_text(),
),
_ => None,
})
.collect::<Vec<_>>()
.join("\n");

if text.is_empty() {
text = "Tool call is done.".to_string();
}
let mut part = Map::new();
let mut function_response = Map::new();
function_response.insert("name".to_string(), json!(response.id));
function_response.insert(
"response".to_string(),
json!({"content": {"text": text}}),
);
part.insert(
"functionResponse".to_string(),
json!(function_response),
);
if include_signature {
if let Some(signature) =
get_thought_signature(&response.metadata)
{
part.insert(
THOUGHT_SIGNATURE_KEY.to_string(),
json!(signature),
);
_ => {
tool_content.push(content.no_annotation());
}
}
parts.push(json!(part));
}
Err(e) => {
let mut part = Map::new();
let mut function_response = Map::new();
function_response.insert("name".to_string(), json!(response.id));
function_response.insert(
"response".to_string(),
json!({"content": {"text": format!("Error: {}", e)}}),
);
part.insert(
"functionResponse".to_string(),
json!(function_response),
);
if include_signature {
if let Some(signature) =
get_thought_signature(&response.metadata)
{
part.insert(
THOUGHT_SIGNATURE_KEY.to_string(),
json!(signature),
);
}
let mut text = tool_content
.iter()
.filter_map(|c| match c.deref() {
RawContent::Text(t) => Some(t.text.clone()),
RawContent::Resource(raw_embedded_resource) => Some(
raw_embedded_resource.clone().no_annotation().get_text(),
),
_ => None,
})
.collect::<Vec<_>>()
.join("\n");

if text.is_empty() {
text = "Tool call is done.".to_string();
}
let mut part = Map::new();
let mut function_response = Map::new();
function_response.insert("name".to_string(), json!(response.id));
function_response
.insert("response".to_string(), json!({"content": {"text": text}}));
part.insert("functionResponse".to_string(), json!(function_response));
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));
}
parts.push(json!(part));
}
}
Err(e) => {
let mut part = Map::new();
let mut function_response = Map::new();
function_response.insert("name".to_string(), json!(response.id));
function_response.insert(
"response".to_string(),
json!({"content": {"text": format!("Error: {}", e)}}),
);
part.insert("functionResponse".to_string(), json!(function_response));
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));
}
},
MessageContent::Thinking(thinking) => {
let mut part = Map::new();
part.insert("text".to_string(), json!(thinking.thinking));
Expand Down
13 changes: 5 additions & 8 deletions crates/goose/src/providers/formats/openai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,16 @@ struct StreamingChunk {

pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<Value> {
let mut messages_spec = Vec::new();
for message in messages.iter().filter(|m| m.is_agent_visible()) {
let filtered = message.agent_visible_content();
for message in messages {
let mut converted = json!({
"role": filtered.role
"role": message.role
});

let mut output = Vec::new();
let mut content_array = Vec::new();
let mut text_array = Vec::new();

for content in &filtered.content {
for content in &message.content {
match content {
MessageContent::Text(text) => {
if !text.text.is_empty() {
Expand Down Expand Up @@ -136,13 +135,11 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<
MessageContent::ToolResponse(response) => {
match &response.tool_result {
Ok(result) => {
let abridged: Vec<_> = result.content.to_vec();

// Process all content, replacing images with placeholder text
let mut tool_content = Vec::new();
let mut image_messages = Vec::new();

for content in abridged {
for content in result.content.iter() {
match content.deref() {
RawContent::Image(image) => {
// Add placeholder text in the tool response
Expand All @@ -164,7 +161,7 @@ pub fn format_messages(messages: &[Message], image_format: &ImageFormat) -> Vec<
tool_content.push(Content::text(text));
}
_ => {
tool_content.push(content);
tool_content.push(content.clone());
}
}
}
Expand Down
5 changes: 2 additions & 3 deletions crates/goose/src/providers/formats/openai_responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,8 @@ fn add_function_calls(input_items: &mut Vec<Value>, messages: &[Message]) {
}

fn add_function_call_outputs(input_items: &mut Vec<Value>, messages: &[Message]) {
for message in messages.iter().filter(|m| m.is_agent_visible()) {
let filtered = message.agent_visible_content();
for content in &filtered.content {
for message in messages {
for content in &message.content {
if let MessageContent::ToolResponse(response) = content {
match &response.tool_result {
Ok(contents) => {
Expand Down
8 changes: 1 addition & 7 deletions crates/goose/src/providers/formats/snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@ use std::collections::HashSet;
pub fn format_messages(messages: &[Message]) -> Vec<Value> {
let mut snowflake_messages = Vec::new();

let filtered_messages: Vec<Message> = messages
.iter()
.filter(|m| m.is_agent_visible())
.map(|m| m.agent_visible_content())
.collect();

for message in &filtered_messages {
for message in messages {
let role = match message.role {
Role::User => "user",
Role::Assistant => "assistant",
Expand Down
7 changes: 3 additions & 4 deletions crates/goose/src/providers/toolshim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,11 +321,10 @@ pub fn convert_tool_messages_to_text(messages: &[Message]) -> Conversation {
let converted_messages: Vec<Message> = 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 &filtered.content {
for content in &message.content {
match content {
MessageContent::ToolRequest(req) => {
has_tool_content = true;
Expand Down Expand Up @@ -370,9 +369,9 @@ pub fn convert_tool_messages_to_text(messages: &[Message]) -> Conversation {
}

if has_tool_content {
Message::new(filtered.role.clone(), filtered.created, new_content)
Message::new(message.role.clone(), message.created, new_content)
} else {
filtered
message.clone()
}
})
.collect();
Expand Down
Loading