Skip to main content

everruns_core/capabilities/
test_math.rs

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