vtcode_core/core/agent/
core.rs

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