use serde_json::{json, Value};
use std::sync::Arc;
use synaptic_core::{SynapticError, Tool};
use synaptic_macros::{tool, traceable};
use crate::backend::{Backend, GrepOutputMode};
#[traceable(skip = "backend")]
pub fn create_filesystem_tools(backend: Arc<dyn Backend>) -> Vec<Arc<dyn Tool>> {
let mut tools: Vec<Arc<dyn Tool>> = vec![
ls(backend.clone()),
read_file(backend.clone()),
write_file(backend.clone()),
edit_file(backend.clone()),
glob_files(backend.clone()),
grep(backend.clone()),
];
if backend.supports_execution() {
tools.push(execute(backend));
}
tools
}
#[tool]
async fn ls(
#[field] backend: Arc<dyn Backend>,
path: String,
) -> Result<Value, SynapticError> {
let entries = backend.ls(&path).await?;
serde_json::to_value(entries).map_err(|e| SynapticError::Tool(format!("serialization: {}", e)))
}
#[tool]
async fn read_file(
#[field] backend: Arc<dyn Backend>,
path: String,
#[default = 0]
offset: usize,
#[default = 2000]
limit: usize,
) -> Result<Value, SynapticError> {
let content = backend.read_file(&path, offset, limit).await?;
Ok(Value::String(content))
}
#[tool]
async fn write_file(
#[field] backend: Arc<dyn Backend>,
path: String,
content: String,
) -> Result<Value, SynapticError> {
backend.write_file(&path, &content).await?;
Ok(Value::String(format!("wrote {}", path)))
}
#[tool]
async fn edit_file(
#[field] backend: Arc<dyn Backend>,
path: String,
old_string: String,
new_string: String,
#[default = false]
replace_all: bool,
) -> Result<Value, SynapticError> {
backend
.edit_file(&path, &old_string, &new_string, replace_all)
.await?;
Ok(Value::String(format!("edited {}", path)))
}
#[tool(name = "glob")]
async fn glob_files(
#[field] backend: Arc<dyn Backend>,
pattern: String,
#[default = ".".to_string()]
path: String,
) -> Result<Value, SynapticError> {
let matches = backend.glob(&pattern, &path).await?;
Ok(Value::String(matches.join("\n")))
}
#[tool]
async fn grep(
#[field] backend: Arc<dyn Backend>,
pattern: String,
path: Option<String>,
glob: Option<String>,
output_mode: Option<String>,
) -> Result<Value, SynapticError> {
let mode = match output_mode.as_deref() {
Some("content") => GrepOutputMode::Content,
Some("count") => GrepOutputMode::Count,
_ => GrepOutputMode::FilesWithMatches,
};
let result = backend
.grep(&pattern, path.as_deref(), glob.as_deref(), mode)
.await?;
Ok(Value::String(result))
}
#[tool]
async fn execute(
#[field] backend: Arc<dyn Backend>,
command: String,
timeout: Option<u64>,
) -> Result<Value, SynapticError> {
let duration = timeout.map(std::time::Duration::from_secs);
let result = backend.execute(&command, duration).await?;
Ok(json!({
"stdout": result.stdout,
"stderr": result.stderr,
"exit_code": result.exit_code,
}))
}