use crate::types::AgentMessage;
pub trait TokenEstimator: Send + Sync + 'static {
fn estimate_message(&self, message: &AgentMessage) -> usize;
fn estimate_messages(&self, messages: &[AgentMessage]) -> usize {
messages.iter().map(|m| self.estimate_message(m)).sum()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CharHeuristicEstimator;
impl TokenEstimator for CharHeuristicEstimator {
fn estimate_message(&self, message: &AgentMessage) -> usize {
serde_json::to_string(message)
.map(|s| s.len() / 4)
.unwrap_or(0)
}
}
pub static CHAR_HEURISTIC: CharHeuristicEstimator = CharHeuristicEstimator;
#[cfg(test)]
mod tests {
use super::*;
use crate::types::UserContent;
#[test]
fn char_heuristic_estimates_in_token_units() {
let msg = AgentMessage::User {
content: UserContent::Text("x".repeat(400)),
timestamp: None,
};
let est = CHAR_HEURISTIC.estimate_message(&msg);
assert!(est > 80 && est < 200, "estimator out of range: {est}");
}
#[test]
fn estimate_messages_sums() {
let one = AgentMessage::User {
content: UserContent::Text("a".into()),
timestamp: None,
};
let two = AgentMessage::User {
content: UserContent::Text("b".into()),
timestamp: None,
};
let messages = vec![one.clone(), two.clone()];
let summed = CHAR_HEURISTIC.estimate_messages(&messages);
assert_eq!(
summed,
CHAR_HEURISTIC.estimate_message(&one) + CHAR_HEURISTIC.estimate_message(&two)
);
}
}