Skip to main content

kaizen/core/
event.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2//! Core event + session-record types. Pure data, no IO.
3
4use 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}