auto-coder 0.1.259__py3-none-any.whl → 0.1.261__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 (36) hide show
  1. {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/METADATA +1 -1
  2. {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/RECORD +36 -27
  3. autocoder/agent/auto_review_commit.py +51 -24
  4. autocoder/auto_coder.py +24 -1
  5. autocoder/chat_auto_coder.py +377 -399
  6. autocoder/chat_auto_coder_lang.py +20 -0
  7. autocoder/commands/__init__.py +0 -0
  8. autocoder/commands/auto_command.py +1174 -0
  9. autocoder/commands/tools.py +533 -0
  10. autocoder/common/__init__.py +8 -0
  11. autocoder/common/auto_coder_lang.py +61 -8
  12. autocoder/common/auto_configure.py +304 -0
  13. autocoder/common/code_auto_merge.py +2 -2
  14. autocoder/common/code_auto_merge_diff.py +2 -2
  15. autocoder/common/code_auto_merge_editblock.py +2 -2
  16. autocoder/common/code_auto_merge_strict_diff.py +2 -2
  17. autocoder/common/code_modification_ranker.py +8 -7
  18. autocoder/common/command_completer.py +557 -0
  19. autocoder/common/conf_validator.py +245 -0
  20. autocoder/common/conversation_pruner.py +131 -0
  21. autocoder/common/git_utils.py +82 -1
  22. autocoder/common/index_import_export.py +101 -0
  23. autocoder/common/result_manager.py +115 -0
  24. autocoder/common/shells.py +22 -6
  25. autocoder/common/utils_code_auto_generate.py +2 -2
  26. autocoder/dispacher/actions/action.py +45 -4
  27. autocoder/dispacher/actions/plugins/action_regex_project.py +13 -1
  28. autocoder/index/filter/quick_filter.py +22 -7
  29. autocoder/utils/auto_coder_utils/chat_stream_out.py +13 -6
  30. autocoder/utils/project_structure.py +15 -0
  31. autocoder/utils/thread_utils.py +4 -0
  32. autocoder/version.py +1 -1
  33. {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/LICENSE +0 -0
  34. {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/WHEEL +0 -0
  35. {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/entry_points.txt +0 -0
  36. {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,533 @@
1
+ from typing import Optional
2
+ from autocoder.common.result_manager import ResultManager
3
+ from autocoder.index.index import IndexManager
4
+ from autocoder.pyproject import PyProject
5
+ from autocoder.tsproject import TSProject
6
+ from autocoder.suffixproject import SuffixProject
7
+ from autocoder.common import AutoCoderArgs, SourceCode
8
+ from autocoder.common.interpreter import Interpreter
9
+ from autocoder.common import ExecuteSteps, ExecuteStep, detect_env
10
+ from autocoder.common import code_auto_execute
11
+ from typing import List, Tuple
12
+ import os
13
+ import byzerllm
14
+ import json
15
+ from pydantic import BaseModel
16
+ from byzerllm.types import Bool
17
+ from contextlib import contextmanager
18
+ from rich.console import Console
19
+ from rich.panel import Panel
20
+ from rich.text import Text
21
+ from rich.prompt import Prompt
22
+ from typing import Union
23
+ from autocoder.utils.queue_communicate import (
24
+ queue_communicate,
25
+ CommunicateEvent,
26
+ CommunicateEventType,
27
+ )
28
+ import sys
29
+ import io
30
+
31
+ @byzerllm.prompt()
32
+ def detect_rm_command(command: str) -> Bool:
33
+ """
34
+ 给定如下shell脚本:
35
+
36
+ ```shell
37
+ {{ command }}
38
+ ```
39
+
40
+ 如果该脚本中包含删除目录或者文件的命令,请返回True,否则返回False。
41
+ """
42
+
43
+ @contextmanager
44
+ def redirect_stdout():
45
+ original_stdout = sys.stdout
46
+ sys.stdout = f = io.StringIO()
47
+ try:
48
+ yield f
49
+ finally:
50
+ sys.stdout = original_stdout
51
+
52
+ class AutoCommandTools:
53
+ def __init__(self, args: AutoCoderArgs,
54
+ llm: Union[byzerllm.ByzerLLM, byzerllm.SimpleByzerLLM]):
55
+ self.args = args
56
+ self.llm = llm
57
+ self.result_manager = ResultManager()
58
+
59
+ def ask_user(self,question:str) -> str:
60
+ '''
61
+ 如果你对用户的问题有什么疑问,或者你想从用户收集一些额外信息,可以调用此方法。
62
+ 输入参数 question 是你对用户的提问。
63
+ 返回值是 用户对你问题的回答。
64
+
65
+ 注意,尽量不要询问用户,除非你感受到你无法回答用户的问题。
66
+ '''
67
+
68
+ if self.args.request_id and not self.args.silence and not self.args.skip_events:
69
+ event_data = {
70
+ "question": question
71
+ }
72
+ response_json = queue_communicate.send_event(
73
+ request_id=self.args.request_id,
74
+ event=CommunicateEvent(
75
+ event_type=CommunicateEventType.ASK_HUMAN.value,
76
+ data=json.dumps(event_data, ensure_ascii=False),
77
+ ),
78
+ )
79
+ return response_json
80
+
81
+ console = Console()
82
+
83
+ # 创建一个醒目的问题面板
84
+ question_text = Text(question, style="bold cyan")
85
+ question_panel = Panel(
86
+ question_text,
87
+ title="[bold yellow]auto-coder.chat's Question[/bold yellow]",
88
+ border_style="blue",
89
+ expand=False
90
+ )
91
+
92
+ # 显示问题面板
93
+ console.print(question_panel)
94
+
95
+ # 创建一个自定义提示符
96
+ prompt = Prompt.ask(
97
+ "\n[bold green]Your Answer[/bold green]",
98
+ console=console
99
+ )
100
+
101
+ # 获取用户的回答
102
+ answer = prompt
103
+
104
+ # 显示用户的回答
105
+ answer_text = Text(answer, style="italic")
106
+ answer_panel = Panel(
107
+ answer_text,
108
+ title="[bold yellow]Your Response[/bold yellow]",
109
+ border_style="green",
110
+ expand=False
111
+ )
112
+ console.print(answer_panel)
113
+
114
+ self.result_manager.append(content=answer, meta = {
115
+ "action": "ask_user",
116
+ "input": {
117
+ "question": question
118
+ }
119
+ })
120
+
121
+ return answer
122
+
123
+ def run_python_code(self, code: str) -> str:
124
+ """
125
+ 你可以通过该工具运行指定的Python代码。
126
+ 输入参数 code: Python代码
127
+ 返回值是Python代码的sys output 或者 sys error 信息。
128
+
129
+ 通常你需要在代码中指定项目的根目录(前面我们已经提到了)。
130
+ """
131
+ interpreter = Interpreter(cwd=self.args.source_dir)
132
+ s = ""
133
+ try:
134
+ s = interpreter.execute_steps(
135
+ ExecuteSteps(steps=[ExecuteStep(lang="python", code=code)])
136
+ )
137
+ finally:
138
+ interpreter.close()
139
+
140
+ self.result_manager.append(content=s, meta = {
141
+ "action": "run_python_code",
142
+ "input": {
143
+ "code": code
144
+ }
145
+ })
146
+
147
+ return s
148
+
149
+ def run_shell_code(self, script: str) -> str:
150
+ """
151
+ 你可以通过该工具运行指定的Shell代码。主要用于一些编译,运行,测试等任务。
152
+ 输入参数 script: Shell代码
153
+ 返回值是Shell代码的output 或者 error 信息。
154
+ """
155
+
156
+ if detect_rm_command.with_llm(self.llm).run(script).value:
157
+ return "The script contains rm command, which is not allowed."
158
+
159
+ interpreter = Interpreter(cwd=self.args.source_dir)
160
+ s = ""
161
+ try:
162
+ s = interpreter.execute_steps(
163
+ ExecuteSteps(steps=[ExecuteStep(lang="shell", code=script)])
164
+ )
165
+ finally:
166
+ interpreter.close()
167
+
168
+ self.result_manager.append(content=s, meta = {
169
+ "action": "run_shell_code",
170
+ "input": {
171
+ "script": script
172
+ }
173
+ })
174
+
175
+ return s
176
+
177
+ def get_related_files_by_symbols(self, query: str) -> str:
178
+ """
179
+ 你可以给出类名,函数名,以及文件的用途描述等信息,该工具会根据这些信息返回项目中相关的文件。
180
+ """
181
+ v = self.get_project_related_files(query)
182
+ self.result_manager.append(content=v, meta = {
183
+ "action": "get_related_files_by_symbols",
184
+ "input": {
185
+ "query": query
186
+ }
187
+ })
188
+ return v
189
+
190
+ def get_project_related_files(self, query: str) -> str:
191
+ """
192
+ 该工具会根据查询描述,根据索引返回项目中与查询相关的文件。
193
+ 返回值为按逗号分隔的文件路径列表。
194
+
195
+ 注意,该工具无法涵盖当前项目中所有文件,因为有些文件可能没有被索引。
196
+ """
197
+ if self.args.project_type == "ts":
198
+ pp = TSProject(args=self.args, llm=self.llm)
199
+ elif self.args.project_type == "py":
200
+ pp = PyProject(args=self.args, llm=self.llm)
201
+ else:
202
+ pp = SuffixProject(args=self.args, llm=self.llm, file_filter=None)
203
+ pp.run()
204
+ sources = pp.sources
205
+
206
+ index_manager = IndexManager(llm=self.llm, sources=sources, args=self.args)
207
+ target_files = index_manager.get_target_files_by_query(query)
208
+ file_list = target_files.file_list
209
+ v = ",".join([file.file_path for file in file_list])
210
+ self.result_manager.append(content=v, meta = {
211
+ "action": "get_project_related_files",
212
+ "input": {
213
+ "query": query
214
+ }
215
+ })
216
+ return v
217
+
218
+ def get_project_map(self, file_path: Optional[str] = None) -> str:
219
+ """
220
+ 该工具会返回项目中所有已经被构建索引的文件以及该文件的信息,诸如该文件的用途,导入的包,定义的类,函数,变量等信息。
221
+ 返回的是json格式文本。
222
+
223
+ 注意,这个工具无法返回所有文件的信息,因为有些文件可能没有被索引。
224
+ 尽量避免使用该工具。
225
+ """
226
+ if self.args.project_type == "ts":
227
+ pp = TSProject(args=self.args, llm=self.llm)
228
+ elif self.args.project_type == "py":
229
+ pp = PyProject(args=self.args, llm=self.llm)
230
+ else:
231
+ pp = SuffixProject(args=self.args, llm=self.llm, file_filter=None)
232
+ pp.run()
233
+ sources = pp.sources
234
+
235
+ index_manager = IndexManager(llm=self.llm, sources=sources, args=self.args)
236
+ s = index_manager.read_index_as_str()
237
+ index_data = json.loads(s)
238
+
239
+ final_result = []
240
+ for k in index_data.values():
241
+ value = {}
242
+ value["file_name"] = k["module_name"]
243
+ value["symbols"] = k["symbols"]
244
+ value["file_tokens"] = k.get("input_tokens_count", -1)
245
+ value["index_tokens"] = k.get("generated_tokens_count", -1)
246
+ value["file_tokens_cost"] = k.get("input_tokens_cost", -1)
247
+ value["index_tokens_cost"] = k.get("generated_tokens_cost", -1)
248
+ if file_path and file_path in k["module_name"]:
249
+ final_result.append(value)
250
+ v = json.dumps(final_result, ensure_ascii=False)
251
+ self.result_manager.add_result(content=v, meta = {
252
+ "action": "get_project_map",
253
+ "input": {
254
+ }
255
+ })
256
+ return v
257
+
258
+ def read_file_with_keyword_ranges(self, file_path: str, keyword:str, before_size:int = 100, after_size:int = 100) -> str:
259
+ """
260
+ 该函数用于读取包含了关键字(keyword)的行,以及该行前后指定大小的行。
261
+ 输入参数:
262
+ - file_path: 文件路径
263
+ - keyword: 关键字
264
+ - before_size: 关键字所在行之前的行数
265
+ - after_size: 关键字所在行之后的行数
266
+
267
+ 返回值:
268
+ - 返回str类型,返回包含关键字的行,以及该行前后指定大小的行。
269
+
270
+ 返回值的格式如下:
271
+ ```
272
+ ##File: /path/to/file.py
273
+ ##Line: 10-20
274
+
275
+ 内容
276
+ ```
277
+ """
278
+ absolute_path = file_path
279
+ if not os.path.isabs(file_path):
280
+ # Find the first matching absolute path by traversing args.source_dir
281
+ for root, _, files in os.walk(self.args.source_dir):
282
+ for file in files:
283
+ if file_path in os.path.join(root, file):
284
+ absolute_path = os.path.join(root, file)
285
+ break
286
+
287
+ result = []
288
+ try:
289
+ with open(absolute_path, 'r', encoding='utf-8') as f:
290
+ lines = f.readlines()
291
+
292
+ # Find all lines containing the keyword
293
+ keyword_lines = []
294
+ for i, line in enumerate(lines):
295
+ if keyword.lower() in line.lower():
296
+ keyword_lines.append(i)
297
+
298
+ # Process each keyword line and its surrounding range
299
+ processed_ranges = set()
300
+ for line_num in keyword_lines:
301
+ # Calculate range boundaries
302
+ start = max(0, line_num - before_size)
303
+ end = min(len(lines), line_num + after_size + 1)
304
+
305
+ # Check if this range overlaps with any previously processed range
306
+ range_key = (start, end)
307
+ if range_key in processed_ranges:
308
+ continue
309
+
310
+ processed_ranges.add(range_key)
311
+
312
+ # Format the content block
313
+ content = f"##File: {absolute_path}\n"
314
+ content += f"##Line: {start+1}-{end}\n\n"
315
+ content += "".join(lines[start:end])
316
+ result.append(content)
317
+
318
+ except Exception as e:
319
+ v = f"Error reading file {absolute_path}: {str(e)}"
320
+ self.result_manager.add_result(content=v, meta={
321
+ "action": "read_file_with_keyword_ranges",
322
+ "input": {
323
+ "file_path": file_path,
324
+ "keyword": keyword,
325
+ "before_size": before_size,
326
+ "after_size": after_size
327
+ }
328
+ })
329
+ return v
330
+
331
+ final_result = "\n\n".join(result)
332
+ self.result_manager.add_result(content=final_result, meta={
333
+ "action": "read_file_with_keyword_ranges",
334
+ "input": {
335
+ "file_path": file_path,
336
+ "keyword": keyword,
337
+ "before_size": before_size,
338
+ "after_size": after_size
339
+ }
340
+ })
341
+
342
+ return final_result
343
+
344
+ def read_files(self, paths: str, line_ranges: Optional[str] = None) -> str:
345
+ """
346
+ 该工具用于读取指定文件的内容。
347
+
348
+ 参数说明:
349
+ 1. paths (str):
350
+ - 以逗号分隔的文件路径列表
351
+ - 支持两种格式:
352
+ a) 文件名: 如果多个文件匹配该名称,将选择第一个匹配项
353
+ b) 绝对路径: 直接指定文件的完整路径
354
+ - 示例: "main.py,utils.py" 或 "/path/to/main.py,/path/to/utils.py"
355
+ - 建议: 每次调用最多指定5-6个最相关的文件,以避免返回内容过多
356
+
357
+ 2. line_ranges (Optional[str]):
358
+ - 可选参数,用于指定每个文件要读取的具体行范围
359
+ - 格式说明:
360
+ * 使用逗号分隔不同文件的行范围
361
+ * 每个文件可以指定多个行范围,用/分隔
362
+ * 每个行范围使用-连接起始行和结束行
363
+ - 示例:
364
+ * "1-100,2-50" (为两个文件分别指定一个行范围)
365
+ * "1-100/200-300,50-100" (第一个文件指定两个行范围,第二个文件指定一个行范围)
366
+ - 注意: line_ranges中的文件数量必须与paths中的文件数量一致
367
+
368
+ 返回值:
369
+ - 返回str类型,包含所有请求文件的内容
370
+ - 每个文件内容前会标注文件路径和行范围信息(如果指定了行范围)
371
+ """
372
+ paths = [p.strip() for p in paths.split(",")]
373
+ source_code_str = ""
374
+
375
+ # Parse line ranges if provided
376
+ file_line_ranges = {}
377
+ if line_ranges:
378
+ ranges_per_file = line_ranges.split(",")
379
+ if len(ranges_per_file) != len(paths):
380
+ self.result_manager.add_result(content="Number of line ranges must match number of files", meta = {
381
+ "action": "read_files",
382
+ "input": {
383
+ "paths": paths,
384
+ "line_ranges": line_ranges
385
+ }
386
+ })
387
+ raise ValueError("Number of line ranges must match number of files")
388
+
389
+ for path, ranges in zip(paths, ranges_per_file):
390
+ file_line_ranges[path] = []
391
+ for range_str in ranges.split("/"):
392
+ if not range_str:
393
+ continue
394
+ start, end = map(int, range_str.split("-"))
395
+ file_line_ranges[path].append((start, end))
396
+
397
+ for path in paths:
398
+ absolute_path = path
399
+ if not os.path.isabs(path):
400
+ # Find the first matching absolute path by traversing args.source_dir
401
+ for root, _, files in os.walk(self.args.source_dir):
402
+ for file in files:
403
+ if path in os.path.join(root, file):
404
+ absolute_path = os.path.join(root, file)
405
+ break
406
+
407
+ with open(absolute_path, "r", encoding="utf-8") as f:
408
+ if path in file_line_ranges:
409
+ # Read specific line ranges
410
+ lines = f.readlines()
411
+ filtered_lines = []
412
+ for start, end in file_line_ranges[path]:
413
+ # Adjust for 0-based indexing
414
+ start = max(0, start - 1)
415
+ end = min(len(lines), end)
416
+ content = "".join(lines[start:end])
417
+ filtered_lines.extend(f"##File: {absolute_path}\n##Line: {start}-{end}\n\n{content}")
418
+ source_code = "".join(filtered_lines)
419
+ else:
420
+ # Read entire file if no range specified
421
+ content = f.read()
422
+ source_code = f"##File: {absolute_path}\n\n{content}"
423
+
424
+ sc = SourceCode(module_name=absolute_path, source_code=source_code)
425
+ source_code_str += f"{sc.source_code}\n\n"
426
+
427
+ self.result_manager.add_result(content=source_code_str, meta = {
428
+ "action": "read_files",
429
+ "input": {
430
+ "paths": paths,
431
+ "line_ranges": line_ranges
432
+ }
433
+ })
434
+ return source_code_str
435
+
436
+ def get_project_structure(self) -> str:
437
+ if self.args.project_type == "ts":
438
+ pp = TSProject(args=self.args, llm=self.llm)
439
+ elif self.args.project_type == "py":
440
+ pp = PyProject(args=self.args, llm=self.llm)
441
+ else:
442
+ pp = SuffixProject(args=self.args, llm=self.llm, file_filter=None)
443
+ pp.run()
444
+ s = pp.get_tree_like_directory_structure()
445
+ self.result_manager.add_result(content=s, meta = {
446
+ "action": "get_project_structure",
447
+ "input": {
448
+ }
449
+ })
450
+ return s
451
+
452
+
453
+ def find_files_by_name(self, keyword: str) -> str:
454
+ """
455
+ 根据关键字在项目中搜索文件名。
456
+ 输入参数 keyword: 要搜索的关键字
457
+ 返回值是文件名包含该关键字的文件路径列表,以逗号分隔。
458
+
459
+ 该工具会搜索文件名,返回所有匹配的文件。
460
+ 搜索不区分大小写。
461
+ """
462
+ matched_files = []
463
+ for root, _, files in os.walk(self.args.source_dir):
464
+ for file in files:
465
+ if keyword.lower() in file.lower():
466
+ matched_files.append(os.path.join(root, file))
467
+
468
+ v = ",".join(matched_files)
469
+ self.result_manager.add_result(content=v, meta = {
470
+ "action": "find_files_by_name",
471
+ "input": {
472
+ "keyword": keyword
473
+ }
474
+ })
475
+ return v
476
+
477
+ def find_files_by_content(self, keyword: str) -> str:
478
+ """
479
+ 根据关键字在项目中搜索文件内容。
480
+ 输入参数 keyword: 要搜索的关键字
481
+ 返回值是内容包含该关键字的文件路径列表,以逗号分隔。
482
+
483
+ 该工具会搜索文件内容,返回所有匹配的文件。
484
+ 如果结果过多,只返回前10个匹配项。
485
+ 搜索不区分大小写。
486
+
487
+ 默认排除以下目录:['node_modules', '.git', '.venv', 'venv', '__pycache__', 'dist', 'build']
488
+ """
489
+ # 需要排除的目录和文件模式
490
+ excluded_dirs = [
491
+ 'node_modules', '.git', '.venv', 'venv', '__pycache__', 'dist', 'build',
492
+ '.DS_Store', '.idea', '.vscode', 'tmp', 'temp', 'cache', 'coverage',
493
+ 'htmlcov', '.mypy_cache', '.pytest_cache', '.hypothesis'
494
+ ]
495
+ excluded_file_patterns = [
496
+ '*.pyc', '*.pyo', '*.pyd', '*.egg-info', '*.log'
497
+ ]
498
+
499
+ matched_files = []
500
+
501
+ for root, dirs, files in os.walk(self.args.source_dir):
502
+ # 移除需要排除的目录
503
+ dirs[:] = [d for d in dirs if d not in excluded_dirs]
504
+
505
+ # 过滤掉需要排除的文件
506
+ files[:] = [f for f in files if not any(
507
+ f.endswith(pattern[1:]) for pattern in excluded_file_patterns
508
+ )]
509
+
510
+ for file in files:
511
+ file_path = os.path.join(root, file)
512
+ try:
513
+ with open(file_path, "r", encoding="utf-8") as f:
514
+ content = f.read()
515
+ if keyword.lower() in content.lower():
516
+ matched_files.append(file_path)
517
+ # Limit to first 10 matches
518
+ if len(matched_files) >= 10:
519
+ break
520
+ except Exception:
521
+ # Skip files that can't be read
522
+ pass
523
+ if len(matched_files) >= 10:
524
+ break
525
+
526
+ v = ",".join(matched_files[:10])
527
+ self.result_manager.add_result(content=v, meta = {
528
+ "action": "find_files_by_content",
529
+ "input": {
530
+ "keyword": keyword
531
+ }
532
+ })
533
+ return v
@@ -370,6 +370,14 @@ class AutoCoderArgs(pydantic.BaseModel):
370
370
  in_code_apply: bool = False
371
371
  model_filter_path: Optional[str] = None
372
372
 
373
+ conversation_prune_safe_zone_tokens: Optional[int] = 50*1024
374
+ conversation_prune_group_size: Optional[int] = 4
375
+ conversation_prune_strategy: Optional[str] = "summarize"
376
+
377
+ auto_command_max_iterations: Optional[int] = 10
378
+
379
+ skip_commit: Optional[bool] = False
380
+
373
381
  class Config:
374
382
  protected_namespaces = ()
375
383