1use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub enum EventKind {
8 ToolCall,
9 ToolResult,
10 Message,
11 Error,
12 Cost,
13 Hook,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub enum EventSource {
18 Tail,
19 Hook,
20 Proxy,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct Event {
25 pub session_id: String,
26 pub seq: u64,
27 pub ts_ms: u64,
28 pub ts_exact: bool,
29 pub kind: EventKind,
30 pub source: EventSource,
31 pub tool: Option<String>,
32 pub tool_call_id: Option<String>,
33 pub tokens_in: Option<u32>,
34 pub tokens_out: Option<u32>,
35 pub reasoning_tokens: Option<u32>,
36 pub cost_usd_e6: Option<i64>,
37 pub payload: serde_json::Value,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub enum SessionStatus {
42 Running,
43 Waiting,
44 Idle,
45 Done,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct SessionRecord {
50 pub id: String,
51 pub agent: String,
52 pub model: Option<String>,
53 pub workspace: String,
54 pub started_at_ms: u64,
55 pub ended_at_ms: Option<u64>,
56 pub status: SessionStatus,
57 pub trace_path: String,
58 pub start_commit: Option<String>,
59 pub end_commit: Option<String>,
60 pub branch: Option<String>,
61 pub dirty_start: Option<bool>,
62 pub dirty_end: Option<bool>,
63 pub repo_binding_source: Option<String>,
64 pub prompt_fingerprint: Option<String>,
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use serde_json::json;
71
72 #[test]
73 fn event_serde_round_trip() {
74 let e = Event {
75 session_id: "s1".to_string(),
76 seq: 0,
77 ts_ms: 1000,
78 ts_exact: false,
79 kind: EventKind::ToolCall,
80 source: EventSource::Tail,
81 tool: Some("read_file".to_string()),
82 tool_call_id: Some("call_1".to_string()),
83 tokens_in: None,
84 tokens_out: None,
85 reasoning_tokens: None,
86 cost_usd_e6: None,
87 payload: json!({"path": "src/main.rs"}),
88 };
89 let s = serde_json::to_string(&e).unwrap();
90 let e2: Event = serde_json::from_str(&s).unwrap();
91 assert_eq!(e.session_id, e2.session_id);
92 assert_eq!(e.kind, e2.kind);
93 assert_eq!(e.tool, e2.tool);
94 }
95
96 #[test]
97 fn session_record_serde_round_trip() {
98 let r = SessionRecord {
99 id: "abc".to_string(),
100 agent: "cursor".to_string(),
101 model: Some("gpt-4".to_string()),
102 workspace: "/home/user/proj".to_string(),
103 started_at_ms: 0,
104 ended_at_ms: Some(9999),
105 status: SessionStatus::Done,
106 trace_path: "/tmp/abc".to_string(),
107 start_commit: Some("abc".to_string()),
108 end_commit: Some("def".to_string()),
109 branch: Some("main".to_string()),
110 dirty_start: Some(false),
111 dirty_end: Some(true),
112 repo_binding_source: Some("git".to_string()),
113 prompt_fingerprint: None,
114 };
115 let s = serde_json::to_string(&r).unwrap();
116 let r2: SessionRecord = serde_json::from_str(&s).unwrap();
117 assert_eq!(r.id, r2.id);
118 assert_eq!(r.status, r2.status);
119 assert_eq!(r.ended_at_ms, r2.ended_at_ms);
120 }
121}