Skip to main content

bamboo_agent/agent/core/tools/
agentic.rs

1//! Agentic tool execution framework.
2//!
3//! This module provides a framework for implementing autonomous agent tools
4//! that can iterate and interact with the environment through tool calls.
5//!
6//! # Overview
7//!
8//! The agentic system allows tools to:
9//! - Execute multiple iterations toward a goal
10//! - Maintain state across iterations
11//! - Record interactions between user, assistant, and tools
12//! - Perform autonomous code review and refinement
13//!
14//! # Key Types
15//!
16//! - [`ToolGoal`] - Defines the objective and parameters for an agentic tool
17//! - [`AgenticContext`] - Execution context with state and interaction history
18//! - [`Interaction`] - Records of user/assistant/tool exchanges
19//! - [`ToolExecutor`] - Trait for executing tool calls
20//! - [`AgenticTool`] - Trait for autonomous agentic tools
21//!
22//! # Example
23//!
24//! ```rust,ignore
25//! use bamboo_agent::agent::core::tools::agentic::*;
26//!
27//! let goal = ToolGoal::new("Review code for bugs", json!({"file": "main.rs"}));
28//! let context = AgenticContext::new(executor);
29//! let tool = SmartCodeReviewTool::new();
30//! let result = tool.execute(goal, context).await?;
31//! ```
32
33use async_trait::async_trait;
34use chrono::{DateTime, Utc};
35use serde::{Deserialize, Serialize};
36use serde_json::Value;
37use std::sync::Arc;
38use tokio::sync::RwLock;
39
40use crate::agent::core::tools::{FunctionCall, ToolCall, ToolError};
41
42/// Represents the objective and parameters for an agentic tool execution.
43///
44/// A `ToolGoal` defines what an agentic tool should accomplish, including
45/// the intent, input parameters, and iteration limits.
46///
47/// # Fields
48///
49/// * `intent` - High-level description of what the tool should achieve
50/// * `params` - JSON parameters for the tool execution
51/// * `max_iterations` - Maximum number of iterations before stopping (default: 10)
52///
53/// # Example
54///
55/// ```rust,ignore
56/// let goal = ToolGoal::new("Fix all bugs", json!({"files": ["main.rs", "lib.rs"]}))
57///     .with_max_iterations(20);
58/// ```
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct ToolGoal {
61    pub intent: String,
62    /// Input parameters for the tool execution
63    pub params: Value,
64    /// Maximum iterations before stopping
65    pub max_iterations: usize,
66}
67
68impl ToolGoal {
69    /// Create a new tool goal with intent and parameters.
70    ///
71    /// Uses the default maximum iteration count of 10.
72    ///
73    /// # Arguments
74    ///
75    /// * `intent` - High-level description of the goal
76    /// * `params` - JSON parameters for execution
77    ///
78    /// # Example
79    ///
80    /// ```rust,ignore
81    /// let goal = ToolGoal::new("Review code", json!({"path": "src/main.rs"}));
82    /// ```
83    pub fn new(intent: impl Into<String>, params: Value) -> Self {
84        Self {
85            intent: intent.into(),
86            params,
87            max_iterations: 10,
88        }
89    }
90
91    /// Set custom maximum iteration count.
92    ///
93    /// # Arguments
94    ///
95    /// * `max_iterations` - Maximum iterations allowed
96    ///
97    /// # Example
98    ///
99    /// ```rust,ignore
100    /// let goal = ToolGoal::new("Complex task", params)
101    ///     .with_max_iterations(50);
102    /// ```
103    pub fn with_max_iterations(mut self, max_iterations: usize) -> Self {
104        self.max_iterations = max_iterations;
105        self
106    }
107}
108
109/// Role of an interaction participant.
110///
111/// Identifies who generated an interaction in the agentic loop.
112///
113/// # Variants
114///
115/// * `User` - Human user input
116/// * `Assistant` - AI assistant response
117/// * `Tool` - Tool execution result
118/// * `System` - System message
119#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
120#[serde(rename_all = "lowercase")]
121pub enum InteractionRole {
122    User,
123    Assistant,
124    Tool,
125    System,
126}
127
128/// Represents a single interaction in the agentic execution loop.
129///
130/// Records exchanges between user, assistant, and tools during
131/// autonomous agent execution.
132///
133/// # Variants
134///
135/// * `User` - User message with timestamp
136/// * `Assistant` - Assistant response with optional metadata
137/// * `ToolAction` - Tool call initiated
138/// * `ToolObservation` - Tool execution result
139/// * `System` - System message
140///
141/// # Example
142///
143/// ```rust,ignore
144/// let user_msg = Interaction::User {
145///     message: "Fix the bug".to_string(),
146///     timestamp: Utc::now(),
147/// };
148///
149/// let tool_action = Interaction::ToolAction {
150///     call: tool_call,
151///     timestamp: Utc::now(),
152/// };
153/// ```
154#[derive(Debug, Clone, Serialize, Deserialize)]
155#[serde(tag = "type", rename_all = "snake_case")]
156pub enum Interaction {
157    /// User message
158    User {
159        /// Message content from user
160        message: String,
161        /// When the message was sent
162        timestamp: DateTime<Utc>,
163    },
164    /// Assistant response
165    Assistant {
166        /// Response message
167        message: String,
168        /// Optional metadata (e.g., confidence, reasoning)
169        metadata: Option<Value>,
170        /// When the response was generated
171        timestamp: DateTime<Utc>,
172    },
173    /// Tool call initiated
174    ToolAction {
175        /// The tool call being made
176        call: ToolCall,
177        /// When the call was initiated
178        timestamp: DateTime<Utc>,
179    },
180    /// Tool execution result
181    ToolObservation {
182        /// Name of the tool that was executed
183        tool_name: String,
184        /// Output/result from the tool
185        output: String,
186        /// When the observation was made
187        timestamp: DateTime<Utc>,
188    },
189    /// System message
190    System {
191        /// System message content
192        message: String,
193        /// When the message was generated
194        timestamp: DateTime<Utc>,
195    },
196}
197
198impl Interaction {
199    fn from_role(
200        role: InteractionRole,
201        content: impl Into<String>,
202        metadata: Option<Value>,
203    ) -> Self {
204        let content = content.into();
205        let timestamp = Utc::now();
206
207        match role {
208            InteractionRole::User => Self::User {
209                message: content,
210                timestamp,
211            },
212            InteractionRole::Assistant => Self::Assistant {
213                message: content,
214                metadata,
215                timestamp,
216            },
217            InteractionRole::Tool => Self::ToolObservation {
218                tool_name: "agentic_tool".to_string(),
219                output: content,
220                timestamp,
221            },
222            InteractionRole::System => Self::System {
223                message: content,
224                timestamp,
225            },
226        }
227    }
228}
229
230/// Execution context for agentic tools.
231///
232/// Maintains state, interaction history, and tool executor reference
233/// for autonomous agent execution loops.
234///
235/// # Fields
236///
237/// * `state` - Shared mutable state for cross-iteration persistence
238/// * `interaction_history` - Record of all interactions
239/// * `base_executor` - Tool executor for running tools
240///
241/// # Thread Safety
242///
243/// The `state` field uses `Arc<RwLock<Value>>` for thread-safe
244/// concurrent access from multiple async tasks.
245///
246/// # Example
247///
248/// ```rust,ignore
249/// let executor = Arc::new(BuiltinToolExecutor::new());
250/// let context = AgenticContext::new(executor);
251///
252/// // Record interactions
253/// context.record_interaction(InteractionRole::User, "Hello");
254///
255/// // Update state
256/// context.update_state(json!({"step": 1})).await;
257///
258/// // Check iterations
259/// if !context.increment_iteration(10) {
260///     // Continue execution
261/// }
262/// ```
263pub struct AgenticContext {
264    /// Shared mutable state (thread-safe)
265    pub state: Arc<RwLock<Value>>,
266    /// History of all interactions
267    pub interaction_history: Vec<Interaction>,
268    /// Tool executor reference
269    pub base_executor: Arc<dyn ToolExecutor>,
270    /// Current iteration count
271    iteration_count: usize,
272}
273
274impl AgenticContext {
275    /// Create a new agentic context with empty state.
276    ///
277    /// # Arguments
278    ///
279    /// * `base_executor` - Tool executor for running tools
280    ///
281    /// # Example
282    ///
283    /// ```rust,ignore
284    /// let executor = Arc::new(MyExecutor::new());
285    /// let context = AgenticContext::new(executor);
286    /// ```
287    pub fn new(base_executor: Arc<dyn ToolExecutor>) -> Self {
288        Self {
289            state: Arc::new(RwLock::new(serde_json::json!({}))),
290            interaction_history: Vec::new(),
291            base_executor,
292            iteration_count: 0,
293        }
294    }
295
296    /// Create context with custom initial state.
297    ///
298    /// # Arguments
299    ///
300    /// * `base_executor` - Tool executor
301    /// * `initial_state` - Initial state value
302    ///
303    /// # Example
304    ///
305    /// ```rust,ignore
306    /// let state = json!({"files": ["main.rs"]});
307    /// let context = AgenticContext::with_state(executor, state);
308    /// ```
309    pub fn with_state(base_executor: Arc<dyn ToolExecutor>, initial_state: Value) -> Self {
310        Self {
311            state: Arc::new(RwLock::new(initial_state)),
312            interaction_history: Vec::new(),
313            base_executor,
314            iteration_count: 0,
315        }
316    }
317
318    /// Record a simple interaction without metadata.
319    ///
320    /// # Arguments
321    ///
322    /// * `role` - Role of the interaction source
323    /// * `content` - Interaction content
324    pub fn record_interaction(&mut self, role: InteractionRole, content: impl Into<String>) {
325        self.interaction_history
326            .push(Interaction::from_role(role, content, None));
327    }
328
329    /// Record an interaction with metadata.
330    ///
331    /// # Arguments
332    ///
333    /// * `role` - Role of the interaction source
334    /// * `content` - Interaction content
335    /// * `metadata` - Additional metadata
336    pub fn record_interaction_with_metadata(
337        &mut self,
338        role: InteractionRole,
339        content: impl Into<String>,
340        metadata: Value,
341    ) {
342        self.interaction_history
343            .push(Interaction::from_role(role, content, Some(metadata)));
344    }
345
346    /// Record a tool call action.
347    ///
348    /// # Arguments
349    ///
350    /// * `call` - Tool call to record
351    pub fn record_tool_action(&mut self, call: ToolCall) {
352        self.interaction_history.push(Interaction::ToolAction {
353            call,
354            timestamp: Utc::now(),
355        });
356    }
357
358    /// Record a tool execution result.
359    ///
360    /// # Arguments
361    ///
362    /// * `tool_name` - Name of the tool
363    /// * `output` - Tool execution output
364    pub fn record_tool_observation(
365        &mut self,
366        tool_name: impl Into<String>,
367        output: impl Into<String>,
368    ) {
369        self.interaction_history.push(Interaction::ToolObservation {
370            tool_name: tool_name.into(),
371            output: output.into(),
372            timestamp: Utc::now(),
373        });
374    }
375
376    /// Increment iteration count and check limit.
377    ///
378    /// # Arguments
379    ///
380    /// * `max_iterations` - Maximum allowed iterations
381    ///
382    /// # Returns
383    ///
384    /// `true` if limit exceeded, `false` if within limit
385    pub fn increment_iteration(&mut self, max_iterations: usize) -> bool {
386        self.iteration_count += 1;
387        self.iteration_count > max_iterations
388    }
389
390    /// Get current iteration count.
391    pub fn iteration_count(&self) -> usize {
392        self.iteration_count
393    }
394
395    /// Check if this is the first iteration.
396    pub fn is_first_iteration(&self) -> bool {
397        self.iteration_count == 0
398    }
399
400    /// Read shared state asynchronously.
401    ///
402    /// Returns a read guard that releases when dropped.
403    pub async fn read_state(&self) -> tokio::sync::RwLockReadGuard<'_, Value> {
404        self.state.read().await
405    }
406
407    /// Write shared state asynchronously.
408    ///
409    /// Returns a write guard that releases when dropped.
410    pub async fn write_state(&self) -> tokio::sync::RwLockWriteGuard<'_, Value> {
411        self.state.write().await
412    }
413
414    /// Replace state with a new value.
415    ///
416    /// # Arguments
417    ///
418    /// * `new_state` - New state value
419    pub async fn update_state(&self, new_state: Value) {
420        let mut state = self.state.write().await;
421        *state = new_state;
422    }
423
424    /// Merge partial state into existing state.
425    ///
426    /// Only updates keys that exist in the partial value.
427    /// Requires both existing and partial states to be JSON objects.
428    ///
429    /// # Arguments
430    ///
431    /// * `partial` - Partial state to merge
432    pub async fn merge_state(&self, partial: Value) {
433        let mut state = self.state.write().await;
434        if let Value::Object(ref mut existing) = *state {
435            if let Value::Object(partial_map) = partial {
436                for (key, value) in partial_map {
437                    existing.insert(key, value);
438                }
439            }
440        }
441    }
442}
443
444/// Trait for executing tool calls in agentic context.
445///
446/// Implement this trait to provide custom tool execution logic
447/// for agentic tools.
448///
449/// # Required Methods
450///
451/// - `execute` - Execute a tool call
452///
453/// # Example
454///
455/// ```rust,ignore
456/// struct MyExecutor;
457///
458/// #[async_trait]
459/// impl ToolExecutor for MyExecutor {
460///     async fn execute(&self, call: &ToolCall) -> Result<ToolResult, ToolError> {
461///         // Execute tool and return result
462///         Ok(ToolResult::success("Done"))
463///     }
464/// }
465/// ```
466#[async_trait]
467pub trait ToolExecutor: Send + Sync {
468    /// Execute a tool call.
469    async fn execute(&self, call: &ToolCall) -> Result<ToolResult, ToolError>;
470}
471
472/// Result from agentic tool execution.
473///
474/// Indicates the outcome of an agentic tool's execution step.
475///
476/// # Variants
477///
478/// * `Success` - Task completed successfully
479/// * `Error` - Execution failed
480/// * `NeedClarification` - Need user input
481/// * `NeedMoreActions` - More tool calls needed
482///
483/// # Example
484///
485/// ```rust,ignore
486/// let result = ToolResult::success("Task completed");
487/// let error = ToolResult::error("Failed to execute");
488/// let clarify = ToolResult::need_clarification("Which file?");
489/// let more = ToolResult::need_more_actions(vec![tool_call], "Need to read file");
490/// ```
491#[derive(Debug, Clone, Serialize, Deserialize)]
492#[serde(tag = "type", content = "data")]
493pub enum ToolResult {
494    Success {
495        result: String,
496    },
497    Error {
498        error: String,
499    },
500    NeedClarification {
501        question: String,
502        options: Option<Vec<String>>,
503    },
504    NeedMoreActions {
505        /// Tool calls to execute
506        actions: Vec<ToolCall>,
507        /// Reason for needing more actions
508        reason: String,
509    },
510}
511
512/// Type alias for agentic tool results.
513pub type AgenticToolResult = ToolResult;
514
515impl ToolResult {
516    /// Create a success result.
517    pub fn success(result: impl Into<String>) -> Self {
518        Self::Success {
519            result: result.into(),
520        }
521    }
522
523    /// Create an error result.
524    pub fn error(error: impl Into<String>) -> Self {
525        Self::Error {
526            error: error.into(),
527        }
528    }
529
530    /// Create a clarification request without options.
531    pub fn need_clarification(question: impl Into<String>) -> Self {
532        Self::NeedClarification {
533            question: question.into(),
534            options: None,
535        }
536    }
537
538    /// Create a clarification request with predefined options.
539    pub fn need_clarification_with_options(
540        question: impl Into<String>,
541        options: Vec<String>,
542    ) -> Self {
543        Self::NeedClarification {
544            question: question.into(),
545            options: Some(options),
546        }
547    }
548
549    /// Request more tool executions.
550    pub fn need_more_actions(actions: Vec<ToolCall>, reason: impl Into<String>) -> Self {
551        Self::NeedMoreActions {
552            actions,
553            reason: reason.into(),
554        }
555    }
556
557    /// Check if result is successful.
558    pub fn is_success(&self) -> bool {
559        matches!(self, Self::Success { .. })
560    }
561
562    /// Check if result is an error.
563    pub fn is_error(&self) -> bool {
564        matches!(self, Self::Error { .. })
565    }
566
567    /// Check if clarification is needed.
568    pub fn needs_clarification(&self) -> bool {
569        matches!(self, Self::NeedClarification { .. })
570    }
571
572    /// Check if more actions are needed.
573    pub fn needs_more_actions(&self) -> bool {
574        matches!(self, Self::NeedMoreActions { .. })
575    }
576}
577
578/// Trait for implementing autonomous agentic tools.
579///
580/// Agentic tools can execute multiple iterations, maintain state,
581/// and make autonomous decisions about which tools to call.
582///
583/// # Required Methods
584///
585/// - `name()` - Tool identifier
586/// - `description()` - Tool description
587/// - `execute()` - Execute the agentic tool
588///
589/// # Difference from Regular Tools
590///
591/// Unlike regular [`Tool`](crate::agent::core::tools::registry::Tool) trait:
592/// - Receives a [`ToolGoal`] instead of direct arguments
593/// - Has access to [`AgenticContext`] for state and history
594/// - Can execute multiple iterations
595/// - Returns [`ToolResult`] with continuation options
596///
597/// # Example
598///
599/// ```rust,ignore
600/// struct SmartCodeReviewTool;
601///
602/// #[async_trait]
603/// impl AgenticTool for SmartCodeReviewTool {
604///     fn name(&self) -> &str {
605///         "smart_code_review"
606///     }
607///
608///     fn description(&self) -> &str {
609///         "Autonomously review and fix code issues"
610///     }
611///
612///     async fn execute(
613///         &self,
614///         goal: ToolGoal,
615///         context: &mut AgenticContext,
616///     ) -> Result<ToolResult, ToolError> {
617///         // Iterate until goal achieved
618///         loop {
619///             if context.increment_iteration(goal.max_iterations) {
620///                 return Ok(ToolResult::error("Max iterations reached"));
621///             }
622///
623///             // Analyze code and decide actions
624///             // Execute tools via context.base_executor
625///             // Update state and record interactions
626///
627///             if goal_achieved(&context).await {
628///                 return Ok(ToolResult::success("Review complete"));
629///             }
630///         }
631///     }
632/// }
633/// ```
634#[async_trait]
635pub trait AgenticTool: Send + Sync {
636    /// Get tool name.
637    fn name(&self) -> &str;
638
639    /// Get tool description.
640    fn description(&self) -> &str;
641
642    /// Execute the agentic tool with goal and context.
643    ///
644    /// # Arguments
645    ///
646    /// * `goal` - Execution goal and parameters
647    /// * `context` - Execution context with state and history
648    ///
649    /// # Returns
650    ///
651    /// Tool execution result indicating success, error, or need for clarification.
652    async fn execute(
653        &self,
654        goal: ToolGoal,
655        context: &mut AgenticContext,
656    ) -> Result<ToolResult, ToolError>;
657}
658
659/// Convert agentic tool result to standard tool result.
660///
661/// Maps [`ToolResult`] from this module to the standard
662/// [`crate::agent::core::tools::types::ToolResult`] format.
663///
664/// # Arguments
665///
666/// * `agentic_result` - Agentic tool result to convert
667///
668/// # Returns
669///
670/// Standard tool result with appropriate display preferences.
671///
672/// # Mapping
673///
674/// - `Success` → `success: true, display_preference: None`
675/// - `Error` → `success: false, display_preference: "error"`
676/// - `NeedClarification` → `success: true, display_preference: "clarification"`
677/// - `NeedMoreActions` → `success: true, display_preference: "continuation"`
678pub fn convert_to_standard_result(
679    agentic_result: ToolResult,
680) -> crate::agent::core::tools::types::ToolResult {
681    match agentic_result {
682        ToolResult::Success { result } => crate::agent::core::tools::types::ToolResult {
683            success: true,
684            result,
685            display_preference: None,
686        },
687        ToolResult::Error { error } => crate::agent::core::tools::types::ToolResult {
688            success: false,
689            result: error,
690            display_preference: Some("error".to_string()),
691        },
692        ToolResult::NeedClarification { question, .. } => {
693            crate::agent::core::tools::types::ToolResult {
694                success: true,
695                result: question,
696                display_preference: Some("clarification".to_string()),
697            }
698        }
699        ToolResult::NeedMoreActions { reason, .. } => {
700            crate::agent::core::tools::types::ToolResult {
701                success: true,
702                result: reason,
703                display_preference: Some("actions_needed".to_string()),
704            }
705        }
706    }
707}
708
709/// Convert standard tool result to agentic tool result.
710///
711/// Inverse of [`convert_to_standard_result`].
712///
713/// # Arguments
714///
715/// * `standard_result` - Standard tool result to convert
716///
717/// # Returns
718///
719/// Agentic tool result based on success and display preference.
720///
721/// # Mapping
722///
723/// - `success: true` + `"clarification"` → `NeedClarification`
724/// - `success: true` + `"actions_needed"` → `NeedMoreActions`
725/// - `success: true` + other → `Success`
726/// - `success: false` → `Error`
727pub fn convert_from_standard_result(
728    standard_result: crate::agent::core::tools::types::ToolResult,
729) -> ToolResult {
730    if standard_result.success {
731        match standard_result.display_preference.as_deref() {
732            Some("clarification") => ToolResult::NeedClarification {
733                question: standard_result.result,
734                options: None,
735            },
736            Some("actions_needed") => ToolResult::NeedMoreActions {
737                actions: Vec::new(),
738                reason: standard_result.result,
739            },
740            _ => ToolResult::Success {
741                result: standard_result.result,
742            },
743        }
744    } else {
745        ToolResult::Error {
746            error: standard_result.result,
747        }
748    }
749}
750
751/// Smart code review tool with autonomous execution.
752///
753/// An agentic tool that autonomously reviews code, identifies issues,
754/// and plans fixes using multiple tool calls.
755///
756/// # Capabilities
757///
758/// - Chooses appropriate review strategy
759/// - Asks clarifying questions when needed
760/// - Plans and executes follow-up actions
761/// - Tracks progress across iterations
762///
763/// # Example
764///
765/// ```rust,ignore
766/// use bamboo_agent::agent::core::tools::agentic::*;
767///
768/// let tool = SmartCodeReviewTool::new();
769/// let executor = Arc::new(BuiltinToolExecutor::new());
770/// let mut context = AgenticContext::new(executor);
771///
772/// let goal = ToolGoal::new(
773///     "Review code for bugs",
774///     json!({"files": ["src/main.rs"]})
775/// );
776///
777/// let result = tool.execute(goal, &mut context).await?;
778/// ```
779pub struct SmartCodeReviewTool {
780    /// Tool name
781    name: String,
782    /// Tool description
783    description: String,
784}
785
786impl Default for SmartCodeReviewTool {
787    fn default() -> Self {
788        Self {
789            name: "smart_code_review".to_string(),
790            description: "Autonomous reviewer that chooses review strategy, asks clarifying questions, and plans follow-up tool actions.".to_string(),
791        }
792    }
793}
794
795impl SmartCodeReviewTool {
796    /// Create a new smart code review tool.
797    pub fn new() -> Self {
798        Self::default()
799    }
800
801    /// Collect code findings from content analysis.
802    ///
803    /// Analyzes code for common issues and patterns.
804    ///
805    /// # Arguments
806    ///
807    /// * `content` - Code content to analyze
808    ///
809    /// # Returns
810    ///
811    /// Tuple of (findings list, has_critical_issues)
812    fn collect_findings(&self, content: &str) -> (Vec<String>, bool) {
813        let mut findings = Vec::new();
814        let mut has_critical = false;
815
816        if content.contains("unsafe ") {
817            has_critical = true;
818            findings.push("critical: unsafe block detected".to_string());
819        }
820
821        if content.contains("unwrap()") {
822            findings.push("warning: unwrap() detected".to_string());
823        }
824
825        if content.contains("todo!") || content.contains("TODO") {
826            findings.push("warning: unresolved TODO detected".to_string());
827        }
828
829        let long_line_count = content.lines().filter(|line| line.len() > 120).count();
830        if long_line_count > 0 {
831            findings.push(format!(
832                "warning: {} line(s) exceed 120 characters",
833                long_line_count
834            ));
835        }
836
837        (findings, has_critical)
838    }
839
840    /// Choose review strategy based on content and findings.
841    ///
842    /// # Arguments
843    ///
844    /// * `content` - Code content
845    /// * `findings` - List of findings
846    ///
847    /// # Returns
848    ///
849    /// Strategy name: "quick", "standard", or "deep"
850    fn choose_strategy(&self, content: &str, findings: &[String]) -> &'static str {
851        let line_count = content.lines().count();
852
853        if line_count > 300 || findings.len() > 3 {
854            "deep"
855        } else if line_count > 80 || !findings.is_empty() {
856            "standard"
857        } else {
858            "quick"
859        }
860    }
861
862    /// Build follow-up tool actions for code review.
863    ///
864    /// Creates tool calls for running linters and tests.
865    ///
866    /// # Arguments
867    ///
868    /// * `file_path` - Optional file path to review
869    ///
870    /// # Returns
871    ///
872    /// List of tool calls to execute
873    fn build_actions(&self, file_path: Option<&str>) -> Vec<ToolCall> {
874        let path_hint = file_path.unwrap_or("<unknown-file>");
875
876        vec![
877            ToolCall {
878                id: "agentic-action-1".to_string(),
879                tool_type: "function".to_string(),
880                function: FunctionCall {
881                    name: "execute_command".to_string(),
882                    arguments: serde_json::json!({
883                        "command": format!("cargo clippy --all-targets --all-features -- {}", path_hint)
884                    })
885                    .to_string(),
886                },
887            },
888            ToolCall {
889                id: "agentic-action-2".to_string(),
890                tool_type: "function".to_string(),
891                function: FunctionCall {
892                    name: "execute_command".to_string(),
893                    arguments: serde_json::json!({
894                        "command": "cargo test"
895                    })
896                    .to_string(),
897                },
898            },
899        ]
900    }
901}
902
903#[async_trait]
904impl AgenticTool for SmartCodeReviewTool {
905    fn name(&self) -> &str {
906        &self.name
907    }
908
909    fn description(&self) -> &str {
910        &self.description
911    }
912
913    async fn execute(
914        &self,
915        goal: ToolGoal,
916        context: &mut AgenticContext,
917    ) -> Result<ToolResult, ToolError> {
918        context.record_interaction(
919            InteractionRole::System,
920            format!("Start smart review for intent: {}", goal.intent),
921        );
922
923        let content = goal.params.get("content").and_then(Value::as_str);
924        if content.is_none() {
925            return Ok(ToolResult::need_clarification_with_options(
926                "I need the source code content to run a review. Please provide `content`."
927                    .to_string(),
928                vec![
929                    "Paste the full file content".to_string(),
930                    "Provide a trimmed snippet and target concerns".to_string(),
931                ],
932            ));
933        }
934
935        let content = content.unwrap_or_default();
936        if content.trim().is_empty() {
937            return Ok(ToolResult::need_clarification(
938                "The provided content is empty. Please share non-empty code.".to_string(),
939            ));
940        }
941
942        let first_iteration = context.is_first_iteration();
943
944        if context.increment_iteration(goal.max_iterations) {
945            return Ok(ToolResult::error(format!(
946                "max_iterations reached: {}",
947                goal.max_iterations
948            )));
949        }
950
951        let file_path = goal.params.get("file_path").and_then(Value::as_str);
952        let (findings, has_critical) = self.collect_findings(content);
953        let strategy = self.choose_strategy(content, &findings).to_string();
954
955        context
956            .merge_state(serde_json::json!({
957                "intent": goal.intent,
958                "strategy": strategy,
959                "findings": findings,
960                "line_count": content.lines().count(),
961            }))
962            .await;
963
964        if has_critical && first_iteration {
965            return Ok(ToolResult::need_clarification_with_options(
966                "I found critical safety signals (for example `unsafe`). Should I continue with defensive recommendations or stop for manual review?".to_string(),
967                vec![
968                    "Continue with defensive recommendations".to_string(),
969                    "Stop and return only critical issues".to_string(),
970                ],
971            ));
972        }
973
974        if strategy == "deep" {
975            let actions = self.build_actions(file_path);
976            let auto_execute = goal
977                .params
978                .get("auto_execute_actions")
979                .and_then(Value::as_bool)
980                .unwrap_or(false);
981
982            if auto_execute {
983                if let Some(first_action) = actions.first() {
984                    context.record_tool_action(first_action.clone());
985                    let action_result = context.base_executor.execute(first_action).await?;
986                    context.record_tool_observation(
987                        first_action.function.name.as_str(),
988                        format!("{:?}", action_result),
989                    );
990                }
991            } else {
992                return Ok(ToolResult::need_more_actions(
993                    actions,
994                    "Deep review requires lint/test evidence before final verdict.",
995                ));
996            }
997        }
998
999        let summary = serde_json::json!({
1000            "strategy": strategy,
1001            "finding_count": findings.len(),
1002            "findings": findings,
1003        });
1004
1005        Ok(ToolResult::success(
1006            serde_json::to_string_pretty(&summary).unwrap_or_default(),
1007        ))
1008    }
1009}
1010
1011#[cfg(test)]
1012mod tests {
1013    use super::*;
1014
1015    struct MockExecutor;
1016
1017    #[async_trait]
1018    impl ToolExecutor for MockExecutor {
1019        async fn execute(&self, _call: &ToolCall) -> Result<ToolResult, ToolError> {
1020            Ok(ToolResult::success("mock-executed"))
1021        }
1022    }
1023
1024    #[test]
1025    fn tool_goal_should_store_intent_params_and_iteration_limit() {
1026        let goal = ToolGoal::new(
1027            "review rust",
1028            serde_json::json!({ "content": "fn main() {}" }),
1029        )
1030        .with_max_iterations(3);
1031
1032        assert_eq!(goal.intent, "review rust");
1033        assert_eq!(goal.max_iterations, 3);
1034        assert_eq!(goal.params["content"], "fn main() {}");
1035    }
1036
1037    #[test]
1038    fn interaction_history_should_use_enum_variants() {
1039        let executor: Arc<dyn ToolExecutor> = Arc::new(MockExecutor);
1040        let mut context = AgenticContext::new(executor);
1041
1042        context.record_interaction(InteractionRole::User, "Review this function");
1043
1044        assert_eq!(context.interaction_history.len(), 1);
1045        assert!(matches!(
1046            &context.interaction_history[0],
1047            Interaction::User { message, .. } if message == "Review this function"
1048        ));
1049    }
1050
1051    #[test]
1052    fn tool_result_should_support_new_agentic_variants() {
1053        let clarification = ToolResult::need_clarification_with_options(
1054            "Which standard should I follow?",
1055            vec!["Rust style".to_string(), "Project style".to_string()],
1056        );
1057        assert!(clarification.needs_clarification());
1058
1059        let actions = ToolResult::need_more_actions(
1060            vec![ToolCall {
1061                id: "1".to_string(),
1062                tool_type: "function".to_string(),
1063                function: FunctionCall {
1064                    name: "execute_command".to_string(),
1065                    arguments: "{}".to_string(),
1066                },
1067            }],
1068            "Need more evidence",
1069        );
1070        assert!(actions.needs_more_actions());
1071    }
1072
1073    #[tokio::test]
1074    async fn smart_review_should_ask_for_content_when_missing() {
1075        let tool = SmartCodeReviewTool::new();
1076        let executor: Arc<dyn ToolExecutor> = Arc::new(MockExecutor);
1077        let mut context = AgenticContext::new(executor);
1078
1079        let goal = ToolGoal::new("review", serde_json::json!({}));
1080        let result = tool.execute(goal, &mut context).await.unwrap();
1081
1082        assert!(result.needs_clarification());
1083    }
1084
1085    #[tokio::test]
1086    async fn smart_review_should_request_more_actions_for_deep_review() {
1087        let tool = SmartCodeReviewTool::new();
1088        let executor: Arc<dyn ToolExecutor> = Arc::new(MockExecutor);
1089        let mut context = AgenticContext::new(executor);
1090
1091        let large_code = (0..360)
1092            .map(|idx| format!("fn f{}() {{ println!(\"{}\") }}", idx, idx))
1093            .collect::<Vec<_>>()
1094            .join("\n");
1095
1096        let goal = ToolGoal::new(
1097            "review",
1098            serde_json::json!({
1099                "file_path": "src/lib.rs",
1100                "content": large_code,
1101            }),
1102        );
1103
1104        let result = tool.execute(goal, &mut context).await.unwrap();
1105        assert!(result.needs_more_actions());
1106    }
1107
1108    #[tokio::test]
1109    async fn smart_review_should_return_success_for_small_clean_code() {
1110        let tool = SmartCodeReviewTool::new();
1111        let executor: Arc<dyn ToolExecutor> = Arc::new(MockExecutor);
1112        let mut context = AgenticContext::new(executor);
1113
1114        let goal = ToolGoal::new(
1115            "review",
1116            serde_json::json!({
1117                "content": "fn sum(a: i32, b: i32) -> i32 { a + b }",
1118            }),
1119        )
1120        .with_max_iterations(5);
1121
1122        let result = tool.execute(goal, &mut context).await.unwrap();
1123        assert!(result.is_success());
1124    }
1125}