use anyhow::Result;
use crate::utils::tokens::{estimate_tokens, truncate_to_tokens};
pub mod execution;
pub mod file_ops;
pub mod search;
#[derive(Debug, Clone)]
pub struct SavingsEstimate {
pub llm_tokens: usize,
pub ui_tokens: usize,
pub savings_percent: f32,
}
use std::borrow::Cow;
pub(super) fn truncate_line<'a>(line: &'a str, max_len: usize) -> Cow<'a, str> {
if line.len() <= max_len {
Cow::Borrowed(line)
} else {
let target = max_len.saturating_sub(3);
let end = line.char_indices().map(|(i, _)| i).rfind(|&i| i <= target).unwrap_or(0);
Cow::Owned(format!("{}...", &line[..end]))
}
}
pub trait Summarizer {
fn summarize(&self, full_output: &str, metadata: Option<&serde_json::Value>) -> Result<String>;
fn estimate_savings(&self, full_output: &str, summary: &str) -> SavingsEstimate {
let ui_tokens = estimate_tokens(full_output);
let llm_tokens = estimate_tokens(summary);
let savings = ui_tokens.saturating_sub(llm_tokens);
let savings_percent = if ui_tokens > 0 {
(savings as f32 / ui_tokens as f32) * 100.0
} else {
0.0
};
SavingsEstimate { llm_tokens, ui_tokens, savings_percent }
}
}
pub fn extract_key_info(text: &str, max_lines: usize) -> String {
let mut lines: Vec<&str> = Vec::with_capacity(max_lines.min(32));
let mut total_lines = 0usize;
for line in text.lines() {
total_lines += 1;
if lines.len() < max_lines {
lines.push(line);
}
}
if total_lines > max_lines {
format!("{}\n[...{} more lines]", lines.join("\n"), total_lines - max_lines)
} else {
lines.join("\n")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_estimate_tokens() {
assert_eq!(estimate_tokens("Hello world"), 2);
assert_eq!(estimate_tokens(""), 0);
assert_eq!(estimate_tokens("a".repeat(1000).as_str()), 125);
}
#[test]
fn test_truncate_to_tokens() {
let text = "a".repeat(1000);
let truncated = truncate_to_tokens(&text, 50); assert!(truncated.len() <= 500); assert!(truncated.len() < text.len());
}
#[test]
fn test_truncate_short_text() {
let text = "Short text";
let truncated = truncate_to_tokens(text, 100);
assert_eq!(truncated, text);
}
#[test]
fn test_extract_key_info() {
let text = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5";
let extracted = extract_key_info(text, 3);
assert!(extracted.contains("Line 1"));
assert!(extracted.contains("Line 3"));
assert!(extracted.contains("[...2 more lines]"));
}
#[test]
fn test_extract_key_info_exact() {
let text = "Line 1\nLine 2\nLine 3";
let extracted = extract_key_info(text, 3);
assert_eq!(extracted, text);
assert!(!extracted.contains("more lines"));
}
}