toolplane-python-client 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.
Files changed (81) hide show
  1. toolplane/__init__.py +106 -0
  2. toolplane/common/__init__.py +93 -0
  3. toolplane/common/base_config.py +129 -0
  4. toolplane/common/base_connection_manager.py +171 -0
  5. toolplane/common/base_session_manager.py +321 -0
  6. toolplane/common/base_tool_manager.py +347 -0
  7. toolplane/common/constants.py +47 -0
  8. toolplane/common/utils.py +310 -0
  9. toolplane/core/__init__.py +67 -0
  10. toolplane/core/config.py +107 -0
  11. toolplane/core/connection.py +285 -0
  12. toolplane/core/errors.py +298 -0
  13. toolplane/core/machine.py +480 -0
  14. toolplane/core/request.py +775 -0
  15. toolplane/core/session.py +332 -0
  16. toolplane/core/session_context.py +514 -0
  17. toolplane/core/task.py +130 -0
  18. toolplane/core/tool.py +329 -0
  19. toolplane/http_core/__init__.py +37 -0
  20. toolplane/http_core/http_config.py +97 -0
  21. toolplane/http_core/http_connection.py +409 -0
  22. toolplane/http_core/http_machine.py +298 -0
  23. toolplane/http_core/http_request.py +748 -0
  24. toolplane/http_core/http_session.py +348 -0
  25. toolplane/http_core/http_session_context.py +491 -0
  26. toolplane/http_core/http_task.py +101 -0
  27. toolplane/http_core/http_tool.py +400 -0
  28. toolplane/interfaces/__init__.py +27 -0
  29. toolplane/interfaces/client_interface.py +122 -0
  30. toolplane/interfaces/connection_interface.py +193 -0
  31. toolplane/interfaces/event_interface.py +290 -0
  32. toolplane/interfaces/request_interface.py +439 -0
  33. toolplane/interfaces/session_interface.py +288 -0
  34. toolplane/interfaces/tool_interface.py +441 -0
  35. toolplane/proto/__init__.py +0 -0
  36. toolplane/proto/service_pb2.py +315 -0
  37. toolplane/proto/service_pb2_grpc.py +2240 -0
  38. toolplane/provider_cli.py +268 -0
  39. toolplane/provider_registry.py +77 -0
  40. toolplane/provider_runtime.py +302 -0
  41. toolplane/toolkits/__init__.py +0 -0
  42. toolplane/toolkits/standalone_tools/__init__.py +0 -0
  43. toolplane/toolkits/standalone_tools/create_directory.py +94 -0
  44. toolplane/toolkits/standalone_tools/create_file.py +124 -0
  45. toolplane/toolkits/standalone_tools/file_search.py +229 -0
  46. toolplane/toolkits/standalone_tools/grep_search.py +372 -0
  47. toolplane/toolkits/standalone_tools/launcher.py +146 -0
  48. toolplane/toolkits/standalone_tools/list_dir.py +395 -0
  49. toolplane/toolkits/standalone_tools/read_file.py +346 -0
  50. toolplane/toolkits/standalone_tools/replace_string_in_file.py +407 -0
  51. toolplane/toolkits/standalone_tools/run_tests.py +66 -0
  52. toolplane/toolkits/standalone_tools/semantic_search.py +485 -0
  53. toolplane/toolkits/standalone_tools/standalone_toolkit.py +979 -0
  54. toolplane/toolkits/standalone_tools/test_failure_analysis.py +618 -0
  55. toolplane/toolkits/standalone_tools/test_standalone_toolkit.py +517 -0
  56. toolplane/toolkits/swe/__init__.py +35 -0
  57. toolplane/toolkits/swe/create_directory.py +15 -0
  58. toolplane/toolkits/swe/create_file.py +15 -0
  59. toolplane/toolkits/swe/descriptions.py +273 -0
  60. toolplane/toolkits/swe/execute_bash.py +93 -0
  61. toolplane/toolkits/swe/file_editor.py +775 -0
  62. toolplane/toolkits/swe/file_search.py +16 -0
  63. toolplane/toolkits/swe/finish.py +50 -0
  64. toolplane/toolkits/swe/grep_search.py +19 -0
  65. toolplane/toolkits/swe/list_dir.py +407 -0
  66. toolplane/toolkits/swe/read_file.py +18 -0
  67. toolplane/toolkits/swe/replace_string_in_file.py +17 -0
  68. toolplane/toolkits/swe/search.py +260 -0
  69. toolplane/toolkits/swe/semantic_search.py +20 -0
  70. toolplane/toolkits/swe/str_replace_editor.py +647 -0
  71. toolplane/toolkits/swe/submit.py +29 -0
  72. toolplane/toolkits/swe/swe_toolkit.py +1296 -0
  73. toolplane/toolplane_client.py +686 -0
  74. toolplane/toolplane_http_client.py +681 -0
  75. toolplane/utils/__init__.py +3 -0
  76. toolplane/utils/schema.py +146 -0
  77. toolplane_python_client-0.1.0.dist-info/METADATA +543 -0
  78. toolplane_python_client-0.1.0.dist-info/RECORD +81 -0
  79. toolplane_python_client-0.1.0.dist-info/WHEEL +5 -0
  80. toolplane_python_client-0.1.0.dist-info/entry_points.txt +2 -0
  81. toolplane_python_client-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1296 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import asyncio
4
+ import io
5
+ import os
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Any, Dict, List, Optional, Type, Union
9
+
10
+ from langchain.tools import BaseTool
11
+ from pydantic import BaseModel, Field
12
+
13
+ # Import descriptions from descriptions.py
14
+ try:
15
+ from toolplane.toolkits.swe.descriptions import (
16
+ _BASH_DESCRIPTION,
17
+ _FILE_EDITOR_DESCRIPTION,
18
+ _FINISH_DESCRIPTION,
19
+ _SEARCH_DESCRIPTION,
20
+ _SUBMIT_DESCRIPTION,
21
+ )
22
+ except ImportError:
23
+ # Fallback for when running as standalone
24
+ from descriptions import (
25
+ _BASH_DESCRIPTION,
26
+ _FILE_EDITOR_DESCRIPTION,
27
+ _FINISH_DESCRIPTION,
28
+ _SEARCH_DESCRIPTION,
29
+ _SUBMIT_DESCRIPTION,
30
+ )
31
+
32
+ # Import functions from individual tool files
33
+ try:
34
+ from toolplane.toolkits.swe.execute_bash import run_command
35
+ from toolplane.toolkits.swe.finish import submit as finish_submit
36
+ from toolplane.toolkits.swe.read_file import read_file
37
+ from toolplane.toolkits.swe.search import search_in_directory, search_in_file
38
+ from toolplane.toolkits.swe.str_replace_editor import (
39
+ StrReplaceEditor,
40
+ load_history,
41
+ save_history,
42
+ )
43
+ from toolplane.toolkits.swe.submit import submit as simple_submit
44
+ except ImportError:
45
+ # Fallback for when running as standalone
46
+ from execute_bash import run_command
47
+ from finish import submit as finish_submit
48
+ from read_file import read_file
49
+ from search import search_in_directory, search_in_file
50
+ from str_replace_editor import StrReplaceEditor, load_history, save_history
51
+ from submit import submit as simple_submit
52
+
53
+
54
+ class ToolExecutionError(RuntimeError):
55
+ """A tool failed while executing.
56
+
57
+ Raised — never returned as text — so the provider submits a rejection
58
+ and the request records FAILED: models, eval scoring, and retry
59
+ heuristics see the failure instead of parsing output strings.
60
+ """
61
+
62
+
63
+ #!/usr/bin/env python3
64
+ """
65
+ Standalone Toolkit for LangChain Integration
66
+
67
+ This module provides a comprehensive set of standalone development tools
68
+ wrapped as LangChain tools for AI agent integration.
69
+ """
70
+
71
+ import os
72
+ import sys
73
+ from pathlib import Path
74
+ from typing import Any, Dict, List, Optional, Type, Union
75
+
76
+ from langchain.tools import BaseTool
77
+ from langchain_core.callbacks.manager import (
78
+ AsyncCallbackManagerForToolRun,
79
+ CallbackManagerForToolRun,
80
+ )
81
+ from pydantic import BaseModel, Field
82
+
83
+ # Import functions from standalone tools
84
+ try:
85
+ from toolplane.toolkits.swe.create_directory import create_directory
86
+ from toolplane.toolkits.swe.create_file import create_file
87
+ from toolplane.toolkits.swe.file_search import file_search
88
+ from toolplane.toolkits.swe.grep_search import grep_search
89
+ from toolplane.toolkits.swe.list_dir import list_dir
90
+ from toolplane.toolkits.swe.replace_string_in_file import replace_string_in_file
91
+ from toolplane.toolkits.swe.semantic_search import semantic_search
92
+ except ImportError:
93
+ # Fallback for when running as standalone
94
+ from create_directory import create_directory
95
+ from create_file import create_file
96
+ from file_search import file_search
97
+ from grep_search import grep_search
98
+ from list_dir import list_dir
99
+ from replace_string_in_file import replace_string_in_file
100
+ from semantic_search import semantic_search
101
+
102
+
103
+ # Input Models for each tool
104
+ class CreateDirectoryInput(BaseModel):
105
+ dir_path: str = Field(description="The absolute path to the directory to create")
106
+ mode: Optional[int] = Field(
107
+ default=0o755, description="Permission mode for the directory"
108
+ )
109
+ parents: Optional[bool] = Field(
110
+ default=True, description="Create parent directories if they don't exist"
111
+ )
112
+ exist_ok: Optional[bool] = Field(
113
+ default=True, description="Don't raise error if directory already exists"
114
+ )
115
+
116
+
117
+ class WriteFileInput(BaseModel):
118
+ file_path: str = Field(description="The absolute path to the file to create")
119
+ content: str = Field(description="The content to write to the file")
120
+ encoding: Optional[str] = Field(
121
+ default="utf-8", description="The encoding to use for the file"
122
+ )
123
+ mode: Optional[str] = Field(default="w", description="The file creation mode")
124
+ auto_create_dirs: Optional[bool] = Field(
125
+ default=True, description="Create parent directories if they don't exist"
126
+ )
127
+ overwrite: Optional[bool] = Field(
128
+ default=False, description="Overwrite the file if it already exists"
129
+ )
130
+
131
+
132
+ class FetchWebpageInput(BaseModel):
133
+ urls: List[str] = Field(description="List of URLs to fetch content from")
134
+ query: str = Field(description="The query to search for in the web page's content")
135
+ timeout: Optional[int] = Field(default=30, description="Request timeout in seconds")
136
+ max_content_length: Optional[int] = Field(
137
+ default=50000, description="Maximum content length to process"
138
+ )
139
+ user_agent: Optional[str] = Field(
140
+ default=None, description="Custom user agent string"
141
+ )
142
+
143
+
144
+ class FileSearchInput(BaseModel):
145
+ query: str = Field(description="Glob pattern to search for files")
146
+ max_results: Optional[int] = Field(
147
+ default=None, description="Maximum number of results to return"
148
+ )
149
+ include_hidden: Optional[bool] = Field(
150
+ default=False, description="Include hidden files in results"
151
+ )
152
+ sort_by: Optional[str] = Field(
153
+ default="name", description="Sort results by 'name', 'size', or 'modified'"
154
+ )
155
+ reverse_sort: Optional[bool] = Field(
156
+ default=False, description="Reverse the sort order"
157
+ )
158
+ show_details: Optional[bool] = Field(
159
+ default=False, description="Show file details like size and modification time"
160
+ )
161
+ base_path: Optional[str] = Field(
162
+ default=None, description="Base directory to search from"
163
+ )
164
+
165
+
166
+ class GrepSearchInput(BaseModel):
167
+ query: str = Field(description="The pattern to search for in files")
168
+ is_regexp: bool = Field(description="Whether the pattern is a regex")
169
+ include_pattern: Optional[str] = Field(
170
+ default=None, description="Search files matching this glob pattern"
171
+ )
172
+ max_results: Optional[int] = Field(
173
+ default=None, description="Maximum number of results to return"
174
+ )
175
+ context_lines: Optional[int] = Field(
176
+ default=0, description="Number of context lines to show around matches"
177
+ )
178
+ ignore_case: Optional[bool] = Field(
179
+ default=False, description="Perform case-insensitive search"
180
+ )
181
+ whole_word: Optional[bool] = Field(
182
+ default=False, description="Match whole words only"
183
+ )
184
+ invert_match: Optional[bool] = Field(
185
+ default=False, description="Show lines that don't match the pattern"
186
+ )
187
+ base_path: Optional[str] = Field(
188
+ default=None, description="Base directory to search from"
189
+ )
190
+
191
+
192
+ class ListDirInput(BaseModel):
193
+ path: Optional[str] = Field(
194
+ default=".",
195
+ description="Directory to list. Use '.' for current working directory. Accepts absolute or relative paths.",
196
+ )
197
+ show_hidden: Optional[bool] = Field(
198
+ default=False, description="Show hidden files and directories"
199
+ )
200
+ show_details: Optional[bool] = Field(
201
+ default=False, description="Show detailed information like size and permissions"
202
+ )
203
+ sort_by: Optional[str] = Field(
204
+ default="name", description="Sort by 'name', 'size', 'modified', or 'type'"
205
+ )
206
+ reverse_sort: Optional[bool] = Field(
207
+ default=False, description="Reverse the sort order"
208
+ )
209
+ recursive: Optional[bool] = Field(
210
+ default=False, description="List contents recursively"
211
+ )
212
+ max_depth: Optional[int] = Field(
213
+ default=3, description="Maximum depth for recursive listing"
214
+ )
215
+ file_filter: Optional[str] = Field(
216
+ default=None, description="Filter files by extension"
217
+ )
218
+
219
+
220
+ class ReadFileInput(BaseModel):
221
+ file_path: str = Field(description="The absolute path of the file to read")
222
+ start_line: int = Field(
223
+ description="The line number to start reading from (1-based)"
224
+ )
225
+ end_line: int = Field(
226
+ description="The inclusive line number to end reading at (1-based, -1 for end)"
227
+ )
228
+ encoding: Optional[str] = Field(
229
+ default=None, description="The encoding to use for reading the file"
230
+ )
231
+ show_line_numbers: Optional[bool] = Field(
232
+ default=True, description="Show line numbers in output"
233
+ )
234
+ highlight_syntax: Optional[bool] = Field(
235
+ default=False, description="Attempt to highlight syntax"
236
+ )
237
+ max_line_length: Optional[int] = Field(
238
+ default=1000, description="Maximum line length before truncation"
239
+ )
240
+
241
+
242
+ class ReplaceStringInput(BaseModel):
243
+ file_path: str = Field(description="The absolute path to the file to edit")
244
+ old_string: str = Field(description="The string to be replaced")
245
+ new_string: str = Field(description="The replacement string")
246
+ create_backup: Optional[bool] = Field(
247
+ default=True, description="Create a backup before editing"
248
+ )
249
+ dry_run: Optional[bool] = Field(
250
+ default=False, description="Show what would be changed without making changes"
251
+ )
252
+ whole_word: Optional[bool] = Field(
253
+ default=False, description="Only replace whole words"
254
+ )
255
+ ignore_case: Optional[bool] = Field(
256
+ default=False, description="Perform case-insensitive matching"
257
+ )
258
+ max_replacements: Optional[int] = Field(
259
+ default=None, description="Maximum number of replacements to make"
260
+ )
261
+
262
+
263
+ class SemanticSearchInput(BaseModel):
264
+ query: str = Field(description="The search query in natural language")
265
+ max_results: Optional[int] = Field(
266
+ default=10, description="Maximum number of results to return"
267
+ )
268
+ file_types: Optional[List[str]] = Field(
269
+ default=None, description="File types to search in"
270
+ )
271
+ similarity_threshold: Optional[float] = Field(
272
+ default=0.1, description="Minimum similarity score to include"
273
+ )
274
+ context_size: Optional[int] = Field(
275
+ default=3, description="Number of lines of context around matches"
276
+ )
277
+ search_path: Optional[str] = Field(
278
+ default=None, description="Directory to search in"
279
+ )
280
+
281
+
282
+ class WebSearchInput(BaseModel):
283
+ query: str = Field(description="The search query")
284
+ max_results: Optional[int] = Field(
285
+ default=10, description="Maximum number of results to return"
286
+ )
287
+ search_engine: Optional[str] = Field(
288
+ default="duckduckgo", description="Search engine to use"
289
+ )
290
+ include_content: Optional[bool] = Field(
291
+ default=False, description="Include page content in results"
292
+ )
293
+ content_length: Optional[int] = Field(
294
+ default=1000, description="Maximum content length to extract"
295
+ )
296
+ filter_domain: Optional[str] = Field(
297
+ default=None, description="Only include results from this domain"
298
+ )
299
+
300
+
301
+ class TestFailureAnalysisInput(BaseModel):
302
+ test_output: Optional[str] = Field(
303
+ default=None, description="Path to test output file or direct test output"
304
+ )
305
+ test_framework: Optional[str] = Field(
306
+ default=None, description="Test framework used"
307
+ )
308
+ verbose: Optional[bool] = Field(default=False, description="Show detailed analysis")
309
+ suggest_fixes: Optional[bool] = Field(
310
+ default=True, description="Suggest potential fixes"
311
+ )
312
+ group_by_type: Optional[bool] = Field(
313
+ default=True, description="Group failures by error type"
314
+ )
315
+
316
+
317
+ # Tool Classes
318
+ class CreateDirectoryTool(BaseTool):
319
+ """📁 Create directories with enhanced features and permission handling."""
320
+
321
+ name: str = "create_directory"
322
+ description: str = """Create a directory structure with enhanced features.
323
+
324
+ This tool creates directories recursively (like mkdir -p) and provides
325
+ additional features like permission handling and validation.
326
+
327
+ Best for:
328
+ - Creating project directory structures
329
+ - Setting up development environments
330
+ - Organizing file systems
331
+ - Batch directory creation"""
332
+
333
+ args_schema: Type[BaseModel] = CreateDirectoryInput
334
+
335
+ def _run(
336
+ self,
337
+ dir_path: str,
338
+ mode: Optional[int] = 0o755,
339
+ parents: Optional[bool] = True,
340
+ exist_ok: Optional[bool] = True,
341
+ run_manager: Optional[CallbackManagerForToolRun] = None,
342
+ ) -> str:
343
+ try:
344
+ success = create_directory(
345
+ dir_path=dir_path,
346
+ mode=mode or 0o755,
347
+ parents=parents if parents is not None else True,
348
+ exist_ok=exist_ok if exist_ok is not None else True,
349
+ )
350
+
351
+ if success:
352
+ return f"Directory created successfully: {dir_path}"
353
+ else:
354
+ raise ToolExecutionError(f"Failed to create directory: {dir_path}")
355
+
356
+ except Exception as e:
357
+ raise ToolExecutionError(f"Error creating directory: {str(e)}")
358
+
359
+
360
+ class WriteFileTool(BaseTool):
361
+ """📄 Create files with specified content and automatic directory creation."""
362
+
363
+ name: str = "write_file"
364
+ description: str = """Write to a file or create a new file with the given content and automatically creates
365
+ parent directories if they don't exist. It includes validation and error handling.
366
+
367
+ Best for:
368
+ - Creating new source files
369
+ - Generating configuration files
370
+ - Setting up project templates
371
+ - Batch file creation"""
372
+
373
+ args_schema: Type[BaseModel] = WriteFileInput
374
+
375
+ def _run(
376
+ self,
377
+ file_path: str,
378
+ content: str,
379
+ encoding: Optional[str] = "utf-8",
380
+ mode: Optional[str] = "w",
381
+ auto_create_dirs: Optional[bool] = True,
382
+ overwrite: Optional[bool] = False,
383
+ run_manager: Optional[CallbackManagerForToolRun] = None,
384
+ ) -> str:
385
+ try:
386
+ success = create_file(
387
+ file_path=file_path,
388
+ content=content,
389
+ encoding=encoding or "utf-8",
390
+ mode=mode or "w",
391
+ auto_create_dirs=(
392
+ auto_create_dirs if auto_create_dirs is not None else True
393
+ ),
394
+ overwrite=overwrite if overwrite is not None else False,
395
+ )
396
+
397
+ if success:
398
+ return f"File created successfully: {file_path}"
399
+ else:
400
+ raise ToolExecutionError(f"Failed to create file: {file_path}")
401
+
402
+ except Exception as e:
403
+ raise ToolExecutionError(f"Error creating file: {str(e)}")
404
+
405
+
406
+ class FileSearchTool(BaseTool):
407
+ """🔍 Search for files using glob patterns with advanced features."""
408
+
409
+ name: str = "file_search"
410
+ description: str = """Search for files using glob patterns and provides additional
411
+ filtering and sorting options. It returns file paths matching the pattern.
412
+
413
+ Best for:
414
+ - Finding files by pattern
415
+ - Project file discovery
416
+ - Build system file location
417
+ - Code organization analysis"""
418
+
419
+ args_schema: Type[BaseModel] = FileSearchInput
420
+
421
+ def _run(
422
+ self,
423
+ query: str,
424
+ max_results: Optional[int] = None,
425
+ include_hidden: Optional[bool] = False,
426
+ sort_by: Optional[str] = "name",
427
+ reverse_sort: Optional[bool] = False,
428
+ show_details: Optional[bool] = False,
429
+ base_path: Optional[str] = None,
430
+ run_manager: Optional[CallbackManagerForToolRun] = None,
431
+ ) -> str:
432
+ try:
433
+ results = file_search(
434
+ query=query,
435
+ max_results=max_results,
436
+ include_hidden=include_hidden or False,
437
+ sort_by=sort_by or "name",
438
+ reverse_sort=reverse_sort or False,
439
+ show_details=show_details or False,
440
+ base_path=base_path,
441
+ )
442
+
443
+ if not results:
444
+ return f"No files found matching pattern: {query}"
445
+
446
+ # Format results for display
447
+ output = [f"Found {len(results)} files matching pattern: {query}"]
448
+
449
+ if show_details:
450
+ output.append("-" * 80)
451
+ for result in results:
452
+ file_type = "DIR" if result["is_dir"] else "FILE"
453
+ size_str = f"{result['size']} bytes" if result["is_file"] else "-"
454
+ output.append(f"{result['name']:<40} {file_type:<5} {size_str}")
455
+ else:
456
+ for result in results:
457
+ suffix = "/" if result["is_dir"] else ""
458
+ output.append(f"{result['path']}{suffix}")
459
+
460
+ return "\n".join(output)
461
+
462
+ except Exception as e:
463
+ raise ToolExecutionError(f"Error searching files: {str(e)}")
464
+
465
+
466
+ class GrepSearchTool(BaseTool):
467
+ """🔎 Fast text search in files using grep-like functionality."""
468
+
469
+ name: str = "grep_search"
470
+ description: str = """Perform fast text search in files using exact strings or regex patterns.
471
+ It provides context lines, file filtering, and other advanced search options.
472
+
473
+ Best for:
474
+ - Code pattern searching
475
+ - Log file analysis
476
+ - Documentation searching
477
+ - Multi-file text analysis"""
478
+
479
+ args_schema: Type[BaseModel] = GrepSearchInput
480
+
481
+ def _run(
482
+ self,
483
+ query: str,
484
+ is_regexp: bool,
485
+ include_pattern: Optional[str] = None,
486
+ max_results: Optional[int] = None,
487
+ context_lines: Optional[int] = 0,
488
+ ignore_case: Optional[bool] = False,
489
+ whole_word: Optional[bool] = False,
490
+ invert_match: Optional[bool] = False,
491
+ base_path: Optional[str] = None,
492
+ run_manager: Optional[CallbackManagerForToolRun] = None,
493
+ ) -> str:
494
+ try:
495
+ results = grep_search(
496
+ query=query,
497
+ is_regexp=is_regexp,
498
+ include_pattern=include_pattern,
499
+ max_results=max_results,
500
+ context_lines=context_lines or 0,
501
+ ignore_case=ignore_case or False,
502
+ whole_word=whole_word or False,
503
+ invert_match=invert_match or False,
504
+ base_path=base_path,
505
+ )
506
+
507
+ if not results:
508
+ return f"No matches found for pattern: {query}"
509
+
510
+ # Format results for display
511
+ total_matches = sum(r["total_matches"] for r in results)
512
+ output = [f"Found {total_matches} matches in {len(results)} files"]
513
+ output.append("=" * 60)
514
+
515
+ for result in results:
516
+ if "error" in result:
517
+ output.append(f"Error in {result['file']}: {result['error']}")
518
+ continue
519
+
520
+ output.append(f"\nFile: {result['file']}")
521
+ output.append(f"Matches: {result['total_matches']}")
522
+ output.append("-" * 40)
523
+
524
+ for match in result["matches"][:5]: # Show first 5 matches
525
+ if context_lines and match.get("context"):
526
+ for ctx in match["context"]:
527
+ prefix = ">" if ctx["is_match"] else " "
528
+ output.append(
529
+ f"{prefix}{ctx['line_number']:4d}: {ctx['content']}"
530
+ )
531
+ else:
532
+ output.append(f"{match['line_number']:4d}: {match['content']}")
533
+
534
+ return "\n".join(output)
535
+
536
+ except Exception as e:
537
+ raise ToolExecutionError(f"Error searching with grep: {str(e)}")
538
+
539
+
540
+ class ListDirTool(BaseTool):
541
+ """📂 List directory contents with enhanced features."""
542
+
543
+ name: str = "list_dir"
544
+ description: str = """List the contents of a directory with various display options,
545
+ sorting capabilities, and filtering features.
546
+
547
+ Best for:
548
+ - Directory exploration
549
+ - Project structure analysis
550
+ - File system navigation
551
+ - Content organization"""
552
+
553
+ args_schema: Type[BaseModel] = ListDirInput
554
+
555
+ def _run(
556
+ self,
557
+ path: Optional[str] = ".",
558
+ show_hidden: Optional[bool] = False,
559
+ show_details: Optional[bool] = False,
560
+ sort_by: Optional[str] = "name",
561
+ reverse_sort: Optional[bool] = False,
562
+ recursive: Optional[bool] = False,
563
+ max_depth: Optional[int] = 3,
564
+ file_filter: Optional[str] = None,
565
+ run_manager: Optional[CallbackManagerForToolRun] = None,
566
+ ) -> str:
567
+ try:
568
+ path = path or "."
569
+ normalized_path = os.path.abspath(path)
570
+ results = list_dir(
571
+ path=normalized_path,
572
+ show_hidden=show_hidden or False,
573
+ show_details=show_details or False,
574
+ sort_by=sort_by or "name",
575
+ reverse_sort=reverse_sort or False,
576
+ recursive=recursive or False,
577
+ max_depth=max_depth or 3,
578
+ file_filter=file_filter,
579
+ )
580
+
581
+ if not results:
582
+ return f"No contents found in directory: {path}"
583
+
584
+ # Format results for display
585
+ output = [f"Contents of {path}:"]
586
+
587
+ if show_details:
588
+ output.append(f"{'Name':<40} {'Type':<5} {'Size':<10}")
589
+ output.append("-" * 60)
590
+
591
+ for result in results:
592
+ file_type = "DIR" if result["is_dir"] else "FILE"
593
+ if result["is_symlink"]:
594
+ file_type = "LINK"
595
+
596
+ size_str = f"{result['size']} bytes" if result["is_file"] else "-"
597
+ indent = " " * result.get("depth", 0)
598
+
599
+ output.append(
600
+ f"{indent}{result['name']:<40} {file_type:<5} {size_str}"
601
+ )
602
+ else:
603
+ for result in results:
604
+ suffix = "/" if result["is_dir"] else ""
605
+ indent = " " * result.get("depth", 0)
606
+ output.append(f"{indent}{result['name']}{suffix}")
607
+
608
+ return "\n".join(output)
609
+
610
+ except Exception as e:
611
+ raise ToolExecutionError(f"Error listing directory: {str(e)}")
612
+
613
+
614
+ class ReadFileTool(BaseTool):
615
+ """📖 Read file contents with line range support and encoding detection."""
616
+
617
+ name: str = "read_file"
618
+ description: str = """Read the contents of a file with support for line ranges,
619
+ encoding detection, and various output formats.
620
+
621
+ Best for:
622
+ - Code review and analysis
623
+ - Configuration file inspection
624
+ - Log file examination
625
+ - Content verification"""
626
+
627
+ args_schema: Type[BaseModel] = ReadFileInput
628
+
629
+ def _run(
630
+ self,
631
+ file_path: str,
632
+ start_line: int,
633
+ end_line: int,
634
+ encoding: Optional[str] = None,
635
+ show_line_numbers: Optional[bool] = True,
636
+ highlight_syntax: Optional[bool] = False,
637
+ max_line_length: Optional[int] = 1000,
638
+ run_manager: Optional[CallbackManagerForToolRun] = None,
639
+ ) -> str:
640
+ try:
641
+ result = read_file(
642
+ file_path=file_path,
643
+ start_line=start_line,
644
+ end_line=end_line,
645
+ encoding=encoding,
646
+ show_line_numbers=(
647
+ show_line_numbers if show_line_numbers is not None else True
648
+ ),
649
+ highlight_syntax=highlight_syntax or False,
650
+ max_line_length=max_line_length or 1000,
651
+ )
652
+
653
+ if not result["success"]:
654
+ raise ToolExecutionError(f"Error: {result['error']}")
655
+
656
+ # Format output
657
+ output = []
658
+ output.append(f"File: {result['file_path']}")
659
+ output.append(
660
+ f"Lines: {result['start_line']}-{result['end_line']} (of {result['total_lines']})"
661
+ )
662
+ output.append(f"Encoding: {result['encoding']}")
663
+ if result["file_language"]:
664
+ output.append(f"Language: {result['file_language']}")
665
+ output.append("-" * 60)
666
+
667
+ # Content
668
+ for line_info in result["content"]:
669
+ if result["show_line_numbers"]:
670
+ line_num = f"{line_info['line_number']:4d}"
671
+ content = line_info["content"]
672
+
673
+ # Show truncation indicator
674
+ if line_info["original_length"] > len(content):
675
+ truncated = " [truncated]"
676
+ else:
677
+ truncated = ""
678
+
679
+ output.append(f"{line_num}: {content}{truncated}")
680
+ else:
681
+ output.append(line_info["content"])
682
+
683
+ return "\n".join(output)
684
+
685
+ except Exception as e:
686
+ raise ToolExecutionError(f"Error reading file: {str(e)}")
687
+
688
+
689
+ class ReplaceStringTool(BaseTool):
690
+ """🔄 Replace strings in files with safety checks and backup options."""
691
+
692
+ name: str = "replace_string"
693
+ description: str = """Replace strings in files with validation, backup options,
694
+ and various safety features to prevent accidental data loss.
695
+
696
+ Best for:
697
+ - Code refactoring
698
+ - Configuration updates
699
+ - Batch text replacements
700
+ - Safe file modifications"""
701
+
702
+ args_schema: Type[BaseModel] = ReplaceStringInput
703
+
704
+ def _run(
705
+ self,
706
+ file_path: str,
707
+ old_string: str,
708
+ new_string: str,
709
+ create_backup: Optional[bool] = True,
710
+ dry_run: Optional[bool] = False,
711
+ whole_word: Optional[bool] = False,
712
+ ignore_case: Optional[bool] = False,
713
+ max_replacements: Optional[int] = None,
714
+ run_manager: Optional[CallbackManagerForToolRun] = None,
715
+ ) -> str:
716
+ try:
717
+ result = replace_string_in_file(
718
+ file_path=file_path,
719
+ old_string=old_string,
720
+ new_string=new_string,
721
+ create_backup=create_backup if create_backup is not None else True,
722
+ dry_run=dry_run or False,
723
+ whole_word=whole_word or False,
724
+ ignore_case=ignore_case or False,
725
+ max_replacements=max_replacements,
726
+ )
727
+
728
+ if not result["success"]:
729
+ raise ToolExecutionError(f"Error: {result['error']}")
730
+
731
+ output = [result["message"]]
732
+
733
+ if result.get("dry_run") and "changes_preview" in result:
734
+ output.append("\nChanges preview:")
735
+ for change in result["changes_preview"]:
736
+ output.append(
737
+ f"\nMatch {change['match_number']} at line {change['line_number']}:"
738
+ )
739
+ output.append(f"Before: {change['before']}")
740
+ output.append(f"After: {change['after']}")
741
+
742
+ if result.get("backup_created"):
743
+ output.append(f"Backup created: {result['backup_path']}")
744
+
745
+ return "\n".join(output)
746
+
747
+ except Exception as e:
748
+ raise ToolExecutionError(f"Error replacing string: {str(e)}")
749
+
750
+
751
+ class SemanticSearchTool(BaseTool):
752
+ """🧠 Semantic search for relevant code using text similarity."""
753
+
754
+ name: str = "semantic_search"
755
+ description: str = """Perform semantic search across code and documentation files using
756
+ text similarity algorithms to find relevant content based on natural language queries.
757
+
758
+ Best for:
759
+ - Finding relevant code snippets
760
+ - Documentation discovery
761
+ - Code understanding and navigation
762
+ - Knowledge base search"""
763
+
764
+ args_schema: Type[BaseModel] = SemanticSearchInput
765
+
766
+ def _run(
767
+ self,
768
+ query: str,
769
+ max_results: Optional[int] = 10,
770
+ file_types: Optional[List[str]] = None,
771
+ similarity_threshold: Optional[float] = 0.1,
772
+ context_size: Optional[int] = 3,
773
+ search_path: Optional[str] = None,
774
+ run_manager: Optional[CallbackManagerForToolRun] = None,
775
+ ) -> str:
776
+ try:
777
+ results = semantic_search(
778
+ query=query,
779
+ max_results=max_results or 10,
780
+ file_types=file_types,
781
+ similarity_threshold=similarity_threshold or 0.1,
782
+ context_size=context_size or 3,
783
+ search_path=search_path,
784
+ )
785
+
786
+ if not results:
787
+ return f"No results found for query: {query}"
788
+
789
+ # Format results for display
790
+ output = [f"Semantic search results for: {query}"]
791
+ output.append(f"Found {len(results)} results")
792
+ output.append("=" * 80)
793
+
794
+ for i, result in enumerate(results, 1):
795
+ output.append(
796
+ f"\nResult {i}: {result['file_path']}:{result['line_number']}"
797
+ )
798
+ output.append(f"Similarity Score: {result['similarity_score']:.3f}")
799
+ output.append(f"File Type: {result['file_type']}")
800
+
801
+ if result.get("match_type") == "function_definition":
802
+ output.append(f"Function: {result.get('function_name', 'unknown')}")
803
+
804
+ output.append("Context:")
805
+ for context_line in result["context"]:
806
+ marker = ">>>" if context_line["is_match"] else " "
807
+ output.append(
808
+ f"{marker} {context_line['line_number']:4d}: {context_line['content']}"
809
+ )
810
+
811
+ output.append("-" * 80)
812
+
813
+ return "\n".join(output)
814
+
815
+ except Exception as e:
816
+ raise ToolExecutionError(f"Error performing semantic search: {str(e)}")
817
+
818
+
819
+ def search_directory_for_term(search_term: str, directory: str = "."):
820
+ """Wrapper function for search_dir functionality"""
821
+ import os
822
+
823
+ if not os.path.isdir(directory):
824
+ raise ToolExecutionError(f"Directory {directory} not found")
825
+
826
+ directory = os.path.realpath(directory)
827
+ matches = {}
828
+ num_files_matched = 0
829
+
830
+ for root, dirs, files in os.walk(directory):
831
+ # Exclude hidden directories
832
+ dirs[:] = [d for d in dirs if not d.startswith(".")]
833
+ for file in files:
834
+ if file.startswith("."):
835
+ continue # Skip hidden files
836
+ filepath = os.path.join(root, file)
837
+ try:
838
+ with open(filepath, "r", errors="ignore") as f:
839
+ file_matches = 0
840
+ for line_num, line in enumerate(f, 1):
841
+ if search_term in line:
842
+ file_matches += 1
843
+ if file_matches > 0:
844
+ matches[filepath] = file_matches
845
+ num_files_matched += 1
846
+ except (UnicodeDecodeError, PermissionError):
847
+ continue # Skip files that can't be read
848
+
849
+ if not matches:
850
+ return f'No matches found for "{search_term}" in {directory}'
851
+
852
+ num_matches = sum(matches.values())
853
+
854
+ if num_files_matched > 100:
855
+ return f'More than {num_files_matched} files matched for "{search_term}" in {directory}. Please narrow your search.'
856
+
857
+ result = f'Found {num_matches} matches for "{search_term}" in {directory}:\n'
858
+
859
+ for filepath, count in matches.items():
860
+ # Replace leading path with './' for consistency
861
+ relative_path = os.path.relpath(filepath, start=os.getcwd())
862
+ if not relative_path.startswith("./"):
863
+ relative_path = "./" + relative_path
864
+ result += f"{relative_path} ({count} matches)\n"
865
+
866
+ result += f'End of matches for "{search_term}" in {directory}'
867
+ return result
868
+
869
+
870
+ class FileEditorInput(BaseModel):
871
+ command: str = Field(
872
+ description="The command to run. Allowed options are: `view`, `create`, `str_replace`, `insert`, `undo_edit`."
873
+ )
874
+ path: str = Field(
875
+ description="Absolute path to file or directory, e.g. `/testbed/file.py` or `/testbed`."
876
+ )
877
+ file_text: Optional[str] = Field(
878
+ default=None,
879
+ description="Required for the `create` command, contains the content of the file to be created.",
880
+ )
881
+ old_str: Optional[str] = Field(
882
+ default=None,
883
+ description="Required for the `str_replace` command, specifies the string in `path` to replace.",
884
+ )
885
+ new_str: Optional[str] = Field(
886
+ default=None,
887
+ description="Optional for the `str_replace` command to specify the replacement string. Required for the `insert` command to specify the string to insert.",
888
+ )
889
+ insert_line: Optional[int] = Field(
890
+ default=None,
891
+ description="Required for the `insert` command. The `new_str` will be inserted AFTER the line specified.",
892
+ )
893
+ view_range: Optional[List[int]] = Field(
894
+ default=None,
895
+ description="Optional for the `view` command when `path` points to a file. Specifies the line range to view. E.g., [11, 12] shows lines 11 and 12. Indexing starts at 1. Use [start_line, -1] to show all lines from `start_line` to the end.",
896
+ )
897
+ enable_linting: bool = Field(
898
+ default=False, description="Enable Python linting checks before saving changes"
899
+ )
900
+
901
+
902
+ class SearchInput(BaseModel):
903
+ search_term: str = Field(description="The term to search for in files.")
904
+ path: Optional[str] = Field(
905
+ default=".",
906
+ description="The file or directory to search in. Defaults to `.` if not specified.",
907
+ )
908
+ python_only: bool = Field(default=False, description="Only search in Python files")
909
+
910
+
911
+ class SearchDirInput(BaseModel):
912
+ # Keep existing descriptions as there's no direct match in descriptions.py
913
+ search_term: str = Field(description="The term to search for")
914
+ directory: Optional[str] = Field(
915
+ default=".", description="The directory to search in"
916
+ )
917
+
918
+
919
+ class FinishInput(BaseModel):
920
+ result: str = Field(
921
+ default="",
922
+ description="Optional. The result text to submit. Defaults to an empty string if not provided.",
923
+ )
924
+
925
+
926
+ class FileEditorTool(BaseTool):
927
+ """📝 Advanced file editor with view, create, edit, and undo capabilities."""
928
+
929
+ name: str = "file_editor"
930
+ description: str = _FILE_EDITOR_DESCRIPTION
931
+
932
+ args_schema: Type[BaseModel] = FileEditorInput
933
+
934
+ def __init__(self, **kwargs):
935
+ super().__init__(**kwargs)
936
+
937
+ def _run(
938
+ self,
939
+ command: str,
940
+ path: str,
941
+ file_text: Optional[str] = None,
942
+ old_str: Optional[str] = None,
943
+ new_str: Optional[str] = None,
944
+ insert_line: Optional[int] = None,
945
+ view_range: Optional[List[int]] = None,
946
+ enable_linting: bool = False,
947
+ run_manager: Optional[CallbackManagerForToolRun] = None,
948
+ ) -> str:
949
+ try:
950
+ # Load edit history and create editor instance
951
+ history = load_history()
952
+ editor = StrReplaceEditor(history, enable_linting)
953
+
954
+ # Capture stdout to get the result
955
+ old_stdout = sys.stdout
956
+ sys.stdout = captured_output = io.StringIO()
957
+
958
+ try:
959
+ # Run the editor command
960
+ result = editor.run(
961
+ command=command,
962
+ path_str=path,
963
+ file_text=file_text,
964
+ view_range=view_range,
965
+ old_str=old_str,
966
+ new_str=new_str,
967
+ insert_line=insert_line,
968
+ )
969
+
970
+ # Save updated history
971
+ save_history(editor.file_history)
972
+
973
+ # Return the result
974
+ return str(result)
975
+
976
+ finally:
977
+ sys.stdout = old_stdout
978
+
979
+ except Exception as e:
980
+ raise ToolExecutionError(f"Error running file editor: {str(e)}")
981
+
982
+
983
+ class SearchTool(BaseTool):
984
+ """🔍 Search for text within files and directories."""
985
+
986
+ name: str = "search"
987
+ description: str = _SEARCH_DESCRIPTION
988
+
989
+ args_schema: Type[BaseModel] = SearchInput
990
+
991
+ def __init__(self, **kwargs):
992
+ super().__init__(**kwargs)
993
+
994
+ def _run(
995
+ self,
996
+ search_term: str,
997
+ path: str = ".",
998
+ python_only: bool = False,
999
+ run_manager: Optional[CallbackManagerForToolRun] = None,
1000
+ ) -> str:
1001
+ try:
1002
+ # Capture stdout to get the result
1003
+ old_stdout = sys.stdout
1004
+ sys.stdout = captured_output = io.StringIO()
1005
+
1006
+ try:
1007
+ # Check if path is a file or directory
1008
+ if os.path.isfile(path):
1009
+ search_in_file(search_term, path)
1010
+ else:
1011
+ search_in_directory(search_term, path, python_only)
1012
+
1013
+ # Get the captured output
1014
+ result = captured_output.getvalue()
1015
+ return result
1016
+
1017
+ finally:
1018
+ sys.stdout = old_stdout
1019
+
1020
+ except Exception as e:
1021
+ raise ToolExecutionError(f"Error running search: {str(e)}")
1022
+
1023
+
1024
+ class SearchDirTool(BaseTool):
1025
+ """📁 Search for text within all files in a directory."""
1026
+
1027
+ name: str = "search_dir"
1028
+ description: str = """Recursively search for text patterns in all files within a directory.
1029
+
1030
+ Features:
1031
+ - Recursive directory traversal
1032
+ - Match counting per file
1033
+ - Excludes hidden files and directories
1034
+ - Performance optimized for large directories
1035
+
1036
+ Best for:
1037
+ - Codebase-wide searches
1038
+ - Finding all occurrences of a pattern
1039
+ - Project-wide refactoring preparation
1040
+ - Code analysis and auditing"""
1041
+
1042
+ args_schema: Type[BaseModel] = SearchDirInput
1043
+
1044
+ def __init__(self, **kwargs):
1045
+ super().__init__(**kwargs)
1046
+
1047
+ def _run(
1048
+ self,
1049
+ search_term: str,
1050
+ directory: str = ".",
1051
+ run_manager: Optional[CallbackManagerForToolRun] = None,
1052
+ ) -> str:
1053
+ try:
1054
+ return search_directory_for_term(search_term, directory)
1055
+ except Exception as e:
1056
+ raise ToolExecutionError(f"Error running search_dir: {str(e)}")
1057
+
1058
+
1059
+ class BashInput(BaseModel):
1060
+ command: str = Field(
1061
+ description="The command (and optional arguments) to execute. For example: 'python my_script.py'"
1062
+ )
1063
+
1064
+
1065
+ class BashTool(BaseTool):
1066
+ """⚡ Execute bash commands. UNSANDBOXED: only register on providers that
1067
+ are themselves isolated (container/VM). Commands are killed after the
1068
+ configured timeout; there is no command filtering."""
1069
+
1070
+ name: str = "execute_bash"
1071
+ description: str = _BASH_DESCRIPTION.format(PWD=os.getcwd())
1072
+ args_schema: Type[BaseModel] = BashInput
1073
+
1074
+ def __init__(self, **kwargs):
1075
+ super().__init__(**kwargs)
1076
+
1077
+ def _run(
1078
+ self,
1079
+ command: str,
1080
+ run_manager: Optional[CallbackManagerForToolRun] = None,
1081
+ ) -> str:
1082
+ try:
1083
+ # Run the command using the imported function (hard timeout and
1084
+ # optional workspace cwd are applied inside run_command).
1085
+ result = run_command(command)
1086
+
1087
+ if result.returncode != 0:
1088
+ # A nonzero exit is a tool failure: raise so the provider
1089
+ # submits a rejection and the durable record says FAILED,
1090
+ # keeping the captured output in the message for the model.
1091
+ output = "Error executing command:\n"
1092
+ output += "[STDOUT]\n"
1093
+ output += result.stdout.strip() + "\n"
1094
+ output += "[STDERR]\n"
1095
+ output += result.stderr.strip()
1096
+ raise ToolExecutionError(output)
1097
+
1098
+ output = "[STDOUT]\n"
1099
+ output += result.stdout.strip() + "\n"
1100
+ output += "[STDERR]\n"
1101
+ output += result.stderr.strip()
1102
+ return output
1103
+
1104
+ except ToolExecutionError:
1105
+ raise
1106
+ except Exception as e:
1107
+ raise ToolExecutionError(f"Error running bash command: {e}")
1108
+
1109
+
1110
+ class FinishTool(BaseTool):
1111
+ """✅ Submit results and finish tasks."""
1112
+
1113
+ name: str = "finish"
1114
+ description: str = _FINISH_DESCRIPTION
1115
+
1116
+ args_schema: Type[BaseModel] = FinishInput
1117
+
1118
+ def __init__(self, **kwargs):
1119
+ super().__init__(**kwargs)
1120
+
1121
+ def _run(
1122
+ self,
1123
+ result: str = "",
1124
+ run_manager: Optional[CallbackManagerForToolRun] = None,
1125
+ ) -> str:
1126
+ try:
1127
+ # Capture stdout to get the result
1128
+ old_stdout = sys.stdout
1129
+ sys.stdout = captured_output = io.StringIO()
1130
+
1131
+ try:
1132
+ # Call the finish submit function
1133
+ finish_submit(result)
1134
+
1135
+ # Get the captured output
1136
+ output = captured_output.getvalue()
1137
+ return output
1138
+
1139
+ finally:
1140
+ sys.stdout = old_stdout
1141
+
1142
+ except Exception as e:
1143
+ raise ToolExecutionError(f"Error running finish: {str(e)}")
1144
+
1145
+
1146
+ class SubmitTool(BaseTool):
1147
+ """🎯 Simple task submission tool."""
1148
+
1149
+ name: str = "submit"
1150
+ description: str = _SUBMIT_DESCRIPTION
1151
+
1152
+ args_schema: Type[BaseModel] = BaseModel
1153
+
1154
+ def __init__(self, **kwargs):
1155
+ super().__init__(**kwargs)
1156
+
1157
+ def _run(
1158
+ self,
1159
+ run_manager: Optional[CallbackManagerForToolRun] = None,
1160
+ ) -> str:
1161
+ try:
1162
+ # Capture stdout to get the result
1163
+ old_stdout = sys.stdout
1164
+ sys.stdout = captured_output = io.StringIO()
1165
+
1166
+ try:
1167
+ # Call the simple submit function
1168
+ simple_submit()
1169
+
1170
+ # Get the captured output
1171
+ output = captured_output.getvalue()
1172
+ return output
1173
+
1174
+ finally:
1175
+ sys.stdout = old_stdout
1176
+
1177
+ except Exception as e:
1178
+ raise ToolExecutionError(f"Error running submit: {str(e)}")
1179
+
1180
+
1181
+ def get_swe_toolkit() -> List[BaseTool]:
1182
+ """Get all SWE (Software Engineering) tools with proper initialization.
1183
+
1184
+ Args:
1185
+ tools_dir: Path to the tools directory. If None, uses the current file's directory.
1186
+ Note: This parameter is maintained for compatibility but is no longer used
1187
+ since tools now import functions directly.
1188
+
1189
+ Returns:
1190
+ List of initialized SWE tools ready for LangChain integration.
1191
+ """
1192
+
1193
+ return [
1194
+ # FileEditorTool(),
1195
+ # SearchTool(),
1196
+ # SearchDirTool(),
1197
+ BashTool(),
1198
+ CreateDirectoryTool(),
1199
+ WriteFileTool(),
1200
+ FileSearchTool(),
1201
+ GrepSearchTool(),
1202
+ # ListDirTool(),
1203
+ ReadFileTool(),
1204
+ ReplaceStringTool(),
1205
+ SemanticSearchTool(),
1206
+ ]
1207
+
1208
+
1209
+ def get_file_editor_only(tools_dir: Optional[str] = None) -> FileEditorTool:
1210
+ """Get only the file editor tool for focused file operations."""
1211
+ if tools_dir is None:
1212
+ tools_dir = os.path.dirname(os.path.abspath(__file__))
1213
+ return FileEditorTool(tools_dir=tools_dir)
1214
+
1215
+
1216
+ def get_search_tools_only(tools_dir: Optional[str] = None) -> List[BaseTool]:
1217
+ """Get only the search-related tools."""
1218
+ if tools_dir is None:
1219
+ tools_dir = os.path.dirname(os.path.abspath(__file__))
1220
+ return [
1221
+ SearchTool(tools_dir=tools_dir),
1222
+ SearchDirTool(tools_dir=tools_dir),
1223
+ ]
1224
+
1225
+
1226
+ # Tool selection guide for LLMs
1227
+ SWE_TOOL_SELECTION_GUIDE = """
1228
+ 🛠️ SWE TOOLKIT SELECTION GUIDE FOR AI AGENTS:
1229
+
1230
+ 1. **file_editor** 📝
1231
+ - Default choice for all file operations
1232
+ - View, create, edit, and undo file changes
1233
+ - Supports Python linting and syntax checking
1234
+ - Persistent edit history
1235
+
1236
+ 2. **search** 🔍
1237
+ - Search within specific files or directories
1238
+ - Line number reporting for matches
1239
+ - Cross-platform grep/Python fallback
1240
+ - Use when you need to find specific patterns
1241
+
1242
+ 3. **search_dir** 📁
1243
+ - Recursive directory-wide searches
1244
+ - Match counting per file
1245
+ - Use for codebase-wide pattern finding
1246
+ - Great for refactoring preparation
1247
+
1248
+ 4. **execute_bash** ⚡
1249
+ - Run system commands and scripts
1250
+ - Security restrictions for safety
1251
+ - Cross-platform compatibility
1252
+ - NOT for interactive or long-running commands
1253
+
1254
+ 5. **finish** ✅
1255
+ - Submit results and complete tasks
1256
+ - Optional result text submission
1257
+ - Use when task objectives are met
1258
+
1259
+ 6. **submit** 🎯
1260
+ - Simple task completion signal
1261
+ - No parameters needed
1262
+ - Quick completion indicator
1263
+
1264
+ ⚠️ IMPORTANT WORKFLOW:
1265
+ 1. Start with file_editor to view project structure
1266
+ 2. Use search/search_dir to find relevant code
1267
+ 3. Use file_editor to make changes
1268
+ 4. Use execute_bash to test changes
1269
+ 5. Use finish/submit to complete tasks
1270
+
1271
+ 💡 BEST PRACTICES:
1272
+ - Always view files before editing
1273
+ - Use search to understand codebase first
1274
+ - Test changes with execute_bash
1275
+ - Use specific, unique strings for str_replace
1276
+ - Include context in old_str for uniqueness
1277
+ """
1278
+
1279
+ # Export all tools and utilities
1280
+ __all__ = [
1281
+ "FileEditorInput",
1282
+ "SearchInput",
1283
+ "SearchDirInput",
1284
+ "BashInput",
1285
+ "FinishInput",
1286
+ "FileEditorTool",
1287
+ "SearchTool",
1288
+ "SearchDirTool",
1289
+ "BashTool",
1290
+ "FinishTool",
1291
+ "SubmitTool",
1292
+ "get_swe_toolkit",
1293
+ "get_file_editor_only",
1294
+ "get_search_tools_only",
1295
+ "SWE_TOOL_SELECTION_GUIDE",
1296
+ ]