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::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
19pub 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 pub fn new(config: AgentConfig) -> Result<Self> {
36 let components = AgentComponentBuilder::new(&config).build()?;
37 Ok(Self::with_components(config, components))
38 }
39
40 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 pub fn component_builder(config: &AgentConfig) -> AgentComponentBuilder<'_> {
63 AgentComponentBuilder::new(config)
64 }
65
66 pub async fn initialize(&mut self) -> Result<()> {
68 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 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 pub fn config(&self) -> &AgentConfig {
101 &self.config
102 }
103
104 pub fn session_info(&self) -> &SessionInfo {
106 &self.session_info
107 }
108
109 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, 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 pub fn decision_tracker(&self) -> &DecisionTracker {
130 &self.decision_tracker
131 }
132
133 pub fn decision_tracker_mut(&mut self) -> &mut DecisionTracker {
135 &mut self.decision_tracker
136 }
137
138 pub fn error_recovery(&self) -> &ErrorRecoveryManager {
140 &self.error_recovery
141 }
142
143 pub fn error_recovery_mut(&mut self) -> &mut ErrorRecoveryManager {
145 &mut self.error_recovery
146 }
147
148 pub fn summarizer(&self) -> &ConversationSummarizer {
150 &self.summarizer
151 }
152
153 pub fn tool_registry(&self) -> Arc<ToolRegistry> {
155 Arc::clone(&self.tool_registry)
156 }
157
158 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 pub fn llm(&self) -> &AnyClient {
166 &self.client
167 }
168
169 pub fn tree_sitter_analyzer(&self) -> &TreeSitterAnalyzer {
171 &self.tree_sitter_analyzer
172 }
173
174 pub fn tree_sitter_analyzer_mut(&mut self) -> &mut TreeSitterAnalyzer {
176 &mut self.tree_sitter_analyzer
177 }
178
179 pub fn compaction_engine(&self) -> Arc<CompactionEngine> {
181 Arc::clone(&self.compaction_engine)
182 }
183
184 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 pub async fn should_compact(&self) -> Result<bool> {
211 self.compaction_engine.should_compact().await
212 }
213
214 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 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 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 pub fn analyze_file_with_tree_sitter(
241 &mut self,
242 file_path: &std::path::Path,
243 source_code: &str,
244 ) -> Result<CodeAnalysis> {
245 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 let syntax_tree = self
259 .tree_sitter_analyzer
260 .parse(source_code, language.clone())?;
261
262 let symbols = self
264 .tree_sitter_analyzer
265 .extract_symbols(&syntax_tree, source_code, language.clone())
266 .unwrap_or_default();
267
268 let dependencies = self
270 .tree_sitter_analyzer
271 .extract_dependencies(&syntax_tree, language.clone())
272 .unwrap_or_default();
273
274 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 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 pub fn should_compress_context(&self, context_size: usize) -> bool {
301 self.error_recovery.should_compress_context(context_size)
302 }
303
304 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 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 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 }
327 }
328
329 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 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 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 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 pub async fn shutdown(&mut self) -> Result<()> {
422 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
437pub 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}