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::{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
20pub 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 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 pub fn with_components(config: AgentConfig, components: AgentComponentSet) -> Self {
46 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 pub fn with_components_and_optimization(
66 config: AgentConfig,
67 components: AgentComponentSet,
68 optimization_config: OptimizationConfig,
69 ) -> Self {
70 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 pub fn component_builder(config: &AgentConfig) -> AgentComponentBuilder<'_> {
89 AgentComponentBuilder::new(config)
90 }
91
92 pub async fn initialize(&mut self) -> Result<()> {
94 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 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 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 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 pub fn config(&self) -> &AgentConfig {
139 &self.config
140 }
141
142 pub fn session_info(&self) -> &SessionInfo {
144 &self.session_info
145 }
146
147 pub fn optimization_config(&self) -> &OptimizationConfig {
149 &self.optimization_config
150 }
151
152 pub fn tool_registry_ref(&self) -> &ToolRegistry {
154 self.tool_registry.as_ref()
155 }
156
157 pub fn tool_registry(&self) -> &Arc<ToolRegistry> {
159 &self.tool_registry
160 }
161
162 pub fn has_optimizations_enabled(&self) -> bool {
164 self.optimization_config.memory_pool.enabled || self.tool_registry.has_optimizations_enabled()
165 }
166
167 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, 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 pub fn decision_tracker(&self) -> &DecisionTracker {
188 &self.decision_tracker
189 }
190
191 pub fn decision_tracker_mut(&mut self) -> &mut DecisionTracker {
193 &mut self.decision_tracker
194 }
195
196 pub fn error_recovery(&self) -> &ErrorRecoveryManager {
198 &self.error_recovery
199 }
200
201 pub fn error_recovery_mut(&mut self) -> &mut ErrorRecoveryManager {
203 &mut self.error_recovery
204 }
205
206 pub fn tool_registry_clone(&self) -> Arc<ToolRegistry> {
208 Arc::clone(&self.tool_registry)
209 }
210
211 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 pub fn llm(&self) -> &AnyClient {
222 &self.client
223 }
224
225 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 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 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 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 }
253 }
254
255 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 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 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 pub async fn shutdown(&mut self) -> Result<()> {
308 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
319pub struct AgentBuilder {
321 config: AgentConfig,
322}
323
324impl AgentBuilder {
325 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 pub fn with_provider<S: Into<String>>(mut self, provider: S) -> Self {
355 self.config.provider = provider.into();
356 self
357 }
358
359 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 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 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 pub fn with_verbose(mut self, verbose: bool) -> Self {
380 self.config.verbose = verbose;
381 self
382 }
383
384 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}