pub trait ToolResultCacher: Send + Sync {
fn cache(&self, tool_name: &str, content: &str) -> Option<CachedResult>;
fn inline_threshold(&self) -> u32 {
2_000
}
}
#[derive(Debug, Clone)]
pub struct CachedResult {
pub summary: String,
pub original_tokens_est: u32,
pub summary_tokens_est: u32,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::estimate_tokens;
struct TestCacher {
threshold: u32,
}
impl ToolResultCacher for TestCacher {
fn cache(&self, tool_name: &str, content: &str) -> Option<CachedResult> {
let summary = format!("[cached: {tool_name}, {} chars]", content.len());
let summary_tokens = estimate_tokens(&summary);
Some(CachedResult {
summary,
original_tokens_est: estimate_tokens(content),
summary_tokens_est: summary_tokens,
})
}
fn inline_threshold(&self) -> u32 {
self.threshold
}
}
struct FailingCacher;
impl ToolResultCacher for FailingCacher {
fn cache(&self, _tool_name: &str, _content: &str) -> Option<CachedResult> {
None }
}
#[test]
fn test_cacher_returns_summary() {
let test_cacher = TestCacher { threshold: 10 };
let result = test_cacher.cache("db_sql", "lots of data here").unwrap();
assert!(result.summary.contains("cached: db_sql"));
assert!(result.summary.contains("17 chars"));
assert!(result.original_tokens_est > 0);
assert!(result.summary_tokens_est > 0);
}
#[test]
fn test_failing_cacher_returns_none() {
let cacher = FailingCacher;
assert!(cacher.cache("tool", "data").is_none());
}
#[test]
fn test_default_threshold() {
let cacher = TestCacher { threshold: 2_000 };
assert_eq!(cacher.inline_threshold(), 2_000);
}
#[test]
fn test_custom_threshold() {
let cacher = TestCacher { threshold: 500 };
assert_eq!(cacher.inline_threshold(), 500);
}
#[test]
fn test_cacher_is_object_safe() {
let cacher: Box<dyn ToolResultCacher> = Box::new(TestCacher { threshold: 100 });
let result = cacher.cache("tool", "data");
assert!(result.is_some());
}
}