Skip to main content

claude_sdk/
server_tools.rs

1//! Server-side tool definitions for Claude API built-in tools.
2//!
3//! These tools are executed by Claude's infrastructure, not by your code.
4//! Add them to `MessagesRequest.tools` to enable server-side capabilities.
5
6use crate::types::CacheControl;
7use serde::{Deserialize, Serialize};
8
9/// Web search tool — lets Claude search the internet during a response.
10///
11/// # Example
12///
13/// ```rust
14/// use claude_sdk::server_tools::WebSearchTool;
15///
16/// let tool = WebSearchTool::new()
17///     .with_allowed_domains(vec!["example.com".into()])
18///     .with_max_uses(5);
19/// ```
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct WebSearchTool {
22    /// Always `"web_search_20260209"` (or overridden via constructor)
23    #[serde(rename = "type")]
24    pub tool_type: String,
25
26    /// Tool name (always `"web_search"`)
27    pub name: String,
28
29    /// Restrict searches to these domains only
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub allowed_domains: Option<Vec<String>>,
32
33    /// Never search these domains
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub blocked_domains: Option<Vec<String>>,
36
37    /// Maximum number of searches per request
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub max_uses: Option<u32>,
40
41    /// User's location for localizing search results
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub user_location: Option<UserLocation>,
44
45    /// Cache control for this tool definition
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub cache_control: Option<CacheControl>,
48}
49
50impl WebSearchTool {
51    /// Create a new WebSearchTool with the latest tool version.
52    pub fn new() -> Self {
53        Self {
54            tool_type: "web_search_20260209".into(),
55            name: "web_search".into(),
56            allowed_domains: None,
57            blocked_domains: None,
58            max_uses: None,
59            user_location: None,
60            cache_control: None,
61        }
62    }
63
64    /// Restrict searches to the specified domains.
65    pub fn with_allowed_domains(mut self, domains: Vec<String>) -> Self {
66        self.allowed_domains = Some(domains);
67        self
68    }
69
70    /// Block searches from the specified domains.
71    pub fn with_blocked_domains(mut self, domains: Vec<String>) -> Self {
72        self.blocked_domains = Some(domains);
73        self
74    }
75
76    /// Set the maximum number of web searches allowed per request.
77    pub fn with_max_uses(mut self, max: u32) -> Self {
78        self.max_uses = Some(max);
79        self
80    }
81
82    /// Set the user's location for localizing search results.
83    pub fn with_user_location(mut self, location: UserLocation) -> Self {
84        self.user_location = Some(location);
85        self
86    }
87
88    /// Set cache control for this tool definition.
89    pub fn with_cache_control(mut self, cache_control: CacheControl) -> Self {
90        self.cache_control = Some(cache_control);
91        self
92    }
93}
94
95impl Default for WebSearchTool {
96    fn default() -> Self {
97        Self::new()
98    }
99}
100
101/// User location for localizing web search results.
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct UserLocation {
104    /// Always `"approximate"`
105    #[serde(rename = "type")]
106    pub location_type: String,
107
108    /// City name (e.g., `"San Francisco"`)
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub city: Option<String>,
111
112    /// ISO 3166-1 alpha-2 country code (e.g., `"US"`)
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub country: Option<String>,
115
116    /// Region or state (e.g., `"California"`)
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub region: Option<String>,
119
120    /// IANA timezone (e.g., `"America/Los_Angeles"`)
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub timezone: Option<String>,
123}
124
125impl UserLocation {
126    /// Create a new approximate UserLocation.
127    pub fn new() -> Self {
128        Self {
129            location_type: "approximate".into(),
130            city: None,
131            country: None,
132            region: None,
133            timezone: None,
134        }
135    }
136}
137
138impl Default for UserLocation {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144/// Web fetch tool — lets Claude retrieve content from a specific URL.
145///
146/// # Example
147///
148/// ```rust
149/// use claude_sdk::server_tools::WebFetchTool;
150///
151/// let tool = WebFetchTool::new();
152/// ```
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct WebFetchTool {
155    /// Always `"web_fetch_20260309"`
156    #[serde(rename = "type")]
157    pub tool_type: String,
158
159    /// Tool name (always `"web_fetch"`)
160    pub name: String,
161
162    /// Restrict fetches to these domains only
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub allowed_domains: Option<Vec<String>>,
165
166    /// Never fetch from these domains
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub blocked_domains: Option<Vec<String>>,
169
170    /// Maximum number of fetch operations per request
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub max_uses: Option<u32>,
173
174    /// Maximum number of tokens to return from fetched content
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub max_content_tokens: Option<u32>,
177
178    /// Cache control for this tool definition
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub cache_control: Option<CacheControl>,
181}
182
183impl WebFetchTool {
184    /// Create a new WebFetchTool with the latest tool version.
185    pub fn new() -> Self {
186        Self {
187            tool_type: "web_fetch_20260309".into(),
188            name: "web_fetch".into(),
189            allowed_domains: None,
190            blocked_domains: None,
191            max_uses: None,
192            max_content_tokens: None,
193            cache_control: None,
194        }
195    }
196}
197
198impl Default for WebFetchTool {
199    fn default() -> Self {
200        Self::new()
201    }
202}
203
204/// Code execution tool — lets Claude execute code in a sandboxed environment.
205///
206/// # Example
207///
208/// ```rust
209/// use claude_sdk::server_tools::CodeExecutionTool;
210///
211/// let tool = CodeExecutionTool::new();
212/// ```
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct CodeExecutionTool {
215    /// Always `"code_execution_20260120"`
216    #[serde(rename = "type")]
217    pub tool_type: String,
218
219    /// Tool name (always `"code_execution"`)
220    pub name: String,
221
222    /// Cache control for this tool definition
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub cache_control: Option<CacheControl>,
225}
226
227impl CodeExecutionTool {
228    /// Create a new CodeExecutionTool with the latest tool version.
229    pub fn new() -> Self {
230        Self {
231            tool_type: "code_execution_20260120".into(),
232            name: "code_execution".into(),
233            cache_control: None,
234        }
235    }
236}
237
238impl Default for CodeExecutionTool {
239    fn default() -> Self {
240        Self::new()
241    }
242}
243
244/// Bash tool — lets Claude execute bash commands in a sandboxed environment.
245///
246/// # Example
247///
248/// ```rust
249/// use claude_sdk::server_tools::BashTool;
250///
251/// let tool = BashTool::new();
252/// ```
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct BashTool {
255    /// Always `"bash_20250124"`
256    #[serde(rename = "type")]
257    pub tool_type: String,
258
259    /// Tool name (always `"bash"`)
260    pub name: String,
261
262    /// Cache control for this tool definition
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub cache_control: Option<CacheControl>,
265}
266
267impl BashTool {
268    /// Create a new BashTool with the latest tool version.
269    pub fn new() -> Self {
270        Self {
271            tool_type: "bash_20250124".into(),
272            name: "bash".into(),
273            cache_control: None,
274        }
275    }
276}
277
278impl Default for BashTool {
279    fn default() -> Self {
280        Self::new()
281    }
282}
283
284/// Text editor tool — lets Claude read and edit files using structured operations.
285///
286/// # Example
287///
288/// ```rust
289/// use claude_sdk::server_tools::TextEditorTool;
290///
291/// let tool = TextEditorTool::new();
292/// ```
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct TextEditorTool {
295    /// Always `"text_editor_20250728"`
296    #[serde(rename = "type")]
297    pub tool_type: String,
298
299    /// Tool name (always `"str_replace_editor"`)
300    pub name: String,
301
302    /// Maximum number of characters to return when viewing file contents
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub max_characters: Option<u32>,
305
306    /// Cache control for this tool definition
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub cache_control: Option<CacheControl>,
309}
310
311impl TextEditorTool {
312    /// Create a new TextEditorTool with the latest tool version.
313    pub fn new() -> Self {
314        Self {
315            tool_type: "text_editor_20250728".into(),
316            name: "str_replace_editor".into(),
317            max_characters: None,
318            cache_control: None,
319        }
320    }
321}
322
323impl Default for TextEditorTool {
324    fn default() -> Self {
325        Self::new()
326    }
327}
328
329/// Memory tool for persistent memory across conversations
330///
331/// # Example
332///
333/// ```rust
334/// use claude_sdk::server_tools::MemoryTool;
335///
336/// let tool = MemoryTool::new();
337/// ```
338#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct MemoryTool {
340    /// Tool version: `"memory_20250818"`
341    #[serde(rename = "type")]
342    pub tool_type: String,
343
344    /// Always `"memory"`
345    pub name: String,
346
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub cache_control: Option<CacheControl>,
349}
350
351impl MemoryTool {
352    /// Create a new MemoryTool.
353    pub fn new() -> Self {
354        Self {
355            tool_type: "memory_20250818".into(),
356            name: "memory".into(),
357            cache_control: None,
358        }
359    }
360}
361
362impl Default for MemoryTool {
363    fn default() -> Self {
364        Self::new()
365    }
366}
367
368/// BM25-based tool search for large tool sets
369///
370/// # Example
371///
372/// ```rust
373/// use claude_sdk::server_tools::ToolSearchBm25;
374///
375/// let tool = ToolSearchBm25::new();
376/// ```
377#[derive(Debug, Clone, Serialize, Deserialize)]
378pub struct ToolSearchBm25 {
379    /// Tool version: `"tool_search_tool_bm25_20251119"`
380    #[serde(rename = "type")]
381    pub tool_type: String,
382
383    /// Always `"tool_search_tool_bm25"`
384    pub name: String,
385
386    #[serde(skip_serializing_if = "Option::is_none")]
387    pub cache_control: Option<CacheControl>,
388}
389
390impl ToolSearchBm25 {
391    /// Create a new ToolSearchBm25.
392    pub fn new() -> Self {
393        Self {
394            tool_type: "tool_search_tool_bm25_20251119".into(),
395            name: "tool_search_tool_bm25".into(),
396            cache_control: None,
397        }
398    }
399}
400
401impl Default for ToolSearchBm25 {
402    fn default() -> Self {
403        Self::new()
404    }
405}
406
407/// Regex-based tool search for large tool sets
408///
409/// # Example
410///
411/// ```rust
412/// use claude_sdk::server_tools::ToolSearchRegex;
413///
414/// let tool = ToolSearchRegex::new();
415/// ```
416#[derive(Debug, Clone, Serialize, Deserialize)]
417pub struct ToolSearchRegex {
418    /// Tool version: `"tool_search_tool_regex_20251119"`
419    #[serde(rename = "type")]
420    pub tool_type: String,
421
422    /// Always `"tool_search_tool_regex"`
423    pub name: String,
424
425    #[serde(skip_serializing_if = "Option::is_none")]
426    pub cache_control: Option<CacheControl>,
427}
428
429impl ToolSearchRegex {
430    /// Create a new ToolSearchRegex.
431    pub fn new() -> Self {
432        Self {
433            tool_type: "tool_search_tool_regex_20251119".into(),
434            name: "tool_search_tool_regex".into(),
435            cache_control: None,
436        }
437    }
438}
439
440impl Default for ToolSearchRegex {
441    fn default() -> Self {
442        Self::new()
443    }
444}
445
446// --- From impls for ToolDefinition ---
447
448impl From<WebSearchTool> for crate::types::ToolDefinition {
449    fn from(tool: WebSearchTool) -> crate::types::ToolDefinition {
450        crate::types::ToolDefinition::Server(
451            serde_json::to_value(tool).expect("Failed to serialize WebSearchTool"),
452        )
453    }
454}
455
456impl From<WebFetchTool> for crate::types::ToolDefinition {
457    fn from(tool: WebFetchTool) -> crate::types::ToolDefinition {
458        crate::types::ToolDefinition::Server(
459            serde_json::to_value(tool).expect("Failed to serialize WebFetchTool"),
460        )
461    }
462}
463
464impl From<CodeExecutionTool> for crate::types::ToolDefinition {
465    fn from(tool: CodeExecutionTool) -> crate::types::ToolDefinition {
466        crate::types::ToolDefinition::Server(
467            serde_json::to_value(tool).expect("Failed to serialize CodeExecutionTool"),
468        )
469    }
470}
471
472impl From<BashTool> for crate::types::ToolDefinition {
473    fn from(tool: BashTool) -> crate::types::ToolDefinition {
474        crate::types::ToolDefinition::Server(
475            serde_json::to_value(tool).expect("Failed to serialize BashTool"),
476        )
477    }
478}
479
480impl From<TextEditorTool> for crate::types::ToolDefinition {
481    fn from(tool: TextEditorTool) -> crate::types::ToolDefinition {
482        crate::types::ToolDefinition::Server(
483            serde_json::to_value(tool).expect("Failed to serialize TextEditorTool"),
484        )
485    }
486}
487
488impl From<MemoryTool> for crate::types::ToolDefinition {
489    fn from(tool: MemoryTool) -> crate::types::ToolDefinition {
490        crate::types::ToolDefinition::Server(
491            serde_json::to_value(tool).expect("Failed to serialize MemoryTool"),
492        )
493    }
494}
495
496impl From<ToolSearchBm25> for crate::types::ToolDefinition {
497    fn from(tool: ToolSearchBm25) -> crate::types::ToolDefinition {
498        crate::types::ToolDefinition::Server(
499            serde_json::to_value(tool).expect("Failed to serialize ToolSearchBm25"),
500        )
501    }
502}
503
504impl From<ToolSearchRegex> for crate::types::ToolDefinition {
505    fn from(tool: ToolSearchRegex) -> crate::types::ToolDefinition {
506        crate::types::ToolDefinition::Server(
507            serde_json::to_value(tool).expect("Failed to serialize ToolSearchRegex"),
508        )
509    }
510}
511
512#[cfg(test)]
513mod tests {
514    use super::*;
515    use crate::types::CacheControl;
516
517    // --- WebSearchTool ---
518
519    #[test]
520    fn test_web_search_tool_new_type_and_name() {
521        let tool = WebSearchTool::new();
522        assert_eq!(tool.tool_type, "web_search_20260209");
523        assert_eq!(tool.name, "web_search");
524    }
525
526    #[test]
527    fn test_web_search_tool_serialization() {
528        let tool = WebSearchTool::new();
529        let json = serde_json::to_value(&tool).unwrap();
530        assert_eq!(json["type"], "web_search_20260209");
531        assert_eq!(json["name"], "web_search");
532        assert!(json.get("allowed_domains").is_none());
533        assert!(json.get("blocked_domains").is_none());
534        assert!(json.get("max_uses").is_none());
535        assert!(json.get("user_location").is_none());
536        assert!(json.get("cache_control").is_none());
537    }
538
539    #[test]
540    fn test_web_search_tool_builder_allowed_domains() {
541        let tool =
542            WebSearchTool::new().with_allowed_domains(vec!["example.com".into(), "docs.rs".into()]);
543        let json = serde_json::to_value(&tool).unwrap();
544        let domains = json["allowed_domains"].as_array().unwrap();
545        assert_eq!(domains.len(), 2);
546        assert_eq!(domains[0], "example.com");
547        assert_eq!(domains[1], "docs.rs");
548    }
549
550    #[test]
551    fn test_web_search_tool_builder_blocked_domains() {
552        let tool = WebSearchTool::new().with_blocked_domains(vec!["spam.example.com".into()]);
553        let json = serde_json::to_value(&tool).unwrap();
554        let domains = json["blocked_domains"].as_array().unwrap();
555        assert_eq!(domains[0], "spam.example.com");
556    }
557
558    #[test]
559    fn test_web_search_tool_builder_max_uses() {
560        let tool = WebSearchTool::new().with_max_uses(10);
561        let json = serde_json::to_value(&tool).unwrap();
562        assert_eq!(json["max_uses"], 10);
563    }
564
565    #[test]
566    fn test_web_search_tool_builder_user_location() {
567        let location = UserLocation {
568            location_type: "approximate".into(),
569            city: Some("San Francisco".into()),
570            country: Some("US".into()),
571            region: Some("California".into()),
572            timezone: Some("America/Los_Angeles".into()),
573        };
574        let tool = WebSearchTool::new().with_user_location(location);
575        let json = serde_json::to_value(&tool).unwrap();
576        assert_eq!(json["user_location"]["type"], "approximate");
577        assert_eq!(json["user_location"]["city"], "San Francisco");
578        assert_eq!(json["user_location"]["country"], "US");
579        assert_eq!(json["user_location"]["region"], "California");
580        assert_eq!(json["user_location"]["timezone"], "America/Los_Angeles");
581    }
582
583    #[test]
584    fn test_web_search_tool_builder_cache_control() {
585        let tool = WebSearchTool::new().with_cache_control(CacheControl::ephemeral());
586        let json = serde_json::to_value(&tool).unwrap();
587        assert_eq!(json["cache_control"]["type"], "ephemeral");
588    }
589
590    #[test]
591    fn test_web_search_tool_roundtrip() {
592        let tool = WebSearchTool::new()
593            .with_allowed_domains(vec!["example.com".into()])
594            .with_max_uses(5);
595        let json = serde_json::to_string(&tool).unwrap();
596        let deserialized: WebSearchTool = serde_json::from_str(&json).unwrap();
597        assert_eq!(deserialized.tool_type, "web_search_20260209");
598        assert_eq!(deserialized.name, "web_search");
599        assert_eq!(deserialized.max_uses, Some(5));
600        let domains = deserialized.allowed_domains.unwrap();
601        assert_eq!(domains[0], "example.com");
602    }
603
604    // --- UserLocation ---
605
606    #[test]
607    fn test_user_location_new() {
608        let loc = UserLocation::new();
609        assert_eq!(loc.location_type, "approximate");
610        assert!(loc.city.is_none());
611        assert!(loc.country.is_none());
612        assert!(loc.region.is_none());
613        assert!(loc.timezone.is_none());
614    }
615
616    #[test]
617    fn test_user_location_serialization_skips_none() {
618        let loc = UserLocation::new();
619        let json = serde_json::to_value(&loc).unwrap();
620        assert_eq!(json["type"], "approximate");
621        assert!(json.get("city").is_none());
622    }
623
624    // --- WebFetchTool ---
625
626    #[test]
627    fn test_web_fetch_tool_new_type_and_name() {
628        let tool = WebFetchTool::new();
629        assert_eq!(tool.tool_type, "web_fetch_20260309");
630        assert_eq!(tool.name, "web_fetch");
631    }
632
633    #[test]
634    fn test_web_fetch_tool_serialization() {
635        let tool = WebFetchTool::new();
636        let json = serde_json::to_value(&tool).unwrap();
637        assert_eq!(json["type"], "web_fetch_20260309");
638        assert_eq!(json["name"], "web_fetch");
639        assert!(json.get("max_uses").is_none());
640        assert!(json.get("max_content_tokens").is_none());
641    }
642
643    #[test]
644    fn test_web_fetch_tool_roundtrip() {
645        let tool = WebFetchTool::new();
646        let json = serde_json::to_string(&tool).unwrap();
647        let deserialized: WebFetchTool = serde_json::from_str(&json).unwrap();
648        assert_eq!(deserialized.tool_type, "web_fetch_20260309");
649        assert_eq!(deserialized.name, "web_fetch");
650    }
651
652    // --- CodeExecutionTool ---
653
654    #[test]
655    fn test_code_execution_tool_new_type_and_name() {
656        let tool = CodeExecutionTool::new();
657        assert_eq!(tool.tool_type, "code_execution_20260120");
658        assert_eq!(tool.name, "code_execution");
659    }
660
661    #[test]
662    fn test_code_execution_tool_serialization() {
663        let tool = CodeExecutionTool::new();
664        let json = serde_json::to_value(&tool).unwrap();
665        assert_eq!(json["type"], "code_execution_20260120");
666        assert_eq!(json["name"], "code_execution");
667        assert!(json.get("cache_control").is_none());
668    }
669
670    #[test]
671    fn test_code_execution_tool_roundtrip() {
672        let tool = CodeExecutionTool::new();
673        let json = serde_json::to_string(&tool).unwrap();
674        let deserialized: CodeExecutionTool = serde_json::from_str(&json).unwrap();
675        assert_eq!(deserialized.tool_type, "code_execution_20260120");
676        assert_eq!(deserialized.name, "code_execution");
677    }
678
679    // --- BashTool ---
680
681    #[test]
682    fn test_bash_tool_new_type_and_name() {
683        let tool = BashTool::new();
684        assert_eq!(tool.tool_type, "bash_20250124");
685        assert_eq!(tool.name, "bash");
686    }
687
688    #[test]
689    fn test_bash_tool_serialization() {
690        let tool = BashTool::new();
691        let json = serde_json::to_value(&tool).unwrap();
692        assert_eq!(json["type"], "bash_20250124");
693        assert_eq!(json["name"], "bash");
694        assert!(json.get("cache_control").is_none());
695    }
696
697    #[test]
698    fn test_bash_tool_roundtrip() {
699        let tool = BashTool::new();
700        let json = serde_json::to_string(&tool).unwrap();
701        let deserialized: BashTool = serde_json::from_str(&json).unwrap();
702        assert_eq!(deserialized.tool_type, "bash_20250124");
703        assert_eq!(deserialized.name, "bash");
704    }
705
706    // --- TextEditorTool ---
707
708    #[test]
709    fn test_text_editor_tool_new_type_and_name() {
710        let tool = TextEditorTool::new();
711        assert_eq!(tool.tool_type, "text_editor_20250728");
712        assert_eq!(tool.name, "str_replace_editor");
713    }
714
715    #[test]
716    fn test_text_editor_tool_serialization() {
717        let tool = TextEditorTool::new();
718        let json = serde_json::to_value(&tool).unwrap();
719        assert_eq!(json["type"], "text_editor_20250728");
720        assert_eq!(json["name"], "str_replace_editor");
721        assert!(json.get("max_characters").is_none());
722        assert!(json.get("cache_control").is_none());
723    }
724
725    #[test]
726    fn test_text_editor_tool_roundtrip() {
727        let tool = TextEditorTool::new();
728        let json = serde_json::to_string(&tool).unwrap();
729        let deserialized: TextEditorTool = serde_json::from_str(&json).unwrap();
730        assert_eq!(deserialized.tool_type, "text_editor_20250728");
731        assert_eq!(deserialized.name, "str_replace_editor");
732    }
733
734    // --- MemoryTool ---
735
736    #[test]
737    fn test_memory_tool_serialization() {
738        let tool = MemoryTool::new();
739        let json = serde_json::to_value(&tool).unwrap();
740        assert_eq!(json["type"], "memory_20250818");
741        assert_eq!(json["name"], "memory");
742    }
743
744    // --- ToolSearchBm25 ---
745
746    #[test]
747    fn test_tool_search_bm25_serialization() {
748        let tool = ToolSearchBm25::new();
749        let json = serde_json::to_value(&tool).unwrap();
750        assert_eq!(json["type"], "tool_search_tool_bm25_20251119");
751        assert_eq!(json["name"], "tool_search_tool_bm25");
752    }
753
754    // --- ToolSearchRegex ---
755
756    #[test]
757    fn test_tool_search_regex_serialization() {
758        let tool = ToolSearchRegex::new();
759        let json = serde_json::to_value(&tool).unwrap();
760        assert_eq!(json["type"], "tool_search_tool_regex_20251119");
761        assert_eq!(json["name"], "tool_search_tool_regex");
762    }
763}