everruns_core/capabilities/
test_math.rs1use super::{Capability, CapabilityStatus};
4use crate::tool_types::ToolHints;
5use crate::tools::{Tool, ToolExecutionResult};
6use async_trait::async_trait;
7use serde_json::Value;
8
9pub 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
47pub 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
106pub 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
165pub 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
224pub 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}