refactor(g3-core): extract tool output formatting to streaming.rs

Centralize tool output formatting logic that was duplicated/scattered in
stream_completion_with_tools(). This eliminates code-path aliasing where
tool type checks were done in multiple places.

Changes:
- Add ToolOutputFormat enum (SelfHandled, Compact, Regular)
- Add format_tool_result_summary() for centralized formatting decisions
- Add is_compact_tool() and is_self_handled_tool() helper functions
- Move parse_diff_stats() from lib.rs to streaming.rs
- Simplify tool execution display logic in lib.rs using new helpers

Net effect: -86 lines in lib.rs, +112 lines in streaming.rs
The streaming.rs additions are reusable, well-named functions.

All 585+ workspace tests pass.

Agent: fowler
This commit is contained in:
Dhanji R. Prasanna
2026-01-20 15:45:35 +05:30
parent 9abb3735d2
commit 168cfff2ed
3 changed files with 137 additions and 88 deletions

View File

@@ -53,6 +53,118 @@ impl StreamingState {
}
}
/// Result of formatting a tool's output for display
pub enum ToolOutputFormat {
/// Tool handles its own output (e.g., TODO tools)
SelfHandled,
/// Compact one-line summary for file operations
Compact(String),
/// Regular multi-line output (already displayed via ui_writer)
Regular,
}
/// Format a tool result for compact display.
/// Returns the appropriate format based on tool type and success.
pub fn format_tool_result_summary(
tool_name: &str,
tool_result: &str,
tool_success: bool,
) -> ToolOutputFormat {
let is_todo_tool = tool_name == "todo_read" || tool_name == "todo_write";
let is_compact_tool = matches!(
tool_name,
"read_file"
| "write_file"
| "str_replace"
| "remember"
| "screenshot"
| "coverage"
| "rehydrate"
| "code_search"
);
if is_todo_tool {
ToolOutputFormat::SelfHandled
} else if is_compact_tool {
if !tool_success {
ToolOutputFormat::Compact(truncate_for_display(tool_result, 60))
} else {
let summary = format_compact_tool_summary(tool_name, tool_result);
ToolOutputFormat::Compact(summary)
}
} else {
ToolOutputFormat::Regular
}
}
/// Check if a tool is a "compact" tool that shows one-line summaries
pub fn is_compact_tool(tool_name: &str) -> bool {
matches!(
tool_name,
"read_file"
| "write_file"
| "str_replace"
| "remember"
| "screenshot"
| "coverage"
| "rehydrate"
| "code_search"
)
}
/// Check if a tool handles its own output display
pub fn is_self_handled_tool(tool_name: &str) -> bool {
tool_name == "todo_read" || tool_name == "todo_write"
}
/// Format a compact summary for a successful compact tool
fn format_compact_tool_summary(tool_name: &str, tool_result: &str) -> String {
let output_lines: Vec<&str> = tool_result.lines().collect();
let output_len = output_lines.len();
match tool_name {
"read_file" => format_read_file_summary(output_len, tool_result.len()),
"write_file" => format_write_file_result(tool_result),
"str_replace" => {
let (ins, del) = parse_diff_stats(tool_result);
format_str_replace_summary(ins, del)
}
"remember" => format_remember_summary(tool_result),
"screenshot" => format_screenshot_summary(tool_result),
"coverage" => format_coverage_summary(tool_result),
"rehydrate" => format_rehydrate_summary(tool_result),
"code_search" => format_code_search_summary(tool_result),
_ => "✅ completed".to_string(),
}
}
/// Parse diff stats from str_replace result
fn parse_diff_stats(result: &str) -> (i32, i32) {
// Format: "✅ +N insertions | -M deletions"
let mut insertions = 0i32;
let mut deletions = 0i32;
// Look for "+N insertions" pattern
if let Some(pos) = result.find("+") {
let after_plus = &result[pos + 1..];
insertions = after_plus
.split_whitespace()
.next()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
}
// Look for "-M deletions" pattern
if let Some(pos) = result.find("-") {
let after_minus = &result[pos + 1..];
deletions = after_minus
.split_whitespace()
.next()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
}
(insertions, deletions)
}
impl Default for StreamingState {
fn default() -> Self {
Self::new()