Skip to content

Commit 05ceca7

Browse files
fix(selfHosted): support file path aliases
Sync-Source-Commit: cf3337b788fc74ac1cde11d50f78730774cd7e5c Hand-Written-Reason: No Ark-APIs provenance marker; treated as a hand-written source commit. Release-Version: 0.12.0
1 parent a8910e0 commit 05ceca7

2 files changed

Lines changed: 56 additions & 6 deletions

File tree

‎src/arkruntime/selfhosted/tools.py‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class ReadFileTool(Tool):
177177
def execute(self, tool_input: Any, context: ToolContext) -> ToolResult:
178178
args = _as_mapping(tool_input)
179179
try:
180-
path = _safe_path(context, _read_file_path(args))
180+
path = _safe_path(context, _file_path(args))
181181
except ValueError as exc:
182182
return error_result(str(exc))
183183
view_range = args.get("view_range")
@@ -239,7 +239,7 @@ def execute(self, tool_input: Any, context: ToolContext) -> ToolResult:
239239
return text_result(_render_read_lines(text, offset, limit))
240240

241241

242-
def _read_file_path(args: Mapping[str, Any]) -> str:
242+
def _file_path(args: Mapping[str, Any]) -> str:
243243
path = ""
244244
for name in ("file_path", "path", "file"):
245245
value = args.get(name)
@@ -329,8 +329,11 @@ class WriteFileTool(Tool):
329329

330330
def execute(self, tool_input: Any, context: ToolContext) -> ToolResult:
331331
args = _as_mapping(tool_input)
332-
raw_path = str(args.get("path") or args.get("file") or "")
333-
path = _safe_path(context, raw_path)
332+
try:
333+
raw_path = _file_path(args)
334+
path = _safe_path(context, raw_path)
335+
except ValueError as exc:
336+
return error_result(str(exc))
334337
content = str(args.get("content") or "")
335338
Path(path).parent.mkdir(parents=True, exist_ok=True)
336339
verified = _safe_path(context, raw_path)
@@ -345,8 +348,11 @@ class EditFileTool(Tool):
345348

346349
def execute(self, tool_input: Any, context: ToolContext) -> ToolResult:
347350
args = _as_mapping(tool_input)
348-
raw_path = str(args.get("path") or args.get("file") or "")
349-
path = _safe_path(context, raw_path)
351+
try:
352+
raw_path = _file_path(args)
353+
path = _safe_path(context, raw_path)
354+
except ValueError as exc:
355+
return error_result(str(exc))
350356
old = str(args.get("old_string") or args.get("old") or "")
351357
new = str(args.get("new_string") or args.get("new") or "")
352358
if not old:

‎tests/selfhosted/test_tools.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,50 @@ def test_read_keeps_legacy_inputs_without_ambiguity(tmp_path) -> None:
165165
assert _text(range_conflict) == "view_range cannot be combined with offset or limit"
166166

167167

168+
@pytest.mark.parametrize("path_key", ["file_path", "path", "file"])
169+
def test_file_tools_accept_path_aliases(tmp_path, path_key) -> None:
170+
context = ToolContext(workdir=str(tmp_path))
171+
source = tmp_path / "source.txt"
172+
source.write_text("old")
173+
174+
read = ReadFileTool().execute({path_key: "source.txt"}, context)
175+
write = WriteFileTool().execute({path_key: "written.txt", "content": "created"}, context)
176+
edit = EditFileTool().execute(
177+
{path_key: "source.txt", "old_string": "old", "new_string": "new"},
178+
context,
179+
)
180+
181+
assert not read.is_error
182+
assert not write.is_error
183+
assert not edit.is_error
184+
assert (tmp_path / "written.txt").read_text() == "created"
185+
assert source.read_text() == "new"
186+
187+
188+
@pytest.mark.parametrize("tool", [ReadFileTool(), WriteFileTool(), EditFileTool()])
189+
def test_file_tools_validate_path_aliases(tmp_path, tool) -> None:
190+
context = ToolContext(workdir=str(tmp_path))
191+
(tmp_path / "example.txt").write_text("old")
192+
common = {
193+
"file_path": "example.txt",
194+
"path": "example.txt",
195+
"file": "example.txt",
196+
"content": "new",
197+
"old_string": "old",
198+
"new_string": "new",
199+
}
200+
201+
same = tool.execute(common, context)
202+
conflict = tool.execute({**common, "path": "other.txt"}, context)
203+
missing = tool.execute({"content": "new", "old_string": "old", "new_string": "new"}, context)
204+
205+
assert not same.is_error
206+
assert conflict.is_error
207+
assert _text(conflict) == "file_path, path, and file must not conflict"
208+
assert missing.is_error
209+
assert _text(missing) == "file_path is required"
210+
211+
168212
def test_grep_skips_symlink_that_escapes_workdir(tmp_path) -> None:
169213
outside = tmp_path.parent / f"{tmp_path.name}-outside"
170214
outside.mkdir()

0 commit comments

Comments
 (0)