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,148 @@
|
|
1
|
+
import os
|
2
|
+
import typing
|
3
|
+
from typing import Tuple
|
4
|
+
|
5
|
+
from autocoder_nano.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
|
6
|
+
from autocoder_nano.agent.agentic_edit_types import *
|
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 ReplaceInFileToolResolver(BaseToolResolver):
|
14
|
+
def __init__(self, agent: Optional['AgenticEdit'], tool: ReplaceInFileTool, args: AutoCoderArgs):
|
15
|
+
super().__init__(agent, tool, args)
|
16
|
+
self.tool: ReplaceInFileTool = tool # For type hinting
|
17
|
+
self.args = args
|
18
|
+
|
19
|
+
@staticmethod
|
20
|
+
def parse_diff(diff_content: str) -> List[Tuple[str, str]]:
|
21
|
+
"""
|
22
|
+
Parses the diff content into a list of (search_block, replace_block) tuples.
|
23
|
+
"""
|
24
|
+
blocks = []
|
25
|
+
lines = diff_content.splitlines(keepends=True)
|
26
|
+
i = 0
|
27
|
+
n = len(lines)
|
28
|
+
|
29
|
+
while i < n:
|
30
|
+
line = lines[i]
|
31
|
+
if line.strip() == "<<<<<<< SEARCH":
|
32
|
+
i += 1
|
33
|
+
search_lines = []
|
34
|
+
# Accumulate search block
|
35
|
+
while i < n and lines[i].strip() != "=======":
|
36
|
+
search_lines.append(lines[i])
|
37
|
+
i += 1
|
38
|
+
if i >= n:
|
39
|
+
# warning: Unterminated SEARCH block found in diff content.
|
40
|
+
break
|
41
|
+
i += 1 # skip '======='
|
42
|
+
replace_lines = []
|
43
|
+
# Accumulate replace block
|
44
|
+
while i < n and lines[i].strip() != ">>>>>>> REPLACE":
|
45
|
+
replace_lines.append(lines[i])
|
46
|
+
i += 1
|
47
|
+
if i >= n:
|
48
|
+
# warning: Unterminated REPLACE block found in diff content.
|
49
|
+
break
|
50
|
+
i += 1 # skip '>>>>>>> REPLACE'
|
51
|
+
|
52
|
+
search_block = ''.join(search_lines)
|
53
|
+
replace_block = ''.join(replace_lines)
|
54
|
+
blocks.append((search_block, replace_block))
|
55
|
+
else:
|
56
|
+
i += 1
|
57
|
+
|
58
|
+
if not blocks and diff_content.strip():
|
59
|
+
pass
|
60
|
+
# warning: Could not parse any SEARCH/REPLACE blocks from diff: {diff_content}
|
61
|
+
return blocks
|
62
|
+
|
63
|
+
def replace_in_file_normal(
|
64
|
+
self, file_path: str, diff_content: str, source_dir: str, abs_project_dir: str, abs_file_path: str
|
65
|
+
) -> ToolResult:
|
66
|
+
"""Replace content in file directly without using shadow manager"""
|
67
|
+
try:
|
68
|
+
if not os.path.exists(abs_file_path):
|
69
|
+
return ToolResult(success=False, message=f"错误:未找到文件路径:{file_path}")
|
70
|
+
if not os.path.isfile(abs_file_path):
|
71
|
+
return ToolResult(success=False, message=f"错误:该路径不是文件:{file_path}")
|
72
|
+
|
73
|
+
with open(abs_file_path, 'r', encoding='utf-8', errors='replace') as f:
|
74
|
+
original_content = f.read()
|
75
|
+
|
76
|
+
parsed_blocks = self.parse_diff(diff_content)
|
77
|
+
if not parsed_blocks:
|
78
|
+
return ToolResult(success=False, message="错误:在提供的diff中未找到有效的SEARCH/REPLACE代码块. ")
|
79
|
+
|
80
|
+
current_content = original_content
|
81
|
+
applied_count = 0
|
82
|
+
errors = []
|
83
|
+
|
84
|
+
# Apply blocks sequentially
|
85
|
+
for i, (search_block, replace_block) in enumerate(parsed_blocks):
|
86
|
+
start_index = current_content.find(search_block)
|
87
|
+
|
88
|
+
if start_index != -1:
|
89
|
+
current_content = current_content[:start_index] + replace_block + current_content[
|
90
|
+
start_index + len(search_block):]
|
91
|
+
applied_count += 1
|
92
|
+
# f"Applied SEARCH/REPLACE block {i + 1} in file {file_path}"
|
93
|
+
else:
|
94
|
+
error_message = (f"SEARCH block {i+1} not found in the current file content. Content to "
|
95
|
+
f"search:\n---\n{search_block}\n---")
|
96
|
+
# logger.warning(error_message)
|
97
|
+
context_start = max(0, original_content.find(search_block[:20]) - 100)
|
98
|
+
context_end = min(len(original_content), context_start + 200 + len(search_block[:20]))
|
99
|
+
# warning: f"Approximate context in file:\n---\n{original_content[context_start:context_end]}\n---"
|
100
|
+
errors.append(error_message)
|
101
|
+
|
102
|
+
return_errors = "\n".join(errors)
|
103
|
+
if applied_count == 0 and errors:
|
104
|
+
return ToolResult(success=False, message=f"未能应用任何更改, 错误信息: {return_errors}")
|
105
|
+
|
106
|
+
# todo: 应该是先备份,再写入, 参考 autocoder checkpoint_manager
|
107
|
+
|
108
|
+
with open(abs_file_path, 'w', encoding='utf-8') as f:
|
109
|
+
f.write(current_content)
|
110
|
+
|
111
|
+
# info: f"已成功将 {applied_count}/{len(parsed_blocks)} 个更改应用到文件:{file_path}"
|
112
|
+
|
113
|
+
# todo: 写入后执行代码质量检查
|
114
|
+
|
115
|
+
# 构建包含 lint 结果的返回消息
|
116
|
+
if errors:
|
117
|
+
message = f"成功应用了 {applied_count}/{len(parsed_blocks)} 个更改到文件:{file_path}. \n警告信息: \n{return_errors}"
|
118
|
+
else:
|
119
|
+
message = f"成功应用了 {applied_count}/{len(parsed_blocks)} 个更改到文件:{file_path}"
|
120
|
+
|
121
|
+
# 变更跟踪,回调AgenticEdit
|
122
|
+
if self.agent:
|
123
|
+
rel_path = os.path.relpath(abs_file_path, abs_project_dir)
|
124
|
+
self.agent.record_file_change(
|
125
|
+
rel_path, "modified", diff=diff_content, content=current_content)
|
126
|
+
|
127
|
+
result_content = {"content": current_content}
|
128
|
+
|
129
|
+
return ToolResult(success=True, message=message, content=result_content)
|
130
|
+
except Exception as e:
|
131
|
+
return ToolResult(success=False,
|
132
|
+
message=f"An error occurred while processing the file '{file_path}': {str(e)}")
|
133
|
+
|
134
|
+
def resolve(self) -> ToolResult:
|
135
|
+
"""Resolve the replacement in file tool by calling the appropriate implementation"""
|
136
|
+
file_path = self.tool.path
|
137
|
+
diff_content = self.tool.diff
|
138
|
+
source_dir = self.args.source_dir or "."
|
139
|
+
abs_project_dir = os.path.abspath(source_dir)
|
140
|
+
abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
|
141
|
+
|
142
|
+
# 安全检查
|
143
|
+
if not abs_file_path.startswith(abs_project_dir):
|
144
|
+
return ToolResult(
|
145
|
+
success=False,
|
146
|
+
message=f"错误: 拒绝访问, 尝试修改项目目录之外的文件:{file_path}")
|
147
|
+
|
148
|
+
return self.replace_in_file_normal(file_path, diff_content, source_dir, abs_project_dir, abs_file_path)
|
@@ -0,0 +1,135 @@
|
|
1
|
+
import glob
|
2
|
+
import os
|
3
|
+
import re
|
4
|
+
import typing
|
5
|
+
from typing import Optional, List, Dict, Any, Union
|
6
|
+
|
7
|
+
from autocoder_nano.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
|
8
|
+
from autocoder_nano.agent.agentic_edit_types import SearchFilesTool, ToolResult
|
9
|
+
from autocoder_nano.llm_types import AutoCoderArgs
|
10
|
+
from autocoder_nano.sys_utils import default_exclude_dirs
|
11
|
+
|
12
|
+
if typing.TYPE_CHECKING:
|
13
|
+
from autocoder_nano.agent.agentic_edit import AgenticEdit
|
14
|
+
|
15
|
+
|
16
|
+
class SearchFilesToolResolver(BaseToolResolver):
|
17
|
+
def __init__(self, agent: Optional['AgenticEdit'], tool: SearchFilesTool, args: AutoCoderArgs):
|
18
|
+
super().__init__(agent, tool, args)
|
19
|
+
self.tool: SearchFilesTool = tool
|
20
|
+
self.exclude_files = args.exclude_files + default_exclude_dirs
|
21
|
+
self.exclude_patterns = self.parse_exclude_files(self.exclude_files)
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def parse_exclude_files(exclude_files):
|
25
|
+
if not exclude_files:
|
26
|
+
return []
|
27
|
+
|
28
|
+
if isinstance(exclude_files, str):
|
29
|
+
exclude_files = [exclude_files]
|
30
|
+
|
31
|
+
exclude_patterns = []
|
32
|
+
for pattern in exclude_files:
|
33
|
+
if pattern.startswith("regex://"):
|
34
|
+
pattern = pattern[8:]
|
35
|
+
exclude_patterns.append(re.compile(pattern))
|
36
|
+
else:
|
37
|
+
exclude_patterns.append(re.compile(pattern))
|
38
|
+
return exclude_patterns
|
39
|
+
|
40
|
+
def should_exclude(self, file_path):
|
41
|
+
for pattern in self.exclude_patterns:
|
42
|
+
if pattern.search(file_path):
|
43
|
+
return True
|
44
|
+
return False
|
45
|
+
|
46
|
+
def search_in_dir(
|
47
|
+
self, base_dir: str, regex_pattern: str, file_pattern: str, source_dir: str,
|
48
|
+
is_shadow: bool = False, compiled_regex: Optional[re.Pattern] = None
|
49
|
+
) -> List[Dict[str, Any]]:
|
50
|
+
"""Helper function to search in a directory"""
|
51
|
+
search_results = []
|
52
|
+
search_glob_pattern = os.path.join(base_dir, "**", file_pattern)
|
53
|
+
|
54
|
+
if compiled_regex is None:
|
55
|
+
compiled_regex = re.compile(regex_pattern)
|
56
|
+
|
57
|
+
for filepath in glob.glob(search_glob_pattern, recursive=True):
|
58
|
+
abs_path = os.path.abspath(filepath)
|
59
|
+
if self.should_exclude(abs_path):
|
60
|
+
continue
|
61
|
+
if os.path.isfile(filepath):
|
62
|
+
try:
|
63
|
+
with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
|
64
|
+
lines = f.readlines()
|
65
|
+
for i, line in enumerate(lines):
|
66
|
+
if compiled_regex.search(line):
|
67
|
+
context_start = max(0, i - 2)
|
68
|
+
context_end = min(len(lines), i + 3)
|
69
|
+
context = "".join([f"{j + 1}: {lines[j]}" for j in range(context_start, context_end)])
|
70
|
+
|
71
|
+
relative_path = os.path.relpath(filepath, source_dir)
|
72
|
+
|
73
|
+
search_results.append({
|
74
|
+
"path": relative_path,
|
75
|
+
"line_number": i + 1,
|
76
|
+
"match_line": line.strip(),
|
77
|
+
"context": context.strip()
|
78
|
+
})
|
79
|
+
except Exception as e:
|
80
|
+
# logger.warning(f"Could not read or process file {filepath}: {e}")
|
81
|
+
continue
|
82
|
+
|
83
|
+
return search_results
|
84
|
+
|
85
|
+
def search_files_normal(
|
86
|
+
self, search_path_str: str, regex_pattern: str, file_pattern: str, source_dir: str,
|
87
|
+
absolute_source_dir: str, absolute_search_path: str
|
88
|
+
) -> Union[ToolResult, List[Dict[str, Any]]]:
|
89
|
+
"""Search files directly without using shadow manager"""
|
90
|
+
# Security check
|
91
|
+
if not absolute_search_path.startswith(absolute_source_dir):
|
92
|
+
return ToolResult(success=False,
|
93
|
+
message=f"错误: 拒绝访问, 尝试搜索项目目录之外的文件: {search_path_str}")
|
94
|
+
|
95
|
+
# Validate that the directory exists
|
96
|
+
if not os.path.exists(absolute_search_path):
|
97
|
+
return ToolResult(success=False, message=f"错误: 搜索路径未找到 {search_path_str}")
|
98
|
+
if not os.path.isdir(absolute_search_path):
|
99
|
+
return ToolResult(success=False, message=f"错误: 搜错路径不是目录 {search_path_str}")
|
100
|
+
|
101
|
+
try:
|
102
|
+
compiled_regex = re.compile(regex_pattern)
|
103
|
+
# Search in the directory
|
104
|
+
search_results = self.search_in_dir(absolute_search_path, regex_pattern, file_pattern, source_dir,
|
105
|
+
is_shadow=False, compiled_regex=compiled_regex)
|
106
|
+
return search_results
|
107
|
+
except re.error as e:
|
108
|
+
return ToolResult(success=False, message=f"无效的正则表达式: {e}")
|
109
|
+
except Exception as e:
|
110
|
+
return ToolResult(success=False, message=f"搜索过程中出现未知错误: {str(e)}")
|
111
|
+
|
112
|
+
def resolve(self) -> ToolResult:
|
113
|
+
"""Resolve the search files tool by calling the appropriate implementation"""
|
114
|
+
search_path_str = self.tool.path
|
115
|
+
regex_pattern = self.tool.regex
|
116
|
+
file_pattern = self.tool.file_pattern or "*"
|
117
|
+
source_dir = self.args.source_dir or "."
|
118
|
+
absolute_source_dir = os.path.abspath(source_dir)
|
119
|
+
absolute_search_path = os.path.abspath(os.path.join(source_dir, search_path_str))
|
120
|
+
|
121
|
+
result = self.search_files_normal(
|
122
|
+
search_path_str, regex_pattern, file_pattern, source_dir, absolute_source_dir, absolute_search_path
|
123
|
+
)
|
124
|
+
|
125
|
+
if isinstance(result, list):
|
126
|
+
total_results = len(result)
|
127
|
+
if total_results > 200:
|
128
|
+
truncated_results = result[:200]
|
129
|
+
message = f"搜索完成. 总计匹配 {total_results} 条结果, 当前展示前 200 条"
|
130
|
+
return ToolResult(success=True, message=message, content=truncated_results)
|
131
|
+
else:
|
132
|
+
message = f"搜索完成. 总计匹配 {total_results} 条结果."
|
133
|
+
return ToolResult(success=True, message=message, content=result)
|
134
|
+
else:
|
135
|
+
return result
|
@@ -0,0 +1,57 @@
|
|
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 WriteToFileTool, 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 WriteToFileToolResolver(BaseToolResolver):
|
14
|
+
def __init__(self, agent: Optional['AgenticEdit'], tool: WriteToFileTool, args: AutoCoderArgs):
|
15
|
+
super().__init__(agent, tool, args)
|
16
|
+
self.tool: WriteToFileTool = tool # For type hinting
|
17
|
+
self.args = args
|
18
|
+
|
19
|
+
def write_file_normal(self, file_path: str, content: str, source_dir: str, abs_project_dir: str,
|
20
|
+
abs_file_path: str) -> ToolResult:
|
21
|
+
"""Write file directly without using shadow manager"""
|
22
|
+
try:
|
23
|
+
os.makedirs(os.path.dirname(abs_file_path), exist_ok=True)
|
24
|
+
|
25
|
+
if self.agent:
|
26
|
+
rel_path = os.path.relpath(abs_file_path, abs_project_dir)
|
27
|
+
self.agent.record_file_change(rel_path, "added", diff=None, content=content)
|
28
|
+
|
29
|
+
# todo: 应该是先备份,再写入, 参考 autocoder checkpoint_manager
|
30
|
+
|
31
|
+
with open(abs_file_path, 'w', encoding='utf-8') as f:
|
32
|
+
f.write(content)
|
33
|
+
|
34
|
+
# todo: 写入后执行代码质量检查
|
35
|
+
|
36
|
+
message = f"{file_path}"
|
37
|
+
result_content = {"content": content}
|
38
|
+
|
39
|
+
return ToolResult(success=True, message=message, content=result_content)
|
40
|
+
except Exception as e:
|
41
|
+
return ToolResult(success=False, message=f"An error occurred while writing to the file: {str(e)}")
|
42
|
+
|
43
|
+
def resolve(self) -> ToolResult:
|
44
|
+
"""Resolve the write file tool by calling the appropriate implementation"""
|
45
|
+
file_path = self.tool.path
|
46
|
+
content = self.tool.content
|
47
|
+
source_dir = self.args.source_dir or "."
|
48
|
+
abs_project_dir = os.path.abspath(source_dir)
|
49
|
+
abs_file_path = os.path.abspath(os.path.join(source_dir, file_path))
|
50
|
+
|
51
|
+
# Security check: ensure the path is within the source directory
|
52
|
+
if not abs_file_path.startswith(abs_project_dir):
|
53
|
+
return ToolResult(
|
54
|
+
success=False,
|
55
|
+
message=f"错误: 拒绝访问, 尝试修改项目目录之外的文件:{file_path}")
|
56
|
+
|
57
|
+
return self.write_file_normal(file_path, content, source_dir, abs_project_dir, abs_file_path)
|
@@ -0,0 +1,151 @@
|
|
1
|
+
from typing import List, Optional, Dict, Type, Any
|
2
|
+
|
3
|
+
from pydantic import BaseModel, SkipValidation
|
4
|
+
|
5
|
+
|
6
|
+
class FileChangeEntry(BaseModel):
|
7
|
+
""" 文件变更条目,用于记录文件的变更信息 """
|
8
|
+
type: str # 'added' 或 'modified'
|
9
|
+
diffs: List[str] = [] # 使用 replace_in_file 时,记录 diff 内容
|
10
|
+
content: Optional[str] = None # 使用 write_to_file 时,记录文件内容
|
11
|
+
|
12
|
+
|
13
|
+
class AgenticEditRequest(BaseModel):
|
14
|
+
user_input: str
|
15
|
+
|
16
|
+
|
17
|
+
# 工具的基本Pydantic模型
|
18
|
+
class BaseTool(BaseModel):
|
19
|
+
""" 代理工具的基类,所有工具类都应继承此类 """
|
20
|
+
pass
|
21
|
+
|
22
|
+
|
23
|
+
class ExecuteCommandTool(BaseTool):
|
24
|
+
command: str
|
25
|
+
requires_approval: bool
|
26
|
+
|
27
|
+
|
28
|
+
class ReadFileTool(BaseTool):
|
29
|
+
path: str
|
30
|
+
|
31
|
+
|
32
|
+
class WriteToFileTool(BaseTool):
|
33
|
+
path: str
|
34
|
+
content: str
|
35
|
+
|
36
|
+
|
37
|
+
class ReplaceInFileTool(BaseTool):
|
38
|
+
path: str
|
39
|
+
diff: str
|
40
|
+
|
41
|
+
|
42
|
+
class SearchFilesTool(BaseTool):
|
43
|
+
path: str
|
44
|
+
regex: str
|
45
|
+
file_pattern: Optional[str] = None
|
46
|
+
|
47
|
+
|
48
|
+
class ListFilesTool(BaseTool):
|
49
|
+
path: str
|
50
|
+
recursive: Optional[bool] = False
|
51
|
+
|
52
|
+
|
53
|
+
class ListCodeDefinitionNamesTool(BaseTool):
|
54
|
+
path: str
|
55
|
+
|
56
|
+
|
57
|
+
class AskFollowupQuestionTool(BaseTool):
|
58
|
+
question: str
|
59
|
+
options: Optional[List[str]] = None
|
60
|
+
|
61
|
+
|
62
|
+
class AttemptCompletionTool(BaseTool):
|
63
|
+
result: str
|
64
|
+
command: Optional[str] = None
|
65
|
+
|
66
|
+
|
67
|
+
class PlanModeRespondTool(BaseTool):
|
68
|
+
response: str
|
69
|
+
options: Optional[List[str]] = None
|
70
|
+
|
71
|
+
|
72
|
+
class UseRAGTool(BaseTool):
|
73
|
+
server_name: str
|
74
|
+
query: str
|
75
|
+
|
76
|
+
|
77
|
+
class ListPackageInfoTool(BaseTool):
|
78
|
+
path: str # 源码包目录,相对路径或绝对路径
|
79
|
+
|
80
|
+
|
81
|
+
class LLMOutputEvent(BaseModel):
|
82
|
+
"""Represents plain text output from the LLM."""
|
83
|
+
text: str
|
84
|
+
|
85
|
+
|
86
|
+
class LLMThinkingEvent(BaseModel):
|
87
|
+
"""Represents text within <thinking> tags from the LLM."""
|
88
|
+
text: str
|
89
|
+
|
90
|
+
|
91
|
+
class ToolCallEvent(BaseModel):
|
92
|
+
"""Represents the LLM deciding to call a tool."""
|
93
|
+
tool: SkipValidation[BaseTool] # Use SkipValidation as BaseTool itself is complex
|
94
|
+
tool_xml: str
|
95
|
+
|
96
|
+
|
97
|
+
# Result class used by Tool Resolvers
|
98
|
+
class ToolResult(BaseModel):
|
99
|
+
success: bool
|
100
|
+
message: str
|
101
|
+
content: Any = None # Can store file content, command output, etc.
|
102
|
+
|
103
|
+
|
104
|
+
class ToolResultEvent(BaseModel):
|
105
|
+
"""Represents the result of executing a tool."""
|
106
|
+
tool_name: str
|
107
|
+
result: ToolResult
|
108
|
+
|
109
|
+
|
110
|
+
class TokenUsageEvent(BaseModel):
|
111
|
+
"""Represents the result of executing a tool."""
|
112
|
+
usage: Any
|
113
|
+
|
114
|
+
|
115
|
+
class PlanModeRespondEvent(BaseModel):
|
116
|
+
"""Represents the LLM attempting to complete the task."""
|
117
|
+
completion: SkipValidation[PlanModeRespondTool] # Skip validation
|
118
|
+
completion_xml: str
|
119
|
+
|
120
|
+
|
121
|
+
class CompletionEvent(BaseModel):
|
122
|
+
"""Represents the LLM attempting to complete the task."""
|
123
|
+
completion: SkipValidation[AttemptCompletionTool] # Skip validation
|
124
|
+
completion_xml: str
|
125
|
+
|
126
|
+
|
127
|
+
class ErrorEvent(BaseModel):
|
128
|
+
"""Represents an error during the process."""
|
129
|
+
message: str
|
130
|
+
|
131
|
+
|
132
|
+
class WindowLengthChangeEvent(BaseModel):
|
133
|
+
"""Represents the token usage in the conversation window."""
|
134
|
+
tokens_used: int
|
135
|
+
|
136
|
+
|
137
|
+
# Mapping from tool tag names to Pydantic models
|
138
|
+
TOOL_MODEL_MAP: Dict[str, Type[BaseTool]] = {
|
139
|
+
"execute_command": ExecuteCommandTool,
|
140
|
+
"read_file": ReadFileTool,
|
141
|
+
"write_to_file": WriteToFileTool,
|
142
|
+
"replace_in_file": ReplaceInFileTool,
|
143
|
+
"search_files": SearchFilesTool,
|
144
|
+
"list_files": ListFilesTool,
|
145
|
+
"list_code_definition_names": ListCodeDefinitionNamesTool,
|
146
|
+
"ask_followup_question": AskFollowupQuestionTool,
|
147
|
+
"attempt_completion": AttemptCompletionTool,
|
148
|
+
"plan_mode_respond": PlanModeRespondTool,
|
149
|
+
"use_rag_tool": UseRAGTool,
|
150
|
+
"list_package_info": ListPackageInfoTool,
|
151
|
+
}
|