From d39109ef8fa491215e83f3194da4d6e6cbccd084 Mon Sep 17 00:00:00 2001 From: Alex Hancock Date: Sat, 20 Sep 2025 09:38:06 -0400 Subject: [PATCH] feat(acp): add nicer formatting for tool names (#4686) --- crates/goose-cli/src/commands/acp.rs | 76 +++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/crates/goose-cli/src/commands/acp.rs b/crates/goose-cli/src/commands/acp.rs index 157d074b95..bedfbf6a88 100644 --- a/crates/goose-cli/src/commands/acp.rs +++ b/crates/goose-cli/src/commands/acp.rs @@ -49,6 +49,50 @@ fn read_resource_link(link: acp::ResourceLink) -> Option { } } +/// Format a tool name to be more human-friendly by splitting extension and tool names +/// and converting underscores to spaces with proper capitalization +fn format_tool_name(tool_name: &str) -> String { + // Split on double underscore to separate extension from tool name + if let Some((extension, tool)) = tool_name.split_once("__") { + let formatted_extension = extension.replace('_', " "); + let formatted_tool = tool.replace('_', " "); + + // Capitalize first letter of each word + let capitalize = |s: &str| { + s.split_whitespace() + .map(|word| { + let mut chars = word.chars(); + match chars.next() { + None => String::new(), + Some(first) => first.to_uppercase().collect::() + chars.as_str(), + } + }) + .collect::>() + .join(" ") + }; + + format!( + "{}: {}", + capitalize(&formatted_extension), + capitalize(&formatted_tool) + ) + } else { + // Fallback for tools without double underscore + let formatted = tool_name.replace('_', " "); + formatted + .split_whitespace() + .map(|word| { + let mut chars = word.chars(); + match chars.next() { + None => String::new(), + Some(first) => first.to_uppercase().collect::() + chars.as_str(), + } + }) + .collect::>() + .join(" ") + } +} + impl GooseAcpAgent { async fn new( session_update_tx: mpsc::UnboundedSender<(acp::SessionNotification, oneshot::Sender<()>)>, @@ -269,7 +313,7 @@ impl GooseAcpAgent { session_id: session_id.clone(), update: acp::SessionUpdate::ToolCall(acp::ToolCall { id: acp::ToolCallId(acp_tool_id.clone().into()), - title: format!("Calling tool: {}", tool_name), + title: format_tool_name(&tool_name), kind: acp::ToolKind::default(), status: acp::ToolCallStatus::Pending, content: Vec::new(), @@ -673,7 +717,7 @@ mod tests { use std::io::Write; use tempfile::NamedTempFile; - use crate::commands::acp::read_resource_link; + use crate::commands::acp::{format_tool_name, read_resource_link}; fn new_resource_link(content: &str) -> anyhow::Result<(ResourceLink, NamedTempFile)> { let mut file = NamedTempFile::new()?; @@ -714,4 +758,32 @@ print(\"hello, world\") assert_eq!(result, expected,) } + + #[test] + fn test_format_tool_name_with_extension() { + assert_eq!( + format_tool_name("developer__text_editor"), + "Developer: Text Editor" + ); + assert_eq!( + format_tool_name("platform__manage_extensions"), + "Platform: Manage Extensions" + ); + assert_eq!(format_tool_name("todo__read"), "Todo: Read"); + } + + #[test] + fn test_format_tool_name_without_extension() { + assert_eq!(format_tool_name("simple_tool"), "Simple Tool"); + assert_eq!(format_tool_name("another_name"), "Another Name"); + assert_eq!(format_tool_name("single"), "Single"); + } + + #[test] + fn test_format_tool_name_edge_cases() { + assert_eq!(format_tool_name(""), ""); + assert_eq!(format_tool_name("__"), ": "); + assert_eq!(format_tool_name("extension__"), "Extension: "); + assert_eq!(format_tool_name("__tool"), ": Tool"); + } }