Compact tool output improvements
- Rename take_screenshot -> screenshot, code_coverage -> coverage (shorter names) - Align | character across all compact tools (pad to 11 chars for str_replace) - Make code_search a compact tool with summary display - Show language and search name in code_search output (e.g., rust:"find structs") - Add format_code_search_summary() to extract match/file counts from JSON response
This commit is contained in:
@@ -238,8 +238,9 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
if tool_name == "shell" {
|
if tool_name == "shell" {
|
||||||
*self.is_shell_compact.lock().unwrap() = true;
|
*self.is_shell_compact.lock().unwrap() = true;
|
||||||
// Print compact shell header: "● shell | command"
|
// Print compact shell header: "● shell | command"
|
||||||
|
// Pad to align with longest compact tool (str_replace = 11 chars)
|
||||||
println!(
|
println!(
|
||||||
" \x1b[2m●\x1b[0m {}{} \x1b[2m|\x1b[0m \x1b[35m{}\x1b[0m",
|
" \x1b[2m●\x1b[0m {}{:<11}\x1b[0m \x1b[2m|\x1b[0m \x1b[35m{}\x1b[0m",
|
||||||
tool_color, tool_name, display_value
|
tool_color, tool_name, display_value
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -323,7 +324,7 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
|
|
||||||
fn print_tool_compact(&self, tool_name: &str, summary: &str, duration_str: &str, tokens_delta: u32, _context_percentage: f32) -> bool {
|
fn print_tool_compact(&self, tool_name: &str, summary: &str, duration_str: &str, tokens_delta: u32, _context_percentage: f32) -> bool {
|
||||||
// Handle file operation tools and other compact tools
|
// Handle file operation tools and other compact tools
|
||||||
let is_compact_tool = matches!(tool_name, "read_file" | "write_file" | "str_replace" | "remember" | "take_screenshot" | "code_coverage" | "rehydrate");
|
let is_compact_tool = matches!(tool_name, "read_file" | "write_file" | "str_replace" | "remember" | "screenshot" | "coverage" | "rehydrate" | "code_search");
|
||||||
if !is_compact_tool {
|
if !is_compact_tool {
|
||||||
// Reset continuation tracking for non-compact tools
|
// Reset continuation tracking for non-compact tools
|
||||||
*self.last_read_file_path.lock().unwrap() = None;
|
*self.last_read_file_path.lock().unwrap() = None;
|
||||||
@@ -354,8 +355,35 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
|
|
||||||
// For tools without file_path, get other relevant args
|
// For tools without file_path, get other relevant args
|
||||||
let display_arg = if file_path.is_empty() {
|
let display_arg = if file_path.is_empty() {
|
||||||
// For remember, take_screenshot, etc. - no path to show
|
// For code_search, extract language and name from searches
|
||||||
|
if tool_name == "code_search" {
|
||||||
|
// searches arg is JSON array, try to extract first search's language and name
|
||||||
|
if let Some((_, searches_json)) = args.iter().find(|(k, _)| k == "searches") {
|
||||||
|
if let Ok(searches) = serde_json::from_str::<serde_json::Value>(searches_json) {
|
||||||
|
if let Some(first_search) = searches.as_array().and_then(|arr| arr.first()) {
|
||||||
|
let lang = first_search.get("language").and_then(|v| v.as_str()).unwrap_or("?");
|
||||||
|
let name = first_search.get("name").and_then(|v| v.as_str()).unwrap_or("?");
|
||||||
|
// Truncate name if too long
|
||||||
|
let display_name = if name.len() > 30 {
|
||||||
|
let truncate_at = name.char_indices().nth(27).map(|(i, _)| i).unwrap_or(name.len());
|
||||||
|
format!("{}...", &name[..truncate_at])
|
||||||
|
} else {
|
||||||
|
name.to_string()
|
||||||
|
};
|
||||||
|
format!("{}:\"{}\"", lang, display_name)
|
||||||
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For remember, screenshot, etc. - no path to show
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Truncate long paths
|
// Truncate long paths
|
||||||
if file_path.len() > 60 {
|
if file_path.len() > 60 {
|
||||||
@@ -409,14 +437,16 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
);
|
);
|
||||||
} else if display_arg.is_empty() {
|
} else if display_arg.is_empty() {
|
||||||
// Tools without file path: " ● tool_name | summary | tokens ◉ time"
|
// Tools without file path: " ● tool_name | summary | tokens ◉ time"
|
||||||
|
// Pad to align with longest compact tool (str_replace = 11 chars)
|
||||||
println!(
|
println!(
|
||||||
" \x1b[2m●\x1b[0m {}{} \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m",
|
" \x1b[2m●\x1b[0m {}{:<11}\x1b[0m \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m",
|
||||||
tool_color, tool_name, summary, tokens_delta, duration_str
|
tool_color, tool_name, summary, tokens_delta, duration_str
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Tools with file path: " ● tool_name | path [range] | summary | tokens ◉ time"
|
// Tools with file path: " ● tool_name | path [range] | summary | tokens ◉ time"
|
||||||
|
// Pad to align with longest compact tool (str_replace = 11 chars)
|
||||||
println!(
|
println!(
|
||||||
" \x1b[2m●\x1b[0m {}{} \x1b[2m|\x1b[0m \x1b[35m{}{}\x1b[0m \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m",
|
" \x1b[2m●\x1b[0m {}{:<11}\x1b[0m \x1b[2m|\x1b[0m \x1b[35m{}{}\x1b[0m \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m",
|
||||||
tool_color, tool_name, display_arg, range_suffix, summary, tokens_delta, duration_str
|
tool_color, tool_name, display_arg, range_suffix, summary, tokens_delta, duration_str
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -455,11 +485,13 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
match content {
|
match content {
|
||||||
None => {
|
None => {
|
||||||
// Empty TODO
|
// Empty TODO
|
||||||
println!(" \x1b[2m●\x1b[0m {}{}\x1b[0m \x1b[2m|\x1b[0m \x1b[35mempty\x1b[0m", tool_color, tool_name);
|
// Pad to align with longest compact tool (str_replace = 11 chars)
|
||||||
|
println!(" \x1b[2m●\x1b[0m {}{:<11}\x1b[0m \x1b[2m|\x1b[0m \x1b[35mempty\x1b[0m", tool_color, tool_name);
|
||||||
}
|
}
|
||||||
Some(text) => {
|
Some(text) => {
|
||||||
// Header
|
// Header
|
||||||
println!(" \x1b[2m●\x1b[0m {}{}\x1b[0m", tool_color, tool_name);
|
// Pad to align with longest compact tool (str_replace = 11 chars)
|
||||||
|
println!(" \x1b[2m●\x1b[0m {}{:<11}\x1b[0m", tool_color, tool_name);
|
||||||
|
|
||||||
let lines: Vec<&str> = text.lines().collect();
|
let lines: Vec<&str> = text.lines().collect();
|
||||||
let last_idx = lines.len().saturating_sub(1);
|
let last_idx = lines.len().saturating_sub(1);
|
||||||
|
|||||||
@@ -2047,7 +2047,7 @@ Skip if nothing new. Be brief."#;
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a compact tool (file operations)
|
// Check if this is a compact tool (file operations)
|
||||||
let is_compact_tool = matches!(tool_call.tool.as_str(), "read_file" | "write_file" | "str_replace" | "remember" | "take_screenshot" | "code_coverage" | "rehydrate");
|
let is_compact_tool = matches!(tool_call.tool.as_str(), "read_file" | "write_file" | "str_replace" | "remember" | "screenshot" | "coverage" | "rehydrate" | "code_search");
|
||||||
|
|
||||||
// Only print output header for non-compact tools
|
// Only print output header for non-compact tools
|
||||||
if !is_compact_tool && !is_todo_tool {
|
if !is_compact_tool && !is_todo_tool {
|
||||||
@@ -2120,11 +2120,11 @@ Skip if nothing new. Be brief."#;
|
|||||||
// Extract size from result like "Memory updated. Size: 1.2k"
|
// Extract size from result like "Memory updated. Size: 1.2k"
|
||||||
Some(streaming::format_remember_summary(&tool_result))
|
Some(streaming::format_remember_summary(&tool_result))
|
||||||
}
|
}
|
||||||
"take_screenshot" => {
|
"screenshot" => {
|
||||||
// Extract path from result
|
// Extract path from result
|
||||||
Some(streaming::format_screenshot_summary(&tool_result))
|
Some(streaming::format_screenshot_summary(&tool_result))
|
||||||
}
|
}
|
||||||
"code_coverage" => {
|
"coverage" => {
|
||||||
// Show coverage summary
|
// Show coverage summary
|
||||||
Some(streaming::format_coverage_summary(&tool_result))
|
Some(streaming::format_coverage_summary(&tool_result))
|
||||||
}
|
}
|
||||||
@@ -2132,6 +2132,10 @@ Skip if nothing new. Be brief."#;
|
|||||||
// Show fragment info
|
// Show fragment info
|
||||||
Some(streaming::format_rehydrate_summary(&tool_result))
|
Some(streaming::format_rehydrate_summary(&tool_result))
|
||||||
}
|
}
|
||||||
|
"code_search" => {
|
||||||
|
// Show search summary (matches and files)
|
||||||
|
Some(streaming::format_code_search_summary(&tool_result))
|
||||||
|
}
|
||||||
_ => Some(format!("✅ completed"))
|
_ => Some(format!("✅ completed"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -386,6 +386,25 @@ pub fn format_rehydrate_summary(result: &str) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Format a code_search result summary.
|
||||||
|
pub fn format_code_search_summary(result: &str) -> String {
|
||||||
|
// Result format: "✅ Code search completed\n{json}"
|
||||||
|
// JSON contains: {"searches": [...], "total_matches": N, "total_files_searched": M}
|
||||||
|
if result.contains("❌") {
|
||||||
|
"❌ failed".to_string()
|
||||||
|
} else if let Some(json_start) = result.find('{') {
|
||||||
|
// Try to parse the JSON to extract summary stats
|
||||||
|
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&result[json_start..]) {
|
||||||
|
let matches = parsed.get("total_matches").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
|
let files = parsed.get("total_files_searched").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
|
return format!("🔍 {} matches in {} files", matches, files);
|
||||||
|
}
|
||||||
|
"🔍 search complete".to_string()
|
||||||
|
} else {
|
||||||
|
"🔍 search complete".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Tool Call Deduplication
|
// Tool Call Deduplication
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ mod take_screenshot_tests {
|
|||||||
fn test_take_screenshot_requires_window_id() {
|
fn test_take_screenshot_requires_window_id() {
|
||||||
// Create a tool call without window_id
|
// Create a tool call without window_id
|
||||||
let tool_call = ToolCall {
|
let tool_call = ToolCall {
|
||||||
tool: "take_screenshot".to_string(),
|
tool: "screenshot".to_string(),
|
||||||
args: json!({
|
args: json!({
|
||||||
"path": "test.png"
|
"path": "test.png"
|
||||||
}),
|
}),
|
||||||
@@ -23,7 +23,7 @@ mod take_screenshot_tests {
|
|||||||
fn test_take_screenshot_with_window_id() {
|
fn test_take_screenshot_with_window_id() {
|
||||||
// Create a tool call with window_id
|
// Create a tool call with window_id
|
||||||
let tool_call = ToolCall {
|
let tool_call = ToolCall {
|
||||||
tool: "take_screenshot".to_string(),
|
tool: "screenshot".to_string(),
|
||||||
args: json!({
|
args: json!({
|
||||||
"path": "test.png",
|
"path": "test.png",
|
||||||
"window_id": "Safari"
|
"window_id": "Safari"
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ fn create_core_tools(exclude_research: bool) -> Vec<Tool> {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Tool {
|
Tool {
|
||||||
name: "take_screenshot".to_string(),
|
name: "screenshot".to_string(),
|
||||||
description: "Capture a screenshot of a specific application window. You MUST specify the window_id parameter with the application name (e.g., 'Safari', 'Terminal', 'Google Chrome'). The tool will automatically use the native screencapture command with the application's window ID for a clean capture. Use list_windows first to identify available windows.".to_string(),
|
description: "Capture a screenshot of a specific application window. You MUST specify the window_id parameter with the application name (e.g., 'Safari', 'Terminal', 'Google Chrome'). The tool will automatically use the native screencapture command with the application's window ID for a clean capture. Use list_windows first to identify available windows.".to_string(),
|
||||||
input_schema: json!({
|
input_schema: json!({
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -217,7 +217,7 @@ fn create_core_tools(exclude_research: bool) -> Vec<Tool> {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Tool {
|
Tool {
|
||||||
name: "code_coverage".to_string(),
|
name: "coverage".to_string(),
|
||||||
description: "Generate a code coverage report for the entire workspace using cargo llvm-cov. This runs all tests with coverage instrumentation and returns a summary of coverage statistics. Requires llvm-tools-preview and cargo-llvm-cov to be installed (they will be auto-installed if missing).".to_string(),
|
description: "Generate a code coverage report for the entire workspace using cargo llvm-cov. This runs all tests with coverage instrumentation and returns a summary of coverage statistics. Requires llvm-tools-preview and cargo-llvm-cov to be installed (they will be auto-installed if missing).".to_string(),
|
||||||
input_schema: json!({
|
input_schema: json!({
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -508,8 +508,8 @@ mod tests {
|
|||||||
fn test_core_tools_count() {
|
fn test_core_tools_count() {
|
||||||
let tools = create_core_tools(false);
|
let tools = create_core_tools(false);
|
||||||
// Should have the core tools: shell, background_process, read_file, read_image,
|
// Should have the core tools: shell, background_process, read_file, read_image,
|
||||||
// write_file, str_replace, take_screenshot,
|
// write_file, str_replace, screenshot,
|
||||||
// todo_read, todo_write, code_coverage, code_search, research, remember
|
// todo_read, todo_write, coverage, code_search, research, remember
|
||||||
// (13 total - memory is auto-loaded, only remember tool needed)
|
// (13 total - memory is auto-loaded, only remember tool needed)
|
||||||
assert_eq!(tools.len(), 14);
|
assert_eq!(tools.len(), 14);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ pub async fn dispatch_tool<W: UiWriter>(
|
|||||||
"todo_write" => todo::execute_todo_write(tool_call, ctx).await,
|
"todo_write" => todo::execute_todo_write(tool_call, ctx).await,
|
||||||
|
|
||||||
// Miscellaneous tools
|
// Miscellaneous tools
|
||||||
"take_screenshot" => misc::execute_take_screenshot(tool_call, ctx).await,
|
"screenshot" => misc::execute_take_screenshot(tool_call, ctx).await,
|
||||||
"code_coverage" => misc::execute_code_coverage(tool_call, ctx).await,
|
"coverage" => misc::execute_code_coverage(tool_call, ctx).await,
|
||||||
"code_search" => misc::execute_code_search(tool_call, ctx).await,
|
"code_search" => misc::execute_code_search(tool_call, ctx).await,
|
||||||
|
|
||||||
// Research tool
|
// Research tool
|
||||||
|
|||||||
@@ -393,14 +393,14 @@ mod screenshot_tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_screenshot_tool_call_structure() {
|
fn test_screenshot_tool_call_structure() {
|
||||||
let tool_call = make_tool_call(
|
let tool_call = make_tool_call(
|
||||||
"take_screenshot",
|
"screenshot",
|
||||||
json!({
|
json!({
|
||||||
"path": "screenshot.png",
|
"path": "screenshot.png",
|
||||||
"window_id": "Safari"
|
"window_id": "Safari"
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(tool_call.tool, "take_screenshot");
|
assert_eq!(tool_call.tool, "screenshot");
|
||||||
assert_eq!(tool_call.args.get("path").unwrap().as_str(), Some("screenshot.png"));
|
assert_eq!(tool_call.args.get("path").unwrap().as_str(), Some("screenshot.png"));
|
||||||
assert_eq!(tool_call.args.get("window_id").unwrap().as_str(), Some("Safari"));
|
assert_eq!(tool_call.args.get("window_id").unwrap().as_str(), Some("Safari"));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user