Skip to content

Commit bf3f4cd

Browse files
committed
split file view from code edit tools
1 parent 31be1e3 commit bf3f4cd

File tree

2 files changed

+85
-52
lines changed

2 files changed

+85
-52
lines changed

patchwork/common/tools/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from patchwork.common.tools.bash_tool import BashTool
2-
from patchwork.common.tools.code_edit_tools import CodeEditTool
2+
from patchwork.common.tools.code_edit_tools import CodeEditTool, FileViewTool
33
from patchwork.common.tools.grep_tool import FindTextTool, FindTool
44
from patchwork.common.tools.tool import Tool
55

66
__all__ = [
77
"Tool",
88
"CodeEditTool",
99
"BashTool",
10+
"FileViewTool",
1011
"FindTool",
1112
"FindTextTool",
1213
]

patchwork/common/tools/code_edit_tools.py

Lines changed: 83 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,86 @@
88
from patchwork.common.utils.utils import detect_newline
99

1010

11-
class CodeEditTool(Tool, tool_name="code_edit_tool"):
11+
class FileViewTool(Tool, tool_name="file_view"):
12+
__TRUNCATION_TOKEN = "<TRUNCATED>"
1213
__VIEW_LIMIT = 3000
1314

15+
def __init__(self, path: Union[Path, str]):
16+
super().__init__()
17+
self.repo_path = Path(path).resolve()
18+
19+
@property
20+
def json_schema(self) -> dict:
21+
return {
22+
"name": "file_view",
23+
"description": f"""\
24+
Custom tool for viewing files
25+
26+
* If `path` is a file, `view` displays the result of applying `cat -n` up to {self.__VIEW_LIMIT} characters. If `path` is a directory, `view` lists non-hidden files and directories.
27+
* The output is too lone, it will be truncated and marked with `{self.__TRUNCATION_TOKEN}`
28+
* The working directory is always {self.repo_path}
29+
""",
30+
"input_schema": {
31+
"type": "object",
32+
"properties": {
33+
"path": {
34+
"description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`.",
35+
"type": "string",
36+
},
37+
"view_range": {
38+
"description": "Optional parameter when `path` points to a file. If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file.",
39+
"items": {"type": "integer"},
40+
"type": "array",
41+
},
42+
},
43+
"required": ["path"],
44+
},
45+
}
46+
47+
def __get_abs_path(self, path: str):
48+
wanted_path = Path(path).resolve()
49+
if wanted_path.is_relative_to(self.repo_path):
50+
return wanted_path
51+
else:
52+
raise ValueError(f"Path {path} contains illegal path traversal")
53+
54+
def execute(self, path: str, view_range: Optional[list[int]] = None) -> str:
55+
abs_path = self.__get_abs_path(path)
56+
if not abs_path.exists():
57+
return f"Error: Path {abs_path} does not exist"
58+
59+
if abs_path.is_file():
60+
with open(abs_path, "r") as f:
61+
content = f.read()
62+
63+
if view_range:
64+
lines = content.splitlines()
65+
start, end = view_range
66+
content = "\n".join(lines[start - 1 : end])
67+
68+
if len(content) > self.__VIEW_LIMIT:
69+
content = content[: self.__VIEW_LIMIT] + self.__TRUNCATION_TOKEN
70+
return content
71+
elif abs_path.is_dir():
72+
directories = []
73+
files = []
74+
for file in abs_path.iterdir():
75+
directories.append(file.name) if file.is_dir() else files.append(file.name)
76+
77+
rv = ""
78+
if len(directories) > 0:
79+
rv += "Directories: \n"
80+
rv += "\n".join(directories)
81+
rv += "\n"
82+
83+
if len(files) > 0:
84+
rv += "Files: \n"
85+
rv += "\n".join(files)
86+
87+
return rv
88+
89+
90+
class CodeEditTool(Tool, tool_name="code_edit_tool"):
1491
def __init__(self, path: Union[Path, str]):
1592
super().__init__()
1693
self.repo_path = Path(path).resolve()
@@ -24,9 +101,7 @@ def json_schema(self) -> dict:
24101
Custom editing tool for viewing, creating and editing files
25102
26103
* State is persistent across command calls and discussions with the user
27-
* If `path` is a file, `view` displays the result of applying `cat -n` up to {self.__VIEW_LIMIT} characters. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep
28104
* The `create` command cannot be used if the specified `path` already exists as a file
29-
* If a `command` generates a long output, it will be truncated and marked with `<response clipped>`
30105
* The working directory is always {self.repo_path}
31106
32107
Notes for using the `str_replace` command:
@@ -38,8 +113,8 @@ def json_schema(self) -> dict:
38113
"properties": {
39114
"command": {
40115
"type": "string",
41-
"enum": ["view", "create", "str_replace", "insert"],
42-
"description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`.",
116+
"enum": ["create", "str_replace", "insert"],
117+
"description": "The commands to run. Allowed options are: `create`, `str_replace`, `insert`.",
43118
},
44119
"file_text": {
45120
"description": "Required parameter of `create` command, with the content of the file to be created.",
@@ -61,25 +136,19 @@ def json_schema(self) -> dict:
61136
"description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`.",
62137
"type": "string",
63138
},
64-
"view_range": {
65-
"description": "Optional parameter of `view` command when `path` points to a file. If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file.",
66-
"items": {"type": "integer"},
67-
"type": "array",
68-
},
69139
},
70140
"required": ["command", "path"],
71141
},
72142
}
73143

74144
def execute(
75145
self,
76-
command: Optional[Literal["view", "create", "str_replace", "insert"]] = None,
146+
command: Optional[Literal["create", "str_replace", "insert"]] = None,
77147
file_text: str = "",
78148
insert_line: Optional[int] = None,
79149
new_str: str = "",
80150
old_str: Optional[str] = None,
81151
path: Optional[str] = None,
82-
view_range: Optional[list[int]] = None,
83152
) -> str:
84153
"""Execute editor commands on files in the repository."""
85154
required_dict = dict(command=command, path=path)
@@ -89,9 +158,7 @@ def execute(
89158

90159
try:
91160
abs_path = self.__get_abs_path(path)
92-
if command == "view":
93-
result = self.__view(abs_path, view_range)
94-
elif command == "create":
161+
if command == "create":
95162
result = self.__create(file_text, abs_path)
96163
elif command == "str_replace":
97164
result = self.__str_replace(new_str, old_str, abs_path)
@@ -102,9 +169,8 @@ def execute(
102169
except Exception as e:
103170
return f"Error: {str(e)}"
104171

105-
if command in {"create", "str_replace", "insert"}:
106-
self.modified_files.update({abs_path})
107172

173+
self.modified_files.update({abs_path})
108174
return result
109175

110176
@property
@@ -118,40 +184,6 @@ def __get_abs_path(self, path: str):
118184
else:
119185
raise ValueError(f"Path {path} contains illegal path traversal")
120186

121-
def __view(self, abs_path: Path, view_range):
122-
if not abs_path.exists():
123-
return f"Error: Path {abs_path} does not exist"
124-
125-
if abs_path.is_file():
126-
with open(abs_path, "r") as f:
127-
content = f.read()
128-
129-
if view_range:
130-
lines = content.splitlines()
131-
start, end = view_range
132-
content = "\n".join(lines[start - 1 : end])
133-
134-
if len(content) > self.__VIEW_LIMIT:
135-
content = content[: self.__VIEW_LIMIT] + "<TRUNCATED>"
136-
return content
137-
elif abs_path.is_dir():
138-
directories = []
139-
files = []
140-
for file in abs_path.iterdir():
141-
directories.append(file.name) if file.is_dir() else files.append(file.name)
142-
143-
rv = ""
144-
if len(directories) > 0:
145-
rv += "Directories: \n"
146-
rv += "\n".join(directories)
147-
rv += "\n"
148-
149-
if len(files) > 0:
150-
rv += "Files: \n"
151-
rv += "\n".join(files)
152-
153-
return rv
154-
155187
def __create(self, file_text, abs_path):
156188
if abs_path.exists():
157189
return f"Error: File {abs_path} already exists"

0 commit comments

Comments
 (0)