auto-coder 0.1.259__py3-none-any.whl → 0.1.260__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.260.dist-info}/METADATA +1 -1
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.260.dist-info}/RECORD +25 -20
- autocoder/auto_coder.py +24 -1
- autocoder/chat_auto_coder.py +327 -399
- autocoder/chat_auto_coder_lang.py +2 -0
- autocoder/commands/__init__.py +0 -0
- autocoder/commands/auto_command.py +1145 -0
- autocoder/commands/tools.py +533 -0
- autocoder/common/auto_coder_lang.py +34 -6
- autocoder/common/auto_configure.py +304 -0
- autocoder/common/code_modification_ranker.py +8 -7
- autocoder/common/command_completer.py +566 -0
- autocoder/common/git_utils.py +82 -1
- autocoder/common/result_manager.py +115 -0
- autocoder/common/utils_code_auto_generate.py +2 -2
- autocoder/dispacher/actions/action.py +8 -4
- autocoder/dispacher/actions/plugins/action_regex_project.py +2 -1
- autocoder/index/filter/quick_filter.py +14 -2
- autocoder/utils/auto_coder_utils/chat_stream_out.py +13 -6
- autocoder/utils/thread_utils.py +4 -0
- autocoder/version.py +1 -1
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.260.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.260.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.260.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.259.dist-info → auto_coder-0.1.260.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
|
|
@@ -3,7 +3,16 @@ from byzerllm.utils import format_str_jinja2
|
|
|
3
3
|
|
|
4
4
|
MESSAGES = {
|
|
5
5
|
"en": {
|
|
6
|
+
"auto_command_action_break": "Command {{command}} execution failed (got {{action}} result), no result can be obtained, please try again",
|
|
7
|
+
"auto_command_break": "Auto command execution failed to execute command: {{command}}",
|
|
8
|
+
"auto_command_executing": "\n\n============= Executing command: {{command}} =============\n\n",
|
|
6
9
|
"model_provider_select_title": "Select Model Provider",
|
|
10
|
+
"auto_config_analyzing": "Analyzing configuration...",
|
|
11
|
+
"config_delete_success": "Successfully deleted configuration: {{key}}",
|
|
12
|
+
"config_not_found": "Configuration not found: {{key}}",
|
|
13
|
+
"config_invalid_format": "Invalid configuration format. Expected 'key:value'",
|
|
14
|
+
"config_value_empty": "Configuration value cannot be empty",
|
|
15
|
+
"config_set_success": "Successfully set configuration: {{key}} = {{value}}",
|
|
7
16
|
"model_provider_select_text": "Please select your model provider:",
|
|
8
17
|
"model_provider_volcano": "Volcano Engine",
|
|
9
18
|
"model_provider_siliconflow": "SiliconFlow AI",
|
|
@@ -71,7 +80,7 @@ MESSAGES = {
|
|
|
71
80
|
"Paste the answer to the input box below, use '/break' to exit, '/clear' to clear the screen, '/eof' to submit."
|
|
72
81
|
),
|
|
73
82
|
"code_generation_start": "Auto generate the code...",
|
|
74
|
-
"code_generation_complete": "{{ model_names}} Code generation completed in {{ duration }} seconds, input_tokens_count: {{ input_tokens }}, generated_tokens_count: {{ output_tokens }}, input_cost: {{ input_cost }}, output_cost: {{ output_cost }}, speed: {{ speed }} tokens/s",
|
|
83
|
+
"code_generation_complete": "{{ model_names}} Code generation completed in {{ duration }} seconds (sampling_count: {{ sampling_count }}), input_tokens_count: {{ input_tokens }}, generated_tokens_count: {{ output_tokens }}, input_cost: {{ input_cost }}, output_cost: {{ output_cost }}, speed: {{ speed }} tokens/s",
|
|
75
84
|
"code_merge_start": "Auto merge the code...",
|
|
76
85
|
"code_execution_warning": "Content(send to model) is {{ content_length }} tokens (you may collect too much files), which is larger than the maximum input length {{ max_length }}",
|
|
77
86
|
"quick_filter_start": "{{ model_name }} Starting filter context(quick_filter)...",
|
|
@@ -89,12 +98,12 @@ MESSAGES = {
|
|
|
89
98
|
"ranking_start": "Start ranking {{ count }} candidates using model {{ model_name }}",
|
|
90
99
|
"ranking_failed_request": "Ranking request failed: {{ error }}",
|
|
91
100
|
"ranking_all_failed": "All ranking requests failed",
|
|
92
|
-
"ranking_complete": "{{ model_names }} Ranking completed in {{ elapsed }}s, total voters: {{ total_tasks }}, best candidate index: {{ best_candidate }}, scores: {{ scores }}, input_tokens: {{ input_tokens }}, output_tokens: {{ output_tokens }}, input_cost: {{ input_cost }}, output_cost: {{ output_cost }}",
|
|
101
|
+
"ranking_complete": "{{ model_names }} Ranking completed in {{ elapsed }}s, total voters: {{ total_tasks }}, best candidate index: {{ best_candidate }}, scores: {{ scores }}, input_tokens: {{ input_tokens }}, output_tokens: {{ output_tokens }}, input_cost: {{ input_cost }}, output_cost: {{ output_cost }}, speed: {{ speed }} tokens/s",
|
|
93
102
|
"ranking_process_failed": "Ranking process failed: {{ error }}",
|
|
94
103
|
"ranking_failed": "Ranking failed in {{ elapsed }}s, using original order",
|
|
95
104
|
"begin_index_source_code": "🚀 Begin to index source code in {{ source_dir }}",
|
|
96
105
|
"stream_out_stats": "Model: {{ model_name }}, Total time: {{ elapsed_time }} seconds, First token time: {{ first_token_time }} seconds, Speed: {{ speed }} tokens/s, Input tokens: {{ input_tokens }}, Output tokens: {{ output_tokens }}, Input cost: {{ input_cost }}, Output cost: {{ output_cost }}",
|
|
97
|
-
"quick_filter_stats": "{{ model_names }}
|
|
106
|
+
"quick_filter_stats": "{{ model_names }} Quick filter completed in {{ elapsed_time }} seconds, input tokens: {{ input_tokens }}, output tokens: {{ output_tokens }}, input cost: {{ input_cost }}, output cost: {{ output_cost }} speed: {{ speed }} tokens/s",
|
|
98
107
|
"upsert_file": "✅ Updated file: {{ file_path }}",
|
|
99
108
|
"unmerged_blocks_title": "Unmerged Blocks",
|
|
100
109
|
"quick_filter_title": "{{ model_name }} is analyzing how to filter context...",
|
|
@@ -110,9 +119,23 @@ MESSAGES = {
|
|
|
110
119
|
"estimated_chat_input_tokens": "Estimated chat input tokens: {{ estimated_input_tokens }}",
|
|
111
120
|
"estimated_input_tokens_in_generate": "Estimated input tokens in generate ({{ generate_mode }}): {{ estimated_input_tokens }}",
|
|
112
121
|
"model_has_access_restrictions": "{{model_name}} has access restrictions, cannot use the current function",
|
|
122
|
+
"auto_command_not_found": "Auto command not found: {{command}}. Please check your input and try again.",
|
|
123
|
+
"auto_command_failed": "Auto command failed: {{error}}. Please check your input and try again.",
|
|
124
|
+
"command_execution_result": "{{action}} execution result",
|
|
125
|
+
"satisfied_prompt": "Requirements satisfied, no further action needed",
|
|
126
|
+
"auto_command_analyzed": "Selected command"
|
|
113
127
|
},
|
|
114
128
|
"zh": {
|
|
129
|
+
"auto_command_action_break": "命令 {{command}} 执行失败(获取到了 {{action}} 的结果),无法获得任何结果,请重试",
|
|
130
|
+
"auto_command_break": "自动命令执行失败: {{command}}",
|
|
131
|
+
"auto_command_executing": "\n\n============= 正在执行指令: {{command}} =============\n\n",
|
|
115
132
|
"model_provider_select_title": "选择模型供应商",
|
|
133
|
+
"auto_config_analyzing": "正在分析配置...",
|
|
134
|
+
"config_delete_success": "成功删除配置: {{key}}",
|
|
135
|
+
"config_not_found": "未找到配置: {{key}}",
|
|
136
|
+
"config_invalid_format": "配置格式无效,应为'key:value'格式",
|
|
137
|
+
"config_value_empty": "配置值不能为空",
|
|
138
|
+
"config_set_success": "成功设置配置: {{key}} = {{value}}",
|
|
116
139
|
"model_provider_select_text": "请选择您的模型供应商:",
|
|
117
140
|
"model_provider_volcano": "火山方舟",
|
|
118
141
|
"model_provider_siliconflow": "硅基流动",
|
|
@@ -179,7 +202,7 @@ MESSAGES = {
|
|
|
179
202
|
"将获得答案黏贴到下面的输入框,换行后,使用 '/break' 退出,'/clear' 清屏,'/eof' 提交。"
|
|
180
203
|
),
|
|
181
204
|
"code_generation_start": "正在自动生成代码...",
|
|
182
|
-
"code_generation_complete": "{{ model_names}} 代码生成完成,耗时 {{ duration }}
|
|
205
|
+
"code_generation_complete": "{{ model_names}} 代码生成完成,耗时 {{ duration }} 秒 (采样数: {{ sampling_count }}), 输入token数: {{ input_tokens }}, 输出token数: {{ output_tokens }}, 输入成本: {{ input_cost }}, 输出成本: {{ output_cost }}, 速度: {{ speed }} tokens/秒",
|
|
183
206
|
"code_merge_start": "正在自动合并代码...",
|
|
184
207
|
"code_execution_warning": "发送给模型的内容长度为 {{ content_length }} tokens(您可能收集了太多文件),超过了最大输入长度 {{ max_length }}",
|
|
185
208
|
"quick_filter_start": "{{ model_name }} 开始查找上下文(quick_filter)...",
|
|
@@ -208,16 +231,21 @@ MESSAGES = {
|
|
|
208
231
|
"ranking_start": "开始对 {{ count }} 个候选项进行排序,使用模型 {{ model_name }} 打分",
|
|
209
232
|
"ranking_failed_request": "排序请求失败: {{ error }}",
|
|
210
233
|
"ranking_all_failed": "所有排序请求都失败",
|
|
211
|
-
"ranking_complete": "{{ model_names }} 排序完成,耗时 {{ elapsed }} 秒,总投票数: {{ total_tasks }},最佳候选索引: {{ best_candidate }},得分: {{ scores }},输入token数: {{ input_tokens }},输出token数: {{ output_tokens }}
|
|
234
|
+
"ranking_complete": "{{ model_names }} 排序完成,耗时 {{ elapsed }} 秒,总投票数: {{ total_tasks }},最佳候选索引: {{ best_candidate }},得分: {{ scores }},输入token数: {{ input_tokens }},输出token数: {{ output_tokens }},输入成本: {{ input_cost }}, 输出成本: {{ output_cost }},速度: {{ speed }} tokens/秒",
|
|
212
235
|
"ranking_process_failed": "排序过程失败: {{ error }}",
|
|
213
236
|
"ranking_failed": "排序失败,耗时 {{ elapsed }} 秒,使用原始顺序",
|
|
214
237
|
"stream_out_stats": "模型: {{ model_name }},总耗时 {{ elapsed_time }} 秒,首token时间: {{ first_token_time }} 秒, 速度: {{ speed }} tokens/秒, 输入token数: {{ input_tokens }}, 输出token数: {{ output_tokens }}, 输入成本: {{ input_cost }}, 输出成本: {{ output_cost }}",
|
|
215
|
-
"quick_filter_stats": "{{ model_names }} Quick
|
|
238
|
+
"quick_filter_stats": "{{ model_names }} Quick Filter 完成耗时 {{ elapsed_time }} 秒,输入token数: {{ input_tokens }}, 输出token数: {{ output_tokens }}, 输入成本: {{ input_cost }}, 输出成本: {{ output_cost }} 速度: {{ speed }} tokens/秒",
|
|
216
239
|
"quick_filter_title": "{{ model_name }} 正在分析如何筛选上下文...",
|
|
217
240
|
"quick_filter_failed": "❌ 快速过滤器失败: {{ error }}. ",
|
|
218
241
|
"estimated_chat_input_tokens": "对话输入token预估为: {{ estimated_input_tokens }}",
|
|
219
242
|
"estimated_input_tokens_in_generate": "生成代码({{ generate_mode }})预计输入token数: {{ estimated_input_tokens_in_generate }}",
|
|
220
243
|
"model_has_access_restrictions": "{{model_name}} 有访问限制,无法使用当前功能",
|
|
244
|
+
"auto_command_not_found": "未找到自动命令: {{command}}。请检查您的输入并重试。",
|
|
245
|
+
"auto_command_failed": "自动命令执行失败: {{error}}。请检查您的输入并重试。",
|
|
246
|
+
"command_execution_result": "{{action}} 执行结果",
|
|
247
|
+
"satisfied_prompt": "已满足需求,无需进一步操作",
|
|
248
|
+
"auto_command_analyzed": "被选择指令"
|
|
221
249
|
}}
|
|
222
250
|
|
|
223
251
|
|