ソースを参照

Validate to_replace in edit_file_by_replace AgentSkill (#3073)

* Validate to_replace in edit_file_by_replace AgentSkill

* Remove redundant replace reminder prompt

* Add unit tests

* Fix prompt
Boxuan Li 1 年間 前
コミット
445f290beb
34 ファイル変更84 行追加92 行削除
  1. 8 1
      opendevin/runtime/plugins/agent_skills/agentskills.py
  2. 0 1
      tests/integration/mock/CodeActAgent/test_browse_internet/prompt_001.log
  3. 0 1
      tests/integration/mock/CodeActAgent/test_browse_internet/prompt_005.log
  4. 0 1
      tests/integration/mock/CodeActAgent/test_edits/prompt_001.log
  5. 0 1
      tests/integration/mock/CodeActAgent/test_edits/prompt_002.log
  6. 0 1
      tests/integration/mock/CodeActAgent/test_edits/prompt_003.log
  7. 0 1
      tests/integration/mock/CodeActAgent/test_edits/prompt_004.log
  8. 0 1
      tests/integration/mock/CodeActAgent/test_edits/prompt_005.log
  9. 0 1
      tests/integration/mock/CodeActAgent/test_ipython/prompt_001.log
  10. 0 1
      tests/integration/mock/CodeActAgent/test_ipython/prompt_002.log
  11. 0 1
      tests/integration/mock/CodeActAgent/test_ipython/prompt_003.log
  12. 0 1
      tests/integration/mock/CodeActAgent/test_ipython_module/prompt_001.log
  13. 0 1
      tests/integration/mock/CodeActAgent/test_ipython_module/prompt_002.log
  14. 0 1
      tests/integration/mock/CodeActAgent/test_ipython_module/prompt_003.log
  15. 0 1
      tests/integration/mock/CodeActAgent/test_ipython_module/prompt_004.log
  16. 0 1
      tests/integration/mock/CodeActAgent/test_write_simple_script/prompt_001.log
  17. 0 1
      tests/integration/mock/CodeActAgent/test_write_simple_script/prompt_002.log
  18. 0 1
      tests/integration/mock/CodeActAgent/test_write_simple_script/prompt_003.log
  19. 0 1
      tests/integration/mock/CodeActAgent/test_write_simple_script/prompt_004.log
  20. 0 1
      tests/integration/mock/CodeActSWEAgent/test_edits/prompt_001.log
  21. 0 1
      tests/integration/mock/CodeActSWEAgent/test_edits/prompt_002.log
  22. 0 1
      tests/integration/mock/CodeActSWEAgent/test_edits/prompt_003.log
  23. 0 1
      tests/integration/mock/CodeActSWEAgent/test_edits/prompt_004.log
  24. 29 30
      tests/integration/mock/CodeActSWEAgent/test_edits/prompt_005.log
  25. 0 1
      tests/integration/mock/CodeActSWEAgent/test_ipython/prompt_001.log
  26. 0 1
      tests/integration/mock/CodeActSWEAgent/test_ipython/prompt_002.log
  27. 0 1
      tests/integration/mock/CodeActSWEAgent/test_ipython_module/prompt_001.log
  28. 0 1
      tests/integration/mock/CodeActSWEAgent/test_ipython_module/prompt_002.log
  29. 0 1
      tests/integration/mock/CodeActSWEAgent/test_ipython_module/prompt_003.log
  30. 0 1
      tests/integration/mock/CodeActSWEAgent/test_write_simple_script/prompt_001.log
  31. 0 1
      tests/integration/mock/CodeActSWEAgent/test_write_simple_script/prompt_002.log
  32. 0 1
      tests/integration/mock/CodeActSWEAgent/test_write_simple_script/prompt_003.log
  33. 0 1
      tests/integration/mock/CodeActSWEAgent/test_write_simple_script/prompt_004.log
  34. 47 30
      tests/unit/test_agent_skill.py

+ 8 - 1
opendevin/runtime/plugins/agent_skills/agentskills.py

@@ -605,7 +605,6 @@ def edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> N
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
 
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
 
     For example, given a file "/workspace/example.txt" with the following content:
     ```
@@ -650,12 +649,20 @@ def edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> N
     if to_replace.strip() == '':
         raise ValueError('`to_replace` must not be empty.')
 
+    if to_replace == new_content:
+        raise ValueError('`to_replace` and `new_content` must be different.')
+
     # search for `to_replace` in the file
     # if found, replace it with `new_content`
     # if not found, perform a fuzzy search to find the closest match and replace it with `new_content`
     with open(file_name, 'r') as file:
         file_content = file.read()
 
+    if file_content.count(to_replace) > 1:
+        raise ValueError(
+            '`to_replace` appears more than once, please include enough lines to make code in `to_replace` unique.'
+        )
+
     start = file_content.find(to_replace)
     if start != -1:
         # Convert start from index to line number

+ 0 - 1
tests/integration/mock/CodeActAgent/test_browse_internet/prompt_001.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_browse_internet/prompt_005.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_edits/prompt_001.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_edits/prompt_002.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_edits/prompt_003.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_edits/prompt_004.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_edits/prompt_005.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_ipython/prompt_001.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_ipython/prompt_002.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_ipython/prompt_003.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_ipython_module/prompt_001.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_ipython_module/prompt_002.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_ipython_module/prompt_003.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_ipython_module/prompt_004.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_write_simple_script/prompt_001.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_write_simple_script/prompt_002.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_write_simple_script/prompt_003.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActAgent/test_write_simple_script/prompt_004.log

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_edits/prompt_001.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_edits/prompt_002.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_edits/prompt_003.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_edits/prompt_004.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 29 - 30
tests/integration/mock/CodeActSWEAgent/test_edits/prompt_005.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1
@@ -602,13 +601,13 @@ open_file("bad.txt")
 ----------
 
 OBSERVATION:
-[File: /workspace/bad.txt (4 lines total)]
-(this is the beginning of the file)
-1|This is a stupid typoo.
-2|Really?
-3|No mor typos!
-4|Enjoy!
-(this is the end of the file)
+[File: /workspace/bad.txt (4 lines total)]
+(this is the beginning of the file)
+1|This is a stupid typoo.
+2|Really?
+3|No mor typos!
+4|Enjoy!
+(this is the end of the file)
 
 
 ----------
@@ -630,12 +629,12 @@ edit_file_by_replace(
 ----------
 
 OBSERVATION:
-[No exact match found in bad.txt for
-```
-This is a stupid typoo.
-No mor typos!
-```
-]
+[No exact match found in bad.txt for
+```
+This is a stupid typoo.
+No mor typos!
+```
+]
 
 
 ----------
@@ -657,14 +656,14 @@ edit_file_by_replace(
 ----------
 
 OBSERVATION:
-[File: /workspace/bad.txt (4 lines total after edit)]
-(this is the beginning of the file)
-1|This is a stupid typo.
-2|Really?
-3|No mor typos!
-4|Enjoy!
-(this is the end of the file)
-[File updated (edited at line 1). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]
+[File: /workspace/bad.txt (4 lines total after edit)]
+(this is the beginning of the file)
+1|This is a stupid typo.
+2|Really?
+3|No mor typos!
+4|Enjoy!
+(this is the end of the file)
+[File updated (edited at line 1). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]
 
 
 ----------
@@ -681,14 +680,14 @@ edit_file_by_replace(
 ----------
 
 OBSERVATION:
-[File: /workspace/bad.txt (4 lines total after edit)]
-(this is the beginning of the file)
-1|This is a stupid typo.
-2|Really?
-3|No more typos!
-4|Enjoy!
-(this is the end of the file)
-[File updated (edited at line 3). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]
+[File: /workspace/bad.txt (4 lines total after edit)]
+(this is the beginning of the file)
+1|This is a stupid typo.
+2|Really?
+3|No more typos!
+4|Enjoy!
+(this is the end of the file)
+[File updated (edited at line 3). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]
 
 
 ENVIRONMENT REMINDER: You have 10 turns left to complete the task.

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_ipython/prompt_001.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_ipython/prompt_002.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_ipython_module/prompt_001.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_ipython_module/prompt_002.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_ipython_module/prompt_003.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_write_simple_script/prompt_001.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_write_simple_script/prompt_002.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_write_simple_script/prompt_003.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 0 - 1
tests/integration/mock/CodeActSWEAgent/test_write_simple_script/prompt_004.log

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
     Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
-    `edit_file_by_replace` will only replace the *first* matching occurrences.
     For example, given a file "/workspace/example.txt" with the following content:
     ```
     line 1

+ 47 - 30
tests/unit/test_agent_skill.py

@@ -693,41 +693,58 @@ def test_edit_file_by_replace_multiline(tmp_path):
 
     with io.StringIO() as buf:
         with contextlib.redirect_stdout(buf):
-            edit_file_by_replace(
-                file_name=str(temp_file_path),
-                to_replace='Line 2',
-                new_content='REPLACE TEXT',
-            )
-        result = buf.getvalue()
-        expected = (
-            f'[File: {temp_file_path} (5 lines total after edit)]\n'
-            '(this is the beginning of the file)\n'
-            '1|Line 1\n'
-            '2|REPLACE TEXT\n'
-            '3|Line 2\n'
-            '4|Line 4\n'
-            '5|Line 5\n'
-            '(this is the end of the file)\n'
-            + MSG_FILE_UPDATED.format(line_number=2)
-            + '\n'
-        )
-        assert result.split('\n') == expected.split('\n')
+            with pytest.raises(
+                ValueError,
+                match='`to_replace` appears more than once, please include enough lines to make code in `to_replace` unique',
+            ):
+                edit_file_by_replace(
+                    file_name=str(temp_file_path),
+                    to_replace='Line 2',
+                    new_content='REPLACE TEXT',
+                )
 
-    with open(temp_file_path, 'r') as file:
-        lines = file.readlines()
-    assert len(lines) == 5
-    assert lines[0].rstrip() == 'Line 1'
-    assert lines[1].rstrip() == 'REPLACE TEXT'
-    assert lines[2].rstrip() == 'Line 2'
-    assert lines[3].rstrip() == 'Line 4'
-    assert lines[4].rstrip() == 'Line 5'
 
+def test_edit_file_by_replace_no_diff(tmp_path):
+    temp_file_path = tmp_path / 'a.txt'
+    content = 'Line 1\nLine 2\nLine 2\nLine 4\nLine 5'
+    temp_file_path.write_text(content)
 
-def test_edit_file_by_replace_toreplace_empty():
-    with pytest.raises(ValueError):
+    open_file(str(temp_file_path))
+
+    with io.StringIO() as buf:
+        with contextlib.redirect_stdout(buf):
+            with pytest.raises(
+                ValueError, match='`to_replace` and `new_content` must be different'
+            ):
+                edit_file_by_replace(
+                    file_name=str(temp_file_path),
+                    to_replace='Line 1',
+                    new_content='Line 1',
+                )
+
+
+def test_edit_file_by_replace_toreplace_empty(tmp_path):
+    temp_file_path = tmp_path / 'a.txt'
+    content = 'Line 1\nLine 2\nLine 2\nLine 4\nLine 5'
+    temp_file_path.write_text(content)
+
+    open_file(str(temp_file_path))
+
+    with io.StringIO() as buf:
+        with contextlib.redirect_stdout(buf):
+            with pytest.raises(ValueError, match='`to_replace` must not be empty.'):
+                edit_file_by_replace(
+                    file_name=str(temp_file_path),
+                    to_replace='    ',
+                    new_content='Line 1',
+                )
+
+
+def test_edit_file_by_replace_unknown_file():
+    with pytest.raises(FileNotFoundError):
         edit_file_by_replace(
             str('unknown file'),
-            '',
+            'ORIGINAL TEXT',
             'REPLACE TEXT',
         )