1use std::sync::atomic::{AtomicU64, Ordering};
2use std::time::{SystemTime, UNIX_EPOCH};
3
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::types::{AgentStatus, Metadata};
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct RunEventVersion(String);
12
13impl RunEventVersion {
14 pub fn v1() -> Self {
15 Self("v1".to_string())
16 }
17
18 pub fn as_str(&self) -> &str {
19 &self.0
20 }
21}
22
23impl Default for RunEventVersion {
24 fn default() -> Self {
25 Self::v1()
26 }
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(transparent)]
31pub struct EventId(String);
32
33impl EventId {
34 pub fn new() -> Self {
35 static NEXT_EVENT_ID: AtomicU64 = AtomicU64::new(1);
36 let sequence = NEXT_EVENT_ID.fetch_add(1, Ordering::Relaxed);
37 Self(format!("evt_{}_{}", timestamp_millis(), sequence))
38 }
39
40 pub fn as_str(&self) -> &str {
41 &self.0
42 }
43}
44
45impl Default for EventId {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52pub struct RunEvent {
53 pub version: RunEventVersion,
54 pub event_id: EventId,
55 pub run_id: String,
56 pub trace_id: String,
57 pub session_id: Option<String>,
58 pub parent_event_id: Option<String>,
59 pub parent_run_id: Option<String>,
60 pub created_at_ms: u128,
61 pub cycle_index: Option<u32>,
62 pub agent_name: Option<String>,
63 pub metadata: Metadata,
64 #[serde(flatten)]
65 pub payload: RunEventPayload,
66}
67
68impl RunEvent {
69 pub fn new(
70 run_id: impl Into<String>,
71 trace_id: impl Into<String>,
72 agent_name: impl Into<String>,
73 cycle_index: Option<u32>,
74 payload: RunEventPayload,
75 ) -> Self {
76 Self {
77 version: RunEventVersion::v1(),
78 event_id: EventId::new(),
79 run_id: run_id.into(),
80 trace_id: trace_id.into(),
81 session_id: None,
82 parent_event_id: None,
83 parent_run_id: None,
84 created_at_ms: timestamp_millis(),
85 cycle_index,
86 agent_name: Some(agent_name.into()),
87 metadata: Metadata::new(),
88 payload,
89 }
90 }
91
92 pub fn run_started(
93 run_id: impl Into<String>,
94 trace_id: impl Into<String>,
95 agent_name: impl Into<String>,
96 input: impl Into<String>,
97 ) -> Self {
98 Self::new(
99 run_id,
100 trace_id,
101 agent_name,
102 None,
103 RunEventPayload::RunStarted {
104 input: input.into(),
105 },
106 )
107 }
108
109 pub fn cycle_started(
110 run_id: impl Into<String>,
111 trace_id: impl Into<String>,
112 agent_name: impl Into<String>,
113 cycle_index: u32,
114 ) -> Self {
115 Self::new(
116 run_id,
117 trace_id,
118 agent_name,
119 Some(cycle_index),
120 RunEventPayload::CycleStarted,
121 )
122 }
123
124 pub fn assistant_delta(
125 run_id: impl Into<String>,
126 trace_id: impl Into<String>,
127 agent_name: impl Into<String>,
128 cycle_index: u32,
129 delta: impl Into<String>,
130 ) -> Self {
131 Self::new(
132 run_id,
133 trace_id,
134 agent_name,
135 Some(cycle_index),
136 RunEventPayload::AssistantDelta {
137 delta: delta.into(),
138 },
139 )
140 }
141
142 pub fn tool_call_started(
143 run_id: impl Into<String>,
144 trace_id: impl Into<String>,
145 agent_name: impl Into<String>,
146 cycle_index: u32,
147 tool_call_id: impl Into<String>,
148 tool_name: impl Into<String>,
149 arguments: Value,
150 ) -> Self {
151 Self::new(
152 run_id,
153 trace_id,
154 agent_name,
155 Some(cycle_index),
156 RunEventPayload::ToolCallStarted {
157 tool_call_id: tool_call_id.into(),
158 tool_name: tool_name.into(),
159 arguments,
160 },
161 )
162 }
163
164 pub fn tool_call_completed(
165 run_id: impl Into<String>,
166 trace_id: impl Into<String>,
167 agent_name: impl Into<String>,
168 cycle_index: Option<u32>,
169 tool_call_id: impl Into<String>,
170 tool_name: impl Into<String>,
171 status: ToolStatus,
172 ) -> Self {
173 Self::new(
174 run_id,
175 trace_id,
176 agent_name,
177 cycle_index,
178 RunEventPayload::ToolCallCompleted {
179 tool_call_id: tool_call_id.into(),
180 tool_name: tool_name.into(),
181 status,
182 },
183 )
184 }
185
186 pub fn approval_requested(
187 run_id: impl Into<String>,
188 trace_id: impl Into<String>,
189 agent_name: impl Into<String>,
190 request_id: impl Into<String>,
191 tool_call_id: impl Into<String>,
192 tool_name: impl Into<String>,
193 preview: impl Into<String>,
194 ) -> Self {
195 Self::new(
196 run_id,
197 trace_id,
198 agent_name,
199 None,
200 RunEventPayload::ApprovalRequested {
201 request_id: request_id.into(),
202 tool_call_id: tool_call_id.into(),
203 tool_name: tool_name.into(),
204 preview: preview.into(),
205 },
206 )
207 }
208
209 pub fn memory_compact_started(
210 run_id: impl Into<String>,
211 trace_id: impl Into<String>,
212 agent_name: impl Into<String>,
213 cycle_index: u32,
214 message_count: usize,
215 estimated_tokens: Option<u64>,
216 ) -> Self {
217 Self::new(
218 run_id,
219 trace_id,
220 agent_name,
221 Some(cycle_index),
222 RunEventPayload::MemoryCompactStarted {
223 message_count,
224 estimated_tokens,
225 },
226 )
227 }
228
229 pub fn memory_compact_completed(
230 run_id: impl Into<String>,
231 trace_id: impl Into<String>,
232 agent_name: impl Into<String>,
233 cycle_index: u32,
234 before_count: usize,
235 after_count: usize,
236 summary_tokens: Option<u64>,
237 ) -> Self {
238 Self::new(
239 run_id,
240 trace_id,
241 agent_name,
242 Some(cycle_index),
243 RunEventPayload::MemoryCompactCompleted {
244 before_count,
245 after_count,
246 summary_tokens,
247 },
248 )
249 }
250
251 pub fn handoff_completed(
252 run_id: impl Into<String>,
253 trace_id: impl Into<String>,
254 source_agent: impl Into<String>,
255 target_agent: impl Into<String>,
256 tool_call_id: impl Into<String>,
257 ) -> Self {
258 let source_agent = source_agent.into();
259 Self::new(
260 run_id,
261 trace_id,
262 source_agent.clone(),
263 None,
264 RunEventPayload::HandoffCompleted {
265 source_agent,
266 target_agent: target_agent.into(),
267 tool_call_id: tool_call_id.into(),
268 },
269 )
270 }
271
272 pub fn run_completed(
273 run_id: impl Into<String>,
274 trace_id: impl Into<String>,
275 agent_name: impl Into<String>,
276 status: AgentStatus,
277 ) -> Self {
278 Self::new(
279 run_id,
280 trace_id,
281 agent_name,
282 None,
283 RunEventPayload::RunCompleted { status },
284 )
285 }
286
287 pub fn run_failed(
288 run_id: impl Into<String>,
289 trace_id: impl Into<String>,
290 agent_name: impl Into<String>,
291 error: AgentErrorPayload,
292 ) -> Self {
293 Self::new(
294 run_id,
295 trace_id,
296 agent_name,
297 None,
298 RunEventPayload::RunFailed { error },
299 )
300 }
301
302 pub fn version(&self) -> &RunEventVersion {
303 &self.version
304 }
305
306 pub fn event_id(&self) -> &EventId {
307 &self.event_id
308 }
309
310 pub fn run_id(&self) -> &str {
311 &self.run_id
312 }
313
314 pub fn trace_id(&self) -> &str {
315 &self.trace_id
316 }
317
318 pub fn session_id(&self) -> Option<&str> {
319 self.session_id.as_deref()
320 }
321
322 pub fn parent_event_id(&self) -> Option<&str> {
323 self.parent_event_id.as_deref()
324 }
325
326 pub fn parent_run_id(&self) -> Option<&str> {
327 self.parent_run_id.as_deref()
328 }
329
330 pub fn created_at_ms(&self) -> u128 {
331 self.created_at_ms
332 }
333
334 pub fn cycle_index(&self) -> Option<u32> {
335 self.cycle_index
336 }
337
338 pub fn agent_name(&self) -> Option<&str> {
339 self.agent_name.as_deref()
340 }
341
342 pub fn payload(&self) -> &RunEventPayload {
343 &self.payload
344 }
345
346 pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
347 self.session_id = Some(session_id.into());
348 self
349 }
350
351 pub fn with_parent_event_id(mut self, parent_event_id: impl Into<String>) -> Self {
352 self.parent_event_id = Some(parent_event_id.into());
353 self
354 }
355
356 pub fn with_parent_run_id(mut self, parent_run_id: impl Into<String>) -> Self {
357 self.parent_run_id = Some(parent_run_id.into());
358 self
359 }
360
361 pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
362 self.metadata.insert(key.into(), value);
363 self
364 }
365}
366
367#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
368#[serde(tag = "type", rename_all = "snake_case")]
369pub enum RunEventPayload {
370 RunStarted {
371 input: String,
372 },
373 RunStateChanged {
374 state: String,
375 },
376 CycleStarted,
377 LlmStarted {
378 model: String,
379 },
380 AssistantDelta {
381 delta: String,
382 },
383 ToolCallStarted {
384 tool_call_id: String,
385 tool_name: String,
386 arguments: Value,
387 },
388 ApprovalRequested {
389 request_id: String,
390 tool_call_id: String,
391 tool_name: String,
392 preview: String,
393 },
394 ApprovalResolved {
395 request_id: String,
396 tool_call_id: String,
397 tool_name: String,
398 approved: bool,
399 },
400 ToolCallCompleted {
401 tool_call_id: String,
402 tool_name: String,
403 status: ToolStatus,
404 },
405 MemoryCompactStarted {
406 message_count: usize,
407 estimated_tokens: Option<u64>,
408 },
409 MemoryCompactCompleted {
410 before_count: usize,
411 after_count: usize,
412 summary_tokens: Option<u64>,
413 },
414 SubRunStarted {
415 parent_tool_call_id: String,
416 child_session_id: Option<String>,
417 task_id: Option<String>,
418 },
419 SubRunCompleted {
420 parent_tool_call_id: String,
421 status: AgentStatus,
422 final_output: Option<String>,
423 },
424 HandoffStarted {
425 source_agent: String,
426 target_agent: String,
427 tool_call_id: String,
428 },
429 HandoffCompleted {
430 source_agent: String,
431 target_agent: String,
432 tool_call_id: String,
433 },
434 SessionPersisted {
435 session_id: String,
436 },
437 RunCompleted {
438 status: AgentStatus,
439 },
440 RunFailed {
441 error: AgentErrorPayload,
442 },
443 RunCancelled {
444 reason: String,
445 },
446}
447
448#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
449#[serde(rename_all = "snake_case")]
450pub enum ToolStatus {
451 Started,
452 Success,
453 Error,
454 WaitResponse,
455}
456
457#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
458pub struct AgentErrorPayload {
459 pub message: String,
460 pub code: Option<String>,
461}
462
463impl AgentErrorPayload {
464 pub fn new(message: impl Into<String>) -> Self {
465 Self {
466 message: message.into(),
467 code: None,
468 }
469 }
470}
471
472fn timestamp_millis() -> u128 {
473 SystemTime::now()
474 .duration_since(UNIX_EPOCH)
475 .map(|duration| duration.as_millis())
476 .unwrap_or_default()
477}