auto-coder 0.1.335__py3-none-any.whl → 0.1.336__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.335.dist-info → auto_coder-0.1.336.dist-info}/METADATA +2 -2
- {auto_coder-0.1.335.dist-info → auto_coder-0.1.336.dist-info}/RECORD +34 -16
- autocoder/auto_coder.py +34 -17
- autocoder/auto_coder_rag.py +18 -9
- autocoder/auto_coder_runner.py +45 -4
- autocoder/common/__init__.py +1 -0
- autocoder/common/v2/agent/__init__.py +0 -0
- autocoder/common/v2/agent/agentic_edit.py +1302 -0
- autocoder/common/v2/agent/agentic_edit_tools/__init__.py +28 -0
- autocoder/common/v2/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py +70 -0
- autocoder/common/v2/agent/agentic_edit_tools/attempt_completion_tool_resolver.py +35 -0
- autocoder/common/v2/agent/agentic_edit_tools/base_tool_resolver.py +33 -0
- autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py +88 -0
- autocoder/common/v2/agent/agentic_edit_tools/list_code_definition_names_tool_resolver.py +80 -0
- autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +105 -0
- autocoder/common/v2/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py +35 -0
- autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +51 -0
- autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +144 -0
- autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +99 -0
- autocoder/common/v2/agent/agentic_edit_tools/use_mcp_tool_resolver.py +46 -0
- autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +58 -0
- autocoder/common/v2/agent/agentic_edit_types.py +162 -0
- autocoder/common/v2/agent/agentic_tool_display.py +184 -0
- autocoder/common/v2/code_agentic_editblock_manager.py +812 -0
- autocoder/events/event_manager.py +4 -4
- autocoder/events/event_types.py +1 -0
- autocoder/memory/active_context_manager.py +2 -29
- autocoder/models.py +10 -2
- autocoder/utils/llms.py +4 -2
- autocoder/version.py +1 -1
- {auto_coder-0.1.335.dist-info → auto_coder-0.1.336.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.335.dist-info → auto_coder-0.1.336.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.335.dist-info → auto_coder-0.1.336.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.335.dist-info → auto_coder-0.1.336.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
|
|
3
|
+
from autocoder.common.v2.agent.agentic_edit_types import AskFollowupQuestionTool, ToolResult # Import ToolResult from types
|
|
4
|
+
from loguru import logger
|
|
5
|
+
import typing
|
|
6
|
+
from autocoder.common import AutoCoderArgs
|
|
7
|
+
from autocoder.run_context import get_run_context
|
|
8
|
+
from autocoder.events.event_manager_singleton import get_event_manager
|
|
9
|
+
from autocoder.events import event_content as EventContentCreator
|
|
10
|
+
from prompt_toolkit import PromptSession
|
|
11
|
+
from prompt_toolkit.styles import Style
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.text import Text
|
|
15
|
+
|
|
16
|
+
if typing.TYPE_CHECKING:
|
|
17
|
+
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
18
|
+
|
|
19
|
+
class AskFollowupQuestionToolResolver(BaseToolResolver):
|
|
20
|
+
def __init__(self, agent: Optional['AgenticEdit'], tool: AskFollowupQuestionTool, args: AutoCoderArgs):
|
|
21
|
+
super().__init__(agent, tool, args)
|
|
22
|
+
self.tool: AskFollowupQuestionTool = tool # For type hinting
|
|
23
|
+
|
|
24
|
+
def resolve(self) -> ToolResult:
|
|
25
|
+
"""
|
|
26
|
+
Packages the question and options to be handled by the main loop/UI.
|
|
27
|
+
This resolver doesn't directly ask the user but prepares the data for it.
|
|
28
|
+
"""
|
|
29
|
+
question = self.tool.question
|
|
30
|
+
options = self.tool.options
|
|
31
|
+
|
|
32
|
+
if get_run_context().is_web():
|
|
33
|
+
answer = get_event_manager(
|
|
34
|
+
self.args.event_file).ask_user(prompt=question)
|
|
35
|
+
self.result_manager.append(content=answer, meta={
|
|
36
|
+
"action": "ask_user",
|
|
37
|
+
"input": {
|
|
38
|
+
"question": question
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
return answer
|
|
42
|
+
|
|
43
|
+
console = Console()
|
|
44
|
+
|
|
45
|
+
# 创建一个醒目的问题面板
|
|
46
|
+
question_text = Text(question, style="bold cyan")
|
|
47
|
+
question_panel = Panel(
|
|
48
|
+
question_text,
|
|
49
|
+
title="[bold yellow]auto-coder.chat's Question[/bold yellow]",
|
|
50
|
+
border_style="blue",
|
|
51
|
+
expand=False
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# 显示问题面板
|
|
55
|
+
console.print(question_panel)
|
|
56
|
+
|
|
57
|
+
session = PromptSession(
|
|
58
|
+
message=self.printer.get_message_from_key('tool_ask_user'))
|
|
59
|
+
try:
|
|
60
|
+
answer = session.prompt()
|
|
61
|
+
except KeyboardInterrupt:
|
|
62
|
+
answer = ""
|
|
63
|
+
|
|
64
|
+
# The actual asking logic resides outside the resolver, typically in the agent's main loop
|
|
65
|
+
# or UI interaction layer. The resolver's job is to validate and package the request.
|
|
66
|
+
if not answer:
|
|
67
|
+
return ToolResult(success=False, message="Error: Question not answered.")
|
|
68
|
+
|
|
69
|
+
# Indicate success in preparing the question data
|
|
70
|
+
return ToolResult(success=True, message="Follow-up question prepared.", content=answer)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
|
|
3
|
+
from autocoder.common.v2.agent.agentic_edit_types import AttemptCompletionTool, ToolResult # Import ToolResult from types
|
|
4
|
+
from loguru import logger
|
|
5
|
+
import typing
|
|
6
|
+
from autocoder.common import AutoCoderArgs
|
|
7
|
+
|
|
8
|
+
if typing.TYPE_CHECKING:
|
|
9
|
+
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
10
|
+
|
|
11
|
+
class AttemptCompletionToolResolver(BaseToolResolver):
|
|
12
|
+
def __init__(self, agent: Optional['AgenticEdit'], tool: AttemptCompletionTool, args: AutoCoderArgs):
|
|
13
|
+
super().__init__(agent, tool, args)
|
|
14
|
+
self.tool: AttemptCompletionTool = tool # For type hinting
|
|
15
|
+
|
|
16
|
+
def resolve(self) -> ToolResult:
|
|
17
|
+
"""
|
|
18
|
+
Packages the completion result and optional command to signal task completion.
|
|
19
|
+
"""
|
|
20
|
+
result_text = self.tool.result
|
|
21
|
+
command = self.tool.command
|
|
22
|
+
|
|
23
|
+
logger.info(f"Resolving AttemptCompletionTool: Result='{result_text[:100]}...', Command='{command}'")
|
|
24
|
+
|
|
25
|
+
if not result_text:
|
|
26
|
+
return ToolResult(success=False, message="Error: Completion result cannot be empty.")
|
|
27
|
+
|
|
28
|
+
# The actual presentation of the result happens outside the resolver.
|
|
29
|
+
result_content = {
|
|
30
|
+
"result": result_text,
|
|
31
|
+
"command": command
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Indicate success in preparing the completion data
|
|
35
|
+
return ToolResult(success=True, message="Task completion attempted.", content=result_content)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
from autocoder.common.v2.agent.agentic_edit_types import BaseTool, ToolResult # Import ToolResult from types
|
|
4
|
+
from autocoder.common import AutoCoderArgs
|
|
5
|
+
import typing
|
|
6
|
+
|
|
7
|
+
if typing.TYPE_CHECKING:
|
|
8
|
+
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseToolResolver(ABC):
|
|
12
|
+
def __init__(self, agent: Optional['AgenticEdit'], tool: BaseTool, args: AutoCoderArgs):
|
|
13
|
+
"""
|
|
14
|
+
Initializes the resolver.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
agent: The AutoCoder agent instance.
|
|
18
|
+
tool: The Pydantic model instance representing the tool call.
|
|
19
|
+
args: Additional arguments needed for execution (e.g., source_dir).
|
|
20
|
+
"""
|
|
21
|
+
self.agent = agent
|
|
22
|
+
self.tool = tool
|
|
23
|
+
self.args = args
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def resolve(self) -> ToolResult:
|
|
27
|
+
"""
|
|
28
|
+
Executes the tool's logic.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
A ToolResult object indicating success or failure and a message.
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import os
|
|
3
|
+
from typing import Dict, Any, Optional
|
|
4
|
+
from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
|
|
5
|
+
from autocoder.common.v2.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
|
+
import typing
|
|
10
|
+
from autocoder.common import AutoCoderArgs
|
|
11
|
+
|
|
12
|
+
if typing.TYPE_CHECKING:
|
|
13
|
+
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
14
|
+
|
|
15
|
+
class ExecuteCommandToolResolver(BaseToolResolver):
|
|
16
|
+
def __init__(self, agent: Optional['AgenticEdit'], tool: ExecuteCommandTool, args: AutoCoderArgs):
|
|
17
|
+
super().__init__(agent, tool, args)
|
|
18
|
+
self.tool: ExecuteCommandTool = tool # For type hinting
|
|
19
|
+
|
|
20
|
+
def resolve(self) -> ToolResult:
|
|
21
|
+
printer = Printer()
|
|
22
|
+
command = self.tool.command
|
|
23
|
+
requires_approval = self.tool.requires_approval
|
|
24
|
+
source_dir = self.args.source_dir or "."
|
|
25
|
+
|
|
26
|
+
# Basic security check (can be expanded)
|
|
27
|
+
if ";" in command or "&&" in command or "|" in command or "`" in command:
|
|
28
|
+
# Allow && for cd chaining, but be cautious
|
|
29
|
+
if not command.strip().startswith("cd ") and " && " in command:
|
|
30
|
+
pass # Allow cd chaining like 'cd subdir && command'
|
|
31
|
+
else:
|
|
32
|
+
return ToolResult(success=False, message=f"Command '{command}' contains potentially unsafe characters.")
|
|
33
|
+
|
|
34
|
+
# Approval mechanism (simplified)
|
|
35
|
+
if requires_approval:
|
|
36
|
+
# In a real scenario, this would involve user interaction
|
|
37
|
+
printer.print_str_in_terminal(f"Command requires approval: {command}")
|
|
38
|
+
# For now, let's assume approval is granted in non-interactive mode or handled elsewhere
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
printer.print_str_in_terminal(f"Executing command: {command} in {os.path.abspath(source_dir)}")
|
|
42
|
+
try:
|
|
43
|
+
# Determine shell based on OS
|
|
44
|
+
shell = True
|
|
45
|
+
executable = None
|
|
46
|
+
if shells.is_windows():
|
|
47
|
+
# Decide between cmd and powershell if needed, default is cmd
|
|
48
|
+
pass # shell=True uses default shell
|
|
49
|
+
else:
|
|
50
|
+
# Use bash or zsh? Default is usually fine.
|
|
51
|
+
pass # shell=True uses default shell
|
|
52
|
+
|
|
53
|
+
# Execute the command
|
|
54
|
+
process = subprocess.Popen(
|
|
55
|
+
command,
|
|
56
|
+
shell=True,
|
|
57
|
+
stdout=subprocess.PIPE,
|
|
58
|
+
stderr=subprocess.PIPE,
|
|
59
|
+
cwd=source_dir,
|
|
60
|
+
text=True,
|
|
61
|
+
encoding=shells.get_terminal_encoding(),
|
|
62
|
+
errors='replace' # Handle potential decoding errors
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
stdout, stderr = process.communicate()
|
|
66
|
+
returncode = process.returncode
|
|
67
|
+
|
|
68
|
+
logger.info(f"Command executed: {command}")
|
|
69
|
+
logger.info(f"Return Code: {returncode}")
|
|
70
|
+
if stdout:
|
|
71
|
+
logger.info(f"stdout:\n{stdout}")
|
|
72
|
+
if stderr:
|
|
73
|
+
logger.info(f"stderr:\n{stderr}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if returncode == 0:
|
|
77
|
+
return ToolResult(success=True, message="Command executed successfully.", content=stdout)
|
|
78
|
+
else:
|
|
79
|
+
error_message = f"Command failed with return code {returncode}.\nStderr:\n{stderr}\nStdout:\n{stdout}"
|
|
80
|
+
return ToolResult(success=False, message=error_message, content={"stdout": stdout, "stderr": stderr, "returncode": returncode})
|
|
81
|
+
|
|
82
|
+
except FileNotFoundError:
|
|
83
|
+
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.")
|
|
84
|
+
except PermissionError:
|
|
85
|
+
return ToolResult(success=False, message=f"Error: Permission denied when trying to execute '{command}'.")
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Error executing command '{command}': {str(e)}")
|
|
88
|
+
return ToolResult(success=False, message=f"An unexpected error occurred while executing the command: {str(e)}")
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
|
|
4
|
+
from autocoder.common.v2.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
|
+
import typing
|
|
15
|
+
from autocoder.common import AutoCoderArgs
|
|
16
|
+
|
|
17
|
+
if typing.TYPE_CHECKING:
|
|
18
|
+
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ListCodeDefinitionNamesToolResolver(BaseToolResolver):
|
|
22
|
+
def __init__(self, agent: Optional['AgenticEdit'], tool: ListCodeDefinitionNamesTool, args: AutoCoderArgs):
|
|
23
|
+
super().__init__(agent, tool, args)
|
|
24
|
+
self.tool: ListCodeDefinitionNamesTool = tool # For type hinting
|
|
25
|
+
self.llm = self.agent.llm
|
|
26
|
+
|
|
27
|
+
def _get_index(self):
|
|
28
|
+
index_manager = IndexManager(
|
|
29
|
+
llm=self.llm, sources=[], args=self.args)
|
|
30
|
+
return index_manager
|
|
31
|
+
|
|
32
|
+
def resolve(self) -> ToolResult:
|
|
33
|
+
|
|
34
|
+
index_items = self._get_index().read_index()
|
|
35
|
+
index_data = {item.module_name: item for item in index_items}
|
|
36
|
+
|
|
37
|
+
target_path_str = self.tool.path
|
|
38
|
+
source_dir = self.args.source_dir or "."
|
|
39
|
+
absolute_target_path = os.path.abspath(os.path.join(source_dir, target_path_str))
|
|
40
|
+
|
|
41
|
+
# Security check
|
|
42
|
+
if not absolute_target_path.startswith(os.path.abspath(source_dir)):
|
|
43
|
+
return ToolResult(success=False, message=f"Error: Access denied. Attempted to analyze code outside the project directory: {target_path_str}")
|
|
44
|
+
|
|
45
|
+
if not os.path.exists(absolute_target_path):
|
|
46
|
+
return ToolResult(success=False, message=f"Error: Path not found: {target_path_str}")
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
# Use RepoParser or a similar mechanism to extract definitions
|
|
50
|
+
# RepoParser might need adjustments or a specific method for this tool's purpose.
|
|
51
|
+
# This is a placeholder implementation. A real implementation needs robust code parsing.
|
|
52
|
+
logger.info(f"Analyzing definitions in: {absolute_target_path}")
|
|
53
|
+
all_symbols = []
|
|
54
|
+
|
|
55
|
+
if os.path.isfile(absolute_target_path):
|
|
56
|
+
file_paths = [absolute_target_path]
|
|
57
|
+
else:
|
|
58
|
+
return ToolResult(success=False, message=f"Error: Path is neither a file nor a directory: {target_path_str}")
|
|
59
|
+
|
|
60
|
+
for file_path in file_paths:
|
|
61
|
+
try:
|
|
62
|
+
item = index_data[file_path]
|
|
63
|
+
symbols_str = item.symbols
|
|
64
|
+
symbols = extract_symbols(symbols_str)
|
|
65
|
+
if symbols:
|
|
66
|
+
all_symbols.append({
|
|
67
|
+
"path": file_path,
|
|
68
|
+
"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]
|
|
69
|
+
})
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.warning(f"Could not parse symbols from {file_path}: {e}")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
message = f"Successfully extracted {sum(len(s['definitions']) for s in all_symbols)} definitions from {len(all_symbols)} files in '{target_path_str}'."
|
|
75
|
+
logger.info(message)
|
|
76
|
+
return ToolResult(success=True, message=message, content=all_symbols)
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.error(f"Error extracting code definitions from '{target_path_str}': {str(e)}")
|
|
80
|
+
return ToolResult(success=False, message=f"An unexpected error occurred while extracting code definitions: {str(e)}")
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
|
|
4
|
+
from autocoder.common.v2.agent.agentic_edit_types import ListFilesTool, ToolResult # Import ToolResult from types
|
|
5
|
+
from loguru import logger
|
|
6
|
+
import typing
|
|
7
|
+
from autocoder.common import AutoCoderArgs
|
|
8
|
+
|
|
9
|
+
if typing.TYPE_CHECKING:
|
|
10
|
+
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ListFilesToolResolver(BaseToolResolver):
|
|
14
|
+
def __init__(self, agent: Optional['AgenticEdit'], tool: ListFilesTool, args: AutoCoderArgs):
|
|
15
|
+
super().__init__(agent, tool, args)
|
|
16
|
+
self.tool: ListFilesTool = tool # For type hinting
|
|
17
|
+
self.shadow_manager = self.agent.shadow_manager
|
|
18
|
+
|
|
19
|
+
def resolve(self) -> ToolResult:
|
|
20
|
+
list_path_str = self.tool.path
|
|
21
|
+
recursive = self.tool.recursive or False
|
|
22
|
+
source_dir = self.args.source_dir or "."
|
|
23
|
+
absolute_list_path = os.path.abspath(os.path.join(source_dir, list_path_str))
|
|
24
|
+
|
|
25
|
+
# Security check: Allow listing outside source_dir IF the original path is outside?
|
|
26
|
+
# For now, let's restrict to source_dir for safety, unless path explicitly starts absolute
|
|
27
|
+
# This needs careful consideration based on security requirements.
|
|
28
|
+
# Let's allow listing anywhere for now, but log a warning if outside source_dir.
|
|
29
|
+
is_outside_source = not absolute_list_path.startswith(os.path.abspath(source_dir))
|
|
30
|
+
if is_outside_source:
|
|
31
|
+
logger.warning(f"Listing path is outside the project source directory: {list_path_str}")
|
|
32
|
+
# Add more checks if needed, e.g., prevent listing sensitive system dirs
|
|
33
|
+
|
|
34
|
+
# Check if shadow directory exists for this path
|
|
35
|
+
shadow_paths = []
|
|
36
|
+
shadow_exists = False
|
|
37
|
+
shadow_dir_path = None
|
|
38
|
+
if self.shadow_manager:
|
|
39
|
+
try:
|
|
40
|
+
shadow_dir_path = self.shadow_manager.to_shadow_path(absolute_list_path)
|
|
41
|
+
if os.path.exists(shadow_dir_path) and os.path.isdir(shadow_dir_path):
|
|
42
|
+
shadow_exists = True
|
|
43
|
+
except Exception as e:
|
|
44
|
+
logger.warning(f"Error checking shadow path for {absolute_list_path}: {e}")
|
|
45
|
+
|
|
46
|
+
# Validate that at least one of the directories exists
|
|
47
|
+
if not os.path.exists(absolute_list_path) and not shadow_exists:
|
|
48
|
+
return ToolResult(success=False, message=f"Error: Path not found: {list_path_str}")
|
|
49
|
+
if os.path.exists(absolute_list_path) and not os.path.isdir(absolute_list_path):
|
|
50
|
+
return ToolResult(success=False, message=f"Error: Path is not a directory: {list_path_str}")
|
|
51
|
+
if shadow_exists and not os.path.isdir(shadow_dir_path):
|
|
52
|
+
return ToolResult(success=False, message=f"Error: Shadow path is not a directory: {shadow_dir_path}")
|
|
53
|
+
|
|
54
|
+
# Helper function to list files in a directory
|
|
55
|
+
def list_files_in_dir(base_dir: str) -> set:
|
|
56
|
+
result = set()
|
|
57
|
+
try:
|
|
58
|
+
if recursive:
|
|
59
|
+
for root, dirs, files in os.walk(base_dir):
|
|
60
|
+
for name in files:
|
|
61
|
+
full_path = os.path.join(root, name)
|
|
62
|
+
display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
|
|
63
|
+
result.add(display_path)
|
|
64
|
+
for name in dirs:
|
|
65
|
+
full_path = os.path.join(root, name)
|
|
66
|
+
display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
|
|
67
|
+
result.add(display_path + "/")
|
|
68
|
+
else:
|
|
69
|
+
for item in os.listdir(base_dir):
|
|
70
|
+
full_path = os.path.join(base_dir, item)
|
|
71
|
+
display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
|
|
72
|
+
if os.path.isdir(full_path):
|
|
73
|
+
result.add(display_path + "/")
|
|
74
|
+
else:
|
|
75
|
+
result.add(display_path)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.warning(f"Error listing files in {base_dir}: {e}")
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
# Collect files from shadow and/or source directory
|
|
81
|
+
shadow_files_set = set()
|
|
82
|
+
if shadow_exists:
|
|
83
|
+
shadow_files_set = list_files_in_dir(shadow_dir_path)
|
|
84
|
+
|
|
85
|
+
source_files_set = set()
|
|
86
|
+
if os.path.exists(absolute_list_path) and os.path.isdir(absolute_list_path):
|
|
87
|
+
source_files_set = list_files_in_dir(absolute_list_path)
|
|
88
|
+
|
|
89
|
+
# Merge results, prioritizing shadow files if exist
|
|
90
|
+
merged_files = set()
|
|
91
|
+
if shadow_exists:
|
|
92
|
+
# Use shadow files + source files that are NOT shadowed
|
|
93
|
+
merged_files = shadow_files_set.union(
|
|
94
|
+
{f for f in source_files_set if f not in shadow_files_set}
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
merged_files = source_files_set
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
message = f"Successfully listed contents of '{list_path_str}' (Recursive: {recursive}). Found {len(merged_files)} items."
|
|
101
|
+
logger.info(message)
|
|
102
|
+
return ToolResult(success=True, message=message, content=sorted(merged_files))
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.error(f"Error listing files in '{list_path_str}': {str(e)}")
|
|
105
|
+
return ToolResult(success=False, message=f"An unexpected error occurred while listing files: {str(e)}")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Dict, Any, Optional
|
|
2
|
+
import typing
|
|
3
|
+
from autocoder.common import AutoCoderArgs
|
|
4
|
+
from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
|
|
5
|
+
from autocoder.common.v2.agent.agentic_edit_types import PlanModeRespondTool, ToolResult # Import ToolResult from types
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
if typing.TYPE_CHECKING:
|
|
9
|
+
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
10
|
+
|
|
11
|
+
class PlanModeRespondToolResolver(BaseToolResolver):
|
|
12
|
+
def __init__(self, agent: Optional['AgenticEdit'], tool: PlanModeRespondTool, args: AutoCoderArgs):
|
|
13
|
+
super().__init__(agent, tool, args)
|
|
14
|
+
self.tool: PlanModeRespondTool = tool # For type hinting
|
|
15
|
+
|
|
16
|
+
def resolve(self) -> ToolResult:
|
|
17
|
+
"""
|
|
18
|
+
Packages the response and options for Plan Mode interaction.
|
|
19
|
+
"""
|
|
20
|
+
response_text = self.tool.response
|
|
21
|
+
options = self.tool.options
|
|
22
|
+
|
|
23
|
+
logger.info(f"Resolving PlanModeRespondTool: Response='{response_text[:100]}...', Options={options}")
|
|
24
|
+
|
|
25
|
+
if not response_text:
|
|
26
|
+
return ToolResult(success=False, message="Error: Plan mode response cannot be empty.")
|
|
27
|
+
|
|
28
|
+
# The actual presentation happens outside the resolver.
|
|
29
|
+
result_content = {
|
|
30
|
+
"response": response_text,
|
|
31
|
+
"options": options
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Indicate success in preparing the plan mode response data
|
|
35
|
+
return ToolResult(success=True, message="Plan mode response prepared.", content=result_content)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
from autocoder.common import AutoCoderArgs
|
|
4
|
+
from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
|
|
5
|
+
from autocoder.common.v2.agent.agentic_edit_types import ReadFileTool, ToolResult # Import ToolResult from types
|
|
6
|
+
from loguru import logger
|
|
7
|
+
import typing
|
|
8
|
+
|
|
9
|
+
if typing.TYPE_CHECKING:
|
|
10
|
+
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ReadFileToolResolver(BaseToolResolver):
|
|
14
|
+
def __init__(self, agent: Optional['AgenticEdit'], tool: ReadFileTool, args: AutoCoderArgs):
|
|
15
|
+
super().__init__(agent, tool, args)
|
|
16
|
+
self.tool: ReadFileTool = tool # For type hinting
|
|
17
|
+
self.shadow_manager = self.agent.shadow_manager if self.agent else None
|
|
18
|
+
|
|
19
|
+
def resolve(self) -> ToolResult:
|
|
20
|
+
file_path = self.tool.path
|
|
21
|
+
source_dir = self.args.source_dir or "."
|
|
22
|
+
abs_project_dir = os.path.abspath(source_dir)
|
|
23
|
+
abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
|
|
24
|
+
|
|
25
|
+
# Security check: ensure the path is within the source directory
|
|
26
|
+
if not abs_file_path.startswith(abs_project_dir):
|
|
27
|
+
return ToolResult(success=False, message=f"Error: Access denied. Attempted to read file outside the project directory: {file_path}")
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
if self.shadow_manager:
|
|
31
|
+
shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
|
|
32
|
+
# If shadow file exists, read from it
|
|
33
|
+
if os.path.exists(shadow_path) and os.path.isfile(shadow_path):
|
|
34
|
+
with open(shadow_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
35
|
+
content = f.read()
|
|
36
|
+
logger.info(f"[Shadow] Successfully read shadow file: {shadow_path}")
|
|
37
|
+
return ToolResult(success=True, message=f"Successfully read file (shadow): {file_path}", content=content)
|
|
38
|
+
# else fallback to original file
|
|
39
|
+
# Fallback to original file
|
|
40
|
+
if not os.path.exists(abs_file_path):
|
|
41
|
+
return ToolResult(success=False, message=f"Error: File not found at path: {file_path}")
|
|
42
|
+
if not os.path.isfile(abs_file_path):
|
|
43
|
+
return ToolResult(success=False, message=f"Error: Path is not a file: {file_path}")
|
|
44
|
+
|
|
45
|
+
with open(abs_file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
46
|
+
content = f.read()
|
|
47
|
+
logger.info(f"Successfully read file: {file_path}")
|
|
48
|
+
return ToolResult(success=True, message=f"Successfully read file: {file_path}", content=content)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.error(f"Error reading file '{file_path}': {str(e)}")
|
|
51
|
+
return ToolResult(success=False, message=f"An error occurred while reading the file: {str(e)}")
|