1use serde::Serialize;
10
11use crate::llm::Usage;
12
13#[derive(Debug, Clone, Serialize)]
15pub struct AgentTrace {
16 pub started_at: String,
18 pub user_message: String,
20 pub history: Vec<TraceMessage>,
22 pub events: Vec<TraceEvent>,
24 pub usage: Option<TraceUsage>,
26}
27
28#[derive(Debug, Clone, Serialize)]
30pub struct TraceMessage {
31 pub role: String,
32 pub content: String,
33}
34
35#[derive(Debug, Clone, Serialize)]
37#[serde(tag = "type")]
38pub enum TraceEvent {
39 #[serde(rename = "tool_call")]
41 ToolCall {
42 name: String,
44 arguments: String,
46 },
47 #[serde(rename = "tool_result")]
49 ToolResult {
50 name: String,
52 content: String,
54 },
55 #[serde(rename = "text_delta")]
57 TextDelta {
58 text: String,
60 },
61 #[serde(rename = "build_mutation")]
63 BuildMutation {
64 label: String,
66 },
67}
68
69#[derive(Debug, Clone, Serialize)]
71pub struct TraceUsage {
72 pub input_tokens: u32,
73 pub output_tokens: u32,
74 pub cached_tokens: u32,
75 pub total_tokens: u32,
76}
77
78impl From<&Usage> for TraceUsage {
79 fn from(u: &Usage) -> Self {
80 Self {
81 input_tokens: u.input_tokens,
82 output_tokens: u.output_tokens,
83 cached_tokens: u.cached_tokens(),
84 total_tokens: u.total_tokens,
85 }
86 }
87}
88
89pub(crate) struct TraceBuilder {
94 started_at: String,
95 user_message: String,
96 history: Vec<TraceMessage>,
97 events: Vec<TraceEvent>,
98}
99
100impl TraceBuilder {
101 pub fn new(user_message: &str, history: impl IntoIterator<Item = TraceMessage>) -> Self {
103 Self {
104 started_at: now_iso8601(),
105 user_message: user_message.to_owned(),
106 history: history.into_iter().collect(),
107 events: Vec::new(),
108 }
109 }
110
111 pub fn tool_call(&mut self, name: &str, arguments: &str) {
113 self.events.push(TraceEvent::ToolCall {
114 name: name.to_owned(),
115 arguments: arguments.to_owned(),
116 });
117 }
118
119 pub fn tool_result(&mut self, name: &str, content: &str) {
121 self.events.push(TraceEvent::ToolResult {
122 name: name.to_owned(),
123 content: content.to_owned(),
124 });
125 }
126
127 pub fn text_delta(&mut self, text: &str) {
129 self.events.push(TraceEvent::TextDelta {
130 text: text.to_owned(),
131 });
132 }
133
134 pub fn build_mutation(&mut self, label: &str) {
136 self.events.push(TraceEvent::BuildMutation {
137 label: label.to_owned(),
138 });
139 }
140
141 pub fn finish(self, usage: &Usage) -> AgentTrace {
143 AgentTrace {
144 started_at: self.started_at,
145 user_message: self.user_message,
146 history: self.history,
147 events: self.events,
148 usage: Some(TraceUsage::from(usage)),
149 }
150 }
151}
152
153fn now_iso8601() -> String {
154 let now = std::time::SystemTime::now();
156 let duration = now
157 .duration_since(std::time::UNIX_EPOCH)
158 .unwrap_or_default();
159 let secs = duration.as_secs();
160
161 let days = secs / 86400;
163 let time_secs = secs % 86400;
164 let hours = time_secs / 3600;
165 let minutes = (time_secs % 3600) / 60;
166 let seconds = time_secs % 60;
167
168 let (y, m, d) = days_to_ymd(days);
170 format!("{y:04}-{m:02}-{d:02}T{hours:02}:{minutes:02}:{seconds:02}Z")
171}
172
173fn days_to_ymd(days: u64) -> (u64, u64, u64) {
175 let z = days + 719468;
177 let era = z / 146097;
178 let doe = z - era * 146097;
179 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
180 let y = yoe + era * 400;
181 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
182 let mp = (5 * doy + 2) / 153;
183 let d = doy - (153 * mp + 2) / 5 + 1;
184 let m = if mp < 10 { mp + 3 } else { mp - 9 };
185 let y = if m <= 2 { y + 1 } else { y };
186 (y, m, d)
187}