use std::sync::Arc;
use serde_json::Value;
use tokio_util::sync::CancellationToken;
use crate::tool::{AgentTool, AgentToolResult, ToolFuture, permissive_object_schema};
#[derive(Debug, Clone)]
pub struct NoopTool {
name: String,
}
impl NoopTool {
#[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 {
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."
))
})
}
}
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<NoopTool>();
};
#[cfg(test)]
mod tests {
use serde_json::json;
use tokio_util::sync::CancellationToken;
use super::*;
use crate::tool::AgentTool;
fn test_state() -> Arc<std::sync::RwLock<crate::SessionState>> {
Arc::new(std::sync::RwLock::new(crate::SessionState::new()))
}
#[test]
fn noop_tool_name_matches() {
let tool = NoopTool::new("old_tool");
assert_eq!(tool.name(), "old_tool");
}
#[test]
fn noop_tool_no_approval_required() {
let tool = NoopTool::new("removed_tool");
assert!(!tool.requires_approval());
}
#[tokio::test]
async fn noop_tool_returns_error() {
let tool = NoopTool::new("deleted_tool");
let result = tool
.execute(
"call_1",
json!({"any": "args"}),
CancellationToken::new(),
None,
test_state(),
None,
)
.await;
assert!(result.is_error);
let crate::types::ContentBlock::Text { text } = &result.content[0] else {
panic!("expected text content");
};
assert!(text.contains("deleted_tool"));
assert!(text.contains("no longer available"));
}
#[tokio::test]
async fn noop_tool_ignores_arguments() {
let tool = NoopTool::new("any");
let result = tool
.execute(
"call_2",
json!({"complex": {"nested": true}, "array": [1, 2, 3]}),
CancellationToken::new(),
None,
test_state(),
None,
)
.await;
assert!(result.is_error);
}
}