auto-coder 0.1.263__py3-none-any.whl → 0.1.265__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 (58) hide show
  1. {auto_coder-0.1.263.dist-info → auto_coder-0.1.265.dist-info}/METADATA +1 -1
  2. {auto_coder-0.1.263.dist-info → auto_coder-0.1.265.dist-info}/RECORD +58 -55
  3. autocoder/agent/planner.py +4 -4
  4. autocoder/auto_coder.py +26 -21
  5. autocoder/auto_coder_server.py +7 -7
  6. autocoder/chat_auto_coder.py +203 -98
  7. autocoder/commands/auto_command.py +81 -4
  8. autocoder/commands/tools.py +48 -50
  9. autocoder/common/__init__.py +6 -1
  10. autocoder/common/auto_coder_lang.py +41 -3
  11. autocoder/common/code_auto_generate.py +3 -3
  12. autocoder/common/code_auto_generate_diff.py +12 -15
  13. autocoder/common/code_auto_generate_editblock.py +3 -3
  14. autocoder/common/code_auto_generate_strict_diff.py +3 -3
  15. autocoder/common/code_auto_merge.py +23 -3
  16. autocoder/common/code_auto_merge_diff.py +29 -4
  17. autocoder/common/code_auto_merge_editblock.py +25 -5
  18. autocoder/common/code_auto_merge_strict_diff.py +26 -6
  19. autocoder/common/code_modification_ranker.py +65 -3
  20. autocoder/common/command_completer.py +3 -0
  21. autocoder/common/command_generator.py +24 -8
  22. autocoder/common/command_templates.py +2 -2
  23. autocoder/common/conf_import_export.py +105 -0
  24. autocoder/common/conf_validator.py +7 -1
  25. autocoder/common/context_pruner.py +305 -0
  26. autocoder/common/files.py +41 -2
  27. autocoder/common/image_to_page.py +11 -11
  28. autocoder/common/index_import_export.py +38 -18
  29. autocoder/common/mcp_hub.py +3 -3
  30. autocoder/common/mcp_server.py +2 -2
  31. autocoder/common/shells.py +254 -13
  32. autocoder/common/stats_panel.py +126 -0
  33. autocoder/dispacher/actions/action.py +6 -18
  34. autocoder/dispacher/actions/copilot.py +2 -2
  35. autocoder/dispacher/actions/plugins/action_regex_project.py +1 -3
  36. autocoder/dispacher/actions/plugins/action_translate.py +1 -1
  37. autocoder/index/entry.py +8 -2
  38. autocoder/index/filter/normal_filter.py +13 -2
  39. autocoder/index/filter/quick_filter.py +127 -13
  40. autocoder/index/index.py +8 -7
  41. autocoder/models.py +2 -2
  42. autocoder/pyproject/__init__.py +5 -5
  43. autocoder/rag/cache/byzer_storage_cache.py +4 -4
  44. autocoder/rag/cache/file_monitor_cache.py +2 -2
  45. autocoder/rag/cache/simple_cache.py +4 -4
  46. autocoder/rag/long_context_rag.py +2 -2
  47. autocoder/regexproject/__init__.py +3 -2
  48. autocoder/suffixproject/__init__.py +3 -2
  49. autocoder/tsproject/__init__.py +3 -2
  50. autocoder/utils/conversation_store.py +1 -1
  51. autocoder/utils/operate_config_api.py +3 -3
  52. autocoder/utils/project_structure.py +258 -3
  53. autocoder/utils/thread_utils.py +6 -1
  54. autocoder/version.py +1 -1
  55. {auto_coder-0.1.263.dist-info → auto_coder-0.1.265.dist-info}/LICENSE +0 -0
  56. {auto_coder-0.1.263.dist-info → auto_coder-0.1.265.dist-info}/WHEEL +0 -0
  57. {auto_coder-0.1.263.dist-info → auto_coder-0.1.265.dist-info}/entry_points.txt +0 -0
  58. {auto_coder-0.1.263.dist-info → auto_coder-0.1.265.dist-info}/top_level.txt +0 -0
@@ -10,6 +10,7 @@ import hashlib
10
10
  from pathlib import Path
11
11
  from autocoder.common.types import CodeGenerateResult, MergeCodeWithoutEffect
12
12
  from autocoder.common.code_modification_ranker import CodeModificationRanker
13
+ from autocoder.common import files as FileUtils
13
14
 
14
15
  class PathAndCode(pydantic.BaseModel):
15
16
  path: str
@@ -138,15 +139,35 @@ class CodeAutoMergeStrictDiff:
138
139
  def choose_best_choice(self, generate_result: CodeGenerateResult) -> CodeGenerateResult:
139
140
  if len(generate_result.contents) == 1:
140
141
  return generate_result
142
+
143
+ merge_results = []
144
+ for content,conversations in zip(generate_result.contents,generate_result.conversations):
145
+ merge_result = self._merge_code_without_effect(content)
146
+ merge_results.append(merge_result)
147
+
148
+ # If all merge results are None, return first one
149
+ if all(len(result.failed_blocks) != 0 for result in merge_results):
150
+ self.printer.print_in_terminal("all_merge_results_failed")
151
+ return CodeGenerateResult(contents=[generate_result.contents[0]], conversations=[generate_result.conversations[0]])
152
+
153
+ # If only one merge result is not None, return that one
154
+ not_none_indices = [i for i, result in enumerate(merge_results) if len(result.failed_blocks) == 0]
155
+ if len(not_none_indices) == 1:
156
+ idx = not_none_indices[0]
157
+ self.printer.print_in_terminal("only_one_merge_result_success")
158
+ return CodeGenerateResult(contents=[generate_result.contents[idx]], conversations=[generate_result.conversations[idx]])
141
159
 
160
+ # 最后,如果有多个,那么根据质量排序再返回
142
161
  ranker = CodeModificationRanker(self.llm, self.args)
143
- ranked_result = ranker.rank_modifications(generate_result)
144
- # Filter out contents with failed blocks
162
+ ranked_result = ranker.rank_modifications(generate_result,merge_results)
163
+
164
+ ## 得到的结果,再做一次合并,第一个通过的返回 , 返回做合并有点重复低效,未来修改。
145
165
  for content,conversations in zip(ranked_result.contents,ranked_result.conversations):
146
166
  merge_result = self._merge_code_without_effect(content)
147
167
  if not merge_result.failed_blocks:
148
168
  return CodeGenerateResult(contents=[content], conversations=[conversations])
149
- # If all have failed blocks, return the first one
169
+
170
+ # 最后保底,但实际不会出现
150
171
  return CodeGenerateResult(contents=[ranked_result.contents[0]], conversations=[ranked_result.conversations[0]])
151
172
 
152
173
 
@@ -175,8 +196,7 @@ class CodeAutoMergeStrictDiff:
175
196
  continue
176
197
 
177
198
  if full_path not in file_content_mapping:
178
- with open(full_path, "r") as f:
179
- file_content_mapping[full_path] = f.read()
199
+ file_content_mapping[full_path] = FileUtils.read_file(full_path)
180
200
 
181
201
  try:
182
202
  import patch
@@ -204,7 +224,7 @@ class CodeAutoMergeStrictDiff:
204
224
  def _merge_code(self, content: str, force_skip_git: bool = False):
205
225
  total = 0
206
226
 
207
- file_content = open(self.args.file).read()
227
+ file_content = FileUtils.read_file(self.args.file)
208
228
  md5 = hashlib.md5(file_content.encode('utf-8')).hexdigest()
209
229
  # get the file name
210
230
  file_name = os.path.basename(self.args.file)
@@ -9,6 +9,8 @@ import traceback
9
9
  from autocoder.common.utils_code_auto_generate import chat_with_continue
10
10
  from byzerllm.utils.str2model import to_model
11
11
  from autocoder.utils.llms import get_llm_names, get_model_info
12
+ from autocoder.common.types import CodeGenerateResult, MergeCodeWithoutEffect
13
+ import os
12
14
 
13
15
  class RankResult(BaseModel):
14
16
  rank_result: List[int]
@@ -51,12 +53,67 @@ class CodeModificationRanker:
51
53
  }
52
54
  ```
53
55
 
54
- 注意:
56
+ 注意:
55
57
  1. id 为 edit_block 的 id,按质量从高到低排序,并且 id 必须是数字
56
58
  2. 只输出前面要求的 Json 格式就好,不要输出其他内容,Json 需要使用 ```json ```包裹
57
59
  '''
58
60
 
59
- def rank_modifications(self, generate_result: CodeGenerateResult) -> CodeGenerateResult:
61
+ @byzerllm.prompt()
62
+ def _rank_modifications_with_merge_result(self, s: CodeGenerateResult,merge_results: List[MergeCodeWithoutEffect]) -> str:
63
+ '''
64
+ 对一组代码修改进行质量评估并排序。
65
+
66
+ 下面是修改需求:
67
+
68
+ <edit_requirement>
69
+ {{ s.conversations[0][-2]["content"] }}
70
+ </edit_requirement>
71
+
72
+ 下面是相应的代码修改,如果Before 为空,那么表示是新增文件,如果After 为空,那么表示是删除文件,如果Before 和 After 都不为空,那么表示是修改文件:
73
+ {% for change in changes %}
74
+ <edit_file id="{{ loop.index0 }}">
75
+ {{change}}
76
+ </edit_file>
77
+ {% endfor %}
78
+
79
+ 请输出如下格式的评估结果,只包含 JSON 数据:
80
+
81
+ ```json
82
+ {
83
+ "rank_result": [id1, id2, id3]
84
+ }
85
+ ```
86
+
87
+ 注意:
88
+ 1. 像python的缩进,前端诸如 reacjs,vue 的标签闭合匹配,这些很重要,需要在排序中作为重点考虑对象之一。
89
+ 1. id 为 edit_file 的 id,按质量从高到低排序,并且 id 必须是数字
90
+ 2. 只输出前面要求的 Json 格式就好,不要输出其他内容,Json 需要使用 ```json ```包裹
91
+ '''
92
+ changes = []
93
+ for merge_result in merge_results:
94
+ s = ""
95
+ for block in merge_result.success_blocks:
96
+ file_path,content = block
97
+ s += f"##File: {file_path}\n\n"
98
+ if not os.path.exists(file_path):
99
+ s += f"##Before: \n\n"
100
+ s += f"##File: {file_path}\n\n"
101
+ s += f"##After: \n\n"
102
+ s += content
103
+ else:
104
+ with open(file_path, "r",encoding="utf-8") as f:
105
+ original_content = f.read()
106
+ s += f"##Before: \n\n"
107
+ s += original_content
108
+ s += f"##File: {file_path}\n\n"
109
+ s += f"##After: \n\n"
110
+ s += content
111
+ changes.append(s)
112
+ return {
113
+ "changes": changes
114
+ }
115
+
116
+ def rank_modifications(self, generate_result: CodeGenerateResult, merge_result: List[MergeCodeWithoutEffect]) -> CodeGenerateResult:
60
117
  import time
61
118
  from collections import defaultdict
62
119
 
@@ -69,8 +126,13 @@ class CodeModificationRanker:
69
126
 
70
127
  rank_times = self.args.rank_times_same_model
71
128
  total_tasks = len(self.llms) * rank_times
129
+ if self.args.rank_strategy == "block":
130
+ query = self._rank_modifications.prompt(generate_result)
131
+ elif self.args.rank_strategy == "file":
132
+ query = self._rank_modifications_with_merge_result.prompt(generate_result, merge_result)
133
+ else:
134
+ raise Exception(f"Invalid rank strategy: {self.args.rank_strategy}")
72
135
 
73
- query = self._rank_modifications.prompt(generate_result)
74
136
  input_tokens_count = 0
75
137
  generated_tokens_count = 0
76
138
  try:
@@ -43,6 +43,9 @@ COMMANDS = {
43
43
  "/output_price": "",
44
44
  },
45
45
  "/auto": {
46
+ },
47
+ "/shell": {
48
+ "/chat": "",
46
49
  }
47
50
  }
48
51
 
@@ -4,6 +4,7 @@ from autocoder.utils.auto_coder_utils.chat_stream_out import stream_out
4
4
  from autocoder.common import detect_env
5
5
  from autocoder.common import shells
6
6
  from autocoder.common.printer import Printer
7
+ from typing import Dict,Union
7
8
 
8
9
  @byzerllm.prompt()
9
10
  def _generate_shell_script(user_input: str) -> str:
@@ -14,6 +15,11 @@ def _generate_shell_script(user_input: str) -> str:
14
15
  Python版本: {{ env_info.python_version }}
15
16
  终端类型: {{ env_info.shell_type }}
16
17
  终端编码: {{ env_info.shell_encoding }}
18
+
19
+ {%- if shell_type %}
20
+ 脚本类型:{{ shell_type }}
21
+ {%- endif %}
22
+
17
23
  {%- if env_info.conda_env %}
18
24
  Conda环境: {{ env_info.conda_env }}
19
25
  {%- endif %}
@@ -21,29 +27,39 @@ def _generate_shell_script(user_input: str) -> str:
21
27
  虚拟环境: {{ env_info.virtualenv }}
22
28
  {%- endif %}
23
29
 
24
- 根据用户的输入以及当前的操作系统和Shell类型生成合适的 shell 脚本,注意只能生成一个shell脚本,不要生成多个。
30
+ 根据用户的输入以及当前的操作系统和终端类型以及脚本类型生成脚本,
31
+ 注意只能生成一个shell脚本,不要生成多个。
25
32
 
26
33
  用户输入: {{ user_input }}
27
34
 
28
- 请生成一个适当的 shell 脚本来执行用户的请求。确保脚本是安全的,并且可以在当前Shell环境中运行。
35
+ 请生成一个适当的脚本来执行用户的请求。确保脚本是安全的,并且可以在当前Shell环境中运行。
29
36
  脚本应该包含必要的注释来解释每个步骤。
30
37
  脚本内容请用如下方式返回:
31
38
 
32
- ```shell
33
- # 你的 shell 脚本内容
39
+ ```script
40
+ # 你的 script 脚本内容
34
41
  ```
35
42
  """
36
- env_info = detect_env()
43
+ env_info = detect_env()
44
+ shell_type = "bash"
45
+ if shells.is_running_in_cmd():
46
+ shell_type = "cmd"
47
+ elif shells.is_running_in_powershell():
48
+ shell_type = "powershell"
37
49
  return {
38
50
  "env_info": env_info,
39
- "shell_type": shells.get_terminal_name(),
51
+ "shell_type": shell_type,
40
52
  "shell_encoding": shells.get_terminal_encoding()
41
53
  }
42
54
 
43
55
 
44
- def generate_shell_script(user_input: str, llm: byzerllm.ByzerLLM) -> str:
56
+ def generate_shell_script(user_input: str, llm: Union[byzerllm.ByzerLLM,byzerllm.SimpleByzerLLM]) -> str:
45
57
  # 获取 prompt 内容
46
58
  prompt = _generate_shell_script.prompt(user_input=user_input)
59
+ if llm.get_sub_client("chat_model"):
60
+ shell_llm = llm.get_sub_client("chat_model")
61
+ else:
62
+ shell_llm = llm
47
63
 
48
64
  # 构造对话上下文
49
65
  conversations = [{"role": "user", "content": prompt}]
@@ -52,7 +68,7 @@ def generate_shell_script(user_input: str, llm: byzerllm.ByzerLLM) -> str:
52
68
  printer = Printer()
53
69
  title = printer.get_message_from_key("generating_shell_script")
54
70
  result, _ = stream_out(
55
- llm.stream_chat_oai(conversations=conversations, delta_mode=True),
71
+ shell_llm.stream_chat_oai(conversations=conversations, delta_mode=True),
56
72
  model_name=llm.default_model_name,
57
73
  title=title
58
74
  )
@@ -139,7 +139,7 @@ def create_actions(source_dir:str,params:Dict[str,str]):
139
139
  "000_example": base_000_example.prompt(),
140
140
  }
141
141
  init_file_path = os.path.join(source_dir, "actions", "101_current_work.yml")
142
- with open(init_file_path, "w") as f:
142
+ with open(init_file_path, "w", encoding="utf-8") as f:
143
143
  f.write(init_command_template.prompt(source_dir=source_dir))
144
144
 
145
145
  for k,v in mapping.items():
@@ -152,7 +152,7 @@ def create_actions(source_dir:str,params:Dict[str,str]):
152
152
  if k == "000_example":
153
153
  file_path = os.path.join(source_dir, "actions", f"{k}.yml")
154
154
 
155
- with open(file_path, "w") as f:
155
+ with open(file_path, "w", encoding="utf-8") as f:
156
156
  f.write(v)
157
157
 
158
158
  @byzerllm.prompt()
@@ -0,0 +1,105 @@
1
+ import os
2
+ import json
3
+ import shutil
4
+ from loguru import logger
5
+ from autocoder.common.printer import Printer
6
+ from autocoder.common.result_manager import ResultManager
7
+
8
+ result_manager = ResultManager()
9
+
10
+ def export_conf(project_root: str, export_path: str) -> bool:
11
+ printer = Printer()
12
+ """
13
+ Export conf from memory.json to a specified directory
14
+
15
+ Args:
16
+ project_root: Project root directory
17
+ export_path: Path to export the conf file
18
+
19
+ Returns:
20
+ bool: True if successful, False otherwise
21
+ """
22
+ project_root = os.path.abspath(project_root) or os.getcwd()
23
+ try:
24
+ memory_path = os.path.join(project_root, ".auto-coder", "plugins", "chat-auto-coder", "memory.json")
25
+ if not os.path.exists(memory_path):
26
+ printer.print_in_terminal("conf_not_found", path=memory_path)
27
+ return False
28
+
29
+ # Read and extract conf
30
+ with open(memory_path, "r",encoding="utf-8") as f:
31
+ memory_data = json.load(f)
32
+
33
+ conf_data = memory_data.get("conf", {})
34
+
35
+ # Write to export location
36
+ export_file = os.path.join(export_path, "conf.json")
37
+ os.makedirs(export_path, exist_ok=True)
38
+ with open(export_file, "w",encoding="utf-8") as f:
39
+ json.dump(conf_data, f, indent=2)
40
+ printer.print_in_terminal("conf_export_success", path=export_file)
41
+ result_manager.add_result(content=printer.get_message_from_key_with_format("conf_export_success", path=export_file), meta={"action": "conf_export", "input": {
42
+ "path": export_file
43
+ }})
44
+ return True
45
+
46
+ except Exception as e:
47
+ result_manager.add_result(content=printer.get_message_from_key_with_format("conf_export_error", error=str(e)), meta={"action": "conf_export", "input": {
48
+ "path": export_file
49
+ }})
50
+ printer.print_in_terminal("conf_export_error", error=str(e))
51
+ return False
52
+
53
+
54
+ def import_conf(project_root: str, import_path: str) -> bool:
55
+ project_root = os.path.abspath(project_root) or os.getcwd()
56
+ printer = Printer()
57
+ """
58
+ Import conf from a specified directory into memory.json
59
+
60
+ Args:
61
+ project_root: Project root directory
62
+ import_path: Path containing the conf file to import
63
+
64
+ Returns:
65
+ bool: True if successful, False otherwise
66
+ """
67
+ try:
68
+ import_file = os.path.join(import_path, "conf.json")
69
+ if not os.path.exists(import_file):
70
+ printer.print_in_terminal("conf_not_found", path=import_file)
71
+ return False
72
+
73
+ # Read conf file
74
+ with open(import_file, "r",encoding="utf-8") as f:
75
+ conf_data = json.load(f)
76
+
77
+ # Backup existing memory
78
+ memory_path = os.path.join(project_root, ".auto-coder", "plugins", "chat-auto-coder", "memory.json")
79
+ if os.path.exists(memory_path):
80
+ backup_path = memory_path + ".bak"
81
+ shutil.copy2(memory_path, backup_path)
82
+ printer.print_in_terminal("conf_backup_success", path=backup_path)
83
+
84
+ # Update conf in memory
85
+ with open(memory_path, "r",encoding="utf-8") as f:
86
+ memory_data = json.load(f)
87
+
88
+ memory_data["conf"] = conf_data
89
+
90
+ # Write updated memory
91
+ with open(memory_path, "w",encoding="utf-8") as f:
92
+ json.dump(memory_data, f, indent=2)
93
+
94
+ printer.print_in_terminal("conf_import_success", path=memory_path)
95
+ result_manager.add_result(content=printer.get_message_from_key_with_format("conf_import_success", path=memory_path), meta={"action": "conf_import", "input": {
96
+ "path": memory_path
97
+ }})
98
+ return True
99
+
100
+ except Exception as e:
101
+ result_manager.add_result(content=printer.get_message_from_key_with_format("conf_import_error", error=str(e)), meta={"action": "conf_import", "input": {
102
+ "path": memory_path
103
+ }})
104
+ printer.print_in_terminal("conf_import_error", error=str(e))
105
+ return False
@@ -132,11 +132,17 @@ class ConfigValidator:
132
132
  "type": str,
133
133
  "default": "v3_chat",
134
134
  "description": "提交信息生成模型名称"
135
+ },
136
+ "rank_strategy": {
137
+ "type": str,
138
+ "allowed": ["block", "file"],
139
+ "default": "block",
140
+ "description": "排序策略(block/file)"
135
141
  }
136
142
  }
137
143
 
138
144
  @classmethod
139
- def validate(cls, key: str, value: Any, product_mode: str) -> Any:
145
+ def validate(cls, key: str, value: Any, product_mode: str) -> Any:
140
146
  # 获取配置规范
141
147
  spec = cls.CONFIG_SPEC.get(key)
142
148
  if not spec: