1mod fan_in;
12
13use std::io::Write;
14
15use crate::event::{Event, JsonlWriter};
16
17pub use fan_in::{EventFanIn, MergedEvents, TaggedEvent, TaggedSink};
18
19pub trait EventSink: Send + 'static {
29 fn emit(&mut self, event: Event) -> std::io::Result<()>;
30}
31
32impl EventSink for Box<dyn EventSink> {
39 fn emit(&mut self, event: Event) -> std::io::Result<()> {
40 (**self).emit(event)
41 }
42}
43
44impl<W: Write + Send + 'static> EventSink for JsonlWriter<W> {
45 fn emit(&mut self, event: Event) -> std::io::Result<()> {
46 self.write(event).map(|_| ())
47 }
48}
49
50#[derive(Debug, Default, Clone, PartialEq)]
53pub struct CollectingSink {
54 events: Vec<Event>,
55}
56
57impl CollectingSink {
58 pub fn new() -> Self {
59 Self::default()
60 }
61
62 pub fn events(&self) -> &[Event] {
63 &self.events
64 }
65
66 pub fn into_events(self) -> Vec<Event> {
67 self.events
68 }
69}
70
71impl EventSink for CollectingSink {
72 fn emit(&mut self, event: Event) -> std::io::Result<()> {
73 self.events.push(event);
74 Ok(())
75 }
76}
77
78#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
80pub struct NullSink;
81
82impl EventSink for NullSink {
83 fn emit(&mut self, _event: Event) -> std::io::Result<()> {
84 Ok(())
85 }
86}
87
88pub struct FnSink<F>(F)
90where
91 F: FnMut(Event) -> std::io::Result<()> + Send + 'static;
92
93impl<F> FnSink<F>
94where
95 F: FnMut(Event) -> std::io::Result<()> + Send + 'static,
96{
97 pub fn new(callback: F) -> Self {
98 Self(callback)
99 }
100}
101
102impl<F> EventSink for FnSink<F>
103where
104 F: FnMut(Event) -> std::io::Result<()> + Send + 'static,
105{
106 fn emit(&mut self, event: Event) -> std::io::Result<()> {
107 (self.0)(event)
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114 use crate::event::RunOutcome;
115
116 fn delta(text: &str) -> Event {
117 Event::AssistantDelta {
118 text: text.to_string(),
119 }
120 }
121
122 #[test]
123 fn collecting_sink_keeps_order() {
124 let mut sink = CollectingSink::new();
125 sink.emit(delta("a")).expect("emits");
126 sink.emit(delta("b")).expect("emits");
127
128 assert_eq!(sink.into_events(), vec![delta("a"), delta("b")]);
129 }
130
131 #[test]
132 fn null_sink_accepts_everything() {
133 let mut sink = NullSink;
134
135 assert!(
136 sink.emit(Event::RunFinished {
137 outcome: RunOutcome::Ok,
138 stopped_by: None,
139 usage: None
140 })
141 .is_ok()
142 );
143 }
144
145 #[test]
146 fn fn_sink_forwards_to_the_closure() {
147 let (tx, rx) = std::sync::mpsc::channel();
148 let mut sink = FnSink::new(move |event| {
149 tx.send(event).expect("receiver alive");
150 Ok(())
151 });
152
153 sink.emit(delta("x")).expect("emits");
154
155 assert_eq!(rx.recv().expect("an event"), delta("x"));
156 }
157
158 #[test]
159 fn a_jsonl_writer_is_a_sink() {
160 let mut sink = JsonlWriter::new(Vec::new());
161 sink.emit(delta("hi")).expect("emits");
162
163 let written = String::from_utf8(sink.into_inner()).expect("utf-8");
164 assert!(written.contains("\"type\":\"assistant_delta\""));
165 }
166
167 #[test]
168 fn a_sink_chosen_at_runtime_is_still_a_sink() {
169 let mut sink: Box<dyn EventSink> = if cfg!(test) {
172 Box::new(CollectingSink::new())
173 } else {
174 Box::new(NullSink)
175 };
176
177 sink.emit(delta("boxed")).expect("emits");
178 }
179}