auto-coder 0.1.398__py3-none-any.whl → 0.1.399__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.399.dist-info/METADATA +396 -0
- {auto_coder-0.1.398.dist-info → auto_coder-0.1.399.dist-info}/RECORD +62 -28
- {auto_coder-0.1.398.dist-info → auto_coder-0.1.399.dist-info}/WHEEL +1 -1
- {auto_coder-0.1.398.dist-info → auto_coder-0.1.399.dist-info}/entry_points.txt +2 -0
- autocoder/agent/base_agentic/base_agent.py +2 -2
- autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +1 -1
- autocoder/agent/entry_command_agent/__init__.py +29 -0
- autocoder/agent/entry_command_agent/auto_tool.py +61 -0
- autocoder/agent/entry_command_agent/chat.py +475 -0
- autocoder/agent/entry_command_agent/designer.py +53 -0
- autocoder/agent/entry_command_agent/generate_command.py +50 -0
- autocoder/agent/entry_command_agent/project_reader.py +58 -0
- autocoder/agent/entry_command_agent/voice2text.py +71 -0
- autocoder/auto_coder.py +23 -548
- autocoder/auto_coder_runner.py +510 -8
- autocoder/chat/rules_command.py +1 -1
- autocoder/chat_auto_coder.py +6 -1
- autocoder/common/ac_style_command_parser/__init__.py +15 -0
- autocoder/common/ac_style_command_parser/example.py +7 -0
- autocoder/{command_parser.py → common/ac_style_command_parser/parser.py} +1 -33
- autocoder/common/ac_style_command_parser/test_parser.py +516 -0
- autocoder/common/command_completer_v2.py +1 -1
- autocoder/common/command_file_manager/examples.py +22 -8
- autocoder/common/command_file_manager/manager.py +37 -6
- autocoder/common/conversations/get_conversation_manager.py +143 -0
- autocoder/common/conversations/manager.py +122 -11
- autocoder/common/conversations/storage/index_manager.py +89 -0
- autocoder/common/v2/agent/agentic_edit.py +131 -18
- autocoder/common/v2/agent/agentic_edit_types.py +10 -0
- autocoder/common/v2/code_auto_generate_editblock.py +10 -2
- autocoder/dispacher/__init__.py +10 -0
- autocoder/rags.py +0 -27
- autocoder/run_context.py +1 -0
- autocoder/sdk/__init__.py +188 -0
- autocoder/sdk/cli/__init__.py +15 -0
- autocoder/sdk/cli/__main__.py +26 -0
- autocoder/sdk/cli/completion_wrapper.py +38 -0
- autocoder/sdk/cli/formatters.py +211 -0
- autocoder/sdk/cli/handlers.py +174 -0
- autocoder/sdk/cli/install_completion.py +301 -0
- autocoder/sdk/cli/main.py +284 -0
- autocoder/sdk/cli/options.py +72 -0
- autocoder/sdk/constants.py +102 -0
- autocoder/sdk/core/__init__.py +20 -0
- autocoder/sdk/core/auto_coder_core.py +867 -0
- autocoder/sdk/core/bridge.py +497 -0
- autocoder/sdk/example.py +0 -0
- autocoder/sdk/exceptions.py +72 -0
- autocoder/sdk/models/__init__.py +19 -0
- autocoder/sdk/models/messages.py +209 -0
- autocoder/sdk/models/options.py +194 -0
- autocoder/sdk/models/responses.py +311 -0
- autocoder/sdk/session/__init__.py +32 -0
- autocoder/sdk/session/session.py +106 -0
- autocoder/sdk/session/session_manager.py +56 -0
- autocoder/sdk/utils/__init__.py +24 -0
- autocoder/sdk/utils/formatters.py +216 -0
- autocoder/sdk/utils/io_utils.py +302 -0
- autocoder/sdk/utils/validators.py +287 -0
- autocoder/version.py +2 -1
- auto_coder-0.1.398.dist-info/METADATA +0 -111
- autocoder/common/conversations/compatibility.py +0 -303
- autocoder/common/conversations/conversation_manager.py +0 -502
- autocoder/common/conversations/example.py +0 -152
- {auto_coder-0.1.398.dist-info → auto_coder-0.1.399.dist-info/licenses}/LICENSE +0 -0
- {auto_coder-0.1.398.dist-info → auto_coder-0.1.399.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
|
|
2
|
+
import pytest
|
|
3
|
+
from typing import Dict, List, Any
|
|
4
|
+
|
|
5
|
+
# 导入被测模块
|
|
6
|
+
from .parser import (
|
|
7
|
+
CommandParser,
|
|
8
|
+
parse_query,
|
|
9
|
+
has_command,
|
|
10
|
+
get_command_args,
|
|
11
|
+
get_command_kwargs
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestCommandParser:
|
|
16
|
+
"""CommandParser类的单元测试"""
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def parser(self):
|
|
20
|
+
"""提供CommandParser实例"""
|
|
21
|
+
return CommandParser()
|
|
22
|
+
|
|
23
|
+
def test_init(self, parser):
|
|
24
|
+
"""测试CommandParser初始化"""
|
|
25
|
+
assert parser is not None
|
|
26
|
+
assert hasattr(parser, 'command_pattern')
|
|
27
|
+
assert hasattr(parser, 'key_value_pattern')
|
|
28
|
+
assert hasattr(parser, 'path_pattern')
|
|
29
|
+
|
|
30
|
+
def test_empty_query(self, parser):
|
|
31
|
+
"""测试空查询字符串"""
|
|
32
|
+
assert parser.parse("") == {}
|
|
33
|
+
assert parser.parse(" ") == {}
|
|
34
|
+
assert parser.parse(None) == {}
|
|
35
|
+
|
|
36
|
+
def test_single_command_no_args(self, parser):
|
|
37
|
+
"""测试单个命令无参数"""
|
|
38
|
+
result = parser.parse("/help")
|
|
39
|
+
expected = {
|
|
40
|
+
"help": {
|
|
41
|
+
"args": [],
|
|
42
|
+
"kwargs": {}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
assert result == expected
|
|
46
|
+
|
|
47
|
+
def test_single_command_with_args(self, parser):
|
|
48
|
+
"""测试单个命令带位置参数"""
|
|
49
|
+
result = parser.parse("/add file1.txt file2.py")
|
|
50
|
+
expected = {
|
|
51
|
+
"add": {
|
|
52
|
+
"args": ["file1.txt", "file2.py"],
|
|
53
|
+
"kwargs": {}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
assert result == expected
|
|
57
|
+
|
|
58
|
+
def test_single_command_with_kwargs(self, parser):
|
|
59
|
+
"""测试单个命令带键值对参数"""
|
|
60
|
+
result = parser.parse("/config model=gpt-4 temperature=0.7")
|
|
61
|
+
expected = {
|
|
62
|
+
"config": {
|
|
63
|
+
"args": [],
|
|
64
|
+
"kwargs": {
|
|
65
|
+
"model": "gpt-4",
|
|
66
|
+
"temperature": "0.7"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
assert result == expected
|
|
71
|
+
|
|
72
|
+
def test_single_command_mixed_params(self, parser):
|
|
73
|
+
"""测试单个命令混合参数"""
|
|
74
|
+
result = parser.parse("/deploy myapp env=prod version=1.0")
|
|
75
|
+
expected = {
|
|
76
|
+
"deploy": {
|
|
77
|
+
"args": ["myapp"],
|
|
78
|
+
"kwargs": {
|
|
79
|
+
"env": "prod",
|
|
80
|
+
"version": "1.0"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
assert result == expected
|
|
85
|
+
|
|
86
|
+
def test_quoted_args_double_quotes(self, parser):
|
|
87
|
+
"""测试双引号参数"""
|
|
88
|
+
result = parser.parse('/say "hello world" "this is a test"')
|
|
89
|
+
expected = {
|
|
90
|
+
"say": {
|
|
91
|
+
"args": ["hello world", "this is a test"],
|
|
92
|
+
"kwargs": {}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
assert result == expected
|
|
96
|
+
|
|
97
|
+
def test_quoted_args_single_quotes(self, parser):
|
|
98
|
+
"""测试单引号参数"""
|
|
99
|
+
result = parser.parse("/say 'hello world' 'this is a test'")
|
|
100
|
+
expected = {
|
|
101
|
+
"say": {
|
|
102
|
+
"args": ["hello world", "this is a test"],
|
|
103
|
+
"kwargs": {}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
assert result == expected
|
|
107
|
+
|
|
108
|
+
def test_quoted_kwargs(self, parser):
|
|
109
|
+
"""测试带引号的键值对参数"""
|
|
110
|
+
result = parser.parse('/config message="hello world" path=\'/tmp/test dir\'')
|
|
111
|
+
expected = {
|
|
112
|
+
"config": {
|
|
113
|
+
"args": [],
|
|
114
|
+
"kwargs": {
|
|
115
|
+
"message": "hello world",
|
|
116
|
+
"path": "/tmp/test dir"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
assert result == expected
|
|
121
|
+
|
|
122
|
+
def test_multiple_commands(self, parser):
|
|
123
|
+
"""测试多个命令"""
|
|
124
|
+
result = parser.parse("/add file1.txt /remove file2.txt")
|
|
125
|
+
expected = {
|
|
126
|
+
"add": {
|
|
127
|
+
"args": ["file1.txt"],
|
|
128
|
+
"kwargs": {}
|
|
129
|
+
},
|
|
130
|
+
"remove": {
|
|
131
|
+
"args": ["file2.txt"],
|
|
132
|
+
"kwargs": {}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
assert result == expected
|
|
136
|
+
|
|
137
|
+
def test_multiple_commands_with_mixed_params(self, parser):
|
|
138
|
+
"""测试多个命令带混合参数"""
|
|
139
|
+
result = parser.parse("/deploy app1 env=prod /config model=gpt-4 /status")
|
|
140
|
+
expected = {
|
|
141
|
+
"deploy": {
|
|
142
|
+
"args": ["app1"],
|
|
143
|
+
"kwargs": {"env": "prod"}
|
|
144
|
+
},
|
|
145
|
+
"config": {
|
|
146
|
+
"args": [],
|
|
147
|
+
"kwargs": {"model": "gpt-4"}
|
|
148
|
+
},
|
|
149
|
+
"status": {
|
|
150
|
+
"args": [],
|
|
151
|
+
"kwargs": {}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
assert result == expected
|
|
155
|
+
|
|
156
|
+
def test_path_not_recognized_as_command(self, parser):
|
|
157
|
+
"""测试路径不被识别为命令"""
|
|
158
|
+
result = parser.parse("/add /path/to/file.txt /config model=gpt-4")
|
|
159
|
+
expected = {
|
|
160
|
+
"add": {
|
|
161
|
+
"args": ["/path/to/file.txt"],
|
|
162
|
+
"kwargs": {}
|
|
163
|
+
},
|
|
164
|
+
"config": {
|
|
165
|
+
"args": [],
|
|
166
|
+
"kwargs": {"model": "gpt-4"}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
assert result == expected
|
|
170
|
+
|
|
171
|
+
def test_complex_path_handling(self, parser):
|
|
172
|
+
"""测试复杂路径处理"""
|
|
173
|
+
result = parser.parse("/analyze /home/user/project/src/main.py /output /tmp/results")
|
|
174
|
+
expected = {
|
|
175
|
+
"analyze": {
|
|
176
|
+
"args": ["/home/user/project/src/main.py"],
|
|
177
|
+
"kwargs": {}
|
|
178
|
+
},
|
|
179
|
+
"output": {
|
|
180
|
+
"args": ["/tmp/results"],
|
|
181
|
+
"kwargs": {}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
assert result == expected
|
|
185
|
+
|
|
186
|
+
def test_command_with_dots_not_recognized(self, parser):
|
|
187
|
+
"""测试带点的字符串不被识别为命令"""
|
|
188
|
+
result = parser.parse("check /config.json /setup.py")
|
|
189
|
+
# 不以/开头,所以没有命令被识别
|
|
190
|
+
assert result == {}
|
|
191
|
+
|
|
192
|
+
def test_parse_command_existing(self, parser):
|
|
193
|
+
"""测试解析特定存在的命令"""
|
|
194
|
+
query = "/add file1.txt /remove file2.txt mode=force"
|
|
195
|
+
result = parser.parse_command(query, "remove")
|
|
196
|
+
expected = {
|
|
197
|
+
"args": ["file2.txt"],
|
|
198
|
+
"kwargs": {"mode": "force"}
|
|
199
|
+
}
|
|
200
|
+
assert result == expected
|
|
201
|
+
|
|
202
|
+
def test_parse_command_nonexistent(self, parser):
|
|
203
|
+
"""测试解析不存在的命令"""
|
|
204
|
+
query = "/add file1.txt /remove file2.txt"
|
|
205
|
+
result = parser.parse_command(query, "deploy")
|
|
206
|
+
assert result is None
|
|
207
|
+
|
|
208
|
+
def test_edge_case_empty_kwargs_value(self, parser):
|
|
209
|
+
"""测试空的键值对值"""
|
|
210
|
+
result = parser.parse("/config key1= key2=value")
|
|
211
|
+
expected = {
|
|
212
|
+
"config": {
|
|
213
|
+
"args": [],
|
|
214
|
+
"kwargs": {
|
|
215
|
+
"key1": "",
|
|
216
|
+
"key2": "value"
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
assert result == expected
|
|
221
|
+
|
|
222
|
+
def test_edge_case_special_characters_in_values(self, parser):
|
|
223
|
+
"""测试值中包含特殊字符"""
|
|
224
|
+
result = parser.parse("/config url=https://api.example.com/v1 pattern='*.py'")
|
|
225
|
+
expected = {
|
|
226
|
+
"config": {
|
|
227
|
+
"args": [],
|
|
228
|
+
"kwargs": {
|
|
229
|
+
"url": "https://api.example.com/v1",
|
|
230
|
+
"pattern": "*.py"
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
assert result == expected
|
|
235
|
+
|
|
236
|
+
def test_command_at_end_of_string(self, parser):
|
|
237
|
+
"""测试字符串末尾的命令"""
|
|
238
|
+
result = parser.parse("some text /help")
|
|
239
|
+
expected = {
|
|
240
|
+
"help": {
|
|
241
|
+
"args": [],
|
|
242
|
+
"kwargs": {}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
assert result == expected
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class TestConvenienceFunctions:
|
|
249
|
+
"""测试便捷函数"""
|
|
250
|
+
|
|
251
|
+
def test_parse_query_function(self):
|
|
252
|
+
"""测试parse_query函数"""
|
|
253
|
+
result = parse_query("/add file1.txt mode=fast")
|
|
254
|
+
expected = {
|
|
255
|
+
"add": {
|
|
256
|
+
"args": ["file1.txt"],
|
|
257
|
+
"kwargs": {"mode": "fast"}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
assert result == expected
|
|
261
|
+
|
|
262
|
+
def test_has_command_true(self):
|
|
263
|
+
"""测试has_command函数 - 命令存在"""
|
|
264
|
+
assert has_command("/add file1.txt /remove file2.txt", "add") == True
|
|
265
|
+
assert has_command("/add file1.txt /remove file2.txt", "remove") == True
|
|
266
|
+
|
|
267
|
+
def test_has_command_false(self):
|
|
268
|
+
"""测试has_command函数 - 命令不存在"""
|
|
269
|
+
assert has_command("/add file1.txt /remove file2.txt", "deploy") == False
|
|
270
|
+
assert has_command("no commands here", "add") == False
|
|
271
|
+
assert has_command("", "add") == False
|
|
272
|
+
|
|
273
|
+
def test_get_command_args_existing(self):
|
|
274
|
+
"""测试get_command_args函数 - 命令存在"""
|
|
275
|
+
query = "/add file1.txt file2.py /remove file3.txt"
|
|
276
|
+
assert get_command_args(query, "add") == ["file1.txt", "file2.py"]
|
|
277
|
+
assert get_command_args(query, "remove") == ["file3.txt"]
|
|
278
|
+
|
|
279
|
+
def test_get_command_args_nonexistent(self):
|
|
280
|
+
"""测试get_command_args函数 - 命令不存在"""
|
|
281
|
+
query = "/add file1.txt file2.py"
|
|
282
|
+
assert get_command_args(query, "remove") == []
|
|
283
|
+
assert get_command_args("", "add") == []
|
|
284
|
+
|
|
285
|
+
def test_get_command_kwargs_existing(self):
|
|
286
|
+
"""测试get_command_kwargs函数 - 命令存在"""
|
|
287
|
+
query = "/config model=gpt-4 temp=0.7 /deploy env=prod"
|
|
288
|
+
assert get_command_kwargs(query, "config") == {"model": "gpt-4", "temp": "0.7"}
|
|
289
|
+
assert get_command_kwargs(query, "deploy") == {"env": "prod"}
|
|
290
|
+
|
|
291
|
+
def test_get_command_kwargs_nonexistent(self):
|
|
292
|
+
"""测试get_command_kwargs函数 - 命令不存在"""
|
|
293
|
+
query = "/config model=gpt-4"
|
|
294
|
+
assert get_command_kwargs(query, "deploy") == {}
|
|
295
|
+
assert get_command_kwargs("", "config") == {}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class TestComplexScenarios:
|
|
299
|
+
"""测试复杂场景"""
|
|
300
|
+
|
|
301
|
+
def test_real_world_scenario_1(self):
|
|
302
|
+
"""测试真实场景1:文件操作命令"""
|
|
303
|
+
query = '/add "src/main.py" "tests/test_main.py" /config model="gpt-4" temperature=0.7 /deploy env=production version="1.2.3"'
|
|
304
|
+
result = parse_query(query)
|
|
305
|
+
|
|
306
|
+
expected = {
|
|
307
|
+
"add": {
|
|
308
|
+
"args": ["src/main.py", "tests/test_main.py"],
|
|
309
|
+
"kwargs": {}
|
|
310
|
+
},
|
|
311
|
+
"config": {
|
|
312
|
+
"args": [],
|
|
313
|
+
"kwargs": {
|
|
314
|
+
"model": "gpt-4",
|
|
315
|
+
"temperature": "0.7"
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
"deploy": {
|
|
319
|
+
"args": [],
|
|
320
|
+
"kwargs": {
|
|
321
|
+
"env": "production",
|
|
322
|
+
"version": "1.2.3"
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
assert result == expected
|
|
327
|
+
|
|
328
|
+
def test_real_world_scenario_2(self):
|
|
329
|
+
"""测试真实场景2:包含路径的复杂命令"""
|
|
330
|
+
query = "/analyze /home/user/project/src /output /tmp/analysis.json format=json verbose=true"
|
|
331
|
+
result = parse_query(query)
|
|
332
|
+
|
|
333
|
+
expected = {
|
|
334
|
+
"analyze": {
|
|
335
|
+
"args": ["/home/user/project/src"],
|
|
336
|
+
"kwargs": {}
|
|
337
|
+
},
|
|
338
|
+
"output": {
|
|
339
|
+
"args": ["/tmp/analysis.json"],
|
|
340
|
+
"kwargs": {
|
|
341
|
+
"format": "json",
|
|
342
|
+
"verbose": "true"
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
assert result == expected
|
|
347
|
+
|
|
348
|
+
def test_real_world_scenario_3(self):
|
|
349
|
+
"""测试真实场景3:混合引号和特殊字符"""
|
|
350
|
+
query = """/search pattern="*.py" path='/home/user/My Documents/project' /filter exclude="__pycache__" """
|
|
351
|
+
result = parse_query(query)
|
|
352
|
+
|
|
353
|
+
expected = {
|
|
354
|
+
"search": {
|
|
355
|
+
"args": [],
|
|
356
|
+
"kwargs": {
|
|
357
|
+
"pattern": "*.py",
|
|
358
|
+
"path": "/home/user/My Documents/project"
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
"filter": {
|
|
362
|
+
"args": [],
|
|
363
|
+
"kwargs": {
|
|
364
|
+
"exclude": "__pycache__"
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
assert result == expected
|
|
369
|
+
|
|
370
|
+
def test_edge_case_consecutive_commands(self):
|
|
371
|
+
"""测试连续命令"""
|
|
372
|
+
query = "/start /stop /restart mode=fast"
|
|
373
|
+
result = parse_query(query)
|
|
374
|
+
|
|
375
|
+
expected = {
|
|
376
|
+
"start": {
|
|
377
|
+
"args": [],
|
|
378
|
+
"kwargs": {}
|
|
379
|
+
},
|
|
380
|
+
"stop": {
|
|
381
|
+
"args": [],
|
|
382
|
+
"kwargs": {}
|
|
383
|
+
},
|
|
384
|
+
"restart": {
|
|
385
|
+
"args": [],
|
|
386
|
+
"kwargs": {"mode": "fast"}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
assert result == expected
|
|
390
|
+
|
|
391
|
+
def test_edge_case_command_with_numbers_and_underscores(self):
|
|
392
|
+
"""测试包含数字和下划线的命令"""
|
|
393
|
+
query = "/test_case_1 /deploy_v2 app_name=test123"
|
|
394
|
+
result = parse_query(query)
|
|
395
|
+
|
|
396
|
+
expected = {
|
|
397
|
+
"test_case_1": {
|
|
398
|
+
"args": [],
|
|
399
|
+
"kwargs": {}
|
|
400
|
+
},
|
|
401
|
+
"deploy_v2": {
|
|
402
|
+
"args": [],
|
|
403
|
+
"kwargs": {"app_name": "test123"}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
assert result == expected
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class TestErrorHandling:
|
|
410
|
+
"""测试错误处理和边界情况"""
|
|
411
|
+
|
|
412
|
+
def test_malformed_quotes(self):
|
|
413
|
+
"""测试格式错误的引号"""
|
|
414
|
+
# 这些应该仍能部分解析,不会抛出异常
|
|
415
|
+
parser = CommandParser()
|
|
416
|
+
|
|
417
|
+
# 未闭合的引号 - 应该被当作普通参数处理
|
|
418
|
+
result = parser.parse('/test "unclosed quote arg')
|
|
419
|
+
assert "test" in result
|
|
420
|
+
|
|
421
|
+
# 混合引号
|
|
422
|
+
result = parser.parse('/test "mixed\' quotes"')
|
|
423
|
+
assert "test" in result
|
|
424
|
+
|
|
425
|
+
def test_special_characters_in_commands(self):
|
|
426
|
+
"""测试命令中的特殊字符"""
|
|
427
|
+
parser = CommandParser()
|
|
428
|
+
|
|
429
|
+
# 命令名只能包含单词字符,所以这些不会被识别为命令
|
|
430
|
+
result = parser.parse("/test-command /test.command")
|
|
431
|
+
assert result == {}
|
|
432
|
+
|
|
433
|
+
def test_whitespace_handling(self):
|
|
434
|
+
"""测试空白字符处理"""
|
|
435
|
+
parser = CommandParser()
|
|
436
|
+
|
|
437
|
+
query = " /add file1.txt file2.py key=value "
|
|
438
|
+
result = parser.parse(query)
|
|
439
|
+
|
|
440
|
+
expected = {
|
|
441
|
+
"add": {
|
|
442
|
+
"args": ["file1.txt", "file2.py"],
|
|
443
|
+
"kwargs": {"key": "value"}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
assert result == expected
|
|
447
|
+
|
|
448
|
+
def test_unicode_characters(self):
|
|
449
|
+
"""测试Unicode字符"""
|
|
450
|
+
parser = CommandParser()
|
|
451
|
+
|
|
452
|
+
query = '/test "中文参数" key="中文值"'
|
|
453
|
+
result = parser.parse(query)
|
|
454
|
+
|
|
455
|
+
expected = {
|
|
456
|
+
"test": {
|
|
457
|
+
"args": ["中文参数"],
|
|
458
|
+
"kwargs": {"key": "中文值"}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
assert result == expected
|
|
462
|
+
|
|
463
|
+
def test__command_with_path(self, parser):
|
|
464
|
+
"""测试单个命令混合参数"""
|
|
465
|
+
result = parser.parse('/command "tdd/hello.md" name="威廉"')
|
|
466
|
+
expected = {
|
|
467
|
+
"command": {
|
|
468
|
+
"args": ["tdd/hello.md"],
|
|
469
|
+
"kwargs": {
|
|
470
|
+
"name": "威廉"
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
assert result == expected
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
# 参数化测试用例
|
|
478
|
+
@pytest.mark.parametrize("query,expected_commands", [
|
|
479
|
+
("/help", ["help"]),
|
|
480
|
+
("/add /remove", ["add", "remove"]),
|
|
481
|
+
("/config model=gpt-4", ["config"]),
|
|
482
|
+
("no commands", []),
|
|
483
|
+
("/start /stop /restart", ["start", "stop", "restart"]),
|
|
484
|
+
])
|
|
485
|
+
def test_command_detection_parametrized(query, expected_commands):
|
|
486
|
+
"""参数化测试命令检测"""
|
|
487
|
+
result = parse_query(query)
|
|
488
|
+
actual_commands = list(result.keys())
|
|
489
|
+
assert actual_commands == expected_commands
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@pytest.mark.parametrize("query,command,expected_args", [
|
|
493
|
+
("/add file1 file2", "add", ["file1", "file2"]),
|
|
494
|
+
("/remove", "remove", []),
|
|
495
|
+
("/test arg1 'arg with spaces'", "test", ["arg1", "arg with spaces"]),
|
|
496
|
+
("", "any", []),
|
|
497
|
+
])
|
|
498
|
+
def test_get_args_parametrized(query, command, expected_args):
|
|
499
|
+
"""参数化测试获取参数"""
|
|
500
|
+
assert get_command_args(query, command) == expected_args
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@pytest.mark.parametrize("query,command,expected_kwargs", [
|
|
504
|
+
("/config model=gpt-4 temp=0.7", "config", {"model": "gpt-4", "temp": "0.7"}),
|
|
505
|
+
("/deploy", "deploy", {}),
|
|
506
|
+
("/test key='value with spaces'", "test", {"key": "value with spaces"}),
|
|
507
|
+
("", "any", {}),
|
|
508
|
+
])
|
|
509
|
+
def test_get_kwargs_parametrized(query, command, expected_kwargs):
|
|
510
|
+
"""参数化测试获取键值对参数"""
|
|
511
|
+
assert get_command_kwargs(query, command) == expected_kwargs
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
if __name__ == "__main__":
|
|
515
|
+
# 可以直接运行此文件进行测试
|
|
516
|
+
pytest.main([__file__, "-v"])
|
|
@@ -22,7 +22,7 @@ COMMAND_HIERARCHY = {
|
|
|
22
22
|
"/mcp": {"/add": {}, "/remove": {}, "/list": {}, "/list_running": {}, "/refresh": {}, "/info": {}},
|
|
23
23
|
"/lib": {"/add": {}, "/remove": {}, "/list": {}, "/set-proxy": {}, "/refresh": {}, "/get": {}},
|
|
24
24
|
"/models": {"/chat": {}, "/add": {}, "/add_model": {}, "/remove": {}, "/list": {}, "/speed": {}, "/speed-test": {}, "/input_price": {}, "/output_price": {}, "/activate": {}},
|
|
25
|
-
"/auto": {},
|
|
25
|
+
"/auto": {"/new": {}, "/id": {}, "/list": {}},
|
|
26
26
|
"/shell": {"/chat": {}},
|
|
27
27
|
"/active_context": {"/list": {}, "/run": {}},
|
|
28
28
|
"/index": {"/query": {}, "/build": {}, "/export": {}, "/import": {}},
|
|
@@ -9,7 +9,7 @@ import sys
|
|
|
9
9
|
import json
|
|
10
10
|
from typing import Dict, Set, List
|
|
11
11
|
|
|
12
|
-
from autocoder.common.
|
|
12
|
+
from autocoder.common.command_file_manager import (
|
|
13
13
|
CommandManager, CommandFile, JinjaVariable, CommandFileAnalysisResult
|
|
14
14
|
)
|
|
15
15
|
|
|
@@ -122,16 +122,30 @@ def get_all_variables_example(manager: CommandManager):
|
|
|
122
122
|
print()
|
|
123
123
|
|
|
124
124
|
|
|
125
|
-
def
|
|
126
|
-
"""
|
|
127
|
-
|
|
125
|
+
def initialization_examples():
|
|
126
|
+
"""初始化示例"""
|
|
127
|
+
print("\n=== CommandManager 初始化示例 ===")
|
|
128
|
+
|
|
129
|
+
# 方式1: 使用默认目录(工作目录下的.autocodercommands目录)
|
|
130
|
+
print("方式1: 使用默认目录")
|
|
131
|
+
default_manager = CommandManager()
|
|
132
|
+
print(f"默认命令目录: {default_manager.commands_dir}")
|
|
133
|
+
|
|
134
|
+
# 方式2: 指定自定义目录
|
|
135
|
+
print("\n方式2: 指定自定义目录")
|
|
128
136
|
test_dir = setup_test_environment()
|
|
129
|
-
|
|
137
|
+
custom_manager = CommandManager(test_dir)
|
|
138
|
+
print(f"自定义命令目录: {custom_manager.commands_dir}")
|
|
130
139
|
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
return custom_manager
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def main():
|
|
144
|
+
"""主函数"""
|
|
145
|
+
# 展示初始化示例
|
|
146
|
+
manager = initialization_examples()
|
|
133
147
|
|
|
134
|
-
#
|
|
148
|
+
# 运行其他示例
|
|
135
149
|
list_command_files_example(manager)
|
|
136
150
|
read_command_file_example(manager)
|
|
137
151
|
analyze_command_file_example(manager)
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
-
import
|
|
9
|
-
from typing import Dict, List, Optional, Set, Tuple
|
|
8
|
+
from loguru import logger
|
|
9
|
+
from typing import Dict, List, Optional, Set, Tuple, Any
|
|
10
|
+
from byzerllm.utils import format_str_jinja2
|
|
10
11
|
|
|
11
12
|
from autocoder.common.command_file_manager.models import (
|
|
12
13
|
CommandFile, JinjaVariable, CommandFileAnalysisResult, ListCommandsResult
|
|
@@ -16,19 +17,19 @@ from autocoder.common.command_file_manager.utils import (
|
|
|
16
17
|
analyze_command_file, is_command_file
|
|
17
18
|
)
|
|
18
19
|
|
|
19
|
-
logger = logging.getLogger(__name__)
|
|
20
|
-
|
|
21
20
|
|
|
22
21
|
class CommandManager:
|
|
23
22
|
"""命令管理器,提供高层次的API接口"""
|
|
24
23
|
|
|
25
|
-
def __init__(self, commands_dir: str):
|
|
24
|
+
def __init__(self, commands_dir: Optional[str] = None):
|
|
26
25
|
"""
|
|
27
26
|
初始化命令管理器
|
|
28
27
|
|
|
29
28
|
Args:
|
|
30
|
-
commands_dir:
|
|
29
|
+
commands_dir: 命令文件目录路径,如果为None则默认使用工作目录下的.autocodercommands目录
|
|
31
30
|
"""
|
|
31
|
+
if commands_dir is None:
|
|
32
|
+
commands_dir = os.path.join(os.getcwd(), ".autocodercommands")
|
|
32
33
|
self.commands_dir = os.path.abspath(commands_dir)
|
|
33
34
|
|
|
34
35
|
# 确保目录存在
|
|
@@ -97,6 +98,36 @@ class CommandManager:
|
|
|
97
98
|
logger.error(f"读取命令文件时出错: {str(e)}")
|
|
98
99
|
return None
|
|
99
100
|
|
|
101
|
+
def read_command_file_with_render(self, file_name: str, render_variables: Dict[str, Any] = {}) -> Optional[str]:
|
|
102
|
+
"""
|
|
103
|
+
读取指定的命令文件并使用 Jinja2 进行渲染
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
file_name: 命令文件名或相对路径
|
|
107
|
+
render_variables: 用于 Jinja2 渲染的变量字典,如果为 None 则使用空字典
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Optional[str]: 渲染后的文件内容,如果文件不存在或渲染失败则返回None
|
|
111
|
+
"""
|
|
112
|
+
if render_variables is None:
|
|
113
|
+
render_variables = {}
|
|
114
|
+
|
|
115
|
+
# 首先读取命令文件
|
|
116
|
+
command_file = self.read_command_file(file_name)
|
|
117
|
+
if command_file is None:
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
# 使用 format_str_jinja2 进行渲染
|
|
122
|
+
rendered_content = format_str_jinja2(command_file.content, **render_variables)
|
|
123
|
+
|
|
124
|
+
logger.info(f"成功渲染命令文件: {file_name}, 使用变量: {render_variables}")
|
|
125
|
+
return rendered_content
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.error(f"渲染命令文件时出错: {file_name}, 错误: {str(e)}")
|
|
129
|
+
return None
|
|
130
|
+
|
|
100
131
|
def analyze_command_file(self, file_name: str) -> Optional[CommandFileAnalysisResult]:
|
|
101
132
|
"""
|
|
102
133
|
分析指定的命令文件,提取其中的Jinja2变量
|