Skip to main content

vv_agent/
events.rs

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}