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