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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ description = "An AI agent"
uninlined_format_args = "allow"

[workspace.dependencies]
rmcp = "0.2.1"
rmcp = { version = "0.2.1", features = ["schemars"] }

# Patch for Windows cross-compilation issue with crunchy
[patch.crates-io]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use crate::eval_suites::{
use crate::register_evaluation;
use async_trait::async_trait;
use goose::message::MessageContent;
use mcp_core::content::Content;
use rmcp::model::Role;
use serde_json::{self, Value};

Expand Down Expand Up @@ -68,7 +67,7 @@ impl Evaluation for DeveloperImage {
if let Ok(result) = &tool_resp.tool_result {
// Check each item in the result list
for item in result {
if let Content::Image(image) = item {
if let Some(image) = item.as_image() {
// Image content already contains mime_type and data
if image.mime_type.starts_with("image/")
&& !image.data.is_empty()
Expand Down
101 changes: 65 additions & 36 deletions crates/goose-cli/src/session/export.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use goose::message::{Message, MessageContent, ToolRequest, ToolResponse};
use goose::utils::safe_truncate;
use mcp_core::content::Content as McpContent;
use mcp_core::resource::ResourceContents;
use rmcp::model::Role;
use rmcp::model::{RawContent, ResourceContents, Role};
use serde_json::Value;

const MAX_STRING_LENGTH_MD_EXPORT: usize = 4096; // Generous limit for export
Expand Down Expand Up @@ -219,8 +217,8 @@ pub fn tool_response_to_markdown(resp: &ToolResponse, export_all_content: bool)
}
}

match content {
McpContent::Text(text_content) => {
match &content.raw {
RawContent::Text(text_content) => {
let trimmed_text = text_content.text.trim();
if (trimmed_text.starts_with('{') && trimmed_text.ends_with('}'))
|| (trimmed_text.starts_with('[') && trimmed_text.ends_with(']'))
Expand All @@ -236,7 +234,7 @@ pub fn tool_response_to_markdown(resp: &ToolResponse, export_all_content: bool)
md.push_str("\n\n");
}
}
McpContent::Image(image_content) => {
RawContent::Image(image_content) => {
if image_content.mime_type.starts_with("image/") {
// For actual images, provide a placeholder that indicates it's an image
md.push_str(&format!(
Expand All @@ -252,7 +250,7 @@ pub fn tool_response_to_markdown(resp: &ToolResponse, export_all_content: bool)
));
}
}
McpContent::Resource(resource) => {
RawContent::Resource(resource) => {
match &resource.resource {
ResourceContents::TextResourceContents {
uri,
Expand Down Expand Up @@ -299,6 +297,9 @@ pub fn tool_response_to_markdown(resp: &ToolResponse, export_all_content: bool)
}
}
}
RawContent::Audio(_) => {
md.push_str("[audio content not displayed in Markdown export]\n\n")
}
}
}
}
Expand Down Expand Up @@ -360,8 +361,8 @@ pub fn message_to_markdown(message: &Message, export_all_content: bool) -> Strin
mod tests {
use super::*;
use goose::message::{Message, ToolRequest, ToolResponse};
use mcp_core::content::{Content as McpContent, TextContent};
use mcp_core::tool::ToolCall;
use rmcp::model::{Content, RawTextContent, TextContent};
use serde_json::json;

#[test]
Expand Down Expand Up @@ -521,12 +522,14 @@ mod tests {
#[test]
fn test_tool_response_to_markdown_text() {
let text_content = TextContent {
text: "Command executed successfully".to_string(),
raw: RawTextContent {
text: "Command executed successfully".to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "test-id".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let result = tool_response_to_markdown(&tool_response, true);
Expand All @@ -538,12 +541,14 @@ mod tests {
fn test_tool_response_to_markdown_json() {
let json_text = r#"{"status": "success", "data": "test"}"#;
let text_content = TextContent {
text: json_text.to_string(),
raw: RawTextContent {
text: json_text.to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "test-id".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let result = tool_response_to_markdown(&tool_response, true);
Expand Down Expand Up @@ -640,12 +645,14 @@ if __name__ == "__main__":
hello_world()"#;

let text_content = TextContent {
text: python_code.to_string(),
raw: RawTextContent {
text: python_code.to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "shell-cat".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let request_result = tool_request_to_markdown(&tool_request, true);
Expand Down Expand Up @@ -677,12 +684,14 @@ if __name__ == "__main__":

let git_output = " M src/main.rs\n?? temp.txt\n A new_feature.rs";
let text_content = TextContent {
text: git_output.to_string(),
raw: RawTextContent {
text: git_output.to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "git-status".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let request_result = tool_request_to_markdown(&tool_request, true);
Expand Down Expand Up @@ -722,12 +731,14 @@ warning: unused variable `x`
Finished dev [unoptimized + debuginfo] target(s) in 2.45s"#;

let text_content = TextContent {
text: build_output.to_string(),
raw: RawTextContent {
text: build_output.to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "cargo-build".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let response_result = tool_response_to_markdown(&tool_response, true);
Expand Down Expand Up @@ -765,12 +776,14 @@ warning: unused variable `x`
}"#;

let text_content = TextContent {
text: api_response.to_string(),
raw: RawTextContent {
text: api_response.to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "curl-api".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let response_result = tool_response_to_markdown(&tool_response, true);
Expand All @@ -797,12 +810,14 @@ warning: unused variable `x`
};

let text_content = TextContent {
text: "File created successfully".to_string(),
raw: RawTextContent {
text: "File created successfully".to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "editor-write".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let request_result = tool_request_to_markdown(&tool_request, true);
Expand Down Expand Up @@ -850,12 +865,14 @@ def process_data(data: List[Dict]) -> List[Dict]:
return [item for item in data if item.get('active', False)]"#;

let text_content = TextContent {
text: python_code.to_string(),
raw: RawTextContent {
text: python_code.to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "editor-view".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let response_result = tool_response_to_markdown(&tool_response, true);
Expand Down Expand Up @@ -883,12 +900,14 @@ def process_data(data: List[Dict]) -> List[Dict]:
Command failed with exit code 2"#;

let text_content = TextContent {
text: error_output.to_string(),
raw: RawTextContent {
text: error_output.to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "shell-error".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let response_result = tool_response_to_markdown(&tool_response, true);
Expand Down Expand Up @@ -919,12 +938,14 @@ Command failed with exit code 2"#;
5^2 = 25"#;

let text_content = TextContent {
text: script_output.to_string(),
raw: RawTextContent {
text: script_output.to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "script-exec".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let request_result = tool_request_to_markdown(&tool_request, true);
Expand Down Expand Up @@ -962,12 +983,14 @@ drwx------ 3 user staff 96 Dec 6 16:20 com.apple.launchd.abc
/tmp"#;

let text_content = TextContent {
text: multi_output.to_string(),
raw: RawTextContent {
text: multi_output.to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "multi-cmd".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let request_result = tool_request_to_markdown(&_tool_request, true);
Expand Down Expand Up @@ -1001,12 +1024,14 @@ src/database.rs:23:async fn query_users(pool: &Pool) -> Result<Vec<User>> {
src/middleware.rs:12:async fn auth_middleware(req: Request, next: Next) -> Result<Response> {"#;

let text_content = TextContent {
text: grep_output.to_string(),
raw: RawTextContent {
text: grep_output.to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "grep-search".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let request_result = tool_request_to_markdown(&tool_request, true);
Expand Down Expand Up @@ -1037,12 +1062,14 @@ src/middleware.rs:12:async fn auth_middleware(req: Request, next: Next) -> Resul

let json_output = r#"{"status": "success", "data": {"count": 42}}"#;
let text_content = TextContent {
text: json_output.to_string(),
raw: RawTextContent {
text: json_output.to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "json-test".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let response_result = tool_response_to_markdown(&tool_response, true);
Expand Down Expand Up @@ -1074,12 +1101,14 @@ src/middleware.rs:12:async fn auth_middleware(req: Request, next: Next) -> Resul
found 0 vulnerabilities"#;

let text_content = TextContent {
text: npm_output.to_string(),
raw: RawTextContent {
text: npm_output.to_string(),
},
annotations: None,
};
let tool_response = ToolResponse {
id: "npm-install".to_string(),
tool_result: Ok(vec![McpContent::Text(text_content)]),
tool_result: Ok(vec![Content::text(text_content.raw.text)]),
};

let request_result = tool_request_to_markdown(&tool_request, true);
Expand Down
30 changes: 28 additions & 2 deletions crates/goose-cli/src/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ use goose::providers::pricing::initialize_pricing_cache;
use goose::session;
use input::InputResult;
use mcp_core::handler::ToolError;
use mcp_core::prompt::PromptMessage;
use mcp_core::protocol::JsonRpcMessage;
use mcp_core::protocol::JsonRpcNotification;
use rmcp::model::PromptMessage;

use rand::{distributions::Alphanumeric, Rng};
use rustyline::EditMode;
Expand Down Expand Up @@ -359,7 +359,33 @@ impl Session {

pub async fn get_prompt(&mut self, name: &str, arguments: Value) -> Result<Vec<PromptMessage>> {
let result = self.agent.get_prompt(name, arguments).await?;
Ok(result.messages)
// Convert mcp_core::prompt::PromptMessage to rmcp::model::PromptMessage
let converted_messages = result
.messages
.into_iter()
.map(|msg| rmcp::model::PromptMessage {
role: match msg.role {
mcp_core::prompt::PromptMessageRole::User => {
rmcp::model::PromptMessageRole::User
}
mcp_core::prompt::PromptMessageRole::Assistant => {
rmcp::model::PromptMessageRole::Assistant
}
},
content: match msg.content {
mcp_core::prompt::PromptMessageContent::Text { text } => {
rmcp::model::PromptMessageContent::Text { text }
}
mcp_core::prompt::PromptMessageContent::Image { image } => {
rmcp::model::PromptMessageContent::Image { image }
}
mcp_core::prompt::PromptMessageContent::Resource { resource } => {
rmcp::model::PromptMessageContent::Resource { resource }
}
},
})
.collect();
Ok(converted_messages)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should go away once we do the PromptMessage migration. But we needed to do a bit of it here since we want to work with the rmcp content types.

}

/// Process a single message and get the response
Expand Down
2 changes: 1 addition & 1 deletion crates/goose-cli/src/session/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) {

if debug {
println!("{:#?}", content);
} else if let mcp_core::content::Content::Text(text) = content {
} else if let Some(text) = content.as_text() {
print_markdown(&text.text, theme);
}
}
Expand Down
Loading
Loading