swink-agent 0.13.2

Core scaffolding for running LLM-powered agentic loops
//! Placeholder tool for session history compatibility.
//!
//! When a session references a tool that is no longer registered,
//! [`NoopTool`] is auto-injected to prevent deserialization failures.
//! It returns an error result explaining the tool is no longer available.
//!
//! # Example
//!
//! ```
//! use swink_agent::{AgentTool, NoopTool};
//!
//! let tool = NoopTool::new("old_tool");
//! assert_eq!(tool.name(), "old_tool");
//! assert!(!tool.requires_approval());
//! ```

use std::sync::Arc;

use serde_json::Value;
use tokio_util::sync::CancellationToken;

use crate::tool::{AgentTool, AgentToolResult, ToolFuture, permissive_object_schema};

// ─── NoopTool ──────────────────────────────────────────────────────────────

/// A placeholder tool that returns an error message when invoked.
///
/// Used for session history compatibility when a tool referenced in a saved
/// session no longer exists in the agent's registry.
#[derive(Debug, Clone)]
pub struct NoopTool {
    name: String,
}

impl NoopTool {
    /// Create a new `NoopTool` with the given name.
    #[must_use]
    pub fn new(name: impl Into<String>) -> Self {
        Self { name: name.into() }
    }
}

impl AgentTool for NoopTool {
    fn name(&self) -> &str {
        &self.name
    }

    fn label(&self) -> &str {
        &self.name
    }

    fn description(&self) -> &'static str {
        "This tool is no longer available."
    }

    fn parameters_schema(&self) -> &Value {
        // Accept any arguments (the tool won't execute them anyway).
        static SCHEMA: std::sync::LazyLock<Value> =
            std::sync::LazyLock::new(permissive_object_schema);
        &SCHEMA
    }

    fn requires_approval(&self) -> bool {
        false
    }

    fn execute(
        &self,
        _tool_call_id: &str,
        _params: Value,
        _cancellation_token: CancellationToken,
        _on_update: Option<Box<dyn Fn(AgentToolResult) + Send + Sync>>,
        _state: Arc<std::sync::RwLock<crate::SessionState>>,
        _credential: Option<crate::credential::ResolvedCredential>,
    ) -> ToolFuture<'_> {
        let name = self.name.clone();
        Box::pin(async move {
            AgentToolResult::error(format!(
                "Tool '{name}' is no longer available. It may have been removed or renamed."
            ))
        })
    }
}

// ─── Compile-time Send + Sync assertion ─────────────────────────────────────

const _: () = {
    const fn assert_send_sync<T: Send + Sync>() {}
    assert_send_sync::<NoopTool>();
};

#[cfg(test)]
#[path = "noop_tool_tests.rs"]
mod tests;