agtrace_types/event/event.rs
1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use uuid::Uuid;
5
6use super::payload::EventPayload;
7use super::stream::StreamId;
8
9#[cfg(test)]
10use super::payload::UserPayload;
11
12// NOTE: Schema Design Goals
13//
14// 1. Normalization: Abstract provider-specific quirks into unified time-series events
15// - Gemini: Unfold nested batch records into sequential events
16// - Codex: Align async token notifications and eliminate echo duplicates
17// - Claude: Extract embedded usage into independent events
18//
19// 2. Observability: Enable accurate cost/performance tracking
20// - Token: Sidecar pattern + incremental detection for precise billing (no double-counting)
21// - Latency: Measure turnaround time (T_req → T_res) from user perspective
22//
23// 3. Replayability: Reconstruct full conversation context via parent_id chain
24// - Linked-list structure ensures deterministic history recovery regardless of parallel execution
25//
26// 4. Separation: Distinguish time-series flow (parent_id) from logical relations (tool_call_id)
27// - Enables both "conversation replay" and "request/response mapping"
28//
29// NOTE: Intentional Limitations (Not Goals)
30//
31// - OS-level execution timestamps: Unavailable in logs; command issue time ≒ execution start
32// - Tree/branch structure: Parallel tool calls are linearized in chronological/array order
33// - Real-time token sync: Codex-style delayed tokens handled via eventual consistency (sidecar)
34// - Gemini token breakdown: Total usage attached to final generation event (no speculation)
35
36/// Agent event
37/// Maps 1:1 to database table row
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct AgentEvent {
40 /// Unique event ID
41 pub id: Uuid,
42
43 /// Session/trace ID (groups entire conversation)
44 pub session_id: Uuid,
45
46 /// Parent event ID in time-series chain (Linked List structure)
47 /// None for root events (first User input)
48 pub parent_id: Option<Uuid>,
49
50 /// Event timestamp (UTC)
51 pub timestamp: DateTime<Utc>,
52
53 /// Stream identifier (main, sidechain, subagent)
54 /// Enables parallel conversation streams within same session
55 #[serde(default)]
56 pub stream_id: StreamId,
57
58 /// Event type and content (flattened enum)
59 #[serde(flatten)]
60 pub payload: EventPayload,
61
62 /// Provider-specific raw data and debug information
63 /// Examples: Codex "call_id", Gemini "finish_reason", etc.
64 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub metadata: Option<Value>,
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn test_serialization() {
74 let event = AgentEvent {
75 id: Uuid::new_v4(),
76 session_id: Uuid::new_v4(),
77 parent_id: None,
78 timestamp: Utc::now(),
79 stream_id: StreamId::Main,
80 payload: EventPayload::User(UserPayload {
81 text: "Hello".to_string(),
82 }),
83 metadata: None,
84 };
85
86 let json = serde_json::to_string(&event).unwrap();
87 let deserialized: AgentEvent = serde_json::from_str(&json).unwrap();
88
89 match deserialized.payload {
90 EventPayload::User(payload) => assert_eq!(payload.text, "Hello"),
91 _ => panic!("Wrong payload type"),
92 }
93 }
94}