kader 0.1.0__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.
- cli/README.md +169 -0
- cli/__init__.py +5 -0
- cli/__main__.py +6 -0
- cli/app.py +547 -0
- cli/app.tcss +648 -0
- cli/utils.py +62 -0
- cli/widgets/__init__.py +13 -0
- cli/widgets/confirmation.py +309 -0
- cli/widgets/conversation.py +55 -0
- cli/widgets/loading.py +59 -0
- kader/__init__.py +22 -0
- kader/agent/__init__.py +8 -0
- kader/agent/agents.py +126 -0
- kader/agent/base.py +920 -0
- kader/agent/logger.py +188 -0
- kader/config.py +139 -0
- kader/memory/__init__.py +66 -0
- kader/memory/conversation.py +409 -0
- kader/memory/session.py +385 -0
- kader/memory/state.py +211 -0
- kader/memory/types.py +116 -0
- kader/prompts/__init__.py +9 -0
- kader/prompts/agent_prompts.py +27 -0
- kader/prompts/base.py +81 -0
- kader/prompts/templates/planning_agent.j2 +26 -0
- kader/prompts/templates/react_agent.j2 +18 -0
- kader/providers/__init__.py +9 -0
- kader/providers/base.py +581 -0
- kader/providers/mock.py +96 -0
- kader/providers/ollama.py +447 -0
- kader/tools/README.md +483 -0
- kader/tools/__init__.py +130 -0
- kader/tools/base.py +955 -0
- kader/tools/exec_commands.py +249 -0
- kader/tools/filesys.py +650 -0
- kader/tools/filesystem.py +607 -0
- kader/tools/protocol.py +456 -0
- kader/tools/rag.py +555 -0
- kader/tools/todo.py +210 -0
- kader/tools/utils.py +456 -0
- kader/tools/web.py +246 -0
- kader-0.1.0.dist-info/METADATA +319 -0
- kader-0.1.0.dist-info/RECORD +45 -0
- kader-0.1.0.dist-info/WHEEL +4 -0
- kader-0.1.0.dist-info/entry_points.txt +2 -0
kader/tools/filesys.py
ADDED
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File System Tools for Agentic Operations.
|
|
3
|
+
|
|
4
|
+
All tools operate relative to the current working directory (CWD) for security.
|
|
5
|
+
Uses FilesystemBackend from filesystem.py for the underlying operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from .base import (
|
|
13
|
+
BaseTool,
|
|
14
|
+
ParameterSchema,
|
|
15
|
+
ToolCategory,
|
|
16
|
+
)
|
|
17
|
+
from .filesystem import FilesystemBackend
|
|
18
|
+
from .protocol import FileInfo
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ReadFileTool(BaseTool[str]):
|
|
22
|
+
"""
|
|
23
|
+
Tool to read the contents of a file.
|
|
24
|
+
|
|
25
|
+
Uses FilesystemBackend for secure file access with path containment.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
base_path: Path | None = None,
|
|
31
|
+
virtual_mode: bool = False,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Initialize the read file tool.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
base_path: Base path for file operations (defaults to CWD)
|
|
38
|
+
virtual_mode: If True, use virtual path mode (sandboxed to base_path)
|
|
39
|
+
"""
|
|
40
|
+
super().__init__(
|
|
41
|
+
name="read_file",
|
|
42
|
+
description=(
|
|
43
|
+
"Read the contents of a file. Returns the file content as text with line numbers. "
|
|
44
|
+
"Supports pagination with offset and limit for large files."
|
|
45
|
+
),
|
|
46
|
+
parameters=[
|
|
47
|
+
ParameterSchema(
|
|
48
|
+
name="path",
|
|
49
|
+
type="string",
|
|
50
|
+
description="Path to the file to read",
|
|
51
|
+
),
|
|
52
|
+
ParameterSchema(
|
|
53
|
+
name="offset",
|
|
54
|
+
type="integer",
|
|
55
|
+
description="Line offset to start reading from (0-indexed)",
|
|
56
|
+
required=False,
|
|
57
|
+
default=0,
|
|
58
|
+
minimum=0,
|
|
59
|
+
),
|
|
60
|
+
ParameterSchema(
|
|
61
|
+
name="limit",
|
|
62
|
+
type="integer",
|
|
63
|
+
description="Maximum number of lines to read",
|
|
64
|
+
required=False,
|
|
65
|
+
default=2000,
|
|
66
|
+
minimum=1,
|
|
67
|
+
),
|
|
68
|
+
],
|
|
69
|
+
category=ToolCategory.FILE_SYSTEM,
|
|
70
|
+
)
|
|
71
|
+
self._backend = FilesystemBackend(
|
|
72
|
+
root_dir=base_path,
|
|
73
|
+
virtual_mode=virtual_mode,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def execute(
|
|
77
|
+
self,
|
|
78
|
+
path: str,
|
|
79
|
+
offset: int = 0,
|
|
80
|
+
limit: int = 2000,
|
|
81
|
+
) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Read file contents.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
path: Path to the file
|
|
87
|
+
offset: Line offset to start reading from (0-indexed)
|
|
88
|
+
limit: Maximum number of lines to read
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
File contents with line numbers, or error message
|
|
92
|
+
"""
|
|
93
|
+
return self._backend.read(path, offset=offset, limit=limit)
|
|
94
|
+
|
|
95
|
+
async def aexecute(
|
|
96
|
+
self,
|
|
97
|
+
path: str,
|
|
98
|
+
offset: int = 0,
|
|
99
|
+
limit: int = 2000,
|
|
100
|
+
) -> str:
|
|
101
|
+
"""Async version of execute."""
|
|
102
|
+
return await asyncio.to_thread(self.execute, path, offset, limit)
|
|
103
|
+
|
|
104
|
+
def get_interruption_message(self, path: str, **kwargs) -> str:
|
|
105
|
+
"""Get interruption message for user confirmation."""
|
|
106
|
+
return f"execute read_file: {path}"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ReadDirectoryTool(BaseTool[list[dict[str, Any]]]):
|
|
110
|
+
"""
|
|
111
|
+
Tool to list the contents of a directory.
|
|
112
|
+
|
|
113
|
+
Uses FilesystemBackend for secure directory listing.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
base_path: Path | None = None,
|
|
119
|
+
virtual_mode: bool = False,
|
|
120
|
+
) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Initialize the read directory tool.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
base_path: Base path for file operations (defaults to CWD)
|
|
126
|
+
virtual_mode: If True, use virtual path mode (sandboxed to base_path)
|
|
127
|
+
"""
|
|
128
|
+
super().__init__(
|
|
129
|
+
name="read_directory",
|
|
130
|
+
description=(
|
|
131
|
+
"List the contents of a directory. Returns a list of files and "
|
|
132
|
+
"subdirectories with their types and sizes."
|
|
133
|
+
),
|
|
134
|
+
parameters=[
|
|
135
|
+
ParameterSchema(
|
|
136
|
+
name="path",
|
|
137
|
+
type="string",
|
|
138
|
+
description="Path to the directory (use '.' or '/' for root)",
|
|
139
|
+
default=".",
|
|
140
|
+
),
|
|
141
|
+
],
|
|
142
|
+
category=ToolCategory.FILE_SYSTEM,
|
|
143
|
+
)
|
|
144
|
+
self._backend = FilesystemBackend(
|
|
145
|
+
root_dir=base_path,
|
|
146
|
+
virtual_mode=virtual_mode,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def execute(
|
|
150
|
+
self,
|
|
151
|
+
path: str = ".",
|
|
152
|
+
) -> list[dict[str, Any]]:
|
|
153
|
+
"""
|
|
154
|
+
List directory contents.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
path: Path to directory
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of file/directory information dictionaries
|
|
161
|
+
"""
|
|
162
|
+
result: list[FileInfo] = self._backend.ls_info(path)
|
|
163
|
+
# Convert FileInfo TypedDict to regular dict for JSON serialization
|
|
164
|
+
return [dict(item) for item in result]
|
|
165
|
+
|
|
166
|
+
async def aexecute(
|
|
167
|
+
self,
|
|
168
|
+
path: str = ".",
|
|
169
|
+
) -> list[dict[str, Any]]:
|
|
170
|
+
"""Async version of execute."""
|
|
171
|
+
return await asyncio.to_thread(self.execute, path)
|
|
172
|
+
|
|
173
|
+
def get_interruption_message(self, path: str = ".", **kwargs) -> str:
|
|
174
|
+
"""Get interruption message for user confirmation."""
|
|
175
|
+
return f"execute read_dir: {path}"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class WriteFileTool(BaseTool[dict[str, Any]]):
|
|
179
|
+
"""
|
|
180
|
+
Tool to write content to a new file.
|
|
181
|
+
|
|
182
|
+
Uses FilesystemBackend for secure file creation.
|
|
183
|
+
Only creates new files; fails if file already exists.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __init__(
|
|
187
|
+
self,
|
|
188
|
+
base_path: Path | None = None,
|
|
189
|
+
virtual_mode: bool = False,
|
|
190
|
+
) -> None:
|
|
191
|
+
"""
|
|
192
|
+
Initialize the write file tool.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
base_path: Base path for file operations (defaults to CWD)
|
|
196
|
+
virtual_mode: If True, use virtual path mode (sandboxed to base_path)
|
|
197
|
+
"""
|
|
198
|
+
super().__init__(
|
|
199
|
+
name="write_file",
|
|
200
|
+
description=(
|
|
201
|
+
"Create a new file with content. Fails if the file already exists. "
|
|
202
|
+
"Use edit_file tool to modify existing files."
|
|
203
|
+
),
|
|
204
|
+
parameters=[
|
|
205
|
+
ParameterSchema(
|
|
206
|
+
name="path",
|
|
207
|
+
type="string",
|
|
208
|
+
description="Path to the file to create",
|
|
209
|
+
),
|
|
210
|
+
ParameterSchema(
|
|
211
|
+
name="content",
|
|
212
|
+
type="string",
|
|
213
|
+
description="Content to write to the file",
|
|
214
|
+
),
|
|
215
|
+
],
|
|
216
|
+
category=ToolCategory.FILE_SYSTEM,
|
|
217
|
+
)
|
|
218
|
+
self._backend = FilesystemBackend(
|
|
219
|
+
root_dir=base_path,
|
|
220
|
+
virtual_mode=virtual_mode,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def execute(
|
|
224
|
+
self,
|
|
225
|
+
path: str,
|
|
226
|
+
content: str,
|
|
227
|
+
) -> dict[str, Any]:
|
|
228
|
+
"""
|
|
229
|
+
Write content to a new file.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
path: Path to the file to create
|
|
233
|
+
content: Content to write
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Dictionary with operation result
|
|
237
|
+
"""
|
|
238
|
+
result = self._backend.write(path, content)
|
|
239
|
+
|
|
240
|
+
if result.error:
|
|
241
|
+
return {"error": result.error}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
"path": result.path,
|
|
245
|
+
"success": True,
|
|
246
|
+
"bytes_written": len(content.encode("utf-8")),
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async def aexecute(
|
|
250
|
+
self,
|
|
251
|
+
path: str,
|
|
252
|
+
content: str,
|
|
253
|
+
) -> dict[str, Any]:
|
|
254
|
+
"""Async version of execute."""
|
|
255
|
+
return await asyncio.to_thread(self.execute, path, content)
|
|
256
|
+
|
|
257
|
+
def get_interruption_message(self, path: str, **kwargs) -> str:
|
|
258
|
+
"""Get interruption message for user confirmation."""
|
|
259
|
+
return f"execute write_file: {path}"
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class EditFileTool(BaseTool[dict[str, Any]]):
|
|
263
|
+
"""
|
|
264
|
+
Tool to edit an existing file by string replacement.
|
|
265
|
+
|
|
266
|
+
Uses FilesystemBackend for secure file editing.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
def __init__(
|
|
270
|
+
self,
|
|
271
|
+
base_path: Path | None = None,
|
|
272
|
+
virtual_mode: bool = False,
|
|
273
|
+
) -> None:
|
|
274
|
+
"""
|
|
275
|
+
Initialize the edit file tool.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
base_path: Base path for file operations (defaults to CWD)
|
|
279
|
+
virtual_mode: If True, use virtual path mode (sandboxed to base_path)
|
|
280
|
+
"""
|
|
281
|
+
super().__init__(
|
|
282
|
+
name="edit_file",
|
|
283
|
+
description=(
|
|
284
|
+
"Edit an existing file by replacing text. Replaces exact string matches. "
|
|
285
|
+
"Use replace_all=True to replace all occurrences."
|
|
286
|
+
),
|
|
287
|
+
parameters=[
|
|
288
|
+
ParameterSchema(
|
|
289
|
+
name="path",
|
|
290
|
+
type="string",
|
|
291
|
+
description="Path to the file to edit",
|
|
292
|
+
),
|
|
293
|
+
ParameterSchema(
|
|
294
|
+
name="old_string",
|
|
295
|
+
type="string",
|
|
296
|
+
description="Exact string to search for and replace",
|
|
297
|
+
),
|
|
298
|
+
ParameterSchema(
|
|
299
|
+
name="new_string",
|
|
300
|
+
type="string",
|
|
301
|
+
description="String to replace old_string with",
|
|
302
|
+
),
|
|
303
|
+
ParameterSchema(
|
|
304
|
+
name="replace_all",
|
|
305
|
+
type="boolean",
|
|
306
|
+
description="If True, replace all occurrences",
|
|
307
|
+
required=False,
|
|
308
|
+
default=False,
|
|
309
|
+
),
|
|
310
|
+
],
|
|
311
|
+
category=ToolCategory.FILE_SYSTEM,
|
|
312
|
+
)
|
|
313
|
+
self._backend = FilesystemBackend(
|
|
314
|
+
root_dir=base_path,
|
|
315
|
+
virtual_mode=virtual_mode,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def execute(
|
|
319
|
+
self,
|
|
320
|
+
path: str,
|
|
321
|
+
old_string: str,
|
|
322
|
+
new_string: str,
|
|
323
|
+
replace_all: bool = False,
|
|
324
|
+
) -> dict[str, Any]:
|
|
325
|
+
"""
|
|
326
|
+
Edit a file by replacing string occurrences.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
path: Path to the file
|
|
330
|
+
old_string: String to replace
|
|
331
|
+
new_string: Replacement string
|
|
332
|
+
replace_all: Whether to replace all occurrences
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
Dictionary with operation result
|
|
336
|
+
"""
|
|
337
|
+
result = self._backend.edit(path, old_string, new_string, replace_all)
|
|
338
|
+
|
|
339
|
+
if result.error:
|
|
340
|
+
return {"error": result.error}
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
"path": result.path,
|
|
344
|
+
"success": True,
|
|
345
|
+
"occurrences": result.occurrences,
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async def aexecute(
|
|
349
|
+
self,
|
|
350
|
+
path: str,
|
|
351
|
+
old_string: str,
|
|
352
|
+
new_string: str,
|
|
353
|
+
replace_all: bool = False,
|
|
354
|
+
) -> dict[str, Any]:
|
|
355
|
+
"""Async version of execute."""
|
|
356
|
+
return await asyncio.to_thread(
|
|
357
|
+
self.execute, path, old_string, new_string, replace_all
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
def get_interruption_message(self, path: str, **kwargs) -> str:
|
|
361
|
+
"""Get interruption message for user confirmation."""
|
|
362
|
+
return f"execute edit_file: {path}"
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class GrepTool(BaseTool[list[dict[str, Any]]]):
|
|
366
|
+
"""
|
|
367
|
+
Tool to search for patterns in files using regex.
|
|
368
|
+
|
|
369
|
+
Uses FilesystemBackend's grep_raw with ripgrep fallback to Python.
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
def __init__(
|
|
373
|
+
self,
|
|
374
|
+
base_path: Path | None = None,
|
|
375
|
+
virtual_mode: bool = False,
|
|
376
|
+
) -> None:
|
|
377
|
+
"""
|
|
378
|
+
Initialize the grep tool.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
base_path: Base path for search operations (defaults to CWD)
|
|
382
|
+
virtual_mode: If True, use virtual path mode (sandboxed to base_path)
|
|
383
|
+
"""
|
|
384
|
+
super().__init__(
|
|
385
|
+
name="grep",
|
|
386
|
+
description=(
|
|
387
|
+
"Search for a regex pattern in files. Returns matching lines with "
|
|
388
|
+
"file paths and line numbers. Optionally filter by glob pattern."
|
|
389
|
+
),
|
|
390
|
+
parameters=[
|
|
391
|
+
ParameterSchema(
|
|
392
|
+
name="pattern",
|
|
393
|
+
type="string",
|
|
394
|
+
description="Regex pattern to search for",
|
|
395
|
+
),
|
|
396
|
+
ParameterSchema(
|
|
397
|
+
name="path",
|
|
398
|
+
type="string",
|
|
399
|
+
description="Directory path to search in (defaults to current directory)",
|
|
400
|
+
required=False,
|
|
401
|
+
default=".",
|
|
402
|
+
),
|
|
403
|
+
ParameterSchema(
|
|
404
|
+
name="glob",
|
|
405
|
+
type="string",
|
|
406
|
+
description="Glob pattern to filter files (e.g., '*.py')",
|
|
407
|
+
required=False,
|
|
408
|
+
),
|
|
409
|
+
],
|
|
410
|
+
category=ToolCategory.SEARCH,
|
|
411
|
+
)
|
|
412
|
+
self._backend = FilesystemBackend(
|
|
413
|
+
root_dir=base_path,
|
|
414
|
+
virtual_mode=virtual_mode,
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
def execute(
|
|
418
|
+
self,
|
|
419
|
+
pattern: str,
|
|
420
|
+
path: str | None = ".",
|
|
421
|
+
glob: str | None = None,
|
|
422
|
+
) -> list[dict[str, Any]]:
|
|
423
|
+
"""
|
|
424
|
+
Search for pattern in files.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
pattern: Regex pattern to search for
|
|
428
|
+
path: Directory to search in
|
|
429
|
+
glob: Optional glob pattern to filter files
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
List of match dictionaries with path, line, and text
|
|
433
|
+
"""
|
|
434
|
+
result = self._backend.grep_raw(pattern, path, glob)
|
|
435
|
+
|
|
436
|
+
if isinstance(result, str):
|
|
437
|
+
# Error message
|
|
438
|
+
return [{"error": result}]
|
|
439
|
+
|
|
440
|
+
# Convert GrepMatch TypedDict to regular dict
|
|
441
|
+
return [dict(match) for match in result]
|
|
442
|
+
|
|
443
|
+
async def aexecute(
|
|
444
|
+
self,
|
|
445
|
+
pattern: str,
|
|
446
|
+
path: str | None = ".",
|
|
447
|
+
glob: str | None = None,
|
|
448
|
+
) -> list[dict[str, Any]]:
|
|
449
|
+
"""Async version of execute."""
|
|
450
|
+
return await asyncio.to_thread(self.execute, pattern, path, glob)
|
|
451
|
+
|
|
452
|
+
def get_interruption_message(self, **kwargs) -> str:
|
|
453
|
+
"""Get interruption message for user confirmation."""
|
|
454
|
+
return "execute grep"
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
class GlobTool(BaseTool[list[dict[str, Any]]]):
|
|
458
|
+
"""
|
|
459
|
+
Tool to find files matching a glob pattern.
|
|
460
|
+
|
|
461
|
+
Uses FilesystemBackend's glob_info for pattern matching.
|
|
462
|
+
"""
|
|
463
|
+
|
|
464
|
+
def __init__(
|
|
465
|
+
self,
|
|
466
|
+
base_path: Path | None = None,
|
|
467
|
+
virtual_mode: bool = False,
|
|
468
|
+
) -> None:
|
|
469
|
+
"""
|
|
470
|
+
Initialize the glob tool.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
base_path: Base path for search operations (defaults to CWD)
|
|
474
|
+
virtual_mode: If True, use virtual path mode (sandboxed to base_path)
|
|
475
|
+
"""
|
|
476
|
+
super().__init__(
|
|
477
|
+
name="glob",
|
|
478
|
+
description=(
|
|
479
|
+
"Find files matching a glob pattern. Supports wildcards: "
|
|
480
|
+
"* (any chars), ** (recursive), ? (single char), [abc] (char set)."
|
|
481
|
+
),
|
|
482
|
+
parameters=[
|
|
483
|
+
ParameterSchema(
|
|
484
|
+
name="pattern",
|
|
485
|
+
type="string",
|
|
486
|
+
description="Glob pattern to match (e.g., '*.py', '**/*.txt')",
|
|
487
|
+
),
|
|
488
|
+
ParameterSchema(
|
|
489
|
+
name="path",
|
|
490
|
+
type="string",
|
|
491
|
+
description="Base directory to search from",
|
|
492
|
+
required=False,
|
|
493
|
+
default="/",
|
|
494
|
+
),
|
|
495
|
+
],
|
|
496
|
+
category=ToolCategory.SEARCH,
|
|
497
|
+
)
|
|
498
|
+
self._backend = FilesystemBackend(
|
|
499
|
+
root_dir=base_path,
|
|
500
|
+
virtual_mode=virtual_mode,
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
def execute(
|
|
504
|
+
self,
|
|
505
|
+
pattern: str,
|
|
506
|
+
path: str = "/",
|
|
507
|
+
) -> list[dict[str, Any]]:
|
|
508
|
+
"""
|
|
509
|
+
Find files matching glob pattern.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
pattern: Glob pattern to match
|
|
513
|
+
path: Base directory to search from
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
List of file info dictionaries
|
|
517
|
+
"""
|
|
518
|
+
result: list[FileInfo] = self._backend.glob_info(pattern, path)
|
|
519
|
+
return [dict(item) for item in result]
|
|
520
|
+
|
|
521
|
+
async def aexecute(
|
|
522
|
+
self,
|
|
523
|
+
pattern: str,
|
|
524
|
+
path: str = "/",
|
|
525
|
+
) -> list[dict[str, Any]]:
|
|
526
|
+
"""Async version of execute."""
|
|
527
|
+
return await asyncio.to_thread(self.execute, pattern, path)
|
|
528
|
+
|
|
529
|
+
def get_interruption_message(self, **kwargs) -> str:
|
|
530
|
+
"""Get interruption message for user confirmation."""
|
|
531
|
+
return "execute glob"
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
class SearchInDirectoryTool(BaseTool[list[dict[str, Any]]]):
|
|
535
|
+
"""
|
|
536
|
+
Tool to search for content in files using RAG-based semantic search.
|
|
537
|
+
|
|
538
|
+
Uses Ollama embeddings and FAISS for intelligent code search.
|
|
539
|
+
"""
|
|
540
|
+
|
|
541
|
+
def __init__(
|
|
542
|
+
self,
|
|
543
|
+
base_path: Path | None = None,
|
|
544
|
+
embedding_model: str = "all-minilm:22m",
|
|
545
|
+
) -> None:
|
|
546
|
+
"""
|
|
547
|
+
Initialize the search tool.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
base_path: Base path for search operations (defaults to CWD)
|
|
551
|
+
embedding_model: Ollama embedding model to use
|
|
552
|
+
"""
|
|
553
|
+
super().__init__(
|
|
554
|
+
name="search_in_directory",
|
|
555
|
+
description=(
|
|
556
|
+
"Search for code and content using semantic (meaning-based) search. "
|
|
557
|
+
"Finds relevant files and snippets based on your natural language query."
|
|
558
|
+
),
|
|
559
|
+
parameters=[
|
|
560
|
+
ParameterSchema(
|
|
561
|
+
name="query",
|
|
562
|
+
type="string",
|
|
563
|
+
description="Natural language search query (e.g., 'function to read JSON files')",
|
|
564
|
+
),
|
|
565
|
+
ParameterSchema(
|
|
566
|
+
name="top_k",
|
|
567
|
+
type="integer",
|
|
568
|
+
description="Number of results to return",
|
|
569
|
+
required=False,
|
|
570
|
+
default=5,
|
|
571
|
+
minimum=1,
|
|
572
|
+
maximum=20,
|
|
573
|
+
),
|
|
574
|
+
ParameterSchema(
|
|
575
|
+
name="rebuild_index",
|
|
576
|
+
type="boolean",
|
|
577
|
+
description="Force rebuild the search index (use after file changes)",
|
|
578
|
+
required=False,
|
|
579
|
+
default=False,
|
|
580
|
+
),
|
|
581
|
+
],
|
|
582
|
+
category=ToolCategory.SEARCH,
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
# Lazy import to avoid loading dependencies at module import time
|
|
586
|
+
from .rag import RAGSearchTool
|
|
587
|
+
|
|
588
|
+
self._rag_tool = RAGSearchTool(
|
|
589
|
+
base_path=base_path,
|
|
590
|
+
embedding_model=embedding_model,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
def execute(
|
|
594
|
+
self,
|
|
595
|
+
query: str,
|
|
596
|
+
top_k: int = 5,
|
|
597
|
+
rebuild_index: bool = False,
|
|
598
|
+
) -> list[dict[str, Any]]:
|
|
599
|
+
"""
|
|
600
|
+
Search for content in the directory.
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
query: Natural language search query
|
|
604
|
+
top_k: Number of results to return
|
|
605
|
+
rebuild_index: Force rebuild the index
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
List of search result dictionaries
|
|
609
|
+
"""
|
|
610
|
+
return self._rag_tool.execute(query, top_k, rebuild_index)
|
|
611
|
+
|
|
612
|
+
async def aexecute(
|
|
613
|
+
self,
|
|
614
|
+
query: str,
|
|
615
|
+
top_k: int = 5,
|
|
616
|
+
rebuild_index: bool = False,
|
|
617
|
+
) -> list[dict[str, Any]]:
|
|
618
|
+
"""Async version of execute."""
|
|
619
|
+
return await self._rag_tool.aexecute(query, top_k, rebuild_index)
|
|
620
|
+
|
|
621
|
+
def get_interruption_message(self, **kwargs) -> str:
|
|
622
|
+
"""Get interruption message for user confirmation."""
|
|
623
|
+
return "execute search_in_directory"
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
# Convenience function to get all file system tools
|
|
627
|
+
def get_filesystem_tools(
|
|
628
|
+
base_path: Path | None = None,
|
|
629
|
+
virtual_mode: bool = False,
|
|
630
|
+
) -> list[BaseTool]:
|
|
631
|
+
"""
|
|
632
|
+
Get all file system tools configured with the given base path.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
base_path: Base path for all tools (defaults to CWD)
|
|
636
|
+
virtual_mode: If True, use virtual path mode for security
|
|
637
|
+
|
|
638
|
+
Returns:
|
|
639
|
+
List of configured file system tools
|
|
640
|
+
"""
|
|
641
|
+
bp = base_path or Path.cwd()
|
|
642
|
+
return [
|
|
643
|
+
ReadFileTool(bp, virtual_mode),
|
|
644
|
+
ReadDirectoryTool(bp, virtual_mode),
|
|
645
|
+
WriteFileTool(bp, virtual_mode),
|
|
646
|
+
EditFileTool(bp, virtual_mode),
|
|
647
|
+
GrepTool(bp, virtual_mode),
|
|
648
|
+
GlobTool(bp, virtual_mode),
|
|
649
|
+
SearchInDirectoryTool(bp),
|
|
650
|
+
]
|