ag_agent/model/
session.rs1use std::fmt;
4use std::str::FromStr;
5
6#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
8pub enum SpeedMode {
9 #[default]
11 Normal,
12 Fast,
14}
15
16impl SpeedMode {
17 pub const ALL: [Self; 2] = [Self::Normal, Self::Fast];
19
20 pub const fn as_str(self) -> &'static str {
22 match self {
23 Self::Normal => "normal",
24 Self::Fast => "fast",
25 }
26 }
27
28 pub const fn name(self) -> &'static str {
30 match self {
31 Self::Normal => "Normal",
32 Self::Fast => "Fast",
33 }
34 }
35
36 pub const fn description(self) -> &'static str {
38 match self {
39 Self::Normal => "Standard response speed and provider pricing.",
40 Self::Fast => "Faster responses at a higher provider cost.",
41 }
42 }
43
44 pub const fn codex_service_tier(self) -> &'static str {
46 match self {
47 Self::Normal => "default",
48 Self::Fast => "fast",
49 }
50 }
51
52 pub const fn claude_fast_mode(self) -> bool {
54 matches!(self, Self::Fast)
55 }
56}
57
58impl fmt::Display for SpeedMode {
59 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
60 formatter.write_str(self.as_str())
61 }
62}
63
64impl FromStr for SpeedMode {
65 type Err = String;
66
67 fn from_str(value: &str) -> Result<Self, Self::Err> {
68 match value {
69 "normal" => Ok(Self::Normal),
70 "fast" => Ok(Self::Fast),
71 _ => Err(format!("Unknown speed mode: {value}")),
72 }
73 }
74}
75
76#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
78pub enum SessionDiffState {
79 #[default]
82 Unknown,
83 Empty,
85 Present,
87}
88
89#[derive(Clone, PartialEq, Eq, Debug, Default)]
92pub struct SessionStats {
93 pub added_lines: u64,
95 pub deleted_lines: u64,
97 pub diff_state: SessionDiffState,
99 pub input_tokens: u64,
101 pub output_tokens: u64,
103}
104
105impl SessionStats {
106 pub fn should_show_diff(&self) -> bool {
111 self.diff_state != SessionDiffState::Empty
112 }
113
114 pub fn line_change_counts(diff: &str) -> (u64, u64) {
117 diff.lines()
118 .fold((0_u64, 0_u64), |(added_lines, deleted_lines), line| {
119 if line.starts_with('+') && !line.starts_with("+++") {
120 return (added_lines.saturating_add(1), deleted_lines);
121 }
122
123 if line.starts_with('-') && !line.starts_with("---") {
124 return (added_lines, deleted_lines.saturating_add(1));
125 }
126
127 (added_lines, deleted_lines)
128 })
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn speed_mode_round_trips_persisted_values() {
138 for speed_mode in SpeedMode::ALL {
140 assert_eq!(speed_mode.as_str().parse::<SpeedMode>(), Ok(speed_mode));
141 assert_eq!(speed_mode.to_string(), speed_mode.as_str());
142 }
143 }
144
145 #[test]
146 fn speed_mode_maps_provider_settings() {
147 assert_eq!(SpeedMode::Normal.codex_service_tier(), "default");
149 assert!(!SpeedMode::Normal.claude_fast_mode());
150 assert_eq!(SpeedMode::Fast.codex_service_tier(), "fast");
151 assert!(SpeedMode::Fast.claude_fast_mode());
152 }
153
154 #[test]
155 fn speed_mode_rejects_unknown_persisted_value() {
156 let result = "turbo".parse::<SpeedMode>();
158
159 assert_eq!(result, Err("Unknown speed mode: turbo".to_string()));
161 }
162
163 #[test]
164 fn should_show_diff_hides_only_known_empty_diffs() {
165 let unknown = SessionStats::default();
167 let empty = SessionStats {
168 diff_state: SessionDiffState::Empty,
169 ..SessionStats::default()
170 };
171 let present = SessionStats {
172 diff_state: SessionDiffState::Present,
173 ..SessionStats::default()
174 };
175
176 assert!(unknown.should_show_diff());
178 assert!(!empty.should_show_diff());
179 assert!(present.should_show_diff());
180 }
181}