mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-22 01:35:28 -04:00
fix(sdk): normalize read slices after windowing (#3888)
Changes `slice_read_response` to split first and normalize line endings only across the requested read window. This avoids rewriting the full file contents for paginated reads while preserving LF-normalized output for downstream edit and grep behavior. ### Benchmarks Focused local microbenchmarks compared the original combined branch against `main` on Python 3.14.2: - `slice_read_response` on large CRLF content: **2.55x faster** for both first-window and late-window reads (~60.8% improvement), with **~29.2% lower peak memory**.
This commit is contained in:
@@ -291,15 +291,12 @@ def slice_read_response(
|
||||
if not content or content.strip() == "":
|
||||
return content
|
||||
|
||||
# Normalize line endings to LF before slicing. State/Store backends may
|
||||
# carry CRLF or CR content as written; downstream tooling (edit match,
|
||||
# grep, format) assumes LF.
|
||||
content = content.replace("\r\n", "\n").replace("\r", "\n")
|
||||
|
||||
# `splitlines(keepends=True)` retains each line's terminator, including
|
||||
# the absence of one on the final line. Joining with `""` therefore
|
||||
# round-trips the trailing-newline state of the file faithfully —
|
||||
# required so `edit()` can report EOF-newline mismatches accurately.
|
||||
# required so `edit()` can report EOF-newline mismatches accurately. It
|
||||
# also splits on CR / CRLF, so line indexing matches the LF-normalized
|
||||
# form without first rewriting the whole (potentially huge) string.
|
||||
lines = content.splitlines(keepends=True)
|
||||
start_idx = offset
|
||||
end_idx = min(start_idx + limit, len(lines))
|
||||
@@ -307,7 +304,10 @@ def slice_read_response(
|
||||
if start_idx >= len(lines):
|
||||
return ReadResult(error=f"Line offset {offset} exceeds file length ({len(lines)} lines)")
|
||||
|
||||
return "".join(lines[start_idx:end_idx])
|
||||
# Normalize line endings to LF, but only across the requested window.
|
||||
# State/Store backends may carry CRLF or CR content as written;
|
||||
# downstream tooling (edit match, grep, format) assumes LF.
|
||||
return "".join(lines[start_idx:end_idx]).replace("\r\n", "\n").replace("\r", "\n")
|
||||
|
||||
|
||||
def perform_string_replacement(
|
||||
|
||||
@@ -343,6 +343,12 @@ class TestSliceReadResponse:
|
||||
result = slice_read_response(self._file("a\nb\nc\nd\n"), offset=1, limit=2)
|
||||
assert result == "b\nc\n"
|
||||
|
||||
def test_partial_window_normalizes_crlf(self) -> None:
|
||||
"""An internal CRLF slice is LF-normalized even though only the window is rewritten."""
|
||||
result = slice_read_response(self._file("a\r\nb\r\nc\r\nd\r\n"), offset=1, limit=2)
|
||||
assert result == "b\nc\n"
|
||||
assert "\r" not in result
|
||||
|
||||
def test_partial_window_ending_on_unterminated_last_line(self) -> None:
|
||||
"""A window covering the last line keeps that line's missing-terminator state."""
|
||||
result = slice_read_response(self._file("a\nb\nc"), offset=2, limit=1)
|
||||
|
||||
Reference in New Issue
Block a user