tool calling boxes

This commit is contained in:
Dhanji Prasanna
2025-10-02 15:50:04 +10:00
parent 4e457960ed
commit 56e13ced64
2 changed files with 110 additions and 9 deletions

View File

@@ -31,6 +31,7 @@ const TERMINAL_DARK_AMBER: Color = Color::Rgb(204, 119, 34); // Dark amber for P
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum TuiMessage { pub enum TuiMessage {
AgentOutput(String), AgentOutput(String),
ToolOutput { name: String, content: String },
SystemStatus(String), SystemStatus(String),
ContextUpdate { ContextUpdate {
used: u32, used: u32,
@@ -91,6 +92,64 @@ impl TerminalState {
} }
} }
/// Format tool call output with a box
fn format_tool_output(&mut self, tool_name: &str, content: &str) {
// Calculate box width (use a reasonable width, accounting for terminal size)
let box_width = 80;
let border_char = "";
let corner_tl = "";
let corner_tr = "";
let corner_bl = "";
let corner_br = "";
let vertical = "";
// Add top border
self.output_history.push(format!(
"{}{}{}",
corner_tl,
border_char.repeat(box_width - 2),
corner_tr
));
// Add header with tool name (will be styled with green background in draw)
let header_text = format!(" {} ", tool_name.to_uppercase());
let padding = box_width - 2 - header_text.len();
self.output_history.push(format!(
"{}[TOOL_HEADER]{}{}{}",
vertical,
header_text,
" ".repeat(padding),
vertical
));
// Add separator between header and content
self.output_history.push(format!(
"{}{}{}",
"",
border_char.repeat(box_width - 2),
""
));
// Add content lines
for line in content.lines() {
// Wrap long lines if needed
let max_content_width = box_width - 4; // Account for borders and padding
if line.len() <= max_content_width {
self.output_history.push(format!("{} {:<width$} {}", vertical, line, vertical, width = max_content_width));
} else {
// Simple word wrapping for long lines
for chunk in line.chars().collect::<Vec<_>>().chunks(max_content_width) {
let chunk_str: String = chunk.iter().collect();
self.output_history.push(format!("{} {:<width$} {}", vertical, chunk_str, vertical, width = max_content_width));
}
}
}
// Add bottom border
self.output_history.push(format!("{}{}{}", corner_bl, border_char.repeat(box_width - 2), corner_br));
self.output_history.push(String::new()); // Empty line after box
}
/// Add text to output history /// Add text to output history
fn add_output(&mut self, text: &str) { fn add_output(&mut self, text: &str) {
// Split text by newlines and add each line // Split text by newlines and add each line
@@ -142,6 +201,9 @@ impl RetroTui {
TuiMessage::AgentOutput(text) => { TuiMessage::AgentOutput(text) => {
state.add_output(&text); state.add_output(&text);
} }
TuiMessage::ToolOutput { name, content } => {
state.format_tool_output(&name, &content);
}
TuiMessage::SystemStatus(status) => { TuiMessage::SystemStatus(status) => {
state.status_line = status; state.status_line = status;
} }
@@ -293,6 +355,24 @@ impl RetroTui {
.skip(scroll) .skip(scroll)
.take(visible_height) .take(visible_height)
.map(|line| { .map(|line| {
// Check if this is a tool header line
if line.contains("[TOOL_HEADER]") {
// Extract the actual header text
let cleaned = line.replace("[TOOL_HEADER]", "");
// Style with green background and black text
return Line::from(Span::styled(
format!(" {}", cleaned),
Style::default()
.bg(TERMINAL_GREEN)
.fg(Color::Black)
.add_modifier(Modifier::BOLD),
));
}
// Check if this is a box border line
if line.starts_with("") || line.starts_with("") || line.starts_with("") || line.starts_with("") {
return Line::from(Span::styled(format!(" {}", line), Style::default().fg(TERMINAL_DIM_GREEN)));
}
// Apply different colors based on content // Apply different colors based on content
let style = if line.starts_with("ERROR:") { let style = if line.starts_with("ERROR:") {
Style::default() Style::default()
@@ -445,6 +525,11 @@ impl RetroTui {
let _ = self.tx.send(TuiMessage::AgentOutput(text.to_string())); let _ = self.tx.send(TuiMessage::AgentOutput(text.to_string()));
} }
/// Send tool output to the terminal
pub fn tool_output(&self, name: &str, content: &str) {
let _ = self.tx.send(TuiMessage::ToolOutput { name: name.to_string(), content: content.to_string() });
}
/// Update system status /// Update system status
pub fn status(&self, status: &str) { pub fn status(&self, status: &str) {
let _ = self.tx.send(TuiMessage::SystemStatus(status.to_string())); let _ = self.tx.send(TuiMessage::SystemStatus(status.to_string()));

View File

@@ -1,6 +1,7 @@
use crate::retro_tui::RetroTui; use crate::retro_tui::RetroTui;
use g3_core::ui_writer::UiWriter; use g3_core::ui_writer::UiWriter;
use std::io::{self, Write}; use std::io::{self, Write};
use std::sync::Mutex;
/// Console implementation of UiWriter that prints to stdout /// Console implementation of UiWriter that prints to stdout
pub struct ConsoleUiWriter; pub struct ConsoleUiWriter;
@@ -84,11 +85,13 @@ impl UiWriter for ConsoleUiWriter {
/// RetroTui implementation of UiWriter that sends output to the TUI /// RetroTui implementation of UiWriter that sends output to the TUI
pub struct RetroTuiWriter { pub struct RetroTuiWriter {
tui: RetroTui, tui: RetroTui,
current_tool_name: Mutex<Option<String>>,
current_tool_output: Mutex<Vec<String>>,
} }
impl RetroTuiWriter { impl RetroTuiWriter {
pub fn new(tui: RetroTui) -> Self { pub fn new(tui: RetroTui) -> Self {
Self { tui } Self { tui, current_tool_name: Mutex::new(None), current_tool_output: Mutex::new(Vec::new()) }
} }
} }
@@ -121,32 +124,45 @@ impl UiWriter for RetroTuiWriter {
} }
fn print_tool_header(&self, tool_name: &str) { fn print_tool_header(&self, tool_name: &str) {
self.tui.output(&format!("┌─ {}", tool_name)); // Start collecting tool output
*self.current_tool_name.lock().unwrap() = Some(tool_name.to_string());
self.current_tool_output.lock().unwrap().clear();
self.current_tool_output.lock().unwrap().push(format!("Tool: {}", tool_name));
} }
fn print_tool_arg(&self, key: &str, value: &str) { fn print_tool_arg(&self, key: &str, value: &str) {
self.tui.output(&format!("{}: {}", key, value)); self.current_tool_output.lock().unwrap().push(format!("{}: {}", key, value));
} }
fn print_tool_output_header(&self) { fn print_tool_output_header(&self) {
self.tui.output("├─ output:"); self.current_tool_output.lock().unwrap().push(String::new());
self.current_tool_output.lock().unwrap().push("Output:".to_string());
} }
fn print_tool_output_line(&self, line: &str) { fn print_tool_output_line(&self, line: &str) {
self.tui.output(&format!("{}", line)); self.current_tool_output.lock().unwrap().push(line.to_string());
} }
fn print_tool_output_summary(&self, hidden_count: usize) { fn print_tool_output_summary(&self, hidden_count: usize) {
self.tui.output(&format!( self.current_tool_output.lock().unwrap().push(format!(
"... ({} more line{} hidden)", "... ({} more line{} hidden)",
hidden_count, hidden_count,
if hidden_count == 1 { "" } else { "s" } if hidden_count == 1 { "" } else { "s" }
)); ));
} }
fn print_tool_timing(&self, duration_str: &str) { fn print_tool_timing(&self, duration_str: &str) {
self.tui.output(&format!("└─ ⚡️ {}", duration_str)); self.current_tool_output.lock().unwrap().push(format!("⚡️ {}", duration_str));
self.tui.output("");
// Now send the complete tool output as a box
if let Some(tool_name) = self.current_tool_name.lock().unwrap().as_ref() {
let content = self.current_tool_output.lock().unwrap().join("\n");
self.tui.tool_output(tool_name, &content);
}
// Clear the buffers
*self.current_tool_name.lock().unwrap() = None;
self.current_tool_output.lock().unwrap().clear();
} }
fn print_agent_prompt(&self) { fn print_agent_prompt(&self) {