1use crate::agent::inference::{tool_metadata_for_name, ToolDefinition, ToolFunction};
2use serde_json::Value;
3
4fn make_tool(name: &str, description: &str, parameters: Value) -> ToolDefinition {
5 ToolDefinition {
6 tool_type: "function".into(),
7 function: ToolFunction {
8 name: name.into(),
9 description: description.into(),
10 parameters,
11 },
12 metadata: tool_metadata_for_name(name),
13 }
14}
15
16pub fn get_tools() -> Vec<ToolDefinition> {
18 let os = std::env::consts::OS;
19 let mut tools = vec![
20 make_tool(
21 "shell",
22 &format!(
23 "Execute a command in the host shell ({os}). \
24 Use this for building, testing, or system operations. \
25 Output is capped at 64KB. Prefer non-interactive commands."
26 ),
27 serde_json::json!({
28 "type": "object",
29 "properties": {
30 "command": {
31 "type": "string",
32 "description": "The command to run"
33 },
34 "reason": {
35 "type": "string",
36 "description": "For risky shell calls, explain what this command is verifying or changing."
37 },
38 "timeout_secs": {
39 "type": "integer",
40 "description": "Optional timeout in seconds (default 60)"
41 }
42 },
43 "required": ["command"]
44 }),
45 ),
46 make_tool(
47 "run_code",
48 "Execute a short JavaScript/TypeScript or Python snippet in a sandboxed subprocess. \
49 No network access, no filesystem escape, hard 10-second timeout. \
50 Use this to verify logic, test algorithms, compute values, or test functions \
51 when you need real output rather than a guess. \
52 ALWAYS include the `language` field — there is no default. \
53 \
54 JAVASCRIPT/TYPESCRIPT (language: \"javascript\"): \
55 Runs via Deno, NOT Node.js. `require()` does not exist — never use it. \
56 URL imports (e.g. from 'https://deno.land/...') are blocked — network is off. \
57 Use built-in Web APIs only: `crypto.subtle`, `TextEncoder`, `URL`, `atob`/`btoa`, etc. \
58 SHA-256 example: \
59 const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode('hello')); \
60 console.log([...new Uint8Array(buf)].map(b=>b.toString(16).padStart(2,'0')).join('')); \
61 \
62 PYTHON (language: \"python\"): \
63 Standard library is available. `hashlib`, `json`, `math`, `datetime`, `re`, `itertools` all work. \
64 `subprocess`, `socket`, `urllib`, `requests` are blocked. \
65 SHA-256 example: import hashlib; print(hashlib.sha256(b'hello').hexdigest()) \
66 \
67 Do NOT fall back to shell to run deno, python, or node — use this tool directly.",
68 serde_json::json!({
69 "type": "object",
70 "properties": {
71 "language": {
72 "type": "string",
73 "enum": ["javascript", "typescript", "python"],
74 "description": "The language to run. javascript/typescript requires Deno; python requires Python 3."
75 },
76 "code": {
77 "type": "string",
78 "description": "The code to execute. Keep it short and self-contained. Print results to stdout."
79 },
80 "timeout_seconds": {
81 "type": "integer",
82 "description": "Max execution time in seconds (default 10, max 60). Use higher values for longer computations."
83 }
84 },
85 "required": ["language", "code"]
86 }),
87 ),
88
89 make_tool(
90 "trace_runtime_flow",
91 "Return an authoritative read-only trace of Hematite runtime flow. \
92 Use this for architecture questions about keyboard input to final output, \
93 reasoning/specular separation, startup wiring, runtime subsystems, \
94 voice synthesis and Ctrl+T toggle, or \
95 session reset commands like /clear, /new, and /forget. Prefer this over guessing.",
96 serde_json::json!({
97 "type": "object",
98 "properties": {
99 "topic": {
100 "type": "string",
101 "enum": ["user_turn", "session_reset", "reasoning_split", "runtime_subsystems", "startup", "voice"],
102 "description": "Which verified runtime report to return. Use 'voice' for any question about Ctrl+T, voice toggle, or TTS pipeline. Use 'user_turn' for keyboard-to-output flow. Use 'session_reset' for /clear, /forget, /new. Use 'startup' for startup wiring. Use 'reasoning_split' for specular/thought routing. Use 'runtime_subsystems' for background subsystem overview."
103 },
104 "input": {
105 "type": "string",
106 "description": "Optional user input to label a normal user-turn trace"
107 },
108 "command": {
109 "type": "string",
110 "enum": ["/clear", "/new", "/forget", "all"],
111 "description": "Optional reset command when topic=session_reset"
112 }
113 },
114 "required": ["topic"]
115 }),
116 ),
117 make_tool(
118 "describe_toolchain",
119 "Return an authoritative read-only description of Hematite's actual tool surface and investigation strategy. \
120 Use this for tooling-discipline questions, best-tool selection, or read-only plans for tracing runtime behavior. \
121 Prefer this over improvising tool names or investigation steps from memory.",
122 serde_json::json!({
123 "type": "object",
124 "properties": {
125 "topic": {
126 "type": "string",
127 "enum": ["read_only_codebase", "user_turn_plan", "voice_latency_plan", "host_inspection_plan", "all"],
128 "description": "Which authoritative toolchain report to return"
129 },
130 "question": {
131 "type": "string",
132 "description": "Optional user question to label or tailor the read-only investigation plan"
133 }
134 }
135 }),
136 ),
137 make_tool(
138 "inspect_host",
139 "Return a structured read-only inspection of the current machine and environment. \
140 Prefer this over raw shell for questions about installed developer tools, PATH issues, package-manager and environment health, network state, service state, running processes, desktop items, Downloads size, open listening ports, repo health, or directory/disk summaries. \
141 For remediation questions phrased like 'how do I fix cargo not found', 'how do I fix port 3000 already in use', or 'how do I fix LM Studio not reachable', use topic=fix_plan instead of diagnosis-only topics like env_doctor, path, or ports. \
142 Use topic=summary for a compact host snapshot, topic=toolchains for common dev tool versions, topic=path for PATH analysis, topic=env_doctor for package-manager and PATH health, topic=fix_plan for structured remediation plans, topic=network for adapters/IPs/gateways/DNS, topic=services for service status and startup mode, \
143 topic=processes for running-process snapshots, topic=desktop or topic=downloads for known folders, topic=ports for listening endpoints, topic=repo_doctor for a structured workspace health report, \
144 and topic=directory or topic=disk for arbitrary paths.",
145 serde_json::json!({
146 "type": "object",
147 "properties": {
148 "topic": {
149 "type": "string",
150 "enum": ["summary", "toolchains", "path", "env_doctor", "fix_plan", "network", "services", "processes", "desktop", "downloads", "directory", "disk", "ports", "repo_doctor"],
151 "description": "Which structured host inspection to run."
152 },
153 "name": {
154 "type": "string",
155 "description": "Optional when topic=processes or topic=services. Case-insensitive substring filter for process or service names."
156 },
157 "issue": {
158 "type": "string",
159 "description": "Optional when topic=fix_plan. Plain-English issue description such as 'cargo not found', 'port 3000 already in use', or 'LM Studio not reachable on localhost:1234'."
160 },
161 "path": {
162 "type": "string",
163 "description": "Required when topic=directory. Optional for topic=disk or topic=repo_doctor. Absolute or relative path to inspect."
164 },
165 "port": {
166 "type": "integer",
167 "description": "Optional when topic=ports or topic=fix_plan. Filter the result to one listening TCP port or anchor a port-conflict fix plan."
168 },
169 "max_entries": {
170 "type": "integer",
171 "description": "Optional cap for listed entries. Defaults to 10 and is capped internally."
172 }
173 }
174 }),
175 ),
176 make_tool(
177 "run_hematite_maintainer_workflow",
178 "Run one of Hematite's known maintainer or release workflows with explicit approval. \
179 Prefer this over raw shell when the user explicitly asks to run one of Hematite's own scripts such as `clean.ps1`, `scripts/package-windows.ps1`, or `release.ps1`. \
180 Use workflow=clean for cleanup, workflow=package_windows for rebuilding the local Windows portable or installer, and workflow=release for the normal version bump/tag/push/publish flow. \
181 Keep this tool constrained to Hematite's own known workflows instead of inventing ad hoc shell commands or pretending to run arbitrary project scripts.",
182 serde_json::json!({
183 "type": "object",
184 "properties": {
185 "workflow": {
186 "type": "string",
187 "enum": ["clean", "package_windows", "release"],
188 "description": "Which known Hematite maintainer workflow to run."
189 },
190 "deep": {
191 "type": "boolean",
192 "description": "For workflow=clean. Also remove heavy build/runtime artifacts such as target/ and vein.db."
193 },
194 "reset": {
195 "type": "boolean",
196 "description": "For workflow=clean. Reset PLAN/TASK state in addition to normal cleanup."
197 },
198 "prune_dist": {
199 "type": "boolean",
200 "description": "For workflow=clean. Keep only the current Cargo.toml version under dist/."
201 },
202 "installer": {
203 "type": "boolean",
204 "description": "For workflow=package_windows. Also build the Windows installer."
205 },
206 "add_to_path": {
207 "type": "boolean",
208 "description": "For workflow=package_windows or workflow=release. Update the user PATH to the rebuilt portable."
209 },
210 "version": {
211 "type": "string",
212 "description": "For workflow=release. Exact semantic version such as 0.4.5."
213 },
214 "bump": {
215 "type": "string",
216 "enum": ["patch", "minor", "major"],
217 "description": "For workflow=release. Ask release.ps1 to calculate the next version."
218 },
219 "push": {
220 "type": "boolean",
221 "description": "For workflow=release. Push main and the new tag."
222 },
223 "skip_installer": {
224 "type": "boolean",
225 "description": "For workflow=release. Skip the Windows installer build."
226 },
227 "publish_crates": {
228 "type": "boolean",
229 "description": "For workflow=release. Publish hematite-cli to crates.io after a successful push."
230 },
231 "publish_voice_crate": {
232 "type": "boolean",
233 "description": "For workflow=release. Publish hematite-kokoros first, then hematite-cli."
234 }
235 },
236 "required": ["workflow"]
237 }),
238 ),
239 make_tool(
240 "run_workspace_workflow",
241 "Run an approval-gated workflow or script in the locked project workspace root. \
242 Use this for the current project's build, test, lint, fix, package.json scripts, just/task/make targets, explicit local script paths, or an exact workspace command. \
243 This tool is for the active workspace, not for Hematite's own maintainer scripts.",
244 serde_json::json!({
245 "type": "object",
246 "properties": {
247 "workflow": {
248 "type": "string",
249 "enum": ["build", "test", "lint", "fix", "package_script", "task", "just", "make", "script_path", "command"],
250 "description": "Which workspace workflow to run."
251 },
252 "name": {
253 "type": "string",
254 "description": "Required for workflow=package_script, task, just, or make. The script or target name."
255 },
256 "path": {
257 "type": "string",
258 "description": "Required for workflow=script_path. Relative path to a script inside the locked workspace root."
259 },
260 "command": {
261 "type": "string",
262 "description": "Required for workflow=command. Exact command to execute from the locked workspace root."
263 },
264 "timeout_ms": {
265 "type": "integer",
266 "description": "Optional timeout override in milliseconds."
267 }
268 },
269 "required": ["workflow"]
270 }),
271 ),
272 make_tool(
273 "read_file",
274 "Read the contents of a file. For large files, use 'offset' and 'limit' to navigate.",
275 serde_json::json!({
276 "type": "object",
277 "properties": {
278 "path": {
279 "type": "string",
280 "description": "Path to the file, relative to the project root"
281 },
282 "offset": {
283 "type": "integer",
284 "description": "Starting line number (0-indexed)"
285 },
286 "limit": {
287 "type": "integer",
288 "description": "Number of lines to read"
289 }
290 },
291 "required": ["path"]
292 }),
293 ),
294 make_tool(
295 "lsp_definitions",
296 "Get the precise definition location (file:line:char) for a symbol at a specific position. \
297 Use this to jump to function/struct source code accurately.",
298 serde_json::json!({
299 "type": "object",
300 "properties": {
301 "path": { "type": "string", "description": "File path" },
302 "line": { "type": "integer", "description": "0-indexed line" },
303 "character": { "type": "integer", "description": "0-indexed character" }
304 },
305 "required": ["path", "line", "character"]
306 }),
307 ),
308 make_tool(
309 "lsp_references",
310 "Find all locations where a symbol is used across the entire workspace. \
311 Use this to understand the impact of a refactor or discover internal API users.",
312 serde_json::json!({
313 "type": "object",
314 "properties": {
315 "path": { "type": "string", "description": "File path" },
316 "line": { "type": "integer", "description": "0-indexed line" },
317 "character": { "type": "integer", "description": "0-indexed character" }
318 },
319 "required": ["path", "line", "character"]
320 }),
321 ),
322 make_tool(
323 "lsp_hover",
324 "Get hover information (documentation, function signature, type details) for a symbol. \
325 Use this for rapid spatial awareness without opening every file.",
326 serde_json::json!({
327 "type": "object",
328 "properties": {
329 "path": { "type": "string", "description": "File path" },
330 "line": { "type": "integer", "description": "0-indexed line" },
331 "character": { "type": "integer", "description": "0-indexed character" }
332 },
333 "required": ["path", "line", "character"]
334 }),
335 ),
336 make_tool(
337 "lsp_rename_symbol",
338 "Rename a symbol project-wide using the Language Server. Ensures all references are updated safely.",
339 serde_json::json!({
340 "type": "object",
341 "properties": {
342 "path": { "type": "string", "description": "File path" },
343 "line": { "type": "integer", "description": "0-indexed line" },
344 "character": { "type": "integer", "description": "0-indexed character" },
345 "new_name": { "type": "string", "description": "The new name for the symbol" }
346 },
347 "required": ["path", "line", "character", "new_name"]
348 }),
349 ),
350 make_tool(
351 "lsp_get_diagnostics",
352 "Get a list of current compiler errors and warnings for a specific file. \
353 Use this to verify your code compiles and and to find exactly where errors are located.",
354 serde_json::json!({
355 "type": "object",
356 "properties": {
357 "path": { "type": "string", "description": "File path" }
358 },
359 "required": ["path"]
360 }),
361 ),
362 make_tool(
363 "vision_analyze",
364 "Send an image file (screenshot, diagram, or UI mockup) to the multimodal vision model for technical analysis. \
365 Use this to identify UI bugs, confirm visual states, or understand architectural diagrams.",
366 serde_json::json!({
367 "type": "object",
368 "properties": {
369 "path": { "type": "string", "description": "Absolute or relative path to the image file." },
370 "prompt": { "type": "string", "description": "The specific question or analysis request for the vision model." }
371 },
372 "required": ["path", "prompt"]
373 }),
374 ),
375 make_tool(
376 "patch_hunk",
377 "Replace a specific line range [start_line, end_line] with new content. \
378 This is the most precise way to edit code and avoids search string failures.",
379 serde_json::json!({
380 "type": "object",
381 "properties": {
382 "path": { "type": "string", "description": "File path" },
383 "start_line": { "type": "integer", "description": "Starting line (1-indexed)" },
384 "end_line": { "type": "integer", "description": "Ending line (inclusive)" },
385 "replacement": { "type": "string", "description": "The new content for this range" }
386 },
387 "required": ["path", "start_line", "end_line", "replacement"]
388 }),
389 ),
390 make_tool(
391 "multi_search_replace",
392 "Replace multiple existing code blocks in a single file with new content. \
393 Each hunk specifies an EXACT 'search' string and a 'replace' string. \
394 The 'search' string MUST exactly match the existing file contents (including whitespace). \
395 This is the safest and most reliable way to make multiple structural edits.",
396 serde_json::json!({
397 "type": "object",
398 "properties": {
399 "path": { "type": "string", "description": "File path" },
400 "hunks": {
401 "type": "array",
402 "items": {
403 "type": "object",
404 "properties": {
405 "search": { "type": "string", "description": "Exact existing text to find and replace" },
406 "replace": { "type": "string", "description": "The new replacement text" }
407 },
408 "required": ["search", "replace"]
409 }
410 }
411 },
412 "required": ["path", "hunks"]
413 }),
414 ),
415 make_tool(
416 "write_file",
417 "Write content to a file, creating it (and any parent dirs) if needed. \
418 Overwrites existing files.",
419 serde_json::json!({
420 "type": "object",
421 "properties": {
422 "path": { "type": "string", "description": "File path" },
423 "content": { "type": "string", "description": "Full file content to write" }
424 },
425 "required": ["path", "content"]
426 }),
427 ),
428 make_tool(
429 "research_web",
430 "Perform a zero-cost technical search using DuckDuckGo. \
431 Use this to find documentation, latest API changes, or solutions to complex errors \
432 when your internal knowledge is insufficient. Returns snippets and URLs.",
433 serde_json::json!({
434 "type": "object",
435 "properties": {
436 "query": { "type": "string", "description": "The technical search query" }
437 },
438 "required": ["query"]
439 }),
440 ),
441 make_tool(
442 "fetch_docs",
443 "Fetch a URL and convert it to clean Markdown. Use this to 'read' the documentation \
444 links found via research_web. This tool uses a proxy to bypass IP blocks.",
445 serde_json::json!({
446 "type": "object",
447 "properties": {
448 "url": { "type": "string", "description": "The URL of the documentation to fetch" }
449 },
450 "required": ["url"]
451 }),
452 ),
453 make_tool(
454 "edit_file",
455 "Edit a file by replacing an exact string with another. \
456 The 'search' string does NOT need perfectly matching indentation (it is fuzzy), \
457 but the non-whitespace text must match exactly. Use this for targeted edits.",
458 serde_json::json!({
459 "type": "object",
460 "properties": {
461 "path": { "type": "string", "description": "File path" },
462 "search": {
463 "type": "string",
464 "description": "The exact text to find (must match whitespace/indentation precisely)"
465 },
466 "replace": {
467 "type": "string",
468 "description": "The replacement text"
469 }
470 },
471 "required": ["path", "search", "replace"]
472 }),
473 ),
474 make_tool(
475 "auto_pin_context",
476 "Select 1-3 core files to 'Lock' into high-fidelity memory. \
477 Use this to ensure the most important architecture files \
478 are always visible during complex refactorings.",
479 serde_json::json!({
480 "type": "object",
481 "properties": {
482 "paths": {
483 "type": "array",
484 "items": { "type": "string" }
485 },
486 "reason": { "type": "string" }
487 },
488 "required": ["paths", "reason"]
489 }),
490 ),
491 make_tool(
492 "list_pinned",
493 "List all files currently pinned in the model's active context.",
494 serde_json::json!({
495 "type": "object",
496 "properties": {}
497 }),
498 ),
499 make_tool(
500 "list_files",
501 "List files in a directory, optionally filtered by extension.",
502 serde_json::json!({
503 "type": "object",
504 "properties": {
505 "path": {
506 "type": "string",
507 "description": "Directory to list (default: current dir)"
508 },
509 "extension": {
510 "type": "string",
511 "description": "Only return files with this extension, e.g. 'rs', 'toml' (no dot)"
512 }
513 },
514 "required": []
515 }),
516 ),
517 make_tool(
518 "grep_files",
519 "Search file contents for a regex pattern. Supports context lines, files-only mode, \
520 and pagination. Returns file:line:content format by default.",
521 serde_json::json!({
522 "type": "object",
523 "properties": {
524 "pattern": {
525 "type": "string",
526 "description": "Regex pattern to search for (case-insensitive by default)"
527 },
528 "path": {
529 "type": "string",
530 "description": "Directory to search (default: current dir)"
531 },
532 "extension": {
533 "type": "string",
534 "description": "Only search files with this extension, e.g. 'rs'"
535 },
536 "mode": {
537 "type": "string",
538 "enum": ["content", "files_only"],
539 "description": "'content' (default) returns matching lines; 'files_only' returns only filenames"
540 },
541 "context": {
542 "type": "integer",
543 "description": "Lines of context before AND after each match (like rg -C)"
544 },
545 "before": {
546 "type": "integer",
547 "description": "Lines of context before each match (overrides context)"
548 },
549 "after": {
550 "type": "integer",
551 "description": "Lines of context after each match (overrides context)"
552 },
553 "head_limit": {
554 "type": "integer",
555 "description": "Max hunks (or files in files_only) to return (default: 50)"
556 },
557 "offset": {
558 "type": "integer",
559 "description": "Skip first N hunks/files - for pagination (default: 0)"
560 }
561 },
562 "required": ["pattern"]
563 }),
564 ),
565 make_tool(
566 "git_commit",
567 "Stage all changes (git add -A) and create a commit. You MUST use 'Conventional Commits' (e.g. 'feat: description').",
568 serde_json::json!({
569 "type": "object",
570 "properties": {
571 "message": { "type": "string", "description": "Commit message (Conventional Commit style)" }
572 },
573 "required": ["message"]
574 }),
575 ),
576 make_tool(
577 "git_push",
578 "Push current branched changes to the remote origin. Requires an existing remote connection.",
579 serde_json::json!({
580 "type": "object",
581 "properties": {},
582 "required": []
583 }),
584 ),
585 make_tool(
586 "git_remote",
587 "View or manage git remotes. Use this for onboarding to GitHub/GitLab services.",
588 serde_json::json!({
589 "type": "object",
590 "properties": {
591 "action": {
592 "type": "string",
593 "enum": ["list", "add", "remove"],
594 "description": "Operation to perform"
595 },
596 "name": { "type": "string", "description": "Remote name (e.g. origin)" },
597 "url": { "type": "string", "description": "Remote URL (for 'add' action)" }
598 },
599 "required": ["action"]
600 }),
601 ),
602 make_tool(
603 "git_onboarding",
604 "High-level wizard to connect this repository to a remote host (GitHub/GitLab). \
605 Handles adding the remote and performing the initial tracking push in one step.",
606 serde_json::json!({
607 "type": "object",
608 "properties": {
609 "url": { "type": "string", "description": "The remote repository URL (HTTPS or SSH)" },
610 "name": { "type": "string", "description": "The remote name (default: origin)" },
611 "push": { "type": "boolean", "description": "Whether to perform an initial push to establish tracking (default: false)" }
612 },
613 "required": ["url"]
614 }),
615 ),
616 make_tool(
617 "verify_build",
618 "Run project verification for build, test, lint, or fix workflows. \
619 Prefer per-project verify profiles from `.hematite/settings.json`, and fall back to \
620 auto-detected defaults when no profile is configured. Returns BUILD OK or BUILD FAILED \
621 with command output. ALWAYS call this after scaffolding a new project or making structural changes.",
622 serde_json::json!({
623 "type": "object",
624 "properties": {
625 "action": {
626 "type": "string",
627 "enum": ["build", "test", "lint", "fix"],
628 "description": "Which verification action to run. Defaults to build."
629 },
630 "profile": {
631 "type": "string",
632 "description": "Optional named verify profile from `.hematite/settings.json`."
633 },
634 "timeout_secs": {
635 "type": "integer",
636 "description": "Optional timeout override for this verification run."
637 }
638 }
639 }),
640 ),
641 make_tool(
642 "git_worktree",
643 "Manage Git worktrees - isolated working directories on separate branches. \
644 Use 'add' to create a safe sandbox for risky/experimental work, \
645 'list' to see all worktrees, 'remove' to clean up, 'prune' to remove stale entries.",
646 serde_json::json!({
647 "type": "object",
648 "properties": {
649 "action": {
650 "type": "string",
651 "enum": ["list", "add", "remove", "prune"],
652 "description": "Worktree operation to perform"
653 },
654 "path": {
655 "type": "string",
656 "description": "Directory path for the new worktree (required for add/remove)"
657 },
658 "branch": {
659 "type": "string",
660 "description": "Branch name for the worktree (add only; defaults to path basename)"
661 }
662 },
663 "required": ["action"]
664 }),
665 ),
666 make_tool(
667 "clarify",
668 "Ask the user a clarifying question when you genuinely cannot proceed without \
669 more information. Use this ONLY when you are blocked and cannot make a \
670 reasonable assumption. Do NOT use it to ask permission - just act.",
671 serde_json::json!({
672 "type": "object",
673 "properties": {
674 "question": {
675 "type": "string",
676 "description": "The specific question to ask the user"
677 }
678 },
679 "required": ["question"]
680 }),
681 ),
682 make_tool(
683 "manage_tasks",
684 "Manage the persistent task ledger in .hematite/TASK.md. Use this to track long-term goals across restarts.",
685 crate::tools::tasks::get_tasks_params(),
686 ),
687 make_tool(
688 "maintain_plan",
689 "Document the architectural strategy and session blueprint in .hematite/PLAN.md. Use this to maintain context across restarts.",
690 crate::tools::plan::get_plan_params(),
691 ),
692 make_tool(
693 "generate_walkthrough",
694 "Generate a final session report in .hematite/WALKTHROUGH.md including achievements and verification results.",
695 crate::tools::plan::get_walkthrough_params(),
696 ),
697 make_tool(
698 "swarm",
699 "Delegate high-volume parallel tasks to a swarm of background workers. \
700 Use this for large-scale refactors, multi-file research, or parallel documentation updates. \
701 You must provide a 'tasks' array where each task has an 'id', 'target' (file), and 'instruction'.",
702 serde_json::json!({
703 "type": "object",
704 "properties": {
705 "tasks": {
706 "type": "array",
707 "items": {
708 "type": "object",
709 "properties": {
710 "id": { "type": "string" },
711 "target": { "type": "string", "description": "Target file or directory" },
712 "instruction": { "type": "string", "description": "Specific task for this worker" }
713 },
714 "required": ["id", "target", "instruction"]
715 }
716 },
717 "max_workers": {
718 "type": "integer",
719 "description": "Max parallel workers (default 3, auto-throttled by hardware)",
720 "default": 3
721 }
722 },
723 "required": ["tasks"]
724 }),
725 ),
726 ];
727
728 let lsp_defs = crate::tools::lsp_tools::get_lsp_definitions();
729 tools.push(make_tool(
730 "lsp_search_symbol",
731 "Find the location (file/line) of any function, struct, or variable in the entire project workspace. \
732 This is the fastest 'Golden Path' for navigating to a symbol by name.",
733 serde_json::json!({
734 "type": "object",
735 "properties": {
736 "query": { "type": "string", "description": "The name of the symbol to find (e.g. 'initialize_mcp')" }
737 },
738 "required": ["query"]
739 }),
740 ));
741 for def in lsp_defs {
742 let name = def["name"].as_str().unwrap();
743 tools.push(ToolDefinition {
744 tool_type: "function".into(),
745 function: ToolFunction {
746 name: name.into(),
747 description: def["description"].as_str().unwrap().into(),
748 parameters: def["parameters"].clone(),
749 },
750 metadata: tool_metadata_for_name(name),
751 });
752 }
753
754 tools
755}
756
757pub async fn dispatch_builtin_tool(name: &str, args: &Value) -> Result<String, String> {
758 match name {
759 "shell" => crate::tools::shell::execute(args).await,
760 "run_code" => crate::tools::code_sandbox::execute(args).await,
761 "trace_runtime_flow" => crate::tools::runtime_trace::trace_runtime_flow(args).await,
762 "describe_toolchain" => crate::tools::toolchain::describe_toolchain(args).await,
763 "inspect_host" => crate::tools::host_inspect::inspect_host(args).await,
764 "run_hematite_maintainer_workflow" => {
765 crate::tools::repo_script::run_hematite_maintainer_workflow(args).await
766 }
767 "run_workspace_workflow" => crate::tools::workspace_workflow::run_workspace_workflow(args).await,
768 "read_file" => crate::tools::file_ops::read_file(args).await,
769 "inspect_lines" => crate::tools::file_ops::inspect_lines(args).await,
770 "write_file" => crate::tools::file_ops::write_file(args).await,
771 "edit_file" => crate::tools::file_ops::edit_file(args).await,
772 "patch_hunk" => crate::tools::file_ops::patch_hunk(args).await,
773 "multi_search_replace" => crate::tools::file_ops::multi_search_replace(args).await,
774 "list_files" => crate::tools::file_ops::list_files(args).await,
775 "grep_files" => crate::tools::file_ops::grep_files(args).await,
776 "git_commit" => crate::tools::git::execute(args).await,
777 "git_push" => crate::tools::git::execute_push(args).await,
778 "git_remote" => crate::tools::git::execute_remote(args).await,
779 "git_onboarding" => crate::tools::git_onboarding::execute(args).await,
780 "verify_build" => crate::tools::verify_build::execute(args).await,
781 "git_worktree" => crate::tools::git::execute_worktree(args).await,
782 "health" => crate::tools::health::execute(args).await,
783 "research_web" => crate::tools::research::execute_search(args).await,
784 "fetch_docs" => crate::tools::research::execute_fetch(args).await,
785 "manage_tasks" => crate::tools::tasks::manage_tasks(args).await,
786 "maintain_plan" => crate::tools::plan::maintain_plan(args).await,
787 "generate_walkthrough" => crate::tools::plan::generate_walkthrough(args).await,
788 "clarify" => {
789 let q = args.get("question").and_then(|v| v.as_str()).unwrap_or("?");
790 Ok(format!("[clarify] {q}"))
791 }
792 "vision_analyze" => Err(
793 "Tool 'vision_analyze' must be dispatched by ConversationManager (it requires hardware engine access)."
794 .into(),
795 ),
796 other => {
797 if other.contains('.') || other.contains('/') || other.contains('\\') {
798 Err(format!(
799 "'{}' is a PATH, not a tool. You correctly identified the location, but you MUST use `read_file` or `list_files` (internal) or `powershell` (external) to access it.",
800 other
801 ))
802 } else if matches!(other.to_lowercase().as_str(), "hematite" | "assistant" | "ai") {
803 Err(format!(
804 "'{}' is YOUR IDENTITY, not a tool. Use list_files or read_file to explore the codebase.",
805 other
806 ))
807 } else if matches!(
808 other.to_lowercase().as_str(),
809 "thought" | "think" | "reasoning" | "thinking" | "internal"
810 ) {
811 Err(format!(
812 "'{}' is NOT a tool - it is a reasoning tag. Output your answer as plain text after your <think> block.",
813 other
814 ))
815 } else {
816 Err(format!("Unknown tool: '{}'", other))
817 }
818 }
819 }
820}