|
22 | 22 | import java.nio.file.Files; |
23 | 23 | import java.nio.file.Path; |
24 | 24 | import java.nio.file.Paths; |
| 25 | +import java.util.ArrayList; |
| 26 | +import java.util.Comparator; |
25 | 27 | import java.util.List; |
| 28 | +import java.util.stream.Stream; |
26 | 29 | import org.slf4j.Logger; |
27 | 30 | import org.slf4j.LoggerFactory; |
28 | 31 | import reactor.core.publisher.Mono; |
29 | 32 |
|
30 | 33 | /** |
31 | | - * Tool for viewing text file content with optional line range specification. |
| 34 | + * Tool for viewing text file content with optional line range specification and listing directory |
| 35 | + * contents. |
32 | 36 | * |
33 | | - * <p>This tool allows reading file content with line numbers, supporting: |
| 37 | + * <p>This tool provides the following capabilities: |
34 | 38 | * <ul> |
35 | 39 | * <li>Viewing entire file content</li> |
36 | 40 | * <li>Viewing specific line ranges (e.g., lines 1-100)</li> |
37 | 41 | * <li>Viewing from the end using negative indices (e.g., last 100 lines: [-100, -1])</li> |
| 42 | + * <li>Listing files and directories in a specified directory with full paths</li> |
38 | 43 | * </ul> |
39 | 44 | * |
40 | 45 | * <p>Security: When baseDir is specified, all file operations are restricted to that directory |
@@ -209,6 +214,124 @@ public Mono<ToolResultBlock> viewTextFile( |
209 | 214 | }); |
210 | 215 | } |
211 | 216 |
|
| 217 | + /** |
| 218 | + * List files and directories in the specified directory. |
| 219 | + * |
| 220 | + * @param dirPath The target directory path |
| 221 | + * @return A list of files and directories with their full paths |
| 222 | + */ |
| 223 | + @Tool( |
| 224 | + name = "list_directory", |
| 225 | + description = |
| 226 | + "List all files and directories in the specified directory. Returns the full" |
| 227 | + + " paths of all files and folders in the directory. Use this to explore" |
| 228 | + + " the file system structure.") |
| 229 | + public Mono<ToolResultBlock> listDirectory( |
| 230 | + @ToolParam( |
| 231 | + name = "dir_path", |
| 232 | + description = "The target directory path to list files and folders") |
| 233 | + String dirPath) { |
| 234 | + |
| 235 | + logger.debug("list_directory called: dirPath='{}'", dirPath); |
| 236 | + |
| 237 | + return Mono.fromCallable( |
| 238 | + () -> { |
| 239 | + // Validate path is within base directory |
| 240 | + Path path; |
| 241 | + try { |
| 242 | + path = FileToolUtils.validatePath(dirPath, baseDir); |
| 243 | + } catch (Exception e) { |
| 244 | + logger.warn( |
| 245 | + "Path validation failed for '{}': {}", |
| 246 | + dirPath, |
| 247 | + e.getMessage()); |
| 248 | + return ToolResultBlock.error(e.getMessage()); |
| 249 | + } |
| 250 | + |
| 251 | + // Check if path exists |
| 252 | + if (!Files.exists(path)) { |
| 253 | + logger.warn("Directory does not exist: {}", dirPath); |
| 254 | + return ToolResultBlock.error( |
| 255 | + String.format("The directory %s does not exist.", dirPath)); |
| 256 | + } |
| 257 | + |
| 258 | + // Check if path is a directory |
| 259 | + if (!Files.isDirectory(path)) { |
| 260 | + logger.warn("Path is not a directory: {}", dirPath); |
| 261 | + return ToolResultBlock.error( |
| 262 | + String.format("The path %s is not a directory.", dirPath)); |
| 263 | + } |
| 264 | + |
| 265 | + // List files and directories |
| 266 | + List<String> files = new ArrayList<>(); |
| 267 | + List<String> directories = new ArrayList<>(); |
| 268 | + |
| 269 | + try (Stream<Path> paths = Files.list(path)) { |
| 270 | + paths.sorted(Comparator.comparing(Path::toString)) |
| 271 | + .forEach( |
| 272 | + p -> { |
| 273 | + String fullPath = p.toAbsolutePath().toString(); |
| 274 | + if (Files.isDirectory(p)) { |
| 275 | + directories.add(fullPath); |
| 276 | + } else { |
| 277 | + files.add(fullPath); |
| 278 | + } |
| 279 | + }); |
| 280 | + } catch (Exception e) { |
| 281 | + logger.error( |
| 282 | + "Error listing directory '{}': {}", |
| 283 | + dirPath, |
| 284 | + e.getMessage(), |
| 285 | + e); |
| 286 | + return ToolResultBlock.error( |
| 287 | + "Error listing directory: " + e.getMessage()); |
| 288 | + } |
| 289 | + |
| 290 | + // Format output |
| 291 | + StringBuilder result = new StringBuilder(); |
| 292 | + result.append(String.format("Contents of directory %s:\n\n", dirPath)); |
| 293 | + |
| 294 | + if (!directories.isEmpty()) { |
| 295 | + result.append("Directories:\n"); |
| 296 | + for (String dir : directories) { |
| 297 | + result.append(" ").append(dir).append("\n"); |
| 298 | + } |
| 299 | + result.append("\n"); |
| 300 | + } |
| 301 | + |
| 302 | + if (!files.isEmpty()) { |
| 303 | + result.append("Files:\n"); |
| 304 | + for (String file : files) { |
| 305 | + result.append(" ").append(file).append("\n"); |
| 306 | + } |
| 307 | + result.append("\n"); |
| 308 | + } |
| 309 | + |
| 310 | + if (directories.isEmpty() && files.isEmpty()) { |
| 311 | + result.append("(empty directory)\n"); |
| 312 | + } else { |
| 313 | + result.append( |
| 314 | + String.format( |
| 315 | + "Total: %d directory(ies), %d file(s)", |
| 316 | + directories.size(), files.size())); |
| 317 | + } |
| 318 | + |
| 319 | + logger.debug( |
| 320 | + "Listed {} directories and {} files in: {}", |
| 321 | + directories.size(), |
| 322 | + files.size(), |
| 323 | + dirPath); |
| 324 | + |
| 325 | + return ToolResultBlock.text(result.toString()); |
| 326 | + }) |
| 327 | + .onErrorResume( |
| 328 | + e -> { |
| 329 | + logger.error( |
| 330 | + "Error listing directory '{}': {}", dirPath, e.getMessage(), e); |
| 331 | + return Mono.just(ToolResultBlock.error("Error: " + e.getMessage())); |
| 332 | + }); |
| 333 | + } |
| 334 | + |
212 | 335 | /** |
213 | 336 | * Format lines with line numbers for display. |
214 | 337 | * |
|
0 commit comments