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.
- {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/METADATA +2 -2
- {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/RECORD +27 -57
- autocoder/agent/base_agentic/base_agent.py +202 -52
- autocoder/agent/base_agentic/default_tools.py +38 -6
- autocoder/agent/base_agentic/tools/list_files_tool_resolver.py +83 -43
- autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +88 -25
- autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +171 -62
- autocoder/agent/base_agentic/tools/search_files_tool_resolver.py +101 -56
- autocoder/agent/base_agentic/tools/talk_to_group_tool_resolver.py +5 -0
- autocoder/agent/base_agentic/tools/talk_to_tool_resolver.py +5 -0
- autocoder/agent/base_agentic/tools/write_to_file_tool_resolver.py +145 -32
- autocoder/auto_coder_rag.py +80 -11
- autocoder/models.py +2 -2
- autocoder/rag/agentic_rag.py +217 -0
- autocoder/rag/cache/local_duckdb_storage_cache.py +63 -33
- autocoder/rag/conversation_to_queries.py +37 -5
- autocoder/rag/long_context_rag.py +161 -41
- autocoder/rag/tools/__init__.py +10 -0
- autocoder/rag/tools/recall_tool.py +163 -0
- autocoder/rag/tools/search_tool.py +126 -0
- autocoder/rag/types.py +36 -0
- autocoder/utils/_markitdown.py +59 -13
- autocoder/version.py +1 -1
- autocoder/agent/agentic_edit.py +0 -833
- autocoder/agent/agentic_edit_tools/__init__.py +0 -28
- autocoder/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py +0 -32
- autocoder/agent/agentic_edit_tools/attempt_completion_tool_resolver.py +0 -29
- autocoder/agent/agentic_edit_tools/base_tool_resolver.py +0 -29
- autocoder/agent/agentic_edit_tools/execute_command_tool_resolver.py +0 -84
- autocoder/agent/agentic_edit_tools/list_code_definition_names_tool_resolver.py +0 -75
- autocoder/agent/agentic_edit_tools/list_files_tool_resolver.py +0 -62
- autocoder/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py +0 -30
- autocoder/agent/agentic_edit_tools/read_file_tool_resolver.py +0 -36
- autocoder/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +0 -95
- autocoder/agent/agentic_edit_tools/search_files_tool_resolver.py +0 -70
- autocoder/agent/agentic_edit_tools/use_mcp_tool_resolver.py +0 -55
- autocoder/agent/agentic_edit_tools/write_to_file_tool_resolver.py +0 -98
- autocoder/agent/agentic_edit_types.py +0 -124
- autocoder/auto_coder_lang.py +0 -60
- autocoder/auto_coder_rag_client_mcp.py +0 -170
- autocoder/auto_coder_rag_mcp.py +0 -193
- autocoder/common/llm_rerank.py +0 -84
- autocoder/common/model_speed_test.py +0 -392
- autocoder/common/v2/agent/agentic_edit_conversation.py +0 -188
- autocoder/common/v2/agent/ignore_utils.py +0 -50
- autocoder/dispacher/actions/plugins/action_translate.py +0 -214
- autocoder/ignorefiles/__init__.py +0 -4
- autocoder/ignorefiles/ignore_file_utils.py +0 -63
- autocoder/ignorefiles/test_ignore_file_utils.py +0 -91
- autocoder/linters/code_linter.py +0 -588
- autocoder/rag/loaders/test_image_loader.py +0 -209
- autocoder/rag/raw_rag.py +0 -96
- autocoder/rag/simple_directory_reader.py +0 -646
- autocoder/rag/simple_rag.py +0 -404
- autocoder/regex_project/__init__.py +0 -162
- autocoder/utils/coder.py +0 -125
- autocoder/utils/tests.py +0 -37
- {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/entry_points.txt +0 -0
- {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)}")
|