1use punch_types::{Capability, ToolCategory, ToolDefinition};
7
8pub fn tools_for_capabilities(capabilities: &[Capability]) -> Vec<ToolDefinition> {
13 let mut tools = Vec::new();
14
15 for cap in capabilities {
16 match cap {
17 Capability::FileRead(_) => {
18 push_unique(&mut tools, file_read());
19 push_unique(&mut tools, file_list());
20 push_unique(&mut tools, file_search());
21 push_unique(&mut tools, file_info());
22 }
23 Capability::FileWrite(_) => {
24 push_unique(&mut tools, file_write());
25 push_unique(&mut tools, patch_apply());
26 }
27 Capability::ShellExec(_) => {
28 push_unique(&mut tools, shell_exec());
29 push_unique(&mut tools, process_list());
30 push_unique(&mut tools, process_kill());
31 push_unique(&mut tools, env_get());
32 push_unique(&mut tools, env_list());
33 }
34 Capability::Network(_) => {
35 push_unique(&mut tools, web_fetch());
36 push_unique(&mut tools, web_search());
37 push_unique(&mut tools, http_request());
38 push_unique(&mut tools, http_post());
39 }
40 Capability::Memory => {
41 push_unique(&mut tools, memory_store());
42 push_unique(&mut tools, memory_recall());
43 }
44 Capability::KnowledgeGraph => {
45 push_unique(&mut tools, knowledge_add_entity());
46 push_unique(&mut tools, knowledge_add_relation());
47 push_unique(&mut tools, knowledge_query());
48 }
49 Capability::AgentSpawn => {
50 push_unique(&mut tools, agent_spawn());
51 }
52 Capability::AgentMessage => {
53 push_unique(&mut tools, agent_message());
54 push_unique(&mut tools, agent_list());
55 }
56 Capability::BrowserControl => {
57 push_unique(&mut tools, browser_navigate());
58 push_unique(&mut tools, browser_screenshot());
59 push_unique(&mut tools, browser_click());
60 push_unique(&mut tools, browser_type());
61 push_unique(&mut tools, browser_content());
62 }
63 Capability::SourceControl => {
64 push_unique(&mut tools, git_status());
65 push_unique(&mut tools, git_diff());
66 push_unique(&mut tools, git_log());
67 push_unique(&mut tools, git_commit());
68 push_unique(&mut tools, git_branch());
69 }
70 Capability::Container => {
71 push_unique(&mut tools, docker_ps());
72 push_unique(&mut tools, docker_run());
73 push_unique(&mut tools, docker_build());
74 push_unique(&mut tools, docker_logs());
75 }
76 Capability::DataManipulation => {
77 push_unique(&mut tools, json_query());
78 push_unique(&mut tools, json_transform());
79 push_unique(&mut tools, yaml_parse());
80 push_unique(&mut tools, regex_match());
81 push_unique(&mut tools, regex_replace());
82 push_unique(&mut tools, text_diff());
83 push_unique(&mut tools, text_count());
84 }
85 Capability::Schedule => {
86 push_unique(&mut tools, schedule_task());
87 push_unique(&mut tools, schedule_list());
88 push_unique(&mut tools, schedule_cancel());
89 }
90 Capability::CodeAnalysis => {
91 push_unique(&mut tools, code_search());
92 push_unique(&mut tools, code_symbols());
93 }
94 Capability::Archive => {
95 push_unique(&mut tools, archive_create());
96 push_unique(&mut tools, archive_extract());
97 push_unique(&mut tools, archive_list());
98 }
99 Capability::Template => {
100 push_unique(&mut tools, template_render());
101 }
102 Capability::Crypto => {
103 push_unique(&mut tools, hash_compute());
104 push_unique(&mut tools, hash_verify());
105 }
106 Capability::A2ADelegate => {
107 push_unique(&mut tools, a2a_delegate());
108 }
109 Capability::PluginInvoke => {
110 push_unique(&mut tools, wasm_invoke());
111 }
112 _ => {}
113 }
114 }
115
116 tools
117}
118
119pub fn all_tools() -> Vec<ToolDefinition> {
121 vec![
122 file_read(),
123 file_write(),
124 file_list(),
125 patch_apply(),
126 shell_exec(),
127 web_fetch(),
128 web_search(),
129 memory_store(),
130 memory_recall(),
131 knowledge_add_entity(),
132 knowledge_add_relation(),
133 knowledge_query(),
134 agent_spawn(),
135 agent_message(),
136 agent_list(),
137 browser_navigate(),
138 browser_screenshot(),
139 browser_click(),
140 browser_type(),
141 browser_content(),
142 git_status(),
144 git_diff(),
145 git_log(),
146 git_commit(),
147 git_branch(),
148 docker_ps(),
150 docker_run(),
151 docker_build(),
152 docker_logs(),
153 http_request(),
155 http_post(),
156 json_query(),
158 json_transform(),
159 yaml_parse(),
160 regex_match(),
161 regex_replace(),
162 process_list(),
164 process_kill(),
165 schedule_task(),
167 schedule_list(),
168 schedule_cancel(),
169 code_search(),
171 code_symbols(),
172 archive_create(),
174 archive_extract(),
175 archive_list(),
176 template_render(),
178 hash_compute(),
180 hash_verify(),
181 env_get(),
183 env_list(),
184 text_diff(),
186 text_count(),
187 file_search(),
189 file_info(),
190 a2a_delegate(),
192 wasm_invoke(),
194 ]
195}
196
197fn push_unique(tools: &mut Vec<ToolDefinition>, tool: ToolDefinition) {
198 if !tools.iter().any(|t| t.name == tool.name) {
199 tools.push(tool);
200 }
201}
202
203fn file_read() -> ToolDefinition {
208 ToolDefinition {
209 name: "file_read".into(),
210 description: "Read the contents of a file at the given path.".into(),
211 input_schema: serde_json::json!({
212 "type": "object",
213 "properties": {
214 "path": {
215 "type": "string",
216 "description": "The file path to read (relative to working directory or absolute)."
217 }
218 },
219 "required": ["path"]
220 }),
221 category: ToolCategory::FileSystem,
222 }
223}
224
225fn file_write() -> ToolDefinition {
226 ToolDefinition {
227 name: "file_write".into(),
228 description:
229 "Write content to a file at the given path. Creates parent directories if needed."
230 .into(),
231 input_schema: serde_json::json!({
232 "type": "object",
233 "properties": {
234 "path": {
235 "type": "string",
236 "description": "The file path to write to."
237 },
238 "content": {
239 "type": "string",
240 "description": "The content to write to the file."
241 }
242 },
243 "required": ["path", "content"]
244 }),
245 category: ToolCategory::FileSystem,
246 }
247}
248
249fn file_list() -> ToolDefinition {
250 ToolDefinition {
251 name: "file_list".into(),
252 description: "List files and directories at the given path.".into(),
253 input_schema: serde_json::json!({
254 "type": "object",
255 "properties": {
256 "path": {
257 "type": "string",
258 "description": "The directory path to list (defaults to working directory)."
259 }
260 }
261 }),
262 category: ToolCategory::FileSystem,
263 }
264}
265
266fn shell_exec() -> ToolDefinition {
267 ToolDefinition {
268 name: "shell_exec".into(),
269 description: "Execute a shell command and return stdout, stderr, and exit code.".into(),
270 input_schema: serde_json::json!({
271 "type": "object",
272 "properties": {
273 "command": {
274 "type": "string",
275 "description": "The shell command to execute."
276 }
277 },
278 "required": ["command"]
279 }),
280 category: ToolCategory::Shell,
281 }
282}
283
284fn web_fetch() -> ToolDefinition {
285 ToolDefinition {
286 name: "web_fetch".into(),
287 description: "Fetch the content of a URL via HTTP GET.".into(),
288 input_schema: serde_json::json!({
289 "type": "object",
290 "properties": {
291 "url": {
292 "type": "string",
293 "description": "The URL to fetch."
294 }
295 },
296 "required": ["url"]
297 }),
298 category: ToolCategory::Web,
299 }
300}
301
302fn web_search() -> ToolDefinition {
303 ToolDefinition {
304 name: "web_search".into(),
305 description:
306 "Search the web using DuckDuckGo and return the top results with titles and URLs."
307 .into(),
308 input_schema: serde_json::json!({
309 "type": "object",
310 "properties": {
311 "query": {
312 "type": "string",
313 "description": "The search query."
314 }
315 },
316 "required": ["query"]
317 }),
318 category: ToolCategory::Web,
319 }
320}
321
322fn memory_store() -> ToolDefinition {
323 ToolDefinition {
324 name: "memory_store".into(),
325 description: "Store a key-value pair in your persistent memory. Use this to remember important facts, user preferences, or context across conversations.".into(),
326 input_schema: serde_json::json!({
327 "type": "object",
328 "properties": {
329 "key": {
330 "type": "string",
331 "description": "A short descriptive key for the memory."
332 },
333 "value": {
334 "type": "string",
335 "description": "The value to remember."
336 },
337 "confidence": {
338 "type": "number",
339 "description": "Confidence level from 0.0 to 1.0 (default: 0.9)."
340 }
341 },
342 "required": ["key", "value"]
343 }),
344 category: ToolCategory::Memory,
345 }
346}
347
348fn memory_recall() -> ToolDefinition {
349 ToolDefinition {
350 name: "memory_recall".into(),
351 description: "Search your persistent memory for previously stored information.".into(),
352 input_schema: serde_json::json!({
353 "type": "object",
354 "properties": {
355 "query": {
356 "type": "string",
357 "description": "Search query to find relevant memories."
358 },
359 "limit": {
360 "type": "integer",
361 "description": "Maximum number of results (default: 10)."
362 }
363 },
364 "required": ["query"]
365 }),
366 category: ToolCategory::Memory,
367 }
368}
369
370fn knowledge_add_entity() -> ToolDefinition {
371 ToolDefinition {
372 name: "knowledge_add_entity".into(),
373 description: "Add an entity to your knowledge graph.".into(),
374 input_schema: serde_json::json!({
375 "type": "object",
376 "properties": {
377 "name": {
378 "type": "string",
379 "description": "Name of the entity."
380 },
381 "entity_type": {
382 "type": "string",
383 "description": "Type of entity (e.g. 'person', 'company', 'concept')."
384 },
385 "properties": {
386 "type": "object",
387 "description": "Additional properties as key-value pairs."
388 }
389 },
390 "required": ["name", "entity_type"]
391 }),
392 category: ToolCategory::Knowledge,
393 }
394}
395
396fn knowledge_add_relation() -> ToolDefinition {
397 ToolDefinition {
398 name: "knowledge_add_relation".into(),
399 description: "Add a relation between two entities in your knowledge graph.".into(),
400 input_schema: serde_json::json!({
401 "type": "object",
402 "properties": {
403 "from": {
404 "type": "string",
405 "description": "Source entity name."
406 },
407 "relation": {
408 "type": "string",
409 "description": "The relation type (e.g. 'works_at', 'depends_on')."
410 },
411 "to": {
412 "type": "string",
413 "description": "Target entity name."
414 },
415 "properties": {
416 "type": "object",
417 "description": "Additional properties."
418 }
419 },
420 "required": ["from", "relation", "to"]
421 }),
422 category: ToolCategory::Knowledge,
423 }
424}
425
426fn knowledge_query() -> ToolDefinition {
427 ToolDefinition {
428 name: "knowledge_query".into(),
429 description: "Search your knowledge graph for entities and their relations.".into(),
430 input_schema: serde_json::json!({
431 "type": "object",
432 "properties": {
433 "query": {
434 "type": "string",
435 "description": "Search query to find entities."
436 }
437 },
438 "required": ["query"]
439 }),
440 category: ToolCategory::Knowledge,
441 }
442}
443
444fn agent_spawn() -> ToolDefinition {
449 ToolDefinition {
450 name: "agent_spawn".into(),
451 description: "Spawn a new fighter (AI agent). Returns the new fighter's ID. Use this to create subordinate agents that can handle specialized tasks.".into(),
452 input_schema: serde_json::json!({
453 "type": "object",
454 "properties": {
455 "name": {
456 "type": "string",
457 "description": "A human-readable name for the new fighter."
458 },
459 "system_prompt": {
460 "type": "string",
461 "description": "The system prompt that shapes the new fighter's behavior and specialization."
462 },
463 "description": {
464 "type": "string",
465 "description": "A short description of the fighter's purpose (optional)."
466 },
467 "capabilities": {
468 "type": "array",
469 "description": "Capabilities to grant the new fighter (optional). Each item is a capability object.",
470 "items": {
471 "type": "object"
472 }
473 }
474 },
475 "required": ["name", "system_prompt"]
476 }),
477 category: ToolCategory::Agent,
478 }
479}
480
481fn agent_message() -> ToolDefinition {
482 ToolDefinition {
483 name: "agent_message".into(),
484 description: "Send a message to another fighter by ID or name and get its response. Use this for inter-agent coordination and delegation.".into(),
485 input_schema: serde_json::json!({
486 "type": "object",
487 "properties": {
488 "fighter_id": {
489 "type": "string",
490 "description": "The UUID of the target fighter (provide either this or 'name')."
491 },
492 "name": {
493 "type": "string",
494 "description": "The name of the target fighter (provide either this or 'fighter_id')."
495 },
496 "message": {
497 "type": "string",
498 "description": "The message to send to the target fighter."
499 }
500 },
501 "required": ["message"]
502 }),
503 category: ToolCategory::Agent,
504 }
505}
506
507fn agent_list() -> ToolDefinition {
508 ToolDefinition {
509 name: "agent_list".into(),
510 description: "List all active fighters (AI agents) with their IDs, names, and status."
511 .into(),
512 input_schema: serde_json::json!({
513 "type": "object",
514 "properties": {}
515 }),
516 category: ToolCategory::Agent,
517 }
518}
519
520fn patch_apply() -> ToolDefinition {
525 ToolDefinition {
526 name: "patch_apply".into(),
527 description: "Apply a unified diff patch to a file. Reads the file, validates the patch, \
528 applies it, and writes the result back. Supports standard unified diff format."
529 .into(),
530 input_schema: serde_json::json!({
531 "type": "object",
532 "properties": {
533 "path": {
534 "type": "string",
535 "description": "The file path to patch (relative to working directory or absolute)."
536 },
537 "diff": {
538 "type": "string",
539 "description": "The unified diff text to apply to the file."
540 }
541 },
542 "required": ["path", "diff"]
543 }),
544 category: ToolCategory::FileSystem,
545 }
546}
547
548fn browser_navigate() -> ToolDefinition {
553 ToolDefinition {
554 name: "browser_navigate".into(),
555 description: "Navigate the browser to a URL. Opens the page and waits for it to load."
556 .into(),
557 input_schema: serde_json::json!({
558 "type": "object",
559 "properties": {
560 "url": {
561 "type": "string",
562 "description": "The URL to navigate to."
563 }
564 },
565 "required": ["url"]
566 }),
567 category: ToolCategory::Browser,
568 }
569}
570
571fn browser_screenshot() -> ToolDefinition {
572 ToolDefinition {
573 name: "browser_screenshot".into(),
574 description: "Take a screenshot of the current page. Returns a base64-encoded PNG image."
575 .into(),
576 input_schema: serde_json::json!({
577 "type": "object",
578 "properties": {
579 "full_page": {
580 "type": "boolean",
581 "description": "Capture the full scrollable page (true) or just the viewport (false). Default: false."
582 }
583 }
584 }),
585 category: ToolCategory::Browser,
586 }
587}
588
589fn browser_click() -> ToolDefinition {
590 ToolDefinition {
591 name: "browser_click".into(),
592 description: "Click an element on the page matching the given CSS selector.".into(),
593 input_schema: serde_json::json!({
594 "type": "object",
595 "properties": {
596 "selector": {
597 "type": "string",
598 "description": "CSS selector of the element to click."
599 }
600 },
601 "required": ["selector"]
602 }),
603 category: ToolCategory::Browser,
604 }
605}
606
607fn browser_type() -> ToolDefinition {
608 ToolDefinition {
609 name: "browser_type".into(),
610 description: "Type text into an input element matching the given CSS selector.".into(),
611 input_schema: serde_json::json!({
612 "type": "object",
613 "properties": {
614 "selector": {
615 "type": "string",
616 "description": "CSS selector of the input element."
617 },
618 "text": {
619 "type": "string",
620 "description": "The text to type into the element."
621 }
622 },
623 "required": ["selector", "text"]
624 }),
625 category: ToolCategory::Browser,
626 }
627}
628
629fn browser_content() -> ToolDefinition {
630 ToolDefinition {
631 name: "browser_content".into(),
632 description:
633 "Get the text content of the page or a specific element. Useful for extracting readable text from a web page."
634 .into(),
635 input_schema: serde_json::json!({
636 "type": "object",
637 "properties": {
638 "selector": {
639 "type": "string",
640 "description": "Optional CSS selector. If omitted, returns the full page text content."
641 }
642 }
643 }),
644 category: ToolCategory::Browser,
645 }
646}
647
648fn git_status() -> ToolDefinition {
653 ToolDefinition {
654 name: "git_status".into(),
655 description: "Run `git status --porcelain` in the working directory to show changed files."
656 .into(),
657 input_schema: serde_json::json!({
658 "type": "object",
659 "properties": {}
660 }),
661 category: ToolCategory::SourceControl,
662 }
663}
664
665fn git_diff() -> ToolDefinition {
666 ToolDefinition {
667 name: "git_diff".into(),
668 description:
669 "Run `git diff` to show unstaged changes. Use `staged: true` to see staged changes."
670 .into(),
671 input_schema: serde_json::json!({
672 "type": "object",
673 "properties": {
674 "staged": {
675 "type": "boolean",
676 "description": "If true, show staged changes (--staged). Default: false."
677 },
678 "path": {
679 "type": "string",
680 "description": "Optional file path to restrict the diff to."
681 }
682 }
683 }),
684 category: ToolCategory::SourceControl,
685 }
686}
687
688fn git_log() -> ToolDefinition {
689 ToolDefinition {
690 name: "git_log".into(),
691 description: "Show recent git commits with `git log --oneline`.".into(),
692 input_schema: serde_json::json!({
693 "type": "object",
694 "properties": {
695 "count": {
696 "type": "integer",
697 "description": "Number of commits to show (default: 10)."
698 }
699 }
700 }),
701 category: ToolCategory::SourceControl,
702 }
703}
704
705fn git_commit() -> ToolDefinition {
706 ToolDefinition {
707 name: "git_commit".into(),
708 description: "Stage files and create a git commit with the given message.".into(),
709 input_schema: serde_json::json!({
710 "type": "object",
711 "properties": {
712 "message": {
713 "type": "string",
714 "description": "The commit message."
715 },
716 "files": {
717 "type": "array",
718 "items": { "type": "string" },
719 "description": "Files to stage before committing. If empty, commits all staged changes."
720 }
721 },
722 "required": ["message"]
723 }),
724 category: ToolCategory::SourceControl,
725 }
726}
727
728fn git_branch() -> ToolDefinition {
729 ToolDefinition {
730 name: "git_branch".into(),
731 description: "List, create, or switch git branches.".into(),
732 input_schema: serde_json::json!({
733 "type": "object",
734 "properties": {
735 "action": {
736 "type": "string",
737 "enum": ["list", "create", "switch"],
738 "description": "Action to perform: list, create, or switch. Default: list."
739 },
740 "name": {
741 "type": "string",
742 "description": "Branch name (required for create and switch)."
743 }
744 }
745 }),
746 category: ToolCategory::SourceControl,
747 }
748}
749
750fn docker_ps() -> ToolDefinition {
755 ToolDefinition {
756 name: "docker_ps".into(),
757 description: "List running Docker containers.".into(),
758 input_schema: serde_json::json!({
759 "type": "object",
760 "properties": {
761 "all": {
762 "type": "boolean",
763 "description": "Show all containers, not just running ones. Default: false."
764 }
765 }
766 }),
767 category: ToolCategory::Container,
768 }
769}
770
771fn docker_run() -> ToolDefinition {
772 ToolDefinition {
773 name: "docker_run".into(),
774 description: "Run a Docker container from an image.".into(),
775 input_schema: serde_json::json!({
776 "type": "object",
777 "properties": {
778 "image": {
779 "type": "string",
780 "description": "The Docker image to run."
781 },
782 "command": {
783 "type": "string",
784 "description": "Optional command to run inside the container."
785 },
786 "env": {
787 "type": "object",
788 "description": "Environment variables as key-value pairs."
789 },
790 "ports": {
791 "type": "array",
792 "items": { "type": "string" },
793 "description": "Port mappings (e.g. '8080:80')."
794 },
795 "detach": {
796 "type": "boolean",
797 "description": "Run in detached mode. Default: false."
798 },
799 "name": {
800 "type": "string",
801 "description": "Optional container name."
802 }
803 },
804 "required": ["image"]
805 }),
806 category: ToolCategory::Container,
807 }
808}
809
810fn docker_build() -> ToolDefinition {
811 ToolDefinition {
812 name: "docker_build".into(),
813 description: "Build a Docker image from a Dockerfile.".into(),
814 input_schema: serde_json::json!({
815 "type": "object",
816 "properties": {
817 "path": {
818 "type": "string",
819 "description": "Path to the build context directory (default: '.')."
820 },
821 "tag": {
822 "type": "string",
823 "description": "Tag for the built image (e.g. 'myapp:latest')."
824 },
825 "dockerfile": {
826 "type": "string",
827 "description": "Path to the Dockerfile (default: 'Dockerfile')."
828 }
829 }
830 }),
831 category: ToolCategory::Container,
832 }
833}
834
835fn docker_logs() -> ToolDefinition {
836 ToolDefinition {
837 name: "docker_logs".into(),
838 description: "Get logs from a Docker container.".into(),
839 input_schema: serde_json::json!({
840 "type": "object",
841 "properties": {
842 "container": {
843 "type": "string",
844 "description": "Container ID or name."
845 },
846 "tail": {
847 "type": "integer",
848 "description": "Number of lines to show from the end (default: 100)."
849 }
850 },
851 "required": ["container"]
852 }),
853 category: ToolCategory::Container,
854 }
855}
856
857fn http_request() -> ToolDefinition {
862 ToolDefinition {
863 name: "http_request".into(),
864 description: "Send a full HTTP request with custom method, headers, body, and timeout."
865 .into(),
866 input_schema: serde_json::json!({
867 "type": "object",
868 "properties": {
869 "url": {
870 "type": "string",
871 "description": "The URL to send the request to."
872 },
873 "method": {
874 "type": "string",
875 "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"],
876 "description": "HTTP method. Default: GET."
877 },
878 "headers": {
879 "type": "object",
880 "description": "Request headers as key-value pairs."
881 },
882 "body": {
883 "type": "string",
884 "description": "Request body."
885 },
886 "timeout_secs": {
887 "type": "integer",
888 "description": "Request timeout in seconds (default: 30)."
889 }
890 },
891 "required": ["url"]
892 }),
893 category: ToolCategory::Web,
894 }
895}
896
897fn http_post() -> ToolDefinition {
898 ToolDefinition {
899 name: "http_post".into(),
900 description: "Shorthand for an HTTP POST request with a JSON body.".into(),
901 input_schema: serde_json::json!({
902 "type": "object",
903 "properties": {
904 "url": {
905 "type": "string",
906 "description": "The URL to POST to."
907 },
908 "json": {
909 "type": "object",
910 "description": "JSON body to send."
911 },
912 "headers": {
913 "type": "object",
914 "description": "Additional headers."
915 }
916 },
917 "required": ["url", "json"]
918 }),
919 category: ToolCategory::Web,
920 }
921}
922
923fn json_query() -> ToolDefinition {
928 ToolDefinition {
929 name: "json_query".into(),
930 description: "Query a JSON value using a dot-separated path (e.g. 'users.0.name'). Array indices are numeric.".into(),
931 input_schema: serde_json::json!({
932 "type": "object",
933 "properties": {
934 "data": {
935 "description": "The JSON data to query (object, array, or string to parse)."
936 },
937 "path": {
938 "type": "string",
939 "description": "Dot-separated path to query (e.g. 'a.b.0.c')."
940 }
941 },
942 "required": ["data", "path"]
943 }),
944 category: ToolCategory::Data,
945 }
946}
947
948fn json_transform() -> ToolDefinition {
949 ToolDefinition {
950 name: "json_transform".into(),
951 description: "Transform JSON data: extract specific keys, rename keys, or filter an array of objects.".into(),
952 input_schema: serde_json::json!({
953 "type": "object",
954 "properties": {
955 "data": {
956 "description": "The JSON data to transform."
957 },
958 "extract": {
959 "type": "array",
960 "items": { "type": "string" },
961 "description": "List of keys to extract from each object."
962 },
963 "rename": {
964 "type": "object",
965 "description": "Key rename mapping (old_name -> new_name)."
966 },
967 "filter_key": {
968 "type": "string",
969 "description": "Key to filter array items by."
970 },
971 "filter_value": {
972 "type": "string",
973 "description": "Value the filter_key must match."
974 }
975 },
976 "required": ["data"]
977 }),
978 category: ToolCategory::Data,
979 }
980}
981
982fn yaml_parse() -> ToolDefinition {
983 ToolDefinition {
984 name: "yaml_parse".into(),
985 description: "Parse a YAML string and return it as JSON.".into(),
986 input_schema: serde_json::json!({
987 "type": "object",
988 "properties": {
989 "content": {
990 "type": "string",
991 "description": "The YAML string to parse."
992 }
993 },
994 "required": ["content"]
995 }),
996 category: ToolCategory::Data,
997 }
998}
999
1000fn regex_match() -> ToolDefinition {
1001 ToolDefinition {
1002 name: "regex_match".into(),
1003 description: "Match a regex pattern against text and return all captures.".into(),
1004 input_schema: serde_json::json!({
1005 "type": "object",
1006 "properties": {
1007 "pattern": {
1008 "type": "string",
1009 "description": "The regex pattern."
1010 },
1011 "text": {
1012 "type": "string",
1013 "description": "The text to match against."
1014 },
1015 "global": {
1016 "type": "boolean",
1017 "description": "Find all matches (true) or just the first (false). Default: false."
1018 }
1019 },
1020 "required": ["pattern", "text"]
1021 }),
1022 category: ToolCategory::Data,
1023 }
1024}
1025
1026fn regex_replace() -> ToolDefinition {
1027 ToolDefinition {
1028 name: "regex_replace".into(),
1029 description: "Find and replace text using a regex pattern. Supports capture group references ($1, $2, etc.) in the replacement.".into(),
1030 input_schema: serde_json::json!({
1031 "type": "object",
1032 "properties": {
1033 "pattern": {
1034 "type": "string",
1035 "description": "The regex pattern to find."
1036 },
1037 "replacement": {
1038 "type": "string",
1039 "description": "The replacement string (supports $1, $2, etc. for captures)."
1040 },
1041 "text": {
1042 "type": "string",
1043 "description": "The text to perform replacement on."
1044 }
1045 },
1046 "required": ["pattern", "replacement", "text"]
1047 }),
1048 category: ToolCategory::Data,
1049 }
1050}
1051
1052fn process_list() -> ToolDefinition {
1057 ToolDefinition {
1058 name: "process_list".into(),
1059 description: "List running processes with PID, name, and CPU/memory usage.".into(),
1060 input_schema: serde_json::json!({
1061 "type": "object",
1062 "properties": {
1063 "filter": {
1064 "type": "string",
1065 "description": "Optional filter string to match process names."
1066 }
1067 }
1068 }),
1069 category: ToolCategory::Shell,
1070 }
1071}
1072
1073fn process_kill() -> ToolDefinition {
1074 ToolDefinition {
1075 name: "process_kill".into(),
1076 description: "Kill a process by PID.".into(),
1077 input_schema: serde_json::json!({
1078 "type": "object",
1079 "properties": {
1080 "pid": {
1081 "type": "integer",
1082 "description": "The process ID to kill."
1083 },
1084 "signal": {
1085 "type": "string",
1086 "description": "Signal to send (e.g. 'TERM', 'KILL'). Default: 'TERM'."
1087 }
1088 },
1089 "required": ["pid"]
1090 }),
1091 category: ToolCategory::Shell,
1092 }
1093}
1094
1095fn schedule_task() -> ToolDefinition {
1100 ToolDefinition {
1101 name: "schedule_task".into(),
1102 description: "Schedule a one-shot or recurring task. Returns a task ID.".into(),
1103 input_schema: serde_json::json!({
1104 "type": "object",
1105 "properties": {
1106 "name": {
1107 "type": "string",
1108 "description": "Human-readable name for the task."
1109 },
1110 "command": {
1111 "type": "string",
1112 "description": "Shell command to execute when the task fires."
1113 },
1114 "delay_secs": {
1115 "type": "integer",
1116 "description": "Delay in seconds before first execution."
1117 },
1118 "interval_secs": {
1119 "type": "integer",
1120 "description": "Interval in seconds for recurring execution. If omitted, the task runs once."
1121 }
1122 },
1123 "required": ["name", "command", "delay_secs"]
1124 }),
1125 category: ToolCategory::Schedule,
1126 }
1127}
1128
1129fn schedule_list() -> ToolDefinition {
1130 ToolDefinition {
1131 name: "schedule_list".into(),
1132 description: "List all scheduled tasks with their IDs, names, and status.".into(),
1133 input_schema: serde_json::json!({
1134 "type": "object",
1135 "properties": {}
1136 }),
1137 category: ToolCategory::Schedule,
1138 }
1139}
1140
1141fn schedule_cancel() -> ToolDefinition {
1142 ToolDefinition {
1143 name: "schedule_cancel".into(),
1144 description: "Cancel a scheduled task by its ID.".into(),
1145 input_schema: serde_json::json!({
1146 "type": "object",
1147 "properties": {
1148 "task_id": {
1149 "type": "string",
1150 "description": "The UUID of the task to cancel."
1151 }
1152 },
1153 "required": ["task_id"]
1154 }),
1155 category: ToolCategory::Schedule,
1156 }
1157}
1158
1159fn code_search() -> ToolDefinition {
1164 ToolDefinition {
1165 name: "code_search".into(),
1166 description: "Search for text or a regex pattern in files recursively under a directory. Returns matching lines with file paths and line numbers.".into(),
1167 input_schema: serde_json::json!({
1168 "type": "object",
1169 "properties": {
1170 "pattern": {
1171 "type": "string",
1172 "description": "The regex pattern to search for."
1173 },
1174 "path": {
1175 "type": "string",
1176 "description": "Root directory to search in (default: working directory)."
1177 },
1178 "file_pattern": {
1179 "type": "string",
1180 "description": "Glob pattern to filter files (e.g. '*.rs', '*.py')."
1181 },
1182 "max_results": {
1183 "type": "integer",
1184 "description": "Maximum number of matches to return (default: 50)."
1185 }
1186 },
1187 "required": ["pattern"]
1188 }),
1189 category: ToolCategory::CodeAnalysis,
1190 }
1191}
1192
1193fn code_symbols() -> ToolDefinition {
1194 ToolDefinition {
1195 name: "code_symbols".into(),
1196 description: "Extract function, struct, class, and method definitions from a source file using regex-based heuristics.".into(),
1197 input_schema: serde_json::json!({
1198 "type": "object",
1199 "properties": {
1200 "path": {
1201 "type": "string",
1202 "description": "Path to the source file to analyze."
1203 }
1204 },
1205 "required": ["path"]
1206 }),
1207 category: ToolCategory::CodeAnalysis,
1208 }
1209}
1210
1211fn archive_create() -> ToolDefinition {
1216 ToolDefinition {
1217 name: "archive_create".into(),
1218 description: "Create a tar.gz archive from a list of file or directory paths.".into(),
1219 input_schema: serde_json::json!({
1220 "type": "object",
1221 "properties": {
1222 "output_path": {
1223 "type": "string",
1224 "description": "Path for the output .tar.gz archive file."
1225 },
1226 "paths": {
1227 "type": "array",
1228 "items": { "type": "string" },
1229 "description": "List of file or directory paths to include in the archive."
1230 }
1231 },
1232 "required": ["output_path", "paths"]
1233 }),
1234 category: ToolCategory::Archive,
1235 }
1236}
1237
1238fn archive_extract() -> ToolDefinition {
1239 ToolDefinition {
1240 name: "archive_extract".into(),
1241 description: "Extract a tar.gz archive to a destination directory.".into(),
1242 input_schema: serde_json::json!({
1243 "type": "object",
1244 "properties": {
1245 "archive_path": {
1246 "type": "string",
1247 "description": "Path to the .tar.gz archive to extract."
1248 },
1249 "destination": {
1250 "type": "string",
1251 "description": "Directory to extract the archive into."
1252 }
1253 },
1254 "required": ["archive_path", "destination"]
1255 }),
1256 category: ToolCategory::Archive,
1257 }
1258}
1259
1260fn archive_list() -> ToolDefinition {
1261 ToolDefinition {
1262 name: "archive_list".into(),
1263 description: "List the contents of a tar.gz archive without extracting.".into(),
1264 input_schema: serde_json::json!({
1265 "type": "object",
1266 "properties": {
1267 "archive_path": {
1268 "type": "string",
1269 "description": "Path to the .tar.gz archive to list."
1270 }
1271 },
1272 "required": ["archive_path"]
1273 }),
1274 category: ToolCategory::Archive,
1275 }
1276}
1277
1278fn template_render() -> ToolDefinition {
1283 ToolDefinition {
1284 name: "template_render".into(),
1285 description: "Render a Handlebars-style template by substituting {{variable}} placeholders with provided values.".into(),
1286 input_schema: serde_json::json!({
1287 "type": "object",
1288 "properties": {
1289 "template": {
1290 "type": "string",
1291 "description": "The template string containing {{variable}} placeholders."
1292 },
1293 "variables": {
1294 "type": "object",
1295 "description": "Key-value pairs mapping variable names to their values."
1296 }
1297 },
1298 "required": ["template", "variables"]
1299 }),
1300 category: ToolCategory::Template,
1301 }
1302}
1303
1304fn hash_compute() -> ToolDefinition {
1309 ToolDefinition {
1310 name: "hash_compute".into(),
1311 description: "Compute a cryptographic hash (SHA-256, SHA-512, or MD5) of a string or file."
1312 .into(),
1313 input_schema: serde_json::json!({
1314 "type": "object",
1315 "properties": {
1316 "algorithm": {
1317 "type": "string",
1318 "enum": ["sha256", "sha512", "md5"],
1319 "description": "Hash algorithm to use. Default: sha256."
1320 },
1321 "input": {
1322 "type": "string",
1323 "description": "The string to hash (provide either this or 'file')."
1324 },
1325 "file": {
1326 "type": "string",
1327 "description": "Path to a file to hash (provide either this or 'input')."
1328 }
1329 }
1330 }),
1331 category: ToolCategory::Crypto,
1332 }
1333}
1334
1335fn hash_verify() -> ToolDefinition {
1336 ToolDefinition {
1337 name: "hash_verify".into(),
1338 description: "Verify that a hash matches an expected value.".into(),
1339 input_schema: serde_json::json!({
1340 "type": "object",
1341 "properties": {
1342 "algorithm": {
1343 "type": "string",
1344 "enum": ["sha256", "sha512", "md5"],
1345 "description": "Hash algorithm to use. Default: sha256."
1346 },
1347 "input": {
1348 "type": "string",
1349 "description": "The string to hash (provide either this or 'file')."
1350 },
1351 "file": {
1352 "type": "string",
1353 "description": "Path to a file to hash (provide either this or 'input')."
1354 },
1355 "expected": {
1356 "type": "string",
1357 "description": "The expected hex-encoded hash value to compare against."
1358 }
1359 },
1360 "required": ["expected"]
1361 }),
1362 category: ToolCategory::Crypto,
1363 }
1364}
1365
1366fn env_get() -> ToolDefinition {
1371 ToolDefinition {
1372 name: "env_get".into(),
1373 description: "Get the value of an environment variable.".into(),
1374 input_schema: serde_json::json!({
1375 "type": "object",
1376 "properties": {
1377 "name": {
1378 "type": "string",
1379 "description": "The environment variable name."
1380 }
1381 },
1382 "required": ["name"]
1383 }),
1384 category: ToolCategory::Shell,
1385 }
1386}
1387
1388fn env_list() -> ToolDefinition {
1389 ToolDefinition {
1390 name: "env_list".into(),
1391 description: "List all environment variables, with optional prefix filter.".into(),
1392 input_schema: serde_json::json!({
1393 "type": "object",
1394 "properties": {
1395 "prefix": {
1396 "type": "string",
1397 "description": "Optional prefix to filter environment variable names by."
1398 }
1399 }
1400 }),
1401 category: ToolCategory::Shell,
1402 }
1403}
1404
1405fn text_diff() -> ToolDefinition {
1410 ToolDefinition {
1411 name: "text_diff".into(),
1412 description: "Compute a unified diff between two text strings.".into(),
1413 input_schema: serde_json::json!({
1414 "type": "object",
1415 "properties": {
1416 "old_text": {
1417 "type": "string",
1418 "description": "The original text."
1419 },
1420 "new_text": {
1421 "type": "string",
1422 "description": "The modified text."
1423 },
1424 "label": {
1425 "type": "string",
1426 "description": "Optional label for the diff output (default: 'a' / 'b')."
1427 }
1428 },
1429 "required": ["old_text", "new_text"]
1430 }),
1431 category: ToolCategory::Data,
1432 }
1433}
1434
1435fn text_count() -> ToolDefinition {
1436 ToolDefinition {
1437 name: "text_count".into(),
1438 description: "Count lines, words, and characters in text.".into(),
1439 input_schema: serde_json::json!({
1440 "type": "object",
1441 "properties": {
1442 "text": {
1443 "type": "string",
1444 "description": "The text to count."
1445 }
1446 },
1447 "required": ["text"]
1448 }),
1449 category: ToolCategory::Data,
1450 }
1451}
1452
1453fn file_search() -> ToolDefinition {
1458 ToolDefinition {
1459 name: "file_search".into(),
1460 description: "Search for files by name pattern (glob) recursively under a directory."
1461 .into(),
1462 input_schema: serde_json::json!({
1463 "type": "object",
1464 "properties": {
1465 "pattern": {
1466 "type": "string",
1467 "description": "Glob pattern to match file names (e.g. '*.rs', 'Cargo.*')."
1468 },
1469 "path": {
1470 "type": "string",
1471 "description": "Root directory to search in (default: working directory)."
1472 },
1473 "max_results": {
1474 "type": "integer",
1475 "description": "Maximum number of results to return (default: 100)."
1476 }
1477 },
1478 "required": ["pattern"]
1479 }),
1480 category: ToolCategory::FileSystem,
1481 }
1482}
1483
1484fn file_info() -> ToolDefinition {
1485 ToolDefinition {
1486 name: "file_info".into(),
1487 description: "Get file metadata: size, modified time, permissions, and type.".into(),
1488 input_schema: serde_json::json!({
1489 "type": "object",
1490 "properties": {
1491 "path": {
1492 "type": "string",
1493 "description": "Path to the file or directory to inspect."
1494 }
1495 },
1496 "required": ["path"]
1497 }),
1498 category: ToolCategory::FileSystem,
1499 }
1500}
1501
1502fn a2a_delegate() -> ToolDefinition {
1503 ToolDefinition {
1504 name: "a2a_delegate".into(),
1505 description: "Delegate a task to a remote A2A agent. Discovers the agent, sends the task, \
1506 polls for completion, and returns the result."
1507 .into(),
1508 input_schema: serde_json::json!({
1509 "type": "object",
1510 "properties": {
1511 "agent_url": {
1512 "type": "string",
1513 "description": "Base URL of the remote A2A agent (e.g. 'https://agent.example.com')."
1514 },
1515 "prompt": {
1516 "type": "string",
1517 "description": "The task description / prompt to send to the remote agent."
1518 },
1519 "context": {
1520 "type": "object",
1521 "description": "Optional additional context as key-value pairs."
1522 },
1523 "timeout_secs": {
1524 "type": "integer",
1525 "description": "Maximum time to wait for the task to complete (default: 60)."
1526 }
1527 },
1528 "required": ["agent_url", "prompt"]
1529 }),
1530 category: ToolCategory::Agent,
1531 }
1532}
1533
1534fn wasm_invoke() -> ToolDefinition {
1535 ToolDefinition {
1536 name: "wasm_invoke".into(),
1537 description: "Invoke a function on a loaded WASM plugin (imported technique). \
1538 Executes the named function within the plugin's sandboxed WASM runtime \
1539 and returns the result."
1540 .into(),
1541 input_schema: serde_json::json!({
1542 "type": "object",
1543 "properties": {
1544 "plugin": {
1545 "type": "string",
1546 "description": "Name of the loaded WASM plugin to invoke."
1547 },
1548 "function": {
1549 "type": "string",
1550 "description": "Name of the exported function to call within the plugin."
1551 },
1552 "input": {
1553 "type": "object",
1554 "description": "Input arguments to pass to the plugin function (optional)."
1555 }
1556 },
1557 "required": ["plugin", "function"]
1558 }),
1559 category: ToolCategory::Plugin,
1560 }
1561}
1562
1563#[cfg(test)]
1568mod tests {
1569 use super::*;
1570
1571 #[test]
1572 fn test_browser_tool_definitions_correct() {
1573 let nav = browser_navigate();
1574 assert_eq!(nav.name, "browser_navigate");
1575 assert_eq!(nav.category, ToolCategory::Browser);
1576 assert!(
1577 nav.input_schema["required"]
1578 .as_array()
1579 .expect("required should be array")
1580 .iter()
1581 .any(|v| v == "url")
1582 );
1583
1584 let ss = browser_screenshot();
1585 assert_eq!(ss.name, "browser_screenshot");
1586 assert_eq!(ss.category, ToolCategory::Browser);
1587
1588 let click = browser_click();
1589 assert_eq!(click.name, "browser_click");
1590 assert!(
1591 click.input_schema["required"]
1592 .as_array()
1593 .expect("required should be array")
1594 .iter()
1595 .any(|v| v == "selector")
1596 );
1597
1598 let typ = browser_type();
1599 assert_eq!(typ.name, "browser_type");
1600 let required = typ.input_schema["required"]
1601 .as_array()
1602 .expect("required should be array");
1603 assert!(required.iter().any(|v| v == "selector"));
1604 assert!(required.iter().any(|v| v == "text"));
1605
1606 let content = browser_content();
1607 assert_eq!(content.name, "browser_content");
1608 assert_eq!(content.category, ToolCategory::Browser);
1609 }
1610
1611 #[test]
1612 fn test_browser_tools_require_browser_control_capability() {
1613 let caps = vec![Capability::BrowserControl];
1614 let tools = tools_for_capabilities(&caps);
1615
1616 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1617 assert!(
1618 names.contains(&"browser_navigate"),
1619 "missing browser_navigate"
1620 );
1621 assert!(
1622 names.contains(&"browser_screenshot"),
1623 "missing browser_screenshot"
1624 );
1625 assert!(names.contains(&"browser_click"), "missing browser_click");
1626 assert!(names.contains(&"browser_type"), "missing browser_type");
1627 assert!(
1628 names.contains(&"browser_content"),
1629 "missing browser_content"
1630 );
1631 }
1632
1633 #[test]
1634 fn test_browser_tools_absent_without_capability() {
1635 let caps = vec![Capability::Memory];
1636 let tools = tools_for_capabilities(&caps);
1637
1638 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1639 assert!(!names.iter().any(|n| n.starts_with("browser_")));
1640 }
1641
1642 #[test]
1643 fn test_all_tools_includes_browser() {
1644 let tools = all_tools();
1645 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
1646 assert!(names.contains(&"browser_navigate"));
1647 assert!(names.contains(&"browser_screenshot"));
1648 assert!(names.contains(&"browser_click"));
1649 assert!(names.contains(&"browser_type"));
1650 assert!(names.contains(&"browser_content"));
1651 }
1652
1653 #[test]
1658 fn test_file_read_definition() {
1659 let t = file_read();
1660 assert_eq!(t.name, "file_read");
1661 assert_eq!(t.category, ToolCategory::FileSystem);
1662 assert_eq!(t.input_schema["type"], "object");
1663 let required = t.input_schema["required"].as_array().unwrap();
1664 assert!(required.iter().any(|v| v == "path"));
1665 }
1666
1667 #[test]
1668 fn test_file_write_definition() {
1669 let t = file_write();
1670 assert_eq!(t.name, "file_write");
1671 assert_eq!(t.category, ToolCategory::FileSystem);
1672 let required = t.input_schema["required"].as_array().unwrap();
1673 assert!(required.iter().any(|v| v == "path"));
1674 assert!(required.iter().any(|v| v == "content"));
1675 }
1676
1677 #[test]
1678 fn test_file_list_definition() {
1679 let t = file_list();
1680 assert_eq!(t.name, "file_list");
1681 assert_eq!(t.category, ToolCategory::FileSystem);
1682 assert_eq!(t.input_schema["type"], "object");
1683 }
1684
1685 #[test]
1686 fn test_file_search_definition() {
1687 let t = file_search();
1688 assert_eq!(t.name, "file_search");
1689 assert_eq!(t.category, ToolCategory::FileSystem);
1690 let required = t.input_schema["required"].as_array().unwrap();
1691 assert!(required.iter().any(|v| v == "pattern"));
1692 }
1693
1694 #[test]
1695 fn test_file_info_definition() {
1696 let t = file_info();
1697 assert_eq!(t.name, "file_info");
1698 assert_eq!(t.category, ToolCategory::FileSystem);
1699 let required = t.input_schema["required"].as_array().unwrap();
1700 assert!(required.iter().any(|v| v == "path"));
1701 }
1702
1703 #[test]
1704 fn test_patch_apply_definition() {
1705 let t = patch_apply();
1706 assert_eq!(t.name, "patch_apply");
1707 assert_eq!(t.category, ToolCategory::FileSystem);
1708 let required = t.input_schema["required"].as_array().unwrap();
1709 assert!(required.iter().any(|v| v == "path"));
1710 assert!(required.iter().any(|v| v == "diff"));
1711 }
1712
1713 #[test]
1714 fn test_shell_exec_definition() {
1715 let t = shell_exec();
1716 assert_eq!(t.name, "shell_exec");
1717 assert_eq!(t.category, ToolCategory::Shell);
1718 let required = t.input_schema["required"].as_array().unwrap();
1719 assert!(required.iter().any(|v| v == "command"));
1720 }
1721
1722 #[test]
1723 fn test_web_fetch_definition() {
1724 let t = web_fetch();
1725 assert_eq!(t.name, "web_fetch");
1726 assert_eq!(t.category, ToolCategory::Web);
1727 let required = t.input_schema["required"].as_array().unwrap();
1728 assert!(required.iter().any(|v| v == "url"));
1729 }
1730
1731 #[test]
1732 fn test_web_search_definition() {
1733 let t = web_search();
1734 assert_eq!(t.name, "web_search");
1735 assert_eq!(t.category, ToolCategory::Web);
1736 let required = t.input_schema["required"].as_array().unwrap();
1737 assert!(required.iter().any(|v| v == "query"));
1738 }
1739
1740 #[test]
1741 fn test_memory_store_definition() {
1742 let t = memory_store();
1743 assert_eq!(t.name, "memory_store");
1744 assert_eq!(t.category, ToolCategory::Memory);
1745 let required = t.input_schema["required"].as_array().unwrap();
1746 assert!(required.iter().any(|v| v == "key"));
1747 assert!(required.iter().any(|v| v == "value"));
1748 }
1749
1750 #[test]
1751 fn test_memory_recall_definition() {
1752 let t = memory_recall();
1753 assert_eq!(t.name, "memory_recall");
1754 assert_eq!(t.category, ToolCategory::Memory);
1755 let required = t.input_schema["required"].as_array().unwrap();
1756 assert!(required.iter().any(|v| v == "query"));
1757 }
1758
1759 #[test]
1760 fn test_knowledge_tools_definitions() {
1761 let ae = knowledge_add_entity();
1762 assert_eq!(ae.name, "knowledge_add_entity");
1763 assert_eq!(ae.category, ToolCategory::Knowledge);
1764 let required = ae.input_schema["required"].as_array().unwrap();
1765 assert!(required.iter().any(|v| v == "name"));
1766 assert!(required.iter().any(|v| v == "entity_type"));
1767
1768 let ar = knowledge_add_relation();
1769 assert_eq!(ar.name, "knowledge_add_relation");
1770 let required = ar.input_schema["required"].as_array().unwrap();
1771 assert!(required.iter().any(|v| v == "from"));
1772 assert!(required.iter().any(|v| v == "relation"));
1773 assert!(required.iter().any(|v| v == "to"));
1774
1775 let kq = knowledge_query();
1776 assert_eq!(kq.name, "knowledge_query");
1777 let required = kq.input_schema["required"].as_array().unwrap();
1778 assert!(required.iter().any(|v| v == "query"));
1779 }
1780
1781 #[test]
1782 fn test_agent_tools_definitions() {
1783 let spawn = agent_spawn();
1784 assert_eq!(spawn.name, "agent_spawn");
1785 assert_eq!(spawn.category, ToolCategory::Agent);
1786 let required = spawn.input_schema["required"].as_array().unwrap();
1787 assert!(required.iter().any(|v| v == "name"));
1788 assert!(required.iter().any(|v| v == "system_prompt"));
1789
1790 let msg = agent_message();
1791 assert_eq!(msg.name, "agent_message");
1792 assert_eq!(msg.category, ToolCategory::Agent);
1793 let required = msg.input_schema["required"].as_array().unwrap();
1794 assert!(required.iter().any(|v| v == "message"));
1795
1796 let list = agent_list();
1797 assert_eq!(list.name, "agent_list");
1798 assert_eq!(list.category, ToolCategory::Agent);
1799 }
1800
1801 #[test]
1802 fn test_git_tools_definitions() {
1803 let status = git_status();
1804 assert_eq!(status.name, "git_status");
1805 assert_eq!(status.category, ToolCategory::SourceControl);
1806
1807 let diff = git_diff();
1808 assert_eq!(diff.name, "git_diff");
1809
1810 let log = git_log();
1811 assert_eq!(log.name, "git_log");
1812
1813 let commit = git_commit();
1814 assert_eq!(commit.name, "git_commit");
1815 let required = commit.input_schema["required"].as_array().unwrap();
1816 assert!(required.iter().any(|v| v == "message"));
1817
1818 let branch = git_branch();
1819 assert_eq!(branch.name, "git_branch");
1820 }
1821
1822 #[test]
1823 fn test_docker_tools_definitions() {
1824 let ps = docker_ps();
1825 assert_eq!(ps.name, "docker_ps");
1826 assert_eq!(ps.category, ToolCategory::Container);
1827
1828 let run = docker_run();
1829 assert_eq!(run.name, "docker_run");
1830 let required = run.input_schema["required"].as_array().unwrap();
1831 assert!(required.iter().any(|v| v == "image"));
1832
1833 let build = docker_build();
1834 assert_eq!(build.name, "docker_build");
1835
1836 let logs = docker_logs();
1837 assert_eq!(logs.name, "docker_logs");
1838 let required = logs.input_schema["required"].as_array().unwrap();
1839 assert!(required.iter().any(|v| v == "container"));
1840 }
1841
1842 #[test]
1843 fn test_http_tools_definitions() {
1844 let req = http_request();
1845 assert_eq!(req.name, "http_request");
1846 assert_eq!(req.category, ToolCategory::Web);
1847 let required = req.input_schema["required"].as_array().unwrap();
1848 assert!(required.iter().any(|v| v == "url"));
1849
1850 let post = http_post();
1851 assert_eq!(post.name, "http_post");
1852 let required = post.input_schema["required"].as_array().unwrap();
1853 assert!(required.iter().any(|v| v == "url"));
1854 assert!(required.iter().any(|v| v == "json"));
1855 }
1856
1857 #[test]
1858 fn test_data_tools_definitions() {
1859 let jq = json_query();
1860 assert_eq!(jq.name, "json_query");
1861 assert_eq!(jq.category, ToolCategory::Data);
1862 let required = jq.input_schema["required"].as_array().unwrap();
1863 assert!(required.iter().any(|v| v == "data"));
1864 assert!(required.iter().any(|v| v == "path"));
1865
1866 let jt = json_transform();
1867 assert_eq!(jt.name, "json_transform");
1868 let required = jt.input_schema["required"].as_array().unwrap();
1869 assert!(required.iter().any(|v| v == "data"));
1870
1871 let yp = yaml_parse();
1872 assert_eq!(yp.name, "yaml_parse");
1873 let required = yp.input_schema["required"].as_array().unwrap();
1874 assert!(required.iter().any(|v| v == "content"));
1875
1876 let rm = regex_match();
1877 assert_eq!(rm.name, "regex_match");
1878 let required = rm.input_schema["required"].as_array().unwrap();
1879 assert!(required.iter().any(|v| v == "pattern"));
1880 assert!(required.iter().any(|v| v == "text"));
1881
1882 let rr = regex_replace();
1883 assert_eq!(rr.name, "regex_replace");
1884 let required = rr.input_schema["required"].as_array().unwrap();
1885 assert!(required.iter().any(|v| v == "pattern"));
1886 assert!(required.iter().any(|v| v == "replacement"));
1887 assert!(required.iter().any(|v| v == "text"));
1888 }
1889
1890 #[test]
1891 fn test_process_tools_definitions() {
1892 let pl = process_list();
1893 assert_eq!(pl.name, "process_list");
1894 assert_eq!(pl.category, ToolCategory::Shell);
1895
1896 let pk = process_kill();
1897 assert_eq!(pk.name, "process_kill");
1898 let required = pk.input_schema["required"].as_array().unwrap();
1899 assert!(required.iter().any(|v| v == "pid"));
1900 }
1901
1902 #[test]
1903 fn test_schedule_tools_definitions() {
1904 let st = schedule_task();
1905 assert_eq!(st.name, "schedule_task");
1906 assert_eq!(st.category, ToolCategory::Schedule);
1907 let required = st.input_schema["required"].as_array().unwrap();
1908 assert!(required.iter().any(|v| v == "name"));
1909 assert!(required.iter().any(|v| v == "command"));
1910 assert!(required.iter().any(|v| v == "delay_secs"));
1911
1912 let sl = schedule_list();
1913 assert_eq!(sl.name, "schedule_list");
1914
1915 let sc = schedule_cancel();
1916 assert_eq!(sc.name, "schedule_cancel");
1917 let required = sc.input_schema["required"].as_array().unwrap();
1918 assert!(required.iter().any(|v| v == "task_id"));
1919 }
1920
1921 #[test]
1922 fn test_code_analysis_tools_definitions() {
1923 let cs = code_search();
1924 assert_eq!(cs.name, "code_search");
1925 assert_eq!(cs.category, ToolCategory::CodeAnalysis);
1926 let required = cs.input_schema["required"].as_array().unwrap();
1927 assert!(required.iter().any(|v| v == "pattern"));
1928
1929 let sym = code_symbols();
1930 assert_eq!(sym.name, "code_symbols");
1931 let required = sym.input_schema["required"].as_array().unwrap();
1932 assert!(required.iter().any(|v| v == "path"));
1933 }
1934
1935 #[test]
1936 fn test_archive_tools_definitions() {
1937 let ac = archive_create();
1938 assert_eq!(ac.name, "archive_create");
1939 assert_eq!(ac.category, ToolCategory::Archive);
1940 let required = ac.input_schema["required"].as_array().unwrap();
1941 assert!(required.iter().any(|v| v == "output_path"));
1942 assert!(required.iter().any(|v| v == "paths"));
1943
1944 let ae = archive_extract();
1945 assert_eq!(ae.name, "archive_extract");
1946 let required = ae.input_schema["required"].as_array().unwrap();
1947 assert!(required.iter().any(|v| v == "archive_path"));
1948 assert!(required.iter().any(|v| v == "destination"));
1949
1950 let al = archive_list();
1951 assert_eq!(al.name, "archive_list");
1952 let required = al.input_schema["required"].as_array().unwrap();
1953 assert!(required.iter().any(|v| v == "archive_path"));
1954 }
1955
1956 #[test]
1957 fn test_template_render_definition() {
1958 let t = template_render();
1959 assert_eq!(t.name, "template_render");
1960 assert_eq!(t.category, ToolCategory::Template);
1961 let required = t.input_schema["required"].as_array().unwrap();
1962 assert!(required.iter().any(|v| v == "template"));
1963 assert!(required.iter().any(|v| v == "variables"));
1964 }
1965
1966 #[test]
1967 fn test_crypto_tools_definitions() {
1968 let hc = hash_compute();
1969 assert_eq!(hc.name, "hash_compute");
1970 assert_eq!(hc.category, ToolCategory::Crypto);
1971
1972 let hv = hash_verify();
1973 assert_eq!(hv.name, "hash_verify");
1974 let required = hv.input_schema["required"].as_array().unwrap();
1975 assert!(required.iter().any(|v| v == "expected"));
1976 }
1977
1978 #[test]
1979 fn test_env_tools_definitions() {
1980 let eg = env_get();
1981 assert_eq!(eg.name, "env_get");
1982 assert_eq!(eg.category, ToolCategory::Shell);
1983 let required = eg.input_schema["required"].as_array().unwrap();
1984 assert!(required.iter().any(|v| v == "name"));
1985
1986 let el = env_list();
1987 assert_eq!(el.name, "env_list");
1988 }
1989
1990 #[test]
1991 fn test_text_tools_definitions() {
1992 let td = text_diff();
1993 assert_eq!(td.name, "text_diff");
1994 assert_eq!(td.category, ToolCategory::Data);
1995 let required = td.input_schema["required"].as_array().unwrap();
1996 assert!(required.iter().any(|v| v == "old_text"));
1997 assert!(required.iter().any(|v| v == "new_text"));
1998
1999 let tc = text_count();
2000 assert_eq!(tc.name, "text_count");
2001 let required = tc.input_schema["required"].as_array().unwrap();
2002 assert!(required.iter().any(|v| v == "text"));
2003 }
2004
2005 #[test]
2010 fn test_tools_for_file_read_capability() {
2011 let caps = vec![Capability::FileRead("**".into())];
2012 let tools = tools_for_capabilities(&caps);
2013 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2014 assert!(names.contains(&"file_read"));
2015 assert!(names.contains(&"file_list"));
2016 assert!(names.contains(&"file_search"));
2017 assert!(names.contains(&"file_info"));
2018 assert!(!names.contains(&"file_write"));
2019 }
2020
2021 #[test]
2022 fn test_tools_for_file_write_capability() {
2023 let caps = vec![Capability::FileWrite("**".into())];
2024 let tools = tools_for_capabilities(&caps);
2025 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2026 assert!(names.contains(&"file_write"));
2027 assert!(names.contains(&"patch_apply"));
2028 assert!(!names.contains(&"file_read"));
2029 }
2030
2031 #[test]
2032 fn test_tools_for_shell_exec_capability() {
2033 let caps = vec![Capability::ShellExec("*".into())];
2034 let tools = tools_for_capabilities(&caps);
2035 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2036 assert!(names.contains(&"shell_exec"));
2037 assert!(names.contains(&"process_list"));
2038 assert!(names.contains(&"process_kill"));
2039 assert!(names.contains(&"env_get"));
2040 assert!(names.contains(&"env_list"));
2041 }
2042
2043 #[test]
2044 fn test_tools_for_network_capability() {
2045 let caps = vec![Capability::Network("*".into())];
2046 let tools = tools_for_capabilities(&caps);
2047 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2048 assert!(names.contains(&"web_fetch"));
2049 assert!(names.contains(&"web_search"));
2050 assert!(names.contains(&"http_request"));
2051 assert!(names.contains(&"http_post"));
2052 }
2053
2054 #[test]
2055 fn test_tools_for_memory_capability() {
2056 let caps = vec![Capability::Memory];
2057 let tools = tools_for_capabilities(&caps);
2058 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2059 assert!(names.contains(&"memory_store"));
2060 assert!(names.contains(&"memory_recall"));
2061 assert_eq!(names.len(), 2);
2062 }
2063
2064 #[test]
2065 fn test_tools_for_knowledge_graph_capability() {
2066 let caps = vec![Capability::KnowledgeGraph];
2067 let tools = tools_for_capabilities(&caps);
2068 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2069 assert!(names.contains(&"knowledge_add_entity"));
2070 assert!(names.contains(&"knowledge_add_relation"));
2071 assert!(names.contains(&"knowledge_query"));
2072 assert_eq!(names.len(), 3);
2073 }
2074
2075 #[test]
2076 fn test_tools_for_agent_spawn_capability() {
2077 let caps = vec![Capability::AgentSpawn];
2078 let tools = tools_for_capabilities(&caps);
2079 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2080 assert!(names.contains(&"agent_spawn"));
2081 assert_eq!(names.len(), 1);
2082 }
2083
2084 #[test]
2085 fn test_tools_for_agent_message_capability() {
2086 let caps = vec![Capability::AgentMessage];
2087 let tools = tools_for_capabilities(&caps);
2088 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2089 assert!(names.contains(&"agent_message"));
2090 assert!(names.contains(&"agent_list"));
2091 assert_eq!(names.len(), 2);
2092 }
2093
2094 #[test]
2095 fn test_tools_for_source_control_capability() {
2096 let caps = vec![Capability::SourceControl];
2097 let tools = tools_for_capabilities(&caps);
2098 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2099 assert!(names.contains(&"git_status"));
2100 assert!(names.contains(&"git_diff"));
2101 assert!(names.contains(&"git_log"));
2102 assert!(names.contains(&"git_commit"));
2103 assert!(names.contains(&"git_branch"));
2104 assert_eq!(names.len(), 5);
2105 }
2106
2107 #[test]
2108 fn test_tools_for_container_capability() {
2109 let caps = vec![Capability::Container];
2110 let tools = tools_for_capabilities(&caps);
2111 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2112 assert!(names.contains(&"docker_ps"));
2113 assert!(names.contains(&"docker_run"));
2114 assert!(names.contains(&"docker_build"));
2115 assert!(names.contains(&"docker_logs"));
2116 assert_eq!(names.len(), 4);
2117 }
2118
2119 #[test]
2120 fn test_tools_for_data_manipulation_capability() {
2121 let caps = vec![Capability::DataManipulation];
2122 let tools = tools_for_capabilities(&caps);
2123 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2124 assert!(names.contains(&"json_query"));
2125 assert!(names.contains(&"json_transform"));
2126 assert!(names.contains(&"yaml_parse"));
2127 assert!(names.contains(&"regex_match"));
2128 assert!(names.contains(&"regex_replace"));
2129 assert!(names.contains(&"text_diff"));
2130 assert!(names.contains(&"text_count"));
2131 assert_eq!(names.len(), 7);
2132 }
2133
2134 #[test]
2135 fn test_tools_for_schedule_capability() {
2136 let caps = vec![Capability::Schedule];
2137 let tools = tools_for_capabilities(&caps);
2138 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2139 assert!(names.contains(&"schedule_task"));
2140 assert!(names.contains(&"schedule_list"));
2141 assert!(names.contains(&"schedule_cancel"));
2142 assert_eq!(names.len(), 3);
2143 }
2144
2145 #[test]
2146 fn test_tools_for_code_analysis_capability() {
2147 let caps = vec![Capability::CodeAnalysis];
2148 let tools = tools_for_capabilities(&caps);
2149 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2150 assert!(names.contains(&"code_search"));
2151 assert!(names.contains(&"code_symbols"));
2152 assert_eq!(names.len(), 2);
2153 }
2154
2155 #[test]
2156 fn test_tools_for_archive_capability() {
2157 let caps = vec![Capability::Archive];
2158 let tools = tools_for_capabilities(&caps);
2159 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2160 assert!(names.contains(&"archive_create"));
2161 assert!(names.contains(&"archive_extract"));
2162 assert!(names.contains(&"archive_list"));
2163 assert_eq!(names.len(), 3);
2164 }
2165
2166 #[test]
2167 fn test_tools_for_template_capability() {
2168 let caps = vec![Capability::Template];
2169 let tools = tools_for_capabilities(&caps);
2170 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2171 assert!(names.contains(&"template_render"));
2172 assert_eq!(names.len(), 1);
2173 }
2174
2175 #[test]
2176 fn test_tools_for_crypto_capability() {
2177 let caps = vec![Capability::Crypto];
2178 let tools = tools_for_capabilities(&caps);
2179 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2180 assert!(names.contains(&"hash_compute"));
2181 assert!(names.contains(&"hash_verify"));
2182 assert_eq!(names.len(), 2);
2183 }
2184
2185 #[test]
2186 fn test_tools_for_empty_capabilities() {
2187 let caps: Vec<Capability> = vec![];
2188 let tools = tools_for_capabilities(&caps);
2189 assert!(tools.is_empty());
2190 }
2191
2192 #[test]
2193 fn test_tools_for_event_publish_returns_empty() {
2194 let caps = vec![Capability::EventPublish];
2196 let tools = tools_for_capabilities(&caps);
2197 assert!(tools.is_empty());
2198 }
2199
2200 #[test]
2205 fn test_push_unique_dedup() {
2206 let mut tools = Vec::new();
2207 push_unique(&mut tools, file_read());
2208 push_unique(&mut tools, file_read());
2209 push_unique(&mut tools, file_read());
2210 assert_eq!(tools.len(), 1);
2211 }
2212
2213 #[test]
2214 fn test_push_unique_different_tools() {
2215 let mut tools = Vec::new();
2216 push_unique(&mut tools, file_read());
2217 push_unique(&mut tools, file_write());
2218 push_unique(&mut tools, shell_exec());
2219 assert_eq!(tools.len(), 3);
2220 }
2221
2222 #[test]
2223 fn test_tools_for_multiple_capabilities_dedup() {
2224 let caps = vec![
2226 Capability::FileRead("src/**".into()),
2227 Capability::FileRead("tests/**".into()),
2228 ];
2229 let tools = tools_for_capabilities(&caps);
2230 let file_read_count = tools.iter().filter(|t| t.name == "file_read").count();
2231 assert_eq!(file_read_count, 1);
2232 }
2233
2234 #[test]
2239 fn test_all_tools_count() {
2240 let tools = all_tools();
2241 assert!(
2243 tools.len() >= 50,
2244 "expected at least 50 tools, got {}",
2245 tools.len()
2246 );
2247 }
2248
2249 #[test]
2250 fn test_all_tools_unique_names() {
2251 let tools = all_tools();
2252 let mut names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2253 let original_len = names.len();
2254 names.sort();
2255 names.dedup();
2256 assert_eq!(names.len(), original_len, "all_tools has duplicate names");
2257 }
2258
2259 #[test]
2260 fn test_all_tools_valid_schemas() {
2261 let tools = all_tools();
2262 for tool in &tools {
2263 assert_eq!(
2264 tool.input_schema["type"], "object",
2265 "tool {} has non-object schema",
2266 tool.name
2267 );
2268 assert!(!tool.name.is_empty(), "tool has empty name");
2269 assert!(
2270 !tool.description.is_empty(),
2271 "tool {} has empty description",
2272 tool.name
2273 );
2274 }
2275 }
2276
2277 #[test]
2278 fn test_a2a_delegate_tool_definition() {
2279 let tool = a2a_delegate();
2280 assert_eq!(tool.name, "a2a_delegate");
2281 assert_eq!(tool.category, ToolCategory::Agent);
2282 let required = tool.input_schema["required"]
2283 .as_array()
2284 .expect("required should be array");
2285 assert!(required.iter().any(|v| v == "agent_url"));
2286 assert!(required.iter().any(|v| v == "prompt"));
2287 }
2288
2289 #[test]
2290 fn test_tools_for_a2a_delegate_capability() {
2291 let caps = vec![Capability::A2ADelegate];
2292 let tools = tools_for_capabilities(&caps);
2293 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2294 assert!(names.contains(&"a2a_delegate"));
2295 assert_eq!(names.len(), 1);
2296 }
2297
2298 #[test]
2299 fn test_all_tools_includes_a2a_delegate() {
2300 let tools = all_tools();
2301 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2302 assert!(names.contains(&"a2a_delegate"));
2303 }
2304
2305 #[test]
2306 fn test_wasm_invoke_tool_definition() {
2307 let tool = wasm_invoke();
2308 assert_eq!(tool.name, "wasm_invoke");
2309 assert_eq!(tool.category, ToolCategory::Plugin);
2310 let required = tool.input_schema["required"]
2311 .as_array()
2312 .expect("required should be array");
2313 assert!(required.iter().any(|v| v == "plugin"));
2314 assert!(required.iter().any(|v| v == "function"));
2315 }
2316
2317 #[test]
2318 fn test_tools_for_plugin_invoke_capability() {
2319 let caps = vec![Capability::PluginInvoke];
2320 let tools = tools_for_capabilities(&caps);
2321 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2322 assert!(names.contains(&"wasm_invoke"));
2323 assert_eq!(names.len(), 1);
2324 }
2325
2326 #[test]
2327 fn test_all_tools_includes_wasm_invoke() {
2328 let tools = all_tools();
2329 let names: Vec<&str> = tools.iter().map(|t| t.name.as_str()).collect();
2330 assert!(names.contains(&"wasm_invoke"));
2331 }
2332}