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