mojentic 0.7.1__py3-none-any.whl → 0.7.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _examples/broker_as_tool.py +13 -10
- _examples/coding_file_tool.py +170 -77
- _examples/file_tool.py +5 -3
- mojentic/__init__.py +2 -7
- mojentic/agents/__init__.py +11 -2
- mojentic/context/__init__.py +4 -0
- mojentic/llm/__init__.py +14 -2
- mojentic/llm/gateways/__init__.py +22 -0
- mojentic/llm/message_composers.py +1 -1
- mojentic/llm/registry/__init__.py +6 -0
- mojentic/llm/registry/populate_registry_from_ollama.py +13 -12
- mojentic/llm/tools/__init__.py +18 -0
- mojentic/llm/tools/date_resolver.py +5 -2
- mojentic/llm/tools/ephemeral_task_manager/__init__.py +8 -8
- mojentic/llm/tools/file_manager.py +603 -42
- mojentic/llm/tools/file_manager_spec.py +723 -0
- mojentic/llm/tools/tool_wrapper.py +7 -3
- mojentic/tracer/__init__.py +8 -3
- {mojentic-0.7.1.dist-info → mojentic-0.7.2.dist-info}/METADATA +3 -2
- {mojentic-0.7.1.dist-info → mojentic-0.7.2.dist-info}/RECORD +23 -22
- {mojentic-0.7.1.dist-info → mojentic-0.7.2.dist-info}/WHEEL +0 -0
- {mojentic-0.7.1.dist-info → mojentic-0.7.2.dist-info}/licenses/LICENSE.md +0 -0
- {mojentic-0.7.1.dist-info → mojentic-0.7.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import glob
|
|
4
|
+
import tempfile
|
|
5
|
+
import shutil
|
|
6
|
+
from unittest.mock import Mock, patch
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from mojentic.llm.tools.file_manager import (
|
|
11
|
+
FilesystemGateway, FileManager, ListFilesTool, ReadFileTool, WriteFileTool,
|
|
12
|
+
ListAllFilesTool, FindFilesByGlobTool, FindFilesContainingTool, FindLinesMatchingTool,
|
|
13
|
+
EditFileWithDiffTool, CreateDirectoryTool
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def temp_dir():
|
|
19
|
+
"""Create a temporary directory for testing."""
|
|
20
|
+
temp_dir = tempfile.mkdtemp()
|
|
21
|
+
yield temp_dir
|
|
22
|
+
shutil.rmtree(temp_dir)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def fs_gateway(temp_dir):
|
|
27
|
+
"""Create a FilesystemGateway with a temporary directory."""
|
|
28
|
+
return FilesystemGateway(base_path=temp_dir)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def file_manager(fs_gateway):
|
|
33
|
+
"""Create a FileManager with a filesystem gateway."""
|
|
34
|
+
return FileManager(fs=fs_gateway)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def setup_test_files(temp_dir):
|
|
39
|
+
"""Set up test files in the temporary directory."""
|
|
40
|
+
# Create a directory structure
|
|
41
|
+
os.makedirs(os.path.join(temp_dir, "subdir"), exist_ok=True)
|
|
42
|
+
|
|
43
|
+
# Create test files
|
|
44
|
+
with open(os.path.join(temp_dir, "test1.txt"), "w") as f:
|
|
45
|
+
f.write("This is test file 1\nIt has multiple lines\nFor testing purposes")
|
|
46
|
+
|
|
47
|
+
with open(os.path.join(temp_dir, "test2.py"), "w") as f:
|
|
48
|
+
f.write("def test_function():\n return 'Hello, World!'")
|
|
49
|
+
|
|
50
|
+
with open(os.path.join(temp_dir, "subdir", "test3.txt"), "w") as f:
|
|
51
|
+
f.write("This is a test file in a subdirectory")
|
|
52
|
+
|
|
53
|
+
return temp_dir
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class DescribeFilesystemGateway:
|
|
57
|
+
|
|
58
|
+
def should_resolve_path_correctly(self, fs_gateway, temp_dir):
|
|
59
|
+
"""
|
|
60
|
+
Given a FilesystemGateway
|
|
61
|
+
When resolving a path
|
|
62
|
+
Then it should correctly join with the base path
|
|
63
|
+
"""
|
|
64
|
+
resolved_path = fs_gateway._resolve_path("test.txt")
|
|
65
|
+
expected_path = os.path.normpath(os.path.join(temp_dir, "test.txt"))
|
|
66
|
+
assert resolved_path == expected_path
|
|
67
|
+
|
|
68
|
+
def should_prevent_path_traversal(self, fs_gateway):
|
|
69
|
+
"""
|
|
70
|
+
Given a FilesystemGateway
|
|
71
|
+
When resolving a path that attempts to escape the sandbox
|
|
72
|
+
Then it should raise a ValueError
|
|
73
|
+
"""
|
|
74
|
+
with pytest.raises(ValueError):
|
|
75
|
+
fs_gateway._resolve_path("../../../etc/passwd")
|
|
76
|
+
|
|
77
|
+
def should_list_files(self, fs_gateway, setup_test_files):
|
|
78
|
+
"""
|
|
79
|
+
Given a FilesystemGateway with test files
|
|
80
|
+
When listing files in the root directory
|
|
81
|
+
Then it should return the correct list of files
|
|
82
|
+
"""
|
|
83
|
+
files = fs_gateway.ls("")
|
|
84
|
+
assert sorted(files) == sorted(["subdir", "test1.txt", "test2.py"])
|
|
85
|
+
|
|
86
|
+
def should_list_all_files_recursively(self, fs_gateway, setup_test_files):
|
|
87
|
+
"""
|
|
88
|
+
Given a FilesystemGateway with test files
|
|
89
|
+
When listing all files recursively
|
|
90
|
+
Then it should return all files including those in subdirectories
|
|
91
|
+
"""
|
|
92
|
+
all_files = fs_gateway.list_all_files("")
|
|
93
|
+
assert sorted(all_files) == sorted(["test1.txt", "test2.py", os.path.join("subdir", "test3.txt")])
|
|
94
|
+
|
|
95
|
+
def should_find_files_by_glob(self, fs_gateway, setup_test_files):
|
|
96
|
+
"""
|
|
97
|
+
Given a FilesystemGateway with test files
|
|
98
|
+
When finding files by glob pattern
|
|
99
|
+
Then it should return files matching the pattern
|
|
100
|
+
"""
|
|
101
|
+
txt_files = fs_gateway.find_files_by_glob("", "*.txt")
|
|
102
|
+
assert sorted(txt_files) == sorted(["test1.txt"])
|
|
103
|
+
|
|
104
|
+
all_txt_files = fs_gateway.find_files_by_glob("", "**/*.txt")
|
|
105
|
+
assert sorted(all_txt_files) == sorted(["test1.txt", os.path.join("subdir", "test3.txt")])
|
|
106
|
+
|
|
107
|
+
def should_find_files_containing(self, fs_gateway, setup_test_files):
|
|
108
|
+
"""
|
|
109
|
+
Given a FilesystemGateway with test files
|
|
110
|
+
When finding files containing a pattern
|
|
111
|
+
Then it should return files with content matching the pattern
|
|
112
|
+
"""
|
|
113
|
+
files_with_hello = fs_gateway.find_files_containing("", "Hello")
|
|
114
|
+
assert files_with_hello == ["test2.py"]
|
|
115
|
+
|
|
116
|
+
files_with_test = fs_gateway.find_files_containing("", "test file")
|
|
117
|
+
assert sorted(files_with_test) == sorted(["test1.txt", os.path.join("subdir", "test3.txt")])
|
|
118
|
+
|
|
119
|
+
def should_find_lines_matching(self, fs_gateway, setup_test_files):
|
|
120
|
+
"""
|
|
121
|
+
Given a FilesystemGateway with test files
|
|
122
|
+
When finding lines matching a pattern in a file
|
|
123
|
+
Then it should return the matching lines with line numbers
|
|
124
|
+
"""
|
|
125
|
+
matching_lines = fs_gateway.find_lines_matching("", "test1.txt", "multiple")
|
|
126
|
+
assert matching_lines == [{"line_number": 2, "content": "It has multiple lines"}]
|
|
127
|
+
|
|
128
|
+
def should_edit_file_with_diff(self, fs_gateway, setup_test_files):
|
|
129
|
+
"""
|
|
130
|
+
Given a FilesystemGateway with test files
|
|
131
|
+
When editing a file with a diff
|
|
132
|
+
Then it should apply the changes correctly
|
|
133
|
+
"""
|
|
134
|
+
# Create a diff that changes "test file 1" to "modified file 1"
|
|
135
|
+
diff = """--- test1.txt
|
|
136
|
+
+++ test1.txt
|
|
137
|
+
@@ -1,3 +1,3 @@
|
|
138
|
+
-This is test file 1
|
|
139
|
+
+This is modified file 1
|
|
140
|
+
It has multiple lines
|
|
141
|
+
For testing purposes"""
|
|
142
|
+
|
|
143
|
+
result = fs_gateway.edit_file_with_diff("", "test1.txt", diff)
|
|
144
|
+
assert "Successfully" in result
|
|
145
|
+
|
|
146
|
+
# Verify the file was modified
|
|
147
|
+
with open(os.path.join(setup_test_files, "test1.txt"), "r") as f:
|
|
148
|
+
content = f.read()
|
|
149
|
+
|
|
150
|
+
assert "This is modified file 1" in content
|
|
151
|
+
assert "This is test file 1" not in content
|
|
152
|
+
assert "It has multiple lines" in content
|
|
153
|
+
assert "For testing purposes" in content
|
|
154
|
+
|
|
155
|
+
def should_edit_file_with_diff_adding_lines(self, fs_gateway, setup_test_files):
|
|
156
|
+
"""
|
|
157
|
+
Given a FilesystemGateway with test files
|
|
158
|
+
When editing a file with a diff that adds lines
|
|
159
|
+
Then it should add the lines while preserving the rest of the content
|
|
160
|
+
"""
|
|
161
|
+
# Create a test file with known content
|
|
162
|
+
test_file_path = os.path.join(setup_test_files, "add_lines_test.txt")
|
|
163
|
+
with open(test_file_path, 'w') as f:
|
|
164
|
+
f.write("Line 1\nLine 2\nLine 3\n")
|
|
165
|
+
|
|
166
|
+
# Create a diff that adds a new line after Line 2
|
|
167
|
+
diff = """--- add_lines_test.txt
|
|
168
|
+
+++ add_lines_test.txt
|
|
169
|
+
@@ -1,3 +1,4 @@
|
|
170
|
+
Line 1
|
|
171
|
+
Line 2
|
|
172
|
+
+New Line 2.5
|
|
173
|
+
Line 3
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
result = fs_gateway.edit_file_with_diff("", "add_lines_test.txt", diff)
|
|
177
|
+
assert "Successfully" in result
|
|
178
|
+
|
|
179
|
+
# Verify the file was modified correctly
|
|
180
|
+
with open(test_file_path, "r") as f:
|
|
181
|
+
content = f.read()
|
|
182
|
+
|
|
183
|
+
assert "Line 1\nLine 2\nNew Line 2.5\nLine 3\n" == content
|
|
184
|
+
|
|
185
|
+
def should_edit_file_with_diff_removing_lines(self, fs_gateway, setup_test_files):
|
|
186
|
+
"""
|
|
187
|
+
Given a FilesystemGateway with test files
|
|
188
|
+
When editing a file with a diff that removes lines
|
|
189
|
+
Then it should remove the lines while preserving the rest of the content
|
|
190
|
+
"""
|
|
191
|
+
# Create a test file with known content
|
|
192
|
+
test_file_path = os.path.join(setup_test_files, "remove_lines_test.txt")
|
|
193
|
+
with open(test_file_path, 'w') as f:
|
|
194
|
+
f.write("Line 1\nLine 2\nLine 3\nLine 4\n")
|
|
195
|
+
|
|
196
|
+
# Create a diff that removes Line 3
|
|
197
|
+
diff = """--- remove_lines_test.txt
|
|
198
|
+
+++ remove_lines_test.txt
|
|
199
|
+
@@ -1,4 +1,3 @@
|
|
200
|
+
Line 1
|
|
201
|
+
Line 2
|
|
202
|
+
-Line 3
|
|
203
|
+
Line 4
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
result = fs_gateway.edit_file_with_diff("", "remove_lines_test.txt", diff)
|
|
207
|
+
assert "Successfully" in result
|
|
208
|
+
|
|
209
|
+
# Verify the file was modified correctly
|
|
210
|
+
with open(test_file_path, "r") as f:
|
|
211
|
+
content = f.read()
|
|
212
|
+
|
|
213
|
+
assert "Line 1\nLine 2\nLine 4\n" == content
|
|
214
|
+
|
|
215
|
+
def should_edit_file_with_diff_multiple_hunks(self, fs_gateway, setup_test_files):
|
|
216
|
+
"""
|
|
217
|
+
Given a FilesystemGateway with test files
|
|
218
|
+
When editing a file with a diff that contains multiple hunks
|
|
219
|
+
Then it should apply all changes correctly
|
|
220
|
+
"""
|
|
221
|
+
# Create a test file with known content
|
|
222
|
+
test_file_path = os.path.join(setup_test_files, "multiple_hunks_test.txt")
|
|
223
|
+
with open(test_file_path, 'w') as f:
|
|
224
|
+
f.write("Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\n")
|
|
225
|
+
|
|
226
|
+
# Create a diff with multiple hunks
|
|
227
|
+
diff = """--- multiple_hunks_test.txt
|
|
228
|
+
+++ multiple_hunks_test.txt
|
|
229
|
+
@@ -1,3 +1,3 @@
|
|
230
|
+
Line 1
|
|
231
|
+
-Line 2
|
|
232
|
+
+Modified Line 2
|
|
233
|
+
Line 3
|
|
234
|
+
@@ -4,3 +4,4 @@
|
|
235
|
+
Line 4
|
|
236
|
+
Line 5
|
|
237
|
+
+New Line 5.5
|
|
238
|
+
Line 6
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
result = fs_gateway.edit_file_with_diff("", "multiple_hunks_test.txt", diff)
|
|
242
|
+
assert "Successfully" in result
|
|
243
|
+
|
|
244
|
+
# Verify the file was modified correctly
|
|
245
|
+
with open(test_file_path, "r") as f:
|
|
246
|
+
content = f.read()
|
|
247
|
+
|
|
248
|
+
expected = "Line 1\nModified Line 2\nLine 3\nLine 4\nLine 5\nNew Line 5.5\nLine 6\n"
|
|
249
|
+
assert expected == content
|
|
250
|
+
|
|
251
|
+
def should_read_file(self, fs_gateway, setup_test_files):
|
|
252
|
+
"""
|
|
253
|
+
Given a FilesystemGateway with test files
|
|
254
|
+
When reading a file
|
|
255
|
+
Then it should return the file content
|
|
256
|
+
"""
|
|
257
|
+
content = fs_gateway.read("", "test1.txt")
|
|
258
|
+
assert "This is test file 1" in content
|
|
259
|
+
|
|
260
|
+
def should_write_file(self, fs_gateway, setup_test_files):
|
|
261
|
+
"""
|
|
262
|
+
Given a FilesystemGateway with test files
|
|
263
|
+
When writing to a file
|
|
264
|
+
Then it should update the file content
|
|
265
|
+
"""
|
|
266
|
+
fs_gateway.write("", "new_file.txt", "This is a new file")
|
|
267
|
+
|
|
268
|
+
# Verify the file was created
|
|
269
|
+
with open(os.path.join(setup_test_files, "new_file.txt"), "r") as f:
|
|
270
|
+
content = f.read()
|
|
271
|
+
|
|
272
|
+
assert content == "This is a new file"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class DescribeFileManager:
|
|
276
|
+
|
|
277
|
+
def should_list_files_with_extension(self, file_manager, setup_test_files):
|
|
278
|
+
"""
|
|
279
|
+
Given a FileManager with test files
|
|
280
|
+
When listing files with a specific extension
|
|
281
|
+
Then it should return only files with that extension
|
|
282
|
+
"""
|
|
283
|
+
txt_files = file_manager.ls("", ".txt")
|
|
284
|
+
assert sorted(txt_files) == sorted(["test1.txt"])
|
|
285
|
+
|
|
286
|
+
def should_list_all_files(self, file_manager, setup_test_files):
|
|
287
|
+
"""
|
|
288
|
+
Given a FileManager with test files
|
|
289
|
+
When listing all files
|
|
290
|
+
Then it should return all files recursively
|
|
291
|
+
"""
|
|
292
|
+
all_files = file_manager.list_all_files("")
|
|
293
|
+
assert sorted(all_files) == sorted(["test1.txt", "test2.py", os.path.join("subdir", "test3.txt")])
|
|
294
|
+
|
|
295
|
+
def should_find_files_by_glob(self, file_manager, setup_test_files):
|
|
296
|
+
"""
|
|
297
|
+
Given a FileManager with test files
|
|
298
|
+
When finding files by glob pattern
|
|
299
|
+
Then it should return files matching the pattern
|
|
300
|
+
"""
|
|
301
|
+
py_files = file_manager.find_files_by_glob("", "*.py")
|
|
302
|
+
assert py_files == ["test2.py"]
|
|
303
|
+
|
|
304
|
+
def should_find_files_containing(self, file_manager, setup_test_files):
|
|
305
|
+
"""
|
|
306
|
+
Given a FileManager with test files
|
|
307
|
+
When finding files containing a pattern
|
|
308
|
+
Then it should return files with content matching the pattern
|
|
309
|
+
"""
|
|
310
|
+
files_with_multiple = file_manager.find_files_containing("", "multiple")
|
|
311
|
+
assert files_with_multiple == ["test1.txt"]
|
|
312
|
+
|
|
313
|
+
def should_find_lines_matching(self, file_manager, setup_test_files):
|
|
314
|
+
"""
|
|
315
|
+
Given a FileManager with test files
|
|
316
|
+
When finding lines matching a pattern in a file
|
|
317
|
+
Then it should return the matching lines with line numbers
|
|
318
|
+
"""
|
|
319
|
+
matching_lines = file_manager.find_lines_matching("", "test1.txt", "testing")
|
|
320
|
+
assert matching_lines == [{"line_number": 3, "content": "For testing purposes"}]
|
|
321
|
+
|
|
322
|
+
def should_edit_file_with_diff(self, file_manager, setup_test_files):
|
|
323
|
+
"""
|
|
324
|
+
Given a FileManager with test files
|
|
325
|
+
When editing a file with a diff
|
|
326
|
+
Then it should apply the changes correctly
|
|
327
|
+
"""
|
|
328
|
+
# Create a diff that changes "test file 1" to "edited file 1"
|
|
329
|
+
diff = """--- test1.txt
|
|
330
|
+
+++ test1.txt
|
|
331
|
+
@@ -1,3 +1,3 @@
|
|
332
|
+
-This is test file 1
|
|
333
|
+
+This is edited file 1
|
|
334
|
+
It has multiple lines
|
|
335
|
+
For testing purposes"""
|
|
336
|
+
|
|
337
|
+
result = file_manager.edit_file_with_diff("", "test1.txt", diff)
|
|
338
|
+
assert "Successfully" in result
|
|
339
|
+
|
|
340
|
+
# Verify the file was modified
|
|
341
|
+
content = file_manager.read("", "test1.txt")
|
|
342
|
+
assert "This is edited file 1" in content
|
|
343
|
+
|
|
344
|
+
def should_read_file(self, file_manager, setup_test_files):
|
|
345
|
+
"""
|
|
346
|
+
Given a FileManager with test files
|
|
347
|
+
When reading a file
|
|
348
|
+
Then it should return the file content
|
|
349
|
+
"""
|
|
350
|
+
content = file_manager.read("", "test2.py")
|
|
351
|
+
assert "def test_function()" in content
|
|
352
|
+
|
|
353
|
+
def should_write_file(self, file_manager, setup_test_files):
|
|
354
|
+
"""
|
|
355
|
+
Given a FileManager with test files
|
|
356
|
+
When writing to a file
|
|
357
|
+
Then it should update the file content
|
|
358
|
+
"""
|
|
359
|
+
file_manager.write("", "new_file2.txt", "This is another new file")
|
|
360
|
+
|
|
361
|
+
# Verify the file was created
|
|
362
|
+
content = file_manager.read("", "new_file2.txt")
|
|
363
|
+
assert content == "This is another new file"
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class DescribeListFilesTool:
|
|
367
|
+
|
|
368
|
+
def should_list_files_with_extension(self, setup_test_files):
|
|
369
|
+
"""
|
|
370
|
+
Given a ListFilesTool
|
|
371
|
+
When running with an extension
|
|
372
|
+
Then it should return files with that extension
|
|
373
|
+
"""
|
|
374
|
+
fs = FilesystemGateway(base_path=setup_test_files)
|
|
375
|
+
tool = ListFilesTool(fs)
|
|
376
|
+
result = tool.run(path="", extension=".txt")
|
|
377
|
+
assert sorted(result) == sorted(["test1.txt"])
|
|
378
|
+
|
|
379
|
+
def should_have_correct_descriptor(self):
|
|
380
|
+
"""
|
|
381
|
+
Given a ListFilesTool
|
|
382
|
+
When accessing its descriptor
|
|
383
|
+
Then it should have the correct structure
|
|
384
|
+
"""
|
|
385
|
+
fs = FilesystemGateway(base_path="/tmp")
|
|
386
|
+
tool = ListFilesTool(fs)
|
|
387
|
+
descriptor = tool.descriptor
|
|
388
|
+
assert descriptor["type"] == "function"
|
|
389
|
+
assert descriptor["function"]["name"] == "list_files"
|
|
390
|
+
assert "path" in descriptor["function"]["parameters"]["properties"]
|
|
391
|
+
assert "extension" in descriptor["function"]["parameters"]["properties"]
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class DescribeReadFileTool:
|
|
395
|
+
|
|
396
|
+
def should_read_file_content(self, setup_test_files):
|
|
397
|
+
"""
|
|
398
|
+
Given a ReadFileTool
|
|
399
|
+
When running with a path
|
|
400
|
+
Then it should return the file content
|
|
401
|
+
"""
|
|
402
|
+
fs = FilesystemGateway(base_path=setup_test_files)
|
|
403
|
+
tool = ReadFileTool(fs)
|
|
404
|
+
result = tool.run(path="test1.txt")
|
|
405
|
+
assert "This is test file 1" in result
|
|
406
|
+
|
|
407
|
+
def should_have_correct_descriptor(self):
|
|
408
|
+
"""
|
|
409
|
+
Given a ReadFileTool
|
|
410
|
+
When accessing its descriptor
|
|
411
|
+
Then it should have the correct structure
|
|
412
|
+
"""
|
|
413
|
+
fs = FilesystemGateway(base_path="/tmp")
|
|
414
|
+
tool = ReadFileTool(fs)
|
|
415
|
+
descriptor = tool.descriptor
|
|
416
|
+
assert descriptor["type"] == "function"
|
|
417
|
+
assert descriptor["function"]["name"] == "read_file"
|
|
418
|
+
assert "path" in descriptor["function"]["parameters"]["properties"]
|
|
419
|
+
assert "file_name" not in descriptor["function"]["parameters"]["properties"]
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
class DescribeWriteFileTool:
|
|
423
|
+
|
|
424
|
+
def should_write_file_content(self, setup_test_files):
|
|
425
|
+
"""
|
|
426
|
+
Given a WriteFileTool
|
|
427
|
+
When running with a path and content
|
|
428
|
+
Then it should write the content to the file
|
|
429
|
+
"""
|
|
430
|
+
fs = FilesystemGateway(base_path=setup_test_files)
|
|
431
|
+
tool = WriteFileTool(fs)
|
|
432
|
+
result = tool.run(path="write_test.txt", content="Test content")
|
|
433
|
+
assert "Successfully" in result
|
|
434
|
+
|
|
435
|
+
# Verify the file was created
|
|
436
|
+
with open(os.path.join(setup_test_files, "write_test.txt"), "r") as f:
|
|
437
|
+
content = f.read()
|
|
438
|
+
assert content == "Test content"
|
|
439
|
+
|
|
440
|
+
def should_have_correct_descriptor(self):
|
|
441
|
+
"""
|
|
442
|
+
Given a WriteFileTool
|
|
443
|
+
When accessing its descriptor
|
|
444
|
+
Then it should have the correct structure
|
|
445
|
+
"""
|
|
446
|
+
fs = FilesystemGateway(base_path="/tmp")
|
|
447
|
+
tool = WriteFileTool(fs)
|
|
448
|
+
descriptor = tool.descriptor
|
|
449
|
+
assert descriptor["type"] == "function"
|
|
450
|
+
assert descriptor["function"]["name"] == "write_file"
|
|
451
|
+
assert "path" in descriptor["function"]["parameters"]["properties"]
|
|
452
|
+
assert "content" in descriptor["function"]["parameters"]["properties"]
|
|
453
|
+
assert "file_name" not in descriptor["function"]["parameters"]["properties"]
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class DescribeListAllFilesTool:
|
|
457
|
+
|
|
458
|
+
def should_list_all_files(self, setup_test_files):
|
|
459
|
+
"""
|
|
460
|
+
Given a ListAllFilesTool
|
|
461
|
+
When running
|
|
462
|
+
Then it should return all files recursively
|
|
463
|
+
"""
|
|
464
|
+
fs = FilesystemGateway(base_path=setup_test_files)
|
|
465
|
+
tool = ListAllFilesTool(fs)
|
|
466
|
+
result = tool.run(path="")
|
|
467
|
+
assert sorted(result) == sorted(["test1.txt", "test2.py", os.path.join("subdir", "test3.txt")])
|
|
468
|
+
|
|
469
|
+
def should_have_correct_descriptor(self):
|
|
470
|
+
"""
|
|
471
|
+
Given a ListAllFilesTool
|
|
472
|
+
When accessing its descriptor
|
|
473
|
+
Then it should have the correct structure
|
|
474
|
+
"""
|
|
475
|
+
fs = FilesystemGateway(base_path="/tmp")
|
|
476
|
+
tool = ListAllFilesTool(fs)
|
|
477
|
+
descriptor = tool.descriptor
|
|
478
|
+
assert descriptor["type"] == "function"
|
|
479
|
+
assert descriptor["function"]["name"] == "list_all_files"
|
|
480
|
+
assert "path" in descriptor["function"]["parameters"]["properties"]
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
class DescribeFindFilesByGlobTool:
|
|
484
|
+
|
|
485
|
+
def should_find_files_by_glob_pattern(self, setup_test_files):
|
|
486
|
+
"""
|
|
487
|
+
Given a FindFilesByGlobTool
|
|
488
|
+
When running with a glob pattern
|
|
489
|
+
Then it should return files matching the pattern
|
|
490
|
+
"""
|
|
491
|
+
fs = FilesystemGateway(base_path=setup_test_files)
|
|
492
|
+
tool = FindFilesByGlobTool(fs)
|
|
493
|
+
result = tool.run(path="", pattern="*.py")
|
|
494
|
+
assert result == ["test2.py"]
|
|
495
|
+
|
|
496
|
+
def should_have_correct_descriptor(self):
|
|
497
|
+
"""
|
|
498
|
+
Given a FindFilesByGlobTool
|
|
499
|
+
When accessing its descriptor
|
|
500
|
+
Then it should have the correct structure
|
|
501
|
+
"""
|
|
502
|
+
fs = FilesystemGateway(base_path="/tmp")
|
|
503
|
+
tool = FindFilesByGlobTool(fs)
|
|
504
|
+
descriptor = tool.descriptor
|
|
505
|
+
assert descriptor["type"] == "function"
|
|
506
|
+
assert descriptor["function"]["name"] == "find_files_by_glob"
|
|
507
|
+
assert "path" in descriptor["function"]["parameters"]["properties"]
|
|
508
|
+
assert "pattern" in descriptor["function"]["parameters"]["properties"]
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
class DescribeFindFilesContainingTool:
|
|
512
|
+
|
|
513
|
+
def should_find_files_containing_pattern(self, setup_test_files):
|
|
514
|
+
"""
|
|
515
|
+
Given a FindFilesContainingTool
|
|
516
|
+
When running with a regex pattern
|
|
517
|
+
Then it should return files containing text matching the pattern
|
|
518
|
+
"""
|
|
519
|
+
fs = FilesystemGateway(base_path=setup_test_files)
|
|
520
|
+
tool = FindFilesContainingTool(fs)
|
|
521
|
+
result = tool.run(path="", pattern="Hello")
|
|
522
|
+
assert result == ["test2.py"]
|
|
523
|
+
|
|
524
|
+
def should_have_correct_descriptor(self):
|
|
525
|
+
"""
|
|
526
|
+
Given a FindFilesContainingTool
|
|
527
|
+
When accessing its descriptor
|
|
528
|
+
Then it should have the correct structure
|
|
529
|
+
"""
|
|
530
|
+
fs = FilesystemGateway(base_path="/tmp")
|
|
531
|
+
tool = FindFilesContainingTool(fs)
|
|
532
|
+
descriptor = tool.descriptor
|
|
533
|
+
assert descriptor["type"] == "function"
|
|
534
|
+
assert descriptor["function"]["name"] == "find_files_containing"
|
|
535
|
+
assert "path" in descriptor["function"]["parameters"]["properties"]
|
|
536
|
+
assert "pattern" in descriptor["function"]["parameters"]["properties"]
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
class DescribeFindLinesMatchingTool:
|
|
540
|
+
|
|
541
|
+
def should_find_lines_matching_pattern(self, setup_test_files):
|
|
542
|
+
"""
|
|
543
|
+
Given a FindLinesMatchingTool
|
|
544
|
+
When running with a path and regex pattern
|
|
545
|
+
Then it should return lines matching the pattern with line numbers
|
|
546
|
+
"""
|
|
547
|
+
fs = FilesystemGateway(base_path=setup_test_files)
|
|
548
|
+
tool = FindLinesMatchingTool(fs)
|
|
549
|
+
result = tool.run(path="test1.txt", pattern="multiple")
|
|
550
|
+
assert result == [{"line_number": 2, "content": "It has multiple lines"}]
|
|
551
|
+
|
|
552
|
+
def should_have_correct_descriptor(self):
|
|
553
|
+
"""
|
|
554
|
+
Given a FindLinesMatchingTool
|
|
555
|
+
When accessing its descriptor
|
|
556
|
+
Then it should have the correct structure
|
|
557
|
+
"""
|
|
558
|
+
fs = FilesystemGateway(base_path="/tmp")
|
|
559
|
+
tool = FindLinesMatchingTool(fs)
|
|
560
|
+
descriptor = tool.descriptor
|
|
561
|
+
assert descriptor["type"] == "function"
|
|
562
|
+
assert descriptor["function"]["name"] == "find_lines_matching"
|
|
563
|
+
assert "path" in descriptor["function"]["parameters"]["properties"]
|
|
564
|
+
assert "file_name" not in descriptor["function"]["parameters"]["properties"]
|
|
565
|
+
assert "pattern" in descriptor["function"]["parameters"]["properties"]
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class DescribeEditFileWithDiffTool:
|
|
569
|
+
|
|
570
|
+
def should_edit_file_with_diff(self, setup_test_files):
|
|
571
|
+
"""
|
|
572
|
+
Given an EditFileWithDiffTool
|
|
573
|
+
When running with a path and diff
|
|
574
|
+
Then it should apply the diff to the file
|
|
575
|
+
"""
|
|
576
|
+
fs = FilesystemGateway(base_path=setup_test_files)
|
|
577
|
+
tool = EditFileWithDiffTool(fs)
|
|
578
|
+
|
|
579
|
+
# Create a diff that changes "test file 1" to "modified by tool"
|
|
580
|
+
diff = """--- test1.txt
|
|
581
|
+
+++ test1.txt
|
|
582
|
+
@@ -1,3 +1,3 @@
|
|
583
|
+
-This is test file 1
|
|
584
|
+
+This is modified by tool
|
|
585
|
+
It has multiple lines
|
|
586
|
+
For testing purposes"""
|
|
587
|
+
|
|
588
|
+
result = tool.run(path="test1.txt", diff=diff)
|
|
589
|
+
assert "Successfully" in result
|
|
590
|
+
|
|
591
|
+
# Verify the file was modified
|
|
592
|
+
with open(os.path.join(setup_test_files, "test1.txt"), "r") as f:
|
|
593
|
+
content = f.read()
|
|
594
|
+
assert "This is modified by tool" in content
|
|
595
|
+
assert "This is test file 1" not in content
|
|
596
|
+
assert "It has multiple lines" in content
|
|
597
|
+
assert "For testing purposes" in content
|
|
598
|
+
|
|
599
|
+
def should_edit_file_with_diff_adding_lines(self, setup_test_files):
|
|
600
|
+
"""
|
|
601
|
+
Given an EditFileWithDiffTool
|
|
602
|
+
When running with a path and diff that adds lines
|
|
603
|
+
Then it should add the lines while preserving the rest of the content
|
|
604
|
+
"""
|
|
605
|
+
fs = FilesystemGateway(base_path=setup_test_files)
|
|
606
|
+
tool = EditFileWithDiffTool(fs)
|
|
607
|
+
|
|
608
|
+
# Create a test file with known content
|
|
609
|
+
test_file_path = os.path.join(setup_test_files, "tool_add_lines_test.txt")
|
|
610
|
+
with open(test_file_path, 'w') as f:
|
|
611
|
+
f.write("Line 1\nLine 2\nLine 3\n")
|
|
612
|
+
|
|
613
|
+
# Create a diff that adds a new line after Line 2
|
|
614
|
+
diff = """--- tool_add_lines_test.txt
|
|
615
|
+
+++ tool_add_lines_test.txt
|
|
616
|
+
@@ -1,3 +1,4 @@
|
|
617
|
+
Line 1
|
|
618
|
+
Line 2
|
|
619
|
+
+New Line 2.5
|
|
620
|
+
Line 3
|
|
621
|
+
"""
|
|
622
|
+
|
|
623
|
+
result = tool.run(path="tool_add_lines_test.txt", diff=diff)
|
|
624
|
+
assert "Successfully" in result
|
|
625
|
+
|
|
626
|
+
# Verify the file was modified correctly
|
|
627
|
+
with open(test_file_path, "r") as f:
|
|
628
|
+
content = f.read()
|
|
629
|
+
|
|
630
|
+
assert "Line 1\nLine 2\nNew Line 2.5\nLine 3\n" == content
|
|
631
|
+
|
|
632
|
+
def should_edit_file_with_diff_multiple_hunks(self, setup_test_files):
|
|
633
|
+
"""
|
|
634
|
+
Given an EditFileWithDiffTool
|
|
635
|
+
When running with a path and diff that contains multiple hunks
|
|
636
|
+
Then it should apply all changes correctly
|
|
637
|
+
"""
|
|
638
|
+
fs = FilesystemGateway(base_path=setup_test_files)
|
|
639
|
+
tool = EditFileWithDiffTool(fs)
|
|
640
|
+
|
|
641
|
+
# Create a test file with known content
|
|
642
|
+
test_file_path = os.path.join(setup_test_files, "tool_multiple_hunks_test.txt")
|
|
643
|
+
with open(test_file_path, 'w') as f:
|
|
644
|
+
f.write("Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\n")
|
|
645
|
+
|
|
646
|
+
# Create a diff with multiple hunks
|
|
647
|
+
diff = """--- tool_multiple_hunks_test.txt
|
|
648
|
+
+++ tool_multiple_hunks_test.txt
|
|
649
|
+
@@ -1,3 +1,3 @@
|
|
650
|
+
Line 1
|
|
651
|
+
-Line 2
|
|
652
|
+
+Modified Line 2
|
|
653
|
+
Line 3
|
|
654
|
+
@@ -4,3 +4,4 @@
|
|
655
|
+
Line 4
|
|
656
|
+
Line 5
|
|
657
|
+
+New Line 5.5
|
|
658
|
+
Line 6
|
|
659
|
+
"""
|
|
660
|
+
|
|
661
|
+
result = tool.run(path="tool_multiple_hunks_test.txt", diff=diff)
|
|
662
|
+
assert "Successfully" in result
|
|
663
|
+
|
|
664
|
+
# Verify the file was modified correctly
|
|
665
|
+
with open(test_file_path, "r") as f:
|
|
666
|
+
content = f.read()
|
|
667
|
+
|
|
668
|
+
expected = "Line 1\nModified Line 2\nLine 3\nLine 4\nLine 5\nNew Line 5.5\nLine 6\n"
|
|
669
|
+
assert expected == content
|
|
670
|
+
|
|
671
|
+
def should_have_correct_descriptor(self):
|
|
672
|
+
"""
|
|
673
|
+
Given an EditFileWithDiffTool
|
|
674
|
+
When accessing its descriptor
|
|
675
|
+
Then it should have the correct structure
|
|
676
|
+
"""
|
|
677
|
+
fs = FilesystemGateway(base_path="/tmp")
|
|
678
|
+
tool = EditFileWithDiffTool(fs)
|
|
679
|
+
descriptor = tool.descriptor
|
|
680
|
+
assert descriptor["type"] == "function"
|
|
681
|
+
assert descriptor["function"]["name"] == "edit_file_with_diff"
|
|
682
|
+
assert "path" in descriptor["function"]["parameters"]["properties"]
|
|
683
|
+
assert "file_name" not in descriptor["function"]["parameters"]["properties"]
|
|
684
|
+
assert "diff" in descriptor["function"]["parameters"]["properties"]
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
class DescribeCreateDirectoryTool:
|
|
688
|
+
|
|
689
|
+
def should_create_directory(self, setup_test_files):
|
|
690
|
+
"""
|
|
691
|
+
Given a CreateDirectoryTool
|
|
692
|
+
When running with a path
|
|
693
|
+
Then it should create the directory
|
|
694
|
+
"""
|
|
695
|
+
fs = FilesystemGateway(base_path=setup_test_files)
|
|
696
|
+
tool = CreateDirectoryTool(fs)
|
|
697
|
+
|
|
698
|
+
# Create a new directory
|
|
699
|
+
result = tool.run(path="new_directory")
|
|
700
|
+
assert "Successfully" in result
|
|
701
|
+
|
|
702
|
+
# Verify the directory was created
|
|
703
|
+
assert os.path.isdir(os.path.join(setup_test_files, "new_directory"))
|
|
704
|
+
|
|
705
|
+
# Test creating nested directories
|
|
706
|
+
result = tool.run(path="nested/directory/structure")
|
|
707
|
+
assert "Successfully" in result
|
|
708
|
+
|
|
709
|
+
# Verify the nested directories were created
|
|
710
|
+
assert os.path.isdir(os.path.join(setup_test_files, "nested/directory/structure"))
|
|
711
|
+
|
|
712
|
+
def should_have_correct_descriptor(self):
|
|
713
|
+
"""
|
|
714
|
+
Given a CreateDirectoryTool
|
|
715
|
+
When accessing its descriptor
|
|
716
|
+
Then it should have the correct structure
|
|
717
|
+
"""
|
|
718
|
+
fs = FilesystemGateway(base_path="/tmp")
|
|
719
|
+
tool = CreateDirectoryTool(fs)
|
|
720
|
+
descriptor = tool.descriptor
|
|
721
|
+
assert descriptor["type"] == "function"
|
|
722
|
+
assert descriptor["function"]["name"] == "create_directory"
|
|
723
|
+
assert "path" in descriptor["function"]["parameters"]["properties"]
|