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.
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/METADATA +1 -1
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/RECORD +36 -27
- autocoder/agent/auto_review_commit.py +51 -24
- autocoder/auto_coder.py +24 -1
- autocoder/chat_auto_coder.py +377 -399
- autocoder/chat_auto_coder_lang.py +20 -0
- autocoder/commands/__init__.py +0 -0
- autocoder/commands/auto_command.py +1174 -0
- autocoder/commands/tools.py +533 -0
- autocoder/common/__init__.py +8 -0
- autocoder/common/auto_coder_lang.py +61 -8
- autocoder/common/auto_configure.py +304 -0
- autocoder/common/code_auto_merge.py +2 -2
- autocoder/common/code_auto_merge_diff.py +2 -2
- autocoder/common/code_auto_merge_editblock.py +2 -2
- autocoder/common/code_auto_merge_strict_diff.py +2 -2
- autocoder/common/code_modification_ranker.py +8 -7
- autocoder/common/command_completer.py +557 -0
- autocoder/common/conf_validator.py +245 -0
- autocoder/common/conversation_pruner.py +131 -0
- autocoder/common/git_utils.py +82 -1
- autocoder/common/index_import_export.py +101 -0
- autocoder/common/result_manager.py +115 -0
- autocoder/common/shells.py +22 -6
- autocoder/common/utils_code_auto_generate.py +2 -2
- autocoder/dispacher/actions/action.py +45 -4
- autocoder/dispacher/actions/plugins/action_regex_project.py +13 -1
- autocoder/index/filter/quick_filter.py +22 -7
- autocoder/utils/auto_coder_utils/chat_stream_out.py +13 -6
- autocoder/utils/project_structure.py +15 -0
- autocoder/utils/thread_utils.py +4 -0
- autocoder/version.py +1 -1
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/entry_points.txt +0 -0
- {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
|
autocoder/common/__init__.py
CHANGED
|
@@ -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
|
|