use anyhow::Result;
use async_trait::async_trait;
use serde_json::Value;
use std::fmt::Debug;
#[async_trait]
pub trait Tool: Send + Sync + Debug {
fn name(&self) -> String;
fn description(&self) -> Option<String>;
fn args_schema(&self) -> Option<Value>;
async fn execute(&self, args: Value) -> Result<String>;
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[derive(Debug, Clone)]
pub struct EchoTool;
impl EchoTool {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl Tool for EchoTool {
fn name(&self) -> String {
"echo".to_string()
}
fn description(&self) -> Option<String> {
Some("A simple echo tool that returns the input text".to_string())
}
fn args_schema(&self) -> Option<Value> {
Some(serde_json::json!({
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "the text to echo back"
}
},
"required": ["text"]
}))
}
async fn execute(&self, args: Value) -> Result<String> {
let text = args
.get("text")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing 'text' argument"))?;
Ok(text.to_string())
}
}
#[tokio::test]
async fn test_echo_tool() {
let tool = EchoTool::new();
assert_eq!(tool.name(), "echo");
assert!(!tool.description().is_none());
let args = serde_json::json!({"text": "Hello, World!"});
let result = tool.execute(args).await.unwrap();
assert_eq!(result, "Hello, World!");
let args = serde_json::json!({});
let result = tool.execute(args).await;
assert!(result.is_err());
}
}