auto-coder 0.1.362__py3-none-any.whl → 0.1.364__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 (65) hide show
  1. {auto_coder-0.1.362.dist-info → auto_coder-0.1.364.dist-info}/METADATA +2 -2
  2. {auto_coder-0.1.362.dist-info → auto_coder-0.1.364.dist-info}/RECORD +65 -22
  3. autocoder/agent/base_agentic/__init__.py +0 -0
  4. autocoder/agent/base_agentic/agent_hub.py +169 -0
  5. autocoder/agent/base_agentic/agentic_lang.py +112 -0
  6. autocoder/agent/base_agentic/agentic_tool_display.py +180 -0
  7. autocoder/agent/base_agentic/base_agent.py +1582 -0
  8. autocoder/agent/base_agentic/default_tools.py +683 -0
  9. autocoder/agent/base_agentic/test_base_agent.py +82 -0
  10. autocoder/agent/base_agentic/tool_registry.py +425 -0
  11. autocoder/agent/base_agentic/tools/__init__.py +12 -0
  12. autocoder/agent/base_agentic/tools/ask_followup_question_tool_resolver.py +72 -0
  13. autocoder/agent/base_agentic/tools/attempt_completion_tool_resolver.py +37 -0
  14. autocoder/agent/base_agentic/tools/base_tool_resolver.py +35 -0
  15. autocoder/agent/base_agentic/tools/example_tool_resolver.py +46 -0
  16. autocoder/agent/base_agentic/tools/execute_command_tool_resolver.py +72 -0
  17. autocoder/agent/base_agentic/tools/list_files_tool_resolver.py +110 -0
  18. autocoder/agent/base_agentic/tools/plan_mode_respond_tool_resolver.py +35 -0
  19. autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +54 -0
  20. autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +156 -0
  21. autocoder/agent/base_agentic/tools/search_files_tool_resolver.py +134 -0
  22. autocoder/agent/base_agentic/tools/talk_to_group_tool_resolver.py +96 -0
  23. autocoder/agent/base_agentic/tools/talk_to_tool_resolver.py +79 -0
  24. autocoder/agent/base_agentic/tools/use_mcp_tool_resolver.py +44 -0
  25. autocoder/agent/base_agentic/tools/write_to_file_tool_resolver.py +58 -0
  26. autocoder/agent/base_agentic/types.py +189 -0
  27. autocoder/agent/base_agentic/utils.py +100 -0
  28. autocoder/auto_coder_runner.py +6 -4
  29. autocoder/chat/conf_command.py +11 -10
  30. autocoder/common/__init__.py +2 -0
  31. autocoder/common/file_checkpoint/__init__.py +21 -0
  32. autocoder/common/file_checkpoint/backup.py +264 -0
  33. autocoder/common/file_checkpoint/examples.py +217 -0
  34. autocoder/common/file_checkpoint/manager.py +404 -0
  35. autocoder/common/file_checkpoint/models.py +156 -0
  36. autocoder/common/file_checkpoint/store.py +383 -0
  37. autocoder/common/file_checkpoint/test_backup.py +242 -0
  38. autocoder/common/file_checkpoint/test_manager.py +570 -0
  39. autocoder/common/file_checkpoint/test_models.py +360 -0
  40. autocoder/common/file_checkpoint/test_store.py +327 -0
  41. autocoder/common/file_checkpoint/test_utils.py +297 -0
  42. autocoder/common/file_checkpoint/utils.py +119 -0
  43. autocoder/common/rulefiles/autocoderrules_utils.py +138 -55
  44. autocoder/common/save_formatted_log.py +76 -5
  45. autocoder/common/v2/agent/agentic_edit.py +339 -216
  46. autocoder/common/v2/agent/agentic_edit_tools/read_file_tool_resolver.py +2 -2
  47. autocoder/common/v2/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +100 -5
  48. autocoder/common/v2/agent/agentic_edit_tools/test_write_to_file_tool_resolver.py +322 -0
  49. autocoder/common/v2/agent/agentic_edit_tools/write_to_file_tool_resolver.py +160 -10
  50. autocoder/common/v2/agent/agentic_edit_types.py +1 -2
  51. autocoder/common/v2/agent/agentic_tool_display.py +2 -3
  52. autocoder/compilers/normal_compiler.py +64 -0
  53. autocoder/events/event_manager_singleton.py +133 -4
  54. autocoder/linters/normal_linter.py +373 -0
  55. autocoder/linters/python_linter.py +4 -2
  56. autocoder/rag/long_context_rag.py +424 -397
  57. autocoder/rag/test_doc_filter.py +393 -0
  58. autocoder/rag/test_long_context_rag.py +473 -0
  59. autocoder/rag/test_token_limiter.py +342 -0
  60. autocoder/shadows/shadow_manager.py +1 -3
  61. autocoder/version.py +1 -1
  62. {auto_coder-0.1.362.dist-info → auto_coder-0.1.364.dist-info}/LICENSE +0 -0
  63. {auto_coder-0.1.362.dist-info → auto_coder-0.1.364.dist-info}/WHEEL +0 -0
  64. {auto_coder-0.1.362.dist-info → auto_coder-0.1.364.dist-info}/entry_points.txt +0 -0
  65. {auto_coder-0.1.362.dist-info → auto_coder-0.1.364.dist-info}/top_level.txt +0 -0
@@ -40,9 +40,15 @@ 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
+
48
+ from autocoder.common.file_checkpoint.models import FileChange as CheckpointFileChange
49
+ from autocoder.common.file_checkpoint.manager import FileChangeManager as CheckpointFileChangeManager
50
+ from autocoder.linters.normal_linter import NormalLinter
51
+ from autocoder.compilers.normal_compiler import NormalCompiler
46
52
  from autocoder.common.v2.agent.agentic_edit_tools import ( # Import specific resolvers
47
53
  BaseToolResolver,
48
54
  ExecuteCommandToolResolver, ReadFileToolResolver, WriteToFileToolResolver,
@@ -51,7 +57,7 @@ from autocoder.common.v2.agent.agentic_edit_tools import ( # Import specific re
51
57
  AttemptCompletionToolResolver, PlanModeRespondToolResolver, UseMcpToolResolver,
52
58
  ListPackageInfoToolResolver
53
59
  )
54
- from autocoder.common.rulefiles.autocoderrules_utils import get_rules
60
+ from autocoder.common.rulefiles.autocoderrules_utils import get_rules,auto_select_rules,get_required_and_index_rules
55
61
  from autocoder.common.v2.agent.agentic_edit_types import (AgenticEditRequest, ToolResult,
56
62
  MemoryConfig, CommandConfig, BaseTool,
57
63
  ExecuteCommandTool, ReadFileTool,
@@ -72,7 +78,6 @@ from autocoder.common.v2.agent.agentic_edit_types import (AgenticEditRequest, To
72
78
  AskFollowupQuestionTool, UseMcpTool, AttemptCompletionTool
73
79
  )
74
80
 
75
-
76
81
  # Map Pydantic Tool Models to their Resolver Classes
77
82
  TOOL_RESOLVER_MAP: Dict[Type[BaseTool], Type[BaseToolResolver]] = {
78
83
  ExecuteCommandTool: ExecuteCommandToolResolver,
@@ -86,7 +91,7 @@ TOOL_RESOLVER_MAP: Dict[Type[BaseTool], Type[BaseToolResolver]] = {
86
91
  AskFollowupQuestionTool: AskFollowupQuestionToolResolver,
87
92
  AttemptCompletionTool: AttemptCompletionToolResolver, # Will stop the loop anyway
88
93
  PlanModeRespondTool: PlanModeRespondToolResolver,
89
- UseMcpTool: UseMcpToolResolver,
94
+ UseMcpTool: UseMcpToolResolver
90
95
  }
91
96
 
92
97
 
@@ -102,7 +107,7 @@ class AgenticEdit:
102
107
  args: AutoCoderArgs,
103
108
  memory_config: MemoryConfig,
104
109
  command_config: Optional[CommandConfig] = None,
105
- conversation_name: str = "current"
110
+ conversation_name:Optional[str] = "current"
106
111
  ):
107
112
  self.llm = llm
108
113
  self.args = args
@@ -117,11 +122,22 @@ class AgenticEdit:
117
122
  self.project_type_analyzer = ProjectTypeAnalyzer(
118
123
  args=args, llm=self.llm)
119
124
 
120
- self.shadow_manager = ShadowManager(
121
- args.source_dir, args.event_file, args.ignore_clean_shadows)
122
- self.shadow_linter = ShadowLinter(self.shadow_manager, verbose=False)
123
- self.shadow_compiler = ShadowCompiler(
124
- self.shadow_manager, verbose=False)
125
+ # self.shadow_manager = ShadowManager(
126
+ # args.source_dir, args.event_file, args.ignore_clean_shadows)
127
+ self.shadow_manager = None
128
+ # self.shadow_linter = ShadowLinter(self.shadow_manager, verbose=False)
129
+ self.shadow_compiler = None
130
+ # self.shadow_compiler = ShadowCompiler(self.shadow_manager, verbose=False)
131
+ self.shadow_linter = None
132
+
133
+ self.checkpoint_manager = CheckpointFileChangeManager(
134
+ project_dir=args.source_dir,
135
+ backup_dir=os.path.join(args.source_dir,".auto-coder","checkpoint"),
136
+ store_dir=os.path.join(args.source_dir,".auto-coder","checkpoint_store"),
137
+ max_history=50)
138
+ self.linter = NormalLinter(args.source_dir,verbose=False)
139
+ self.compiler = NormalCompiler(args.source_dir,verbose=False)
140
+
125
141
 
126
142
  self.mcp_server_info = ""
127
143
  try:
@@ -233,7 +249,7 @@ class AgenticEdit:
233
249
  # Tools
234
250
 
235
251
  ## execute_command
236
- Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Commands will be executed in the current working directory: ${cwd.toPosix()}
252
+ Description: Request to execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. For command chaining, use the appropriate chaining syntax for the user's shell. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run. Commands will be executed in the current working directory: {{current_project}}
237
253
  Parameters:
238
254
  - command: (required) The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.
239
255
  - requires_approval: (required) A boolean indicating whether this command requires explicit user approval before execution in case the user has auto-approve mode enabled. Set to 'true' for potentially impactful operations like installing/uninstalling packages, deleting/overwriting files, system configuration changes, network operations, or any commands that could have unintended side effects. Set to 'false' for safe operations like reading files/directories, running development servers, building projects, and other non-destructive operations.
@@ -255,7 +271,7 @@ class AgenticEdit:
255
271
  ## read_file
256
272
  Description: Request to read the contents of a file at the specified path. Use this when you need to examine the contents of an existing file you do not know the contents of, for example to analyze code, review text files, or extract information from configuration files. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files, as it returns the raw content as a string.
257
273
  Parameters:
258
- - path: (required) The path of the file to read (relative to the current working directory ${cwd.toPosix()})
274
+ - path: (required) The path of the file to read (relative to the current working directory {{ current_project }})
259
275
  Usage:
260
276
  <read_file>
261
277
  <path>File path here</path>
@@ -264,7 +280,7 @@ class AgenticEdit:
264
280
  ## write_to_file
265
281
  Description: Request to write content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. This tool will automatically create any directories needed to write the file.
266
282
  Parameters:
267
- - path: (required) The path of the file to write to (relative to the current working directory ${cwd.toPosix()})
283
+ - path: (required) The path of the file to write to (relative to the current working directory {{ current_project }})
268
284
  - content: (required) The content to write to the file. ALWAYS provide the COMPLETE intended content of the file, without any truncation or omissions. You MUST include ALL parts of the file, even if they haven't been modified.
269
285
  Usage:
270
286
  <write_to_file>
@@ -277,15 +293,15 @@ class AgenticEdit:
277
293
  ## replace_in_file
278
294
  Description: Request to replace sections of content in an existing file using SEARCH/REPLACE blocks that define exact changes to specific parts of the file. This tool should be used when you need to make targeted changes to specific parts of a file.
279
295
  Parameters:
280
- - path: (required) The path of the file to modify (relative to the current working directory ${cwd.toPosix()})
296
+ - path: (required) The path of the file to modify (relative to the current working directory {{ current_project }})
281
297
  - diff: (required) One or more SEARCH/REPLACE blocks following this exact format:
282
- \`\`\`
298
+ ```
283
299
  <<<<<<< SEARCH
284
300
  [exact content to find]
285
301
  =======
286
302
  [new content to replace with]
287
303
  >>>>>>> REPLACE
288
- \`\`\`
304
+ ```
289
305
  Critical rules:
290
306
  1. SEARCH content must match the associated file section to find EXACTLY:
291
307
  * Match character-for-character including whitespace, indentation, line endings
@@ -313,7 +329,7 @@ class AgenticEdit:
313
329
  ## search_files
314
330
  Description: Request to perform a regex search across files in a specified directory, providing context-rich results. This tool searches for patterns or specific content across multiple files, displaying each match with encapsulating context.
315
331
  Parameters:
316
- - path: (required) The path of the directory to search in (relative to the current working directory ${cwd.toPosix()}). This directory will be recursively searched.
332
+ - path: (required) The path of the directory to search in (relative to the current working directory {{ current_project }}). This directory will be recursively searched.
317
333
  - regex: (required) The regular expression pattern to search for. Uses Rust regex syntax.
318
334
  - file_pattern: (optional) Glob pattern to filter files (e.g., '*.ts' for TypeScript files). If not provided, it will search all files (*).
319
335
  Usage:
@@ -326,7 +342,7 @@ class AgenticEdit:
326
342
  ## list_files
327
343
  Description: Request to list files and directories within the specified directory. If recursive is true, it will list all files and directories recursively. If recursive is false or not provided, it will only list the top-level contents. Do not use this tool to confirm the existence of files you may have created, as the user will let you know if the files were created successfully or not.
328
344
  Parameters:
329
- - path: (required) The path of the directory to list contents for (relative to the current working directory ${cwd.toPosix()})
345
+ - path: (required) The path of the directory to list contents for (relative to the current working directory {{ current_project }})
330
346
  - recursive: (optional) Whether to list files recursively. Use true for recursive listing, false or omit for top-level only.
331
347
  Usage:
332
348
  <list_files>
@@ -337,7 +353,7 @@ class AgenticEdit:
337
353
  ## list_code_definition_names
338
354
  Description: Request to list definition names (classes, functions, methods, etc.) used in source code files at the top level of the specified directory. This tool provides insights into the codebase structure and important constructs, encapsulating high-level concepts and relationships that are crucial for understanding the overall architecture.
339
355
  Parameters:
340
- - path: (required) The path of the directory (relative to the current working directory ${cwd.toPosix()}) to list top level source code definitions for.
356
+ - path: (required) The path of the directory (relative to the current working directory {{ current_project }}) to list top level source code definitions for.
341
357
  Usage:
342
358
  <list_code_definition_names>
343
359
  <path>Directory path here</path>
@@ -636,7 +652,7 @@ class AgenticEdit:
636
652
  - Your current working directory is: {{current_project}}
637
653
  - You cannot \`cd\` into a different directory to complete a task. You are stuck operating from '{{ current_project }}', so be sure to pass in the correct 'path' parameter when using tools that require a path.
638
654
  - Do not use the ~ character or $HOME to refer to the home directory.
639
- - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '${cwd.toPosix()}', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '${cwd.toPosix()}'). For example, if you needed to run \`npm install\` in a project outside of '${cwd.toPosix()}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`.
655
+ - Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '{{ current_project }}', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '{{ current_project }}'). For example, if you needed to run \`npm install\` in a project outside of '{{ current_project }}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`.
640
656
  - When using the search_files tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the search_files tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use read_file to examine the full context of interesting matches before using replace_in_file to make informed changes.
641
657
  - When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when creating files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
642
658
  - Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
@@ -664,12 +680,14 @@ class AgenticEdit:
664
680
  The following rules are provided by the user, and you must follow them strictly.
665
681
 
666
682
  {% for key, value in extra_docs.items() %}
667
- <user_rule>
683
+ <user_rule_file>
668
684
  ##File: {{ key }}
669
685
  {{ value }}
670
- </user_rule>
671
- {% endfor %}
686
+ </user_rule_file>
687
+ {% endfor %}
688
+ 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.
672
689
  {% endif %}
690
+
673
691
 
674
692
  ====
675
693
 
@@ -695,14 +713,15 @@ class AgenticEdit:
695
713
  {% if file_paths_str %}
696
714
  ====
697
715
  The following are files that the user is currently focusing on.
698
- Make sure you always start your analysis by using the read_file tool to get the content of the files.
716
+ Make sure you always start your task by using the read_file tool to get the content of the files.
699
717
  <files>
700
718
  {{file_paths_str}}
701
719
  </files>
702
720
  {% endif %}
703
- """
704
- import os
705
- extra_docs = get_rules()
721
+ """
722
+ ## auto_select_rules(context=request.user_input, llm=self.llm,args=self.args) rules =
723
+ # extra_docs = get_rules()
724
+ extra_docs = get_required_and_index_rules()
706
725
 
707
726
  env_info = detect_env()
708
727
  shell_type = "bash"
@@ -791,9 +810,7 @@ class AgenticEdit:
791
810
 
792
811
  logger.info(
793
812
  f"Initial conversation history size: {len(conversations)}")
794
-
795
- logger.info(f"Conversation history: {json.dumps(conversations, indent=2,ensure_ascii=False)}")
796
-
813
+
797
814
  iteration_count = 0
798
815
  tool_executed = False
799
816
  while True:
@@ -813,22 +830,23 @@ class AgenticEdit:
813
830
 
814
831
  assistant_buffer = ""
815
832
  logger.info("Initializing stream chat with LLM")
833
+
834
+ ## 实际请求大模型
816
835
  llm_response_gen = stream_chat_with_continue(
817
836
  llm=self.llm,
818
837
  conversations=conversations,
819
838
  llm_config={}, # Placeholder for future LLM configs
820
839
  args=self.args
821
840
  )
822
-
823
- meta_holder = byzerllm.MetaHolder()
841
+
824
842
  logger.info("Starting to parse LLM response stream")
825
843
  parsed_events = self.stream_and_parse_llm_response(
826
- llm_response_gen, meta_holder)
844
+ llm_response_gen)
827
845
 
828
846
  event_count = 0
829
847
  for event in parsed_events:
830
848
  event_count += 1
831
- logger.info(f"Processing event #{event_count}: {type(event).__name__}")
849
+ # logger.info(f"Processing event #{event_count}: {type(event).__name__}")
832
850
  global_cancel.check_and_raise(token=self.args.event_file)
833
851
  if isinstance(event, (LLMOutputEvent, LLMThinkingEvent)):
834
852
  assistant_buffer += event.text
@@ -861,6 +879,7 @@ class AgenticEdit:
861
879
  yield CompletionEvent(completion=tool_obj, completion_xml=tool_xml)
862
880
  logger.info(
863
881
  "AgenticEdit analyze loop finished due to AttemptCompletion.")
882
+ save_formatted_log(self.args.source_dir, json.dumps(conversations, ensure_ascii=False), "agentic_conversation")
864
883
  return
865
884
 
866
885
  if isinstance(tool_obj, PlanModeRespondTool):
@@ -870,6 +889,7 @@ class AgenticEdit:
870
889
  yield PlanModeRespondEvent(completion=tool_obj, completion_xml=tool_xml)
871
890
  logger.info(
872
891
  "AgenticEdit analyze loop finished due to PlanModeRespond.")
892
+ save_formatted_log(self.args.source_dir, json.dumps(conversations, ensure_ascii=False), "agentic_conversation")
873
893
  return
874
894
 
875
895
  # Resolve the tool
@@ -942,8 +962,9 @@ class AgenticEdit:
942
962
  # logger.error("Stopping analyze loop due to parsing error.")
943
963
  # return
944
964
 
945
- logger.info("Yielding token usage event")
946
- yield TokenUsageEvent(usage=meta_holder.meta)
965
+ elif isinstance(event, TokenUsageEvent):
966
+ logger.info("Yielding token usage event")
967
+ yield event
947
968
 
948
969
  if not tool_executed:
949
970
  # No tool executed in this LLM response cycle
@@ -971,9 +992,10 @@ class AgenticEdit:
971
992
  continue
972
993
 
973
994
  logger.info(f"AgenticEdit analyze loop finished after {iteration_count} iterations.")
995
+ save_formatted_log(self.args.source_dir, json.dumps(conversations, ensure_ascii=False), "agentic_conversation")
974
996
 
975
997
  def stream_and_parse_llm_response(
976
- self, generator: Generator[Tuple[str, Any], None, None], meta_holder: byzerllm.MetaHolder
998
+ self, generator: Generator[Tuple[str, Any], None, None]
977
999
  ) -> Generator[Union[LLMOutputEvent, LLMThinkingEvent, ToolCallEvent, ErrorEvent], None, None]:
978
1000
  """
979
1001
  Streamingly parses the LLM response generator, distinguishing between
@@ -1050,7 +1072,8 @@ class AgenticEdit:
1050
1072
  logger.exception(
1051
1073
  f"Failed to parse tool XML for <{tool_tag}>: {e}\nXML:\n{tool_xml}")
1052
1074
  return None
1053
-
1075
+
1076
+ meta_holder = byzerllm.MetaHolder()
1054
1077
  for content_chunk, metadata in generator:
1055
1078
  global_cancel.check_and_raise(token=self.args.event_file)
1056
1079
  meta_holder.meta = metadata
@@ -1185,174 +1208,28 @@ class AgenticEdit:
1185
1208
  elif buffer:
1186
1209
  # Yield remaining plain text
1187
1210
  yield LLMOutputEvent(text=buffer)
1188
-
1189
- def run_with_events(self, request: AgenticEditRequest):
1190
- """
1191
- Runs the agentic edit process, converting internal events to the
1192
- standard event system format and writing them using the event manager.
1193
- """
1194
- event_manager = get_event_manager(self.args.event_file)
1195
- self.apply_pre_changes()
1196
-
1197
- try:
1198
- event_stream = self.analyze(request)
1199
- for agent_event in event_stream:
1200
- content = None
1201
- metadata = EventMetadata(
1202
- action_file=self.args.file,
1203
- is_streaming=False,
1204
- stream_out_type="/agent/edit")
1205
-
1206
- if isinstance(agent_event, LLMThinkingEvent):
1207
- content = EventContentCreator.create_stream_thinking(
1208
- content=agent_event.text)
1209
- metadata.is_streaming = True
1210
- metadata.path = "/agent/edit/thinking"
1211
- event_manager.write_stream(
1212
- content=content.to_dict(), metadata=metadata.to_dict())
1213
- elif isinstance(agent_event, LLMOutputEvent):
1214
- content = EventContentCreator.create_stream_content(
1215
- content=agent_event.text)
1216
- metadata.is_streaming = True
1217
- metadata.path = "/agent/edit/output"
1218
- event_manager.write_stream(content=content.to_dict(),
1219
- metadata=metadata.to_dict())
1220
- elif isinstance(agent_event, ToolCallEvent):
1221
- tool_name = type(agent_event.tool).__name__
1222
- metadata.path = "/agent/edit/tool/call"
1223
- content = EventContentCreator.create_result(
1224
- content={
1225
- "tool_name": tool_name,
1226
- **agent_event.tool.model_dump()
1227
- },
1228
- metadata={}
1229
- )
1230
- event_manager.write_result(
1231
- content=content.to_dict(), metadata=metadata.to_dict())
1232
- elif isinstance(agent_event, ToolResultEvent):
1233
- metadata.path = "/agent/edit/tool/result"
1234
- content = EventContentCreator.create_result(
1235
- content={
1236
- "tool_name": agent_event.tool_name,
1237
- **agent_event.result.model_dump()
1238
- },
1239
- metadata={}
1240
- )
1241
- event_manager.write_result(
1242
- content=content.to_dict(), metadata=metadata.to_dict())
1243
- elif isinstance(agent_event, PlanModeRespondEvent):
1244
- metadata.path = "/agent/edit/plan_mode_respond"
1245
- content = EventContentCreator.create_markdown_result(
1246
- content=agent_event.completion.response,
1247
- metadata={}
1248
- )
1249
- event_manager.write_result(
1250
- content=content.to_dict(), metadata=metadata.to_dict())
1251
-
1252
- elif isinstance(agent_event, TokenUsageEvent):
1253
- last_meta: SingleOutputMeta = agent_event.usage
1254
- # Get model info for pricing
1255
- from autocoder.utils import llms as llm_utils
1256
- model_name = ",".join(llm_utils.get_llm_names(self.llm))
1257
- model_info = llm_utils.get_model_info(
1258
- model_name, self.args.product_mode) or {}
1259
- input_price = model_info.get(
1260
- "input_price", 0.0) if model_info else 0.0
1261
- output_price = model_info.get(
1262
- "output_price", 0.0) if model_info else 0.0
1263
-
1264
- # Calculate costs
1265
- input_cost = (last_meta.input_tokens_count *
1266
- input_price) / 1000000 # Convert to millions
1267
- # Convert to millions
1268
- output_cost = (
1269
- last_meta.generated_tokens_count * output_price) / 1000000
1270
-
1271
- # 添加日志记录
1272
- 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}")
1273
-
1274
- get_event_manager(self.args.event_file).write_result(
1275
- EventContentCreator.create_result(content=EventContentCreator.ResultTokenStatContent(
1276
- model_name=model_name,
1277
- elapsed_time=0.0,
1278
- first_token_time=last_meta.first_token_time,
1279
- input_tokens=last_meta.input_tokens_count,
1280
- output_tokens=last_meta.generated_tokens_count,
1281
- input_cost=input_cost,
1282
- output_cost=output_cost
1283
- ).to_dict()), metadata=metadata.to_dict())
1284
-
1285
- elif isinstance(agent_event, CompletionEvent):
1286
- # 在这里完成实际合并
1287
- try:
1288
- self.apply_changes()
1289
- except Exception as e:
1290
- logger.exception(
1291
- f"Error merging shadow changes to project: {e}")
1292
-
1293
- metadata.path = "/agent/edit/completion"
1294
- content = EventContentCreator.create_completion(
1295
- success_code="AGENT_COMPLETE",
1296
- success_message="Agent attempted task completion.",
1297
- result={
1298
- "response": agent_event.completion.result
1299
- }
1300
- )
1301
- event_manager.write_completion(
1302
- content=content.to_dict(), metadata=metadata.to_dict())
1303
- elif isinstance(agent_event, ErrorEvent):
1304
- metadata.path = "/agent/edit/error"
1305
- content = EventContentCreator.create_error(
1306
- error_code="AGENT_ERROR",
1307
- error_message=agent_event.message,
1308
- details={"agent_event_type": "ErrorEvent"}
1309
- )
1310
- event_manager.write_error(
1311
- content=content.to_dict(), metadata=metadata.to_dict())
1312
- else:
1313
- metadata.path = "/agent/edit/error"
1314
- logger.warning(
1315
- f"Unhandled agent event type: {type(agent_event)}")
1316
- content = EventContentCreator.create_error(
1317
- error_code="AGENT_ERROR",
1318
- error_message=f"Unhandled agent event type: {type(agent_event)}",
1319
- details={"agent_event_type": type(
1320
- agent_event).__name__}
1321
- )
1322
- event_manager.write_error(
1323
- content=content.to_dict(), metadata=metadata.to_dict())
1324
-
1325
- except Exception as e:
1326
- logger.exception(
1327
- "An unexpected error occurred during agent execution:")
1328
- metadata = EventMetadata(
1329
- action_file=self.args.file,
1330
- is_streaming=False,
1331
- stream_out_type="/agent/edit/error")
1332
- error_content = EventContentCreator.create_error(
1333
- error_code="AGENT_FATAL_ERROR",
1334
- error_message=f"An unexpected error occurred: {str(e)}",
1335
- details={"exception_type": type(e).__name__}
1336
- )
1337
- event_manager.write_error(
1338
- content=error_content.to_dict(), metadata=metadata.to_dict())
1339
- # Re-raise the exception if needed, or handle appropriately
1340
- raise e
1211
+
1341
1212
 
1342
1213
  def apply_pre_changes(self):
1343
1214
  # get the file name
1344
1215
  file_name = os.path.basename(self.args.file)
1345
1216
  if not self.args.skip_commit:
1346
1217
  try:
1218
+ commit_result = git_utils.commit_changes(
1219
+ self.args.source_dir, f"auto_coder_pre_{file_name}")
1347
1220
  get_event_manager(self.args.event_file).write_result(
1348
1221
  EventContentCreator.create_result(
1349
- content=self.printer.get_message_from_key("/agent/edit/apply_pre_changes")), metadata=EventMetadata(
1222
+ content={
1223
+ "have_commit":commit_result.success,
1224
+ "commit_hash":commit_result.commit_hash,
1225
+ "diff_file_num":len(commit_result.changed_files),
1226
+ "event_file":self.args.event_file
1227
+ }), metadata=EventMetadata(
1350
1228
  action_file=self.args.file,
1351
1229
  is_streaming=False,
1352
1230
  path="/agent/edit/apply_pre_changes",
1353
1231
  stream_out_type="/agent/edit").to_dict())
1354
- git_utils.commit_changes(
1355
- self.args.source_dir, f"auto_coder_pre_{file_name}")
1232
+
1356
1233
  except Exception as e:
1357
1234
  self.printer.print_in_terminal("git_init_required",
1358
1235
  source_dir=self.args.source_dir, error=str(e))
@@ -1362,16 +1239,22 @@ class AgenticEdit:
1362
1239
  """
1363
1240
  Apply all tracked file changes to the original project directory.
1364
1241
  """
1365
- for (file_path, change) in self.get_all_file_changes().items():
1366
- # Ensure the directory exists before writing the file
1367
- dir_path = os.path.dirname(file_path)
1368
- if dir_path: # Ensure dir_path is not empty (for files in root)
1369
- os.makedirs(dir_path, exist_ok=True)
1370
-
1371
- with open(file_path, 'w', encoding='utf-8') as f:
1372
- f.write(change.content)
1373
-
1374
- if len(self.get_all_file_changes()) > 0:
1242
+ diff_file_num = 0
1243
+ if self.shadow_manager:
1244
+ for (file_path, change) in self.get_all_file_changes().items():
1245
+ # Ensure the directory exists before writing the file
1246
+ dir_path = os.path.dirname(file_path)
1247
+ if dir_path: # Ensure dir_path is not empty (for files in root)
1248
+ os.makedirs(dir_path, exist_ok=True)
1249
+
1250
+ with open(file_path, 'w', encoding='utf-8') as f:
1251
+ f.write(change.content)
1252
+ diff_file_num = len(self.get_all_file_changes())
1253
+ else:
1254
+ changes = self.checkpoint_manager.get_changes_by_group(self.args.event_file)
1255
+ diff_file_num = len(changes)
1256
+
1257
+ if diff_file_num > 0:
1375
1258
  if not self.args.skip_commit:
1376
1259
  try:
1377
1260
  file_name = os.path.basename(self.args.file)
@@ -1379,13 +1262,20 @@ class AgenticEdit:
1379
1262
  self.args.source_dir,
1380
1263
  f"{self.args.query}\nauto_coder_{file_name}",
1381
1264
  )
1382
-
1265
+
1383
1266
  get_event_manager(self.args.event_file).write_result(
1384
1267
  EventContentCreator.create_result(
1385
- content=self.printer.get_message_from_key("/agent/edit/apply_changes")), metadata=EventMetadata(
1268
+ content={
1269
+ "have_commit":commit_result.success,
1270
+ "commit_hash":commit_result.commit_hash,
1271
+ "diff_file_num":diff_file_num,
1272
+ "event_file":self.args.event_file
1273
+ }), metadata=EventMetadata(
1386
1274
  action_file=self.args.file,
1387
1275
  is_streaming=False,
1276
+ path="/agent/edit/apply_changes",
1388
1277
  stream_out_type="/agent/edit").to_dict())
1278
+
1389
1279
  action_yml_file_manager = ActionYmlFileManager(
1390
1280
  self.args.source_dir)
1391
1281
  action_file_name = os.path.basename(self.args.file)
@@ -1431,6 +1321,15 @@ class AgenticEdit:
1431
1321
  console.print(Panel(
1432
1322
  f"[bold]{get_message('/agent/edit/user_query')}:[/bold]\n{request.user_input}", title=get_message("/agent/edit/objective"), border_style="blue"))
1433
1323
 
1324
+ # 用于累计TokenUsageEvent数据
1325
+ accumulated_token_usage = {
1326
+ "model_name": "",
1327
+ "input_tokens": 0,
1328
+ "output_tokens": 0,
1329
+ "input_cost": 0.0,
1330
+ "output_cost": 0.0
1331
+ }
1332
+
1434
1333
  try:
1435
1334
  self.apply_pre_changes()
1436
1335
  event_stream = self.analyze(request)
@@ -1457,17 +1356,12 @@ class AgenticEdit:
1457
1356
  # 添加日志记录
1458
1357
  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}")
1459
1358
 
1460
- self.printer.print_in_terminal(
1461
- "code_generation_complete",
1462
- duration=0.0,
1463
- input_tokens=last_meta.input_tokens_count,
1464
- output_tokens=last_meta.generated_tokens_count,
1465
- input_cost=input_cost,
1466
- output_cost=output_cost,
1467
- speed=0.0,
1468
- model_names=model_name,
1469
- sampling_count=1
1470
- )
1359
+ # 累计token使用情况
1360
+ accumulated_token_usage["model_name"] = model_name
1361
+ accumulated_token_usage["input_tokens"] += last_meta.input_tokens_count
1362
+ accumulated_token_usage["output_tokens"] += last_meta.generated_tokens_count
1363
+ accumulated_token_usage["input_cost"] += input_cost
1364
+ accumulated_token_usage["output_cost"] += output_cost
1471
1365
 
1472
1366
  if isinstance(event, LLMThinkingEvent):
1473
1367
  # Render thinking within a less prominent style, maybe grey?
@@ -1590,7 +1484,35 @@ class AgenticEdit:
1590
1484
 
1591
1485
  time.sleep(0.1) # Small delay for better visual flow
1592
1486
 
1487
+ # 在处理完所有事件后打印累计的token使用情况
1488
+ if accumulated_token_usage["input_tokens"] > 0:
1489
+ self.printer.print_in_terminal(
1490
+ "code_generation_complete",
1491
+ duration=0.0,
1492
+ input_tokens=accumulated_token_usage["input_tokens"],
1493
+ output_tokens=accumulated_token_usage["output_tokens"],
1494
+ input_cost=accumulated_token_usage["input_cost"],
1495
+ output_cost=accumulated_token_usage["output_cost"],
1496
+ speed=0.0,
1497
+ model_names=accumulated_token_usage["model_name"],
1498
+ sampling_count=1
1499
+ )
1500
+
1593
1501
  except Exception as e:
1502
+ # 在处理异常时也打印累计的token使用情况
1503
+ if accumulated_token_usage["input_tokens"] > 0:
1504
+ self.printer.print_in_terminal(
1505
+ "code_generation_complete",
1506
+ duration=0.0,
1507
+ input_tokens=accumulated_token_usage["input_tokens"],
1508
+ output_tokens=accumulated_token_usage["output_tokens"],
1509
+ input_cost=accumulated_token_usage["input_cost"],
1510
+ output_cost=accumulated_token_usage["output_cost"],
1511
+ speed=0.0,
1512
+ model_names=accumulated_token_usage["model_name"],
1513
+ sampling_count=1
1514
+ )
1515
+
1594
1516
  logger.exception(
1595
1517
  "An unexpected error occurred during agent execution:")
1596
1518
  console.print(Panel(
@@ -1598,3 +1520,204 @@ class AgenticEdit:
1598
1520
  raise e
1599
1521
  finally:
1600
1522
  console.rule("[bold cyan]Agentic Edit Finished[/]")
1523
+
1524
+ def run_with_events(self, request: AgenticEditRequest):
1525
+ """
1526
+ Runs the agentic edit process, converting internal events to the
1527
+ standard event system format and writing them using the event manager.
1528
+ """
1529
+ 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
+ }
1542
+
1543
+ try:
1544
+ event_stream = self.analyze(request)
1545
+ for agent_event in event_stream:
1546
+ content = None
1547
+ metadata = EventMetadata(
1548
+ action_file=self.args.file,
1549
+ is_streaming=False,
1550
+ stream_out_type="/agent/edit")
1551
+
1552
+ if isinstance(agent_event, LLMThinkingEvent):
1553
+ content = EventContentCreator.create_stream_thinking(
1554
+ content=agent_event.text)
1555
+ metadata.is_streaming = True
1556
+ metadata.path = "/agent/edit/thinking"
1557
+ event_manager.write_stream(
1558
+ content=content.to_dict(), metadata=metadata.to_dict())
1559
+ elif isinstance(agent_event, LLMOutputEvent):
1560
+ content = EventContentCreator.create_stream_content(
1561
+ content=agent_event.text)
1562
+ metadata.is_streaming = True
1563
+ metadata.path = "/agent/edit/output"
1564
+ event_manager.write_stream(content=content.to_dict(),
1565
+ metadata=metadata.to_dict())
1566
+ elif isinstance(agent_event, ToolCallEvent):
1567
+ tool_name = type(agent_event.tool).__name__
1568
+ metadata.path = "/agent/edit/tool/call"
1569
+ content = EventContentCreator.create_result(
1570
+ content={
1571
+ "tool_name": tool_name,
1572
+ **agent_event.tool.model_dump()
1573
+ },
1574
+ metadata={}
1575
+ )
1576
+ event_manager.write_result(
1577
+ content=content.to_dict(), metadata=metadata.to_dict())
1578
+ elif isinstance(agent_event, ToolResultEvent):
1579
+ metadata.path = "/agent/edit/tool/result"
1580
+ content = EventContentCreator.create_result(
1581
+ content={
1582
+ "tool_name": agent_event.tool_name,
1583
+ **agent_event.result.model_dump()
1584
+ },
1585
+ metadata={}
1586
+ )
1587
+ event_manager.write_result(
1588
+ content=content.to_dict(), metadata=metadata.to_dict())
1589
+ elif isinstance(agent_event, PlanModeRespondEvent):
1590
+ metadata.path = "/agent/edit/plan_mode_respond"
1591
+ content = EventContentCreator.create_markdown_result(
1592
+ content=agent_event.completion.response,
1593
+ metadata={}
1594
+ )
1595
+ event_manager.write_result(
1596
+ content=content.to_dict(), metadata=metadata.to_dict())
1597
+
1598
+ elif isinstance(agent_event, TokenUsageEvent):
1599
+ last_meta: SingleOutputMeta = agent_event.usage
1600
+ # Get model info for pricing
1601
+ from autocoder.utils import llms as llm_utils
1602
+ model_name = ",".join(llm_utils.get_llm_names(self.llm))
1603
+ model_info = llm_utils.get_model_info(
1604
+ model_name, self.args.product_mode) or {}
1605
+ input_price = model_info.get(
1606
+ "input_price", 0.0) if model_info else 0.0
1607
+ output_price = model_info.get(
1608
+ "output_price", 0.0) if model_info else 0.0
1609
+
1610
+ # Calculate costs
1611
+ input_cost = (last_meta.input_tokens_count *
1612
+ input_price) / 1000000 # Convert to millions
1613
+ # Convert to millions
1614
+ output_cost = (
1615
+ last_meta.generated_tokens_count * output_price) / 1000000
1616
+
1617
+ # 添加日志记录
1618
+ 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
1628
+
1629
+ elif isinstance(agent_event, CompletionEvent):
1630
+ # 在这里完成实际合并
1631
+ try:
1632
+ self.apply_changes()
1633
+ except Exception as e:
1634
+ 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())
1648
+
1649
+ metadata.path = "/agent/edit/completion"
1650
+ content = EventContentCreator.create_completion(
1651
+ success_code="AGENT_COMPLETE",
1652
+ success_message="Agent attempted task completion.",
1653
+ result={
1654
+ "response": agent_event.completion.result
1655
+ }
1656
+ )
1657
+ event_manager.write_completion(
1658
+ 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())
1672
+
1673
+ metadata.path = "/agent/edit/error"
1674
+ content = EventContentCreator.create_error(
1675
+ error_code="AGENT_ERROR",
1676
+ error_message=agent_event.message,
1677
+ details={"agent_event_type": "ErrorEvent"}
1678
+ )
1679
+ event_manager.write_error(
1680
+ content=content.to_dict(), metadata=metadata.to_dict())
1681
+ else:
1682
+ metadata.path = "/agent/edit/error"
1683
+ logger.warning(
1684
+ f"Unhandled agent event type: {type(agent_event)}")
1685
+ content = EventContentCreator.create_error(
1686
+ error_code="AGENT_ERROR",
1687
+ error_message=f"Unhandled agent event type: {type(agent_event)}",
1688
+ details={"agent_event_type": type(
1689
+ agent_event).__name__}
1690
+ )
1691
+ event_manager.write_error(
1692
+ content=content.to_dict(), metadata=metadata.to_dict())
1693
+
1694
+ except Exception as e:
1695
+ logger.exception(
1696
+ "An unexpected error occurred during agent execution:")
1697
+ metadata = EventMetadata(
1698
+ action_file=self.args.file,
1699
+ is_streaming=False,
1700
+ stream_out_type="/agent/edit/error")
1701
+
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())
1714
+
1715
+ error_content = EventContentCreator.create_error(
1716
+ error_code="AGENT_FATAL_ERROR",
1717
+ error_message=f"An unexpected error occurred: {str(e)}",
1718
+ details={"exception_type": type(e).__name__}
1719
+ )
1720
+ event_manager.write_error(
1721
+ content=error_content.to_dict(), metadata=metadata.to_dict())
1722
+ # Re-raise the exception if needed, or handle appropriately
1723
+ raise e