1use 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
22pub 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 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 pub fn with_components(config: AgentConfig, components: AgentComponentSet) -> Self {
48 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 pub fn with_components_and_optimization(
68 config: AgentConfig,
69 components: AgentComponentSet,
70 optimization_config: OptimizationConfig,
71 ) -> Self {
72 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 pub fn component_builder(config: &AgentConfig) -> AgentComponentBuilder<'_> {
91 AgentComponentBuilder::new(config)
92 }
93
94 pub async fn initialize(&mut self) -> Result<()> {
96 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 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 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 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 pub fn config(&self) -> &AgentConfig {
145 &self.config
146 }
147
148 pub fn session_info(&self) -> &SessionInfo {
150 &self.session_info
151 }
152
153 pub fn optimization_config(&self) -> &OptimizationConfig {
155 &self.optimization_config
156 }
157
158 pub fn tool_registry_ref(&self) -> &ToolRegistry {
160 self.tool_registry.as_ref()
161 }
162
163 pub fn tool_registry(&self) -> &Arc<ToolRegistry> {
165 &self.tool_registry
166 }
167
168 pub fn has_optimizations_enabled(&self) -> bool {
170 self.optimization_config.memory_pool.enabled
171 || self.tool_registry.has_optimizations_enabled()
172 }
173
174 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, 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 pub fn decision_tracker(&self) -> &DecisionTracker {
195 &self.decision_tracker
196 }
197
198 pub fn decision_tracker_mut(&mut self) -> &mut DecisionTracker {
200 &mut self.decision_tracker
201 }
202
203 pub fn error_recovery(&self) -> &ErrorRecoveryManager {
205 &self.error_recovery
206 }
207
208 pub fn error_recovery_mut(&mut self) -> &mut ErrorRecoveryManager {
210 &mut self.error_recovery
211 }
212
213 pub fn tool_registry_clone(&self) -> Arc<ToolRegistry> {
215 Arc::clone(&self.tool_registry)
216 }
217
218 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 pub fn llm(&self) -> &AnyClient {
230 &self.client
231 }
232
233 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 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 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 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 }
265 }
266
267 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 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 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 pub async fn shutdown(&mut self) -> Result<()> {
339 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
353pub 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}