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    /// Discriminated by `payload["type"]` (e.g. todo_write, mode_transition).
15    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}