#[inline]
pub fn floor_char_boundary(s: &str, index: usize) -> usize {
if index >= s.len() {
s.len()
} else {
let mut i = index;
while i > 0 && !s.is_char_boundary(i) {
i -= 1;
}
i
}
}
use crate::session::estimate_tokens;
pub fn format_content_with_line_numbers(
lines: &[&str],
start_line_number: usize,
view_range: Option<(usize, i64)>,
) -> String {
if let Some((start, end)) = view_range {
let start_idx = if start == 0 {
0
} else {
start.saturating_sub(1)
}; let end_idx = if end == -1 {
lines.len()
} else {
(end as usize).min(lines.len())
};
if start_idx >= lines.len() || start_idx > end_idx {
return if start_idx >= lines.len() {
format!(
"Start line {} exceeds content length ({} lines)",
start,
lines.len()
)
} else {
format!(
"Start line {} must be less than or equal to end line {}",
start, end
)
};
}
let mut result_lines = Vec::new();
if start_idx > 3 {
for (i, line) in lines.iter().enumerate().take(2) {
result_lines.push(format!("{}: {}", start_line_number + i, line));
}
if start_idx > 5 {
result_lines.push(format!("[...{} lines more]", start_idx - 2));
} else {
for (i, line) in lines.iter().enumerate().take(start_idx).skip(2) {
result_lines.push(format!("{}: {}", start_line_number + i, line));
}
}
} else {
for (i, line) in lines.iter().enumerate().take(start_idx) {
result_lines.push(format!("{}: {}", start_line_number + i, line));
}
}
for (i, line) in lines.iter().enumerate().take(end_idx).skip(start_idx) {
result_lines.push(format!("{}: {}", start_line_number + i, line));
}
let remaining_lines = lines.len() - end_idx;
if remaining_lines > 3 {
if remaining_lines > 5 {
result_lines.push(format!("[...{} lines more]", remaining_lines - 2));
for (i, line) in lines.iter().enumerate().skip(lines.len() - 2) {
result_lines.push(format!("{}: {}", start_line_number + i, line));
}
} else {
for (i, line) in lines.iter().enumerate().skip(end_idx) {
result_lines.push(format!("{}: {}", start_line_number + i, line));
}
}
} else {
for (i, line) in lines.iter().enumerate().skip(end_idx) {
result_lines.push(format!("{}: {}", start_line_number + i, line));
}
}
result_lines.join("\n")
} else {
lines
.iter()
.enumerate()
.map(|(i, line)| format!("{}: {}", start_line_number + i, line))
.collect::<Vec<_>>()
.join("\n")
}
}
pub fn format_extracted_content_smart(
lines: &[&str],
start_line: usize,
max_display_lines: Option<usize>,
) -> String {
let max_lines = max_display_lines.unwrap_or(50);
if lines.len() <= max_lines {
lines
.iter()
.enumerate()
.map(|(i, line)| format!("{}: {}", start_line + i, line))
.collect::<Vec<_>>()
.join("\n")
} else {
let show_first = (max_lines * 2) / 3; let show_last = max_lines - show_first - 1;
let mut result_lines = Vec::new();
for (i, line) in lines.iter().enumerate().take(show_first) {
result_lines.push(format!("{}: {}", start_line + i, line));
}
let hidden_lines = lines.len() - show_first - show_last;
result_lines.push(format!("[...{} lines more]", hidden_lines));
let skip_count = lines.len() - show_last;
for (i, line) in lines.iter().enumerate().skip(skip_count) {
result_lines.push(format!("{}: {}", start_line + i, line));
}
result_lines.join("\n")
}
}
pub fn truncate_content_smart(content: &str, max_tokens: usize) -> String {
let token_count = estimate_tokens(content);
if token_count <= max_tokens {
return content.to_string();
}
let truncated = crate::session::truncate_to_tokens(content, max_tokens);
format!(
"{truncated}\n\n[Content truncated - {token_count} tokens estimated, max {max_tokens} allowed. Use more specific commands to reduce output size]"
)
}
pub fn truncate_tool_output_smart(content: &str, max_lines: usize, max_chars: usize) -> String {
let lines: Vec<&str> = content.lines().collect();
if lines.len() <= max_lines && content.chars().count() <= max_chars {
content.to_string()
} else if lines.len() > max_lines {
let show_lines = max_lines.saturating_sub(1); let mut result = lines
.iter()
.take(show_lines)
.cloned()
.collect::<Vec<_>>()
.join("\n");
result.push_str(&format!(
"\n... [{} more lines]",
lines.len().saturating_sub(show_lines)
));
result
} else {
let truncated: String = content.chars().take(max_chars.saturating_sub(3)).collect();
format!("{}...", truncated)
}
}
pub fn truncate_mcp_response_global(content: &str, max_tokens: usize) -> (String, bool) {
if max_tokens == 0 {
return (content.to_string(), false);
}
let token_count = estimate_tokens(content);
if token_count <= max_tokens {
return (content.to_string(), false);
}
let truncated = truncate_content_smart(content, max_tokens).replace(
"[Content truncated -",
"⚠️ **MCP RESPONSE TRUNCATED** - Original:",
);
(truncated, true)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mcp_truncation_unlimited() {
let content = "This is a test content";
let (result, was_truncated) = truncate_mcp_response_global(content, 0);
assert_eq!(result, content);
assert!(!was_truncated);
}
#[test]
fn test_mcp_truncation_under_limit() {
let content = "Short content";
let (result, was_truncated) = truncate_mcp_response_global(content, 1000);
assert_eq!(result, content);
assert!(!was_truncated);
}
#[test]
fn test_mcp_truncation_over_limit() {
let content = "This is a very long content that should be truncated when it exceeds the token limit. ".repeat(100);
let (result, was_truncated) = truncate_mcp_response_global(&content, 50);
assert!(result.contains("⚠️ **MCP RESPONSE TRUNCATED**"));
assert!(result.len() < content.len());
assert!(was_truncated);
}
}