Skip to main content

everruns_core/capabilities/
test_math.rs

1//! TestMath Capability - calculator tools for testing tool calling
2
3use super::{Capability, CapabilityStatus};
4use crate::tool_types::ToolHints;
5use crate::tools::{Tool, ToolExecutionResult};
6use async_trait::async_trait;
7use serde_json::Value;
8
9/// TestMath capability - calculator tools for testing tool calling
10pub struct TestMathCapability;
11
12impl Capability for TestMathCapability {
13    fn id(&self) -> &str {
14        "test_math"
15    }
16
17    fn name(&self) -> &str {
18        "Test Math"
19    }
20
21    fn description(&self) -> &str {
22        "Testing capability: adds calculator tools (add, subtract, multiply, divide) for tool calling tests."
23    }
24
25    fn status(&self) -> CapabilityStatus {
26        CapabilityStatus::Available
27    }
28
29    fn icon(&self) -> Option<&str> {
30        Some("calculator")
31    }
32
33    fn category(&self) -> Option<&str> {
34        Some("Testing")
35    }
36
37    fn tools(&self) -> Vec<Box<dyn Tool>> {
38        vec![
39            Box::new(AddTool),
40            Box::new(SubtractTool),
41            Box::new(MultiplyTool),
42            Box::new(DivideTool),
43        ]
44    }
45}
46
47// ============================================================================
48// Tool: add
49// ============================================================================
50
51/// Tool that adds two numbers
52pub struct AddTool;
53
54#[async_trait]
55impl Tool for AddTool {
56    fn name(&self) -> &str {
57        "add"
58    }
59
60    fn display_name(&self) -> Option<&str> {
61        Some("Add")
62    }
63
64    fn description(&self) -> &str {
65        "Add two numbers together and return the result."
66    }
67
68    fn parameters_schema(&self) -> Value {
69        serde_json::json!({
70            "type": "object",
71            "properties": {
72                "a": {
73                    "type": "number",
74                    "description": "The first number"
75                },
76                "b": {
77                    "type": "number",
78                    "description": "The second number"
79                }
80            },
81            "required": ["a", "b"],
82            "additionalProperties": false
83        })
84    }
85
86    fn hints(&self) -> ToolHints {
87        ToolHints::default()
88            .with_readonly(true)
89            .with_idempotent(true)
90    }
91
92    async fn execute(&self, arguments: Value) -> ToolExecutionResult {
93        let a = arguments.get("a").and_then(|v| v.as_f64()).unwrap_or(0.0);
94        let b = arguments.get("b").and_then(|v| v.as_f64()).unwrap_or(0.0);
95        let result = a + b;
96
97        ToolExecutionResult::success(serde_json::json!({
98            "result": result,
99            "operation": "add",
100            "a": a,
101            "b": b
102        }))
103    }
104}
105
106// ============================================================================
107// Tool: subtract
108// ============================================================================
109
110/// Tool that subtracts two numbers
111pub struct SubtractTool;
112
113#[async_trait]
114impl Tool for SubtractTool {
115    fn name(&self) -> &str {
116        "subtract"
117    }
118
119    fn display_name(&self) -> Option<&str> {
120        Some("Subtract")
121    }
122
123    fn description(&self) -> &str {
124        "Subtract the second number from the first and return the result."
125    }
126
127    fn parameters_schema(&self) -> Value {
128        serde_json::json!({
129            "type": "object",
130            "properties": {
131                "a": {
132                    "type": "number",
133                    "description": "The number to subtract from"
134                },
135                "b": {
136                    "type": "number",
137                    "description": "The number to subtract"
138                }
139            },
140            "required": ["a", "b"],
141            "additionalProperties": false
142        })
143    }
144
145    fn hints(&self) -> ToolHints {
146        ToolHints::default()
147            .with_readonly(true)
148            .with_idempotent(true)
149    }
150
151    async fn execute(&self, arguments: Value) -> ToolExecutionResult {
152        let a = arguments.get("a").and_then(|v| v.as_f64()).unwrap_or(0.0);
153        let b = arguments.get("b").and_then(|v| v.as_f64()).unwrap_or(0.0);
154        let result = a - b;
155
156        ToolExecutionResult::success(serde_json::json!({
157            "result": result,
158            "operation": "subtract",
159            "a": a,
160            "b": b
161        }))
162    }
163}
164
165// ============================================================================
166// Tool: multiply
167// ============================================================================
168
169/// Tool that multiplies two numbers
170pub struct MultiplyTool;
171
172#[async_trait]
173impl Tool for MultiplyTool {
174    fn name(&self) -> &str {
175        "multiply"
176    }
177
178    fn display_name(&self) -> Option<&str> {
179        Some("Multiply")
180    }
181
182    fn description(&self) -> &str {
183        "Multiply two numbers together and return the result."
184    }
185
186    fn parameters_schema(&self) -> Value {
187        serde_json::json!({
188            "type": "object",
189            "properties": {
190                "a": {
191                    "type": "number",
192                    "description": "The first number"
193                },
194                "b": {
195                    "type": "number",
196                    "description": "The second number"
197                }
198            },
199            "required": ["a", "b"],
200            "additionalProperties": false
201        })
202    }
203
204    fn hints(&self) -> ToolHints {
205        ToolHints::default()
206            .with_readonly(true)
207            .with_idempotent(true)
208    }
209
210    async fn execute(&self, arguments: Value) -> ToolExecutionResult {
211        let a = arguments.get("a").and_then(|v| v.as_f64()).unwrap_or(0.0);
212        let b = arguments.get("b").and_then(|v| v.as_f64()).unwrap_or(0.0);
213        let result = a * b;
214
215        ToolExecutionResult::success(serde_json::json!({
216            "result": result,
217            "operation": "multiply",
218            "a": a,
219            "b": b
220        }))
221    }
222}
223
224// ============================================================================
225// Tool: divide
226// ============================================================================
227
228/// Tool that divides two numbers
229pub struct DivideTool;
230
231#[async_trait]
232impl Tool for DivideTool {
233    fn name(&self) -> &str {
234        "divide"
235    }
236
237    fn display_name(&self) -> Option<&str> {
238        Some("Divide")
239    }
240
241    fn description(&self) -> &str {
242        "Divide the first number by the second and return the result. Returns an error if dividing by zero."
243    }
244
245    fn parameters_schema(&self) -> Value {
246        serde_json::json!({
247            "type": "object",
248            "properties": {
249                "a": {
250                    "type": "number",
251                    "description": "The dividend (number to be divided)"
252                },
253                "b": {
254                    "type": "number",
255                    "description": "The divisor (number to divide by)"
256                }
257            },
258            "required": ["a", "b"],
259            "additionalProperties": false
260        })
261    }
262
263    fn hints(&self) -> ToolHints {
264        ToolHints::default()
265            .with_readonly(true)
266            .with_idempotent(true)
267    }
268
269    async fn execute(&self, arguments: Value) -> ToolExecutionResult {
270        let a = arguments.get("a").and_then(|v| v.as_f64()).unwrap_or(0.0);
271        let b = arguments.get("b").and_then(|v| v.as_f64()).unwrap_or(0.0);
272
273        if b == 0.0 {
274            return ToolExecutionResult::tool_error("Cannot divide by zero");
275        }
276
277        let result = a / b;
278
279        ToolExecutionResult::success(serde_json::json!({
280            "result": result,
281            "operation": "divide",
282            "a": a,
283            "b": b
284        }))
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use crate::capabilities::CapabilityRegistry;
292
293    #[test]
294    fn test_capability_metadata() {
295        let cap = TestMathCapability;
296
297        assert_eq!(cap.id(), "test_math");
298        assert_eq!(cap.name(), "Test Math");
299        assert_eq!(cap.icon(), Some("calculator"));
300        assert_eq!(cap.category(), Some("Testing"));
301        assert_eq!(cap.status(), CapabilityStatus::Available);
302    }
303
304    #[test]
305    fn test_capability_has_tools() {
306        let cap = TestMathCapability;
307        let tools = cap.tools();
308
309        assert_eq!(tools.len(), 4);
310        let tool_names: Vec<&str> = tools.iter().map(|t| t.name()).collect();
311        assert!(tool_names.contains(&"add"));
312        assert!(tool_names.contains(&"subtract"));
313        assert!(tool_names.contains(&"multiply"));
314        assert!(tool_names.contains(&"divide"));
315    }
316
317    #[test]
318    fn test_capability_no_system_prompt() {
319        let cap = TestMathCapability;
320        assert!(cap.system_prompt_addition().is_none());
321    }
322
323    #[test]
324    fn test_capability_in_registry() {
325        let registry = CapabilityRegistry::with_builtins();
326        let cap = registry.get("test_math").unwrap();
327
328        assert_eq!(cap.id(), "test_math");
329        assert_eq!(cap.tools().len(), 4);
330    }
331
332    #[tokio::test]
333    async fn test_add_tool() {
334        let tool = AddTool;
335        let result = tool.execute(serde_json::json!({"a": 5, "b": 3})).await;
336
337        if let ToolExecutionResult::Success(value) = result {
338            assert_eq!(value.get("result").unwrap().as_f64().unwrap(), 8.0);
339            assert_eq!(value.get("operation").unwrap().as_str().unwrap(), "add");
340        } else {
341            panic!("Expected success");
342        }
343    }
344
345    #[tokio::test]
346    async fn test_subtract_tool() {
347        let tool = SubtractTool;
348        let result = tool.execute(serde_json::json!({"a": 10, "b": 4})).await;
349
350        if let ToolExecutionResult::Success(value) = result {
351            assert_eq!(value.get("result").unwrap().as_f64().unwrap(), 6.0);
352            assert_eq!(
353                value.get("operation").unwrap().as_str().unwrap(),
354                "subtract"
355            );
356        } else {
357            panic!("Expected success");
358        }
359    }
360
361    #[tokio::test]
362    async fn test_multiply_tool() {
363        let tool = MultiplyTool;
364        let result = tool.execute(serde_json::json!({"a": 6, "b": 7})).await;
365
366        if let ToolExecutionResult::Success(value) = result {
367            assert_eq!(value.get("result").unwrap().as_f64().unwrap(), 42.0);
368            assert_eq!(
369                value.get("operation").unwrap().as_str().unwrap(),
370                "multiply"
371            );
372        } else {
373            panic!("Expected success");
374        }
375    }
376
377    #[tokio::test]
378    async fn test_divide_tool() {
379        let tool = DivideTool;
380        let result = tool.execute(serde_json::json!({"a": 20, "b": 4})).await;
381
382        if let ToolExecutionResult::Success(value) = result {
383            assert_eq!(value.get("result").unwrap().as_f64().unwrap(), 5.0);
384            assert_eq!(value.get("operation").unwrap().as_str().unwrap(), "divide");
385        } else {
386            panic!("Expected success");
387        }
388    }
389
390    #[tokio::test]
391    async fn test_divide_by_zero() {
392        let tool = DivideTool;
393        let result = tool.execute(serde_json::json!({"a": 10, "b": 0})).await;
394
395        if let ToolExecutionResult::ToolError(msg) = result {
396            assert!(msg.contains("divide by zero"));
397        } else {
398            panic!("Expected tool error for division by zero");
399        }
400    }
401}