1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(tag = "type")]
10pub enum GatewayEvent {
11 Connected { connection_id: Uuid },
13 Disconnected { connection_id: Uuid },
15 TaskSubmitted { task_id: Uuid, description: String },
17 TaskProgress {
19 task_id: Uuid,
20 progress: f32,
21 message: String,
22 },
23 TaskCompleted {
25 task_id: Uuid,
26 success: bool,
27 summary: String,
28 },
29 AssistantMessage { content: String },
31 StreamToken { token: String },
33 ToolExecution {
35 tool_name: String,
36 status: ToolStatus,
37 },
38 Error { code: String, message: String },
40 ChannelMessageReceived {
42 channel_type: String,
43 message: String,
44 },
45 NodeTaskDispatched { node_id: String, task_name: String },
47 AgentSpawned { agent_id: String, name: String },
49 AgentTerminated { agent_id: String },
51 MetricsUpdate {
53 active_connections: usize,
54 active_sessions: usize,
55 total_tool_calls: u64,
56 total_llm_requests: u64,
57 uptime_secs: u64,
58 },
59 ApprovalRequest {
61 approval_id: Uuid,
62 tool_name: String,
63 description: String,
64 risk_level: String,
65 },
66 ConfigSnapshot { config_json: String },
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub enum ToolStatus {
73 Started,
74 Running,
75 Completed,
76 Failed,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81#[serde(tag = "type")]
82pub enum ClientMessage {
83 Authenticate { token: String },
85 SubmitTask { description: String },
87 CancelTask { task_id: Uuid },
89 GetStatus,
91 Ping { timestamp: DateTime<Utc> },
93 ListChannels,
95 ListNodes,
97 GetMetrics,
99 GetConfig,
101 ApprovalDecision {
103 approval_id: Uuid,
104 approved: bool,
105 reason: Option<String>,
106 },
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111#[serde(tag = "type")]
112pub enum ServerMessage {
113 Authenticated { connection_id: Uuid },
115 AuthFailed { reason: String },
117 Event { event: GatewayEvent },
119 StatusResponse {
121 connected_clients: usize,
122 active_tasks: usize,
123 uptime_secs: u64,
124 },
125 Pong { timestamp: DateTime<Utc> },
127 ChannelStatus { channels: Vec<(String, String)> },
129 NodeStatus { nodes: Vec<(String, String)> },
131 MetricsResponse {
133 active_connections: usize,
134 active_sessions: usize,
135 total_tool_calls: u64,
136 total_llm_requests: u64,
137 uptime_secs: u64,
138 },
139 ConfigResponse { config_json: String },
141 ApprovalAck { approval_id: Uuid, accepted: bool },
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_gateway_event_serialization() {
151 let event = GatewayEvent::Connected {
152 connection_id: Uuid::new_v4(),
153 };
154 let json = serde_json::to_string(&event).unwrap();
155 assert!(json.contains("Connected"));
156
157 let restored: GatewayEvent = serde_json::from_str(&json).unwrap();
158 match restored {
159 GatewayEvent::Connected { connection_id } => {
160 assert!(!connection_id.is_nil());
161 }
162 _ => panic!("Wrong variant"),
163 }
164 }
165
166 #[test]
167 fn test_client_message_serialization() {
168 let msg = ClientMessage::Authenticate {
169 token: "secret".into(),
170 };
171 let json = serde_json::to_string(&msg).unwrap();
172 let restored: ClientMessage = serde_json::from_str(&json).unwrap();
173 match restored {
174 ClientMessage::Authenticate { token } => assert_eq!(token, "secret"),
175 _ => panic!("Wrong variant"),
176 }
177 }
178
179 #[test]
180 fn test_server_message_serialization() {
181 let msg = ServerMessage::StatusResponse {
182 connected_clients: 3,
183 active_tasks: 1,
184 uptime_secs: 3600,
185 };
186 let json = serde_json::to_string(&msg).unwrap();
187 let restored: ServerMessage = serde_json::from_str(&json).unwrap();
188 match restored {
189 ServerMessage::StatusResponse {
190 connected_clients,
191 active_tasks,
192 uptime_secs,
193 } => {
194 assert_eq!(connected_clients, 3);
195 assert_eq!(active_tasks, 1);
196 assert_eq!(uptime_secs, 3600);
197 }
198 _ => panic!("Wrong variant"),
199 }
200 }
201
202 #[test]
203 fn test_all_event_variants_serialize() {
204 let events: Vec<GatewayEvent> = vec![
205 GatewayEvent::Connected {
206 connection_id: Uuid::new_v4(),
207 },
208 GatewayEvent::Disconnected {
209 connection_id: Uuid::new_v4(),
210 },
211 GatewayEvent::TaskSubmitted {
212 task_id: Uuid::new_v4(),
213 description: "test".into(),
214 },
215 GatewayEvent::TaskProgress {
216 task_id: Uuid::new_v4(),
217 progress: 0.5,
218 message: "halfway".into(),
219 },
220 GatewayEvent::TaskCompleted {
221 task_id: Uuid::new_v4(),
222 success: true,
223 summary: "done".into(),
224 },
225 GatewayEvent::AssistantMessage {
226 content: "hello".into(),
227 },
228 GatewayEvent::StreamToken {
229 token: "tok".into(),
230 },
231 GatewayEvent::ToolExecution {
232 tool_name: "read_file".into(),
233 status: ToolStatus::Started,
234 },
235 GatewayEvent::Error {
236 code: "E001".into(),
237 message: "bad".into(),
238 },
239 GatewayEvent::ChannelMessageReceived {
240 channel_type: "telegram".into(),
241 message: "hello".into(),
242 },
243 GatewayEvent::NodeTaskDispatched {
244 node_id: "n1".into(),
245 task_name: "shell".into(),
246 },
247 GatewayEvent::AgentSpawned {
248 agent_id: "a1".into(),
249 name: "helper".into(),
250 },
251 GatewayEvent::AgentTerminated {
252 agent_id: "a1".into(),
253 },
254 GatewayEvent::MetricsUpdate {
255 active_connections: 5,
256 active_sessions: 2,
257 total_tool_calls: 100,
258 total_llm_requests: 50,
259 uptime_secs: 3600,
260 },
261 GatewayEvent::ApprovalRequest {
262 approval_id: Uuid::new_v4(),
263 tool_name: "shell_exec".into(),
264 description: "Run rm -rf".into(),
265 risk_level: "high".into(),
266 },
267 GatewayEvent::ConfigSnapshot {
268 config_json: "{}".into(),
269 },
270 ];
271
272 for event in &events {
273 let json = serde_json::to_string(event).unwrap();
274 let _: GatewayEvent = serde_json::from_str(&json).unwrap();
275 }
276 assert_eq!(events.len(), 16);
277 }
278
279 #[test]
280 fn test_tool_status_serialization() {
281 let statuses = vec![
282 ToolStatus::Started,
283 ToolStatus::Running,
284 ToolStatus::Completed,
285 ToolStatus::Failed,
286 ];
287 for status in statuses {
288 let json = serde_json::to_string(&status).unwrap();
289 let _: ToolStatus = serde_json::from_str(&json).unwrap();
290 }
291 }
292
293 #[test]
294 fn test_ping_pong_messages() {
295 let now = Utc::now();
296 let ping = ClientMessage::Ping { timestamp: now };
297 let json = serde_json::to_string(&ping).unwrap();
298 let restored: ClientMessage = serde_json::from_str(&json).unwrap();
299 match restored {
300 ClientMessage::Ping { timestamp } => {
301 assert_eq!(timestamp, now);
302 }
303 _ => panic!("Wrong variant"),
304 }
305
306 let pong = ServerMessage::Pong { timestamp: now };
307 let json = serde_json::to_string(&pong).unwrap();
308 let _: ServerMessage = serde_json::from_str(&json).unwrap();
309 }
310
311 #[test]
312 fn test_gateway_event_channel_message_received() {
313 let event = GatewayEvent::ChannelMessageReceived {
314 channel_type: "slack".into(),
315 message: "hello world".into(),
316 };
317 let json = serde_json::to_string(&event).unwrap();
318 assert!(json.contains("slack"));
319 let restored: GatewayEvent = serde_json::from_str(&json).unwrap();
320 match restored {
321 GatewayEvent::ChannelMessageReceived {
322 channel_type,
323 message,
324 } => {
325 assert_eq!(channel_type, "slack");
326 assert_eq!(message, "hello world");
327 }
328 _ => panic!("Wrong variant"),
329 }
330 }
331
332 #[test]
333 fn test_gateway_event_node_task_dispatched() {
334 let event = GatewayEvent::NodeTaskDispatched {
335 node_id: "node-1".into(),
336 task_name: "shell".into(),
337 };
338 let json = serde_json::to_string(&event).unwrap();
339 let restored: GatewayEvent = serde_json::from_str(&json).unwrap();
340 match restored {
341 GatewayEvent::NodeTaskDispatched { node_id, task_name } => {
342 assert_eq!(node_id, "node-1");
343 assert_eq!(task_name, "shell");
344 }
345 _ => panic!("Wrong variant"),
346 }
347 }
348
349 #[test]
350 fn test_gateway_event_agent_spawned_terminated() {
351 let spawned = GatewayEvent::AgentSpawned {
352 agent_id: "a1".into(),
353 name: "helper".into(),
354 };
355 let terminated = GatewayEvent::AgentTerminated {
356 agent_id: "a1".into(),
357 };
358 let json1 = serde_json::to_string(&spawned).unwrap();
359 let json2 = serde_json::to_string(&terminated).unwrap();
360 let _: GatewayEvent = serde_json::from_str(&json1).unwrap();
361 let _: GatewayEvent = serde_json::from_str(&json2).unwrap();
362 }
363
364 #[test]
365 fn test_server_message_channel_status() {
366 let msg = ServerMessage::ChannelStatus {
367 channels: vec![
368 ("telegram".into(), "connected".into()),
369 ("slack".into(), "disconnected".into()),
370 ],
371 };
372 let json = serde_json::to_string(&msg).unwrap();
373 let restored: ServerMessage = serde_json::from_str(&json).unwrap();
374 match restored {
375 ServerMessage::ChannelStatus { channels } => {
376 assert_eq!(channels.len(), 2);
377 assert_eq!(channels[0].0, "telegram");
378 }
379 _ => panic!("Wrong variant"),
380 }
381 }
382
383 #[test]
384 fn test_server_message_node_status() {
385 let msg = ServerMessage::NodeStatus {
386 nodes: vec![("macos-local".into(), "healthy".into())],
387 };
388 let json = serde_json::to_string(&msg).unwrap();
389 let restored: ServerMessage = serde_json::from_str(&json).unwrap();
390 match restored {
391 ServerMessage::NodeStatus { nodes } => {
392 assert_eq!(nodes.len(), 1);
393 assert_eq!(nodes[0].1, "healthy");
394 }
395 _ => panic!("Wrong variant"),
396 }
397 }
398
399 #[test]
400 fn test_client_message_list_channels_nodes() {
401 let lc = ClientMessage::ListChannels;
402 let ln = ClientMessage::ListNodes;
403 let json1 = serde_json::to_string(&lc).unwrap();
404 let json2 = serde_json::to_string(&ln).unwrap();
405 let _: ClientMessage = serde_json::from_str(&json1).unwrap();
406 let _: ClientMessage = serde_json::from_str(&json2).unwrap();
407 }
408}