vtcode_core/core/agent/
core.rs

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