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,979 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Standalone Toolkit for LangChain Integration
4
+
5
+ This module provides a comprehensive set of standalone development tools
6
+ wrapped as LangChain tools for AI agent integration.
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional, Type, Union
13
+
14
+ from langchain.tools import BaseTool
15
+ from langchain_core.callbacks.manager import (
16
+ AsyncCallbackManagerForToolRun,
17
+ CallbackManagerForToolRun,
18
+ )
19
+ from pydantic import BaseModel, Field
20
+
21
+ # Import functions from standalone tools
22
+ try:
23
+ from .create_directory import create_directory
24
+ from .create_file import create_file
25
+ from .file_search import file_search
26
+ from .grep_search import grep_search
27
+ from .list_dir import list_dir
28
+ from .read_file import read_file
29
+ from .replace_string_in_file import replace_string_in_file
30
+ from .semantic_search import semantic_search
31
+ from .test_failure_analysis import analyze_test_failures
32
+ except ImportError:
33
+ # Fallback for when running as standalone
34
+ from standalone_tools.create_directory import create_directory
35
+ from standalone_tools.create_file import create_file
36
+ from standalone_tools.file_search import file_search
37
+ from standalone_tools.grep_search import grep_search
38
+ from standalone_tools.list_dir import list_dir
39
+ from standalone_tools.read_file import read_file
40
+ from standalone_tools.replace_string_in_file import replace_string_in_file
41
+ from standalone_tools.semantic_search import semantic_search
42
+ from standalone_tools.test_failure_analysis import analyze_test_failures
43
+
44
+
45
+ # Input Models for each tool
46
+ class CreateDirectoryInput(BaseModel):
47
+ dir_path: str = Field(description="The absolute path to the directory to create")
48
+ mode: Optional[int] = Field(
49
+ default=0o755, description="Permission mode for the directory"
50
+ )
51
+ parents: Optional[bool] = Field(
52
+ default=True, description="Create parent directories if they don't exist"
53
+ )
54
+ exist_ok: Optional[bool] = Field(
55
+ default=True, description="Don't raise error if directory already exists"
56
+ )
57
+
58
+
59
+ class CreateFileInput(BaseModel):
60
+ file_path: str = Field(description="The absolute path to the file to create")
61
+ content: str = Field(description="The content to write to the file")
62
+ encoding: Optional[str] = Field(
63
+ default="utf-8", description="The encoding to use for the file"
64
+ )
65
+ mode: Optional[str] = Field(default="w", description="The file creation mode")
66
+ auto_create_dirs: Optional[bool] = Field(
67
+ default=True, description="Create parent directories if they don't exist"
68
+ )
69
+ overwrite: Optional[bool] = Field(
70
+ default=False, description="Overwrite the file if it already exists"
71
+ )
72
+
73
+
74
+ class FetchWebpageInput(BaseModel):
75
+ urls: List[str] = Field(description="List of URLs to fetch content from")
76
+ query: str = Field(description="The query to search for in the web page's content")
77
+ timeout: Optional[int] = Field(default=30, description="Request timeout in seconds")
78
+ max_content_length: Optional[int] = Field(
79
+ default=50000, description="Maximum content length to process"
80
+ )
81
+ user_agent: Optional[str] = Field(
82
+ default=None, description="Custom user agent string"
83
+ )
84
+
85
+
86
+ class FileSearchInput(BaseModel):
87
+ query: str = Field(description="Glob pattern to search for files")
88
+ max_results: Optional[int] = Field(
89
+ default=None, description="Maximum number of results to return"
90
+ )
91
+ include_hidden: Optional[bool] = Field(
92
+ default=False, description="Include hidden files in results"
93
+ )
94
+ sort_by: Optional[str] = Field(
95
+ default="name", description="Sort results by 'name', 'size', or 'modified'"
96
+ )
97
+ reverse_sort: Optional[bool] = Field(
98
+ default=False, description="Reverse the sort order"
99
+ )
100
+ show_details: Optional[bool] = Field(
101
+ default=False, description="Show file details like size and modification time"
102
+ )
103
+ base_path: Optional[str] = Field(
104
+ default=None, description="Base directory to search from"
105
+ )
106
+
107
+
108
+ class GrepSearchInput(BaseModel):
109
+ query: str = Field(description="The pattern to search for in files")
110
+ is_regexp: bool = Field(description="Whether the pattern is a regex")
111
+ include_pattern: Optional[str] = Field(
112
+ default=None, description="Search files matching this glob pattern"
113
+ )
114
+ max_results: Optional[int] = Field(
115
+ default=None, description="Maximum number of results to return"
116
+ )
117
+ context_lines: Optional[int] = Field(
118
+ default=0, description="Number of context lines to show around matches"
119
+ )
120
+ ignore_case: Optional[bool] = Field(
121
+ default=False, description="Perform case-insensitive search"
122
+ )
123
+ whole_word: Optional[bool] = Field(
124
+ default=False, description="Match whole words only"
125
+ )
126
+ invert_match: Optional[bool] = Field(
127
+ default=False, description="Show lines that don't match the pattern"
128
+ )
129
+ base_path: Optional[str] = Field(
130
+ default=None, description="Base directory to search from"
131
+ )
132
+
133
+
134
+ class ListDirInput(BaseModel):
135
+ path: str = Field(description="The absolute path to the directory to list")
136
+ show_hidden: Optional[bool] = Field(
137
+ default=False, description="Show hidden files and directories"
138
+ )
139
+ show_details: Optional[bool] = Field(
140
+ default=False, description="Show detailed information like size and permissions"
141
+ )
142
+ sort_by: Optional[str] = Field(
143
+ default="name", description="Sort by 'name', 'size', 'modified', or 'type'"
144
+ )
145
+ reverse_sort: Optional[bool] = Field(
146
+ default=False, description="Reverse the sort order"
147
+ )
148
+ recursive: Optional[bool] = Field(
149
+ default=False, description="List contents recursively"
150
+ )
151
+ max_depth: Optional[int] = Field(
152
+ default=3, description="Maximum depth for recursive listing"
153
+ )
154
+ file_filter: Optional[str] = Field(
155
+ default=None, description="Filter files by extension"
156
+ )
157
+
158
+
159
+ class ReadFileInput(BaseModel):
160
+ file_path: str = Field(description="The absolute path of the file to read")
161
+ start_line: int = Field(
162
+ description="The line number to start reading from (1-based)"
163
+ )
164
+ end_line: int = Field(
165
+ description="The inclusive line number to end reading at (1-based, -1 for end)"
166
+ )
167
+ encoding: Optional[str] = Field(
168
+ default=None, description="The encoding to use for reading the file"
169
+ )
170
+ show_line_numbers: Optional[bool] = Field(
171
+ default=True, description="Show line numbers in output"
172
+ )
173
+ highlight_syntax: Optional[bool] = Field(
174
+ default=False, description="Attempt to highlight syntax"
175
+ )
176
+ max_line_length: Optional[int] = Field(
177
+ default=1000, description="Maximum line length before truncation"
178
+ )
179
+
180
+
181
+ class ReplaceStringInput(BaseModel):
182
+ file_path: str = Field(description="The absolute path to the file to edit")
183
+ old_string: str = Field(description="The string to be replaced")
184
+ new_string: str = Field(description="The replacement string")
185
+ create_backup: Optional[bool] = Field(
186
+ default=True, description="Create a backup before editing"
187
+ )
188
+ dry_run: Optional[bool] = Field(
189
+ default=False, description="Show what would be changed without making changes"
190
+ )
191
+ whole_word: Optional[bool] = Field(
192
+ default=False, description="Only replace whole words"
193
+ )
194
+ ignore_case: Optional[bool] = Field(
195
+ default=False, description="Perform case-insensitive matching"
196
+ )
197
+ max_replacements: Optional[int] = Field(
198
+ default=None, description="Maximum number of replacements to make"
199
+ )
200
+
201
+
202
+ class SemanticSearchInput(BaseModel):
203
+ query: str = Field(description="The search query in natural language")
204
+ max_results: Optional[int] = Field(
205
+ default=10, description="Maximum number of results to return"
206
+ )
207
+ file_types: Optional[List[str]] = Field(
208
+ default=None, description="File types to search in"
209
+ )
210
+ similarity_threshold: Optional[float] = Field(
211
+ default=0.1, description="Minimum similarity score to include"
212
+ )
213
+ context_size: Optional[int] = Field(
214
+ default=3, description="Number of lines of context around matches"
215
+ )
216
+ search_path: Optional[str] = Field(
217
+ default=None, description="Directory to search in"
218
+ )
219
+
220
+
221
+ class WebSearchInput(BaseModel):
222
+ query: str = Field(description="The search query")
223
+ max_results: Optional[int] = Field(
224
+ default=10, description="Maximum number of results to return"
225
+ )
226
+ search_engine: Optional[str] = Field(
227
+ default="duckduckgo", description="Search engine to use"
228
+ )
229
+ include_content: Optional[bool] = Field(
230
+ default=False, description="Include page content in results"
231
+ )
232
+ content_length: Optional[int] = Field(
233
+ default=1000, description="Maximum content length to extract"
234
+ )
235
+ filter_domain: Optional[str] = Field(
236
+ default=None, description="Only include results from this domain"
237
+ )
238
+
239
+
240
+ class TestFailureAnalysisInput(BaseModel):
241
+ test_output: Optional[str] = Field(
242
+ default=None, description="Path to test output file or direct test output"
243
+ )
244
+ test_framework: Optional[str] = Field(
245
+ default=None, description="Test framework used"
246
+ )
247
+ verbose: Optional[bool] = Field(default=False, description="Show detailed analysis")
248
+ suggest_fixes: Optional[bool] = Field(
249
+ default=True, description="Suggest potential fixes"
250
+ )
251
+ group_by_type: Optional[bool] = Field(
252
+ default=True, description="Group failures by error type"
253
+ )
254
+
255
+
256
+ # Tool Classes
257
+ class CreateDirectoryTool(BaseTool):
258
+ """📁 Create directories with enhanced features and permission handling."""
259
+
260
+ name: str = "create_directory"
261
+ description: str = """Create a directory structure with enhanced features.
262
+
263
+ This tool creates directories recursively (like mkdir -p) and provides
264
+ additional features like permission handling and validation.
265
+
266
+ Best for:
267
+ - Creating project directory structures
268
+ - Setting up development environments
269
+ - Organizing file systems
270
+ - Batch directory creation"""
271
+
272
+ args_schema: Type[BaseModel] = CreateDirectoryInput
273
+
274
+ def _run(
275
+ self,
276
+ dir_path: str,
277
+ mode: Optional[int] = 0o755,
278
+ parents: Optional[bool] = True,
279
+ exist_ok: Optional[bool] = True,
280
+ run_manager: Optional[CallbackManagerForToolRun] = None,
281
+ ) -> str:
282
+ try:
283
+ success = create_directory(
284
+ dir_path=dir_path,
285
+ mode=mode or 0o755,
286
+ parents=parents if parents is not None else True,
287
+ exist_ok=exist_ok if exist_ok is not None else True,
288
+ )
289
+
290
+ if success:
291
+ return f"Directory created successfully: {dir_path}"
292
+ else:
293
+ return f"Failed to create directory: {dir_path}"
294
+
295
+ except Exception as e:
296
+ return f"Error creating directory: {str(e)}"
297
+
298
+
299
+ class CreateFileTool(BaseTool):
300
+ """📄 Create files with specified content and automatic directory creation."""
301
+
302
+ name: str = "create_file"
303
+ description: str = """Create a new file with the given content and automatically creates
304
+ parent directories if they don't exist. It includes validation and error handling.
305
+
306
+ Best for:
307
+ - Creating new source files
308
+ - Generating configuration files
309
+ - Setting up project templates
310
+ - Batch file creation"""
311
+
312
+ args_schema: Type[BaseModel] = CreateFileInput
313
+
314
+ def _run(
315
+ self,
316
+ file_path: str,
317
+ content: str,
318
+ encoding: Optional[str] = "utf-8",
319
+ mode: Optional[str] = "w",
320
+ auto_create_dirs: Optional[bool] = True,
321
+ overwrite: Optional[bool] = False,
322
+ run_manager: Optional[CallbackManagerForToolRun] = None,
323
+ ) -> str:
324
+ try:
325
+ success = create_file(
326
+ file_path=file_path,
327
+ content=content,
328
+ encoding=encoding or "utf-8",
329
+ mode=mode or "w",
330
+ auto_create_dirs=(
331
+ auto_create_dirs if auto_create_dirs is not None else True
332
+ ),
333
+ overwrite=overwrite if overwrite is not None else False,
334
+ )
335
+
336
+ if success:
337
+ return f"File created successfully: {file_path}"
338
+ else:
339
+ return f"Failed to create file: {file_path}"
340
+
341
+ except Exception as e:
342
+ return f"Error creating file: {str(e)}"
343
+
344
+
345
+ class FileSearchTool(BaseTool):
346
+ """🔍 Search for files using glob patterns with advanced features."""
347
+
348
+ name: str = "file_search"
349
+ description: str = """Search for files using glob patterns and provides additional
350
+ filtering and sorting options. It returns file paths matching the pattern.
351
+
352
+ Best for:
353
+ - Finding files by pattern
354
+ - Project file discovery
355
+ - Build system file location
356
+ - Code organization analysis"""
357
+
358
+ args_schema: Type[BaseModel] = FileSearchInput
359
+
360
+ def _run(
361
+ self,
362
+ query: str,
363
+ max_results: Optional[int] = None,
364
+ include_hidden: Optional[bool] = False,
365
+ sort_by: Optional[str] = "name",
366
+ reverse_sort: Optional[bool] = False,
367
+ show_details: Optional[bool] = False,
368
+ base_path: Optional[str] = None,
369
+ run_manager: Optional[CallbackManagerForToolRun] = None,
370
+ ) -> str:
371
+ try:
372
+ results = file_search(
373
+ query=query,
374
+ max_results=max_results,
375
+ include_hidden=include_hidden or False,
376
+ sort_by=sort_by or "name",
377
+ reverse_sort=reverse_sort or False,
378
+ show_details=show_details or False,
379
+ base_path=base_path,
380
+ )
381
+
382
+ if not results:
383
+ return f"No files found matching pattern: {query}"
384
+
385
+ # Format results for display
386
+ output = [f"Found {len(results)} files matching pattern: {query}"]
387
+
388
+ if show_details:
389
+ output.append("-" * 80)
390
+ for result in results:
391
+ file_type = "DIR" if result["is_dir"] else "FILE"
392
+ size_str = f"{result['size']} bytes" if result["is_file"] else "-"
393
+ output.append(f"{result['name']:<40} {file_type:<5} {size_str}")
394
+ else:
395
+ for result in results:
396
+ suffix = "/" if result["is_dir"] else ""
397
+ output.append(f"{result['path']}{suffix}")
398
+
399
+ return "\n".join(output)
400
+
401
+ except Exception as e:
402
+ return f"Error searching files: {str(e)}"
403
+
404
+
405
+ class GrepSearchTool(BaseTool):
406
+ """🔎 Fast text search in files using grep-like functionality."""
407
+
408
+ name: str = "grep_search"
409
+ description: str = """Perform fast text search in files using exact strings or regex patterns.
410
+ It provides context lines, file filtering, and other advanced search options.
411
+
412
+ Best for:
413
+ - Code pattern searching
414
+ - Log file analysis
415
+ - Documentation searching
416
+ - Multi-file text analysis"""
417
+
418
+ args_schema: Type[BaseModel] = GrepSearchInput
419
+
420
+ def _run(
421
+ self,
422
+ query: str,
423
+ is_regexp: bool,
424
+ include_pattern: Optional[str] = None,
425
+ max_results: Optional[int] = None,
426
+ context_lines: Optional[int] = 0,
427
+ ignore_case: Optional[bool] = False,
428
+ whole_word: Optional[bool] = False,
429
+ invert_match: Optional[bool] = False,
430
+ base_path: Optional[str] = None,
431
+ run_manager: Optional[CallbackManagerForToolRun] = None,
432
+ ) -> str:
433
+ try:
434
+ results = grep_search(
435
+ query=query,
436
+ is_regexp=is_regexp,
437
+ include_pattern=include_pattern,
438
+ max_results=max_results,
439
+ context_lines=context_lines or 0,
440
+ ignore_case=ignore_case or False,
441
+ whole_word=whole_word or False,
442
+ invert_match=invert_match or False,
443
+ base_path=base_path,
444
+ )
445
+
446
+ if not results:
447
+ return f"No matches found for pattern: {query}"
448
+
449
+ # Format results for display
450
+ total_matches = sum(r["total_matches"] for r in results)
451
+ output = [f"Found {total_matches} matches in {len(results)} files"]
452
+ output.append("=" * 60)
453
+
454
+ for result in results:
455
+ if "error" in result:
456
+ output.append(f"Error in {result['file']}: {result['error']}")
457
+ continue
458
+
459
+ output.append(f"\nFile: {result['file']}")
460
+ output.append(f"Matches: {result['total_matches']}")
461
+ output.append("-" * 40)
462
+
463
+ for match in result["matches"][:5]: # Show first 5 matches
464
+ if context_lines and match.get("context"):
465
+ for ctx in match["context"]:
466
+ prefix = ">" if ctx["is_match"] else " "
467
+ output.append(
468
+ f"{prefix}{ctx['line_number']:4d}: {ctx['content']}"
469
+ )
470
+ else:
471
+ output.append(f"{match['line_number']:4d}: {match['content']}")
472
+
473
+ return "\n".join(output)
474
+
475
+ except Exception as e:
476
+ return f"Error searching with grep: {str(e)}"
477
+
478
+
479
+ class ListDirTool(BaseTool):
480
+ """📂 List directory contents with enhanced features."""
481
+
482
+ name: str = "list_dir"
483
+ description: str = """List the contents of a directory with various display options,
484
+ sorting capabilities, and filtering features.
485
+
486
+ Best for:
487
+ - Directory exploration
488
+ - Project structure analysis
489
+ - File system navigation
490
+ - Content organization"""
491
+
492
+ args_schema: Type[BaseModel] = ListDirInput
493
+
494
+ def _run(
495
+ self,
496
+ path: str,
497
+ show_hidden: Optional[bool] = False,
498
+ show_details: Optional[bool] = False,
499
+ sort_by: Optional[str] = "name",
500
+ reverse_sort: Optional[bool] = False,
501
+ recursive: Optional[bool] = False,
502
+ max_depth: Optional[int] = 3,
503
+ file_filter: Optional[str] = None,
504
+ run_manager: Optional[CallbackManagerForToolRun] = None,
505
+ ) -> str:
506
+ try:
507
+ results = list_dir(
508
+ path=path,
509
+ show_hidden=show_hidden or False,
510
+ show_details=show_details or False,
511
+ sort_by=sort_by or "name",
512
+ reverse_sort=reverse_sort or False,
513
+ recursive=recursive or False,
514
+ max_depth=max_depth or 3,
515
+ file_filter=file_filter,
516
+ )
517
+
518
+ if not results:
519
+ return f"No contents found in directory: {path}"
520
+
521
+ # Format results for display
522
+ output = [f"Contents of {path}:"]
523
+
524
+ if show_details:
525
+ output.append(f"{'Name':<40} {'Type':<5} {'Size':<10}")
526
+ output.append("-" * 60)
527
+
528
+ for result in results:
529
+ file_type = "DIR" if result["is_dir"] else "FILE"
530
+ if result["is_symlink"]:
531
+ file_type = "LINK"
532
+
533
+ size_str = f"{result['size']} bytes" if result["is_file"] else "-"
534
+ indent = " " * result.get("depth", 0)
535
+
536
+ output.append(
537
+ f"{indent}{result['name']:<40} {file_type:<5} {size_str}"
538
+ )
539
+ else:
540
+ for result in results:
541
+ suffix = "/" if result["is_dir"] else ""
542
+ indent = " " * result.get("depth", 0)
543
+ output.append(f"{indent}{result['name']}{suffix}")
544
+
545
+ return "\n".join(output)
546
+
547
+ except Exception as e:
548
+ return f"Error listing directory: {str(e)}"
549
+
550
+
551
+ class ReadFileTool(BaseTool):
552
+ """📖 Read file contents with line range support and encoding detection."""
553
+
554
+ name: str = "read_file"
555
+ description: str = """Read the contents of a file with support for line ranges,
556
+ encoding detection, and various output formats.
557
+
558
+ Best for:
559
+ - Code review and analysis
560
+ - Configuration file inspection
561
+ - Log file examination
562
+ - Content verification"""
563
+
564
+ args_schema: Type[BaseModel] = ReadFileInput
565
+
566
+ def _run(
567
+ self,
568
+ file_path: str,
569
+ start_line: int,
570
+ end_line: int,
571
+ encoding: Optional[str] = None,
572
+ show_line_numbers: Optional[bool] = True,
573
+ highlight_syntax: Optional[bool] = False,
574
+ max_line_length: Optional[int] = 1000,
575
+ run_manager: Optional[CallbackManagerForToolRun] = None,
576
+ ) -> str:
577
+ try:
578
+ result = read_file(
579
+ file_path=file_path,
580
+ start_line=start_line,
581
+ end_line=end_line,
582
+ encoding=encoding,
583
+ show_line_numbers=(
584
+ show_line_numbers if show_line_numbers is not None else True
585
+ ),
586
+ highlight_syntax=highlight_syntax or False,
587
+ max_line_length=max_line_length or 1000,
588
+ )
589
+
590
+ if not result["success"]:
591
+ return f"Error: {result['error']}"
592
+
593
+ # Format output
594
+ output = []
595
+ output.append(f"File: {result['file_path']}")
596
+ output.append(
597
+ f"Lines: {result['start_line']}-{result['end_line']} (of {result['total_lines']})"
598
+ )
599
+ output.append(f"Encoding: {result['encoding']}")
600
+ if result["file_language"]:
601
+ output.append(f"Language: {result['file_language']}")
602
+ output.append("-" * 60)
603
+
604
+ # Content
605
+ for line_info in result["content"]:
606
+ if result["show_line_numbers"]:
607
+ line_num = f"{line_info['line_number']:4d}"
608
+ content = line_info["content"]
609
+
610
+ # Show truncation indicator
611
+ if line_info["original_length"] > len(content):
612
+ truncated = " [truncated]"
613
+ else:
614
+ truncated = ""
615
+
616
+ output.append(f"{line_num}: {content}{truncated}")
617
+ else:
618
+ output.append(line_info["content"])
619
+
620
+ return "\n".join(output)
621
+
622
+ except Exception as e:
623
+ return f"Error reading file: {str(e)}"
624
+
625
+
626
+ class ReplaceStringTool(BaseTool):
627
+ """🔄 Replace strings in files with safety checks and backup options."""
628
+
629
+ name: str = "replace_string"
630
+ description: str = """Replace strings in files with validation, backup options,
631
+ and various safety features to prevent accidental data loss.
632
+
633
+ Best for:
634
+ - Code refactoring
635
+ - Configuration updates
636
+ - Batch text replacements
637
+ - Safe file modifications"""
638
+
639
+ args_schema: Type[BaseModel] = ReplaceStringInput
640
+
641
+ def _run(
642
+ self,
643
+ file_path: str,
644
+ old_string: str,
645
+ new_string: str,
646
+ create_backup: Optional[bool] = True,
647
+ dry_run: Optional[bool] = False,
648
+ whole_word: Optional[bool] = False,
649
+ ignore_case: Optional[bool] = False,
650
+ max_replacements: Optional[int] = None,
651
+ run_manager: Optional[CallbackManagerForToolRun] = None,
652
+ ) -> str:
653
+ try:
654
+ result = replace_string_in_file(
655
+ file_path=file_path,
656
+ old_string=old_string,
657
+ new_string=new_string,
658
+ create_backup=create_backup if create_backup is not None else True,
659
+ dry_run=dry_run or False,
660
+ whole_word=whole_word or False,
661
+ ignore_case=ignore_case or False,
662
+ max_replacements=max_replacements,
663
+ )
664
+
665
+ if not result["success"]:
666
+ return f"Error: {result['error']}"
667
+
668
+ output = [result["message"]]
669
+
670
+ if result.get("dry_run") and "changes_preview" in result:
671
+ output.append("\nChanges preview:")
672
+ for change in result["changes_preview"]:
673
+ output.append(
674
+ f"\nMatch {change['match_number']} at line {change['line_number']}:"
675
+ )
676
+ output.append(f"Before: {change['before']}")
677
+ output.append(f"After: {change['after']}")
678
+
679
+ if result.get("backup_created"):
680
+ output.append(f"Backup created: {result['backup_path']}")
681
+
682
+ return "\n".join(output)
683
+
684
+ except Exception as e:
685
+ return f"Error replacing string: {str(e)}"
686
+
687
+
688
+ class SemanticSearchTool(BaseTool):
689
+ """🧠 Semantic search for relevant code using text similarity."""
690
+
691
+ name: str = "semantic_search"
692
+ description: str = """Perform semantic search across code and documentation files using
693
+ text similarity algorithms to find relevant content based on natural language queries.
694
+
695
+ Best for:
696
+ - Finding relevant code snippets
697
+ - Documentation discovery
698
+ - Code understanding and navigation
699
+ - Knowledge base search"""
700
+
701
+ args_schema: Type[BaseModel] = SemanticSearchInput
702
+
703
+ def _run(
704
+ self,
705
+ query: str,
706
+ max_results: Optional[int] = 10,
707
+ file_types: Optional[List[str]] = None,
708
+ similarity_threshold: Optional[float] = 0.1,
709
+ context_size: Optional[int] = 3,
710
+ search_path: Optional[str] = None,
711
+ run_manager: Optional[CallbackManagerForToolRun] = None,
712
+ ) -> str:
713
+ try:
714
+ results = semantic_search(
715
+ query=query,
716
+ max_results=max_results or 10,
717
+ file_types=file_types,
718
+ similarity_threshold=similarity_threshold or 0.1,
719
+ context_size=context_size or 3,
720
+ search_path=search_path,
721
+ )
722
+
723
+ if not results:
724
+ return f"No results found for query: {query}"
725
+
726
+ # Format results for display
727
+ output = [f"Semantic search results for: {query}"]
728
+ output.append(f"Found {len(results)} results")
729
+ output.append("=" * 80)
730
+
731
+ for i, result in enumerate(results, 1):
732
+ output.append(
733
+ f"\nResult {i}: {result['file_path']}:{result['line_number']}"
734
+ )
735
+ output.append(f"Similarity Score: {result['similarity_score']:.3f}")
736
+ output.append(f"File Type: {result['file_type']}")
737
+
738
+ if result.get("match_type") == "function_definition":
739
+ output.append(f"Function: {result.get('function_name', 'unknown')}")
740
+
741
+ output.append("Context:")
742
+ for context_line in result["context"]:
743
+ marker = ">>>" if context_line["is_match"] else " "
744
+ output.append(
745
+ f"{marker} {context_line['line_number']:4d}: {context_line['content']}"
746
+ )
747
+
748
+ output.append("-" * 80)
749
+
750
+ return "\n".join(output)
751
+
752
+ except Exception as e:
753
+ return f"Error performing semantic search: {str(e)}"
754
+
755
+
756
+ class TestFailureAnalysisTool(BaseTool):
757
+ """🔧 Test failure analysis tool for examining and debugging test failures."""
758
+
759
+ name: str = "test_failure_analysis"
760
+ description: str = """Analyze test failures, provide detailed error analysis, and suggest
761
+ potential fixes based on common failure patterns.
762
+
763
+ Best for:
764
+ - Debugging test failures
765
+ - Test maintenance
766
+ - Error pattern recognition
767
+ - Development workflow optimization"""
768
+
769
+ args_schema: Type[BaseModel] = TestFailureAnalysisInput
770
+
771
+ def _run(
772
+ self,
773
+ test_output: Optional[str] = None,
774
+ test_framework: Optional[str] = None,
775
+ verbose: Optional[bool] = False,
776
+ suggest_fixes: Optional[bool] = True,
777
+ group_by_type: Optional[bool] = True,
778
+ run_manager: Optional[CallbackManagerForToolRun] = None,
779
+ ) -> str:
780
+ try:
781
+ analysis = analyze_test_failures(
782
+ test_output=test_output,
783
+ test_framework=test_framework,
784
+ verbose=verbose or False,
785
+ suggest_fixes=suggest_fixes if suggest_fixes is not None else True,
786
+ group_by_type=group_by_type if group_by_type is not None else True,
787
+ )
788
+
789
+ # Format analysis results
790
+ output = []
791
+
792
+ # Header
793
+ output.append("TEST FAILURE ANALYSIS")
794
+ output.append("=" * 50)
795
+
796
+ # Summary
797
+ if "summary" in analysis:
798
+ summary = analysis["summary"]
799
+ output.append(f"Total failures: {summary['total_failures']}")
800
+ output.append(f"Unique tests: {summary['unique_tests']}")
801
+ output.append(f"Unique files: {summary['unique_files']}")
802
+ output.append(f"Most common error: {summary['most_common_error']}")
803
+ output.append("")
804
+
805
+ # Error groups
806
+ if "error_groups" in analysis:
807
+ output.append("ERROR GROUPS:")
808
+ output.append("-" * 30)
809
+ for error_type, group_failures in analysis["error_groups"].items():
810
+ output.append(f"{error_type}: {len(group_failures)} failures")
811
+ for failure in group_failures[:3]: # Show first 3
812
+ output.append(f" - {failure['test_name']}")
813
+ if len(group_failures) > 3:
814
+ output.append(f" ... and {len(group_failures) - 3} more")
815
+ output.append("")
816
+
817
+ # Suggestions
818
+ if analysis.get("suggestions"):
819
+ output.append("SUGGESTIONS:")
820
+ output.append("-" * 30)
821
+ for suggestion in analysis["suggestions"]:
822
+ output.append(
823
+ f"[{suggestion['priority'].upper()}] {suggestion['message']}"
824
+ )
825
+ for action in suggestion["actions"]:
826
+ output.append(f" • {action}")
827
+ output.append("")
828
+
829
+ # Detailed failures
830
+ if analysis.get("failures"):
831
+ output.append("DETAILED FAILURES:")
832
+ output.append("-" * 30)
833
+ for i, failure in enumerate(
834
+ analysis["failures"][:5], 1
835
+ ): # Show first 5
836
+ output.append(f"{i}. {failure['test_name']}")
837
+ if failure.get("file_path"):
838
+ output.append(
839
+ f" File: {failure['file_path']}:{failure.get('line_number', 'N/A')}"
840
+ )
841
+ output.append(f" Error: {failure['error_type']}")
842
+ output.append(f" Message: {failure['error_message'][:100]}...")
843
+ output.append("")
844
+
845
+ if len(analysis["failures"]) > 5:
846
+ output.append(
847
+ f"... and {len(analysis['failures']) - 5} more failures"
848
+ )
849
+
850
+ return "\n".join(output)
851
+
852
+ except Exception as e:
853
+ return f"Error analyzing test failures: {str(e)}"
854
+
855
+
856
+ def get_standalone_toolkit() -> List[BaseTool]:
857
+ """Get all standalone tools with proper initialization.
858
+
859
+ Returns:
860
+ List of initialized standalone tools ready for LangChain integration.
861
+ """
862
+ return [
863
+ CreateDirectoryTool(),
864
+ CreateFileTool(),
865
+ FileSearchTool(),
866
+ GrepSearchTool(),
867
+ ListDirTool(),
868
+ ReadFileTool(),
869
+ ReplaceStringTool(),
870
+ SemanticSearchTool(),
871
+ TestFailureAnalysisTool(),
872
+ ]
873
+
874
+
875
+ def get_file_tools() -> List[BaseTool]:
876
+ """Get only file-related tools."""
877
+ return [
878
+ CreateDirectoryTool(),
879
+ CreateFileTool(),
880
+ ListDirTool(),
881
+ ReadFileTool(),
882
+ ReplaceStringTool(),
883
+ ]
884
+
885
+
886
+ def get_search_tools() -> List[BaseTool]:
887
+ """Get only search-related tools."""
888
+ return [
889
+ FileSearchTool(),
890
+ GrepSearchTool(),
891
+ SemanticSearchTool(),
892
+ ]
893
+
894
+
895
+ def get_analysis_tools() -> List[BaseTool]:
896
+ """Get only analysis-related tools."""
897
+ return [
898
+ TestFailureAnalysisTool(),
899
+ SemanticSearchTool(),
900
+ ]
901
+
902
+
903
+ # Tool selection guide for LLMs
904
+ STANDALONE_TOOL_SELECTION_GUIDE = """
905
+ 🛠️ STANDALONE TOOLKIT SELECTION GUIDE FOR AI AGENTS:
906
+
907
+ 📁 FILE OPERATIONS:
908
+ 1. **create_directory** - Create directory structures with permissions
909
+ 2. **create_file** - Create files with content and auto-directory creation
910
+ 3. **list_dir** - List directory contents with filtering and sorting
911
+ 4. **read_file** - Read file contents with line ranges and encoding detection
912
+ 5. **replace_string** - Replace strings in files with backup and validation
913
+
914
+ 🔍 SEARCH OPERATIONS:
915
+ 6. **file_search** - Find files using glob patterns
916
+ 7. **grep_search** - Fast text search in files with regex support
917
+ 8. **semantic_search** - Semantic code search using text similarity
918
+
919
+ 🌐 WEB OPERATIONS:
920
+ 9. **fetch_webpage** - Fetch and search web page content
921
+ 10. **web_search** - Search the web for information
922
+
923
+ 🔧 ANALYSIS OPERATIONS:
924
+ 11. **test_failure_analysis** - Analyze test failures and suggest fixes
925
+
926
+ ⚠️ WORKFLOW RECOMMENDATIONS:
927
+ 1. Use create_directory/create_file for project setup
928
+ 2. Use file_search to locate files by pattern
929
+ 3. Use grep_search for content-based searches
930
+ 4. Use semantic_search for natural language code queries
931
+ 5. Use read_file to examine specific file contents
932
+ 6. Use replace_string for safe file modifications
933
+ 7. Use web_search for external information
934
+ 8. Use test_failure_analysis for debugging tests
935
+
936
+ 💡 BEST PRACTICES:
937
+ - Always use dry_run=True for replace_string before actual changes
938
+ - Use appropriate file_types filters for semantic_search
939
+ - Combine tools for comprehensive analysis workflows
940
+ - Use context_lines in grep_search for better understanding
941
+ - Enable show_details in file operations for thorough analysis
942
+
943
+ 🔧 EXAMPLE USAGE PATTERNS:
944
+ # Find Python files and search for functions
945
+ file_search(query="*.py", show_details=True)
946
+ grep_search(query="def ", is_regexp=True, include_pattern="*.py")
947
+
948
+ # Safe file modification
949
+ replace_string(file_path="config.py", old_string="DEBUG = False",
950
+ new_string="DEBUG = True", dry_run=True)
951
+
952
+ # Semantic code search
953
+ semantic_search(query="authentication function", file_types=[".py"])
954
+
955
+ # Web research
956
+ web_search(query="Python async programming best practices")
957
+ """
958
+
959
+ # Export all tools and utilities
960
+ __all__ = [
961
+ # Tool classes
962
+ "CreateDirectoryTool",
963
+ "CreateFileTool",
964
+ "FileSearchTool",
965
+ "GrepSearchTool",
966
+ "ListDirTool",
967
+ "ReadFileTool",
968
+ "ReplaceStringTool",
969
+ "SemanticSearchTool",
970
+ "TestFailureAnalysisTool",
971
+ # Tool getters
972
+ "get_standalone_toolkit",
973
+ "get_file_tools",
974
+ "get_search_tools",
975
+ "get_analysis_tools",
976
+ "get_web_tools",
977
+ # Guide
978
+ "STANDALONE_TOOL_SELECTION_GUIDE",
979
+ ]