Skip to main content

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::memory_pool::global_pool;
8use vtcode_config::OptimizationConfig;
9
10use crate::core::agent::snapshots::{
11    DEFAULT_CHECKPOINTS_ENABLED, DEFAULT_MAX_AGE_DAYS, DEFAULT_MAX_SNAPSHOTS,
12};
13use crate::core::decision_tracker::DecisionTracker;
14use crate::core::error_recovery::{ErrorRecoveryManager, ErrorType};
15use crate::llm::AnyClient;
16use crate::tools::ToolRegistry;
17use crate::utils::colors::style;
18use anyhow::Result;
19use std::collections::BTreeMap;
20use std::sync::Arc;
21
22/// Main agent orchestrator
23pub struct Agent {
24    config: AgentConfig,
25    client: AnyClient,
26    tool_registry: Arc<ToolRegistry>,
27    optimization_config: OptimizationConfig,
28    decision_tracker: DecisionTracker,
29    error_recovery: ErrorRecoveryManager,
30
31    session_info: SessionInfo,
32    start_time: std::time::Instant,
33}
34
35impl Agent {
36    /// Create a new agent instance
37    pub async fn new(config: AgentConfig) -> Result<Self> {
38        let components = AgentComponentBuilder::new(&config).build().await?;
39        Ok(Self::with_components(config, components))
40    }
41
42    /// Construct an agent from explicit components.
43    ///
44    /// This helper enables embedding scenarios where callers manage dependencies
45    /// (for example in open-source integrations or when providing custom tool
46    /// registries).
47    pub fn with_components(config: AgentConfig, components: AgentComponentSet) -> Self {
48        // Use default optimization config for now - will be enhanced to read from VTCodeConfig
49        let optimization_config = OptimizationConfig::default();
50
51        Self {
52            config,
53            client: components.client,
54            tool_registry: components.tool_registry,
55            optimization_config,
56            decision_tracker: components.decision_tracker,
57            error_recovery: components.error_recovery,
58            session_info: components.session_info,
59            start_time: std::time::Instant::now(),
60        }
61    }
62
63    /// Construct an agent with optimization configuration from VTCodeConfig.
64    ///
65    /// This method provides full integration with the VT Code configuration system,
66    /// enabling optimizations based on user settings in vtcode.toml.
67    pub fn with_components_and_optimization(
68        config: AgentConfig,
69        components: AgentComponentSet,
70        optimization_config: OptimizationConfig,
71    ) -> Self {
72        // Note: We can't modify the registry after it's in an Arc, so we'll need to
73        // configure optimizations during registry creation in the future.
74        // For now, we store the config and the registry will use default optimizations.
75
76        Self {
77            config,
78            client: components.client,
79            tool_registry: components.tool_registry,
80            optimization_config,
81            decision_tracker: components.decision_tracker,
82            error_recovery: components.error_recovery,
83            session_info: components.session_info,
84            start_time: std::time::Instant::now(),
85        }
86    }
87
88    /// Convenience constructor for customizing agent components via the builder
89    /// pattern without manually importing the bootstrap module.
90    pub fn component_builder(config: &AgentConfig) -> AgentComponentBuilder<'_> {
91        AgentComponentBuilder::new(config)
92    }
93
94    /// Initialize the agent with system setup
95    pub async fn initialize(&mut self) -> Result<()> {
96        // Initialize memory pool if enabled
97        if self.optimization_config.memory_pool.enabled {
98            let pool = global_pool();
99            let _test_string = pool.get_string();
100            pool.return_string(_test_string);
101        }
102
103        // Initialize available tools in decision tracker
104        let tool_names = self.tool_registry.available_tools().await;
105        let tool_count = tool_names.len();
106        self.decision_tracker.update_available_tools(tool_names);
107
108        // Update session info
109        self.session_info.start_time = std::time::SystemTime::now()
110            .duration_since(std::time::UNIX_EPOCH)
111            .unwrap_or_else(|_| std::time::Duration::from_secs(0))
112            .as_secs();
113
114        if self.config.verbose {
115            println!("{} Agent initialized", style("[INIT]").cyan().bold());
116            println!("  {} Model: {}", style("").dim(), self.config.model);
117            println!(
118                "  {} Workspace: {}",
119                style("").dim(),
120                self.config.workspace.display()
121            );
122            println!("  {} Tools loaded: {}", style("").dim(), tool_count);
123
124            // Show REAL optimization status
125            if self.optimization_config.memory_pool.enabled {
126                println!("  {} Memory pool: enabled", style("").dim());
127            }
128            if self.tool_registry.has_optimizations_enabled() {
129                let (cache_size, cache_cap) = self.tool_registry.hot_cache_stats();
130                println!(
131                    "  {} Tool registry optimizations: enabled (cache: {}/{})",
132                    style("").dim(),
133                    cache_size,
134                    cache_cap
135                );
136            }
137            println!();
138        }
139
140        Ok(())
141    }
142
143    /// Get the agent's current configuration
144    pub fn config(&self) -> &AgentConfig {
145        &self.config
146    }
147
148    /// Get session information
149    pub fn session_info(&self) -> &SessionInfo {
150        &self.session_info
151    }
152
153    /// Get the optimization configuration
154    pub fn optimization_config(&self) -> &OptimizationConfig {
155        &self.optimization_config
156    }
157
158    /// Borrow the tool registry (now with integrated optimizations).
159    pub fn tool_registry_ref(&self) -> &ToolRegistry {
160        self.tool_registry.as_ref()
161    }
162
163    /// Get the shared tool registry handle for callers that need to clone it.
164    pub fn tool_registry(&self) -> &Arc<ToolRegistry> {
165        &self.tool_registry
166    }
167
168    /// Check if optimizations are enabled in the tool registry
169    pub fn has_optimizations_enabled(&self) -> bool {
170        self.optimization_config.memory_pool.enabled
171            || self.tool_registry.has_optimizations_enabled()
172    }
173
174    /// Get performance metrics
175    pub fn performance_metrics(&self) -> PerformanceMetrics {
176        let duration = self.start_time.elapsed();
177
178        PerformanceMetrics {
179            session_duration_seconds: duration.as_secs(),
180            total_api_calls: self.session_info.total_turns,
181            total_tokens_used: None, // Would need to track from API responses
182            average_response_time_ms: if self.session_info.total_turns > 0 {
183                duration.as_millis() as f64 / self.session_info.total_turns as f64
184            } else {
185                0.0
186            },
187            tool_execution_count: self.session_info.total_decisions,
188            error_count: self.session_info.error_count,
189            recovery_success_rate: self.calculate_recovery_rate(),
190        }
191    }
192
193    /// Get decision tracker reference
194    pub fn decision_tracker(&self) -> &DecisionTracker {
195        &self.decision_tracker
196    }
197
198    /// Get mutable decision tracker reference
199    pub fn decision_tracker_mut(&mut self) -> &mut DecisionTracker {
200        &mut self.decision_tracker
201    }
202
203    /// Get error recovery manager reference
204    pub fn error_recovery(&self) -> &ErrorRecoveryManager {
205        &self.error_recovery
206    }
207
208    /// Get mutable error recovery manager reference
209    pub fn error_recovery_mut(&mut self) -> &mut ErrorRecoveryManager {
210        &mut self.error_recovery
211    }
212
213    /// Get tool registry reference
214    pub fn tool_registry_clone(&self) -> Arc<ToolRegistry> {
215        Arc::clone(&self.tool_registry)
216    }
217
218    /// Get mutable tool registry reference
219    ///
220    /// # Errors
221    /// Returns an error if the Arc has outstanding references (another clone exists).
222    pub fn tool_registry_mut(&mut self) -> Result<&mut ToolRegistry> {
223        Arc::get_mut(&mut self.tool_registry).ok_or_else(|| {
224            anyhow::anyhow!("ToolRegistry has outstanding references; cannot get mutable access")
225        })
226    }
227
228    /// Get model-agnostic client reference
229    pub fn llm(&self) -> &AnyClient {
230        &self.client
231    }
232
233    /// Update session statistics
234    pub fn update_session_stats(&mut self, turns: usize, decisions: usize, errors: usize) {
235        self.session_info.total_turns = turns;
236        self.session_info.total_decisions = decisions;
237        self.session_info.error_count = errors;
238    }
239
240    // Removed: Context compression check has been removed as part of complete context optimization feature removal
241
242    /// Generate context preservation plan
243    pub fn generate_context_plan(
244        &self,
245        context_size: usize,
246    ) -> crate::core::error_recovery::ContextPreservationPlan {
247        self.error_recovery
248            .generate_context_preservation_plan(context_size, self.session_info.error_count)
249    }
250
251    /// Check for error patterns
252    pub fn detect_error_pattern(&self, error_type: &ErrorType, time_window_seconds: u64) -> bool {
253        self.error_recovery
254            .detect_error_pattern(error_type, time_window_seconds)
255    }
256
257    /// Calculate recovery success rate
258    fn calculate_recovery_rate(&self) -> f64 {
259        let stats = self.error_recovery.get_error_statistics();
260        if stats.total_errors > 0 {
261            stats.resolved_errors as f64 / stats.total_errors as f64
262        } else {
263            1.0 // Perfect rate if no errors
264        }
265    }
266
267    /// Show transparency report
268    pub fn show_transparency_report(&self, detailed: bool) {
269        let report = self.decision_tracker.generate_transparency_report();
270        let error_stats = self.error_recovery.get_error_statistics();
271
272        if detailed && self.config.verbose {
273            println!(
274                "{} Session Transparency Summary:",
275                style("[TRANSPARENCY]").magenta().bold()
276            );
277            println!(
278                "  {} total decisions made",
279                style(report.total_decisions).cyan()
280            );
281            println!(
282                "  {} successful ({}% success rate)",
283                style(report.successful_decisions).green(),
284                if report.total_decisions > 0 {
285                    (report.successful_decisions * 100) / report.total_decisions
286                } else {
287                    0
288                }
289            );
290            println!(
291                "  {} failed decisions",
292                style(report.failed_decisions).red()
293            );
294            println!("  {} tool calls executed", style(report.tool_calls).cyan());
295            println!(
296                "  Session duration: {} seconds",
297                style(report.session_duration).cyan()
298            );
299            if let Some(avg_confidence) = report.avg_confidence {
300                println!(
301                    "  {:.1}% average decision confidence",
302                    avg_confidence * 100.0
303                );
304            }
305
306            // Error recovery statistics
307            println!(
308                "\n{} Error Statistics:",
309                style("[ERROR RECOVERY]").red().bold()
310            );
311            println!(
312                "  {} total errors occurred",
313                style(error_stats.total_errors).red()
314            );
315            println!(
316                "  {} errors resolved ({}% recovery rate)",
317                style(error_stats.resolved_errors).green(),
318                if error_stats.total_errors > 0 {
319                    (error_stats.resolved_errors * 100) / error_stats.total_errors
320                } else {
321                    0
322                }
323            );
324            println!(
325                "  {:.1} average recovery attempts per error",
326                style(error_stats.avg_recovery_attempts).cyan()
327            );
328        } else {
329            // Brief summary for non-verbose mode
330            println!("{}", style(format!("  ↳ Session complete: {} decisions, {} successful ({}% success rate), {} errors",
331                         report.total_decisions, report.successful_decisions,
332                         if report.total_decisions > 0 { (report.successful_decisions * 100) / report.total_decisions } else { 0 },
333                         error_stats.total_errors)).dim());
334        }
335    }
336
337    /// Shutdown the agent and cleanup resources
338    pub async fn shutdown(&mut self) -> Result<()> {
339        // Show final transparency report
340        self.show_transparency_report(true);
341
342        if self.config.verbose {
343            println!(
344                "{} Agent shutdown complete",
345                style("[SHUTDOWN]").cyan().bold()
346            );
347        }
348
349        Ok(())
350    }
351}
352
353/// Builder pattern for creating agents with custom configuration
354pub struct AgentBuilder {
355    config: AgentConfig,
356}
357
358impl AgentBuilder {
359    pub fn new() -> Self {
360        Self {
361            config: AgentConfig {
362                model: ModelId::default().to_string(),
363                api_key: String::new(),
364                provider: Provider::Gemini.to_string(),
365                api_key_env: Provider::Gemini.default_api_key_env().to_string(),
366                workspace: std::env::current_dir()
367                    .unwrap_or_else(|_| std::path::PathBuf::from(".")),
368                verbose: false,
369                quiet: false,
370                theme: crate::config::constants::defaults::DEFAULT_THEME.to_string(),
371                reasoning_effort: ReasoningEffortLevel::default(),
372                ui_surface: UiSurfacePreference::default(),
373                prompt_cache: PromptCachingConfig::default(),
374                model_source: ModelSelectionSource::WorkspaceConfig,
375                custom_api_keys: BTreeMap::new(),
376                checkpointing_enabled: DEFAULT_CHECKPOINTS_ENABLED,
377                checkpointing_storage_dir: None,
378                checkpointing_max_snapshots: DEFAULT_MAX_SNAPSHOTS,
379                checkpointing_max_age_days: Some(DEFAULT_MAX_AGE_DAYS),
380                max_conversation_turns:
381                    crate::config::constants::defaults::DEFAULT_MAX_CONVERSATION_TURNS,
382                model_behavior: None,
383                openai_chatgpt_auth: None,
384            },
385        }
386    }
387
388    pub fn with_provider<S: Into<String>>(mut self, provider: S) -> Self {
389        self.config.provider = provider.into();
390        self
391    }
392
393    pub fn with_model<S: Into<String>>(mut self, model: S) -> Self {
394        self.config.model = model.into();
395        self.config.model_source = ModelSelectionSource::CliOverride;
396        self
397    }
398
399    pub fn with_api_key<S: Into<String>>(mut self, api_key: S) -> Self {
400        self.config.api_key = api_key.into();
401        self
402    }
403
404    pub fn with_workspace<P: Into<std::path::PathBuf>>(mut self, workspace: P) -> Self {
405        self.config.workspace = workspace.into();
406        self
407    }
408
409    pub fn with_verbose(mut self, verbose: bool) -> Self {
410        self.config.verbose = verbose;
411        self
412    }
413
414    pub async fn build(self) -> Result<Agent> {
415        Agent::new(self.config).await
416    }
417}
418
419impl Default for AgentBuilder {
420    fn default() -> Self {
421        Self::new()
422    }
423}