Skip to main content

oxicode_sdk/
closure_tool.rs

1//! ClosureTool — ergonomic custom tool from a closure
2
3use async_trait::async_trait;
4use oxicode_agent::{AgentTool, AgentToolResult, ToolContext, ToolError};
5use serde_json::Value;
6use std::future::Future;
7use std::pin::Pin;
8use std::sync::Arc;
9
10/// Handler function type for ClosureTool.
11pub type ToolHandler =
12    Arc<dyn Fn(Value, &ToolContext) -> Result<AgentToolResult, ToolError> + Send + Sync>;
13
14/// Async handler function type for ClosureTool.
15pub type AsyncToolHandler = Arc<
16    dyn Fn(
17            Value,
18            &ToolContext,
19        ) -> Pin<Box<dyn Future<Output = Result<AgentToolResult, ToolError>> + Send>>
20        + Send
21        + Sync,
22>;
23
24/// A tool defined by a closure function.
25///
26/// Created via [`ClosureTool::new_sync`] or [`ClosureTool::new_async`].
27/// For ergonomic creation, use the macros or AgentBuilder's `custom_tool` method.
28pub struct ClosureTool {
29    name: String,
30    description: String,
31    schema: Value,
32    handler: AsyncToolHandler,
33}
34
35impl ClosureTool {
36    /// Create a new sync tool from a closure.
37    ///
38    /// The closure receives `(params: Value, ctx: &ToolContext)`.
39    pub fn new_sync(
40        name: impl Into<String>,
41        description: impl Into<String>,
42        schema: Value,
43        handler: impl Fn(Value, &ToolContext) -> Result<AgentToolResult, ToolError>
44        + Send
45        + Sync
46        + 'static,
47    ) -> Self {
48        #[allow(clippy::type_complexity)]
49        let handler_arc: Arc<
50            dyn Fn(Value, &ToolContext) -> Result<AgentToolResult, ToolError> + Send + Sync,
51        > = Arc::new(handler);
52        Self {
53            name: name.into(),
54            description: description.into(),
55            schema,
56            handler: Arc::new(move |params, ctx| {
57                let result = handler_arc(params, ctx);
58                Box::pin(async move { result })
59            }),
60        }
61    }
62
63    /// Create a new async tool from a closure.
64    ///
65    /// The closure receives `(params: Value, ctx: &ToolContext)` and returns a Future.
66    pub fn new_async(
67        name: impl Into<String>,
68        description: impl Into<String>,
69        schema: Value,
70        handler: impl Fn(
71            Value,
72            &ToolContext,
73        )
74            -> Pin<Box<dyn Future<Output = Result<AgentToolResult, ToolError>> + Send>>
75        + Send
76        + Sync
77        + 'static,
78    ) -> Self {
79        Self {
80            name: name.into(),
81            description: description.into(),
82            schema,
83            handler: Arc::new(handler),
84        }
85    }
86}
87
88#[async_trait]
89impl AgentTool for ClosureTool {
90    fn name(&self) -> &str {
91        &self.name
92    }
93
94    fn label(&self) -> &str {
95        &self.name
96    }
97
98    fn description(&self) -> &str {
99        &self.description
100    }
101
102    fn parameters_schema(&self) -> Value {
103        self.schema.clone()
104    }
105
106    async fn execute(
107        &self,
108        _tool_call_id: &str,
109        params: Value,
110        _signal: Option<tokio::sync::oneshot::Receiver<()>>,
111        ctx: &ToolContext,
112    ) -> Result<AgentToolResult, ToolError> {
113        (self.handler)(params, ctx).await
114    }
115}