agents_toolkit/
tools.rs

1//! Helper functions for creating user tools from regular Rust functions
2//!
3//! This module provides utilities to convert regular Rust functions into ToolHandle
4//! implementations for the `tools` parameter in create_deep_agent(), making it easier
5//! to create custom tools while keeping all existing built-in tools unchanged.
6
7use agents_core::agent::{ToolHandle, ToolResponse};
8use agents_core::messaging::{AgentMessage, MessageContent, MessageRole, ToolInvocation};
9use async_trait::async_trait;
10use serde_json::Value;
11use std::sync::Arc;
12
13pub fn create_tool<F, Fut>(
14    name: &'static str,
15    _description: &'static str,
16    handler: F,
17) -> Arc<dyn ToolHandle>
18where
19    F: Fn(Value) -> Fut + Send + Sync + 'static,
20    Fut: std::future::Future<Output = anyhow::Result<String>> + Send + 'static,
21{
22    Arc::new(FunctionTool {
23        name,
24        // description,
25        handler: Box::new(move |args| Box::pin(handler(args))),
26    })
27}
28
29/// Create a tool from a synchronous function
30pub fn create_sync_tool<F>(
31    name: &'static str,
32    // description: &'static str,
33    handler: F,
34) -> Arc<dyn ToolHandle>
35where
36    F: Fn(Value) -> anyhow::Result<String> + Send + Sync + 'static,
37{
38    Arc::new(SyncFunctionTool {
39        name,
40        // description,
41        handler: Box::new(handler),
42    })
43}
44
45// Type alias to simplify complex async handler type
46type AsyncHandler = Box<
47    dyn Fn(
48            Value,
49        )
50            -> std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<String>> + Send>>
51        + Send
52        + Sync,
53>;
54
55// Internal implementation for async tools
56struct FunctionTool {
57    name: &'static str,
58    // description: &'static str,
59    handler: AsyncHandler,
60}
61
62#[async_trait]
63impl ToolHandle for FunctionTool {
64    fn name(&self) -> &str {
65        self.name
66    }
67
68    async fn invoke(&self, invocation: ToolInvocation) -> anyhow::Result<ToolResponse> {
69        let result = (self.handler)(invocation.args).await?;
70
71        Ok(ToolResponse::Message(AgentMessage {
72            role: MessageRole::Tool,
73            content: MessageContent::Text(result),
74            metadata: invocation
75                .tool_call_id
76                .map(|id| agents_core::messaging::MessageMetadata {
77                    tool_call_id: Some(id),
78                    cache_control: None,
79                }),
80        }))
81    }
82}
83
84// Internal implementation for sync tools
85struct SyncFunctionTool {
86    name: &'static str,
87    // description: &'static str,
88    handler: Box<dyn Fn(Value) -> anyhow::Result<String> + Send + Sync>,
89}
90
91#[async_trait]
92impl ToolHandle for SyncFunctionTool {
93    fn name(&self) -> &str {
94        self.name
95    }
96
97    async fn invoke(&self, invocation: ToolInvocation) -> anyhow::Result<ToolResponse> {
98        let result = (self.handler)(invocation.args)?;
99
100        Ok(ToolResponse::Message(AgentMessage {
101            role: MessageRole::Tool,
102            content: MessageContent::Text(result),
103            metadata: invocation
104                .tool_call_id
105                .map(|id| agents_core::messaging::MessageMetadata {
106                    tool_call_id: Some(id),
107                    cache_control: None,
108                }),
109        }))
110    }
111}
112
113/// Macro for creating tools with typed parameters (advanced usage)
114#[macro_export]
115macro_rules! tool_fn {
116    (
117        name: $name:expr,
118        description: $desc:expr,
119        |$($param:ident: $param_type:ty),*| $body:expr
120    ) => {
121        $crate::create_tool($name, $desc, move |args: serde_json::Value| async move {
122            // Extract parameters with proper error handling
123            $(
124                let $param: $param_type = args.get(stringify!($param))
125                    .ok_or_else(|| anyhow::anyhow!("Missing required parameter: {}", stringify!($param)))?
126                    .clone()
127                    .try_into()
128                    .map_err(|_| anyhow::anyhow!("Invalid type for parameter: {}", stringify!($param)))?;
129            )*
130
131            // Call the user's function
132            $body.await
133        })
134    };
135}