mateclaw/mateclaw-server/src/main/java/vip/mate/tool/builtin/WriteFileTool.java

105 lines
3.7 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package vip.mate.tool.builtin;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 内置工具:写入文件
* <p>
* 创建新文件或完全覆写已有文件。自动创建不存在的父目录。
* 创建新文件或完全覆写已有文件。
* <p>
* 安全说明:
* <ul>
* <li>写入操作经过 ToolGuard 审批DefaultToolGuard 对 file_write 工具默认返回 NEEDS_APPROVAL</li>
* <li>覆写已有文件前需要用户确认</li>
* </ul>
*
* @author MateClaw Team
*/
@Slf4j
@Component
@lombok.RequiredArgsConstructor
public class WriteFileTool {
private final vip.mate.i18n.I18nService i18n;
@Tool(description = "Write content to a file. Overwrites if exists, creates if not (auto-creates parent directories). "
+ "Returns structured JSON with filePath, bytesWritten. "
+ "Requires user approval.")
public String write_file(
@ToolParam(description = "Absolute or relative file path") String filePath,
@ToolParam(description = "Content to write to the file") String content) {
JSONObject result = new JSONObject();
result.set("filePath", filePath);
try {
if (filePath == null || filePath.isBlank()) {
return errorResult(filePath, i18n.msg("tool.write_file.error.path_empty"));
}
if (content == null) {
content = "";
}
Path path;
try {
path = vip.mate.tool.guard.WorkspacePathGuard.validatePath(filePath);
} catch (IllegalArgumentException e) {
return errorResult(filePath, e.getMessage());
}
// 如果路径是已有目录,拒绝
if (Files.isDirectory(path)) {
return errorResult(filePath, i18n.msg("tool.write_file.error.is_directory", path));
}
// 自动创建父目录
Path parent = path.getParent();
if (parent != null && !Files.exists(parent)) {
Files.createDirectories(parent);
log.info("[WriteFile] Created parent directories: {}", parent);
}
boolean existed = Files.exists(path);
// 写入文件
byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
Files.write(path, bytes);
result.set("bytesWritten", bytes.length);
result.set("created", !existed);
result.set("overwritten", existed);
result.set("message", existed
? "Overwritten: " + path + " (" + bytes.length + " bytes)"
: "Created: " + path + " (" + bytes.length + " bytes)");
log.info("[WriteFile] {} file: {} ({} bytes)",
existed ? "Overwritten" : "Created", path, bytes.length);
} catch (Exception e) {
log.error("[WriteFile] Failed to write file: {}", e.getMessage(), e);
return errorResult(filePath, i18n.msg("tool.write_file.error.write_exception", e.getMessage()));
}
return JSONUtil.toJsonPrettyStr(result);
}
private String errorResult(String filePath, String message) {
JSONObject result = new JSONObject();
result.set("filePath", filePath);
result.set("error", true);
result.set("message", message);
return JSONUtil.toJsonPrettyStr(result);
}
}