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 Lifecycle,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub enum EventSource {
20 Tail,
21 Hook,
22 Proxy,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct Event {
27 pub session_id: String,
28 pub seq: u64,
29 pub ts_ms: u64,
30 pub ts_exact: bool,
31 pub kind: EventKind,
32 pub source: EventSource,
33 pub tool: Option<String>,
34 pub tool_call_id: Option<String>,
35 pub tokens_in: Option<u32>,
36 pub tokens_out: Option<u32>,
37 pub reasoning_tokens: Option<u32>,
38 pub cost_usd_e6: Option<i64>,
39 pub stop_reason: Option<String>,
40 pub latency_ms: Option<u32>,
41 pub ttft_ms: Option<u32>,
42 pub retry_count: Option<u16>,
43 pub context_used_tokens: Option<u32>,
44 pub context_max_tokens: Option<u32>,
45 pub cache_creation_tokens: Option<u32>,
46 pub cache_read_tokens: Option<u32>,
47 pub system_prompt_tokens: Option<u32>,
48 pub payload: serde_json::Value,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
52pub enum SessionStatus {
53 Running,
54 Waiting,
55 Idle,
56 Done,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct SessionRecord {
61 pub id: String,
62 pub agent: String,
63 pub model: Option<String>,
64 pub workspace: String,
65 pub started_at_ms: u64,
66 pub ended_at_ms: Option<u64>,
67 pub status: SessionStatus,
68 pub trace_path: String,
69 pub start_commit: Option<String>,
70 pub end_commit: Option<String>,
71 pub branch: Option<String>,
72 pub dirty_start: Option<bool>,
73 pub dirty_end: Option<bool>,
74 pub repo_binding_source: Option<String>,
75 pub prompt_fingerprint: Option<String>,
76 pub parent_session_id: Option<String>,
77 pub agent_version: Option<String>,
78 pub os: Option<String>,
79 pub arch: Option<String>,
80 pub repo_file_count: Option<u32>,
81 pub repo_total_loc: Option<u64>,
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use serde_json::json;
88
89 #[test]
90 fn event_serde_round_trip() {
91 let e = Event {
92 session_id: "s1".to_string(),
93 seq: 0,
94 ts_ms: 1000,
95 ts_exact: false,
96 kind: EventKind::ToolCall,
97 source: EventSource::Tail,
98 tool: Some("read_file".to_string()),
99 tool_call_id: Some("call_1".to_string()),
100 tokens_in: None,
101 tokens_out: None,
102 reasoning_tokens: None,
103 cost_usd_e6: None,
104 stop_reason: None,
105 latency_ms: None,
106 ttft_ms: None,
107 retry_count: None,
108 context_used_tokens: None,
109 context_max_tokens: None,
110 cache_creation_tokens: None,
111 cache_read_tokens: None,
112 system_prompt_tokens: None,
113 payload: json!({"path": "src/main.rs"}),
114 };
115 let s = serde_json::to_string(&e).unwrap();
116 let e2: Event = serde_json::from_str(&s).unwrap();
117 assert_eq!(e.session_id, e2.session_id);
118 assert_eq!(e.kind, e2.kind);
119 assert_eq!(e.tool, e2.tool);
120 }
121
122 #[test]
123 fn session_record_serde_round_trip() {
124 let r = SessionRecord {
125 id: "abc".to_string(),
126 agent: "cursor".to_string(),
127 model: Some("gpt-4".to_string()),
128 workspace: "/home/user/proj".to_string(),
129 started_at_ms: 0,
130 ended_at_ms: Some(9999),
131 status: SessionStatus::Done,
132 trace_path: "/tmp/abc".to_string(),
133 start_commit: Some("abc".to_string()),
134 end_commit: Some("def".to_string()),
135 branch: Some("main".to_string()),
136 dirty_start: Some(false),
137 dirty_end: Some(true),
138 repo_binding_source: Some("git".to_string()),
139 prompt_fingerprint: None,
140 parent_session_id: None,
141 agent_version: None,
142 os: None,
143 arch: None,
144 repo_file_count: None,
145 repo_total_loc: None,
146 };
147 let s = serde_json::to_string(&r).unwrap();
148 let r2: SessionRecord = serde_json::from_str(&s).unwrap();
149 assert_eq!(r.id, r2.id);
150 assert_eq!(r.status, r2.status);
151 assert_eq!(r.ended_at_ms, r2.ended_at_ms);
152 }
153}