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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-coder
3
- Version: 0.1.304
3
+ Version: 0.1.305
4
4
  Summary: AutoCoder: AutoCoder
5
5
  Author: allwefantasy
6
6
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
@@ -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=d3YmmmryiVVQ2h45ytNBl1OQ8PJLRr75GUbEFV3zbaU,23
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=q_6hD0XHmVkoo0pj9vN-x7qoqgrXWF7rlLs9n2yMGNk,62628
32
- autocoder/commands/auto_web.py,sha256=_449f4rCoRG7Sv0SB0hIBRFLPLPJ5DgWW4DlI22a3XY,39383
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=xAizNW6PIRAIhCfmmJg_2cnfJkyQu73PFdw-K3DieYo,36321
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=hT7J7J5ChThIhk2x11_v4v9ASIn4HtwyPD26t2s-fwc,418
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=neQQwF1q7M-HDYheyuIL-UfIqN_hPS0c6UEWKdRhZaI,28917
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=ZZ3bca55Js45at_lXHFtha7j8LMLLpYUtZ7URmx6eEk,7886
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=3-SHlmeQMv6SFxNj7vVcNRDAYj9ZshuJJ1zXwBi0cDc,30873
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=tv9fhFZOjI18AxVUJbpe_xjBGMpkqgDcOlz9pnDtNik,8583
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=Wh_KSjYIxE3r4cU4sdFuSoNVxqcUw9c4-V-DaTVYWvg,13131
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.304.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
197
- auto_coder-0.1.304.dist-info/METADATA,sha256=zG9ItZR6be3SWKK1w-9EF_vaq1Tf9tiQ1HuAv3NTvhs,2721
198
- auto_coder-0.1.304.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
199
- auto_coder-0.1.304.dist-info/entry_points.txt,sha256=0nzHtHH4pNcM7xq4EBA2toS28Qelrvcbrr59GqD_0Ak,350
200
- auto_coder-0.1.304.dist-info/top_level.txt,sha256=Jqc0_uJSw2GwoFQAa9iJxYns-2mWla-9ok_Y3Gcznjk,10
201
- auto_coder-0.1.304.dist-info/RECORD,,
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
- if global_cancel.requested:
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
@@ -794,15 +794,7 @@ class AutoWebTuner:
794
794
  logger.info(f"开始执行迭代 {iterations}/{max_iterations}")
795
795
 
796
796
  # 检查是否需要取消操作
797
- if global_cancel.requested:
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._flag = False
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
- return self._flag
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
- self._flag = True
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
- self._flag = False
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
- if global_cancel.requested:
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
- if global_cancel.requested:
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
- if global_cancel.requested:
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
- if global_cancel.requested:
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
- if global_cancel.requested:
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
- if global_cancel.requested:
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
- if global_cancel.requested:
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
- if global_cancel.requested:
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
- if global_cancel.requested:
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
- if global_cancel.requested:
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
- if global_cancel.requested:
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
- if global_cancel.requested:
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]]) -> List[str]:
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
- if global_cancel.requested:
201
- printer = Printer(console)
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)}",
@@ -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
- class CancellationRequested(Exception):
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
- func: The function to run in a thread
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
- exception = []
177
- def worker():
178
- try:
179
- # 如果刚开始就遇到了,可能是用户中断的还没有释放
180
- # 等待五秒后强行释放
181
- if global_cancel.requested:
182
- time.sleep(5)
183
- global_cancel.reset()
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
- global_cancel.reset()
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
- global_cancel.reset()
190
- raise
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
- except KeyboardInterrupt:
203
- global_cancel.set()
204
- raise KeyboardInterrupt("Task was cancelled by user")
205
- except Exception as e:
206
- global_cancel.reset()
207
- raise
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.304"
1
+ __version__ = "0.1.305"