auto-coder 0.1.374__py3-none-any.whl → 0.1.376__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.

Potentially problematic release.


This version of auto-coder might be problematic. Click here for more details.

Files changed (61) hide show
  1. {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/METADATA +2 -2
  2. {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/RECORD +27 -57
  3. autocoder/agent/base_agentic/base_agent.py +202 -52
  4. autocoder/agent/base_agentic/default_tools.py +38 -6
  5. autocoder/agent/base_agentic/tools/list_files_tool_resolver.py +83 -43
  6. autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +88 -25
  7. autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +171 -62
  8. autocoder/agent/base_agentic/tools/search_files_tool_resolver.py +101 -56
  9. autocoder/agent/base_agentic/tools/talk_to_group_tool_resolver.py +5 -0
  10. autocoder/agent/base_agentic/tools/talk_to_tool_resolver.py +5 -0
  11. autocoder/agent/base_agentic/tools/write_to_file_tool_resolver.py +145 -32
  12. autocoder/auto_coder_rag.py +80 -11
  13. autocoder/models.py +2 -2
  14. autocoder/rag/agentic_rag.py +217 -0
  15. autocoder/rag/cache/local_duckdb_storage_cache.py +63 -33
  16. autocoder/rag/conversation_to_queries.py +37 -5
  17. autocoder/rag/long_context_rag.py +161 -41
  18. autocoder/rag/tools/__init__.py +10 -0
  19. autocoder/rag/tools/recall_tool.py +163 -0
  20. autocoder/rag/tools/search_tool.py +126 -0
  21. autocoder/rag/types.py +36 -0
  22. autocoder/utils/_markitdown.py +59 -13
  23. autocoder/version.py +1 -1
  24. autocoder/agent/agentic_edit.py +0 -833
  25. autocoder/agent/agentic_edit_tools/__init__.py +0 -28
  26. autocoder/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py +0 -32
  27. autocoder/agent/agentic_edit_tools/attempt_completion_tool_resolver.py +0 -29
  28. autocoder/agent/agentic_edit_tools/base_tool_resolver.py +0 -29
  29. autocoder/agent/agentic_edit_tools/execute_command_tool_resolver.py +0 -84
  30. autocoder/agent/agentic_edit_tools/list_code_definition_names_tool_resolver.py +0 -75
  31. autocoder/agent/agentic_edit_tools/list_files_tool_resolver.py +0 -62
  32. autocoder/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py +0 -30
  33. autocoder/agent/agentic_edit_tools/read_file_tool_resolver.py +0 -36
  34. autocoder/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +0 -95
  35. autocoder/agent/agentic_edit_tools/search_files_tool_resolver.py +0 -70
  36. autocoder/agent/agentic_edit_tools/use_mcp_tool_resolver.py +0 -55
  37. autocoder/agent/agentic_edit_tools/write_to_file_tool_resolver.py +0 -98
  38. autocoder/agent/agentic_edit_types.py +0 -124
  39. autocoder/auto_coder_lang.py +0 -60
  40. autocoder/auto_coder_rag_client_mcp.py +0 -170
  41. autocoder/auto_coder_rag_mcp.py +0 -193
  42. autocoder/common/llm_rerank.py +0 -84
  43. autocoder/common/model_speed_test.py +0 -392
  44. autocoder/common/v2/agent/agentic_edit_conversation.py +0 -188
  45. autocoder/common/v2/agent/ignore_utils.py +0 -50
  46. autocoder/dispacher/actions/plugins/action_translate.py +0 -214
  47. autocoder/ignorefiles/__init__.py +0 -4
  48. autocoder/ignorefiles/ignore_file_utils.py +0 -63
  49. autocoder/ignorefiles/test_ignore_file_utils.py +0 -91
  50. autocoder/linters/code_linter.py +0 -588
  51. autocoder/rag/loaders/test_image_loader.py +0 -209
  52. autocoder/rag/raw_rag.py +0 -96
  53. autocoder/rag/simple_directory_reader.py +0 -646
  54. autocoder/rag/simple_rag.py +0 -404
  55. autocoder/regex_project/__init__.py +0 -162
  56. autocoder/utils/coder.py +0 -125
  57. autocoder/utils/tests.py +0 -37
  58. {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/LICENSE +0 -0
  59. {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/WHEEL +0 -0
  60. {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/entry_points.txt +0 -0
  61. {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/top_level.txt +0 -0
@@ -1,28 +0,0 @@
1
- # flake8: noqa
2
- from .base_tool_resolver import BaseToolResolver
3
- from .execute_command_tool_resolver import ExecuteCommandToolResolver
4
- from .read_file_tool_resolver import ReadFileToolResolver
5
- from .write_to_file_tool_resolver import WriteToFileToolResolver
6
- from .replace_in_file_tool_resolver import ReplaceInFileToolResolver
7
- from .search_files_tool_resolver import SearchFilesToolResolver
8
- from .list_files_tool_resolver import ListFilesToolResolver
9
- from .list_code_definition_names_tool_resolver import ListCodeDefinitionNamesToolResolver
10
- from .ask_followup_question_tool_resolver import AskFollowupQuestionToolResolver
11
- from .attempt_completion_tool_resolver import AttemptCompletionToolResolver
12
- from .plan_mode_respond_tool_resolver import PlanModeRespondToolResolver
13
- from .use_mcp_tool_resolver import UseMcpToolResolver
14
-
15
- __all__ = [
16
- "BaseToolResolver",
17
- "ExecuteCommandToolResolver",
18
- "ReadFileToolResolver",
19
- "WriteToFileToolResolver",
20
- "ReplaceInFileToolResolver",
21
- "SearchFilesToolResolver",
22
- "ListFilesToolResolver",
23
- "ListCodeDefinitionNamesToolResolver",
24
- "AskFollowupQuestionToolResolver",
25
- "AttemptCompletionToolResolver",
26
- "PlanModeRespondToolResolver",
27
- "UseMcpToolResolver",
28
- ]
@@ -1,32 +0,0 @@
1
- from typing import Dict, Any, Optional
2
- from .base_tool_resolver import BaseToolResolver
3
- from autocoder.agent.agentic_edit_types import AskFollowupQuestionTool, ToolResult # Import ToolResult from types
4
- from loguru import logger
5
-
6
- class AskFollowupQuestionToolResolver(BaseToolResolver):
7
- def __init__(self, agent: Optional[Any], tool: AskFollowupQuestionTool, args: Dict[str, Any]):
8
- super().__init__(agent, tool, args)
9
- self.tool: AskFollowupQuestionTool = tool # For type hinting
10
-
11
- def resolve(self) -> ToolResult:
12
- """
13
- Packages the question and options to be handled by the main loop/UI.
14
- This resolver doesn't directly ask the user but prepares the data for it.
15
- """
16
- question = self.tool.question
17
- options = self.tool.options
18
-
19
- logger.info(f"Resolving AskFollowupQuestionTool: Question='{question}', Options={options}")
20
-
21
- # The actual asking logic resides outside the resolver, typically in the agent's main loop
22
- # or UI interaction layer. The resolver's job is to validate and package the request.
23
- if not question:
24
- return ToolResult(success=False, message="Error: Question cannot be empty.")
25
-
26
- result_content = {
27
- "question": question,
28
- "options": options
29
- }
30
-
31
- # Indicate success in preparing the question data
32
- return ToolResult(success=True, message="Follow-up question prepared.", content=result_content)
@@ -1,29 +0,0 @@
1
- from typing import Dict, Any, Optional
2
- from .base_tool_resolver import BaseToolResolver
3
- from autocoder.agent.agentic_edit_types import AttemptCompletionTool, ToolResult # Import ToolResult from types
4
- from loguru import logger
5
- class AttemptCompletionToolResolver(BaseToolResolver):
6
- def __init__(self, agent: Optional[Any], tool: AttemptCompletionTool, args: Dict[str, Any]):
7
- super().__init__(agent, tool, args)
8
- self.tool: AttemptCompletionTool = tool # For type hinting
9
-
10
- def resolve(self) -> ToolResult:
11
- """
12
- Packages the completion result and optional command to signal task completion.
13
- """
14
- result_text = self.tool.result
15
- command = self.tool.command
16
-
17
- logger.info(f"Resolving AttemptCompletionTool: Result='{result_text[:100]}...', Command='{command}'")
18
-
19
- if not result_text:
20
- return ToolResult(success=False, message="Error: Completion result cannot be empty.")
21
-
22
- # The actual presentation of the result happens outside the resolver.
23
- result_content = {
24
- "result": result_text,
25
- "command": command
26
- }
27
-
28
- # Indicate success in preparing the completion data
29
- return ToolResult(success=True, message="Task completion attempted.", content=result_content)
@@ -1,29 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- from typing import Any, Dict, Optional
3
- from autocoder.agent.agentic_edit_types import BaseTool, ToolResult # Import ToolResult from types
4
- from autocoder.common import AutoCoderArgs
5
-
6
-
7
- class BaseToolResolver(ABC):
8
- def __init__(self, agent: Optional[Any], tool: BaseTool, args: AutoCoderArgs):
9
- """
10
- Initializes the resolver.
11
-
12
- Args:
13
- agent: The AutoCoder agent instance.
14
- tool: The Pydantic model instance representing the tool call.
15
- args: Additional arguments needed for execution (e.g., source_dir).
16
- """
17
- self.agent = agent
18
- self.tool = tool
19
- self.args = args
20
-
21
- @abstractmethod
22
- def resolve(self) -> ToolResult:
23
- """
24
- Executes the tool's logic.
25
-
26
- Returns:
27
- A ToolResult object indicating success or failure and a message.
28
- """
29
- pass
@@ -1,84 +0,0 @@
1
- import subprocess
2
- import os
3
- from typing import Dict, Any, Optional
4
- from .base_tool_resolver import BaseToolResolver
5
- from autocoder.agent.agentic_edit_types import ExecuteCommandTool, ToolResult # Import ToolResult from types
6
- from autocoder.common import shells
7
- from autocoder.common.printer import Printer
8
- from loguru import logger
9
-
10
-
11
- class ExecuteCommandToolResolver(BaseToolResolver):
12
- def __init__(self, agent: Optional[Any], tool: ExecuteCommandTool, args: Dict[str, Any]):
13
- super().__init__(agent, tool, args)
14
- self.tool: ExecuteCommandTool = tool # For type hinting
15
-
16
- def resolve(self) -> ToolResult:
17
- printer = Printer()
18
- command = self.tool.command
19
- requires_approval = self.tool.requires_approval
20
- source_dir = self.args.get("source_dir", ".")
21
-
22
- # Basic security check (can be expanded)
23
- if ";" in command or "&&" in command or "|" in command or "`" in command:
24
- # Allow && for cd chaining, but be cautious
25
- if not command.strip().startswith("cd ") and " && " in command:
26
- pass # Allow cd chaining like 'cd subdir && command'
27
- else:
28
- return ToolResult(success=False, message=f"Command '{command}' contains potentially unsafe characters.")
29
-
30
- # Approval mechanism (simplified)
31
- if requires_approval:
32
- # In a real scenario, this would involve user interaction
33
- printer.print_warning(f"Command requires approval: {command}")
34
- # For now, let's assume approval is granted in non-interactive mode or handled elsewhere
35
- pass
36
-
37
- printer.print_message(f"Executing command: {command} in {os.path.abspath(source_dir)}")
38
- try:
39
- # Determine shell based on OS
40
- shell = True
41
- executable = None
42
- if shells.is_windows():
43
- # Decide between cmd and powershell if needed, default is cmd
44
- pass # shell=True uses default shell
45
- else:
46
- # Use bash or zsh? Default is usually fine.
47
- pass # shell=True uses default shell
48
-
49
- # Execute the command
50
- process = subprocess.Popen(
51
- command,
52
- shell=True,
53
- stdout=subprocess.PIPE,
54
- stderr=subprocess.PIPE,
55
- cwd=source_dir,
56
- text=True,
57
- encoding=shells.get_terminal_encoding(),
58
- errors='replace' # Handle potential decoding errors
59
- )
60
-
61
- stdout, stderr = process.communicate()
62
- returncode = process.returncode
63
-
64
- logger.info(f"Command executed: {command}")
65
- logger.info(f"Return Code: {returncode}")
66
- if stdout:
67
- logger.info(f"stdout:\n{stdout}")
68
- if stderr:
69
- logger.info(f"stderr:\n{stderr}")
70
-
71
-
72
- if returncode == 0:
73
- return ToolResult(success=True, message="Command executed successfully.", content=stdout)
74
- else:
75
- error_message = f"Command failed with return code {returncode}.\nStderr:\n{stderr}\nStdout:\n{stdout}"
76
- return ToolResult(success=False, message=error_message, content={"stdout": stdout, "stderr": stderr, "returncode": returncode})
77
-
78
- except FileNotFoundError:
79
- return ToolResult(success=False, message=f"Error: The command '{command.split()[0]}' was not found. Please ensure it is installed and in the system's PATH.")
80
- except PermissionError:
81
- return ToolResult(success=False, message=f"Error: Permission denied when trying to execute '{command}'.")
82
- except Exception as e:
83
- logger.error(f"Error executing command '{command}': {str(e)}")
84
- return ToolResult(success=False, message=f"An unexpected error occurred while executing the command: {str(e)}")
@@ -1,75 +0,0 @@
1
- import os
2
- from typing import Dict, Any, Optional
3
- from .base_tool_resolver import BaseToolResolver
4
- from autocoder.agent.agentic_edit_types import ListCodeDefinitionNamesTool, ToolResult # Import ToolResult from types
5
- import json
6
- from autocoder.index.index import IndexManager
7
- from loguru import logger
8
- import traceback
9
- from autocoder.index.symbols_utils import (
10
- extract_symbols,
11
- SymbolType,
12
- symbols_info_to_str,
13
- )
14
-
15
-
16
- class ListCodeDefinitionNamesToolResolver(BaseToolResolver):
17
- def __init__(self, agent: Optional[Any], tool: ListCodeDefinitionNamesTool, args: Dict[str, Any]):
18
- super().__init__(agent, tool, args)
19
- self.tool: ListCodeDefinitionNamesTool = tool # For type hinting
20
-
21
- def _get_index(self):
22
- sources = self._get_sources()
23
- index_manager = IndexManager(
24
- llm=self.llm, sources=sources, args=self.args)
25
- return index_manager
26
-
27
- def resolve(self) -> ToolResult:
28
-
29
- index_items = self._get_index().read_index()
30
- index_data = {item.module_name: item for item in index_items}
31
-
32
- target_path_str = self.tool.path
33
- source_dir = self.args.get("source_dir", ".")
34
- absolute_target_path = os.path.abspath(os.path.join(source_dir, target_path_str))
35
-
36
- # Security check
37
- if not absolute_target_path.startswith(os.path.abspath(source_dir)):
38
- return ToolResult(success=False, message=f"Error: Access denied. Attempted to analyze code outside the project directory: {target_path_str}")
39
-
40
- if not os.path.exists(absolute_target_path):
41
- return ToolResult(success=False, message=f"Error: Path not found: {target_path_str}")
42
-
43
- try:
44
- # Use RepoParser or a similar mechanism to extract definitions
45
- # RepoParser might need adjustments or a specific method for this tool's purpose.
46
- # This is a placeholder implementation. A real implementation needs robust code parsing.
47
- logger.info(f"Analyzing definitions in: {absolute_target_path}")
48
- all_symbols = []
49
-
50
- if os.path.isfile(absolute_target_path):
51
- file_paths = [absolute_target_path]
52
- else:
53
- return ToolResult(success=False, message=f"Error: Path is neither a file nor a directory: {target_path_str}")
54
-
55
- for file_path in file_paths:
56
- try:
57
- item = index_data[file_path]
58
- symbols_str = item.symbols
59
- symbols = extract_symbols(symbols_str)
60
- if symbols:
61
- all_symbols.append({
62
- "path": file_path,
63
- "definitions": [{"name": s, "type": "function"} for s in symbols.functions] + [{"name": s, "type": "variable"} for s in symbols.variables] + [{"name": s, "type": "class"} for s in symbols.classes]
64
- })
65
- except Exception as e:
66
- logger.warning(f"Could not parse symbols from {file_path}: {e}")
67
-
68
-
69
- message = f"Successfully extracted {sum(len(s['definitions']) for s in all_symbols)} definitions from {len(all_symbols)} files in '{target_path_str}'."
70
- logger.info(message)
71
- return ToolResult(success=True, message=message, content=all_symbols)
72
-
73
- except Exception as e:
74
- logger.error(f"Error extracting code definitions from '{target_path_str}': {str(e)}")
75
- return ToolResult(success=False, message=f"An unexpected error occurred while extracting code definitions: {str(e)}")
@@ -1,62 +0,0 @@
1
- import os
2
- from typing import Dict, Any, Optional
3
- from .base_tool_resolver import BaseToolResolver
4
- from autocoder.agent.agentic_edit_types import ListFilesTool, ToolResult # Import ToolResult from types
5
- from loguru import logger
6
-
7
-
8
- class ListFilesToolResolver(BaseToolResolver):
9
- def __init__(self, agent: Optional[Any], tool: ListFilesTool, args: Dict[str, Any]):
10
- super().__init__(agent, tool, args)
11
- self.tool: ListFilesTool = tool # For type hinting
12
-
13
- def resolve(self) -> ToolResult:
14
- list_path_str = self.tool.path
15
- recursive = self.tool.recursive or False
16
- source_dir = self.args.get("source_dir", ".")
17
- absolute_list_path = os.path.abspath(os.path.join(source_dir, list_path_str))
18
-
19
- # Security check: Allow listing outside source_dir IF the original path is outside?
20
- # For now, let's restrict to source_dir for safety, unless path explicitly starts absolute
21
- # This needs careful consideration based on security requirements.
22
- # Let's allow listing anywhere for now, but log a warning if outside source_dir.
23
- is_outside_source = not absolute_list_path.startswith(os.path.abspath(source_dir))
24
- if is_outside_source:
25
- logger.warning(f"Listing path is outside the project source directory: {list_path_str}")
26
- # Add more checks if needed, e.g., prevent listing sensitive system dirs
27
-
28
- if not os.path.exists(absolute_list_path):
29
- return ToolResult(success=False, message=f"Error: Path not found: {list_path_str}")
30
- if not os.path.isdir(absolute_list_path):
31
- return ToolResult(success=False, message=f"Error: Path is not a directory: {list_path_str}")
32
-
33
- file_list = []
34
- try:
35
- if recursive:
36
- for root, dirs, files in os.walk(absolute_list_path):
37
- for name in files:
38
- full_path = os.path.join(root, name)
39
- # Return relative path if inside source_dir, else absolute
40
- display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
41
- file_list.append(display_path)
42
- for name in dirs:
43
- # Optionally list directories too? The description implies files and dirs.
44
- full_path = os.path.join(root, name)
45
- display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
46
- file_list.append(display_path + "/") # Indicate directory
47
- else:
48
- for item in os.listdir(absolute_list_path):
49
- full_path = os.path.join(absolute_list_path, item)
50
- display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
51
- if os.path.isdir(full_path):
52
- file_list.append(display_path + "/")
53
- else:
54
- file_list.append(display_path)
55
-
56
- message = f"Successfully listed contents of '{list_path_str}' (Recursive: {recursive}). Found {len(file_list)} items."
57
- logger.info(message)
58
- return ToolResult(success=True, message=message, content=sorted(file_list))
59
-
60
- except Exception as e:
61
- logger.error(f"Error listing files in '{list_path_str}': {str(e)}")
62
- return ToolResult(success=False, message=f"An unexpected error occurred while listing files: {str(e)}")
@@ -1,30 +0,0 @@
1
- from typing import Dict, Any, Optional
2
- from .base_tool_resolver import BaseToolResolver
3
- from autocoder.agent.agentic_edit_types import PlanModeRespondTool, ToolResult # Import ToolResult from types
4
- from loguru import logger
5
-
6
- class PlanModeRespondToolResolver(BaseToolResolver):
7
- def __init__(self, agent: Optional[Any], tool: PlanModeRespondTool, args: Dict[str, Any]):
8
- super().__init__(agent, tool, args)
9
- self.tool: PlanModeRespondTool = tool # For type hinting
10
-
11
- def resolve(self) -> ToolResult:
12
- """
13
- Packages the response and options for Plan Mode interaction.
14
- """
15
- response_text = self.tool.response
16
- options = self.tool.options
17
-
18
- logger.info(f"Resolving PlanModeRespondTool: Response='{response_text[:100]}...', Options={options}")
19
-
20
- if not response_text:
21
- return ToolResult(success=False, message="Error: Plan mode response cannot be empty.")
22
-
23
- # The actual presentation happens outside the resolver.
24
- result_content = {
25
- "response": response_text,
26
- "options": options
27
- }
28
-
29
- # Indicate success in preparing the plan mode response data
30
- return ToolResult(success=True, message="Plan mode response prepared.", content=result_content)
@@ -1,36 +0,0 @@
1
- import os
2
- from typing import Dict, Any, Optional
3
- from .base_tool_resolver import BaseToolResolver
4
- from autocoder.agent.agentic_edit_types import ReadFileTool, ToolResult # Import ToolResult from types
5
- from loguru import logger
6
-
7
-
8
- class ReadFileToolResolver(BaseToolResolver):
9
- def __init__(self, agent: Optional[Any], tool: ReadFileTool, args: Dict[str, Any]):
10
- super().__init__(agent, tool, args)
11
- self.tool: ReadFileTool = tool # For type hinting
12
-
13
- def resolve(self) -> ToolResult:
14
- file_path = self.tool.path
15
- source_dir = self.args.get("source_dir", ".")
16
- absolute_path = os.path.abspath(os.path.join(source_dir, file_path))
17
-
18
- # Security check: ensure the path is within the source directory
19
- if not absolute_path.startswith(os.path.abspath(source_dir)):
20
- return ToolResult(success=False, message=f"Error: Access denied. Attempted to read file outside the project directory: {file_path}")
21
-
22
- try:
23
- if not os.path.exists(absolute_path):
24
- return ToolResult(success=False, message=f"Error: File not found at path: {file_path}")
25
- if not os.path.isfile(absolute_path):
26
- return ToolResult(success=False, message=f"Error: Path is not a file: {file_path}")
27
-
28
- # Handle different file types if necessary (e.g., PDF, DOCX)
29
- # For now, assume text files
30
- with open(absolute_path, 'r', encoding='utf-8', errors='replace') as f:
31
- content = f.read()
32
- logger.info(f"Successfully read file: {file_path}")
33
- return ToolResult(success=True, message=f"Successfully read file: {file_path}", content=content)
34
- except Exception as e:
35
- logger.error(f"Error reading file '{file_path}': {str(e)}")
36
- return ToolResult(success=False, message=f"An error occurred while reading the file: {str(e)}")
@@ -1,95 +0,0 @@
1
- import os
2
- import re
3
- from typing import Dict, Any, Optional, List, Tuple
4
- from .base_tool_resolver import BaseToolResolver
5
- from autocoder.agent.agentic_edit_types import ReplaceInFileTool, ToolResult # Import ToolResult from types
6
- from loguru import logger
7
-
8
-
9
- class ReplaceInFileToolResolver(BaseToolResolver):
10
- def __init__(self, agent: Optional[Any], tool: ReplaceInFileTool, args: Dict[str, Any]):
11
- super().__init__(agent, tool, args)
12
- self.tool: ReplaceInFileTool = tool # For type hinting
13
-
14
- def parse_diff(self, diff_content: str) -> List[Tuple[str, str]]:
15
- """
16
- Parses the diff content into a list of (search_block, replace_block) tuples.
17
- """
18
- blocks = []
19
- # Regex to find SEARCH/REPLACE blocks, handling potential variations in line endings
20
- pattern = re.compile(r"<<<<<<< SEARCH\r?\n(.*?)\r?\n=======\r?\n(.*?)\r?\n>>>>>>> REPLACE", re.DOTALL)
21
- matches = pattern.findall(diff_content)
22
- for search_block, replace_block in matches:
23
- # Normalize line endings within blocks if needed, though exact match is preferred
24
- blocks.append((search_block, replace_block))
25
- if not matches and diff_content.strip():
26
- logger.warning(f"Could not parse any SEARCH/REPLACE blocks from diff: {diff_content}")
27
- return blocks
28
-
29
- def resolve(self) -> ToolResult:
30
- file_path = self.tool.path
31
- diff_content = self.tool.diff
32
- source_dir = self.args.get("source_dir", ".")
33
- absolute_path = os.path.abspath(os.path.join(source_dir, file_path))
34
-
35
- # Security check
36
- if not absolute_path.startswith(os.path.abspath(source_dir)):
37
- return ToolResult(success=False, message=f"Error: Access denied. Attempted to modify file outside the project directory: {file_path}")
38
-
39
- if not os.path.exists(absolute_path):
40
- return ToolResult(success=False, message=f"Error: File not found at path: {file_path}")
41
- if not os.path.isfile(absolute_path):
42
- return ToolResult(success=False, message=f"Error: Path is not a file: {file_path}")
43
-
44
- try:
45
- with open(absolute_path, 'r', encoding='utf-8', errors='replace') as f:
46
- original_content = f.read()
47
- except Exception as e:
48
- logger.error(f"Error reading file for replace '{file_path}': {str(e)}")
49
- return ToolResult(success=False, message=f"An error occurred while reading the file for replacement: {str(e)}")
50
-
51
- parsed_blocks = self.parse_diff(diff_content)
52
- if not parsed_blocks:
53
- return ToolResult(success=False, message="Error: No valid SEARCH/REPLACE blocks found in the provided diff.")
54
-
55
- current_content = original_content
56
- applied_count = 0
57
- errors = []
58
-
59
- # Apply blocks sequentially
60
- for i, (search_block, replace_block) in enumerate(parsed_blocks):
61
- # Use find() for exact, first match semantics
62
- start_index = current_content.find(search_block)
63
-
64
- if start_index != -1:
65
- # Replace the first occurrence
66
- current_content = current_content[:start_index] + replace_block + current_content[start_index + len(search_block):]
67
- applied_count += 1
68
- logger.info(f"Applied SEARCH/REPLACE block {i+1} in file {file_path}")
69
- else:
70
- error_message = f"SEARCH block {i+1} not found in the current file content. Content to search:\n---\n{search_block}\n---"
71
- logger.warning(error_message)
72
- # Log surrounding context for debugging
73
- context_start = max(0, original_content.find(search_block[:20]) - 100) # Approximate location
74
- context_end = min(len(original_content), context_start + 200 + len(search_block[:20]))
75
- logger.warning(f"Approximate context in file:\n---\n{original_content[context_start:context_end]}\n---")
76
- errors.append(error_message)
77
- # Stop applying further changes if one fails? Or continue? Let's continue for now.
78
-
79
- if applied_count == 0 and errors:
80
- return ToolResult(success=False, message=f"Failed to apply any changes. Errors:\n" + "\n".join(errors))
81
-
82
- try:
83
- with open(absolute_path, 'w', encoding='utf-8') as f:
84
- f.write(current_content)
85
- logger.info(f"Successfully applied {applied_count}/{len(parsed_blocks)} changes to file: {file_path}")
86
-
87
- message = f"Successfully applied {applied_count}/{len(parsed_blocks)} changes to file: {file_path}."
88
- if errors:
89
- message += "\nWarnings:\n" + "\n".join(errors)
90
-
91
- # Return the final content (might be changed by auto-formatting later)
92
- return ToolResult(success=True, message=message, content=current_content)
93
- except Exception as e:
94
- logger.error(f"Error writing replaced content to file '{file_path}': {str(e)}")
95
- return ToolResult(success=False, message=f"An error occurred while writing the modified file: {str(e)}")
@@ -1,70 +0,0 @@
1
- import os
2
- import re
3
- import glob
4
- from typing import Dict, Any, Optional
5
- from .base_tool_resolver import BaseToolResolver
6
- from autocoder.agent.agentic_edit_types import SearchFilesTool, ToolResult # Import ToolResult from types
7
- from loguru import logger
8
-
9
-
10
- class SearchFilesToolResolver(BaseToolResolver):
11
- def __init__(self, agent: Optional[Any], tool: SearchFilesTool, args: Dict[str, Any]):
12
- super().__init__(agent, tool, args)
13
- self.tool: SearchFilesTool = tool # For type hinting
14
-
15
- def resolve(self) -> ToolResult:
16
- search_path_str = self.tool.path
17
- regex_pattern = self.tool.regex
18
- file_pattern = self.tool.file_pattern or "*" # Default to all files
19
- source_dir = self.args.get("source_dir", ".")
20
- absolute_search_path = os.path.abspath(os.path.join(source_dir, search_path_str))
21
-
22
- # Security check
23
- if not absolute_search_path.startswith(os.path.abspath(source_dir)):
24
- return ToolResult(success=False, message=f"Error: Access denied. Attempted to search outside the project directory: {search_path_str}")
25
-
26
- if not os.path.exists(absolute_search_path):
27
- return ToolResult(success=False, message=f"Error: Search path not found: {search_path_str}")
28
- if not os.path.isdir(absolute_search_path):
29
- return ToolResult(success=False, message=f"Error: Search path is not a directory: {search_path_str}")
30
-
31
- results = []
32
- try:
33
- compiled_regex = re.compile(regex_pattern)
34
- search_glob_pattern = os.path.join(absolute_search_path, "**", file_pattern)
35
-
36
- logger.info(f"Searching for regex '{regex_pattern}' in files matching '{file_pattern}' under '{absolute_search_path}'")
37
-
38
- for filepath in glob.glob(search_glob_pattern, recursive=True):
39
- if os.path.isfile(filepath):
40
- try:
41
- with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
42
- lines = f.readlines()
43
- for i, line in enumerate(lines):
44
- if compiled_regex.search(line):
45
- # Provide context (e.g., line number and surrounding lines)
46
- context_start = max(0, i - 2)
47
- context_end = min(len(lines), i + 3)
48
- context = "".join([f"{j+1}: {lines[j]}" for j in range(context_start, context_end)])
49
- relative_path = os.path.relpath(filepath, source_dir)
50
- results.append({
51
- "path": relative_path,
52
- "line_number": i + 1,
53
- "match_line": line.strip(),
54
- "context": context.strip()
55
- })
56
- # Limit results per file? Or overall? For now, collect all.
57
- except Exception as e:
58
- logger.warning(f"Could not read or process file {filepath}: {e}")
59
- continue # Skip files that can't be read
60
-
61
- message = f"Search completed. Found {len(results)} matches."
62
- logger.info(message)
63
- return ToolResult(success=True, message=message, content=results)
64
-
65
- except re.error as e:
66
- logger.error(f"Invalid regex pattern '{regex_pattern}': {e}")
67
- return ToolResult(success=False, message=f"Invalid regex pattern: {e}")
68
- except Exception as e:
69
- logger.error(f"Error during file search: {str(e)}")
70
- return ToolResult(success=False, message=f"An unexpected error occurred during search: {str(e)}")
@@ -1,55 +0,0 @@
1
- from typing import Dict, Any, Optional
2
- from autocoder.common import AutoCoderArgs
3
- from .base_tool_resolver import BaseToolResolver
4
- from autocoder.agent.agentic_edit_types import UseMcpTool, ToolResult # Import ToolResult from types
5
- from autocoder.common.mcp_server import get_mcp_server
6
- from loguru import logger
7
-
8
-
9
- class UseMcpToolResolver(BaseToolResolver):
10
- def __init__(self, agent: Optional[Any], tool: UseMcpTool, args: AutoCoderArgs):
11
- super().__init__(agent, tool, args)
12
- self.tool: UseMcpTool = tool # For type hinting
13
-
14
- def resolve(self) -> ToolResult:
15
- """
16
- Executes a tool via the Model Context Protocol (MCP) server.
17
- """
18
- server_name = self.tool.server_name
19
- tool_name = self.tool.tool_name
20
- arguments = self.tool.arguments
21
- model = self.args.model
22
- product_mode = self.args.product_mode
23
-
24
-
25
- logger.info(f"Resolving UseMcpTool: Server='{server_name}', Tool='{tool_name}', Args={arguments}")
26
-
27
- # if not server_name or not tool_name:
28
- return ToolResult(success=False, message="Error: MCP server name and tool name are required.")
29
-
30
- # try:
31
- # mcp_server = get_mcp_server()
32
- # if not mcp_server:
33
- # return ToolResult(success=False, message="Error: MCP server is not available or configured.")
34
-
35
- # request = McpExecuteToolRequest(
36
- # server_name=server_name,
37
- # tool_name=tool_name,
38
- # arguments=arguments,
39
- # model=model, # Pass model info if required by MCP server
40
- # product_mode=product_mode
41
- # )
42
-
43
- # logger.debug(f"Sending MCP request: {request.dict()}")
44
- # response: McpExecuteToolResponse = mcp_server.send_request(request)
45
- # logger.debug(f"Received MCP response: Success={response.success}, Message={response.message}")
46
-
47
-
48
- # if response.success:
49
- # return ToolResult(success=True, message=f"MCP tool '{tool_name}' executed successfully. {response.message}", content=response.result)
50
- # else:
51
- # return ToolResult(success=False, message=f"MCP tool '{tool_name}' failed: {response.message}", content=response.result)
52
-
53
- # except Exception as e:
54
- # logger.error(f"Error executing MCP tool '{tool_name}' on server '{server_name}': {str(e)}")
55
- # return ToolResult(success=False, message=f"An unexpected error occurred while executing the MCP tool: {str(e)}")