codewhale_protocol/
event_msg.rs1use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11use crate::ids::{SessionId, ThreadId};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(tag = "event", rename_all = "snake_case")]
21pub enum EventMsg {
22 TurnStarted {
23 thread_id: ThreadId,
24 session_id: SessionId,
25 turn_id: String,
26 },
27 ResponseDelta {
28 thread_id: ThreadId,
29 session_id: SessionId,
30 delta: String,
31 #[serde(default)]
32 channel: String,
33 },
34 ToolCallStarted {
35 thread_id: ThreadId,
36 session_id: SessionId,
37 tool_call_id: String,
38 tool_name: String,
39 input: Value,
40 },
41 ToolCallComplete {
42 thread_id: ThreadId,
43 session_id: SessionId,
44 tool_call_id: String,
45 tool_name: String,
46 result: Value,
47 },
48 TurnComplete {
49 thread_id: ThreadId,
50 session_id: SessionId,
51 turn_id: String,
52 status: String,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 error: Option<String>,
55 },
56 TurnUsage {
57 thread_id: ThreadId,
58 session_id: SessionId,
59 input_tokens: u32,
60 output_tokens: u32,
61 },
62 CompactionStarted {
63 thread_id: ThreadId,
64 session_id: SessionId,
65 message: String,
66 },
67 CompactionCompleted {
68 thread_id: ThreadId,
69 session_id: SessionId,
70 message: String,
71 },
72 Error {
73 thread_id: ThreadId,
74 session_id: SessionId,
75 message: String,
76 },
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct EventEnvelope {
84 pub seq: u64,
85 pub thread_id: ThreadId,
86 pub session_id: SessionId,
87 pub turn_id: Option<String>,
88 pub event: EventMsg,
89}
90
91impl EventMsg {
92 #[must_use]
93 pub fn kind_str(&self) -> &'static str {
94 match self {
95 Self::TurnStarted { .. } => "turn_started",
96 Self::ResponseDelta { .. } => "response_delta",
97 Self::ToolCallStarted { .. } => "tool_call_started",
98 Self::ToolCallComplete { .. } => "tool_call_complete",
99 Self::TurnComplete { .. } => "turn_complete",
100 Self::TurnUsage { .. } => "turn_usage",
101 Self::CompactionStarted { .. } => "compaction_started",
102 Self::CompactionCompleted { .. } => "compaction_completed",
103 Self::Error { .. } => "error",
104 }
105 }
106
107 #[must_use]
108 pub fn thread_id(&self) -> &ThreadId {
109 match self {
110 Self::TurnStarted { thread_id, .. }
111 | Self::ResponseDelta { thread_id, .. }
112 | Self::ToolCallStarted { thread_id, .. }
113 | Self::ToolCallComplete { thread_id, .. }
114 | Self::TurnComplete { thread_id, .. }
115 | Self::TurnUsage { thread_id, .. }
116 | Self::CompactionStarted { thread_id, .. }
117 | Self::CompactionCompleted { thread_id, .. }
118 | Self::Error { thread_id, .. } => thread_id,
119 }
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn event_msg_roundtrip() {
129 let msg = EventMsg::TurnComplete {
130 thread_id: ThreadId::new(),
131 session_id: SessionId::new(),
132 turn_id: "turn-1".into(),
133 status: "completed".into(),
134 error: None,
135 };
136 let json = serde_json::to_string(&msg).unwrap();
137 let back: EventMsg = serde_json::from_str(&json).unwrap();
138 assert_eq!(back.kind_str(), "turn_complete");
139 }
140}