auto-coder 0.1.334__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.

Files changed (67) hide show
  1. {auto_coder-0.1.334.dist-info → auto_coder-0.1.336.dist-info}/METADATA +2 -2
  2. {auto_coder-0.1.334.dist-info → auto_coder-0.1.336.dist-info}/RECORD +67 -32
  3. autocoder/agent/agentic_edit.py +833 -0
  4. autocoder/agent/agentic_edit_tools/__init__.py +28 -0
  5. autocoder/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py +32 -0
  6. autocoder/agent/agentic_edit_tools/attempt_completion_tool_resolver.py +29 -0
  7. autocoder/agent/agentic_edit_tools/base_tool_resolver.py +29 -0
  8. autocoder/agent/agentic_edit_tools/execute_command_tool_resolver.py +84 -0
  9. autocoder/agent/agentic_edit_tools/list_code_definition_names_tool_resolver.py +75 -0
  10. autocoder/agent/agentic_edit_tools/list_files_tool_resolver.py +62 -0
  11. autocoder/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py +30 -0
  12. autocoder/agent/agentic_edit_tools/read_file_tool_resolver.py +36 -0
  13. autocoder/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +95 -0
  14. autocoder/agent/agentic_edit_tools/search_files_tool_resolver.py +70 -0
  15. autocoder/agent/agentic_edit_tools/use_mcp_tool_resolver.py +55 -0
  16. autocoder/agent/agentic_edit_tools/write_to_file_tool_resolver.py +98 -0
  17. autocoder/agent/agentic_edit_types.py +124 -0
  18. autocoder/auto_coder.py +39 -18
  19. autocoder/auto_coder_rag.py +18 -9
  20. autocoder/auto_coder_runner.py +50 -5
  21. autocoder/chat_auto_coder_lang.py +18 -2
  22. autocoder/commands/tools.py +5 -1
  23. autocoder/common/__init__.py +2 -0
  24. autocoder/common/auto_coder_lang.py +40 -8
  25. autocoder/common/code_auto_generate_diff.py +1 -1
  26. autocoder/common/code_auto_generate_editblock.py +1 -1
  27. autocoder/common/code_auto_generate_strict_diff.py +1 -1
  28. autocoder/common/mcp_hub.py +185 -2
  29. autocoder/common/mcp_server.py +243 -306
  30. autocoder/common/mcp_server_install.py +269 -0
  31. autocoder/common/mcp_server_types.py +169 -0
  32. autocoder/common/stream_out_type.py +3 -0
  33. autocoder/common/v2/agent/__init__.py +0 -0
  34. autocoder/common/v2/agent/agentic_edit.py +1302 -0
  35. autocoder/common/v2/agent/agentic_edit_tools/__init__.py +28 -0
  36. autocoder/common/v2/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py +70 -0
  37. autocoder/common/v2/agent/agentic_edit_tools/attempt_completion_tool_resolver.py +35 -0
  38. autocoder/common/v2/agent/agentic_edit_tools/base_tool_resolver.py +33 -0
  39. autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py +88 -0
  40. autocoder/common/v2/agent/agentic_edit_tools/list_code_definition_names_tool_resolver.py +80 -0
  41. autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +105 -0
  42. autocoder/common/v2/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py +35 -0
  43. autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +51 -0
  44. autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +144 -0
  45. autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +99 -0
  46. autocoder/common/v2/agent/agentic_edit_tools/use_mcp_tool_resolver.py +46 -0
  47. autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +58 -0
  48. autocoder/common/v2/agent/agentic_edit_types.py +162 -0
  49. autocoder/common/v2/agent/agentic_tool_display.py +184 -0
  50. autocoder/common/v2/code_agentic_editblock_manager.py +812 -0
  51. autocoder/common/v2/code_auto_generate.py +1 -1
  52. autocoder/common/v2/code_auto_generate_diff.py +1 -1
  53. autocoder/common/v2/code_auto_generate_editblock.py +1 -1
  54. autocoder/common/v2/code_auto_generate_strict_diff.py +1 -1
  55. autocoder/common/v2/code_editblock_manager.py +151 -178
  56. autocoder/compilers/provided_compiler.py +3 -2
  57. autocoder/events/event_manager.py +4 -4
  58. autocoder/events/event_types.py +1 -0
  59. autocoder/memory/active_context_manager.py +2 -29
  60. autocoder/models.py +10 -2
  61. autocoder/shadows/shadow_manager.py +1 -1
  62. autocoder/utils/llms.py +4 -2
  63. autocoder/version.py +1 -1
  64. {auto_coder-0.1.334.dist-info → auto_coder-0.1.336.dist-info}/LICENSE +0 -0
  65. {auto_coder-0.1.334.dist-info → auto_coder-0.1.336.dist-info}/WHEEL +0 -0
  66. {auto_coder-0.1.334.dist-info → auto_coder-0.1.336.dist-info}/entry_points.txt +0 -0
  67. {auto_coder-0.1.334.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)}")