1use crate::config::core::PromptCachingConfig;
4use crate::config::models::ModelId;
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::sync::Arc;
17
18pub struct Agent {
20 config: AgentConfig,
21 client: AnyClient,
22 tool_registry: Arc<ToolRegistry>,
23 decision_tracker: DecisionTracker,
24 error_recovery: ErrorRecoveryManager,
25 summarizer: ConversationSummarizer,
26 tree_sitter_analyzer: TreeSitterAnalyzer,
27 compaction_engine: Arc<CompactionEngine>,
28 session_info: SessionInfo,
29 start_time: std::time::Instant,
30}
31
32impl Agent {
33 pub fn new(config: AgentConfig) -> Result<Self> {
35 let components = AgentComponentBuilder::new(&config).build()?;
36 Ok(Self::with_components(config, components))
37 }
38
39 pub fn with_components(config: AgentConfig, components: AgentComponentSet) -> Self {
45 Self {
46 config,
47 client: components.client,
48 tool_registry: components.tool_registry,
49 decision_tracker: components.decision_tracker,
50 error_recovery: components.error_recovery,
51 summarizer: components.summarizer,
52 tree_sitter_analyzer: components.tree_sitter_analyzer,
53 compaction_engine: components.compaction_engine,
54 session_info: components.session_info,
55 start_time: std::time::Instant::now(),
56 }
57 }
58
59 pub fn component_builder(config: &AgentConfig) -> AgentComponentBuilder<'_> {
62 AgentComponentBuilder::new(config)
63 }
64
65 pub async fn initialize(&mut self) -> Result<()> {
67 let tool_names = self.tool_registry.available_tools();
69 let tool_count = tool_names.len();
70 self.decision_tracker.update_available_tools(tool_names);
71
72 self.session_info.start_time = std::time::SystemTime::now()
74 .duration_since(std::time::UNIX_EPOCH)
75 .unwrap()
76 .as_secs();
77
78 if self.config.verbose {
79 println!("{} {}", style("[INIT]").cyan().bold(), "Agent initialized");
80 println!(" {} Model: {}", style("").dim(), self.config.model);
81 println!(
82 " {} Workspace: {}",
83 style("").dim(),
84 self.config.workspace.display()
85 );
86 println!(" {} Tools loaded: {}", style("").dim(), tool_count);
87 println!(
88 " {} Session ID: {}",
89 style("(ID)").dim(),
90 self.session_info.session_id
91 );
92 println!();
93 }
94
95 Ok(())
96 }
97
98 pub fn config(&self) -> &AgentConfig {
100 &self.config
101 }
102
103 pub fn session_info(&self) -> &SessionInfo {
105 &self.session_info
106 }
107
108 pub fn performance_metrics(&self) -> PerformanceMetrics {
110 let duration = self.start_time.elapsed();
111
112 PerformanceMetrics {
113 session_duration_seconds: duration.as_secs(),
114 total_api_calls: self.session_info.total_turns,
115 total_tokens_used: None, average_response_time_ms: if self.session_info.total_turns > 0 {
117 duration.as_millis() as f64 / self.session_info.total_turns as f64
118 } else {
119 0.0
120 },
121 tool_execution_count: self.session_info.total_decisions,
122 error_count: self.session_info.error_count,
123 recovery_success_rate: self.calculate_recovery_rate(),
124 }
125 }
126
127 pub fn decision_tracker(&self) -> &DecisionTracker {
129 &self.decision_tracker
130 }
131
132 pub fn decision_tracker_mut(&mut self) -> &mut DecisionTracker {
134 &mut self.decision_tracker
135 }
136
137 pub fn error_recovery(&self) -> &ErrorRecoveryManager {
139 &self.error_recovery
140 }
141
142 pub fn error_recovery_mut(&mut self) -> &mut ErrorRecoveryManager {
144 &mut self.error_recovery
145 }
146
147 pub fn summarizer(&self) -> &ConversationSummarizer {
149 &self.summarizer
150 }
151
152 pub fn tool_registry(&self) -> Arc<ToolRegistry> {
154 Arc::clone(&self.tool_registry)
155 }
156
157 pub fn tool_registry_mut(&mut self) -> &mut ToolRegistry {
159 Arc::get_mut(&mut self.tool_registry)
160 .expect("ToolRegistry should not have other references")
161 }
162
163 pub fn llm(&self) -> &AnyClient {
165 &self.client
166 }
167
168 pub fn tree_sitter_analyzer(&self) -> &TreeSitterAnalyzer {
170 &self.tree_sitter_analyzer
171 }
172
173 pub fn tree_sitter_analyzer_mut(&mut self) -> &mut TreeSitterAnalyzer {
175 &mut self.tree_sitter_analyzer
176 }
177
178 pub fn compaction_engine(&self) -> Arc<CompactionEngine> {
180 Arc::clone(&self.compaction_engine)
181 }
182
183 pub async fn make_intelligent_compaction_decision(
185 &self,
186 ) -> Result<crate::core::agent::intelligence::CompactionDecision> {
187 let stats = self.compaction_engine.get_statistics().await?;
188 let should_compact = self.compaction_engine.should_compact().await?;
189 let strategy = if should_compact {
190 crate::core::agent::intelligence::CompactionStrategy::Aggressive
191 } else {
192 crate::core::agent::intelligence::CompactionStrategy::Conservative
193 };
194 let reasoning = if should_compact {
195 format!("{} messages exceed thresholds", stats.total_messages)
196 } else {
197 "within configured thresholds".to_string()
198 };
199
200 Ok(crate::core::agent::intelligence::CompactionDecision {
201 should_compact,
202 strategy,
203 reasoning,
204 estimated_benefit: stats.total_memory_usage,
205 })
206 }
207
208 pub async fn should_compact(&self) -> Result<bool> {
210 self.compaction_engine.should_compact().await
211 }
212
213 pub async fn compact_messages(&self) -> Result<crate::core::agent::types::CompactionResult> {
215 self.compaction_engine
216 .compact_messages_intelligently()
217 .await
218 }
219
220 pub async fn compact_context(
222 &self,
223 context_key: &str,
224 context_data: &mut std::collections::HashMap<String, serde_json::Value>,
225 ) -> Result<crate::core::agent::types::CompactionResult> {
226 self.compaction_engine
227 .compact_context(context_key, context_data)
228 .await
229 }
230
231 pub async fn get_compaction_stats(
233 &self,
234 ) -> Result<crate::core::agent::types::CompactionStatistics> {
235 self.compaction_engine.get_statistics().await
236 }
237
238 pub fn analyze_file_with_tree_sitter(
240 &mut self,
241 file_path: &std::path::Path,
242 source_code: &str,
243 ) -> Result<CodeAnalysis> {
244 let language = self
246 .tree_sitter_analyzer
247 .detect_language_from_path(file_path)
248 .map_err(|e| {
249 anyhow!(
250 "Failed to detect language for {}: {}",
251 file_path.display(),
252 e
253 )
254 })?;
255
256 let syntax_tree = self
258 .tree_sitter_analyzer
259 .parse(source_code, language.clone())?;
260
261 let symbols = self
263 .tree_sitter_analyzer
264 .extract_symbols(&syntax_tree, source_code, language.clone())
265 .unwrap_or_default();
266
267 let dependencies = self
269 .tree_sitter_analyzer
270 .extract_dependencies(&syntax_tree, language.clone())
271 .unwrap_or_default();
272
273 let metrics = self
275 .tree_sitter_analyzer
276 .calculate_metrics(&syntax_tree, source_code)
277 .unwrap_or_default();
278
279 Ok(CodeAnalysis {
280 file_path: file_path.to_string_lossy().to_string(),
281 language,
282 symbols,
283 dependencies,
284 metrics,
285 issues: Vec::new(),
286 complexity: crate::tools::tree_sitter::analysis::ComplexityMetrics::default(),
287 structure: crate::tools::tree_sitter::analysis::CodeStructure::default(),
288 })
289 }
290
291 pub fn update_session_stats(&mut self, turns: usize, decisions: usize, errors: usize) {
293 self.session_info.total_turns = turns;
294 self.session_info.total_decisions = decisions;
295 self.session_info.error_count = errors;
296 }
297
298 pub fn should_compress_context(&self, context_size: usize) -> bool {
300 self.error_recovery.should_compress_context(context_size)
301 }
302
303 pub fn generate_context_plan(
305 &self,
306 context_size: usize,
307 ) -> crate::core::error_recovery::ContextPreservationPlan {
308 self.error_recovery
309 .generate_context_preservation_plan(context_size, self.session_info.error_count)
310 }
311
312 pub fn detect_error_pattern(&self, error_type: &ErrorType, time_window_seconds: u64) -> bool {
314 self.error_recovery
315 .detect_error_pattern(error_type, time_window_seconds)
316 }
317
318 fn calculate_recovery_rate(&self) -> f64 {
320 let stats = self.error_recovery.get_error_statistics();
321 if stats.total_errors > 0 {
322 stats.resolved_errors as f64 / stats.total_errors as f64
323 } else {
324 1.0 }
326 }
327
328 pub fn show_transparency_report(&self, detailed: bool) {
330 let report = self.decision_tracker.generate_transparency_report();
331 let error_stats = self.error_recovery.get_error_statistics();
332
333 if detailed && self.config.verbose {
334 println!(
335 "{} {}",
336 style("[TRANSPARENCY]").magenta().bold(),
337 "Session Transparency Summary:"
338 );
339 println!(
340 " {} total decisions made",
341 style(report.total_decisions).cyan()
342 );
343 println!(
344 " {} successful ({}% success rate)",
345 style(report.successful_decisions).green(),
346 if report.total_decisions > 0 {
347 (report.successful_decisions * 100) / report.total_decisions
348 } else {
349 0
350 }
351 );
352 println!(
353 " {} failed decisions",
354 style(report.failed_decisions).red()
355 );
356 println!(" {} tool calls executed", style(report.tool_calls).blue());
357 println!(
358 " Session duration: {} seconds",
359 style(report.session_duration).yellow()
360 );
361 if let Some(avg_confidence) = report.avg_confidence {
362 println!(
363 " {:.1}% average decision confidence",
364 avg_confidence * 100.0
365 );
366 }
367
368 println!(
370 "\n{} {}",
371 style("[ERROR RECOVERY]").red().bold(),
372 "Error Statistics:"
373 );
374 println!(
375 " {} total errors occurred",
376 style(error_stats.total_errors).red()
377 );
378 println!(
379 " {} errors resolved ({}% recovery rate)",
380 style(error_stats.resolved_errors).green(),
381 if error_stats.total_errors > 0 {
382 (error_stats.resolved_errors * 100) / error_stats.total_errors
383 } else {
384 0
385 }
386 );
387 println!(
388 " {:.1} average recovery attempts per error",
389 style(error_stats.avg_recovery_attempts).yellow()
390 );
391
392 let summaries = self.summarizer.get_summaries();
394 if !summaries.is_empty() {
395 println!(
396 "\n{} {}",
397 style("[CONVERSATION SUMMARY]").green().bold(),
398 "Statistics:"
399 );
400 println!(" {} summaries generated", style(summaries.len()).cyan());
401 if let Some(latest) = self.summarizer.get_latest_summary() {
402 println!(
403 " {} Latest summary: {} turns, {:.1}% compression",
404 style("(SUMMARY)").dim(),
405 latest.total_turns,
406 latest.compression_ratio * 100.0
407 );
408 }
409 }
410 } else {
411 println!("{}", style(format!(" ↳ Session complete: {} decisions, {} successful ({}% success rate), {} errors",
413 report.total_decisions, report.successful_decisions,
414 if report.total_decisions > 0 { (report.successful_decisions * 100) / report.total_decisions } else { 0 },
415 error_stats.total_errors)).dim());
416 }
417 }
418
419 pub async fn shutdown(&mut self) -> Result<()> {
421 self.show_transparency_report(true);
423
424 if self.config.verbose {
425 println!(
426 "{} {}",
427 style("[SHUTDOWN]").cyan().bold(),
428 "Agent shutdown complete"
429 );
430 }
431
432 Ok(())
433 }
434}
435
436pub struct AgentBuilder {
438 config: AgentConfig,
439}
440
441impl AgentBuilder {
442 pub fn new() -> Self {
443 Self {
444 config: AgentConfig {
445 model: ModelId::default().as_str().to_string(),
446 api_key: String::new(),
447 provider: "gemini".to_string(),
448 workspace: std::env::current_dir()
449 .unwrap_or_else(|_| std::path::PathBuf::from(".")),
450 verbose: false,
451 theme: crate::config::constants::defaults::DEFAULT_THEME.to_string(),
452 reasoning_effort: ReasoningEffortLevel::default(),
453 ui_surface: UiSurfacePreference::default(),
454 prompt_cache: PromptCachingConfig::default(),
455 model_source: ModelSelectionSource::WorkspaceConfig,
456 },
457 }
458 }
459
460 pub fn with_provider<S: Into<String>>(mut self, provider: S) -> Self {
461 self.config.provider = provider.into();
462 self
463 }
464
465 pub fn with_model<S: Into<String>>(mut self, model: S) -> Self {
466 self.config.model = model.into();
467 self.config.model_source = ModelSelectionSource::CliOverride;
468 self
469 }
470
471 pub fn with_api_key<S: Into<String>>(mut self, api_key: S) -> Self {
472 self.config.api_key = api_key.into();
473 self
474 }
475
476 pub fn with_workspace<P: Into<std::path::PathBuf>>(mut self, workspace: P) -> Self {
477 self.config.workspace = workspace.into();
478 self
479 }
480
481 pub fn with_verbose(mut self, verbose: bool) -> Self {
482 self.config.verbose = verbose;
483 self
484 }
485
486 pub fn build(self) -> Result<Agent> {
487 Agent::new(self.config)
488 }
489}
490
491impl Default for AgentBuilder {
492 fn default() -> Self {
493 Self::new()
494 }
495}