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 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.util.List;
  import java.util.concurrent.CompletableFuture;
  
  /**
   * 文件读取工具
   * 支持读取文本文件，可以分页读取大文件
   */
  @Component
  public class ReadFileTool extends BaseTool<ReadFileTool.ReadFileParams> {
  
      private final String rootDirectory;
      private final AppProperties appProperties;
  
      @Autowired
      private ToolExecutionLogger executionLogger;
  
      public ReadFileTool(AppProperties appProperties) {
          super(
              "read_file",
              "ReadFile",
              "Reads and returns the content of a specified file from the local filesystem. " +
              "Handles text files and supports pagination for large files. " +
              "Always 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("absolute_path", JsonSchema.string(
                  "MUST be an absolute path to the file to read. Path must be within the workspace directory (" + 
                  getWorkspaceBasePath() + "). " +
                  getPathExample("project/src/main.java") + ". " +
                  "Relative paths are NOT allowed."
              ))
              .addProperty("offset", JsonSchema.integer(
                  "Optional: For text files, the 0-based line number to start reading from. " +
                  "Requires 'limit' to be set. Use for paginating through large files."
              ).minimum(0))
              .addProperty("limit", JsonSchema.integer(
                  "Optional: For text files, the number of lines to read from the offset. " +
                  "Use for paginating through large files."
              ).minimum(1))
              .required("absolute_path");
      }
  
      @Override
      public String validateToolParams(ReadFileParams params) {
          String baseValidation = super.validateToolParams(params);
          if (baseValidation != null) {
              return baseValidation;
          }
  
          // 验证路径
          if (params.absolutePath == null || params.absolutePath.trim().isEmpty()) {
              return "File path cannot be empty";
          }
  
          Path filePath = Paths.get(params.absolutePath);
  
          // 验证是否为绝对路径
          if (!filePath.isAbsolute()) {
              return "File path must be absolute: " + params.absolutePath;
          }
  
          // 验证是否在工作目录内
          if (!isWithinWorkspace(filePath)) {
              return "File path must be within the workspace directory (" + rootDirectory + "): " + params.absolutePath;
          }
  
          // 验证分页参数
          if (params.offset != null && params.limit == null) {
              return "When 'offset' is specified, 'limit' must also be specified";
          }
  
          if (params.offset != null && params.offset < 0) {
              return "Offset must be non-negative";
          }
  
          if (params.limit != null && params.limit <= 0) {
              return "Limit must be positive";
          }
  
          return null;
      }
  
      /**
       * Read file tool method for Spring AI integration
       */
      @Tool(name = "read_file", description = "Reads and returns the content of a specified file from the local filesystem")
      public String readFile(String absolutePath, Integer offset, Integer limit) {
          long callId = executionLogger.logToolStart("read_file", "读取文件内容",
              String.format("文件路径=%s, 偏移量=%s, 限制行数=%s", absolutePath, offset, limit));
          long startTime = System.currentTimeMillis();
  
          try {
              ReadFileParams params = new ReadFileParams();
              params.setAbsolutePath(absolutePath);
              params.setOffset(offset);
              params.setLimit(limit);
  
              executionLogger.logToolStep(callId, "read_file", "参数验证", "验证文件路径和分页参数");
  
              // Validate parameters
              String validation = validateToolParams(params);
              if (validation != null) {
                  long executionTime = System.currentTimeMillis() - startTime;
                  executionLogger.logToolError(callId, "read_file", "参数验证失败: " + validation, executionTime);
                  return "Error: " + validation;
              }
  
              executionLogger.logFileOperation(callId, "读取文件", absolutePath,
                  offset != null ? String.format("分页读取: 偏移=%d, 限制=%d", offset, limit) : "完整读取");
  
              // Execute the tool
              ToolResult result = execute(params).join();
  
              long executionTime = System.currentTimeMillis() - startTime;
  
              if (result.isSuccess()) {
                  executionLogger.logToolSuccess(callId, "read_file", "文件读取成功", executionTime);
                  return result.getLlmContent();
              } else {
                  executionLogger.logToolError(callId, "read_file", result.getErrorMessage(), executionTime);
                  return "Error: " + result.getErrorMessage();
              }
  
          } catch (Exception e) {
              long executionTime = System.currentTimeMillis() - startTime;
              executionLogger.logToolError(callId, "read_file", "工具执行异常: " + e.getMessage(), executionTime);
              logger.error("Error in read file tool", e);
              return "Error: " + e.getMessage();
          }
      }
  
      @Override
      public CompletableFuture<ToolResult> execute(ReadFileParams params) {
          return CompletableFuture.supplyAsync(() -> {
              try {
                  Path filePath = Paths.get(params.absolutePath);
  
                  // 检查文件是否存在
                  if (!Files.exists(filePath)) {
                      return ToolResult.error("File not found: " + params.absolutePath);
                  }
  
                  // 检查是否为文件
                  if (!Files.isRegularFile(filePath)) {
                      return ToolResult.error("Path is not a regular file: " + params.absolutePath);
                  }
  
                  // 检查文件大小
                  long fileSize = Files.size(filePath);
                  if (fileSize > appProperties.getWorkspace().getMaxFileSize()) {
                      return ToolResult.error("File too large: " + fileSize + " bytes. Maximum allowed: " +
                          appProperties.getWorkspace().getMaxFileSize() + " bytes");
                  }
  
                  // 检查文件扩展名
                  String fileName = filePath.getFileName().toString();
                  if (!isAllowedFileType(fileName)) {
                      return ToolResult.error("File type not allowed: " + fileName +
                          ". Allowed extensions: " + appProperties.getWorkspace().getAllowedExtensions());
                  }
  
                  // 读取文件
                  if (params.offset != null && params.limit != null) {
                      return readFileWithPagination(filePath, params.offset, params.limit);
                  } else {
                      return readFullFile(filePath);
                  }
  
              } catch (IOException e) {
                  logger.error("Error reading file: " + params.absolutePath, e);
                  return ToolResult.error("Error reading file: " + e.getMessage());
              } catch (Exception e) {
                  logger.error("Unexpected error reading file: " + params.absolutePath, e);
                  return ToolResult.error("Unexpected error: " + e.getMessage());
              }
          });
      }
  
      private ToolResult readFullFile(Path filePath) throws IOException {
          String content = Files.readString(filePath, StandardCharsets.UTF_8);
          String relativePath = getRelativePath(filePath);
  
          long lineCount = content.lines().count();
          String displayMessage = String.format("Read file: %s (%d lines, %d bytes)",
              relativePath, lineCount, content.getBytes(StandardCharsets.UTF_8).length);
  
          return ToolResult.success(content, displayMessage);
      }
  
      private ToolResult readFileWithPagination(Path filePath, int offset, int limit) throws IOException {
          List<String> allLines = Files.readAllLines(filePath, StandardCharsets.UTF_8);
  
          if (offset >= allLines.size()) {
              return ToolResult.error("Offset " + offset + " is beyond file length (" + allLines.size() + " lines)");
          }
  
          int endIndex = Math.min(offset + limit, allLines.size());
          List<String> selectedLines = allLines.subList(offset, endIndex);
          String content = String.join("\n", selectedLines);
  
          String relativePath = getRelativePath(filePath);
          String displayMessage = String.format("Read file: %s (lines %d-%d of %d total)",
              relativePath, offset + 1, endIndex, allLines.size());
  
          return ToolResult.success(content, displayMessage);
      }
  
      private boolean isWithinWorkspace(Path filePath) {
          try {
              Path workspaceRoot = Paths.get(rootDirectory).toRealPath();
              Path resolvedPath = filePath.toRealPath();
              return resolvedPath.startsWith(workspaceRoot);
          } catch (IOException e) {
              // 如果路径不存在，检查其父目录
              try {
                  Path workspaceRoot = Paths.get(rootDirectory).toRealPath();
                  Path normalizedPath = filePath.normalize();
                  return normalizedPath.startsWith(workspaceRoot.normalize());
              } catch (IOException ex) {
                  logger.warn("Could not resolve workspace path", ex);
                  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 ReadFileParams {
          @JsonProperty("absolute_path")
          private String absolutePath;
  
          private Integer offset;
          private Integer limit;
  
          // 构造器
          public ReadFileParams() {}
  
          public ReadFileParams(String absolutePath) {
              this.absolutePath = absolutePath;
          }
  
          public ReadFileParams(String absolutePath, Integer offset, Integer limit) {
              this.absolutePath = absolutePath;
              this.offset = offset;
              this.limit = limit;
          }
  
          // Getters and Setters
          public String getAbsolutePath() { return absolutePath; }
          public void setAbsolutePath(String absolutePath) { this.absolutePath = absolutePath; }
  
          public Integer getOffset() { return offset; }
          public void setOffset(Integer offset) { this.offset = offset; }
  
          public Integer getLimit() { return limit; }
          public void setLimit(Integer limit) { this.limit = limit; }
  
          @Override
          public String toString() {
              return String.format("ReadFileParams{path='%s', offset=%d, limit=%d}",
                  absolutePath, offset, limit);
          }
      }
  }
  