1use crate::types::*;
3use std::future::Future;
4
5pub use crate::types::{ToolDefinition, ToolInputSchema};
6
7fn sleep_schema() -> ToolInputSchema {
9 ToolInputSchema {
10 schema_type: "object".to_string(),
11 properties: serde_json::json!({
12 "duration": {
13 "type": "number",
14 "description": "Duration to sleep in seconds (default: 60)"
15 }
16 }),
17 required: None,
18 }
19}
20
21fn powershell_schema() -> ToolInputSchema {
22 ToolInputSchema {
23 schema_type: "object".to_string(),
24 properties: serde_json::json!({
25 "command": {
26 "type": "string",
27 "description": "PowerShell command to execute"
28 },
29 "timeout": {
30 "type": "number",
31 "description": "Optional timeout in milliseconds (default: 120000, max: 600000)"
32 },
33 "description": {
34 "type": "string",
35 "description": "Brief description of what this command does"
36 },
37 "run_in_background": {
38 "type": "boolean",
39 "description": "Run the command in the background (default: false)"
40 }
41 }),
42 required: Some(vec!["command".to_string()]),
43 }
44}
45
46fn monitor_schema() -> ToolInputSchema {
47 ToolInputSchema {
48 schema_type: "object".to_string(),
49 properties: serde_json::json!({}),
50 required: None,
51 }
52}
53
54fn send_user_file_schema() -> ToolInputSchema {
55 ToolInputSchema {
56 schema_type: "object".to_string(),
57 properties: serde_json::json!({}),
58 required: None,
59 }
60}
61
62fn web_browser_schema() -> ToolInputSchema {
63 ToolInputSchema {
64 schema_type: "object".to_string(),
65 properties: serde_json::json!({}),
66 required: None,
67 }
68}
69
70fn brief_schema() -> ToolInputSchema {
71 ToolInputSchema {
72 schema_type: "object".to_string(),
73 properties: serde_json::json!({
74 "message": {
75 "type": "string",
76 "description": "The message for the user. Supports markdown formatting."
77 },
78 "attachments": {
79 "type": "array",
80 "items": {"type": "string"},
81 "description": "Optional file paths to attach"
82 },
83 "status": {
84 "type": "string",
85 "enum": ["normal", "proactive"],
86 "description": "Use 'proactive' when surfacing something the user hasn't asked for"
87 }
88 }),
89 required: Some(vec!["message".to_string()]),
90 }
91}
92
93fn synthetic_output_schema() -> ToolInputSchema {
94 ToolInputSchema {
95 schema_type: "object".to_string(),
96 properties: serde_json::json!({}),
97 required: None,
98 }
99}
100
101pub type ToolFuture =
104 std::pin::Pin<Box<dyn Future<Output = Result<ToolResult, crate::error::AgentError>> + Send>>;
105
106pub trait Tool {
107 fn name(&self) -> &str;
108 fn description(&self) -> &str;
109 fn input_schema(&self) -> ToolInputSchema;
110 fn execute(
111 &self,
112 input: serde_json::Value,
113 context: &ToolContext,
114 ) -> impl Future<Output = Result<ToolResult, crate::error::AgentError>> + Send;
115 fn backfill_observable_input(&self, _input: &mut serde_json::Value) {}
119}
120
121fn bash_schema() -> ToolInputSchema {
122 ToolInputSchema {
123 schema_type: "object".to_string(),
124 properties: serde_json::json!({
125 "command": {
126 "type": "string",
127 "description": "The command to execute"
128 },
129 "timeout": {
130 "type": "number",
131 "description": "Optional timeout in milliseconds"
132 },
133 "description": {
134 "type": "string",
135 "description": "Clear, concise description of what this command does in active voice"
136 },
137 "run_in_background": {
138 "type": "boolean",
139 "description": "Set to true to run this command in the background. Use Read to read the output later."
140 },
141 "dangerouslyDisableSandbox": {
142 "type": "boolean",
143 "description": "Set this to true to dangerously override sandbox mode and run commands without sandboxing."
144 }
145 }),
146 required: Some(vec!["command".to_string()]),
147 }
148}
149
150fn file_read_schema() -> ToolInputSchema {
151 ToolInputSchema {
152 schema_type: "object".to_string(),
153 properties: serde_json::json!({
154 "file_path": {
155 "type": "string",
156 "description": "The absolute path to the file to read"
157 },
158 "offset": {
159 "type": "integer",
160 "description": "The line number to start reading from. Only provide if the file is too large to read at once"
161 },
162 "limit": {
163 "type": "integer",
164 "description": "The number of lines to read. Only provide if the file is too large to read at once."
165 },
166 "pages": {
167 "type": "string",
168 "description": "Page range for PDF files (e.g., \"1-5\", \"3\", \"10-20\"). Only applicable to PDF files. Maximum 20 pages per request."
169 }
170 }),
171 required: Some(vec!["file_path".to_string()]),
172 }
173}
174
175fn file_write_schema() -> ToolInputSchema {
176 ToolInputSchema {
177 schema_type: "object".to_string(),
178 properties: serde_json::json!({
179 "file_path": {
180 "type": "string",
181 "description": "The absolute path to the file to write (must be absolute, not relative)"
182 },
183 "content": {
184 "type": "string",
185 "description": "The content to write to the file"
186 }
187 }),
188 required: Some(vec!["file_path".to_string(), "content".to_string()]),
189 }
190}
191
192fn glob_schema() -> ToolInputSchema {
193 ToolInputSchema {
194 schema_type: "object".to_string(),
195 properties: serde_json::json!({
196 "pattern": {
197 "type": "string",
198 "description": "The glob pattern to match files against"
199 },
200 "path": {
201 "type": "string",
202 "description": "The directory to search in. If not specified, the current working directory will be used."
203 }
204 }),
205 required: Some(vec!["pattern".to_string()]),
206 }
207}
208
209fn grep_schema() -> ToolInputSchema {
210 ToolInputSchema {
211 schema_type: "object".to_string(),
212 properties: serde_json::json!({
213 "pattern": {
214 "type": "string",
215 "description": "The regex pattern to search for"
216 },
217 "path": {
218 "type": "string",
219 "description": "The file or directory to search in"
220 }
221 }),
222 required: Some(vec!["pattern".to_string()]),
223 }
224}
225
226fn file_edit_schema() -> ToolInputSchema {
227 ToolInputSchema {
228 schema_type: "object".to_string(),
229 properties: serde_json::json!({
230 "file_path": {
231 "type": "string",
232 "description": "The absolute path to the file to modify"
233 },
234 "old_string": {
235 "type": "string",
236 "description": "The exact text to find and replace"
237 },
238 "new_string": {
239 "type": "string",
240 "description": "The replacement text"
241 },
242 "replace_all": {
243 "type": "boolean",
244 "description": "Replace all occurrences (default false)"
245 }
246 }),
247 required: Some(vec![
248 "file_path".to_string(),
249 "old_string".to_string(),
250 "new_string".to_string(),
251 ]),
252 }
253}
254
255fn notebook_edit_schema() -> ToolInputSchema {
256 ToolInputSchema {
257 schema_type: "object".to_string(),
258 properties: serde_json::json!({
259 "notebook_path": {
260 "type": "string",
261 "description": "The absolute path to the Jupyter notebook file to edit (must be absolute, not relative)"
262 },
263 "cell_id": {
264 "type": "string",
265 "description": "The ID of the cell to edit. When inserting a new cell, the new cell will be inserted after the cell with this ID."
266 },
267 "new_source": {
268 "type": "string",
269 "description": "The new source for the cell"
270 },
271 "cell_type": {
272 "type": "string",
273 "enum": ["code", "markdown"],
274 "description": "The type of the cell (code or markdown). If not specified, defaults to the current cell type."
275 },
276 "edit_mode": {
277 "type": "string",
278 "enum": ["replace", "insert", "delete"],
279 "description": "The type of edit to make (replace, insert, delete). Defaults to replace."
280 }
281 }),
282 required: Some(vec!["notebook_path".to_string(), "new_source".to_string()]),
283 }
284}
285
286fn web_fetch_schema() -> ToolInputSchema {
287 ToolInputSchema {
288 schema_type: "object".to_string(),
289 properties: serde_json::json!({
290 "url": {
291 "type": "string",
292 "description": "The URL to fetch content from"
293 },
294 "headers": {
295 "type": "object",
296 "description": "Optional HTTP headers",
297 "additionalProperties": {
298 "type": "string"
299 }
300 },
301 "prompt": {
302 "type": "string",
303 "description": "Optional prompt for LLM-based content extraction"
304 }
305 }),
306 required: Some(vec!["url".to_string()]),
307 }
308}
309
310fn web_search_schema() -> ToolInputSchema {
311 ToolInputSchema {
312 schema_type: "object".to_string(),
313 properties: serde_json::json!({
314 "query": {
315 "type": "string",
316 "description": "The search query to use"
317 },
318 "allowed_domains": {
319 "type": "array",
320 "items": {"type": "string"},
321 "description": "Only include search results from these domains"
322 },
323 "blocked_domains": {
324 "type": "array",
325 "items": {"type": "string"},
326 "description": "Never include search results from these domains"
327 }
328 }),
329 required: Some(vec!["query".to_string()]),
330 }
331}
332
333const ALL_TOOLS: &[(&str, &str, fn() -> ToolDefinition)] = &[
334 ("Bash", "Execute shell commands", || ToolDefinition {
335 name: "Bash".to_string(),
336 description: "Execute shell commands".to_string(),
337 input_schema: bash_schema(),
338 annotations: None,
339 should_defer: None,
340 always_load: None,
341 is_mcp: None,
342 search_hint: None,
343 aliases: None,
344 user_facing_name: None,
345 interrupt_behavior: None,
346 }),
347 ("Read", "Read files, images, PDFs, notebooks", || {
348 ToolDefinition {
349 name: "Read".to_string(),
350 description: "Read files from filesystem. Supports text files, images (PNG, JPG, GIF, WebP), PDFs, and Jupyter notebooks. Use offset and limit for large files.".to_string(),
351 input_schema: file_read_schema(),
352 annotations: Some(ToolAnnotations::concurrency_safe()),
353 should_defer: Some(false),
354 always_load: Some(true),
355 is_mcp: None,
356 search_hint: Some("read files, images, PDFs, notebooks".to_string()),
357 aliases: None,
358 user_facing_name: None,
359 interrupt_behavior: None,
360 }
361 }),
362 ("Write", "Write content to files", || ToolDefinition {
363 name: "Write".to_string(),
364 description: "Write content to files".to_string(),
365 input_schema: file_write_schema(),
366 annotations: None,
367 should_defer: None,
368 always_load: None,
369 is_mcp: None,
370 search_hint: None,
371 aliases: None,
372 user_facing_name: None,
373 interrupt_behavior: None,
374 }),
375 ("Glob", "Find files by name pattern or wildcard", || {
376 ToolDefinition {
377 name: "Glob".to_string(),
378 description: "Find files by glob pattern (glob pattern matching for file discovery)"
379 .to_string(),
380 input_schema: glob_schema(),
381 annotations: Some(ToolAnnotations::concurrency_safe()),
382 should_defer: Some(false),
383 always_load: Some(true),
384 is_mcp: None,
385 search_hint: Some("find files by name pattern or wildcard".to_string()),
386 aliases: None,
387 user_facing_name: None,
388 interrupt_behavior: None,
389 }
390 }),
391 ("Grep", "Search file contents using regex", || {
392 ToolDefinition {
393 name: "Grep".to_string(),
394 description: "Search file contents using regex patterns. Uses ripgrep (rg) if available, falls back to grep.".to_string(),
395 input_schema: grep_schema(),
396 annotations: Some(ToolAnnotations::concurrency_safe()),
397 should_defer: Some(false),
398 always_load: Some(true),
399 is_mcp: None,
400 search_hint: Some("search file contents using regex".to_string()),
401 aliases: None,
402 user_facing_name: None,
403 interrupt_behavior: None,
404 }
405 }),
406 (
407 "FileEdit",
408 "Edit files by performing exact string replacements",
409 || ToolDefinition {
410 name: "FileEdit".to_string(),
411 description: "Edit files by performing exact string replacements".to_string(),
412 input_schema: file_edit_schema(),
413 annotations: None,
414 should_defer: None,
415 always_load: None,
416 is_mcp: None,
417 search_hint: None,
418 aliases: None,
419 user_facing_name: None,
420 interrupt_behavior: None,
421 },
422 ),
423 (
424 "NotebookEdit",
425 "Edit Jupyter notebook cells (.ipynb)",
426 || ToolDefinition {
427 name: "NotebookEdit".to_string(),
428 description:
429 "Edit Jupyter notebook (.ipynb) cells: replace, insert, or delete cell content"
430 .to_string(),
431 input_schema: notebook_edit_schema(),
432 annotations: None,
433 should_defer: Some(true),
434 always_load: None,
435 is_mcp: None,
436 search_hint: Some("edit Jupyter notebook cells (.ipynb)".to_string()),
437 aliases: None,
438 user_facing_name: None,
439 interrupt_behavior: None,
440 },
441 ),
442 (
443 "WebFetch",
444 "Fetch content from a URL and return it as text",
445 || {
446 ToolDefinition {
447 name: "WebFetch".to_string(),
448 description: "Fetch content from a URL and return it as text. Supports HTML pages, JSON APIs, and plain text. Strips HTML tags for readability. Preapproved hosts can be fetched without additional permission.".to_string(),
449 input_schema: web_fetch_schema(),
450 annotations: None,
451 should_defer: Some(true),
452 always_load: None,
453 is_mcp: None,
454 search_hint: Some("fetch web pages and URLs".to_string()),
455 aliases: None,
456 user_facing_name: None,
457 interrupt_behavior: None,
458 }
459 },
460 ),
461 ("WebSearch", "Search the web for information", || {
462 ToolDefinition {
463 name: "WebSearch".to_string(),
464 description: "Search the web for information. Returns search results with titles, URLs, and snippets.".to_string(),
465 input_schema: web_search_schema(),
466 annotations: Some(ToolAnnotations::concurrency_safe()),
467 should_defer: Some(true),
468 always_load: None,
469 is_mcp: None,
470 search_hint: Some("web search for information".to_string()),
471 aliases: None,
472 user_facing_name: None,
473 interrupt_behavior: None,
474 }
475 }),
476 (
477 "Agent",
478 "Launch a new agent to handle complex multi-step tasks",
479 || {
480 ToolDefinition {
481 name: "Agent".to_string(),
482 description: "Launch a new agent to handle complex, multi-step tasks autonomously. Use this tool to spawn specialized subagents.".to_string(),
483 input_schema: agent_schema(),
484 annotations: None,
485 should_defer: None,
486 always_load: None,
487 is_mcp: None,
488 search_hint: None,
489 aliases: None,
490 user_facing_name: None,
491 interrupt_behavior: None,
492 }
493 },
494 ),
495 ("TaskCreate", "Create a new task in the task list", || {
496 ToolDefinition {
497 name: "TaskCreate".to_string(),
498 description: "Create a new task in the task list".to_string(),
499 input_schema: task_create_schema(),
500 annotations: None,
501 should_defer: None,
502 always_load: None,
503 is_mcp: None,
504 search_hint: None,
505 aliases: None,
506 user_facing_name: None,
507 interrupt_behavior: None,
508 }
509 }),
510 ("TaskList", "List all tasks in the task list", || {
511 ToolDefinition {
512 name: "TaskList".to_string(),
513 description: "List all tasks in the task list".to_string(),
514 input_schema: task_list_schema(),
515 annotations: Some(ToolAnnotations::concurrency_safe()),
516 should_defer: None,
517 always_load: None,
518 is_mcp: None,
519 search_hint: None,
520 aliases: None,
521 user_facing_name: None,
522 interrupt_behavior: None,
523 }
524 }),
525 ("TaskUpdate", "Update an existing task", || ToolDefinition {
526 name: "TaskUpdate".to_string(),
527 description: "Update an existing task's status or details".to_string(),
528 input_schema: task_update_schema(),
529 annotations: None,
530 should_defer: None,
531 always_load: None,
532 is_mcp: None,
533 search_hint: None,
534 aliases: None,
535 user_facing_name: None,
536 interrupt_behavior: None,
537 }),
538 ("TaskGet", "Get details of a specific task", || {
539 ToolDefinition {
540 name: "TaskGet".to_string(),
541 description: "Get details of a specific task by ID".to_string(),
542 input_schema: task_get_schema(),
543 annotations: Some(ToolAnnotations::concurrency_safe()),
544 should_defer: None,
545 always_load: None,
546 is_mcp: None,
547 search_hint: None,
548 aliases: None,
549 user_facing_name: None,
550 interrupt_behavior: None,
551 }
552 }),
553 (
554 "TeamCreate",
555 "Create a team of agents for parallel work",
556 || ToolDefinition {
557 name: "TeamCreate".to_string(),
558 description: "Create a team of agents that can work in parallel".to_string(),
559 input_schema: team_create_schema(),
560 annotations: None,
561 should_defer: None,
562 always_load: None,
563 is_mcp: None,
564 search_hint: None,
565 aliases: None,
566 user_facing_name: None,
567 interrupt_behavior: None,
568 },
569 ),
570 ("TeamDelete", "Delete a team of agents", || ToolDefinition {
571 name: "TeamDelete".to_string(),
572 description: "Delete a previously created team".to_string(),
573 input_schema: team_delete_schema(),
574 annotations: None,
575 should_defer: None,
576 always_load: None,
577 is_mcp: None,
578 search_hint: None,
579 aliases: None,
580 user_facing_name: None,
581 interrupt_behavior: None,
582 }),
583 ("SendMessage", "Send a message to another agent", || {
584 ToolDefinition {
585 name: "SendMessage".to_string(),
586 description: "Send a message to another agent".to_string(),
587 input_schema: send_message_schema(),
588 annotations: None,
589 should_defer: None,
590 always_load: None,
591 is_mcp: None,
592 search_hint: None,
593 aliases: None,
594 user_facing_name: None,
595 interrupt_behavior: None,
596 }
597 }),
598 ("EnterWorktree", "Create and enter a git worktree", || {
599 ToolDefinition {
600 name: "EnterWorktree".to_string(),
601 description: "Create and enter a git worktree for isolated work".to_string(),
602 input_schema: enter_worktree_schema(),
603 annotations: None,
604 should_defer: None,
605 always_load: None,
606 is_mcp: None,
607 search_hint: None,
608 aliases: None,
609 user_facing_name: None,
610 interrupt_behavior: None,
611 }
612 }),
613 (
614 "ExitWorktree",
615 "Exit a worktree and return to original directory",
616 || ToolDefinition {
617 name: "ExitWorktree".to_string(),
618 description: "Exit a worktree and return to the original working directory".to_string(),
619 input_schema: exit_worktree_schema(),
620 annotations: None,
621 should_defer: None,
622 always_load: None,
623 is_mcp: None,
624 search_hint: None,
625 aliases: None,
626 user_facing_name: None,
627 interrupt_behavior: None,
628 },
629 ),
630 ("EnterPlanMode", "Enter structured planning mode", || {
631 ToolDefinition {
632 name: "EnterPlanMode".to_string(),
633 description: "Enter structured planning mode to explore and design implementation"
634 .to_string(),
635 input_schema: enter_plan_mode_schema(),
636 annotations: None,
637 should_defer: None,
638 always_load: None,
639 is_mcp: None,
640 search_hint: None,
641 aliases: None,
642 user_facing_name: None,
643 interrupt_behavior: None,
644 }
645 }),
646 ("ExitPlanMode", "Exit planning mode", || ToolDefinition {
647 name: "ExitPlanMode".to_string(),
648 description: "Exit planning mode and present the plan for approval".to_string(),
649 input_schema: exit_plan_mode_schema(),
650 annotations: None,
651 should_defer: None,
652 always_load: None,
653 is_mcp: None,
654 search_hint: None,
655 aliases: None,
656 user_facing_name: None,
657 interrupt_behavior: None,
658 }),
659 (
660 "AskUserQuestion",
661 "Ask the user a question with multiple choice options",
662 || ToolDefinition {
663 name: "AskUserQuestion".to_string(),
664 description: "Ask the user a question with multiple choice options".to_string(),
665 input_schema: ask_user_question_schema(),
666 annotations: Some(ToolAnnotations::concurrency_safe()),
667 should_defer: None,
668 always_load: None,
669 is_mcp: None,
670 search_hint: None,
671 aliases: None,
672 user_facing_name: None,
673 interrupt_behavior: None,
674 },
675 ),
676 ("ToolSearch", "Search for available tools", || {
677 ToolDefinition {
678 name: "ToolSearch".to_string(),
679 description: "Search for available tools by name or description".to_string(),
680 input_schema: tool_search_schema(),
681 annotations: Some(ToolAnnotations::concurrency_safe()),
682 should_defer: None,
683 always_load: None,
684 is_mcp: None,
685 search_hint: None,
686 aliases: None,
687 user_facing_name: None,
688 interrupt_behavior: None,
689 }
690 }),
691 ("CronCreate", "Create a scheduled task", || ToolDefinition {
692 name: "CronCreate".to_string(),
693 description: "Create a scheduled task that runs on a cron schedule".to_string(),
694 input_schema: cron_create_schema(),
695 annotations: None,
696 should_defer: None,
697 always_load: None,
698 is_mcp: None,
699 search_hint: None,
700 aliases: None,
701 user_facing_name: None,
702 interrupt_behavior: None,
703 }),
704 ("CronDelete", "Delete a scheduled task", || ToolDefinition {
705 name: "CronDelete".to_string(),
706 description: "Delete a previously created scheduled task".to_string(),
707 input_schema: cron_delete_schema(),
708 annotations: Some(ToolAnnotations::concurrency_safe()),
709 should_defer: None,
710 always_load: None,
711 is_mcp: None,
712 search_hint: None,
713 aliases: None,
714 user_facing_name: None,
715 interrupt_behavior: None,
716 }),
717 ("CronList", "List all scheduled tasks", || ToolDefinition {
718 name: "CronList".to_string(),
719 description: "List all scheduled tasks".to_string(),
720 input_schema: cron_list_schema(),
721 annotations: Some(ToolAnnotations::concurrency_safe()),
722 should_defer: None,
723 always_load: None,
724 is_mcp: None,
725 search_hint: None,
726 aliases: None,
727 user_facing_name: None,
728 interrupt_behavior: None,
729 }),
730 ("Config", "Read or update configuration", || {
731 ToolDefinition {
732 name: "Config".to_string(),
733 description: "Read or update dynamic configuration".to_string(),
734 input_schema: config_schema(),
735 annotations: Some(ToolAnnotations::concurrency_safe()),
736 should_defer: None,
737 always_load: None,
738 is_mcp: None,
739 search_hint: None,
740 aliases: None,
741 user_facing_name: None,
742 interrupt_behavior: None,
743 }
744 }),
745 ("TodoWrite", "Manage the session task checklist", || {
746 ToolDefinition {
747 name: "TodoWrite".to_string(),
748 description:
749 "Update the todo list for this session. Provide the complete updated list of todos."
750 .to_string(),
751 input_schema: todo_write_schema(),
752 annotations: Some(ToolAnnotations::concurrency_safe()),
753 should_defer: Some(true),
754 always_load: None,
755 is_mcp: None,
756 search_hint: Some("manage the session task checklist".to_string()),
757 aliases: None,
758 user_facing_name: None,
759 interrupt_behavior: None,
760 }
761 }),
762 ("Skill", "Invoke a skill by name", || {
763 ToolDefinition {
764 name: "Skill".to_string(),
765 description: "Invoke a skill by name. Skills are pre-built workflows or commands that can be executed to accomplish specific tasks.".to_string(),
766 input_schema: skill_schema(),
767 annotations: Some(ToolAnnotations::concurrency_safe()),
768 should_defer: None,
769 always_load: None,
770 is_mcp: None,
771 search_hint: Some("invoke skills and workflows".to_string()),
772 aliases: None,
773 user_facing_name: None,
774 interrupt_behavior: None,
775 }
776 }),
777 ("TaskStop", "Stop a running background task", || {
778 ToolDefinition {
779 name: "TaskStop".to_string(),
780 description: "Stop a running background task by ID. Also accepts shell_id for backward compatibility with the deprecated KillShell tool.".to_string(),
781 input_schema: task_stop_schema(),
782 annotations: Some(ToolAnnotations::concurrency_safe()),
783 should_defer: Some(true),
784 always_load: None,
785 is_mcp: None,
786 search_hint: Some("kill a running background task".to_string()),
787 aliases: None,
788 user_facing_name: None,
789 interrupt_behavior: None,
790 }
791 }),
792 ("TaskOutput", "Retrieve output from background tasks", || {
793 ToolDefinition {
794 name: "TaskOutput".to_string(),
795 description: "Retrieve output from a running or completed background task (bash command, agent, etc.). Supports blocking wait for completion with configurable timeout.".to_string(),
796 input_schema: task_output_schema(),
797 annotations: Some(ToolAnnotations::concurrency_safe()),
798 should_defer: None,
799 always_load: None,
800 is_mcp: None,
801 search_hint: Some("get task output and results".to_string()),
802 aliases: None,
803 user_facing_name: None,
804 interrupt_behavior: None,
805 }
806 }),
807 ("Monitor", "Monitor system resources", || ToolDefinition {
808 name: "Monitor".to_string(),
809 description: "Monitor system resources and performance".to_string(),
810 input_schema: monitor_schema(),
811 annotations: None,
812 should_defer: None,
813 always_load: None,
814 is_mcp: None,
815 search_hint: None,
816 aliases: None,
817 user_facing_name: None,
818 interrupt_behavior: None,
819 }),
820 ("send_user_file", "Send a file from user to agent", || {
821 ToolDefinition {
822 name: "send_user_file".to_string(),
823 description: "Send a file from the user to the agent".to_string(),
824 input_schema: send_user_file_schema(),
825 annotations: None,
826 should_defer: None,
827 always_load: None,
828 is_mcp: None,
829 search_hint: None,
830 aliases: None,
831 user_facing_name: None,
832 interrupt_behavior: None,
833 }
834 }),
835 ("WebBrowser", "Control a web browser", || ToolDefinition {
836 name: "WebBrowser".to_string(),
837 description: "Control a web browser for automation".to_string(),
838 input_schema: web_browser_schema(),
839 annotations: None,
840 should_defer: None,
841 always_load: None,
842 is_mcp: None,
843 search_hint: None,
844 aliases: None,
845 user_facing_name: None,
846 interrupt_behavior: None,
847 }),
848 (
849 "LSP",
850 "Code intelligence via Language Server Protocol",
851 || {
852 ToolDefinition {
853 name: "LSP".to_string(),
854 description: "Interact with Language Server Protocol servers for code intelligence (definitions, references, symbols, hover, call hierarchy)".to_string(),
855 input_schema: lsp_schema(),
856 annotations: Some(ToolAnnotations::concurrency_safe()),
857 should_defer: None,
858 always_load: None,
859 is_mcp: None,
860 search_hint: None,
861 aliases: None,
862 user_facing_name: None,
863 interrupt_behavior: None,
864 }
865 },
866 ),
867 (
868 "RemoteTrigger",
869 "Manage remote Claude Code agents via CCR API",
870 || ToolDefinition {
871 name: "RemoteTrigger".to_string(),
872 description:
873 "Manage scheduled remote Claude Code agents (triggers) via the claude.ai CCR API"
874 .to_string(),
875 input_schema: remote_trigger_schema(),
876 annotations: None,
877 should_defer: None,
878 always_load: None,
879 is_mcp: None,
880 search_hint: None,
881 aliases: None,
882 user_facing_name: None,
883 interrupt_behavior: None,
884 },
885 ),
886 ("ListMcpResourcesTool", "List MCP server resources", || {
887 ToolDefinition {
888 name: "ListMcpResourcesTool".to_string(),
889 description: "List available resources from configured MCP servers".to_string(),
890 input_schema: list_mcp_resources_schema(),
891 annotations: Some(ToolAnnotations::concurrency_safe()),
892 should_defer: None,
893 always_load: None,
894 is_mcp: None,
895 search_hint: None,
896 aliases: None,
897 user_facing_name: None,
898 interrupt_behavior: None,
899 }
900 }),
901 (
902 "ReadMcpResourceTool",
903 "Read MCP server resources by URI",
904 || ToolDefinition {
905 name: "ReadMcpResourceTool".to_string(),
906 description: "Read a specific resource from an MCP server by URI".to_string(),
907 input_schema: read_mcp_resource_schema(),
908 annotations: Some(ToolAnnotations::concurrency_safe()),
909 should_defer: None,
910 always_load: None,
911 is_mcp: None,
912 search_hint: None,
913 aliases: None,
914 user_facing_name: None,
915 interrupt_behavior: None,
916 },
917 ),
918 (
919 "MCPTool",
920 "Execute a tool on an MCP server",
921 || ToolDefinition {
922 name: "MCPTool".to_string(),
923 description: "Execute a tool on an MCP server. MCP tools define their own schemas and are registered dynamically.".to_string(),
924 input_schema: mcp_tool_schema(),
925 annotations: None,
926 should_defer: None,
927 always_load: None,
928 is_mcp: Some(true),
929 search_hint: Some("execute MCP server tools".to_string()),
930 aliases: None,
931 user_facing_name: None,
932 interrupt_behavior: None,
933 },
934 ),
935 (
936 "McpAuth",
937 "Authenticate an MCP server",
938 || ToolDefinition {
939 name: "McpAuth".to_string(),
940 description: "Authenticate an MCP server that requires OAuth. Returns an authorization URL for the user to complete the flow.".to_string(),
941 input_schema: mcp_auth_schema(),
942 annotations: None,
943 should_defer: None,
944 always_load: None,
945 is_mcp: Some(true),
946 search_hint: Some("authenticate MCP server OAuth".to_string()),
947 aliases: None,
948 user_facing_name: None,
949 interrupt_behavior: None,
950 },
951 ),
952 (
953 "SendUserMessage",
954 "Send a message to the user",
955 || ToolDefinition {
956 name: "SendUserMessage".to_string(),
957 description: "Send a message to the user that they will actually read. Text outside this tool is visible in the detail view, but most won't open it -- the answer lives here.".to_string(),
958 input_schema: brief_schema(),
959 annotations: Some(ToolAnnotations::concurrency_safe()),
960 should_defer: None,
961 always_load: None,
962 is_mcp: None,
963 search_hint: Some("send message to user".to_string()),
964 aliases: None,
965 user_facing_name: None,
966 interrupt_behavior: None,
967 },
968 ),
969 (
970 "StructuredOutput",
971 "Return structured output in the requested format",
972 || ToolDefinition {
973 name: "StructuredOutput".to_string(),
974 description: "Return structured output in the requested format. You MUST call this tool exactly once at the end of your response to provide the structured output.".to_string(),
975 input_schema: synthetic_output_schema(),
976 annotations: Some(ToolAnnotations::concurrency_safe()),
977 should_defer: None,
978 always_load: None,
979 is_mcp: None,
980 search_hint: Some("return the final response as structured JSON".to_string()),
981 aliases: None,
982 user_facing_name: None,
983 interrupt_behavior: None,
984 },
985 ),
986 (
987 "Sleep",
988 "Wait for a specified duration",
989 || ToolDefinition {
990 name: "Sleep".to_string(),
991 description: "Wait for a specified duration. The user can interrupt the sleep at any time. Prefer this over Bash(sleep ...) — it doesn't hold a shell process.".to_string(),
992 input_schema: sleep_schema(),
993 annotations: Some(ToolAnnotations::concurrency_safe()),
994 should_defer: None,
995 always_load: None,
996 is_mcp: None,
997 search_hint: None,
998 aliases: None,
999 user_facing_name: None,
1000 interrupt_behavior: None,
1001 },
1002 ),
1003 (
1004 "PowerShell",
1005 "Execute PowerShell commands",
1006 || ToolDefinition {
1007 name: "PowerShell".to_string(),
1008 description: "Execute a PowerShell command. Windows-only tool for PowerShell cmdlets and native executable execution".to_string(),
1009 input_schema: powershell_schema(),
1010 annotations: None,
1011 should_defer: None,
1012 always_load: None,
1013 is_mcp: None,
1014 search_hint: None,
1015 aliases: None,
1016 user_facing_name: None,
1017 interrupt_behavior: None,
1018 },
1019 ),
1020 (
1021 "OverflowTest",
1022 "Test overflow behavior",
1023 || ToolDefinition {
1024 name: "OverflowTest".to_string(),
1025 description: "Test overflow behavior (not implemented)".to_string(),
1026 input_schema: overflow_test_schema(),
1027 annotations: None,
1028 should_defer: None,
1029 always_load: None,
1030 is_mcp: None,
1031 search_hint: None,
1032 aliases: None,
1033 user_facing_name: None,
1034 interrupt_behavior: None,
1035 },
1036 ),
1037 (
1038 "ReviewArtifact",
1039 "Review artifacts",
1040 || ToolDefinition {
1041 name: "ReviewArtifact".to_string(),
1042 description: "Review artifacts (not implemented)".to_string(),
1043 input_schema: review_artifact_schema(),
1044 annotations: None,
1045 should_defer: None,
1046 always_load: None,
1047 is_mcp: None,
1048 search_hint: None,
1049 aliases: None,
1050 user_facing_name: None,
1051 interrupt_behavior: None,
1052 },
1053 ),
1054 (
1055 "Workflow",
1056 "Manage workflows",
1057 || ToolDefinition {
1058 name: "Workflow".to_string(),
1059 description: "Manage workflows (not implemented)".to_string(),
1060 input_schema: workflow_schema(),
1061 annotations: None,
1062 should_defer: None,
1063 always_load: None,
1064 is_mcp: None,
1065 search_hint: None,
1066 aliases: None,
1067 user_facing_name: None,
1068 interrupt_behavior: None,
1069 },
1070 ),
1071 (
1072 "Snip",
1073 "Compaction tool",
1074 || ToolDefinition {
1075 name: "Snip".to_string(),
1076 description: "Model-callable compaction tool (not implemented)".to_string(),
1077 input_schema: snip_schema(),
1078 annotations: None,
1079 should_defer: None,
1080 always_load: None,
1081 is_mcp: None,
1082 search_hint: None,
1083 aliases: None,
1084 user_facing_name: None,
1085 interrupt_behavior: None,
1086 },
1087 ),
1088 (
1089 "DiscoverSkills",
1090 "Discover available skills",
1091 || ToolDefinition {
1092 name: "DiscoverSkills".to_string(),
1093 description: "On-demand skill discovery (not implemented)".to_string(),
1094 input_schema: discover_skills_schema(),
1095 annotations: None,
1096 should_defer: None,
1097 always_load: None,
1098 is_mcp: None,
1099 search_hint: None,
1100 aliases: None,
1101 user_facing_name: None,
1102 interrupt_behavior: None,
1103 },
1104 ),
1105 (
1106 "TerminalCapture",
1107 "Capture terminal screen",
1108 || ToolDefinition {
1109 name: "TerminalCapture".to_string(),
1110 description: "Terminal screen capture (not implemented)".to_string(),
1111 input_schema: terminal_capture_schema(),
1112 annotations: None,
1113 should_defer: None,
1114 always_load: None,
1115 is_mcp: None,
1116 search_hint: None,
1117 aliases: None,
1118 user_facing_name: None,
1119 interrupt_behavior: None,
1120 },
1121 ),
1122];
1123
1124fn agent_schema() -> ToolInputSchema {
1125 ToolInputSchema {
1126 schema_type: "object".to_string(),
1127 properties: serde_json::json!({
1128 "description": {
1129 "type": "string",
1130 "description": "A short description (3-5 words) summarizing what the agent will do"
1131 },
1132 "subagent_type": {
1133 "type": "string",
1134 "description": "The type of subagent to use. If omitted, uses the general-purpose agent."
1135 },
1136 "prompt": {
1137 "type": "string",
1138 "description": "The task prompt for the subagent to execute"
1139 },
1140 "model": {
1141 "type": "string",
1142 "description": "Optional model override for this subagent"
1143 },
1144 "max_turns": {
1145 "type": "number",
1146 "description": "Maximum number of turns for this subagent (default: 10)"
1147 },
1148 "run_in_background": {
1149 "type": "boolean",
1150 "description": "Whether to run the agent in the background (default: false)"
1151 },
1152 "isolation": {
1153 "type": "string",
1154 "enum": ["worktree", "remote"],
1155 "description": "Isolation mode: 'worktree' for git worktree, 'remote' for remote CCR"
1156 }
1157 }),
1158 required: Some(vec!["description".to_string(), "prompt".to_string()]),
1159 }
1160}
1161
1162fn task_create_schema() -> ToolInputSchema {
1164 ToolInputSchema {
1165 schema_type: "object".to_string(),
1166 properties: serde_json::json!({
1167 "subject": { "type": "string", "description": "A brief title for the task" },
1168 "description": { "type": "string", "description": "What needs to be done" },
1169 "activeForm": { "type": "string", "description": "Present continuous form shown in spinner when in_progress (e.g., \"Running tests\")" },
1170 "metadata": {
1171 "type": "object",
1172 "description": "Arbitrary metadata to attach to the task"
1173 }
1174 }),
1175 required: Some(vec!["subject".to_string(), "description".to_string()]),
1176 }
1177}
1178
1179fn task_list_schema() -> ToolInputSchema {
1180 ToolInputSchema {
1181 schema_type: "object".to_string(),
1182 properties: serde_json::json!({}),
1183 required: None,
1184 }
1185}
1186
1187fn task_update_schema() -> ToolInputSchema {
1188 ToolInputSchema {
1189 schema_type: "object".to_string(),
1190 properties: serde_json::json!({
1191 "taskId": { "type": "string", "description": "The ID of the task to update" },
1192 "subject": { "type": "string", "description": "New subject for the task" },
1193 "description": { "type": "string", "description": "New description for the task" },
1194 "status": { "type": "string", "enum": ["pending", "in_progress", "completed", "deleted"], "description": "New status for the task" },
1195 "activeForm": { "type": "string", "description": "Present continuous form shown in spinner when in_progress (e.g., \"Running tests\")" },
1196 "addBlocks": { "type": "array", "items": {"type": "string"}, "description": "Task IDs that this task blocks" },
1197 "addBlockedBy": { "type": "array", "items": {"type": "string"}, "description": "Task IDs that block this task" },
1198 "owner": { "type": "string", "description": "New owner for the task" },
1199 "metadata": {
1200 "type": "object",
1201 "description": "Metadata keys to merge into the task. Set a key to null to delete it."
1202 }
1203 }),
1204 required: Some(vec!["taskId".to_string()]),
1205 }
1206}
1207
1208fn task_get_schema() -> ToolInputSchema {
1209 ToolInputSchema {
1210 schema_type: "object".to_string(),
1211 properties: serde_json::json!({
1212 "taskId": { "type": "string", "description": "The ID of the task to retrieve" }
1213 }),
1214 required: Some(vec!["taskId".to_string()]),
1215 }
1216}
1217
1218fn team_create_schema() -> ToolInputSchema {
1220 ToolInputSchema {
1221 schema_type: "object".to_string(),
1222 properties: serde_json::json!({
1223 "name": { "type": "string", "description": "Name of the team" },
1224 "description": { "type": "string", "description": "Description of what the team does" },
1225 "agents": { "type": "array", "items": serde_json::json!({}), "description": "List of agents in the team" }
1226 }),
1227 required: Some(vec!["name".to_string()]),
1228 }
1229}
1230
1231fn team_delete_schema() -> ToolInputSchema {
1232 ToolInputSchema {
1233 schema_type: "object".to_string(),
1234 properties: serde_json::json!({
1235 "name": { "type": "string", "description": "Name of the team to delete" }
1236 }),
1237 required: Some(vec!["name".to_string()]),
1238 }
1239}
1240
1241fn send_message_schema() -> ToolInputSchema {
1242 ToolInputSchema {
1243 schema_type: "object".to_string(),
1244 properties: serde_json::json!({
1245 "to": { "type": "string", "description": "Agent name to send message to" },
1246 "message": { "type": "string", "description": "Message content" }
1247 }),
1248 required: Some(vec!["to".to_string(), "message".to_string()]),
1249 }
1250}
1251
1252fn enter_worktree_schema() -> ToolInputSchema {
1254 ToolInputSchema {
1255 schema_type: "object".to_string(),
1256 properties: serde_json::json!({
1257 "name": { "type": "string", "description": "Optional name for the worktree" }
1258 }),
1259 required: None,
1260 }
1261}
1262
1263fn exit_worktree_schema() -> ToolInputSchema {
1264 ToolInputSchema {
1265 schema_type: "object".to_string(),
1266 properties: serde_json::json!({
1267 "action": { "type": "string", "enum": ["keep", "remove"], "description": "What to do with the worktree" },
1268 "discardChanges": { "type": "boolean", "description": "Discard uncommitted changes before removing" }
1269 }),
1270 required: None,
1271 }
1272}
1273
1274fn enter_plan_mode_schema() -> ToolInputSchema {
1276 ToolInputSchema {
1277 schema_type: "object".to_string(),
1278 properties: serde_json::json!({
1279 "allowedPrompts": { "type": "array", "items": { "type": "string" }, "description": "Prompt-based permissions" }
1280 }),
1281 required: None,
1282 }
1283}
1284
1285fn exit_plan_mode_schema() -> ToolInputSchema {
1286 ToolInputSchema {
1287 schema_type: "object".to_string(),
1288 properties: serde_json::json!({}),
1289 required: None,
1290 }
1291}
1292
1293fn ask_user_question_schema() -> ToolInputSchema {
1295 ToolInputSchema {
1296 schema_type: "object".to_string(),
1297 properties: serde_json::json!({
1298 "question": { "type": "string", "description": "The complete question to ask the user" },
1299 "header": { "type": "string", "description": "Very short label displayed as a chip/tag (max 12 chars)" },
1300 "options": { "type": "array", "items": serde_json::json!({}), "description": "Available choices for this question. Must have 2-4 options." },
1301 "multiSelect": { "type": "boolean", "description": "Set to true to allow the user to select multiple options instead of just one" },
1302 "preview": { "type": "object", "properties": { "type": { "type": "string", "enum": ["html", "markdown"] }, "content": { "type": "string" } }, "description": "Optional HTML or Markdown preview to show the user alongside the question" }
1303 }),
1304 required: Some(vec![
1305 "question".to_string(),
1306 "header".to_string(),
1307 "options".to_string(),
1308 ]),
1309 }
1310}
1311
1312fn tool_search_schema() -> ToolInputSchema {
1314 ToolInputSchema {
1315 schema_type: "object".to_string(),
1316 properties: serde_json::json!({
1317 "query": { "type": "string", "description": "Query to find deferred tools. Use \"select:<tool_name>\" for direct selection, or keywords to search." },
1318 "max_results": { "type": "number", "description": "Maximum number of results to return (default: 5)" }
1319 }),
1320 required: Some(vec!["query".to_string()]),
1321 }
1322}
1323
1324fn task_stop_schema() -> ToolInputSchema {
1326 ToolInputSchema {
1327 schema_type: "object".to_string(),
1328 properties: serde_json::json!({
1329 "task_id": { "type": "string", "description": "The ID of the background task to stop" },
1330 "shell_id": { "type": "string", "description": "Deprecated: use task_id instead" }
1331 }),
1332 required: None,
1333 }
1334}
1335
1336fn task_output_schema() -> ToolInputSchema {
1338 ToolInputSchema {
1339 schema_type: "object".to_string(),
1340 properties: serde_json::json!({
1341 "task_id": { "type": "string", "description": "The task ID to get output from" },
1342 "block": { "type": "boolean", "description": "Whether to wait for completion. Default: true" },
1343 "timeout": { "type": "number", "description": "Max wait time in ms. Default: 30000, max: 600000" }
1344 }),
1345 required: Some(vec!["task_id".to_string()]),
1346 }
1347}
1348
1349fn cron_create_schema() -> ToolInputSchema {
1351 ToolInputSchema {
1352 schema_type: "object".to_string(),
1353 properties: serde_json::json!({
1354 "cron": { "type": "string", "description": "5-field cron expression" },
1355 "prompt": { "type": "string", "description": "The prompt to execute" },
1356 "recurring": { "type": "boolean", "description": "true = repeat, false = one-shot" },
1357 "durable": { "type": "boolean", "description": "true = persist across restarts" }
1358 }),
1359 required: Some(vec!["cron".to_string(), "prompt".to_string()]),
1360 }
1361}
1362
1363fn cron_delete_schema() -> ToolInputSchema {
1364 ToolInputSchema {
1365 schema_type: "object".to_string(),
1366 properties: serde_json::json!({
1367 "id": { "type": "string", "description": "Job ID returned by CronCreate" }
1368 }),
1369 required: Some(vec!["id".to_string()]),
1370 }
1371}
1372
1373fn cron_list_schema() -> ToolInputSchema {
1374 ToolInputSchema {
1375 schema_type: "object".to_string(),
1376 properties: serde_json::json!({}),
1377 required: None,
1378 }
1379}
1380
1381fn config_schema() -> ToolInputSchema {
1383 ToolInputSchema {
1384 schema_type: "object".to_string(),
1385 properties: serde_json::json!({
1386 "action": { "type": "string", "enum": ["get", "set", "list"], "description": "Action to perform" },
1387 "key": { "type": "string", "description": "Configuration key" },
1388 "value": { "type": "string", "description": "Configuration value" }
1389 }),
1390 required: Some(vec!["action".to_string()]),
1391 }
1392}
1393
1394fn todo_write_schema() -> ToolInputSchema {
1396 ToolInputSchema {
1397 schema_type: "object".to_string(),
1398 properties: serde_json::json!({
1399 "todos": { "type": "array", "items": serde_json::json!({}), "description": "List of todo items" }
1400 }),
1401 required: Some(vec!["todos".to_string()]),
1402 }
1403}
1404
1405fn skill_schema() -> ToolInputSchema {
1407 ToolInputSchema {
1408 schema_type: "object".to_string(),
1409 properties: serde_json::json!({
1410 "skill": { "type": "string", "description": "The name of the skill to invoke" }
1411 }),
1412 required: Some(vec!["skill".to_string()]),
1413 }
1414}
1415
1416fn lsp_schema() -> ToolInputSchema {
1418 ToolInputSchema {
1419 schema_type: "object".to_string(),
1420 properties: serde_json::json!({
1421 "operation": {
1422 "type": "string",
1423 "enum": ["goToDefinition", "findReferences", "hover", "documentSymbol", "workspaceSymbol", "goToImplementation", "prepareCallHierarchy", "incomingCalls", "outgoingCalls"],
1424 "description": "The LSP operation to perform"
1425 },
1426 "filePath": { "type": "string", "description": "The file to operate on" },
1427 "line": { "type": "integer", "description": "Line number (1-based)" },
1428 "character": { "type": "integer", "description": "Character offset (1-based)" }
1429 }),
1430 required: Some(vec!["operation".to_string(), "filePath".to_string()]),
1431 }
1432}
1433
1434fn remote_trigger_schema() -> ToolInputSchema {
1436 ToolInputSchema {
1437 schema_type: "object".to_string(),
1438 properties: serde_json::json!({
1439 "action": { "type": "string", "enum": ["list", "get", "create", "update", "run"], "description": "The action to perform" },
1440 "trigger_id": { "type": "string", "description": "Required for get, update, and run" },
1441 "body": { "type": "object", "description": "JSON body for create and update" }
1442 }),
1443 required: Some(vec!["action".to_string()]),
1444 }
1445}
1446
1447fn list_mcp_resources_schema() -> ToolInputSchema {
1449 ToolInputSchema {
1450 schema_type: "object".to_string(),
1451 properties: serde_json::json!({
1452 "server": { "type": "string", "description": "Optional server name to filter resources by" }
1453 }),
1454 required: None,
1455 }
1456}
1457
1458fn read_mcp_resource_schema() -> ToolInputSchema {
1460 ToolInputSchema {
1461 schema_type: "object".to_string(),
1462 properties: serde_json::json!({
1463 "server": { "type": "string", "description": "The MCP server name" },
1464 "uri": { "type": "string", "description": "The resource URI to read" }
1465 }),
1466 required: Some(vec!["server".to_string(), "uri".to_string()]),
1467 }
1468}
1469
1470fn mcp_tool_schema() -> ToolInputSchema {
1472 ToolInputSchema {
1473 schema_type: "object".to_string(),
1474 properties: serde_json::json!({
1475 "server": {
1476 "type": "string",
1477 "description": "The MCP server name"
1478 },
1479 "tool": {
1480 "type": "string",
1481 "description": "The tool name to execute on the server"
1482 },
1483 "arguments": {
1484 "type": "object",
1485 "description": "Arguments to pass to the MCP tool"
1486 }
1487 }),
1488 required: Some(vec!["server".to_string(), "tool".to_string()]),
1489 }
1490}
1491
1492fn mcp_auth_schema() -> ToolInputSchema {
1494 ToolInputSchema {
1495 schema_type: "object".to_string(),
1496 properties: serde_json::json!({
1497 "server": {
1498 "type": "string",
1499 "description": "The MCP server name to authenticate"
1500 }
1501 }),
1502 required: Some(vec!["server".to_string()]),
1503 }
1504}
1505
1506fn overflow_test_schema() -> ToolInputSchema {
1508 ToolInputSchema {
1509 schema_type: "object".to_string(),
1510 properties: serde_json::json!({}),
1511 required: None,
1512 }
1513}
1514
1515fn review_artifact_schema() -> ToolInputSchema {
1517 ToolInputSchema {
1518 schema_type: "object".to_string(),
1519 properties: serde_json::json!({}),
1520 required: None,
1521 }
1522}
1523
1524fn workflow_schema() -> ToolInputSchema {
1526 ToolInputSchema {
1527 schema_type: "object".to_string(),
1528 properties: serde_json::json!({}),
1529 required: None,
1530 }
1531}
1532
1533fn snip_schema() -> ToolInputSchema {
1535 ToolInputSchema {
1536 schema_type: "object".to_string(),
1537 properties: serde_json::json!({}),
1538 required: None,
1539 }
1540}
1541
1542fn discover_skills_schema() -> ToolInputSchema {
1544 ToolInputSchema {
1545 schema_type: "object".to_string(),
1546 properties: serde_json::json!({}),
1547 required: None,
1548 }
1549}
1550
1551fn terminal_capture_schema() -> ToolInputSchema {
1553 ToolInputSchema {
1554 schema_type: "object".to_string(),
1555 properties: serde_json::json!({}),
1556 required: None,
1557 }
1558}
1559
1560pub fn get_all_base_tools() -> Vec<ToolDefinition> {
1561 ALL_TOOLS.iter().map(|f| f.2()).collect()
1562}
1563
1564pub fn filter_tools(
1565 tools: Vec<ToolDefinition>,
1566 allowed: Option<Vec<String>>,
1567 disallowed: Option<Vec<String>>,
1568) -> Vec<ToolDefinition> {
1569 let mut result = tools;
1570 if let Some(allowed) = allowed {
1571 let allowed_set: std::collections::HashSet<_> = allowed.into_iter().collect();
1572 result.retain(|t| allowed_set.contains(&t.name));
1573 }
1574 if let Some(disallowed) = disallowed {
1575 let disallowed_set: std::collections::HashSet<_> = disallowed.into_iter().collect();
1576 result.retain(|t| !disallowed_set.contains(&t.name));
1577 }
1578 result
1579}
1580
1581#[derive(Debug, Clone)]
1587pub struct ToolWithMetadata {
1588 pub name: String,
1589 pub aliases: Option<Vec<String>>,
1590}
1591
1592pub fn tool_matches_name(tool: &ToolWithMetadata, name: &str) -> bool {
1594 tool.name == name
1595 || tool
1596 .aliases
1597 .as_ref()
1598 .map_or(false, |a| a.contains(&name.to_string()))
1599}
1600
1601pub fn find_tool_by_name<'a>(
1603 tools: &'a [ToolDefinition],
1604 name: &str,
1605) -> Option<&'a ToolDefinition> {
1606 tools.iter().find(|t| t.name == name)
1607}
1608
1609pub struct PartialToolDefinition {
1611 pub name: String,
1612 pub description: Option<String>,
1613 pub input_schema: Option<ToolInputSchema>,
1614 pub aliases: Option<Vec<String>>,
1615 pub search_hint: Option<String>,
1616 pub max_result_size_chars: Option<usize>,
1617 pub should_defer: Option<bool>,
1618 pub always_load: Option<bool>,
1619 pub is_enabled: Option<Box<dyn Fn() -> bool + Send + Sync>>,
1620 pub is_concurrency_safe: Option<Box<dyn Fn(&serde_json::Value) -> bool + Send + Sync>>,
1621 pub is_read_only: Option<Box<dyn Fn(&serde_json::Value) -> bool + Send + Sync>>,
1622 pub is_destructive: Option<Box<dyn Fn(&serde_json::Value) -> bool + Send + Sync>>,
1623 pub interrupt_behavior: Option<Box<dyn Fn() -> InterruptBehavior + Send + Sync>>,
1624 pub is_search_or_read_command:
1625 Option<Box<dyn Fn(&serde_json::Value) -> SearchOrReadCommand + Send + Sync>>,
1626 pub is_open_world: Option<Box<dyn Fn(&serde_json::Value) -> bool + Send + Sync>>,
1627 pub requires_user_interaction: Option<Box<dyn Fn() -> bool + Send + Sync>>,
1628 pub is_mcp: Option<bool>,
1629 pub is_lsp: Option<bool>,
1630 pub user_facing_name: Option<Box<dyn Fn(Option<&serde_json::Value>) -> String + Send + Sync>>,
1631}
1632
1633impl Default for PartialToolDefinition {
1634 fn default() -> Self {
1635 Self {
1636 name: String::new(),
1637 description: None,
1638 input_schema: None,
1639 aliases: None,
1640 user_facing_name: None,
1641 search_hint: None,
1642 max_result_size_chars: None,
1643 should_defer: None,
1644 always_load: None,
1645 is_enabled: None,
1646 is_concurrency_safe: None,
1647 is_read_only: None,
1648 is_destructive: None,
1649 interrupt_behavior: None,
1650 is_search_or_read_command: None,
1651 is_open_world: None,
1652 requires_user_interaction: None,
1653 is_mcp: None,
1654 is_lsp: None,
1655 }
1656 }
1657}
1658
1659#[derive(Debug, Clone, Copy, PartialEq)]
1661pub enum InterruptBehavior {
1662 Cancel,
1664 Block,
1666}
1667
1668impl Default for InterruptBehavior {
1669 fn default() -> Self {
1670 InterruptBehavior::Block
1671 }
1672}
1673
1674#[derive(Debug, Clone, Default)]
1676pub struct SearchOrReadCommand {
1677 pub is_search: bool,
1678 pub is_read: bool,
1679 pub is_list: Option<bool>,
1680}
1681
1682pub fn build_tool(def: PartialToolDefinition) -> ToolDefinition {
1684 ToolDefinition {
1685 name: def.name.clone(),
1686 description: def.description.unwrap_or_default(),
1687 input_schema: def.input_schema.unwrap_or_default(),
1688 annotations: Some(ToolAnnotations {
1689 read_only: Some(
1690 def.is_read_only
1691 .map_or(false, |f| f(&serde_json::json!({}))),
1692 ),
1693 destructive: Some(
1694 def.is_destructive
1695 .as_ref()
1696 .map_or(false, |f| f(&serde_json::json!({}))),
1697 ),
1698 concurrency_safe: Some(
1699 def.is_concurrency_safe
1700 .as_ref()
1701 .map_or(false, |f| f(&serde_json::json!({}))),
1702 ),
1703 open_world: None,
1704 idempotent: None,
1705 }),
1706 should_defer: None,
1707 always_load: None,
1708 is_mcp: None,
1709 search_hint: def.search_hint,
1710 aliases: def.aliases,
1711 user_facing_name: def.user_facing_name.map(|f| f(None)),
1712 interrupt_behavior: None,
1713 }
1714}