add markdown format only to final_output and fix todo duplication
This commit is contained in:
@@ -105,4 +105,9 @@ impl UiWriter for MachineUiWriter {
|
||||
// Default to first option (index 0) for automation
|
||||
0
|
||||
}
|
||||
|
||||
fn print_final_output(&self, summary: &str) {
|
||||
println!("FINAL_OUTPUT:");
|
||||
println!("{}", summary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use g3_core::ui_writer::UiWriter;
|
||||
use std::io::{self, Write};
|
||||
use termimad::MadSkin;
|
||||
|
||||
/// Console implementation of UiWriter that prints to stdout
|
||||
pub struct ConsoleUiWriter {
|
||||
@@ -104,6 +105,9 @@ impl UiWriter for ConsoleUiWriter {
|
||||
|
||||
fn print_tool_output_header(&self) {
|
||||
println!();
|
||||
// Reset output_line_printed at the start of a new tool output
|
||||
// This ensures the header isn't cleared by update_tool_output_line
|
||||
*self.output_line_printed.lock().unwrap() = false;
|
||||
// Now print the tool header with the most important arg in bold green
|
||||
if let Some(tool_name) = self.current_tool_name.lock().unwrap().as_ref() {
|
||||
let args = self.current_tool_args.lock().unwrap();
|
||||
@@ -306,4 +310,44 @@ impl UiWriter for ConsoleUiWriter {
|
||||
let _ = io::stdout().flush();
|
||||
}
|
||||
}
|
||||
|
||||
fn print_final_output(&self, summary: &str) {
|
||||
// Show spinner while "formatting"
|
||||
let spinner_frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
||||
let message = "summarizing work done...";
|
||||
|
||||
// Brief spinner animation (about 0.5 seconds)
|
||||
for i in 0..5 {
|
||||
let frame = spinner_frames[i % spinner_frames.len()];
|
||||
print!("\r\x1b[36m{} {}\x1b[0m", frame, message);
|
||||
let _ = io::stdout().flush();
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
|
||||
// Clear the spinner line
|
||||
print!("\r\x1b[2K");
|
||||
let _ = io::stdout().flush();
|
||||
|
||||
// Create a styled markdown skin
|
||||
let mut skin = MadSkin::default();
|
||||
// Customize colors for better terminal appearance
|
||||
skin.bold.set_fg(termimad::crossterm::style::Color::Green);
|
||||
skin.italic.set_fg(termimad::crossterm::style::Color::Cyan);
|
||||
skin.headers[0].set_fg(termimad::crossterm::style::Color::Magenta);
|
||||
skin.headers[1].set_fg(termimad::crossterm::style::Color::Magenta);
|
||||
skin.code_block.set_fg(termimad::crossterm::style::Color::Yellow);
|
||||
skin.inline_code.set_fg(termimad::crossterm::style::Color::Yellow);
|
||||
|
||||
// Print a header separator
|
||||
println!("\x1b[1;35m━━━ Summary ━━━\x1b[0m");
|
||||
println!();
|
||||
|
||||
// Render the markdown
|
||||
let rendered = skin.term_text(summary);
|
||||
print!("{}", rendered);
|
||||
|
||||
// Print a footer separator
|
||||
println!();
|
||||
println!("\x1b[1;35m━━━━━━━━━━━━━━━\x1b[0m");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3529,11 +3529,9 @@ impl<W: UiWriter> Agent<W> {
|
||||
|
||||
// Display tool execution result with proper indentation
|
||||
if tool_call.tool == "final_output" {
|
||||
// For final_output, display the summary without truncation
|
||||
for line in tool_result.lines() {
|
||||
self.ui_writer.update_tool_output_line(line);
|
||||
}
|
||||
self.ui_writer.println("");
|
||||
// For final_output, use the dedicated method that renders markdown
|
||||
// with a spinner animation
|
||||
self.ui_writer.print_final_output(&tool_result);
|
||||
} else {
|
||||
let output_lines: Vec<&str> = tool_result.lines().collect();
|
||||
|
||||
@@ -3563,44 +3561,32 @@ impl<W: UiWriter> Agent<W> {
|
||||
const MAX_LINE_WIDTH: usize = 80;
|
||||
let output_len = output_lines.len();
|
||||
|
||||
// For todo tools, show all lines without truncation
|
||||
// Skip printing for todo tools - they already print their content
|
||||
let is_todo_tool =
|
||||
tool_call.tool == "todo_read" || tool_call.tool == "todo_write";
|
||||
let max_lines_to_show = if is_todo_tool || wants_full {
|
||||
output_len
|
||||
} else {
|
||||
MAX_LINES
|
||||
};
|
||||
|
||||
for (idx, line) in output_lines.iter().enumerate() {
|
||||
if !is_todo_tool && !wants_full && idx >= max_lines_to_show {
|
||||
break;
|
||||
}
|
||||
// Clip line to max width (but not for todo tools)
|
||||
let clipped_line = truncate_line(
|
||||
line,
|
||||
MAX_LINE_WIDTH,
|
||||
!wants_full && !is_todo_tool,
|
||||
);
|
||||
if !is_todo_tool {
|
||||
let max_lines_to_show = if wants_full { output_len } else { MAX_LINES };
|
||||
|
||||
// Use print_tool_output_line for todo tools to get special formatting
|
||||
if is_todo_tool {
|
||||
self.ui_writer.print_tool_output_line(&clipped_line);
|
||||
} else {
|
||||
for (idx, line) in output_lines.iter().enumerate() {
|
||||
if !wants_full && idx >= max_lines_to_show {
|
||||
break;
|
||||
}
|
||||
let clipped_line = truncate_line(line, MAX_LINE_WIDTH, !wants_full);
|
||||
self.ui_writer.update_tool_output_line(&clipped_line);
|
||||
}
|
||||
}
|
||||
|
||||
if !is_todo_tool && !wants_full && output_len > MAX_LINES {
|
||||
self.ui_writer.print_tool_output_summary(output_len);
|
||||
if !wants_full && output_len > MAX_LINES {
|
||||
self.ui_writer.print_tool_output_summary(output_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this was a final_output tool call
|
||||
if tool_call.tool == "final_output" {
|
||||
// The summary was displayed above when we printed the tool result
|
||||
// Add it to full_response so it's included in the TaskResult
|
||||
full_response.push_str(&tool_result);
|
||||
// The summary was already displayed via print_final_output
|
||||
// Don't add it to full_response to avoid duplicate printing
|
||||
// full_response is intentionally left empty/unchanged
|
||||
self.ui_writer.println("");
|
||||
let _ttft =
|
||||
first_token_time.unwrap_or_else(|| stream_start.elapsed());
|
||||
@@ -3608,13 +3594,13 @@ impl<W: UiWriter> Agent<W> {
|
||||
// Add timing if needed
|
||||
let final_response = if show_timing {
|
||||
format!(
|
||||
"{}\n\n🕝 {} | 💭 {}",
|
||||
full_response,
|
||||
"🕝 {} | 💭 {}",
|
||||
Self::format_duration(stream_start.elapsed()),
|
||||
Self::format_duration(_ttft)
|
||||
)
|
||||
} else {
|
||||
full_response
|
||||
// Return empty string since content was already displayed
|
||||
String::new()
|
||||
};
|
||||
|
||||
return Ok(TaskResult::new(
|
||||
|
||||
@@ -65,6 +65,10 @@ pub trait UiWriter: Send + Sync {
|
||||
/// Prompt the user to choose from a list of options
|
||||
/// Returns the index of the selected option
|
||||
fn prompt_user_choice(&self, message: &str, options: &[&str]) -> usize;
|
||||
|
||||
/// Print the final output summary with markdown formatting
|
||||
/// Shows a spinner while formatting, then renders the markdown
|
||||
fn print_final_output(&self, summary: &str);
|
||||
}
|
||||
|
||||
/// A no-op implementation for when UI output is not needed
|
||||
@@ -97,4 +101,7 @@ impl UiWriter for NullUiWriter {
|
||||
fn prompt_user_choice(&self, _message: &str, _options: &[&str]) -> usize {
|
||||
0
|
||||
}
|
||||
fn print_final_output(&self, _summary: &str) {
|
||||
// No-op for null writer
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,9 @@ impl UiWriter for MockUiWriter {
|
||||
.push(format!("CHOICE: {} Options: {:?}", message, options));
|
||||
self.choice_responses.lock().unwrap().pop().unwrap_or(0)
|
||||
}
|
||||
fn print_final_output(&self, summary: &str) {
|
||||
self.output.lock().unwrap().push(format!("FINAL: {}", summary));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user