auto-coder 0.1.304__py3-none-any.whl → 0.1.305__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.304.dist-info → auto_coder-0.1.305.dist-info}/METADATA +1 -1
- {auto_coder-0.1.304.dist-info → auto_coder-0.1.305.dist-info}/RECORD +16 -16
- autocoder/commands/auto_command.py +1 -4
- autocoder/commands/auto_web.py +1 -9
- autocoder/common/auto_coder_lang.py +10 -1
- autocoder/common/global_cancel.py +68 -7
- autocoder/dispacher/actions/action.py +8 -32
- autocoder/dispacher/actions/plugins/action_regex_project.py +2 -6
- autocoder/index/index.py +2 -4
- autocoder/utils/auto_coder_utils/chat_stream_out.py +56 -11
- autocoder/utils/thread_utils.py +78 -169
- autocoder/version.py +1 -1
- {auto_coder-0.1.304.dist-info → auto_coder-0.1.305.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.304.dist-info → auto_coder-0.1.305.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.304.dist-info → auto_coder-0.1.305.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.304.dist-info → auto_coder-0.1.305.dist-info}/top_level.txt +0 -0
|
@@ -14,7 +14,7 @@ autocoder/command_parser.py,sha256=fx1g9E6GaM273lGTcJqaFQ-hoksS_Ik2glBMnVltPCE,1
|
|
|
14
14
|
autocoder/lang.py,sha256=U6AjVV8Rs1uLyjFCZ8sT6WWuNUxMBqkXXIOs4S120uk,14511
|
|
15
15
|
autocoder/models.py,sha256=AyoZ-Pzy0oyYUmWCxOIRiOImsqboSfRET7LO9-UOuxI,11172
|
|
16
16
|
autocoder/run_context.py,sha256=IUfSO6_gp2Wt1blFWAmOpN0b0nDrTTk4LmtCYUBIoro,1643
|
|
17
|
-
autocoder/version.py,sha256=
|
|
17
|
+
autocoder/version.py,sha256=5GWW6mtaPl30mzqFO3ZxXUWEcruPKj3ItmKpxg9QBvQ,23
|
|
18
18
|
autocoder/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
autocoder/agent/auto_demand_organizer.py,sha256=NWSAEsEk94vT3lGjfo25kKLMwYdPcpy9e-i21txPasQ,6942
|
|
20
20
|
autocoder/agent/auto_filegroup.py,sha256=CW7bqp0FW1GIEMnl-blyAc2UGT7O9Mom0q66ITz1ckM,6635
|
|
@@ -28,8 +28,8 @@ autocoder/agent/planner.py,sha256=SZTSZHxHzDmuWZo3K5fs79RwvJLWurg-nbJRRNbX65o,91
|
|
|
28
28
|
autocoder/agent/project_reader.py,sha256=tWLaPoLw1gI6kO_NzivQj28KbobU2ceOLuppHMbfGl8,18234
|
|
29
29
|
autocoder/chat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
30
|
autocoder/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
-
autocoder/commands/auto_command.py,sha256=
|
|
32
|
-
autocoder/commands/auto_web.py,sha256=
|
|
31
|
+
autocoder/commands/auto_command.py,sha256=mreV3kJTo1QwaM7kWY9kfol569pQTb8EHFq53B6BugI,62501
|
|
32
|
+
autocoder/commands/auto_web.py,sha256=Cc0eb6JN3SvFy3GD_lpSLvIqj7F1eFDTcwg1t-zDcKg,39024
|
|
33
33
|
autocoder/commands/tools.py,sha256=uSPyEcx6o1upuw6clHdL4yeqoXbywnvCxAPo2mt5rJk,28117
|
|
34
34
|
autocoder/common/JupyterClient.py,sha256=O-wi6pXeAEYhAY24kDa0BINrLYvKS6rKyWe98pDClS0,2816
|
|
35
35
|
autocoder/common/ShellClient.py,sha256=fM1q8t_XMSbLBl2zkCNC2J9xuyKN3eXzGm6hHhqL2WY,2286
|
|
@@ -38,7 +38,7 @@ autocoder/common/action_yml_file_manager.py,sha256=q0QBlptWtR9DhqguMBZJfHmmTP9fV
|
|
|
38
38
|
autocoder/common/anything2images.py,sha256=0ILBbWzY02M-CiWB-vzuomb_J1hVdxRcenAfIrAXq9M,25283
|
|
39
39
|
autocoder/common/anything2img.py,sha256=iZQmg8srXlD7N5uGl5b_ONKJMBjYoW8kPmokkG6ISF0,10118
|
|
40
40
|
autocoder/common/audio.py,sha256=Kn9nWKQddWnUrAz0a_ZUgjcu4VUU_IcZBigT7n3N3qc,7439
|
|
41
|
-
autocoder/common/auto_coder_lang.py,sha256=
|
|
41
|
+
autocoder/common/auto_coder_lang.py,sha256=NSVg3pJldxoyfqxD68mtj6VDsx-Dn-lCvEcPunmYKzM,36972
|
|
42
42
|
autocoder/common/auto_configure.py,sha256=D4N-fl9v8bKM5-Ds-uhkC2uGDmHH_ZjLJ759F8KXMKs,13129
|
|
43
43
|
autocoder/common/buildin_tokenizer.py,sha256=L7d5t39ZFvUd6EoMPXUhYK1toD0FHlRH1jtjKRGokWU,1236
|
|
44
44
|
autocoder/common/chunk_validation.py,sha256=BrR_ZWavW8IANuueEE7hS8NFAwEvm8TX34WnPx_1hs8,3030
|
|
@@ -64,7 +64,7 @@ autocoder/common/context_pruner.py,sha256=HlU5BmxpCX7uVTJUsTFLlXvkwcOQuidI9uCKZa
|
|
|
64
64
|
autocoder/common/conversation_pruner.py,sha256=pzmrQEa7pFzA66eYSS_h7VqP6ZwUABeooDQzm0PGu0A,5770
|
|
65
65
|
autocoder/common/files.py,sha256=nPiKcnUcYZbSUn3TskKeTVnAxCJRtuehPuB_5d2imX8,4618
|
|
66
66
|
autocoder/common/git_utils.py,sha256=EK8gekbXsG6BNDVrd1Nsan_7kJ71dd8_w9FiOFxjsVI,26276
|
|
67
|
-
autocoder/common/global_cancel.py,sha256=
|
|
67
|
+
autocoder/common/global_cancel.py,sha256=TyjYQPESwo04D1BOTmC9hH7IbkKDDM-b2zPacEHGIQ8,3264
|
|
68
68
|
autocoder/common/image_to_page.py,sha256=yWiTJQ49Lm3j0FngiJhQ9u7qayqE_bOGb8Rk0TmSWx0,14123
|
|
69
69
|
autocoder/common/index_import_export.py,sha256=h758AYY1df6JMTKUXYmMkSgxItfymDt82XT7O-ygEuw,4565
|
|
70
70
|
autocoder/common/interpreter.py,sha256=62-dIakOunYB4yjmX8SHC0Gdy2h8NtxdgbpdqRZJ5vk,2833
|
|
@@ -96,10 +96,10 @@ autocoder/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
96
96
|
autocoder/db/store.py,sha256=tFT66bP2ZKIqZip-uhLkHRSLaaOAUUDZfozJwcqix3c,1908
|
|
97
97
|
autocoder/dispacher/__init__.py,sha256=YoA64dIxnx4jcE1pwSfg81sjkQtjDkhddkfac1-cMWo,1230
|
|
98
98
|
autocoder/dispacher/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
99
|
-
autocoder/dispacher/actions/action.py,sha256=
|
|
99
|
+
autocoder/dispacher/actions/action.py,sha256=Bc_6Iwcd7KhNM6jbMB9zUt3uhzJz8QWRCsO0tfnz3OI,27865
|
|
100
100
|
autocoder/dispacher/actions/copilot.py,sha256=2nQzKt8Sr40mIDOizZWyl4ekCwaHYklvgGlVfvhOlFM,13106
|
|
101
101
|
autocoder/dispacher/actions/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
102
|
-
autocoder/dispacher/actions/plugins/action_regex_project.py,sha256=
|
|
102
|
+
autocoder/dispacher/actions/plugins/action_regex_project.py,sha256=vH-O0rM2ig-5u-n-hlX1SvwQW8IjsGSl-dC4DUAwfII,7632
|
|
103
103
|
autocoder/dispacher/actions/plugins/action_translate.py,sha256=GEn7dZA22jy5WyzINomjmzzB795p2Olg-CJla97lRF8,7744
|
|
104
104
|
autocoder/events/__init__.py,sha256=1x_juwr9Ows2RADDa2LyI4QlmPxOVOXZeLO1cht-slM,1443
|
|
105
105
|
autocoder/events/event_content.py,sha256=0unVmskGrfvnsniLvNXobco9KG-r6cv_C-gXK3L5zXQ,11557
|
|
@@ -110,7 +110,7 @@ autocoder/events/event_types.py,sha256=wONd3wC_BhGXTbXIlb4kFIr7gkhYSBjQE30JjTc8t
|
|
|
110
110
|
autocoder/index/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
111
111
|
autocoder/index/entry.py,sha256=a8KhkNd6tKuD6W9fh9ohdri2r4UYeZWswi14pVwpr-Q,15740
|
|
112
112
|
autocoder/index/for_command.py,sha256=BFvljE4t6VaMBGboZAuhUCzVK0EitCy_n5D_7FEnihw,3204
|
|
113
|
-
autocoder/index/index.py,sha256=
|
|
113
|
+
autocoder/index/index.py,sha256=Y72N_w6tdI-Lv3GhgGDeCKxKfSzhxpdVuZpSAdJTsDU,30831
|
|
114
114
|
autocoder/index/symbols_utils.py,sha256=_EP7E_qWXxluAxq3FGZLlLfdrfwx3FmxCdulI8VGuac,2244
|
|
115
115
|
autocoder/index/types.py,sha256=a2s_KV5FJlq7jqA2ELSo9E1sjuLwDB-JJYMhSpzBAhU,596
|
|
116
116
|
autocoder/index/filter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -188,14 +188,14 @@ autocoder/utils/request_queue.py,sha256=nwp6PMtgTCiuwJI24p8OLNZjUiprC-TsefQrhMI-
|
|
|
188
188
|
autocoder/utils/rest.py,sha256=hLBhr78y-WVnV0oQf9Rxc22EwqF78KINkScvYa1MuYA,6435
|
|
189
189
|
autocoder/utils/stream_thinking.py,sha256=vbDObflBFW53eWEjMTEHf3nyL167_cqpDLh9zRx7Yk8,7015
|
|
190
190
|
autocoder/utils/tests.py,sha256=BqphrwyycGAvs-5mhH8pKtMZdObwhFtJ5MC_ZAOiLq8,1340
|
|
191
|
-
autocoder/utils/thread_utils.py,sha256=
|
|
191
|
+
autocoder/utils/thread_utils.py,sha256=VQCDrkTdij_5-01FE_X2Fprz_0uA-GahUY4h7DnAlw0,5426
|
|
192
192
|
autocoder/utils/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
193
193
|
autocoder/utils/auto_coder_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
194
|
-
autocoder/utils/auto_coder_utils/chat_stream_out.py,sha256=
|
|
194
|
+
autocoder/utils/auto_coder_utils/chat_stream_out.py,sha256=A1ka4FVf_SKH7T6pez5R460qvGdEquzfIAjRR_TRxF8,15010
|
|
195
195
|
autocoder/utils/chat_auto_coder_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
196
|
-
auto_coder-0.1.
|
|
197
|
-
auto_coder-0.1.
|
|
198
|
-
auto_coder-0.1.
|
|
199
|
-
auto_coder-0.1.
|
|
200
|
-
auto_coder-0.1.
|
|
201
|
-
auto_coder-0.1.
|
|
196
|
+
auto_coder-0.1.305.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
197
|
+
auto_coder-0.1.305.dist-info/METADATA,sha256=IM7ILt4a0lg_Lq7SHGZK0T0oPsi272WyLa9gLhSLjcE,2721
|
|
198
|
+
auto_coder-0.1.305.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
199
|
+
auto_coder-0.1.305.dist-info/entry_points.txt,sha256=0nzHtHH4pNcM7xq4EBA2toS28Qelrvcbrr59GqD_0Ak,350
|
|
200
|
+
auto_coder-0.1.305.dist-info/top_level.txt,sha256=Jqc0_uJSw2GwoFQAa9iJxYns-2mWla-9ok_Y3Gcznjk,10
|
|
201
|
+
auto_coder-0.1.305.dist-info/RECORD,,
|
|
@@ -500,10 +500,7 @@ class CommandAutoTuner:
|
|
|
500
500
|
result_manager = ResultManager()
|
|
501
501
|
|
|
502
502
|
while True:
|
|
503
|
-
|
|
504
|
-
printer = Printer(console)
|
|
505
|
-
printer.print_in_terminal("generation_cancelled")
|
|
506
|
-
break
|
|
503
|
+
global_cancel.check_and_raise()
|
|
507
504
|
# 执行命令
|
|
508
505
|
command = response.suggestions[0].command
|
|
509
506
|
parameters = response.suggestions[0].parameters
|
autocoder/commands/auto_web.py
CHANGED
|
@@ -794,15 +794,7 @@ class AutoWebTuner:
|
|
|
794
794
|
logger.info(f"开始执行迭代 {iterations}/{max_iterations}")
|
|
795
795
|
|
|
796
796
|
# 检查是否需要取消操作
|
|
797
|
-
|
|
798
|
-
logger.info("检测到取消请求,停止操作")
|
|
799
|
-
self.printer.print_in_terminal(
|
|
800
|
-
"operation_cancelled", style="yellow")
|
|
801
|
-
return AutoWebResponse(
|
|
802
|
-
explanation="操作已取消",
|
|
803
|
-
overall_status="cancelled",
|
|
804
|
-
actions=[]
|
|
805
|
-
)
|
|
797
|
+
global_cancel.check_and_raise()
|
|
806
798
|
|
|
807
799
|
# 如果没有更多操作,认为任务完成
|
|
808
800
|
if not plan.actions:
|
|
@@ -66,6 +66,11 @@ MESSAGES = {
|
|
|
66
66
|
"index_related_files_fail": "⚠️ Failed to find related files for chunk {{ chunk_count }}",
|
|
67
67
|
"index_file_removed": "🗑️ Removed non-existent file index: {{ file_path }}",
|
|
68
68
|
"index_file_saved": "💾 Saved index file, updated {{ updated_files }} files, removed {{ removed_files }} files, input_tokens: {{ input_tokens }}, output_tokens: {{ output_tokens }}, input_cost: {{ input_cost }}, output_cost: {{ output_cost }}",
|
|
69
|
+
"task_cancelled_by_user": "Task was cancelled by user",
|
|
70
|
+
"cancellation_requested": "Cancellation requested, waiting for thread to terminate...",
|
|
71
|
+
"force_terminating_thread": "Force terminating thread after timeout",
|
|
72
|
+
"force_raising_keyboard_interrupt": "Force raising KeyboardInterrupt after timeout",
|
|
73
|
+
"thread_terminated": "Thread terminated",
|
|
69
74
|
"human_as_model_instructions": (
|
|
70
75
|
"You are now in Human as Model mode. The content has been copied to your clipboard.\n"
|
|
71
76
|
"The system is waiting for your input. When finished, enter 'EOF' on a new line to submit.\n"
|
|
@@ -156,7 +161,6 @@ MESSAGES = {
|
|
|
156
161
|
"generated_shell_script": "Generated Shell Script",
|
|
157
162
|
"confirm_execute_shell_script": "Do you want to execute this shell script?",
|
|
158
163
|
"shell_script_not_executed": "Shell script was not executed",
|
|
159
|
-
"conf_not_found": "Configuration file not found: {{path}}",
|
|
160
164
|
"index_export_success": "Index exported successfully: {{path}}",
|
|
161
165
|
"index_import_success": "Index imported successfully: {{path}}",
|
|
162
166
|
"edits_title": "edits",
|
|
@@ -279,6 +283,11 @@ MESSAGES = {
|
|
|
279
283
|
"index_related_files_fail": "⚠️ 无法为块 {{ chunk_count }} 找到相关文件",
|
|
280
284
|
"index_file_removed": "🗑️ 已移除不存在的文件索引:{{ file_path }}",
|
|
281
285
|
"index_file_saved": "💾 已保存索引文件,更新了 {{ updated_files }} 个文件,移除了 {{ removed_files }} 个文件,输入token数: {{ input_tokens }}, 输出token数: {{ output_tokens }}, 输入成本: {{ input_cost }}, 输出成本: {{ output_cost }}",
|
|
286
|
+
"task_cancelled_by_user": "任务被用户取消",
|
|
287
|
+
"cancellation_requested": "已请求取消,正在等待线程终止...",
|
|
288
|
+
"force_terminating_thread": "线程超时强制终止",
|
|
289
|
+
"force_raising_keyboard_interrupt": "超时强制抛出键盘中断异常",
|
|
290
|
+
"thread_terminated": "线程已终止",
|
|
282
291
|
"human_as_model_instructions": (
|
|
283
292
|
"您现在处于人类作为模型模式。内容已复制到您的剪贴板。\n"
|
|
284
293
|
"系统正在等待您的输入。完成后,在新行输入'EOF'提交。\n"
|
|
@@ -1,21 +1,82 @@
|
|
|
1
1
|
import threading
|
|
2
|
+
from typing import Dict, Optional, Any
|
|
3
|
+
|
|
4
|
+
class CancelRequestedException(Exception):
|
|
5
|
+
"""当取消请求被触发时抛出的异常"""
|
|
6
|
+
def __init__(self, token: Optional[str] = None, message: str = "Operation was cancelled"):
|
|
7
|
+
self.token = token
|
|
8
|
+
self.message = message
|
|
9
|
+
super().__init__(self.message)
|
|
2
10
|
|
|
3
11
|
class GlobalCancel:
|
|
4
12
|
def __init__(self):
|
|
5
|
-
self.
|
|
13
|
+
self._global_flag = False
|
|
14
|
+
self._token_flags: Dict[str, bool] = {}
|
|
6
15
|
self._lock = threading.Lock()
|
|
16
|
+
self._context: Dict[str, Any] = {} # 存储与取消相关的上下文信息
|
|
7
17
|
|
|
8
18
|
@property
|
|
9
|
-
def requested(self):
|
|
19
|
+
def requested(self) -> bool:
|
|
20
|
+
"""检查是否请求了全局取消(向后兼容)"""
|
|
21
|
+
with self._lock:
|
|
22
|
+
return self._global_flag
|
|
23
|
+
|
|
24
|
+
def is_requested(self, token: Optional[str] = None) -> bool:
|
|
25
|
+
"""检查是否请求了特定token或全局的取消"""
|
|
10
26
|
with self._lock:
|
|
11
|
-
|
|
27
|
+
# 全局标志总是优先
|
|
28
|
+
if self._global_flag:
|
|
29
|
+
return True
|
|
30
|
+
# 如果提供了token,检查该token的标志
|
|
31
|
+
if token is not None and token in self._token_flags:
|
|
32
|
+
return self._token_flags[token]
|
|
33
|
+
return False
|
|
12
34
|
|
|
13
|
-
def set(self):
|
|
35
|
+
def set(self, token: Optional[str] = None, context: Optional[Dict[str, Any]] = None) -> None:
|
|
36
|
+
"""设置特定token或全局的取消标志"""
|
|
14
37
|
with self._lock:
|
|
15
|
-
|
|
38
|
+
if token is None:
|
|
39
|
+
self._global_flag = True
|
|
40
|
+
else:
|
|
41
|
+
self._token_flags[token] = True
|
|
42
|
+
|
|
43
|
+
# 存储上下文
|
|
44
|
+
if context:
|
|
45
|
+
if token is None:
|
|
46
|
+
self._context.update(context)
|
|
47
|
+
else:
|
|
48
|
+
if "tokens" not in self._context:
|
|
49
|
+
self._context["tokens"] = {}
|
|
50
|
+
self._context["tokens"][token] = context
|
|
16
51
|
|
|
17
|
-
def reset(self):
|
|
52
|
+
def reset(self, token: Optional[str] = None) -> None:
|
|
53
|
+
"""重置特定token或全局的取消标志"""
|
|
18
54
|
with self._lock:
|
|
19
|
-
|
|
55
|
+
if token is None:
|
|
56
|
+
# 全局重置
|
|
57
|
+
self._global_flag = False
|
|
58
|
+
self._token_flags.clear()
|
|
59
|
+
self._context.clear()
|
|
60
|
+
else:
|
|
61
|
+
# 特定token重置
|
|
62
|
+
if token in self._token_flags:
|
|
63
|
+
del self._token_flags[token]
|
|
64
|
+
if "tokens" in self._context and token in self._context["tokens"]:
|
|
65
|
+
del self._context["tokens"][token]
|
|
66
|
+
|
|
67
|
+
def get_context(self, token: Optional[str] = None) -> Dict[str, Any]:
|
|
68
|
+
"""获取与取消相关的上下文信息"""
|
|
69
|
+
with self._lock:
|
|
70
|
+
if token is None:
|
|
71
|
+
return self._context.copy()
|
|
72
|
+
if "tokens" in self._context and token in self._context["tokens"]:
|
|
73
|
+
return self._context["tokens"][token].copy()
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
def check_and_raise(self, token: Optional[str] = None) -> None:
|
|
77
|
+
"""检查是否请求了取消,如果是则抛出异常"""
|
|
78
|
+
if self.is_requested(token):
|
|
79
|
+
context = self.get_context(token)
|
|
80
|
+
raise CancelRequestedException(token, context.get("message", "Operation was cancelled"))
|
|
20
81
|
|
|
21
82
|
global_cancel = GlobalCancel()
|
|
@@ -112,10 +112,7 @@ class ActionTSProject(BaseAction):
|
|
|
112
112
|
f"Content(send to model) is {content_length} tokens, which is larger than the maximum input length {self.args.model_max_input_length}"
|
|
113
113
|
)
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
printer = Printer()
|
|
117
|
-
raise Exception(printer.get_message_from_key(
|
|
118
|
-
"generation_cancelled"))
|
|
115
|
+
global_cancel.check_and_raise()
|
|
119
116
|
|
|
120
117
|
if args.execute:
|
|
121
118
|
self.printer.print_in_terminal("code_generation_start")
|
|
@@ -181,10 +178,7 @@ class ActionTSProject(BaseAction):
|
|
|
181
178
|
action_file=self.args.file
|
|
182
179
|
).to_dict())
|
|
183
180
|
|
|
184
|
-
|
|
185
|
-
printer = Printer()
|
|
186
|
-
raise Exception(printer.get_message_from_key(
|
|
187
|
-
"generation_cancelled"))
|
|
181
|
+
global_cancel.check_and_raise()
|
|
188
182
|
|
|
189
183
|
merge_result = None
|
|
190
184
|
if args.execute and args.auto_merge:
|
|
@@ -249,10 +243,7 @@ class ActionPyScriptProject(BaseAction):
|
|
|
249
243
|
|
|
250
244
|
def process_content(self, source_code_list: SourceCodeList):
|
|
251
245
|
args = self.args
|
|
252
|
-
|
|
253
|
-
printer = Printer()
|
|
254
|
-
raise Exception(printer.get_message_from_key(
|
|
255
|
-
"generation_cancelled"))
|
|
246
|
+
global_cancel.check_and_raise()
|
|
256
247
|
|
|
257
248
|
if args.execute:
|
|
258
249
|
self.printer.print_in_terminal("code_generation_start")
|
|
@@ -318,10 +309,7 @@ class ActionPyScriptProject(BaseAction):
|
|
|
318
309
|
action_file=self.args.file
|
|
319
310
|
).to_dict())
|
|
320
311
|
|
|
321
|
-
|
|
322
|
-
printer = Printer()
|
|
323
|
-
raise Exception(printer.get_message_from_key(
|
|
324
|
-
"generation_cancelled"))
|
|
312
|
+
global_cancel.check_and_raise()
|
|
325
313
|
|
|
326
314
|
merge_result = None
|
|
327
315
|
if args.execute and args.auto_merge:
|
|
@@ -410,10 +398,7 @@ class ActionPyProject(BaseAction):
|
|
|
410
398
|
max_length=self.args.model_max_input_length
|
|
411
399
|
)
|
|
412
400
|
|
|
413
|
-
|
|
414
|
-
printer = Printer()
|
|
415
|
-
raise Exception(printer.get_message_from_key(
|
|
416
|
-
"generation_cancelled"))
|
|
401
|
+
global_cancel.check_and_raise()
|
|
417
402
|
|
|
418
403
|
if args.execute:
|
|
419
404
|
self.printer.print_in_terminal("code_generation_start")
|
|
@@ -479,10 +464,7 @@ class ActionPyProject(BaseAction):
|
|
|
479
464
|
action_file=self.args.file
|
|
480
465
|
).to_dict())
|
|
481
466
|
|
|
482
|
-
|
|
483
|
-
printer = Printer()
|
|
484
|
-
raise Exception(printer.get_message_from_key(
|
|
485
|
-
"generation_cancelled"))
|
|
467
|
+
global_cancel.check_and_raise()
|
|
486
468
|
|
|
487
469
|
merge_result = None
|
|
488
470
|
if args.execute and args.auto_merge:
|
|
@@ -563,10 +545,7 @@ class ActionSuffixProject(BaseAction):
|
|
|
563
545
|
f"Content(send to model) is {content_length} tokens, which is larger than the maximum input length {self.args.model_max_input_length}"
|
|
564
546
|
)
|
|
565
547
|
|
|
566
|
-
|
|
567
|
-
printer = Printer()
|
|
568
|
-
raise Exception(printer.get_message_from_key(
|
|
569
|
-
"generation_cancelled"))
|
|
548
|
+
global_cancel.check_and_raise()
|
|
570
549
|
|
|
571
550
|
if args.execute:
|
|
572
551
|
self.printer.print_in_terminal("code_generation_start")
|
|
@@ -631,10 +610,7 @@ class ActionSuffixProject(BaseAction):
|
|
|
631
610
|
action_file=self.args.file
|
|
632
611
|
).to_dict())
|
|
633
612
|
|
|
634
|
-
|
|
635
|
-
printer = Printer()
|
|
636
|
-
raise Exception(printer.get_message_from_key(
|
|
637
|
-
"generation_cancelled"))
|
|
613
|
+
global_cancel.check_and_raise()
|
|
638
614
|
|
|
639
615
|
merge_result = None
|
|
640
616
|
if args.execute and args.auto_merge:
|
|
@@ -66,9 +66,7 @@ class ActionRegexProject:
|
|
|
66
66
|
|
|
67
67
|
start_time = time.time()
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
printer = Printer()
|
|
71
|
-
raise Exception(printer.get_message_from_key("generation_cancelled"))
|
|
69
|
+
global_cancel.check_and_raise()
|
|
72
70
|
|
|
73
71
|
if args.execute:
|
|
74
72
|
self.printer.print_in_terminal("code_generation_start")
|
|
@@ -128,9 +126,7 @@ class ActionRegexProject:
|
|
|
128
126
|
action_file=self.args.file
|
|
129
127
|
).to_dict())
|
|
130
128
|
|
|
131
|
-
|
|
132
|
-
printer = Printer()
|
|
133
|
-
raise Exception(printer.get_message_from_key("generation_cancelled"))
|
|
129
|
+
global_cancel.check_and_raise()
|
|
134
130
|
|
|
135
131
|
merge_result = None
|
|
136
132
|
if args.execute and args.auto_merge:
|
autocoder/index/index.py
CHANGED
|
@@ -296,8 +296,7 @@ class IndexManager:
|
|
|
296
296
|
return False
|
|
297
297
|
|
|
298
298
|
def build_index_for_single_source(self, source: SourceCode):
|
|
299
|
-
|
|
300
|
-
return None
|
|
299
|
+
global_cancel.check_and_raise()
|
|
301
300
|
|
|
302
301
|
file_path = source.module_name
|
|
303
302
|
if not os.path.exists(file_path):
|
|
@@ -552,8 +551,7 @@ class IndexManager:
|
|
|
552
551
|
for source in wait_to_build_files
|
|
553
552
|
]
|
|
554
553
|
for future in as_completed(futures):
|
|
555
|
-
|
|
556
|
-
break
|
|
554
|
+
global_cancel.check_and_raise()
|
|
557
555
|
result = future.result()
|
|
558
556
|
if result is not None:
|
|
559
557
|
counter += 1
|
|
@@ -13,7 +13,7 @@ from autocoder.utils.request_queue import request_queue
|
|
|
13
13
|
import time
|
|
14
14
|
from byzerllm.utils.types import SingleOutputMeta
|
|
15
15
|
from autocoder.common import AutoCoderArgs
|
|
16
|
-
from autocoder.common.global_cancel import global_cancel
|
|
16
|
+
from autocoder.common.global_cancel import global_cancel, CancelRequestedException
|
|
17
17
|
from autocoder.events.event_manager_singleton import get_event_manager
|
|
18
18
|
from autocoder.events import event_content as EventContentCreator
|
|
19
19
|
from autocoder.events.event_types import EventMetadata
|
|
@@ -68,23 +68,35 @@ class MultiStreamRenderer:
|
|
|
68
68
|
|
|
69
69
|
def _process_stream(self,
|
|
70
70
|
stream_idx: int,
|
|
71
|
-
stream_generator: Generator[Tuple[str, Dict[str, Any]], None, None]
|
|
71
|
+
stream_generator: Generator[Tuple[str, Dict[str, Any]], None, None],
|
|
72
|
+
cancel_token: Optional[str] = None):
|
|
72
73
|
"""Process a single stream in a separate thread"""
|
|
73
74
|
stream = self.streams[stream_idx]
|
|
74
75
|
try:
|
|
75
76
|
for content, meta in stream_generator:
|
|
77
|
+
try:
|
|
78
|
+
# 使用新的异常机制检查取消请求
|
|
79
|
+
global_cancel.check_and_raise(cancel_token)
|
|
80
|
+
except CancelRequestedException:
|
|
81
|
+
break
|
|
82
|
+
|
|
76
83
|
if content:
|
|
77
84
|
stream.update(content)
|
|
85
|
+
except CancelRequestedException:
|
|
86
|
+
# 处理取消异常
|
|
87
|
+
stream.update("\n\n**Operation was cancelled**")
|
|
78
88
|
finally:
|
|
79
89
|
stream.complete()
|
|
80
90
|
|
|
81
91
|
def render_streams(self,
|
|
82
|
-
stream_generators: List[Generator[Tuple[str, Dict[str, Any]], None, None]]
|
|
92
|
+
stream_generators: List[Generator[Tuple[str, Dict[str, Any]], None, None]],
|
|
93
|
+
cancel_token: Optional[str] = None) -> List[str]:
|
|
83
94
|
"""
|
|
84
95
|
Render multiple streams simultaneously
|
|
85
96
|
|
|
86
97
|
Args:
|
|
87
98
|
stream_generators: List of stream generators to render
|
|
99
|
+
cancel_token: Optional cancellation token
|
|
88
100
|
|
|
89
101
|
Returns:
|
|
90
102
|
List of final content from each stream
|
|
@@ -94,7 +106,7 @@ class MultiStreamRenderer:
|
|
|
94
106
|
# Start processing threads
|
|
95
107
|
threads = []
|
|
96
108
|
for i, generator in enumerate(stream_generators):
|
|
97
|
-
thread = Thread(target=self._process_stream, args=(i, generator))
|
|
109
|
+
thread = Thread(target=self._process_stream, args=(i, generator, cancel_token))
|
|
98
110
|
thread.daemon = True
|
|
99
111
|
thread.start()
|
|
100
112
|
threads.append(thread)
|
|
@@ -102,6 +114,13 @@ class MultiStreamRenderer:
|
|
|
102
114
|
try:
|
|
103
115
|
with Live(self.layout, console=self.console, refresh_per_second=10) as live:
|
|
104
116
|
while any(not stream.is_complete for stream in self.streams):
|
|
117
|
+
try:
|
|
118
|
+
# 使用新的异常机制检查取消请求
|
|
119
|
+
global_cancel.check_and_raise(cancel_token)
|
|
120
|
+
except CancelRequestedException:
|
|
121
|
+
print("\nCancelling streams...")
|
|
122
|
+
break
|
|
123
|
+
|
|
105
124
|
# Update all panels
|
|
106
125
|
for i, stream in enumerate(self.streams):
|
|
107
126
|
panel = Panel(
|
|
@@ -116,7 +135,11 @@ class MultiStreamRenderer:
|
|
|
116
135
|
time.sleep(0.1) # Prevent excessive CPU usage
|
|
117
136
|
|
|
118
137
|
except KeyboardInterrupt:
|
|
138
|
+
# 键盘中断时设置取消标志
|
|
139
|
+
global_cancel.set(cancel_token, {"message": "Keyboard interrupt"})
|
|
119
140
|
print("\nStopping streams...")
|
|
141
|
+
except CancelRequestedException:
|
|
142
|
+
print("\nCancelling streams...")
|
|
120
143
|
|
|
121
144
|
# Wait for all threads to complete
|
|
122
145
|
for thread in threads:
|
|
@@ -128,7 +151,8 @@ def multi_stream_out(
|
|
|
128
151
|
stream_generators: List[Generator[Tuple[str, Dict[str, Any]], None, None]],
|
|
129
152
|
titles: List[str],
|
|
130
153
|
layout: str = "horizontal",
|
|
131
|
-
console: Optional[Console] = None
|
|
154
|
+
console: Optional[Console] = None,
|
|
155
|
+
cancel_token: Optional[str] = None
|
|
132
156
|
) -> List[str]:
|
|
133
157
|
"""
|
|
134
158
|
Render multiple streams with Rich
|
|
@@ -138,12 +162,13 @@ def multi_stream_out(
|
|
|
138
162
|
titles: List of titles for each stream
|
|
139
163
|
layout: "horizontal" or "vertical"
|
|
140
164
|
console: Optional Rich console instance
|
|
165
|
+
cancel_token: Optional cancellation token
|
|
141
166
|
|
|
142
167
|
Returns:
|
|
143
168
|
List of final content from each stream
|
|
144
169
|
"""
|
|
145
170
|
renderer = MultiStreamRenderer(titles, layout, console)
|
|
146
|
-
return renderer.render_streams(stream_generators)
|
|
171
|
+
return renderer.render_streams(stream_generators, cancel_token)
|
|
147
172
|
|
|
148
173
|
|
|
149
174
|
def stream_out(
|
|
@@ -155,7 +180,8 @@ def stream_out(
|
|
|
155
180
|
final_title: Optional[str] = None,
|
|
156
181
|
args: Optional[AutoCoderArgs] = None,
|
|
157
182
|
display_func: Optional[Callable] = None,
|
|
158
|
-
extra_meta: Dict[str, Any] = {}
|
|
183
|
+
extra_meta: Dict[str, Any] = {},
|
|
184
|
+
cancel_token: Optional[str] = None
|
|
159
185
|
) -> Tuple[str, Optional[SingleOutputMeta]]:
|
|
160
186
|
"""
|
|
161
187
|
处理流式输出事件并在终端中展示
|
|
@@ -167,6 +193,9 @@ def stream_out(
|
|
|
167
193
|
model_name: 模型名称
|
|
168
194
|
title: 面板标题,如果没有提供则使用默认值
|
|
169
195
|
args: AutoCoderArgs对象
|
|
196
|
+
display_func: 可选的显示函数
|
|
197
|
+
extra_meta: 额外的元数据
|
|
198
|
+
cancel_token: 可选的取消令牌
|
|
170
199
|
Returns:
|
|
171
200
|
Tuple[str, Dict[SingleOutputMeta]]: 返回完整的响应内容和最后的元数据
|
|
172
201
|
"""
|
|
@@ -197,10 +226,8 @@ def stream_out(
|
|
|
197
226
|
console=console
|
|
198
227
|
) as live:
|
|
199
228
|
for res in stream_generator:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
printer.print_in_terminal("generation_cancelled")
|
|
203
|
-
break
|
|
229
|
+
global_cancel.check_and_raise(cancel_token)
|
|
230
|
+
|
|
204
231
|
last_meta = res[1]
|
|
205
232
|
content = res[0]
|
|
206
233
|
|
|
@@ -312,6 +339,24 @@ def stream_out(
|
|
|
312
339
|
)
|
|
313
340
|
)
|
|
314
341
|
|
|
342
|
+
except CancelRequestedException as cancel_exc:
|
|
343
|
+
# 捕获取消异常,显示取消信息
|
|
344
|
+
console.print(Panel(
|
|
345
|
+
"Generation was cancelled",
|
|
346
|
+
title=f"Cancelled[ {panel_title} ]",
|
|
347
|
+
border_style="yellow"
|
|
348
|
+
))
|
|
349
|
+
|
|
350
|
+
if request_id and request_queue:
|
|
351
|
+
request_queue.add_request(
|
|
352
|
+
request_id,
|
|
353
|
+
RequestValue(
|
|
354
|
+
value=StreamValue(value=["Operation was cancelled"]),
|
|
355
|
+
status=RequestOption.FAILED
|
|
356
|
+
),
|
|
357
|
+
)
|
|
358
|
+
raise cancel_exc
|
|
359
|
+
|
|
315
360
|
except Exception as e:
|
|
316
361
|
console.print(Panel(
|
|
317
362
|
f"Error: {str(e)}",
|
autocoder/utils/thread_utils.py
CHANGED
|
@@ -1,161 +1,22 @@
|
|
|
1
|
-
from concurrent.futures import ThreadPoolExecutor, TimeoutError, CancelledError
|
|
2
|
-
from threading import Event
|
|
3
|
-
from inspect import signature
|
|
4
1
|
from functools import wraps
|
|
5
|
-
from typing import Any, Optional
|
|
2
|
+
from typing import Any, Optional, Dict, Callable
|
|
6
3
|
import threading
|
|
7
|
-
import logging
|
|
8
4
|
import time
|
|
9
|
-
from autocoder.common.global_cancel import global_cancel
|
|
5
|
+
from autocoder.common.global_cancel import global_cancel, CancelRequestedException
|
|
6
|
+
from autocoder.common.printer import Printer
|
|
7
|
+
from autocoder.common.auto_coder_lang import get_message, get_message_with_format
|
|
8
|
+
from autocoder.events.event_manager_singleton import get_event_manager
|
|
9
|
+
from autocoder.events import event_content as EventContentCreator
|
|
10
|
+
from autocoder.events.event_types import EventMetadata
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
"""Raised when a task is requested to be cancelled."""
|
|
13
|
-
pass
|
|
12
|
+
printer = Printer()
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
def run_in_thread(timeout: Optional[float] = None):
|
|
17
|
-
"""Decorator that runs a function in a thread with signal handling.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
timeout (float, optional): Maximum time to wait for thread completion in seconds.
|
|
21
|
-
If None, will wait indefinitely.
|
|
22
|
-
|
|
23
|
-
The decorated function will run in a separate thread and can be interrupted by
|
|
24
|
-
signals like Ctrl+C (KeyboardInterrupt). When interrupted, it will log the event
|
|
25
|
-
and clean up gracefully.
|
|
26
|
-
"""
|
|
27
|
-
def decorator(func):
|
|
28
|
-
@wraps(func)
|
|
29
|
-
def wrapper(*args, **kwargs):
|
|
30
|
-
with ThreadPoolExecutor(max_workers=1) as executor:
|
|
31
|
-
future = executor.submit(func, *args, **kwargs)
|
|
32
|
-
start_time = time.time()
|
|
33
|
-
|
|
34
|
-
while True:
|
|
35
|
-
try:
|
|
36
|
-
# 使用较短的超时时间进行轮询,确保能够响应中断信号
|
|
37
|
-
poll_timeout = 0.1
|
|
38
|
-
if timeout is not None:
|
|
39
|
-
remaining = timeout - (time.time() - start_time)
|
|
40
|
-
if remaining <= 0:
|
|
41
|
-
future.cancel()
|
|
42
|
-
raise TimeoutError(f"Timeout after {timeout}s in {func.__name__}")
|
|
43
|
-
poll_timeout = min(poll_timeout, remaining)
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
return future.result(timeout=poll_timeout)
|
|
47
|
-
except TimeoutError:
|
|
48
|
-
continue # 继续轮询
|
|
49
|
-
|
|
50
|
-
except KeyboardInterrupt:
|
|
51
|
-
logging.warning("KeyboardInterrupt received, attempting to cancel task...")
|
|
52
|
-
future.cancel()
|
|
53
|
-
raise
|
|
54
|
-
except Exception as e:
|
|
55
|
-
logging.error(f"Error occurred in thread: {str(e)}")
|
|
56
|
-
raise
|
|
57
|
-
return wrapper
|
|
58
|
-
return decorator
|
|
59
|
-
|
|
60
|
-
def run_in_thread_with_cancel(timeout: Optional[float] = None):
|
|
61
|
-
"""Decorator that runs a function in a thread with explicit cancellation support.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
timeout (float, optional): Maximum time to wait for thread completion in seconds.
|
|
65
|
-
If None, will wait indefinitely.
|
|
66
|
-
|
|
67
|
-
The decorated function MUST accept 'cancel_event' as its first parameter.
|
|
68
|
-
This cancel_event is a threading.Event object that can be used to check if
|
|
69
|
-
cancellation has been requested.
|
|
70
|
-
|
|
71
|
-
The decorated function can be called with an external cancel_event passed as a keyword argument.
|
|
72
|
-
If not provided, a new Event will be created.
|
|
73
|
-
|
|
74
|
-
Example:
|
|
75
|
-
@run_in_thread_with_cancel(timeout=10)
|
|
76
|
-
def long_task(cancel_event, arg1, arg2):
|
|
77
|
-
while not cancel_event.is_set():
|
|
78
|
-
# do work
|
|
79
|
-
if cancel_event.is_set():
|
|
80
|
-
raise CancellationRequested()
|
|
81
|
-
|
|
82
|
-
# 使用外部传入的cancel_event
|
|
83
|
-
external_cancel = Event()
|
|
84
|
-
try:
|
|
85
|
-
result = long_task(arg1, arg2, cancel_event=external_cancel)
|
|
86
|
-
except CancelledError:
|
|
87
|
-
print("Task was cancelled")
|
|
88
|
-
|
|
89
|
-
# 在其他地方取消任务
|
|
90
|
-
external_cancel.set()
|
|
91
|
-
"""
|
|
92
|
-
def decorator(func):
|
|
93
|
-
# 检查函数签名
|
|
94
|
-
sig = signature(func)
|
|
95
|
-
params = list(sig.parameters.keys())
|
|
96
|
-
if not params or params[0] != 'cancel_event':
|
|
97
|
-
raise ValueError(
|
|
98
|
-
f"Function {func.__name__} must have 'cancel_event' as its first parameter. "
|
|
99
|
-
f"Current parameters: {params}"
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
@wraps(func)
|
|
103
|
-
def wrapper(*args, **kwargs):
|
|
104
|
-
# 从kwargs中提取或创建cancel_event
|
|
105
|
-
cancel_event = kwargs.pop('cancel_event', None) or Event()
|
|
106
|
-
|
|
107
|
-
def cancellable_task():
|
|
108
|
-
try:
|
|
109
|
-
return func(cancel_event, *args, **kwargs)
|
|
110
|
-
except CancellationRequested:
|
|
111
|
-
logging.info(f"Task {func.__name__} was cancelled")
|
|
112
|
-
raise
|
|
113
|
-
except Exception as e:
|
|
114
|
-
logging.error(f"Error in {func.__name__}: {str(e)}")
|
|
115
|
-
raise
|
|
116
|
-
|
|
117
|
-
with ThreadPoolExecutor(max_workers=1) as executor:
|
|
118
|
-
future = executor.submit(cancellable_task)
|
|
119
|
-
start_time = time.time()
|
|
120
|
-
|
|
121
|
-
while True:
|
|
122
|
-
try:
|
|
123
|
-
# 使用较短的超时时间进行轮询,确保能够响应中断信号
|
|
124
|
-
poll_timeout = 0.1
|
|
125
|
-
if timeout is not None:
|
|
126
|
-
remaining = timeout - (time.time() - start_time)
|
|
127
|
-
if remaining <= 0:
|
|
128
|
-
cancel_event.set()
|
|
129
|
-
future.cancel()
|
|
130
|
-
raise TimeoutError(f"Timeout after {timeout}s in {func.__name__}")
|
|
131
|
-
poll_timeout = min(poll_timeout, remaining)
|
|
132
|
-
|
|
133
|
-
try:
|
|
134
|
-
return future.result(timeout=poll_timeout)
|
|
135
|
-
except TimeoutError:
|
|
136
|
-
continue # 继续轮询
|
|
137
|
-
|
|
138
|
-
except KeyboardInterrupt:
|
|
139
|
-
logging.warning(f"KeyboardInterrupt received, cancelling {func.__name__}...")
|
|
140
|
-
cancel_event.set()
|
|
141
|
-
future.cancel()
|
|
142
|
-
raise CancelledError("Task cancelled by user")
|
|
143
|
-
except CancellationRequested:
|
|
144
|
-
logging.info(f"Task {func.__name__} was cancelled")
|
|
145
|
-
raise CancelledError("Task cancelled by request")
|
|
146
|
-
except Exception as e:
|
|
147
|
-
logging.error(f"Error occurred in thread: {str(e)}")
|
|
148
|
-
raise
|
|
149
|
-
|
|
150
|
-
return wrapper
|
|
151
|
-
return decorator
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def run_in_raw_thread():
|
|
14
|
+
def run_in_raw_thread(token: Optional[str] = None, context: Optional[Dict[str, Any]] = None):
|
|
155
15
|
"""A decorator that runs a function in a separate thread and handles exceptions.
|
|
156
16
|
|
|
157
17
|
Args:
|
|
158
|
-
|
|
18
|
+
token (Optional[str]): Optional cancellation token for this specific thread
|
|
19
|
+
context (Optional[Dict[str, Any]]): Optional context information for cancellation
|
|
159
20
|
|
|
160
21
|
Returns:
|
|
161
22
|
A wrapper function that executes the decorated function in a thread
|
|
@@ -166,28 +27,36 @@ def run_in_raw_thread():
|
|
|
166
27
|
3. Propagate exceptions from the thread
|
|
167
28
|
4. Support function arguments
|
|
168
29
|
5. Preserve function metadata
|
|
30
|
+
6. Support token-based cancellation
|
|
31
|
+
7. Provide context information for cancellation
|
|
169
32
|
"""
|
|
170
|
-
def decorator(func):
|
|
33
|
+
def decorator(func: Callable):
|
|
171
34
|
|
|
172
35
|
@wraps(func)
|
|
173
36
|
def wrapper(*args, **kwargs):
|
|
174
37
|
# Store thread results
|
|
175
38
|
result = []
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
39
|
+
exception_raised = [None] # 存储工作线程中的异常
|
|
40
|
+
thread_token = token
|
|
41
|
+
thread_context = context or {}
|
|
42
|
+
thread_terminated = threading.Event() # 用于标记线程是否已终止
|
|
43
|
+
|
|
44
|
+
def worker():
|
|
45
|
+
try:
|
|
46
|
+
# 执行用户函数
|
|
185
47
|
ret = func(*args, **kwargs)
|
|
186
48
|
result.append(ret)
|
|
187
|
-
|
|
49
|
+
except CancelRequestedException as e:
|
|
50
|
+
# 处理取消异常
|
|
51
|
+
printer.print_in_terminal("generation_cancelled")
|
|
52
|
+
exception_raised[0] = e
|
|
188
53
|
except Exception as e:
|
|
189
|
-
|
|
190
|
-
|
|
54
|
+
# 存储其他异常
|
|
55
|
+
exception_raised[0] = e
|
|
56
|
+
finally:
|
|
57
|
+
# 无论如何执行完毕后,重置取消标志并标记线程已终止
|
|
58
|
+
global_cancel.reset(thread_token)
|
|
59
|
+
thread_terminated.set()
|
|
191
60
|
|
|
192
61
|
# Create and start thread with a meaningful name
|
|
193
62
|
thread = threading.Thread(target=worker, name=f"{func.__name__}_thread")
|
|
@@ -195,16 +64,56 @@ def run_in_raw_thread():
|
|
|
195
64
|
|
|
196
65
|
try:
|
|
197
66
|
thread.start()
|
|
67
|
+
|
|
68
|
+
# Poll thread status with timeout to allow for interruption
|
|
69
|
+
cancelled_by_keyboard = False
|
|
70
|
+
max_wait_time = 30 # 最大等待时间(秒)
|
|
71
|
+
wait_start_time = time.time()
|
|
72
|
+
|
|
198
73
|
while thread.is_alive():
|
|
74
|
+
# 每次等待较短时间,以便能够及时响应中断
|
|
199
75
|
thread.join(0.1)
|
|
76
|
+
|
|
77
|
+
# 检查是否已经超过最大等待时间(仅适用于已取消的情况)
|
|
78
|
+
elapsed_time = time.time() - wait_start_time
|
|
79
|
+
if cancelled_by_keyboard and elapsed_time > max_wait_time:
|
|
80
|
+
printer.print_in_terminal("force_terminating_thread")
|
|
81
|
+
break
|
|
82
|
+
|
|
83
|
+
# 检查线程间的取消请求
|
|
84
|
+
if global_cancel.is_requested(thread_token):
|
|
85
|
+
# 传播取消请求到工作线程
|
|
86
|
+
raise CancelRequestedException(thread_token)
|
|
200
87
|
|
|
88
|
+
# 如果工作线程出现了异常,在主线程中重新抛出
|
|
89
|
+
if exception_raised[0] is not None:
|
|
90
|
+
raise exception_raised[0]
|
|
91
|
+
|
|
92
|
+
# 返回结果
|
|
201
93
|
return result[0] if result else None
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
94
|
+
|
|
95
|
+
except KeyboardInterrupt:
|
|
96
|
+
# 设置取消标志
|
|
97
|
+
cancel_context = {"message": get_message("task_cancelled_by_user"), "source": "keyboard_interrupt"}
|
|
98
|
+
cancel_context.update(thread_context)
|
|
99
|
+
global_cancel.set(thread_token, cancel_context)
|
|
100
|
+
printer.print_in_terminal("cancellation_requested")
|
|
101
|
+
|
|
102
|
+
# 标记为键盘中断取消
|
|
103
|
+
cancelled_by_keyboard = True
|
|
104
|
+
wait_start_time = time.time()
|
|
105
|
+
|
|
106
|
+
# 等待线程终止或检测到取消
|
|
107
|
+
while thread.is_alive() and not thread_terminated.is_set():
|
|
108
|
+
thread.join(0.5)
|
|
109
|
+
elapsed_time = time.time() - wait_start_time
|
|
110
|
+
if elapsed_time > max_wait_time:
|
|
111
|
+
printer.print_in_terminal("force_raising_keyboard_interrupt")
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
# 如果线程已终止且有异常,优先抛出该异常
|
|
115
|
+
if exception_raised[0] is not None:
|
|
116
|
+
raise exception_raised[0]
|
|
208
117
|
|
|
209
118
|
return wrapper
|
|
210
119
|
return decorator
|
autocoder/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.305"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|