1use serde_json::Value;
19
20#[derive(Debug, Clone, PartialEq)]
21pub enum EventKind {
22 Started,
23 ToolCalled,
24 ToolResult,
25 Response,
26 Error,
27 Finished,
28 Custom(String),
29}
30
31impl EventKind {
32 pub fn label(&self) -> &str {
33 match self {
34 EventKind::Started => "STARTED",
35 EventKind::ToolCalled => "TOOL_CALLED",
36 EventKind::ToolResult => "TOOL_RESULT",
37 EventKind::Response => "RESPONSE",
38 EventKind::Error => "ERROR",
39 EventKind::Finished => "FINISHED",
40 EventKind::Custom(s) => s.as_str(),
41 }
42 }
43
44 pub fn is_terminal(&self) -> bool {
45 matches!(self, EventKind::Finished | EventKind::Error)
46 }
47}
48
49#[derive(Debug, Clone)]
50pub struct Event {
51 pub seq: usize,
52 pub run_id: String,
53 pub kind: EventKind,
54 pub message: String,
55 pub metadata: Option<Value>,
56}
57
58#[derive(Debug)]
60pub struct EventLog {
61 pub run_id: String,
62 events: Vec<Event>,
63}
64
65impl EventLog {
66 pub fn new(run_id: impl Into<String>) -> Self {
67 Self { run_id: run_id.into(), events: Vec::new() }
68 }
69
70 fn push(&mut self, kind: EventKind, message: impl Into<String>, meta: Option<Value>) {
71 let seq = self.events.len();
72 self.events.push(Event {
73 seq,
74 run_id: self.run_id.clone(),
75 kind,
76 message: message.into(),
77 metadata: meta,
78 });
79 }
80
81 pub fn started(&mut self) { self.push(EventKind::Started, "agent started", None); }
82
83 pub fn tool_called(&mut self, tool: impl Into<String>, args: Value) {
84 let tool = tool.into();
85 let msg = format!("calling {}", tool);
86 self.push(EventKind::ToolCalled, msg, Some(args));
87 }
88
89 pub fn tool_result(&mut self, tool: impl Into<String>, result: Value) {
90 let msg = format!("result from {}", tool.into());
91 self.push(EventKind::ToolResult, msg, Some(result));
92 }
93
94 pub fn response(&mut self, text: impl Into<String>) {
95 self.push(EventKind::Response, text, None);
96 }
97
98 pub fn error(&mut self, err: impl Into<String>) {
99 self.push(EventKind::Error, err, None);
100 }
101
102 pub fn finished(&mut self, reason: impl Into<String>) {
103 self.push(EventKind::Finished, reason, None);
104 }
105
106 pub fn custom(&mut self, label: impl Into<String>, msg: impl Into<String>) {
107 self.push(EventKind::Custom(label.into()), msg, None);
108 }
109
110 pub fn len(&self) -> usize { self.events.len() }
111 pub fn is_empty(&self) -> bool { self.events.is_empty() }
112 pub fn events(&self) -> &[Event] { &self.events }
113
114 pub fn by_kind(&self, kind: &EventKind) -> Vec<&Event> {
115 self.events.iter().filter(|e| &e.kind == kind).collect()
116 }
117
118 pub fn has_error(&self) -> bool {
119 self.events.iter().any(|e| e.kind == EventKind::Error)
120 }
121
122 pub fn is_finished(&self) -> bool {
123 self.events.iter().any(|e| e.kind.is_terminal())
124 }
125
126 pub fn last(&self) -> Option<&Event> { self.events.last() }
127
128 pub fn tool_calls(&self) -> Vec<&Event> {
129 self.by_kind(&EventKind::ToolCalled)
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use serde_json::json;
137
138 #[test]
139 fn empty_log() {
140 let log = EventLog::new("r1");
141 assert!(log.is_empty());
142 assert_eq!(log.run_id, "r1");
143 }
144
145 #[test]
146 fn started_adds_event() {
147 let mut log = EventLog::new("r1");
148 log.started();
149 assert_eq!(log.len(), 1);
150 assert_eq!(log.events()[0].kind, EventKind::Started);
151 }
152
153 #[test]
154 fn tool_called_has_metadata() {
155 let mut log = EventLog::new("r1");
156 log.tool_called("search", json!({"q": "rust"}));
157 let e = &log.events()[0];
158 assert!(e.metadata.is_some());
159 }
160
161 #[test]
162 fn tool_result_recorded() {
163 let mut log = EventLog::new("r1");
164 log.tool_result("search", json!(["result1"]));
165 assert_eq!(log.by_kind(&EventKind::ToolResult).len(), 1);
166 }
167
168 #[test]
169 fn response_event() {
170 let mut log = EventLog::new("r1");
171 log.response("Hello!");
172 assert_eq!(log.events()[0].message, "Hello!");
173 }
174
175 #[test]
176 fn error_detected() {
177 let mut log = EventLog::new("r1");
178 log.error("timeout");
179 assert!(log.has_error());
180 }
181
182 #[test]
183 fn finished_is_terminal() {
184 let mut log = EventLog::new("r1");
185 log.finished("success");
186 assert!(log.is_finished());
187 }
188
189 #[test]
190 fn seq_increments() {
191 let mut log = EventLog::new("r1");
192 log.started();
193 log.response("ok");
194 log.finished("done");
195 let seqs: Vec<usize> = log.events().iter().map(|e| e.seq).collect();
196 assert_eq!(seqs, vec![0, 1, 2]);
197 }
198
199 #[test]
200 fn by_kind_filter() {
201 let mut log = EventLog::new("r1");
202 log.tool_called("a", json!({}));
203 log.tool_called("b", json!({}));
204 log.response("ok");
205 assert_eq!(log.tool_calls().len(), 2);
206 }
207
208 #[test]
209 fn last_event() {
210 let mut log = EventLog::new("r1");
211 log.started();
212 log.finished("done");
213 assert_eq!(log.last().unwrap().kind, EventKind::Finished);
214 }
215
216 #[test]
217 fn custom_event() {
218 let mut log = EventLog::new("r1");
219 log.custom("THINKING", "deciding what to do");
220 assert_eq!(log.events()[0].kind, EventKind::Custom("THINKING".into()));
221 }
222
223 #[test]
224 fn no_error_by_default() {
225 let log = EventLog::new("r1");
226 assert!(!log.has_error());
227 }
228
229 #[test]
230 fn not_finished_without_terminal_event() {
231 let mut log = EventLog::new("r1");
232 log.started();
233 log.response("hello");
234 assert!(!log.is_finished());
235 }
236}