Skip to content

Commit 9ced664

Browse files
committed
x
1 parent 3bbcf06 commit 9ced664

File tree

5 files changed

+33
-86
lines changed

5 files changed

+33
-86
lines changed

libs/partners/anthropic/langchain_anthropic/middleware/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
StateClaudeTextEditorMiddleware,
88
)
99
from langchain_anthropic.middleware.file_search import (
10-
FilesystemFileSearchMiddleware,
1110
StateFileSearchMiddleware,
1211
)
1312
from langchain_anthropic.middleware.prompt_caching import (
@@ -18,7 +17,6 @@
1817
"AnthropicPromptCachingMiddleware",
1918
"FilesystemClaudeMemoryMiddleware",
2019
"FilesystemClaudeTextEditorMiddleware",
21-
"FilesystemFileSearchMiddleware",
2220
"StateClaudeMemoryMiddleware",
2321
"StateClaudeTextEditorMiddleware",
2422
"StateFileSearchMiddleware",

libs/partners/anthropic/langchain_anthropic/middleware/anthropic_tools.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,8 @@ def _handle_view(self, args: dict, tool_call_id: str | None) -> Command:
748748

749749
# Check file size
750750
if full_path.stat().st_size > self.max_file_size_bytes:
751-
msg = f"File too large: {path} exceeds {self.max_file_size_bytes / 1024 / 1024}MB"
751+
max_mb = self.max_file_size_bytes / 1024 / 1024
752+
msg = f"File too large: {path} exceeds {max_mb}MB"
752753
raise ValueError(msg)
753754

754755
# Read file
@@ -971,7 +972,8 @@ def __init__(
971972
972973
Args:
973974
root_path: Root directory for file operations.
974-
allowed_prefixes: Optional list of allowed virtual path prefixes (default: ["/"]).
975+
allowed_prefixes: Optional list of allowed virtual path prefixes
976+
(default: ["/"]).
975977
max_file_size_mb: Maximum file size in MB (default: 10).
976978
"""
977979
super().__init__(

libs/partners/anthropic/langchain_anthropic/middleware/file_search.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
import fnmatch
1010
import re
1111
from pathlib import Path, PurePosixPath
12-
from typing import Annotated, Literal, cast
12+
from typing import TYPE_CHECKING, Annotated, Literal, cast
13+
14+
if TYPE_CHECKING:
15+
from typing import Any
1316

1417
from langchain.agents.middleware.types import AgentMiddleware
1518
from langchain_core.tools import InjectedToolArg, tool
@@ -166,7 +169,8 @@ def glob_search( # noqa: D417
166169

167170
# Match against pattern
168171
# Handle ** pattern which requires special care
169-
# PurePosixPath.match doesn't match single-level paths against **/pattern
172+
# PurePosixPath.match doesn't match single-level paths
173+
# against **/pattern
170174
is_match = PurePosixPath(relative).match(pattern)
171175
if not is_match and pattern.startswith("**/"):
172176
# Also try matching without the **/ prefix for files in base dir

libs/partners/anthropic/tests/unit_tests/middleware/test_anthropic_tools.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,8 @@ def test_path_prefix_enforcement(self) -> None:
153153
# Should fail with /etc/passwd
154154
args = {"command": "create", "path": "/etc/passwd", "file_text": "test"}
155155

156-
try:
156+
with pytest.raises(ValueError, match="Path must start with"):
157157
middleware._handle_create(args, state, "test_id")
158-
msg = "Should have raised ValueError"
159-
raise AssertionError(msg)
160-
except ValueError as e:
161-
assert "Path must start with" in str(e)
162158

163159
def test_memories_prefix_enforcement(self) -> None:
164160
"""Test that /memories prefix is enforced for memory middleware."""
@@ -169,12 +165,8 @@ def test_memories_prefix_enforcement(self) -> None:
169165
# Should fail with /other/path
170166
args = {"command": "create", "path": "/other/path.txt", "file_text": "test"}
171167

172-
try:
168+
with pytest.raises(ValueError, match="/memories"):
173169
middleware._handle_create(args, state, "test_id")
174-
msg = "Should have raised ValueError"
175-
raise AssertionError(msg)
176-
except ValueError as e:
177-
assert "/memories" in str(e)
178170

179171
def test_str_replace_operation(self) -> None:
180172
"""Test str_replace command execution."""
@@ -200,6 +192,7 @@ def test_str_replace_operation(self) -> None:
200192
result = middleware._handle_str_replace(args, state, "test_id")
201193

202194
assert isinstance(result, Command)
195+
assert result.update is not None
203196
files = result.update.get("text_editor_files", {})
204197
# Should only replace first occurrence
205198
assert files["/test.txt"]["content"] == ["Hello universe", "Goodbye world"]
@@ -228,6 +221,7 @@ def test_insert_operation(self) -> None:
228221
result = middleware._handle_insert(args, state, "test_id")
229222

230223
assert isinstance(result, Command)
224+
assert result.update is not None
231225
files = result.update.get("text_editor_files", {})
232226
assert files["/test.txt"]["content"] == ["inserted", "line1", "line2"]
233227

@@ -250,6 +244,7 @@ def test_delete_operation(self) -> None:
250244
result = middleware._handle_delete(args, state, "test_id")
251245

252246
assert isinstance(result, Command)
247+
assert result.update is not None
253248
files = result.update.get("memory_files", {})
254249
# Deleted files are marked as None in state
255250
assert files.get("/memories/test.txt") is None
@@ -277,6 +272,7 @@ def test_rename_operation(self) -> None:
277272
result = middleware._handle_rename(args, state, "test_id")
278273

279274
assert isinstance(result, Command)
275+
assert result.update is not None
280276
files = result.update.get("memory_files", {})
281277
# Old path is marked as None (deleted)
282278
assert files.get("/memories/old.txt") is None

libs/partners/anthropic/tests/unit_tests/middleware/test_file_search.py

Lines changed: 17 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
"""Unit tests for file search middleware."""
22

3-
from pathlib import Path
4-
from typing import Any
5-
6-
import pytest
7-
83
from langchain_anthropic.middleware.anthropic_tools import AnthropicToolsState
94
from langchain_anthropic.middleware.file_search import (
10-
FilesystemFileSearchMiddleware,
115
StateFileSearchMiddleware,
126
)
137

@@ -56,7 +50,7 @@ def test_glob_basic_pattern(self) -> None:
5650
}
5751

5852
# Call tool function directly (state is injected in real usage)
59-
result = middleware.glob_search.func(pattern="*.py", state=test_state)
53+
result = middleware.glob_search.func(pattern="*.py", state=test_state) # type: ignore[attr-defined]
6054

6155
assert isinstance(result, str)
6256
assert "/src/main.py" in result
@@ -88,7 +82,7 @@ def test_glob_recursive_pattern(self) -> None:
8882
},
8983
}
9084

91-
result = middleware.glob_search.func(pattern="**/*.py", state=state)
85+
result = middleware.glob_search.func(pattern="**/*.py", state=state) # type: ignore[attr-defined]
9286

9387
assert isinstance(result, str)
9488
lines = result.split("\n")
@@ -115,7 +109,7 @@ def test_glob_with_base_path(self) -> None:
115109
},
116110
}
117111

118-
result = middleware.glob_search.func(
112+
result = middleware.glob_search.func( # type: ignore[attr-defined]
119113
pattern="**/*.py", path="/src", state=state
120114
)
121115

@@ -138,7 +132,7 @@ def test_glob_no_matches(self) -> None:
138132
},
139133
}
140134

141-
result = middleware.glob_search.func(pattern="*.ts", state=state)
135+
result = middleware.glob_search.func(pattern="*.ts", state=state) # type: ignore[attr-defined]
142136

143137
assert isinstance(result, str)
144138
assert result == "No files found"
@@ -163,7 +157,7 @@ def test_glob_sorts_by_modified_time(self) -> None:
163157
},
164158
}
165159

166-
result = middleware.glob_search.func(pattern="*.py", state=state)
160+
result = middleware.glob_search.func(pattern="*.py", state=state) # type: ignore[attr-defined]
167161

168162
lines = result.split("\n")
169163
# Most recent first
@@ -199,7 +193,7 @@ def test_grep_files_with_matches_mode(self) -> None:
199193
},
200194
}
201195

202-
result = middleware.grep_search.func(pattern=r"def \w+\(\):", state=state)
196+
result = middleware.grep_search.func(pattern=r"def \w+\(\):", state=state) # type: ignore[attr-defined]
203197

204198
assert isinstance(result, str)
205199
assert "/src/main.py" in result
@@ -222,7 +216,7 @@ def test_grep_invalid_include_pattern(self) -> None:
222216
},
223217
}
224218

225-
result = middleware.grep_search.func(
219+
result = middleware.grep_search.func( # type: ignore[attr-defined]
226220
pattern=r"def", include="*.{py", state=state
227221
)
228222

@@ -232,53 +226,6 @@ def test_grep_invalid_include_pattern(self) -> None:
232226
class TestFilesystemGrepSearch:
233227
"""Tests for filesystem-backed grep search."""
234228

235-
def test_grep_invalid_include_pattern(self, tmp_path: Path) -> None:
236-
"""Return error when include glob cannot be parsed."""
237-
238-
(tmp_path / "example.py").write_text("print('hello')\n", encoding="utf-8")
239-
240-
middleware = FilesystemFileSearchMiddleware(
241-
root_path=str(tmp_path), use_ripgrep=False
242-
)
243-
244-
result = middleware.grep_search.func(pattern="print", include="*.{py")
245-
246-
assert result == "Invalid include pattern"
247-
248-
def test_ripgrep_command_uses_literal_pattern(
249-
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
250-
) -> None:
251-
"""Ensure ripgrep receives pattern after ``--`` to avoid option parsing."""
252-
253-
(tmp_path / "example.py").write_text("print('hello')\n", encoding="utf-8")
254-
255-
middleware = FilesystemFileSearchMiddleware(
256-
root_path=str(tmp_path), use_ripgrep=True
257-
)
258-
259-
captured: dict[str, list[str]] = {}
260-
261-
class DummyResult:
262-
stdout = ""
263-
264-
def fake_run(*args: Any, **kwargs: Any) -> DummyResult:
265-
cmd = args[0]
266-
captured["cmd"] = cmd
267-
return DummyResult()
268-
269-
monkeypatch.setattr(
270-
"langchain_anthropic.middleware.file_search.subprocess.run", fake_run
271-
)
272-
273-
middleware._ripgrep_search("--pattern", "/", None)
274-
275-
assert "cmd" in captured
276-
cmd = captured["cmd"]
277-
assert cmd[:2] == ["rg", "--json"]
278-
assert "--" in cmd
279-
separator_index = cmd.index("--")
280-
assert cmd[separator_index + 1] == "--pattern"
281-
282229
def test_grep_content_mode(self) -> None:
283230
"""Test grep with content output mode."""
284231
middleware = StateFileSearchMiddleware()
@@ -294,7 +241,7 @@ def test_grep_content_mode(self) -> None:
294241
},
295242
}
296243

297-
result = middleware.grep_search.func(
244+
result = middleware.grep_search.func( # type: ignore[attr-defined]
298245
pattern=r"def \w+\(\):", output_mode="content", state=state
299246
)
300247

@@ -324,7 +271,7 @@ def test_grep_count_mode(self) -> None:
324271
},
325272
}
326273

327-
result = middleware.grep_search.func(
274+
result = middleware.grep_search.func( # type: ignore[attr-defined]
328275
pattern=r"TODO", output_mode="count", state=state
329276
)
330277

@@ -353,7 +300,7 @@ def test_grep_with_include_filter(self) -> None:
353300
},
354301
}
355302

356-
result = middleware.grep_search.func(
303+
result = middleware.grep_search.func( # type: ignore[attr-defined]
357304
pattern="import", include="*.py", state=state
358305
)
359306

@@ -386,7 +333,7 @@ def test_grep_with_brace_expansion_filter(self) -> None:
386333
},
387334
}
388335

389-
result = middleware.grep_search.func(
336+
result = middleware.grep_search.func( # type: ignore[attr-defined]
390337
pattern="const", include="*.{ts,tsx}", state=state
391338
)
392339

@@ -415,7 +362,7 @@ def test_grep_with_base_path(self) -> None:
415362
},
416363
}
417364

418-
result = middleware.grep_search.func(pattern="import", path="/src", state=state)
365+
result = middleware.grep_search.func(pattern="import", path="/src", state=state) # type: ignore[attr-defined]
419366

420367
assert isinstance(result, str)
421368
assert "/src/main.py" in result
@@ -436,7 +383,7 @@ def test_grep_no_matches(self) -> None:
436383
},
437384
}
438385

439-
result = middleware.grep_search.func(pattern=r"TODO", state=state)
386+
result = middleware.grep_search.func(pattern=r"TODO", state=state) # type: ignore[attr-defined]
440387

441388
assert isinstance(result, str)
442389
assert result == "No matches found"
@@ -450,7 +397,7 @@ def test_grep_invalid_regex(self) -> None:
450397
"text_editor_files": {},
451398
}
452399

453-
result = middleware.grep_search.func(pattern=r"[unclosed", state=state)
400+
result = middleware.grep_search.func(pattern=r"[unclosed", state=state) # type: ignore[attr-defined]
454401

455402
assert isinstance(result, str)
456403
assert "Invalid regex pattern" in result
@@ -481,7 +428,7 @@ def test_glob_default_backend(self) -> None:
481428
},
482429
}
483430

484-
result = middleware.glob_search.func(pattern="**/*", state=state)
431+
result = middleware.glob_search.func(pattern="**/*", state=state) # type: ignore[attr-defined]
485432

486433
assert isinstance(result, str)
487434
assert "/src/main.py" in result
@@ -510,7 +457,7 @@ def test_grep_default_backend(self) -> None:
510457
},
511458
}
512459

513-
result = middleware.grep_search.func(pattern=r"TODO", state=state)
460+
result = middleware.grep_search.func(pattern=r"TODO", state=state) # type: ignore[attr-defined]
514461

515462
assert isinstance(result, str)
516463
assert "/src/main.py" in result
@@ -539,7 +486,7 @@ def test_search_with_single_store(self) -> None:
539486
},
540487
}
541488

542-
result = middleware.grep_search.func(pattern=r".*", state=state)
489+
result = middleware.grep_search.func(pattern=r".*", state=state) # type: ignore[attr-defined]
543490

544491
assert isinstance(result, str)
545492
assert "/src/main.py" in result

0 commit comments

Comments
 (0)