use async_trait::async_trait;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::generate_schema;
use ai_agents_core::{Tool, ToolResult, ToolSafetyMetadata};
pub struct CalculatorTool;
impl CalculatorTool {
pub fn new() -> Self {
Self
}
}
impl Default for CalculatorTool {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Deserialize, JsonSchema)]
struct CalculatorInput {
expression: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct CalculatorOutput {
result: f64,
expression: String,
}
#[async_trait]
impl Tool for CalculatorTool {
fn id(&self) -> &str {
"calculator"
}
fn name(&self) -> &str {
"Calculator"
}
fn description(&self) -> &str {
"Evaluates mathematical expressions. Supports +, -, *, /, ^ and parentheses."
}
fn input_schema(&self) -> Value {
generate_schema::<CalculatorInput>()
}
fn safety_metadata(&self) -> ToolSafetyMetadata {
ToolSafetyMetadata::compute()
}
async fn execute(&self, args: Value, _ctx: ai_agents_core::ToolExecutionContext) -> ToolResult {
let input: CalculatorInput = match serde_json::from_value(args) {
Ok(input) => input,
Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
};
match evalexpr::eval(&input.expression) {
Ok(value) => {
let result = match value {
evalexpr::Value::Float(f) => f,
evalexpr::Value::Int(i) => i as f64,
_ => return ToolResult::error("Expression must evaluate to a number"),
};
let output = CalculatorOutput {
result,
expression: input.expression,
};
match serde_json::to_string(&output) {
Ok(json) => ToolResult::ok(json),
Err(e) => ToolResult::error(format!("Serialization error: {}", e)),
}
}
Err(e) => ToolResult::error(format!("Calculation error: {}", e)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_basic_operations() {
let calc = CalculatorTool::new();
let result = calc
.execute(
serde_json::json!({"expression": "2 + 3"}),
ai_agents_core::ToolExecutionContext::test("test"),
)
.await;
assert!(result.success);
let result = calc
.execute(
serde_json::json!({"expression": "10 * 5"}),
ai_agents_core::ToolExecutionContext::test("test"),
)
.await;
assert!(result.success);
}
#[tokio::test]
async fn test_operator_precedence() {
let calc = CalculatorTool::new();
let result = calc
.execute(
serde_json::json!({"expression": "2 + 3 * 4"}),
ai_agents_core::ToolExecutionContext::test("test"),
)
.await;
assert!(result.success);
let output: CalculatorOutput = serde_json::from_str(&result.output).unwrap();
assert_eq!(output.result, 14.0);
}
#[tokio::test]
async fn test_parentheses() {
let calc = CalculatorTool::new();
let result = calc
.execute(
serde_json::json!({"expression": "(2 + 3) * 4"}),
ai_agents_core::ToolExecutionContext::test("test"),
)
.await;
assert!(result.success);
let output: CalculatorOutput = serde_json::from_str(&result.output).unwrap();
assert_eq!(output.result, 20.0);
}
#[tokio::test]
async fn test_invalid_expression() {
let calc = CalculatorTool::new();
let result = calc
.execute(
serde_json::json!({"expression": "2 +"}),
ai_agents_core::ToolExecutionContext::test("test"),
)
.await;
assert!(!result.success);
}
#[tokio::test]
async fn test_invalid_input() {
let calc = CalculatorTool::new();
let result = calc
.execute(
serde_json::json!({"wrong_field": "test"}),
ai_agents_core::ToolExecutionContext::test("test"),
)
.await;
assert!(!result.success);
}
}