auto-coder 0.1.336__py3-none-any.whl → 0.1.338__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of auto-coder might be problematic. Click here for more details.
- {auto_coder-0.1.336.dist-info → auto_coder-0.1.338.dist-info}/METADATA +1 -1
- {auto_coder-0.1.336.dist-info → auto_coder-0.1.338.dist-info}/RECORD +17 -16
- autocoder/agent/agentic_filter.py +14 -7
- autocoder/auto_coder_runner.py +63 -14
- autocoder/chat_auto_coder.py +1 -2
- autocoder/common/auto_coder_lang.py +44 -0
- autocoder/common/v2/agent/agentic_edit.py +136 -34
- autocoder/common/v2/agent/agentic_edit_conversation.py +178 -0
- autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +17 -8
- autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +6 -1
- autocoder/common/v2/agent/agentic_edit_types.py +5 -0
- autocoder/common/v2/agent/agentic_tool_display.py +3 -3
- autocoder/version.py +1 -1
- {auto_coder-0.1.336.dist-info → auto_coder-0.1.338.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.336.dist-info → auto_coder-0.1.338.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.336.dist-info → auto_coder-0.1.338.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.336.dist-info → auto_coder-0.1.338.dist-info}/top_level.txt +0 -0
|
@@ -4,21 +4,21 @@ autocoder/auto_coder_lang.py,sha256=Rtupq6N3_HT7JRhDKdgCBcwRaiAnyCOR_Gsp4jUomrI,
|
|
|
4
4
|
autocoder/auto_coder_rag.py,sha256=NesRm7sIJrRQL1xxm_lbMtM7gi-KrYv9f26RfBuloZE,35386
|
|
5
5
|
autocoder/auto_coder_rag_client_mcp.py,sha256=QRxUbjc6A8UmDMQ8lXgZkjgqtq3lgKYeatJbDY6rSo0,6270
|
|
6
6
|
autocoder/auto_coder_rag_mcp.py,sha256=-RrjNwFaS2e5v8XDIrKR-zlUNUE8UBaeOtojffBrvJo,8521
|
|
7
|
-
autocoder/auto_coder_runner.py,sha256=
|
|
7
|
+
autocoder/auto_coder_runner.py,sha256=Xrzeo9u7UIvIjKgaV12O-XItl10dhjogp-7TNIvqAP8,111518
|
|
8
8
|
autocoder/auto_coder_server.py,sha256=bLORGEclcVdbBVfM140JCI8WtdrU0jbgqdJIVVupiEU,20578
|
|
9
9
|
autocoder/benchmark.py,sha256=Ypomkdzd1T3GE6dRICY3Hj547dZ6_inqJbBJIp5QMco,4423
|
|
10
|
-
autocoder/chat_auto_coder.py,sha256=
|
|
10
|
+
autocoder/chat_auto_coder.py,sha256=CthuvdjVjTQOVv-zREsl8OCsZHPSP9OQcIgHULrW2Ro,25842
|
|
11
11
|
autocoder/chat_auto_coder_lang.py,sha256=RxkYAMWUB5ayX0x03yBOcEkjTcWG_EBsLXBC_bh--cc,22265
|
|
12
12
|
autocoder/command_args.py,sha256=HxflngkYtTrV17Vfgk6lyUyiG68jP2ftSc7FYr9AXwY,30585
|
|
13
13
|
autocoder/command_parser.py,sha256=fx1g9E6GaM273lGTcJqaFQ-hoksS_Ik2glBMnVltPCE,10013
|
|
14
14
|
autocoder/lang.py,sha256=PFtATuOhHRnfpqHQkXr6p4C893JvpsgwTMif3l-GEi0,14321
|
|
15
15
|
autocoder/models.py,sha256=_SCar82QIeBFTZZBdM2jPS6atKVhHnvE0gX3V0CsxD4,11590
|
|
16
16
|
autocoder/run_context.py,sha256=IUfSO6_gp2Wt1blFWAmOpN0b0nDrTTk4LmtCYUBIoro,1643
|
|
17
|
-
autocoder/version.py,sha256=
|
|
17
|
+
autocoder/version.py,sha256=oUXeUGWr7ewx0MgKWn_rfyWO62ZnbdWizzFCRsj8frY,23
|
|
18
18
|
autocoder/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
autocoder/agent/agentic_edit.py,sha256=XsfePZ-t6M-uBSdG1VLZXk1goqXk2HPeJ_A8IYyBuWQ,58896
|
|
20
20
|
autocoder/agent/agentic_edit_types.py,sha256=oFcDd_cxJ2yH9Ed1uTpD3BipudgoIEWDMPb5pAkq4gI,3288
|
|
21
|
-
autocoder/agent/agentic_filter.py,sha256=
|
|
21
|
+
autocoder/agent/agentic_filter.py,sha256=9Miqj9lVOkKrKRXK9MZDouhkp2n1s_RakpRwdmLfFtY,39516
|
|
22
22
|
autocoder/agent/auto_demand_organizer.py,sha256=URAq0gSEiHeV_W4zwhOI_83kHz0Ryfj1gcfh5jwCv_w,6501
|
|
23
23
|
autocoder/agent/auto_filegroup.py,sha256=pBsAkBcpFTff-9L5OwI8xhf2xPKpl-aZwz-skF2B6dc,6296
|
|
24
24
|
autocoder/agent/auto_guess_query.py,sha256=rDSdhpPHcOGE5MuDXvIrhCXAPR4ARS1LqpyoLsx2Jhw,11374
|
|
@@ -54,7 +54,7 @@ autocoder/common/action_yml_file_manager.py,sha256=DdF5P1R_B_chCnnqoA2IgogakWLZk
|
|
|
54
54
|
autocoder/common/anything2images.py,sha256=0ILBbWzY02M-CiWB-vzuomb_J1hVdxRcenAfIrAXq9M,25283
|
|
55
55
|
autocoder/common/anything2img.py,sha256=iZQmg8srXlD7N5uGl5b_ONKJMBjYoW8kPmokkG6ISF0,10118
|
|
56
56
|
autocoder/common/audio.py,sha256=Kn9nWKQddWnUrAz0a_ZUgjcu4VUU_IcZBigT7n3N3qc,7439
|
|
57
|
-
autocoder/common/auto_coder_lang.py,sha256=
|
|
57
|
+
autocoder/common/auto_coder_lang.py,sha256=ozoGTy4ZFn3YsO5zWhvAGCu54mK4LtnRfC2yCvrMc_8,42462
|
|
58
58
|
autocoder/common/auto_configure.py,sha256=D4N-fl9v8bKM5-Ds-uhkC2uGDmHH_ZjLJ759F8KXMKs,13129
|
|
59
59
|
autocoder/common/buildin_tokenizer.py,sha256=L7d5t39ZFvUd6EoMPXUhYK1toD0FHlRH1jtjKRGokWU,1236
|
|
60
60
|
autocoder/common/chunk_validation.py,sha256=BrR_ZWavW8IANuueEE7hS8NFAwEvm8TX34WnPx_1hs8,3030
|
|
@@ -126,9 +126,10 @@ autocoder/common/v2/code_editblock_manager.py,sha256=G0CIuV9Ki0FqMLnpA8nBT4pnkCN
|
|
|
126
126
|
autocoder/common/v2/code_manager.py,sha256=C403bS-f6urixwitlKHcml-J03hci-UyNwHJOqBiY6Q,9182
|
|
127
127
|
autocoder/common/v2/code_strict_diff_manager.py,sha256=v-J1kDyLg7tLGg_6_lbO9S4fNkx7M_L8Xr2G7fPptiU,9347
|
|
128
128
|
autocoder/common/v2/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
129
|
-
autocoder/common/v2/agent/agentic_edit.py,sha256=
|
|
130
|
-
autocoder/common/v2/agent/
|
|
131
|
-
autocoder/common/v2/agent/
|
|
129
|
+
autocoder/common/v2/agent/agentic_edit.py,sha256=mw85tMint-m6MA7OrPmrwOOZw_uW_N7fboTGfXNQYcE,88790
|
|
130
|
+
autocoder/common/v2/agent/agentic_edit_conversation.py,sha256=2GZrw5f8Sh9GgUDnuFc2v7Q4syrm77kKEfXI6k6uEFY,7310
|
|
131
|
+
autocoder/common/v2/agent/agentic_edit_types.py,sha256=6qBLLmvdlcsbzrpMHsYQVIHqbOWubMXOnmkqTs1pBWQ,4629
|
|
132
|
+
autocoder/common/v2/agent/agentic_tool_display.py,sha256=M-fFfmCzAyYrT916tXbc8HZxbv4ePksR9WqvSrhcZIs,7345
|
|
132
133
|
autocoder/common/v2/agent/agentic_edit_tools/__init__.py,sha256=wGICCc1dYh07osB21j62zOQ9Ws0PyyOQ12UYRHmHrtI,1229
|
|
133
134
|
autocoder/common/v2/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py,sha256=pjrukXjWXMIfUAUzoHzr7j2Onf1L7bxmjsUR1gGaFoA,2809
|
|
134
135
|
autocoder/common/v2/agent/agentic_edit_tools/attempt_completion_tool_resolver.py,sha256=82ZGKeRBSDKeead_XVBW4FxpiE-5dS7tBOk_3RZ6B5s,1511
|
|
@@ -138,8 +139,8 @@ autocoder/common/v2/agent/agentic_edit_tools/list_code_definition_names_tool_res
|
|
|
138
139
|
autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py,sha256=ERM5E7s2azQ8vcvogan4A_LZci8Pmhmxw1uQaNQhon4,5469
|
|
139
140
|
autocoder/common/v2/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py,sha256=SZwFUxK6d2BaKWqQXi_c3IVe2iffviF6VUXJA9T9sx0,1492
|
|
140
141
|
autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py,sha256=9Bh0KVbL0qiIqwChlb77biiBiETQ3zekxGe5Fj7hXAg,2800
|
|
141
|
-
autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py,sha256=
|
|
142
|
-
autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py,sha256=
|
|
142
|
+
autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py,sha256=lpD4fCbVR8GTrynqXON69IjM94nPy3nuUL62Ashm5O4,7988
|
|
143
|
+
autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py,sha256=K-TcqY0z7nDupMkTRDAJdqW3z2Y_RUM_wUb-pOEVQRI,6044
|
|
143
144
|
autocoder/common/v2/agent/agentic_edit_tools/use_mcp_tool_resolver.py,sha256=wM2Xy4bcnD0TSLEmcM8rvvyyWenN5_KQnJMO6hJ8lTE,1716
|
|
144
145
|
autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py,sha256=UO4SrkDek3WDlRdlHH022W1roSNMdMcipJqDxRBlheM,3044
|
|
145
146
|
autocoder/compilers/__init__.py,sha256=C0HOms70QA747XD0uZEMmGtRFcIPenohyqECNStv0Bw,1647
|
|
@@ -275,9 +276,9 @@ autocoder/utils/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
275
276
|
autocoder/utils/auto_coder_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
276
277
|
autocoder/utils/auto_coder_utils/chat_stream_out.py,sha256=KW0mlmcHlStXi8-_6fXZ2-ifeJ5mgP0OV7DQFzCtIsw,14008
|
|
277
278
|
autocoder/utils/chat_auto_coder_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
278
|
-
auto_coder-0.1.
|
|
279
|
-
auto_coder-0.1.
|
|
280
|
-
auto_coder-0.1.
|
|
281
|
-
auto_coder-0.1.
|
|
282
|
-
auto_coder-0.1.
|
|
283
|
-
auto_coder-0.1.
|
|
279
|
+
auto_coder-0.1.338.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
280
|
+
auto_coder-0.1.338.dist-info/METADATA,sha256=szlrczWaHSc5hCrxKrOcI6lPK2aeYHLQX-J42_mlg9o,2747
|
|
281
|
+
auto_coder-0.1.338.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
282
|
+
auto_coder-0.1.338.dist-info/entry_points.txt,sha256=0nzHtHH4pNcM7xq4EBA2toS28Qelrvcbrr59GqD_0Ak,350
|
|
283
|
+
auto_coder-0.1.338.dist-info/top_level.txt,sha256=Jqc0_uJSw2GwoFQAa9iJxYns-2mWla-9ok_Y3Gcznjk,10
|
|
284
|
+
auto_coder-0.1.338.dist-info/RECORD,,
|
|
@@ -259,8 +259,8 @@ class AgenticFilter:
|
|
|
259
259
|
3. **深入分析**:
|
|
260
260
|
* 使用 `read_files` 读取关键文件的内容进行确认。如果文件过大,使用 `line_ranges` 参数分段读取。
|
|
261
261
|
* 如有必要,使用 `run_python` 或 `execute_shell_command` 执行代码或命令进行更复杂的分析。
|
|
262
|
-
4. **迭代决策**: 根据工具的返回结果,你可能需要多次调用不同的工具来逐步缩小范围或获取更多信息。
|
|
263
|
-
|
|
262
|
+
4. **迭代决策**: 根据工具的返回结果,你可能需要多次调用不同的工具来逐步缩小范围或获取更多信息。
|
|
263
|
+
6. **最终响应**: 当你确定了所有需要参考和修改的文件后,**必须**调用 `output_result` 工具,并提供符合其要求格式的JSON字符串作为其 `response` 参数。
|
|
264
264
|
该json格式要求为:
|
|
265
265
|
```json
|
|
266
266
|
{
|
|
@@ -273,9 +273,17 @@ class AgenticFilter:
|
|
|
273
273
|
"reasoning": "详细说明你是如何通过分析和使用工具得出这个文件列表的。"
|
|
274
274
|
}
|
|
275
275
|
```
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
276
|
+
|
|
277
|
+
{% if enable_active_context %}
|
|
278
|
+
** 非常非常重要的提示 **
|
|
279
|
+
每一个目录都有一个描述信息,比如 {{ project_path }}/src/abc/bbc 的目录描述信息会放在 {{ project_path }}/.auto-coder/active-context/src/abc/bbc/active.md 文件中。
|
|
280
|
+
你可以使用 read_files 函数读取,从而帮助你更好的挑选要详细阅读哪个文件。值得注意的是,active.md 并不会包含该目录下所有的文件信息,只保存最近发生变更的文件的信息。
|
|
281
|
+
{% endif %}
|
|
282
|
+
"""
|
|
283
|
+
return {
|
|
284
|
+
"project_path": os.path.abspath(self.args.source_dir),
|
|
285
|
+
"enable_active_context": self.args.enable_active_context,
|
|
286
|
+
}
|
|
279
287
|
|
|
280
288
|
@byzerllm.prompt()
|
|
281
289
|
def _execute_command_result(self, result: str) -> str:
|
|
@@ -590,8 +598,7 @@ class AgenticFilter:
|
|
|
590
598
|
- 如果未找到匹配项,会返回提示信息
|
|
591
599
|
|
|
592
600
|
</usage>
|
|
593
|
-
</command>
|
|
594
|
-
|
|
601
|
+
</command>
|
|
595
602
|
<command>
|
|
596
603
|
<n>execute_mcp_server</n>
|
|
597
604
|
<description>执行MCP服务器</description>
|
autocoder/auto_coder_runner.py
CHANGED
|
@@ -22,6 +22,8 @@ from autocoder.common.result_manager import ResultManager
|
|
|
22
22
|
from autocoder.version import __version__
|
|
23
23
|
from autocoder.auto_coder import main as auto_coder_main
|
|
24
24
|
from autocoder.utils import get_last_yaml_file
|
|
25
|
+
from autocoder.commands.auto_command import CommandAutoTuner, AutoCommandRequest, CommandConfig, MemoryConfig
|
|
26
|
+
from autocoder.common.v2.agent.agentic_edit import AgenticEdit,AgenticEditRequest
|
|
25
27
|
from autocoder.index.symbols_utils import (
|
|
26
28
|
extract_symbols,
|
|
27
29
|
SymbolType,
|
|
@@ -60,7 +62,7 @@ from autocoder.common.conf_validator import ConfigValidator
|
|
|
60
62
|
from autocoder import command_parser as CommandParser
|
|
61
63
|
from loguru import logger as global_logger
|
|
62
64
|
from autocoder.utils.project_structure import EnhancedFileAnalyzer
|
|
63
|
-
from autocoder.common import SourceCodeList
|
|
65
|
+
from autocoder.common import SourceCodeList,SourceCode
|
|
64
66
|
|
|
65
67
|
|
|
66
68
|
## 对外API,用于第三方集成 auto-coder 使用。
|
|
@@ -2777,22 +2779,69 @@ def conf_import(path: str):
|
|
|
2777
2779
|
from autocoder.common.conf_import_export import import_conf
|
|
2778
2780
|
import_conf(os.getcwd(), path)
|
|
2779
2781
|
|
|
2782
|
+
def generate_new_yaml(query: str):
|
|
2783
|
+
memory = get_memory()
|
|
2784
|
+
conf = memory.get("conf",{})
|
|
2785
|
+
current_files = memory.get("current_files",{}).get("files",[])
|
|
2786
|
+
auto_coder_main(["next", "chat_action"])
|
|
2787
|
+
latest_yaml_file = get_last_yaml_file("actions")
|
|
2788
|
+
if latest_yaml_file:
|
|
2789
|
+
yaml_config = {
|
|
2790
|
+
"include_file": ["./base/base.yml"],
|
|
2791
|
+
"auto_merge": conf.get("auto_merge", "editblock"),
|
|
2792
|
+
"human_as_model": conf.get("human_as_model", "false") == "true",
|
|
2793
|
+
"skip_build_index": conf.get("skip_build_index", "true") == "true",
|
|
2794
|
+
"skip_confirm": conf.get("skip_confirm", "true") == "true",
|
|
2795
|
+
"silence": conf.get("silence", "true") == "true",
|
|
2796
|
+
"include_project_structure": conf.get("include_project_structure", "true")
|
|
2797
|
+
== "true",
|
|
2798
|
+
"exclude_files": memory.get("exclude_files", []),
|
|
2799
|
+
}
|
|
2800
|
+
yaml_config["context"] = ""
|
|
2801
|
+
for key, value in conf.items():
|
|
2802
|
+
converted_value = convert_config_value(key, value)
|
|
2803
|
+
if converted_value is not None:
|
|
2804
|
+
yaml_config[key] = converted_value
|
|
2805
|
+
|
|
2806
|
+
yaml_config["urls"] = current_files + get_llm_friendly_package_docs(
|
|
2807
|
+
return_paths=True
|
|
2808
|
+
)
|
|
2809
|
+
# handle image
|
|
2810
|
+
v = Image.convert_image_paths_from(query)
|
|
2811
|
+
yaml_config["query"] = v
|
|
2812
|
+
|
|
2813
|
+
yaml_content = convert_yaml_config_to_str(yaml_config=yaml_config)
|
|
2814
|
+
|
|
2815
|
+
execute_file = os.path.join("actions", latest_yaml_file)
|
|
2816
|
+
with open(os.path.join(execute_file), "w",encoding="utf-8") as f:
|
|
2817
|
+
f.write(yaml_content)
|
|
2818
|
+
return execute_file,convert_yaml_to_config(execute_file)
|
|
2819
|
+
|
|
2780
2820
|
@run_in_raw_thread()
|
|
2781
2821
|
def auto_command(query: str,extra_args: Dict[str,Any]={}):
|
|
2782
|
-
"""处理/auto指令"""
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2822
|
+
"""处理/auto指令"""
|
|
2823
|
+
args = get_final_config()
|
|
2824
|
+
memory = get_memory()
|
|
2786
2825
|
if args.enable_agentic_edit:
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2826
|
+
execute_file,args = generate_new_yaml(query)
|
|
2827
|
+
args.file = execute_file
|
|
2828
|
+
current_files = memory.get("current_files",{}).get("files",[])
|
|
2829
|
+
sources = []
|
|
2830
|
+
for file in current_files:
|
|
2831
|
+
with open(file,"r",encoding="utf-8") as f:
|
|
2832
|
+
sources.append(SourceCode(module_name=file,source_code=f.read()))
|
|
2833
|
+
|
|
2834
|
+
llm = get_single_llm(args.code_model or args.model,product_mode=args.product_mode)
|
|
2835
|
+
agent = AgenticEdit(llm=llm,args=args,files=SourceCodeList(sources=sources),
|
|
2836
|
+
conversation_history=[],
|
|
2837
|
+
memory_config=MemoryConfig(memory=memory,
|
|
2838
|
+
save_memory_func=save_memory), command_config=CommandConfig,
|
|
2839
|
+
conversation_name="current"
|
|
2840
|
+
)
|
|
2841
|
+
agent.run_in_terminal(AgenticEditRequest(user_input=query))
|
|
2842
|
+
return
|
|
2843
|
+
|
|
2844
|
+
args = get_final_config()
|
|
2796
2845
|
# 准备请求参数
|
|
2797
2846
|
request = AutoCommandRequest(
|
|
2798
2847
|
user_input=query
|
autocoder/chat_auto_coder.py
CHANGED
|
@@ -825,10 +825,54 @@ MESSAGES = {
|
|
|
825
825
|
"/compile/check/end": {
|
|
826
826
|
"en": "Finished compile error checking process.",
|
|
827
827
|
"zh": "结束编译错误检查过程."
|
|
828
|
+
},
|
|
829
|
+
"/agent/edit/objective":{
|
|
830
|
+
"en":"Objective",
|
|
831
|
+
"zh":"目标"
|
|
832
|
+
},
|
|
833
|
+
"/agent/edit/user_query":{
|
|
834
|
+
"en":"User Query",
|
|
835
|
+
"zh":"用户查询"
|
|
828
836
|
}
|
|
829
837
|
}
|
|
830
838
|
|
|
831
839
|
|
|
840
|
+
# 新增 ReplaceInFileToolResolver 国际化消息
|
|
841
|
+
MESSAGES.update({
|
|
842
|
+
"replace_in_file.access_denied": {
|
|
843
|
+
"en": "Error: Access denied. Attempted to modify file outside the project directory: {{file_path}}",
|
|
844
|
+
"zh": "错误:拒绝访问。尝试修改项目目录之外的文件:{{file_path}}"
|
|
845
|
+
},
|
|
846
|
+
"replace_in_file.file_not_found": {
|
|
847
|
+
"en": "Error: File not found at path: {{file_path}}",
|
|
848
|
+
"zh": "错误:未找到文件路径:{{file_path}}"
|
|
849
|
+
},
|
|
850
|
+
"replace_in_file.read_error": {
|
|
851
|
+
"en": "An error occurred while reading the file for replacement: {{error}}",
|
|
852
|
+
"zh": "读取待替换文件时发生错误:{{error}}"
|
|
853
|
+
},
|
|
854
|
+
"replace_in_file.no_valid_blocks": {
|
|
855
|
+
"en": "Error: No valid SEARCH/REPLACE blocks found in the provided diff.",
|
|
856
|
+
"zh": "错误:在提供的diff中未找到有效的SEARCH/REPLACE代码块。"
|
|
857
|
+
},
|
|
858
|
+
"replace_in_file.apply_failed": {
|
|
859
|
+
"en": "Failed to apply any changes. Errors:\n{{errors}}",
|
|
860
|
+
"zh": "未能应用任何更改。错误信息:\n{{errors}}"
|
|
861
|
+
},
|
|
862
|
+
"replace_in_file.apply_success": {
|
|
863
|
+
"en": "Successfully applied {{applied}}/{{total}} changes to file: {{file_path}}.",
|
|
864
|
+
"zh": "成功应用了 {{applied}}/{{total}} 个更改到文件:{{file_path}}。"
|
|
865
|
+
},
|
|
866
|
+
"replace_in_file.apply_success_with_warnings": {
|
|
867
|
+
"en": "Successfully applied {{applied}}/{{total}} changes to file: {{file_path}}.\nWarnings:\n{{errors}}",
|
|
868
|
+
"zh": "成功应用了 {{applied}}/{{total}} 个更改到文件:{{file_path}}。\n警告信息:\n{{errors}}"
|
|
869
|
+
},
|
|
870
|
+
"replace_in_file.write_error": {
|
|
871
|
+
"en": "An error occurred while writing the modified file: {{error}}",
|
|
872
|
+
"zh": "写入修改后的文件时发生错误:{{error}}"
|
|
873
|
+
}
|
|
874
|
+
})
|
|
875
|
+
|
|
832
876
|
def get_system_language():
|
|
833
877
|
try:
|
|
834
878
|
return locale.getdefaultlocale()[0][:2]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from autocoder.common.v2.agent.agentic_edit_conversation import AgenticConversation
|
|
1
2
|
from enum import Enum
|
|
2
3
|
from enum import Enum
|
|
3
4
|
import json
|
|
@@ -12,7 +13,8 @@ from rich.panel import Panel
|
|
|
12
13
|
from pydantic import SkipValidation
|
|
13
14
|
|
|
14
15
|
# Removed ResultManager, stream_out, git_utils, AutoCommandTools, count_tokens, global_cancel, ActionYmlFileManager, get_event_manager, EventContentCreator, get_run_context, AgenticFilterStreamOutType
|
|
15
|
-
from autocoder.
|
|
16
|
+
from autocoder.common import AutoCoderArgs, git_utils, SourceCodeList, SourceCode
|
|
17
|
+
from autocoder.common.global_cancel import global_cancel
|
|
16
18
|
from autocoder.common import detect_env
|
|
17
19
|
from autocoder.common import shells
|
|
18
20
|
from loguru import logger
|
|
@@ -34,10 +36,13 @@ from rich.syntax import Syntax # Added
|
|
|
34
36
|
from rich.markdown import Markdown # Added
|
|
35
37
|
from autocoder.events.event_manager_singleton import get_event_manager
|
|
36
38
|
from autocoder.events.event_types import Event, EventType, EventMetadata
|
|
39
|
+
from autocoder.memory.active_context_manager import ActiveContextManager
|
|
37
40
|
from autocoder.events import event_content as EventContentCreator
|
|
38
41
|
from autocoder.shadows.shadow_manager import ShadowManager
|
|
39
42
|
from autocoder.linters.shadow_linter import ShadowLinter
|
|
40
43
|
from autocoder.compilers.shadow_compiler import ShadowCompiler
|
|
44
|
+
from autocoder.common.action_yml_file_manager import ActionYmlFileManager
|
|
45
|
+
from autocoder.common.auto_coder_lang import get_message
|
|
41
46
|
# Import the new display function
|
|
42
47
|
from autocoder.common.v2.agent.agentic_tool_display import get_tool_display_message
|
|
43
48
|
from autocoder.common.v2.agent.agentic_edit_types import FileChangeEntry
|
|
@@ -60,7 +65,7 @@ from autocoder.common.v2.agent.agentic_edit_types import (AgenticEditRequest, To
|
|
|
60
65
|
TOOL_MODEL_MAP,
|
|
61
66
|
# Event Types
|
|
62
67
|
LLMOutputEvent, LLMThinkingEvent, ToolCallEvent,
|
|
63
|
-
ToolResultEvent, CompletionEvent, ErrorEvent,TokenUsageEvent,
|
|
68
|
+
ToolResultEvent, CompletionEvent, PlanModeRespondEvent, ErrorEvent, TokenUsageEvent,
|
|
64
69
|
# Import specific tool types for display mapping
|
|
65
70
|
ReadFileTool, WriteToFileTool, ReplaceInFileTool, ExecuteCommandTool,
|
|
66
71
|
ListFilesTool, SearchFilesTool, ListCodeDefinitionNamesTool,
|
|
@@ -83,6 +88,7 @@ TOOL_RESOLVER_MAP: Dict[Type[BaseTool], Type[BaseToolResolver]] = {
|
|
|
83
88
|
UseMcpTool: UseMcpToolResolver,
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
|
|
86
92
|
# --- Tool Display Customization is now handled by agentic_tool_display.py ---
|
|
87
93
|
|
|
88
94
|
|
|
@@ -95,6 +101,7 @@ class AgenticEdit:
|
|
|
95
101
|
args: AutoCoderArgs,
|
|
96
102
|
memory_config: MemoryConfig,
|
|
97
103
|
command_config: Optional[CommandConfig] = None,
|
|
104
|
+
conversation_name: str = "current"
|
|
98
105
|
):
|
|
99
106
|
self.llm = llm
|
|
100
107
|
self.args = args
|
|
@@ -109,12 +116,15 @@ class AgenticEdit:
|
|
|
109
116
|
self.project_type_analyzer = ProjectTypeAnalyzer(
|
|
110
117
|
args=args, llm=self.llm)
|
|
111
118
|
|
|
119
|
+
self.conversation_manager = AgenticConversation(
|
|
120
|
+
args, self.conversation_history, conversation_name=conversation_name)
|
|
121
|
+
|
|
112
122
|
self.shadow_manager = ShadowManager(
|
|
113
123
|
args.source_dir, args.event_file, args.ignore_clean_shadows)
|
|
114
124
|
self.shadow_linter = ShadowLinter(self.shadow_manager, verbose=False)
|
|
115
125
|
self.shadow_compiler = ShadowCompiler(
|
|
116
|
-
self.shadow_manager, verbose=False)
|
|
117
|
-
|
|
126
|
+
self.shadow_manager, verbose=False)
|
|
127
|
+
|
|
118
128
|
self.mcp_server_info = ""
|
|
119
129
|
# try:
|
|
120
130
|
# self.mcp_server = get_mcp_server()
|
|
@@ -129,7 +139,7 @@ class AgenticEdit:
|
|
|
129
139
|
# logger.error(f"Error getting MCP server info: {str(e)}")
|
|
130
140
|
|
|
131
141
|
# 变更跟踪信息
|
|
132
|
-
# 格式: { file_path: FileChangeEntry(...) }
|
|
142
|
+
# 格式: { file_path: FileChangeEntry(...) }
|
|
133
143
|
self.file_changes: Dict[str, FileChangeEntry] = {}
|
|
134
144
|
|
|
135
145
|
def record_file_change(self, file_path: str, change_type: str, diff: Optional[str] = None, content: Optional[str] = None):
|
|
@@ -144,7 +154,8 @@ class AgenticEdit:
|
|
|
144
154
|
"""
|
|
145
155
|
entry = self.file_changes.get(file_path)
|
|
146
156
|
if entry is None:
|
|
147
|
-
entry = FileChangeEntry(
|
|
157
|
+
entry = FileChangeEntry(
|
|
158
|
+
type=change_type, diffs=[], content=content)
|
|
148
159
|
self.file_changes[file_path] = entry
|
|
149
160
|
else:
|
|
150
161
|
# 文件已经存在,可能之前是 added,现在又被 modified,或者多次 modified
|
|
@@ -181,8 +192,10 @@ class AgenticEdit:
|
|
|
181
192
|
for fname in files:
|
|
182
193
|
shadow_file_path = os.path.join(root, fname)
|
|
183
194
|
try:
|
|
184
|
-
project_file_path = self.shadow_manager.from_shadow_path(
|
|
185
|
-
|
|
195
|
+
project_file_path = self.shadow_manager.from_shadow_path(
|
|
196
|
+
shadow_file_path)
|
|
197
|
+
rel_path = os.path.relpath(
|
|
198
|
+
project_file_path, self.args.source_dir)
|
|
186
199
|
changed_files.append(rel_path)
|
|
187
200
|
except Exception:
|
|
188
201
|
# 非映射关系,忽略
|
|
@@ -387,7 +400,7 @@ class AgenticEdit:
|
|
|
387
400
|
Your query here
|
|
388
401
|
</query>
|
|
389
402
|
</use_mcp_tool>
|
|
390
|
-
|
|
403
|
+
|
|
391
404
|
{%if mcp_server_info %}
|
|
392
405
|
### MCP_SERVER_LIST
|
|
393
406
|
{{mcp_server_info}}
|
|
@@ -655,6 +668,13 @@ class AgenticEdit:
|
|
|
655
668
|
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis within <thinking></thinking> tags. First, analyze the file structure provided in environment_details to gain context and insights for proceeding effectively. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
|
|
656
669
|
4. Once you've completed the user's task, you must use the attempt_completion tool to present the result of the task to the user. You may also provide a CLI command to showcase the result of your task; this can be particularly useful for web development tasks, where you can run e.g. \`open index.html\` to show the website you've built.
|
|
657
670
|
5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.
|
|
671
|
+
|
|
672
|
+
{{ enable_active_context }}
|
|
673
|
+
**Very Important Notice**
|
|
674
|
+
Each directory has a description file stored separately. For example, the description for the directory `{{ current_project }}/src/abc/bbc` can be found in the file `{{ current_project }}/.auto-coder/active-context/src/abc/bbc/active.md`.
|
|
675
|
+
You can use the tool `read_file` to read these description files, which helps you decide exactly which files need detailed attention. Note that the `active.md` file does not contain information about all files within the directory—it only includes information
|
|
676
|
+
about the files that were recently changed.
|
|
677
|
+
{{ enable_active_context }}
|
|
658
678
|
"""
|
|
659
679
|
env_info = detect_env()
|
|
660
680
|
shell_type = "bash"
|
|
@@ -674,6 +694,7 @@ class AgenticEdit:
|
|
|
674
694
|
"home_dir": os.path.expanduser("~"),
|
|
675
695
|
"files": self.files.to_str(),
|
|
676
696
|
"mcp_server_info": self.mcp_server_info,
|
|
697
|
+
"enable_active_context": self.args.enable_active_context,
|
|
677
698
|
}
|
|
678
699
|
|
|
679
700
|
# Removed _execute_command_result and execute_auto_command methods
|
|
@@ -724,14 +745,19 @@ class AgenticEdit:
|
|
|
724
745
|
executes tools, and yields structured events for visualization until completion or error.
|
|
725
746
|
"""
|
|
726
747
|
system_prompt = self._analyze.prompt(request)
|
|
748
|
+
|
|
727
749
|
conversations = [
|
|
728
750
|
{"role": "system", "content": system_prompt},
|
|
729
|
-
|
|
730
|
-
|
|
751
|
+
] + self.conversation_manager.get_history()
|
|
752
|
+
conversations.append({
|
|
753
|
+
"role": "user", "content": request.user_input
|
|
754
|
+
})
|
|
755
|
+
self.conversation_manager.add_user_message(request.user_input)
|
|
731
756
|
logger.debug(
|
|
732
757
|
f"Initial conversation history size: {len(conversations)}")
|
|
733
758
|
|
|
734
759
|
while True:
|
|
760
|
+
global_cancel.check_and_raise()
|
|
735
761
|
logger.info(
|
|
736
762
|
f"Starting LLM interaction cycle. History size: {len(conversations)}")
|
|
737
763
|
tool_executed = False
|
|
@@ -743,12 +769,13 @@ class AgenticEdit:
|
|
|
743
769
|
llm_config={}, # Placeholder for future LLM configs
|
|
744
770
|
args=self.args
|
|
745
771
|
)
|
|
746
|
-
|
|
772
|
+
|
|
747
773
|
meta_holder = byzerllm.MetaHolder()
|
|
748
774
|
parsed_events = self.stream_and_parse_llm_response(
|
|
749
|
-
llm_response_gen,meta_holder)
|
|
775
|
+
llm_response_gen, meta_holder)
|
|
750
776
|
|
|
751
777
|
for event in parsed_events:
|
|
778
|
+
global_cancel.check_and_raise()
|
|
752
779
|
if isinstance(event, (LLMOutputEvent, LLMThinkingEvent)):
|
|
753
780
|
assistant_buffer += event.text
|
|
754
781
|
yield event # Yield text/thinking immediately for display
|
|
@@ -763,6 +790,8 @@ class AgenticEdit:
|
|
|
763
790
|
"role": "assistant",
|
|
764
791
|
"content": assistant_buffer + tool_xml
|
|
765
792
|
})
|
|
793
|
+
self.conversation_manager.add_assistant_message(
|
|
794
|
+
assistant_buffer + tool_xml)
|
|
766
795
|
assistant_buffer = "" # Reset buffer after tool call
|
|
767
796
|
|
|
768
797
|
yield event # Yield the ToolCallEvent for display
|
|
@@ -776,6 +805,14 @@ class AgenticEdit:
|
|
|
776
805
|
"AgenticEdit analyze loop finished due to AttemptCompletion.")
|
|
777
806
|
return
|
|
778
807
|
|
|
808
|
+
if isinstance(tool_obj, PlanModeRespondTool):
|
|
809
|
+
logger.info(
|
|
810
|
+
"PlanModeRespondTool received. Finalizing session.")
|
|
811
|
+
yield PlanModeRespondEvent(completion=tool_obj, completion_xml=tool_xml)
|
|
812
|
+
logger.info(
|
|
813
|
+
"AgenticEdit analyze loop finished due to PlanModeRespond.")
|
|
814
|
+
return
|
|
815
|
+
|
|
779
816
|
# Resolve the tool
|
|
780
817
|
resolver_cls = TOOL_RESOLVER_MAP.get(type(tool_obj))
|
|
781
818
|
if not resolver_cls:
|
|
@@ -830,6 +867,7 @@ class AgenticEdit:
|
|
|
830
867
|
"role": "user", # Simulating the user providing the tool result
|
|
831
868
|
"content": error_xml
|
|
832
869
|
})
|
|
870
|
+
self.conversation_manager.add_user_message(error_xml)
|
|
833
871
|
logger.debug(
|
|
834
872
|
f"Added tool result to conversations for tool {type(tool_obj).__name__}")
|
|
835
873
|
break # After tool execution and result, break to start a new LLM cycle
|
|
@@ -848,6 +886,8 @@ class AgenticEdit:
|
|
|
848
886
|
if assistant_buffer:
|
|
849
887
|
conversations.append(
|
|
850
888
|
{"role": "assistant", "content": assistant_buffer})
|
|
889
|
+
self.conversation_manager.add_assistant_message(
|
|
890
|
+
assistant_buffer)
|
|
851
891
|
# If the loop ends without AttemptCompletion, it means the LLM finished talking
|
|
852
892
|
# without signaling completion. We might just stop or yield a final message.
|
|
853
893
|
# Let's assume it stops here.
|
|
@@ -856,7 +896,7 @@ class AgenticEdit:
|
|
|
856
896
|
logger.info("AgenticEdit analyze loop finished.")
|
|
857
897
|
|
|
858
898
|
def stream_and_parse_llm_response(
|
|
859
|
-
self, generator: Generator[Tuple[str, Any], None, None],meta_holder: byzerllm.MetaHolder
|
|
899
|
+
self, generator: Generator[Tuple[str, Any], None, None], meta_holder: byzerllm.MetaHolder
|
|
860
900
|
) -> Generator[Union[LLMOutputEvent, LLMThinkingEvent, ToolCallEvent, ErrorEvent], None, None]:
|
|
861
901
|
"""
|
|
862
902
|
Streamingly parses the LLM response generator, distinguishing between
|
|
@@ -929,6 +969,8 @@ class AgenticEdit:
|
|
|
929
969
|
return None
|
|
930
970
|
|
|
931
971
|
for content_chunk, metadata in generator:
|
|
972
|
+
global_cancel.check_and_raise()
|
|
973
|
+
|
|
932
974
|
meta_holder.meta = metadata
|
|
933
975
|
if not content_chunk:
|
|
934
976
|
continue
|
|
@@ -1065,17 +1107,17 @@ class AgenticEdit:
|
|
|
1065
1107
|
Runs the agentic edit process, converting internal events to the
|
|
1066
1108
|
standard event system format and writing them using the event manager.
|
|
1067
1109
|
"""
|
|
1068
|
-
event_manager = get_event_manager(self.args.event_file)
|
|
1110
|
+
event_manager = get_event_manager(self.args.event_file)
|
|
1069
1111
|
|
|
1070
1112
|
try:
|
|
1071
1113
|
event_stream = self.analyze(request)
|
|
1072
1114
|
for agent_event in event_stream:
|
|
1073
1115
|
content = None
|
|
1074
1116
|
metadata = EventMetadata(
|
|
1075
|
-
action_file=self.args.event_file,
|
|
1076
|
-
is_streaming=False,
|
|
1117
|
+
action_file=self.args.event_file,
|
|
1118
|
+
is_streaming=False,
|
|
1077
1119
|
stream_out_type="/agent/edit")
|
|
1078
|
-
|
|
1120
|
+
|
|
1079
1121
|
if isinstance(agent_event, LLMThinkingEvent):
|
|
1080
1122
|
content = EventContentCreator.create_stream_thinking(
|
|
1081
1123
|
content=agent_event.text)
|
|
@@ -1118,7 +1160,8 @@ class AgenticEdit:
|
|
|
1118
1160
|
try:
|
|
1119
1161
|
self.apply_changes()
|
|
1120
1162
|
except Exception as e:
|
|
1121
|
-
logger.exception(
|
|
1163
|
+
logger.exception(
|
|
1164
|
+
f"Error merging shadow changes to project: {e}")
|
|
1122
1165
|
|
|
1123
1166
|
metadata.path = "/agent/edit/completion"
|
|
1124
1167
|
content = EventContentCreator.create_completion(
|
|
@@ -1128,7 +1171,8 @@ class AgenticEdit:
|
|
|
1128
1171
|
**agent_event.completion.model_dump()
|
|
1129
1172
|
}
|
|
1130
1173
|
)
|
|
1131
|
-
event_manager.write_completion(
|
|
1174
|
+
event_manager.write_completion(
|
|
1175
|
+
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1132
1176
|
elif isinstance(agent_event, ErrorEvent):
|
|
1133
1177
|
metadata.path = "/agent/edit/error"
|
|
1134
1178
|
content = EventContentCreator.create_error(
|
|
@@ -1136,7 +1180,8 @@ class AgenticEdit:
|
|
|
1136
1180
|
error_message=agent_event.message,
|
|
1137
1181
|
details={"agent_event_type": "ErrorEvent"}
|
|
1138
1182
|
)
|
|
1139
|
-
event_manager.write_error(
|
|
1183
|
+
event_manager.write_error(
|
|
1184
|
+
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1140
1185
|
else:
|
|
1141
1186
|
metadata.path = "/agent/edit/error"
|
|
1142
1187
|
logger.warning(
|
|
@@ -1147,7 +1192,8 @@ class AgenticEdit:
|
|
|
1147
1192
|
details={"agent_event_type": type(
|
|
1148
1193
|
agent_event).__name__}
|
|
1149
1194
|
)
|
|
1150
|
-
event_manager.write_error(
|
|
1195
|
+
event_manager.write_error(
|
|
1196
|
+
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1151
1197
|
|
|
1152
1198
|
except Exception as e:
|
|
1153
1199
|
logger.exception(
|
|
@@ -1158,19 +1204,61 @@ class AgenticEdit:
|
|
|
1158
1204
|
error_message=f"An unexpected error occurred: {str(e)}",
|
|
1159
1205
|
details={"exception_type": type(e).__name__}
|
|
1160
1206
|
)
|
|
1161
|
-
event_manager.write_error(
|
|
1207
|
+
event_manager.write_error(
|
|
1208
|
+
content=error_content.to_dict(), metadata=metadata.to_dict())
|
|
1162
1209
|
# Re-raise the exception if needed, or handle appropriately
|
|
1163
|
-
|
|
1210
|
+
raise e
|
|
1164
1211
|
|
|
1165
1212
|
def apply_changes(self):
|
|
1166
1213
|
"""
|
|
1167
1214
|
Apply all tracked file changes to the original project directory.
|
|
1168
1215
|
"""
|
|
1169
|
-
for change in self.get_all_file_changes():
|
|
1170
|
-
file_path = change['file_path']
|
|
1171
|
-
content = change['content']
|
|
1216
|
+
for (file_path, change) in self.get_all_file_changes().items():
|
|
1172
1217
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
1173
|
-
f.write(content)
|
|
1218
|
+
f.write(change.content)
|
|
1219
|
+
|
|
1220
|
+
if len(self.get_all_file_changes()) > 0:
|
|
1221
|
+
if not self.args.skip_commit:
|
|
1222
|
+
try:
|
|
1223
|
+
file_name = os.path.basename(self.args.file)
|
|
1224
|
+
commit_result = git_utils.commit_changes(
|
|
1225
|
+
self.args.source_dir,
|
|
1226
|
+
f"{self.args.query}\nauto_coder_{file_name}",
|
|
1227
|
+
)
|
|
1228
|
+
|
|
1229
|
+
action_yml_file_manager = ActionYmlFileManager(
|
|
1230
|
+
self.args.source_dir)
|
|
1231
|
+
action_file_name = os.path.basename(self.args.file)
|
|
1232
|
+
add_updated_urls = []
|
|
1233
|
+
commit_result.changed_files
|
|
1234
|
+
for file in commit_result.changed_files:
|
|
1235
|
+
add_updated_urls.append(
|
|
1236
|
+
os.path.join(self.args.source_dir, file))
|
|
1237
|
+
|
|
1238
|
+
self.args.add_updated_urls = add_updated_urls
|
|
1239
|
+
update_yaml_success = action_yml_file_manager.update_yaml_field(
|
|
1240
|
+
action_file_name, "add_updated_urls", add_updated_urls)
|
|
1241
|
+
if not update_yaml_success:
|
|
1242
|
+
self.printer.print_in_terminal(
|
|
1243
|
+
"yaml_save_error", style="red", yaml_file=action_file_name)
|
|
1244
|
+
|
|
1245
|
+
if self.args.enable_active_context:
|
|
1246
|
+
active_context_manager = ActiveContextManager(
|
|
1247
|
+
self.llm, self.args.source_dir)
|
|
1248
|
+
task_id = active_context_manager.process_changes(
|
|
1249
|
+
self.args)
|
|
1250
|
+
self.printer.print_in_terminal("active_context_background_task",
|
|
1251
|
+
style="blue",
|
|
1252
|
+
task_id=task_id)
|
|
1253
|
+
git_utils.print_commit_info(commit_result=commit_result)
|
|
1254
|
+
except Exception as e:
|
|
1255
|
+
self.printer.print_str_in_terminal(
|
|
1256
|
+
self.git_require_msg(
|
|
1257
|
+
source_dir=self.args.source_dir, error=str(e)),
|
|
1258
|
+
style="red"
|
|
1259
|
+
)
|
|
1260
|
+
else:
|
|
1261
|
+
self.printer.print_in_terminal("no_changes_made")
|
|
1174
1262
|
|
|
1175
1263
|
def run_in_terminal(self, request: AgenticEditRequest):
|
|
1176
1264
|
"""
|
|
@@ -1181,7 +1269,7 @@ class AgenticEdit:
|
|
|
1181
1269
|
project_name = os.path.basename(os.path.abspath(self.args.source_dir))
|
|
1182
1270
|
console.rule(f"[bold cyan]Starting Agentic Edit: {project_name}[/]")
|
|
1183
1271
|
console.print(Panel(
|
|
1184
|
-
f"[bold]
|
|
1272
|
+
f"[bold]{get_message('/agent/edit/user_query')}:[/bold]\n{request.user_input}", title=get_message("/agent/edit/objective"), border_style="blue"))
|
|
1185
1273
|
|
|
1186
1274
|
try:
|
|
1187
1275
|
event_stream = self.analyze(request)
|
|
@@ -1208,18 +1296,26 @@ class AgenticEdit:
|
|
|
1208
1296
|
if event.tool_name == "AttemptCompletionTool":
|
|
1209
1297
|
continue # Do not display AttemptCompletionTool result
|
|
1210
1298
|
|
|
1299
|
+
if event.tool_name == "PlanModeRespondTool":
|
|
1300
|
+
continue
|
|
1301
|
+
|
|
1211
1302
|
result = event.result
|
|
1212
1303
|
title = f"✅ Tool Result: {event.tool_name}" if result.success else f"❌ Tool Result: {event.tool_name}"
|
|
1213
1304
|
border_style = "green" if result.success else "red"
|
|
1214
1305
|
base_content = f"[bold]Status:[/bold] {'Success' if result.success else 'Failure'}\n"
|
|
1215
1306
|
base_content += f"[bold]Message:[/bold] {result.message}\n"
|
|
1216
1307
|
|
|
1308
|
+
def _format_content(content):
|
|
1309
|
+
if len(content) > 200:
|
|
1310
|
+
return f"{content[:100]}\n...\n{content[-100:]}"
|
|
1311
|
+
else:
|
|
1312
|
+
return content
|
|
1313
|
+
|
|
1217
1314
|
# Prepare panel for base info first
|
|
1218
1315
|
panel_content = [base_content]
|
|
1219
1316
|
syntax_content = None
|
|
1220
1317
|
|
|
1221
1318
|
if result.content is not None:
|
|
1222
|
-
panel_content.append("[bold]Content:[/bold]\n")
|
|
1223
1319
|
content_str = ""
|
|
1224
1320
|
try:
|
|
1225
1321
|
if isinstance(result.content, (dict, list)):
|
|
@@ -1257,16 +1353,17 @@ class AgenticEdit:
|
|
|
1257
1353
|
lexer = "text"
|
|
1258
1354
|
|
|
1259
1355
|
syntax_content = Syntax(
|
|
1260
|
-
result.content
|
|
1356
|
+
_format_content(result.content), lexer, theme="default", line_numbers=True)
|
|
1261
1357
|
else:
|
|
1262
1358
|
content_str = str(result.content)
|
|
1263
1359
|
# Append simple string content directly
|
|
1264
|
-
panel_content.append(
|
|
1360
|
+
panel_content.append(
|
|
1361
|
+
_format_content(content_str))
|
|
1265
1362
|
except Exception as e:
|
|
1266
1363
|
logger.warning(
|
|
1267
1364
|
f"Error formatting tool result content: {e}")
|
|
1268
1365
|
panel_content.append(
|
|
1269
|
-
str(result.content)) # Fallback
|
|
1366
|
+
_format_content(str(result.content))) # Fallback
|
|
1270
1367
|
|
|
1271
1368
|
# Print the base info panel
|
|
1272
1369
|
console.print(Panel("\n".join(
|
|
@@ -1274,13 +1371,17 @@ class AgenticEdit:
|
|
|
1274
1371
|
# Print syntax highlighted content separately if it exists
|
|
1275
1372
|
if syntax_content:
|
|
1276
1373
|
console.print(syntax_content)
|
|
1374
|
+
elif isinstance(event, PlanModeRespondEvent):
|
|
1375
|
+
console.print(Panel(Markdown(event.completion.response),
|
|
1376
|
+
title="🏁 Task Completion", border_style="green", title_align="left"))
|
|
1277
1377
|
|
|
1278
1378
|
elif isinstance(event, CompletionEvent):
|
|
1279
1379
|
# 在这里完成实际合并
|
|
1280
1380
|
try:
|
|
1281
1381
|
self.apply_changes()
|
|
1282
1382
|
except Exception as e:
|
|
1283
|
-
logger.exception(
|
|
1383
|
+
logger.exception(
|
|
1384
|
+
f"Error merging shadow changes to project: {e}")
|
|
1284
1385
|
|
|
1285
1386
|
console.print(Panel(Markdown(event.completion.result),
|
|
1286
1387
|
title="🏁 Task Completion", border_style="green", title_align="left"))
|
|
@@ -1298,5 +1399,6 @@ class AgenticEdit:
|
|
|
1298
1399
|
"An unexpected error occurred during agent execution:")
|
|
1299
1400
|
console.print(Panel(
|
|
1300
1401
|
f"[bold red]FATAL ERROR:[/bold red]\n{str(e)}", title="🔥 System Error", border_style="red"))
|
|
1402
|
+
raise e
|
|
1301
1403
|
finally:
|
|
1302
1404
|
console.rule("[bold cyan]Agentic Edit Finished[/]")
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# src/autocoder/common/v2/agent/agentic_edit_conversation.py
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import List, Dict, Any, Optional
|
|
6
|
+
from autocoder.common import AutoCoderArgs
|
|
7
|
+
|
|
8
|
+
# Define a type alias for a message dictionary
|
|
9
|
+
MessageType = Dict[str, Any]
|
|
10
|
+
|
|
11
|
+
class AgenticConversation:
|
|
12
|
+
"""
|
|
13
|
+
Manages the conversation history for an agentic editing process.
|
|
14
|
+
|
|
15
|
+
Handles adding messages (user, assistant, tool calls, tool results)
|
|
16
|
+
and retrieving the history.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, args: AutoCoderArgs, initial_history: Optional[List[MessageType]] = None, conversation_name: Optional[str] = None):
|
|
20
|
+
"""
|
|
21
|
+
Initializes the conversation history.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
initial_history: An optional list of messages to start with.
|
|
25
|
+
conversation_name: Optional conversation identifier. If provided, history is saved/loaded from a file named after it.
|
|
26
|
+
"""
|
|
27
|
+
self.project_path = args.source_dir
|
|
28
|
+
self._history: List[MessageType] = initial_history if initial_history is not None else []
|
|
29
|
+
|
|
30
|
+
# Determine the memory directory
|
|
31
|
+
memory_dir = os.path.join(self.project_path, ".auto-coder", "memory", "agentic_edit_memory")
|
|
32
|
+
os.makedirs(memory_dir, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
# Determine conversation file path
|
|
35
|
+
if conversation_name:
|
|
36
|
+
filename = f"{conversation_name}.json"
|
|
37
|
+
else:
|
|
38
|
+
conversation_name = str(uuid.uuid4())
|
|
39
|
+
filename = f"{conversation_name}.json"
|
|
40
|
+
|
|
41
|
+
self.conversation_name = conversation_name
|
|
42
|
+
self.memory_file_path = os.path.join(memory_dir, filename)
|
|
43
|
+
|
|
44
|
+
# Load existing history if file exists
|
|
45
|
+
self._load_memory()
|
|
46
|
+
|
|
47
|
+
def add_message(self, role: str, content: Any, **kwargs):
|
|
48
|
+
"""
|
|
49
|
+
Adds a message to the conversation history.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
role: The role of the message sender (e.g., "user", "assistant", "tool").
|
|
53
|
+
content: The content of the message. Can be None for messages like tool calls.
|
|
54
|
+
**kwargs: Additional key-value pairs to include in the message dictionary (e.g., tool_calls, tool_call_id).
|
|
55
|
+
"""
|
|
56
|
+
message: MessageType = {"role": role}
|
|
57
|
+
if content is not None:
|
|
58
|
+
message["content"] = content
|
|
59
|
+
message.update(kwargs)
|
|
60
|
+
self._history.append(message)
|
|
61
|
+
self._save_memory()
|
|
62
|
+
|
|
63
|
+
def add_user_message(self, content: str):
|
|
64
|
+
"""Adds a user message."""
|
|
65
|
+
self.add_message(role="user", content=content)
|
|
66
|
+
|
|
67
|
+
def add_assistant_message(self, content: str):
|
|
68
|
+
"""Adds an assistant message (potentially containing text response)."""
|
|
69
|
+
self.add_message(role="assistant", content=content)
|
|
70
|
+
|
|
71
|
+
def add_assistant_tool_call_message(self, tool_calls: List[Dict[str, Any]], content: Optional[str] = None):
|
|
72
|
+
"""
|
|
73
|
+
Adds a message representing one or more tool calls from the assistant.
|
|
74
|
+
Optionally includes assistant's textual reasoning/content alongside the calls.
|
|
75
|
+
"""
|
|
76
|
+
self.add_message(role="assistant", content=content, tool_calls=tool_calls)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def add_tool_result_message(self, tool_call_id: str, content: Any):
|
|
80
|
+
"""Adds a message representing the result of a specific tool call."""
|
|
81
|
+
# The content here is typically the output/result from the tool execution.
|
|
82
|
+
self.add_message(role="tool", content=content, tool_call_id=tool_call_id)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_history(self) -> List[MessageType]:
|
|
86
|
+
"""
|
|
87
|
+
Returns the latest 20 pairs of (user, assistant) conversation history.
|
|
88
|
+
Merges adjacent same-role messages into one, concatenated by newline.
|
|
89
|
+
Ensures that each user message is paired with the subsequent assistant response,
|
|
90
|
+
skips other roles, and that the last message is always assistant (drops trailing user if unpaired).
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
A list of message dictionaries, ordered chronologically.
|
|
94
|
+
"""
|
|
95
|
+
paired_history = []
|
|
96
|
+
pair_count = 0
|
|
97
|
+
pending_assistant = None
|
|
98
|
+
pending_user = None
|
|
99
|
+
|
|
100
|
+
# Traverse history in reverse to collect latest pairs with merging
|
|
101
|
+
for msg in reversed(self._history):
|
|
102
|
+
role = msg.get("role")
|
|
103
|
+
if role == "assistant":
|
|
104
|
+
if pending_assistant is None:
|
|
105
|
+
pending_assistant = dict(msg)
|
|
106
|
+
else:
|
|
107
|
+
# Merge with previous assistant
|
|
108
|
+
prev_content = pending_assistant.get("content", "")
|
|
109
|
+
curr_content = msg.get("content", "")
|
|
110
|
+
merged_content = (curr_content.strip() + "\n" + prev_content.strip()).strip()
|
|
111
|
+
pending_assistant["content"] = merged_content
|
|
112
|
+
elif role == "user":
|
|
113
|
+
if pending_user is None:
|
|
114
|
+
pending_user = dict(msg)
|
|
115
|
+
else:
|
|
116
|
+
# Merge with previous user
|
|
117
|
+
prev_content = pending_user.get("content", "")
|
|
118
|
+
curr_content = msg.get("content", "")
|
|
119
|
+
merged_content = (curr_content.strip() + "\n" + prev_content.strip()).strip()
|
|
120
|
+
pending_user["content"] = merged_content
|
|
121
|
+
|
|
122
|
+
if pending_assistant is not None:
|
|
123
|
+
# Have a full pair, insert in order
|
|
124
|
+
paired_history.insert(0, pending_user)
|
|
125
|
+
paired_history.insert(1, pending_assistant)
|
|
126
|
+
pair_count += 1
|
|
127
|
+
pending_assistant = None
|
|
128
|
+
pending_user = None
|
|
129
|
+
if pair_count >= 20:
|
|
130
|
+
break
|
|
131
|
+
else:
|
|
132
|
+
# User without assistant yet, continue accumulating
|
|
133
|
+
continue
|
|
134
|
+
else:
|
|
135
|
+
# Ignore other roles
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
# Ensure last message is assistant, drop trailing user if unpaired
|
|
139
|
+
if paired_history and paired_history[-1].get("role") == "user":
|
|
140
|
+
paired_history.pop()
|
|
141
|
+
|
|
142
|
+
return paired_history
|
|
143
|
+
|
|
144
|
+
def clear_history(self):
|
|
145
|
+
"""Clears the conversation history."""
|
|
146
|
+
self._history = []
|
|
147
|
+
|
|
148
|
+
def __len__(self) -> int:
|
|
149
|
+
"""Returns the number of messages in the history."""
|
|
150
|
+
return len(self._history)
|
|
151
|
+
|
|
152
|
+
def __str__(self) -> str:
|
|
153
|
+
"""Returns a string representation of the conversation history."""
|
|
154
|
+
# Consider a more readable format if needed for debugging
|
|
155
|
+
return str(self._history)
|
|
156
|
+
|
|
157
|
+
# Potential future enhancements:
|
|
158
|
+
# - Method to limit history size (by tokens or message count)
|
|
159
|
+
# - Method to format history specifically for different LLM APIs
|
|
160
|
+
# - Serialization/deserialization methods
|
|
161
|
+
|
|
162
|
+
def _save_memory(self):
|
|
163
|
+
try:
|
|
164
|
+
os.makedirs(os.path.dirname(self.memory_file_path), exist_ok=True)
|
|
165
|
+
with open(self.memory_file_path, "w", encoding="utf-8") as f:
|
|
166
|
+
json.dump(self._history, f, ensure_ascii=False, indent=2)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
# Optionally log or ignore
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
def _load_memory(self):
|
|
172
|
+
try:
|
|
173
|
+
if os.path.exists(self.memory_file_path):
|
|
174
|
+
with open(self.memory_file_path, "r", encoding="utf-8") as f:
|
|
175
|
+
self._history = json.load(f)
|
|
176
|
+
except Exception as e:
|
|
177
|
+
# Ignore loading errors, start fresh
|
|
178
|
+
pass
|
|
@@ -6,6 +6,7 @@ from autocoder.common import AutoCoderArgs
|
|
|
6
6
|
from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
|
|
7
7
|
from autocoder.common.v2.agent.agentic_edit_types import ReplaceInFileTool, ToolResult # Import ToolResult from types
|
|
8
8
|
from loguru import logger
|
|
9
|
+
from autocoder.common.auto_coder_lang import get_message_with_format
|
|
9
10
|
if typing.TYPE_CHECKING:
|
|
10
11
|
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
11
12
|
|
|
@@ -66,7 +67,7 @@ class ReplaceInFileToolResolver(BaseToolResolver):
|
|
|
66
67
|
|
|
67
68
|
# Security check
|
|
68
69
|
if not abs_file_path.startswith(abs_project_dir):
|
|
69
|
-
return ToolResult(success=False, message=
|
|
70
|
+
return ToolResult(success=False, message=get_message_with_format("replace_in_file.access_denied", file_path=file_path))
|
|
70
71
|
|
|
71
72
|
# Determine target path: shadow file if shadow_manager exists
|
|
72
73
|
target_path = abs_file_path
|
|
@@ -90,14 +91,14 @@ class ReplaceInFileToolResolver(BaseToolResolver):
|
|
|
90
91
|
f.write(original_content)
|
|
91
92
|
logger.info(f"[Shadow] Initialized shadow file from original: {target_path}")
|
|
92
93
|
else:
|
|
93
|
-
return ToolResult(success=False, message=
|
|
94
|
+
return ToolResult(success=False, message=get_message_with_format("replace_in_file.file_not_found", file_path=file_path))
|
|
94
95
|
except Exception as e:
|
|
95
96
|
logger.error(f"Error reading file for replace '{file_path}': {str(e)}")
|
|
96
|
-
return ToolResult(success=False, message=
|
|
97
|
+
return ToolResult(success=False, message=get_message_with_format("replace_in_file.read_error", error=str(e)))
|
|
97
98
|
|
|
98
99
|
parsed_blocks = self.parse_diff(diff_content)
|
|
99
100
|
if not parsed_blocks:
|
|
100
|
-
return ToolResult(success=False, message="
|
|
101
|
+
return ToolResult(success=False, message=get_message_with_format("replace_in_file.no_valid_blocks"))
|
|
101
102
|
|
|
102
103
|
current_content = original_content
|
|
103
104
|
applied_count = 0
|
|
@@ -121,7 +122,7 @@ class ReplaceInFileToolResolver(BaseToolResolver):
|
|
|
121
122
|
# continue applying remaining blocks
|
|
122
123
|
|
|
123
124
|
if applied_count == 0 and errors:
|
|
124
|
-
return ToolResult(success=False, message=
|
|
125
|
+
return ToolResult(success=False, message=get_message_with_format("replace_in_file.apply_failed", errors="\n".join(errors)))
|
|
125
126
|
|
|
126
127
|
try:
|
|
127
128
|
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
|
@@ -129,9 +130,17 @@ class ReplaceInFileToolResolver(BaseToolResolver):
|
|
|
129
130
|
f.write(current_content)
|
|
130
131
|
logger.info(f"Successfully applied {applied_count}/{len(parsed_blocks)} changes to file: {file_path}")
|
|
131
132
|
|
|
132
|
-
message = f"Successfully applied {applied_count}/{len(parsed_blocks)} changes to file: {file_path}."
|
|
133
133
|
if errors:
|
|
134
|
-
message
|
|
134
|
+
message = get_message_with_format("replace_in_file.apply_success_with_warnings",
|
|
135
|
+
applied=applied_count,
|
|
136
|
+
total=len(parsed_blocks),
|
|
137
|
+
file_path=file_path,
|
|
138
|
+
errors="\n".join(errors))
|
|
139
|
+
else:
|
|
140
|
+
message = get_message_with_format("replace_in_file.apply_success",
|
|
141
|
+
applied=applied_count,
|
|
142
|
+
total=len(parsed_blocks),
|
|
143
|
+
file_path=file_path)
|
|
135
144
|
|
|
136
145
|
# 变更跟踪,回调AgenticEdit
|
|
137
146
|
if self.agent:
|
|
@@ -141,4 +150,4 @@ class ReplaceInFileToolResolver(BaseToolResolver):
|
|
|
141
150
|
return ToolResult(success=True, message=message, content=current_content)
|
|
142
151
|
except Exception as e:
|
|
143
152
|
logger.error(f"Error writing replaced content to file '{file_path}': {str(e)}")
|
|
144
|
-
return ToolResult(success=False, message=
|
|
153
|
+
return ToolResult(success=False, message=get_message_with_format("replace_in_file.write_error", error=str(e)))
|
|
@@ -54,9 +54,14 @@ class SearchFilesToolResolver(BaseToolResolver):
|
|
|
54
54
|
compiled_regex = re.compile(regex_pattern)
|
|
55
55
|
search_glob_pattern = os.path.join(search_base_path, "**", file_pattern)
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
ignored_dirs = ['.git', 'node_modules', '.mvn', '.idea', '__pycache__', '.venv', 'venv', 'dist', 'build', '.gradle']
|
|
58
|
+
logger.info(f"Searching for regex '{regex_pattern}' in files matching '{file_pattern}' under '{search_base_path}' (shadow: {shadow_exists}), ignoring directories: {ignored_dirs}")
|
|
58
59
|
|
|
59
60
|
for filepath in glob.glob(search_glob_pattern, recursive=True):
|
|
61
|
+
normalized_path = filepath.replace("\\", "/") # Normalize for Windows paths
|
|
62
|
+
if any(f"/{ignored_dir}/" in normalized_path or normalized_path.endswith(f"/{ignored_dir}") or f"/{ignored_dir}/" in normalized_path for ignored_dir in ignored_dirs):
|
|
63
|
+
continue
|
|
64
|
+
|
|
60
65
|
if os.path.isfile(filepath):
|
|
61
66
|
try:
|
|
62
67
|
with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
|
|
@@ -80,6 +80,11 @@ class TokenUsageEvent(BaseModel):
|
|
|
80
80
|
"""Represents the result of executing a tool."""
|
|
81
81
|
usage: Any
|
|
82
82
|
|
|
83
|
+
class PlanModeRespondEvent(BaseModel):
|
|
84
|
+
"""Represents the LLM attempting to complete the task."""
|
|
85
|
+
completion: SkipValidation[PlanModeRespondTool] # Skip validation
|
|
86
|
+
completion_xml: str
|
|
87
|
+
|
|
83
88
|
class CompletionEvent(BaseModel):
|
|
84
89
|
"""Represents the LLM attempting to complete the task."""
|
|
85
90
|
completion: SkipValidation[AttemptCompletionTool] # Skip validation
|
|
@@ -93,7 +93,7 @@ TOOL_DISPLAY_MESSAGES: Dict[Type[BaseTool], Dict[str, str]] = {
|
|
|
93
93
|
"[dim]工具:[/dim] [blue]{{ tool_name }}[/]\n"
|
|
94
94
|
"[dim]参数:[/dim] {{ arguments_snippet }}{{ ellipsis }}"
|
|
95
95
|
)
|
|
96
|
-
}
|
|
96
|
+
}
|
|
97
97
|
# AttemptCompletionTool is handled separately in the display loop
|
|
98
98
|
}
|
|
99
99
|
|
|
@@ -129,11 +129,11 @@ def get_tool_display_message(tool: BaseTool) -> str:
|
|
|
129
129
|
"ellipsis": '...' if len(tool.content) > 150 else ''
|
|
130
130
|
}
|
|
131
131
|
elif isinstance(tool, ReplaceInFileTool):
|
|
132
|
-
snippet = tool.diff
|
|
132
|
+
snippet = tool.diff
|
|
133
133
|
context = {
|
|
134
134
|
"path": tool.path,
|
|
135
135
|
"diff_snippet": snippet,
|
|
136
|
-
"ellipsis": '
|
|
136
|
+
"ellipsis": ''
|
|
137
137
|
}
|
|
138
138
|
elif isinstance(tool, ExecuteCommandTool):
|
|
139
139
|
context = {"command": tool.command, "requires_approval": tool.requires_approval}
|
autocoder/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.338"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|