From 79127299b16e1d52665af2cb4f7f653213a39351 Mon Sep 17 00:00:00 2001 From: Kalvin Chau Date: Thu, 6 Mar 2025 15:55:47 -0800 Subject: [PATCH 1/2] feat: add --debug flag to session/run cli --- crates/goose-cli/src/commands/bench.rs | 2 +- crates/goose-cli/src/main.rs | 22 ++++++- crates/goose-cli/src/session/builder.rs | 3 +- crates/goose-cli/src/session/mod.rs | 20 +++++-- crates/goose-cli/src/session/output.rs | 76 +++++++++++++++---------- 5 files changed, 84 insertions(+), 39 deletions(-) diff --git a/crates/goose-cli/src/commands/bench.rs b/crates/goose-cli/src/commands/bench.rs index 823cd4ebc270..bdc44e6575bf 100644 --- a/crates/goose-cli/src/commands/bench.rs +++ b/crates/goose-cli/src/commands/bench.rs @@ -85,7 +85,7 @@ async fn run_eval( let required_extensions = evaluation.required_extensions(); // Create session with error capture - let base_session = build_session(None, false, Vec::new(), required_extensions).await; + let base_session = build_session(None, false, Vec::new(), required_extensions, false).await; let bench_session = Arc::new(Mutex::new(BenchSession::new(base_session))); let bench_session_clone = bench_session.clone(); diff --git a/crates/goose-cli/src/main.rs b/crates/goose-cli/src/main.rs index 708f4674ed41..aa8bf238f640 100644 --- a/crates/goose-cli/src/main.rs +++ b/crates/goose-cli/src/main.rs @@ -90,6 +90,14 @@ enum Command { )] resume: bool, + /// Enable debug output mode + #[arg( + long, + help = "Enable debug output mode with full content and no truncation", + long_help = "When enabled, shows complete tool responses without truncation and full paths." + )] + debug: bool, + /// Add stdio extensions with environment variables and commands #[arg( long = "with-extension", @@ -157,6 +165,14 @@ enum Command { )] resume: bool, + /// Enable debug output mode + #[arg( + long, + help = "Enable debug output mode with full content and no truncation", + long_help = "When enabled, shows complete tool responses without truncation and full paths." + )] + debug: bool, + /// Add stdio extensions with environment variables and commands #[arg( long = "with-extension", @@ -285,6 +301,7 @@ async fn main() -> Result<()> { Some(Command::Session { identifier, resume, + debug, extension, builtin, }) => { @@ -293,6 +310,7 @@ async fn main() -> Result<()> { resume, extension, builtin, + debug, ) .await; @@ -306,6 +324,7 @@ async fn main() -> Result<()> { interactive, identifier, resume, + debug, extension, builtin, }) => { @@ -332,6 +351,7 @@ async fn main() -> Result<()> { resume, extension, builtin, + debug, ) .await; setup_logging(session.session_file().file_stem().and_then(|s| s.to_str()))?; @@ -410,7 +430,7 @@ async fn main() -> Result<()> { return Ok(()); } else { // Run session command by default - let mut session = build_session(None, false, vec![], vec![]).await; + let mut session = build_session(None, false, vec![], vec![], false).await; setup_logging(session.session_file().file_stem().and_then(|s| s.to_str()))?; let _ = session.interactive(None).await; return Ok(()); diff --git a/crates/goose-cli/src/session/builder.rs b/crates/goose-cli/src/session/builder.rs index 1889cb27b374..d875f8ba9b5c 100644 --- a/crates/goose-cli/src/session/builder.rs +++ b/crates/goose-cli/src/session/builder.rs @@ -15,6 +15,7 @@ pub async fn build_session( resume: bool, extensions: Vec, builtins: Vec, + debug: bool, ) -> Session { // Load config and get provider/model let config = Config::global(); @@ -92,7 +93,7 @@ pub async fn build_session( }; // Create new session - let mut session = Session::new(agent, session_file.clone()); + let mut session = Session::new(agent, session_file.clone(), debug); // Add extensions if provided for extension_str in extensions { diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index d6ad1470bf7f..6af4287d6ea0 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -36,6 +36,7 @@ pub struct Session { session_file: PathBuf, // Cache for completion data - using std::sync for thread safety without async completion_cache: Arc>, + debug: bool, // New field for debug mode } // Cache structure for completion data @@ -56,7 +57,7 @@ impl CompletionCache { } impl Session { - pub fn new(agent: Box, session_file: PathBuf) -> Self { + pub fn new(agent: Box, session_file: PathBuf, debug: bool) -> Self { let messages = match session::read_messages(&session_file) { Ok(msgs) => msgs, Err(e) => { @@ -70,6 +71,7 @@ impl Session { messages, session_file, completion_cache: Arc::new(std::sync::RwLock::new(CompletionCache::new())), + debug, } } @@ -393,7 +395,7 @@ impl Session { } if msg.role == mcp_core::Role::User { - output::render_message(&msg); + output::render_message(&msg, self.debug); } self.messages.push(msg); } @@ -461,7 +463,7 @@ impl Session { session::persist_messages(&self.session_file, &self.messages, None).await?; if interactive {output::hide_thinking()}; - output::render_message(&message); + output::render_message(&message, self.debug); if interactive {output::show_thinking()}; } } @@ -547,7 +549,7 @@ impl Session { // No need for description update here session::persist_messages(&self.session_file, &self.messages, None).await?; - output::render_message(&Message::assistant().with_text(&prompt)); + output::render_message(&Message::assistant().with_text(&prompt), self.debug); } else { // An interruption occurred outside of a tool request-response. if let Some(last_msg) = self.messages.last() { @@ -562,13 +564,19 @@ impl Session { session::persist_messages(&self.session_file, &self.messages, None) .await?; - output::render_message(&Message::assistant().with_text(prompt)); + output::render_message( + &Message::assistant().with_text(prompt), + self.debug, + ); } Some(_) => { // A real users message self.messages.pop(); let prompt = "Interrupted before the model replied and removed the last message."; - output::render_message(&Message::assistant().with_text(prompt)); + output::render_message( + &Message::assistant().with_text(prompt), + self.debug, + ); } None => panic!("No content in last message"), } diff --git a/crates/goose-cli/src/session/output.rs b/crates/goose-cli/src/session/output.rs index 094f81e44772..3adbbeb9b77c 100644 --- a/crates/goose-cli/src/session/output.rs +++ b/crates/goose-cli/src/session/output.rs @@ -96,14 +96,14 @@ pub fn hide_thinking() { THINKING.with(|t| t.borrow_mut().hide()); } -pub fn render_message(message: &Message) { +pub fn render_message(message: &Message, debug: bool) { let theme = get_theme(); for content in &message.content { match content { MessageContent::Text(text) => print_markdown(&text.text, theme), - MessageContent::ToolRequest(req) => render_tool_request(req, theme), - MessageContent::ToolResponse(resp) => render_tool_response(resp, theme), + MessageContent::ToolRequest(req) => render_tool_request(req, theme, debug), + MessageContent::ToolResponse(resp) => render_tool_response(resp, theme, debug), MessageContent::Image(image) => { println!("Image: [data: {}, type: {}]", image.data, image.mime_type); } @@ -126,18 +126,18 @@ pub fn render_message(message: &Message) { println!(); } -fn render_tool_request(req: &ToolRequest, theme: Theme) { +fn render_tool_request(req: &ToolRequest, theme: Theme, debug: bool) { match &req.tool_call { Ok(call) => match call.name.as_str() { - "developer__text_editor" => render_text_editor_request(call), - "developer__shell" => render_shell_request(call), - _ => render_default_request(call), + "developer__text_editor" => render_text_editor_request(call, debug), + "developer__shell" => render_shell_request(call, debug), + _ => render_default_request(call, debug), }, Err(e) => print_markdown(&e.to_string(), theme), } } -fn render_tool_response(resp: &ToolResponse, theme: Theme) { +fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) { let config = Config::global(); match &resp.tool_result { @@ -157,12 +157,14 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme) { if content .priority() .is_some_and(|priority| priority < min_priority) - || content.priority().is_none() + || (content.priority().is_none() && !debug) { continue; } - if let mcp_core::content::Content::Text(text) = content { + if debug { + println!("{:#?}", content); + } else if let mcp_core::content::Content::Text(text) = content { print_markdown(&text.text, theme); } } @@ -266,7 +268,7 @@ pub fn render_builtin_error(names: &str, error: &str) { println!(); } -fn render_text_editor_request(call: &ToolCall) { +fn render_text_editor_request(call: &ToolCall, debug: bool) { print_tool_header(call); // Print path first with special formatting @@ -274,7 +276,7 @@ fn render_text_editor_request(call: &ToolCall) { println!( "{}: {}", style("path").dim(), - style(shorten_path(path)).green() + style(shorten_path(path, debug)).green() ); } @@ -286,26 +288,26 @@ fn render_text_editor_request(call: &ToolCall) { other_args.insert(k.clone(), v.clone()); } } - print_params(&Value::Object(other_args), 0); + print_params(&Value::Object(other_args), 0, debug); } println!(); } -fn render_shell_request(call: &ToolCall) { +fn render_shell_request(call: &ToolCall, debug: bool) { print_tool_header(call); match call.arguments.get("command") { Some(Value::String(s)) => { println!("{}: {}", style("command").dim(), style(s).green()); } - _ => print_params(&call.arguments, 0), + _ => print_params(&call.arguments, 0, debug), } println!(); } -fn render_default_request(call: &ToolCall) { +fn render_default_request(call: &ToolCall, debug: bool) { print_tool_header(call); - print_params(&call.arguments, 0); + print_params(&call.arguments, 0, debug); println!(); } @@ -342,7 +344,7 @@ fn print_markdown(content: &str, theme: Theme) { const MAX_STRING_LENGTH: usize = 40; const INDENT: &str = " "; -fn print_params(value: &Value, depth: usize) { +fn print_params(value: &Value, depth: usize, debug: bool) { let indent = INDENT.repeat(depth); match value { @@ -351,17 +353,17 @@ fn print_params(value: &Value, depth: usize) { match val { Value::Object(_) => { println!("{}{}:", indent, style(key).dim()); - print_params(val, depth + 1); + print_params(val, depth + 1, debug); } Value::Array(arr) => { println!("{}{}:", indent, style(key).dim()); for item in arr.iter() { println!("{}{}- ", indent, INDENT); - print_params(item, depth + 2); + print_params(item, depth + 2, debug); } } Value::String(s) => { - if s.len() > MAX_STRING_LENGTH { + if !debug && s.len() > MAX_STRING_LENGTH { println!("{}{}: {}", indent, style(key).dim(), style("...").dim()); } else { println!("{}{}: {}", indent, style(key).dim(), style(s).green()); @@ -382,11 +384,11 @@ fn print_params(value: &Value, depth: usize) { Value::Array(arr) => { for (i, item) in arr.iter().enumerate() { println!("{}{}.", indent, i + 1); - print_params(item, depth + 1); + print_params(item, depth + 1, debug); } } Value::String(s) => { - if s.len() > MAX_STRING_LENGTH { + if !debug && s.len() > MAX_STRING_LENGTH { println!( "{}{}", indent, @@ -408,7 +410,12 @@ fn print_params(value: &Value, depth: usize) { } } -fn shorten_path(path: &str) -> String { +fn shorten_path(path: &str, debug: bool) -> String { + // In debug mode, return the full path + if debug { + return path.to_string(); + } + let path = Path::new(path); // First try to convert to ~ if it's in home directory @@ -485,9 +492,17 @@ mod tests { #[test] fn test_short_paths_unchanged() { - assert_eq!(shorten_path("/usr/bin"), "/usr/bin"); - assert_eq!(shorten_path("/a/b/c"), "/a/b/c"); - assert_eq!(shorten_path("file.txt"), "file.txt"); + assert_eq!(shorten_path("/usr/bin", false), "/usr/bin"); + assert_eq!(shorten_path("/a/b/c", false), "/a/b/c"); + assert_eq!(shorten_path("file.txt", false), "file.txt"); + } + + #[test] + fn test_debug_mode_returns_full_path() { + assert_eq!( + shorten_path("/very/long/path/that/would/normally/be/shortened", true), + "/very/long/path/that/would/normally/be/shortened" + ); } #[test] @@ -499,13 +514,13 @@ mod tests { env::set_var("HOME", "/Users/testuser"); assert_eq!( - shorten_path("/Users/testuser/documents/file.txt"), + shorten_path("/Users/testuser/documents/file.txt", false), "~/documents/file.txt" ); // A path that starts similarly to home but isn't in home assert_eq!( - shorten_path("/Users/testuser2/documents/file.txt"), + shorten_path("/Users/testuser2/documents/file.txt", false), "/Users/testuser2/documents/file.txt" ); @@ -521,7 +536,8 @@ mod tests { fn test_long_path_shortening() { assert_eq!( shorten_path( - "/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv/long/path/with/many/components/file.txt" + "/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv/long/path/with/many/components/file.txt", + false ), "/v/l/p/w/m/components/file.txt" ); From e88551dc34b5848b66ab66319aa294325e038a82 Mon Sep 17 00:00:00 2001 From: Kalvin Chau Date: Thu, 6 Mar 2025 17:58:20 -0800 Subject: [PATCH 2/2] style: cargo fmt --- crates/goose-cli/src/commands/bench.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/goose-cli/src/commands/bench.rs b/crates/goose-cli/src/commands/bench.rs index 4cd7a3cb6377..a16d589d0b56 100644 --- a/crates/goose-cli/src/commands/bench.rs +++ b/crates/goose-cli/src/commands/bench.rs @@ -85,8 +85,14 @@ async fn run_eval( let requirements = evaluation.required_extensions(); // Create session with error capture - let base_session = - build_session(None, false, requirements.external, requirements.builtin, false).await; + let base_session = build_session( + None, + false, + requirements.external, + requirements.builtin, + false, + ) + .await; let bench_session = Arc::new(Mutex::new(BenchSession::new(base_session))); let bench_session_clone = bench_session.clone();