1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use uuid::Uuid;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct EventMetadata {
11 pub event_id: Uuid,
13 pub timestamp: DateTime<Utc>,
15 pub correlation_id: Option<Uuid>,
17 pub session_id: Option<Uuid>,
19 pub user_id: Option<Uuid>,
21 pub source: String,
23}
24
25impl EventMetadata {
26 #[must_use]
28 pub fn new(source: impl Into<String>) -> Self {
29 Self {
30 event_id: Uuid::new_v4(),
31 timestamp: Utc::now(),
32 correlation_id: None,
33 session_id: None,
34 user_id: None,
35 source: source.into(),
36 }
37 }
38
39 #[must_use]
41 pub fn with_correlation_id(mut self, id: Uuid) -> Self {
42 self.correlation_id = Some(id);
43 self
44 }
45
46 #[must_use]
48 pub fn with_session_id(mut self, id: Uuid) -> Self {
49 self.session_id = Some(id);
50 self
51 }
52
53 #[must_use]
55 pub fn with_user_id(mut self, id: Uuid) -> Self {
56 self.user_id = Some(id);
57 self
58 }
59}
60
61impl Default for EventMetadata {
62 fn default() -> Self {
63 Self::new("unknown")
64 }
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
72#[serde(tag = "type", rename_all = "snake_case")]
73#[expect(clippy::large_enum_variant)]
74pub enum AstridEvent {
75 RuntimeStarted {
78 metadata: EventMetadata,
80 version: String,
82 },
83
84 RuntimeStopped {
86 metadata: EventMetadata,
88 reason: Option<String>,
90 },
91
92 AgentStarted {
94 metadata: EventMetadata,
96 agent_id: Uuid,
98 agent_name: String,
100 },
101
102 AgentStopped {
104 metadata: EventMetadata,
106 agent_id: Uuid,
108 reason: Option<String>,
110 },
111
112 SessionCreated {
115 metadata: EventMetadata,
117 session_id: Uuid,
119 },
120
121 SessionEnded {
123 metadata: EventMetadata,
125 session_id: Uuid,
127 reason: Option<String>,
129 },
130
131 SessionResumed {
133 metadata: EventMetadata,
135 session_id: Uuid,
137 },
138
139 MessageReceived {
142 metadata: EventMetadata,
144 message_id: Uuid,
146 platform: String,
148 },
149
150 MessageSent {
155 metadata: EventMetadata,
157 message_id: Uuid,
159 platform: String,
161 },
162
163 MessageProcessed {
165 metadata: EventMetadata,
167 message_id: Uuid,
169 duration_ms: u64,
171 },
172
173 PromptBuilding {
179 metadata: EventMetadata,
181 request_id: Uuid,
183 },
184
185 MessageSending {
189 metadata: EventMetadata,
191 message_id: Uuid,
193 platform: String,
195 },
196
197 ContextCompactionStarted {
199 metadata: EventMetadata,
201 session_id: Uuid,
203 message_count: u32,
205 },
206
207 ContextCompactionCompleted {
209 metadata: EventMetadata,
211 session_id: Uuid,
213 messages_remaining: u32,
215 },
216
217 SessionResetting {
219 metadata: EventMetadata,
221 session_id: Uuid,
223 },
224
225 ModelResolving {
229 metadata: EventMetadata,
231 request_id: Uuid,
233 provider: Option<String>,
235 model: Option<String>,
237 },
238
239 AgentLoopCompleted {
244 metadata: EventMetadata,
246 agent_id: Uuid,
248 turns: u32,
250 duration_ms: u64,
252 },
253
254 ToolResultPersisting {
259 metadata: EventMetadata,
261 call_id: Uuid,
263 tool_name: String,
265 },
266
267 LlmRequestStarted {
270 metadata: EventMetadata,
272 request_id: Uuid,
274 provider: String,
276 model: String,
278 },
279
280 LlmRequestCompleted {
282 metadata: EventMetadata,
284 request_id: Uuid,
286 success: bool,
288 input_tokens: Option<u32>,
290 output_tokens: Option<u32>,
292 duration_ms: u64,
294 },
295
296 LlmStreamStarted {
298 metadata: EventMetadata,
300 request_id: Uuid,
302 model: String,
304 },
305
306 LlmStreamChunk {
308 metadata: EventMetadata,
310 request_id: Uuid,
312 chunk_index: u32,
314 token_count: u32,
316 },
317
318 LlmStreamCompleted {
320 metadata: EventMetadata,
322 request_id: Uuid,
324 input_tokens: Option<u32>,
326 output_tokens: Option<u32>,
328 duration_ms: u64,
330 },
331
332 ToolCallStarted {
335 metadata: EventMetadata,
337 call_id: Uuid,
339 tool_name: String,
341 server_name: Option<String>,
343 },
344
345 ToolCallCompleted {
347 metadata: EventMetadata,
349 call_id: Uuid,
351 tool_name: String,
353 duration_ms: u64,
355 },
356
357 ToolCallFailed {
359 metadata: EventMetadata,
361 call_id: Uuid,
363 tool_name: String,
365 error: String,
367 duration_ms: u64,
369 },
370
371 McpServerConnected {
374 metadata: EventMetadata,
376 server_name: String,
378 protocol_version: String,
380 },
381
382 McpServerDisconnected {
384 metadata: EventMetadata,
386 server_name: String,
388 reason: Option<String>,
390 },
391
392 McpToolCalled {
394 metadata: EventMetadata,
396 server_name: String,
398 tool_name: String,
400 arguments: Option<Value>,
402 },
403
404 McpToolCompleted {
406 metadata: EventMetadata,
408 server_name: String,
410 tool_name: String,
412 success: bool,
414 duration_ms: u64,
416 },
417
418 SubAgentSpawned {
421 metadata: EventMetadata,
423 subagent_id: Uuid,
425 parent_id: Uuid,
427 task: String,
429 depth: u32,
431 },
432
433 SubAgentProgress {
435 metadata: EventMetadata,
437 subagent_id: Uuid,
439 message: String,
441 },
442
443 SubAgentCompleted {
445 metadata: EventMetadata,
447 subagent_id: Uuid,
449 duration_ms: u64,
451 },
452
453 SubAgentFailed {
455 metadata: EventMetadata,
457 subagent_id: Uuid,
459 error: String,
461 duration_ms: u64,
463 },
464
465 SubAgentCancelled {
467 metadata: EventMetadata,
469 subagent_id: Uuid,
471 reason: Option<String>,
473 },
474
475 CapabilityGranted {
478 metadata: EventMetadata,
480 capability_id: Uuid,
482 resource: String,
484 action: String,
486 },
487
488 CapabilityRevoked {
490 metadata: EventMetadata,
492 capability_id: Uuid,
494 reason: Option<String>,
496 },
497
498 CapabilityChecked {
500 metadata: EventMetadata,
502 resource: String,
504 action: String,
506 allowed: bool,
508 },
509
510 AuthorizationDenied {
512 metadata: EventMetadata,
514 resource: String,
516 action: String,
518 reason: String,
520 },
521
522 SecurityViolation {
524 metadata: EventMetadata,
526 violation_type: String,
528 details: String,
530 },
531
532 ApprovalRequested {
535 metadata: EventMetadata,
537 request_id: Uuid,
539 resource: String,
541 action: String,
543 description: String,
545 },
546
547 ApprovalGranted {
549 metadata: EventMetadata,
551 request_id: Uuid,
553 duration: Option<String>,
555 },
556
557 ApprovalDenied {
559 metadata: EventMetadata,
561 request_id: Uuid,
563 reason: Option<String>,
565 },
566
567 BudgetAllocated {
570 metadata: EventMetadata,
572 budget_id: Uuid,
574 amount_cents: u64,
576 currency: String,
578 },
579
580 BudgetWarning {
582 metadata: EventMetadata,
584 budget_id: Uuid,
586 remaining_cents: u64,
588 percent_used: f64,
590 },
591
592 BudgetExceeded {
594 metadata: EventMetadata,
596 budget_id: Uuid,
598 overage_cents: u64,
600 },
601
602 CapsuleLoaded {
605 metadata: EventMetadata,
607 capsule_id: String,
609 capsule_name: String,
611 },
612
613 CapsuleFailed {
615 metadata: EventMetadata,
617 capsule_id: String,
619 error: String,
621 },
622
623 CapsuleUnloaded {
625 metadata: EventMetadata,
627 capsule_id: String,
629 capsule_name: String,
631 },
632
633 KernelStarted {
636 metadata: EventMetadata,
638 version: String,
640 },
641
642 KernelShutdown {
644 metadata: EventMetadata,
646 reason: Option<String>,
648 },
649
650 ConfigReloaded {
652 metadata: EventMetadata,
654 },
655
656 ConfigChanged {
658 metadata: EventMetadata,
660 key: String,
662 },
663
664 HealthCheckCompleted {
666 metadata: EventMetadata,
668 healthy: bool,
670 checks_performed: u32,
672 checks_failed: u32,
674 },
675
676 AuditEntryCreated {
679 metadata: EventMetadata,
681 entry_id: Uuid,
683 entry_type: String,
685 },
686
687 ErrorOccurred {
690 metadata: EventMetadata,
692 code: String,
694 message: String,
696 stack_trace: Option<String>,
698 },
699
700 Ipc {
703 metadata: EventMetadata,
705 message: crate::ipc::IpcMessage,
707 },
708
709 Custom {
712 metadata: EventMetadata,
714 name: String,
716 data: Value,
718 },
719}
720
721impl AstridEvent {
722 #[must_use]
724 pub fn metadata(&self) -> &EventMetadata {
725 match self {
726 Self::RuntimeStarted { metadata, .. }
727 | Self::RuntimeStopped { metadata, .. }
728 | Self::AgentStarted { metadata, .. }
729 | Self::AgentStopped { metadata, .. }
730 | Self::SessionCreated { metadata, .. }
731 | Self::SessionEnded { metadata, .. }
732 | Self::SessionResumed { metadata, .. }
733 | Self::PromptBuilding { metadata, .. }
734 | Self::MessageSending { metadata, .. }
735 | Self::ContextCompactionStarted { metadata, .. }
736 | Self::ContextCompactionCompleted { metadata, .. }
737 | Self::SessionResetting { metadata, .. }
738 | Self::ModelResolving { metadata, .. }
739 | Self::AgentLoopCompleted { metadata, .. }
740 | Self::ToolResultPersisting { metadata, .. }
741 | Self::MessageReceived { metadata, .. }
742 | Self::MessageSent { metadata, .. }
743 | Self::MessageProcessed { metadata, .. }
744 | Self::LlmRequestStarted { metadata, .. }
745 | Self::LlmRequestCompleted { metadata, .. }
746 | Self::LlmStreamStarted { metadata, .. }
747 | Self::LlmStreamChunk { metadata, .. }
748 | Self::LlmStreamCompleted { metadata, .. }
749 | Self::ToolCallStarted { metadata, .. }
750 | Self::ToolCallCompleted { metadata, .. }
751 | Self::ToolCallFailed { metadata, .. }
752 | Self::McpServerConnected { metadata, .. }
753 | Self::McpServerDisconnected { metadata, .. }
754 | Self::McpToolCalled { metadata, .. }
755 | Self::McpToolCompleted { metadata, .. }
756 | Self::SubAgentSpawned { metadata, .. }
757 | Self::SubAgentProgress { metadata, .. }
758 | Self::SubAgentCompleted { metadata, .. }
759 | Self::SubAgentFailed { metadata, .. }
760 | Self::SubAgentCancelled { metadata, .. }
761 | Self::CapsuleLoaded { metadata, .. }
762 | Self::CapsuleFailed { metadata, .. }
763 | Self::CapsuleUnloaded { metadata, .. }
764 | Self::CapabilityGranted { metadata, .. }
765 | Self::CapabilityRevoked { metadata, .. }
766 | Self::CapabilityChecked { metadata, .. }
767 | Self::AuthorizationDenied { metadata, .. }
768 | Self::SecurityViolation { metadata, .. }
769 | Self::ApprovalRequested { metadata, .. }
770 | Self::ApprovalGranted { metadata, .. }
771 | Self::ApprovalDenied { metadata, .. }
772 | Self::BudgetAllocated { metadata, .. }
773 | Self::BudgetWarning { metadata, .. }
774 | Self::BudgetExceeded { metadata, .. }
775 | Self::KernelStarted { metadata, .. }
776 | Self::KernelShutdown { metadata, .. }
777 | Self::ConfigReloaded { metadata, .. }
778 | Self::ConfigChanged { metadata, .. }
779 | Self::HealthCheckCompleted { metadata, .. }
780 | Self::AuditEntryCreated { metadata, .. }
781 | Self::ErrorOccurred { metadata, .. }
782 | Self::Ipc { metadata, .. }
783 | Self::Custom { metadata, .. } => metadata,
784 }
785 }
786
787 #[must_use]
789 pub fn event_type(&self) -> &'static str {
790 match self {
791 Self::RuntimeStarted { .. } => "astrid.v1.lifecycle.runtime_started",
793 Self::RuntimeStopped { .. } => "astrid.v1.lifecycle.runtime_stopped",
794 Self::AgentStarted { .. } => "astrid.v1.lifecycle.agent_started",
795 Self::AgentStopped { .. } => "astrid.v1.lifecycle.agent_stopped",
796 Self::SessionCreated { .. } => "astrid.v1.lifecycle.session_created",
798 Self::SessionEnded { .. } => "astrid.v1.lifecycle.session_ended",
799 Self::SessionResumed { .. } => "astrid.v1.lifecycle.session_resumed",
800 Self::PromptBuilding { .. } => "astrid.v1.lifecycle.prompt_building",
802 Self::MessageSending { .. } => "astrid.v1.lifecycle.message_sending",
803 Self::ContextCompactionStarted { .. } => {
804 "astrid.v1.lifecycle.context_compaction_started"
805 },
806 Self::ContextCompactionCompleted { .. } => {
807 "astrid.v1.lifecycle.context_compaction_completed"
808 },
809 Self::SessionResetting { .. } => "astrid.v1.lifecycle.session_resetting",
810 Self::ModelResolving { .. } => "astrid.v1.lifecycle.model_resolving",
811 Self::AgentLoopCompleted { .. } => "astrid.v1.lifecycle.agent_loop_completed",
812 Self::ToolResultPersisting { .. } => "astrid.v1.lifecycle.tool_result_persisting",
813 Self::MessageReceived { .. } => "astrid.v1.lifecycle.message_received",
815 Self::MessageSent { .. } => "astrid.v1.lifecycle.message_sent",
816 Self::MessageProcessed { .. } => "astrid.v1.lifecycle.message_processed",
817 Self::LlmRequestStarted { .. } => "astrid.v1.lifecycle.llm_request_started",
819 Self::LlmRequestCompleted { .. } => "astrid.v1.lifecycle.llm_request_completed",
820 Self::LlmStreamStarted { .. } => "astrid.v1.lifecycle.llm_stream_started",
821 Self::LlmStreamChunk { .. } => "astrid.v1.lifecycle.llm_stream_chunk",
822 Self::LlmStreamCompleted { .. } => "astrid.v1.lifecycle.llm_stream_completed",
823 Self::ToolCallStarted { .. } => "astrid.v1.lifecycle.tool_call_started",
825 Self::ToolCallCompleted { .. } => "astrid.v1.lifecycle.tool_call_completed",
826 Self::ToolCallFailed { .. } => "astrid.v1.lifecycle.tool_call_failed",
827 Self::McpServerConnected { .. } => "astrid.v1.lifecycle.mcp_server_connected",
829 Self::McpServerDisconnected { .. } => "astrid.v1.lifecycle.mcp_server_disconnected",
830 Self::McpToolCalled { .. } => "astrid.v1.lifecycle.mcp_tool_called",
831 Self::McpToolCompleted { .. } => "astrid.v1.lifecycle.mcp_tool_completed",
832 Self::SubAgentSpawned { .. } => "astrid.v1.lifecycle.sub_agent_spawned",
834 Self::SubAgentProgress { .. } => "astrid.v1.lifecycle.sub_agent_progress",
835 Self::SubAgentCompleted { .. } => "astrid.v1.lifecycle.sub_agent_completed",
836 Self::SubAgentFailed { .. } => "astrid.v1.lifecycle.sub_agent_failed",
837 Self::SubAgentCancelled { .. } => "astrid.v1.lifecycle.sub_agent_cancelled",
838 Self::CapsuleLoaded { .. } => "astrid.v1.lifecycle.capsule_loaded",
840 Self::CapsuleFailed { .. } => "astrid.v1.lifecycle.capsule_failed",
841 Self::CapsuleUnloaded { .. } => "astrid.v1.lifecycle.capsule_unloaded",
842 Self::CapabilityGranted { .. } => "astrid.v1.lifecycle.capability_granted",
844 Self::CapabilityRevoked { .. } => "astrid.v1.lifecycle.capability_revoked",
845 Self::CapabilityChecked { .. } => "astrid.v1.lifecycle.capability_checked",
846 Self::AuthorizationDenied { .. } => "astrid.v1.lifecycle.authorization_denied",
847 Self::SecurityViolation { .. } => "astrid.v1.lifecycle.security_violation",
848 Self::ApprovalRequested { .. } => "astrid.v1.lifecycle.approval_requested",
850 Self::ApprovalGranted { .. } => "astrid.v1.lifecycle.approval_granted",
851 Self::ApprovalDenied { .. } => "astrid.v1.lifecycle.approval_denied",
852 Self::BudgetAllocated { .. } => "astrid.v1.lifecycle.budget_allocated",
854 Self::BudgetWarning { .. } => "astrid.v1.lifecycle.budget_warning",
855 Self::BudgetExceeded { .. } => "astrid.v1.lifecycle.budget_exceeded",
856 Self::KernelStarted { .. } => "astrid.v1.lifecycle.kernel_started",
858 Self::KernelShutdown { .. } => "astrid.v1.lifecycle.kernel_shutdown",
859 Self::ConfigReloaded { .. } => "astrid.v1.lifecycle.config_reloaded",
860 Self::ConfigChanged { .. } => "astrid.v1.lifecycle.config_changed",
861 Self::HealthCheckCompleted { .. } => "astrid.v1.lifecycle.health_check_completed",
862 Self::AuditEntryCreated { .. } => "astrid.v1.lifecycle.audit_entry_created",
864 Self::ErrorOccurred { .. } => "astrid.v1.lifecycle.error_occurred",
866 Self::Ipc { .. } => "ipc",
868 Self::Custom { .. } => "custom",
870 }
871 }
872
873 #[cfg(test)]
875 #[must_use]
876 pub(crate) fn is_security_event(&self) -> bool {
877 matches!(
878 self,
879 Self::CapabilityGranted { .. }
880 | Self::CapabilityRevoked { .. }
881 | Self::CapabilityChecked { .. }
882 | Self::AuthorizationDenied { .. }
883 | Self::SecurityViolation { .. }
884 | Self::ApprovalRequested { .. }
885 | Self::ApprovalGranted { .. }
886 | Self::ApprovalDenied { .. }
887 )
888 }
889}
890
891#[cfg(test)]
892mod tests {
893 use super::*;
894
895 #[test]
896 fn test_event_metadata_creation() {
897 let meta = EventMetadata::new("test_source");
898 assert_eq!(meta.source, "test_source");
899 assert!(meta.correlation_id.is_none());
900 assert!(meta.session_id.is_none());
901 assert!(meta.user_id.is_none());
902 }
903
904 #[test]
905 fn test_event_metadata_builder() {
906 let correlation = Uuid::new_v4();
907 let session = Uuid::new_v4();
908 let user = Uuid::new_v4();
909
910 let meta = EventMetadata::new("test")
911 .with_correlation_id(correlation)
912 .with_session_id(session)
913 .with_user_id(user);
914
915 assert_eq!(meta.correlation_id, Some(correlation));
916 assert_eq!(meta.session_id, Some(session));
917 assert_eq!(meta.user_id, Some(user));
918 }
919
920 #[test]
921 fn test_event_type() {
922 let event = AstridEvent::RuntimeStarted {
923 metadata: EventMetadata::new("runtime"),
924 version: "0.1.0".to_string(),
925 };
926 assert_eq!(event.event_type(), "astrid.v1.lifecycle.runtime_started");
927 }
928
929 #[test]
930 fn test_security_event_detection() {
931 let security_event = AstridEvent::CapabilityGranted {
932 metadata: EventMetadata::new("security"),
933 capability_id: Uuid::new_v4(),
934 resource: "tool:test".to_string(),
935 action: "execute".to_string(),
936 };
937 assert!(security_event.is_security_event());
938
939 let non_security_event = AstridEvent::RuntimeStarted {
940 metadata: EventMetadata::new("runtime"),
941 version: "0.1.0".to_string(),
942 };
943 assert!(!non_security_event.is_security_event());
944 }
945
946 #[test]
947 fn test_event_serialization() {
948 let event = AstridEvent::McpToolCalled {
949 metadata: EventMetadata::new("mcp"),
950 server_name: "filesystem".to_string(),
951 tool_name: "read_file".to_string(),
952 arguments: Some(serde_json::json!({"path": "/tmp/test.txt"})),
953 };
954
955 let json = serde_json::to_string(&event).unwrap();
956 assert!(json.contains("mcp_tool_called"));
957 assert!(json.contains("filesystem"));
958 }
959}