cortex-agent 0.2.0

Self-learning AI agent with persistent memory, tools, plugins, and a beautiful terminal UI
use std::sync::Arc;

use serde_json::Value;

/// A registered tool with its schema and implementation.
#[derive(Clone)]
pub struct ToolSpec {
    pub name: String,
    pub description: String,
    pub parameters: Value,
    pub handler: Arc<dyn Fn(Value) -> Result<String, String> + Send + Sync>,
}

impl ToolSpec {
    pub fn new(
        name: impl Into<String>,
        description: impl Into<String>,
        parameters: Value,
        handler: Arc<dyn Fn(Value) -> Result<String, String> + Send + Sync>,
    ) -> Self {
        Self {
            name: name.into(),
            description: description.into(),
            parameters,
            handler,
        }
    }

    pub fn call(&self, args: Value) -> Result<String, String> {
        (self.handler)(args)
    }

    /// Return the tool definition in OpenAI function-calling format.
    pub fn to_openai_tool(&self) -> Value {
        serde_json::json!({
            "type": "function",
            "function": {
                "name": self.name,
                "description": self.description,
                "parameters": self.parameters,
            }
        })
    }
}

/// Helper to build JSON Schema for a parameter.
pub fn param_string(description: impl Into<String>) -> Value {
    serde_json::json!({
        "type": "string",
        "description": description.into(),
    })
}

pub fn param_integer(description: impl Into<String>) -> Value {
    serde_json::json!({
        "type": "integer",
        "description": description.into(),
    })
}

#[allow(dead_code)]
pub fn param_number(description: impl Into<String>) -> Value {
    serde_json::json!({
        "type": "number",
        "description": description.into(),
    })
}

#[allow(dead_code)]
pub fn param_boolean(description: impl Into<String>) -> Value {
    serde_json::json!({
        "type": "boolean",
        "description": description.into(),
    })
}

pub fn required_params(params: &[(&str, Value)]) -> (Value, Vec<String>) {
    let mut properties = serde_json::Map::new();
    let mut required = Vec::new();

    for (name, schema) in params {
        let is_required = schema.get("default").is_none();
        properties.insert(name.to_string(), schema.clone());
        if is_required {
            required.push(name.to_string());
        }
    }

    let mut obj = serde_json::json!({
        "type": "object",
        "properties": properties,
    });
    if !required.is_empty() {
        obj["required"] = Value::Array(required.iter().map(|s| Value::String(s.clone())).collect());
    }
    (obj, required)
}