use async_trait::async_trait;
use rucora_core::{
error::ToolError,
tool::{Tool, ToolCategory},
};
use serde_json::{Value, json};
use std::path::PathBuf;
use super::FileToolConfig;
pub struct FileWriteTool {
config: FileToolConfig,
}
impl FileWriteTool {
pub fn new() -> Self {
Self {
config: FileToolConfig::new(),
}
}
pub fn with_allowed_dirs(self, dirs: Vec<PathBuf>) -> Self {
Self {
config: self.config.with_allowed_dirs(dirs),
}
}
pub fn with_max_file_size(self, size: u64) -> Self {
Self {
config: self.config.with_max_file_size(size),
}
}
}
impl Default for FileWriteTool {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl Tool for FileWriteTool {
fn name(&self) -> &str {
"file_write"
}
fn description(&self) -> Option<&str> {
Some("写入文件内容(有安全限制:仅允许特定扩展名,禁止系统路径)")
}
fn categories(&self) -> &'static [ToolCategory] {
&[ToolCategory::File]
}
fn input_schema(&self) -> Value {
json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "文件路径"
},
"content": {
"type": "string",
"description": "文件内容"
}
},
"required": ["path", "content"]
})
}
async fn call(&self, input: Value) -> Result<Value, ToolError> {
let path_str = input
.get("path")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::Message("缺少必需的 'path' 字段".to_string()))?;
let content = input
.get("content")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::Message("缺少必需的 'content' 字段".to_string()))?;
self.config.check_file_size(content.len() as u64, "内容")?;
let path = self.config.validate_path_for_write(path_str)?;
tokio::fs::write(&path, content)
.await
.map_err(|e| ToolError::Message(format!("写入文件失败:{e}")))?;
Ok(json!({
"path": path_str,
"success": true,
"bytes_written": content.len()
}))
}
}