use async_trait::async_trait;
use crate::types::agent_state::AgentState;
#[async_trait]
pub trait OutputTransformer: Send + Sync {
async fn transform(&self, output: String, tool_name: &str, state: &AgentState) -> String;
fn estimate_output_tokens(&self, output: &str) -> usize {
output.len() / 4 + 1
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::model_info::ModelTier;
use std::sync::Arc;
#[test]
fn test_output_transformer_is_object_safe() {
struct Dummy;
#[async_trait]
impl OutputTransformer for Dummy {
async fn transform(
&self,
output: String,
_tool_name: &str,
_state: &AgentState,
) -> String {
output
}
}
let _: Arc<dyn OutputTransformer> = Arc::new(Dummy);
}
#[test]
fn test_default_estimate_output_tokens() {
struct Dummy;
#[async_trait]
impl OutputTransformer for Dummy {
async fn transform(
&self,
output: String,
_tool_name: &str,
_state: &AgentState,
) -> String {
output
}
}
let t = Dummy;
assert_eq!(t.estimate_output_tokens(&"a".repeat(400)), 101);
assert_eq!(t.estimate_output_tokens(""), 1);
}
#[tokio::test]
async fn test_context_aware_transformer() {
struct ToolAwareTransformer;
#[async_trait]
impl OutputTransformer for ToolAwareTransformer {
async fn transform(
&self,
output: String,
tool_name: &str,
state: &AgentState,
) -> String {
format!(
"[tool={}, util={:.0}%] {}",
tool_name,
state.context_utilization() * 100.0,
output
)
}
}
let t = ToolAwareTransformer;
let mut state = AgentState::new(ModelTier::Medium, 1000);
state.total_context_tokens = 750;
let result = t.transform("data".to_string(), "search", &state).await;
assert!(result.contains("tool=search"), "should include tool name");
assert!(result.contains("75%"), "should include utilization");
assert!(result.contains("data"), "should include original output");
}
}