auto-coder 0.1.364__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.

@@ -44,6 +44,7 @@ from autocoder.common.save_formatted_log import save_formatted_log
44
44
  # Import the new display function
45
45
  from autocoder.common.v2.agent.agentic_tool_display import get_tool_display_message
46
46
  from autocoder.common.v2.agent.agentic_edit_types import FileChangeEntry
47
+ from autocoder.utils.llms import get_single_llm
47
48
 
48
49
  from autocoder.common.file_checkpoint.models import FileChange as CheckpointFileChange
49
50
  from autocoder.common.file_checkpoint.manager import FileChangeManager as CheckpointFileChangeManager
@@ -72,12 +73,15 @@ from autocoder.common.v2.agent.agentic_edit_types import (AgenticEditRequest, To
72
73
  # Event Types
73
74
  LLMOutputEvent, LLMThinkingEvent, ToolCallEvent,
74
75
  ToolResultEvent, CompletionEvent, PlanModeRespondEvent, ErrorEvent, TokenUsageEvent,
76
+ WindowLengthChangeEvent,
75
77
  # Import specific tool types for display mapping
76
78
  ReadFileTool, WriteToFileTool, ReplaceInFileTool, ExecuteCommandTool,
77
79
  ListFilesTool, SearchFilesTool, ListCodeDefinitionNamesTool,
78
80
  AskFollowupQuestionTool, UseMcpTool, AttemptCompletionTool
79
81
  )
80
82
 
83
+ from autocoder.rag.token_counter import count_tokens
84
+
81
85
  # Map Pydantic Tool Models to their Resolver Classes
82
86
  TOOL_RESOLVER_MAP: Dict[Type[BaseTool], Type[BaseToolResolver]] = {
83
87
  ExecuteCommandTool: ExecuteCommandToolResolver,
@@ -110,6 +114,7 @@ class AgenticEdit:
110
114
  conversation_name:Optional[str] = "current"
111
115
  ):
112
116
  self.llm = llm
117
+ self.context_prune_llm = get_single_llm(args.context_prune_model or args.model,product_mode=args.product_mode)
113
118
  self.args = args
114
119
  self.printer = Printer()
115
120
  # Removed self.tools and self.result_manager
@@ -117,6 +122,8 @@ class AgenticEdit:
117
122
  # Removed self.max_iterations
118
123
  # Note: This might need updating based on the new flow
119
124
  self.conversation_history = conversation_history
125
+
126
+ self.current_conversations = []
120
127
  self.memory_config = memory_config
121
128
  self.command_config = command_config # Note: command_config might be unused now
122
129
  self.project_type_analyzer = ProjectTypeAnalyzer(
@@ -610,30 +617,43 @@ class AgenticEdit:
610
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.)
611
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.
612
619
 
613
- {% if enable_active_context_in_generate %}
614
620
  ====
615
621
 
616
- PROJECT PACKAGE CONTEXT
622
+ PACKAGE CONTEXT INFORMATION
623
+
624
+ # Understanding Directory Context
625
+
626
+ ## Purpose
617
627
 
618
- Each directory can contain a short **`active.md`** summary file located under the mirrored path inside
619
- `{{ 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
631
+
632
+ ## Accessing Directory Context
633
+
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
620
638
 
621
- * **Purpose** captures only the files that have **recently changed** in that directory. It is *not* a full listing.
622
- * **Example** for `{{ current_project }}/src/abc/bbc`, the summary is
623
- `{{ current_project }}/.auto-coder/active-context/src/abc/bbc/active.md`.
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
624
642
 
625
- **Reading a summary**
643
+ ## Example
626
644
 
627
645
  ```xml
628
- <read_file>
629
- <path>.auto-coder/active-context/src/abc/bbc/active.md</path>
630
- </read_file>
646
+ <list_package_info>
647
+ <path>src/some/directory</path>
648
+ </list_package_info>
631
649
  ```
632
650
 
633
- Use these summaries to quickly decide which files deserve a deeper look with tools like
634
- `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
635
656
 
636
- {% endif %}
637
657
  ====
638
658
 
639
659
  CAPABILITIES
@@ -675,18 +695,34 @@ class AgenticEdit:
675
695
  {% if extra_docs %}
676
696
  ====
677
697
 
678
- RULES PROVIDED BY USER
698
+ RULES OR DOCUMENTS PROVIDED BY USER
679
699
 
680
700
  The following rules are provided by the user, and you must follow them strictly.
681
701
 
702
+ <user_rule_or_document_files>
682
703
  {% for key, value in extra_docs.items() %}
683
- <user_rule_file>
704
+ <user_rule_or_document_file>
684
705
  ##File: {{ key }}
685
706
  {{ value }}
686
- </user_rule_file>
687
- {% endfor %}
707
+ </user_rule_or_document_file>
708
+ {% endfor %}
709
+ </user_rule_or_document_files>
710
+
688
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.
689
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>
725
+ {% endif %}
690
726
 
691
727
 
692
728
  ====
@@ -709,15 +745,7 @@ class AgenticEdit:
709
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.
710
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.
711
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.
712
-
713
- {% if file_paths_str %}
714
- ====
715
- The following are files that the user is currently focusing on.
716
- Make sure you always start your task by using the read_file tool to get the content of the files.
717
- <files>
718
- {{file_paths_str}}
719
- </files>
720
- {% endif %}
748
+
721
749
  """
722
750
  ## auto_select_rules(context=request.user_input, llm=self.llm,args=self.args) rules =
723
751
  # extra_docs = get_rules()
@@ -790,7 +818,7 @@ class AgenticEdit:
790
818
  # Join with newline for readability, matching prompt examples
791
819
  return "\n".join(xml_parts)
792
820
 
793
- 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]:
794
822
  """
795
823
  Analyzes the user request, interacts with the LLM, parses responses,
796
824
  executes tools, and yields structured events for visualization until completion or error.
@@ -800,6 +828,7 @@ class AgenticEdit:
800
828
  logger.info(f"Generated system prompt with length: {len(system_prompt)}")
801
829
 
802
830
  # print(system_prompt)
831
+
803
832
  conversations = [
804
833
  {"role": "system", "content": system_prompt},
805
834
  ]
@@ -808,8 +837,16 @@ class AgenticEdit:
808
837
  "role": "user", "content": request.user_input
809
838
  })
810
839
 
840
+
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
+
811
848
  logger.info(
812
- f"Initial conversation history size: {len(conversations)}")
849
+ f"Initial conversation history size: {len(conversations)}, tokens: {current_tokens}")
813
850
 
814
851
  iteration_count = 0
815
852
  tool_executed = False
@@ -862,11 +899,18 @@ class AgenticEdit:
862
899
 
863
900
  # Append assistant's thoughts and the tool call to history
864
901
  logger.info(f"Adding assistant message with tool call to conversation history")
902
+
903
+ # 记录当前对话的token数量
865
904
  conversations.append({
866
905
  "role": "assistant",
867
906
  "content": assistant_buffer + tool_xml
868
907
  })
869
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)
870
914
 
871
915
  yield event # Yield the ToolCallEvent for display
872
916
  logger.info("Yielded ToolCallEvent")
@@ -946,10 +990,18 @@ class AgenticEdit:
946
990
 
947
991
  # Append the tool result (as user message) to history
948
992
  logger.info("Adding tool result to conversation history")
993
+
994
+ # 添加工具结果到对话历史
949
995
  conversations.append({
950
996
  "role": "user", # Simulating the user providing the tool result
951
997
  "content": error_xml
952
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
+
953
1005
  logger.info(
954
1006
  f"Added tool result to conversations for tool {type(tool_obj).__name__}")
955
1007
  logger.info(f"Breaking LLM cycle after executing tool: {tool_name}")
@@ -972,6 +1024,7 @@ class AgenticEdit:
972
1024
  # Append any remaining assistant buffer to history if it wasn't followed by a tool
973
1025
  if assistant_buffer:
974
1026
  logger.info(f"Appending assistant buffer to history: {len(assistant_buffer)} chars")
1027
+
975
1028
  last_message = conversations[-1]
976
1029
  if last_message["role"] != "assistant":
977
1030
  logger.info("Adding new assistant message")
@@ -980,13 +1033,24 @@ class AgenticEdit:
980
1033
  elif last_message["role"] == "assistant":
981
1034
  logger.info("Appending to existing assistant message")
982
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)
983
1041
 
984
1042
  # 添加系统提示,要求LLM必须使用工具或明确结束,而不是直接退出
985
1043
  logger.info("Adding system reminder to use tools or attempt completion")
1044
+
986
1045
  conversations.append({
987
1046
  "role": "user",
988
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."
989
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)
990
1054
  # 继续循环,让 LLM 再思考,而不是 break
991
1055
  logger.info("Continuing the LLM interaction loop without breaking")
992
1056
  continue
@@ -1189,9 +1253,7 @@ class AgenticEdit:
1189
1253
 
1190
1254
  # If no event was processed in this iteration, break inner loop
1191
1255
  if not found_event:
1192
- break
1193
-
1194
- yield TokenUsageEvent(usage=meta_holder.meta)
1256
+ break
1195
1257
 
1196
1258
  # After generator exhausted, yield any remaining content
1197
1259
  if in_thinking_block:
@@ -1208,6 +1270,9 @@ class AgenticEdit:
1208
1270
  elif buffer:
1209
1271
  # Yield remaining plain text
1210
1272
  yield LLMOutputEvent(text=buffer)
1273
+
1274
+ # 这个要放在最后,防止其他关联的多个事件的信息中断
1275
+ yield TokenUsageEvent(usage=meta_holder.meta)
1211
1276
 
1212
1277
 
1213
1278
  def apply_pre_changes(self):
@@ -1235,6 +1300,162 @@ class AgenticEdit:
1235
1300
  source_dir=self.args.source_dir, error=str(e))
1236
1301
  return
1237
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
+
1238
1459
  def apply_changes(self):
1239
1460
  """
1240
1461
  Apply all tracked file changes to the original project directory.
@@ -1363,7 +1584,12 @@ class AgenticEdit:
1363
1584
  accumulated_token_usage["input_cost"] += input_cost
1364
1585
  accumulated_token_usage["output_cost"] += output_cost
1365
1586
 
1366
- if isinstance(event, LLMThinkingEvent):
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]")
1591
+
1592
+ elif isinstance(event, LLMThinkingEvent):
1367
1593
  # Render thinking within a less prominent style, maybe grey?
1368
1594
  console.print(f"[grey50]{event.text}[/grey50]", end="")
1369
1595
  elif isinstance(event, LLMOutputEvent):
@@ -1527,18 +1753,7 @@ class AgenticEdit:
1527
1753
  standard event system format and writing them using the event manager.
1528
1754
  """
1529
1755
  event_manager = get_event_manager(self.args.event_file)
1530
- self.apply_pre_changes()
1531
-
1532
- # 用于累计TokenUsageEvent数据
1533
- accumulated_token_usage = {
1534
- "model_name": "",
1535
- "elapsed_time": 0.0,
1536
- "first_token_time": 0.0,
1537
- "input_tokens": 0,
1538
- "output_tokens": 0,
1539
- "input_cost": 0.0,
1540
- "output_cost": 0.0
1541
- }
1756
+ self.apply_pre_changes()
1542
1757
 
1543
1758
  try:
1544
1759
  event_stream = self.analyze(request)
@@ -1616,15 +1831,20 @@ class AgenticEdit:
1616
1831
 
1617
1832
  # 添加日志记录
1618
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}")
1619
-
1620
- # 累计TokenUsageEvent数据而不是立即发送
1621
- accumulated_token_usage["model_name"] = model_name
1622
- if accumulated_token_usage["first_token_time"] == 0.0:
1623
- accumulated_token_usage["first_token_time"] = last_meta.first_token_time
1624
- accumulated_token_usage["input_tokens"] += last_meta.input_tokens_count
1625
- accumulated_token_usage["output_tokens"] += last_meta.generated_tokens_count
1626
- accumulated_token_usage["input_cost"] += input_cost
1627
- accumulated_token_usage["output_cost"] += output_cost
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
+
1628
1848
 
1629
1849
  elif isinstance(agent_event, CompletionEvent):
1630
1850
  # 在这里完成实际合并
@@ -1632,19 +1852,7 @@ class AgenticEdit:
1632
1852
  self.apply_changes()
1633
1853
  except Exception as e:
1634
1854
  logger.exception(
1635
- f"Error merging shadow changes to project: {e}")
1636
-
1637
- # 发送累计的TokenUsageEvent数据
1638
- get_event_manager(self.args.event_file).write_result(
1639
- EventContentCreator.create_result(content=EventContentCreator.ResultTokenStatContent(
1640
- model_name=accumulated_token_usage["model_name"],
1641
- elapsed_time=0.0,
1642
- first_token_time=accumulated_token_usage["first_token_time"],
1643
- input_tokens=accumulated_token_usage["input_tokens"],
1644
- output_tokens=accumulated_token_usage["output_tokens"],
1645
- input_cost=accumulated_token_usage["input_cost"],
1646
- output_cost=accumulated_token_usage["output_cost"]
1647
- ).to_dict()), metadata=metadata.to_dict())
1855
+ f"Error merging shadow changes to project: {e}")
1648
1856
 
1649
1857
  metadata.path = "/agent/edit/completion"
1650
1858
  content = EventContentCreator.create_completion(
@@ -1656,20 +1864,22 @@ class AgenticEdit:
1656
1864
  )
1657
1865
  event_manager.write_completion(
1658
1866
  content=content.to_dict(), metadata=metadata.to_dict())
1659
- elif isinstance(agent_event, ErrorEvent):
1660
- # 发送累计的TokenUsageEvent数据(在错误情况下也需要发送)
1661
- if accumulated_token_usage["input_tokens"] > 0:
1662
- get_event_manager(self.args.event_file).write_result(
1663
- EventContentCreator.create_result(content=EventContentCreator.ResultTokenStatContent(
1664
- model_name=accumulated_token_usage["model_name"],
1665
- elapsed_time=0.0,
1666
- first_token_time=accumulated_token_usage["first_token_time"],
1667
- input_tokens=accumulated_token_usage["input_tokens"],
1668
- output_tokens=accumulated_token_usage["output_tokens"],
1669
- input_cost=accumulated_token_usage["input_cost"],
1670
- output_cost=accumulated_token_usage["output_cost"]
1671
- ).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())
1672
1878
 
1879
+ # 记录日志
1880
+ logger.info(f"当前会话总 tokens: {agent_event.tokens_used}")
1881
+
1882
+ elif isinstance(agent_event, ErrorEvent):
1673
1883
  metadata.path = "/agent/edit/error"
1674
1884
  content = EventContentCreator.create_error(
1675
1885
  error_code="AGENT_ERROR",
@@ -1699,18 +1909,7 @@ class AgenticEdit:
1699
1909
  is_streaming=False,
1700
1910
  stream_out_type="/agent/edit/error")
1701
1911
 
1702
- # 发送累计的TokenUsageEvent数据(在错误情况下也需要发送)
1703
- if accumulated_token_usage["input_tokens"] > 0:
1704
- get_event_manager(self.args.event_file).write_result(
1705
- EventContentCreator.create_result(content=EventContentCreator.ResultTokenStatContent(
1706
- model_name=accumulated_token_usage["model_name"],
1707
- elapsed_time=0.0,
1708
- first_token_time=accumulated_token_usage["first_token_time"],
1709
- input_tokens=accumulated_token_usage["input_tokens"],
1710
- output_tokens=accumulated_token_usage["output_tokens"],
1711
- input_cost=accumulated_token_usage["input_cost"],
1712
- output_cost=accumulated_token_usage["output_cost"]
1713
- ).to_dict()), metadata=metadata.to_dict())
1912
+ # 发送累计的TokenUsageEvent数据(在错误情况下也需要发送)
1714
1913
 
1715
1914
  error_content = EventContentCreator.create_error(
1716
1915
  error_code="AGENT_FATAL_ERROR",