Browse Source

重构(文件工具): 合并 Diff 并清理静态冗余

让 write 与 edit 共用统一 diff renderer,保持原私有兼容名称和输出格式;同时清理文件工具中的无用局部变量、冗余分支和静态问题,补充共享实现测试。
SamLee 18 giờ trước cách đây
mục cha
commit
c3f92354e8

+ 22 - 0
agent/agent/tools/builtin/file/diff_utils.py

@@ -0,0 +1,22 @@
+"""Shared unified-diff rendering for file mutation tools."""
+
+from __future__ import annotations
+
+import difflib
+
+
+def create_unified_diff(filepath: str, old_content: str, new_content: str) -> str:
+    """Return a stable unified diff, or a localized no-change marker."""
+
+    diff_lines = difflib.unified_diff(
+        old_content.splitlines(keepends=True),
+        new_content.splitlines(keepends=True),
+        fromfile=f"a/{filepath}",
+        tofile=f"b/{filepath}",
+        lineterm="",
+    )
+    rendered = "".join(diff_lines)
+    return rendered or "(无变更)"
+
+
+__all__ = ["create_unified_diff"]

+ 1 - 28
agent/agent/tools/builtin/file/edit.py

@@ -11,10 +11,10 @@ Edit Tool - 文件编辑工具
 
 from pathlib import Path
 from typing import Optional, Generator
-import difflib
 import re
 
 from agent.tools import tool, ToolResult, ToolContext
+from agent.tools.builtin.file.diff_utils import create_unified_diff as _create_diff
 
 
 @tool(description="编辑文件,使用精确字符串替换。支持多种智能匹配策略。", hidden_params=["context"], groups=["core"], capabilities=["write"])
@@ -225,8 +225,6 @@ def block_anchor_replacer(content: str, find: str) -> Generator[str, None, None]
 
     first_line_find = find_lines[0].strip()
     last_line_find = find_lines[-1].strip()
-    find_block_size = len(find_lines)
-
     # 收集所有候选位置(首尾行都匹配)
     candidates = []
     for i in range(len(content_lines)):
@@ -245,8 +243,6 @@ def block_anchor_replacer(content: str, find: str) -> Generator[str, None, None]
     # 单个候选:使用宽松阈值
     if len(candidates) == 1:
         start_line, end_line = candidates[0]
-        actual_block_size = end_line - start_line + 1
-
         similarity = _calculate_block_similarity(
             content_lines[start_line:end_line + 1],
             find_lines
@@ -506,26 +502,3 @@ def multi_occurrence_replacer(content: str, find: str) -> Generator[str, None, N
             break
         yield find
         start_index = index + len(find)
-
-
-# ============================================================================
-# 辅助函数
-# ============================================================================
-
-def _create_diff(filepath: str, old_content: str, new_content: str) -> str:
-    """生成 unified diff"""
-    old_lines = old_content.splitlines(keepends=True)
-    new_lines = new_content.splitlines(keepends=True)
-
-    diff_lines = list(difflib.unified_diff(
-        old_lines,
-        new_lines,
-        fromfile=f"a/{filepath}",
-        tofile=f"b/{filepath}",
-        lineterm=''
-    ))
-
-    if not diff_lines:
-        return "(无变更)"
-
-    return ''.join(diff_lines)

+ 5 - 1
agent/agent/tools/builtin/file/grep.py

@@ -9,6 +9,7 @@ Grep Tool - 内容搜索工具
 - 按修改时间排序结果
 """
 
+import logging
 import re
 import subprocess
 from pathlib import Path
@@ -16,6 +17,8 @@ from typing import Optional, List, Tuple
 
 from agent.tools import tool, ToolResult, ToolContext
 
+logger = logging.getLogger(__name__)
+
 # 常量
 LIMIT = 100  # 最大返回匹配数(参考 opencode grep.ts:107)
 MAX_LINE_LENGTH = 2000  # 最大行长度(参考 opencode grep.ts:10)
@@ -210,7 +213,8 @@ async def _python_search(
                     # 限制数量避免过多搜索
                     if len(matches) >= LIMIT * 2:
                         return matches
-        except Exception:
+        except (OSError, UnicodeError) as exc:
+            logger.debug("Failed to search file %s: %s", file_path, exc)
             continue
 
     return matches

+ 1 - 1
agent/agent/tools/builtin/file/image_cdn.py

@@ -10,7 +10,7 @@ import hashlib
 import json
 import logging
 import re
-from typing import Any, Union
+from typing import Any
 import httpx
 
 logger = logging.getLogger(__name__)

+ 0 - 1
agent/agent/tools/builtin/file/read.py

@@ -10,7 +10,6 @@ Read Tool - 文件读取工具
 - 行长度和字节限制
 """
 
-import os
 import base64
 import mimetypes
 from pathlib import Path

+ 1 - 20
agent/agent/tools/builtin/file/write.py

@@ -11,9 +11,9 @@ Write Tool - 文件写入工具
 
 from pathlib import Path
 from typing import Optional
-import difflib
 
 from agent.tools import tool, ToolResult, ToolContext
+from agent.tools.builtin.file.diff_utils import create_unified_diff as _create_diff
 
 
 @tool(description="写入文件内容(创建新文件、覆盖现有文件或追加内容)", hidden_params=["context"], groups=["core"], capabilities=["write"])
@@ -117,22 +117,3 @@ async def write_file(
         },
         long_term_memory=f"{operation}文件 {path.name}"
     )
-
-
-def _create_diff(filepath: str, old_content: str, new_content: str) -> str:
-    """生成 unified diff"""
-    old_lines = old_content.splitlines(keepends=True)
-    new_lines = new_content.splitlines(keepends=True)
-
-    diff_lines = list(difflib.unified_diff(
-        old_lines,
-        new_lines,
-        fromfile=f"a/{filepath}",
-        tofile=f"b/{filepath}",
-        lineterm=''
-    ))
-
-    if not diff_lines:
-        return "(无变更)"
-
-    return ''.join(diff_lines)

+ 1 - 1
agent/agent/tools/builtin/file/write_json.py

@@ -4,7 +4,7 @@ Write JSON Tool - 用于直接安全写入结构化 JSON 数据,避免大模
 
 import json
 from pathlib import Path
-from typing import Optional, Dict, Any
+from typing import Optional
 
 from agent.tools import tool, ToolResult, ToolContext
 

+ 17 - 0
agent/tests/test_shared_diff_utils.py

@@ -0,0 +1,17 @@
+from agent.tools.builtin.file import edit, write
+from agent.tools.builtin.file.diff_utils import create_unified_diff
+
+
+def test_file_tools_share_one_diff_implementation():
+    assert edit._create_diff is create_unified_diff
+    assert write._create_diff is create_unified_diff
+
+
+def test_unified_diff_contract_is_stable():
+    diff = create_unified_diff("demo.txt", "old\n", "new\n")
+
+    assert "--- a/demo.txt" in diff
+    assert "+++ b/demo.txt" in diff
+    assert "-old" in diff
+    assert "+new" in diff
+    assert create_unified_diff("demo.txt", "same", "same") == "(无变更)"