spec_ai_core/tools/builtin/
echo.rs

1use crate::tools::{Tool, ToolResult};
2use anyhow::{Context, Result};
3use async_trait::async_trait;
4use serde::Deserialize;
5use serde_json::Value;
6
7/// A simple echo tool that returns its input
8pub struct EchoTool;
9
10#[derive(Debug, Deserialize)]
11struct EchoArgs {
12    message: String,
13}
14
15impl EchoTool {
16    pub fn new() -> Self {
17        Self
18    }
19}
20
21impl Default for EchoTool {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27#[async_trait]
28impl Tool for EchoTool {
29    fn name(&self) -> &str {
30        "echo"
31    }
32
33    fn description(&self) -> &str {
34        "Echoes back the provided message"
35    }
36
37    fn parameters(&self) -> Value {
38        serde_json::json!({
39            "type": "object",
40            "properties": {
41                "message": {
42                    "type": "string",
43                    "description": "The message to echo back"
44                }
45            },
46            "required": ["message"]
47        })
48    }
49
50    async fn execute(&self, args: Value) -> Result<ToolResult> {
51        let echo_args: EchoArgs =
52            serde_json::from_value(args).context("Failed to parse echo arguments")?;
53
54        Ok(ToolResult::success(echo_args.message))
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[tokio::test]
63    async fn test_echo_tool_basic() {
64        let tool = EchoTool::new();
65
66        assert_eq!(tool.name(), "echo");
67        assert!(!tool.description().is_empty());
68    }
69
70    #[tokio::test]
71    async fn test_echo_tool_parameters() {
72        let tool = EchoTool::new();
73        let params = tool.parameters();
74
75        assert!(params.is_object());
76        assert!(params["properties"]["message"].is_object());
77        assert_eq!(params["required"][0], "message");
78    }
79
80    #[tokio::test]
81    async fn test_echo_tool_execute() {
82        let tool = EchoTool::new();
83        let args = serde_json::json!({
84            "message": "Hello, world!"
85        });
86
87        let result = tool.execute(args).await.unwrap();
88
89        assert!(result.success);
90        assert_eq!(result.output, "Hello, world!");
91        assert!(result.error.is_none());
92    }
93
94    #[tokio::test]
95    async fn test_echo_tool_execute_empty_message() {
96        let tool = EchoTool::new();
97        let args = serde_json::json!({
98            "message": ""
99        });
100
101        let result = tool.execute(args).await.unwrap();
102
103        assert!(result.success);
104        assert_eq!(result.output, "");
105    }
106
107    #[tokio::test]
108    async fn test_echo_tool_execute_missing_argument() {
109        let tool = EchoTool::new();
110        let args = serde_json::json!({});
111
112        let result = tool.execute(args).await;
113
114        assert!(result.is_err());
115    }
116
117    #[tokio::test]
118    async fn test_echo_tool_execute_invalid_type() {
119        let tool = EchoTool::new();
120        let args = serde_json::json!({
121            "message": 123
122        });
123
124        let result = tool.execute(args).await;
125
126        assert!(result.is_err());
127    }
128}