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