Skip to main content

agent_engine/events/
types.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use uuid::Uuid;
5
6#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
7pub enum Severity {
8    Low,
9    Medium,
10    High,
11    Critical,
12}
13
14impl Severity {
15    pub fn as_str(&self) -> &str {
16        match self {
17            Severity::Low => "low",
18            Severity::Medium => "medium",
19            Severity::High => "high",
20            Severity::Critical => "critical",
21        }
22    }
23
24    #[allow(clippy::should_implement_trait)]
25    pub fn from_str(s: &str) -> Self {
26        match s.to_lowercase().as_str() {
27            "low" => Severity::Low,
28            "high" => Severity::High,
29            "critical" => Severity::Critical,
30            _ => Severity::Medium,
31        }
32    }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct EventSource {
37    pub source_type: String,
38    pub name: String,
39    pub callback: Option<String>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct EventChannel {
44    pub id: String,
45    pub name: String,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct EventSender {
50    pub id: String,
51    pub name: String,
52    pub sender_type: String,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct EventContent {
57    pub text: String,
58    pub content_type: String,
59    pub severity: Option<Severity>,
60    pub data: Option<Value>,
61    /// Disclosure class (spec §9.7). Absent means the baseline
62    /// `model_visible`; every other class is enforced at the agent-facing
63    /// rendering boundary (`format_event_for_agent`).
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub disclosure: Option<agent_core::disclosure::DisclosureClass>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct Event {
70    pub id: String,
71    pub timestamp: DateTime<Utc>,
72    pub source: EventSource,
73    pub channel: Option<EventChannel>,
74    pub sender: Option<EventSender>,
75    pub content: EventContent,
76    pub expects_response: bool,
77    pub reply_to: Option<String>,
78}
79
80impl Event {
81    /// Create a simple event from minimal params (for CLI / quick use).
82    pub fn simple(source_type: &str, text: &str, severity: Option<Severity>) -> Self {
83        Event {
84            id: Uuid::new_v4().to_string(),
85            timestamp: Utc::now(),
86            source: EventSource {
87                source_type: source_type.to_string(),
88                name: source_type.to_string(),
89                callback: None,
90            },
91            channel: None,
92            sender: None,
93            content: EventContent {
94                text: text.to_string(),
95                content_type: "message".to_string(),
96                severity,
97                data: None,
98                disclosure: None,
99            },
100            expects_response: false,
101            reply_to: None,
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn severity_ordering() {
112        assert!(Severity::Low < Severity::Medium);
113        assert!(Severity::Medium < Severity::High);
114        assert!(Severity::High < Severity::Critical);
115    }
116
117    #[test]
118    fn severity_str_roundtrip() {
119        for s in [
120            Severity::Low,
121            Severity::Medium,
122            Severity::High,
123            Severity::Critical,
124        ] {
125            assert_eq!(Severity::from_str(s.as_str()), s);
126        }
127        assert_eq!(Severity::from_str("garbage"), Severity::Medium);
128    }
129
130    #[test]
131    fn event_simple_defaults() {
132        let e = Event::simple("cli", "hello", Some(Severity::High));
133        assert_eq!(e.source.source_type, "cli");
134        assert_eq!(e.content.text, "hello");
135        assert_eq!(e.content.severity, Some(Severity::High));
136        assert!(!e.expects_response);
137        assert!(e.channel.is_none());
138    }
139
140    #[test]
141    fn event_serde_roundtrip() {
142        let e = Event::simple("discord", "yo", Some(Severity::Critical));
143        let json = serde_json::to_string(&e).unwrap();
144        let back: Event = serde_json::from_str(&json).unwrap();
145        assert_eq!(back.id, e.id);
146        assert_eq!(back.content.text, "yo");
147        assert_eq!(back.content.severity, Some(Severity::Critical));
148    }
149}