1use serde::{Deserialize, Serialize};
11
12#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
14#[serde(rename_all = "camelCase")]
15pub enum CompressionTrigger {
16 Auto,
18 Manual,
20}
21
22#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
33#[serde(tag = "phase", rename_all = "snake_case")]
34pub enum CompressionEvent {
35 Preparing {
37 session_id: u64,
39 tokens_before: usize,
41 msg_count: usize,
43 trigger: CompressionTrigger,
45 },
46 Started {
48 session_id: u64,
50 tokens_before: usize,
52 msg_count: usize,
54 trigger: CompressionTrigger,
56 },
57 Progress {
59 session_id: u64,
61 chars: usize,
63 },
64 Completed {
66 session_id: u64,
68 tokens_before: usize,
70 tokens_after: usize,
72 reduction_pct: i32,
74 msg_count_before: usize,
76 msg_count_after: usize,
78 trigger: CompressionTrigger,
80 },
81 Failed {
83 session_id: u64,
85 error: String,
87 trigger: CompressionTrigger,
89 },
90}
91
92impl CompressionEvent {
93 pub fn into_user_event(self) -> agent_base::UserEvent {
95 agent_base::UserEvent::Structured {
96 event_type: "compression".to_string(),
97 data: serde_json::to_value(self).expect("CompressionEvent is always serializable"),
98 }
99 }
100
101 pub fn from_user_event(event: &agent_base::UserEvent) -> Option<Self> {
106 match event {
107 agent_base::UserEvent::Structured { event_type, data }
108 if event_type == "compression" =>
109 {
110 serde_json::from_value(data.clone()).ok()
111 }
112 _ => None,
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
124 fn test_preparing_roundtrip() {
125 let event = CompressionEvent::Preparing {
126 session_id: 42,
127 tokens_before: 4200,
128 msg_count: 8,
129 trigger: CompressionTrigger::Auto,
130 };
131 let json = serde_json::to_string(&event).unwrap();
132 let parsed: CompressionEvent = serde_json::from_str(&json).unwrap();
133 assert_eq!(event, parsed);
134 }
135
136 #[test]
137 fn test_started_roundtrip() {
138 let event = CompressionEvent::Started {
139 session_id: 42,
140 tokens_before: 4200,
141 msg_count: 8,
142 trigger: CompressionTrigger::Manual,
143 };
144 let json = serde_json::to_string(&event).unwrap();
145 let parsed: CompressionEvent = serde_json::from_str(&json).unwrap();
146 assert_eq!(event, parsed);
147 }
148
149 #[test]
150 fn test_progress_roundtrip() {
151 let event = CompressionEvent::Progress {
152 session_id: 42,
153 chars: 755,
154 };
155 let json = serde_json::to_string(&event).unwrap();
156 let parsed: CompressionEvent = serde_json::from_str(&json).unwrap();
157 assert_eq!(event, parsed);
158 }
159
160 #[test]
161 fn test_completed_roundtrip() {
162 let event = CompressionEvent::Completed {
163 session_id: 42,
164 tokens_before: 4200,
165 tokens_after: 3800,
166 reduction_pct: 10,
167 msg_count_before: 20,
168 msg_count_after: 8,
169 trigger: CompressionTrigger::Auto,
170 };
171 let json = serde_json::to_string(&event).unwrap();
172 let parsed: CompressionEvent = serde_json::from_str(&json).unwrap();
173 assert_eq!(event, parsed);
174 }
175
176 #[test]
177 fn test_failed_roundtrip() {
178 let event = CompressionEvent::Failed {
179 session_id: 42,
180 error: "LLM timeout".to_string(),
181 trigger: CompressionTrigger::Manual,
182 };
183 let json = serde_json::to_string(&event).unwrap();
184 let parsed: CompressionEvent = serde_json::from_str(&json).unwrap();
185 assert_eq!(event, parsed);
186 }
187
188 #[test]
191 fn test_json_uses_snake_case() {
192 let event = CompressionEvent::Completed {
193 session_id: 1,
194 tokens_before: 100,
195 tokens_after: 50,
196 reduction_pct: 50,
197 msg_count_before: 10,
198 msg_count_after: 5,
199 trigger: CompressionTrigger::Auto,
200 };
201 let json = serde_json::to_value(&event).unwrap();
202
203 assert_eq!(json["phase"], "completed");
205 assert!(json.get("tokens_before").is_some(), "expected snake_case");
207 assert!(
208 json.get("tokensAfter").is_none(),
209 "camelCase must not appear"
210 );
211 assert!(json.get("msg_count_before").is_some());
212 assert!(json.get("msg_count_after").is_some());
213 assert!(json.get("reduction_pct").is_some());
214 assert!(json.get("session_id").is_some());
215 }
216
217 #[test]
218 fn test_trigger_json_values() {
219 let auto = serde_json::to_value(&CompressionTrigger::Auto).unwrap();
220 assert_eq!(auto, "auto");
221
222 let manual = serde_json::to_value(&CompressionTrigger::Manual).unwrap();
223 assert_eq!(manual, "manual");
224 }
225
226 #[test]
229 fn test_into_user_event_and_back() {
230 let event = CompressionEvent::Preparing {
231 session_id: 42,
232 tokens_before: 4200,
233 msg_count: 8,
234 trigger: CompressionTrigger::Auto,
235 };
236 let user_event = event.clone().into_user_event();
237
238 match &user_event {
240 agent_base::UserEvent::Structured { event_type, .. } => {
241 assert_eq!(event_type, "compression");
242 }
243 other => panic!("expected Structured, got {:?}", other),
244 }
245
246 let recovered = CompressionEvent::from_user_event(&user_event).unwrap();
248 assert_eq!(event, recovered);
249 }
250
251 #[test]
252 fn test_from_user_event_ignores_non_compression() {
253 let other = agent_base::UserEvent::Structured {
254 event_type: "other".to_string(),
255 data: serde_json::json!({}),
256 };
257 assert!(CompressionEvent::from_user_event(&other).is_none());
258 }
259
260 #[test]
261 fn test_from_user_event_returns_none_on_malformed_data() {
262 let bad = agent_base::UserEvent::Structured {
263 event_type: "compression".to_string(),
264 data: serde_json::json!({ "phase": "unknown_phase" }),
265 };
266 assert!(CompressionEvent::from_user_event(&bad).is_none());
267 }
268
269 #[test]
272 fn test_deserialize_progress_from_json() {
273 let json = serde_json::json!({
275 "phase": "progress",
276 "session_id": 1,
277 "chars": 42,
278 });
279 let event: CompressionEvent = serde_json::from_value(json).unwrap();
280 assert_eq!(
281 event,
282 CompressionEvent::Progress {
283 session_id: 1,
284 chars: 42
285 }
286 );
287 }
288
289 #[test]
290 fn test_deserialize_start_from_json() {
291 let json = serde_json::json!({
292 "phase": "start",
293 "tokens_before": 4200,
294 "msg_count": 8,
295 });
296 let result: Result<CompressionEvent, _> = serde_json::from_value(json);
299 assert!(result.is_err(), "\"start\" should not match \"started\"");
301 }
302}