1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/// Create a tool using the builder pattern (convenience function).
///
/// This is the recommended way to create tools. It returns a [`ToolBuilder`] that
/// allows you to fluently configure the tool's schema and handler.
///
/// ## Typical Usage Pattern
///
/// ```text
/// tool(name, description)
/// .param(name, type) // Add parameters (optional, can repeat)
/// .build(handler) // Provide handler and create Tool
/// ```
///
/// ## Why Use This Instead of Tool::new?
///
/// - **More readable**: The builder pattern reads like natural language
/// - **Incremental schema building**: Add parameters one at a time
/// - **Flexible**: Can conditionally add parameters or use `.schema()` for complex cases
/// - **Type-safe**: Method chaining ensures you can't forget the handler
///
/// ## Parameters
///
/// - `name`: Unique identifier for the tool (snake_case recommended)
/// - `description`: Human-readable explanation of what the tool does
///
/// Both parameters accept any type that implements `Into<String>`, so you can
/// pass string literals, `String` values, or anything else convertible to String.
///
/// ## Examples
///
/// ### Basic Calculator Tool
///
/// ```rust,no_run
/// use open_agent::tool;
/// use serde_json::json;
///
/// let add_tool = tool("add", "Add two numbers")
/// .param("a", "number")
/// .param("b", "number")
/// .build(|args| async move {
/// let a = args.get("a")
/// .and_then(|v| v.as_f64())
/// .ok_or_else(|| open_agent::Error::invalid_input("Parameter 'a' must be a number"))?;
/// let b = args.get("b")
/// .and_then(|v| v.as_f64())
/// .ok_or_else(|| open_agent::Error::invalid_input("Parameter 'b' must be a number"))?;
/// Ok(json!({"result": a + b}))
/// });
/// ```
///
/// ### Tool with External HTTP Client
///
/// ```rust,no_run
/// use open_agent::{tool, Error};
/// use serde_json::json;
/// # use std::sync::Arc;
///
/// // Shared HTTP client (example - use your actual HTTP client)
/// # struct HttpClient;
/// # impl HttpClient {
/// # fn new() -> Self { HttpClient }
/// # async fn get(&self, url: &str) -> Result<String, Box<dyn std::error::Error>> {
/// # Ok("response".to_string())
/// # }
/// # }
/// let http_client = Arc::new(HttpClient::new());
///
/// let fetch_tool = tool("fetch_url", "Fetch content from a URL")
/// .param("url", "string")
/// .build(move |args| {
/// let client = http_client.clone();
/// async move {
/// let url = args["url"].as_str().unwrap_or("");
/// let content = client.get(url).await
/// .map_err(|e| Error::tool(format!("Failed to fetch: {}", e)))?;
/// Ok(json!({"content": content}))
/// }
/// });
/// ```
///
/// ### Tool with Complex Schema
///
/// ```rust,no_run
/// use open_agent::tool;
/// use serde_json::json;
///
/// let search_tool = tool("search", "Search for information")
/// .schema(json!({
/// "query": {
/// "type": "string",
/// "description": "Search query"
/// },
/// "filters": {
/// "type": "object",
/// "description": "Optional filters",
/// "optional": true,
/// "properties": {
/// "date_from": {"type": "string"},
/// "date_to": {"type": "string"}
/// }
/// },
/// "max_results": {
/// "type": "integer",
/// "default": 10,
/// "optional": true
/// }
/// }))
/// .build(|args| async move {
/// // Implementation
/// Ok(json!({"results": []}))
/// });
/// ```
///
/// ### Conditional Parameter Addition
///
/// ```rust,no_run
/// use open_agent::tool;
/// use serde_json::json;
///
/// # let enable_advanced = true;
/// let mut builder = tool("process", "Process data")
/// .param("input", "string");
///
/// // Conditionally add parameters
/// if enable_advanced {
/// builder = builder.param("advanced_mode", "boolean");
/// }
///
/// let my_tool = builder.build(|args| async move {
/// Ok(json!({"status": "processed"}))
/// });
/// ```
///
/// ### Integration with Agent
///
/// ```rust,no_run
/// use open_agent::{Client, AgentOptions, tool};
/// use serde_json::json;
///
/// # async fn example() -> open_agent::Result<()> {
/// let weather_tool = tool("get_weather", "Get weather for a location")
/// .param("location", "string")
/// .build(|args| async move {
/// Ok(json!({"temp": 72, "conditions": "sunny"}))
/// });
///
/// let options = AgentOptions::builder()
/// .model("gpt-4")
/// .base_url("http://localhost:1234/v1")
/// .tool(weather_tool)
/// .build()?;
///
/// let client = Client::new(options)?;
/// // Client can now use the tool when responding to queries
/// # Ok(())
/// # }
/// ```
///
/// ## See Also
///
/// - [`Tool::new`] - Direct constructor if you prefer not using the builder
/// - [`ToolBuilder`] - The builder type returned by this function
/// - [`Tool`] - The final tool type produced by `.build()`