hippox 0.3.0

The most reliable AI agent.🦛
Documentation
use anyhow::Result;
use serde_json::{Value, json};
use std::collections::HashMap;

use crate::executors::{
    skills::common,
    types::{Skill, SkillParameter},
};

#[derive(Debug)]
pub struct UnitConverterSkill;

#[async_trait::async_trait]
impl Skill for UnitConverterSkill {
    fn name(&self) -> &str {
        "unit_converter"
    }

    fn description(&self) -> &str {
        "Convert between different units of measurement"
    }

    fn usage_hint(&self) -> &str {
        "Use this skill when the user asks to convert between units like meters to feet, kilometers to miles, etc."
    }

    fn parameters(&self) -> Vec<SkillParameter> {
        vec![
            SkillParameter {
                name: "value".to_string(),
                param_type: "string".to_string(),
                description: "The numeric value to convert".to_string(),
                required: true,
                default: None,
                example: Some(Value::String("100".to_string())),
                enum_values: None,
            },
            SkillParameter {
                name: "from".to_string(),
                param_type: "string".to_string(),
                description: "Source unit (m, km, cm, mm, mi, ft, in)".to_string(),
                required: true,
                default: None,
                example: Some(Value::String("km".to_string())),
                enum_values: Some(vec![
                    "m".to_string(),
                    "km".to_string(),
                    "cm".to_string(),
                    "mm".to_string(),
                    "mi".to_string(),
                    "ft".to_string(),
                    "in".to_string(),
                ]),
            },
            SkillParameter {
                name: "to".to_string(),
                param_type: "string".to_string(),
                description: "Target unit (m, km, cm, mm, mi, ft, in)".to_string(),
                required: true,
                default: None,
                example: Some(Value::String("miles".to_string())),
                enum_values: Some(vec![
                    "m".to_string(),
                    "km".to_string(),
                    "cm".to_string(),
                    "mm".to_string(),
                    "mi".to_string(),
                    "ft".to_string(),
                    "in".to_string(),
                ]),
            },
            SkillParameter {
                name: "precision".to_string(),
                param_type: "integer".to_string(),
                description: "Number of decimal places in the result".to_string(),
                required: false,
                default: Some(Value::Number(2.into())),
                example: Some(Value::Number(2.into())),
                enum_values: None,
            },
        ]
    }

    fn example_call(&self) -> Value {
        json!({
            "action": "unit_converter",
            "parameters": {
                "value": "100",
                "from": "km",
                "to": "miles"
            }
        })
    }

    fn example_output(&self) -> String {
        "100 km = 62.14 miles".to_string()
    }

    fn category(&self) -> &str {
        "math"
    }

    async fn execute(&self, parameters: &HashMap<String, Value>) -> Result<String> {
        let value_str = parameters
            .get("value")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing 'value' parameter"))?;
        let from_unit = parameters
            .get("from")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing 'from' parameter"))?
            .to_lowercase();
        let to_unit = parameters
            .get("to")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing 'to' parameter"))?
            .to_lowercase();
        let value = common::Math::validate_number(value_str)?;
        let result = convert_units(value, &from_unit, &to_unit)?;
        let precision = parameters
            .get("precision")
            .and_then(|v| v.as_u64())
            .unwrap_or(2);
        Ok(format!(
            "{} {} = {} {}",
            value,
            from_unit,
            common::Math::format_number(result, precision as usize),
            to_unit
        ))
    }

    fn validate(&self, parameters: &HashMap<String, Value>) -> Result<()> {
        parameters
            .get("value")
            .ok_or_else(|| anyhow::anyhow!("Missing required parameter: value"))?;
        parameters
            .get("from")
            .ok_or_else(|| anyhow::anyhow!("Missing required parameter: from"))?;
        parameters
            .get("to")
            .ok_or_else(|| anyhow::anyhow!("Missing required parameter: to"))?;
        Ok(())
    }
}

fn convert_units(value: f64, from: &str, to: &str) -> Result<f64> {
    let to_meters = |unit: &str, val: f64| -> Result<f64> {
        match unit {
            "m" | "meter" | "meters" => Ok(val),
            "km" | "kilometer" | "kilometers" => Ok(val * 1000.0),
            "cm" | "centimeter" | "centimeters" => Ok(val / 100.0),
            "mm" | "millimeter" | "millimeters" => Ok(val / 1000.0),
            "mi" | "mile" | "miles" => Ok(val * 1609.344),
            "ft" | "foot" | "feet" => Ok(val * 0.3048),
            "in" | "inch" | "inches" => Ok(val * 0.0254),
            _ => anyhow::bail!("Unknown length unit: {}", unit),
        }
    };
    let from_meters = to_meters(from, value)?;
    let from_meters_to_target = |unit: &str, val: f64| -> Result<f64> {
        match unit {
            "m" | "meter" | "meters" => Ok(val),
            "km" | "kilometer" | "kilometers" => Ok(val / 1000.0),
            "cm" | "centimeter" | "centimeters" => Ok(val * 100.0),
            "mm" | "millimeter" | "millimeters" => Ok(val * 1000.0),
            "mi" | "mile" | "miles" => Ok(val / 1609.344),
            "ft" | "foot" | "feet" => Ok(val / 0.3048),
            "in" | "inch" | "inches" => Ok(val / 0.0254),
            _ => anyhow::bail!("Unknown length unit: {}", unit),
        }
    };
    from_meters_to_target(to, from_meters)
}