auto-coder 0.1.302__py3-none-any.whl → 0.1.303__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.

@@ -1,3 +1,4 @@
1
+ from enum import Enum
1
2
  import json
2
3
  import os
3
4
  import time
@@ -24,8 +25,12 @@ from autocoder.common.global_cancel import global_cancel
24
25
  from autocoder.common.auto_configure import config_readme
25
26
  from autocoder.utils.auto_project_type import ProjectTypeAnalyzer
26
27
  from rich.text import Text
27
- from autocoder.common.mcp_server import get_mcp_server,McpServerInfoRequest
28
+ from autocoder.common.mcp_server import get_mcp_server, McpServerInfoRequest
28
29
  from autocoder.common.action_yml_file_manager import ActionYmlFileManager
30
+ from autocoder.events.event_manager_singleton import get_event_manager
31
+ from autocoder.events import event_content as EventContentCreator
32
+ from autocoder.run_context import get_run_context
33
+ from autocoder.common.stream_out_type import AutoCommandStreamOutType
29
34
  class CommandMessage(BaseModel):
30
35
  role: str
31
36
  content: str
@@ -38,19 +43,19 @@ class ExtendedCommandMessage(BaseModel):
38
43
 
39
44
  class CommandConversation(BaseModel):
40
45
  history: Dict[str, ExtendedCommandMessage]
41
- current_conversation: List[CommandMessage]
46
+ current_conversation: List[ExtendedCommandMessage]
42
47
 
43
48
 
44
49
  def load_memory_file(args: AutoCoderArgs) -> CommandConversation:
45
- """Load command conversations from memory file"""
46
-
50
+ """Load command conversations from memory file"""
51
+
47
52
  memory_dir = os.path.join(".auto-coder", "memory")
48
53
  file_path = os.path.join(memory_dir, "command_chat_history.json")
49
54
  if os.path.exists(file_path):
50
55
  with open(file_path, "r", encoding="utf-8") as f:
51
56
  try:
52
- conversation = CommandConversation.model_validate_json(f.read())
53
- conversation.current_conversation = conversation.current_conversation
57
+ conversation = CommandConversation.model_validate_json(
58
+ f.read())
54
59
  return conversation
55
60
  except Exception:
56
61
  return CommandConversation(history={}, current_conversation=[])
@@ -58,7 +63,7 @@ def load_memory_file(args: AutoCoderArgs) -> CommandConversation:
58
63
 
59
64
 
60
65
  class TimeBasedStrategy:
61
- def __init__(self, max_idle_time=3600): # 1 hour in seconds
66
+ def __init__(self, max_idle_time=3600*24): # 24 hour in seconds
62
67
  self.max_idle_time = max_idle_time
63
68
 
64
69
  def should_archive(self, last_message_time):
@@ -66,12 +71,13 @@ class TimeBasedStrategy:
66
71
  current_time = time.time()
67
72
  return current_time - last_message_time > self.max_idle_time
68
73
 
74
+
69
75
  def save_to_memory_file(query: str, response: str):
70
76
  """Save command conversation to memory file using CommandConversation structure"""
71
77
  memory_dir = os.path.join(".auto-coder", "memory")
72
78
  os.makedirs(memory_dir, exist_ok=True)
73
79
  file_path = os.path.join(memory_dir, "command_chat_history.json")
74
-
80
+
75
81
  # Initialize time-based strategy
76
82
  time_strategy = TimeBasedStrategy()
77
83
 
@@ -97,7 +103,8 @@ def save_to_memory_file(query: str, response: str):
97
103
  f.read())
98
104
  # Check if we should archive current conversation
99
105
  if existing_conv.current_conversation:
100
- last_message_time = float(existing_conv.current_conversation[-1].timestamp)
106
+ last_message_time = float(
107
+ existing_conv.current_conversation[-1].timestamp)
101
108
  if time_strategy.should_archive(last_message_time):
102
109
  # Move current conversation to history
103
110
  timestamp = str(int(last_message_time))
@@ -127,13 +134,14 @@ class CommandSuggestion(BaseModel):
127
134
  confidence: float
128
135
  reasoning: str
129
136
 
137
+
130
138
  class AutoCommandResponse(BaseModel):
131
139
  suggestions: List[CommandSuggestion]
132
140
  reasoning: Optional[str] = None
133
141
 
134
142
 
135
143
  class AutoCommandRequest(BaseModel):
136
- user_input: str
144
+ user_input: str
137
145
 
138
146
 
139
147
  class MemoryConfig(BaseModel):
@@ -173,7 +181,6 @@ class CommandConfig(BaseModel):
173
181
  index_import: SkipValidation[Callable]
174
182
  exclude_files: SkipValidation[Callable]
175
183
 
176
-
177
184
 
178
185
  class CommandAutoTuner:
179
186
  def __init__(self, llm: Union[byzerllm.ByzerLLM, byzerllm.SimpleByzerLLM],
@@ -185,7 +192,8 @@ class CommandAutoTuner:
185
192
  self.memory_config = memory_config
186
193
  self.command_config = command_config
187
194
  self.tools = AutoCommandTools(args=args, llm=self.llm)
188
- self.project_type_analyzer = ProjectTypeAnalyzer(args=args, llm=self.llm)
195
+ self.project_type_analyzer = ProjectTypeAnalyzer(
196
+ args=args, llm=self.llm)
189
197
  try:
190
198
  self.mcp_server = get_mcp_server()
191
199
  mcp_server_info_response = self.mcp_server.send_request(McpServerInfoRequest(
@@ -196,7 +204,7 @@ class CommandAutoTuner:
196
204
  except Exception as e:
197
205
  logger.error(f"Error getting MCP server info: {str(e)}")
198
206
  self.mcp_server_info = ""
199
-
207
+
200
208
  def get_conversations(self) -> List[CommandMessage]:
201
209
  """Get conversation history from memory file"""
202
210
  conversation = load_memory_file(args=self.args)
@@ -213,7 +221,7 @@ class CommandAutoTuner:
213
221
  终端类型: {{ env_info.shell_type }}
214
222
  终端编码: {{ env_info.shell_encoding }}
215
223
  当前用户: {{ current_user }}
216
-
224
+
217
225
  {%- if shell_type %}
218
226
  脚本类型:{{ shell_type }}
219
227
  {%- endif %}
@@ -225,9 +233,9 @@ class CommandAutoTuner:
225
233
  虚拟环境: {{ env_info.virtualenv }}
226
234
  {%- endif %}
227
235
  </os_info>
228
-
236
+
229
237
  我们的目标是根据用户输入和当前上下文,组合多个函数来完成用户的需求。
230
-
238
+
231
239
  {% if current_files %}
232
240
  ## 当前活跃区文件列表:
233
241
  <current_files>
@@ -242,7 +250,7 @@ class CommandAutoTuner:
242
250
  <current_conf>
243
251
  {{ current_conf }}
244
252
  </current_conf>
245
-
253
+
246
254
  ## 可用函数列表:
247
255
  {{ available_commands }}
248
256
 
@@ -268,7 +276,7 @@ class CommandAutoTuner:
268
276
 
269
277
  请分析用户意图,组合一个或者多个函数,帮助用户完成需求。
270
278
  返回格式必须是严格的JSON格式:
271
-
279
+
272
280
  ```json
273
281
  {
274
282
  "suggestions": [
@@ -287,7 +295,7 @@ class CommandAutoTuner:
287
295
  满足需求。
288
296
  """
289
297
 
290
- env_info = detect_env()
298
+ env_info = detect_env()
291
299
  shell_type = "bash"
292
300
  if shells.is_running_in_cmd():
293
301
  shell_type = "cmd"
@@ -296,10 +304,10 @@ class CommandAutoTuner:
296
304
 
297
305
  return {
298
306
  "user_input": request.user_input,
299
- "current_files": self.memory_config.memory["current_files"]["files"],
307
+ "current_files": self.memory_config.memory["current_files"]["files"],
300
308
  "conversation_history": self.get_conversations(),
301
309
  "available_commands": self._command_readme.prompt(),
302
- "current_conf": json.dumps(self.memory_config.memory["conf"], indent=2),
310
+ "current_conf": json.dumps(self.memory_config.memory["conf"], indent=2),
303
311
  "env_info": env_info,
304
312
  "shell_type": shell_type,
305
313
  "shell_encoding": shells.get_terminal_encoding(),
@@ -308,7 +316,7 @@ class CommandAutoTuner:
308
316
  "current_user": shells.get_current_username(),
309
317
  "command_combination_readme": self._command_combination_readme.prompt()
310
318
  }
311
-
319
+
312
320
  @byzerllm.prompt()
313
321
  def _command_combination_readme(self) -> str:
314
322
  """
@@ -317,28 +325,28 @@ class CommandAutoTuner:
317
325
 
318
326
  ### 是否根据需求动态修改auto-coder软件配置
319
327
  关注下当前软件的配置,结合当前用户的需求,如果觉得不合理的地方,可以通过 ask_user 函数来询问用户,是否要通过 help 函数修改一些配置。
320
-
328
+
321
329
  ### 如何了解当前项目
322
330
 
323
331
  通常可以自己通过调用 get_project_structure 函数来获取项目结构(如果项目结构太大,该函数会拒绝返回,你可以选择 list_files 函数来查看目录),然后通过 get_project_map 函数来获取某几个文件的用途,符号列表,以及
324
332
  文件大小(tokens数),最后再通过 read_files/read_file_with_keyword_ranges 函数来读取文件内容,从而更好的结合当前项目理解用户的需求。
325
-
333
+
326
334
  ### 复杂需求,先做讨论设计
327
335
  对于一个比较复杂的代码需求,你可以先通过 chat 函数来获得一些设计,根据chat返回的结果,你可以选择多次调用chat调整最后的设计。最后,当你满意后,可以通过 coding("/apply") 来完成最后的编码。
328
336
  注意,为了防止对话过长,你可以使用 chat("/new") 来创新新的会话。然后接着正常再次调用 chat 函数。 即可。
329
337
  尽可通过了解项目后,多用 @文件和@@符号,这样 chat 函数可以更清晰的理解你关注的代码,文档和意图。
330
-
338
+
331
339
  ### 调用 coding 函数应该注意的事项
332
340
  调用 coding 函数的时候,尽可能多的 @文件和@@符号,让需求更加清晰明了,建议多描述具体怎么完成对应的需求。
333
341
  对于代码需求设计,尽可能使用 chat 函数。如果成功执行了 coding 函数, 最好再调用一次 chat("/review /commit"),方便总结这次代码变更。
334
342
  注意,review 完后,需要询问用户是否要做啥调整不,如果用户说不用,那么就停止。否则根据意图进行后续操作。
335
-
343
+
336
344
  ### 关于对话大小的问题
337
345
  我们对话历史以及查看的内容累计不能超过 {{ conversation_safe_zone_tokens }} 个tokens,当你读取索引文件 (get_project_map) 的时候,你可以看到
338
346
  每个文件的tokens数,你可以根据这个信息来决定如何读取这个文件。如果不确定,使用 count_file_tokens 函数来获取文件的tokens数,再决定如何读取。
339
347
  而对于分析一个超大文件推荐组合 read_files 带上 line_ranges 参数来读取,或者组合 read_file_with_keyword_ranges 等来读取,
340
348
  每个函数你还可以使用多次来获取更多信息。
341
-
349
+
342
350
  ### 善用脚本完成一些基本的操作
343
351
  根据操作系统,终端类型,脚本类型等各种信息,在涉及到路径或者脚本的时候,需要考虑平台差异性。
344
352
 
@@ -348,51 +356,52 @@ class CommandAutoTuner:
348
356
 
349
357
  如果你没有明确目标,需要单纯查看这个文件获取必要的信息,可以先通过 count_file_tokens 函数来获取文件的tokens数,如果数目小于安全对话窗口的tokens数的1/2, 那么可以直接用
350
358
  read_files 函数来读取,否则建议一次读取200-600行,多次读取直到找到合适的信息。
351
-
359
+
352
360
  ## 其他一些注意事项
353
361
  1. 使用 read_files 时,一次性读取文件数量不要超过1个,每次只读取200行。如果发现读取的内容不够,则继续读取下面200行。
354
362
  2. 确实有必要才使用 get_project_structure 函数,否则可以多使用 list_files 函数来查看目录。
355
363
  3. 最后,不要局限在我们前面描述的使用说明中,根据各个函数的说明,灵活组合和使用各个函数,发挥自己的想象力,尽可能的完成用户的需求。
356
364
  </function_combination_readme>
357
- """
358
-
365
+ """
366
+
359
367
  @byzerllm.prompt()
360
- def _execute_command_result(self, result:str) -> str:
368
+ def _execute_command_result(self, result: str) -> str:
361
369
  '''
362
370
  根据函数执行结果,返回下一个函数。
363
371
 
364
372
  下面是我们上一个函数执行结果:
365
-
373
+
366
374
  <function_result>
367
375
  {{ result }}
368
376
  </function_result>
369
377
 
370
378
  请根据命令执行结果以及前面的对话,返回下一个函数。
371
-
379
+
372
380
  *** 非常非常重要的提示 ***
373
- 1. 如果已经满足要求,则不要返回任何函数,确保 suggestions 为空。
381
+ 1. 如果已经满足要求,则总是调用 response_user函数,对用户的初始问题根据前面所有信息做一次详细的回复。
374
382
  2. 你最多尝试 {{ auto_command_max_iterations }} 次,如果 {{ auto_command_max_iterations }} 次都没有满足要求,则不要返回任何函数,确保 suggestions 为空。
375
- '''
383
+ '''
376
384
  return {
377
385
  "auto_command_max_iterations": self.args.auto_command_max_iterations,
378
386
  "conversation_safe_zone_tokens": self.args.conversation_prune_safe_zone_tokens
379
- }
380
-
387
+ }
388
+
381
389
  def analyze(self, request: AutoCommandRequest) -> AutoCommandResponse:
382
390
  # 获取 prompt 内容
383
391
  prompt = self._analyze.prompt(request)
384
-
392
+
385
393
  # 获取对当前项目变更的最近8条历史人物
386
394
  action_yml_file_manager = ActionYmlFileManager(self.args.source_dir)
387
395
  history_tasks = action_yml_file_manager.to_tasks_prompt(limit=8)
388
396
  new_messages = []
389
397
  if self.args.enable_task_history:
390
398
  new_messages.append({"role": "user", "content": history_tasks})
391
- new_messages.append({"role": "assistant", "content": "好的,我知道最近的任务对项目的变更了,我会参考这些来更好的理解你的需求。"})
392
-
399
+ new_messages.append(
400
+ {"role": "assistant", "content": "好的,我知道最近的任务对项目的变更了,我会参考这些来更好的理解你的需求。"})
401
+
393
402
  # 构造对话上下文
394
403
  conversations = new_messages + [{"role": "user", "content": prompt}]
395
-
404
+
396
405
  # 使用 stream_out 进行输出
397
406
  printer = Printer()
398
407
  title = printer.get_message_from_key("auto_command_analyzing")
@@ -401,116 +410,163 @@ class CommandAutoTuner:
401
410
  def extract_command_response(content: str) -> str:
402
411
  # 提取 JSON 并转换为 AutoCommandResponse
403
412
  try:
404
- response = to_model(content, AutoCommandResponse)
405
- if response.suggestions:
413
+ response = to_model(content, AutoCommandResponse)
414
+ if response.suggestions:
406
415
  command = response.suggestions[0].command
407
- parameters = response.suggestions[0].parameters
416
+ parameters = response.suggestions[0].parameters
408
417
  if parameters:
409
- params_str = ", ".join([f"{k}={v}" for k, v in parameters.items()])
418
+ params_str = ", ".join(
419
+ [f"{k}={v}" for k, v in parameters.items()])
410
420
  else:
411
- params_str = ""
421
+ params_str = ""
412
422
  return f"{command}({params_str})"
413
423
  else:
414
- return printer.get_message_from_key("satisfied_prompt")
415
- except Exception as e:
424
+ return printer.get_message_from_key("satisfied_prompt")
425
+ except Exception as e:
416
426
  logger.error(f"Error extracting command response: {str(e)}")
417
427
  return content
418
-
428
+
419
429
  model_name = ",".join(llms_utils.get_llm_names(self.llm))
420
430
  start_time = time.monotonic()
421
431
  result, last_meta = stream_out(
422
- self.llm.stream_chat_oai(conversations=conversations, delta_mode=True),
432
+ self.llm.stream_chat_oai(
433
+ conversations=conversations, delta_mode=True),
423
434
  model_name=model_name,
424
435
  title=title,
425
436
  final_title=final_title,
426
- display_func= extract_command_response
437
+ display_func=extract_command_response,
438
+ args=self.args,
439
+ extra_meta={
440
+ "stream_out_type": AutoCommandStreamOutType.COMMAND_SUGGESTION.value
441
+ }
427
442
  )
428
-
443
+
429
444
  if last_meta:
430
- elapsed_time = time.monotonic() - start_time
445
+ elapsed_time = time.monotonic() - start_time
431
446
  speed = last_meta.generated_tokens_count / elapsed_time
432
-
447
+
433
448
  # Get model info for pricing
434
449
  from autocoder.utils import llms as llm_utils
435
- model_info = llm_utils.get_model_info(model_name, self.args.product_mode) or {}
436
- input_price = model_info.get("input_price", 0.0) if model_info else 0.0
437
- output_price = model_info.get("output_price", 0.0) if model_info else 0.0
438
-
450
+ model_info = llm_utils.get_model_info(
451
+ model_name, self.args.product_mode) or {}
452
+ input_price = model_info.get(
453
+ "input_price", 0.0) if model_info else 0.0
454
+ output_price = model_info.get(
455
+ "output_price", 0.0) if model_info else 0.0
456
+
439
457
  # Calculate costs
440
- input_cost = (last_meta.input_tokens_count * input_price) / 1000000 # Convert to millions
441
- output_cost = (last_meta.generated_tokens_count * output_price) / 1000000 # Convert to millions
442
-
443
- printer.print_in_terminal("stream_out_stats",
444
- model_name=",".join(llms_utils.get_llm_names(self.llm)),
445
- elapsed_time=elapsed_time,
446
- first_token_time=last_meta.first_token_time,
447
- input_tokens=last_meta.input_tokens_count,
448
- output_tokens=last_meta.generated_tokens_count,
449
- input_cost=round(input_cost, 4),
450
- output_cost=round(output_cost, 4),
451
- speed=round(speed, 2))
452
-
453
- ## 这里打印
454
-
455
- conversations.append({"role": "assistant", "content": result})
456
- # 提取 JSON 并转换为 AutoCommandResponse
457
- response = to_model(result, AutoCommandResponse)
458
-
458
+ input_cost = (last_meta.input_tokens_count *
459
+ input_price) / 1000000 # Convert to millions
460
+ output_cost = (last_meta.generated_tokens_count *
461
+ output_price) / 1000000 # Convert to millions
462
+
463
+ temp_content = printer.get_message_from_key_with_format("stream_out_stats",
464
+ model_name=",".join(
465
+ llms_utils.get_llm_names(self.llm)),
466
+ elapsed_time=elapsed_time,
467
+ first_token_time=last_meta.first_token_time,
468
+ input_tokens=last_meta.input_tokens_count,
469
+ output_tokens=last_meta.generated_tokens_count,
470
+ input_cost=round(
471
+ input_cost, 4),
472
+ output_cost=round(
473
+ output_cost, 4),
474
+ speed=round(speed, 2))
475
+ printer.print_str_in_terminal(temp_content)
476
+ get_event_manager(self.args.event_file).write_result(
477
+ EventContentCreator.create_result(content=EventContentCreator.ResultTokenStatContent(
478
+ model_name=model_name,
479
+ elapsed_time=elapsed_time,
480
+ first_token_time=last_meta.first_token_time,
481
+ input_tokens=last_meta.input_tokens_count,
482
+ output_tokens=last_meta.generated_tokens_count,
483
+ input_cost=round(input_cost, 4),
484
+ output_cost=round(output_cost, 4),
485
+ speed=round(speed, 2)
486
+ )).to_dict()
487
+ )
488
+
489
+ # 这里打印
490
+
491
+ conversations.append({"role": "assistant", "content": result})
492
+ # 提取 JSON 并转换为 AutoCommandResponse
493
+ response = to_model(result, AutoCommandResponse)
494
+
459
495
  # 保存对话记录
460
496
  save_to_memory_file(
461
497
  query=request.user_input,
462
498
  response=response.model_dump_json(indent=2)
463
499
  )
464
500
  result_manager = ResultManager()
465
-
501
+
466
502
  while True:
467
503
  if global_cancel.requested:
468
504
  printer = Printer(console)
469
- printer.print_in_terminal("generation_cancelled")
505
+ printer.print_in_terminal("generation_cancelled")
470
506
  break
471
507
  # 执行命令
472
508
  command = response.suggestions[0].command
473
509
  parameters = response.suggestions[0].parameters
510
+
511
+ # 打印正在执行的命令
512
+ temp_content = printer.get_message_from_key_with_format("auto_command_executing",
513
+ command=command
514
+ )
515
+ printer.print_str_in_terminal(temp_content,style="blue")
516
+
517
+ get_event_manager(self.args.event_file).write_result(EventContentCreator.create_result(content=
518
+ EventContentCreator.ResultCommandPrepareStatContent(
519
+ command=command,
520
+ parameters=parameters
521
+ ).to_dict()))
474
522
 
475
- # 打印正在执行的命令
476
- self.printer.print_in_terminal(
477
- "auto_command_executing",
478
- style="blue",
479
- command=command
480
- )
481
-
482
- self.execute_auto_command(command, parameters)
523
+ self.execute_auto_command(command, parameters)
483
524
  content = ""
484
525
  last_result = result_manager.get_last()
485
526
  if last_result:
486
- action = last_result.meta["action"]
487
- if action == "coding":
527
+ action = last_result.meta["action"]
528
+ if action == "coding":
488
529
  # 如果上一步是 coding,则需要把上一步的更改前和更改后的内容作为上下文
489
- changes = git_utils.get_changes_by_commit_message("", last_result.meta["commit_message"])
530
+ changes = git_utils.get_changes_by_commit_message(
531
+ "", last_result.meta["commit_message"])
490
532
  if changes.success:
491
533
  for file_path, change in changes.changes.items():
492
534
  if change:
493
535
  content += f"## File: {file_path}[更改前]\n{change.before or 'New File'}\n\nFile: {file_path}\n\n[更改后]\n{change.after or 'Deleted File'}\n\n"
494
536
  else:
495
- content = printer.get_message_from_key_with_format("no_changes_made")
537
+ content = printer.get_message_from_key("no_changes_made")
496
538
  else:
497
539
  # 其他的直接获取执行结果
498
540
  content = last_result.content
499
541
 
500
542
  if action != command:
501
543
  # command 和 action 不一致,则认为命令执行失败,退出
502
- printer.print_in_terminal("auto_command_action_break", style="yellow", command=command, action=action)
544
+ temp_content = printer.get_message_from_key_with_format(
545
+ "auto_command_action_break", command=command, action=action)
546
+ printer.print_str_in_terminal(temp_content,style="yellow")
547
+ get_event_manager(self.args.event_file).write_result(
548
+ EventContentCreator.create_result(content=temp_content))
549
+ break
550
+
551
+ if command == "response_user":
503
552
  break
504
553
 
554
+ get_event_manager(self.args.event_file).write_result(
555
+ EventContentCreator.create_result(content=EventContentCreator.ResultCommandExecuteStatContent(
556
+ command=command,
557
+ content=content
558
+ ).to_dict()))
559
+
505
560
  # 打印执行结果
506
561
  console = Console()
507
562
  # 截取content前后200字符
508
- truncated_content = content[:200] + "\n...\n" + content[-200:] if len(content) > 400 else content
563
+ truncated_content = content[:200] + "\n...\n" + \
564
+ content[-200:] if len(content) > 400 else content
509
565
  title = printer.get_message_from_key_with_format(
510
- "command_execution_result",
566
+ "command_execution_result",
511
567
  action=action
512
568
  )
513
- # 转义内容,避免Rich将内容中的[]解释为markup语法
569
+ # 转义内容,避免Rich将内容中的[]解释为markup语法
514
570
  text_content = Text(truncated_content)
515
571
  console.print(Panel(
516
572
  text_content,
@@ -518,88 +574,111 @@ class CommandAutoTuner:
518
574
  border_style="blue",
519
575
  padding=(1, 2)
520
576
  ))
521
- # 保持原content不变,继续后续处理
522
577
 
523
578
  # 添加新的对话内容
524
579
  new_content = self._execute_command_result.prompt(content)
525
580
  conversations.append({"role": "user", "content": new_content})
526
-
527
- # 统计 token 数量
528
- total_tokens = count_tokens(json.dumps(conversations,ensure_ascii=False))
581
+
582
+ # 统计 token 数量
583
+ total_tokens = count_tokens(json.dumps(
584
+ conversations, ensure_ascii=False))
529
585
 
530
586
  # 如果对话过长,使用默认策略进行修剪
531
587
  if total_tokens > self.args.conversation_prune_safe_zone_tokens:
532
588
  self.printer.print_in_terminal(
533
- "conversation_pruning_start",
589
+ "conversation_pruning_start",
534
590
  style="yellow",
535
591
  total_tokens=total_tokens,
536
592
  safe_zone=self.args.conversation_prune_safe_zone_tokens
537
593
  )
538
594
  from autocoder.common.conversation_pruner import ConversationPruner
539
595
  pruner = ConversationPruner(self.args, self.llm)
540
- conversations = pruner.prune_conversations(conversations)
596
+ conversations = pruner.prune_conversations(conversations)
541
597
 
542
598
  title = printer.get_message_from_key("auto_command_analyzing")
543
599
  model_name = ",".join(llms_utils.get_llm_names(self.llm))
544
-
600
+
545
601
  start_time = time.monotonic()
546
602
  result, last_meta = stream_out(
547
- self.llm.stream_chat_oai(conversations=conversations, delta_mode=True),
603
+ self.llm.stream_chat_oai(
604
+ conversations=conversations, delta_mode=True),
548
605
  model_name=model_name,
549
606
  title=title,
550
607
  final_title=final_title,
551
- display_func= extract_command_response
552
- )
553
-
608
+ display_func=extract_command_response,
609
+ args=self.args,
610
+ extra_meta={
611
+ "stream_out_type": AutoCommandStreamOutType.COMMAND_SUGGESTION.value
612
+ }
613
+ )
614
+
554
615
  if last_meta:
555
616
  elapsed_time = time.monotonic() - start_time
556
617
  printer = Printer()
557
618
  speed = last_meta.generated_tokens_count / elapsed_time
558
-
619
+
559
620
  # Get model info for pricing
560
621
  from autocoder.utils import llms as llm_utils
561
- model_info = llm_utils.get_model_info(model_name, self.args.product_mode) or {}
562
- input_price = model_info.get("input_price", 0.0) if model_info else 0.0
563
- output_price = model_info.get("output_price", 0.0) if model_info else 0.0
564
-
622
+ model_info = llm_utils.get_model_info(
623
+ model_name, self.args.product_mode) or {}
624
+ input_price = model_info.get(
625
+ "input_price", 0.0) if model_info else 0.0
626
+ output_price = model_info.get(
627
+ "output_price", 0.0) if model_info else 0.0
628
+
565
629
  # Calculate costs
566
- input_cost = (last_meta.input_tokens_count * input_price) / 1000000 # Convert to millions
567
- output_cost = (last_meta.generated_tokens_count * output_price) / 1000000 # Convert to millions
568
-
569
- printer.print_in_terminal("stream_out_stats",
570
- model_name=model_name,
571
- elapsed_time=elapsed_time,
572
- first_token_time=last_meta.first_token_time,
573
- input_tokens=last_meta.input_tokens_count,
574
- output_tokens=last_meta.generated_tokens_count,
575
- input_cost=round(input_cost, 4),
576
- output_cost=round(output_cost, 4),
577
- speed=round(speed, 2))
578
-
579
- conversations.append({"role": "assistant", "content": result})
580
- # 提取 JSON 并转换为 AutoCommandResponse
581
- response = to_model(result, AutoCommandResponse)
582
- if not response or not response.suggestions:
583
- break
584
- # 保存对话记录
630
+ input_cost = (last_meta.input_tokens_count *
631
+ input_price) / 1000000 # Convert to millions
632
+ # Convert to millions
633
+ output_cost = (
634
+ last_meta.generated_tokens_count * output_price) / 1000000
635
+
636
+ temp_content = printer.get_message_from_key_with_format("stream_out_stats",
637
+ model_name=model_name,
638
+ elapsed_time=elapsed_time,
639
+ first_token_time=last_meta.first_token_time,
640
+ input_tokens=last_meta.input_tokens_count,
641
+ output_tokens=last_meta.generated_tokens_count,
642
+ input_cost=round(input_cost, 4),
643
+ output_cost=round(
644
+ output_cost, 4),
645
+ speed=round(speed, 2))
646
+ printer.print_str_in_terminal(temp_content)
647
+ get_event_manager(self.args.event_file).write_result(
648
+ EventContentCreator.create_result(content=EventContentCreator.ResultTokenStatContent(
649
+ model_name=model_name,
650
+ elapsed_time=elapsed_time,
651
+ first_token_time=last_meta.first_token_time,
652
+ input_tokens=last_meta.input_tokens_count,
653
+ output_tokens=last_meta.generated_tokens_count,
654
+ ).to_dict()))
655
+
656
+ conversations.append({"role": "assistant", "content": result})
657
+ # 提取 JSON 并转换为 AutoCommandResponse
658
+ response = to_model(result, AutoCommandResponse)
659
+ if not response or not response.suggestions:
660
+ break
661
+
585
662
  save_to_memory_file(
586
663
  query=request.user_input,
587
664
  response=response.model_dump_json(indent=2)
588
- )
665
+ )
666
+ else:
667
+ temp_content = printer.get_message_from_key_with_format("auto_command_break", command=command)
668
+ printer.print_str_in_terminal(temp_content,style="yellow")
669
+ get_event_manager(self.args.event_file).write_result(
670
+ EventContentCreator.create_result(content=temp_content))
671
+ break
672
+
673
+ return response
589
674
 
590
- else:
591
- self.printer.print_in_terminal("auto_command_break", style="yellow", command=command)
592
- break
593
-
594
- return response
595
-
596
675
  @byzerllm.prompt()
597
676
  def _command_readme(self) -> str:
598
677
  '''
599
678
  你有如下函数可供使用:
600
-
679
+
601
680
  <commands>
602
-
681
+
603
682
  <command>
604
683
  <name>add_files</name>
605
684
  <description>
@@ -713,7 +792,7 @@ class CommandAutoTuner:
713
792
  - 如果没有可撤销的操作会提示错误
714
793
  </usage>
715
794
  </command>
716
-
795
+
717
796
  <command>
718
797
  <name>help</name>
719
798
  <description>
@@ -731,7 +810,7 @@ class CommandAutoTuner:
731
810
  help(query="")
732
811
 
733
812
  ## 帮助用户执行特定的配置
734
-
813
+
735
814
  help(query="关闭索引")
736
815
 
737
816
  这条命令会触发:
@@ -743,7 +822,7 @@ class CommandAutoTuner:
743
822
  常见的一些配置选项示例:
744
823
 
745
824
  {{ config_readme }}
746
-
825
+
747
826
  比如你想开启索引,则可以执行:
748
827
 
749
828
  help(query="开启索引")
@@ -754,7 +833,7 @@ class CommandAutoTuner:
754
833
 
755
834
  </usage>
756
835
  </command>
757
-
836
+
758
837
  <command>
759
838
  <name>chat</name>
760
839
  <description>进入聊天模式,与AI进行交互对话。支持多轮对话和上下文理解。</description>
@@ -817,7 +896,7 @@ class CommandAutoTuner:
817
896
  使用例子:
818
897
 
819
898
  coding(query="创建一个处理用户登录的函数")
820
-
899
+
821
900
 
822
901
  ## 和 chat 搭配使用
823
902
  当你用过 chat 之后,继续使用 coding 时,可以添加 /apply 来带上 chat 的对话内容。
@@ -858,7 +937,7 @@ class CommandAutoTuner:
858
937
  使用例子:
859
938
 
860
939
  lib(args=["/add", "byzer-llm"])
861
-
940
+
862
941
 
863
942
  ## 移除库
864
943
  使用 /remove 移除库
@@ -900,7 +979,7 @@ class CommandAutoTuner:
900
979
  <description>模型控制面板命令,用于管理和控制AI模型。</description>
901
980
  <usage>
902
981
  该命令用于管理和控制AI模型的配置和运行。 包含一个参数:query,字符串类型。
903
-
982
+
904
983
  ## 罗列模型模板
905
984
 
906
985
  models(query="/list")
@@ -911,11 +990,11 @@ class CommandAutoTuner:
911
990
  ##添加模型模板
912
991
 
913
992
  比如我想添加 open router 或者硅基流动的模型,则可以通过如下方式:
914
-
993
+
915
994
  models(query="/add_model name=openrouter-sonnet-3.5 base_url=https://openrouter.ai/api/v1")
916
-
995
+
917
996
  这样就能添加自定义模型: openrouter-sonnet-3.5
918
-
997
+
919
998
 
920
999
  如果你想添加添加硅基流动deepseek 模型的方式为:
921
1000
 
@@ -966,7 +1045,7 @@ class CommandAutoTuner:
966
1045
  models(query="/add_model name=tencent_v3_chat base_url=https://tencent.ai.qq.com/v1 model_name=deepseek-v3")
967
1046
 
968
1047
  *** 特别注意 ***
969
-
1048
+
970
1049
  在使用本函数时,如果添加的模型用户在需求中没有提供像推理点名称,激活时的 api key,以及模型名称等,从而导致添加模型会发生不确定性,
971
1050
  你务必需要先通过函数 ask_user 来获取,之后得到完整信息再来执行 models 相关的操作。
972
1051
 
@@ -976,8 +1055,8 @@ class CommandAutoTuner:
976
1055
  models(query="/add_model name=ark_r1_chat base_url=https://ark.cn-beijing.volces.com/api/v3 model_name=<收集到的推理点名称> is_reasoning=true")
977
1056
 
978
1057
  models(query="/activate ark_r1_chat <收集到的API key>")
979
-
980
-
1058
+
1059
+
981
1060
  </usage>
982
1061
  </command>
983
1062
 
@@ -991,10 +1070,10 @@ class CommandAutoTuner:
991
1070
  </description>
992
1071
  <usage>
993
1072
  该命令接受一个参数 question,为需要向用户询问的问题字符串。
994
-
1073
+
995
1074
  使用例子:
996
1075
  ask_user(question="请输入火山引擎的 R1 模型推理点")
997
-
1076
+
998
1077
  </command>
999
1078
 
1000
1079
  <command>
@@ -1002,11 +1081,11 @@ class CommandAutoTuner:
1002
1081
  <description>运行指定的Python代码。主要用于执行一些Python脚本或测试代码。</description>
1003
1082
  <usage>
1004
1083
  该命令接受一个参数 code,为要执行的Python代码字符串。
1005
-
1084
+
1006
1085
  使用例子:
1007
-
1086
+
1008
1087
  run_python(code="print('Hello World')")
1009
-
1088
+
1010
1089
  注意:
1011
1090
  - 代码将在项目根目录下执行
1012
1091
  - 可以访问项目中的所有文件
@@ -1019,12 +1098,12 @@ class CommandAutoTuner:
1019
1098
  <description>运行指定的Shell脚本。主要用于编译、运行、测试等任务。</description>
1020
1099
  <usage>
1021
1100
  该命令接受一个参数 command,为要执行的Shell脚本字符串。
1022
-
1023
-
1101
+
1102
+
1024
1103
  使用例子:
1025
-
1104
+
1026
1105
  execute_shell_command(command="ls -l")
1027
-
1106
+
1028
1107
  注意:
1029
1108
  - 脚本将在项目根目录下执行
1030
1109
  - 禁止执行包含 rm 命令的脚本
@@ -1051,14 +1130,14 @@ class CommandAutoTuner:
1051
1130
  <description>返回当前项目结构</description>
1052
1131
  <usage>
1053
1132
  该命令不需要参数。返回一个目录树结构(类似 tree 命令的输出)
1054
-
1133
+
1055
1134
  使用例子:
1056
-
1135
+
1057
1136
  get_project_structure()
1058
-
1137
+
1059
1138
  该函数特别适合你通过目录结构来了解这个项目是什么类型的项目,有什么文件,如果你对一些文件
1060
1139
  感兴趣,可以配合 read_files 函数来读取文件内容,从而帮你做更好的决策
1061
-
1140
+
1062
1141
  </usage>
1063
1142
  </command>
1064
1143
 
@@ -1068,19 +1147,19 @@ class CommandAutoTuner:
1068
1147
  <usage>
1069
1148
  该命令接受一个参数 file_paths,路径list,或者是以逗号分割的多个文件路径。
1070
1149
  路径支持相对路径和绝对路径。
1071
-
1150
+
1072
1151
  使用例子:
1073
-
1152
+
1074
1153
  get_project_map(file_paths=["full/path/to/main.py","partial/path/to/utils.py"]),
1075
-
1154
+
1076
1155
  或者:
1077
-
1156
+
1078
1157
  get_project_map(file_paths="full/path/to/main.py,partial/path/to/utils.py")
1079
1158
 
1080
1159
  该函数特别适合你想要了解某个文件的用途,以及该文件的导入的包,定义的类,函数,变量等信息。
1081
1160
  同时,你还能看到文件的大小(tokens数),以及索引的大小(tokens数),以及构建索引花费费用等信息。
1082
1161
  如果你觉得该文件确实是你关注的,你可以通过 read_files 函数来读取文件完整内容,从而帮你做更好的决策。
1083
-
1162
+
1084
1163
  注意:
1085
1164
  - 返回值为JSON格式文本
1086
1165
  - 只能返回已被索引的文件
@@ -1125,9 +1204,9 @@ class CommandAutoTuner:
1125
1204
 
1126
1205
  你可以使用 get_project_structure 函数获取项目结构后,然后再通过 get_project_map 函数获取某个文件的用途,符号列表,以及
1127
1206
  文件大小(tokens数),最后再通过 read_files 函数来读取文件内容,从而帮你做更好的决策。如果需要读取的文件过大,
1128
-
1207
+
1129
1208
  特别注意:使用 read_files 时,一次性读取文件数量不要超过1个,每次只读取200行。如果发现读取的内容不够,则继续读取下面200行。
1130
-
1209
+
1131
1210
  </usage>
1132
1211
  </command>
1133
1212
 
@@ -1136,11 +1215,11 @@ class CommandAutoTuner:
1136
1215
  <description>根据文件名中的关键字搜索文件。</description>
1137
1216
  <usage>
1138
1217
  该命令接受一个参数 keyword,为要搜索的关键字字符串。
1139
-
1218
+
1140
1219
  使用例子:
1141
-
1220
+
1142
1221
  find_files_by_name(keyword="test")
1143
-
1222
+
1144
1223
  注意:
1145
1224
  - 搜索不区分大小写
1146
1225
  - 返回所有匹配的文件路径,逗号分隔
@@ -1152,11 +1231,11 @@ class CommandAutoTuner:
1152
1231
  <description>根据文件内容中的关键字搜索文件。</description>
1153
1232
  <usage>
1154
1233
  该命令接受一个参数 keyword,为要搜索的关键字字符串。
1155
-
1234
+
1156
1235
  使用例子:
1157
-
1236
+
1158
1237
  find_files_by_content(keyword="TODO")
1159
-
1238
+
1160
1239
  注意:
1161
1240
  - 搜索不区分大小写
1162
1241
  - 如果结果过多,只返回前10个匹配项
@@ -1181,7 +1260,7 @@ class CommandAutoTuner:
1181
1260
  ```
1182
1261
  ##File: /path/to/file.py
1183
1262
  ##Line: 10-20
1184
-
1263
+
1185
1264
  内容
1186
1265
  ```
1187
1266
 
@@ -1199,10 +1278,10 @@ class CommandAutoTuner:
1199
1278
  <description>配置管理命令,用于管理和控制配置。</description>
1200
1279
  <usage>
1201
1280
  该命令导出当前软件的配置,并保存到指定路径。
1202
-
1281
+
1203
1282
  使用例子:
1204
1283
  conf_export(path="导出路径,通常是.json文件")
1205
-
1284
+
1206
1285
  </usage>
1207
1286
  </command>
1208
1287
 
@@ -1211,10 +1290,10 @@ class CommandAutoTuner:
1211
1290
  <description>配置管理命令,用于管理和控制配置。</description>
1212
1291
  <usage>
1213
1292
  该命令导入指定路径的配置文件到当前软件。
1214
-
1293
+
1215
1294
  使用例子:
1216
1295
  conf_import(path="导入路径,通常是.json文件")
1217
-
1296
+
1218
1297
  </usage>
1219
1298
  </command>
1220
1299
 
@@ -1223,10 +1302,10 @@ class CommandAutoTuner:
1223
1302
  <description>索引管理命令,用于管理和控制索引。</description>
1224
1303
  <usage>
1225
1304
  该命令导出当前软件的索引,并保存到指定路径。
1226
-
1305
+
1227
1306
  使用例子:
1228
1307
  index_export(path="导出路径,通常是.json文件")
1229
-
1308
+
1230
1309
  </usage>
1231
1310
  </command>
1232
1311
 
@@ -1235,10 +1314,10 @@ class CommandAutoTuner:
1235
1314
  <description>索引管理命令,用于管理和控制索引。</description>
1236
1315
  <usage>
1237
1316
  该命令导入指定路径的索引文件到当前软件。
1238
-
1317
+
1239
1318
  使用例子:
1240
1319
  index_import(path="导入路径,通常最后是.json文件")
1241
-
1320
+
1242
1321
  </usage>
1243
1322
  </command>
1244
1323
 
@@ -1247,11 +1326,11 @@ class CommandAutoTuner:
1247
1326
  <description>排除指定文件。</description>
1248
1327
  <usage>
1249
1328
  该命令接受一个参数 query, 为要排除的文件模式字符串,多个文件模式用逗号分隔。
1250
-
1329
+
1251
1330
  使用例子,比如你想要排除 package-lock.json 文件,你可以这样调用:
1252
-
1331
+
1253
1332
  exclude_files(query="regex://.*/package-lock\.json")
1254
-
1333
+
1255
1334
  注意:
1256
1335
  - 文件模式字符串必须以 regex:// 开头
1257
1336
  - regex:// 后面部分是标准的正则表达式
@@ -1284,7 +1363,7 @@ class CommandAutoTuner:
1284
1363
  <description>响应用户。</description>
1285
1364
  <usage>
1286
1365
  如果你需要直接发送信息给用户,那么可以通过 response_user 函数来直接回复用户。
1287
-
1366
+
1288
1367
  比如用户问你是谁?
1289
1368
  你可以通过如下方式来回答:
1290
1369
  response_user(response="你好,我是 auto-coder")
@@ -1299,10 +1378,10 @@ class CommandAutoTuner:
1299
1378
 
1300
1379
  使用例子:
1301
1380
  count_file_tokens(file_path="full")
1302
-
1381
+
1303
1382
  注意:
1304
1383
  - 返回值为int类型,表示文件的token数量。
1305
-
1384
+
1306
1385
  </usage>
1307
1386
  </command>
1308
1387
 
@@ -1314,10 +1393,10 @@ class CommandAutoTuner:
1314
1393
 
1315
1394
  使用例子:
1316
1395
  count_string_tokens(text="你好,世界")
1317
-
1396
+
1318
1397
  注意:
1319
1398
  - 返回值为int类型,表示文本的token数量。
1320
-
1399
+
1321
1400
  </usage>
1322
1401
  </command>
1323
1402
 
@@ -1330,15 +1409,15 @@ class CommandAutoTuner:
1330
1409
  使用例子:
1331
1410
  find_symbol_definition(symbol="MyClass")
1332
1411
  find_symbol_definition(symbol="process_data")
1333
-
1412
+
1334
1413
  注意:
1335
1414
  - 返回值为字符串,包含符号定义所在的文件路径列表,以逗号分隔
1336
1415
  - 支持精确匹配和模糊匹配(不区分大小写)
1337
1416
  - 如果未找到匹配项,会返回提示信息
1338
-
1417
+
1339
1418
  </usage>
1340
1419
  </command>
1341
-
1420
+
1342
1421
  <command>
1343
1422
  <n>execute_mcp_server</n>
1344
1423
  <description>执行MCP服务器</description>
@@ -1351,13 +1430,13 @@ class CommandAutoTuner:
1351
1430
  <mcp_server_info>
1352
1431
  {{ mcp_server_info }}
1353
1432
  </mcp_server_info>
1354
-
1433
+
1355
1434
  </usage>
1356
1435
  </command>
1357
1436
  '''
1358
1437
  return {
1359
1438
  "config_readme": config_readme.prompt(),
1360
- "mcp_server_info": self.mcp_server_info
1439
+ "mcp_server_info": self.mcp_server_info
1361
1440
  }
1362
1441
 
1363
1442
  def execute_auto_command(self, command: str, parameters: Dict[str, Any]) -> None:
@@ -1367,7 +1446,7 @@ class CommandAutoTuner:
1367
1446
  command_map = {
1368
1447
  "add_files": self.command_config.add_files,
1369
1448
  "remove_files": self.command_config.remove_files,
1370
- "list_files": self.command_config.list_files,
1449
+ "list_files": self.command_config.list_files,
1371
1450
  "revert": self.command_config.revert,
1372
1451
  "commit": self.command_config.commit,
1373
1452
  "help": self.command_config.help,
@@ -1387,16 +1466,16 @@ class CommandAutoTuner:
1387
1466
  "index_import": self.command_config.index_import,
1388
1467
  "exclude_files": self.command_config.exclude_files,
1389
1468
 
1390
- "run_python": self.tools.run_python_code,
1469
+ "run_python": self.tools.run_python_code,
1391
1470
  "get_related_files_by_symbols": self.tools.get_related_files_by_symbols,
1392
1471
  "get_project_map": self.tools.get_project_map,
1393
1472
  "get_project_structure": self.tools.get_project_structure,
1394
1473
  "list_files": self.tools.list_files,
1395
1474
  "read_files": self.tools.read_files,
1396
1475
  "find_files_by_name": self.tools.find_files_by_name,
1397
- "find_files_by_content": self.tools.find_files_by_content,
1476
+ "find_files_by_content": self.tools.find_files_by_content,
1398
1477
  "get_project_related_files": self.tools.get_project_related_files,
1399
- "ask_user":self.tools.ask_user,
1478
+ "ask_user": self.tools.ask_user,
1400
1479
  "read_file_with_keyword_ranges": self.tools.read_file_with_keyword_ranges,
1401
1480
  "get_project_type": self.project_type_analyzer.analyze,
1402
1481
  "response_user": self.tools.response_user,
@@ -1404,7 +1483,7 @@ class CommandAutoTuner:
1404
1483
  "count_file_tokens": self.tools.count_file_tokens,
1405
1484
  "count_string_tokens": self.tools.count_string_tokens,
1406
1485
  "find_symbol_definition": self.tools.find_symbol_definition,
1407
-
1486
+
1408
1487
  }
1409
1488
 
1410
1489
  if command not in command_map:
@@ -1417,7 +1496,7 @@ class CommandAutoTuner:
1417
1496
  if parameters:
1418
1497
  command_map[command](**parameters)
1419
1498
  else:
1420
- command_map[command]()
1499
+ command_map[command]()
1421
1500
 
1422
1501
  except Exception as e:
1423
1502
  error_msg = str(e)