glob.py 3.2 KB

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