oxios_kernel/tools/builtin/
mount_tool.rs1use async_trait::async_trait;
10use std::sync::Arc;
11
12use oxi_sdk::{AgentTool, AgentToolResult, ToolContext};
13use serde_json::{Value, json};
14
15use crate::kernel_handle::KernelHandle;
16use crate::mount::{MountId, MountManager};
17
18pub struct MountTool {
23 mount_manager: Option<Arc<MountManager>>,
24}
25
26impl MountTool {
27 pub fn from_kernel(kernel: &KernelHandle) -> Self {
29 Self {
30 mount_manager: kernel.mounts.as_ref().map(|m| m.mount_manager.clone()),
31 }
32 }
33}
34
35impl std::fmt::Debug for MountTool {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.debug_struct("MountTool").finish()
38 }
39}
40
41#[async_trait]
42impl AgentTool for MountTool {
43 fn name(&self) -> &str {
44 "mount"
45 }
46
47 fn label(&self) -> &str {
48 "Mount"
49 }
50
51 fn description(&self) -> &'static str {
52 "Query and enrich Mounts (path aliases). The agent explores a Mount's \
53 filesystem and writes its findings to auto_description/auto_meta via \
54 'update'. Actions: list, get, update."
55 }
56
57 fn parameters_schema(&self) -> Value {
58 json!({
59 "type": "object",
60 "properties": {
61 "action": {
62 "type": "string",
63 "enum": ["list", "get", "update"],
64 "description": "Mount operation to perform"
65 },
66 "id": {
67 "type": "string",
68 "description": "Mount UUID"
69 },
70 "name": {
71 "type": "string",
72 "description": "Mount name (alternative to id for 'get')"
73 },
74 "auto_description": {
75 "type": "string",
76 "description": "(update) Agent-written description, ≤500 chars. Cite real files you read."
77 },
78 "auto_meta": {
79 "type": "object",
80 "description": "(update) Auto-detected metadata to set",
81 "properties": {
82 "languages": { "type": "array", "items": { "type": "string" } },
83 "stack": { "type": "array", "items": { "type": "string" } },
84 "markers": { "type": "array", "items": { "type": "string" } },
85 "summary": { "type": "string" }
86 }
87 }
88 },
89 "required": ["action"]
90 })
91 }
92
93 async fn execute(
94 &self,
95 _tool_call_id: &str,
96 params: Value,
97 _signal: Option<tokio::sync::oneshot::Receiver<()>>,
98 _ctx: &ToolContext,
99 ) -> Result<AgentToolResult, oxi_sdk::ToolError> {
100 let action = params
101 .get("action")
102 .and_then(|v| v.as_str())
103 .ok_or_else(|| "Missing required parameter: action".to_string())?;
104
105 let mm = self
106 .mount_manager
107 .as_ref()
108 .ok_or_else(|| "Mount system not available (SQLite not enabled)".to_string())?;
109
110 match action {
111 "list" => {
112 let mounts = mm.list_mounts();
113 if mounts.is_empty() {
114 return Ok(AgentToolResult::success("No Mounts registered."));
115 }
116 let mut out = format!("Found {} Mount(s):\n\n", mounts.len());
117 for m in &mounts {
118 let paths = m
119 .paths
120 .iter()
121 .map(|p| p.to_string_lossy().to_string())
122 .collect::<Vec<_>>()
123 .join(", ");
124 let langs = m.auto_meta.languages.join(", ");
125 out.push_str(&format!(
126 "- {} ({}) [{}]\n paths: {}\n summary: {}\n",
127 m.name,
128 &m.id.to_string()[..8],
129 if langs.is_empty() { "unknown" } else { &langs },
130 paths,
131 m.summary_line(),
132 ));
133 }
134 Ok(AgentToolResult::success(out))
135 }
136
137 "get" => {
138 let mount = if let Some(id_str) = params.get("id").and_then(|v| v.as_str()) {
139 let id =
140 MountId::parse_str(id_str).map_err(|e| format!("Invalid mount ID: {e}"))?;
141 mm.get_mount(id)
142 } else if let Some(name) = params.get("name").and_then(|v| v.as_str()) {
143 mm.get_mount_by_name(name)
144 } else {
145 return Err("'get' requires 'id' or 'name' parameter".to_string());
146 };
147
148 match mount {
149 Some(m) => Ok(AgentToolResult::success(
150 serde_json::to_string_pretty(&json!({
151 "id": m.id.to_string(),
152 "name": m.name,
153 "source": m.source.to_string(),
154 "paths": m.paths.iter().map(|p| p.to_string_lossy().to_string()).collect::<Vec<_>>(),
155 "auto_description": m.auto_description,
156 "auto_meta": {
157 "languages": m.auto_meta.languages,
158 "stack": m.auto_meta.stack,
159 "markers": m.auto_meta.markers,
160 "summary": m.auto_meta.summary,
161 },
162 "enrichment_pending": m.enrichment_pending,
163 "last_enriched_at": m.last_enriched_at.map(|t| t.to_rfc3339()),
164 "last_active_at": m.last_active_at.to_rfc3339(),
165 }))
166 .unwrap_or_default(),
167 )),
168 None => Ok(AgentToolResult::error("Mount not found")),
169 }
170 }
171
172 "update" => {
173 let id_str = params
174 .get("id")
175 .and_then(|v| v.as_str())
176 .ok_or_else(|| "update requires 'id'".to_string())?;
177 let id =
178 MountId::parse_str(id_str).map_err(|e| format!("Invalid mount ID: {e}"))?;
179
180 let auto_description = params
181 .get("auto_description")
182 .and_then(|v| v.as_str())
183 .map(String::from);
184
185 let auto_meta = params
186 .get("auto_meta")
187 .and_then(|v| v.as_object())
188 .map(|obj| crate::mount::MountMeta {
189 languages: obj
190 .get("languages")
191 .and_then(|v| v.as_array())
192 .map(|a| {
193 a.iter()
194 .filter_map(|v| v.as_str().map(String::from))
195 .collect()
196 })
197 .unwrap_or_default(),
198 stack: obj
199 .get("stack")
200 .and_then(|v| v.as_array())
201 .map(|a| {
202 a.iter()
203 .filter_map(|v| v.as_str().map(String::from))
204 .collect()
205 })
206 .unwrap_or_default(),
207 markers: obj
208 .get("markers")
209 .and_then(|v| v.as_array())
210 .map(|a| {
211 a.iter()
212 .filter_map(|v| v.as_str().map(String::from))
213 .collect()
214 })
215 .unwrap_or_default(),
216 summary: obj
217 .get("summary")
218 .and_then(|v| v.as_str())
219 .map(String::from)
220 .unwrap_or_default(),
221 });
222
223 if auto_description.is_none() && auto_meta.is_none() {
224 return Err("update requires 'auto_description' or 'auto_meta'".to_string());
225 }
226
227 match mm.update_enrichment(id, auto_description, auto_meta) {
228 Ok(m) => Ok(AgentToolResult::success(format!(
229 "Updated Mount '{}' ({}). enrichment_pending cleared.",
230 m.name,
231 &id_str[..8.min(id_str.len())]
232 ))),
233 Err(e) => Ok(AgentToolResult::error(format!(
234 "Failed to update mount: {e}"
235 ))),
236 }
237 }
238
239 other => Err(format!(
240 "Unknown mount action '{other}'. Valid: list, get, update"
241 )),
242 }
243 }
244}