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.

Files changed (39) hide show
  1. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/METADATA +2 -2
  2. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/RECORD +39 -23
  3. autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +1 -1
  4. autocoder/auto_coder.py +46 -2
  5. autocoder/auto_coder_runner.py +2 -0
  6. autocoder/common/__init__.py +5 -0
  7. autocoder/common/file_checkpoint/__init__.py +21 -0
  8. autocoder/common/file_checkpoint/backup.py +264 -0
  9. autocoder/common/file_checkpoint/conversation_checkpoint.py +182 -0
  10. autocoder/common/file_checkpoint/examples.py +217 -0
  11. autocoder/common/file_checkpoint/manager.py +611 -0
  12. autocoder/common/file_checkpoint/models.py +156 -0
  13. autocoder/common/file_checkpoint/store.py +383 -0
  14. autocoder/common/file_checkpoint/test_backup.py +242 -0
  15. autocoder/common/file_checkpoint/test_manager.py +570 -0
  16. autocoder/common/file_checkpoint/test_models.py +360 -0
  17. autocoder/common/file_checkpoint/test_store.py +327 -0
  18. autocoder/common/file_checkpoint/test_utils.py +297 -0
  19. autocoder/common/file_checkpoint/utils.py +119 -0
  20. autocoder/common/rulefiles/autocoderrules_utils.py +114 -55
  21. autocoder/common/save_formatted_log.py +76 -5
  22. autocoder/common/utils_code_auto_generate.py +2 -1
  23. autocoder/common/v2/agent/agentic_edit.py +545 -225
  24. autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +83 -43
  25. autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +116 -29
  26. autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +179 -48
  27. autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +101 -56
  28. autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +322 -0
  29. autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +173 -132
  30. autocoder/common/v2/agent/agentic_edit_types.py +4 -0
  31. autocoder/compilers/normal_compiler.py +64 -0
  32. autocoder/events/event_manager_singleton.py +133 -4
  33. autocoder/linters/normal_linter.py +373 -0
  34. autocoder/linters/python_linter.py +4 -2
  35. autocoder/version.py +1 -1
  36. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/LICENSE +0 -0
  37. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/WHEEL +0 -0
  38. {auto_coder-0.1.363.dist-info → auto_coder-0.1.365.dist-info}/entry_points.txt +0 -0
  39. {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: str = "current"
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
- args.source_dir, args.event_file, args.ignore_clean_shadows)
121
- self.shadow_linter = ShadowLinter(self.shadow_manager, verbose=False)
122
- self.shadow_compiler = ShadowCompiler(
123
- self.shadow_manager, verbose=False)
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
- PROJECT PACKAGE CONTEXT
622
+ PACKAGE CONTEXT INFORMATION
623
+
624
+ # Understanding Directory Context
625
+
626
+ ## Purpose
600
627
 
601
- Each directory can contain a short **`active.md`** summary file located under the mirrored path inside
602
- `{{ current_project }}/.auto-coder/active-context/`.
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
- * **Purpose** captures only the files that have **recently changed** in that directory. It is *not* a full listing.
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
- **Reading a summary**
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
- <read_file>
612
- <path>.auto-coder/active-context/src/abc/bbc/active.md</path>
613
- </read_file>
646
+ <list_package_info>
647
+ <path>src/some/directory</path>
648
+ </list_package_info>
614
649
  ```
615
650
 
616
- Use these summaries to quickly decide which files deserve a deeper look with tools like
617
- `read_file`, `search_files`, or `list_code_definition_names`.
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
- <user_rule>
704
+ <user_rule_or_document_file>
667
705
  ##File: {{ key }}
668
706
  {{ value }}
669
- </user_rule>
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
- {% if file_paths_str %}
695
- ====
696
- The following are files that the user is currently focusing on.
697
- Make sure you always start your analysis by using the read_file tool to get the content of the files.
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
- logger.info(f"Conversation history: {json.dumps(conversations, indent=2,ensure_ascii=False)}")
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
- def run_with_events(self, request: AgenticEditRequest):
1192
- """
1193
- Runs the agentic edit process, converting internal events to the
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=self.printer.get_message_from_key("/agent/edit/apply_pre_changes")), metadata=EventMetadata(
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
- git_utils.commit_changes(
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
- for (file_path, change) in self.get_all_file_changes().items():
1368
- # Ensure the directory exists before writing the file
1369
- dir_path = os.path.dirname(file_path)
1370
- if dir_path: # Ensure dir_path is not empty (for files in root)
1371
- os.makedirs(dir_path, exist_ok=True)
1372
-
1373
- with open(file_path, 'w', encoding='utf-8') as f:
1374
- f.write(change.content)
1375
-
1376
- if len(self.get_all_file_changes()) > 0:
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=self.printer.get_message_from_key("/agent/edit/apply_changes")), metadata=EventMetadata(
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
- self.printer.print_in_terminal(
1463
- "code_generation_complete",
1464
- duration=0.0,
1465
- input_tokens=last_meta.input_tokens_count,
1466
- output_tokens=last_meta.generated_tokens_count,
1467
- input_cost=input_cost,
1468
- output_cost=output_cost,
1469
- speed=0.0,
1470
- model_names=model_name,
1471
- sampling_count=1
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
- if isinstance(event, LLMThinkingEvent):
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