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