Skip to main content

oxicode_sdk/
kernel_bridge.rs

1//! Kernel tool bridge — allows oxios kernel tools to be plugged into the SDK.
2//!
3//! oxios-kernel implements `KernelToolProvider` to register its tools
4//! (exec, memory, browser, persona, etc.) into the SDK's agent builder.
5
6use oxicode_agent::ToolRegistry;
7use std::collections::HashMap;
8use std::path::PathBuf;
9
10/// Context provided to kernel tool providers during registration.
11///
12/// Contains the metadata that kernel tools need to operate correctly
13/// within an oxios agent session.
14///
15/// The `metadata` map is an extension point for kernel-specific data
16/// (e.g., `space_id`, `cspace_name`, `seed_id`) without requiring SDK
17/// changes for every new field.
18#[derive(Debug, Clone)]
19pub struct KernelToolContext {
20    /// Agent's workspace directory.
21    pub workspace_dir: PathBuf,
22    /// oxios agent identifier.
23    pub agent_id: String,
24    /// Session identifier, if available.
25    pub session_id: Option<String>,
26    /// CSpace-based permission list.
27    pub permissions: Vec<String>,
28    /// Extension metadata for kernel-specific data.
29    ///
30    /// Keys are conventionally lowercase snake_case (e.g., `"space_id"`,
31    /// `"cspace_name"`, `"seed_id"`). Consumers should use
32    /// [`Self::get_meta`] / [`Self::get_meta_str`] for typed access.
33    pub metadata: HashMap<String, serde_json::Value>,
34}
35
36impl KernelToolContext {
37    /// Create a new context with the given workspace and agent ID.
38    pub fn new(workspace_dir: impl Into<PathBuf>, agent_id: impl Into<String>) -> Self {
39        Self {
40            workspace_dir: workspace_dir.into(),
41            agent_id: agent_id.into(),
42            session_id: None,
43            permissions: Vec::new(),
44            metadata: HashMap::new(),
45        }
46    }
47
48    /// Set the session ID.
49    pub fn with_session(mut self, session_id: impl Into<String>) -> Self {
50        self.session_id = Some(session_id.into());
51        self
52    }
53
54    /// Set the permissions list.
55    pub fn with_permissions(mut self, permissions: Vec<String>) -> Self {
56        self.permissions = permissions;
57        self
58    }
59
60    /// Add an extension metadata entry.
61    ///
62    /// # Example
63    ///
64    /// ```rust
65    /// use oxicode_sdk::KernelToolContext;
66    ///
67    /// let ctx = KernelToolContext::new("/workspace", "agent-001")
68    ///     .with_meta("space_id", serde_json::json!("test-space"))
69    ///     .with_meta("cspace_name", serde_json::json!("full"));
70    /// ```
71    pub fn with_meta(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
72        self.metadata.insert(key.into(), value);
73        self
74    }
75
76    /// Get a metadata value by key.
77    pub fn get_meta(&self, key: &str) -> Option<&serde_json::Value> {
78        self.metadata.get(key)
79    }
80
81    /// Get a metadata value as a string.
82    pub fn get_meta_str(&self, key: &str) -> Option<&str> {
83        self.metadata.get(key).and_then(|v| v.as_str())
84    }
85}
86
87/// Trait for providing kernel-level tools to the SDK.
88///
89/// oxios-kernel implements this trait to bridge its native tools
90/// (exec, memory, browser, persona, etc.) into the oxicode agent tool registry.
91///
92/// # Example
93///
94/// ```ignore
95/// use oxicode_sdk::{KernelToolProvider, KernelToolContext};
96/// use oxicode_agent::ToolRegistry;
97///
98/// struct MyKernelBridge;
99///
100/// impl KernelToolProvider for MyKernelBridge {
101///     fn tool_names(&self) -> Vec<&str> {
102///         vec!["exec", "memory"]
103///     }
104///
105///     fn register_tools(&self, registry: &ToolRegistry, ctx: &KernelToolContext) {
106///         registry.register(ExecTool::new(ctx.agent_id.clone()));
107///         registry.register(MemoryTool::new(ctx.agent_id.clone()));
108///     }
109/// }
110/// ```
111pub trait KernelToolProvider: Send + Sync {
112    /// Return the names of tools this provider will register.
113    fn tool_names(&self) -> Vec<&str>;
114
115    /// Register tools into the given registry.
116    ///
117    /// The `context` provides agent-specific metadata (workspace, agent_id,
118    /// session, permissions) that tools may need at initialization time.
119    fn register_tools(&self, registry: &ToolRegistry, context: &KernelToolContext);
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use async_trait::async_trait;
126    use oxicode_agent::{AgentTool, AgentToolResult, ToolContext, ToolError};
127    use serde_json::Value;
128
129    struct MockKernelTool {
130        name: String,
131    }
132
133    #[async_trait]
134    impl AgentTool for MockKernelTool {
135        fn name(&self) -> &str {
136            &self.name
137        }
138        fn label(&self) -> &str {
139            "mock"
140        }
141        fn description(&self) -> &str {
142            "A mock kernel tool"
143        }
144        fn parameters_schema(&self) -> Value {
145            serde_json::json!({"type": "object", "properties": {}})
146        }
147
148        async fn execute(
149            &self,
150            _tool_call_id: &str,
151            _params: Value,
152            _signal: Option<tokio::sync::oneshot::Receiver<()>>,
153            _ctx: &ToolContext,
154        ) -> Result<AgentToolResult, ToolError> {
155            Ok(AgentToolResult::success("mock result"))
156        }
157    }
158
159    struct MockKernelBridge;
160
161    impl KernelToolProvider for MockKernelBridge {
162        fn tool_names(&self) -> Vec<&str> {
163            vec!["exec", "memory"]
164        }
165
166        fn register_tools(&self, registry: &ToolRegistry, ctx: &KernelToolContext) {
167            registry.register(MockKernelTool {
168                name: format!("exec_{}", ctx.agent_id),
169            });
170            registry.register(MockKernelTool {
171                name: format!("memory_{}", ctx.agent_id),
172            });
173        }
174    }
175
176    #[test]
177    fn test_kernel_tool_context_builder() {
178        let ctx = KernelToolContext::new("/workspace", "agent-001")
179            .with_session("sess-123")
180            .with_permissions(vec!["read".into(), "write".into()]);
181
182        assert_eq!(ctx.workspace_dir, PathBuf::from("/workspace"));
183        assert_eq!(ctx.agent_id, "agent-001");
184        assert_eq!(ctx.session_id, Some("sess-123".to_string()));
185        assert_eq!(ctx.permissions, vec!["read", "write"]);
186    }
187
188    #[test]
189    fn test_kernel_bridge_registers_tools() {
190        let bridge = MockKernelBridge;
191        let registry = ToolRegistry::new();
192        let ctx = KernelToolContext::new("/workspace", "agent-001");
193
194        bridge.register_tools(&registry, &ctx);
195
196        let names = registry.names();
197        assert!(names.contains(&"exec_agent-001".to_string()));
198        assert!(names.contains(&"memory_agent-001".to_string()));
199    }
200}