agtrace_engine/session/extensions.rs
1//! Extension traits for session analysis.
2//!
3//! These traits provide complex computation logic for session types
4//! defined in agtrace-types, keeping the types crate lightweight and
5//! focused on data structures while engine handles analysis logic.
6
7use super::types::{AgentSession, AgentTurn, TurnMetrics};
8
9/// Detect if context was compacted by analyzing origin and token drop.
10///
11/// Context compaction is detected when:
12/// 1. Turn origin is ContextCompaction (authoritative), OR
13/// 2. Tokens dropped by more than 50% (fallback for backward compatibility)
14fn detect_context_compaction(turn: &AgentTurn, current_tokens: u32, prev_cumulative: u32) -> bool {
15 // Primary: origin-based detection (authoritative)
16 if turn.user.origin.is_context_compaction() {
17 return true;
18 }
19
20 // Fallback: token-based detection for backward compatibility
21 // (handles data assembled before origin was added)
22 prev_cumulative > 0 && current_tokens < prev_cumulative / 2
23}
24
25/// Extension trait for `AgentSession` providing analysis and metrics computation.
26pub trait SessionAnalysisExt {
27 /// Compute presentation metrics for all turns
28 fn compute_turn_metrics(&self, max_context: Option<u32>) -> Vec<TurnMetrics>;
29}
30
31impl SessionAnalysisExt for AgentSession {
32 fn compute_turn_metrics(&self, max_context: Option<u32>) -> Vec<TurnMetrics> {
33 let mut cumulative_total = 0u32;
34 let mut metrics = Vec::new();
35 let total_turns = self.turns.len();
36
37 for (idx, turn) in self.turns.iter().enumerate() {
38 let turn_end_tokens = turn.cumulative_total_tokens(cumulative_total);
39
40 // Detect if context was compacted (reset) during this turn
41 let context_compacted =
42 detect_context_compaction(turn, turn_end_tokens, cumulative_total);
43
44 // Calculate prev_total, delta, and compaction_from based on compaction state
45 let (prev_total, delta, compaction_from) = if context_compacted {
46 // Context was reset: prev=0, delta=new baseline
47 // Store the previous cumulative to show reduction (e.g., 150k → 30k)
48 (0, turn_end_tokens, Some(cumulative_total))
49 } else {
50 // Normal case: additive
51 let delta = turn_end_tokens.saturating_sub(cumulative_total);
52 (cumulative_total, delta, None)
53 };
54
55 // Simplified: Last turn is always considered active for live watching
56 // This avoids flickering caused by per-step status checks
57 let is_active = idx == total_turns - 1;
58
59 metrics.push(TurnMetrics {
60 turn_index: idx,
61 prev_total,
62 delta,
63 is_heavy: TurnMetrics::is_delta_heavy(delta, max_context),
64 is_active,
65 context_compacted,
66 cumulative_total: turn_end_tokens,
67 compaction_from,
68 });
69
70 cumulative_total = turn_end_tokens;
71 }
72
73 metrics
74 }
75}