use std::marker::PhantomData;
pub trait Transcoder: Send {
type Input: Send + 'static;
type Output: Send + 'static;
fn prologue(&mut self) -> Vec<Self::Output> {
Vec::new()
}
fn transcode(&mut self, item: &Self::Input) -> Vec<Self::Output>;
fn epilogue(&mut self) -> Vec<Self::Output> {
Vec::new()
}
}
pub struct Identity<T>(PhantomData<T>);
impl<T> Default for Identity<T> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<T: Clone + Send + 'static> Transcoder for Identity<T> {
type Input = T;
type Output = T;
fn transcode(&mut self, item: &Self::Input) -> Vec<Self::Output> {
vec![item.clone()]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::contract::event::AgentEvent;
#[test]
fn identity_passthrough() {
let mut t = Identity::<u32>::default();
assert_eq!(t.transcode(&42), vec![42]);
}
#[test]
fn identity_passthrough_string() {
let mut t = Identity::<String>::default();
let input = "hello".to_string();
let output = t.transcode(&input);
assert_eq!(output, vec!["hello".to_string()]);
}
#[test]
fn identity_prologue_epilogue_empty() {
let mut t = Identity::<u32>::default();
assert!(t.prologue().is_empty());
assert!(t.epilogue().is_empty());
}
struct JsonTranscoder;
impl Transcoder for JsonTranscoder {
type Input = AgentEvent;
type Output = String;
fn prologue(&mut self) -> Vec<String> {
vec!["[".to_string()]
}
fn transcode(&mut self, item: &AgentEvent) -> Vec<String> {
match serde_json::to_string(item) {
Ok(json) => vec![json],
Err(e) => vec![format!("{{\"error\":\"{e}\"}}")],
}
}
fn epilogue(&mut self) -> Vec<String> {
vec!["]".to_string()]
}
}
#[test]
fn mock_transcoder_converts_agent_event_to_json() {
let mut t = JsonTranscoder;
let event = AgentEvent::TextDelta {
delta: "hello".into(),
};
let prologue = t.prologue();
assert_eq!(prologue, vec!["["]);
let output = t.transcode(&event);
assert_eq!(output.len(), 1);
assert!(output[0].contains("\"event_type\":\"text_delta\""));
let epilogue = t.epilogue();
assert_eq!(epilogue, vec!["]"]);
}
struct FilterTranscoder;
impl Transcoder for FilterTranscoder {
type Input = AgentEvent;
type Output = String;
fn transcode(&mut self, item: &AgentEvent) -> Vec<String> {
match item {
AgentEvent::TextDelta { delta } => vec![delta.clone()],
_ => Vec::new(),
}
}
}
#[test]
fn filter_transcoder_drops_non_text_events() {
let mut t = FilterTranscoder;
let text = AgentEvent::TextDelta { delta: "hi".into() };
assert_eq!(t.transcode(&text), vec!["hi"]);
let step = AgentEvent::StepEnd;
assert!(t.transcode(&step).is_empty());
}
struct CountingTranscoder {
count: usize,
}
impl CountingTranscoder {
fn new() -> Self {
Self { count: 0 }
}
}
impl Transcoder for CountingTranscoder {
type Input = String;
type Output = (usize, String);
fn transcode(&mut self, item: &String) -> Vec<(usize, String)> {
self.count += 1;
vec![(self.count, item.clone())]
}
}
#[test]
fn stateful_transcoder_counts() {
let mut t = CountingTranscoder::new();
assert_eq!(t.transcode(&"a".into()), vec![(1, "a".to_string())]);
assert_eq!(t.transcode(&"b".into()), vec![(2, "b".to_string())]);
assert_eq!(t.transcode(&"c".into()), vec![(3, "c".to_string())]);
}
}