diff --git a/app/tools/read_file.py b/app/tools/read_file.py index a575963..1e301f7 100755 --- a/app/tools/read_file.py +++ b/app/tools/read_file.py @@ -8,13 +8,21 @@ def spec(): "type": "function", "function": { "name": "read_file", - "description": "Read and return the contents of a file", + "description": "Read and return the contents of a file. Supports optional offset and size parameters for partial reads.", "parameters": { "type": "object", "properties": { "file_path": { "type": "string", "description": "The path to the file to read" + }, + "offset": { + "type": "integer", + "description": "Byte offset to start reading from (default: 0)" + }, + "size": { + "type": "integer", + "description": "Maximum number of bytes to read (default: read to end of file)" } }, "required": ["file_path"] @@ -23,12 +31,20 @@ def spec(): } @staticmethod - def call(file_path: str) -> str: - log.info(f"read_file, file_path: {file_path}") + def call(file_path: str, offset: int = 0, size: int | None = None) -> str: + log.info(f"read_file, file_path: {file_path}, offset: {offset}, size: {size}") + + if offset < 0: + return "Error: offset must be non-negative" + if size is not None and size < 0: + return "Error: size must be non-negative" try: - with open(file_path, encoding="utf-8") as f: - return f.read() + with open(file_path, "rb") as f: + if offset: + f.seek(offset) + data = f.read(size) if size is not None else f.read() + return data.decode("utf-8", errors="replace") except FileNotFoundError: return f"Error: file {file_path} does not exist" except Exception as e: diff --git a/tests/test_read_file.py b/tests/test_read_file.py index f93e6c2..f10e19e 100644 --- a/tests/test_read_file.py +++ b/tests/test_read_file.py @@ -25,3 +25,46 @@ def test_read_file_multiline(tmp_path): f = tmp_path / "multi.txt" f.write_text(content, encoding="utf-8") assert read_file(str(f)) == content + + +def test_read_file_with_offset(tmp_path): + f = tmp_path / "offset.txt" + f.write_text("hello world", encoding="utf-8") + assert read_file(str(f), offset=6) == "world" + + +def test_read_file_with_size(tmp_path): + f = tmp_path / "size.txt" + f.write_text("hello world", encoding="utf-8") + assert read_file(str(f), size=5) == "hello" + + +def test_read_file_with_offset_and_size(tmp_path): + f = tmp_path / "both.txt" + f.write_text("hello world", encoding="utf-8") + assert read_file(str(f), offset=3, size=4) == "lo w" + + +def test_read_file_offset_beyond_end(tmp_path): + f = tmp_path / "short.txt" + f.write_text("hi", encoding="utf-8") + assert read_file(str(f), offset=100) == "" + + +def test_read_file_size_larger_than_remaining(tmp_path): + f = tmp_path / "small.txt" + f.write_text("hello", encoding="utf-8") + assert read_file(str(f), offset=3, size=100) == "lo" + + +def test_read_file_negative_offset_or_size(tmp_path): + f = tmp_path / "test.txt" + f.write_text("hello", encoding="utf-8") + assert "Error" in read_file(str(f), offset=-1) + assert "Error" in read_file(str(f), size=-1) + + +def test_read_file_utf8_split(tmp_path): + f = tmp_path / "utf8.txt" + f.write_bytes(b"abc\xf0\x9f\x9a\x80def") + assert read_file(str(f), offset=3, size=2) == "\ufffd"