autocoder-nano 0.1.30__py3-none-any.whl → 0.1.34__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 +159 -700
- autocoder_nano/git_utils.py +63 -1
- autocoder_nano/llm_client.py +170 -3
- autocoder_nano/llm_types.py +72 -16
- autocoder_nano/rules/rules_learn.py +221 -0
- autocoder_nano/templates.py +1 -1
- autocoder_nano/utils/completer_utils.py +616 -0
- 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.34.dist-info}/METADATA +3 -2
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.34.dist-info}/RECORD +34 -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.34.dist-info}/LICENSE +0 -0
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.34.dist-info}/WHEEL +0 -0
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.34.dist-info}/entry_points.txt +0 -0
- {autocoder_nano-0.1.30.dist-info → autocoder_nano-0.1.34.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1584 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
import re
|
4
|
+
import time
|
5
|
+
import xml.sax.saxutils
|
6
|
+
from importlib import resources
|
7
|
+
from typing import List, Dict, Any, Optional, Generator, Union, Tuple, Type
|
8
|
+
|
9
|
+
from rich.markdown import Markdown
|
10
|
+
from tokenizers import Tokenizer
|
11
|
+
|
12
|
+
from autocoder_nano.agent.agentic_edit_types import *
|
13
|
+
from autocoder_nano.git_utils import commit_changes, get_uncommitted_changes
|
14
|
+
from autocoder_nano.llm_client import AutoLLM, stream_chat_with_continue
|
15
|
+
from autocoder_nano.llm_prompt import prompt, format_str_jinja2
|
16
|
+
from autocoder_nano.llm_types import AutoCoderArgs, SourceCodeList, SingleOutputMeta
|
17
|
+
from autocoder_nano.sys_utils import detect_env
|
18
|
+
from autocoder_nano.utils.formatted_log_utils import save_formatted_log
|
19
|
+
from autocoder_nano.utils.printer_utils import Printer
|
20
|
+
from autocoder_nano.agent.agentic_edit_tools import ( # Import specific resolvers
|
21
|
+
BaseToolResolver,
|
22
|
+
ExecuteCommandToolResolver, ReadFileToolResolver, WriteToFileToolResolver,
|
23
|
+
ReplaceInFileToolResolver, SearchFilesToolResolver, ListFilesToolResolver,
|
24
|
+
ListCodeDefinitionNamesToolResolver, AskFollowupQuestionToolResolver,
|
25
|
+
AttemptCompletionToolResolver, PlanModeRespondToolResolver, ListPackageInfoToolResolver
|
26
|
+
)
|
27
|
+
|
28
|
+
printer = Printer()
|
29
|
+
|
30
|
+
TOOL_DISPLAY_MESSAGES: Dict[Type[BaseTool], Dict[str, str]] = {
|
31
|
+
ReadFileTool: {
|
32
|
+
"zh": "AutoCoder Nano 想要读取此文件:\n {{ path }}"
|
33
|
+
},
|
34
|
+
WriteToFileTool: {
|
35
|
+
"zh": (
|
36
|
+
"AutoCoder Nano 想要写入此文件:\n{{ path }} \n\n内容片段:\n{{ content_snippet }} {{ ellipsis }} "
|
37
|
+
)
|
38
|
+
},
|
39
|
+
ReplaceInFileTool: {
|
40
|
+
"zh": (
|
41
|
+
"AutoCoder Nano 想要替换此文件中的内容:\n{{ path }} \n\n差异片段:\n{{ diff_snippet }}{{ ellipsis }}"
|
42
|
+
)
|
43
|
+
},
|
44
|
+
ExecuteCommandTool: {
|
45
|
+
"zh": (
|
46
|
+
"AutoCoder Nano 想要执行此命令:\n{{ command }}\n(需要批准:{{ requires_approval }})"
|
47
|
+
)
|
48
|
+
},
|
49
|
+
ListFilesTool: {
|
50
|
+
"zh": (
|
51
|
+
"AutoCoder Nano 想要列出此目录中的文件:\n{{ path }} {{ recursive_text }}"
|
52
|
+
)
|
53
|
+
},
|
54
|
+
SearchFilesTool: {
|
55
|
+
"zh": (
|
56
|
+
"AutoCoder Nano 想要在此目录中搜索文件:\n{{ path }}\n文件模式: {{ file_pattern }}\n正则表达式:{{ regex }}"
|
57
|
+
)
|
58
|
+
},
|
59
|
+
ListCodeDefinitionNamesTool: {
|
60
|
+
"zh": "AutoCoder Nano 想要列出此路径中的定义:\n{{ path }}"
|
61
|
+
},
|
62
|
+
AskFollowupQuestionTool: {
|
63
|
+
"zh": (
|
64
|
+
"AutoCoder Nano 正在提问:\n{{ question }}\n{{ options_text }}"
|
65
|
+
)
|
66
|
+
},
|
67
|
+
}
|
68
|
+
|
69
|
+
|
70
|
+
def get_tool_display_message(tool: BaseTool, lang: str = "zh") -> str:
|
71
|
+
""" Generates a user-friendly, internationalized string representation for a tool call. """
|
72
|
+
tool_type = type(tool)
|
73
|
+
|
74
|
+
if tool_type not in TOOL_DISPLAY_MESSAGES:
|
75
|
+
# Fallback for unknown tools
|
76
|
+
return f"Unknown tool type: {tool_type.__name__}\nData: {tool.model_dump_json(indent=2)}"
|
77
|
+
|
78
|
+
templates = TOOL_DISPLAY_MESSAGES[tool_type]
|
79
|
+
template = templates.get(lang, templates.get("en", "Tool display template not found")) # Fallback to English
|
80
|
+
|
81
|
+
# Prepare context specific to each tool type
|
82
|
+
context = {}
|
83
|
+
if isinstance(tool, ReadFileTool):
|
84
|
+
context = {"path": tool.path}
|
85
|
+
elif isinstance(tool, WriteToFileTool):
|
86
|
+
snippet = tool.content[:150]
|
87
|
+
context = {
|
88
|
+
"path": tool.path, "content_snippet": snippet, "ellipsis": '...' if len(tool.content) > 150 else ''
|
89
|
+
}
|
90
|
+
elif isinstance(tool, ReplaceInFileTool):
|
91
|
+
snippet = tool.diff
|
92
|
+
context = {
|
93
|
+
"path": tool.path, "diff_snippet": snippet, "ellipsis": ''
|
94
|
+
}
|
95
|
+
elif isinstance(tool, ExecuteCommandTool):
|
96
|
+
context = {"command": tool.command, "requires_approval": tool.requires_approval}
|
97
|
+
elif isinstance(tool, ListFilesTool):
|
98
|
+
context = {"path": tool.path, "recursive_text": '(递归)' if tool.recursive else '(顶层)'}
|
99
|
+
elif isinstance(tool, SearchFilesTool):
|
100
|
+
context = {
|
101
|
+
"path": tool.path, "file_pattern": tool.file_pattern or '*', "regex": tool.regex
|
102
|
+
}
|
103
|
+
elif isinstance(tool, ListCodeDefinitionNamesTool):
|
104
|
+
context = {"path": tool.path}
|
105
|
+
elif isinstance(tool, AskFollowupQuestionTool):
|
106
|
+
options_text_zh = ""
|
107
|
+
if tool.options:
|
108
|
+
options_list_zh = "\n".join(
|
109
|
+
[f"- {opt}" for opt in tool.options]) # Assuming options are simple enough not to need translation
|
110
|
+
options_text_zh = f"选项:\n{options_list_zh}"
|
111
|
+
context = {
|
112
|
+
"question": tool.question, "options_text": options_text_zh
|
113
|
+
}
|
114
|
+
else:
|
115
|
+
# Generic context for tools not specifically handled above
|
116
|
+
context = tool.model_dump()
|
117
|
+
|
118
|
+
try:
|
119
|
+
return format_str_jinja2(template, **context)
|
120
|
+
except Exception as e:
|
121
|
+
# Fallback in case of formatting errors
|
122
|
+
return f"Error formatting display for {tool_type.__name__}: {e}\nTemplate: {template}\nContext: {context}"
|
123
|
+
|
124
|
+
|
125
|
+
# Map Pydantic Tool Models to their Resolver Classes
|
126
|
+
TOOL_RESOLVER_MAP: Dict[Type[BaseTool], Type[BaseToolResolver]] = {
|
127
|
+
ExecuteCommandTool: ExecuteCommandToolResolver,
|
128
|
+
ReadFileTool: ReadFileToolResolver,
|
129
|
+
WriteToFileTool: WriteToFileToolResolver,
|
130
|
+
ReplaceInFileTool: ReplaceInFileToolResolver,
|
131
|
+
SearchFilesTool: SearchFilesToolResolver,
|
132
|
+
ListFilesTool: ListFilesToolResolver,
|
133
|
+
ListCodeDefinitionNamesTool: ListCodeDefinitionNamesToolResolver,
|
134
|
+
ListPackageInfoTool: ListPackageInfoToolResolver,
|
135
|
+
AskFollowupQuestionTool: AskFollowupQuestionToolResolver,
|
136
|
+
AttemptCompletionTool: AttemptCompletionToolResolver, # Will stop the loop anyway
|
137
|
+
PlanModeRespondTool: PlanModeRespondToolResolver
|
138
|
+
}
|
139
|
+
|
140
|
+
|
141
|
+
class AgenticEdit:
|
142
|
+
def __init__(
|
143
|
+
self, args: AutoCoderArgs, llm: AutoLLM, files: SourceCodeList, history_conversation: List[Dict[str, Any]]
|
144
|
+
):
|
145
|
+
self.args = args
|
146
|
+
self.llm = llm
|
147
|
+
self.files = files
|
148
|
+
self.history_conversation = history_conversation
|
149
|
+
self.current_conversations = []
|
150
|
+
|
151
|
+
self.shadow_manager = None
|
152
|
+
|
153
|
+
# self.project_type_analyzer = ""
|
154
|
+
# self.checkpoint_manager = FileChangeManager(
|
155
|
+
# project_dir=args.source_dir,
|
156
|
+
# backup_dir=os.path.join(args.source_dir, ".auto-coder", "checkpoint"),
|
157
|
+
# store_dir=os.path.join(args.source_dir, ".auto-coder", "checkpoint_store"),
|
158
|
+
# max_history=10
|
159
|
+
# )
|
160
|
+
# self.linter = ""
|
161
|
+
# self.compiler = ""
|
162
|
+
|
163
|
+
# 变更跟踪信息
|
164
|
+
# 格式: { file_path: FileChangeEntry(...) }
|
165
|
+
self.file_changes: Dict[str, FileChangeEntry] = {}
|
166
|
+
|
167
|
+
try:
|
168
|
+
tokenizer_path = resources.files("autocoder_nano").joinpath("data/tokenizer.json").__str__()
|
169
|
+
except FileNotFoundError:
|
170
|
+
tokenizer_path = None
|
171
|
+
self.tokenizer_model = Tokenizer.from_file(tokenizer_path)
|
172
|
+
|
173
|
+
def count_tokens(self, text: str) -> int:
|
174
|
+
try:
|
175
|
+
encoded = self.tokenizer_model.encode(text)
|
176
|
+
v = len(encoded.ids)
|
177
|
+
return v
|
178
|
+
except Exception as e:
|
179
|
+
return -1
|
180
|
+
|
181
|
+
def record_file_change(
|
182
|
+
self, file_path: str, change_type: str, diff: Optional[str] = None, content: Optional[str] = None
|
183
|
+
):
|
184
|
+
"""
|
185
|
+
记录单个文件的变更信息。
|
186
|
+
Args:
|
187
|
+
file_path: 相对路径
|
188
|
+
change_type: 'added' 或 'modified'
|
189
|
+
diff: 对于 replace_in_file,传入 diff 内容
|
190
|
+
content: 最新文件内容(可选,通常用于 write_to_file)
|
191
|
+
"""
|
192
|
+
entry = self.file_changes.get(file_path)
|
193
|
+
if entry is None:
|
194
|
+
entry = FileChangeEntry(
|
195
|
+
type=change_type, diffs=[], content=content)
|
196
|
+
self.file_changes[file_path] = entry
|
197
|
+
else:
|
198
|
+
# 文件已经存在,可能之前是 added,现在又被 modified,或者多次 modified
|
199
|
+
# 简单起见,type 用 added 优先,否则为 modified
|
200
|
+
if entry.type != "added":
|
201
|
+
entry.type = change_type
|
202
|
+
|
203
|
+
# content 以最新为准
|
204
|
+
if content is not None:
|
205
|
+
entry.content = content
|
206
|
+
|
207
|
+
if diff:
|
208
|
+
entry.diffs.append(diff)
|
209
|
+
|
210
|
+
def get_all_file_changes(self) -> Dict[str, FileChangeEntry]:
|
211
|
+
""" 获取当前记录的所有文件变更信息 """
|
212
|
+
return self.file_changes
|
213
|
+
|
214
|
+
@prompt()
|
215
|
+
def _analyze(self, request: AgenticEditRequest):
|
216
|
+
"""
|
217
|
+
你是一位技术精湛的软件工程师,在众多编程语言、框架、设计模式和最佳实践方面拥有渊博知识。
|
218
|
+
|
219
|
+
====
|
220
|
+
工具使用说明
|
221
|
+
|
222
|
+
你可使用一系列工具,且需经用户批准才能执行。每条消息中仅能使用一个工具,用户回复中会包含该工具的执行结果。你要借助工具逐步完成给定任务,每个工具的使用都需依据前一个工具的使用结果。
|
223
|
+
|
224
|
+
# 工具使用格式
|
225
|
+
|
226
|
+
工具使用采用 XML 风格标签进行格式化。工具名称包含在开始和结束标签内,每个参数同样包含在各自的标签中。其结构如下:
|
227
|
+
<tool_name>
|
228
|
+
<parameter1_name>value1</parameter1_name>
|
229
|
+
<parameter2_name>value2</parameter2_name>
|
230
|
+
...
|
231
|
+
</tool_name>
|
232
|
+
例如:
|
233
|
+
<read_file>
|
234
|
+
<path>src/main.js</path>
|
235
|
+
</read_file>
|
236
|
+
|
237
|
+
务必严格遵循此工具使用格式,以确保正确解析和执行。
|
238
|
+
|
239
|
+
# 工具列表
|
240
|
+
|
241
|
+
## execute_command(执行命令)
|
242
|
+
描述:请求在系统上执行 CLI 命令。当需要执行系统操作或运行特定命令来完成用户任务的任何步骤时使用此工具。你必须根据用户的系统调整命令,并清晰解释命令的作用。对于命令链,使用适合用户 shell 的链式语法。相较于创建可执行脚本,优先执行复杂的 CLI 命令,因为它们更灵活且易于运行。命令将在当前工作目录{{current_project}}中执行。
|
243
|
+
参数:
|
244
|
+
- command(必填):要执行的 CLI 命令。该命令应适用于当前操作系统,且需正确格式化,不得包含任何有害指令。
|
245
|
+
- requires_approval(必填):一个布尔值,表示此命令在用户启用自动批准模式的情况下是否需要明确的用户批准。对于可能产生影响的操作,如安装/卸载软件包、删除/覆盖文件、系统配置更改、网络操作或任何可能产生意外副作用的命令,设置为 'true';对于安全操作,如读取文件/目录、运行开发服务器、构建项目和其他非破坏性操作,设置为 'false'。
|
246
|
+
用法:
|
247
|
+
<execute_command>
|
248
|
+
<command>需要运行的命令</command>
|
249
|
+
<requires_approval>true 或 false</requires_approval>
|
250
|
+
</execute_command>
|
251
|
+
|
252
|
+
## list_package_info(列出软件包信息)
|
253
|
+
描述:请求检索有关源代码包的信息,如最近的更改或文档摘要,以更好地理解代码上下文。它接受一个目录路径(相对于当前项目的绝对或相对路径)。
|
254
|
+
参数:
|
255
|
+
- path(必填):源代码包目录路径。
|
256
|
+
用法:
|
257
|
+
<list_package_info>
|
258
|
+
<path>相对或绝对的软件包路径</path>
|
259
|
+
</list_package_info>
|
260
|
+
|
261
|
+
## read_file(读取文件)
|
262
|
+
描述:请求读取指定路径文件的内容。当需要检查现有文件的内容(例如分析代码、查看文本文件或从配置文件中提取信息)且不知道文件内容时使用此工具。它可自动从 PDF 和 DOCX 文件中提取纯文本,可能不适用于其他类型的二进制文件,因为它会将原始内容作为字符串返回。
|
263
|
+
参数:
|
264
|
+
- path(必填):要读取的文件路径(相对于当前工作目录{{ current_project }})。
|
265
|
+
用法:
|
266
|
+
<read_file>
|
267
|
+
<path>文件路径在此</path>
|
268
|
+
</read_file>
|
269
|
+
|
270
|
+
write_to_file(写入文件)
|
271
|
+
描述:请求将内容写入指定路径的文件。如果文件存在,将用提供的内容覆盖;如果文件不存在,将创建该文件。此工具会自动创建写入文件所需的任何目录。
|
272
|
+
参数:
|
273
|
+
- path(必填):要写入的文件路径(相对于当前工作目录{{ current_project }})。
|
274
|
+
- content(必填):要写入文件的内容。必须提供文件的完整预期内容,不得有任何截断或遗漏,必须包含文件的所有部分,即使它们未被修改。
|
275
|
+
用法:
|
276
|
+
<write_to_file>
|
277
|
+
<path>文件路径在此</path>
|
278
|
+
<content>
|
279
|
+
你的文件内容在此
|
280
|
+
</content>
|
281
|
+
</write_to_file>
|
282
|
+
|
283
|
+
## replace_in_file(替换文件内容)
|
284
|
+
描述:请求使用定义对文件特定部分进行精确更改的 SEARCH/REPLACE 块来替换现有文件中的部分内容。此工具应用于需要对文件特定部分进行有针对性更改的情况。
|
285
|
+
参数:
|
286
|
+
- path(必填):要修改的文件路径(相对于当前工作目录{{ current_project }})。
|
287
|
+
- diff(必填):一个或多个遵循以下精确格式的 SEARCH/REPLACE 块:
|
288
|
+
```
|
289
|
+
<<<<<<< SEARCH
|
290
|
+
[exact content to find]
|
291
|
+
=======
|
292
|
+
[new content to replace with]
|
293
|
+
>>>>>>> REPLACE
|
294
|
+
```
|
295
|
+
关键规则:
|
296
|
+
1. SEARCH 内容必须与关联的文件部分完全匹配:
|
297
|
+
* 逐字符匹配,包括空格、缩进、行尾符。
|
298
|
+
* 包含所有注释、文档字符串等。
|
299
|
+
2. SEARCH/REPLACE 块仅替换第一个匹配项:
|
300
|
+
* 如果需要进行多次更改,需包含多个唯一的 SEARCH/REPLACE 块。
|
301
|
+
* 每个块的 SEARCH 部分应包含足够的行,以唯一匹配需要更改的每组行。
|
302
|
+
* 使用多个 SEARCH/REPLACE 块时,按它们在文件中出现的顺序列出。
|
303
|
+
3. 保持 SEARCH/REPLACE 块简洁:
|
304
|
+
* 将大型 SEARCH/REPLACE 块分解为一系列较小的块,每个块更改文件的一小部分。
|
305
|
+
* 仅包含更改的行,必要时包含一些周围的行以确保唯一性。
|
306
|
+
* 不要在 SEARCH/REPLACE 块中包含长段未更改的行。
|
307
|
+
* 每行必须完整,切勿在中途截断行,否则可能导致匹配失败。
|
308
|
+
4. 特殊操作:
|
309
|
+
* 移动代码:使用两个 SEARCH/REPLACE 块(一个从原始位置删除,一个插入到新位置)。
|
310
|
+
* 删除代码:使用空的 REPLACE 部分。
|
311
|
+
用法:
|
312
|
+
<replace_in_file>
|
313
|
+
<path>File path here</path>
|
314
|
+
<diff>
|
315
|
+
Search and replace blocks here
|
316
|
+
</diff>
|
317
|
+
</replace_in_file>
|
318
|
+
|
319
|
+
## search_files(搜索文件)
|
320
|
+
描述:请求在指定目录的文件中执行正则表达式搜索,提供富含上下文的结果。此工具在多个文件中搜索模式或特定内容,并显示每个匹配项及其周围的上下文。
|
321
|
+
参数:
|
322
|
+
- path(必填):要搜索的目录路径(相对于当前工作目录{{ current_project }}),该目录将被递归搜索。
|
323
|
+
- regex(必填):要搜索的正则表达式模式,使用 Rust 正则表达式语法。
|
324
|
+
- file_pattern(可选):用于过滤文件的 Glob 模式(例如,'.ts' 表示 TypeScript 文件),若未提供,则搜索所有文件(*)。
|
325
|
+
用法:
|
326
|
+
<search_files>
|
327
|
+
<path>Directory path here</path>
|
328
|
+
<regex>Your regex pattern here</regex>
|
329
|
+
<file_pattern>file pattern here (optional)</file_pattern>
|
330
|
+
</search_files>
|
331
|
+
|
332
|
+
## list_files(列出文件)
|
333
|
+
描述:请求列出指定目录中的文件和目录。如果 recursive 为 true,将递归列出所有文件和目录;如果 recursive 为 false 或未提供,仅列出顶级内容。请勿使用此工具确认你可能已创建的文件的存在,因为用户会告知你文件是否成功创建。
|
334
|
+
参数:
|
335
|
+
- path(必填):要列出内容的目录路径(相对于当前工作目录{{ current_project }})。
|
336
|
+
- recursive(可选):是否递归列出文件,true 表示递归列出,false 或省略表示仅列出顶级内容。
|
337
|
+
用法:
|
338
|
+
<list_files>
|
339
|
+
<path>Directory path here</path>
|
340
|
+
<recursive>true or false (optional)</recursive>
|
341
|
+
</list_files>
|
342
|
+
|
343
|
+
## list_code_definition_names(列出代码定义名称)
|
344
|
+
描述:请求列出指定目录顶级源文件中的定义名称(类、函数、方法等)。此工具提供对代码库结构和重要构造的洞察,概括对于理解整体架构至关重要的高级概念和关系。
|
345
|
+
参数:
|
346
|
+
- path(必填):要列出顶级源代码定义的目录路径(相对于当前工作目录{{ current_project }})。
|
347
|
+
用法:
|
348
|
+
<list_code_definition_names>
|
349
|
+
<path>Directory path here</path>
|
350
|
+
</list_code_definition_names>
|
351
|
+
|
352
|
+
ask_followup_question(提出后续问题)
|
353
|
+
描述:向用户提出问题以收集完成任务所需的额外信息。当遇到歧义、需要澄清或需要更多细节以有效推进时使用此工具。它通过与用户直接沟通实现交互式问题解决,应明智使用,以在收集必要信息和避免过多来回沟通之间取得平衡。
|
354
|
+
参数:
|
355
|
+
- question(必填):要问用户的问题,应清晰、具体,针对所需信息。
|
356
|
+
- options(可选):用户可选择的 2-5 个选项的数组,每个选项应为描述可能答案的字符串。并非总是需要提供选项,但在许多情况下有助于避免用户手动输入响应。重要提示:切勿包含切换到 Act 模式的选项,因为这需要用户自行手动操作(如有需要)。
|
357
|
+
用法:
|
358
|
+
<ask_followup_question>
|
359
|
+
<question>Your question here</question>
|
360
|
+
<options>
|
361
|
+
Array of options here (optional), e.g. ["Option 1", "Option 2", "Option 3"]
|
362
|
+
</options>
|
363
|
+
</ask_followup_question>
|
364
|
+
|
365
|
+
## attempt_completion(尝试完成任务)
|
366
|
+
描述:每次工具使用后,用户会回复该工具使用的结果,即是否成功以及失败原因(如有)。一旦收到工具使用结果并确认任务完成,使用此工具向用户展示工作成果。可选地,你可以提供一个 CLI 命令来展示工作成果。用户可能会提供反馈,你可据此进行改进并再次尝试。
|
367
|
+
重要提示:在确认用户已确认之前的工具使用成功之前,不得使用此工具。否则将导致代码损坏和系统故障。在使用此工具之前,必须在<thinking></thinking>标签中自问是否已从用户处确认之前的工具使用成功。如果没有,则不要使用此工具。
|
368
|
+
参数:
|
369
|
+
- result(必填):任务的结果,应以最终形式表述,无需用户进一步输入,不得在结果结尾提出问题或提供进一步帮助。
|
370
|
+
- command(可选):用于向用户演示结果的 CLI 命令。例如,使用open index.html显示创建的网站,或使用open localhost:3000显示本地运行的开发服务器,但不要使用像echo或cat这样仅打印文本的命令。该命令应适用于当前操作系统,且需正确格式化,不得包含任何有害指令。
|
371
|
+
用法:
|
372
|
+
<attempt_completion>
|
373
|
+
<result>
|
374
|
+
Your final result description here
|
375
|
+
</result>
|
376
|
+
<command>Command to demonstrate result (optional)</command>
|
377
|
+
</attempt_completion>
|
378
|
+
|
379
|
+
# 工具使用示例
|
380
|
+
|
381
|
+
## 示例 1:请求执行命令
|
382
|
+
<execute_command>
|
383
|
+
<command>npm run dev</command>
|
384
|
+
<requires_approval>false</requires_approval>
|
385
|
+
</execute_command>
|
386
|
+
|
387
|
+
## 示例 2:请求创建新文件
|
388
|
+
<write_to_file>
|
389
|
+
<path>src/frontend-config.json</path>
|
390
|
+
<content>
|
391
|
+
{
|
392
|
+
"apiEndpoint": "https://api.example.com",
|
393
|
+
"theme": {
|
394
|
+
"primaryColor": "#007bff",
|
395
|
+
"secondaryColor": "#6c757d",
|
396
|
+
"fontFamily": "Arial, sans-serif"
|
397
|
+
},
|
398
|
+
"features": {
|
399
|
+
"darkMode": true,
|
400
|
+
"notifications": true,
|
401
|
+
"analytics": false
|
402
|
+
},
|
403
|
+
"version": "1.0.0"
|
404
|
+
}
|
405
|
+
</content>
|
406
|
+
</write_to_file>
|
407
|
+
|
408
|
+
## 示例 3:请求对文件进行有针对性的编辑
|
409
|
+
<replace_in_file>
|
410
|
+
<path>src/components/App.tsx</path>
|
411
|
+
<diff>
|
412
|
+
<<<<<<< SEARCH
|
413
|
+
import React from 'react';
|
414
|
+
=======
|
415
|
+
import React, { useState } from 'react';
|
416
|
+
>>>>>>> REPLACE
|
417
|
+
|
418
|
+
<<<<<<< SEARCH
|
419
|
+
function handleSubmit() {
|
420
|
+
saveData();
|
421
|
+
setLoading(false);
|
422
|
+
}
|
423
|
+
|
424
|
+
=======
|
425
|
+
>>>>>>> REPLACE
|
426
|
+
|
427
|
+
<<<<<<< SEARCH
|
428
|
+
return (
|
429
|
+
<div>
|
430
|
+
=======
|
431
|
+
function handleSubmit() {
|
432
|
+
saveData();
|
433
|
+
setLoading(false);
|
434
|
+
}
|
435
|
+
|
436
|
+
return (
|
437
|
+
<div>
|
438
|
+
>>>>>>> REPLACE
|
439
|
+
</diff>
|
440
|
+
</replace_in_file>
|
441
|
+
|
442
|
+
# 工具使用指南
|
443
|
+
0. 始终以全面搜索和探索开始:在进行任何代码更改之前,使用搜索工具(list_files、grep 命令)充分了解代码库的结构、现有模式和依赖关系,这有助于防止错误并确保你的更改符合项目约定。
|
444
|
+
1. 在<thinking>标签中,评估你已有的信息和继续完成任务所需的信息。
|
445
|
+
2. 根据任务和工具描述选择最合适的工具,评估是否需要其他信息来推进,并确定可用工具中哪个最适合收集这些信息。例如,使用 list_files 工具比在终端中运行类似ls的命令更有效。关键是要思考每个可用工具,并使用最适合任务当前步骤的工具。
|
446
|
+
3. 如果需要多个操作,每条消息使用一个工具,以迭代方式完成任务,每个工具的使用都基于前一个工具的使用结果,切勿假设任何工具使用的结果,每个步骤都必须以前一步骤的结果为依据。
|
447
|
+
4. 使用为每个工具指定的 XML 格式来制定工具使用方式。
|
448
|
+
5. 每次工具使用后,用户会回复该工具使用的结果,该结果将为你提供继续任务或做出进一步决策所需的信息,可能包括:
|
449
|
+
* 工具是否成功的信息,以及失败原因(如有)。
|
450
|
+
* 可能因更改而产生的 linter 错误,你需要解决这些错误。
|
451
|
+
* 对更改做出反应的新终端输出,你可能需要考虑或采取行动。
|
452
|
+
* 与工具使用相关的任何其他相关反馈或信息。
|
453
|
+
6. 每次工具使用后务必等待用户确认,切勿在未获得用户对结果的明确确认前假设工具使用成功。
|
454
|
+
|
455
|
+
务必逐步推进,在每次工具使用后等待用户的消息再继续任务,这种方法使你能够:
|
456
|
+
1. 确认每个步骤成功后再继续。
|
457
|
+
2. 立即解决出现的任何问题或错误。
|
458
|
+
3. 根据新信息或意外结果调整方法。
|
459
|
+
4. 确保每个操作正确建立在前一个操作的基础上。
|
460
|
+
|
461
|
+
通过等待并仔细考虑用户在每次工具使用后的回复,你可以做出相应反应,并就是否继续任务做出明智决策,这种迭代过程有助于确保工作的整体成功和准确性。
|
462
|
+
|
463
|
+
===
|
464
|
+
|
465
|
+
文件搜索
|
466
|
+
|
467
|
+
**这是你的核心方法** - 以下先搜索的方法并非可选,而是进行可靠代码工作的必要条件。每个代码任务都应遵循这种系统的探索模式。
|
468
|
+
本指南为 AI 代理和开发人员提供了一种有效搜索、理解和修改代码库的系统方法,强调彻底的预编码调查和后编码验证,以确保可靠和可维护的更改。
|
469
|
+
该方法将多个搜索工具(grep、list_files、read_file)与结构化工作流程相结合,以最大限度地减少代码错误,确保全面理解,系统地验证更改,并遵循已建立的项目模式。
|
470
|
+
|
471
|
+
# list_files(列出文件)
|
472
|
+
## 目的:
|
473
|
+
- 发现项目结构并了解目录组织;
|
474
|
+
- 在深入研究之前获取可用文件和文件夹的概述。
|
475
|
+
## 使用时机:
|
476
|
+
- 初始项目探索以了解代码库布局;
|
477
|
+
- 识别关键目录,如src/、lib/、components/、utils/;
|
478
|
+
- 定位配置文件,如package.json、tsconfig.json、Makefile;
|
479
|
+
- 在使用更有针对性的搜索工具之前。
|
480
|
+
## 优点:
|
481
|
+
- 快速提供项目概述,不会带来过多细节;
|
482
|
+
- 帮助规划在特定目录中的目标搜索;
|
483
|
+
- 不熟悉代码库时的必要第一步。
|
484
|
+
|
485
|
+
# grep(Shell 命令)
|
486
|
+
## 目的:
|
487
|
+
- 在多个文件中查找确切的文本匹配和模式;
|
488
|
+
- 执行输出开销最小的精确搜索;
|
489
|
+
- 验证代码更改并确认实现。
|
490
|
+
|
491
|
+
## 使用时机:
|
492
|
+
- 预编码上下文收集:查找符号、函数、导入和使用模式。
|
493
|
+
- 后编码验证:确认更改已正确应用,且没有过时的引用残留。
|
494
|
+
- 模式分析:了解编码约定和现有实现。
|
495
|
+
|
496
|
+
## 关键命令模式:
|
497
|
+
- 预编码上下文示例:
|
498
|
+
<execute_command>
|
499
|
+
<command>grep -l "className" src/ | head -5</command>
|
500
|
+
<requires_approval>false</requires_approval>
|
501
|
+
</execute_command>
|
502
|
+
|
503
|
+
<execute_command>
|
504
|
+
<command>grep -rc "import.*React" src/ | grep -v ":0"</command>
|
505
|
+
<requires_approval>false</requires_approval>
|
506
|
+
</execute_command>
|
507
|
+
|
508
|
+
<execute_command>
|
509
|
+
<command>grep -Rn "function.*MyFunction\|const.*MyFunction" . | head -10</command>
|
510
|
+
<requires_approval>false</requires_approval>
|
511
|
+
</execute_command>
|
512
|
+
|
513
|
+
<execute_command>
|
514
|
+
<command>grep -R --exclude-dir={node_modules,dist,build,.git} "TODO" .</command>
|
515
|
+
<requires_approval>false</requires_approval>
|
516
|
+
</execute_command>
|
517
|
+
|
518
|
+
- 后编码验证示例:
|
519
|
+
|
520
|
+
<execute_command>
|
521
|
+
<command>ls -la newfile.js 2>/dev/null && echo "File created" || echo "File not found"</command>
|
522
|
+
<requires_approval>false</requires_approval>
|
523
|
+
</execute_command>
|
524
|
+
|
525
|
+
<execute_command>
|
526
|
+
<command>grep -Rn "oldName" . || echo "✓ No stale references found"</command>
|
527
|
+
<requires_approval>false</requires_approval>
|
528
|
+
</execute_command>
|
529
|
+
|
530
|
+
<execute_command>
|
531
|
+
<command>grep -c "newName" src/*.js | grep -v ":0" || echo "⚠ New references not found"</command>
|
532
|
+
<requires_approval>false</requires_approval>
|
533
|
+
</execute_command>
|
534
|
+
|
535
|
+
<execute_command>
|
536
|
+
<command>grep -Rn "import.*newModule\|export.*newFunction" . | wc -l</command>
|
537
|
+
<requires_approval>false</requires_approval>
|
538
|
+
</execute_command>
|
539
|
+
|
540
|
+
## 输出优化技巧:
|
541
|
+
- 使用-l仅获取文件名。
|
542
|
+
- 使用-c仅获取计数。
|
543
|
+
- 使用| head -N限制行数。
|
544
|
+
- 使用| wc -l获取总数。
|
545
|
+
- 使用2>/dev/null抑制错误。
|
546
|
+
- 与|| echo结合使用以显示清晰的状态消息。
|
547
|
+
|
548
|
+
# search_files(备用)
|
549
|
+
|
550
|
+
## 目的:
|
551
|
+
- 当无法使用 grep 命令时的替代搜索方法;
|
552
|
+
- 用于在代码库中进行更广泛、不太精确的搜索的语义搜索功能;
|
553
|
+
- 作为 grep 的补充,用于全面的代码发现。
|
554
|
+
|
555
|
+
## 使用时机:
|
556
|
+
- 当 shell 访问受限或 grep 不可用时;
|
557
|
+
- 需要在代码库中进行更广泛、不太精确的搜索时;
|
558
|
+
- 作为 grep 的补充,用于全面的代码发现。
|
559
|
+
|
560
|
+
# read_file(读取文件)
|
561
|
+
|
562
|
+
## 目的:
|
563
|
+
- 详细检查完整的文件内容;
|
564
|
+
- 理解上下文、模式和实现细节。
|
565
|
+
|
566
|
+
## 使用时机:
|
567
|
+
- 通过 list_files 或 grep 确定目标文件后;
|
568
|
+
- 了解函数签名、接口和契约时;
|
569
|
+
- 分析使用模式和项目约定时;
|
570
|
+
- 进行更改前需要详细检查代码时。
|
571
|
+
|
572
|
+
## 重要注意事项:
|
573
|
+
- 在缩小目标文件范围后策略性地使用;
|
574
|
+
- 进行代码修改前了解上下文的必要步骤;
|
575
|
+
- 有助于识别依赖关系和潜在的副作用。
|
576
|
+
|
577
|
+
# 选择正确的搜索策略
|
578
|
+
- 首先使用 list_files了解项目结构。
|
579
|
+
- 需要查找特定内容时使用 grep。
|
580
|
+
- 需要检查特定文件的详细信息时使用 read_file。
|
581
|
+
- 结合多种方法以全面理解。
|
582
|
+
|
583
|
+
## 默认工作流程:
|
584
|
+
- list_files → 了解结构。
|
585
|
+
- grep → 查找特定模式 / 符号。
|
586
|
+
- read_file → 检查细节。
|
587
|
+
- 实施更改。
|
588
|
+
- grep → 验证更改。
|
589
|
+
|
590
|
+
# 综合工作流程
|
591
|
+
|
592
|
+
## 阶段 1:项目发现与分析
|
593
|
+
|
594
|
+
**项目结构概述**
|
595
|
+
<execute_command>
|
596
|
+
<command>ls -la</command>
|
597
|
+
<requires_approval>false</requires_approval>
|
598
|
+
</execute_command>
|
599
|
+
|
600
|
+
- 使用 list_files 工具了解目录结构。
|
601
|
+
- 识别关键目录:src/、lib/、components/、utils/。
|
602
|
+
- 查找配置文件:package.json、tsconfig.json、Makefile。
|
603
|
+
|
604
|
+
**技术栈识别**
|
605
|
+
<execute_command>
|
606
|
+
<command>grep -E "(import|require|from).*['\"]" src/ | head -20</command>
|
607
|
+
<requires_approval>false</requires_approval>
|
608
|
+
</execute_command>
|
609
|
+
|
610
|
+
- 检查软件包依赖项和导入。
|
611
|
+
- 识别框架、库和编码模式。
|
612
|
+
- 了解项目约定(命名、文件组织)。
|
613
|
+
|
614
|
+
## 阶段 2:上下文代码调查
|
615
|
+
|
616
|
+
**符号和模式搜索**
|
617
|
+
<execute_command>
|
618
|
+
<command>grep -Rn "targetFunction|targetClass" . --exclude-dir={node_modules,dist}</command>
|
619
|
+
<requires_approval>false</requires_approval>
|
620
|
+
</execute_command>
|
621
|
+
|
622
|
+
**使用模式分析**
|
623
|
+
- 使用 read_file 详细检查关键文件。
|
624
|
+
- 了解函数签名、接口和契约。
|
625
|
+
- 检查错误处理模式和边缘情况。
|
626
|
+
|
627
|
+
**依赖关系映射**
|
628
|
+
<execute_command>
|
629
|
+
<command>grep -Rn "import.*targetModule" . | grep -v test</command>
|
630
|
+
<requires_approval>false</requires_approval>
|
631
|
+
</execute_command>
|
632
|
+
|
633
|
+
## 阶段 3:实施计划
|
634
|
+
|
635
|
+
**影响评估**
|
636
|
+
- 识别所有需要修改的文件。
|
637
|
+
- 规划向后兼容性注意事项。
|
638
|
+
- 考虑潜在的副作用。
|
639
|
+
|
640
|
+
**测试策略**
|
641
|
+
<execute_command>
|
642
|
+
<command>find . -name "*test*" -o -name "*spec*" | head -10</command>
|
643
|
+
<requires_approval>false</requires_approval>
|
644
|
+
</execute_command>
|
645
|
+
|
646
|
+
- 查找现有的测试以供参考。
|
647
|
+
- 必要时规划新的测试用例。
|
648
|
+
|
649
|
+
## 阶段 4:代码实现
|
650
|
+
|
651
|
+
更多细节请参考 “文件编辑” 部分。
|
652
|
+
|
653
|
+
## 阶段 5:全面验证
|
654
|
+
|
655
|
+
**文件系统验证**
|
656
|
+
<execute_command>
|
657
|
+
<command>ls -la newfile.* 2>/dev/null || echo "Expected new files not found"</command>
|
658
|
+
<requires_approval>false</requires_approval>
|
659
|
+
</execute_command>
|
660
|
+
|
661
|
+
**代码集成验证**
|
662
|
+
<execute_command>
|
663
|
+
<command>grep -Rn "oldSymbol" . --exclude-dir={node_modules,dist} || echo "✓ No stale references"</command>
|
664
|
+
<requires_approval>false</requires_approval>
|
665
|
+
</execute_command>
|
666
|
+
|
667
|
+
<execute_command>
|
668
|
+
<command>grep -Rn "oldSymbol" . --exclude-dir={node_modules,dist} || echo "✓ No stale references"</command>
|
669
|
+
<requires_approval>false</requires_approval>
|
670
|
+
</execute_command>
|
671
|
+
|
672
|
+
**功能验证**
|
673
|
+
<execute_command>
|
674
|
+
<command>npm run lint 2>/dev/null || echo "Linting not configured"</command>
|
675
|
+
<requires_approval>false</requires_approval>
|
676
|
+
</execute_command>
|
677
|
+
|
678
|
+
<execute_command>
|
679
|
+
<command>npm test 2>/dev/null || echo "Testing not configured"</command>
|
680
|
+
<requires_approval>false</requires_approval>
|
681
|
+
</execute_command>
|
682
|
+
|
683
|
+
<execute_command>
|
684
|
+
<command>npm run build 2>/dev/null || echo "Build not configured"</command>
|
685
|
+
<requires_approval>false</requires_approval>
|
686
|
+
</execute_command>
|
687
|
+
|
688
|
+
**文档和注释**
|
689
|
+
- 验证新函数 / 类是否有适当的文档。
|
690
|
+
- 检查复杂逻辑是否有解释性注释。
|
691
|
+
- 确保 README 或其他文档在需要时已更新。
|
692
|
+
|
693
|
+
## 阶段 6:质量保证
|
694
|
+
|
695
|
+
**性能考虑**
|
696
|
+
- 检查潜在的性能影响。
|
697
|
+
- 验证内存使用模式。
|
698
|
+
- 考虑可伸缩性影响。
|
699
|
+
|
700
|
+
**安全审查**
|
701
|
+
- 查找潜在的安全漏洞。
|
702
|
+
- 验证输入验证和清理。
|
703
|
+
- 检查适当的错误处理。
|
704
|
+
|
705
|
+
**最终集成检查**
|
706
|
+
<execute_command>
|
707
|
+
<command>grep -Rn "TODO\|FIXME\|XXX" . --exclude-dir={node_modules,dist} | wc -l</command>
|
708
|
+
<requires_approval>false</requires_approval>
|
709
|
+
</execute_command>
|
710
|
+
|
711
|
+
# 最佳实践
|
712
|
+
|
713
|
+
- 迭代方法:不要试图一次性理解所有内容,逐步积累知识。
|
714
|
+
- 文档优先:在深入研究代码之前,阅读现有的文档、注释和 README 文件。
|
715
|
+
- 小步骤:进行增量更改并验证每个步骤。
|
716
|
+
- 做好回滚准备:始终知道如何撤销更改(如果出现问题)。
|
717
|
+
- 尽早测试:在开发过程中频繁运行测试,而不仅仅是在最后。
|
718
|
+
- 模式一致性:遵循已建立的项目模式,而不是引入新的模式。
|
719
|
+
|
720
|
+
通过遵循这种全面的方法,你可以确保对所有代码更改的全面理解、可靠实现和稳健验证。
|
721
|
+
|
722
|
+
=====
|
723
|
+
|
724
|
+
文件编辑 EDITING FILES
|
725
|
+
|
726
|
+
在应用以下编辑技术之前,请确保你已按照 “文件搜索” 方法充分了解代码库的上下文。
|
727
|
+
你可以使用两个工具来处理文件:write_to_file和replace_in_file。了解它们的角色并为工作选择合适的工具将有助于确保高效和准确的修改。
|
728
|
+
|
729
|
+
# write_to_file(写入文件)
|
730
|
+
|
731
|
+
## 目的:
|
732
|
+
- 创建新文件或覆盖现有文件的全部内容。
|
733
|
+
|
734
|
+
## 使用时机:
|
735
|
+
- 初始文件创建,如搭建新项目时;
|
736
|
+
- 覆盖大型样板文件,需要一次性替换全部内容时;
|
737
|
+
- 当更改的复杂性或数量使 replace_in_file 难以处理或容易出错时;
|
738
|
+
- 当需要完全重构文件的内容或更改其基本组织时。
|
739
|
+
|
740
|
+
## 重要注意事项:
|
741
|
+
- 使用 write_to_file 需要提供文件的完整最终内容;
|
742
|
+
- 如果只需要对现有文件进行小的更改,考虑使用 replace_in_file,以避免不必要地重写整个文件;
|
743
|
+
- 虽然 write_to_file 不应是你的默认选择,但在情况确实需要时不要犹豫使用它。
|
744
|
+
|
745
|
+
# replace_in_file(替换文件内容)
|
746
|
+
|
747
|
+
## 目的:
|
748
|
+
- 对现有文件的特定部分进行有针对性的编辑,而不覆盖整个文件。
|
749
|
+
|
750
|
+
## 使用时机:
|
751
|
+
- 进行小的、局部的更改,如更新几行代码、函数实现、更改变量名、修改文本部分等;
|
752
|
+
- 有针对性的改进,只需要更改文件内容的特定部分;
|
753
|
+
- 特别适用于长文件,其中大部分内容将保持不变。
|
754
|
+
|
755
|
+
## 优点:
|
756
|
+
- 对于较小的编辑更高效,因为不需要提供整个文件内容;
|
757
|
+
- 减少覆盖大型文件时可能出现的错误机会。
|
758
|
+
|
759
|
+
# 选择合适的工具
|
760
|
+
- 大多数更改默认使用 replace_in_file,它是更安全、更精确的选择,可最大限度地减少潜在问题。
|
761
|
+
- 使用 write_to_file的情况:
|
762
|
+
* 创建新文件。
|
763
|
+
* 更改非常广泛,使用 replace_in_file 会更复杂或更危险。
|
764
|
+
* 需要完全重组或重构文件。
|
765
|
+
* 文件相对较小,更改影响其大部分内容。
|
766
|
+
* 生成样板文件或模板文件。
|
767
|
+
|
768
|
+
# 自动格式化注意事项
|
769
|
+
- 使用 write_to_file 或 replace_in_file 后,用户的编辑器可能会自动格式化文件。
|
770
|
+
- 这种自动格式化可能会修改文件内容,例如:
|
771
|
+
* 将单行拆分为多行。
|
772
|
+
* 调整缩进以匹配项目样式(如 2 个空格、4 个空格或制表符)。
|
773
|
+
* 将单引号转换为双引号(或根据项目首选项反之亦然)。
|
774
|
+
* 组织导入(如排序、按类型分组)。
|
775
|
+
* 在对象和数组中添加 / 删除尾随逗号。
|
776
|
+
* 强制一致的大括号样式(如同一行或新行)。
|
777
|
+
* 标准化分号用法(根据样式添加或删除)。
|
778
|
+
* write_to_file 和 replace_in_file 工具响应将包含自动格式化后的文件最终状态。
|
779
|
+
* 将此最终状态用作任何后续编辑的参考点,这在为 replace_in_file 制作 SEARCH 块时尤其重要,因为需要内容与文件中的内容完全匹配。
|
780
|
+
|
781
|
+
# 工作流程提示
|
782
|
+
1. 编辑前,评估更改的范围并决定使用哪个工具。
|
783
|
+
2. 对于有针对性的编辑,应用带有精心制作的 SEARCH/REPLACE 块的 replace_in_file。如果需要多次更改,可以在单个 replace_in_file 调用中堆叠多个 SEARCH/REPLACE 块。
|
784
|
+
3. 对于重大修改或初始文件创建,依赖 write_to_file。
|
785
|
+
4. 一旦使用 write_to_file 或 replace_in_file 编辑了文件,系统将为你提供修改后文件的最终状态。将此更新后的内容用作任何后续 SEARCH/REPLACE 操作的参考点,因为它反映了任何自动格式化或用户应用的更改。
|
786
|
+
|
787
|
+
通过谨慎选择 write_to_file 和 replace_in_file,你可以使文件编辑过程更流畅、更安全、更高效。
|
788
|
+
|
789
|
+
=====
|
790
|
+
|
791
|
+
软件包上下文信息 PACKAGE CONTEXT INFORMATION
|
792
|
+
|
793
|
+
# 理解目录上下文
|
794
|
+
|
795
|
+
## 目的:
|
796
|
+
- 项目中的每个目录(尤其是源代码目录)都有隐式的上下文信息,包括最近的更改、重要文件及其用途。
|
797
|
+
|
798
|
+
## 访问目录上下文:
|
799
|
+
- 使用list_package_info工具查看特定目录的此信息;
|
800
|
+
- 不要使用其他工具(如 list_files)查看此专门的上下文信息。
|
801
|
+
|
802
|
+
## 使用时机:
|
803
|
+
- 需要了解目录中最近发生的更改时;
|
804
|
+
- 需要深入了解目录的目的和组织时;
|
805
|
+
- 在使用其他工具进行详细文件探索之前。
|
806
|
+
|
807
|
+
## 示例:
|
808
|
+
<list_package_info>
|
809
|
+
<path>src/some/directory</path>
|
810
|
+
</list_package_info>
|
811
|
+
|
812
|
+
# 好处
|
813
|
+
|
814
|
+
- 快速识别可能与你的任务相关的最近修改的文件。
|
815
|
+
- 提供目录内容和目的的高级理解。
|
816
|
+
- 帮助确定使用 read_file、shell 命令或 list_code_definition_names 等工具详细检查哪些文件的优先级。
|
817
|
+
|
818
|
+
=====
|
819
|
+
|
820
|
+
# 功能 CAPABILITIES
|
821
|
+
|
822
|
+
- 先搜索和理解:你的主要优势在于在进行更改之前系统地探索和理解代码库。使用 list_files、execute_command(grep)来映射项目结构、识别模式和理解依赖关系,这种先探索的方法对于可靠的代码修改至关重要。
|
823
|
+
- 你可以使用允许你在用户的计算机上执行 CLI 命令、列出文件、查看源代码定义、正则表达式搜索、读取和编辑文件以及提出后续问题的工具。这些工具可帮助你有效地完成广泛的任务,如编写代码、对现有文件进行编辑或改进、了解项目的当前状态、执行系统操作等。
|
824
|
+
- 当用户最初给你一个任务时,环境详细信息中将包含当前工作目录 {{current_project}} 中所有文件路径的递归列表。这提供了项目文件结构的概述,从目录 / 文件名(开发人员如何概念化和组织他们的代码)和文件扩展名(使用的语言)中提供关键见解,这也可以指导关于进一步探索哪些文件的决策。如果你需要进一步探索当前工作目录之外的目录,如桌面,你可以使用 list_files 工具。如果为 recursive 参数传递 'true',它将递归列出文件,否则,它将列出顶级文件,这更适合通用目录,如桌面,你不一定需要嵌套结构。
|
825
|
+
- 你可以使用 shell_command (grep) 在指定目录的文件中执行正则表达式搜索,输出包含周围行的富含上下文的结果,这对于理解代码模式、查找特定实现或识别需要重构的区域特别有用。
|
826
|
+
- 你可以使用 list_code_definition_names 工具获取指定目录顶级所有文件的源代码定义概述,这在需要了解更广泛的上下文和某些代码部分之间的关系时特别有用。你可能需要多次调用此工具来了解与任务相关的代码库的各个部分。例如,当被要求进行编辑或改进时,你可以分析初始环境详细信息中的文件结构以获取项目概述,然后使用 list_code_definition_names 使用位于相关目录中的文件的源代码定义来获取进一步的见解,然后使用 read_file 检查相关文件的内容,分析代码并提出改进建议或进行必要的编辑,然后使用 replace_in_file 工具实施更改。如果你重构了可能影响代码库其他部分的代码,你可以使用 shell 命令 (grep) 来确保根据需要更新其他文件。
|
827
|
+
- 你可以使用 execute_command 工具在用户的计算机上运行命令,只要你认为这有助于完成用户的任务。当你需要执行 CLI 命令时,你必须清楚地解释命令的作用。相对于创建可执行脚本,优先执行复杂的 CLI 命令,因为它们更灵活且易于运行。允许交互式和长时间运行的命令,因为命令在用户的 VSCode 终端中运行。用户可能会在后台保持命令运行,你会一路收到它们的状态更新。你执行的每个命令都在新的终端实例中运行。
|
828
|
+
|
829
|
+
=====
|
830
|
+
|
831
|
+
# 规则 RULES
|
832
|
+
|
833
|
+
- 你当前的工作目录是:{{current_project}}
|
834
|
+
- 编辑前必须搜索:在编辑任何文件之前,你必须首先搜索以了解其上下文、依赖关系和使用模式。使用 list_files 或 grep 命令查找相关代码、导入和引用。
|
835
|
+
- 通过搜索验证:进行更改后,使用 list_files 或 grep 命令验证是否没有过时的引用残留,并且新代码是否与现有模式正确集成。
|
836
|
+
- 你不能cd到不同的目录来完成任务,你只能在{{ current_project }}中操作,因此在使用需要路径的工具时,请确保传递正确的 'path' 参数。
|
837
|
+
- 不要使用 ~ 字符或 $HOME 来引用主目录。
|
838
|
+
- 在使用 execute_command 工具之前,你必须首先考虑提供的系统信息上下文,以了解用户的环境,并调整你的命令以确保它们与用户的系统兼容。你还必须考虑是否需要在当前工作目录 {{current_project}} 之外的特定目录中执行命令,如果是这样,需在命令前加上cd到该目录 && 然后执行命令(作为一个命令,因为你只能在 {{current_project}} 中操作)。例如,如果你需要在 {{current_project}} 之外的项目中运行npm install,你需要在前面加上cd,即此命令的伪代码为cd (项目路径) && (命令,在这种情况下为npm install)。
|
839
|
+
- 使用 shell 命令工具(grep)时,仔细设计正则表达式模式以平衡特异性和灵活性。根据用户的任务,你可以使用它来查找代码模式、注释、待办事项、函数定义或项目中的任何基于文本的信息。结果包括上下文,因此分析周围的代码以更好地理解匹配项。将 shell 命令工具 (grep) 与其他工具结合使用以进行更全面的分析。例如,使用它查找特定的代码模式,然后使用 read_file 检查有趣匹配项的完整上下文,然后再使用 replace_in_file 进行明智的更改。
|
840
|
+
- 创建新项目(如应用程序、网站或任何软件项目)时,除非用户另有说明,否则将所有新文件组织在专用项目目录中。创建文件时使用适当的文件路径,因为 write_to_file 工具会自动创建任何必要的目录。根据项目的特定类型,逻辑地构建项目结构,遵循最佳实践。除非另有说明,新项目应无需额外设置即可轻松运行,例如大多数项目可以使用 HTML、CSS 和 JavaScript 构建 - 你可以在浏览器中打开。
|
841
|
+
- 确定适当的结构和要包含的文件时,一定要考虑项目的类型(如 Python、JavaScript、Web 应用程序)。还可以考虑哪些文件可能与完成任务最相关,例如查看项目的清单文件将帮助你了解项目的依赖关系,你可以将其纳入你编写的任何代码中。
|
842
|
+
- 对代码进行更改时,始终考虑代码使用的上下文。确保你的更改与现有代码库兼容,并遵循项目的编码标准和最佳实践。
|
843
|
+
- 当你想修改文件时,直接使用 replace_in_file 或 write_to_file 工具进行所需的更改,无需在使用工具前显示更改。
|
844
|
+
- 不要询问不必要的信息。使用提供的工具高效有效地完成用户的请求。完成任务后,必须使用 attempt_completion 工具向用户展示结果。用户可能会提供反馈,你可以利用反馈进行改进并再次尝试。
|
845
|
+
- 你只能使用 ask_followup_question 工具向用户提问。仅当需要额外的细节来完成任务时使用此工具,并且一定要提出清晰简洁的问题,帮助你推进任务。但是,如果你可以使用可用工具避免向用户提问,你应该这样做。例如,如果用户提到一个可能在桌面等外部目录中的文件,你应该使用 list_files 工具列出桌面中的文件,并检查用户提到的文件是否在其中,而不是要求用户自己提供文件路径。
|
846
|
+
- 执行命令时,如果没有看到预期的输出,假设终端成功执行了命令并继续执行任务。用户的终端可能无法正确流式传输输出。如果你绝对需要查看实际的终端输出,请使用 ask_followup_question 工具请求用户将其复制并粘贴回给你。
|
847
|
+
- 用户可能会在其消息中直接提供文件的内容,在这种情况下,你不应使用 read_file 工具再次获取文件内容,因为你已经拥有它。
|
848
|
+
- 你的目标是尝试完成用户的任务,而不是进行来回对话。
|
849
|
+
- 切勿以 “Great”、“Certainly”、“Okay”、“Sure” 等词开头你的消息,你的回复不应是对话式的,而应直接切中要点。例如,你不应说 “Great, I've updated the CSS”,而应说 “I've updated the CSS”。重要的是你在消息中要清晰和专业。
|
850
|
+
- 当呈现图像时,利用你的视觉能力彻底检查它们并提取有意义的信息。在完成用户的任务时,将这些见解纳入你的思维过程。
|
851
|
+
- 在每条用户消息的末尾,你将自动收到 environment_details。此信息不是由用户自己编写的,而是自动生成的,以提供有关项目结构和环境的潜在相关上下文。虽然此信息对于理解项目上下文可能很有价值,但不要将其视为用户请求或响应的直接部分。使用它来为你的操作和决策提供信息,但不要假设用户明确询问或提及此信息,除非他们在消息中明确这样做。使用 environment_details 时,清楚地解释你的操作,以确保用户理解,因为他们可能不知道这些细节。
|
852
|
+
- 执行命令前,检查 environment_details 中的 “Actively Running Terminals” 部分。如果存在,考虑这些活动进程可能如何影响你的任务。例如,如果已经在运行本地开发服务器,你无需再次启动它。如果未列出活动终端,正常继续执行命令。
|
853
|
+
- 使用 replace_in_file 工具时,你必须在 SEARCH 块中包含完整的行,而不是部分行。系统需要精确的行匹配,无法匹配部分行。例如,如果你想匹配包含 “const x = 5;” 的行,你的 SEARCH 块必须包含整行,而不仅仅是 “x = 5” 或其他片段。
|
854
|
+
- 使用 replace_in_file 工具时,如果使用多个 SEARCH/REPLACE 块,请按它们在文件中出现的顺序列出。例如,如果需要对第 10 行和第 50 行进行更改,首先包含第 10 行的 SEARCH/REPLACE 块,然后是第 50 行的 SEARCH/REPLACE 块。
|
855
|
+
- 至关重要的是,你要在每次工具使用后等待用户的响应,以确认工具使用成功。例如,如果要求制作待办事项应用程序,你将创建一个文件,等待用户响应它已成功创建,然后在需要时创建另一个文件,等待用户响应它已成功创建,等等。
|
856
|
+
- 要显示 LaTeX 公式,使用单个美元符号包裹行内公式,如$E=mc^2$,使用双美元符号包裹块级公式,如$$\frac{d}{dx}e^x = e^x$$。
|
857
|
+
- 要包含流程图或图表,你可以使用 Mermaid 语法。
|
858
|
+
- 如果你遇到一些未知或不熟悉的概念或术语,或者用户在提问,你可以尝试使用适当的 MCP 或 RAG 服务来获取信息。
|
859
|
+
|
860
|
+
|
861
|
+
=====
|
862
|
+
|
863
|
+
{% if extra_docs %}
|
864
|
+
用户提供的规则或文档 RULES OR DOCUMENTS PROVIDED BY USER
|
865
|
+
以下规则由用户提供,你必须严格遵守。
|
866
|
+
<user_rule_or_document_files>
|
867
|
+
{% for key, value in extra_docs.items() %}
|
868
|
+
<user_rule_or_document_file>
|
869
|
+
##File: {{ key }}
|
870
|
+
{{ value }}
|
871
|
+
</user_rule_or_document_file>
|
872
|
+
{% endfor %}
|
873
|
+
</user_rule_or_document_files>
|
874
|
+
确保你始终通过使用 read_file 工具根据用户的具体要求获取 index.md 中列出的相关 RULE 文件来开始你的任务。
|
875
|
+
{% endif %}
|
876
|
+
|
877
|
+
=====
|
878
|
+
|
879
|
+
{% if file_paths_str %}
|
880
|
+
|
881
|
+
用户提到的文件 FILES MENTIONED BY USER
|
882
|
+
|
883
|
+
以下是用户提到的文件或目录。
|
884
|
+
确保你始终通过使用 read_file 工具获取文件的内容或使用 list_files 工具列出提到的目录中包含的文件来开始你的任务。
|
885
|
+
如果是目录,请使用 list_files 查看它包含哪些文件,并根据需要使用 read_file 读取文件。如果是文件,请使用 read_file 读取文件。
|
886
|
+
|
887
|
+
<files>
|
888
|
+
{{file_paths_str}}
|
889
|
+
</files>
|
890
|
+
{% endif %}
|
891
|
+
|
892
|
+
=====
|
893
|
+
|
894
|
+
系统信息 SYSTEM INFORMATION
|
895
|
+
|
896
|
+
操作系统:{{os_distribution}}
|
897
|
+
默认 Shell:{{shell_type}}
|
898
|
+
主目录:{{home_dir}}
|
899
|
+
当前工作目录:{{current_project}}
|
900
|
+
|
901
|
+
====
|
902
|
+
|
903
|
+
目标 OBJECTIVE
|
904
|
+
|
905
|
+
你以迭代方式完成给定任务,将其分解为清晰的步骤并系统地完成它们。
|
906
|
+
|
907
|
+
1. 分析用户的任务并设定明确、可实现的目标以完成它,按逻辑顺序对这些目标进行优先排序。
|
908
|
+
2. 按顺序处理这些目标,根据需要一次使用一个可用工具。每个目标应对应你解决问题过程中的一个不同步骤。你会在进行过程中了解已完成的工作和剩余的工作。
|
909
|
+
3. 记住,你拥有广泛的能力,可以根据需要以强大和巧妙的方式使用各种工具来完成每个目标。在调用工具之前,在<thinking></thinking>标签中进行一些分析。首先,分析环境详细信息中提供的文件结构,以获取上下文和有效推进的见解。然后,思考哪个提供的工具是完成用户任务最相关的工具。接下来,仔细检查相关工具的每个所需参数,并确定用户是否已直接提供或给出足够的信息来推断一个值。在决定参数是否可以推断时,仔细考虑所有上下文,看看它是否支持特定的值。如果所有必填参数都存在或可以合理推断,关闭思考标签并继续使用工具。但是,如果缺少必填参数的值,不要调用工具(甚至不要使用填充符填充缺失的参数),而是使用 ask_followup_question 工具要求用户提供缺失的参数。如果未提供可选参数的信息,不要询问。
|
910
|
+
4. 完成用户的任务后,你必须使用 attempt_completion 工具向用户展示任务的结果。你也可以提供一个 CLI 命令来展示你的任务结果;这在 Web 开发任务中特别有用,你可以运行例如open index.html来显示你构建的网站。
|
911
|
+
5. 用户可能会提供反馈,你可以利用反馈进行改进并再次尝试。但不要进行无意义的来回对话,即不要在回复结尾提出问题或提供进一步帮助。
|
912
|
+
6. 按顺序处理这些目标,始终以使用可用工具进行全面搜索和探索开始。对于任何与代码相关的任务,首先使用 list_files 了解结构,然后使用命令 (grep) 查找相关模式,并在进行更改前使用 read_file 检查上下文。
|
913
|
+
"""
|
914
|
+
env_info = detect_env()
|
915
|
+
shell_type = "bash"
|
916
|
+
if not env_info.has_bash:
|
917
|
+
shell_type = "cmd/powershell"
|
918
|
+
file_paths_str = "\n".join([file_source.module_name for file_source in self.files.sources])
|
919
|
+
# extra_docs = get_required_and_index_rules()
|
920
|
+
return {
|
921
|
+
"current_project": os.path.abspath(self.args.source_dir),
|
922
|
+
"home_dir": env_info.home_dir,
|
923
|
+
"os_distribution": env_info.os_name,
|
924
|
+
"shell_type": shell_type,
|
925
|
+
"extra_docs": "",
|
926
|
+
"file_paths_str": file_paths_str
|
927
|
+
}
|
928
|
+
|
929
|
+
@staticmethod
|
930
|
+
def _reconstruct_tool_xml(tool: BaseTool) -> str:
|
931
|
+
"""
|
932
|
+
Reconstructs the XML representation of a tool call from its Pydantic model.
|
933
|
+
"""
|
934
|
+
tool_tag = next((tag for tag, model in TOOL_MODEL_MAP.items() if isinstance(tool, model)), None)
|
935
|
+
if not tool_tag:
|
936
|
+
printer.print_text(f"找不到工具类型 {type(tool).__name__} 对应的标签名", style="red")
|
937
|
+
return f"<error>Could not find tag for tool {type(tool).__name__}</error>"
|
938
|
+
|
939
|
+
xml_parts = [f"<{tool_tag}>"]
|
940
|
+
for field_name, field_value in tool.model_dump(exclude_none=True).items():
|
941
|
+
# 根据类型格式化值,确保XML安全性
|
942
|
+
if isinstance(field_value, bool):
|
943
|
+
value_str = str(field_value).lower()
|
944
|
+
elif isinstance(field_value, (list, dict)):
|
945
|
+
# 目前对列表/字典使用简单字符串表示
|
946
|
+
# 如果需要且提示/LLM支持,可考虑在标签内使用JSON
|
947
|
+
# 对结构化数据使用JSON
|
948
|
+
value_str = json.dumps(field_value, ensure_ascii=False)
|
949
|
+
else:
|
950
|
+
value_str = str(field_value)
|
951
|
+
|
952
|
+
# 对值内容进行转义
|
953
|
+
escaped_value = xml.sax.saxutils.escape(value_str)
|
954
|
+
|
955
|
+
# 处理多行内容(如'content'或'diff')- 确保保留换行符
|
956
|
+
if '\n' in value_str:
|
957
|
+
# 如果内容跨越多行,在闭合标签前添加换行符以提高可读性
|
958
|
+
xml_parts.append(
|
959
|
+
f"<{field_name}>\n{escaped_value}\n</{field_name}>")
|
960
|
+
else:
|
961
|
+
xml_parts.append(
|
962
|
+
f"<{field_name}>{escaped_value}</{field_name}>")
|
963
|
+
xml_parts.append(f"</{tool_tag}>")
|
964
|
+
# 使用换行符连接以提高可读性,与提示示例保持一致
|
965
|
+
return "\n".join(xml_parts)
|
966
|
+
|
967
|
+
def analyze(
|
968
|
+
self, request: AgenticEditRequest
|
969
|
+
) -> Generator[Union[LLMOutputEvent, LLMThinkingEvent, ToolCallEvent, ToolResultEvent, CompletionEvent, ErrorEvent,
|
970
|
+
WindowLengthChangeEvent, TokenUsageEvent, PlanModeRespondEvent] | None, None, None]:
|
971
|
+
|
972
|
+
system_prompt = self._analyze.prompt(request)
|
973
|
+
printer.print_key_value(
|
974
|
+
{"长度(tokens)": f"{len(system_prompt)}"}, title="系统提示词"
|
975
|
+
)
|
976
|
+
|
977
|
+
conversations = [
|
978
|
+
{"role": "system", "content": system_prompt},
|
979
|
+
{"role": "user", "content": request.user_input}
|
980
|
+
]
|
981
|
+
|
982
|
+
self.current_conversations = conversations
|
983
|
+
|
984
|
+
# 计算初始对话窗口长度并触发事件
|
985
|
+
conversation_str = json.dumps(conversations, ensure_ascii=False)
|
986
|
+
current_tokens = len(conversation_str) # 暂时使用len
|
987
|
+
yield WindowLengthChangeEvent(tokens_used=current_tokens)
|
988
|
+
|
989
|
+
iteration_count = 0
|
990
|
+
tool_executed = False
|
991
|
+
should_yield_completion_event = False
|
992
|
+
completion_event = None
|
993
|
+
|
994
|
+
while True:
|
995
|
+
iteration_count += 1
|
996
|
+
tool_executed = False
|
997
|
+
last_message = conversations[-1]
|
998
|
+
printer.print_key_value(
|
999
|
+
{"当前": f"第 {iteration_count} 轮", "历史会话长度": f"{len(conversations)}"}, title="LLM 交互循环"
|
1000
|
+
)
|
1001
|
+
|
1002
|
+
if last_message["role"] == "assistant":
|
1003
|
+
# printer.print_text(f"上一条消息来自 assistant,跳过LLM交互循环", style="green")
|
1004
|
+
if should_yield_completion_event:
|
1005
|
+
if completion_event is None:
|
1006
|
+
yield CompletionEvent(completion=AttemptCompletionTool(
|
1007
|
+
result=last_message["content"],
|
1008
|
+
command=""
|
1009
|
+
), completion_xml="")
|
1010
|
+
else:
|
1011
|
+
yield completion_event
|
1012
|
+
break
|
1013
|
+
|
1014
|
+
assistant_buffer = ""
|
1015
|
+
|
1016
|
+
# 实际请求大模型
|
1017
|
+
llm_response_gen = stream_chat_with_continue(
|
1018
|
+
llm=self.llm,
|
1019
|
+
conversations=conversations,
|
1020
|
+
llm_config={}, # Placeholder for future LLM configs
|
1021
|
+
args=self.args
|
1022
|
+
)
|
1023
|
+
|
1024
|
+
parsed_events = self.stream_and_parse_llm_response(llm_response_gen)
|
1025
|
+
|
1026
|
+
event_count = 0
|
1027
|
+
mark_event_should_finish = False
|
1028
|
+
for event in parsed_events:
|
1029
|
+
event_count += 1
|
1030
|
+
|
1031
|
+
if mark_event_should_finish:
|
1032
|
+
if isinstance(event, TokenUsageEvent):
|
1033
|
+
yield event
|
1034
|
+
continue
|
1035
|
+
|
1036
|
+
if isinstance(event, (LLMOutputEvent, LLMThinkingEvent)):
|
1037
|
+
assistant_buffer += event.text
|
1038
|
+
# printer.print_text(f"当前助手缓冲区累计字符数:{len(assistant_buffer)}", style="green")
|
1039
|
+
yield event # Yield text/thinking immediately for display
|
1040
|
+
|
1041
|
+
elif isinstance(event, ToolCallEvent):
|
1042
|
+
tool_executed = True
|
1043
|
+
tool_obj = event.tool
|
1044
|
+
tool_name = type(tool_obj).__name__
|
1045
|
+
tool_xml = event.tool_xml # Already reconstructed by parser
|
1046
|
+
|
1047
|
+
# Append assistant's thoughts and the tool call to history
|
1048
|
+
printer.print_panel(content=f"tool_xml \n{tool_xml}", title=f"🛠️ 工具触发: {tool_name}", center=True)
|
1049
|
+
|
1050
|
+
# 记录当前对话的token数量
|
1051
|
+
conversations.append({
|
1052
|
+
"role": "assistant",
|
1053
|
+
"content": assistant_buffer + tool_xml
|
1054
|
+
})
|
1055
|
+
assistant_buffer = "" # Reset buffer after tool call
|
1056
|
+
|
1057
|
+
# 计算当前对话的总 token 数量并触发事件
|
1058
|
+
current_conversation_str = json.dumps(conversations, ensure_ascii=False)
|
1059
|
+
total_tokens = self.count_tokens(current_conversation_str)
|
1060
|
+
yield WindowLengthChangeEvent(tokens_used=total_tokens)
|
1061
|
+
|
1062
|
+
yield event # Yield the ToolCallEvent for display
|
1063
|
+
|
1064
|
+
# Handle AttemptCompletion separately as it ends the loop
|
1065
|
+
if isinstance(tool_obj, AttemptCompletionTool):
|
1066
|
+
printer.print_panel(content=f"完成结果: {tool_obj.result[:50]}...",
|
1067
|
+
title="AttemptCompletionTool,正在结束会话", center=True)
|
1068
|
+
completion_event = CompletionEvent(completion=tool_obj, completion_xml=tool_xml)
|
1069
|
+
# save_formatted_log(self.args.source_dir, json.dumps(conversations, ensure_ascii=False),
|
1070
|
+
# "agentic_conversation")
|
1071
|
+
mark_event_should_finish = True
|
1072
|
+
should_yield_completion_event = True
|
1073
|
+
continue
|
1074
|
+
|
1075
|
+
if isinstance(tool_obj, PlanModeRespondTool):
|
1076
|
+
printer.print_panel(content=f"Plan 模式响应内容: {tool_obj.response[:50]}...",
|
1077
|
+
title="PlanModeRespondTool,正在结束会话", center=True)
|
1078
|
+
yield PlanModeRespondEvent(completion=tool_obj, completion_xml=tool_xml)
|
1079
|
+
# save_formatted_log(self.args.source_dir, json.dumps(conversations, ensure_ascii=False),
|
1080
|
+
# "agentic_conversation")
|
1081
|
+
mark_event_should_finish = True
|
1082
|
+
continue
|
1083
|
+
|
1084
|
+
# Resolve the tool
|
1085
|
+
resolver_cls = TOOL_RESOLVER_MAP.get(type(tool_obj))
|
1086
|
+
if not resolver_cls:
|
1087
|
+
tool_result = ToolResult(
|
1088
|
+
success=False, message="错误:工具解析器未实现.", content=None)
|
1089
|
+
result_event = ToolResultEvent(tool_name=type(tool_obj).__name__, result=tool_result)
|
1090
|
+
error_xml = (f"<tool_result tool_name='{type(tool_obj).__name__}' success='false'>"
|
1091
|
+
f"<message>Error: Tool resolver not implemented.</message>"
|
1092
|
+
f"<content></content></tool_result>")
|
1093
|
+
else:
|
1094
|
+
try:
|
1095
|
+
resolver = resolver_cls(agent=self, tool=tool_obj, args=self.args)
|
1096
|
+
tool_result: ToolResult = resolver.resolve()
|
1097
|
+
result_event = ToolResultEvent(tool_name=type(tool_obj).__name__, result=tool_result)
|
1098
|
+
|
1099
|
+
# Prepare XML for conversation history
|
1100
|
+
escaped_message = xml.sax.saxutils.escape(tool_result.message)
|
1101
|
+
content_str = str(
|
1102
|
+
tool_result.content) if tool_result.content is not None else ""
|
1103
|
+
escaped_content = xml.sax.saxutils.escape(
|
1104
|
+
content_str)
|
1105
|
+
error_xml = (
|
1106
|
+
f"<tool_result tool_name='{type(tool_obj).__name__}' success='{str(tool_result.success).lower()}'>"
|
1107
|
+
f"<message>{escaped_message}</message>"
|
1108
|
+
f"<content>{escaped_content}</content>"
|
1109
|
+
f"</tool_result>"
|
1110
|
+
)
|
1111
|
+
except Exception as e:
|
1112
|
+
error_message = f"Critical Error during tool execution: {e}"
|
1113
|
+
tool_result = ToolResult(success=False, message=error_message, content=None)
|
1114
|
+
result_event = ToolResultEvent(tool_name=type(tool_obj).__name__, result=tool_result)
|
1115
|
+
escaped_error = xml.sax.saxutils.escape(error_message)
|
1116
|
+
error_xml = (f"<tool_result tool_name='{type(tool_obj).__name__}' success='false'>"
|
1117
|
+
f"<message>{escaped_error}</message>"
|
1118
|
+
f"<content></content></tool_result>")
|
1119
|
+
|
1120
|
+
yield result_event # Yield the ToolResultEvent for display
|
1121
|
+
|
1122
|
+
# 添加工具结果到对话历史
|
1123
|
+
conversations.append({
|
1124
|
+
"role": "user", # Simulating the user providing the tool result
|
1125
|
+
"content": error_xml
|
1126
|
+
})
|
1127
|
+
|
1128
|
+
# 计算当前对话的总 token 数量并触发事件
|
1129
|
+
current_conversation_str = json.dumps(conversations, ensure_ascii=False)
|
1130
|
+
total_tokens = self.count_tokens(current_conversation_str)
|
1131
|
+
yield WindowLengthChangeEvent(tokens_used=total_tokens)
|
1132
|
+
|
1133
|
+
# 一次交互只能有一次工具,剩下的其实就没有用了,但是如果不让流式处理完,我们就无法获取服务端
|
1134
|
+
# 返回的token消耗和计费,所以通过此标记来完成进入空转,直到流式走完,获取到最后的token消耗和计费
|
1135
|
+
mark_event_should_finish = True
|
1136
|
+
|
1137
|
+
elif isinstance(event, ErrorEvent):
|
1138
|
+
yield event # Pass through errors
|
1139
|
+
# Optionally stop the process on parsing errors
|
1140
|
+
# logger.error("Stopping analyze loop due to parsing error.")
|
1141
|
+
# return
|
1142
|
+
elif isinstance(event, TokenUsageEvent):
|
1143
|
+
yield event
|
1144
|
+
|
1145
|
+
if not tool_executed:
|
1146
|
+
# No tool executed in this LLM response cycle
|
1147
|
+
printer.print_text("LLM响应完成, 未执行任何工具", style="yellow")
|
1148
|
+
if assistant_buffer:
|
1149
|
+
printer.print_text(f"将 Assistant Buffer 内容写入会话历史(字符数:{len(assistant_buffer)})")
|
1150
|
+
|
1151
|
+
last_message = conversations[-1]
|
1152
|
+
if last_message["role"] != "assistant":
|
1153
|
+
printer.print_text("添加新的 Assistant 消息", style="green")
|
1154
|
+
conversations.append({"role": "assistant", "content": assistant_buffer})
|
1155
|
+
elif last_message["role"] == "assistant":
|
1156
|
+
printer.print_text("追加已存在的 Assistant 消息")
|
1157
|
+
last_message["content"] += assistant_buffer
|
1158
|
+
|
1159
|
+
# 计算当前对话的总 token 数量并触发事件
|
1160
|
+
current_conversation_str = json.dumps(conversations, ensure_ascii=False)
|
1161
|
+
total_tokens = self.count_tokens(current_conversation_str)
|
1162
|
+
yield WindowLengthChangeEvent(tokens_used=total_tokens)
|
1163
|
+
|
1164
|
+
# 添加系统提示,要求LLM必须使用工具或明确结束,而不是直接退出
|
1165
|
+
printer.print_text("正在添加系统提示: 请使用工具或尝试直接生成结果", style="green")
|
1166
|
+
|
1167
|
+
conversations.append({
|
1168
|
+
"role": "user",
|
1169
|
+
"content": "NOTE: You must use an appropriate tool (such as read_file, write_to_file, "
|
1170
|
+
"execute_command, etc.) or explicitly complete the task (using attempt_completion). Do "
|
1171
|
+
"not provide text responses without taking concrete actions. Please select a suitable "
|
1172
|
+
"tool to continue based on the user's task."
|
1173
|
+
})
|
1174
|
+
|
1175
|
+
# 计算当前对话的总 token 数量并触发事件
|
1176
|
+
current_conversation_str = json.dumps(conversations, ensure_ascii=False)
|
1177
|
+
total_tokens = self.count_tokens(current_conversation_str)
|
1178
|
+
yield WindowLengthChangeEvent(tokens_used=total_tokens)
|
1179
|
+
# 继续循环,让 LLM 再思考,而不是 break
|
1180
|
+
printer.print_text("持续运行 LLM 交互循环(保持不中断)", style="green")
|
1181
|
+
continue
|
1182
|
+
|
1183
|
+
printer.print_text(f"AgenticEdit 分析循环已完成,共执行 {iteration_count} 次迭代.")
|
1184
|
+
save_formatted_log(self.args.source_dir, json.dumps(conversations, ensure_ascii=False), "agentic_conversation")
|
1185
|
+
|
1186
|
+
def stream_and_parse_llm_response(
|
1187
|
+
self, generator: Generator[Tuple[str, Any], None, None]
|
1188
|
+
) -> Generator[Union[LLMOutputEvent, LLMThinkingEvent, ToolCallEvent, ErrorEvent, TokenUsageEvent], None, None]:
|
1189
|
+
buffer = ""
|
1190
|
+
in_tool_block = False
|
1191
|
+
in_thinking_block = False
|
1192
|
+
current_tool_tag = None
|
1193
|
+
tool_start_pattern = re.compile(r"<(?!thinking\b)([a-zA-Z0-9_]+)>") # Matches tool tags
|
1194
|
+
thinking_start_tag = "<thinking>"
|
1195
|
+
thinking_end_tag = "</thinking>"
|
1196
|
+
|
1197
|
+
def parse_tool_xml(tool_xml: str, tool_tag: str) -> Optional[BaseTool]:
|
1198
|
+
""" Agent工具 XML字符串 解析器 """
|
1199
|
+
params = {}
|
1200
|
+
try:
|
1201
|
+
# 在<tool_tag>和</tool_tag>之间查找内容
|
1202
|
+
inner_xml_match = re.search(rf"<{tool_tag}>(.*?)</{tool_tag}>", tool_xml, re.DOTALL)
|
1203
|
+
if not inner_xml_match:
|
1204
|
+
printer.print_text(f"无法在<{tool_tag}>...</{tool_tag}>标签内找到内容", style="red")
|
1205
|
+
return None
|
1206
|
+
inner_xml = inner_xml_match.group(1).strip()
|
1207
|
+
|
1208
|
+
# 在 tool_tag 内部内容中查找 <param>value</param> 参数键值对
|
1209
|
+
pattern = re.compile(r"<([a-zA-Z0-9_]+)>(.*?)</\1>", re.DOTALL)
|
1210
|
+
for m in pattern.finditer(inner_xml):
|
1211
|
+
key = m.group(1)
|
1212
|
+
# 基础的反转义处理(如果使用复杂值可能需要更健壮的反转义)
|
1213
|
+
val = xml.sax.saxutils.unescape(m.group(2))
|
1214
|
+
params[key] = val
|
1215
|
+
|
1216
|
+
tool_cls = TOOL_MODEL_MAP.get(tool_tag)
|
1217
|
+
if tool_cls:
|
1218
|
+
# 特别处理 requires_approval 的布尔值转换
|
1219
|
+
if 'requires_approval' in params:
|
1220
|
+
params['requires_approval'] = params['requires_approval'].lower() == 'true'
|
1221
|
+
# 特别处理 ask_followup_question_tool 的JSON解析
|
1222
|
+
if tool_tag == 'ask_followup_question' and 'options' in params:
|
1223
|
+
try:
|
1224
|
+
params['options'] = json.loads(params['options'])
|
1225
|
+
except json.JSONDecodeError:
|
1226
|
+
printer.print_text(f"ask_followup_question_tool 参数JSON解码失败: {params['options']}",
|
1227
|
+
style="red")
|
1228
|
+
# 保持为字符串还是处理错误?目前先保持为字符串
|
1229
|
+
pass
|
1230
|
+
if tool_tag == 'plan_mode_respond' and 'options' in params:
|
1231
|
+
try:
|
1232
|
+
params['options'] = json.loads(params['options'])
|
1233
|
+
except json.JSONDecodeError:
|
1234
|
+
printer.print_text(f"plan_mode_respond_tool 参数JSON解码失败: {params['options']}",
|
1235
|
+
style="red")
|
1236
|
+
# 处理 list_files 工具的递归参数
|
1237
|
+
if tool_tag == 'list_files' and 'recursive' in params:
|
1238
|
+
params['recursive'] = params['recursive'].lower() == 'true'
|
1239
|
+
return tool_cls(**params)
|
1240
|
+
else:
|
1241
|
+
printer.print_text(f"未找到标签对应的工具类: {tool_tag}", style="red")
|
1242
|
+
return None
|
1243
|
+
except Exception as e:
|
1244
|
+
printer.print_text(f"解析工具XML <{tool_tag}> 失败: {e}\nXML内容:\n{tool_xml}", style="red")
|
1245
|
+
return None
|
1246
|
+
|
1247
|
+
last_metadata = None
|
1248
|
+
for content_chunk, metadata in generator:
|
1249
|
+
if not content_chunk:
|
1250
|
+
last_metadata = metadata
|
1251
|
+
continue
|
1252
|
+
|
1253
|
+
last_metadata = metadata
|
1254
|
+
buffer += content_chunk
|
1255
|
+
|
1256
|
+
while True:
|
1257
|
+
# Check for transitions: thinking -> text, tool -> text, text -> thinking, text -> tool
|
1258
|
+
found_event = False
|
1259
|
+
|
1260
|
+
# 1. Check for </thinking> if inside thinking block
|
1261
|
+
if in_thinking_block:
|
1262
|
+
end_think_pos = buffer.find(thinking_end_tag)
|
1263
|
+
if end_think_pos != -1:
|
1264
|
+
thinking_content = buffer[:end_think_pos]
|
1265
|
+
yield LLMThinkingEvent(text=thinking_content)
|
1266
|
+
buffer = buffer[end_think_pos + len(thinking_end_tag):]
|
1267
|
+
in_thinking_block = False
|
1268
|
+
found_event = True
|
1269
|
+
continue # Restart loop with updated buffer/state
|
1270
|
+
else:
|
1271
|
+
# Need more data to close thinking block
|
1272
|
+
break
|
1273
|
+
|
1274
|
+
# 2. Check for </tool_tag> if inside tool block
|
1275
|
+
elif in_tool_block:
|
1276
|
+
end_tag = f"</{current_tool_tag}>"
|
1277
|
+
end_tool_pos = buffer.find(end_tag)
|
1278
|
+
if end_tool_pos != -1:
|
1279
|
+
tool_block_end_index = end_tool_pos + len(end_tag)
|
1280
|
+
tool_xml = buffer[:tool_block_end_index]
|
1281
|
+
tool_obj = parse_tool_xml(tool_xml, current_tool_tag)
|
1282
|
+
|
1283
|
+
if tool_obj:
|
1284
|
+
# Reconstruct the XML accurately here AFTER successful parsing
|
1285
|
+
# This ensures the XML yielded matches what was parsed.
|
1286
|
+
reconstructed_xml = self._reconstruct_tool_xml(tool_obj)
|
1287
|
+
if reconstructed_xml.startswith("<error>"):
|
1288
|
+
yield ErrorEvent(message=f"Failed to reconstruct XML for tool {current_tool_tag}")
|
1289
|
+
else:
|
1290
|
+
yield ToolCallEvent(tool=tool_obj, tool_xml=reconstructed_xml)
|
1291
|
+
else:
|
1292
|
+
yield ErrorEvent(message=f"Failed to parse tool: <{current_tool_tag}>")
|
1293
|
+
# Optionally yield the raw XML as plain text?
|
1294
|
+
# yield LLMOutputEvent(text=tool_xml)
|
1295
|
+
|
1296
|
+
buffer = buffer[tool_block_end_index:]
|
1297
|
+
in_tool_block = False
|
1298
|
+
current_tool_tag = None
|
1299
|
+
found_event = True
|
1300
|
+
continue # Restart loop
|
1301
|
+
else:
|
1302
|
+
# Need more data to close tool block
|
1303
|
+
break
|
1304
|
+
|
1305
|
+
# 3. Check for <thinking> or <tool_tag> if in plain text state
|
1306
|
+
else:
|
1307
|
+
start_think_pos = buffer.find(thinking_start_tag)
|
1308
|
+
tool_match = tool_start_pattern.search(buffer)
|
1309
|
+
start_tool_pos = tool_match.start() if tool_match else -1
|
1310
|
+
tool_name = tool_match.group(1) if tool_match else None
|
1311
|
+
|
1312
|
+
# Determine which tag comes first (if any)
|
1313
|
+
first_tag_pos = -1
|
1314
|
+
is_thinking = False
|
1315
|
+
is_tool = False
|
1316
|
+
|
1317
|
+
if start_think_pos != -1 and (start_tool_pos == -1 or start_think_pos < start_tool_pos):
|
1318
|
+
first_tag_pos = start_think_pos
|
1319
|
+
is_thinking = True
|
1320
|
+
elif start_tool_pos != -1 and (start_think_pos == -1 or start_tool_pos < start_think_pos):
|
1321
|
+
# Check if it's a known tool
|
1322
|
+
if tool_name in TOOL_MODEL_MAP:
|
1323
|
+
first_tag_pos = start_tool_pos
|
1324
|
+
is_tool = True
|
1325
|
+
else:
|
1326
|
+
# Unknown tag, treat as text for now, let buffer grow
|
1327
|
+
pass
|
1328
|
+
|
1329
|
+
if first_tag_pos != -1: # Found either <thinking> or a known <tool>
|
1330
|
+
# Yield preceding text if any
|
1331
|
+
preceding_text = buffer[:first_tag_pos]
|
1332
|
+
if preceding_text:
|
1333
|
+
yield LLMOutputEvent(text=preceding_text)
|
1334
|
+
|
1335
|
+
# Transition state
|
1336
|
+
if is_thinking:
|
1337
|
+
buffer = buffer[first_tag_pos + len(thinking_start_tag):]
|
1338
|
+
in_thinking_block = True
|
1339
|
+
elif is_tool:
|
1340
|
+
# Keep the starting tag
|
1341
|
+
buffer = buffer[first_tag_pos:]
|
1342
|
+
in_tool_block = True
|
1343
|
+
current_tool_tag = tool_name
|
1344
|
+
|
1345
|
+
found_event = True
|
1346
|
+
continue # Restart loop
|
1347
|
+
else:
|
1348
|
+
# No tags found, or only unknown tags found. Need more data or end of stream.
|
1349
|
+
# Yield text chunk but keep some buffer for potential tag start
|
1350
|
+
# Keep last 100 chars
|
1351
|
+
split_point = max(0, len(buffer) - 1024)
|
1352
|
+
text_to_yield = buffer[:split_point]
|
1353
|
+
if text_to_yield:
|
1354
|
+
yield LLMOutputEvent(text=text_to_yield)
|
1355
|
+
buffer = buffer[split_point:]
|
1356
|
+
break # Need more data
|
1357
|
+
# If no event was processed in this iteration, break inner loop
|
1358
|
+
if not found_event:
|
1359
|
+
break
|
1360
|
+
|
1361
|
+
# After generator exhausted, yield any remaining content
|
1362
|
+
if in_thinking_block:
|
1363
|
+
# Unterminated thinking block
|
1364
|
+
yield ErrorEvent(message="Stream ended with unterminated <thinking> block.")
|
1365
|
+
if buffer:
|
1366
|
+
# Yield remaining as thinking
|
1367
|
+
yield LLMThinkingEvent(text=buffer)
|
1368
|
+
elif in_tool_block:
|
1369
|
+
# Unterminated tool block
|
1370
|
+
yield ErrorEvent(message=f"Stream ended with unterminated <{current_tool_tag}> block.")
|
1371
|
+
if buffer:
|
1372
|
+
yield LLMOutputEvent(text=buffer) # Yield remaining as text
|
1373
|
+
elif buffer:
|
1374
|
+
# Yield remaining plain text
|
1375
|
+
yield LLMOutputEvent(text=buffer)
|
1376
|
+
|
1377
|
+
# 这个要放在最后,防止其他关联的多个事件的信息中断
|
1378
|
+
yield TokenUsageEvent(usage=last_metadata)
|
1379
|
+
|
1380
|
+
def apply_pre_changes(self):
|
1381
|
+
if not self.args.skip_commit:
|
1382
|
+
try:
|
1383
|
+
commit_message = commit_changes(self.args.source_dir, f"auto_coder_nano_agentic_edit")
|
1384
|
+
if commit_message:
|
1385
|
+
printer.print_text(f"Commit 成功", style="green")
|
1386
|
+
except Exception as err:
|
1387
|
+
import traceback
|
1388
|
+
traceback.print_exc()
|
1389
|
+
printer.print_text(f"Commit 失败: {err}", style="red")
|
1390
|
+
return
|
1391
|
+
|
1392
|
+
def apply_changes(self):
|
1393
|
+
""" Apply all tracked file changes to the original project directory. """
|
1394
|
+
changes = get_uncommitted_changes(self.args.source_dir)
|
1395
|
+
|
1396
|
+
if changes != "No uncommitted changes found.":
|
1397
|
+
if not self.args.skip_commit:
|
1398
|
+
try:
|
1399
|
+
commit_message = commit_changes(
|
1400
|
+
self.args.source_dir, f"{self.args.query}\nauto_coder_nano_agentic_edit",
|
1401
|
+
)
|
1402
|
+
if commit_message:
|
1403
|
+
printer.print_panel(content=f"Commit 成功", title="Commit 信息", center=True)
|
1404
|
+
except Exception as err:
|
1405
|
+
import traceback
|
1406
|
+
traceback.print_exc()
|
1407
|
+
printer.print_panel(content=f"Commit 失败: {err}", title="Commit 信息", center=True)
|
1408
|
+
else:
|
1409
|
+
printer.print_panel(content=f"未进行任何更改", title="Commit 信息", center=True)
|
1410
|
+
|
1411
|
+
def run_in_terminal(self, request: AgenticEditRequest):
|
1412
|
+
project_name = os.path.basename(os.path.abspath(self.args.source_dir))
|
1413
|
+
|
1414
|
+
printer.print_key_value(
|
1415
|
+
items={"项目名": f"{project_name}", "用户目标": f"{request.user_input}"}, title="Agentic Edit 开始运行"
|
1416
|
+
)
|
1417
|
+
|
1418
|
+
# 用于累计TokenUsageEvent数据
|
1419
|
+
accumulated_token_usage = {
|
1420
|
+
"model_name": "",
|
1421
|
+
"input_tokens": 0,
|
1422
|
+
"output_tokens": 0,
|
1423
|
+
}
|
1424
|
+
|
1425
|
+
try:
|
1426
|
+
self.apply_changes() # 在开始 Agentic Edit 之前先提交变更
|
1427
|
+
event_stream = self.analyze(request)
|
1428
|
+
for event in event_stream:
|
1429
|
+
if isinstance(event, TokenUsageEvent):
|
1430
|
+
last_meta: SingleOutputMeta = event.usage
|
1431
|
+
|
1432
|
+
# 累计token使用情况
|
1433
|
+
accumulated_token_usage["model_name"] = self.args.chat_model
|
1434
|
+
accumulated_token_usage["input_tokens"] += last_meta.input_tokens_count
|
1435
|
+
accumulated_token_usage["output_tokens"] += last_meta.generated_tokens_count
|
1436
|
+
|
1437
|
+
printer.print_key_value(accumulated_token_usage)
|
1438
|
+
|
1439
|
+
elif isinstance(event, WindowLengthChangeEvent):
|
1440
|
+
# 显示当前会话的token数量
|
1441
|
+
printer.print_panel(
|
1442
|
+
content=f"当前会话总 tokens: {event.tokens_used}", title="Window Length Change", center=True
|
1443
|
+
)
|
1444
|
+
|
1445
|
+
elif isinstance(event, LLMThinkingEvent):
|
1446
|
+
# Render thinking within a less prominent style, maybe grey?
|
1447
|
+
printer.print_panel(content=f"{event.text}", title="LLM Thinking", center=True)
|
1448
|
+
|
1449
|
+
elif isinstance(event, LLMOutputEvent):
|
1450
|
+
# Print regular LLM output, potentially as markdown if needed later
|
1451
|
+
printer.print_panel(
|
1452
|
+
content=f"{event.text}", title="LLM Output", center=True
|
1453
|
+
)
|
1454
|
+
|
1455
|
+
elif isinstance(event, ToolCallEvent):
|
1456
|
+
# Skip displaying AttemptCompletionTool's tool call
|
1457
|
+
if isinstance(event.tool, AttemptCompletionTool):
|
1458
|
+
continue # Do not display AttemptCompletionTool tool call
|
1459
|
+
|
1460
|
+
tool_name = type(event.tool).__name__
|
1461
|
+
# Use the new internationalized display function
|
1462
|
+
display_content = get_tool_display_message(event.tool)
|
1463
|
+
printer.print_panel(content=display_content, title=f"🛠️ 工具调用: {tool_name}", center=True)
|
1464
|
+
|
1465
|
+
elif isinstance(event, ToolResultEvent):
|
1466
|
+
# Skip displaying AttemptCompletionTool's result
|
1467
|
+
if event.tool_name == "AttemptCompletionTool":
|
1468
|
+
continue # Do not display AttemptCompletionTool result
|
1469
|
+
if event.tool_name == "PlanModeRespondTool":
|
1470
|
+
continue
|
1471
|
+
|
1472
|
+
result = event.result
|
1473
|
+
title = f"✅ 工具返回: {event.tool_name}" if result.success else f"❌ 工具返回: {event.tool_name}"
|
1474
|
+
border_style = "green" if result.success else "red"
|
1475
|
+
base_content = f"状态: {'成功' if result.success else '失败'}\n"
|
1476
|
+
base_content += f"信息: {result.message}\n"
|
1477
|
+
|
1478
|
+
def _format_content(_content):
|
1479
|
+
if len(_content) > 200:
|
1480
|
+
return f"{_content[:100]}\n...\n{_content[-100:]}"
|
1481
|
+
else:
|
1482
|
+
return _content
|
1483
|
+
|
1484
|
+
# Prepare panel for base info first
|
1485
|
+
panel_content = [base_content]
|
1486
|
+
# syntax_content = None
|
1487
|
+
content_str = ""
|
1488
|
+
lexer = "python" # Default guess
|
1489
|
+
|
1490
|
+
if result.content is not None:
|
1491
|
+
try:
|
1492
|
+
if isinstance(result.content, (dict, list)):
|
1493
|
+
content_str = json.dumps(result.content, indent=2, ensure_ascii=False)
|
1494
|
+
# syntax_content = Syntax(content_str, "json", theme="default", line_numbers=False)
|
1495
|
+
elif isinstance(result.content, str) and (
|
1496
|
+
'\n' in result.content or result.content.strip().startswith('<')):
|
1497
|
+
# Heuristic for code or XML/HTML
|
1498
|
+
if event.tool_name == "ReadFileTool" and isinstance(event.result.message, str):
|
1499
|
+
# Try to guess lexer from file extension in message
|
1500
|
+
if ".py" in event.result.message:
|
1501
|
+
lexer = "python"
|
1502
|
+
elif ".js" in event.result.message:
|
1503
|
+
lexer = "javascript"
|
1504
|
+
elif ".ts" in event.result.message:
|
1505
|
+
lexer = "typescript"
|
1506
|
+
elif ".html" in event.result.message:
|
1507
|
+
lexer = "html"
|
1508
|
+
elif ".css" in event.result.message:
|
1509
|
+
lexer = "css"
|
1510
|
+
elif ".json" in event.result.message:
|
1511
|
+
lexer = "json"
|
1512
|
+
elif ".xml" in event.result.message:
|
1513
|
+
lexer = "xml"
|
1514
|
+
elif ".md" in event.result.message:
|
1515
|
+
lexer = "markdown"
|
1516
|
+
else:
|
1517
|
+
lexer = "text" # Fallback lexer
|
1518
|
+
elif event.tool_name == "ExecuteCommandTool":
|
1519
|
+
lexer = "shell"
|
1520
|
+
else:
|
1521
|
+
lexer = "text"
|
1522
|
+
|
1523
|
+
content_str = str(result.content)
|
1524
|
+
# syntax_content = Syntax(
|
1525
|
+
# _format_content(result.content), lexer, theme="default", line_numbers=True
|
1526
|
+
# )
|
1527
|
+
else:
|
1528
|
+
content_str = str(result.content)
|
1529
|
+
# Append simple string content directly
|
1530
|
+
panel_content.append(_format_content(content_str))
|
1531
|
+
|
1532
|
+
except Exception as e:
|
1533
|
+
printer.print_text(f"Error formatting tool result content: {e}", style="yellow")
|
1534
|
+
panel_content.append(
|
1535
|
+
# Fallback
|
1536
|
+
_format_content(str(result.content)))
|
1537
|
+
|
1538
|
+
# Print the base info panel
|
1539
|
+
printer.print_panel(
|
1540
|
+
content="\n".join(panel_content), title=title, border_style=border_style, center=True)
|
1541
|
+
# Print syntax highlighted content separately if it exists
|
1542
|
+
if content_str:
|
1543
|
+
printer.print_code(
|
1544
|
+
code=content_str, lexer=lexer, theme="monokai", line_numbers=True, panel=True)
|
1545
|
+
|
1546
|
+
elif isinstance(event, PlanModeRespondEvent):
|
1547
|
+
printer.print_panel(
|
1548
|
+
content=Markdown(event.completion.response),
|
1549
|
+
title="🏁 任务完成", center=True
|
1550
|
+
)
|
1551
|
+
|
1552
|
+
elif isinstance(event, CompletionEvent):
|
1553
|
+
# 在这里完成实际合并
|
1554
|
+
try:
|
1555
|
+
self.apply_changes()
|
1556
|
+
except Exception as e:
|
1557
|
+
printer.print_text(f"Error merging shadow changes to project: {e}", style="red")
|
1558
|
+
|
1559
|
+
printer.print_panel(
|
1560
|
+
content=Markdown(event.completion.result),
|
1561
|
+
title="🏁 任务完成", center=True
|
1562
|
+
)
|
1563
|
+
if event.completion.command:
|
1564
|
+
printer.print_text(f"Suggested command:{event.completion.command}", style="green")
|
1565
|
+
|
1566
|
+
elif isinstance(event, ErrorEvent):
|
1567
|
+
printer.print_panel(
|
1568
|
+
content=f"Error: {event.message}",
|
1569
|
+
title="🔥 任务失败", center=True
|
1570
|
+
)
|
1571
|
+
|
1572
|
+
time.sleep(0.5) # Small delay for better visual flow
|
1573
|
+
|
1574
|
+
# 在处理完所有事件后打印累计的token使用情况
|
1575
|
+
printer.print_key_value(accumulated_token_usage)
|
1576
|
+
|
1577
|
+
except Exception as err:
|
1578
|
+
# 在处理异常时也打印累计的token使用情况
|
1579
|
+
if accumulated_token_usage["input_tokens"] > 0:
|
1580
|
+
printer.print_key_value(accumulated_token_usage)
|
1581
|
+
printer.print_panel(content=f"FATAL ERROR: {err}", title="🔥 Agentic Edit 运行错误", center=True)
|
1582
|
+
raise err
|
1583
|
+
finally:
|
1584
|
+
printer.print_text("Agentic Edit 结束", style="green")
|