From d42925973e146d3e03a8a51f575b74effb85f247 Mon Sep 17 00:00:00 2001 From: Iurii Bobrykov Date: Wed, 6 May 2026 23:59:54 +1200 Subject: [PATCH] feat: tool module added --- src/core/types.rs | 2 +- src/loop_control/convergence.rs | 2 +- src/loop_control/detection.rs | 11 +-- src/loop_control/fallback.rs | 143 ------------------------------ src/loop_control/loop_detector.rs | 119 ------------------------- src/stream.rs | 2 +- src/tool.rs | 44 ++++----- 7 files changed, 27 insertions(+), 296 deletions(-) diff --git a/src/core/types.rs b/src/core/types.rs index c0b8dc8..9350fd9 100644 --- a/src/core/types.rs +++ b/src/core/types.rs @@ -172,7 +172,7 @@ impl Default for AgentConfig { /// enum to drive the agent loop and report status to observers. /// /// ```text -/// Idle → Processing → WaitingForTool → Processing → … → Completed/Failed +/// Idle → Processing → WaitingForTool → Processing → ... → Completed/Failed /// ↘ Compacting ↗ /// ↘ Reflecting ↗ /// ``` diff --git a/src/loop_control/convergence.rs b/src/loop_control/convergence.rs index 42b1144..d29c5a3 100644 --- a/src/loop_control/convergence.rs +++ b/src/loop_control/convergence.rs @@ -395,7 +395,7 @@ impl Default for ConvergenceConfig { /// consecutive_count ─── how many similar responses in a row /// similarity_score ──── highest Jaccard score among compared pairs /// similar_responses ─── the response texts that triggered detection -/// action ────────────── what the caller should do (stop, warn, …) +/// action ────────────── what the caller should do (stop, warn, ...) /// ``` /// /// # Example diff --git a/src/loop_control/detection.rs b/src/loop_control/detection.rs index f6daa5d..7550751 100644 --- a/src/loop_control/detection.rs +++ b/src/loop_control/detection.rs @@ -171,8 +171,6 @@ pub enum DetectedPattern { /// assert!(repetitions >= 3); /// } /// ``` - /// - /// See the surrounding documentation and cross-references for details. LoopDetected { /// Number of times the pattern has repeated. /// @@ -213,8 +211,6 @@ pub enum DetectedPattern { /// pair of responses (0.0–1.0). /// - `consecutive_count` — how many consecutive response pairs exceeded /// the threshold. - /// - /// See the surrounding documentation and cross-references for details. ConvergenceDetected { /// Jaccard similarity score (0.0–1.0) of the most recent pair of /// responses. @@ -248,8 +244,6 @@ pub enum DetectedPattern { /// /// Callers should simply continue the turn loop when they receive /// this variant. - /// - /// See the surrounding documentation and cross-references for details. NoPattern, } @@ -351,7 +345,6 @@ pub struct DetectionConfig { /// sufficient — typical loops repeat within 3–10 operations. /// /// Default: **100**. - /// pub max_history: usize, // ================================================== @@ -657,7 +650,7 @@ pub struct DetectionStats { /// } /// /// // Record responses -/// dm.record_response("Working on step 1…"); +/// dm.record_response("Working on step 1..."); /// /// // Query at any time /// let stats = dm.stats(); @@ -1436,7 +1429,7 @@ impl DetectionManager { /// /// ```rust,ignore /// dm.record_tool_call("Read", 42); - /// dm.record_response("Working on step 1…"); + /// dm.record_response("Working on step 1..."); /// dm.reset(); /// assert!(matches!(dm.check_current_pattern(), DetectedPattern::NoPattern)); /// assert_eq!(dm.stats().turns_analyzed, 0); diff --git a/src/loop_control/fallback.rs b/src/loop_control/fallback.rs index 3ff1651..3da944c 100644 --- a/src/loop_control/fallback.rs +++ b/src/loop_control/fallback.rs @@ -95,8 +95,6 @@ use tracing::{debug, info, warn}; /// assert_eq!(state as u8, 0); /// assert_eq!(FallbackState::from(1), FallbackState::Fallback); /// ``` -/// -/// See the surrounding documentation and cross-references for details. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FallbackState { /// Circuit is **closed** — using the primary model. @@ -104,8 +102,6 @@ pub enum FallbackState { /// All requests route to the primary LLM. Consecutive failures are /// counted; once they reach the configured threshold the circuit /// trips open and transitions to [`Fallback`](FallbackState::Fallback). - /// - /// See the surrounding documentation and cross-references for details. Primary = 0, /// Circuit is **open** — using the fallback model. @@ -114,8 +110,6 @@ pub enum FallbackState { /// stays in this state until [`FallbackManager::should_try_resume_primary`] /// returns `true`, at which point it transitions to /// [`Recovering`](FallbackState::Recovering) to probe the primary. - /// - /// See the surrounding documentation and cross-references for details. Fallback = 1, /// Circuit is **half-open** — probing whether the primary has recovered. @@ -125,8 +119,6 @@ pub enum FallbackState { /// [`FallbackConfig::recovery_successes_needed`]), the circuit closes /// back to [`Primary`](FallbackState::Primary). A single failure /// immediately reopens the circuit to [`Fallback`](FallbackState::Fallback). - /// - /// See the surrounding documentation and cross-references for details. Recovering = 2, } @@ -152,8 +144,6 @@ pub enum FallbackState { /// assert_eq!(FallbackState::from(2u8), FallbackState::Recovering); /// assert_eq!(FallbackState::from(255u8), FallbackState::Primary); // unknown → safe default /// ``` -/// -/// See the surrounding documentation and cross-references for details. impl From for FallbackState { #[allow(clippy::match_same_arms)] fn from(value: u8) -> Self { @@ -182,8 +172,6 @@ impl From for FallbackState { /// let record = AttemptRecord::new("rate_limit"); /// assert_eq!(record.reason(), Some("rate_limit")); /// ``` -/// -/// See the surrounding documentation and cross-references for details. #[derive(Debug, Clone)] pub struct AttemptRecord { /// When this failure was recorded. @@ -299,8 +287,6 @@ impl AttemptRecord { /// assert_eq!(entry.attempt_count(), 2); /// assert!(entry.failed()); // max_fail_count defaults to 2 /// ``` -/// -/// See the surrounding documentation and cross-references for details. #[derive(Debug, Clone)] pub struct FallbackEntry { /// Model identifier (e.g. `"llm-70b"`). @@ -605,8 +591,6 @@ impl FallbackEntry { /// max_fail_count: 2, /// }; /// ``` -/// -/// See the surrounding documentation and cross-references for details. #[derive(Debug, Clone)] pub struct FallbackConfig { /// Number of consecutive API failures before the circuit trips open. @@ -614,10 +598,6 @@ pub struct FallbackConfig { /// Once this many failures have been recorded in a row (without an /// intervening success), the manager transitions from [`FallbackState::Primary`] /// to [`FallbackState::Fallback`]. Defaults to `3`. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// pub trip_threshold: usize, /// Minimum time to remain in [`FallbackState::Fallback`] before probing @@ -626,10 +606,6 @@ pub struct FallbackConfig { /// Checked by [`FallbackManager::should_try_resume_primary`]. Defaults /// to 60 seconds. Set this higher to give a degraded API more time to /// recover before retrying. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// pub recovery_timeout: Duration, /// Number of consecutive successful requests on the primary model during @@ -638,10 +614,6 @@ pub struct FallbackConfig { /// Each success is recorded via [`FallbackManager::record_model_success`]; /// once the count reaches this threshold, the manager transitions back /// to [`FallbackState::Primary`]. Defaults to `2`. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// pub recovery_successes_needed: usize, /// Per-model failure threshold: how many recorded failures before a @@ -651,10 +623,6 @@ pub struct FallbackConfig { /// When [`FallbackEntry::attempt_count`] reaches this value, the entry /// is considered [`failed`](FallbackEntry::failed) and is skipped by /// [`FallbackManager::fallback_model`]. Defaults to `2`. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// pub max_fail_count: usize, } @@ -672,8 +640,6 @@ pub struct FallbackConfig { /// assert_eq!(config.trip_threshold, 3); /// assert_eq!(config.max_fail_count, 2); /// ``` -/// -/// See the surrounding documentation and cross-references for details. impl Default for FallbackConfig { fn default() -> Self { Self { @@ -733,8 +699,6 @@ impl Default for FallbackConfig { /// mgr.record_model_success(); // → back to Primary /// } /// ``` -/// -/// See the surrounding documentation and cross-references for details. pub struct FallbackManager { // ================================================== // Config @@ -745,10 +709,6 @@ pub struct FallbackManager { /// this value, the circuit trips from [`FallbackState::Primary`] to /// [`FallbackState::Fallback`]. Set at construction time via /// [`FallbackManager::new`] or [`FallbackManager::with_config`]. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// fallback_threshold: usize, /// Successes needed on primary before resuming. @@ -756,10 +716,6 @@ pub struct FallbackManager { /// During [`FallbackState::Recovering`], this many consecutive /// successes must be recorded via [`record_model_success`](Self::record_model_success) /// before the circuit closes back to [`FallbackState::Primary`]. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// primary_resume_threshold: usize, /// Per-model max failure count, passed to new [`FallbackEntry`] instances. @@ -769,10 +725,6 @@ pub struct FallbackManager { /// they use [`FallbackEntry::with_max_fail_count`] with this value. /// Defaults to `2`. Override via /// [`FallbackConfig::max_fail_count`]. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// default_max_fail_count: usize, // ================================================== @@ -784,9 +736,6 @@ pub struct FallbackManager { /// or [`record_api_failure`](Self::record_api_failure). Reset to `0` /// on success ([`record_model_success`](Self::record_model_success)) /// or by calling [`reset`](Self::reset). Starts at `0`. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. consecutive_failures: AtomicUsize, /// Whether fallback has been activated (sticky flag). @@ -796,20 +745,12 @@ pub struct FallbackManager { /// the circuit on every subsequent failure. Cleared by /// [`transition_to_primary`](Self::transition_to_primary) or /// [`reset`](Self::reset). - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// fallback_activated: AtomicBool, /// Circuit breaker state (0=Primary, 1=Fallback, 2=Recovering). /// /// Stored as a [`FallbackState`] discriminant for lock-free reads. /// Use [`state()`](Self::state) to access the typed value. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// fallback_state: AtomicU8, /// Consecutive successes on primary during recovery. @@ -819,10 +760,6 @@ pub struct FallbackManager { /// Once it reaches [`primary_resume_threshold`](Self::primary_resume_threshold), /// the circuit closes to [`FallbackState::Primary`]. Reset to `0` /// on any state transition. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// primary_success_count: AtomicUsize, // ================================================== @@ -836,9 +773,6 @@ pub struct FallbackManager { /// [`original_model`](Self::original_model) and used by /// [`active_model`](Self::active_model) to decide which model to use. /// `None` until explicitly set. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. original_model: Mutex>, /// Ordered list of fallback models with their failure status. @@ -865,10 +799,6 @@ pub struct FallbackManager { /// /// `None` when no fallback model is configured or when all fallbacks /// have failed. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// active_fallback: Mutex>, /// Time when fallback was activated. @@ -880,10 +810,6 @@ pub struct FallbackManager { /// [`reset`](Self::reset). Checked by /// [`should_try_resume_primary`](Self::should_try_resume_primary) /// to enforce the cooldown period. - /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// fallback_switched_at: Mutex>, } @@ -911,8 +837,6 @@ impl FallbackManager { /// let mgr = FallbackManager::new(5, 3); /// // Trip after 5 failures, resume after 3 consecutive successes /// ``` - /// - /// Called internally by the framework during the agent loop. #[must_use] pub fn new(fallback_threshold: usize, primary_resume_threshold: usize) -> Self { Self { @@ -951,8 +875,6 @@ impl FallbackManager { /// }; /// let mgr = FallbackManager::with_config(&config); /// ``` - /// - /// Called internally by the framework during the agent loop. #[must_use] pub fn with_config(config: &FallbackConfig) -> Self { let mut mgr = Self::new(config.trip_threshold, config.recovery_successes_needed); @@ -988,8 +910,6 @@ impl FallbackManager { /// assert!(mgr.is_using_fallback()); /// assert_eq!(mgr.original_model(), Some("llm-70b".to_string())); /// ``` - /// - /// Called internally by the framework during the agent loop. #[must_use] pub fn new_with_fallback(original_model: String, fallback_threshold: usize) -> Self { let mgr = Self::new(fallback_threshold, 2); @@ -1022,8 +942,6 @@ impl FallbackManager { /// assert_eq!(mgr.original_model(), Some("llm-70b".to_string())); /// assert!(!mgr.is_using_fallback()); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn for_model(primary_model: impl Into) -> Self { let mgr = Self::new(3, 2); if let Ok(mut m) = mgr.original_model.lock() { @@ -1078,8 +996,6 @@ impl FallbackManager { /// let mgr = FallbackManager::new(3, 2); /// assert_eq!(mgr.state(), FallbackState::Primary); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn state(&self) -> FallbackState { FallbackState::from(self.fallback_state.load(Ordering::Relaxed)) } @@ -1099,8 +1015,6 @@ impl FallbackManager { /// let mgr = FallbackManager::new(3, 2); /// assert!(!mgr.is_using_fallback()); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn is_using_fallback(&self) -> bool { matches!(self.state(), FallbackState::Fallback) } @@ -1120,8 +1034,6 @@ impl FallbackManager { /// let mgr = FallbackManager::new(3, 2); /// assert!(!mgr.is_fallback_active()); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn is_fallback_active(&self) -> bool { self.fallback_activated.load(Ordering::Relaxed) } @@ -1141,8 +1053,6 @@ impl FallbackManager { /// mgr.record_model_failure(); /// assert_eq!(mgr.consecutive_failures(), 1); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn consecutive_failures(&self) -> usize { self.consecutive_failures.load(Ordering::Relaxed) } @@ -1162,8 +1072,6 @@ impl FallbackManager { /// let mgr = FallbackManager::for_model("llm-70b"); /// assert_eq!(mgr.original_model(), Some("llm-70b".to_string())); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn original_model(&self) -> Option { self.original_model.lock().ok().and_then(|m| m.clone()) } @@ -1183,8 +1091,6 @@ impl FallbackManager { /// mgr.set_original_model("llm-70b".into()); /// assert_eq!(mgr.original_model(), Some("llm-70b".to_string())); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn set_original_model(&self, model: String) { if let Ok(mut m) = self.original_model.lock() { *m = Some(model); @@ -1209,8 +1115,6 @@ impl FallbackManager { /// let mgr = FallbackManager::new(3, 2); /// assert!(mgr.fallback_switched_at().is_none()); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn fallback_switched_at(&self) -> Option { self.fallback_switched_at.lock().ok().and_then(|t| *t) } @@ -1240,8 +1144,6 @@ impl FallbackManager { /// let mgr = FallbackManager::for_model("llm-70b"); /// assert_eq!(mgr.active_model(), Some("llm-70b".to_string())); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn active_model(&self) -> Option { match self.state() { FallbackState::Primary | FallbackState::Recovering => self.original_model(), @@ -1282,8 +1184,6 @@ impl FallbackManager { /// assert_eq!(mgr.state(), FallbackState::Fallback); /// assert_eq!(mgr.active_model(), Some("llm-4".to_string())); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn set_fallback_model(&self, model: impl Into) { if let Ok(mut m) = self.fallback_models.lock() { m.clear(); @@ -1313,8 +1213,6 @@ impl FallbackManager { /// mgr.add_fallback_model("llm-120b"); /// assert_eq!(mgr.fallback_model(), Some("llm-70b".to_string())); // first in chain /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn fallback_model(&self) -> Option { self.active_fallback.lock().ok().and_then(|m| m.clone()) } @@ -1337,8 +1235,6 @@ impl FallbackManager { /// let chain = mgr.fallback_models(); /// assert_eq!(chain, vec!["llm-70b", "llm-120b", "llm-32b"]); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn fallback_models(&self) -> Vec { self.fallback_models .lock() @@ -1369,8 +1265,6 @@ impl FallbackManager { /// let chain = mgr.fallback_models(); /// assert_eq!(chain, vec!["llm-70b", "llm-120b"]); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn add_fallback_model(&self, model: impl Into) { if let Ok(mut m) = self.fallback_models.lock() { m.push(FallbackEntry::with_max_fail_count( @@ -1404,8 +1298,6 @@ impl FallbackManager { /// let chain = mgr.fallback_models(); /// assert_eq!(chain, vec!["llm-70b", "llm-120b", "llm-32b"]); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn insert_fallback_model(&self, index: usize, model: impl Into) { if let Ok(mut m) = self.fallback_models.lock() { let entry = FallbackEntry::with_max_fail_count(model, self.default_max_fail_count); @@ -1439,8 +1331,6 @@ impl FallbackManager { /// let chain = mgr.fallback_models(); /// assert_eq!(chain, vec!["llm-120b"]); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn remove_fallback_model(&self, model: &str) -> bool { let removed = if let Ok(mut m) = self.fallback_models.lock() { if let Some(pos) = m.iter().position(|x| x.name == model) { @@ -1478,8 +1368,6 @@ impl FallbackManager { /// let chain = mgr.fallback_models(); /// assert_eq!(chain, vec!["llm-70b", "llm-120b", "llm-32b"]); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn set_fallback_models(&self, models: Vec) { let max_fc = self.default_max_fail_count; if let Ok(mut m) = self.fallback_models.lock() { @@ -1521,8 +1409,6 @@ impl FallbackManager { /// // active_model skips failed, returns next available /// assert_eq!(mgr.fallback_model(), Some("llm-3".to_string())); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn mark_fallback_failed(&self, model: &str) -> bool { let found = if let Ok(mut m) = self.fallback_models.lock() { if let Some(entry) = m.iter_mut().find(|e| e.name == model) { @@ -1560,8 +1446,6 @@ impl FallbackManager { /// mgr.clear_fallback_failed("llm-2"); /// assert!(mgr.failed_fallbacks().is_empty()); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn clear_fallback_failed(&self, model: &str) -> bool { let found = if let Ok(mut m) = self.fallback_models.lock() { if let Some(entry) = m.iter_mut().find(|e| e.name == model) { @@ -1601,8 +1485,6 @@ impl FallbackManager { /// mgr.clear_all_fallback_failed(); /// assert!(mgr.failed_fallbacks().is_empty()); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn clear_all_fallback_failed(&self) { if let Ok(mut m) = self.fallback_models.lock() { for entry in m.iter_mut() { @@ -1631,8 +1513,6 @@ impl FallbackManager { /// let failed = mgr.failed_fallbacks(); /// assert_eq!(failed, vec!["llm-3"]); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn failed_fallbacks(&self) -> Vec { self.fallback_models .lock() @@ -1665,8 +1545,6 @@ impl FallbackManager { /// let available = mgr.available_fallbacks(); /// assert_eq!(available, vec!["llm-2"]); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn available_fallbacks(&self) -> Vec { self.fallback_models .lock() @@ -1704,8 +1582,6 @@ impl FallbackManager { /// /// assert!(mgr.fallback_entry("nonexistent").is_none()); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn fallback_entry(&self, name: &str) -> Option { self.fallback_models .lock() @@ -1742,8 +1618,6 @@ impl FallbackManager { /// mgr.set_fallback_available("llm-2", true); /// assert_eq!(mgr.fallback_model(), Some("llm-2".to_string())); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn set_fallback_available(&self, model: &str, available: bool) -> bool { let found = if let Ok(mut m) = self.fallback_models.lock() { if let Some(entry) = m.iter_mut().find(|e| e.name == model) { @@ -1792,8 +1666,6 @@ impl FallbackManager { /// assert!(mgr.record_api_failure()); // 3 — threshold reached /// assert!(mgr.record_api_failure()); // 4 — still not activated /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn record_api_failure(&self) -> bool { let failures = self .consecutive_failures @@ -1815,8 +1687,6 @@ impl FallbackManager { /// /// Provided for callers that prefer the shorter `record_failure` name. /// Delegates directly to [`record_api_failure`](Self::record_api_failure). - /// - /// Called internally by the framework during the agent loop. pub fn record_failure(&self) -> bool { self.record_api_failure() } @@ -1839,8 +1709,6 @@ impl FallbackManager { /// mgr.reset_failure_counter(); /// assert_eq!(mgr.consecutive_failures(), 0); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn reset_failure_counter(&self) { self.consecutive_failures.store(0, Ordering::Relaxed); } @@ -1869,8 +1737,6 @@ impl FallbackManager { /// mgr.record_model_success(); // resets failures to 0 /// assert_eq!(mgr.consecutive_failures(), 0); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn record_model_success(&self) { match self.state() { FallbackState::Primary => { @@ -1900,8 +1766,6 @@ impl FallbackManager { /// /// Provided for callers that prefer the shorter `record_success` name. /// Delegates directly to [`record_model_success`](Self::record_model_success). - /// - /// Called internally by the framework during the agent loop. pub fn record_success(&self) { self.record_model_success(); } @@ -1938,8 +1802,6 @@ impl FallbackManager { /// assert!(mgr.record_model_failure()); // 3 → trips to Fallback /// assert_eq!(mgr.state(), FallbackState::Fallback); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn record_model_failure(&self) -> bool { match self.state() { FallbackState::Primary => { @@ -2003,8 +1865,6 @@ impl FallbackManager { /// // Not in fallback state → false /// assert!(!mgr.should_try_resume_primary(Duration::from_secs(10))); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn should_try_resume_primary(&self, min_fallback_duration: Duration) -> bool { if self.state() != FallbackState::Fallback { return false; @@ -2037,8 +1897,6 @@ impl FallbackManager { /// mgr.transition_to_fallback(); /// assert_eq!(mgr.state(), FallbackState::Fallback); /// ``` - /// - /// Called internally by the framework during the agent loop. pub fn transition_to_fallback(&self) { self.fallback_state .store(FallbackState::Fallback as u8, Ordering::Relaxed); @@ -2070,7 +1928,6 @@ impl FallbackManager { /// mgr.transition_to_recovering(); /// assert_eq!(mgr.state(), FallbackState::Recovering); /// ``` - /// pub fn transition_to_recovering(&self) { self.fallback_state .store(FallbackState::Recovering as u8, Ordering::Relaxed); diff --git a/src/loop_control/loop_detector.rs b/src/loop_control/loop_detector.rs index 0e1b9bb..42143c3 100644 --- a/src/loop_control/loop_detector.rs +++ b/src/loop_control/loop_detector.rs @@ -186,8 +186,6 @@ use std::sync::{Arc, Mutex}; /// stored in an [`Arc`] inside [`LoopDetector`]. All methods take `&self`, /// so the implementation should be stateless or use interior mutability. /// -/// This value is consulted during normal operation and may be -/// updated by the framework as the agent loop progresses. pub trait ToolSignature: Send + Sync { /// Extract a primary parameter from tool input for loop comparison. /// @@ -206,8 +204,6 @@ pub trait ToolSignature: Send + Sync { /// /// Returns an empty string — no differentiation between invocations. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. fn extract_primary_param(&self, tool: &str, input: &serde_json::Value) -> String { let _ = (tool, input); String::new() @@ -250,8 +246,6 @@ pub trait ToolSignature: Send + Sync { /// } /// ``` /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. fn is_recoverable_error(&self, tool: &str, error: &str) -> bool { let _ = (tool, error); false @@ -273,8 +267,6 @@ pub trait ToolSignature: Send + Sync { /// /// Returns `None`. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. fn get_suggestion(&self, tool: &str) -> Option { let _ = tool; None @@ -319,8 +311,6 @@ pub trait ToolSignature: Send + Sync { /// } /// ``` /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. fn file_path_for_reset(&self, tool: &str, input: &serde_json::Value) -> Option { let _ = (tool, input); None @@ -341,8 +331,6 @@ pub trait ToolSignature: Send + Sync { /// /// Returns `false`. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. fn is_file_read_tool(&self, tool: &str) -> bool { let _ = tool; false @@ -364,8 +352,6 @@ pub trait ToolSignature: Send + Sync { /// /// Returns `false`. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. fn is_file_edit_tool(&self, tool: &str) -> bool { let _ = tool; false @@ -387,8 +373,6 @@ pub trait ToolSignature: Send + Sync { /// /// Returns an empty `HashMap` — no tool-specific overrides. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. fn tool_thresholds(&self) -> HashMap { HashMap::new() } @@ -481,7 +465,6 @@ pub trait ToolSignature: Send + Sync { /// ); /// ``` /// -/// See the surrounding documentation and cross-references for details. pub struct NoOpToolSignature; /// Blanket [`ToolSignature`] implementation for [`NoOpToolSignature`]. @@ -496,8 +479,6 @@ pub struct NoOpToolSignature; /// default method bodies defined in the [`ToolSignature`] trait. See the /// trait-level documentation for the semantics of each default. /// -/// See the surrounding documentation and cross-references for details. -/// impl ToolSignature for NoOpToolSignature {} /// Configuration for the [`LoopDetector`] — controls sensitivity and limits. @@ -553,7 +534,6 @@ impl ToolSignature for NoOpToolSignature {} /// repetition (e.g. `"Edit" → 2`) or particularly noisy (e.g. /// `"Grep" → 5`). /// -/// See the surrounding documentation and cross-references for details. #[derive(Debug, Clone)] pub struct LoopDetectorConfig { /// Maximum number of operations kept in the sliding window. @@ -566,8 +546,6 @@ pub struct LoopDetectorConfig { /// /// **Default:** `50`. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub window_size: usize, /// Number of identical repetitions required to flag a loop. @@ -579,8 +557,6 @@ pub struct LoopDetectorConfig { /// /// **Default:** `3`. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub repetition_threshold: usize, /// Maximum number of tool calls allowed in a single turn. @@ -592,8 +568,6 @@ pub struct LoopDetectorConfig { /// /// **Default:** `9999` (effectively unlimited). /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub max_tools_per_turn: usize, /// Maximum number of identical file reads before a warning is raised. @@ -604,8 +578,6 @@ pub struct LoopDetectorConfig { /// /// **Default:** `5`. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub max_same_file_reads: usize, /// Number of repetitions required to force-stop the agent. @@ -618,8 +590,6 @@ pub struct LoopDetectorConfig { /// /// **Default:** `10`. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub stop_threshold: usize, /// Tool-specific repetition thresholds that override @@ -633,8 +603,6 @@ pub struct LoopDetectorConfig { /// /// **Default:** empty `HashMap`. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub tool_thresholds: HashMap, } @@ -676,7 +644,6 @@ pub struct LoopDetectorConfig { /// - [`LoopDetector::new`] — constructs a detector from a config. /// - [`LoopDetectorConfig::threshold_for_tool`] — per-tool threshold lookup. /// -/// See the surrounding documentation and cross-references for details. impl Default for LoopDetectorConfig { /// Build a config with the default values described in the trait-level docs. /// @@ -684,8 +651,6 @@ impl Default for LoopDetectorConfig { /// is pre-allocated with capacity matching /// [`window_size`](LoopDetectorConfig::window_size). /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. fn default() -> Self { Self { window_size: 50, @@ -711,7 +676,6 @@ impl Default for LoopDetectorConfig { /// overrides first and falling back to the generic /// [`repetition_threshold`](LoopDetectorConfig::repetition_threshold). /// -/// See the surrounding documentation and cross-references for details. impl LoopDetectorConfig { /// Get the effective repetition threshold for a specific tool. /// @@ -736,8 +700,6 @@ impl LoopDetectorConfig { /// assert_eq!(config.threshold_for_tool("Read"), 3); // generic default /// ``` /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. #[must_use] pub fn threshold_for_tool(&self, tool_name: &str) -> usize { self.tool_thresholds @@ -792,7 +754,6 @@ impl LoopDetectorConfig { /// threshold, a loop is reported. The optional `result_hash` ensures that /// operations producing *different* outputs are not counted as repetitions. /// -/// See the surrounding documentation and cross-references for details. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Operation { /// Name of the tool that was invoked (e.g. `"Read"`, `"Edit"`, `"Bash"`). @@ -802,8 +763,6 @@ pub struct Operation { /// [`result_hash`](Operation::result_hash) it uniquely identifies a /// repeated invocation pattern. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub tool: String, /// Primary parameter that identifies the operation's target. @@ -813,8 +772,6 @@ pub struct Operation { /// operations with the same `tool` but different `primary_param` are /// *not* considered a loop (they target different resources). /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub primary_param: String, /// Hash of the tool result content, for result-aware loop detection. @@ -827,8 +784,6 @@ pub struct Operation { /// /// Generated by the free function [`hash_result`]. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub result_hash: Option, } @@ -856,7 +811,6 @@ pub struct Operation { /// or Operation::new(tool, param).with_result_hash(hash) /// ``` /// -/// See the surrounding documentation and cross-references for details. impl Operation { /// Create a new operation with the given tool name and primary parameter. /// @@ -883,8 +837,6 @@ impl Operation { /// - [`Operation::from_input_with_signature`] — when you have raw JSON input. /// - [`Operation::with_result_hash`] — to attach a result hash after creation. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn new(tool: impl Into, primary_param: impl Into) -> Self { Self { tool: tool.into(), @@ -935,8 +887,6 @@ impl Operation { /// - [`Operation::from_input_with_result_and_signature`] — full construction with hash. /// - [`ToolSignature::extract_primary_param`] — the parsing logic. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn from_input_with_signature( tool: &str, input: &serde_json::Value, @@ -987,8 +937,6 @@ impl Operation { /// - [`Operation::from_input_with_signature`] — same but without result hash. /// - [`LoopDetector::record_from_input`] — the primary caller. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn from_input_with_result_and_signature( tool: &str, input: &serde_json::Value, @@ -1033,8 +981,6 @@ impl Operation { /// - [`hash_result`] — computes the hash from tool output. /// - [`Operation::result_hash`] — the field this sets. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. #[must_use] pub fn with_result_hash(mut self, hash: Option) -> Self { self.result_hash = hash; @@ -1095,8 +1041,6 @@ impl Operation { /// - [`Operation::with_result_hash`] — attaches the hash to an operation. /// - [`Operation::result_hash`] — the field that stores the hash. /// -/// Called internally by the framework during the agent loop. -/// See the module-level documentation for the overall flow. #[must_use] pub fn hash_result(content: &str) -> Option { use std::collections::hash_map::DefaultHasher; @@ -1191,7 +1135,6 @@ pub fn hash_result(content: &str) -> Option { /// Does *not* derive [`PartialEq`] because [`Option`] comparison /// is rarely useful for status objects. /// -/// See the surrounding documentation and cross-references for details. #[derive(Debug, Clone, Default)] pub struct LoopStatus { /// Whether a loop was detected. @@ -1202,9 +1145,6 @@ pub struct LoopStatus { /// or a per-tool override from /// [`tool_thresholds`](LoopDetectorConfig::tool_thresholds)). /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// pub is_looping: bool, /// Operations that triggered the loop detection. @@ -1214,8 +1154,6 @@ pub struct LoopStatus { /// tie for the highest repetition count, all of them are included. /// Empty when [`is_looping`](LoopStatus::is_looping) is `false`. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub repeated_operations: Vec, /// Number of repetitions of the most-repeated operation. @@ -1224,8 +1162,6 @@ pub struct LoopStatus { /// [`repeated_operations`](LoopStatus::repeated_operations). Zero when /// no loop was detected. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub repetition_count: usize, /// Human-readable warning message describing the detected loop. @@ -1237,8 +1173,6 @@ pub struct LoopStatus { /// detected, or when the loop has already been warned about (to avoid /// spamming the agent with duplicate warnings). /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub warning: Option, /// Whether the agent should be force-stopped due to severe looping. @@ -1248,8 +1182,6 @@ pub struct LoopStatus { /// stop threshold is non-zero). The framework should halt the agent's /// event loop when this is `true`. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. pub should_stop: bool, } @@ -1335,7 +1267,6 @@ pub struct LoopStatus { /// | `warned_operations` | `Mutex>` | Already-warned dedup set | /// | `signature` | `Arc` | Tool-specific parsing logic | /// -/// See the surrounding documentation and cross-references for details. pub struct LoopDetector { /// Sliding window of recent [`Operation`] records. /// @@ -1344,9 +1275,6 @@ pub struct LoopDetector { /// window is scanned by [`LoopDetector::check_loop`] to find repeated /// operations. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. - /// operations: Mutex>, /// Configuration controlling thresholds and limits. @@ -1354,8 +1282,6 @@ pub struct LoopDetector { /// Set at construction time via [`LoopDetector::new`]. Immutable for /// the lifetime of the detector. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. config: LoopDetectorConfig, /// Count of tool invocations in the current turn. @@ -1365,8 +1291,6 @@ pub struct LoopDetector { /// [`LoopDetectorConfig::max_tools_per_turn`] by /// [`LoopDetector::check_turn_limit`]. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. turn_count: Mutex, /// Set of operations that have already triggered a warning. @@ -1378,8 +1302,6 @@ pub struct LoopDetector { /// progress) or when [`LoopDetector::clear`] / [`LoopDetector::reset`] /// is called. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. warned_operations: Mutex>, /// Tool signature for extracting tool-specific parameters. @@ -1389,8 +1311,6 @@ pub struct LoopDetector { /// [`LoopDetector::signature`]. The trait object is `Send + Sync` so /// it can be used from any thread. /// - /// This value is consulted during normal operation and may be - /// updated by the framework as the agent loop progresses. signature: Arc, } @@ -1408,7 +1328,6 @@ pub struct LoopDetector { /// (empty status, `false`, zero count) rather than propagulating the panic. /// This ensures the detector never crashes the agent loop. /// -/// See the surrounding documentation and cross-references for details. impl LoopDetector { /// Create a new loop detector with the given configuration and tool signature. /// @@ -1428,8 +1347,6 @@ impl LoopDetector { /// let detector = LoopDetector::new(config, Arc::new(NoOpToolSignature)); /// ``` /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn new(config: LoopDetectorConfig, signature: Arc) -> Self { Self { operations: Mutex::new(VecDeque::with_capacity(config.window_size)), @@ -1452,8 +1369,6 @@ impl LoopDetector { /// LoopDetector::new(LoopDetectorConfig::default(), Arc::new(NoOpToolSignature)); /// ``` /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. #[must_use] pub fn default_detector() -> Self { Self::new(LoopDetectorConfig::default(), Arc::new(NoOpToolSignature)) @@ -1473,8 +1388,6 @@ impl LoopDetector { /// // let detector = LoopDetector::with_signature(Arc::new(MyToolSignature)); /// ``` /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn with_signature(signature: Arc) -> Self { Self::new(LoopDetectorConfig::default(), signature) } @@ -1485,8 +1398,6 @@ impl LoopDetector { /// Useful when external code needs to query the same tool-specific /// logic (e.g. to extract parameters for logging). /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn signature(&self) -> &dyn ToolSignature { self.signature.as_ref() } @@ -1511,8 +1422,6 @@ impl LoopDetector { /// - `result_hash` — Optional hash of the tool result, typically /// generated by [`hash_result`]. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn record_from_input( &self, tool: &str, @@ -1572,8 +1481,6 @@ impl LoopDetector { /// ); /// ``` /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn record_from_input_with_error( &self, tool: &str, @@ -1621,8 +1528,6 @@ impl LoopDetector { /// directly, via [`LoopDetector::record_from_input`], or via /// [`LoopDetector::record_from_input_with_error`]. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn record(&self, operation: Operation) { // Check if this operation was previously warned with a different result if let Ok(mut warned) = self.warned_operations.lock() { @@ -1720,8 +1625,6 @@ impl LoopDetector { /// Called by the framework after each tool invocation, typically /// immediately after [`record`](LoopDetector::record). /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn check_loop(&self) -> LoopStatus { let Ok(ops) = self.operations.lock() else { return LoopStatus::default(); @@ -1823,8 +1726,6 @@ impl LoopDetector { /// Called by the framework before dispatching each tool call within a /// turn. If `true`, the framework should stop the turn. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn check_turn_limit(&self) -> bool { match self.turn_count.lock() { Ok(count) => *count >= self.config.max_tools_per_turn, @@ -1842,8 +1743,6 @@ impl LoopDetector { /// /// The turn-local call count, or `0` if the lock is poisoned. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn turn_count(&self) -> usize { self.turn_count.lock().map_or(0, |c| *c) } @@ -1858,8 +1757,6 @@ impl LoopDetector { /// At the beginning of every new turn, before any tool calls are /// dispatched. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn reset_turn(&self) { if let Ok(mut count) = self.turn_count.lock() { *count = 0; @@ -1888,8 +1785,6 @@ impl LoopDetector { /// `true` if the file has been read ≥ `max_same_file_reads` times, /// `false` otherwise (including if the lock is poisoned). /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn check_file_reads(&self, file_path: &str) -> bool { let Ok(ops) = self.operations.lock() else { return false; @@ -1926,8 +1821,6 @@ impl LoopDetector { /// - `tool` — Name of the tool being dispatched. /// - `file_path` — The file being read. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn check_and_reset_on_file_read(&self, tool: &str, file_path: &str) { if !self.signature.is_file_read_tool(tool) { return; @@ -1970,8 +1863,6 @@ impl LoopDetector { /// Called when the agent wants to clear loop history without resetting /// turn state — for example, after a successful corrective action. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn clear(&self) { if let Ok(mut ops) = self.operations.lock() { ops.clear(); @@ -1993,8 +1884,6 @@ impl LoopDetector { /// starts another — so that loop state from the previous task doesn't /// bleed into the next one. /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. pub fn reset(&self) { if let Ok(mut ops) = self.operations.lock() { ops.clear(); @@ -2034,15 +1923,12 @@ impl LoopDetector { /// - [`LoopDetector::with_signature`] — for custom tool signatures. /// - [`LoopDetector::default_detector`] — the method this delegates to. /// -/// See the surrounding documentation and cross-references for details. impl Default for LoopDetector { /// Build a detector with [`LoopDetectorConfig::default`] and [`NoOpToolSignature`]. /// /// Equivalent to `LoopDetector::default_detector()`. The internal state /// is empty (no operations recorded, zero turn count, no warnings). /// - /// Called internally by the framework during the agent loop. - /// See the module-level documentation for the overall flow. fn default() -> Self { Self::default_detector() } @@ -2065,8 +1951,6 @@ impl Default for LoopDetector { /// [`OnceLock`] guarantees safe concurrent access from multiple threads /// without additional synchronisation. /// -/// This value is consulted during normal operation and may be -/// updated by the framework as the agent loop progresses. static GLOBAL_DETECTOR: std::sync::OnceLock> = std::sync::OnceLock::new(); /// Get a reference-counted handle to the global [`LoopDetector`] singleton. @@ -2097,8 +1981,6 @@ static GLOBAL_DETECTOR: std::sync::OnceLock> = std::sync::Once /// - [`LoopDetector::new`] — for custom configuration. /// - [`LoopDetector::with_signature`] — for custom tool signatures. /// -/// Called internally by the framework during the agent loop. -/// See the module-level documentation for the overall flow. pub fn global_detector() -> Arc { Arc::clone(GLOBAL_DETECTOR.get_or_init(|| Arc::new(LoopDetector::default_detector()))) } @@ -2114,7 +1996,6 @@ mod tests { /// the `file_path` parameter for `Read` calls and the `command` parameter /// for `Bash` calls, falling back to an empty string for unknown tools. /// - /// See the surrounding documentation and cross-references for details. struct TestToolSignature; impl ToolSignature for TestToolSignature { diff --git a/src/stream.rs b/src/stream.rs index b52dece..ccb36d0 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -111,7 +111,7 @@ impl std::error::Error for StreamError { /// → PartStart /// → IndexedDelta (repeated) /// → PartStop -/// → … more parts … +/// → ... more parts ... /// → MessageDelta /// → MessageStop /// ``` diff --git a/src/tool.rs b/src/tool.rs index 220b12b..8f02e5e 100644 --- a/src/tool.rs +++ b/src/tool.rs @@ -11,8 +11,7 @@ //! - **[`Tool`]** — The trait every tool implements. Downstream crates //! provide concrete implementations. //! - **[`FnTool`]** — An adapter that wraps a plain function pointer as a -//! [`Tool`] trait object, bridging the legacy function-pointer API to the -//! new trait system. +//! [`Tool`] trait object, wrapping plain functions as trait implementations. //! - **[`ToolRegistry`]** — A name → tool map used by the agent loop for //! dynamic dispatch. //! - **[`ToolSchema`]** — JSON Schema descriptor sent to the LLM for @@ -36,7 +35,7 @@ //! fn description(&self) -> &str { "Echoes back the input" } //! fn schema(&self) -> ToolSchema { //! ToolSchema { -//! name: "echo".into(), +//! tool: "echo".into(), //! description: "Echoes back the input".into(), //! input_schema: json!({ //! "type": "object", @@ -73,7 +72,7 @@ use crate::message::ToolResult as MessageToolResult; /// /// When the agent loop sends a list of available tools to the LLM, each /// tool is represented by a [`ToolSchema`] instance. The LLM uses the -/// schema's `name`, `description`, and `input_schema` to decide which +/// schema's `tool`, `description`, and `input_schema` to decide which /// tool to call and how to format its arguments. /// /// # Construction @@ -83,7 +82,7 @@ use crate::message::ToolResult as MessageToolResult; /// ```rust,ignore /// fn schema(&self) -> ToolSchema { /// ToolSchema { -/// name: "read_file".into(), +/// tool: "read_file".into(), /// description: "Read a file from disk".into(), /// input_schema: json!({ /// "type": "object", @@ -1124,7 +1123,7 @@ impl PermissionCheck { /// fn description(&self) -> &str { "Read a file from disk" } /// fn schema(&self) -> ToolSchema { /// ToolSchema { -/// name: "read_file".into(), +/// tool: "read_file".into(), /// description: "Read a file from disk".into(), /// input_schema: json!({ /// "type": "object", @@ -1564,7 +1563,7 @@ impl Default for ToolRegistry { /// Matches the signature used by concrete tools in downstream crates: /// `fn(Value, &ToolContext) -> Pin> + Send + 'static>>`. /// -/// Used as the `f` field in [`FnTool`] to bridge function-pointer-based +/// Stored in the `f` field of [`FnTool`] to adapt function-pointer-based /// tool definitions to the [`Tool`] trait. pub type ToolFn = fn( @@ -1661,7 +1660,7 @@ pub struct FnTool { /// Called by [`Tool::call`] with the LLM-supplied input and the /// session's [`ToolContext`]. Must return a pinned, `Send` future /// producing a `Result`. - pub f: ToolFn, + pub tool_fn: ToolFn, /// Whether this tool is safe to run concurrently with itself. /// @@ -1721,12 +1720,12 @@ impl FnTool { /// my_grep_fn as ToolFn, /// ); /// ``` - pub fn new(name: String, description: String, input_schema: Value, f: ToolFn) -> Self { + pub fn new(name: String, description: String, input_schema: Value, tool_fn: ToolFn) -> Self { Self { name, description, input_schema, - f, + tool_fn, is_concurrency_safe: false, concurrency_check_fn: None, is_read_only: false, @@ -1838,16 +1837,16 @@ impl FnTool { /// /// # Delegation map /// -/// | Trait method | Delegates to | -/// |----------------------------------|------------------------------------| -/// | [`Tool::name`] | [`FnTool::name`] field accessor | -/// | [`Tool::description`] | [`FnTool::description`] accessor | -/// | [`Tool::schema`] | Clones fields into [`ToolSchema`] | -/// | [`Tool::call`] | [`FnTool::f`] function pointer | -/// | [`Tool::is_concurrency_safe`] | [`FnTool::is_concurrency_safe`] | +/// | Trait method | Delegates to | +/// |--------------------------------------------|----------------------------------------------| +/// | [`Tool::name`] | [`FnTool::name`] field accessor | +/// | [`Tool::description`] | [`FnTool::description`] accessor | +/// | [`Tool::schema`] | Clones fields into [`ToolSchema`] | +/// | [`Tool::call`] | internal function pointer | +/// | [`Tool::is_concurrency_safe`] | [`FnTool::is_concurrency_safe`] | /// | [`Tool::is_safe_for_concurrent_execution`] | [`FnTool::concurrency_check_fn`] or fallback | -/// | [`Tool::is_read_only`] | [`FnTool::is_read_only`] | -/// | [`Tool::system_prompt`] | [`FnTool::system_prompt`] clone | +/// | [`Tool::is_read_only`] | [`FnTool::is_read_only`] | +/// | [`Tool::system_prompt`] | [`FnTool::system_prompt`] clone | impl Tool for FnTool { /// Return the tool name from the [`FnTool::name`] field. /// @@ -1889,7 +1888,7 @@ impl Tool for FnTool { /// Delegate execution to the stored function pointer. /// - /// Invokes [`FnTool::f`] with the provided `input` and `context`, + /// Invokes the stored function pointer with the provided `input` and `context`, /// returning the pinned future directly. The function pointer owns /// the full future lifecycle — the `'static` bound on [`ToolFn`] /// ensures the future does not borrow from the tool adapter itself. @@ -1904,7 +1903,7 @@ impl Tool for FnTool { input: Value, context: &ToolContext, ) -> Pin> + Send + '_>> { - (self.f)(input, context) + (self.tool_fn)(input, context) } /// Return the static concurrency-safety flag. @@ -1955,14 +1954,15 @@ impl Tool for FnTool { } } +// =================================================== // Tests +// =================================================== #[cfg(test)] mod tests { use super::*; use serde_json::json; - // Test tool implementation struct EchoTool; impl Tool for EchoTool {