Skip to main content

ai_agent/tools/
types.rs

1// Source: /data/home/swei/claudecode/openclaudecode/src/utils/filePersistence/types.ts
2use crate::types::*;
3use std::future::Future;
4
5pub use crate::types::{ToolDefinition, ToolInputSchema};
6
7/// A boxed async future that returns a ToolResult or AgentError.
8/// This is the standard return type for tool executor functions.
9pub type ToolFuture =
10    std::pin::Pin<Box<dyn Future<Output = Result<ToolResult, crate::error::AgentError>> + Send>>;
11
12pub trait Tool {
13    fn name(&self) -> &str;
14    fn description(&self) -> &str;
15    fn input_schema(&self) -> ToolInputSchema;
16    fn execute(
17        &self,
18        input: serde_json::Value,
19        context: &ToolContext,
20    ) -> impl Future<Output = Result<ToolResult, crate::error::AgentError>> + Send;
21}
22
23fn bash_schema() -> ToolInputSchema {
24    ToolInputSchema {
25        schema_type: "object".to_string(),
26        properties: serde_json::json!({
27            "command": {
28                "type": "string",
29                "description": "The shell command to execute"
30            }
31        }),
32        required: Some(vec!["command".to_string()]),
33    }
34}
35
36fn file_read_schema() -> ToolInputSchema {
37    ToolInputSchema {
38        schema_type: "object".to_string(),
39        properties: serde_json::json!({
40            "path": {
41                "type": "string",
42                "description": "The file path to read"
43            }
44        }),
45        required: Some(vec!["path".to_string()]),
46    }
47}
48
49fn file_write_schema() -> ToolInputSchema {
50    ToolInputSchema {
51        schema_type: "object".to_string(),
52        properties: serde_json::json!({
53            "path": {
54                "type": "string",
55                "description": "The file path to write to"
56            },
57            "content": {
58                "type": "string",
59                "description": "The content to write"
60            }
61        }),
62        required: Some(vec!["path".to_string(), "content".to_string()]),
63    }
64}
65
66fn glob_schema() -> ToolInputSchema {
67    ToolInputSchema {
68        schema_type: "object".to_string(),
69        properties: serde_json::json!({
70            "pattern": {
71                "type": "string",
72                "description": "The glob pattern to match"
73            }
74        }),
75        required: Some(vec!["pattern".to_string()]),
76    }
77}
78
79fn grep_schema() -> ToolInputSchema {
80    ToolInputSchema {
81        schema_type: "object".to_string(),
82        properties: serde_json::json!({
83            "pattern": {
84                "type": "string",
85                "description": "The regex pattern to search for"
86            },
87            "path": {
88                "type": "string",
89                "description": "The file or directory to search in"
90            }
91        }),
92        required: Some(vec!["pattern".to_string()]),
93    }
94}
95
96fn file_edit_schema() -> ToolInputSchema {
97    ToolInputSchema {
98        schema_type: "object".to_string(),
99        properties: serde_json::json!({
100            "file_path": {
101                "type": "string",
102                "description": "The absolute path to the file to modify"
103            },
104            "old_string": {
105                "type": "string",
106                "description": "The exact text to find and replace"
107            },
108            "new_string": {
109                "type": "string",
110                "description": "The replacement text"
111            },
112            "replace_all": {
113                "type": "boolean",
114                "description": "Replace all occurrences (default false)"
115            }
116        }),
117        required: Some(vec![
118            "file_path".to_string(),
119            "old_string".to_string(),
120            "new_string".to_string(),
121        ]),
122    }
123}
124
125fn notebook_edit_schema() -> ToolInputSchema {
126    ToolInputSchema {
127        schema_type: "object".to_string(),
128        properties: serde_json::json!({
129            "file_path": {
130                "type": "string",
131                "description": "Path to the .ipynb file"
132            },
133            "command": {
134                "type": "string",
135                "enum": ["insert", "replace", "delete"],
136                "description": "The edit operation to perform"
137            },
138            "cell_number": {
139                "type": "number",
140                "description": "Cell index (0-based) to operate on"
141            },
142            "cell_type": {
143                "type": "string",
144                "enum": ["code", "markdown"],
145                "description": "Type of cell (for insert/replace)"
146            },
147            "source": {
148                "type": "string",
149                "description": "Cell content (for insert/replace)"
150            }
151        }),
152        required: Some(vec![
153            "file_path".to_string(),
154            "command".to_string(),
155            "cell_number".to_string(),
156        ]),
157    }
158}
159
160fn web_fetch_schema() -> ToolInputSchema {
161    ToolInputSchema {
162        schema_type: "object".to_string(),
163        properties: serde_json::json!({
164            "url": {
165                "type": "string",
166                "description": "The URL to fetch content from"
167            },
168            "headers": {
169                "type": "object",
170                "description": "Optional HTTP headers",
171                "additionalProperties": {
172                    "type": "string"
173                }
174            }
175        }),
176        required: Some(vec!["url".to_string()]),
177    }
178}
179
180fn web_search_schema() -> ToolInputSchema {
181    ToolInputSchema {
182        schema_type: "object".to_string(),
183        properties: serde_json::json!({
184            "query": {
185                "type": "string",
186                "description": "The search query"
187            },
188            "num_results": {
189                "type": "number",
190                "description": "Number of results to return (default: 5)"
191            }
192        }),
193        required: Some(vec!["query".to_string()]),
194    }
195}
196
197const ALL_TOOLS: &[(&str, &str, fn() -> ToolDefinition)] = &[
198    ("Bash", "Execute shell commands", || ToolDefinition {
199        name: "Bash".to_string(),
200        description: "Execute shell commands".to_string(),
201        input_schema: bash_schema(),
202        annotations: None,
203    }),
204    ("FileRead", "Read files from filesystem", || {
205        ToolDefinition {
206            name: "FileRead".to_string(),
207            description: "Read files from filesystem".to_string(),
208            input_schema: file_read_schema(),
209            annotations: None,
210        }
211    }),
212    ("FileWrite", "Write content to files", || ToolDefinition {
213        name: "FileWrite".to_string(),
214        description: "Write content to files".to_string(),
215        input_schema: file_write_schema(),
216        annotations: None,
217    }),
218    ("Glob", "Find files by pattern", || ToolDefinition {
219        name: "Glob".to_string(),
220        description: "Find files by pattern".to_string(),
221        input_schema: glob_schema(),
222        annotations: None,
223    }),
224    ("Grep", "Search file contents", || ToolDefinition {
225        name: "Grep".to_string(),
226        description: "Search file contents".to_string(),
227        input_schema: grep_schema(),
228        annotations: None,
229    }),
230    (
231        "FileEdit",
232        "Edit files by performing exact string replacements",
233        || ToolDefinition {
234            name: "FileEdit".to_string(),
235            description: "Edit files by performing exact string replacements".to_string(),
236            input_schema: file_edit_schema(),
237            annotations: None,
238        },
239    ),
240    (
241        "NotebookEdit",
242        "Edit Jupyter notebook (.ipynb) cells",
243        || ToolDefinition {
244            name: "NotebookEdit".to_string(),
245            description: "Edit Jupyter notebook (.ipynb) cells".to_string(),
246            input_schema: notebook_edit_schema(),
247            annotations: None,
248        },
249    ),
250    (
251        "WebFetch",
252        "Fetch content from a URL and return it as text",
253        || {
254            ToolDefinition {
255        name: "WebFetch".to_string(),
256        description: "Fetch content from a URL and return it as text. Supports HTML pages, JSON APIs, and plain text. Strips HTML tags for readability.".to_string(),
257        input_schema: web_fetch_schema(),
258        annotations: None,
259    }
260        },
261    ),
262    ("WebSearch", "Search the web for information", || {
263        ToolDefinition {
264        name: "WebSearch".to_string(),
265        description: "Search the web for information. Returns search results with titles, URLs, and snippets.".to_string(),
266        input_schema: web_search_schema(),
267        annotations: None,
268    }
269    }),
270    (
271        "Agent",
272        "Launch a new agent to handle complex multi-step tasks",
273        || {
274            ToolDefinition {
275        name: "Agent".to_string(),
276        description: "Launch a new agent to handle complex, multi-step tasks autonomously. Use this tool to spawn specialized subagents.".to_string(),
277        input_schema: agent_schema(),
278        annotations: None,
279    }
280        },
281    ),
282    ("TaskCreate", "Create a new task in the task list", || {
283        ToolDefinition {
284            name: "TaskCreate".to_string(),
285            description: "Create a new task in the task list".to_string(),
286            input_schema: task_create_schema(),
287            annotations: None,
288        }
289    }),
290    ("TaskList", "List all tasks in the task list", || {
291        ToolDefinition {
292            name: "TaskList".to_string(),
293            description: "List all tasks in the task list".to_string(),
294            input_schema: task_list_schema(),
295            annotations: None,
296        }
297    }),
298    ("TaskUpdate", "Update an existing task", || ToolDefinition {
299        name: "TaskUpdate".to_string(),
300        description: "Update an existing task's status or details".to_string(),
301        input_schema: task_update_schema(),
302        annotations: None,
303    }),
304    ("TaskGet", "Get details of a specific task", || {
305        ToolDefinition {
306            name: "TaskGet".to_string(),
307            description: "Get details of a specific task by ID".to_string(),
308            input_schema: task_get_schema(),
309            annotations: None,
310        }
311    }),
312    (
313        "TeamCreate",
314        "Create a team of agents for parallel work",
315        || ToolDefinition {
316            name: "TeamCreate".to_string(),
317            description: "Create a team of agents that can work in parallel".to_string(),
318            input_schema: team_create_schema(),
319            annotations: None,
320        },
321    ),
322    ("TeamDelete", "Delete a team of agents", || ToolDefinition {
323        name: "TeamDelete".to_string(),
324        description: "Delete a previously created team".to_string(),
325        input_schema: team_delete_schema(),
326        annotations: None,
327    }),
328    ("SendMessage", "Send a message to another agent", || {
329        ToolDefinition {
330            name: "SendMessage".to_string(),
331            description: "Send a message to another agent".to_string(),
332            input_schema: send_message_schema(),
333            annotations: None,
334        }
335    }),
336    ("EnterWorktree", "Create and enter a git worktree", || {
337        ToolDefinition {
338            name: "EnterWorktree".to_string(),
339            description: "Create and enter a git worktree for isolated work".to_string(),
340            input_schema: enter_worktree_schema(),
341            annotations: None,
342        }
343    }),
344    (
345        "ExitWorktree",
346        "Exit a worktree and return to original directory",
347        || ToolDefinition {
348            name: "ExitWorktree".to_string(),
349            description: "Exit a worktree and return to the original working directory".to_string(),
350            input_schema: exit_worktree_schema(),
351            annotations: None,
352        },
353    ),
354    ("EnterPlanMode", "Enter structured planning mode", || {
355        ToolDefinition {
356            name: "EnterPlanMode".to_string(),
357            description: "Enter structured planning mode to explore and design implementation"
358                .to_string(),
359            input_schema: enter_plan_mode_schema(),
360            annotations: None,
361        }
362    }),
363    ("ExitPlanMode", "Exit planning mode", || ToolDefinition {
364        name: "ExitPlanMode".to_string(),
365        description: "Exit planning mode and present the plan for approval".to_string(),
366        input_schema: exit_plan_mode_schema(),
367        annotations: None,
368    }),
369    (
370        "AskUserQuestion",
371        "Ask the user a question with multiple choice options",
372        || ToolDefinition {
373            name: "AskUserQuestion".to_string(),
374            description: "Ask the user a question with multiple choice options".to_string(),
375            input_schema: ask_user_question_schema(),
376            annotations: None,
377        },
378    ),
379    ("ToolSearch", "Search for available tools", || {
380        ToolDefinition {
381            name: "ToolSearch".to_string(),
382            description: "Search for available tools by name or description".to_string(),
383            input_schema: tool_search_schema(),
384            annotations: None,
385        }
386    }),
387    ("CronCreate", "Create a scheduled task", || ToolDefinition {
388        name: "CronCreate".to_string(),
389        description: "Create a scheduled task that runs on a cron schedule".to_string(),
390        input_schema: cron_create_schema(),
391        annotations: None,
392    }),
393    ("CronDelete", "Delete a scheduled task", || ToolDefinition {
394        name: "CronDelete".to_string(),
395        description: "Delete a previously created scheduled task".to_string(),
396        input_schema: cron_delete_schema(),
397        annotations: None,
398    }),
399    ("CronList", "List all scheduled tasks", || ToolDefinition {
400        name: "CronList".to_string(),
401        description: "List all scheduled tasks".to_string(),
402        input_schema: cron_list_schema(),
403        annotations: None,
404    }),
405    ("Config", "Read or update configuration", || {
406        ToolDefinition {
407            name: "Config".to_string(),
408            description: "Read or update dynamic configuration".to_string(),
409            input_schema: config_schema(),
410            annotations: None,
411        }
412    }),
413    ("TodoWrite", "Write todo list items", || ToolDefinition {
414        name: "TodoWrite".to_string(),
415        description: "Write todo list items for the session".to_string(),
416        input_schema: todo_write_schema(),
417        annotations: None,
418    }),
419    ("Skill", "Invoke a skill by name", || ToolDefinition {
420        name: "Skill".to_string(),
421        description: "Invoke a skill by name to execute its commands".to_string(),
422        input_schema: skill_schema(),
423        annotations: None,
424    }),
425];
426
427fn agent_schema() -> ToolInputSchema {
428    ToolInputSchema {
429        schema_type: "object".to_string(),
430        properties: serde_json::json!({
431            "description": {
432                "type": "string",
433                "description": "A short description (3-5 words) summarizing what the agent will do"
434            },
435            "subagent_type": {
436                "type": "string",
437                "description": "The type of subagent to use. If omitted, uses the general-purpose agent."
438            },
439            "prompt": {
440                "type": "string",
441                "description": "The task prompt for the subagent to execute"
442            },
443            "model": {
444                "type": "string",
445                "description": "Optional model override for this subagent"
446            },
447            "max_turns": {
448                "type": "number",
449                "description": "Maximum number of turns for this subagent (default: 10)"
450            },
451            "run_in_background": {
452                "type": "boolean",
453                "description": "Whether to run the agent in the background (default: false)"
454            },
455            "isolation": {
456                "type": "string",
457                "enum": ["worktree", "remote"],
458                "description": "Isolation mode: 'worktree' for git worktree, 'remote' for remote CCR"
459            }
460        }),
461        required: Some(vec!["description".to_string(), "prompt".to_string()]),
462    }
463}
464
465// Task tool schemas
466fn task_create_schema() -> ToolInputSchema {
467    ToolInputSchema {
468        schema_type: "object".to_string(),
469        properties: serde_json::json!({
470            "subject": { "type": "string", "description": "A brief title for the task" },
471            "description": { "type": "string", "description": "What needs to be done" },
472            "activeForm": { "type": "string", "description": "Spinner text when in_progress" }
473        }),
474        required: Some(vec!["subject".to_string(), "description".to_string()]),
475    }
476}
477
478fn task_list_schema() -> ToolInputSchema {
479    ToolInputSchema {
480        schema_type: "object".to_string(),
481        properties: serde_json::json!({}),
482        required: None,
483    }
484}
485
486fn task_update_schema() -> ToolInputSchema {
487    ToolInputSchema {
488        schema_type: "object".to_string(),
489        properties: serde_json::json!({
490            "taskId": { "type": "string", "description": "The ID of the task to update" },
491            "subject": { "type": "string", "description": "New subject for the task" },
492            "description": { "type": "string", "description": "New description" },
493            "status": { "type": "string", "enum": ["pending", "in_progress", "completed", "deleted"], "description": "New status" },
494            "activeForm": { "type": "string", "description": "New spinner text" }
495        }),
496        required: Some(vec!["taskId".to_string()]),
497    }
498}
499
500fn task_get_schema() -> ToolInputSchema {
501    ToolInputSchema {
502        schema_type: "object".to_string(),
503        properties: serde_json::json!({
504            "taskId": { "type": "string", "description": "The ID of the task to retrieve" }
505        }),
506        required: Some(vec!["taskId".to_string()]),
507    }
508}
509
510// Team tool schemas
511fn team_create_schema() -> ToolInputSchema {
512    ToolInputSchema {
513        schema_type: "object".to_string(),
514        properties: serde_json::json!({
515            "name": { "type": "string", "description": "Name of the team" },
516            "description": { "type": "string", "description": "Description of what the team does" },
517            "agents": { "type": "array", "items": serde_json::json!({}), "description": "List of agents in the team" }
518        }),
519        required: Some(vec!["name".to_string()]),
520    }
521}
522
523fn team_delete_schema() -> ToolInputSchema {
524    ToolInputSchema {
525        schema_type: "object".to_string(),
526        properties: serde_json::json!({
527            "name": { "type": "string", "description": "Name of the team to delete" }
528        }),
529        required: Some(vec!["name".to_string()]),
530    }
531}
532
533fn send_message_schema() -> ToolInputSchema {
534    ToolInputSchema {
535        schema_type: "object".to_string(),
536        properties: serde_json::json!({
537            "to": { "type": "string", "description": "Agent name to send message to" },
538            "message": { "type": "string", "description": "Message content" }
539        }),
540        required: Some(vec!["to".to_string(), "message".to_string()]),
541    }
542}
543
544// Worktree tool schemas
545fn enter_worktree_schema() -> ToolInputSchema {
546    ToolInputSchema {
547        schema_type: "object".to_string(),
548        properties: serde_json::json!({
549            "name": { "type": "string", "description": "Optional name for the worktree" }
550        }),
551        required: None,
552    }
553}
554
555fn exit_worktree_schema() -> ToolInputSchema {
556    ToolInputSchema {
557        schema_type: "object".to_string(),
558        properties: serde_json::json!({
559            "action": { "type": "string", "enum": ["keep", "remove"], "description": "What to do with the worktree" },
560            "discardChanges": { "type": "boolean", "description": "Discard uncommitted changes before removing" }
561        }),
562        required: None,
563    }
564}
565
566// Plan mode tool schemas
567fn enter_plan_mode_schema() -> ToolInputSchema {
568    ToolInputSchema {
569        schema_type: "object".to_string(),
570        properties: serde_json::json!({
571            "allowedPrompts": { "type": "array", "items": { "type": "string" }, "description": "Prompt-based permissions" }
572        }),
573        required: None,
574    }
575}
576
577fn exit_plan_mode_schema() -> ToolInputSchema {
578    ToolInputSchema {
579        schema_type: "object".to_string(),
580        properties: serde_json::json!({}),
581        required: None,
582    }
583}
584
585// Ask user question schema
586fn ask_user_question_schema() -> ToolInputSchema {
587    ToolInputSchema {
588        schema_type: "object".to_string(),
589        properties: serde_json::json!({
590            "question": { "type": "string", "description": "The question to ask the user" },
591            "header": { "type": "string", "description": "Short label displayed as a chip/tag" },
592            "options": { "type": "array", "items": serde_json::json!({}), "description": "Available choices" },
593            "multiSelect": { "type": "boolean", "description": "Allow multiple answers" }
594        }),
595        required: Some(vec![
596            "question".to_string(),
597            "header".to_string(),
598            "options".to_string(),
599        ]),
600    }
601}
602
603// ToolSearch schema
604fn tool_search_schema() -> ToolInputSchema {
605    ToolInputSchema {
606        schema_type: "object".to_string(),
607        properties: serde_json::json!({
608            "query": { "type": "string", "description": "Search query to find relevant tools" }
609        }),
610        required: Some(vec!["query".to_string()]),
611    }
612}
613
614// Cron tool schemas
615fn cron_create_schema() -> ToolInputSchema {
616    ToolInputSchema {
617        schema_type: "object".to_string(),
618        properties: serde_json::json!({
619            "cron": { "type": "string", "description": "5-field cron expression" },
620            "prompt": { "type": "string", "description": "The prompt to execute" },
621            "recurring": { "type": "boolean", "description": "true = repeat, false = one-shot" },
622            "durable": { "type": "boolean", "description": "true = persist across restarts" }
623        }),
624        required: Some(vec!["cron".to_string(), "prompt".to_string()]),
625    }
626}
627
628fn cron_delete_schema() -> ToolInputSchema {
629    ToolInputSchema {
630        schema_type: "object".to_string(),
631        properties: serde_json::json!({
632            "id": { "type": "string", "description": "Job ID returned by CronCreate" }
633        }),
634        required: Some(vec!["id".to_string()]),
635    }
636}
637
638fn cron_list_schema() -> ToolInputSchema {
639    ToolInputSchema {
640        schema_type: "object".to_string(),
641        properties: serde_json::json!({}),
642        required: None,
643    }
644}
645
646// Config schema
647fn config_schema() -> ToolInputSchema {
648    ToolInputSchema {
649        schema_type: "object".to_string(),
650        properties: serde_json::json!({
651            "action": { "type": "string", "enum": ["get", "set", "list"], "description": "Action to perform" },
652            "key": { "type": "string", "description": "Configuration key" },
653            "value": { "type": "string", "description": "Configuration value" }
654        }),
655        required: Some(vec!["action".to_string()]),
656    }
657}
658
659// TodoWrite schema
660fn todo_write_schema() -> ToolInputSchema {
661    ToolInputSchema {
662        schema_type: "object".to_string(),
663        properties: serde_json::json!({
664            "todos": { "type": "array", "items": serde_json::json!({}), "description": "List of todo items" }
665        }),
666        required: Some(vec!["todos".to_string()]),
667    }
668}
669
670// Skill schema
671fn skill_schema() -> ToolInputSchema {
672    ToolInputSchema {
673        schema_type: "object".to_string(),
674        properties: serde_json::json!({
675            "skill": { "type": "string", "description": "The name of the skill to invoke" }
676        }),
677        required: Some(vec!["skill".to_string()]),
678    }
679}
680
681pub fn get_all_base_tools() -> Vec<ToolDefinition> {
682    ALL_TOOLS.iter().map(|f| f.2()).collect()
683}
684
685pub fn filter_tools(
686    tools: Vec<ToolDefinition>,
687    allowed: Option<Vec<String>>,
688    disallowed: Option<Vec<String>>,
689) -> Vec<ToolDefinition> {
690    let mut result = tools;
691    if let Some(allowed) = allowed {
692        let allowed_set: std::collections::HashSet<_> = allowed.into_iter().collect();
693        result.retain(|t| allowed_set.contains(&t.name));
694    }
695    if let Some(disallowed) = disallowed {
696        let disallowed_set: std::collections::HashSet<_> = disallowed.into_iter().collect();
697        result.retain(|t| !disallowed_set.contains(&t.name));
698    }
699    result
700}
701
702// --------------------------------------------------------------------------
703// Tool Helper Functions (translated from Tool.ts)
704// --------------------------------------------------------------------------
705
706/// Tool with metadata for matching
707#[derive(Debug, Clone)]
708pub struct ToolWithMetadata {
709    pub name: String,
710    pub aliases: Option<Vec<String>>,
711}
712
713/// Checks if a tool matches the given name (primary name or alias)
714pub fn tool_matches_name(tool: &ToolWithMetadata, name: &str) -> bool {
715    tool.name == name
716        || tool
717            .aliases
718            .as_ref()
719            .map_or(false, |a| a.contains(&name.to_string()))
720}
721
722/// Finds a tool by name or alias from a list of tools
723pub fn find_tool_by_name<'a>(
724    tools: &'a [ToolDefinition],
725    name: &str,
726) -> Option<&'a ToolDefinition> {
727    tools.iter().find(|t| t.name == name)
728}
729
730/// Tool definition with optional fields (like ToolDef in TypeScript)
731pub struct PartialToolDefinition {
732    pub name: String,
733    pub description: Option<String>,
734    pub input_schema: Option<ToolInputSchema>,
735    pub aliases: Option<Vec<String>>,
736    pub search_hint: Option<String>,
737    pub max_result_size_chars: Option<usize>,
738    pub should_defer: Option<bool>,
739    pub always_load: Option<bool>,
740    pub is_enabled: Option<Box<dyn Fn() -> bool + Send + Sync>>,
741    pub is_concurrency_safe: Option<Box<dyn Fn(&serde_json::Value) -> bool + Send + Sync>>,
742    pub is_read_only: Option<Box<dyn Fn(&serde_json::Value) -> bool + Send + Sync>>,
743    pub is_destructive: Option<Box<dyn Fn(&serde_json::Value) -> bool + Send + Sync>>,
744    pub interrupt_behavior: Option<Box<dyn Fn() -> InterruptBehavior + Send + Sync>>,
745    pub is_search_or_read_command:
746        Option<Box<dyn Fn(&serde_json::Value) -> SearchOrReadCommand + Send + Sync>>,
747    pub is_open_world: Option<Box<dyn Fn(&serde_json::Value) -> bool + Send + Sync>>,
748    pub requires_user_interaction: Option<Box<dyn Fn() -> bool + Send + Sync>>,
749    pub is_mcp: Option<bool>,
750    pub is_lsp: Option<bool>,
751    pub user_facing_name: Option<Box<dyn Fn(Option<&serde_json::Value>) -> String + Send + Sync>>,
752}
753
754impl Default for PartialToolDefinition {
755    fn default() -> Self {
756        Self {
757            name: String::new(),
758            description: None,
759            input_schema: None,
760            aliases: None,
761            search_hint: None,
762            max_result_size_chars: None,
763            should_defer: None,
764            always_load: None,
765            is_enabled: None,
766            is_concurrency_safe: None,
767            is_read_only: None,
768            is_destructive: None,
769            interrupt_behavior: None,
770            is_search_or_read_command: None,
771            is_open_world: None,
772            requires_user_interaction: None,
773            is_mcp: None,
774            is_lsp: None,
775            user_facing_name: None,
776        }
777    }
778}
779
780/// Interrupt behavior when user submits new message while tool is running
781#[derive(Debug, Clone, Copy, PartialEq)]
782pub enum InterruptBehavior {
783    /// Stop the tool and discard its result
784    Cancel,
785    /// Keep running; the new message waits
786    Block,
787}
788
789impl Default for InterruptBehavior {
790    fn default() -> Self {
791        InterruptBehavior::Block
792    }
793}
794
795/// Search or read command result
796#[derive(Debug, Clone, Default)]
797pub struct SearchOrReadCommand {
798    pub is_search: bool,
799    pub is_read: bool,
800    pub is_list: Option<bool>,
801}
802
803/// Build a complete `ToolDefinition` from a partial definition
804pub fn build_tool(def: PartialToolDefinition) -> ToolDefinition {
805    ToolDefinition {
806        name: def.name.clone(),
807        description: def.description.unwrap_or_default(),
808        input_schema: def.input_schema.unwrap_or_default(),
809        annotations: Some(ToolAnnotations {
810            read_only: Some(
811                def.is_read_only
812                    .map_or(false, |f| f(&serde_json::json!({}))),
813            ),
814            destructive: Some(
815                def.is_destructive
816                    .as_ref()
817                    .map_or(false, |f| f(&serde_json::json!({}))),
818            ),
819            concurrency_safe: Some(
820                def.is_concurrency_safe
821                    .as_ref()
822                    .map_or(false, |f| f(&serde_json::json!({}))),
823            ),
824            open_world: None,
825            idempotent: None,
826        }),
827    }
828}