fix tool output
This commit is contained in:
@@ -59,18 +59,18 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
// Collect arguments instead of printing immediately
|
// Collect arguments instead of printing immediately
|
||||||
// Filter out any keys that look like they might be agent message content
|
// Filter out any keys that look like they might be agent message content
|
||||||
// (e.g., keys that are suspiciously long or contain message-like content)
|
// (e.g., keys that are suspiciously long or contain message-like content)
|
||||||
let is_valid_arg_key = key.len() < 50 &&
|
let is_valid_arg_key = key.len() < 50
|
||||||
!key.contains('\n') &&
|
&& !key.contains('\n')
|
||||||
!key.contains("I'll") &&
|
&& !key.contains("I'll")
|
||||||
!key.contains("Let me") &&
|
&& !key.contains("Let me")
|
||||||
!key.contains("Here's") &&
|
&& !key.contains("Here's")
|
||||||
!key.contains("I can");
|
&& !key.contains("I can");
|
||||||
|
|
||||||
if is_valid_arg_key {
|
if is_valid_arg_key {
|
||||||
self.current_tool_args
|
self.current_tool_args
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.push((key.to_string(), value.to_string()));
|
.push((key.to_string(), value.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,14 +90,14 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
if let Some((_, value)) = important_arg {
|
if let Some((_, value)) = important_arg {
|
||||||
// For multi-line values, only show the first line
|
// For multi-line values, only show the first line
|
||||||
let first_line = value.lines().next().unwrap_or("");
|
let first_line = value.lines().next().unwrap_or("");
|
||||||
|
|
||||||
// Truncate long values for display
|
// Truncate long values for display
|
||||||
let display_value = if first_line.len() > 80 {
|
let display_value = if first_line.len() > 80 {
|
||||||
format!("{}...", &first_line[..77])
|
format!("{}...", &first_line[..77])
|
||||||
} else {
|
} else {
|
||||||
first_line.to_string()
|
first_line.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Print with bold green formatting using ANSI escape codes
|
// Print with bold green formatting using ANSI escape codes
|
||||||
println!("┌─\x1b[1;32m {} | {}\x1b[0m", tool_name, display_value);
|
println!("┌─\x1b[1;32m {} | {}\x1b[0m", tool_name, display_value);
|
||||||
} else {
|
} else {
|
||||||
@@ -110,31 +110,31 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
fn update_tool_output_line(&self, line: &str) {
|
fn update_tool_output_line(&self, line: &str) {
|
||||||
let mut current_line = self.current_output_line.lock().unwrap();
|
let mut current_line = self.current_output_line.lock().unwrap();
|
||||||
let mut line_printed = self.output_line_printed.lock().unwrap();
|
let mut line_printed = self.output_line_printed.lock().unwrap();
|
||||||
|
|
||||||
// If we've already printed a line, clear it first
|
// If we've already printed a line, clear it first
|
||||||
if *line_printed {
|
if *line_printed {
|
||||||
// Move cursor up one line and clear it
|
// Move cursor up one line and clear it
|
||||||
print!("\x1b[1A\x1b[2K");
|
print!("\x1b[1A\x1b[2K");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print the new line
|
// Print the new line
|
||||||
println!("│ \x1b[2m{}\x1b[0m", line);
|
println!("│ \x1b[2m{}\x1b[0m", line);
|
||||||
let _ = io::stdout().flush();
|
let _ = io::stdout().flush();
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
*current_line = Some(line.to_string());
|
*current_line = Some(line.to_string());
|
||||||
*line_printed = true;
|
*line_printed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_tool_output_line(&self, line: &str) {
|
fn print_tool_output_line(&self, line: &str) {
|
||||||
println!("│ \x1b[2m{}\x1b[0m", line);
|
println!("│ \x1b[2m{}\x1b[0m", line);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_tool_output_summary(&self, hidden_count: usize) {
|
fn print_tool_output_summary(&self, count: usize) {
|
||||||
println!(
|
println!(
|
||||||
"│ \x1b[2m... ({} more line{})\x1b[0m",
|
"│ \x1b[2m({} line{})\x1b[0m",
|
||||||
hidden_count,
|
count,
|
||||||
if hidden_count == 1 { "" } else { "s" }
|
if count == 1 { "" } else { "s" }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,20 +233,20 @@ impl UiWriter for RetroTuiWriter {
|
|||||||
fn print_tool_arg(&self, key: &str, value: &str) {
|
fn print_tool_arg(&self, key: &str, value: &str) {
|
||||||
// Filter out any keys that look like they might be agent message content
|
// Filter out any keys that look like they might be agent message content
|
||||||
// (e.g., keys that are suspiciously long or contain message-like content)
|
// (e.g., keys that are suspiciously long or contain message-like content)
|
||||||
let is_valid_arg_key = key.len() < 50 &&
|
let is_valid_arg_key = key.len() < 50
|
||||||
!key.contains('\n') &&
|
&& !key.contains('\n')
|
||||||
!key.contains("I'll") &&
|
&& !key.contains("I'll")
|
||||||
!key.contains("Let me") &&
|
&& !key.contains("Let me")
|
||||||
!key.contains("Here's") &&
|
&& !key.contains("Here's")
|
||||||
!key.contains("I can");
|
&& !key.contains("I can");
|
||||||
|
|
||||||
if is_valid_arg_key {
|
if is_valid_arg_key {
|
||||||
self.current_tool_output
|
self.current_tool_output
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.push(format!("{}: {}", key, value));
|
.push(format!("{}: {}", key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build caption from first argument (usually the most important one)
|
// Build caption from first argument (usually the most important one)
|
||||||
let mut caption = self.current_tool_caption.lock().unwrap();
|
let mut caption = self.current_tool_caption.lock().unwrap();
|
||||||
if caption.is_empty() && (key == "file_path" || key == "command" || key == "path") {
|
if caption.is_empty() && (key == "file_path" || key == "command" || key == "path") {
|
||||||
@@ -279,7 +279,10 @@ impl UiWriter for RetroTuiWriter {
|
|||||||
|
|
||||||
fn update_tool_output_line(&self, line: &str) {
|
fn update_tool_output_line(&self, line: &str) {
|
||||||
// For retro mode, we'll just add to the output buffer
|
// For retro mode, we'll just add to the output buffer
|
||||||
self.current_tool_output.lock().unwrap().push(line.to_string());
|
self.current_tool_output
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.push(line.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_tool_output_line(&self, line: &str) {
|
fn print_tool_output_line(&self, line: &str) {
|
||||||
|
|||||||
@@ -706,7 +706,7 @@ impl<W: UiWriter> Agent<W> {
|
|||||||
let provider = self.providers.get(None)?;
|
let provider = self.providers.get(None)?;
|
||||||
let system_prompt = if provider.has_native_tool_calling() {
|
let system_prompt = if provider.has_native_tool_calling() {
|
||||||
// For native tool calling providers, use a more explicit system prompt
|
// For native tool calling providers, use a more explicit system prompt
|
||||||
"You are G3, an AI programming agent. Your goal is to analyze, write and modify code to achieve given goals.
|
"You are G3, an AI programming agent of the same skill level as a seasoned engineer at a major technology company. You analyze given tasks and write code to achieve goals.
|
||||||
|
|
||||||
You have access to tools. When you need to accomplish a task, you MUST use the appropriate tool. Do not just describe what you would do - actually use the tools.
|
You have access to tools. When you need to accomplish a task, you MUST use the appropriate tool. Do not just describe what you would do - actually use the tools.
|
||||||
|
|
||||||
@@ -1278,7 +1278,7 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
|
|
||||||
// Add a small delay between iterations to prevent "model busy" errors
|
// Add a small delay between iterations to prevent "model busy" errors
|
||||||
if iteration_count > 1 {
|
if iteration_count > 1 {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let provider = self.providers.get(None)?;
|
let provider = self.providers.get(None)?;
|
||||||
@@ -1392,14 +1392,6 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log raw chunk data for debugging
|
|
||||||
if chunks_received <= 5 || chunk.finished {
|
|
||||||
debug!(
|
|
||||||
"Chunk #{}: content={:?}, finished={}, tool_calls={:?}",
|
|
||||||
chunks_received, chunk.content, chunk.finished, chunk.tool_calls
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process chunk with the new parser
|
// Process chunk with the new parser
|
||||||
let completed_tools = parser.process_chunk(&chunk);
|
let completed_tools = parser.process_chunk(&chunk);
|
||||||
|
|
||||||
@@ -1529,21 +1521,16 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
|
|
||||||
const MAX_LINES: usize = 5;
|
const MAX_LINES: usize = 5;
|
||||||
const MAX_LINE_WIDTH: usize = 80;
|
const MAX_LINE_WIDTH: usize = 80;
|
||||||
|
let output_len = output_lines.len();
|
||||||
|
|
||||||
if output_lines.len() <= MAX_LINES {
|
for line in output_lines {
|
||||||
for line in output_lines {
|
// Clip line to max width
|
||||||
// Clip line to max width
|
let clipped_line = truncate_line(line, MAX_LINE_WIDTH);
|
||||||
let clipped_line = truncate_line(line, MAX_LINE_WIDTH);
|
self.ui_writer.update_tool_output_line(&clipped_line);
|
||||||
self.ui_writer.print_tool_output_line(&clipped_line);
|
}
|
||||||
}
|
|
||||||
} else {
|
if output_len > MAX_LINES {
|
||||||
for line in output_lines.iter().take(MAX_LINES) {
|
self.ui_writer.print_tool_output_summary(output_len);
|
||||||
// Clip line to max width
|
|
||||||
let clipped_line = truncate_line(line, MAX_LINE_WIDTH);
|
|
||||||
self.ui_writer.print_tool_output_line(&clipped_line);
|
|
||||||
}
|
|
||||||
let hidden_count = output_lines.len() - MAX_LINES;
|
|
||||||
self.ui_writer.print_tool_output_summary(hidden_count);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1552,14 +1539,7 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
full_response.push_str(final_display_content);
|
full_response.push_str(final_display_content);
|
||||||
if let Some(summary) = tool_call.args.get("summary") {
|
if let Some(summary) = tool_call.args.get("summary") {
|
||||||
if let Some(summary_str) = summary.as_str() {
|
if let Some(summary_str) = summary.as_str() {
|
||||||
// Don't add the "=> " prefix in autonomous mode
|
full_response.push_str(&format!("\n\n{}", summary_str));
|
||||||
// as it interferes with coach feedback parsing
|
|
||||||
if !self.is_autonomous {
|
|
||||||
full_response
|
|
||||||
.push_str(&format!("\n\n=> {}", summary_str));
|
|
||||||
} else {
|
|
||||||
full_response.push_str(&format!("\n\n{}", summary_str));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.ui_writer.println("");
|
self.ui_writer.println("");
|
||||||
@@ -1569,7 +1549,7 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
// Add timing if needed
|
// Add timing if needed
|
||||||
let final_response = if show_timing {
|
let final_response = if show_timing {
|
||||||
format!(
|
format!(
|
||||||
"{}\n\n⏱️ {} | 💭 {}",
|
"{}\n\n🕝 {} | 💭 {}",
|
||||||
full_response,
|
full_response,
|
||||||
Self::format_duration(total_execution_time),
|
Self::format_duration(total_execution_time),
|
||||||
Self::format_duration(_ttft)
|
Self::format_duration(_ttft)
|
||||||
@@ -1969,23 +1949,26 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
let escaped_command = shell_escape_command(command_str);
|
let escaped_command = shell_escape_command(command_str);
|
||||||
|
|
||||||
let executor = CodeExecutor::new();
|
let executor = CodeExecutor::new();
|
||||||
|
|
||||||
// Create a receiver for streaming output
|
// Create a receiver for streaming output
|
||||||
struct ToolOutputReceiver<'a, W: UiWriter> {
|
struct ToolOutputReceiver<'a, W: UiWriter> {
|
||||||
ui_writer: &'a W,
|
ui_writer: &'a W,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, W: UiWriter> g3_execution::OutputReceiver for ToolOutputReceiver<'a, W> {
|
impl<'a, W: UiWriter> g3_execution::OutputReceiver for ToolOutputReceiver<'a, W> {
|
||||||
fn on_output_line(&self, line: &str) {
|
fn on_output_line(&self, line: &str) {
|
||||||
self.ui_writer.update_tool_output_line(line);
|
self.ui_writer.update_tool_output_line(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let receiver = ToolOutputReceiver {
|
let receiver = ToolOutputReceiver {
|
||||||
ui_writer: &self.ui_writer,
|
ui_writer: &self.ui_writer,
|
||||||
};
|
};
|
||||||
|
|
||||||
match executor.execute_bash_streaming(&escaped_command, &receiver).await {
|
match executor
|
||||||
|
.execute_bash_streaming(&escaped_command, &receiver)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
if result.success {
|
if result.success {
|
||||||
Ok(if result.stdout.is_empty() {
|
Ok(if result.stdout.is_empty() {
|
||||||
@@ -2292,120 +2275,6 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"edit_file" => {
|
|
||||||
debug!("Processing edit_file tool call");
|
|
||||||
|
|
||||||
// Extract arguments with better error handling
|
|
||||||
let args_obj = match tool_call.args.as_object() {
|
|
||||||
Some(obj) => obj,
|
|
||||||
None => return Ok("❌ Invalid arguments: expected object".to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let file_path = match args_obj.get("file_path").and_then(|v| v.as_str()) {
|
|
||||||
Some(path) => path,
|
|
||||||
None => return Ok("❌ Missing or invalid file_path argument".to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let content = match args_obj.get("content").and_then(|v| v.as_str()) {
|
|
||||||
Some(c) => c,
|
|
||||||
None => return Ok("❌ Missing or invalid content argument".to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let start_line = match args_obj.get("start_of_range").and_then(|v| v.as_i64()) {
|
|
||||||
Some(n) if n >= 1 => n as usize,
|
|
||||||
Some(_) => {
|
|
||||||
return Ok(
|
|
||||||
"❌ start_of_range must be >= 1 (lines are 1-indexed)".to_string()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
None => return Ok("❌ Missing or invalid start_of_range argument".to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let end_line = match args_obj.get("end_of_range").and_then(|v| v.as_i64()) {
|
|
||||||
Some(n) if n >= start_line as i64 => n as usize,
|
|
||||||
Some(_) => return Ok("❌ end_of_range must be >= start_of_range".to_string()),
|
|
||||||
None => return Ok("❌ Missing or invalid end_of_range argument".to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"edit_file: path={}, start={}, end={}",
|
|
||||||
file_path, start_line, end_line
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read the existing file
|
|
||||||
let existing_content = match std::fs::read_to_string(file_path) {
|
|
||||||
Ok(content) => content,
|
|
||||||
Err(e) => return Ok(format!("❌ Failed to read file '{}': {}", file_path, e)),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Split into lines, preserving empty lines
|
|
||||||
let mut lines: Vec<String> =
|
|
||||||
existing_content.lines().map(|s| s.to_string()).collect();
|
|
||||||
let original_line_count = lines.len();
|
|
||||||
|
|
||||||
// Validate the range
|
|
||||||
if start_line > lines.len() {
|
|
||||||
// Allow appending at the end if start_line == lines.len() + 1
|
|
||||||
if start_line == lines.len() + 1 && end_line == start_line {
|
|
||||||
// This is an append operation
|
|
||||||
lines.extend(content.lines().map(|s| s.to_string()));
|
|
||||||
|
|
||||||
// Write back to file
|
|
||||||
let new_content = lines.join("\n");
|
|
||||||
match std::fs::write(file_path, &new_content) {
|
|
||||||
Ok(()) => {
|
|
||||||
let lines_added = content.lines().count();
|
|
||||||
return Ok(format!(
|
|
||||||
"✅ Successfully appended {} lines to '{}'. File now has {} lines (was {} lines)",
|
|
||||||
lines_added, file_path, lines.len(), original_line_count
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
return Ok(format!(
|
|
||||||
"❌ Failed to write to file '{}': {}",
|
|
||||||
file_path, e
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Ok(format!(
|
|
||||||
"❌ start_of_range {} exceeds file length ({} lines)",
|
|
||||||
start_line,
|
|
||||||
lines.len()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split the new content into lines
|
|
||||||
let new_lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
|
|
||||||
|
|
||||||
// Perform the replacement
|
|
||||||
// Convert from 1-indexed (inclusive) to 0-indexed range for splice
|
|
||||||
// splice takes start..end where end is EXCLUSIVE, so for inclusive end_line, we need end_line + 1
|
|
||||||
let start_idx = start_line - 1;
|
|
||||||
let end_idx = (end_line + 1).min(lines.len() + 1); // +1 because splice end is exclusive
|
|
||||||
let actual_end_line = end_line.min(lines.len()); // For reporting
|
|
||||||
let lines_being_replaced = actual_end_line - start_line + 1;
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"Replacing lines {}..={} (0-indexed splice: {}..{})",
|
|
||||||
start_line, end_line, start_idx, end_idx
|
|
||||||
);
|
|
||||||
lines.splice(start_idx..end_idx, new_lines.clone());
|
|
||||||
|
|
||||||
// Write the result back to the file
|
|
||||||
let new_content = lines.join("\n");
|
|
||||||
match std::fs::write(file_path, &new_content) {
|
|
||||||
Ok(()) => {
|
|
||||||
Ok(format!(
|
|
||||||
"✅ Successfully edited '{}': replaced {} lines ({}-{}) with {} lines. File now has {} lines (was {} lines)",
|
|
||||||
file_path, lines_being_replaced, start_line, actual_end_line,
|
|
||||||
new_lines.len(), lines.len(), original_line_count
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Err(e) => Ok(format!("❌ Failed to write to file '{}': {}", file_path, e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"str_replace" => {
|
"str_replace" => {
|
||||||
debug!("Processing str_replace tool call");
|
debug!("Processing str_replace tool call");
|
||||||
|
|
||||||
@@ -2464,10 +2333,10 @@ The tool will execute immediately and you'll receive the result (success or erro
|
|||||||
if let Some(summary_str) = summary.as_str() {
|
if let Some(summary_str) = summary.as_str() {
|
||||||
Ok(format!("{}", summary_str))
|
Ok(format!("{}", summary_str))
|
||||||
} else {
|
} else {
|
||||||
Ok("✅ Task completed".to_string())
|
Ok("✅ Turn completed".to_string())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok("✅ Task completed".to_string())
|
Ok("✅ Turn completed".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
Reference in New Issue
Block a user