Skip to main content

apollo/tools/
toolsets.rs

1//! Toolset classification and filtering.
2
3use crate::config::ToolsetConfig;
4
5pub const CORE_TOOLSET_GROUPS: &[&str] = &["runtime", "fs", "memory", "sessions", "misc"];
6
7pub fn toolset_for_tool(name: &str) -> &'static str {
8    match name {
9        // Delegating a task to the worker runs commands on this machine, so it
10        // belongs with `exec` rather than in the "misc" catch-all.
11        "exec" | "telekinesis" | "build_runner" => "runtime",
12        "Read" | "Write" | "Edit" => "fs",
13        "web_search" | "web_fetch" | "browser" => "web",
14        "memory_search" | "memory_get" | "session_search" | "brain_search" | "brain_query"
15        | "brain_put" | "brain_get" => "memory",
16        "doctor" => "sessions",
17        "message" => "messaging",
18        "skill_manager" => "skills",
19        "praefectus" => "desktop",
20        "mcp" | "create_tool" | "list_custom_tools" | "vibemania" => "advanced",
21        "generate_image" | "text_to_speech" | "speech_to_text" => "media",
22        _ => "misc",
23    }
24}
25
26pub fn expand_package(name: &str) -> Vec<&'static str> {
27    match name.trim().to_ascii_lowercase().as_str() {
28        "web" => vec!["web"],
29        "browser" => vec!["browser"],
30        "skills" => vec!["skills"],
31        "advanced" => vec!["advanced"],
32        "desktop" => vec!["desktop"],
33        "media" => vec!["media"],
34        "apollo-live" | "live" => vec!["web", "browser", "skills", "advanced", "desktop", "media"],
35        "core" | "default" => CORE_TOOLSET_GROUPS.to_vec(),
36        _ => vec![],
37    }
38}
39
40pub fn apply_package_manifest(toolsets: &mut ToolsetConfig, packages: &[String]) {
41    if packages.is_empty() {
42        return;
43    }
44    for g in CORE_TOOLSET_GROUPS {
45        let s = (*g).to_string();
46        if !toolsets.enabled.contains(&s) {
47            toolsets.enabled.push(s);
48        }
49    }
50    for pkg in packages {
51        for g in expand_package(pkg) {
52            let s = g.to_string();
53            if !toolsets.enabled.contains(&s) {
54                toolsets.enabled.push(s);
55            }
56        }
57    }
58}
59
60pub fn is_tool_enabled(name: &str, config: &ToolsetConfig) -> bool {
61    let toolset = toolset_for_tool(name);
62    let enabled = config.enabled.is_empty()
63        || config
64            .enabled
65            .iter()
66            .any(|entry| entry == name || entry == toolset);
67    let disabled = config
68        .disabled
69        .iter()
70        .any(|entry| entry == name || entry == toolset);
71    enabled && !disabled
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn toolset_filtering_allows_by_group() {
80        let cfg = ToolsetConfig {
81            enabled: vec!["memory".to_string()],
82            disabled: Vec::new(),
83        };
84        assert!(is_tool_enabled("memory_search", &cfg));
85        assert!(!is_tool_enabled("exec", &cfg));
86    }
87
88    #[test]
89    fn package_live_enables_web() {
90        let mut cfg = ToolsetConfig {
91            enabled: vec![],
92            disabled: Vec::new(),
93        };
94        apply_package_manifest(&mut cfg, &["live".to_string()]);
95        assert!(is_tool_enabled("web_search", &cfg));
96        assert!(cfg.enabled.iter().any(|e| e == "runtime"));
97    }
98}