gestura_core/tools/
registry.rs

1//! Tool Registry
2//!
3//! Provides an authoritative, deterministic inventory of Gestura's built-in
4//! tools. This is used by interactive agent commands (e.g. `/tools`) and for
5//! answering common "what tools do you have" questions without relying on an
6//! LLM response.
7//!
8//! Also provides dynamic configuration summary including MCP servers, devices,
9//! and current settings.
10
11use crate::config::AppConfig;
12
13// Static tool inventory is owned by the tools domain crate.
14// We re-export it here so callers can keep using
15// `gestura_core::tools::registry::{all_tools, find_tool, ...}`.
16pub use gestura_core_tools::registry::{
17    ToolDefinition, all_tools, code_tool_names, find_tool, is_code_tool_name,
18    looks_like_capabilities_question, looks_like_tools_question, render_tool_detail,
19    render_tools_overview,
20};
21
22/// Render a comprehensive capabilities overview including dynamic config.
23/// This shows built-in tools, MCP servers/tools, device status, and settings.
24pub fn render_capabilities(config: &AppConfig) -> String {
25    let mut out = String::new();
26
27    // Built-in tools
28    out.push_str("## Built-in Tools\n\n");
29    for t in all_tools() {
30        out.push_str(&format!("• **{}** - {}\n", t.name, t.summary));
31    }
32
33    // MCP Servers & Tools
34    out.push_str("\n## MCP Servers & Tools\n\n");
35    if config.mcp_servers.is_empty() {
36        out.push_str("_No MCP servers configured._\n");
37    } else {
38        for srv in &config.mcp_servers {
39            let status = if srv.enabled { "✓" } else { "○" };
40            out.push_str(&format!(
41                "• {} **{}** ({}) → `{}`\n",
42                status,
43                srv.name,
44                srv.transport,
45                srv.effective_uri()
46            ));
47        }
48    }
49
50    // MDH Pointers (data resources)
51    if !config.mdh_pointers.is_empty() {
52        out.push_str("\n## MDH Data Resources\n\n");
53        for (alias, uri) in &config.mdh_pointers {
54            out.push_str(&format!("• **{}** → `{}`\n", alias, uri));
55        }
56    }
57
58    // LLM Configuration
59    out.push_str("\n## LLM Configuration\n\n");
60    out.push_str(&format!("• **Primary Provider:** {}\n", config.llm.primary));
61    if let Some(ref openai) = config.llm.openai {
62        out.push_str(&format!("• **OpenAI Model:** {}\n", openai.model));
63    }
64    if let Some(ref anthropic) = config.llm.anthropic {
65        out.push_str(&format!("• **Anthropic Model:** {}\n", anthropic.model));
66    }
67    if let Some(ref grok) = config.llm.grok {
68        out.push_str(&format!("• **Grok Model:** {}\n", grok.model));
69    }
70    if let Some(ref ollama) = config.llm.ollama {
71        out.push_str(&format!(
72            "• **Ollama:** {} @ {}\n",
73            ollama.model, ollama.base_url
74        ));
75    }
76
77    // Voice Configuration
78    out.push_str("\n## Voice Configuration\n\n");
79    out.push_str(&format!("• **Provider:** {}\n", config.voice.provider));
80    if let Some(ref device) = config.voice.audio_device {
81        out.push_str(&format!("• **Audio Device:** {}\n", device));
82    }
83    if let Some(ref model_path) = config.voice.local_model_path {
84        out.push_str(&format!("• **Local Model:** {}\n", model_path));
85    }
86
87    // Device/Simulator Settings
88    out.push_str("\n## Device & Simulator Settings\n\n");
89    out.push_str(&format!(
90        "• **Developer Mode:** {}\n",
91        if config.developer.developer_mode {
92            "enabled"
93        } else {
94            "disabled"
95        }
96    ));
97    out.push_str(&format!(
98        "• **Simulators:** {}\n",
99        if config.developer.enable_simulators {
100            "enabled"
101        } else {
102            "disabled"
103        }
104    ));
105    if config.developer.enable_simulators {
106        out.push_str(&format!(
107            "• **Auto-discover Simulators:** {}\n",
108            config.developer.auto_discover_simulators
109        ));
110        out.push_str(&format!(
111            "• **Simulator Pattern:** {}\n",
112            config.developer.simulator.device_name_pattern
113        ));
114    }
115
116    // Hotkey
117    out.push_str("\n## System\n\n");
118    out.push_str(&format!("• **Hotkey:** {}\n", config.hotkey_listen));
119    out.push_str(&format!(
120        "• **Grace Period:** {}s\n",
121        config.grace_period_secs
122    ));
123
124    out.push_str("\n---\nUse `/tools <name>` for details on a specific tool.");
125    out
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn registry_contains_expected_tools() {
134        assert!(find_tool("file").is_some());
135        assert!(find_tool("shell").is_some());
136        assert!(find_tool("git").is_some());
137    }
138
139    #[test]
140    fn looks_like_tools_question_matches_common_phrases() {
141        assert!(looks_like_tools_question("what tools do you have?"));
142        assert!(looks_like_tools_question("list tools"));
143        assert!(looks_like_tools_question("/tools"));
144        assert!(!looks_like_tools_question("tell me a joke"));
145    }
146}