1use crate::types::error::APIError;
6use crate::types::permission::{PermissionReply, PermissionRequest};
7use crate::types::session::Session;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct GlobalEventEnvelope {
13 pub directory: String,
15 pub payload: Event,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21#[serde(tag = "type")]
22pub enum Event {
23 #[serde(rename = "server.connected")]
26 ServerConnected {
27 #[serde(default)]
29 properties: serde_json::Value,
30 },
31
32 #[serde(rename = "server.heartbeat")]
34 ServerHeartbeat {
35 #[serde(default)]
37 properties: serde_json::Value,
38 },
39
40 #[serde(rename = "server.instance.disposed")]
42 ServerInstanceDisposed {
43 #[serde(default)]
45 properties: serde_json::Value,
46 },
47
48 #[serde(rename = "global.disposed")]
50 GlobalDisposed {
51 #[serde(default)]
53 properties: serde_json::Value,
54 },
55
56 #[serde(rename = "session.created")]
59 SessionCreated {
60 properties: SessionInfoProps,
62 },
63
64 #[serde(rename = "session.updated")]
66 SessionUpdated {
67 properties: SessionInfoProps,
69 },
70
71 #[serde(rename = "session.deleted")]
73 SessionDeleted {
74 properties: SessionInfoProps,
76 },
77
78 #[serde(rename = "session.diff")]
80 SessionDiff {
81 #[serde(default)]
83 properties: serde_json::Value,
84 },
85
86 #[serde(rename = "session.error")]
88 SessionError {
89 properties: SessionErrorProps,
91 },
92
93 #[serde(rename = "session.compacted")]
95 SessionCompacted {
96 #[serde(default)]
98 properties: serde_json::Value,
99 },
100
101 #[serde(rename = "session.status")]
103 SessionStatus {
104 #[serde(default)]
106 properties: serde_json::Value,
107 },
108
109 #[serde(rename = "session.idle")]
111 SessionIdle {
112 properties: SessionIdleProps,
114 },
115
116 #[serde(rename = "message.updated")]
119 MessageUpdated {
120 properties: MessageUpdatedProps,
122 },
123
124 #[serde(rename = "message.removed")]
126 MessageRemoved {
127 properties: MessageRemovedProps,
129 },
130
131 #[serde(rename = "message.part.updated")]
133 MessagePartUpdated {
134 properties: Box<MessagePartEventProps>,
136 },
137
138 #[serde(rename = "message.part.removed")]
140 MessagePartRemoved {
141 #[serde(default)]
143 properties: serde_json::Value,
144 },
145
146 #[serde(rename = "pty.created")]
149 PtyCreated {
150 #[serde(default)]
152 properties: serde_json::Value,
153 },
154
155 #[serde(rename = "pty.updated")]
157 PtyUpdated {
158 #[serde(default)]
160 properties: serde_json::Value,
161 },
162
163 #[serde(rename = "pty.exited")]
165 PtyExited {
166 #[serde(default)]
168 properties: serde_json::Value,
169 },
170
171 #[serde(rename = "pty.deleted")]
173 PtyDeleted {
174 #[serde(default)]
176 properties: serde_json::Value,
177 },
178
179 #[serde(rename = "permission.updated")]
182 PermissionUpdated {
183 #[serde(default)]
185 properties: serde_json::Value,
186 },
187
188 #[serde(rename = "permission.replied")]
190 PermissionReplied {
191 properties: PermissionRepliedProps,
193 },
194
195 #[serde(rename = "permission.asked")]
197 PermissionAsked {
198 properties: PermissionAskedProps,
200 },
201
202 #[serde(rename = "permission.replied-next")]
204 PermissionRepliedNext {
205 properties: PermissionRepliedProps,
207 },
208
209 #[serde(rename = "project.updated")]
212 ProjectUpdated {
213 #[serde(default)]
215 properties: serde_json::Value,
216 },
217
218 #[serde(rename = "file.edited")]
220 FileEdited {
221 #[serde(default)]
223 properties: serde_json::Value,
224 },
225
226 #[serde(rename = "file.watcher.updated")]
228 FileWatcherUpdated {
229 #[serde(default)]
231 properties: serde_json::Value,
232 },
233
234 #[serde(rename = "vcs.branch.updated")]
236 VcsBranchUpdated {
237 #[serde(default)]
239 properties: serde_json::Value,
240 },
241
242 #[serde(rename = "lsp.updated")]
245 LspUpdated {
246 #[serde(default)]
248 properties: serde_json::Value,
249 },
250
251 #[serde(rename = "lsp.client.diagnostics")]
253 LspClientDiagnostics {
254 #[serde(default)]
256 properties: serde_json::Value,
257 },
258
259 #[serde(rename = "command.executed")]
261 CommandExecuted {
262 #[serde(default)]
264 properties: serde_json::Value,
265 },
266
267 #[serde(rename = "mcp.tools.changed")]
269 McpToolsChanged {
270 #[serde(default)]
272 properties: serde_json::Value,
273 },
274
275 #[serde(rename = "installation.updated")]
278 InstallationUpdated {
279 #[serde(default)]
281 properties: serde_json::Value,
282 },
283
284 #[serde(rename = "installation.update-available")]
286 InstallationUpdateAvailable {
287 #[serde(default)]
289 properties: serde_json::Value,
290 },
291
292 #[serde(rename = "ide.installed")]
294 IdeInstalled {
295 #[serde(default)]
297 properties: serde_json::Value,
298 },
299
300 #[serde(rename = "tui.prompt.append")]
303 TuiPromptAppend {
304 #[serde(default)]
306 properties: serde_json::Value,
307 },
308
309 #[serde(rename = "tui.command.execute")]
311 TuiCommandExecute {
312 #[serde(default)]
314 properties: serde_json::Value,
315 },
316
317 #[serde(rename = "tui.toast.show")]
319 TuiToastShow {
320 #[serde(default)]
322 properties: serde_json::Value,
323 },
324
325 #[serde(rename = "tui.session.select")]
327 TuiSessionSelect {
328 #[serde(default)]
330 properties: serde_json::Value,
331 },
332
333 #[serde(rename = "todo.updated")]
336 TodoUpdated {
337 #[serde(default)]
339 properties: serde_json::Value,
340 },
341
342 #[serde(other)]
344 Unknown,
345}
346
347#[derive(Debug, Clone, Serialize, Deserialize)]
351#[serde(rename_all = "camelCase")]
352pub struct SessionInfoProps {
353 pub info: Session,
355 #[serde(flatten)]
357 pub extra: serde_json::Value,
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize)]
362#[serde(rename_all = "camelCase")]
363pub struct SessionIdleProps {
364 #[serde(default, alias = "sessionID")]
366 pub session_id: Option<String>,
367 #[serde(flatten)]
369 pub extra: serde_json::Value,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
374#[serde(untagged)]
375pub enum AssistantError {
376 Api(APIError),
378 Unknown(serde_json::Value),
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
384#[serde(rename_all = "camelCase")]
385pub struct SessionErrorProps {
386 #[serde(default, alias = "sessionID")]
388 pub session_id: Option<String>,
389 #[serde(default)]
391 pub error: Option<AssistantError>,
392 #[serde(flatten)]
394 pub extra: serde_json::Value,
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize)]
401#[serde(rename_all = "camelCase")]
402pub struct MessageUpdatedProps {
403 pub info: crate::types::message::MessageInfo,
405 #[serde(flatten)]
407 pub extra: serde_json::Value,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize)]
412#[serde(rename_all = "camelCase")]
413pub struct MessageRemovedProps {
414 #[serde(alias = "sessionID")]
416 pub session_id: String,
417 pub message_id: String,
419 #[serde(flatten)]
421 pub extra: serde_json::Value,
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
426#[serde(rename_all = "camelCase")]
427pub struct MessagePartEventProps {
428 #[serde(default, alias = "sessionID")]
430 pub session_id: Option<String>,
431 #[serde(default)]
433 pub message_id: Option<String>,
434 #[serde(default)]
436 pub index: Option<usize>,
437 #[serde(default)]
439 pub part: Option<crate::types::message::Part>,
440 #[serde(default)]
442 pub delta: Option<String>,
443 #[serde(flatten)]
445 pub extra: serde_json::Value,
446}
447
448#[derive(Debug, Clone, Serialize, Deserialize)]
452#[serde(rename_all = "camelCase")]
453pub struct PermissionAskedProps {
454 #[serde(flatten)]
456 pub request: PermissionRequest,
457}
458
459#[derive(Debug, Clone, Serialize, Deserialize)]
461#[serde(rename_all = "camelCase")]
462pub struct PermissionRepliedProps {
463 #[serde(alias = "sessionID")]
465 pub session_id: String,
466 pub request_id: String,
468 pub reply: PermissionReply,
470 #[serde(flatten)]
472 pub extra: serde_json::Value,
473}
474
475impl Event {
476 pub fn session_id(&self) -> Option<&str> {
478 match self {
479 Event::SessionCreated { properties } => Some(&properties.info.id),
480 Event::SessionUpdated { properties } => Some(&properties.info.id),
481 Event::SessionDeleted { properties } => Some(&properties.info.id),
482 Event::SessionIdle { properties } => properties.session_id.as_deref(),
483 Event::SessionError { properties } => properties.session_id.as_deref(),
484 Event::MessageUpdated { properties } => properties.info.session_id.as_deref(),
485 Event::MessageRemoved { properties } => Some(&properties.session_id),
486 Event::MessagePartUpdated { properties } => properties.session_id.as_deref(),
487 Event::PermissionAsked { properties } => properties.request.session_id.as_deref(),
488 Event::PermissionReplied { properties } => Some(&properties.session_id),
489 Event::PermissionRepliedNext { properties } => Some(&properties.session_id),
490 _ => None,
491 }
492 }
493
494 pub fn is_heartbeat(&self) -> bool {
496 matches!(self, Event::ServerHeartbeat { .. })
497 }
498
499 pub fn is_connected(&self) -> bool {
501 matches!(self, Event::ServerConnected { .. })
502 }
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508
509 #[test]
510 fn test_event_deserialize_session_created() {
511 let json = r#"{
512 "type": "session.created",
513 "properties": {
514 "info": {
515 "id": "sess-123",
516 "title": "Test Session",
517 "version": "1.0"
518 }
519 }
520 }"#;
521 let event: Event = serde_json::from_str(json).unwrap();
522 assert!(matches!(event, Event::SessionCreated { .. }));
523 assert_eq!(event.session_id(), Some("sess-123"));
524 }
525
526 #[test]
527 fn test_event_deserialize_heartbeat() {
528 let json = r#"{"type":"server.heartbeat","properties":{}}"#;
529 let event: Event = serde_json::from_str(json).unwrap();
530 assert!(matches!(event, Event::ServerHeartbeat { .. }));
531 assert!(event.is_heartbeat());
532 }
533
534 #[test]
535 fn test_event_deserialize_unknown() {
536 let json = r#"{"type":"some.future.event","properties":{}}"#;
537 let event: Event = serde_json::from_str(json).unwrap();
538 assert!(matches!(event, Event::Unknown));
539 }
540
541 #[test]
542 fn test_message_part_with_delta() {
543 let json = r#"{"type":"message.part.updated","properties":{"sessionId":"s1","messageId":"m1","delta":"Hello"}}"#;
544 let event: Event = serde_json::from_str(json).unwrap();
545 if let Event::MessagePartUpdated { properties } = &event {
546 assert_eq!(properties.delta, Some("Hello".to_string()));
547 } else {
548 panic!("Expected MessagePartUpdated");
549 }
550 }
551
552 #[test]
553 fn test_event_deserialize_pty_created() {
554 let json = r#"{"type":"pty.created","properties":{"id":"pty1"}}"#;
555 let event: Event = serde_json::from_str(json).unwrap();
556 assert!(matches!(event, Event::PtyCreated { .. }));
557 }
558
559 #[test]
560 fn test_event_deserialize_permission_asked() {
561 let json = r#"{
562 "type": "permission.asked",
563 "properties": {
564 "id": "req-123",
565 "sessionId": "sess-456",
566 "permission": "file.write",
567 "patterns": ["**/*.rs"]
568 }
569 }"#;
570 let event: Event = serde_json::from_str(json).unwrap();
571 assert!(matches!(event, Event::PermissionAsked { .. }));
572 assert_eq!(event.session_id(), Some("sess-456"));
573 }
574
575 #[test]
576 fn test_event_deserialize_permission_replied() {
577 let json = r#"{
578 "type": "permission.replied",
579 "properties": {
580 "sessionId": "sess-456",
581 "requestId": "req-123",
582 "reply": "always"
583 }
584 }"#;
585 let event: Event = serde_json::from_str(json).unwrap();
586 assert!(matches!(event, Event::PermissionReplied { .. }));
587 assert_eq!(event.session_id(), Some("sess-456"));
588 }
589
590 #[test]
591 fn test_event_deserialize_message_updated() {
592 let json = r#"{
593 "type": "message.updated",
594 "properties": {
595 "info": {
596 "id": "msg-123",
597 "sessionId": "sess-456",
598 "role": "assistant",
599 "time": {"created": 1234567890}
600 }
601 }
602 }"#;
603 let event: Event = serde_json::from_str(json).unwrap();
604 assert!(matches!(event, Event::MessageUpdated { .. }));
605 assert_eq!(event.session_id(), Some("sess-456"));
606 }
607
608 #[test]
609 fn test_event_deserialize_message_removed() {
610 let json = r#"{
611 "type": "message.removed",
612 "properties": {
613 "sessionId": "sess-456",
614 "messageId": "msg-123"
615 }
616 }"#;
617 let event: Event = serde_json::from_str(json).unwrap();
618 assert!(matches!(event, Event::MessageRemoved { .. }));
619 assert_eq!(event.session_id(), Some("sess-456"));
620 }
621
622 #[test]
623 fn test_event_deserialize_session_error() {
624 let json = r#"{
625 "type": "session.error",
626 "properties": {
627 "sessionId": "sess-456",
628 "error": {"message": "Something went wrong", "isRetryable": false}
629 }
630 }"#;
631 let event: Event = serde_json::from_str(json).unwrap();
632 if let Event::SessionError { properties } = &event {
633 assert!(properties.error.is_some());
634 if let Some(AssistantError::Api(err)) = &properties.error {
635 assert_eq!(err.message, "Something went wrong");
636 } else {
637 panic!("Expected APIError");
638 }
639 } else {
640 panic!("Expected SessionError");
641 }
642 }
643
644 #[test]
645 fn test_event_deserialize_session_idle_with_session_id_alias() {
646 let json = r#"{
647 "type": "session.idle",
648 "properties": {
649 "sessionID": "sess-456"
650 }
651 }"#;
652 let event: Event = serde_json::from_str(json).unwrap();
653 assert!(matches!(event, Event::SessionIdle { .. }));
654 assert_eq!(event.session_id(), Some("sess-456"));
655 }
656
657 #[test]
658 fn test_event_deserialize_message_removed_with_session_id_alias() {
659 let json = r#"{
660 "type": "message.removed",
661 "properties": {
662 "sessionID": "sess-456",
663 "messageId": "msg-123"
664 }
665 }"#;
666 let event: Event = serde_json::from_str(json).unwrap();
667 assert!(matches!(event, Event::MessageRemoved { .. }));
668 assert_eq!(event.session_id(), Some("sess-456"));
669 }
670
671 #[test]
672 fn test_event_deserialize_todo_updated() {
673 let json = r#"{"type":"todo.updated","properties":{}}"#;
674 let event: Event = serde_json::from_str(json).unwrap();
675 assert!(matches!(event, Event::TodoUpdated { .. }));
676 }
677
678 }