use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use crate::agent::ToolResultPagingConfig;
pub(crate) const READ_TOOL_RESULT_TOOL: &str = "read_tool_result";
#[derive(Debug, Clone, Copy)]
pub(crate) struct ToolResultPager {
threshold_bytes: usize,
page_bytes: usize,
}
struct Window<'a> {
text: &'a str,
first_line: usize,
last_line: usize,
total_lines: usize,
hard_cut: Option<(usize, usize, usize)>,
}
impl ToolResultPager {
pub(crate) fn new(config: ToolResultPagingConfig) -> Self {
Self {
threshold_bytes: config.threshold_bytes,
page_bytes: config.page_bytes.max(1),
}
}
pub(crate) fn first_page(&self, tool_use_id: &str, text: &str) -> Option<String> {
if text.len() <= self.threshold_bytes {
return None;
}
Some(self.window(tool_use_id, text, 1))
}
pub(crate) fn window(&self, tool_use_id: &str, text: &str, start_line: usize) -> String {
let window = self.cut(text, start_line.max(1));
let mut rendered = String::with_capacity(window.text.len() + TRAILER_HEADROOM_BYTES);
rendered.push_str(window.text);
if !rendered.is_empty() && !rendered.ends_with('\n') {
rendered.push('\n');
}
if let Some((line, shown, total)) = window.hard_cut {
rendered.push_str(&format!(
"β¦[line {} hard-cut at {} of {} bytes; the remainder of this line is skipped]\n",
thousands(line),
thousands(shown),
thousands(total),
));
}
if window.last_line >= window.total_lines {
rendered.push_str(END_OF_RESULT_MARKER);
return rendered;
}
rendered.push_str(&format!(
"β¦[paged: lines {}β{} of {} ({} KB of {} KB). \
Call {READ_TOOL_RESULT_TOOL}(tool_use_id=\"{tool_use_id}\", start_line={}) \
for the next window.]",
thousands(window.first_line),
thousands(window.last_line),
thousands(window.total_lines),
kilobytes(window.text.len()),
kilobytes(text.len()),
thousands(window.last_line + 1),
));
rendered
}
fn cut<'a>(&self, text: &'a str, start_line: usize) -> Window<'a> {
let total_lines = text.split_inclusive('\n').count();
if start_line > total_lines {
return Window {
text: "",
first_line: start_line,
last_line: total_lines,
total_lines,
hard_cut: None,
};
}
let start = text
.split_inclusive('\n')
.take(start_line - 1)
.map(str::len)
.sum::<usize>();
let mut shown = 0_usize;
let mut lines = 0_usize;
for line in text[start..].split_inclusive('\n') {
if shown + line.len() > self.page_bytes {
break;
}
shown += line.len();
lines += 1;
}
if lines == 0 {
let line = text[start..]
.split_inclusive('\n')
.next()
.expect("start_line is within the result, so a line follows");
let mut end = self.page_bytes.min(line.len());
while end > 0 && !line.is_char_boundary(end) {
end -= 1;
}
return Window {
text: &line[..end],
first_line: start_line,
last_line: start_line,
total_lines,
hard_cut: Some((start_line, end, line.len())),
};
}
Window {
text: &text[start..start + shown],
first_line: start_line,
last_line: start_line + lines - 1,
total_lines,
hard_cut: None,
}
}
}
#[derive(Clone, Default)]
pub(crate) struct PagedToolResults {
entries: Arc<Mutex<HashMap<String, Arc<str>>>>,
}
impl PagedToolResults {
pub(crate) fn record(&self, tool_use_id: &str, full: &str) {
self.entries
.lock()
.expect("paged tool results poisoned")
.insert(tool_use_id.to_string(), Arc::from(full));
}
pub(crate) fn get(&self, tool_use_id: &str) -> Option<Arc<str>> {
self.entries
.lock()
.expect("paged tool results poisoned")
.get(tool_use_id)
.cloned()
}
}
const END_OF_RESULT_MARKER: &str = "β¦[end of result]";
const TRAILER_HEADROOM_BYTES: usize = 256;
fn kilobytes(bytes: usize) -> String {
format!("{:.1}", bytes as f64 / 1024.0)
}
fn thousands(value: usize) -> String {
let digits = value.to_string();
let mut grouped = String::with_capacity(digits.len() + digits.len() / 3);
for (index, digit) in digits.chars().enumerate() {
if index > 0 && (digits.len() - index).is_multiple_of(3) {
grouped.push(',');
}
grouped.push(digit);
}
grouped
}
#[cfg(test)]
mod tests {
use super::*;
fn pager(threshold_bytes: usize, page_bytes: usize) -> ToolResultPager {
ToolResultPager::new(ToolResultPagingConfig {
threshold_bytes,
page_bytes,
})
}
fn numbered_lines(count: usize) -> String {
(1..=count)
.map(|line| format!("line-{line:02}xx\n"))
.collect()
}
#[test]
fn results_at_or_below_the_threshold_are_never_paged() {
let text = numbered_lines(6);
assert_eq!(text.len(), 60);
assert_eq!(pager(60, 20).first_page("call-1", &text), None);
assert!(pager(59, 20).first_page("call-1", &text).is_some());
}
#[test]
fn the_first_page_carries_absolute_lines_and_byte_totals() {
let text = numbered_lines(26);
let page = pager(100, 30).first_page("call-8", &text).expect("paged");
assert!(page.starts_with("line-01xx\nline-02xx\nline-03xx\n"));
assert!(
page.contains(
"β¦[paged: lines 1β3 of 26 (0.0 KB of 0.3 KB). \
Call read_tool_result(tool_use_id=\"call-8\", start_line=4) for the next window.]"
),
"unexpected trailer: {page}"
);
}
#[test]
fn windows_tile_the_result_without_gaps_or_overlap() {
let text = numbered_lines(26);
let pager = pager(100, 30);
let second = pager.window("call-8", &text, 4);
assert!(second.starts_with("line-04xx\nline-05xx\nline-06xx\n"));
assert!(second.contains("lines 4β6 of 26"));
assert!(second.contains("start_line=7"));
}
#[test]
fn the_final_window_carries_the_end_marker_instead_of_a_trailer() {
let text = numbered_lines(26);
let last = pager(100, 30).window("call-8", &text, 25);
assert!(last.starts_with("line-25xx\nline-26xx\n"));
assert!(last.ends_with("β¦[end of result]"));
assert!(!last.contains("[paged:"));
}
#[test]
fn a_start_line_past_the_end_returns_an_empty_window() {
let text = numbered_lines(26);
assert_eq!(
pager(100, 30).window("call-8", &text, 27),
"β¦[end of result]"
);
assert_eq!(
pager(100, 30).window("call-8", &text, 9_999),
"β¦[end of result]"
);
}
#[test]
fn a_line_longer_than_a_page_hard_cuts_on_a_character_boundary() {
let text = format!("{}\nnext line\n", "π".repeat(10));
let window = pager(10, 10).window("call-8", &text, 1);
assert!(window.starts_with("ππ"));
assert!(!window.starts_with("πππ"));
assert!(window.contains("β¦[line 1 hard-cut at 8 of 41 bytes"));
assert!(window.contains("start_line=2"));
}
#[test]
fn the_window_after_a_hard_cut_resumes_at_the_next_whole_line() {
let text = format!("{}\nnext line\n", "π".repeat(10));
let window = pager(10, 10).window("call-8", &text, 2);
assert!(window.starts_with("next line\n"));
assert!(window.ends_with("β¦[end of result]"));
}
#[test]
fn windows_never_split_a_line_that_fits_and_preserve_crlf() {
let text = "alpha\r\nbΓ©ta\r\ngamma\r\n";
let window = pager(4, 8).window("call-8", text, 1);
assert!(window.starts_with("alpha\r\n"));
assert!(!window.contains("bΓ©ta"));
assert!(window.contains("lines 1β1 of 3"));
}
#[test]
fn a_result_without_a_trailing_newline_still_ends_before_the_trailer() {
let window = pager(4, 8).window("call-8", "alpha\nomega", 2);
assert!(window.starts_with("omega\n"));
assert!(window.ends_with("β¦[end of result]"));
}
#[test]
fn thousands_separators_match_the_documented_trailer_format() {
assert_eq!(thousands(0), "0");
assert_eq!(thousands(812), "812");
assert_eq!(thousands(5_723), "5,723");
assert_eq!(thousands(1_234_567), "1,234,567");
}
#[test]
fn recorded_results_are_readable_by_tool_use_id_and_isolated_per_id() {
let store = PagedToolResults::default();
store.record("call-1", "first");
store.record("call-2", "second");
assert_eq!(store.get("call-1").as_deref(), Some("first"));
assert_eq!(store.get("call-2").as_deref(), Some("second"));
assert_eq!(store.get("call-3"), None);
}
}