enact-core 0.0.1

Core agent runtime for Enact - Graph-Native AI agents
Documentation
//! Function tool - wrap any async function as a tool

use super::Tool;
use async_trait::async_trait;
use serde_json::Value;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

/// Type alias for async tool functions
pub type ToolFn = Arc<
    dyn Fn(Value) -> Pin<Box<dyn Future<Output = anyhow::Result<Value>> + Send>>
        + Send
        + Sync,
>;

/// FunctionTool - wraps an async closure as a tool
pub struct FunctionTool {
    name: String,
    description: String,
    parameters: Value,
    func: ToolFn,
}

impl FunctionTool {
    /// Create a new function tool
    pub fn new<F, Fut>(
        name: impl Into<String>,
        description: impl Into<String>,
        parameters: Value,
        func: F,
    ) -> Self
    where
        F: Fn(Value) -> Fut + Send + Sync + 'static,
        Fut: Future<Output = anyhow::Result<Value>> + Send + 'static,
    {
        let func = Arc::new(move |args: Value| {
            let fut = func(args);
            Box::pin(fut) as Pin<Box<dyn Future<Output = anyhow::Result<Value>> + Send>>
        });

        Self {
            name: name.into(),
            description: description.into(),
            parameters,
            func,
        }
    }

    /// Create a simple tool with no parameters
    pub fn simple<F, Fut>(name: impl Into<String>, description: impl Into<String>, func: F) -> Self
    where
        F: Fn(Value) -> Fut + Send + Sync + 'static,
        Fut: Future<Output = anyhow::Result<Value>> + Send + 'static,
    {
        Self::new(
            name,
            description,
            serde_json::json!({
                "type": "object",
                "properties": {},
                "required": []
            }),
            func,
        )
    }
}

#[async_trait]
impl Tool for FunctionTool {
    fn name(&self) -> &str {
        &self.name
    }

    fn description(&self) -> &str {
        &self.description
    }

    fn parameters_schema(&self) -> Value {
        self.parameters.clone()
    }

    async fn execute(&self, args: Value) -> anyhow::Result<Value> {
        (self.func)(args).await
    }
}

/// Macro to create a FunctionTool more easily
#[macro_export]
macro_rules! tool {
    ($name:expr, $desc:expr, |$args:ident| $body:expr) => {
        FunctionTool::new(
            $name,
            $desc,
            serde_json::json!({"type": "object", "properties": {}}),
            |$args| async move { $body },
        )
    };
}