pub trait ToolResultProcessor: Send + Sync {
fn process(&self, tool_name: &str, output: &str) -> ProcessedResult;
}
#[derive(Debug, Clone)]
pub struct ProcessedResult {
pub content: String,
pub was_processed: bool,
pub original_tokens_est: u32,
pub processed_tokens_est: u32,
}
impl ProcessedResult {
pub fn unchanged() -> Self {
Self {
content: String::new(),
was_processed: false,
original_tokens_est: 0,
processed_tokens_est: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct UpperCaseProcessor;
impl ToolResultProcessor for UpperCaseProcessor {
fn process(&self, _tool_name: &str, output: &str) -> ProcessedResult {
ProcessedResult {
content: output.to_uppercase(),
was_processed: true,
original_tokens_est: crate::context::estimate_tokens(output),
processed_tokens_est: crate::context::estimate_tokens(output),
}
}
}
struct SelectiveProcessor;
impl ToolResultProcessor for SelectiveProcessor {
fn process(&self, tool_name: &str, output: &str) -> ProcessedResult {
if tool_name == "web_search" && output.len() > 100 {
ProcessedResult {
content: output[..100].to_string(),
was_processed: true,
original_tokens_est: crate::context::estimate_tokens(output),
processed_tokens_est: 25,
}
} else {
ProcessedResult::unchanged()
}
}
}
#[test]
fn test_unchanged_result() {
let result = ProcessedResult::unchanged();
assert!(!result.was_processed);
assert!(result.content.is_empty());
assert_eq!(result.original_tokens_est, 0);
assert_eq!(result.processed_tokens_est, 0);
}
#[test]
fn test_processor_transforms_output() {
let processor = UpperCaseProcessor;
let result = processor.process("any_tool", "hello world");
assert!(result.was_processed);
assert_eq!(result.content, "HELLO WORLD");
}
#[test]
fn test_selective_processor_skips_non_matching() {
let processor = SelectiveProcessor;
let result = processor.process("calculator", "42");
assert!(!result.was_processed);
}
#[test]
fn test_selective_processor_truncates_matching() {
let processor = SelectiveProcessor;
let long_output = "x".repeat(200);
let result = processor.process("web_search", &long_output);
assert!(result.was_processed);
assert_eq!(result.content.len(), 100);
}
#[test]
fn test_selective_processor_skips_short_matching() {
let processor = SelectiveProcessor;
let result = processor.process("web_search", "short");
assert!(!result.was_processed);
}
#[test]
fn test_processor_is_object_safe() {
let processor: Box<dyn ToolResultProcessor> = Box::new(UpperCaseProcessor);
let result = processor.process("tool", "test");
assert_eq!(result.content, "TEST");
}
#[test]
fn test_processor_token_estimates() {
let processor = UpperCaseProcessor;
let result = processor.process("tool", "Hello world!!");
assert_eq!(result.original_tokens_est, 4);
assert_eq!(result.processed_tokens_est, 4);
}
}