synaptic_tools/
calculator.rs1use async_trait::async_trait;
4use serde_json::{json, Value};
5use synaptic_core::{SynapticError, Tool};
6
7pub struct CalculatorTool;
23
24impl Default for CalculatorTool {
25 fn default() -> Self {
26 CalculatorTool
27 }
28}
29
30#[async_trait]
31impl Tool for CalculatorTool {
32 fn name(&self) -> &'static str {
33 "calculator"
34 }
35
36 fn description(&self) -> &'static str {
37 "Evaluate mathematical expressions. Supports +, -, *, /, ^ (power), sqrt(), abs(), \
38 sin(), cos(), tan(), log(). Example: '2 + 3 * 4' returns 14."
39 }
40
41 fn parameters(&self) -> Option<Value> {
42 Some(json!({
43 "type": "object",
44 "properties": {
45 "expression": {
46 "type": "string",
47 "description": "Mathematical expression to evaluate, e.g. '2 + 3 * 4' or 'sqrt(16)'"
48 }
49 },
50 "required": ["expression"]
51 }))
52 }
53
54 async fn call(&self, args: Value) -> Result<Value, SynapticError> {
55 let expr = args["expression"]
56 .as_str()
57 .ok_or_else(|| SynapticError::Tool("missing 'expression' parameter".to_string()))?;
58
59 let result = meval::eval_str(expr)
60 .map_err(|e| SynapticError::Tool(format!("math evaluation error: {e}")))?;
61
62 Ok(json!({ "expression": expr, "result": result }))
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn tool_metadata() {
72 let tool = CalculatorTool;
73 assert_eq!(tool.name(), "calculator");
74 assert!(!tool.description().is_empty());
75 }
76
77 #[test]
78 fn tool_schema() {
79 let tool = CalculatorTool;
80 let schema = tool.parameters().unwrap();
81 assert_eq!(schema["type"], "object");
82 assert!(schema["properties"]["expression"].is_object());
83 }
84
85 #[tokio::test]
86 async fn basic_arithmetic() {
87 let tool = CalculatorTool;
88 let result = tool.call(json!({"expression": "2 + 3 * 4"})).await.unwrap();
89 assert_eq!(result["result"].as_f64().unwrap(), 14.0);
90 assert_eq!(result["expression"], "2 + 3 * 4");
91 }
92
93 #[tokio::test]
94 async fn sqrt_expression() {
95 let tool = CalculatorTool;
96 let result = tool.call(json!({"expression": "sqrt(16)"})).await.unwrap();
97 assert_eq!(result["result"].as_f64().unwrap(), 4.0);
98 }
99
100 #[tokio::test]
101 async fn power_expression() {
102 let tool = CalculatorTool;
103 let result = tool.call(json!({"expression": "2 ^ 10"})).await.unwrap();
104 assert_eq!(result["result"].as_f64().unwrap(), 1024.0);
105 }
106
107 #[tokio::test]
108 async fn missing_expression_returns_error() {
109 let tool = CalculatorTool;
110 let result = tool.call(json!({})).await;
111 assert!(result.is_err());
112 assert!(result.unwrap_err().to_string().contains("expression"));
113 }
114
115 #[tokio::test]
116 async fn invalid_expression_returns_error() {
117 let tool = CalculatorTool;
118 let result = tool.call(json!({"expression": "not_a_number + ???"})).await;
119 assert!(result.is_err());
120 }
121}