Skip to main content

oxios_kernel/tools/
kernel_bridge.rs

1//! KernelToolProvider bridge — plugs oxios kernel tools into oxi-sdk agent builder.
2//!
3//! Implements [`oxi_sdk::KernelToolProvider`] so that oxios kernel tools
4//! (exec, memory, project, etc.) can be registered into the SDK's
5
6use std::sync::Arc;
7
8use oxi_sdk::SearchCache;
9use oxi_sdk::ToolRegistry;
10use oxi_sdk::{
11    KernelToolContext as SdkKernelToolContext, KernelToolProvider as SdkKernelToolProvider,
12};
13
14use crate::KernelHandle;
15use crate::tools::registration::register_always_on;
16
17/// Bridges all oxios kernel tools into the oxi-sdk agent builder.
18pub struct OxiosKernelBridge {
19    kernel_handle: Arc<KernelHandle>,
20    search_cache: Arc<SearchCache>,
21}
22
23impl OxiosKernelBridge {
24    /// Create a new bridge with the given kernel handle.
25    pub fn new(kernel_handle: Arc<KernelHandle>) -> Self {
26        Self {
27            kernel_handle,
28            search_cache: Arc::new(SearchCache::new()),
29        }
30    }
31
32    /// Create a new bridge with a pre-built search cache.
33    pub fn with_cache(kernel_handle: Arc<KernelHandle>, search_cache: Arc<SearchCache>) -> Self {
34        Self {
35            kernel_handle,
36            search_cache,
37        }
38    }
39}
40
41impl SdkKernelToolProvider for OxiosKernelBridge {
42    fn tool_names(&self) -> Vec<&str> {
43        #[allow(unused_mut)]
44        let mut names = vec![
45            // Always-on file + web-search tools (registration::register_always_on)
46            "read",
47            "write",
48            "edit",
49            "grep",
50            "find",
51            "ls",
52            "web_search",
53            "get_search_results",
54            // Kernel domain tools (builtin::register_all_kernel_tools)
55            "exec",
56            "memory_read",
57            "memory_write",
58            "memory_search",
59            "subagent",
60            "project",
61            "mount",
62            "kernel_agent",
63            "persona",
64            "cron",
65            "security",
66            "budget",
67            "resource",
68            "a2a_delegate",
69            "a2a_send",
70            "a2a_query",
71            "knowledge",
72            "ask_user",
73            "marketplace",
74            "skill_forge",
75            "calendar", // conditional on [calendar] config
76            "send_email",
77            // NOTE: MCP tools use a dynamic `full_name` and are enumerated
78            // per-server at registration time, so they are not listed here.
79        ];
80
81        // Headless browser — pure-Rust oxibrowser-core browse suite.
82        #[cfg(feature = "native-browser")]
83        names.extend([
84            "browse",
85            "browse_extract",
86            "browse_session",
87            "browse_script",
88        ]);
89
90        names
91    }
92
93    fn register_tools(&self, registry: &ToolRegistry, context: &SdkKernelToolContext) {
94        // 1. Always-on file tools + web search
95        register_always_on(registry, Arc::clone(&self.search_cache));
96
97        // 2. Kernel domain tools via KernelHandle
98        crate::tools::builtin::register_all_kernel_tools(
99            registry,
100            &self.kernel_handle,
101            &context.agent_id,
102        );
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    /// Verify that `tool_names()` returns the expected number of tool names.
111    #[tokio::test]
112    async fn test_tool_names_length() {
113        let tmp = tempfile::tempdir().unwrap();
114        let base = tmp.path().to_path_buf();
115
116        // Build a minimal KernelHandle for testing
117        let state_store =
118            Arc::new(crate::state_store::StateStore::new(base.join("workspace")).unwrap());
119
120        let kernel = Arc::new(crate::KernelHandle::new(
121            crate::StateApi::new(state_store.clone()),
122            crate::AgentApi::new(
123                Arc::new(crate::supervisor::NoOpSupervisor),
124                Arc::new(crate::budget::BudgetManager::new()),
125                Arc::new(crate::memory::MemoryManager::new(state_store.clone())),
126                None,
127            ),
128            crate::SecurityApi::new(
129                Arc::new(parking_lot::Mutex::new(crate::auth::AuthManager::new())),
130                Arc::new(oxi_sdk::observability::AuditTrail::new(100)),
131                Arc::new(parking_lot::Mutex::new(
132                    crate::access_manager::AccessManager::new(),
133                )),
134                state_store.clone(),
135            ),
136            crate::PersonaApi::new(Arc::new(crate::persona::PersonaManager::new())),
137            crate::ExtensionApi::new(Arc::new(crate::skill::SkillManager::new(
138                base.join("skills"),
139                base.join("share/skills"),
140            ))),
141            crate::McpApi::new(Arc::new(crate::mcp::McpBridge::new())),
142            crate::InfraApi::new(
143                Arc::new(crate::git_layer::GitLayer::new(base.join("git"), false).unwrap()),
144                Arc::new(crate::cron::CronScheduler::new(state_store.clone(), 60)),
145                Arc::new(crate::resource_monitor::ResourceMonitor::new(60, 60)),
146                crate::event_bus::EventBus::new(256),
147                crate::OxiosConfig::default(),
148                std::time::Instant::now(),
149                std::sync::Arc::new(crate::tools::PendingToolApprovals::new()),
150                std::sync::Arc::new(crate::tools::PendingAskUser::new()),
151                std::sync::Arc::new(parking_lot::RwLock::new(
152                    crate::approval::ApprovalConfig::default(),
153                )),
154                std::sync::Arc::new(crate::tools::PendingPathAccess::new()),
155            ),
156            None,
157            crate::ExecApi::new(
158                Arc::new(parking_lot::RwLock::new(
159                    crate::config::ExecConfig::default(),
160                )),
161                Arc::new(parking_lot::Mutex::new(
162                    crate::access_manager::AccessManager::new(),
163                )),
164            ),
165            crate::A2aApi::new(Arc::new(crate::a2a::A2AProtocol::new(
166                crate::event_bus::EventBus::new(256),
167            ))),
168            crate::EngineApi::new(
169                Arc::new(parking_lot::RwLock::new(crate::OxiosConfig::default())),
170                base.join("config.toml"),
171                Arc::new(crate::RoutingStats::new()),
172                Arc::new(crate::engine::EngineHandle::new(Arc::new(
173                    crate::OxiosEngine::new("anthropic/claude-sonnet-4-20250514"),
174                ))),
175            ),
176            Arc::new(oxios_markdown::KnowledgeBase::new(base.join("knowledge")).unwrap()),
177            Arc::new(
178                crate::kernel_handle::KnowledgeLens::new(
179                    Arc::new(
180                        oxios_markdown::KnowledgeBase::new(base.join("knowledge_lens")).unwrap(),
181                    ),
182                    Arc::new(crate::memory::MemoryManager::new(state_store.clone())),
183                )
184                .unwrap(),
185            ),
186            crate::MarketplaceApi::new(
187                Arc::new(crate::skill::clawhub::ClawHubInstaller::new(
188                    base.join("skills"),
189                    base.join("workspace"),
190                    None,
191                )),
192                Arc::new(
193                    crate::skill::clawhub::ClawHubClient::new(None).expect("valid ClawHub client"),
194                ),
195                Arc::new(crate::skill::skills_sh::SkillsShInstaller::new(
196                    base.join("skills"),
197                    None,
198                    None,
199                )),
200                Arc::new(
201                    crate::skill::skills_sh::SkillsShClient::new(None, None)
202                        .expect("valid Skills.sh client"),
203                ),
204            ),
205            None,                                     // calendar (not configured in test)
206            Arc::new(parking_lot::RwLock::new(None)), // email (not configured in test)
207        ));
208
209        let bridge = OxiosKernelBridge::new(kernel);
210
211        let names = bridge.tool_names();
212        // 8 always-on (file ops + web_search/get_search_results)
213        // + 22 kernel domain (exec, memory×3, subagent, project, mount,
214        //   kernel_agent, persona, cron, security, budget, resource, a2a×3,
215        //   knowledge, ask_user, marketplace, skill_forge, calendar, send_email)
216        // = 30. MCP tools are dynamic (per-server) and excluded.
217        // +4 browse tools when native-browser feature is enabled.
218        #[allow(unused_mut)]
219        let mut expected = 30_usize;
220        #[cfg(feature = "native-browser")]
221        {
222            expected += 4; // browse, browse_extract, browse_session, browse_script
223        }
224        assert_eq!(
225            names.len(),
226            expected,
227            "expected {expected} tools, got {:?}",
228            names
229        );
230    }
231}