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.
- autocoder_nano/agent/agent_base.py +4 -4
- autocoder_nano/agent/agentic_edit.py +1584 -0
- autocoder_nano/agent/agentic_edit_tools/__init__.py +28 -0
- autocoder_nano/agent/agentic_edit_tools/ask_followup_question_tool.py +51 -0
- autocoder_nano/agent/agentic_edit_tools/attempt_completion_tool.py +36 -0
- autocoder_nano/agent/agentic_edit_tools/base_tool_resolver.py +31 -0
- autocoder_nano/agent/agentic_edit_tools/execute_command_tool.py +65 -0
- autocoder_nano/agent/agentic_edit_tools/list_code_definition_names_tool.py +78 -0
- autocoder_nano/agent/agentic_edit_tools/list_files_tool.py +123 -0
- autocoder_nano/agent/agentic_edit_tools/list_package_info_tool.py +42 -0
- autocoder_nano/agent/agentic_edit_tools/plan_mode_respond_tool.py +35 -0
- autocoder_nano/agent/agentic_edit_tools/read_file_tool.py +73 -0
- autocoder_nano/agent/agentic_edit_tools/replace_in_file_tool.py +148 -0
- autocoder_nano/agent/agentic_edit_tools/search_files_tool.py +135 -0
- autocoder_nano/agent/agentic_edit_tools/write_to_file_tool.py +57 -0
- autocoder_nano/agent/agentic_edit_types.py +151 -0
- autocoder_nano/auto_coder_nano.py +145 -91
- autocoder_nano/git_utils.py +63 -1
- autocoder_nano/llm_client.py +170 -3
- autocoder_nano/llm_types.py +53 -14
- autocoder_nano/rules/rules_learn.py +221 -0
- autocoder_nano/templates.py +1 -1
- autocoder_nano/utils/formatted_log_utils.py +128 -0
- autocoder_nano/utils/printer_utils.py +5 -4
- autocoder_nano/utils/shell_utils.py +85 -0
- autocoder_nano/version.py +1 -1
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/METADATA +3 -2
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/RECORD +33 -16
- autocoder_nano/agent/new/auto_new_project.py +0 -278
- /autocoder_nano/{agent/new → rules}/__init__.py +0 -0
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/LICENSE +0 -0
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/WHEEL +0 -0
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.33.dist-info}/entry_points.txt +0 -0
- {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)
|