auto-coder 0.1.301__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.
- {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/METADATA +2 -1
- {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/RECORD +33 -25
- autocoder/auto_coder.py +6 -7
- autocoder/auto_coder_runner.py +9 -9
- autocoder/chat_auto_coder.py +6 -1
- autocoder/commands/auto_command.py +313 -205
- autocoder/commands/tools.py +123 -85
- autocoder/common/__init__.py +2 -0
- autocoder/common/action_yml_file_manager.py +28 -6
- autocoder/common/auto_coder_lang.py +2 -2
- autocoder/common/auto_configure.py +9 -4
- autocoder/common/code_auto_merge.py +1 -1
- autocoder/common/code_auto_merge_diff.py +1 -1
- autocoder/common/code_auto_merge_editblock.py +1 -1
- autocoder/common/code_auto_merge_strict_diff.py +1 -1
- autocoder/common/stream_out_type.py +7 -0
- autocoder/dispacher/actions/action.py +221 -101
- autocoder/dispacher/actions/plugins/action_regex_project.py +18 -0
- autocoder/events/__init__.py +57 -0
- autocoder/events/event_content.py +423 -0
- autocoder/events/event_manager.py +327 -0
- autocoder/events/event_manager_singleton.py +245 -0
- autocoder/events/event_store.py +376 -0
- autocoder/events/event_types.py +103 -0
- autocoder/index/entry.py +88 -60
- autocoder/index/filter/quick_filter.py +71 -3
- autocoder/run_context.py +62 -0
- autocoder/utils/auto_coder_utils/chat_stream_out.py +32 -1
- autocoder/version.py +1 -1
- {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.301.dist-info → auto_coder-0.1.303.dist-info}/top_level.txt +0 -0
autocoder/commands/tools.py
CHANGED
|
@@ -37,6 +37,9 @@ from autocoder.index.symbols_utils import (
|
|
|
37
37
|
SymbolType,
|
|
38
38
|
symbols_info_to_str,
|
|
39
39
|
)
|
|
40
|
+
from autocoder.run_context import get_run_context
|
|
41
|
+
from autocoder.events.event_manager_singleton import get_event_manager
|
|
42
|
+
from autocoder.events import event_content as EventContentCreator
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
@byzerllm.prompt()
|
|
@@ -51,6 +54,7 @@ def detect_rm_command(command: str) -> Bool:
|
|
|
51
54
|
如果该脚本中包含删除目录或者文件的命令,请返回True,否则返回False。
|
|
52
55
|
"""
|
|
53
56
|
|
|
57
|
+
|
|
54
58
|
@contextmanager
|
|
55
59
|
def redirect_stdout():
|
|
56
60
|
original_stdout = sys.stdout
|
|
@@ -58,10 +62,11 @@ def redirect_stdout():
|
|
|
58
62
|
try:
|
|
59
63
|
yield f
|
|
60
64
|
finally:
|
|
61
|
-
sys.stdout = original_stdout
|
|
65
|
+
sys.stdout = original_stdout
|
|
66
|
+
|
|
62
67
|
|
|
63
68
|
class AutoCommandTools:
|
|
64
|
-
def __init__(self, args: AutoCoderArgs,
|
|
69
|
+
def __init__(self, args: AutoCoderArgs,
|
|
65
70
|
llm: Union[byzerllm.ByzerLLM, byzerllm.SimpleByzerLLM]):
|
|
66
71
|
self.args = args
|
|
67
72
|
self.llm = llm
|
|
@@ -69,7 +74,7 @@ class AutoCommandTools:
|
|
|
69
74
|
self.printer = Printer()
|
|
70
75
|
|
|
71
76
|
def execute_mcp_server(self, query: str) -> str:
|
|
72
|
-
from autocoder.common.mcp_server import get_mcp_server, McpRequest, McpInstallRequest, McpRemoveRequest, McpListRequest, McpListRunningRequest, McpRefreshRequest
|
|
77
|
+
from autocoder.common.mcp_server import get_mcp_server, McpRequest, McpInstallRequest, McpRemoveRequest, McpListRequest, McpListRunningRequest, McpRefreshRequest
|
|
73
78
|
mcp_server = get_mcp_server()
|
|
74
79
|
response = mcp_server.send_request(
|
|
75
80
|
McpRequest(
|
|
@@ -78,10 +83,10 @@ class AutoCommandTools:
|
|
|
78
83
|
product_mode=self.args.product_mode
|
|
79
84
|
)
|
|
80
85
|
)
|
|
81
|
-
|
|
86
|
+
|
|
82
87
|
result = response.result
|
|
83
|
-
|
|
84
|
-
self.result_manager.append(content=result, meta
|
|
88
|
+
|
|
89
|
+
self.result_manager.append(content=result, meta={
|
|
85
90
|
"action": "execute_mcp_server",
|
|
86
91
|
"input": {
|
|
87
92
|
"query": query
|
|
@@ -89,8 +94,7 @@ class AutoCommandTools:
|
|
|
89
94
|
})
|
|
90
95
|
return result
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
def ask_user(self,question:str) -> str:
|
|
97
|
+
def ask_user(self, question: str) -> str:
|
|
94
98
|
'''
|
|
95
99
|
如果你对用户的问题有什么疑问,或者你想从用户收集一些额外信息,可以调用此方法。
|
|
96
100
|
输入参数 question 是你对用户的提问。
|
|
@@ -101,7 +105,7 @@ class AutoCommandTools:
|
|
|
101
105
|
|
|
102
106
|
if self.args.request_id and not self.args.silence and not self.args.skip_events:
|
|
103
107
|
event_data = {
|
|
104
|
-
"question": question
|
|
108
|
+
"question": question
|
|
105
109
|
}
|
|
106
110
|
response_json = queue_communicate.send_event(
|
|
107
111
|
request_id=self.args.request_id,
|
|
@@ -112,6 +116,18 @@ class AutoCommandTools:
|
|
|
112
116
|
)
|
|
113
117
|
return response_json
|
|
114
118
|
|
|
119
|
+
# 如果是在web模式下,则使用event_manager事件来询问用户
|
|
120
|
+
if get_run_context().is_web():
|
|
121
|
+
answer = get_event_manager(
|
|
122
|
+
self.args.event_file).ask_user(prompt=question)
|
|
123
|
+
self.result_manager.append(content=answer, meta={
|
|
124
|
+
"action": "ask_user",
|
|
125
|
+
"input": {
|
|
126
|
+
"question": question
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
return answer
|
|
130
|
+
|
|
115
131
|
console = Console()
|
|
116
132
|
|
|
117
133
|
# 创建一个醒目的问题面板
|
|
@@ -124,15 +140,16 @@ class AutoCommandTools:
|
|
|
124
140
|
)
|
|
125
141
|
|
|
126
142
|
# 显示问题面板
|
|
127
|
-
console.print(question_panel)
|
|
143
|
+
console.print(question_panel)
|
|
128
144
|
|
|
129
|
-
session = PromptSession(
|
|
145
|
+
session = PromptSession(
|
|
146
|
+
message=self.printer.get_message_from_key('tool_ask_user'))
|
|
130
147
|
try:
|
|
131
148
|
answer = session.prompt()
|
|
132
149
|
except KeyboardInterrupt:
|
|
133
|
-
answer = ""
|
|
150
|
+
answer = ""
|
|
134
151
|
|
|
135
|
-
self.result_manager.append(content=answer, meta
|
|
152
|
+
self.result_manager.append(content=answer, meta={
|
|
136
153
|
"action": "ask_user",
|
|
137
154
|
"input": {
|
|
138
155
|
"question": question
|
|
@@ -140,8 +157,27 @@ class AutoCommandTools:
|
|
|
140
157
|
})
|
|
141
158
|
|
|
142
159
|
return answer
|
|
143
|
-
|
|
160
|
+
|
|
144
161
|
def response_user(self, response: str):
|
|
162
|
+
|
|
163
|
+
# 如果是在web模式下,则使用event_manager事件来询问用户
|
|
164
|
+
if get_run_context().is_web():
|
|
165
|
+
answer = get_event_manager(
|
|
166
|
+
self.args.event_file).write_result(
|
|
167
|
+
EventContentCreator.create_result(
|
|
168
|
+
EventContentCreator.ResultSummaryContent(
|
|
169
|
+
summary=response
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
self.result_manager.append(content=response, meta={
|
|
174
|
+
"action": "response_user",
|
|
175
|
+
"input": {
|
|
176
|
+
"response": response
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
return answer
|
|
180
|
+
|
|
145
181
|
console = Console()
|
|
146
182
|
answer_text = Text(response, style="italic")
|
|
147
183
|
answer_panel = Panel(
|
|
@@ -152,7 +188,7 @@ class AutoCommandTools:
|
|
|
152
188
|
)
|
|
153
189
|
console.print(answer_panel)
|
|
154
190
|
|
|
155
|
-
self.result_manager.append(content=response, meta
|
|
191
|
+
self.result_manager.append(content=response, meta={
|
|
156
192
|
"action": "response_user",
|
|
157
193
|
"input": {
|
|
158
194
|
"response": response
|
|
@@ -160,7 +196,6 @@ class AutoCommandTools:
|
|
|
160
196
|
})
|
|
161
197
|
|
|
162
198
|
return response
|
|
163
|
-
|
|
164
199
|
|
|
165
200
|
def run_python_code(self, code: str) -> str:
|
|
166
201
|
"""
|
|
@@ -178,8 +213,8 @@ class AutoCommandTools:
|
|
|
178
213
|
)
|
|
179
214
|
finally:
|
|
180
215
|
interpreter.close()
|
|
181
|
-
|
|
182
|
-
self.result_manager.append(content=s, meta
|
|
216
|
+
|
|
217
|
+
self.result_manager.append(content=s, meta={
|
|
183
218
|
"action": "run_python_code",
|
|
184
219
|
"input": {
|
|
185
220
|
"code": code
|
|
@@ -206,8 +241,8 @@ class AutoCommandTools:
|
|
|
206
241
|
)
|
|
207
242
|
finally:
|
|
208
243
|
interpreter.close()
|
|
209
|
-
|
|
210
|
-
self.result_manager.append(content=s, meta
|
|
244
|
+
|
|
245
|
+
self.result_manager.append(content=s, meta={
|
|
211
246
|
"action": "run_shell_code",
|
|
212
247
|
"input": {
|
|
213
248
|
"script": script
|
|
@@ -215,13 +250,13 @@ class AutoCommandTools:
|
|
|
215
250
|
})
|
|
216
251
|
|
|
217
252
|
return s
|
|
218
|
-
|
|
253
|
+
|
|
219
254
|
def get_related_files_by_symbols(self, query: str) -> str:
|
|
220
255
|
"""
|
|
221
256
|
你可以给出类名,函数名,以及文件的用途描述等信息,该工具会根据这些信息返回项目中相关的文件。
|
|
222
257
|
"""
|
|
223
258
|
v = self.get_project_related_files(query)
|
|
224
|
-
self.result_manager.append(content=v, meta
|
|
259
|
+
self.result_manager.append(content=v, meta={
|
|
225
260
|
"action": "get_related_files_by_symbols",
|
|
226
261
|
"input": {
|
|
227
262
|
"query": query
|
|
@@ -245,18 +280,18 @@ class AutoCommandTools:
|
|
|
245
280
|
pp.run()
|
|
246
281
|
sources = pp.sources
|
|
247
282
|
|
|
248
|
-
index_manager = IndexManager(
|
|
283
|
+
index_manager = IndexManager(
|
|
284
|
+
llm=self.llm, sources=sources, args=self.args)
|
|
249
285
|
target_files = index_manager.get_target_files_by_query(query)
|
|
250
286
|
file_list = target_files.file_list
|
|
251
|
-
v =
|
|
252
|
-
self.result_manager.append(content=v, meta
|
|
287
|
+
v = ",".join([file.file_path for file in file_list])
|
|
288
|
+
self.result_manager.append(content=v, meta={
|
|
253
289
|
"action": "get_project_related_files",
|
|
254
290
|
"input": {
|
|
255
291
|
"query": query
|
|
256
292
|
}
|
|
257
293
|
})
|
|
258
294
|
return v
|
|
259
|
-
|
|
260
295
|
|
|
261
296
|
def _get_sources(self):
|
|
262
297
|
if self.args.project_type == "ts":
|
|
@@ -268,11 +303,12 @@ class AutoCommandTools:
|
|
|
268
303
|
pp.run()
|
|
269
304
|
return pp.sources
|
|
270
305
|
|
|
271
|
-
def _get_index(self):
|
|
306
|
+
def _get_index(self):
|
|
272
307
|
sources = self._get_sources()
|
|
273
|
-
index_manager = IndexManager(
|
|
308
|
+
index_manager = IndexManager(
|
|
309
|
+
llm=self.llm, sources=sources, args=self.args)
|
|
274
310
|
return index_manager
|
|
275
|
-
|
|
311
|
+
|
|
276
312
|
def get_project_map(self, file_paths: Optional[str] = None) -> str:
|
|
277
313
|
"""
|
|
278
314
|
该工具会返回项目中所有已经被构建索引的文件以及该文件的信息,诸如该文件的用途,导入的包,定义的类,函数,变量等信息。
|
|
@@ -284,18 +320,18 @@ class AutoCommandTools:
|
|
|
284
320
|
|
|
285
321
|
注意,这个工具无法返回所有文件的信息,因为有些文件可能没有被索引。
|
|
286
322
|
尽量避免使用该工具。
|
|
287
|
-
"""
|
|
323
|
+
"""
|
|
288
324
|
index_manager = self._get_index()
|
|
289
325
|
s = index_manager.read_index_as_str()
|
|
290
326
|
index_data = json.loads(s)
|
|
291
327
|
|
|
292
328
|
final_result = []
|
|
293
|
-
|
|
329
|
+
|
|
294
330
|
# 解析文件路径列表(如果提供了)
|
|
295
331
|
file_path_list = []
|
|
296
332
|
if file_paths:
|
|
297
333
|
file_path_list = [path.strip() for path in file_paths.split(",")]
|
|
298
|
-
|
|
334
|
+
|
|
299
335
|
for k in index_data.values():
|
|
300
336
|
value = {}
|
|
301
337
|
value["file_name"] = k["module_name"]
|
|
@@ -304,19 +340,19 @@ class AutoCommandTools:
|
|
|
304
340
|
value["index_tokens"] = k.get("generated_tokens_count", -1)
|
|
305
341
|
value["file_tokens_cost"] = k.get("input_tokens_cost", -1)
|
|
306
342
|
value["index_tokens_cost"] = k.get("generated_tokens_cost", -1)
|
|
307
|
-
|
|
343
|
+
|
|
308
344
|
# 如果提供了文件路径列表,检查当前文件是否匹配任何一个路径
|
|
309
345
|
if file_path_list:
|
|
310
346
|
if any(path in k["module_name"] for path in file_path_list):
|
|
311
347
|
final_result.append(value)
|
|
312
348
|
else:
|
|
313
349
|
final_result.append(value)
|
|
314
|
-
|
|
350
|
+
|
|
315
351
|
v = json.dumps(final_result, ensure_ascii=False)
|
|
316
352
|
tokens = count_tokens(v)
|
|
317
353
|
if tokens > self.args.conversation_prune_safe_zone_tokens/2.0:
|
|
318
354
|
result = f"The project map is too large to return. (tokens: {tokens}). Try to use another function."
|
|
319
|
-
self.result_manager.add_result(content=result, meta
|
|
355
|
+
self.result_manager.add_result(content=result, meta={
|
|
320
356
|
"action": "get_project_map",
|
|
321
357
|
"input": {
|
|
322
358
|
"file_paths": file_paths
|
|
@@ -324,15 +360,15 @@ class AutoCommandTools:
|
|
|
324
360
|
})
|
|
325
361
|
return result
|
|
326
362
|
|
|
327
|
-
self.result_manager.add_result(content=v, meta
|
|
363
|
+
self.result_manager.add_result(content=v, meta={
|
|
328
364
|
"action": "get_project_map",
|
|
329
365
|
"input": {
|
|
330
366
|
"file_paths": file_paths
|
|
331
367
|
}
|
|
332
368
|
})
|
|
333
369
|
return v
|
|
334
|
-
|
|
335
|
-
def read_file_with_keyword_ranges(self, file_path: str, keyword:str, before_size:int = 100, after_size:int = 100) -> str:
|
|
370
|
+
|
|
371
|
+
def read_file_with_keyword_ranges(self, file_path: str, keyword: str, before_size: int = 100, after_size: int = 100) -> str:
|
|
336
372
|
"""
|
|
337
373
|
该函数用于读取包含了关键字(keyword)的行,以及该行前后指定大小的行。
|
|
338
374
|
输入参数:
|
|
@@ -340,7 +376,7 @@ class AutoCommandTools:
|
|
|
340
376
|
- keyword: 关键字
|
|
341
377
|
- before_size: 关键字所在行之前的行数
|
|
342
378
|
- after_size: 关键字所在行之后的行数
|
|
343
|
-
|
|
379
|
+
|
|
344
380
|
返回值:
|
|
345
381
|
- 返回str类型,返回包含关键字的行,以及该行前后指定大小的行。
|
|
346
382
|
|
|
@@ -348,7 +384,7 @@ class AutoCommandTools:
|
|
|
348
384
|
```
|
|
349
385
|
##File: /path/to/file.py
|
|
350
386
|
##Line: 10-20
|
|
351
|
-
|
|
387
|
+
|
|
352
388
|
内容
|
|
353
389
|
```
|
|
354
390
|
"""
|
|
@@ -363,34 +399,34 @@ class AutoCommandTools:
|
|
|
363
399
|
|
|
364
400
|
result = []
|
|
365
401
|
try:
|
|
366
|
-
|
|
367
|
-
lines = files_utils.read_lines(absolute_path)
|
|
402
|
+
|
|
403
|
+
lines = files_utils.read_lines(absolute_path)
|
|
368
404
|
# Find all lines containing the keyword
|
|
369
405
|
keyword_lines = []
|
|
370
406
|
for i, line in enumerate(lines):
|
|
371
407
|
if keyword.lower() in line.lower():
|
|
372
408
|
keyword_lines.append(i)
|
|
373
|
-
|
|
409
|
+
|
|
374
410
|
# Process each keyword line and its surrounding range
|
|
375
411
|
processed_ranges = set()
|
|
376
412
|
for line_num in keyword_lines:
|
|
377
413
|
# Calculate range boundaries
|
|
378
414
|
start = max(0, line_num - before_size)
|
|
379
415
|
end = min(len(lines), line_num + after_size + 1)
|
|
380
|
-
|
|
416
|
+
|
|
381
417
|
# Check if this range overlaps with any previously processed range
|
|
382
418
|
range_key = (start, end)
|
|
383
419
|
if range_key in processed_ranges:
|
|
384
420
|
continue
|
|
385
|
-
|
|
421
|
+
|
|
386
422
|
processed_ranges.add(range_key)
|
|
387
|
-
|
|
423
|
+
|
|
388
424
|
# Format the content block
|
|
389
425
|
content = f"##File: {absolute_path}\n"
|
|
390
426
|
content += f"##Line: {start+1}-{end}\n\n"
|
|
391
427
|
content += "".join(lines[start:end])
|
|
392
428
|
result.append(content)
|
|
393
|
-
|
|
429
|
+
|
|
394
430
|
except Exception as e:
|
|
395
431
|
v = f"Error reading file {absolute_path}: {str(e)}"
|
|
396
432
|
self.result_manager.add_result(content=v, meta={
|
|
@@ -403,7 +439,7 @@ class AutoCommandTools:
|
|
|
403
439
|
}
|
|
404
440
|
})
|
|
405
441
|
return v
|
|
406
|
-
|
|
442
|
+
|
|
407
443
|
final_result = "\n\n".join(result)
|
|
408
444
|
self.result_manager.add_result(content=final_result, meta={
|
|
409
445
|
"action": "read_file_with_keyword_ranges",
|
|
@@ -414,7 +450,7 @@ class AutoCommandTools:
|
|
|
414
450
|
"after_size": after_size
|
|
415
451
|
}
|
|
416
452
|
})
|
|
417
|
-
|
|
453
|
+
|
|
418
454
|
return final_result
|
|
419
455
|
|
|
420
456
|
def read_files(self, paths: str, line_ranges: Optional[str] = None) -> str:
|
|
@@ -447,21 +483,22 @@ class AutoCommandTools:
|
|
|
447
483
|
"""
|
|
448
484
|
paths = [p.strip() for p in paths.split(",")]
|
|
449
485
|
source_code_str = ""
|
|
450
|
-
|
|
486
|
+
|
|
451
487
|
# Parse line ranges if provided
|
|
452
488
|
file_line_ranges = {}
|
|
453
489
|
if line_ranges:
|
|
454
490
|
ranges_per_file = line_ranges.split(",")
|
|
455
491
|
if len(ranges_per_file) != len(paths):
|
|
456
|
-
self.result_manager.add_result(content="Number of line ranges must match number of files", meta
|
|
492
|
+
self.result_manager.add_result(content="Number of line ranges must match number of files", meta={
|
|
457
493
|
"action": "read_files",
|
|
458
494
|
"input": {
|
|
459
495
|
"paths": paths,
|
|
460
496
|
"line_ranges": line_ranges
|
|
461
497
|
}
|
|
462
498
|
})
|
|
463
|
-
raise ValueError(
|
|
464
|
-
|
|
499
|
+
raise ValueError(
|
|
500
|
+
"Number of line ranges must match number of files")
|
|
501
|
+
|
|
465
502
|
for path, ranges in zip(paths, ranges_per_file):
|
|
466
503
|
file_line_ranges[path] = []
|
|
467
504
|
for range_str in ranges.split("/"):
|
|
@@ -479,7 +516,7 @@ class AutoCommandTools:
|
|
|
479
516
|
if path in os.path.join(root, file):
|
|
480
517
|
absolute_path = os.path.join(root, file)
|
|
481
518
|
break
|
|
482
|
-
|
|
519
|
+
|
|
483
520
|
if path in file_line_ranges:
|
|
484
521
|
# Read specific line ranges
|
|
485
522
|
lines = files_utils.read_lines(absolute_path)
|
|
@@ -489,7 +526,8 @@ class AutoCommandTools:
|
|
|
489
526
|
start = max(0, start - 1)
|
|
490
527
|
end = min(len(lines), end)
|
|
491
528
|
content = "".join(lines[start:end])
|
|
492
|
-
filtered_lines.extend(
|
|
529
|
+
filtered_lines.extend(
|
|
530
|
+
f"##File: {absolute_path}\n##Line: {start}-{end}\n\n{content}")
|
|
493
531
|
source_code = "".join(filtered_lines)
|
|
494
532
|
# Add source_code to source_code_str
|
|
495
533
|
source_code_str += source_code
|
|
@@ -497,11 +535,12 @@ class AutoCommandTools:
|
|
|
497
535
|
# Read entire file if no range specified
|
|
498
536
|
content = files_utils.read_file(absolute_path)
|
|
499
537
|
source_code = f"##File: {absolute_path}\n\n{content}"
|
|
500
|
-
|
|
501
|
-
sc = SourceCode(module_name=absolute_path,
|
|
538
|
+
|
|
539
|
+
sc = SourceCode(module_name=absolute_path,
|
|
540
|
+
source_code=source_code)
|
|
502
541
|
source_code_str += f"{sc.source_code}\n\n"
|
|
503
|
-
|
|
504
|
-
self.result_manager.add_result(content=source_code_str, meta
|
|
542
|
+
|
|
543
|
+
self.result_manager.add_result(content=source_code_str, meta={
|
|
505
544
|
"action": "read_files",
|
|
506
545
|
"input": {
|
|
507
546
|
"paths": paths,
|
|
@@ -519,24 +558,24 @@ class AutoCommandTools:
|
|
|
519
558
|
index_manager = self._get_index()
|
|
520
559
|
result = []
|
|
521
560
|
index_items = index_manager.read_index()
|
|
522
|
-
|
|
561
|
+
|
|
523
562
|
for item in index_items:
|
|
524
563
|
symbols = extract_symbols(item.symbols)
|
|
525
564
|
for symbol_info in symbols:
|
|
526
565
|
# 进行精确匹配和模糊匹配
|
|
527
|
-
if (symbol_info.name == symbol or
|
|
528
|
-
|
|
566
|
+
if (symbol_info.name == symbol or
|
|
567
|
+
symbol.lower() in symbol_info.name.lower()):
|
|
529
568
|
# 检查是否已经添加过该文件路径
|
|
530
569
|
if symbol_info.module_name not in result:
|
|
531
570
|
result.append(symbol_info.module_name)
|
|
532
|
-
|
|
571
|
+
|
|
533
572
|
# 生成以逗号分隔的文件路径列表
|
|
534
573
|
file_paths = ",".join(result)
|
|
535
|
-
|
|
574
|
+
|
|
536
575
|
# 如果没有找到任何匹配项,返回提示信息
|
|
537
576
|
if not file_paths:
|
|
538
577
|
file_paths = f"未找到符号 '{symbol}' 的定义"
|
|
539
|
-
|
|
578
|
+
|
|
540
579
|
# 记录操作结果
|
|
541
580
|
self.result_manager.add_result(content=file_paths, meta={
|
|
542
581
|
"action": "find_symbols_definition",
|
|
@@ -544,7 +583,7 @@ class AutoCommandTools:
|
|
|
544
583
|
"symbol": symbol
|
|
545
584
|
}
|
|
546
585
|
})
|
|
547
|
-
|
|
586
|
+
|
|
548
587
|
return file_paths
|
|
549
588
|
|
|
550
589
|
def list_files(self, path: str) -> str:
|
|
@@ -558,7 +597,7 @@ class AutoCommandTools:
|
|
|
558
597
|
if not os.path.isabs(path):
|
|
559
598
|
# 如果是相对路径,将其转换为绝对路径
|
|
560
599
|
target_path = os.path.join(self.args.source_dir, path)
|
|
561
|
-
|
|
600
|
+
|
|
562
601
|
# 确保路径存在且是目录
|
|
563
602
|
if not os.path.exists(target_path):
|
|
564
603
|
result = f"目录不存在: {target_path}"
|
|
@@ -569,7 +608,7 @@ class AutoCommandTools:
|
|
|
569
608
|
}
|
|
570
609
|
})
|
|
571
610
|
return result
|
|
572
|
-
|
|
611
|
+
|
|
573
612
|
if not os.path.isdir(target_path):
|
|
574
613
|
result = f"指定路径不是目录: {target_path}"
|
|
575
614
|
self.result_manager.add_result(content=result, meta={
|
|
@@ -579,7 +618,7 @@ class AutoCommandTools:
|
|
|
579
618
|
}
|
|
580
619
|
})
|
|
581
620
|
return result
|
|
582
|
-
|
|
621
|
+
|
|
583
622
|
# 只收集当前目录下的文件,不递归子目录
|
|
584
623
|
file_list = []
|
|
585
624
|
for item in os.listdir(target_path):
|
|
@@ -587,10 +626,10 @@ class AutoCommandTools:
|
|
|
587
626
|
# 只添加文件,不添加目录
|
|
588
627
|
if os.path.isfile(item_path):
|
|
589
628
|
file_list.append(item_path)
|
|
590
|
-
|
|
629
|
+
|
|
591
630
|
# 生成以逗号分隔的文件列表
|
|
592
631
|
result = ",".join(file_list)
|
|
593
|
-
|
|
632
|
+
|
|
594
633
|
# 记录结果
|
|
595
634
|
self.result_manager.add_result(content=result, meta={
|
|
596
635
|
"action": "list_files",
|
|
@@ -598,10 +637,10 @@ class AutoCommandTools:
|
|
|
598
637
|
"path": path
|
|
599
638
|
}
|
|
600
639
|
})
|
|
601
|
-
|
|
640
|
+
|
|
602
641
|
return result
|
|
603
642
|
|
|
604
|
-
def get_project_structure(self) -> str:
|
|
643
|
+
def get_project_structure(self) -> str:
|
|
605
644
|
if self.args.project_type == "ts":
|
|
606
645
|
pp = TSProject(args=self.args, llm=self.llm)
|
|
607
646
|
elif self.args.project_type == "py":
|
|
@@ -610,25 +649,24 @@ class AutoCommandTools:
|
|
|
610
649
|
pp = SuffixProject(args=self.args, llm=self.llm, file_filter=None)
|
|
611
650
|
pp.run()
|
|
612
651
|
s = pp.get_tree_like_directory_structure()
|
|
613
|
-
|
|
652
|
+
|
|
614
653
|
tokens = count_tokens(s)
|
|
615
654
|
if tokens > self.args.conversation_prune_safe_zone_tokens / 2.0:
|
|
616
655
|
result = f"The project structure is too large to return. (tokens: {tokens}). Try to use another function."
|
|
617
|
-
self.result_manager.add_result(content=result, meta
|
|
656
|
+
self.result_manager.add_result(content=result, meta={
|
|
618
657
|
"action": "get_project_structure",
|
|
619
658
|
"input": {
|
|
620
659
|
}
|
|
621
660
|
})
|
|
622
661
|
return result
|
|
623
662
|
|
|
624
|
-
self.result_manager.add_result(content=s, meta
|
|
663
|
+
self.result_manager.add_result(content=s, meta={
|
|
625
664
|
"action": "get_project_structure",
|
|
626
665
|
"input": {
|
|
627
666
|
}
|
|
628
667
|
})
|
|
629
668
|
return s
|
|
630
669
|
|
|
631
|
-
|
|
632
670
|
def find_files_by_name(self, keyword: str) -> str:
|
|
633
671
|
"""
|
|
634
672
|
根据关键字在项目中搜索文件名。
|
|
@@ -645,14 +683,14 @@ class AutoCommandTools:
|
|
|
645
683
|
matched_files.append(os.path.join(root, file))
|
|
646
684
|
|
|
647
685
|
v = ",".join(matched_files)
|
|
648
|
-
self.result_manager.add_result(content=v, meta
|
|
686
|
+
self.result_manager.add_result(content=v, meta={
|
|
649
687
|
"action": "find_files_by_name",
|
|
650
688
|
"input": {
|
|
651
689
|
"keyword": keyword
|
|
652
690
|
}
|
|
653
691
|
})
|
|
654
692
|
return v
|
|
655
|
-
|
|
693
|
+
|
|
656
694
|
def count_file_tokens(self, file_path: str) -> int:
|
|
657
695
|
"""
|
|
658
696
|
该工具用于计算指定文件的token数量。
|
|
@@ -691,18 +729,18 @@ class AutoCommandTools:
|
|
|
691
729
|
excluded_file_patterns = [
|
|
692
730
|
'*.pyc', '*.pyo', '*.pyd', '*.egg-info', '*.log'
|
|
693
731
|
]
|
|
694
|
-
|
|
732
|
+
|
|
695
733
|
matched_files = []
|
|
696
|
-
|
|
734
|
+
|
|
697
735
|
for root, dirs, files in os.walk(self.args.source_dir):
|
|
698
736
|
# 移除需要排除的目录
|
|
699
737
|
dirs[:] = [d for d in dirs if d not in excluded_dirs]
|
|
700
|
-
|
|
738
|
+
|
|
701
739
|
# 过滤掉需要排除的文件
|
|
702
740
|
files[:] = [f for f in files if not any(
|
|
703
741
|
f.endswith(pattern[1:]) for pattern in excluded_file_patterns
|
|
704
742
|
)]
|
|
705
|
-
|
|
743
|
+
|
|
706
744
|
for file in files:
|
|
707
745
|
file_path = os.path.join(root, file)
|
|
708
746
|
try:
|
|
@@ -719,10 +757,10 @@ class AutoCommandTools:
|
|
|
719
757
|
break
|
|
720
758
|
|
|
721
759
|
v = ",".join(matched_files[:10])
|
|
722
|
-
self.result_manager.add_result(content=v, meta
|
|
760
|
+
self.result_manager.add_result(content=v, meta={
|
|
723
761
|
"action": "find_files_by_content",
|
|
724
762
|
"input": {
|
|
725
763
|
"keyword": keyword
|
|
726
764
|
}
|
|
727
765
|
})
|
|
728
|
-
return v
|
|
766
|
+
return v
|
autocoder/common/__init__.py
CHANGED
|
@@ -249,7 +249,7 @@ class ActionYmlFileManager:
|
|
|
249
249
|
with open(yaml_path, 'r', encoding='utf-8') as f:
|
|
250
250
|
yaml_content = f.read()
|
|
251
251
|
file_md5 = hashlib.md5(yaml_content.encode("utf-8")).hexdigest()
|
|
252
|
-
return f"auto_coder_{file_name}
|
|
252
|
+
return f"auto_coder_{file_name}"
|
|
253
253
|
except Exception as e:
|
|
254
254
|
logger.error(f"Failed to calculate commit ID: {e}")
|
|
255
255
|
return None
|
|
@@ -269,15 +269,37 @@ class ActionYmlFileManager:
|
|
|
269
269
|
|
|
270
270
|
try:
|
|
271
271
|
# auto_coder_000000001926_chat_action.yml_88614d5bd4046a068786c252fbc39c13
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
return "_".join(
|
|
272
|
+
# auto_coder_000000001926_chat_action.yml
|
|
273
|
+
if commit_id.endswith("_chat_action.yml"):
|
|
274
|
+
return commit_id[len("auto_coder_"):]
|
|
275
|
+
else:
|
|
276
|
+
return "_".join(commit_id[len("auto_coder_"):].split("_")[0:-1])
|
|
277
277
|
return None
|
|
278
278
|
except Exception:
|
|
279
279
|
return None
|
|
280
280
|
|
|
281
|
+
def get_file_name_from_commit_msg(self, commit_msg: str) -> Optional[str]:
|
|
282
|
+
"""
|
|
283
|
+
从 commit 消息中提取文件名,获取消息最后一行,然后调用 get_file_name_from_commit_id
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
commit_msg: commit 消息内容
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Optional[str]: 文件名,如果提取失败返回 None
|
|
290
|
+
"""
|
|
291
|
+
if not commit_msg:
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
try:
|
|
295
|
+
# 获取消息的最后一行
|
|
296
|
+
last_line = commit_msg.strip().split('\n')[-1]
|
|
297
|
+
# 调用 get_file_name_from_commit_id 从最后一行提取文件名
|
|
298
|
+
return self.get_file_name_from_commit_id(last_line)
|
|
299
|
+
except Exception as e:
|
|
300
|
+
logger.error(f"Failed to extract file name from commit message: {e}")
|
|
301
|
+
return None
|
|
302
|
+
|
|
281
303
|
def get_commit_changes(self, file_name: Optional[str] = None) -> List[Tuple[str, List[str], Dict[str, Tuple[str, str]]]]:
|
|
282
304
|
"""
|
|
283
305
|
获取与特定文件相关的 commit 变更
|
|
@@ -195,7 +195,7 @@ MESSAGES = {
|
|
|
195
195
|
"mcp_server_info_error": "Error getting MCP server info: {{ error }}",
|
|
196
196
|
"mcp_server_info_title": "Connected MCP Server Info",
|
|
197
197
|
"no_commit_file_name": "Cannot get the file name of the commit_id in the actions directory: {{commit_id}}",
|
|
198
|
-
"yaml_update_success": "✅ Successfully updated YAML file: {{yaml_file}}
|
|
198
|
+
"yaml_update_success": "✅ Successfully updated YAML file: {{yaml_file}}",
|
|
199
199
|
"yaml_save_error": "❌ Error saving YAML file {{yaml_file}}: {{error}}",
|
|
200
200
|
},
|
|
201
201
|
"zh": {
|
|
@@ -389,7 +389,7 @@ MESSAGES = {
|
|
|
389
389
|
"mcp_server_info_error": "获取MCP服务器信息时出错: {{ error }}",
|
|
390
390
|
"mcp_server_info_title": "已连接的MCP服务器信息",
|
|
391
391
|
"no_commit_file_name": "无法获取commit_id关联的actions 目录下的文件名: {{commit_id}}",
|
|
392
|
-
"yaml_update_success": "✅ 成功更新YAML文件: {{yaml_file}}
|
|
392
|
+
"yaml_update_success": "✅ 成功更新YAML文件: {{yaml_file}}",
|
|
393
393
|
"yaml_save_error": "❌ 保存YAML文件出错 {{yaml_file}}: {{error}}",
|
|
394
394
|
}}
|
|
395
395
|
|