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}