pub const TRUNCATION_MARKER: &str = "…(truncated)";
fn floor_char_boundary(s: &str, max_bytes: usize) -> usize {
if max_bytes >= s.len() {
return s.len();
}
s.char_indices()
.map(|(i, _)| i)
.take_while(|&i| i <= max_bytes)
.last()
.unwrap_or(0)
}
pub fn truncate_str(s: &str, max_bytes: usize) -> &str {
&s[..floor_char_boundary(s, max_bytes)]
}
pub fn truncate_with_suffix(s: &str, max_bytes: usize, suffix: &str) -> String {
let end = floor_char_boundary(s, max_bytes);
if end == s.len() {
s.to_string()
} else {
format!("{}{}", &s[..end], suffix)
}
}
pub fn truncate_for_log(s: &str, max_bytes: usize) -> String {
truncate_with_suffix(s, max_bytes, "...")
}
pub fn truncate_for_llm(s: &str, max_bytes: usize) -> String {
truncate_with_suffix(s, max_bytes, TRUNCATION_MARKER)
}
pub fn truncate_with_notice(s: &str, max_bytes: usize) -> String {
let end = floor_char_boundary(s, max_bytes);
if end == s.len() {
return s.to_string();
}
let omitted = s.len() - end;
format!(
"{}\n\n[... truncated: showing first {} of {} bytes, {} bytes omitted. \
Re-run the tool with a narrower query or range if you need the rest.]",
&s[..end],
end,
s.len(),
omitted
)
}