agtrace_types/domain/session.rs
1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5use super::project::ProjectHash;
6use super::token_usage::ContextWindowUsage;
7use crate::{MessagePayload, ReasoningPayload, ToolCallPayload, ToolResultPayload, UserPayload};
8
9/// Source of the agent log (provider-agnostic identifier)
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct Source(String);
13
14impl Source {
15 pub fn new(name: impl Into<String>) -> Self {
16 Self(name.into())
17 }
18}
19
20/// Tool execution status (used in Span API)
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "snake_case")]
23pub enum ToolStatus {
24 Success,
25 Error,
26 InProgress,
27 Unknown,
28}
29
30/// Session summary for listing
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct SessionSummary {
33 pub session_id: String,
34 pub source: Source,
35 pub project_hash: ProjectHash,
36 pub start_ts: String,
37 pub end_ts: String,
38 pub event_count: usize,
39 pub user_message_count: usize,
40 pub tokens_input_total: u64,
41 pub tokens_output_total: u64,
42}
43
44// ==========================================
45// 1. Session (entire conversation)
46// ==========================================
47
48/// Complete agent conversation session assembled from normalized events.
49///
50/// Represents a full conversation timeline with the agent, containing all
51/// user interactions (turns) and their corresponding agent responses.
52/// The session is the highest-level construct for analyzing agent behavior.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct AgentSession {
55 /// Unique session identifier.
56 pub session_id: Uuid,
57 /// When the session started (first event timestamp).
58 pub start_time: DateTime<Utc>,
59 /// When the session ended (last event timestamp), if completed.
60 pub end_time: Option<DateTime<Utc>>,
61
62 /// All user-initiated turns in chronological order.
63 pub turns: Vec<AgentTurn>,
64
65 /// Aggregated session statistics.
66 pub stats: SessionStats,
67}
68
69// ==========================================
70// 2. Turn (user-initiated interaction unit)
71// ==========================================
72
73/// Single user-initiated interaction cycle within a session.
74///
75/// A turn begins with user input and contains all agent steps taken
76/// in response until the next user input or session end.
77/// Autonomous agents may execute multiple steps per turn.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct AgentTurn {
80 /// Unique turn identifier (ID of the user event that initiated this turn).
81 pub id: Uuid,
82 /// When the turn started (user input timestamp).
83 pub timestamp: DateTime<Utc>,
84
85 /// User input that triggered this turn.
86 pub user: UserMessage,
87
88 /// Agent's response steps in chronological order.
89 /// Single step for simple Q&A, multiple steps for autonomous operation.
90 pub steps: Vec<AgentStep>,
91
92 /// Aggregated turn statistics.
93 pub stats: TurnStats,
94}
95
96// ==========================================
97// 3. Step (single LLM inference + execution unit)
98// ==========================================
99
100/// Single LLM inference cycle with optional tool executions.
101///
102/// A step represents one round of agent thinking and acting:
103/// 1. Generation phase: LLM produces reasoning, messages, and tool calls
104/// 2. Execution phase: Tools are executed and results collected
105///
106/// Steps are the atomic unit of agent behavior analysis.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct AgentStep {
109 /// Unique step identifier (ID of the first event in this step).
110 pub id: Uuid,
111 /// When the step started.
112 pub timestamp: DateTime<Utc>,
113
114 // --- Phase 1: Generation (Agent Outputs) ---
115 // These are generated in parallel or in arbitrary order before seeing tool results
116 /// Chain-of-thought reasoning, if present.
117 pub reasoning: Option<ReasoningBlock>,
118
119 /// Text response to user or explanation of tool usage.
120 pub message: Option<MessageBlock>,
121
122 // --- Phase 2: Execution (System Outputs) ---
123 /// Tool executions (call + result pairs) performed in this step.
124 /// Calls are generated in Phase 1, paired with results here.
125 pub tools: Vec<ToolExecution>,
126
127 // --- Meta ---
128 /// Token usage for this step's LLM inference, if available.
129 pub usage: Option<ContextWindowUsage>,
130 /// Whether this step encountered any failures.
131 pub is_failed: bool,
132 /// Current completion status of this step.
133 pub status: StepStatus,
134}
135
136/// Step completion status
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(rename_all = "snake_case")]
139pub enum StepStatus {
140 /// Step completed successfully (has Message or all tools have results)
141 Done,
142 /// Step is waiting for tool results or next action
143 InProgress,
144 /// Step failed with errors
145 Failed,
146}
147
148// ==========================================
149// Components
150// ==========================================
151
152/// Paired tool call and result with execution metrics.
153///
154/// Represents a complete tool execution lifecycle:
155/// tool invocation → execution → result collection.
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct ToolExecution {
158 /// Tool invocation request.
159 pub call: ToolCallBlock,
160
161 /// Execution result (None if incomplete, lost, or still pending).
162 pub result: Option<ToolResultBlock>,
163
164 /// Execution latency in milliseconds (result.timestamp - call.timestamp).
165 pub duration_ms: Option<i64>,
166
167 /// Whether this tool execution failed (error status in result).
168 pub is_error: bool,
169}
170
171// --- Event Wrappers ---
172
173/// User input message that initiates a turn.
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct UserMessage {
176 /// ID of the source event.
177 pub event_id: Uuid,
178 /// User input content.
179 pub content: UserPayload,
180}
181
182/// Agent reasoning/thinking block.
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct ReasoningBlock {
185 /// ID of the source event.
186 pub event_id: Uuid,
187 /// Reasoning content.
188 pub content: ReasoningPayload,
189}
190
191/// Agent text response message.
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct MessageBlock {
194 /// ID of the source event.
195 pub event_id: Uuid,
196 /// Message content.
197 pub content: MessagePayload,
198}
199
200/// Tool invocation request with timing information.
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct ToolCallBlock {
203 /// ID of the source event.
204 pub event_id: Uuid,
205 /// When the tool was invoked.
206 pub timestamp: DateTime<Utc>,
207 /// Provider-specific call identifier, if available.
208 pub provider_call_id: Option<String>,
209 /// Tool invocation details.
210 pub content: ToolCallPayload,
211}
212
213/// Tool execution result with timing information.
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct ToolResultBlock {
216 /// ID of the source event.
217 pub event_id: Uuid,
218 /// When the result was received.
219 pub timestamp: DateTime<Utc>,
220 /// ID of the tool call this result corresponds to.
221 pub tool_call_id: Uuid,
222 /// Tool execution result details.
223 pub content: ToolResultPayload,
224}
225
226// --- Stats ---
227
228/// Aggregated statistics for an entire session.
229#[derive(Debug, Clone, Serialize, Deserialize, Default)]
230pub struct SessionStats {
231 /// Total number of turns in the session.
232 pub total_turns: usize,
233 /// Session duration in seconds (end_time - start_time).
234 pub duration_seconds: i64,
235 /// Total tokens consumed across all turns.
236 pub total_tokens: i64,
237}
238
239/// Aggregated statistics for a single turn.
240#[derive(Debug, Clone, Serialize, Deserialize, Default)]
241pub struct TurnStats {
242 /// Turn duration in milliseconds.
243 pub duration_ms: i64,
244 /// Number of steps in this turn.
245 pub step_count: usize,
246 /// Total tokens consumed in this turn.
247 pub total_tokens: i32,
248}
249
250// ==========================================
251// Computed Metrics (for presentation)
252// ==========================================
253
254/// Computed context window metrics for turn visualization.
255///
256/// Used by TUI and other presentation layers to show
257/// cumulative token usage and detect high-usage patterns.
258#[derive(Debug, Clone)]
259pub struct TurnMetrics {
260 /// Zero-based turn index.
261 pub turn_index: usize,
262 /// Cumulative tokens before this turn.
263 pub prev_total: u32,
264 /// Tokens added by this turn.
265 pub delta: u32,
266 /// Whether this turn's delta exceeds the heavy threshold.
267 pub is_heavy: bool,
268 /// Whether this turn is currently active (in progress).
269 pub is_active: bool,
270}
271
272impl TurnMetrics {
273 /// Calculate heavy threshold: 10% of max context, or fallback to 15k tokens
274 pub fn heavy_threshold(max_context: Option<u32>) -> u32 {
275 max_context.map(|mc| mc / 10).unwrap_or(15000)
276 }
277
278 /// Check if a delta is considered heavy
279 pub fn is_delta_heavy(delta: u32, max_context: Option<u32>) -> bool {
280 delta >= Self::heavy_threshold(max_context)
281 }
282}
283
284impl AgentTurn {
285 /// Calculate cumulative input tokens at the end of this turn
286 /// Falls back to `fallback` if no usage data found
287 pub fn cumulative_input_tokens(&self, fallback: u32) -> u32 {
288 self.steps
289 .iter()
290 .rev()
291 .find_map(|step| step.usage.as_ref())
292 .map(|usage| usage.input_tokens() as u32)
293 .unwrap_or(fallback)
294 }
295
296 /// Calculate cumulative total tokens (input + output) at the end of this turn
297 /// Falls back to `fallback` if no usage data found
298 pub fn cumulative_total_tokens(&self, fallback: u32) -> u32 {
299 self.steps
300 .iter()
301 .rev()
302 .find_map(|step| step.usage.as_ref())
303 .map(|usage| (usage.input_tokens() + usage.output_tokens()) as u32)
304 .unwrap_or(fallback)
305 }
306
307 /// Check if this turn is currently active
308 ///
309 /// A turn is active if any of the recent steps are in progress.
310 /// Looking at multiple steps provides stability during step transitions
311 /// (e.g., when a step completes but the next one hasn't started yet).
312 pub fn is_active(&self) -> bool {
313 const LOOKBACK_STEPS: usize = 3;
314
315 self.steps
316 .iter()
317 .rev()
318 .take(LOOKBACK_STEPS)
319 .any(|step| matches!(step.status, StepStatus::InProgress))
320 }
321}