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}