Skip to main content

oxios_kernel/tools/
registry.rs

1//! Tool metadata registry — known tools catalog for the frontend.
2//!
3//! This module provides a static catalog of all Oxios kernel tools
4//! and their metadata (name, description, category). The frontend
5//! settings UI uses this via `GET /api/tools/registry` to render
6//! the `allowed_tools` multi-select widget.
7//!
8//! The catalog is a superset of the tools registered in
9//! [`super::registration::register_tools_from_cspace_gated`].
10//! It includes all always-on tools and all CSpace-driven tools.
11//!
12//! MCP tools are dynamically registered per-server and are NOT
13//! included here. Users can type MCP tool names manually when
14//! customising the `allowed_tools` list.
15
16use serde::Serialize;
17
18/// Metadata for a single tool in the registry.
19#[derive(Debug, Clone, Serialize)]
20pub struct ToolMeta {
21    /// Tool identifier (matches `AgentTool::name()`).
22    pub name: &'static str,
23    /// Human-readable description key (frontend translates via i18n).
24    pub description_key: &'static str,
25    /// Category slug for UI grouping.
26    pub category: &'static str,
27}
28
29impl ToolMeta {
30    pub const fn new(
31        name: &'static str,
32        description_key: &'static str,
33        category: &'static str,
34    ) -> Self {
35        Self {
36            name,
37            description_key,
38            category,
39        }
40    }
41}
42
43/// Return the full static tool catalog.
44///
45/// This is the single source of truth for "which tools exist" shown
46/// in the frontend settings. The list mirrors
47/// [`super::registration`] — always-on tools + CSpace-driven tools.
48pub fn known_tools() -> &'static [ToolMeta] {
49    TOOL_CATALOG
50}
51
52const TOOL_CATALOG: &[ToolMeta] = &[
53    // ── Always-on tools (registered for every agent) ──────────────
54    ToolMeta::new("read", "tools.read", "fs"),
55    ToolMeta::new("write", "tools.write", "fs"),
56    ToolMeta::new("edit", "tools.edit", "fs"),
57    ToolMeta::new("grep", "tools.grep", "fs"),
58    ToolMeta::new("find", "tools.find", "fs"),
59    ToolMeta::new("ls", "tools.ls", "fs"),
60    ToolMeta::new("web_search", "tools.webSearch", "comms"),
61    ToolMeta::new("get_search_results", "tools.getSearchResults", "comms"),
62    // ── Kernel domain tools (CSpace-driven) ───────────────────────
63    ToolMeta::new("exec", "tools.exec", "exec"),
64    ToolMeta::new("browse", "tools.browse", "comms"),
65    ToolMeta::new("memory_read", "tools.memoryRead", "memory"),
66    ToolMeta::new("memory_write", "tools.memoryWrite", "memory"),
67    ToolMeta::new("memory_search", "tools.memorySearch", "memory"),
68    ToolMeta::new("project", "tools.project", "system"),
69    ToolMeta::new("kernel_agent", "tools.kernelAgent", "system"),
70    ToolMeta::new("a2a_delegate", "tools.a2aDelegate", "a2a"),
71    ToolMeta::new("a2a_send", "tools.a2aSend", "a2a"),
72    ToolMeta::new("a2a_query", "tools.a2aQuery", "a2a"),
73    ToolMeta::new("persona", "tools.persona", "system"),
74    ToolMeta::new("cron", "tools.cron", "system"),
75    ToolMeta::new("security", "tools.security", "system"),
76    ToolMeta::new("budget", "tools.budget", "system"),
77    ToolMeta::new("resource", "tools.resource", "system"),
78    ToolMeta::new("knowledge", "tools.knowledge", "system"),
79    ToolMeta::new("calendar", "tools.calendar", "system"),
80    ToolMeta::new("send_email", "tools.sendEmail", "comms"),
81];
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn catalog_is_populated() {
89        let tools = known_tools();
90        assert!(!tools.is_empty(), "tool catalog should not be empty");
91        assert!(
92            tools.iter().any(|t| t.name == "read"),
93            "read tool should be in catalog"
94        );
95        assert!(
96            tools.iter().any(|t| t.name == "exec"),
97            "exec tool should be in catalog"
98        );
99        assert!(
100            tools.iter().any(|t| t.name == "memory_read"),
101            "memory_read should be in catalog"
102        );
103    }
104
105    #[test]
106    fn all_tools_have_required_fields() {
107        for tool in known_tools() {
108            assert!(!tool.name.is_empty(), "tool name should not be empty");
109            assert!(
110                !tool.description_key.is_empty(),
111                "description_key should not be empty"
112            );
113            assert!(!tool.category.is_empty(), "category should not be empty");
114        }
115    }
116
117    #[test]
118    fn no_duplicate_names() {
119        let names: Vec<&str> = known_tools().iter().map(|t| t.name).collect();
120        let mut sorted = names.clone();
121        sorted.sort();
122        sorted.dedup();
123        assert_eq!(names.len(), sorted.len(), "duplicate tool names found");
124    }
125}