autocoder-nano 0.1.30__py3-none-any.whl → 0.1.33__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. autocoder_nano/agent/agent_base.py +4 -4
  2. autocoder_nano/agent/agentic_edit.py +1584 -0
  3. autocoder_nano/agent/agentic_edit_tools/__init__.py +28 -0
  4. autocoder_nano/agent/agentic_edit_tools/ask_followup_question_tool.py +51 -0
  5. autocoder_nano/agent/agentic_edit_tools/attempt_completion_tool.py +36 -0
  6. autocoder_nano/agent/agentic_edit_tools/base_tool_resolver.py +31 -0
  7. autocoder_nano/agent/agentic_edit_tools/execute_command_tool.py +65 -0
  8. autocoder_nano/agent/agentic_edit_tools/list_code_definition_names_tool.py +78 -0
  9. autocoder_nano/agent/agentic_edit_tools/list_files_tool.py +123 -0
  10. autocoder_nano/agent/agentic_edit_tools/list_package_info_tool.py +42 -0
  11. autocoder_nano/agent/agentic_edit_tools/plan_mode_respond_tool.py +35 -0
  12. autocoder_nano/agent/agentic_edit_tools/read_file_tool.py +73 -0
  13. autocoder_nano/agent/agentic_edit_tools/replace_in_file_tool.py +148 -0
  14. autocoder_nano/agent/agentic_edit_tools/search_files_tool.py +135 -0
  15. autocoder_nano/agent/agentic_edit_tools/write_to_file_tool.py +57 -0
  16. autocoder_nano/agent/agentic_edit_types.py +151 -0
  17. autocoder_nano/auto_coder_nano.py +145 -91
  18. autocoder_nano/git_utils.py +63 -1
  19. autocoder_nano/llm_client.py +170 -3
  20. autocoder_nano/llm_types.py +53 -14
  21. autocoder_nano/rules/rules_learn.py +221 -0
  22. autocoder_nano/templates.py +1 -1
  23. autocoder_nano/utils/formatted_log_utils.py +128 -0
  24. autocoder_nano/utils/printer_utils.py +5 -4
  25. autocoder_nano/utils/shell_utils.py +85 -0
  26. autocoder_nano/version.py +1 -1
  27. {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/METADATA +3 -2
  28. {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/RECORD +33 -16
  29. autocoder_nano/agent/new/auto_new_project.py +0 -278
  30. /autocoder_nano/{agent/new → rules}/__init__.py +0 -0
  31. {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/LICENSE +0 -0
  32. {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/WHEEL +0 -0
  33. {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/entry_points.txt +0 -0
  34. {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.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 import ExecuteCommandToolResolver
4
+ from .read_file_tool import ReadFileToolResolver
5
+ from .write_to_file_tool import WriteToFileToolResolver
6
+ from .replace_in_file_tool import ReplaceInFileToolResolver
7
+ from .search_files_tool import SearchFilesToolResolver
8
+ from .list_files_tool import ListFilesToolResolver
9
+ from .list_code_definition_names_tool import ListCodeDefinitionNamesToolResolver
10
+ from .ask_followup_question_tool import AskFollowupQuestionToolResolver
11
+ from .attempt_completion_tool import AttemptCompletionToolResolver
12
+ from .plan_mode_respond_tool import PlanModeRespondToolResolver
13
+ from .list_package_info_tool import ListPackageInfoToolResolver
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
+ "ListPackageInfoToolResolver",
28
+ ]
@@ -0,0 +1,51 @@
1
+ import typing
2
+ from typing import Optional
3
+
4
+ from prompt_toolkit import PromptSession
5
+
6
+ from autocoder_nano.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
7
+ from autocoder_nano.agent.agentic_edit_types import ToolResult, AskFollowupQuestionTool
8
+ from autocoder_nano.llm_types import AutoCoderArgs
9
+ from autocoder_nano.utils.printer_utils import Printer
10
+
11
+ if typing.TYPE_CHECKING:
12
+ from autocoder_nano.agent.agentic_edit import AgenticEdit
13
+
14
+ printer = Printer()
15
+
16
+
17
+ class AskFollowupQuestionToolResolver(BaseToolResolver):
18
+ def __init__(self, agent: Optional['AgenticEdit'], tool: AskFollowupQuestionTool, args: AutoCoderArgs):
19
+ super().__init__(agent, tool, args)
20
+ self.tool: AskFollowupQuestionTool = tool
21
+
22
+ def resolve(self) -> ToolResult:
23
+ """
24
+ Packages the question and options to be handled by the main loop/UI.
25
+ This resolver doesn't directly ask the user but prepares the data for it.
26
+ """
27
+ question = self.tool.question
28
+ options = self.tool.options or []
29
+ options_text = "\n".join([f"{i + 1}. {option}" for i, option in enumerate(options)])
30
+
31
+ # 创建一个醒目的问题面板
32
+ printer.print_panel(
33
+ content=question,
34
+ title="[bold yellow]AutoCoder Nano 的提问[/bold yellow]",
35
+ border_style="blue",
36
+ center=True
37
+ )
38
+
39
+ session = PromptSession(message="> 您的回复是: ")
40
+ try:
41
+ answer = session.prompt()
42
+ except KeyboardInterrupt:
43
+ answer = ""
44
+
45
+ # The actual asking logic resides outside the resolver, typically in the agent's main loop
46
+ # or UI interaction layer. The resolver's job is to validate and package the request.
47
+ if not answer:
48
+ return ToolResult(success=False, message="错误: 问题未得到回答.")
49
+
50
+ # Indicate success in preparing the question data
51
+ return ToolResult(success=True, message="已生成后续追问问题.", content=answer)
@@ -0,0 +1,36 @@
1
+ import typing
2
+ from typing import Optional
3
+
4
+ from autocoder_nano.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
5
+ from autocoder_nano.agent.agentic_edit_types import ToolResult, AttemptCompletionTool
6
+ from autocoder_nano.llm_types import AutoCoderArgs
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from autocoder_nano.agent.agentic_edit import AgenticEdit
10
+
11
+
12
+ class AttemptCompletionToolResolver(BaseToolResolver):
13
+ def __init__(self, agent: Optional['AgenticEdit'], tool: AttemptCompletionTool, args: AutoCoderArgs):
14
+ super().__init__(agent, tool, args)
15
+ self.tool: AttemptCompletionTool = tool
16
+
17
+ def resolve(self) -> ToolResult:
18
+ """
19
+ Packages the completion result and optional command to signal task completion.
20
+ """
21
+ result_text = self.tool.result
22
+ command = self.tool.command
23
+
24
+ # logger.info(f"Resolving AttemptCompletionTool: Result='{result_text[:100]}...', Command='{command}'")
25
+
26
+ if not result_text:
27
+ return ToolResult(success=False, message="错误:生成结果不能为空.")
28
+
29
+ # The actual presentation of the result happens outside the resolver.
30
+ result_content = {
31
+ "result": result_text,
32
+ "command": command
33
+ }
34
+
35
+ # Indicate success in preparing the completion data
36
+ return ToolResult(success=True, message="尝试完成任务.", content=result_content)
@@ -0,0 +1,31 @@
1
+ import typing
2
+ from abc import ABC, abstractmethod
3
+
4
+ from autocoder_nano.agent.agentic_edit_types import *
5
+ from autocoder_nano.llm_types import AutoCoderArgs
6
+
7
+ if typing.TYPE_CHECKING:
8
+ from autocoder_nano.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
+ Args:
16
+ agent: The AutoCoder agent instance.
17
+ tool: The Pydantic model instance representing the tool call.
18
+ args: Additional arguments needed for execution (e.g., source_dir).
19
+ """
20
+ self.agent = agent
21
+ self.tool = tool
22
+ self.args = args
23
+
24
+ @abstractmethod
25
+ def resolve(self) -> ToolResult:
26
+ """
27
+ Executes the tool's logic.
28
+ Returns:
29
+ A ToolResult object indicating success or failure and a message.
30
+ """
31
+ pass
@@ -0,0 +1,65 @@
1
+ import typing
2
+ from typing import Optional
3
+
4
+ from autocoder_nano.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
5
+ from autocoder_nano.agent.agentic_edit_types import ExecuteCommandTool, ToolResult
6
+ from autocoder_nano.llm_types import AutoCoderArgs
7
+ from autocoder_nano.utils.printer_utils import Printer
8
+ from autocoder_nano.utils.shell_utils import run_cmd_subprocess
9
+
10
+ if typing.TYPE_CHECKING:
11
+ from autocoder_nano.agent.agentic_edit import AgenticEdit
12
+
13
+
14
+ printer = Printer()
15
+
16
+
17
+ class ExecuteCommandToolResolver(BaseToolResolver):
18
+ def __init__(self, agent: Optional['AgenticEdit'], tool: ExecuteCommandTool, args: AutoCoderArgs):
19
+ super().__init__(agent, tool, args)
20
+ self.tool: ExecuteCommandTool = tool # For type hinting
21
+
22
+ @staticmethod
23
+ def _prune_file_content(content: str, file_path: str) -> str:
24
+ """
25
+ 该函数主要目的是对命令行执行后的结果内容进行剪枝处理
26
+ 因为执行的命令可能包含
27
+ - 读取大量文件
28
+ 等操作
29
+ todo: 该函数目前暂时未实现,直接返回全部结果
30
+ """
31
+ return content
32
+
33
+ def resolve(self) -> ToolResult:
34
+ command = self.tool.command
35
+ requires_approval = self.tool.requires_approval
36
+ source_dir = self.args.source_dir or "."
37
+
38
+ try:
39
+ exit_code, output = run_cmd_subprocess(command, verbose=False, cwd=source_dir)
40
+
41
+ printer.print_key_value(
42
+ items={"执行命令": f"{command}", "返回 Code": f"{exit_code}", "输出大小": f"{len(output)} chars"},
43
+ title="使用 run_cmd_subprocess 执行命令工具"
44
+ )
45
+
46
+ final_output = self._prune_file_content(output, "command_output")
47
+
48
+ if exit_code == 0:
49
+ return ToolResult(success=True, message="Command executed successfully.", content=final_output)
50
+ else:
51
+ # For the human-readable error message, we might prefer the original full output.
52
+ # For the agent-consumable content, we provide the (potentially pruned) final_output.
53
+ error_message_for_human = f"Command failed with return code {exit_code}.\nOutput:\n{output}"
54
+ return ToolResult(success=False, message=error_message_for_human,
55
+ content={"output": final_output, "returncode": exit_code})
56
+
57
+ except FileNotFoundError:
58
+ return ToolResult(success=False,
59
+ message=f"Error: The command '{command.split()[0]}' was not found. Please ensure it is "
60
+ f"installed and in the system's PATH.")
61
+ except PermissionError:
62
+ return ToolResult(success=False, message=f"Error: Permission denied when trying to execute '{command}'.")
63
+ except Exception as e:
64
+ return ToolResult(success=False,
65
+ message=f"An unexpected error occurred while executing the command: {str(e)}")
@@ -0,0 +1,78 @@
1
+ import os
2
+ import typing
3
+ from typing import Optional
4
+
5
+ from autocoder_nano.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
6
+ from autocoder_nano.agent.agentic_edit_types import ToolResult, ListCodeDefinitionNamesTool
7
+ from autocoder_nano.index.index_manager import IndexManager
8
+ from autocoder_nano.index.symbols_utils import extract_symbols
9
+ from autocoder_nano.llm_types import AutoCoderArgs
10
+
11
+ if typing.TYPE_CHECKING:
12
+ from autocoder_nano.agent.agentic_edit import AgenticEdit
13
+
14
+
15
+ class ListCodeDefinitionNamesToolResolver(BaseToolResolver):
16
+ def __init__(self, agent: Optional['AgenticEdit'], tool: ListCodeDefinitionNamesTool, args: AutoCoderArgs):
17
+ super().__init__(agent, tool, args)
18
+ self.tool: ListCodeDefinitionNamesTool = tool
19
+ self.llm = self.agent.llm
20
+
21
+ def _get_index(self):
22
+ index_manager = IndexManager(
23
+ args=self.args,
24
+ source_codes=[],
25
+ llm=self.llm)
26
+ return index_manager
27
+
28
+ def resolve(self) -> ToolResult:
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.source_dir or "."
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,
39
+ message=f"错误: 拒绝访问, 尝试分析项目目录之外的代码 {target_path_str}")
40
+
41
+ if not os.path.exists(absolute_target_path):
42
+ return ToolResult(success=False, message=f"错误: 路径不存在 {target_path_str}")
43
+
44
+ try:
45
+ # Use RepoParser or a similar mechanism to extract definitions
46
+ # RepoParser might need adjustments or a specific method for this tool's purpose.
47
+ # This is a placeholder implementation. A real implementation needs robust code parsing.
48
+ # logger.info(f"Analyzing definitions in: {absolute_target_path}")
49
+ all_symbols = []
50
+
51
+ if os.path.isfile(absolute_target_path):
52
+ file_paths = [absolute_target_path]
53
+ else:
54
+ return ToolResult(success=False,
55
+ message=f"错误:路径既不是文件也不是目录 {target_path_str}")
56
+
57
+ for file_path in file_paths:
58
+ try:
59
+ item = index_data[file_path]
60
+ symbols_str = item.symbols
61
+ symbols = extract_symbols(symbols_str)
62
+ if symbols:
63
+ all_symbols.append({
64
+ "path": file_path,
65
+ "definitions": [{"name": s, "type": "function"} for s in symbols.functions] + [
66
+ {"name": s, "type": "variable"} for s in symbols.variables] + [
67
+ {"name": s, "type": "class"} for s in symbols.classes]
68
+ })
69
+ except Exception as e:
70
+ # logger.warning(f"Could not parse symbols from {file_path}: {e}")
71
+ pass
72
+ total_symbols = sum(len(s['definitions']) for s in all_symbols)
73
+ message = f"成功从目标路径 '{target_path_str}' 的 {len(all_symbols)} 个文件中提取了 {total_symbols} 个定义."
74
+ return ToolResult(success=True, message=message, content=all_symbols)
75
+
76
+ except Exception as e:
77
+ return ToolResult(success=False,
78
+ message=f"提取 Code Definitions 时发生错误: {str(e)}")
@@ -0,0 +1,123 @@
1
+ import os
2
+ import re
3
+ import typing
4
+ from typing import Optional, List, Union, Set
5
+
6
+ from autocoder_nano.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
7
+ from autocoder_nano.agent.agentic_edit_types import ListFilesTool, ToolResult
8
+ from autocoder_nano.llm_types import AutoCoderArgs
9
+ from autocoder_nano.sys_utils import default_exclude_dirs
10
+
11
+ if typing.TYPE_CHECKING:
12
+ from autocoder_nano.agent.agentic_edit import AgenticEdit
13
+
14
+
15
+ class ListFilesToolResolver(BaseToolResolver):
16
+ def __init__(self, agent: Optional['AgenticEdit'], tool: ListFilesTool, args: AutoCoderArgs):
17
+ super().__init__(agent, tool, args)
18
+ self.tool: ListFilesTool = tool
19
+ self.exclude_files = args.exclude_files + default_exclude_dirs
20
+ self.exclude_patterns = self.parse_exclude_files(self.exclude_files)
21
+
22
+ @staticmethod
23
+ def parse_exclude_files(exclude_files):
24
+ if not exclude_files:
25
+ return []
26
+
27
+ if isinstance(exclude_files, str):
28
+ exclude_files = [exclude_files]
29
+
30
+ exclude_patterns = []
31
+ for pattern in exclude_files:
32
+ if pattern.startswith("regex://"):
33
+ pattern = pattern[8:]
34
+ exclude_patterns.append(re.compile(pattern))
35
+ else:
36
+ exclude_patterns.append(re.compile(pattern))
37
+ return exclude_patterns
38
+
39
+ def should_exclude(self, file_path):
40
+ for pattern in self.exclude_patterns:
41
+ if pattern.search(file_path):
42
+ return True
43
+ return False
44
+
45
+ def list_files_in_dir(self, base_dir: str, recursive: bool, source_dir: str, is_outside_source: bool) -> Set[str]:
46
+ """Helper function to list files in a directory"""
47
+ result = set()
48
+ try:
49
+ if recursive:
50
+ for root, dirs, files in os.walk(base_dir):
51
+ # Modify dirs in-place to skip ignored dirs early
52
+ dirs[:] = [d for d in dirs if not self.should_exclude(os.path.join(root, d))]
53
+ for name in files:
54
+ full_path = os.path.join(root, name)
55
+ if self.should_exclude(full_path):
56
+ continue
57
+ display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
58
+ result.add(display_path)
59
+ for d in dirs:
60
+ full_path = os.path.join(root, d)
61
+ display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
62
+ result.add(display_path + "/")
63
+ else:
64
+ for item in os.listdir(base_dir):
65
+ full_path = os.path.join(base_dir, item)
66
+ if self.should_exclude(full_path):
67
+ continue
68
+ display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
69
+ if os.path.isdir(full_path):
70
+ result.add(display_path + "/")
71
+ else:
72
+ result.add(display_path)
73
+ except Exception as e:
74
+ pass
75
+ # logger.warning(f"Error listing files in {base_dir}: {e}")
76
+ return result
77
+
78
+ def list_files_normal(
79
+ self, list_path_str: str, recursive: bool, source_dir: str, absolute_source_dir: str,
80
+ absolute_list_path: str) -> Union[ToolResult, List[str]]:
81
+ """List files directly without using shadow manager"""
82
+ # Security check: Allow listing outside source_dir IF the original path is outside?
83
+ is_outside_source = not absolute_list_path.startswith(absolute_source_dir)
84
+ if is_outside_source:
85
+ return ToolResult(success=False,
86
+ message=f"错误: 拒绝访问, 尝试列出项目目录之外的文件: {list_path_str}")
87
+
88
+ if not os.path.exists(absolute_list_path):
89
+ return ToolResult(success=False, message=f"错误: 路径未找到 {list_path_str}")
90
+ if not os.path.isdir(absolute_list_path):
91
+ return ToolResult(success=False, message=f"错误: 路径不是目录 {list_path_str}")
92
+
93
+ # Collect files from the directory
94
+ files_set = self.list_files_in_dir(absolute_list_path, recursive, source_dir, is_outside_source)
95
+
96
+ try:
97
+ # Successfully listed contents of '{list_path_str}' (Recursive: {recursive}). Found {len(files_set)} items.
98
+ return sorted(files_set)
99
+ except Exception as e:
100
+ return ToolResult(success=False, message=f"列出文件时发生意外错误: {str(e)}")
101
+
102
+ def resolve(self) -> ToolResult:
103
+ """Resolve the list files tool by calling the appropriate implementation"""
104
+ list_path_str = self.tool.path
105
+ recursive = self.tool.recursive or False
106
+ source_dir = self.args.source_dir or "."
107
+ absolute_source_dir = os.path.abspath(source_dir)
108
+ absolute_list_path = os.path.abspath(os.path.join(source_dir, list_path_str))
109
+
110
+ result = self.list_files_normal(list_path_str, recursive, source_dir, absolute_source_dir, absolute_list_path)
111
+
112
+ if isinstance(result, list):
113
+ total_items = len(result)
114
+ # Limit results to 200 if needed
115
+ if total_items > 200:
116
+ truncated_result = result[:200]
117
+ message = f"成功获取目录内容: {list_path_str} (递归:{recursive}), 总计 {total_items} 项, 当前截取前 200 条."
118
+ return ToolResult(success=True, message=message, content=truncated_result)
119
+ else:
120
+ message = f"成功获取目录内容: {list_path_str} (递归:{recursive}), 总计 {total_items} 项."
121
+ return ToolResult(success=True, message=message, content=result)
122
+ else:
123
+ return result
@@ -0,0 +1,42 @@
1
+ import os
2
+ import typing
3
+ from typing import Optional
4
+
5
+ from autocoder_nano.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
6
+ from autocoder_nano.agent.agentic_edit_types import ToolResult, ListPackageInfoTool
7
+ from autocoder_nano.llm_types import AutoCoderArgs
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from autocoder_nano.agent.agentic_edit import AgenticEdit
11
+
12
+
13
+ class ListPackageInfoToolResolver(BaseToolResolver):
14
+ def __init__(self, agent: Optional['AgenticEdit'], tool: ListPackageInfoTool, args: AutoCoderArgs):
15
+ super().__init__(agent, tool, args)
16
+ self.tool: ListPackageInfoTool = tool
17
+
18
+ def resolve(self) -> ToolResult:
19
+ source_dir = self.args.source_dir or "."
20
+ abs_source_dir = os.path.abspath(source_dir)
21
+
22
+ input_path = self.tool.path.strip()
23
+ abs_input_path = os.path.abspath(os.path.join(source_dir, input_path)) if not os.path.isabs(
24
+ input_path) else input_path
25
+
26
+ # 校验输入目录是否在项目目录内
27
+ if not abs_input_path.startswith(abs_source_dir):
28
+ return ToolResult(success=False, message=f"错误: 访问被拒, 路径超出项目范围 {self.tool.path}")
29
+
30
+ rel_package_path = os.path.relpath(abs_input_path, abs_source_dir)
31
+ active_md_path = os.path.join(abs_source_dir, ".auto-coder", "active-context", rel_package_path, "active.md")
32
+
33
+ if not os.path.exists(active_md_path):
34
+ return ToolResult(success=True, message="该路径下未找到包信息.", content="没有相关包信息.")
35
+
36
+ try:
37
+ with open(active_md_path, 'r', encoding='utf-8', errors='replace') as f:
38
+ content = f.read()
39
+ return ToolResult(success=True, message="成功获取包信息.", content=content)
40
+ except Exception as e:
41
+
42
+ return ToolResult(success=False, message=f"读取包信息文件失败: {e}")
@@ -0,0 +1,35 @@
1
+ import typing
2
+ from typing import Optional
3
+
4
+ from autocoder_nano.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
5
+ from autocoder_nano.agent.agentic_edit_types import ToolResult, AttemptCompletionTool, PlanModeRespondTool
6
+ from autocoder_nano.llm_types import AutoCoderArgs
7
+
8
+ if typing.TYPE_CHECKING:
9
+ from autocoder_nano.agent.agentic_edit import AgenticEdit
10
+
11
+
12
+ class PlanModeRespondToolResolver(BaseToolResolver):
13
+ def __init__(self, agent: Optional['AgenticEdit'], tool: PlanModeRespondTool, args: AutoCoderArgs):
14
+ super().__init__(agent, tool, args)
15
+ self.tool: PlanModeRespondTool = tool # For type hinting
16
+
17
+ def resolve(self) -> ToolResult:
18
+ """
19
+ Packages the response and options for Plan Mode interaction.
20
+ """
21
+ response_text = self.tool.response
22
+ options = self.tool.options
23
+ # logger.info(f"Resolving PlanModeRespondTool: Response='{response_text[:100]}...', Options={options}")
24
+
25
+ if not response_text:
26
+ return ToolResult(success=False, message="错误:规划模式返回结果不可为空.")
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="规划模式响应已就绪.", content=result_content)
@@ -0,0 +1,73 @@
1
+ import os
2
+ import typing
3
+ from typing import Optional
4
+
5
+ from autocoder_nano.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
6
+ from autocoder_nano.agent.agentic_edit_types import ReadFileTool, ToolResult
7
+ from autocoder_nano.llm_types import AutoCoderArgs
8
+
9
+ if typing.TYPE_CHECKING:
10
+ from autocoder_nano.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
+ @staticmethod
20
+ def _prune_file_content(content: str, file_path: str) -> str:
21
+ """
22
+ 该函数主要目的是对命令行执行后的结果内容进行剪枝处理
23
+ 因为执行的命令可能包含
24
+ - 读取大量文件
25
+ 等操作
26
+ todo: 该函数目前暂时未实现,直接返回全部结果
27
+ """
28
+ return content
29
+
30
+ def _read_file_content(self, file_path_to_read: str) -> str:
31
+ content = ""
32
+ ext = os.path.splitext(file_path_to_read)[1].lower()
33
+
34
+ if ext == '.pdf':
35
+ # content = extract_text_from_pdf(file_path_to_read)
36
+ # todo: 解析pdf文件
37
+ pass
38
+ elif ext == '.docx':
39
+ # content = extract_text_from_docx(file_path_to_read)
40
+ # todo: 解析doc文件
41
+ pass
42
+ elif ext in ('.pptx', '.ppt'):
43
+ pass # todo: 解析ppt文件
44
+ else:
45
+ with open(file_path_to_read, 'r', encoding='utf-8', errors='replace') as f:
46
+ content = f.read()
47
+
48
+ # 对内容进行剪枝处理
49
+ return self._prune_file_content(content, file_path_to_read)
50
+
51
+ def read_file_normal(self, file_path: str, source_dir: str, abs_project_dir: str, abs_file_path: str) -> ToolResult:
52
+ """Read file directly without using shadow manager"""
53
+ try:
54
+ if not os.path.exists(abs_file_path):
55
+ return ToolResult(success=False, message=f"错误:未找到文件路径:{file_path}")
56
+ if not os.path.isfile(abs_file_path):
57
+ return ToolResult(success=False, message=f"错误:该路径不是文件:{file_path}")
58
+
59
+ content = self._read_file_content(abs_file_path)
60
+ return ToolResult(success=True, message=f"{file_path}", content=content)
61
+
62
+ except Exception as e:
63
+ return ToolResult(success=False,
64
+ message=f"An error occurred while processing the file '{file_path}': {str(e)}")
65
+
66
+ def resolve(self) -> ToolResult:
67
+ """Resolve the read file tool by calling the appropriate implementation"""
68
+ file_path = self.tool.path
69
+ source_dir = self.args.source_dir or "."
70
+ abs_project_dir = os.path.abspath(source_dir)
71
+ abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
72
+
73
+ return self.read_file_normal(file_path, source_dir, abs_project_dir, abs_file_path)