agentkit_provider_anthropic/
server_tool.rs1use std::sync::Arc;
9
10use serde_json::{Value, json};
11
12pub trait ServerTool: Send + Sync {
18 fn to_tool_json(&self) -> Value;
23
24 fn beta_flags(&self) -> Vec<String> {
29 Vec::new()
30 }
31}
32
33pub type ServerToolHandle = Arc<dyn ServerTool>;
35
36pub fn boxed<T: ServerTool + 'static>(tool: T) -> ServerToolHandle {
40 Arc::new(tool)
41}
42
43pub const DEFAULT_WEB_SEARCH_VERSION: &str = "web_search_20260209";
45pub const DEFAULT_WEB_FETCH_VERSION: &str = "web_fetch_20260309";
47pub const DEFAULT_CODE_EXECUTION_VERSION: &str = "code_execution_20260120";
49pub const DEFAULT_BASH_EXECUTION_VERSION: &str = "bash_code_execution_20260120";
51pub const DEFAULT_TEXT_EDITOR_EXECUTION_VERSION: &str = "text_editor_code_execution_20260120";
53
54#[derive(Clone, Debug)]
56pub struct WebSearchTool {
57 pub version: String,
59 pub max_uses: Option<u32>,
61 pub allowed_domains: Vec<String>,
63 pub blocked_domains: Vec<String>,
65}
66
67impl Default for WebSearchTool {
68 fn default() -> Self {
69 Self {
70 version: DEFAULT_WEB_SEARCH_VERSION.into(),
71 max_uses: None,
72 allowed_domains: Vec::new(),
73 blocked_domains: Vec::new(),
74 }
75 }
76}
77
78impl WebSearchTool {
79 pub fn new() -> Self {
81 Self::default()
82 }
83
84 pub fn with_version(mut self, version: impl Into<String>) -> Self {
86 self.version = version.into();
87 self
88 }
89
90 pub fn with_max_uses(mut self, max: u32) -> Self {
92 self.max_uses = Some(max);
93 self
94 }
95
96 pub fn with_allowed_domains(mut self, domains: impl IntoIterator<Item = String>) -> Self {
98 self.allowed_domains = domains.into_iter().collect();
99 self
100 }
101
102 pub fn with_blocked_domains(mut self, domains: impl IntoIterator<Item = String>) -> Self {
104 self.blocked_domains = domains.into_iter().collect();
105 self
106 }
107}
108
109impl ServerTool for WebSearchTool {
110 fn to_tool_json(&self) -> Value {
111 let mut body = serde_json::Map::new();
112 body.insert("type".into(), Value::String(self.version.clone()));
113 body.insert("name".into(), Value::String("web_search".into()));
114 if let Some(max) = self.max_uses {
115 body.insert("max_uses".into(), Value::from(max));
116 }
117 if !self.allowed_domains.is_empty() {
118 body.insert(
119 "allowed_domains".into(),
120 Value::Array(
121 self.allowed_domains
122 .iter()
123 .cloned()
124 .map(Value::String)
125 .collect(),
126 ),
127 );
128 }
129 if !self.blocked_domains.is_empty() {
130 body.insert(
131 "blocked_domains".into(),
132 Value::Array(
133 self.blocked_domains
134 .iter()
135 .cloned()
136 .map(Value::String)
137 .collect(),
138 ),
139 );
140 }
141 Value::Object(body)
142 }
143}
144
145#[derive(Clone, Debug)]
147pub struct WebFetchTool {
148 pub version: String,
150 pub max_uses: Option<u32>,
152 pub max_content_tokens: Option<u32>,
154 pub use_cache: Option<bool>,
156}
157
158impl Default for WebFetchTool {
159 fn default() -> Self {
160 Self {
161 version: DEFAULT_WEB_FETCH_VERSION.into(),
162 max_uses: None,
163 max_content_tokens: None,
164 use_cache: None,
165 }
166 }
167}
168
169impl WebFetchTool {
170 pub fn new() -> Self {
171 Self::default()
172 }
173
174 pub fn with_version(mut self, version: impl Into<String>) -> Self {
175 self.version = version.into();
176 self
177 }
178
179 pub fn with_max_uses(mut self, max: u32) -> Self {
180 self.max_uses = Some(max);
181 self
182 }
183
184 pub fn with_max_content_tokens(mut self, max: u32) -> Self {
185 self.max_content_tokens = Some(max);
186 self
187 }
188
189 pub fn with_use_cache(mut self, enabled: bool) -> Self {
190 self.use_cache = Some(enabled);
191 self
192 }
193}
194
195impl ServerTool for WebFetchTool {
196 fn to_tool_json(&self) -> Value {
197 let mut body = serde_json::Map::new();
198 body.insert("type".into(), Value::String(self.version.clone()));
199 body.insert("name".into(), Value::String("web_fetch".into()));
200 if let Some(max) = self.max_uses {
201 body.insert("max_uses".into(), Value::from(max));
202 }
203 if let Some(max) = self.max_content_tokens {
204 body.insert("max_content_tokens".into(), Value::from(max));
205 }
206 if let Some(flag) = self.use_cache {
207 body.insert("use_cache".into(), Value::Bool(flag));
208 }
209 Value::Object(body)
210 }
211}
212
213#[derive(Clone, Debug)]
215pub struct CodeExecutionTool {
216 pub version: String,
217}
218
219impl Default for CodeExecutionTool {
220 fn default() -> Self {
221 Self {
222 version: DEFAULT_CODE_EXECUTION_VERSION.into(),
223 }
224 }
225}
226
227impl CodeExecutionTool {
228 pub fn new() -> Self {
229 Self::default()
230 }
231
232 pub fn with_version(mut self, version: impl Into<String>) -> Self {
233 self.version = version.into();
234 self
235 }
236}
237
238impl ServerTool for CodeExecutionTool {
239 fn to_tool_json(&self) -> Value {
240 json!({
241 "type": self.version,
242 "name": "code_execution",
243 })
244 }
245}
246
247#[derive(Clone, Debug)]
249pub struct BashCodeExecutionTool {
250 pub version: String,
251}
252
253impl Default for BashCodeExecutionTool {
254 fn default() -> Self {
255 Self {
256 version: DEFAULT_BASH_EXECUTION_VERSION.into(),
257 }
258 }
259}
260
261impl BashCodeExecutionTool {
262 pub fn new() -> Self {
263 Self::default()
264 }
265
266 pub fn with_version(mut self, version: impl Into<String>) -> Self {
267 self.version = version.into();
268 self
269 }
270}
271
272impl ServerTool for BashCodeExecutionTool {
273 fn to_tool_json(&self) -> Value {
274 json!({
275 "type": self.version,
276 "name": "bash_code_execution",
277 })
278 }
279}
280
281#[derive(Clone, Debug)]
283pub struct TextEditorCodeExecutionTool {
284 pub version: String,
285}
286
287impl Default for TextEditorCodeExecutionTool {
288 fn default() -> Self {
289 Self {
290 version: DEFAULT_TEXT_EDITOR_EXECUTION_VERSION.into(),
291 }
292 }
293}
294
295impl TextEditorCodeExecutionTool {
296 pub fn new() -> Self {
297 Self::default()
298 }
299
300 pub fn with_version(mut self, version: impl Into<String>) -> Self {
301 self.version = version.into();
302 self
303 }
304}
305
306impl ServerTool for TextEditorCodeExecutionTool {
307 fn to_tool_json(&self) -> Value {
308 json!({
309 "type": self.version,
310 "name": "text_editor_code_execution",
311 })
312 }
313}
314
315#[derive(Clone, Debug)]
321pub struct RawServerTool {
322 pub value: Value,
324 pub betas: Vec<String>,
326}
327
328impl RawServerTool {
329 pub fn new(value: Value) -> Self {
331 Self {
332 value,
333 betas: Vec::new(),
334 }
335 }
336
337 pub fn with_beta(mut self, flag: impl Into<String>) -> Self {
339 self.betas.push(flag.into());
340 self
341 }
342}
343
344impl ServerTool for RawServerTool {
345 fn to_tool_json(&self) -> Value {
346 self.value.clone()
347 }
348
349 fn beta_flags(&self) -> Vec<String> {
350 self.betas.clone()
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 #[test]
359 fn web_search_serializes_filters() {
360 let tool = WebSearchTool::new()
361 .with_max_uses(3)
362 .with_allowed_domains(["docs.rs".to_string()]);
363 let json = tool.to_tool_json();
364 assert_eq!(json["type"], DEFAULT_WEB_SEARCH_VERSION);
365 assert_eq!(json["name"], "web_search");
366 assert_eq!(json["max_uses"], 3);
367 assert_eq!(json["allowed_domains"][0], "docs.rs");
368 }
369
370 #[test]
371 fn raw_tool_preserves_body_and_betas() {
372 let raw = RawServerTool::new(json!({
373 "type": "future_tool_20271231",
374 "name": "future_tool",
375 }))
376 .with_beta("future-tool-2027-12-31");
377
378 assert_eq!(raw.to_tool_json()["name"], "future_tool");
379 assert_eq!(raw.beta_flags(), vec!["future-tool-2027-12-31".to_string()]);
380 }
381}