glob_tool.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. """
  2. Glob Tool - 文件模式匹配工具
  3. 参考:vendor/opencode/packages/opencode/src/tool/glob.ts
  4. 核心功能:
  5. - 使用 glob 模式匹配文件
  6. - 按修改时间排序
  7. - 限制返回数量
  8. """
  9. import glob as glob_module
  10. from pathlib import Path
  11. from typing import Optional
  12. from agent.tools import tool, ToolResult, ToolContext
  13. # 常量
  14. LIMIT = 100 # 最大返回数量(参考 opencode glob.ts:35)
  15. @tool(description="使用 glob 模式匹配文件")
  16. async def glob_files(
  17. pattern: str,
  18. path: Optional[str] = None,
  19. context: Optional[ToolContext] = None
  20. ) -> ToolResult:
  21. """
  22. 使用 glob 模式匹配文件
  23. 参考 OpenCode 实现
  24. Args:
  25. pattern: glob 模式(如 "*.py", "src/**/*.ts")
  26. path: 搜索目录(默认当前目录)
  27. context: 工具上下文
  28. Returns:
  29. ToolResult: 匹配的文件列表
  30. """
  31. # 确定搜索路径
  32. search_path = Path(path) if path else Path.cwd()
  33. if not search_path.is_absolute():
  34. search_path = Path.cwd() / search_path
  35. if not search_path.exists():
  36. return ToolResult(
  37. title="目录不存在",
  38. output=f"搜索目录不存在: {path}",
  39. error="Directory not found"
  40. )
  41. # 执行 glob 搜索
  42. try:
  43. # 使用 pathlib 的 glob(支持 ** 递归)
  44. if "**" in pattern:
  45. matches = list(search_path.glob(pattern))
  46. else:
  47. # 使用标准 glob(更快)
  48. pattern_path = search_path / pattern
  49. matches = [Path(p) for p in glob_module.glob(str(pattern_path))]
  50. # 过滤掉目录,只保留文件
  51. file_matches = [m for m in matches if m.is_file()]
  52. # 按修改时间排序(参考 opencode:47-56)
  53. file_matches_with_mtime = []
  54. for file_path in file_matches:
  55. try:
  56. mtime = file_path.stat().st_mtime
  57. file_matches_with_mtime.append((file_path, mtime))
  58. except Exception:
  59. file_matches_with_mtime.append((file_path, 0))
  60. # 按修改时间降序排序(最新的在前)
  61. file_matches_with_mtime.sort(key=lambda x: x[1], reverse=True)
  62. # 限制数量
  63. truncated = len(file_matches_with_mtime) > LIMIT
  64. file_matches_with_mtime = file_matches_with_mtime[:LIMIT]
  65. # 格式化输出
  66. if not file_matches_with_mtime:
  67. output = "未找到匹配的文件"
  68. else:
  69. file_paths = [str(f[0]) for f in file_matches_with_mtime]
  70. output = "\n".join(file_paths)
  71. if truncated:
  72. output += f"\n\n(结果已截断。考虑使用更具体的路径或模式。)"
  73. return ToolResult(
  74. title=f"匹配: {pattern}",
  75. output=output,
  76. metadata={
  77. "count": len(file_matches_with_mtime),
  78. "truncated": truncated,
  79. "pattern": pattern,
  80. "search_path": str(search_path)
  81. }
  82. )
  83. except Exception as e:
  84. return ToolResult(
  85. title="Glob 错误",
  86. output=f"glob 匹配失败: {str(e)}",
  87. error=str(e)
  88. )