Truncate tool use. (#9)

Truncate tool use to 10 lines by default.

Reviewed-on: #9
Co-authored-by: Drew Galbraith <drew@tiramisu.one>
Co-committed-by: Drew Galbraith <drew@tiramisu.one>
This commit is contained in:
Drew 2026-03-11 16:47:55 +00:00 committed by Drew
parent 5a49cba1e6
commit b9e9da4354

View file

@ -8,6 +8,9 @@ use similar::{ChangeTag, TextDiff};
use crate::core::types::ToolDisplay; use crate::core::types::ToolDisplay;
/// Maximum number of output lines shown for a tool result before truncation.
const MAX_RESULT_LINES: usize = 10;
/// Format a tool that is currently executing (or awaiting approval). /// Format a tool that is currently executing (or awaiting approval).
pub fn format_executing(name: &str, display: &ToolDisplay) -> String { pub fn format_executing(name: &str, display: &ToolDisplay) -> String {
match display { match display {
@ -31,9 +34,12 @@ pub fn format_executing(name: &str, display: &ToolDisplay) -> String {
} }
/// Format a tool result after execution completes. /// Format a tool result after execution completes.
///
/// Output is truncated to [`MAX_RESULT_LINES`] lines to keep the TUI
/// responsive for commands that produce large output (e.g. verbose test runs).
pub fn format_result(name: &str, display: &ToolDisplay, is_error: bool) -> String { pub fn format_result(name: &str, display: &ToolDisplay, is_error: bool) -> String {
let prefix = if is_error { "error" } else { "result" }; let prefix = if is_error { "error" } else { "result" };
match display { let raw = match display {
ToolDisplay::WriteFile { ToolDisplay::WriteFile {
path, path,
old_content, old_content,
@ -54,7 +60,19 @@ pub fn format_result(name: &str, display: &ToolDisplay, is_error: bool) -> Strin
ToolDisplay::ListDirectory { path } => format!("ls {path}"), ToolDisplay::ListDirectory { path } => format!("ls {path}"),
ToolDisplay::ReadFile { path } => format!("read {path}"), ToolDisplay::ReadFile { path } => format!("read {path}"),
ToolDisplay::Generic { summary } => format!("[{name} {prefix}] {summary}"), ToolDisplay::Generic { summary } => format!("[{name} {prefix}] {summary}"),
};
truncate_lines(&raw, MAX_RESULT_LINES)
} }
/// Keep at most `max` lines, appending a summary if truncated.
fn truncate_lines(s: &str, max: usize) -> String {
let total = s.lines().count();
if total <= max {
return s.to_string();
}
let mut out: String = s.lines().take(max).collect::<Vec<_>>().join("\n");
out.push_str(&format!("\n... ({} lines truncated)", total - max));
out
} }
/// Produce a unified diff between `old` and `new` text. /// Produce a unified diff between `old` and `new` text.
@ -130,4 +148,33 @@ mod tests {
assert!(diff.contains("+line1")); assert!(diff.contains("+line1"));
assert!(diff.contains("+line2")); assert!(diff.contains("+line2"));
} }
#[test]
fn truncate_lines_under_limit() {
assert_eq!(truncate_lines("a\nb\nc", 5), "a\nb\nc");
}
#[test]
fn truncate_lines_over_limit() {
let input: String = (0..300)
.map(|i| format!("line {i}"))
.collect::<Vec<_>>()
.join("\n");
let output = truncate_lines(&input, MAX_RESULT_LINES);
assert_eq!(output.lines().count(), MAX_RESULT_LINES + 1); // +1 for summary
assert!(output.contains("290 lines truncated"));
}
#[test]
fn format_result_shell_truncates_large_output() {
let big_output = (0..500)
.map(|i| format!("line {i}"))
.collect::<Vec<_>>()
.join("\n");
let display = ToolDisplay::ShellExec {
command: format!("cargo test\n{big_output}"),
};
let result = format_result("shell_exec", &display, false);
assert!(result.contains("lines truncated"));
}
} }