auto-coder 0.1.363__py3-none-any.whl → 0.1.365__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.363.dist-info → auto_coder-0.1.365.dist-info}/METADATA +2 -2
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/RECORD +39 -23
- autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +1 -1
- autocoder/auto_coder.py +46 -2
- autocoder/auto_coder_runner.py +2 -0
- autocoder/common/__init__.py +5 -0
- autocoder/common/file_checkpoint/__init__.py +21 -0
- autocoder/common/file_checkpoint/backup.py +264 -0
- autocoder/common/file_checkpoint/conversation_checkpoint.py +182 -0
- autocoder/common/file_checkpoint/examples.py +217 -0
- autocoder/common/file_checkpoint/manager.py +611 -0
- autocoder/common/file_checkpoint/models.py +156 -0
- autocoder/common/file_checkpoint/store.py +383 -0
- autocoder/common/file_checkpoint/test_backup.py +242 -0
- autocoder/common/file_checkpoint/test_manager.py +570 -0
- autocoder/common/file_checkpoint/test_models.py +360 -0
- autocoder/common/file_checkpoint/test_store.py +327 -0
- autocoder/common/file_checkpoint/test_utils.py +297 -0
- autocoder/common/file_checkpoint/utils.py +119 -0
- autocoder/common/rulefiles/autocoderrules_utils.py +114 -55
- autocoder/common/save_formatted_log.py +76 -5
- autocoder/common/utils_code_auto_generate.py +2 -1
- autocoder/common/v2/agent/agentic_edit.py +545 -225
- autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +83 -43
- autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +116 -29
- autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +179 -48
- autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +101 -56
- autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +322 -0
- autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +173 -132
- autocoder/common/v2/agent/agentic_edit_types.py +4 -0
- autocoder/compilers/normal_compiler.py +64 -0
- autocoder/events/event_manager_singleton.py +133 -4
- autocoder/linters/normal_linter.py +373 -0
- autocoder/linters/python_linter.py +4 -2
- autocoder/version.py +1 -1
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/top_level.txt +0 -0
|
@@ -40,9 +40,16 @@ from autocoder.linters.shadow_linter import ShadowLinter
|
|
|
40
40
|
from autocoder.compilers.shadow_compiler import ShadowCompiler
|
|
41
41
|
from autocoder.common.action_yml_file_manager import ActionYmlFileManager
|
|
42
42
|
from autocoder.common.auto_coder_lang import get_message
|
|
43
|
+
from autocoder.common.save_formatted_log import save_formatted_log
|
|
43
44
|
# Import the new display function
|
|
44
45
|
from autocoder.common.v2.agent.agentic_tool_display import get_tool_display_message
|
|
45
46
|
from autocoder.common.v2.agent.agentic_edit_types import FileChangeEntry
|
|
47
|
+
from autocoder.utils.llms import get_single_llm
|
|
48
|
+
|
|
49
|
+
from autocoder.common.file_checkpoint.models import FileChange as CheckpointFileChange
|
|
50
|
+
from autocoder.common.file_checkpoint.manager import FileChangeManager as CheckpointFileChangeManager
|
|
51
|
+
from autocoder.linters.normal_linter import NormalLinter
|
|
52
|
+
from autocoder.compilers.normal_compiler import NormalCompiler
|
|
46
53
|
from autocoder.common.v2.agent.agentic_edit_tools import ( # Import specific resolvers
|
|
47
54
|
BaseToolResolver,
|
|
48
55
|
ExecuteCommandToolResolver, ReadFileToolResolver, WriteToFileToolResolver,
|
|
@@ -51,7 +58,7 @@ from autocoder.common.v2.agent.agentic_edit_tools import ( # Import specific re
|
|
|
51
58
|
AttemptCompletionToolResolver, PlanModeRespondToolResolver, UseMcpToolResolver,
|
|
52
59
|
ListPackageInfoToolResolver
|
|
53
60
|
)
|
|
54
|
-
from autocoder.common.rulefiles.autocoderrules_utils import get_rules
|
|
61
|
+
from autocoder.common.rulefiles.autocoderrules_utils import get_rules,auto_select_rules,get_required_and_index_rules
|
|
55
62
|
from autocoder.common.v2.agent.agentic_edit_types import (AgenticEditRequest, ToolResult,
|
|
56
63
|
MemoryConfig, CommandConfig, BaseTool,
|
|
57
64
|
ExecuteCommandTool, ReadFileTool,
|
|
@@ -66,12 +73,15 @@ from autocoder.common.v2.agent.agentic_edit_types import (AgenticEditRequest, To
|
|
|
66
73
|
# Event Types
|
|
67
74
|
LLMOutputEvent, LLMThinkingEvent, ToolCallEvent,
|
|
68
75
|
ToolResultEvent, CompletionEvent, PlanModeRespondEvent, ErrorEvent, TokenUsageEvent,
|
|
76
|
+
WindowLengthChangeEvent,
|
|
69
77
|
# Import specific tool types for display mapping
|
|
70
78
|
ReadFileTool, WriteToFileTool, ReplaceInFileTool, ExecuteCommandTool,
|
|
71
79
|
ListFilesTool, SearchFilesTool, ListCodeDefinitionNamesTool,
|
|
72
80
|
AskFollowupQuestionTool, UseMcpTool, AttemptCompletionTool
|
|
73
81
|
)
|
|
74
82
|
|
|
83
|
+
from autocoder.rag.token_counter import count_tokens
|
|
84
|
+
|
|
75
85
|
# Map Pydantic Tool Models to their Resolver Classes
|
|
76
86
|
TOOL_RESOLVER_MAP: Dict[Type[BaseTool], Type[BaseToolResolver]] = {
|
|
77
87
|
ExecuteCommandTool: ExecuteCommandToolResolver,
|
|
@@ -101,9 +111,10 @@ class AgenticEdit:
|
|
|
101
111
|
args: AutoCoderArgs,
|
|
102
112
|
memory_config: MemoryConfig,
|
|
103
113
|
command_config: Optional[CommandConfig] = None,
|
|
104
|
-
conversation_name:
|
|
114
|
+
conversation_name:Optional[str] = "current"
|
|
105
115
|
):
|
|
106
116
|
self.llm = llm
|
|
117
|
+
self.context_prune_llm = get_single_llm(args.context_prune_model or args.model,product_mode=args.product_mode)
|
|
107
118
|
self.args = args
|
|
108
119
|
self.printer = Printer()
|
|
109
120
|
# Removed self.tools and self.result_manager
|
|
@@ -111,16 +122,29 @@ class AgenticEdit:
|
|
|
111
122
|
# Removed self.max_iterations
|
|
112
123
|
# Note: This might need updating based on the new flow
|
|
113
124
|
self.conversation_history = conversation_history
|
|
125
|
+
|
|
126
|
+
self.current_conversations = []
|
|
114
127
|
self.memory_config = memory_config
|
|
115
128
|
self.command_config = command_config # Note: command_config might be unused now
|
|
116
129
|
self.project_type_analyzer = ProjectTypeAnalyzer(
|
|
117
130
|
args=args, llm=self.llm)
|
|
118
131
|
|
|
119
|
-
self.shadow_manager = ShadowManager(
|
|
120
|
-
|
|
121
|
-
self.
|
|
122
|
-
self.
|
|
123
|
-
|
|
132
|
+
# self.shadow_manager = ShadowManager(
|
|
133
|
+
# args.source_dir, args.event_file, args.ignore_clean_shadows)
|
|
134
|
+
self.shadow_manager = None
|
|
135
|
+
# self.shadow_linter = ShadowLinter(self.shadow_manager, verbose=False)
|
|
136
|
+
self.shadow_compiler = None
|
|
137
|
+
# self.shadow_compiler = ShadowCompiler(self.shadow_manager, verbose=False)
|
|
138
|
+
self.shadow_linter = None
|
|
139
|
+
|
|
140
|
+
self.checkpoint_manager = CheckpointFileChangeManager(
|
|
141
|
+
project_dir=args.source_dir,
|
|
142
|
+
backup_dir=os.path.join(args.source_dir,".auto-coder","checkpoint"),
|
|
143
|
+
store_dir=os.path.join(args.source_dir,".auto-coder","checkpoint_store"),
|
|
144
|
+
max_history=50)
|
|
145
|
+
self.linter = NormalLinter(args.source_dir,verbose=False)
|
|
146
|
+
self.compiler = NormalCompiler(args.source_dir,verbose=False)
|
|
147
|
+
|
|
124
148
|
|
|
125
149
|
self.mcp_server_info = ""
|
|
126
150
|
try:
|
|
@@ -593,30 +617,43 @@ class AgenticEdit:
|
|
|
593
617
|
- If at any point a mermaid diagram would make your plan clearer to help the user quickly see the structure, you are encouraged to include a Mermaid code block in the response. (Note: if you use colors in your mermaid diagrams, be sure to use high contrast colors so the text is readable.)
|
|
594
618
|
- Finally once it seems like you've reached a good plan, ask the user to switch you back to ACT MODE to implement the solution.
|
|
595
619
|
|
|
596
|
-
{% if enable_active_context_in_generate %}
|
|
597
620
|
====
|
|
598
621
|
|
|
599
|
-
|
|
622
|
+
PACKAGE CONTEXT INFORMATION
|
|
623
|
+
|
|
624
|
+
# Understanding Directory Context
|
|
625
|
+
|
|
626
|
+
## Purpose
|
|
600
627
|
|
|
601
|
-
Each directory
|
|
602
|
-
|
|
628
|
+
- Each directory in the project (especially source code directories) has implicit context information
|
|
629
|
+
- This includes recent changes, important files, and their purposes
|
|
630
|
+
- This contextual information helps you understand the role of the directory and the files in the directory
|
|
603
631
|
|
|
604
|
-
|
|
605
|
-
* **Example** – for `{{ current_project }}/src/abc/bbc`, the summary is
|
|
606
|
-
`{{ current_project }}/.auto-coder/active-context/src/abc/bbc/active.md`.
|
|
632
|
+
## Accessing Directory Context
|
|
607
633
|
|
|
608
|
-
**
|
|
634
|
+
- Use the **list_package_info** tool to view this information for a specific directory
|
|
635
|
+
- Do NOT use other tools like list_files to view this specialized context information
|
|
636
|
+
|
|
637
|
+
## When to Use
|
|
638
|
+
|
|
639
|
+
- When you need to understand what has recently changed in a directory
|
|
640
|
+
- When you need insight into the purpose and organization of a directory
|
|
641
|
+
- Before diving into detailed file exploration with other tools
|
|
642
|
+
|
|
643
|
+
## Example
|
|
609
644
|
|
|
610
645
|
```xml
|
|
611
|
-
<
|
|
612
|
-
<path
|
|
613
|
-
</
|
|
646
|
+
<list_package_info>
|
|
647
|
+
<path>src/some/directory</path>
|
|
648
|
+
</list_package_info>
|
|
614
649
|
```
|
|
615
650
|
|
|
616
|
-
|
|
617
|
-
|
|
651
|
+
# Benefits
|
|
652
|
+
|
|
653
|
+
- Quickly identifies recently modified files that may be relevant to your task
|
|
654
|
+
- Provides high-level understanding of directory contents and purpose
|
|
655
|
+
- Helps prioritize which files to examine in detail with tools like read_file, search_files, or list_code_definition_names
|
|
618
656
|
|
|
619
|
-
{% endif %}
|
|
620
657
|
====
|
|
621
658
|
|
|
622
659
|
CAPABILITIES
|
|
@@ -658,17 +695,35 @@ class AgenticEdit:
|
|
|
658
695
|
{% if extra_docs %}
|
|
659
696
|
====
|
|
660
697
|
|
|
661
|
-
RULES PROVIDED BY USER
|
|
698
|
+
RULES OR DOCUMENTS PROVIDED BY USER
|
|
662
699
|
|
|
663
700
|
The following rules are provided by the user, and you must follow them strictly.
|
|
664
701
|
|
|
702
|
+
<user_rule_or_document_files>
|
|
665
703
|
{% for key, value in extra_docs.items() %}
|
|
666
|
-
<
|
|
704
|
+
<user_rule_or_document_file>
|
|
667
705
|
##File: {{ key }}
|
|
668
706
|
{{ value }}
|
|
669
|
-
</
|
|
670
|
-
{% endfor %}
|
|
707
|
+
</user_rule_or_document_file>
|
|
708
|
+
{% endfor %}
|
|
709
|
+
</user_rule_or_document_files>
|
|
710
|
+
|
|
711
|
+
Make sure you always start your task by using the read_file tool to get the relevant RULE files listed in index.md based on the user's specific requirements.
|
|
712
|
+
{% endif %}
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
{% if file_paths_str %}
|
|
716
|
+
====
|
|
717
|
+
|
|
718
|
+
FILES MENTIONED BY USER
|
|
719
|
+
|
|
720
|
+
The following are files or directories that the user mentioned.
|
|
721
|
+
Make sure you always start your task by using the read_file tool to get the content of the files or list_files tool to list the files contained in the mentioned directories. If it is a directory, please use list_files to see what files it contains, and read the files as needed using read_file. If it is a file, please use read_file to read the file.
|
|
722
|
+
<files>
|
|
723
|
+
{{file_paths_str}}
|
|
724
|
+
</files>
|
|
671
725
|
{% endif %}
|
|
726
|
+
|
|
672
727
|
|
|
673
728
|
====
|
|
674
729
|
|
|
@@ -690,18 +745,11 @@ class AgenticEdit:
|
|
|
690
745
|
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.
|
|
691
746
|
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.
|
|
692
747
|
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.
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
<files>
|
|
699
|
-
{{file_paths_str}}
|
|
700
|
-
</files>
|
|
701
|
-
{% endif %}
|
|
702
|
-
"""
|
|
703
|
-
import os
|
|
704
|
-
extra_docs = get_rules()
|
|
748
|
+
|
|
749
|
+
"""
|
|
750
|
+
## auto_select_rules(context=request.user_input, llm=self.llm,args=self.args) rules =
|
|
751
|
+
# extra_docs = get_rules()
|
|
752
|
+
extra_docs = get_required_and_index_rules()
|
|
705
753
|
|
|
706
754
|
env_info = detect_env()
|
|
707
755
|
shell_type = "bash"
|
|
@@ -770,7 +818,7 @@ class AgenticEdit:
|
|
|
770
818
|
# Join with newline for readability, matching prompt examples
|
|
771
819
|
return "\n".join(xml_parts)
|
|
772
820
|
|
|
773
|
-
def analyze(self, request: AgenticEditRequest) -> Generator[Union[LLMOutputEvent, LLMThinkingEvent, ToolCallEvent, ToolResultEvent, CompletionEvent, ErrorEvent], None, None]:
|
|
821
|
+
def analyze(self, request: AgenticEditRequest) -> Generator[Union[LLMOutputEvent, LLMThinkingEvent, ToolCallEvent, ToolResultEvent, CompletionEvent, ErrorEvent, WindowLengthChangeEvent], None, None]:
|
|
774
822
|
"""
|
|
775
823
|
Analyzes the user request, interacts with the LLM, parses responses,
|
|
776
824
|
executes tools, and yields structured events for visualization until completion or error.
|
|
@@ -780,6 +828,7 @@ class AgenticEdit:
|
|
|
780
828
|
logger.info(f"Generated system prompt with length: {len(system_prompt)}")
|
|
781
829
|
|
|
782
830
|
# print(system_prompt)
|
|
831
|
+
|
|
783
832
|
conversations = [
|
|
784
833
|
{"role": "system", "content": system_prompt},
|
|
785
834
|
]
|
|
@@ -788,11 +837,17 @@ class AgenticEdit:
|
|
|
788
837
|
"role": "user", "content": request.user_input
|
|
789
838
|
})
|
|
790
839
|
|
|
791
|
-
logger.info(
|
|
792
|
-
f"Initial conversation history size: {len(conversations)}")
|
|
793
840
|
|
|
794
|
-
|
|
795
|
-
|
|
841
|
+
self.current_conversations = conversations
|
|
842
|
+
|
|
843
|
+
# 计算初始对话窗口长度并触发事件
|
|
844
|
+
conversation_str = json.dumps(conversations, ensure_ascii=False)
|
|
845
|
+
current_tokens = count_tokens(conversation_str)
|
|
846
|
+
yield WindowLengthChangeEvent(tokens_used=current_tokens)
|
|
847
|
+
|
|
848
|
+
logger.info(
|
|
849
|
+
f"Initial conversation history size: {len(conversations)}, tokens: {current_tokens}")
|
|
850
|
+
|
|
796
851
|
iteration_count = 0
|
|
797
852
|
tool_executed = False
|
|
798
853
|
while True:
|
|
@@ -828,7 +883,7 @@ class AgenticEdit:
|
|
|
828
883
|
event_count = 0
|
|
829
884
|
for event in parsed_events:
|
|
830
885
|
event_count += 1
|
|
831
|
-
logger.info(f"Processing event #{event_count}: {type(event).__name__}")
|
|
886
|
+
# logger.info(f"Processing event #{event_count}: {type(event).__name__}")
|
|
832
887
|
global_cancel.check_and_raise(token=self.args.event_file)
|
|
833
888
|
if isinstance(event, (LLMOutputEvent, LLMThinkingEvent)):
|
|
834
889
|
assistant_buffer += event.text
|
|
@@ -844,11 +899,18 @@ class AgenticEdit:
|
|
|
844
899
|
|
|
845
900
|
# Append assistant's thoughts and the tool call to history
|
|
846
901
|
logger.info(f"Adding assistant message with tool call to conversation history")
|
|
902
|
+
|
|
903
|
+
# 记录当前对话的token数量
|
|
847
904
|
conversations.append({
|
|
848
905
|
"role": "assistant",
|
|
849
906
|
"content": assistant_buffer + tool_xml
|
|
850
907
|
})
|
|
851
908
|
assistant_buffer = "" # Reset buffer after tool call
|
|
909
|
+
|
|
910
|
+
# 计算当前对话的总 token 数量并触发事件
|
|
911
|
+
current_conversation_str = json.dumps(conversations, ensure_ascii=False)
|
|
912
|
+
total_tokens = count_tokens(current_conversation_str)
|
|
913
|
+
yield WindowLengthChangeEvent(tokens_used=total_tokens)
|
|
852
914
|
|
|
853
915
|
yield event # Yield the ToolCallEvent for display
|
|
854
916
|
logger.info("Yielded ToolCallEvent")
|
|
@@ -861,6 +923,7 @@ class AgenticEdit:
|
|
|
861
923
|
yield CompletionEvent(completion=tool_obj, completion_xml=tool_xml)
|
|
862
924
|
logger.info(
|
|
863
925
|
"AgenticEdit analyze loop finished due to AttemptCompletion.")
|
|
926
|
+
save_formatted_log(self.args.source_dir, json.dumps(conversations, ensure_ascii=False), "agentic_conversation")
|
|
864
927
|
return
|
|
865
928
|
|
|
866
929
|
if isinstance(tool_obj, PlanModeRespondTool):
|
|
@@ -870,6 +933,7 @@ class AgenticEdit:
|
|
|
870
933
|
yield PlanModeRespondEvent(completion=tool_obj, completion_xml=tool_xml)
|
|
871
934
|
logger.info(
|
|
872
935
|
"AgenticEdit analyze loop finished due to PlanModeRespond.")
|
|
936
|
+
save_formatted_log(self.args.source_dir, json.dumps(conversations, ensure_ascii=False), "agentic_conversation")
|
|
873
937
|
return
|
|
874
938
|
|
|
875
939
|
# Resolve the tool
|
|
@@ -926,10 +990,18 @@ class AgenticEdit:
|
|
|
926
990
|
|
|
927
991
|
# Append the tool result (as user message) to history
|
|
928
992
|
logger.info("Adding tool result to conversation history")
|
|
993
|
+
|
|
994
|
+
# 添加工具结果到对话历史
|
|
929
995
|
conversations.append({
|
|
930
996
|
"role": "user", # Simulating the user providing the tool result
|
|
931
997
|
"content": error_xml
|
|
932
998
|
})
|
|
999
|
+
|
|
1000
|
+
# 计算当前对话的总 token 数量并触发事件
|
|
1001
|
+
current_conversation_str = json.dumps(conversations, ensure_ascii=False)
|
|
1002
|
+
total_tokens = count_tokens(current_conversation_str)
|
|
1003
|
+
yield WindowLengthChangeEvent(tokens_used=total_tokens)
|
|
1004
|
+
|
|
933
1005
|
logger.info(
|
|
934
1006
|
f"Added tool result to conversations for tool {type(tool_obj).__name__}")
|
|
935
1007
|
logger.info(f"Breaking LLM cycle after executing tool: {tool_name}")
|
|
@@ -952,6 +1024,7 @@ class AgenticEdit:
|
|
|
952
1024
|
# Append any remaining assistant buffer to history if it wasn't followed by a tool
|
|
953
1025
|
if assistant_buffer:
|
|
954
1026
|
logger.info(f"Appending assistant buffer to history: {len(assistant_buffer)} chars")
|
|
1027
|
+
|
|
955
1028
|
last_message = conversations[-1]
|
|
956
1029
|
if last_message["role"] != "assistant":
|
|
957
1030
|
logger.info("Adding new assistant message")
|
|
@@ -960,18 +1033,30 @@ class AgenticEdit:
|
|
|
960
1033
|
elif last_message["role"] == "assistant":
|
|
961
1034
|
logger.info("Appending to existing assistant message")
|
|
962
1035
|
last_message["content"] += assistant_buffer
|
|
1036
|
+
|
|
1037
|
+
# 计算当前对话的总 token 数量并触发事件
|
|
1038
|
+
current_conversation_str = json.dumps(conversations, ensure_ascii=False)
|
|
1039
|
+
total_tokens = count_tokens(current_conversation_str)
|
|
1040
|
+
yield WindowLengthChangeEvent(tokens_used=total_tokens)
|
|
963
1041
|
|
|
964
1042
|
# 添加系统提示,要求LLM必须使用工具或明确结束,而不是直接退出
|
|
965
1043
|
logger.info("Adding system reminder to use tools or attempt completion")
|
|
1044
|
+
|
|
966
1045
|
conversations.append({
|
|
967
1046
|
"role": "user",
|
|
968
1047
|
"content": "NOTE: You must use an appropriate tool (such as read_file, write_to_file, execute_command, etc.) or explicitly complete the task (using attempt_completion). Do not provide text responses without taking concrete actions. Please select a suitable tool to continue based on the user's task."
|
|
969
1048
|
})
|
|
1049
|
+
|
|
1050
|
+
# 计算当前对话的总 token 数量并触发事件
|
|
1051
|
+
current_conversation_str = json.dumps(conversations, ensure_ascii=False)
|
|
1052
|
+
total_tokens = count_tokens(current_conversation_str)
|
|
1053
|
+
yield WindowLengthChangeEvent(tokens_used=total_tokens)
|
|
970
1054
|
# 继续循环,让 LLM 再思考,而不是 break
|
|
971
1055
|
logger.info("Continuing the LLM interaction loop without breaking")
|
|
972
1056
|
continue
|
|
973
1057
|
|
|
974
1058
|
logger.info(f"AgenticEdit analyze loop finished after {iteration_count} iterations.")
|
|
1059
|
+
save_formatted_log(self.args.source_dir, json.dumps(conversations, ensure_ascii=False), "agentic_conversation")
|
|
975
1060
|
|
|
976
1061
|
def stream_and_parse_llm_response(
|
|
977
1062
|
self, generator: Generator[Tuple[str, Any], None, None]
|
|
@@ -1168,9 +1253,7 @@ class AgenticEdit:
|
|
|
1168
1253
|
|
|
1169
1254
|
# If no event was processed in this iteration, break inner loop
|
|
1170
1255
|
if not found_event:
|
|
1171
|
-
break
|
|
1172
|
-
|
|
1173
|
-
yield TokenUsageEvent(usage=meta_holder.meta)
|
|
1256
|
+
break
|
|
1174
1257
|
|
|
1175
1258
|
# After generator exhausted, yield any remaining content
|
|
1176
1259
|
if in_thinking_block:
|
|
@@ -1188,192 +1271,211 @@ class AgenticEdit:
|
|
|
1188
1271
|
# Yield remaining plain text
|
|
1189
1272
|
yield LLMOutputEvent(text=buffer)
|
|
1190
1273
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
standard event system format and writing them using the event manager.
|
|
1195
|
-
"""
|
|
1196
|
-
event_manager = get_event_manager(self.args.event_file)
|
|
1197
|
-
self.apply_pre_changes()
|
|
1198
|
-
|
|
1199
|
-
try:
|
|
1200
|
-
event_stream = self.analyze(request)
|
|
1201
|
-
for agent_event in event_stream:
|
|
1202
|
-
content = None
|
|
1203
|
-
metadata = EventMetadata(
|
|
1204
|
-
action_file=self.args.file,
|
|
1205
|
-
is_streaming=False,
|
|
1206
|
-
stream_out_type="/agent/edit")
|
|
1207
|
-
|
|
1208
|
-
if isinstance(agent_event, LLMThinkingEvent):
|
|
1209
|
-
content = EventContentCreator.create_stream_thinking(
|
|
1210
|
-
content=agent_event.text)
|
|
1211
|
-
metadata.is_streaming = True
|
|
1212
|
-
metadata.path = "/agent/edit/thinking"
|
|
1213
|
-
event_manager.write_stream(
|
|
1214
|
-
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1215
|
-
elif isinstance(agent_event, LLMOutputEvent):
|
|
1216
|
-
content = EventContentCreator.create_stream_content(
|
|
1217
|
-
content=agent_event.text)
|
|
1218
|
-
metadata.is_streaming = True
|
|
1219
|
-
metadata.path = "/agent/edit/output"
|
|
1220
|
-
event_manager.write_stream(content=content.to_dict(),
|
|
1221
|
-
metadata=metadata.to_dict())
|
|
1222
|
-
elif isinstance(agent_event, ToolCallEvent):
|
|
1223
|
-
tool_name = type(agent_event.tool).__name__
|
|
1224
|
-
metadata.path = "/agent/edit/tool/call"
|
|
1225
|
-
content = EventContentCreator.create_result(
|
|
1226
|
-
content={
|
|
1227
|
-
"tool_name": tool_name,
|
|
1228
|
-
**agent_event.tool.model_dump()
|
|
1229
|
-
},
|
|
1230
|
-
metadata={}
|
|
1231
|
-
)
|
|
1232
|
-
event_manager.write_result(
|
|
1233
|
-
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1234
|
-
elif isinstance(agent_event, ToolResultEvent):
|
|
1235
|
-
metadata.path = "/agent/edit/tool/result"
|
|
1236
|
-
content = EventContentCreator.create_result(
|
|
1237
|
-
content={
|
|
1238
|
-
"tool_name": agent_event.tool_name,
|
|
1239
|
-
**agent_event.result.model_dump()
|
|
1240
|
-
},
|
|
1241
|
-
metadata={}
|
|
1242
|
-
)
|
|
1243
|
-
event_manager.write_result(
|
|
1244
|
-
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1245
|
-
elif isinstance(agent_event, PlanModeRespondEvent):
|
|
1246
|
-
metadata.path = "/agent/edit/plan_mode_respond"
|
|
1247
|
-
content = EventContentCreator.create_markdown_result(
|
|
1248
|
-
content=agent_event.completion.response,
|
|
1249
|
-
metadata={}
|
|
1250
|
-
)
|
|
1251
|
-
event_manager.write_result(
|
|
1252
|
-
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1253
|
-
|
|
1254
|
-
elif isinstance(agent_event, TokenUsageEvent):
|
|
1255
|
-
last_meta: SingleOutputMeta = agent_event.usage
|
|
1256
|
-
# Get model info for pricing
|
|
1257
|
-
from autocoder.utils import llms as llm_utils
|
|
1258
|
-
model_name = ",".join(llm_utils.get_llm_names(self.llm))
|
|
1259
|
-
model_info = llm_utils.get_model_info(
|
|
1260
|
-
model_name, self.args.product_mode) or {}
|
|
1261
|
-
input_price = model_info.get(
|
|
1262
|
-
"input_price", 0.0) if model_info else 0.0
|
|
1263
|
-
output_price = model_info.get(
|
|
1264
|
-
"output_price", 0.0) if model_info else 0.0
|
|
1265
|
-
|
|
1266
|
-
# Calculate costs
|
|
1267
|
-
input_cost = (last_meta.input_tokens_count *
|
|
1268
|
-
input_price) / 1000000 # Convert to millions
|
|
1269
|
-
# Convert to millions
|
|
1270
|
-
output_cost = (
|
|
1271
|
-
last_meta.generated_tokens_count * output_price) / 1000000
|
|
1272
|
-
|
|
1273
|
-
# 添加日志记录
|
|
1274
|
-
logger.info(f"Token Usage Details: Model={model_name}, Input Tokens={last_meta.input_tokens_count}, Output Tokens={last_meta.generated_tokens_count}, Input Cost=${input_cost:.6f}, Output Cost=${output_cost:.6f}")
|
|
1275
|
-
|
|
1276
|
-
get_event_manager(self.args.event_file).write_result(
|
|
1277
|
-
EventContentCreator.create_result(content=EventContentCreator.ResultTokenStatContent(
|
|
1278
|
-
model_name=model_name,
|
|
1279
|
-
elapsed_time=0.0,
|
|
1280
|
-
first_token_time=last_meta.first_token_time,
|
|
1281
|
-
input_tokens=last_meta.input_tokens_count,
|
|
1282
|
-
output_tokens=last_meta.generated_tokens_count,
|
|
1283
|
-
input_cost=input_cost,
|
|
1284
|
-
output_cost=output_cost
|
|
1285
|
-
).to_dict()), metadata=metadata.to_dict())
|
|
1286
|
-
|
|
1287
|
-
elif isinstance(agent_event, CompletionEvent):
|
|
1288
|
-
# 在这里完成实际合并
|
|
1289
|
-
try:
|
|
1290
|
-
self.apply_changes()
|
|
1291
|
-
except Exception as e:
|
|
1292
|
-
logger.exception(
|
|
1293
|
-
f"Error merging shadow changes to project: {e}")
|
|
1294
|
-
|
|
1295
|
-
metadata.path = "/agent/edit/completion"
|
|
1296
|
-
content = EventContentCreator.create_completion(
|
|
1297
|
-
success_code="AGENT_COMPLETE",
|
|
1298
|
-
success_message="Agent attempted task completion.",
|
|
1299
|
-
result={
|
|
1300
|
-
"response": agent_event.completion.result
|
|
1301
|
-
}
|
|
1302
|
-
)
|
|
1303
|
-
event_manager.write_completion(
|
|
1304
|
-
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1305
|
-
elif isinstance(agent_event, ErrorEvent):
|
|
1306
|
-
metadata.path = "/agent/edit/error"
|
|
1307
|
-
content = EventContentCreator.create_error(
|
|
1308
|
-
error_code="AGENT_ERROR",
|
|
1309
|
-
error_message=agent_event.message,
|
|
1310
|
-
details={"agent_event_type": "ErrorEvent"}
|
|
1311
|
-
)
|
|
1312
|
-
event_manager.write_error(
|
|
1313
|
-
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1314
|
-
else:
|
|
1315
|
-
metadata.path = "/agent/edit/error"
|
|
1316
|
-
logger.warning(
|
|
1317
|
-
f"Unhandled agent event type: {type(agent_event)}")
|
|
1318
|
-
content = EventContentCreator.create_error(
|
|
1319
|
-
error_code="AGENT_ERROR",
|
|
1320
|
-
error_message=f"Unhandled agent event type: {type(agent_event)}",
|
|
1321
|
-
details={"agent_event_type": type(
|
|
1322
|
-
agent_event).__name__}
|
|
1323
|
-
)
|
|
1324
|
-
event_manager.write_error(
|
|
1325
|
-
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1326
|
-
|
|
1327
|
-
except Exception as e:
|
|
1328
|
-
logger.exception(
|
|
1329
|
-
"An unexpected error occurred during agent execution:")
|
|
1330
|
-
metadata = EventMetadata(
|
|
1331
|
-
action_file=self.args.file,
|
|
1332
|
-
is_streaming=False,
|
|
1333
|
-
stream_out_type="/agent/edit/error")
|
|
1334
|
-
error_content = EventContentCreator.create_error(
|
|
1335
|
-
error_code="AGENT_FATAL_ERROR",
|
|
1336
|
-
error_message=f"An unexpected error occurred: {str(e)}",
|
|
1337
|
-
details={"exception_type": type(e).__name__}
|
|
1338
|
-
)
|
|
1339
|
-
event_manager.write_error(
|
|
1340
|
-
content=error_content.to_dict(), metadata=metadata.to_dict())
|
|
1341
|
-
# Re-raise the exception if needed, or handle appropriately
|
|
1342
|
-
raise e
|
|
1274
|
+
# 这个要放在最后,防止其他关联的多个事件的信息中断
|
|
1275
|
+
yield TokenUsageEvent(usage=meta_holder.meta)
|
|
1276
|
+
|
|
1343
1277
|
|
|
1344
1278
|
def apply_pre_changes(self):
|
|
1345
1279
|
# get the file name
|
|
1346
1280
|
file_name = os.path.basename(self.args.file)
|
|
1347
1281
|
if not self.args.skip_commit:
|
|
1348
1282
|
try:
|
|
1283
|
+
commit_result = git_utils.commit_changes(
|
|
1284
|
+
self.args.source_dir, f"auto_coder_pre_{file_name}")
|
|
1349
1285
|
get_event_manager(self.args.event_file).write_result(
|
|
1350
1286
|
EventContentCreator.create_result(
|
|
1351
|
-
content=
|
|
1287
|
+
content={
|
|
1288
|
+
"have_commit":commit_result.success,
|
|
1289
|
+
"commit_hash":commit_result.commit_hash,
|
|
1290
|
+
"diff_file_num":len(commit_result.changed_files),
|
|
1291
|
+
"event_file":self.args.event_file
|
|
1292
|
+
}), metadata=EventMetadata(
|
|
1352
1293
|
action_file=self.args.file,
|
|
1353
1294
|
is_streaming=False,
|
|
1354
1295
|
path="/agent/edit/apply_pre_changes",
|
|
1355
1296
|
stream_out_type="/agent/edit").to_dict())
|
|
1356
|
-
|
|
1357
|
-
self.args.source_dir, f"auto_coder_pre_{file_name}")
|
|
1297
|
+
|
|
1358
1298
|
except Exception as e:
|
|
1359
1299
|
self.printer.print_in_terminal("git_init_required",
|
|
1360
1300
|
source_dir=self.args.source_dir, error=str(e))
|
|
1361
1301
|
return
|
|
1362
1302
|
|
|
1303
|
+
def get_available_checkpoints(self) -> List[Dict[str, Any]]:
|
|
1304
|
+
"""
|
|
1305
|
+
获取可用的检查点列表
|
|
1306
|
+
|
|
1307
|
+
Returns:
|
|
1308
|
+
List[Dict[str, Any]]: 检查点信息列表
|
|
1309
|
+
"""
|
|
1310
|
+
if not self.checkpoint_manager:
|
|
1311
|
+
return []
|
|
1312
|
+
|
|
1313
|
+
return self.checkpoint_manager.get_available_checkpoints()
|
|
1314
|
+
|
|
1315
|
+
def rollback_to_checkpoint(self, checkpoint_id: str) -> bool:
|
|
1316
|
+
"""
|
|
1317
|
+
回滚到指定的检查点,恢复文件状态和对话状态
|
|
1318
|
+
|
|
1319
|
+
Args:
|
|
1320
|
+
checkpoint_id: 检查点ID
|
|
1321
|
+
|
|
1322
|
+
Returns:
|
|
1323
|
+
bool: 是否成功回滚
|
|
1324
|
+
"""
|
|
1325
|
+
if not self.checkpoint_manager:
|
|
1326
|
+
logger.error("无法回滚:检查点管理器未初始化")
|
|
1327
|
+
return False
|
|
1328
|
+
|
|
1329
|
+
try:
|
|
1330
|
+
# 回滚文件变更
|
|
1331
|
+
undo_result, checkpoint = self.checkpoint_manager.undo_change_group_with_conversation(checkpoint_id)
|
|
1332
|
+
if not undo_result.success:
|
|
1333
|
+
logger.error(f"回滚文件变更失败: {undo_result.errors}")
|
|
1334
|
+
return False
|
|
1335
|
+
|
|
1336
|
+
# 恢复对话状态
|
|
1337
|
+
if checkpoint:
|
|
1338
|
+
self.current_conversations = checkpoint.conversations
|
|
1339
|
+
logger.info(f"已恢复对话状态,包含 {len(checkpoint.conversations)} 条消息")
|
|
1340
|
+
return True
|
|
1341
|
+
else:
|
|
1342
|
+
logger.warning(f"未找到关联的对话检查点: {checkpoint_id},只回滚了文件变更")
|
|
1343
|
+
return undo_result.success
|
|
1344
|
+
except Exception as e:
|
|
1345
|
+
logger.exception(f"回滚到检查点 {checkpoint_id} 失败: {str(e)}")
|
|
1346
|
+
return False
|
|
1347
|
+
|
|
1348
|
+
def handle_rollback_command(self, command: str) -> str:
|
|
1349
|
+
"""
|
|
1350
|
+
处理回滚相关的命令
|
|
1351
|
+
|
|
1352
|
+
Args:
|
|
1353
|
+
command: 命令字符串,如 "rollback list", "rollback to <id>", "rollback info <id>"
|
|
1354
|
+
|
|
1355
|
+
Returns:
|
|
1356
|
+
str: 命令执行结果
|
|
1357
|
+
"""
|
|
1358
|
+
if command == "rollback list":
|
|
1359
|
+
# 列出可用的检查点
|
|
1360
|
+
checkpoints = self.get_available_checkpoints()
|
|
1361
|
+
if not checkpoints:
|
|
1362
|
+
return "没有可用的检查点。"
|
|
1363
|
+
|
|
1364
|
+
result = "可用的检查点列表:\n"
|
|
1365
|
+
for i, cp in enumerate(checkpoints):
|
|
1366
|
+
time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(cp["timestamp"]))
|
|
1367
|
+
result += f"{i+1}. ID: {cp['id'][:8]}... | 时间: {time_str} | 变更文件数: {cp['changes_count']}"
|
|
1368
|
+
result += f" | {'包含对话状态' if cp['has_conversation'] else '不包含对话状态'}\n"
|
|
1369
|
+
|
|
1370
|
+
return result
|
|
1371
|
+
|
|
1372
|
+
elif command.startswith("rollback info "):
|
|
1373
|
+
# 显示检查点详情
|
|
1374
|
+
cp_id = command[len("rollback info "):].strip()
|
|
1375
|
+
|
|
1376
|
+
# 查找检查点
|
|
1377
|
+
checkpoints = self.get_available_checkpoints()
|
|
1378
|
+
target_cp = None
|
|
1379
|
+
|
|
1380
|
+
# 支持通过序号或ID查询
|
|
1381
|
+
if cp_id.isdigit() and 1 <= int(cp_id) <= len(checkpoints):
|
|
1382
|
+
target_cp = checkpoints[int(cp_id) - 1]
|
|
1383
|
+
else:
|
|
1384
|
+
for cp in checkpoints:
|
|
1385
|
+
if cp["id"].startswith(cp_id):
|
|
1386
|
+
target_cp = cp
|
|
1387
|
+
break
|
|
1388
|
+
|
|
1389
|
+
if not target_cp:
|
|
1390
|
+
return f"未找到ID为 {cp_id} 的检查点。"
|
|
1391
|
+
|
|
1392
|
+
# 获取检查点详细信息
|
|
1393
|
+
time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(target_cp["timestamp"]))
|
|
1394
|
+
|
|
1395
|
+
# 获取变更文件列表
|
|
1396
|
+
changes = self.checkpoint_manager.get_changes_by_group(target_cp["id"])
|
|
1397
|
+
changed_files = [change.file_path for change in changes]
|
|
1398
|
+
|
|
1399
|
+
# 获取对话状态信息
|
|
1400
|
+
conversation_info = "无对话状态信息"
|
|
1401
|
+
if target_cp["has_conversation"] and hasattr(self.checkpoint_manager, 'conversation_store'):
|
|
1402
|
+
checkpoint = self.checkpoint_manager.conversation_store.get_checkpoint(target_cp["id"])
|
|
1403
|
+
if checkpoint and checkpoint.conversations:
|
|
1404
|
+
conversation_info = f"包含 {len(checkpoint.conversations)} 条对话消息"
|
|
1405
|
+
|
|
1406
|
+
result = f"检查点详情:\n"
|
|
1407
|
+
result += f"ID: {target_cp['id']}\n"
|
|
1408
|
+
result += f"创建时间: {time_str}\n"
|
|
1409
|
+
result += f"变更文件数: {target_cp['changes_count']}\n"
|
|
1410
|
+
result += f"对话状态: {conversation_info}\n\n"
|
|
1411
|
+
|
|
1412
|
+
if changed_files:
|
|
1413
|
+
result += "变更文件列表:\n"
|
|
1414
|
+
for i, file_path in enumerate(changed_files):
|
|
1415
|
+
result += f"{i+1}. {file_path}\n"
|
|
1416
|
+
|
|
1417
|
+
return result
|
|
1418
|
+
|
|
1419
|
+
elif command.startswith("rollback to "):
|
|
1420
|
+
# 回滚到指定检查点
|
|
1421
|
+
cp_id = command[len("rollback to "):].strip()
|
|
1422
|
+
|
|
1423
|
+
# 查找检查点
|
|
1424
|
+
checkpoints = self.get_available_checkpoints()
|
|
1425
|
+
target_cp = None
|
|
1426
|
+
|
|
1427
|
+
# 支持通过序号或ID回滚
|
|
1428
|
+
if cp_id.isdigit() and 1 <= int(cp_id) <= len(checkpoints):
|
|
1429
|
+
target_cp = checkpoints[int(cp_id) - 1]
|
|
1430
|
+
else:
|
|
1431
|
+
for cp in checkpoints:
|
|
1432
|
+
if cp["id"].startswith(cp_id):
|
|
1433
|
+
target_cp = cp
|
|
1434
|
+
break
|
|
1435
|
+
|
|
1436
|
+
if not target_cp:
|
|
1437
|
+
return f"未找到ID为 {cp_id} 的检查点。"
|
|
1438
|
+
|
|
1439
|
+
# 执行回滚
|
|
1440
|
+
success = self.rollback_to_checkpoint(target_cp["id"])
|
|
1441
|
+
|
|
1442
|
+
if success:
|
|
1443
|
+
# 获取变更文件列表
|
|
1444
|
+
changes = self.checkpoint_manager.get_changes_by_group(target_cp["id"])
|
|
1445
|
+
changed_files = [change.file_path for change in changes]
|
|
1446
|
+
|
|
1447
|
+
result = f"成功回滚到检查点 {target_cp['id'][:8]}...\n"
|
|
1448
|
+
result += f"恢复了 {len(changed_files)} 个文件的状态"
|
|
1449
|
+
|
|
1450
|
+
if target_cp["has_conversation"]:
|
|
1451
|
+
result += f"\n同时恢复了对话状态"
|
|
1452
|
+
|
|
1453
|
+
return result
|
|
1454
|
+
else:
|
|
1455
|
+
return f"回滚到检查点 {target_cp['id'][:8]}... 失败。"
|
|
1456
|
+
|
|
1457
|
+
return "未知命令。可用命令:rollback list, rollback info <id>, rollback to <id>"
|
|
1458
|
+
|
|
1363
1459
|
def apply_changes(self):
|
|
1364
1460
|
"""
|
|
1365
1461
|
Apply all tracked file changes to the original project directory.
|
|
1366
1462
|
"""
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1463
|
+
diff_file_num = 0
|
|
1464
|
+
if self.shadow_manager:
|
|
1465
|
+
for (file_path, change) in self.get_all_file_changes().items():
|
|
1466
|
+
# Ensure the directory exists before writing the file
|
|
1467
|
+
dir_path = os.path.dirname(file_path)
|
|
1468
|
+
if dir_path: # Ensure dir_path is not empty (for files in root)
|
|
1469
|
+
os.makedirs(dir_path, exist_ok=True)
|
|
1470
|
+
|
|
1471
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
1472
|
+
f.write(change.content)
|
|
1473
|
+
diff_file_num = len(self.get_all_file_changes())
|
|
1474
|
+
else:
|
|
1475
|
+
changes = self.checkpoint_manager.get_changes_by_group(self.args.event_file)
|
|
1476
|
+
diff_file_num = len(changes)
|
|
1477
|
+
|
|
1478
|
+
if diff_file_num > 0:
|
|
1377
1479
|
if not self.args.skip_commit:
|
|
1378
1480
|
try:
|
|
1379
1481
|
file_name = os.path.basename(self.args.file)
|
|
@@ -1381,13 +1483,20 @@ class AgenticEdit:
|
|
|
1381
1483
|
self.args.source_dir,
|
|
1382
1484
|
f"{self.args.query}\nauto_coder_{file_name}",
|
|
1383
1485
|
)
|
|
1384
|
-
|
|
1486
|
+
|
|
1385
1487
|
get_event_manager(self.args.event_file).write_result(
|
|
1386
1488
|
EventContentCreator.create_result(
|
|
1387
|
-
content=
|
|
1489
|
+
content={
|
|
1490
|
+
"have_commit":commit_result.success,
|
|
1491
|
+
"commit_hash":commit_result.commit_hash,
|
|
1492
|
+
"diff_file_num":diff_file_num,
|
|
1493
|
+
"event_file":self.args.event_file
|
|
1494
|
+
}), metadata=EventMetadata(
|
|
1388
1495
|
action_file=self.args.file,
|
|
1389
1496
|
is_streaming=False,
|
|
1497
|
+
path="/agent/edit/apply_changes",
|
|
1390
1498
|
stream_out_type="/agent/edit").to_dict())
|
|
1499
|
+
|
|
1391
1500
|
action_yml_file_manager = ActionYmlFileManager(
|
|
1392
1501
|
self.args.source_dir)
|
|
1393
1502
|
action_file_name = os.path.basename(self.args.file)
|
|
@@ -1433,6 +1542,15 @@ class AgenticEdit:
|
|
|
1433
1542
|
console.print(Panel(
|
|
1434
1543
|
f"[bold]{get_message('/agent/edit/user_query')}:[/bold]\n{request.user_input}", title=get_message("/agent/edit/objective"), border_style="blue"))
|
|
1435
1544
|
|
|
1545
|
+
# 用于累计TokenUsageEvent数据
|
|
1546
|
+
accumulated_token_usage = {
|
|
1547
|
+
"model_name": "",
|
|
1548
|
+
"input_tokens": 0,
|
|
1549
|
+
"output_tokens": 0,
|
|
1550
|
+
"input_cost": 0.0,
|
|
1551
|
+
"output_cost": 0.0
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1436
1554
|
try:
|
|
1437
1555
|
self.apply_pre_changes()
|
|
1438
1556
|
event_stream = self.analyze(request)
|
|
@@ -1459,19 +1577,19 @@ class AgenticEdit:
|
|
|
1459
1577
|
# 添加日志记录
|
|
1460
1578
|
logger.info(f"Token Usage: Model={model_name}, Input Tokens={last_meta.input_tokens_count}, Output Tokens={last_meta.generated_tokens_count}, Input Cost=${input_cost:.6f}, Output Cost=${output_cost:.6f}")
|
|
1461
1579
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1580
|
+
# 累计token使用情况
|
|
1581
|
+
accumulated_token_usage["model_name"] = model_name
|
|
1582
|
+
accumulated_token_usage["input_tokens"] += last_meta.input_tokens_count
|
|
1583
|
+
accumulated_token_usage["output_tokens"] += last_meta.generated_tokens_count
|
|
1584
|
+
accumulated_token_usage["input_cost"] += input_cost
|
|
1585
|
+
accumulated_token_usage["output_cost"] += output_cost
|
|
1586
|
+
|
|
1587
|
+
elif isinstance(event, WindowLengthChangeEvent):
|
|
1588
|
+
# 显示当前会话的token数量
|
|
1589
|
+
logger.info(f"当前会话总 tokens: {event.tokens_used}")
|
|
1590
|
+
console.print(f"[dim]当前会话总 tokens: {event.tokens_used}[/dim]")
|
|
1473
1591
|
|
|
1474
|
-
|
|
1592
|
+
elif isinstance(event, LLMThinkingEvent):
|
|
1475
1593
|
# Render thinking within a less prominent style, maybe grey?
|
|
1476
1594
|
console.print(f"[grey50]{event.text}[/grey50]", end="")
|
|
1477
1595
|
elif isinstance(event, LLMOutputEvent):
|
|
@@ -1592,7 +1710,35 @@ class AgenticEdit:
|
|
|
1592
1710
|
|
|
1593
1711
|
time.sleep(0.1) # Small delay for better visual flow
|
|
1594
1712
|
|
|
1713
|
+
# 在处理完所有事件后打印累计的token使用情况
|
|
1714
|
+
if accumulated_token_usage["input_tokens"] > 0:
|
|
1715
|
+
self.printer.print_in_terminal(
|
|
1716
|
+
"code_generation_complete",
|
|
1717
|
+
duration=0.0,
|
|
1718
|
+
input_tokens=accumulated_token_usage["input_tokens"],
|
|
1719
|
+
output_tokens=accumulated_token_usage["output_tokens"],
|
|
1720
|
+
input_cost=accumulated_token_usage["input_cost"],
|
|
1721
|
+
output_cost=accumulated_token_usage["output_cost"],
|
|
1722
|
+
speed=0.0,
|
|
1723
|
+
model_names=accumulated_token_usage["model_name"],
|
|
1724
|
+
sampling_count=1
|
|
1725
|
+
)
|
|
1726
|
+
|
|
1595
1727
|
except Exception as e:
|
|
1728
|
+
# 在处理异常时也打印累计的token使用情况
|
|
1729
|
+
if accumulated_token_usage["input_tokens"] > 0:
|
|
1730
|
+
self.printer.print_in_terminal(
|
|
1731
|
+
"code_generation_complete",
|
|
1732
|
+
duration=0.0,
|
|
1733
|
+
input_tokens=accumulated_token_usage["input_tokens"],
|
|
1734
|
+
output_tokens=accumulated_token_usage["output_tokens"],
|
|
1735
|
+
input_cost=accumulated_token_usage["input_cost"],
|
|
1736
|
+
output_cost=accumulated_token_usage["output_cost"],
|
|
1737
|
+
speed=0.0,
|
|
1738
|
+
model_names=accumulated_token_usage["model_name"],
|
|
1739
|
+
sampling_count=1
|
|
1740
|
+
)
|
|
1741
|
+
|
|
1596
1742
|
logger.exception(
|
|
1597
1743
|
"An unexpected error occurred during agent execution:")
|
|
1598
1744
|
console.print(Panel(
|
|
@@ -1600,3 +1746,177 @@ class AgenticEdit:
|
|
|
1600
1746
|
raise e
|
|
1601
1747
|
finally:
|
|
1602
1748
|
console.rule("[bold cyan]Agentic Edit Finished[/]")
|
|
1749
|
+
|
|
1750
|
+
def run_with_events(self, request: AgenticEditRequest):
|
|
1751
|
+
"""
|
|
1752
|
+
Runs the agentic edit process, converting internal events to the
|
|
1753
|
+
standard event system format and writing them using the event manager.
|
|
1754
|
+
"""
|
|
1755
|
+
event_manager = get_event_manager(self.args.event_file)
|
|
1756
|
+
self.apply_pre_changes()
|
|
1757
|
+
|
|
1758
|
+
try:
|
|
1759
|
+
event_stream = self.analyze(request)
|
|
1760
|
+
for agent_event in event_stream:
|
|
1761
|
+
content = None
|
|
1762
|
+
metadata = EventMetadata(
|
|
1763
|
+
action_file=self.args.file,
|
|
1764
|
+
is_streaming=False,
|
|
1765
|
+
stream_out_type="/agent/edit")
|
|
1766
|
+
|
|
1767
|
+
if isinstance(agent_event, LLMThinkingEvent):
|
|
1768
|
+
content = EventContentCreator.create_stream_thinking(
|
|
1769
|
+
content=agent_event.text)
|
|
1770
|
+
metadata.is_streaming = True
|
|
1771
|
+
metadata.path = "/agent/edit/thinking"
|
|
1772
|
+
event_manager.write_stream(
|
|
1773
|
+
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1774
|
+
elif isinstance(agent_event, LLMOutputEvent):
|
|
1775
|
+
content = EventContentCreator.create_stream_content(
|
|
1776
|
+
content=agent_event.text)
|
|
1777
|
+
metadata.is_streaming = True
|
|
1778
|
+
metadata.path = "/agent/edit/output"
|
|
1779
|
+
event_manager.write_stream(content=content.to_dict(),
|
|
1780
|
+
metadata=metadata.to_dict())
|
|
1781
|
+
elif isinstance(agent_event, ToolCallEvent):
|
|
1782
|
+
tool_name = type(agent_event.tool).__name__
|
|
1783
|
+
metadata.path = "/agent/edit/tool/call"
|
|
1784
|
+
content = EventContentCreator.create_result(
|
|
1785
|
+
content={
|
|
1786
|
+
"tool_name": tool_name,
|
|
1787
|
+
**agent_event.tool.model_dump()
|
|
1788
|
+
},
|
|
1789
|
+
metadata={}
|
|
1790
|
+
)
|
|
1791
|
+
event_manager.write_result(
|
|
1792
|
+
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1793
|
+
elif isinstance(agent_event, ToolResultEvent):
|
|
1794
|
+
metadata.path = "/agent/edit/tool/result"
|
|
1795
|
+
content = EventContentCreator.create_result(
|
|
1796
|
+
content={
|
|
1797
|
+
"tool_name": agent_event.tool_name,
|
|
1798
|
+
**agent_event.result.model_dump()
|
|
1799
|
+
},
|
|
1800
|
+
metadata={}
|
|
1801
|
+
)
|
|
1802
|
+
event_manager.write_result(
|
|
1803
|
+
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1804
|
+
elif isinstance(agent_event, PlanModeRespondEvent):
|
|
1805
|
+
metadata.path = "/agent/edit/plan_mode_respond"
|
|
1806
|
+
content = EventContentCreator.create_markdown_result(
|
|
1807
|
+
content=agent_event.completion.response,
|
|
1808
|
+
metadata={}
|
|
1809
|
+
)
|
|
1810
|
+
event_manager.write_result(
|
|
1811
|
+
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1812
|
+
|
|
1813
|
+
elif isinstance(agent_event, TokenUsageEvent):
|
|
1814
|
+
last_meta: SingleOutputMeta = agent_event.usage
|
|
1815
|
+
# Get model info for pricing
|
|
1816
|
+
from autocoder.utils import llms as llm_utils
|
|
1817
|
+
model_name = ",".join(llm_utils.get_llm_names(self.llm))
|
|
1818
|
+
model_info = llm_utils.get_model_info(
|
|
1819
|
+
model_name, self.args.product_mode) or {}
|
|
1820
|
+
input_price = model_info.get(
|
|
1821
|
+
"input_price", 0.0) if model_info else 0.0
|
|
1822
|
+
output_price = model_info.get(
|
|
1823
|
+
"output_price", 0.0) if model_info else 0.0
|
|
1824
|
+
|
|
1825
|
+
# Calculate costs
|
|
1826
|
+
input_cost = (last_meta.input_tokens_count *
|
|
1827
|
+
input_price) / 1000000 # Convert to millions
|
|
1828
|
+
# Convert to millions
|
|
1829
|
+
output_cost = (
|
|
1830
|
+
last_meta.generated_tokens_count * output_price) / 1000000
|
|
1831
|
+
|
|
1832
|
+
# 添加日志记录
|
|
1833
|
+
logger.info(f"Token Usage Details: Model={model_name}, Input Tokens={last_meta.input_tokens_count}, Output Tokens={last_meta.generated_tokens_count}, Input Cost=${input_cost:.6f}, Output Cost=${output_cost:.6f}")
|
|
1834
|
+
|
|
1835
|
+
# 直接将每次的 TokenUsageEvent 写入到事件中
|
|
1836
|
+
metadata.path = "/agent/edit/token_usage"
|
|
1837
|
+
content = EventContentCreator.create_result(content=EventContentCreator.ResultTokenStatContent(
|
|
1838
|
+
model_name=model_name,
|
|
1839
|
+
elapsed_time=0.0,
|
|
1840
|
+
first_token_time=last_meta.first_token_time,
|
|
1841
|
+
input_tokens=last_meta.input_tokens_count,
|
|
1842
|
+
output_tokens=last_meta.generated_tokens_count,
|
|
1843
|
+
input_cost=input_cost,
|
|
1844
|
+
output_cost=output_cost
|
|
1845
|
+
).to_dict())
|
|
1846
|
+
event_manager.write_result(content=content.to_dict(), metadata=metadata.to_dict())
|
|
1847
|
+
|
|
1848
|
+
|
|
1849
|
+
elif isinstance(agent_event, CompletionEvent):
|
|
1850
|
+
# 在这里完成实际合并
|
|
1851
|
+
try:
|
|
1852
|
+
self.apply_changes()
|
|
1853
|
+
except Exception as e:
|
|
1854
|
+
logger.exception(
|
|
1855
|
+
f"Error merging shadow changes to project: {e}")
|
|
1856
|
+
|
|
1857
|
+
metadata.path = "/agent/edit/completion"
|
|
1858
|
+
content = EventContentCreator.create_completion(
|
|
1859
|
+
success_code="AGENT_COMPLETE",
|
|
1860
|
+
success_message="Agent attempted task completion.",
|
|
1861
|
+
result={
|
|
1862
|
+
"response": agent_event.completion.result
|
|
1863
|
+
}
|
|
1864
|
+
)
|
|
1865
|
+
event_manager.write_completion(
|
|
1866
|
+
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1867
|
+
elif isinstance(agent_event, WindowLengthChangeEvent):
|
|
1868
|
+
# 处理窗口长度变化事件
|
|
1869
|
+
metadata.path = "/agent/edit/window_length_change"
|
|
1870
|
+
content = EventContentCreator.create_result(
|
|
1871
|
+
content={
|
|
1872
|
+
"tokens_used": agent_event.tokens_used
|
|
1873
|
+
},
|
|
1874
|
+
metadata={}
|
|
1875
|
+
)
|
|
1876
|
+
event_manager.write_result(
|
|
1877
|
+
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1878
|
+
|
|
1879
|
+
# 记录日志
|
|
1880
|
+
logger.info(f"当前会话总 tokens: {agent_event.tokens_used}")
|
|
1881
|
+
|
|
1882
|
+
elif isinstance(agent_event, ErrorEvent):
|
|
1883
|
+
metadata.path = "/agent/edit/error"
|
|
1884
|
+
content = EventContentCreator.create_error(
|
|
1885
|
+
error_code="AGENT_ERROR",
|
|
1886
|
+
error_message=agent_event.message,
|
|
1887
|
+
details={"agent_event_type": "ErrorEvent"}
|
|
1888
|
+
)
|
|
1889
|
+
event_manager.write_error(
|
|
1890
|
+
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1891
|
+
else:
|
|
1892
|
+
metadata.path = "/agent/edit/error"
|
|
1893
|
+
logger.warning(
|
|
1894
|
+
f"Unhandled agent event type: {type(agent_event)}")
|
|
1895
|
+
content = EventContentCreator.create_error(
|
|
1896
|
+
error_code="AGENT_ERROR",
|
|
1897
|
+
error_message=f"Unhandled agent event type: {type(agent_event)}",
|
|
1898
|
+
details={"agent_event_type": type(
|
|
1899
|
+
agent_event).__name__}
|
|
1900
|
+
)
|
|
1901
|
+
event_manager.write_error(
|
|
1902
|
+
content=content.to_dict(), metadata=metadata.to_dict())
|
|
1903
|
+
|
|
1904
|
+
except Exception as e:
|
|
1905
|
+
logger.exception(
|
|
1906
|
+
"An unexpected error occurred during agent execution:")
|
|
1907
|
+
metadata = EventMetadata(
|
|
1908
|
+
action_file=self.args.file,
|
|
1909
|
+
is_streaming=False,
|
|
1910
|
+
stream_out_type="/agent/edit/error")
|
|
1911
|
+
|
|
1912
|
+
# 发送累计的TokenUsageEvent数据(在错误情况下也需要发送)
|
|
1913
|
+
|
|
1914
|
+
error_content = EventContentCreator.create_error(
|
|
1915
|
+
error_code="AGENT_FATAL_ERROR",
|
|
1916
|
+
error_message=f"An unexpected error occurred: {str(e)}",
|
|
1917
|
+
details={"exception_type": type(e).__name__}
|
|
1918
|
+
)
|
|
1919
|
+
event_manager.write_error(
|
|
1920
|
+
content=error_content.to_dict(), metadata=metadata.to_dict())
|
|
1921
|
+
# Re-raise the exception if needed, or handle appropriately
|
|
1922
|
+
raise e
|