everruns_core/capabilities/
test_math.rs1use 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
11pub 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
57pub 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
116pub 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
175pub 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
234pub 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 #[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}