agentsight_capture/runners/
agent.rs1use super::{EventStream, Runner, RunnerError};
5use crate::analyzers::Analyzer;
6use async_trait::async_trait;
7use futures::stream::select_all;
8
9#[derive(Default)]
12pub struct AgentRunner {
13 runners: Vec<Box<dyn Runner>>,
14 analyzers: Vec<Box<dyn Analyzer>>,
15}
16
17impl AgentRunner {
18 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn add_runner(mut self, runner: Box<dyn Runner>) -> Self {
25 self.runners.push(runner);
26 self
27 }
28
29 pub fn add_global_analyzer(mut self, analyzer: Box<dyn Analyzer>) -> Self {
31 self.analyzers.push(analyzer);
32 self
33 }
34
35 pub fn runner_count(&self) -> usize {
37 self.runners.len()
38 }
39
40 pub fn analyzer_count(&self) -> usize {
42 self.analyzers.len()
43 }
44}
45
46#[async_trait]
47impl Runner for AgentRunner {
48 async fn run(&mut self) -> Result<EventStream, RunnerError> {
49 if self.runners.is_empty() {
50 return Err("No runners configured for AgentRunner".into());
51 }
52
53 let mut streams = Vec::new();
55 for runner in &mut self.runners {
56 let stream = runner.run().await?;
57 streams.push(stream);
58 }
59
60 let merged_stream = select_all(streams);
62
63 let mut final_stream = Box::pin(merged_stream) as EventStream;
65 for analyzer in &mut self.analyzers {
66 final_stream = analyzer
67 .process(final_stream)
68 .await
69 .map_err(|e| format!("Global analyzer error: {}", e))?;
70 }
71
72 Ok(final_stream)
73 }
74
75 fn add_analyzer(mut self, analyzer: Box<dyn Analyzer>) -> Self {
76 self.analyzers.push(analyzer);
77 self
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::analyzers::{HTTPParser, MaterializingAnalyzer, SSEProcessor};
85 use crate::runners::FakeRunner;
86 use crate::view::MaterializedView;
87 use futures::stream::StreamExt;
88 use std::time::Duration;
89 use tokio::time::timeout;
90
91 fn materializer() -> MaterializingAnalyzer {
92 MaterializingAnalyzer::with_view(MaterializedView::shared_bounded())
93 }
94
95 #[tokio::test]
96 async fn test_agent_runner_basic_composition() {
97 let fake_runner1 = FakeRunner::new().event_count(2).delay_ms(10);
98
99 let fake_runner2 = FakeRunner::new().event_count(3).delay_ms(15);
100
101 let mut agent = AgentRunner::new()
102 .add_runner(Box::new(fake_runner1))
103 .add_runner(Box::new(fake_runner2));
104
105 assert_eq!(agent.runner_count(), 2);
106 assert_eq!(agent.analyzer_count(), 0);
107
108 let stream = agent.run().await.unwrap();
109 let events: Vec<_> = stream.collect().await;
110
111 assert_eq!(events.len(), 10);
114
115 let ssl_events = events.iter().filter(|e| e.source == "ssl").count();
117 assert_eq!(ssl_events, 10); }
119
120 #[tokio::test]
121 async fn test_agent_runner_with_global_analyzers() {
122 let fake_runner = FakeRunner::new()
123 .event_count(2)
124 .delay_ms(10)
125 .add_analyzer(Box::new(HTTPParser::new().disable_raw_data()));
126
127 let mut agent = AgentRunner::new()
128 .add_runner(Box::new(fake_runner))
129 .add_global_analyzer(Box::new(materializer()));
130
131 assert_eq!(agent.runner_count(), 1);
132 assert_eq!(agent.analyzer_count(), 1);
133
134 let stream = agent.run().await.unwrap();
135 let events: Vec<_> = stream.collect().await;
136
137 assert!(events.len() >= 4);
139 }
140
141 #[tokio::test]
142 async fn test_agent_runner_multiple_runners_with_analyzers() {
143 let fake_runner1 = FakeRunner::new()
144 .event_count(1)
145 .delay_ms(10)
146 .add_analyzer(Box::new(SSEProcessor::new_with_timeout(5000)));
147
148 let fake_runner2 = FakeRunner::new()
149 .event_count(1)
150 .delay_ms(10)
151 .add_analyzer(Box::new(HTTPParser::new()));
152
153 let mut agent = AgentRunner::new()
154 .add_runner(Box::new(fake_runner1))
155 .add_runner(Box::new(fake_runner2));
156
157 let stream = agent.run().await.unwrap();
158 let events: Vec<_> = stream.collect().await;
159
160 assert!(events.len() >= 4, "Should have at least 4 events");
162
163 let sources: std::collections::HashSet<_> = events.iter().map(|e| &e.source).collect();
165 assert!(
166 !sources.is_empty(),
167 "Should have events from at least one source"
168 );
169 }
170
171 #[tokio::test]
172 async fn test_agent_runner_empty_runners() {
173 let mut agent = AgentRunner::new();
174
175 assert_eq!(agent.runner_count(), 0);
176
177 let result = agent.run().await;
178 assert!(result.is_err(), "Should fail with no runners configured");
179
180 if let Err(e) = result {
181 let error_msg = format!("{}", e);
182 assert!(
183 error_msg.contains("No runners configured"),
184 "Should have descriptive error message"
185 );
186 }
187 }
188
189 #[tokio::test]
190 async fn test_agent_runner_streaming_behavior() {
191 use std::sync::Arc;
192 use std::time::Instant;
193 use tokio::sync::Mutex;
194
195 let event_timestamps = Arc::new(Mutex::new(Vec::new()));
196
197 struct TimestampRecorder {
199 timestamps: Arc<Mutex<Vec<Instant>>>,
200 }
201
202 impl TimestampRecorder {
203 fn new(timestamps: Arc<Mutex<Vec<Instant>>>) -> Self {
204 Self { timestamps }
205 }
206 }
207
208 #[async_trait]
209 impl Analyzer for TimestampRecorder {
210 async fn process(
211 &mut self,
212 stream: EventStream,
213 ) -> Result<EventStream, Box<dyn std::error::Error + Send + Sync>> {
214 let timestamps = self.timestamps.clone();
215 let recorded_stream = stream.map(move |event| {
216 let timestamps_clone = timestamps.clone();
217 tokio::spawn(async move {
218 let mut guard = timestamps_clone.lock().await;
219 guard.push(Instant::now());
220 });
221 event
222 });
223 Ok(Box::pin(recorded_stream))
224 }
225 }
226
227 let fake_runner = FakeRunner::new().event_count(3).delay_ms(50); let mut agent = AgentRunner::new()
230 .add_runner(Box::new(fake_runner))
231 .add_global_analyzer(Box::new(TimestampRecorder::new(Arc::clone(
232 &event_timestamps,
233 ))));
234
235 let start_time = Instant::now();
236 let stream = agent.run().await.unwrap();
237 let events: Vec<_> = stream.collect().await;
238 let total_time = start_time.elapsed();
239
240 tokio::time::sleep(Duration::from_millis(100)).await;
242
243 assert_eq!(events.len(), 6); let timestamps_guard = event_timestamps.lock().await;
246 assert!(
247 timestamps_guard.len() >= 3,
248 "Should have recorded multiple timestamps"
249 );
250
251 assert!(
253 total_time >= Duration::from_millis(100),
254 "Should demonstrate streaming behavior"
255 );
256 }
257
258 #[tokio::test]
259 async fn test_agent_runner_concurrent_processing() {
260 let mut handles = Vec::new();
262
263 for i in 0..3 {
264 let handle = tokio::spawn(async move {
265 let fake_runner = FakeRunner::new().event_count(2).delay_ms(10);
266
267 let mut agent = AgentRunner::new().add_runner(Box::new(fake_runner));
268
269 let stream = agent.run().await.unwrap();
270 let events: Vec<_> = stream.collect().await;
271
272 (i, events.len())
273 });
274 handles.push(handle);
275 }
276
277 let mut results = Vec::new();
279 for handle in handles {
280 results.push(handle.await.unwrap());
281 }
282
283 assert_eq!(results.len(), 3);
285 for (agent_id, event_count) in results {
286 assert_eq!(
287 event_count, 4,
288 "Agent {} should have 4 events (2 event_count × 2 events)",
289 agent_id
290 );
291 }
292 }
293
294 #[tokio::test]
295 async fn test_agent_runner_error_handling() {
296 struct FailingRunner;
300
301 #[async_trait]
302 impl Runner for FailingRunner {
303 async fn run(&mut self) -> Result<EventStream, RunnerError> {
304 Err("Simulated runner failure".into())
305 }
306
307 fn add_analyzer(self, _analyzer: Box<dyn Analyzer>) -> Self {
308 self
309 }
310 }
311
312 let mut agent = AgentRunner::new().add_runner(Box::new(FailingRunner));
313
314 let result = agent.run().await;
315 assert!(result.is_err(), "Should propagate runner error");
316
317 if let Err(e) = result {
318 let error_msg = format!("{}", e);
319 assert!(
320 error_msg.contains("Simulated runner failure"),
321 "Should contain original error message"
322 );
323 }
324 }
325
326 #[tokio::test]
327 async fn test_agent_runner_with_timeout() {
328 let fake_runner = FakeRunner::new().event_count(5).delay_ms(10);
330
331 let mut agent = AgentRunner::new().add_runner(Box::new(fake_runner));
332
333 let result = timeout(Duration::from_secs(5), async {
334 let stream = agent.run().await.unwrap();
335 let events: Vec<_> = stream.collect().await;
336 events.len()
337 })
338 .await;
339
340 assert!(result.is_ok(), "AgentRunner should complete within timeout");
341 assert_eq!(
342 result.unwrap(),
343 10,
344 "Should process all events (5 event_count × 2 events)"
345 );
346 }
347
348 #[tokio::test]
349 async fn test_agent_runner_fluent_interface() {
350 let fake_runner1 = FakeRunner::new().event_count(1).delay_ms(10);
352 let fake_runner2 = FakeRunner::new().event_count(1).delay_ms(10);
353
354 let agent = AgentRunner::new()
355 .add_runner(Box::new(fake_runner1))
356 .add_runner(Box::new(fake_runner2))
357 .add_global_analyzer(Box::new(materializer()))
358 .add_analyzer(Box::new(materializer()));
359
360 assert_eq!(agent.runner_count(), 2);
361 assert_eq!(agent.analyzer_count(), 2); }
363}