Skip to main content

oxi_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 oxi_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    /// ```ignore
65    /// let ctx = KernelToolContext::new("/workspace", "agent-001")
66    ///     .with_meta("space_id", serde_json::json!(uuid::Uuid::new_v4()))
67    ///     .with_meta("cspace_name", serde_json::json!("full"));
68    /// ```
69    pub fn with_meta(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
70        self.metadata.insert(key.into(), value);
71        self
72    }
73
74    /// Get a metadata value by key.
75    pub fn get_meta(&self, key: &str) -> Option<&serde_json::Value> {
76        self.metadata.get(key)
77    }
78
79    /// Get a metadata value as a string.
80    pub fn get_meta_str(&self, key: &str) -> Option<&str> {
81        self.metadata.get(key).and_then(|v| v.as_str())
82    }
83}
84
85/// Trait for providing kernel-level tools to the SDK.
86///
87/// oxios-kernel implements this trait to bridge its native tools
88/// (exec, memory, browser, persona, etc.) into the oxi agent tool registry.
89///
90/// # Example
91///
92/// ```ignore
93/// use oxi_sdk::{KernelToolProvider, KernelToolContext};
94/// use oxi_agent::ToolRegistry;
95///
96/// struct MyKernelBridge;
97///
98/// impl KernelToolProvider for MyKernelBridge {
99///     fn tool_names(&self) -> Vec<&str> {
100///         vec!["exec", "memory"]
101///     }
102///
103///     fn register_tools(&self, registry: &ToolRegistry, ctx: &KernelToolContext) {
104///         registry.register(ExecTool::new(ctx.agent_id.clone()));
105///         registry.register(MemoryTool::new(ctx.agent_id.clone()));
106///     }
107/// }
108/// ```
109pub trait KernelToolProvider: Send + Sync {
110    /// Return the names of tools this provider will register.
111    fn tool_names(&self) -> Vec<&str>;
112
113    /// Register tools into the given registry.
114    ///
115    /// The `context` provides agent-specific metadata (workspace, agent_id,
116    /// session, permissions) that tools may need at initialization time.
117    fn register_tools(&self, registry: &ToolRegistry, context: &KernelToolContext);
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use async_trait::async_trait;
124    use oxi_agent::{AgentTool, AgentToolResult, ToolContext, ToolError};
125    use serde_json::Value;
126
127    struct MockKernelTool {
128        name: String,
129    }
130
131    #[async_trait]
132    impl AgentTool for MockKernelTool {
133        fn name(&self) -> &str {
134            &self.name
135        }
136        fn label(&self) -> &str {
137            "mock"
138        }
139        fn description(&self) -> &str {
140            "A mock kernel tool"
141        }
142        fn parameters_schema(&self) -> Value {
143            serde_json::json!({"type": "object", "properties": {}})
144        }
145
146        async fn execute(
147            &self,
148            _tool_call_id: &str,
149            _params: Value,
150            _signal: Option<tokio::sync::oneshot::Receiver<()>>,
151            _ctx: &ToolContext,
152        ) -> Result<AgentToolResult, ToolError> {
153            Ok(AgentToolResult::success("mock result"))
154        }
155    }
156
157    struct MockKernelBridge;
158
159    impl KernelToolProvider for MockKernelBridge {
160        fn tool_names(&self) -> Vec<&str> {
161            vec!["exec", "memory"]
162        }
163
164        fn register_tools(&self, registry: &ToolRegistry, ctx: &KernelToolContext) {
165            registry.register(MockKernelTool {
166                name: format!("exec_{}", ctx.agent_id),
167            });
168            registry.register(MockKernelTool {
169                name: format!("memory_{}", ctx.agent_id),
170            });
171        }
172    }
173
174    #[test]
175    fn test_kernel_tool_context_builder() {
176        let ctx = KernelToolContext::new("/workspace", "agent-001")
177            .with_session("sess-123")
178            .with_permissions(vec!["read".into(), "write".into()]);
179
180        assert_eq!(ctx.workspace_dir, PathBuf::from("/workspace"));
181        assert_eq!(ctx.agent_id, "agent-001");
182        assert_eq!(ctx.session_id, Some("sess-123".to_string()));
183        assert_eq!(ctx.permissions, vec!["read", "write"]);
184    }
185
186    #[test]
187    fn test_kernel_bridge_registers_tools() {
188        let bridge = MockKernelBridge;
189        let registry = ToolRegistry::new();
190        let ctx = KernelToolContext::new("/workspace", "agent-001");
191
192        bridge.register_tools(&registry, &ctx);
193
194        let names = registry.names();
195        assert!(names.contains(&"exec_agent-001".to_string()));
196        assert!(names.contains(&"memory_agent-001".to_string()));
197    }
198}