Skip to main content

looprs_core/
events.rs

1use std::collections::HashMap;
2
3domain_event!(Event {
4    SessionStart,
5    SessionEnd,
6    UserPromptSubmit,
7    PreToolUse,
8    PostToolUse,
9    InferenceComplete,
10    OnError,
11    OnWarning,
12    DelegationStart,
13    DelegationComplete,
14});
15
16/// Context data that flows through events
17#[derive(Debug, Clone)]
18pub struct EventContext {
19    pub session_context: Option<String>,
20    pub user_message: Option<String>,
21    pub tool_name: Option<String>,
22    pub tool_output: Option<String>,
23    pub error: Option<String>,
24    pub warning: Option<String>,
25    pub metadata: HashMap<String, String>,
26}
27
28impl EventContext {
29    pub fn new() -> Self {
30        EventContext {
31            session_context: None,
32            user_message: None,
33            tool_name: None,
34            tool_output: None,
35            error: None,
36            warning: None,
37            metadata: HashMap::new(),
38        }
39    }
40
41    pub fn with_session_context(mut self, ctx: String) -> Self {
42        self.session_context = Some(ctx);
43        self
44    }
45
46    pub fn with_user_message(mut self, msg: String) -> Self {
47        self.user_message = Some(msg);
48        self
49    }
50
51    pub fn with_tool_name(mut self, name: String) -> Self {
52        self.tool_name = Some(name);
53        self
54    }
55
56    pub fn with_tool_output(mut self, output: String) -> Self {
57        self.tool_output = Some(output);
58        self
59    }
60
61    pub fn with_error(mut self, error: String) -> Self {
62        self.error = Some(error);
63        self
64    }
65
66    pub fn with_metadata(mut self, key: String, value: String) -> Self {
67        self.metadata.insert(key, value);
68        self
69    }
70}
71
72impl Default for EventContext {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78/// Handler for events
79pub type EventHandler = Box<dyn Fn(Event, &EventContext) + Send + Sync>;
80
81/// Manager for dispatching events
82pub struct EventManager {
83    handlers: HashMap<Event, Vec<EventHandler>>,
84}
85
86impl EventManager {
87    pub fn new() -> Self {
88        EventManager {
89            handlers: HashMap::new(),
90        }
91    }
92
93    pub fn on<F>(&mut self, event: Event, handler: F)
94    where
95        F: Fn(Event, &EventContext) + Send + Sync + 'static,
96    {
97        self.handlers
98            .entry(event)
99            .or_default()
100            .push(Box::new(handler));
101    }
102
103    pub fn fire(&self, event: Event, context: &EventContext) {
104        if let Some(handlers) = self.handlers.get(&event) {
105            for handler in handlers {
106                handler(event, context);
107            }
108        }
109    }
110
111    pub fn clear(&mut self, event: Event) {
112        self.handlers.remove(&event);
113    }
114}
115
116impl Default for EventManager {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use std::sync::{Arc, Mutex};
126
127    #[test]
128    fn event_names_from_macro() {
129        assert_eq!(Event::SessionStart.name(), "SessionStart");
130        assert_eq!(Event::SessionEnd.name(), "SessionEnd");
131        assert_eq!(Event::PreToolUse.name(), "PreToolUse");
132        assert_eq!(Event::OnError.name(), "OnError");
133        assert_eq!(Event::DelegationStart.name(), "DelegationStart");
134    }
135
136    #[test]
137    fn event_context_builder() {
138        let ctx = EventContext::new()
139            .with_session_context("session_data".to_string())
140            .with_user_message("test message".to_string())
141            .with_error("test error".to_string());
142
143        assert_eq!(ctx.session_context, Some("session_data".to_string()));
144        assert_eq!(ctx.user_message, Some("test message".to_string()));
145        assert_eq!(ctx.error, Some("test error".to_string()));
146        assert!(ctx.tool_name.is_none());
147    }
148
149    #[test]
150    fn event_context_metadata() {
151        let ctx = EventContext::new()
152            .with_metadata("key1".to_string(), "value1".to_string())
153            .with_metadata("key2".to_string(), "value2".to_string());
154
155        assert_eq!(ctx.metadata.get("key1"), Some(&"value1".to_string()));
156        assert_eq!(ctx.metadata.get("key2"), Some(&"value2".to_string()));
157    }
158
159    #[test]
160    fn event_manager_fire() {
161        let mut manager = EventManager::new();
162        let counter = Arc::new(Mutex::new(0));
163        let counter_clone = counter.clone();
164
165        manager.on(Event::SessionStart, move |_event, _ctx| {
166            let mut c = counter_clone.lock().unwrap();
167            *c += 1;
168        });
169
170        let ctx = EventContext::new();
171        manager.fire(Event::SessionStart, &ctx);
172
173        assert_eq!(*counter.lock().unwrap(), 1);
174    }
175
176    #[test]
177    fn event_manager_multiple_handlers() {
178        let mut manager = EventManager::new();
179        let counter1 = Arc::new(Mutex::new(0));
180        let counter1_clone = counter1.clone();
181        let counter2 = Arc::new(Mutex::new(0));
182        let counter2_clone = counter2.clone();
183
184        manager.on(Event::PreToolUse, move |_event, _ctx| {
185            let mut c = counter1_clone.lock().unwrap();
186            *c += 1;
187        });
188
189        manager.on(Event::PreToolUse, move |_event, _ctx| {
190            let mut c = counter2_clone.lock().unwrap();
191            *c += 1;
192        });
193
194        let ctx = EventContext::new();
195        manager.fire(Event::PreToolUse, &ctx);
196
197        assert_eq!(*counter1.lock().unwrap(), 1);
198        assert_eq!(*counter2.lock().unwrap(), 1);
199    }
200
201    #[test]
202    fn event_manager_clear() {
203        let mut manager = EventManager::new();
204        let counter = Arc::new(Mutex::new(0));
205        let counter_clone = counter.clone();
206
207        manager.on(Event::SessionStart, move |_event, _ctx| {
208            let mut c = counter_clone.lock().unwrap();
209            *c += 1;
210        });
211
212        manager.clear(Event::SessionStart);
213        let ctx = EventContext::new();
214        manager.fire(Event::SessionStart, &ctx);
215
216        assert_eq!(*counter.lock().unwrap(), 0);
217    }
218}