vtcode_core/core/agent/
core.rs

1//! Core agent implementation and orchestration
2
3use crate::config::models::ModelId;
4use crate::config::types::*;
5use crate::core::agent::compaction::CompactionEngine;
6use crate::core::conversation_summarizer::ConversationSummarizer;
7use crate::core::decision_tracker::DecisionTracker;
8use crate::core::error_recovery::{ErrorRecoveryManager, ErrorType};
9use crate::llm::{AnyClient, make_client};
10use crate::tools::tree_sitter::{CodeAnalysis, TreeSitterAnalyzer};
11use crate::tools::{ToolRegistry, build_function_declarations};
12use anyhow::{Result, anyhow};
13use console::style;
14use std::sync::Arc;
15
16/// Main agent orchestrator
17pub struct Agent {
18    config: AgentConfig,
19    client: AnyClient,
20    tool_registry: Arc<ToolRegistry>,
21    decision_tracker: DecisionTracker,
22    error_recovery: ErrorRecoveryManager,
23    summarizer: ConversationSummarizer,
24    tree_sitter_analyzer: TreeSitterAnalyzer,
25    compaction_engine: Arc<CompactionEngine>,
26    session_info: SessionInfo,
27    start_time: std::time::Instant,
28}
29
30impl Agent {
31    /// Create a new agent instance
32    pub fn new(config: AgentConfig) -> Result<Self> {
33        let model_id = config
34            .model
35            .parse::<ModelId>()
36            .map_err(|_| anyhow!("Invalid model: {}", config.model))?;
37        let client = make_client(config.api_key.clone(), model_id);
38        let tool_registry = Arc::new(ToolRegistry::new(config.workspace.clone()));
39        let decision_tracker = DecisionTracker::new();
40        let error_recovery = ErrorRecoveryManager::new();
41        let summarizer = ConversationSummarizer::new();
42        let tree_sitter_analyzer = match TreeSitterAnalyzer::new() {
43            Ok(analyzer) => analyzer,
44            Err(e) => {
45                eprintln!("Warning: Failed to initialize tree-sitter analyzer: {}", e);
46                eprintln!("Continuing without tree-sitter analysis capabilities");
47                // Create a minimal fallback that doesn't panic
48                // This is a temporary solution - ideally we'd have a proper fallback
49                return Err(anyhow!("Tree-sitter analyzer initialization failed: {}", e));
50            }
51        };
52
53        let session_id = format!(
54            "session_{}",
55            std::time::SystemTime::now()
56                .duration_since(std::time::UNIX_EPOCH)
57                .unwrap()
58                .as_secs()
59        );
60
61        let session_info = SessionInfo {
62            session_id,
63            start_time: std::time::SystemTime::now()
64                .duration_since(std::time::UNIX_EPOCH)
65                .unwrap()
66                .as_secs(),
67            total_turns: 0,
68            total_decisions: 0,
69            error_count: 0,
70        };
71
72        Ok(Self {
73            config,
74            client,
75            tool_registry,
76            decision_tracker,
77            error_recovery,
78            summarizer,
79            tree_sitter_analyzer,
80            compaction_engine: Arc::new(CompactionEngine::new()),
81            session_info,
82            start_time: std::time::Instant::now(),
83        })
84    }
85
86    /// Initialize the agent with system setup
87    pub async fn initialize(&mut self) -> Result<()> {
88        // Initialize available tools in decision tracker
89        let tool_names = build_function_declarations()
90            .iter()
91            .map(|fd| fd.name.clone())
92            .collect::<Vec<_>>();
93        self.decision_tracker.update_available_tools(tool_names);
94
95        // Update session info
96        self.session_info.start_time = std::time::SystemTime::now()
97            .duration_since(std::time::UNIX_EPOCH)
98            .unwrap()
99            .as_secs();
100
101        if self.config.verbose {
102            println!("{} {}", style("[INIT]").cyan().bold(), "Agent initialized");
103            println!("  {} Model: {}", style("").dim(), self.config.model);
104            println!(
105                "  {} Workspace: {}",
106                style("").dim(),
107                self.config.workspace.display()
108            );
109            println!(
110                "  {} Tools loaded: {}",
111                style("").dim(),
112                build_function_declarations().len()
113            );
114            println!(
115                "  {} Session ID: {}",
116                style("(ID)").dim(),
117                self.session_info.session_id
118            );
119            println!();
120        }
121
122        Ok(())
123    }
124
125    /// Get the agent's current configuration
126    pub fn config(&self) -> &AgentConfig {
127        &self.config
128    }
129
130    /// Get session information
131    pub fn session_info(&self) -> &SessionInfo {
132        &self.session_info
133    }
134
135    /// Get performance metrics
136    pub fn performance_metrics(&self) -> PerformanceMetrics {
137        let duration = self.start_time.elapsed();
138
139        PerformanceMetrics {
140            session_duration_seconds: duration.as_secs(),
141            total_api_calls: self.session_info.total_turns,
142            total_tokens_used: None, // Would need to track from API responses
143            average_response_time_ms: if self.session_info.total_turns > 0 {
144                duration.as_millis() as f64 / self.session_info.total_turns as f64
145            } else {
146                0.0
147            },
148            tool_execution_count: self.session_info.total_decisions,
149            error_count: self.session_info.error_count,
150            recovery_success_rate: self.calculate_recovery_rate(),
151        }
152    }
153
154    /// Get decision tracker reference
155    pub fn decision_tracker(&self) -> &DecisionTracker {
156        &self.decision_tracker
157    }
158
159    /// Get mutable decision tracker reference
160    pub fn decision_tracker_mut(&mut self) -> &mut DecisionTracker {
161        &mut self.decision_tracker
162    }
163
164    /// Get error recovery manager reference
165    pub fn error_recovery(&self) -> &ErrorRecoveryManager {
166        &self.error_recovery
167    }
168
169    /// Get mutable error recovery manager reference
170    pub fn error_recovery_mut(&mut self) -> &mut ErrorRecoveryManager {
171        &mut self.error_recovery
172    }
173
174    /// Get conversation summarizer reference
175    pub fn summarizer(&self) -> &ConversationSummarizer {
176        &self.summarizer
177    }
178
179    /// Get tool registry reference
180    pub fn tool_registry(&self) -> Arc<ToolRegistry> {
181        Arc::clone(&self.tool_registry)
182    }
183
184    /// Get mutable tool registry reference
185    pub fn tool_registry_mut(&mut self) -> &mut ToolRegistry {
186        Arc::get_mut(&mut self.tool_registry)
187            .expect("ToolRegistry should not have other references")
188    }
189
190    /// Get model-agnostic client reference
191    pub fn llm(&self) -> &AnyClient {
192        &self.client
193    }
194
195    /// Get tree-sitter analyzer reference
196    pub fn tree_sitter_analyzer(&self) -> &TreeSitterAnalyzer {
197        &self.tree_sitter_analyzer
198    }
199
200    /// Get mutable tree-sitter analyzer reference
201    pub fn tree_sitter_analyzer_mut(&mut self) -> &mut TreeSitterAnalyzer {
202        &mut self.tree_sitter_analyzer
203    }
204
205    /// Get compaction engine reference
206    pub fn compaction_engine(&self) -> Arc<CompactionEngine> {
207        Arc::clone(&self.compaction_engine)
208    }
209
210    /// Make intelligent compaction decision using context analysis
211    pub async fn make_intelligent_compaction_decision(
212        &self,
213    ) -> Result<crate::core::agent::intelligence::CompactionDecision> {
214        let stats = self.compaction_engine.get_statistics().await?;
215        let should_compact = self.compaction_engine.should_compact().await?;
216        let strategy = if should_compact {
217            crate::core::agent::intelligence::CompactionStrategy::Aggressive
218        } else {
219            crate::core::agent::intelligence::CompactionStrategy::Conservative
220        };
221        let reasoning = if should_compact {
222            format!("{} messages exceed thresholds", stats.total_messages)
223        } else {
224            "within configured thresholds".to_string()
225        };
226
227        Ok(crate::core::agent::intelligence::CompactionDecision {
228            should_compact,
229            strategy,
230            reasoning,
231            estimated_benefit: stats.total_memory_usage,
232        })
233    }
234
235    /// Check if compaction is needed
236    pub async fn should_compact(&self) -> Result<bool> {
237        self.compaction_engine.should_compact().await
238    }
239
240    /// Perform intelligent message compaction
241    pub async fn compact_messages(&self) -> Result<crate::core::agent::types::CompactionResult> {
242        self.compaction_engine
243            .compact_messages_intelligently()
244            .await
245    }
246
247    /// Perform context compaction
248    pub async fn compact_context(
249        &self,
250        context_key: &str,
251        context_data: &mut std::collections::HashMap<String, serde_json::Value>,
252    ) -> Result<crate::core::agent::types::CompactionResult> {
253        self.compaction_engine
254            .compact_context(context_key, context_data)
255            .await
256    }
257
258    /// Get compaction statistics
259    pub async fn get_compaction_stats(
260        &self,
261    ) -> Result<crate::core::agent::types::CompactionStatistics> {
262        self.compaction_engine.get_statistics().await
263    }
264
265    /// Analyze a file using tree-sitter
266    pub fn analyze_file_with_tree_sitter(
267        &mut self,
268        file_path: &std::path::Path,
269        source_code: &str,
270    ) -> Result<CodeAnalysis> {
271        // Detect language from file extension
272        let language = self
273            .tree_sitter_analyzer
274            .detect_language_from_path(file_path)
275            .map_err(|e| {
276                anyhow!(
277                    "Failed to detect language for {}: {}",
278                    file_path.display(),
279                    e
280                )
281            })?;
282
283        // Parse the file
284        let syntax_tree = self
285            .tree_sitter_analyzer
286            .parse(source_code, language.clone())?;
287
288        // Extract symbols
289        let symbols = self
290            .tree_sitter_analyzer
291            .extract_symbols(&syntax_tree, source_code, language.clone())
292            .unwrap_or_default();
293
294        // Extract dependencies
295        let dependencies = self
296            .tree_sitter_analyzer
297            .extract_dependencies(&syntax_tree, language.clone())
298            .unwrap_or_default();
299
300        // Calculate metrics
301        let metrics = self
302            .tree_sitter_analyzer
303            .calculate_metrics(&syntax_tree, source_code)
304            .unwrap_or_default();
305
306        Ok(CodeAnalysis {
307            file_path: file_path.to_string_lossy().to_string(),
308            language,
309            symbols,
310            dependencies,
311            metrics,
312            issues: Vec::new(),
313            complexity: crate::tools::tree_sitter::analysis::ComplexityMetrics::default(),
314            structure: crate::tools::tree_sitter::analysis::CodeStructure::default(),
315        })
316    }
317
318    /// Update session statistics
319    pub fn update_session_stats(&mut self, turns: usize, decisions: usize, errors: usize) {
320        self.session_info.total_turns = turns;
321        self.session_info.total_decisions = decisions;
322        self.session_info.error_count = errors;
323    }
324
325    /// Check if context compression is needed
326    pub fn should_compress_context(&self, context_size: usize) -> bool {
327        self.error_recovery.should_compress_context(context_size)
328    }
329
330    /// Generate context preservation plan
331    pub fn generate_context_plan(
332        &self,
333        context_size: usize,
334    ) -> crate::core::error_recovery::ContextPreservationPlan {
335        self.error_recovery
336            .generate_context_preservation_plan(context_size, self.session_info.error_count)
337    }
338
339    /// Check for error patterns
340    pub fn detect_error_pattern(&self, error_type: &ErrorType, time_window_seconds: u64) -> bool {
341        self.error_recovery
342            .detect_error_pattern(error_type, time_window_seconds)
343    }
344
345    /// Calculate recovery success rate
346    fn calculate_recovery_rate(&self) -> f64 {
347        let stats = self.error_recovery.get_error_statistics();
348        if stats.total_errors > 0 {
349            stats.resolved_errors as f64 / stats.total_errors as f64
350        } else {
351            1.0 // Perfect rate if no errors
352        }
353    }
354
355    /// Show transparency report
356    pub fn show_transparency_report(&self, detailed: bool) {
357        let report = self.decision_tracker.generate_transparency_report();
358        let error_stats = self.error_recovery.get_error_statistics();
359
360        if detailed && self.config.verbose {
361            println!(
362                "{} {}",
363                style("[TRANSPARENCY]").magenta().bold(),
364                "Session Transparency Summary:"
365            );
366            println!(
367                "  {} total decisions made",
368                style(report.total_decisions).cyan()
369            );
370            println!(
371                "  {} successful ({}% success rate)",
372                style(report.successful_decisions).green(),
373                if report.total_decisions > 0 {
374                    (report.successful_decisions * 100) / report.total_decisions
375                } else {
376                    0
377                }
378            );
379            println!(
380                "  {} failed decisions",
381                style(report.failed_decisions).red()
382            );
383            println!("  {} tool calls executed", style(report.tool_calls).blue());
384            println!(
385                "  Session duration: {} seconds",
386                style(report.session_duration).yellow()
387            );
388            if let Some(avg_confidence) = report.avg_confidence {
389                println!(
390                    "  {:.1}% average decision confidence",
391                    avg_confidence * 100.0
392                );
393            }
394
395            // Error recovery statistics
396            println!(
397                "\n{} {}",
398                style("[ERROR RECOVERY]").red().bold(),
399                "Error Statistics:"
400            );
401            println!(
402                "  {} total errors occurred",
403                style(error_stats.total_errors).red()
404            );
405            println!(
406                "  {} errors resolved ({}% recovery rate)",
407                style(error_stats.resolved_errors).green(),
408                if error_stats.total_errors > 0 {
409                    (error_stats.resolved_errors * 100) / error_stats.total_errors
410                } else {
411                    0
412                }
413            );
414            println!(
415                "  {:.1} average recovery attempts per error",
416                style(error_stats.avg_recovery_attempts).yellow()
417            );
418
419            // Conversation summarization statistics
420            let summaries = self.summarizer.get_summaries();
421            if !summaries.is_empty() {
422                println!(
423                    "\n{} {}",
424                    style("[CONVERSATION SUMMARY]").green().bold(),
425                    "Statistics:"
426                );
427                println!("  {} summaries generated", style(summaries.len()).cyan());
428                if let Some(latest) = self.summarizer.get_latest_summary() {
429                    println!(
430                        "  {} Latest summary: {} turns, {:.1}% compression",
431                        style("(SUMMARY)").dim(),
432                        latest.total_turns,
433                        latest.compression_ratio * 100.0
434                    );
435                }
436            }
437        } else {
438            // Brief summary for non-verbose mode
439            println!("{}", style(format!("  ↳ Session complete: {} decisions, {} successful ({}% success rate), {} errors",
440                         report.total_decisions, report.successful_decisions,
441                         if report.total_decisions > 0 { (report.successful_decisions * 100) / report.total_decisions } else { 0 },
442                         error_stats.total_errors)).dim());
443        }
444    }
445
446    /// Shutdown the agent and cleanup resources
447    pub async fn shutdown(&mut self) -> Result<()> {
448        // Show final transparency report
449        self.show_transparency_report(true);
450
451        if self.config.verbose {
452            println!(
453                "{} {}",
454                style("[SHUTDOWN]").cyan().bold(),
455                "Agent shutdown complete"
456            );
457        }
458
459        Ok(())
460    }
461}
462
463/// Builder pattern for creating agents with custom configuration
464pub struct AgentBuilder {
465    config: AgentConfig,
466}
467
468impl AgentBuilder {
469    pub fn new() -> Self {
470        Self {
471            config: AgentConfig {
472                model: ModelId::default().as_str().to_string(),
473                api_key: String::new(),
474                workspace: std::env::current_dir()
475                    .unwrap_or_else(|_| std::path::PathBuf::from(".")),
476                verbose: false,
477                theme: crate::config::constants::defaults::DEFAULT_THEME.to_string(),
478            },
479        }
480    }
481
482    pub fn with_model<S: Into<String>>(mut self, model: S) -> Self {
483        self.config.model = model.into();
484        self
485    }
486
487    pub fn with_api_key<S: Into<String>>(mut self, api_key: S) -> Self {
488        self.config.api_key = api_key.into();
489        self
490    }
491
492    pub fn with_workspace<P: Into<std::path::PathBuf>>(mut self, workspace: P) -> Self {
493        self.config.workspace = workspace.into();
494        self
495    }
496
497    pub fn with_verbose(mut self, verbose: bool) -> Self {
498        self.config.verbose = verbose;
499        self
500    }
501
502    pub fn build(self) -> Result<Agent> {
503        Agent::new(self.config)
504    }
505}
506
507impl Default for AgentBuilder {
508    fn default() -> Self {
509        Self::new()
510    }
511}