Skip to main content

a3s_code_core/task/
progress.rs

1//! Progress Tracking for Task Execution
2//!
3//! Provides real-time progress tracking for agent and tool execution,
4//! similar to claw-codes's ProgressTracker.
5
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, VecDeque};
8use std::time::Instant;
9
10/// Tool activity record
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ToolActivity {
13    /// Tool name
14    pub tool_name: String,
15    /// When the activity occurred
16    pub timestamp: chrono::DateTime<chrono::Utc>,
17    /// Arguments summary (truncated)
18    pub args_summary: String,
19    /// Whether the tool succeeded
20    pub success: bool,
21}
22
23impl ToolActivity {
24    /// Create a new tool activity
25    pub fn new(
26        tool_name: impl Into<String>,
27        args_summary: impl Into<String>,
28        success: bool,
29    ) -> Self {
30        Self {
31            tool_name: tool_name.into(),
32            timestamp: chrono::Utc::now(),
33            args_summary: args_summary.into(),
34            success,
35        }
36    }
37}
38
39/// Token usage statistics
40#[derive(Debug, Clone, Default, Serialize, Deserialize)]
41pub struct TaskTokenUsage {
42    /// Input tokens used
43    pub input_tokens: u64,
44    /// Output tokens generated
45    pub output_tokens: u64,
46    /// Cache read tokens
47    pub cache_read_tokens: u64,
48    /// Cache write tokens
49    pub cache_write_tokens: u64,
50}
51
52impl TaskTokenUsage {
53    /// Total tokens used
54    pub fn total(&self) -> u64 {
55        self.input_tokens + self.output_tokens + self.cache_read_tokens + self.cache_write_tokens
56    }
57
58    /// Add token usage
59    pub fn add(&mut self, other: &TaskTokenUsage) {
60        self.input_tokens += other.input_tokens;
61        self.output_tokens += other.output_tokens;
62        self.cache_read_tokens += other.cache_read_tokens;
63        self.cache_write_tokens += other.cache_write_tokens;
64    }
65}
66
67/// Agent execution progress
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct AgentProgress {
70    /// Tool call counts by tool name
71    pub tool_counts: HashMap<String, usize>,
72    /// Total tool calls
73    pub total_tool_calls: usize,
74    /// Token usage
75    pub token_usage: TaskTokenUsage,
76    /// Recent activities (most recent first)
77    pub recent_activities: Vec<ToolActivity>,
78    /// Time since task started in milliseconds
79    pub elapsed_ms: u64,
80    /// Whether task is still running
81    pub running: bool,
82}
83
84impl Default for AgentProgress {
85    fn default() -> Self {
86        Self {
87            tool_counts: HashMap::new(),
88            total_tool_calls: 0,
89            token_usage: TaskTokenUsage::default(),
90            recent_activities: Vec::new(),
91            elapsed_ms: 0,
92            running: true,
93        }
94    }
95}
96
97/// Progress tracker for real-time progress monitoring
98///
99/// Tracks tool calls, token usage, and recent activities in real-time.
100/// Similar to claw-codes's ProgressTracker.
101#[derive(Debug)]
102pub struct ProgressTracker {
103    /// When tracking started
104    start_time: Instant,
105    /// Tool call counts
106    tool_counts: HashMap<String, usize>,
107    /// Token usage
108    token_usage: TaskTokenUsage,
109    /// Recent activities (sliding window)
110    recent_activities: VecDeque<ToolActivity>,
111    /// Maximum recent activities to keep
112    max_recent: usize,
113    /// Whether tracking is active
114    active: bool,
115}
116
117impl Default for ProgressTracker {
118    fn default() -> Self {
119        Self::new(30) // Default: keep last 30 activities
120    }
121}
122
123impl ProgressTracker {
124    /// Create a new progress tracker
125    ///
126    /// # Arguments
127    ///
128    /// * `max_recent` - Maximum number of recent activities to keep
129    pub fn new(max_recent: usize) -> Self {
130        Self {
131            start_time: Instant::now(),
132            tool_counts: HashMap::new(),
133            token_usage: TaskTokenUsage::default(),
134            recent_activities: VecDeque::with_capacity(max_recent),
135            max_recent,
136            active: true,
137        }
138    }
139
140    /// Track a tool call
141    pub fn track_tool_call(
142        &mut self,
143        tool_name: impl Into<String>,
144        args_summary: impl Into<String>,
145        success: bool,
146    ) {
147        if !self.active {
148            return;
149        }
150
151        let tool_name = tool_name.into();
152        *self.tool_counts.entry(tool_name.clone()).or_insert(0) += 1;
153
154        let activity = ToolActivity::new(&tool_name, args_summary, success);
155        self.recent_activities.push_front(activity);
156
157        // Trim to max size
158        while self.recent_activities.len() > self.max_recent {
159            self.recent_activities.pop_back();
160        }
161    }
162
163    /// Track token usage
164    pub fn track_tokens(&mut self, usage: TaskTokenUsage) {
165        if !self.active {
166            return;
167        }
168        self.token_usage.add(&usage);
169    }
170
171    /// Get current progress snapshot
172    pub fn progress(&self) -> AgentProgress {
173        AgentProgress {
174            tool_counts: self.tool_counts.clone(),
175            total_tool_calls: self.tool_counts.values().sum(),
176            token_usage: self.token_usage.clone(),
177            recent_activities: self.recent_activities.iter().cloned().collect(),
178            elapsed_ms: self.start_time.elapsed().as_millis() as u64,
179            running: self.active,
180        }
181    }
182
183    /// Stop tracking
184    pub fn stop(&mut self) {
185        self.active = false;
186    }
187
188    /// Check if tracking is active
189    pub fn is_active(&self) -> bool {
190        self.active
191    }
192
193    /// Reset the tracker
194    pub fn reset(&mut self) {
195        self.start_time = Instant::now();
196        self.tool_counts.clear();
197        self.token_usage = TaskTokenUsage::default();
198        self.recent_activities.clear();
199        self.active = true;
200    }
201
202    /// Get tool count for a specific tool
203    pub fn get_tool_count(&self, tool_name: &str) -> usize {
204        self.tool_counts.get(tool_name).copied().unwrap_or(0)
205    }
206
207    /// Get total tool calls
208    pub fn total_tool_calls(&self) -> usize {
209        self.tool_counts.values().sum()
210    }
211
212    /// Get elapsed time
213    pub fn elapsed(&self) -> std::time::Duration {
214        self.start_time.elapsed()
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn test_progress_tracker() {
224        let mut tracker = ProgressTracker::new(5);
225
226        tracker.track_tool_call("read", "file_path: test.txt", true);
227        tracker.track_tool_call("bash", "command: ls", true);
228        tracker.track_tool_call("read", "file_path: main.rs", true);
229
230        let progress = tracker.progress();
231
232        assert_eq!(progress.total_tool_calls, 3);
233        assert_eq!(tracker.get_tool_count("read"), 2);
234        assert_eq!(tracker.get_tool_count("bash"), 1);
235        assert!(progress.running);
236    }
237
238    #[test]
239    fn test_token_usage() {
240        let mut usage = TaskTokenUsage::default();
241        assert_eq!(usage.total(), 0);
242
243        usage.input_tokens = 100;
244        usage.output_tokens = 50;
245        assert_eq!(usage.total(), 150);
246
247        let other = TaskTokenUsage {
248            input_tokens: 50,
249            output_tokens: 25,
250            cache_read_tokens: 10,
251            cache_write_tokens: 5,
252        };
253        usage.add(&other);
254        assert_eq!(usage.total(), 240);
255    }
256
257    #[test]
258    fn test_progress_sliding_window() {
259        let mut tracker = ProgressTracker::new(3);
260
261        for i in 0..5 {
262            tracker.track_tool_call("tool", format!("arg_{}", i), true);
263        }
264
265        let progress = tracker.progress();
266        assert_eq!(progress.recent_activities.len(), 3);
267        // Most recent should be first
268        assert_eq!(progress.recent_activities[0].args_summary, "arg_4");
269        assert_eq!(progress.recent_activities[2].args_summary, "arg_2");
270    }
271}