ag_agent/model/session.rs
1//! Session usage statistics produced by agent transports.
2
3/// Known availability of a session worktree diff.
4#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
5pub enum SessionDiffState {
6 /// Diff availability could not be determined, so callers should preserve
7 /// access to diagnostic diff output.
8 #[default]
9 Unknown,
10 /// The latest successful diff refresh returned no content.
11 Empty,
12 /// The latest successful diff refresh returned content.
13 Present,
14}
15
16/// Token and diff usage statistics associated with one agent session or
17/// isolated prompt.
18#[derive(Clone, PartialEq, Eq, Debug, Default)]
19pub struct SessionStats {
20 /// Added diff lines currently attributed to the session worktree.
21 pub added_lines: u64,
22 /// Deleted diff lines currently attributed to the session worktree.
23 pub deleted_lines: u64,
24 /// Availability derived from the latest worktree diff refresh.
25 pub diff_state: SessionDiffState,
26 /// Input/prompt tokens consumed by this session.
27 pub input_tokens: u64,
28 /// Output/response tokens produced by this session.
29 pub output_tokens: u64,
30}
31
32impl SessionStats {
33 /// Returns whether the UI should advertise access to the session diff.
34 ///
35 /// Unknown state retains the shortcut so a subsequent diff attempt can
36 /// surface the underlying Git diagnostic instead of hiding it.
37 pub fn should_show_diff(&self) -> bool {
38 self.diff_state != SessionDiffState::Empty
39 }
40
41 /// Counts added and deleted lines in one git patch while ignoring file
42 /// header markers such as `+++` and `---`.
43 pub fn line_change_counts(diff: &str) -> (u64, u64) {
44 diff.lines()
45 .fold((0_u64, 0_u64), |(added_lines, deleted_lines), line| {
46 if line.starts_with('+') && !line.starts_with("+++") {
47 return (added_lines.saturating_add(1), deleted_lines);
48 }
49
50 if line.starts_with('-') && !line.starts_with("---") {
51 return (added_lines, deleted_lines.saturating_add(1));
52 }
53
54 (added_lines, deleted_lines)
55 })
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn should_show_diff_hides_only_known_empty_diffs() {
65 // Arrange
66 let unknown = SessionStats::default();
67 let empty = SessionStats {
68 diff_state: SessionDiffState::Empty,
69 ..SessionStats::default()
70 };
71 let present = SessionStats {
72 diff_state: SessionDiffState::Present,
73 ..SessionStats::default()
74 };
75
76 // Act, Assert
77 assert!(unknown.should_show_diff());
78 assert!(!empty.should_show_diff());
79 assert!(present.should_show_diff());
80 }
81}