content: package com.example.demo.tools;
  
  import com.example.demo.config.AppProperties;
  import com.example.demo.schema.JsonSchema;
  import com.example.demo.service.ToolExecutionLogger;
  import com.fasterxml.jackson.annotation.JsonProperty;
  import com.github.difflib.DiffUtils;
  import com.github.difflib.UnifiedDiffUtils;
  import com.github.difflib.patch.Patch;
  import org.springframework.ai.tool.annotation.Tool;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.stereotype.Component;
  
  import java.io.IOException;
  import java.nio.charset.StandardCharsets;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.nio.file.Paths;
  import java.nio.file.StandardOpenOption;
  import java.time.LocalDateTime;
  import java.time.format.DateTimeFormatter;
  import java.util.Arrays;
  import java.util.List;
  import java.util.concurrent.CompletableFuture;
  
  /**
   * 文件写入工具
   * 支持创建新文件或覆盖现有文件，自动显示差异
   */
  @Component
  public class WriteFileTool extends BaseTool<WriteFileTool.WriteFileParams> {
  
      private final String rootDirectory;
      private final AppProperties appProperties;
  
      @Autowired
      private ToolExecutionLogger executionLogger;
  
      public WriteFileTool(AppProperties appProperties) {
          super(
              "write_file",
              "WriteFile",
              "Writes content to a file. Creates new files or overwrites existing ones. " +
              "Always shows a diff before writing. Automatically creates parent directories if needed. " +
              "Use absolute paths within the workspace directory.",
              createSchema()
          );
          this.appProperties = appProperties;
          this.rootDirectory = appProperties.getWorkspace().getRootDirectory();
      }
  
      private static String getWorkspaceBasePath() {
          return Paths.get(System.getProperty("user.dir"), "workspace").toString();
      }
  
      private static String getPathExample(String subPath) {
          return "Example: \"" + Paths.get(getWorkspaceBasePath(), subPath).toString() + "\"";
      }
  
      private static JsonSchema createSchema() {
          return JsonSchema.object()
              .addProperty("file_path", JsonSchema.string(
                  "MUST be an absolute path to the file to write to. Path must be within the workspace directory (" + 
                  getWorkspaceBasePath() + "). " +
                  getPathExample("project/src/main.java") + ". " +
                  "Relative paths are NOT allowed."
              ))
              .addProperty("content", JsonSchema.string(
                  "The content to write to the file. Will completely replace existing content if file exists."
              ))
              .required("file_path", "content");
      }
  
      @Override
      public String validateToolParams(WriteFileParams params) {
          String baseValidation = super.validateToolParams(params);
          if (baseValidation != null) {
              return baseValidation;
          }
  
          // 验证路径
          if (params.filePath == null || params.filePath.trim().isEmpty()) {
              return "File path cannot be empty";
          }
  
          if (params.content == null) {
              return "Content cannot be null";
          }
  
          Path filePath = Paths.get(params.filePath);
          
          // 验证是否为绝对路径
          if (!filePath.isAbsolute()) {
              return "File path must be absolute: " + params.filePath;
          }
  
          // 验证是否在工作目录内
          if (!isWithinWorkspace(filePath)) {
              return "File path must be within the workspace directory (" + rootDirectory + "): " + params.filePath;
          }
  
          // 验证文件扩展名
          String fileName = filePath.getFileName().toString();
          if (!isAllowedFileType(fileName)) {
              return "File type not allowed: " + fileName + 
                  ". Allowed extensions: " + appProperties.getWorkspace().getAllowedExtensions();
          }
  
          // 验证内容大小
          byte[] contentBytes = params.content.getBytes(StandardCharsets.UTF_8);
          if (contentBytes.length > appProperties.getWorkspace().getMaxFileSize()) {
              return "Content too large: " + contentBytes.length + " bytes. Maximum allowed: " + 
                  appProperties.getWorkspace().getMaxFileSize() + " bytes";
          }
  
          return null;
      }
  
      @Override
      public CompletableFuture<ToolConfirmationDetails> shouldConfirmExecute(WriteFileParams params) {
          // 根据配置决定是否需要确认
          if (appProperties.getSecurity().getApprovalMode() == AppProperties.ApprovalMode.AUTO_EDIT ||
              appProperties.getSecurity().getApprovalMode() == AppProperties.ApprovalMode.YOLO) {
              return CompletableFuture.completedFuture(null);
          }
  
          return CompletableFuture.supplyAsync(() -> {
              try {
                  Path filePath = Paths.get(params.filePath);
                  String currentContent = "";
                  boolean isNewFile = !Files.exists(filePath);
                  
                  if (!isNewFile) {
                      currentContent = Files.readString(filePath, StandardCharsets.UTF_8);
                  }
                  
                  // 生成差异显示
                  String diff = generateDiff(
                      filePath.getFileName().toString(),
                      currentContent,
                      params.content
                  );
                  
                  String title = isNewFile ? 
                      "Confirm Create: " + getRelativePath(filePath) :
                      "Confirm Write: " + getRelativePath(filePath);
                  
                  return ToolConfirmationDetails.edit(title, filePath.getFileName().toString(), diff);
                      
              } catch (IOException e) {
                  logger.warn("Could not read existing file for diff: " + params.filePath, e);
                  return null; // 如果无法读取文件，直接执行
              }
          });
      }
  
      /**
       * Write file tool method for Spring AI integration
       */
      @Tool(name = "write_file", description = "Creates a new file or overwrites an existing file with the specified content")
      public String writeFile(String filePath, String content) {
          long callId = executionLogger.logToolStart("write_file", "写入文件内容",
              String.format("文件路径=%s, 内容长度=%d字符", filePath, content != null ? content.length() : 0));
          long startTime = System.currentTimeMillis();
  
          try {
              WriteFileParams params = new WriteFileParams();
              params.setFilePath(filePath);
              params.setContent(content);
  
              executionLogger.logToolStep(callId, "write_file", "参数验证", "验证文件路径和内容");
  
              // Validate parameters
              String validation = validateToolParams(params);
              if (validation != null) {
                  long executionTime = System.currentTimeMillis() - startTime;
                  executionLogger.logToolError(callId, "write_file", "参数验证失败: " + validation, executionTime);
                  return "Error: " + validation;
              }
  
              executionLogger.logFileOperation(callId, "写入文件", filePath,
                  String.format("内容长度: %d字符", content != null ? content.length() : 0));
  
              // Execute the tool
              ToolResult result = execute(params).join();
  
              long executionTime = System.currentTimeMillis() - startTime;
  
              if (result.isSuccess()) {
                  executionLogger.logToolSuccess(callId, "write_file", "文件写入成功", executionTime);
                  return result.getLlmContent();
              } else {
                  executionLogger.logToolError(callId, "write_file", result.getErrorMessage(), executionTime);
                  return "Error: " + result.getErrorMessage();
              }
  
          } catch (Exception e) {
              long executionTime = System.currentTimeMillis() - startTime;
              executionLogger.logToolError(callId, "write_file", "工具执行异常: " + e.getMessage(), executionTime);
              logger.error("Error in write file tool", e);
              return "Error: " + e.getMessage();
          }
      }
  
      @Override
      public CompletableFuture<ToolResult> execute(WriteFileParams params) {
          return CompletableFuture.supplyAsync(() -> {
              try {
                  Path filePath = Paths.get(params.filePath);
                  boolean isNewFile = !Files.exists(filePath);
                  String originalContent = "";
                  
                  // 读取原始内容（用于备份和差异显示）
                  if (!isNewFile) {
                      originalContent = Files.readString(filePath, StandardCharsets.UTF_8);
                  }
                  
                  // 创建备份（如果启用）
                  if (!isNewFile && shouldCreateBackup()) {
                      createBackup(filePath, originalContent);
                  }
                  
                  // 确保父目录存在
                  Files.createDirectories(filePath.getParent());
                  
                  // 写入文件
                  Files.writeString(filePath, params.content, StandardCharsets.UTF_8,
                      StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
                  
                  // 生成结果
                  String relativePath = getRelativePath(filePath);
                  long lineCount = params.content.lines().count();
                  long byteCount = params.content.getBytes(StandardCharsets.UTF_8).length;
                  
                  if (isNewFile) {
                      String successMessage = String.format("Successfully created file: %s (%d lines, %d bytes)", 
                          params.filePath, lineCount, byteCount);
                      String displayMessage = String.format("Created %s (%d lines)", relativePath, lineCount);
                      return ToolResult.success(successMessage, displayMessage);
                  } else {
                      String diff = generateDiff(filePath.getFileName().toString(), originalContent, params.content);
                      String successMessage = String.format("Successfully wrote to file: %s (%d lines, %d bytes)", 
                          params.filePath, lineCount, byteCount);
                      return ToolResult.success(successMessage, new FileDiff(diff, filePath.getFileName().toString()));
                  }
                  
              } catch (IOException e) {
                  logger.error("Error writing file: " + params.filePath, e);
                  return ToolResult.error("Error writing file: " + e.getMessage());
              } catch (Exception e) {
                  logger.error("Unexpected error writing file: " + params.filePath, e);
                  return ToolResult.error("Unexpected error: " + e.getMessage());
              }
          });
      }
  
      private String generateDiff(String fileName, String oldContent, String newContent) {
          try {
              List<String> oldLines = Arrays.asList(oldContent.split("\n"));
              List<String> newLines = Arrays.asList(newContent.split("\n"));
              
              Patch<String> patch = DiffUtils.diff(oldLines, newLines);
              List<String> unifiedDiff = UnifiedDiffUtils.generateUnifiedDiff(
                  fileName + " (Original)",
                  fileName + " (New)",
                  oldLines,
                  patch,
                  3 // context lines
              );
              
              return String.join("\n", unifiedDiff);
          } catch (Exception e) {
              logger.warn("Could not generate diff", e);
              return "Diff generation failed: " + e.getMessage();
          }
      }
  
      private void createBackup(Path filePath, String content) throws IOException {
          String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
          String backupFileName = filePath.getFileName().toString() + ".backup." + timestamp;
          Path backupPath = filePath.getParent().resolve(backupFileName);
          
          Files.writeString(backupPath, content, StandardCharsets.UTF_8);
          logger.info("Created backup: {}", backupPath);
      }
  
      private boolean shouldCreateBackup() {
          // 可以从配置中读取，这里简化为总是创建备份
          return true;
      }
  
      private boolean isWithinWorkspace(Path filePath) {
          try {
              Path workspaceRoot = Paths.get(rootDirectory).toRealPath();
              Path normalizedPath = filePath.normalize();
              return normalizedPath.startsWith(workspaceRoot.normalize());
          } catch (IOException e) {
              logger.warn("Could not resolve workspace path", e);
              return false;
          }
      }
  
      private boolean isAllowedFileType(String fileName) {
          List<String> allowedExtensions = appProperties.getWorkspace().getAllowedExtensions();
          return allowedExtensions.stream()
              .anyMatch(ext -> fileName.toLowerCase().endsWith(ext.toLowerCase()));
      }
  
      private String getRelativePath(Path filePath) {
          try {
              Path workspaceRoot = Paths.get(rootDirectory);
              return workspaceRoot.relativize(filePath).toString();
          } catch (Exception e) {
              return filePath.toString();
          }
      }
  
      /**
       * 写入文件参数
       */
      public static class WriteFileParams {
          @JsonProperty("file_path")
          private String filePath;
          
          private String content;
  
          // 构造器
          public WriteFileParams() {}
  
          public WriteFileParams(String filePath, String content) {
              this.filePath = filePath;
              this.content = content;
          }
  
          // Getters and Setters
          public String getFilePath() { return filePath; }
          public void setFilePath(String filePath) { this.filePath = filePath; }
  
          public String getContent() { return content; }
          public void setContent(String content) { this.content = content; }
  
          @Override
          public String toString() {
              return String.format("WriteFileParams{path='%s', contentLength=%d}", 
                  filePath, content != null ? content.length() : 0);
          }
      }
  }
  