auto-coder 0.1.268__py3-none-any.whl → 0.1.270__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.

@@ -0,0 +1,2647 @@
1
+ from itertools import product
2
+ from rich.console import Console
3
+ from rich.panel import Panel
4
+ from prompt_toolkit.formatted_text import HTML
5
+ from prompt_toolkit.shortcuts import radiolist_dialog
6
+ from prompt_toolkit import prompt
7
+ import os
8
+ import yaml
9
+ import json
10
+ import sys
11
+ import io
12
+ import uuid
13
+ import glob
14
+ import time
15
+ import hashlib
16
+ from contextlib import contextmanager
17
+ from typing import List, Dict, Any, Optional
18
+ from autocoder.common import AutoCoderArgs
19
+ from pydantic import BaseModel
20
+ from autocoder.common.result_manager import ResultManager
21
+ from autocoder.version import __version__
22
+ from autocoder.auto_coder import main as auto_coder_main
23
+ from autocoder.utils import get_last_yaml_file
24
+ from autocoder.index.symbols_utils import (
25
+ extract_symbols,
26
+ SymbolType,
27
+ )
28
+ import platform
29
+ import subprocess
30
+ from rich.console import Console
31
+ from rich.panel import Panel
32
+ from rich.table import Table
33
+ from rich.live import Live
34
+ from rich.text import Text
35
+ from rich.live import Live
36
+ from rich.markdown import Markdown
37
+ from byzerllm.utils.nontext import Image
38
+ import git
39
+ from autocoder.common import git_utils
40
+ from autocoder.chat_auto_coder_lang import get_message
41
+ from autocoder.agent.auto_guess_query import AutoGuessQuery
42
+ from autocoder.common.mcp_server import get_mcp_server, McpRequest, McpInstallRequest, McpRemoveRequest, McpListRequest, McpListRunningRequest, McpRefreshRequest
43
+ import byzerllm
44
+ from byzerllm.utils import format_str_jinja2
45
+ from autocoder.common.memory_manager import get_global_memory_file_paths
46
+ from autocoder import models as models_module
47
+ import shlex
48
+ from autocoder.utils.llms import get_single_llm
49
+ import pkg_resources
50
+ from autocoder.common.printer import Printer
51
+ from autocoder.utils.thread_utils import run_in_raw_thread
52
+ from autocoder.common.command_completer import CommandCompleter,FileSystemModel as CCFileSystemModel,MemoryConfig as CCMemoryModel
53
+ from autocoder.common.conf_validator import ConfigValidator
54
+
55
+ class SymbolItem(BaseModel):
56
+ symbol_name: str
57
+ symbol_type: SymbolType
58
+ file_name: str
59
+
60
+ class InitializeSystemRequest(BaseModel):
61
+ product_mode: str
62
+ skip_provider_selection: bool
63
+ debug: bool
64
+ quick: bool
65
+ lite: bool
66
+ pro: bool
67
+
68
+
69
+ if platform.system() == "Windows":
70
+ from colorama import init
71
+
72
+ init()
73
+
74
+
75
+ memory = {
76
+ "conversation": [],
77
+ "current_files": {"files": [], "groups": {}},
78
+ "conf": {},
79
+ "exclude_dirs": [],
80
+ "mode": "auto_detect", # 新增mode字段,默认为 auto_detect 模式
81
+ }
82
+
83
+ project_root = os.getcwd()
84
+
85
+ base_persist_dir = os.path.join(".auto-coder", "plugins", "chat-auto-coder")
86
+
87
+ defaut_exclude_dirs = [".git", "node_modules", "dist", "build", "__pycache__"]
88
+
89
+ commands = [
90
+ "/add_files",
91
+ "/remove_files",
92
+ "/list_files",
93
+ "/conf",
94
+ "/coding",
95
+ "/chat",
96
+ "/ask",
97
+ "/commit",
98
+ "/revert",
99
+ "/index/query",
100
+ "/index/build",
101
+ "/index/export",
102
+ "/index/import",
103
+ "/exclude_files",
104
+ "/help",
105
+ "/shell",
106
+ "/voice_input",
107
+ "/exit",
108
+ "/summon",
109
+ "/mode",
110
+ "/lib",
111
+ "/design",
112
+ "/mcp",
113
+ "/models",
114
+ "/auto",
115
+ "/conf/export",
116
+ "/conf/import",
117
+ "/exclude_dirs",
118
+ ]
119
+
120
+ def load_tokenizer():
121
+ from autocoder.rag.variable_holder import VariableHolder
122
+ from tokenizers import Tokenizer
123
+ try:
124
+ tokenizer_path = pkg_resources.resource_filename(
125
+ "autocoder", "data/tokenizer.json"
126
+ )
127
+ VariableHolder.TOKENIZER_PATH = tokenizer_path
128
+ VariableHolder.TOKENIZER_MODEL = Tokenizer.from_file(tokenizer_path)
129
+ except FileNotFoundError:
130
+ tokenizer_path = None
131
+
132
+
133
+ def configure_project_type():
134
+ from prompt_toolkit.lexers import PygmentsLexer
135
+ from pygments.lexers.markup import MarkdownLexer
136
+ from prompt_toolkit.formatted_text import HTML
137
+ from prompt_toolkit.shortcuts import print_formatted_text
138
+ from prompt_toolkit.styles import Style
139
+ from html import escape
140
+
141
+ style = Style.from_dict(
142
+ {
143
+ "info": "#ansicyan",
144
+ "warning": "#ansiyellow",
145
+ "input-area": "#ansigreen",
146
+ "header": "#ansibrightyellow bold",
147
+ }
148
+ )
149
+
150
+ def print_info(text):
151
+ print_formatted_text(HTML(f"<info>{escape(text)}</info>"), style=style)
152
+
153
+ def print_warning(text):
154
+ print_formatted_text(
155
+ HTML(f"<warning>{escape(text)}</warning>"), style=style)
156
+
157
+ def print_header(text):
158
+ print_formatted_text(
159
+ HTML(f"<header>{escape(text)}</header>"), style=style)
160
+
161
+ print_header(f"\n=== {get_message('project_type_config')} ===\n")
162
+ print_info(get_message("project_type_supports"))
163
+ print_info(get_message("language_suffixes"))
164
+ print_info(get_message("predefined_types"))
165
+ print_info(get_message("mixed_projects"))
166
+ print_info(get_message("examples"))
167
+
168
+ print_warning(f"{get_message('default_type')}\n")
169
+
170
+ project_type = prompt(
171
+ get_message("enter_project_type"), default="py", style=style
172
+ ).strip()
173
+
174
+ if project_type:
175
+ configure(f"project_type:{project_type}", skip_print=True)
176
+ configure("skip_build_index:false", skip_print=True)
177
+ print_info(f"\n{get_message('project_type_set')} {project_type}")
178
+ else:
179
+ print_info(f"\n{get_message('using_default_type')}")
180
+
181
+ print_warning(f"\n{get_message('change_setting_later')}:")
182
+ print_warning("/conf project_type:<new_type>\n")
183
+
184
+ return project_type
185
+
186
+
187
+ def initialize_system(args:InitializeSystemRequest):
188
+ from autocoder.utils.model_provider_selector import ModelProviderSelector
189
+ from autocoder import models as models_module
190
+ print(f"\n\033[1;34m{get_message('initializing')}\033[0m")
191
+
192
+ first_time = [False]
193
+ configure_success = [False]
194
+
195
+ def print_status(message, status):
196
+ if status == "success":
197
+ print(f"\033[32m✓ {message}\033[0m")
198
+ elif status == "warning":
199
+ print(f"\033[33m! {message}\033[0m")
200
+ elif status == "error":
201
+ print(f"\033[31m✗ {message}\033[0m")
202
+ else:
203
+ print(f" {message}")
204
+
205
+ def init_project():
206
+ if not os.path.exists(".auto-coder"):
207
+ first_time[0] = True
208
+ print_status(get_message("not_initialized"), "warning")
209
+ init_choice = input(
210
+ f" {get_message('init_prompt')}").strip().lower()
211
+ if init_choice == "y":
212
+ try:
213
+ subprocess.run(
214
+ ["auto-coder", "init", "--source_dir", "."], check=True
215
+ )
216
+ print_status(get_message("init_success"), "success")
217
+ except subprocess.CalledProcessError:
218
+ print_status(get_message("init_fail"), "error")
219
+ print_status(get_message("init_manual"), "warning")
220
+ exit(1)
221
+ else:
222
+ print_status(get_message("exit_no_init"), "warning")
223
+ exit(1)
224
+
225
+ if not os.path.exists(base_persist_dir):
226
+ os.makedirs(base_persist_dir, exist_ok=True)
227
+ print_status(get_message("created_dir").format(
228
+ base_persist_dir), "success")
229
+
230
+ if first_time[0]:
231
+ configure_project_type()
232
+ configure_success[0] = True
233
+
234
+ print_status(get_message("init_complete"), "success")
235
+
236
+ init_project()
237
+
238
+ if not args.skip_provider_selection and first_time[0]:
239
+ if args.product_mode == "lite":
240
+ ## 如果已经是配置过的项目,就无需再选择
241
+ if first_time[0]:
242
+ if not models_module.check_model_exists("v3_chat") or not models_module.check_model_exists("r1_chat"):
243
+ model_provider_selector = ModelProviderSelector()
244
+ model_provider_info = model_provider_selector.select_provider()
245
+ if model_provider_info is not None:
246
+ models_json_list = model_provider_selector.to_models_json(model_provider_info)
247
+ models_module.add_and_activate_models(models_json_list)
248
+
249
+ if args.product_mode == "pro":
250
+ # Check if Ray is running
251
+ print_status(get_message("checking_ray"), "")
252
+ ray_status = subprocess.run(
253
+ ["ray", "status"], capture_output=True, text=True)
254
+ if ray_status.returncode != 0:
255
+ print_status(get_message("ray_not_running"), "warning")
256
+ try:
257
+ subprocess.run(["ray", "start", "--head"], check=True)
258
+ print_status(get_message("ray_start_success"), "success")
259
+ except subprocess.CalledProcessError:
260
+ print_status(get_message("ray_start_fail"), "error")
261
+ return
262
+ else:
263
+ print_status(get_message("ray_running"), "success")
264
+
265
+ # Check if deepseek_chat model is available
266
+ print_status(get_message("checking_model"), "")
267
+ try:
268
+ result = subprocess.run(
269
+ ["easy-byzerllm", "chat", "v3_chat", "你好"],
270
+ capture_output=True,
271
+ text=True,
272
+ timeout=30,
273
+ )
274
+ if result.returncode == 0:
275
+ print_status(get_message("model_available"), "success")
276
+ init_project()
277
+ print_status(get_message("init_complete_final"), "success")
278
+ return
279
+ except subprocess.TimeoutExpired:
280
+ print_status(get_message("model_timeout"), "error")
281
+ except subprocess.CalledProcessError:
282
+ print_status(get_message("model_error"), "error")
283
+
284
+ # If deepseek_chat is not available
285
+ print_status(get_message("model_not_available"), "warning")
286
+ api_key = prompt(HTML(f"<b>{get_message('enter_api_key')} </b>"))
287
+
288
+ print_status(get_message("deploying_model").format("Deepseek官方"), "")
289
+ deploy_cmd = [
290
+ "byzerllm",
291
+ "deploy",
292
+ "--pretrained_model_type",
293
+ "saas/openai",
294
+ "--cpus_per_worker",
295
+ "0.001",
296
+ "--gpus_per_worker",
297
+ "0",
298
+ "--worker_concurrency",
299
+ "1000",
300
+ "--num_workers",
301
+ "1",
302
+ "--infer_params",
303
+ f"saas.base_url=https://api.deepseek.com/v1 saas.api_key={api_key} saas.model=deepseek-chat",
304
+ "--model",
305
+ "v3_chat",
306
+ ]
307
+
308
+ try:
309
+ subprocess.run(deploy_cmd, check=True)
310
+ print_status(get_message("deploy_complete"), "success")
311
+ except subprocess.CalledProcessError:
312
+ print_status(get_message("deploy_fail"), "error")
313
+ return
314
+
315
+
316
+ deploy_cmd = [
317
+ "byzerllm",
318
+ "deploy",
319
+ "--pretrained_model_type",
320
+ "saas/reasoning_openai",
321
+ "--cpus_per_worker",
322
+ "0.001",
323
+ "--gpus_per_worker",
324
+ "0",
325
+ "--worker_concurrency",
326
+ "1000",
327
+ "--num_workers",
328
+ "1",
329
+ "--infer_params",
330
+ f"saas.base_url=https://api.deepseek.com/v1 saas.api_key={api_key} saas.model=deepseek-reasoner",
331
+ "--model",
332
+ "r1_chat",
333
+ ]
334
+
335
+ try:
336
+ subprocess.run(deploy_cmd, check=True)
337
+ print_status(get_message("deploy_complete"), "success")
338
+ except subprocess.CalledProcessError:
339
+ print_status(get_message("deploy_fail"), "error")
340
+ return
341
+
342
+ # Validate the deployment
343
+ print_status(get_message("validating_deploy"), "")
344
+ try:
345
+ validation_result = subprocess.run(
346
+ ["easy-byzerllm", "chat", "v3_chat", "你好"],
347
+ capture_output=True,
348
+ text=True,
349
+ timeout=30,
350
+ check=True,
351
+ )
352
+ print_status(get_message("validation_success"), "success")
353
+ except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
354
+ print_status(get_message("validation_fail"), "error")
355
+ print_status(get_message("manual_start"), "warning")
356
+ print_status("easy-byzerllm chat v3_chat 你好", "")
357
+
358
+ print_status(get_message("init_complete_final"), "success")
359
+ configure_success[0] = True
360
+
361
+ if first_time[0] and args.product_mode == "pro" and configure_success[0]:
362
+ configure(f"model:v3_chat", skip_print=True)
363
+ configure(f"chat_model:r1_chat", skip_print=True)
364
+ configure(f"generate_rerank_model:r1_chat", skip_print=True)
365
+ configure(f"code_model:v3_chat", skip_print=True)
366
+ configure(f"index_filter_model:r1_chat", skip_print=True)
367
+
368
+ if first_time[0] and args.product_mode == "lite" and models_module.check_model_exists("v3_chat"):
369
+ configure(f"model:v3_chat", skip_print=True)
370
+ configure(f"chat_model:r1_chat", skip_print=True)
371
+ configure(f"generate_rerank_model:r1_chat", skip_print=True)
372
+ configure(f"code_model:v3_chat", skip_print=True)
373
+ configure(f"index_filter_model:r1_chat", skip_print=True)
374
+
375
+
376
+ def convert_yaml_config_to_str(yaml_config):
377
+ yaml_content = yaml.safe_dump(
378
+ yaml_config,
379
+ allow_unicode=True,
380
+ default_flow_style=False,
381
+ default_style=None,
382
+ )
383
+ return yaml_content
384
+
385
+
386
+ def get_all_file_names_in_project() -> List[str]:
387
+
388
+ file_names = []
389
+ final_exclude_dirs = defaut_exclude_dirs + memory.get("exclude_dirs", [])
390
+ for root, dirs, files in os.walk(project_root, followlinks=True):
391
+ dirs[:] = [d for d in dirs if d not in final_exclude_dirs]
392
+ file_names.extend(files)
393
+ return file_names
394
+
395
+
396
+ def get_all_file_in_project() -> List[str]:
397
+
398
+ file_names = []
399
+ final_exclude_dirs = defaut_exclude_dirs + memory.get("exclude_dirs", [])
400
+ for root, dirs, files in os.walk(project_root, followlinks=True):
401
+ dirs[:] = [d for d in dirs if d not in final_exclude_dirs]
402
+ for file in files:
403
+ file_names.append(os.path.join(root, file))
404
+ return file_names
405
+
406
+
407
+ def get_all_file_in_project_with_dot() -> List[str]:
408
+ file_names = []
409
+ final_exclude_dirs = defaut_exclude_dirs + memory.get("exclude_dirs", [])
410
+ for root, dirs, files in os.walk(project_root, followlinks=True):
411
+ dirs[:] = [d for d in dirs if d not in final_exclude_dirs]
412
+ for file in files:
413
+ file_names.append(os.path.join(
414
+ root, file).replace(project_root, "."))
415
+ return file_names
416
+
417
+
418
+ def get_all_dir_names_in_project() -> List[str]:
419
+ dir_names = []
420
+ final_exclude_dirs = defaut_exclude_dirs + memory.get("exclude_dirs", [])
421
+ for root, dirs, files in os.walk(project_root, followlinks=True):
422
+ dirs[:] = [d for d in dirs if d not in final_exclude_dirs]
423
+ for dir in dirs:
424
+ dir_names.append(dir)
425
+ return dir_names
426
+
427
+
428
+ def find_files_in_project(patterns: List[str]) -> List[str]:
429
+ matched_files = []
430
+ final_exclude_dirs = defaut_exclude_dirs + memory.get("exclude_dirs", [])
431
+
432
+ for pattern in patterns:
433
+ if "*" in pattern or "?" in pattern:
434
+ for file_path in glob.glob(pattern, recursive=True):
435
+ if os.path.isfile(file_path):
436
+ abs_path = os.path.abspath(file_path)
437
+ if not any(
438
+ exclude_dir in abs_path.split(os.sep)
439
+ for exclude_dir in final_exclude_dirs
440
+ ):
441
+ matched_files.append(abs_path)
442
+ else:
443
+ is_added = False
444
+ # add files belongs to project
445
+ for root, dirs, files in os.walk(project_root, followlinks=True):
446
+ dirs[:] = [d for d in dirs if d not in final_exclude_dirs]
447
+ if pattern in files:
448
+ matched_files.append(os.path.join(root, pattern))
449
+ is_added = True
450
+ else:
451
+ for file in files:
452
+ _pattern = os.path.abspath(pattern)
453
+ if _pattern in os.path.join(root, file):
454
+ matched_files.append(os.path.join(root, file))
455
+ is_added = True
456
+ # add files not belongs to project
457
+ if not is_added:
458
+ matched_files.append(pattern)
459
+
460
+ return list(set(matched_files))
461
+
462
+
463
+ def convert_config_value(key, value):
464
+ field_info = AutoCoderArgs.model_fields.get(key)
465
+ if field_info:
466
+ if value.lower() in ["true", "false"]:
467
+ return value.lower() == "true"
468
+ elif "int" in str(field_info.annotation):
469
+ return int(value)
470
+ elif "float" in str(field_info.annotation):
471
+ return float(value)
472
+ else:
473
+ return value
474
+ else:
475
+ print(f"Invalid configuration key: {key}")
476
+ return None
477
+
478
+
479
+ @contextmanager
480
+ def redirect_stdout():
481
+ original_stdout = sys.stdout
482
+ sys.stdout = f = io.StringIO()
483
+ try:
484
+ yield f
485
+ finally:
486
+ sys.stdout = original_stdout
487
+
488
+
489
+ def configure(conf: str, skip_print=False):
490
+ printer = Printer()
491
+ parts = conf.split(None, 1)
492
+ if len(parts) == 2 and parts[0] in ["/drop", "/unset", "/remove"]:
493
+ key = parts[1].strip()
494
+ if key in memory["conf"]:
495
+ del memory["conf"][key]
496
+ save_memory()
497
+ printer.print_in_terminal("config_delete_success", style="green", key=key)
498
+ else:
499
+ printer.print_in_terminal("config_not_found", style="yellow", key=key)
500
+ else:
501
+ parts = conf.split(":", 1)
502
+ if len(parts) != 2:
503
+ printer.print_in_terminal("config_invalid_format", style="red")
504
+ return
505
+ key, value = parts
506
+ key = key.strip()
507
+ value = value.strip()
508
+ if not value:
509
+ printer.print_in_terminal("config_value_empty", style="red")
510
+ return
511
+ product_mode = memory["conf"].get("product_mode",None)
512
+ if product_mode:
513
+ ConfigValidator.validate(key, value, product_mode)
514
+ memory["conf"][key] = value
515
+ save_memory()
516
+ if not skip_print:
517
+ printer.print_in_terminal("config_set_success", style="green", key=key, value=value)
518
+
519
+ # word_completer = WordCompleter(commands)
520
+
521
+
522
+ def get_symbol_list() -> List[SymbolItem]:
523
+ list_of_symbols = []
524
+ index_file = os.path.join(".auto-coder", "index.json")
525
+
526
+ if os.path.exists(index_file):
527
+ with open(index_file, "r",encoding="utf-8") as file:
528
+ index_data = json.load(file)
529
+ else:
530
+ index_data = {}
531
+
532
+ for item in index_data.values():
533
+ symbols_str = item["symbols"]
534
+ module_name = item["module_name"]
535
+ info1 = extract_symbols(symbols_str)
536
+ for name in info1.classes:
537
+ list_of_symbols.append(
538
+ SymbolItem(
539
+ symbol_name=name,
540
+ symbol_type=SymbolType.CLASSES,
541
+ file_name=module_name,
542
+ )
543
+ )
544
+ for name in info1.functions:
545
+ list_of_symbols.append(
546
+ SymbolItem(
547
+ symbol_name=name,
548
+ symbol_type=SymbolType.FUNCTIONS,
549
+ file_name=module_name,
550
+ )
551
+ )
552
+ for name in info1.variables:
553
+ list_of_symbols.append(
554
+ SymbolItem(
555
+ symbol_name=name,
556
+ symbol_type=SymbolType.VARIABLES,
557
+ file_name=module_name,
558
+ )
559
+ )
560
+ return list_of_symbols
561
+
562
+
563
+ def save_memory():
564
+ with open(os.path.join(base_persist_dir, "memory.json"), "w",encoding="utf-8") as f:
565
+ json.dump(memory, f, indent=2, ensure_ascii=False)
566
+ load_memory()
567
+
568
+
569
+ def load_memory():
570
+ global memory
571
+ memory_path = os.path.join(base_persist_dir, "memory.json")
572
+ if os.path.exists(memory_path):
573
+ with open(memory_path, "r", encoding="utf-8") as f:
574
+ memory = json.load(f)
575
+ completer.update_current_files(memory["current_files"]["files"])
576
+
577
+ def get_memory():
578
+ global memory
579
+ return memory
580
+
581
+
582
+ completer = CommandCompleter(commands,
583
+ file_system_model=CCFileSystemModel(project_root=project_root,
584
+ defaut_exclude_dirs=defaut_exclude_dirs,
585
+ get_all_file_names_in_project=get_all_file_names_in_project,
586
+ get_all_file_in_project=get_all_file_in_project,
587
+ get_all_dir_names_in_project=get_all_dir_names_in_project,
588
+ get_all_file_in_project_with_dot=get_all_file_in_project_with_dot,
589
+ get_symbol_list=get_symbol_list
590
+ ),
591
+ memory_model=CCMemoryModel(memory=memory,
592
+ save_memory_func=save_memory))
593
+
594
+
595
+
596
+
597
+ def print_conf(content:Dict[str,Any]):
598
+ """Display configuration dictionary in a Rich table format with enhanced visual styling.
599
+
600
+ Args:
601
+ conf (Dict[str, Any]): Configuration dictionary to display
602
+ """
603
+ console = Console()
604
+
605
+ # Create a styled table with rounded borders
606
+ table = Table(
607
+ show_header=True,
608
+ header_style="bold magenta",
609
+ title=get_message("conf_title"),
610
+ title_style="bold blue",
611
+ border_style="blue",
612
+ show_lines=True
613
+ )
614
+
615
+ # Add columns with explicit width and alignment
616
+ table.add_column(get_message("conf_key"), style="cyan", justify="right", width=30, no_wrap=False)
617
+ table.add_column(get_message("conf_value"), style="green", justify="left", width=50, no_wrap=False)
618
+
619
+ # Sort keys for consistent display
620
+ for key in sorted(content.keys()):
621
+ value = content[key]
622
+ # Format value based on type
623
+ if isinstance(value, (dict, list)):
624
+ formatted_value = Text(json.dumps(value, indent=2), style="yellow")
625
+ elif isinstance(value, bool):
626
+ formatted_value = Text(str(value), style="bright_green" if value else "red")
627
+ elif isinstance(value, (int, float)):
628
+ formatted_value = Text(str(value), style="bright_cyan")
629
+ else:
630
+ formatted_value = Text(str(value), style="green")
631
+
632
+ table.add_row(str(key), formatted_value)
633
+
634
+ # Add padding and print with a panel
635
+ console.print(Panel(
636
+ table,
637
+ padding=(1, 2),
638
+ subtitle=f"[italic]{get_message('conf_subtitle')}[/italic]",
639
+ border_style="blue"
640
+ ))
641
+
642
+ def revert():
643
+ result_manager = ResultManager()
644
+ last_yaml_file = get_last_yaml_file("actions")
645
+ if last_yaml_file:
646
+ file_path = os.path.join("actions", last_yaml_file)
647
+
648
+ with redirect_stdout() as output:
649
+ auto_coder_main(["revert", "--file", file_path])
650
+ s = output.getvalue()
651
+
652
+ console = Console()
653
+ panel = Panel(
654
+ Markdown(s),
655
+ title="Revert Result",
656
+ border_style="green" if "Successfully reverted changes" in s else "red",
657
+ padding=(1, 2),
658
+ expand=False
659
+ )
660
+ console.print(panel)
661
+
662
+ if "Successfully reverted changes" in s:
663
+ result_manager.append(content=s, meta={"action": "revert","success":False, "input":{
664
+ }})
665
+
666
+ os.remove(file_path)
667
+ else:
668
+ result_manager.append(content=s, meta={"action": "revert","success":False, "input":{
669
+ }})
670
+ else:
671
+ result_manager.append(content="No previous chat action found to revert.", meta={"action": "revert","success":False, "input":{
672
+ }})
673
+
674
+
675
+ def add_files(args: List[str]):
676
+
677
+ result_manager = ResultManager()
678
+ if "groups" not in memory["current_files"]:
679
+ memory["current_files"]["groups"] = {}
680
+ if "groups_info" not in memory["current_files"]:
681
+ memory["current_files"]["groups_info"] = {}
682
+ if "current_groups" not in memory["current_files"]:
683
+ memory["current_files"]["current_groups"] = []
684
+ groups = memory["current_files"]["groups"]
685
+ groups_info = memory["current_files"]["groups_info"]
686
+
687
+ console = Console()
688
+ printer = Printer()
689
+
690
+ if not args:
691
+ printer.print_in_terminal("add_files_no_args", style="red")
692
+ result_manager.append(content=printer.get_message_from_key("add_files_no_args"),
693
+ meta={"action": "add_files","success":False, "input":{ "args": args}})
694
+ return
695
+
696
+ if args[0] == "/refresh":
697
+ completer.refresh_files()
698
+ load_memory()
699
+ console.print(
700
+ Panel("Refreshed file list.",
701
+ title="Files Refreshed", border_style="green")
702
+ )
703
+ result_manager.append(content="Files refreshed.",
704
+ meta={"action": "add_files","success":True, "input":{ "args": args}})
705
+ return
706
+
707
+ if args[0] == "/group":
708
+ if len(args) == 1 or (len(args) == 2 and args[1] == "list"):
709
+ if not groups:
710
+ console.print(
711
+ Panel("No groups defined.", title="Groups",
712
+ border_style="yellow")
713
+ )
714
+ result_manager.append(content="No groups defined.",
715
+ meta={"action": "add_files","success":False, "input":{ "args": args}})
716
+ else:
717
+ table = Table(
718
+ title="Defined Groups",
719
+ show_header=True,
720
+ header_style="bold magenta",
721
+ show_lines=True,
722
+ )
723
+ table.add_column("Group Name", style="cyan", no_wrap=True)
724
+ table.add_column("Files", style="green")
725
+ table.add_column("Query Prefix", style="yellow")
726
+ table.add_column("Active", style="magenta")
727
+
728
+ for i, (group_name, files) in enumerate(groups.items()):
729
+ query_prefix = groups_info.get(group_name, {}).get(
730
+ "query_prefix", ""
731
+ )
732
+ is_active = (
733
+ "✓"
734
+ if group_name in memory["current_files"]["current_groups"]
735
+ else ""
736
+ )
737
+ table.add_row(
738
+ group_name,
739
+ "\n".join([os.path.relpath(f, project_root)
740
+ for f in files]),
741
+ query_prefix,
742
+ is_active,
743
+ end_section=(i == len(groups) - 1),
744
+ )
745
+ console.print(Panel(table, border_style="blue"))
746
+ result_manager.append(content="Defined groups.",
747
+ meta={"action": "add_files","success":True, "input":{ "args": args}})
748
+ elif len(args) >= 2 and args[1] == "/reset":
749
+ memory["current_files"]["current_groups"] = []
750
+ console.print(
751
+ Panel(
752
+ "Active group names have been reset. If you want to clear the active files, you should use the command /remove_files /all.",
753
+ title="Groups Reset",
754
+ border_style="green",
755
+ )
756
+ )
757
+ result_manager.append(content="Active group names have been reset. If you want to clear the active files, you should use the command /remove_files /all.",
758
+ meta={"action": "add_files","success":True, "input":{ "args": args}})
759
+ elif len(args) >= 3 and args[1] == "/add":
760
+ group_name = args[2]
761
+ groups[group_name] = memory["current_files"]["files"].copy()
762
+ console.print(
763
+ Panel(
764
+ f"Added group '{group_name}' with current files.",
765
+ title="Group Added",
766
+ border_style="green",
767
+ )
768
+ )
769
+ result_manager.append(content=f"Added group '{group_name}' with current files.",
770
+ meta={"action": "add_files","success":True, "input":{ "args": args}})
771
+
772
+ elif len(args) >= 3 and args[1] == "/drop":
773
+ group_name = args[2]
774
+ if group_name in groups:
775
+ del memory["current_files"]["groups"][group_name]
776
+ if group_name in groups_info:
777
+ del memory["current_files"]["groups_info"][group_name]
778
+ if group_name in memory["current_files"]["current_groups"]:
779
+ memory["current_files"]["current_groups"].remove(
780
+ group_name)
781
+ console.print(
782
+ Panel(
783
+ f"Dropped group '{group_name}'.",
784
+ title="Group Dropped",
785
+ border_style="green",
786
+ )
787
+ )
788
+ result_manager.append(content=f"Dropped group '{group_name}'.",
789
+ meta={"action": "add_files","success":True, "input":{ "args": args}})
790
+ else:
791
+ console.print(
792
+ Panel(
793
+ f"Group '{group_name}' not found.",
794
+ title="Error",
795
+ border_style="red",
796
+ )
797
+ )
798
+ result_manager.append(content=f"Group '{group_name}' not found.",
799
+ meta={"action": "add_files","success":False, "input":{ "args": args}})
800
+ elif len(args) == 3 and args[1] == "/set":
801
+ group_name = args[2]
802
+
803
+ def multiline_edit():
804
+ from prompt_toolkit.lexers import PygmentsLexer
805
+ from pygments.lexers.markup import MarkdownLexer
806
+ from prompt_toolkit.formatted_text import HTML
807
+ from prompt_toolkit.shortcuts import print_formatted_text
808
+
809
+ style = Style.from_dict(
810
+ {
811
+ "dialog": "bg:#88ff88",
812
+ "dialog frame.label": "bg:#ffffff #000000",
813
+ "dialog.body": "bg:#000000 #00ff00",
814
+ "dialog shadow": "bg:#00aa00",
815
+ }
816
+ )
817
+
818
+ print_formatted_text(
819
+ HTML(
820
+ "<b>Type Atom Group Desc (Prese [Esc] + [Enter] to finish.)</b><br/>"
821
+ )
822
+ )
823
+ text = prompt(
824
+ HTML("<ansicyan>║</ansicyan> "),
825
+ multiline=True,
826
+ lexer=PygmentsLexer(MarkdownLexer),
827
+ style=style,
828
+ wrap_lines=True,
829
+ prompt_continuation=HTML("<ansicyan>║</ansicyan> "),
830
+ rprompt=HTML("<ansicyan>║</ansicyan>"),
831
+ )
832
+ return text
833
+
834
+ query_prefix = multiline_edit()
835
+ if group_name in groups:
836
+ groups_info[group_name] = {"query_prefix": query_prefix}
837
+ console.print(
838
+ Panel(
839
+ f"Set Atom Group Desc for group '{group_name}'.",
840
+ title="Group Info Updated",
841
+ border_style="green",
842
+ )
843
+ )
844
+ else:
845
+ console.print(
846
+ Panel(
847
+ f"Group '{group_name}' not found.",
848
+ title="Error",
849
+ border_style="red",
850
+ )
851
+ )
852
+ elif len(args) >= 2:
853
+ # 支持多个组的合并,允许组名之间使用逗号或空格分隔
854
+ group_names = " ".join(args[1:]).replace(",", " ").split()
855
+ merged_files = set()
856
+ missing_groups = []
857
+ for group_name in group_names:
858
+ if group_name in groups:
859
+ merged_files.update(groups[group_name])
860
+ else:
861
+ missing_groups.append(group_name)
862
+
863
+ if missing_groups:
864
+ console.print(
865
+ Panel(
866
+ f"Group(s) not found: {', '.join(missing_groups)}",
867
+ title="Error",
868
+ border_style="red",
869
+ )
870
+ )
871
+ result_manager.append(content=f"Group(s) not found: {', '.join(missing_groups)}",
872
+ meta={"action": "add_files","success":False, "input":{ "args": args}})
873
+
874
+ if merged_files:
875
+ memory["current_files"]["files"] = list(merged_files)
876
+ memory["current_files"]["current_groups"] = [
877
+ name for name in group_names if name in groups
878
+ ]
879
+ console.print(
880
+ Panel(
881
+ f"Merged files from groups: {', '.join(group_names)}",
882
+ title="Files Merged",
883
+ border_style="green",
884
+ )
885
+ )
886
+ table = Table(
887
+ title="Current Files",
888
+ show_header=True,
889
+ header_style="bold magenta",
890
+ show_lines=True, # 这会在每行之间添加分割线
891
+ )
892
+ table.add_column("File", style="green")
893
+ for i, f in enumerate(memory["current_files"]["files"]):
894
+ table.add_row(
895
+ os.path.relpath(f, project_root),
896
+ end_section=(
897
+ i == len(memory["current_files"]["files"]) - 1
898
+ ), # 在最后一行之后不添加分割线
899
+ )
900
+ console.print(Panel(table, border_style="blue"))
901
+ console.print(
902
+ Panel(
903
+ f"Active groups: {', '.join(memory['current_files']['current_groups'])}",
904
+ title="Active Groups",
905
+ border_style="green",
906
+ )
907
+ )
908
+ result_manager.append(content=f"Active groups: {', '.join(memory['current_files']['current_groups'])}",
909
+ meta={"action": "add_files","success":True, "input":{ "args": args}})
910
+ elif not missing_groups:
911
+ console.print(
912
+ Panel(
913
+ "No files in the specified groups.",
914
+ title="No Files Added",
915
+ border_style="yellow",
916
+ )
917
+ )
918
+ result_manager.append(content="No files in the specified groups.",
919
+ meta={"action": "add_files","success":False, "input":{ "args": args}})
920
+ else:
921
+ existing_files = memory["current_files"]["files"]
922
+ matched_files = find_files_in_project(args)
923
+
924
+ files_to_add = [f for f in matched_files if f not in existing_files]
925
+ if files_to_add:
926
+ memory["current_files"]["files"].extend(files_to_add)
927
+ table = Table(
928
+ title=get_message("add_files_added_files"),
929
+ show_header=True,
930
+ header_style="bold magenta",
931
+ show_lines=True, # 这会在每行之间添加分割线
932
+ )
933
+ table.add_column("File", style="green")
934
+ for i, f in enumerate(files_to_add):
935
+ table.add_row(
936
+ os.path.relpath(f, project_root),
937
+ end_section=(
938
+ i == len(files_to_add) - 1
939
+ ), # 在最后一行之后不添加分割线
940
+ )
941
+ console.print(Panel(table, border_style="green"))
942
+ result_manager.append(content=f"Added files: {', '.join(files_to_add)}",
943
+ meta={"action": "add_files","success":True, "input":{ "args": args}})
944
+ else:
945
+ printer.print_in_terminal("add_files_matched", style="yellow")
946
+ result_manager.append(content=f"No files matched.",
947
+ meta={"action": "add_files","success":False, "input":{ "args": args}})
948
+
949
+ completer.update_current_files(memory["current_files"]["files"])
950
+ save_memory()
951
+
952
+
953
+ def remove_files(file_names: List[str]):
954
+ project_root = os.getcwd()
955
+ printer = Printer()
956
+ result_manager = ResultManager()
957
+
958
+ if "/all" in file_names:
959
+ memory["current_files"]["files"] = []
960
+ memory["current_files"]["current_groups"] = []
961
+ printer.print_in_terminal("remove_files_all", style="green")
962
+ result_manager.append(content="All files removed.",
963
+ meta={"action": "remove_files","success":True, "input":{ "file_names": file_names}})
964
+ else:
965
+ removed_files = []
966
+ for file in memory["current_files"]["files"]:
967
+ if os.path.basename(file) in file_names:
968
+ removed_files.append(file)
969
+ elif file in file_names:
970
+ removed_files.append(file)
971
+ for file in removed_files:
972
+ memory["current_files"]["files"].remove(file)
973
+
974
+ if removed_files:
975
+ table = Table(
976
+ show_header=True,
977
+ header_style="bold magenta"
978
+ )
979
+ table.add_column("File", style="green")
980
+ for f in removed_files:
981
+ table.add_row(os.path.relpath(f, project_root))
982
+
983
+ console = Console()
984
+ console.print(
985
+ Panel(table, border_style="green",
986
+ title=printer.get_message_from_key("files_removed")))
987
+ result_manager.append(content=f"Removed files: {', '.join(removed_files)}",
988
+ meta={"action": "remove_files","success":True, "input":{ "file_names": file_names}})
989
+ else:
990
+ printer.print_in_terminal("remove_files_none", style="yellow")
991
+ result_manager.append(content=printer.get_message_from_key("remove_files_none"),
992
+ meta={"action": "remove_files","success":False, "input":{ "file_names": file_names}})
993
+
994
+ completer.update_current_files(memory["current_files"]["files"])
995
+ save_memory()
996
+
997
+ @run_in_raw_thread()
998
+ def ask(query: str):
999
+ conf = memory.get("conf", {})
1000
+ yaml_config = {
1001
+ "include_file": ["./base/base.yml"],
1002
+ }
1003
+ yaml_config["query"] = query
1004
+
1005
+ if "project_type" in conf:
1006
+ yaml_config["project_type"] = conf["project_type"]
1007
+
1008
+ if "model" in conf:
1009
+ yaml_config["model"] = conf["model"]
1010
+
1011
+ if "index_model" in conf:
1012
+ yaml_config["index_model"] = conf["index_model"]
1013
+
1014
+ if "vl_model" in conf:
1015
+ yaml_config["vl_model"] = conf["vl_model"]
1016
+
1017
+ if "code_model" in conf:
1018
+ yaml_config["code_model"] = conf["code_model"]
1019
+
1020
+ if "product_mode" in conf:
1021
+ yaml_config["product_mode"] = conf["product_mode"]
1022
+
1023
+ yaml_content = convert_yaml_config_to_str(yaml_config=yaml_config)
1024
+
1025
+ execute_file = os.path.join("actions", f"{uuid.uuid4()}.yml")
1026
+
1027
+ with open(os.path.join(execute_file), "w",encoding="utf-8") as f:
1028
+ f.write(yaml_content)
1029
+
1030
+ def execute_ask():
1031
+ auto_coder_main(["agent", "project_reader", "--file", execute_file])
1032
+
1033
+ try:
1034
+ execute_ask()
1035
+ finally:
1036
+ os.remove(execute_file)
1037
+
1038
+
1039
+ def get_llm_friendly_package_docs(
1040
+ package_name: Optional[str] = None, return_paths: bool = False
1041
+ ) -> List[str]:
1042
+ lib_dir = os.path.join(".auto-coder", "libs")
1043
+ llm_friendly_packages_dir = os.path.join(lib_dir, "llm_friendly_packages")
1044
+ docs = []
1045
+
1046
+ if not os.path.exists(llm_friendly_packages_dir):
1047
+ return docs
1048
+
1049
+ libs = list(memory.get("libs", {}).keys())
1050
+
1051
+ for domain in os.listdir(llm_friendly_packages_dir):
1052
+ domain_path = os.path.join(llm_friendly_packages_dir, domain)
1053
+ if os.path.isdir(domain_path):
1054
+ for username in os.listdir(domain_path):
1055
+ username_path = os.path.join(domain_path, username)
1056
+ if os.path.isdir(username_path):
1057
+ for lib_name in os.listdir(username_path):
1058
+ lib_path = os.path.join(username_path, lib_name)
1059
+ if (
1060
+ os.path.isdir(lib_path)
1061
+ and (
1062
+ package_name is None
1063
+ or lib_name == package_name
1064
+ or package_name == os.path.join(username, lib_name)
1065
+ )
1066
+ and lib_name in libs
1067
+ ):
1068
+ for root, _, files in os.walk(lib_path):
1069
+ for file in files:
1070
+ if file.endswith(".md"):
1071
+ file_path = os.path.join(root, file)
1072
+ if return_paths:
1073
+ docs.append(file_path)
1074
+ else:
1075
+ with open(file_path, "r",encoding="utf-8") as f:
1076
+ docs.append(f.read())
1077
+
1078
+ return docs
1079
+
1080
+
1081
+ def convert_yaml_to_config(yaml_file: str):
1082
+ from autocoder.auto_coder import AutoCoderArgs, load_include_files, Template
1083
+
1084
+ args = AutoCoderArgs()
1085
+ with open(yaml_file, "r",encoding="utf-8") as f:
1086
+ config = yaml.safe_load(f)
1087
+ config = load_include_files(config, yaml_file)
1088
+ for key, value in config.items():
1089
+ if key != "file": # 排除 --file 参数本身
1090
+ # key: ENV {{VARIABLE_NAME}}
1091
+ if isinstance(value, str) and value.startswith("ENV"):
1092
+ template = Template(value.removeprefix("ENV").strip())
1093
+ value = template.render(os.environ)
1094
+ setattr(args, key, value)
1095
+ return args
1096
+
1097
+ @run_in_raw_thread()
1098
+ def mcp(query: str):
1099
+ query = query.strip()
1100
+ mcp_server = get_mcp_server()
1101
+ printer = Printer()
1102
+
1103
+ # Handle remove command
1104
+ if query.startswith("/remove"):
1105
+ server_name = query.replace("/remove", "", 1).strip()
1106
+ response = mcp_server.send_request(
1107
+ McpRemoveRequest(server_name=server_name))
1108
+ if response.error:
1109
+ printer.print_in_terminal("mcp_remove_error", style="red", error=response.error)
1110
+ else:
1111
+ printer.print_in_terminal("mcp_remove_success", style="green", result=response.result)
1112
+ return
1113
+
1114
+ # Handle list command
1115
+ if query.startswith("/list_running"):
1116
+ response = mcp_server.send_request(McpListRunningRequest())
1117
+ if response.error:
1118
+ printer.print_in_terminal("mcp_list_running_error", style="red", error=response.error)
1119
+ else:
1120
+ printer.print_in_terminal("mcp_list_running_title")
1121
+ printer.print_str_in_terminal(response.result)
1122
+ return
1123
+
1124
+ # Handle list command
1125
+ if query.startswith("/list"):
1126
+ response = mcp_server.send_request(McpListRequest())
1127
+ if response.error:
1128
+ printer.print_in_terminal("mcp_list_builtin_error", style="red", error=response.error)
1129
+ else:
1130
+ printer.print_in_terminal("mcp_list_builtin_title")
1131
+ printer.print_str_in_terminal(response.result)
1132
+ return
1133
+
1134
+ # Handle refresh command
1135
+ if query.startswith("/refresh"):
1136
+ server_name = query.replace("/refresh", "", 1).strip()
1137
+ response = mcp_server.send_request(McpRefreshRequest(name=server_name or None))
1138
+ if response.error:
1139
+ printer.print_in_terminal("mcp_refresh_error", style="red", error=response.error)
1140
+ else:
1141
+ printer.print_in_terminal("mcp_refresh_success", style="green")
1142
+ return
1143
+
1144
+ # Handle add command
1145
+ if query.startswith("/add"):
1146
+ query = query.replace("/add", "", 1).strip()
1147
+ request = McpInstallRequest(server_name_or_config=query)
1148
+ response = mcp_server.send_request(request)
1149
+
1150
+ if response.error:
1151
+ printer.print_in_terminal("mcp_install_error", style="red", error=response.error)
1152
+ else:
1153
+ printer.print_in_terminal("mcp_install_success", style="green", result=response.result)
1154
+ return
1155
+
1156
+ # Handle default query
1157
+ conf = memory.get("conf", {})
1158
+ yaml_config = {
1159
+ "include_file": ["./base/base.yml"],
1160
+ "auto_merge": conf.get("auto_merge", "editblock"),
1161
+ "human_as_model": conf.get("human_as_model", "false") == "true",
1162
+ "skip_build_index": conf.get("skip_build_index", "true") == "true",
1163
+ "skip_confirm": conf.get("skip_confirm", "true") == "true",
1164
+ "silence": conf.get("silence", "true") == "true",
1165
+ "include_project_structure": conf.get("include_project_structure", "true")
1166
+ == "true",
1167
+ "exclude_files": memory.get("exclude_files", []),
1168
+ }
1169
+ for key, value in conf.items():
1170
+ converted_value = convert_config_value(key, value)
1171
+ if converted_value is not None:
1172
+ yaml_config[key] = converted_value
1173
+
1174
+ temp_yaml = os.path.join("actions", f"{uuid.uuid4()}.yml")
1175
+ try:
1176
+ with open(temp_yaml, "w",encoding="utf-8") as f:
1177
+ f.write(convert_yaml_config_to_str(yaml_config=yaml_config))
1178
+ args = convert_yaml_to_config(temp_yaml)
1179
+ finally:
1180
+ if os.path.exists(temp_yaml):
1181
+ os.remove(temp_yaml)
1182
+
1183
+ mcp_server = get_mcp_server()
1184
+ response = mcp_server.send_request(
1185
+ McpRequest(
1186
+ query=query,
1187
+ model=args.inference_model or args.model,
1188
+ product_mode=args.product_mode
1189
+ )
1190
+ )
1191
+
1192
+ if response.error:
1193
+ printer.print_panel(
1194
+ f"Error from MCP server: {response.error}",
1195
+ text_options={"justify": "left"},
1196
+ panel_options={
1197
+ "title": printer.get_message_from_key("mcp_error_title"),
1198
+ "border_style": "red"
1199
+ }
1200
+ )
1201
+ else:
1202
+ # Save conversation
1203
+ mcp_dir = os.path.join(".auto-coder", "mcp", "conversations")
1204
+ os.makedirs(mcp_dir, exist_ok=True)
1205
+ timestamp = str(int(time.time()))
1206
+ file_path = os.path.join(mcp_dir, f"{timestamp}.md")
1207
+
1208
+ # Format response as markdown
1209
+ markdown_content = response.result
1210
+
1211
+ # Save to file
1212
+ with open(file_path, "w", encoding="utf-8") as f:
1213
+ f.write(markdown_content)
1214
+
1215
+ console = Console()
1216
+ console.print(
1217
+ Panel(
1218
+ Markdown(markdown_content, justify="left"),
1219
+ title=printer.get_message_from_key('mcp_response_title'),
1220
+ border_style="green"
1221
+ )
1222
+ )
1223
+
1224
+
1225
+ @run_in_raw_thread()
1226
+ def code_next(query: str):
1227
+ conf = memory.get("conf", {})
1228
+ yaml_config = {
1229
+ "include_file": ["./base/base.yml"],
1230
+ "auto_merge": conf.get("auto_merge", "editblock"),
1231
+ "human_as_model": conf.get("human_as_model", "false") == "true",
1232
+ "skip_build_index": conf.get("skip_build_index", "true") == "true",
1233
+ "skip_confirm": conf.get("skip_confirm", "true") == "true",
1234
+ "silence": conf.get("silence", "true") == "true",
1235
+ "include_project_structure": conf.get("include_project_structure", "true")
1236
+ == "true",
1237
+ "exclude_files": memory.get("exclude_files", []),
1238
+ }
1239
+ for key, value in conf.items():
1240
+ converted_value = convert_config_value(key, value)
1241
+ if converted_value is not None:
1242
+ yaml_config[key] = converted_value
1243
+
1244
+ temp_yaml = os.path.join("actions", f"{uuid.uuid4()}.yml")
1245
+ try:
1246
+ with open(temp_yaml, "w",encoding="utf-8") as f:
1247
+ f.write(convert_yaml_config_to_str(yaml_config=yaml_config))
1248
+ args = convert_yaml_to_config(temp_yaml)
1249
+ finally:
1250
+ if os.path.exists(temp_yaml):
1251
+ os.remove(temp_yaml)
1252
+
1253
+ product_mode = conf.get("product_mode", "lite")
1254
+ llm = get_single_llm(args.chat_model or args.model, product_mode=product_mode)
1255
+
1256
+ auto_guesser = AutoGuessQuery(
1257
+ llm=llm, project_dir=os.getcwd(), skip_diff=True)
1258
+
1259
+ predicted_tasks = auto_guesser.predict_next_tasks(
1260
+ 5, is_human_as_model=args.human_as_model
1261
+ )
1262
+
1263
+ if not predicted_tasks:
1264
+ console = Console()
1265
+ console.print(Panel("No task predictions available", style="yellow"))
1266
+ return
1267
+
1268
+ console = Console()
1269
+
1270
+ # Create main panel for all predicted tasks
1271
+ table = Table(show_header=True,
1272
+ header_style="bold magenta", show_lines=True)
1273
+ table.add_column("Priority", style="cyan", width=8)
1274
+ table.add_column("Task Description", style="green",
1275
+ width=40, overflow="fold")
1276
+ table.add_column("Files", style="yellow", width=30, overflow="fold")
1277
+ table.add_column("Reason", style="blue", width=30, overflow="fold")
1278
+ table.add_column("Dependencies", style="magenta",
1279
+ width=30, overflow="fold")
1280
+
1281
+ for task in predicted_tasks:
1282
+ # Format file paths to be more readable
1283
+ file_list = "\n".join([os.path.relpath(f, os.getcwd())
1284
+ for f in task.urls])
1285
+
1286
+ # Format dependencies to be more readable
1287
+ dependencies = (
1288
+ "\n".join(
1289
+ task.dependency_queries) if task.dependency_queries else "None"
1290
+ )
1291
+
1292
+ table.add_row(
1293
+ str(task.priority), task.query, file_list, task.reason, dependencies
1294
+ )
1295
+
1296
+ console.print(
1297
+ Panel(
1298
+ table,
1299
+ title="[bold]Predicted Next Tasks[/bold]",
1300
+ border_style="blue",
1301
+ padding=(1, 2), # Add more horizontal padding
1302
+ )
1303
+ )
1304
+
1305
+
1306
+ @run_in_raw_thread()
1307
+ def commit(query: str):
1308
+ conf = memory.get("conf", {})
1309
+ product_mode = conf.get("product_mode", "lite")
1310
+ def prepare_commit_yaml():
1311
+ auto_coder_main(["next", "chat_action"])
1312
+
1313
+ prepare_commit_yaml()
1314
+
1315
+ # no_diff = query.strip().startswith("/no_diff")
1316
+ # if no_diff:
1317
+ # query = query.replace("/no_diff", "", 1).strip()
1318
+
1319
+ latest_yaml_file = get_last_yaml_file("actions")
1320
+
1321
+ conf = memory.get("conf", {})
1322
+ current_files = memory["current_files"]["files"]
1323
+ execute_file = None
1324
+
1325
+ if latest_yaml_file:
1326
+ try:
1327
+ execute_file = os.path.join("actions", latest_yaml_file)
1328
+ yaml_config = {
1329
+ "include_file": ["./base/base.yml"],
1330
+ "auto_merge": conf.get("auto_merge", "editblock"),
1331
+ "human_as_model": conf.get("human_as_model", "false") == "true",
1332
+ "skip_build_index": conf.get("skip_build_index", "true") == "true",
1333
+ "skip_confirm": conf.get("skip_confirm", "true") == "true",
1334
+ "silence": conf.get("silence", "true") == "true",
1335
+ "include_project_structure": conf.get("include_project_structure", "true")
1336
+ == "true",
1337
+ }
1338
+ for key, value in conf.items():
1339
+ converted_value = convert_config_value(key, value)
1340
+ if converted_value is not None:
1341
+ yaml_config[key] = converted_value
1342
+
1343
+ yaml_config["urls"] = current_files + get_llm_friendly_package_docs(
1344
+ return_paths=True
1345
+ )
1346
+
1347
+ if conf.get("enable_global_memory", "true") in ["true", "True",True]:
1348
+ yaml_config["urls"] += get_global_memory_file_paths()
1349
+
1350
+ # 临时保存yaml文件,然后读取yaml文件,转换为args
1351
+ temp_yaml = os.path.join("actions", f"{uuid.uuid4()}.yml")
1352
+ try:
1353
+ with open(temp_yaml, "w",encoding="utf-8") as f:
1354
+ f.write(convert_yaml_config_to_str(
1355
+ yaml_config=yaml_config))
1356
+ args = convert_yaml_to_config(temp_yaml)
1357
+ finally:
1358
+ if os.path.exists(temp_yaml):
1359
+ os.remove(temp_yaml)
1360
+
1361
+ target_model = args.commit_model or args.model
1362
+ llm = get_single_llm(target_model, product_mode)
1363
+ printer = Printer()
1364
+ printer.print_in_terminal("commit_generating", style="yellow", model_name=target_model)
1365
+ commit_message = ""
1366
+
1367
+ try:
1368
+ uncommitted_changes = git_utils.get_uncommitted_changes(".")
1369
+ commit_message = git_utils.generate_commit_message.with_llm(llm).run(
1370
+ uncommitted_changes
1371
+ )
1372
+ memory["conversation"].append(
1373
+ {"role": "user", "content": commit_message})
1374
+ except Exception as e:
1375
+ printer.print_in_terminal("commit_failed", style="red", error=str(e), model_name=target_model)
1376
+ return
1377
+
1378
+ yaml_config["query"] = commit_message
1379
+ yaml_content = convert_yaml_config_to_str(yaml_config=yaml_config)
1380
+ with open(os.path.join(execute_file), "w",encoding="utf-8") as f:
1381
+ f.write(yaml_content)
1382
+
1383
+ file_content = open(execute_file).read()
1384
+ md5 = hashlib.md5(file_content.encode("utf-8")).hexdigest()
1385
+ file_name = os.path.basename(execute_file)
1386
+ commit_result = git_utils.commit_changes(
1387
+ ".", f"auto_coder_{file_name}_{md5}\n{commit_message}"
1388
+ )
1389
+ git_utils.print_commit_info(commit_result=commit_result)
1390
+ if commit_message:
1391
+ printer.print_in_terminal("commit_message", style="green", model_name=target_model, message=commit_message)
1392
+ except Exception as e:
1393
+ import traceback
1394
+ traceback.print_exc()
1395
+ print(f"Failed to commit: {e}")
1396
+ if execute_file:
1397
+ os.remove(execute_file)
1398
+
1399
+
1400
+ @run_in_raw_thread()
1401
+ def coding(query: str):
1402
+ console = Console()
1403
+ is_apply = query.strip().startswith("/apply")
1404
+ if is_apply:
1405
+ query = query.replace("/apply", "", 1).strip()
1406
+
1407
+ is_next = query.strip().startswith("/next")
1408
+ if is_next:
1409
+ query = query.replace("/next", "", 1).strip()
1410
+
1411
+ if is_next:
1412
+ code_next(query)
1413
+ return
1414
+
1415
+ memory["conversation"].append({"role": "user", "content": query})
1416
+ conf = memory.get("conf", {})
1417
+
1418
+ current_files = memory["current_files"]["files"]
1419
+ current_groups = memory["current_files"].get("current_groups", [])
1420
+ groups = memory["current_files"].get("groups", {})
1421
+ groups_info = memory["current_files"].get("groups_info", {})
1422
+
1423
+ def prepare_chat_yaml():
1424
+ auto_coder_main(["next", "chat_action"])
1425
+
1426
+ prepare_chat_yaml()
1427
+
1428
+ latest_yaml_file = get_last_yaml_file("actions")
1429
+
1430
+ if latest_yaml_file:
1431
+ yaml_config = {
1432
+ "include_file": ["./base/base.yml"],
1433
+ "auto_merge": conf.get("auto_merge", "editblock"),
1434
+ "human_as_model": conf.get("human_as_model", "false") == "true",
1435
+ "skip_build_index": conf.get("skip_build_index", "true") == "true",
1436
+ "skip_confirm": conf.get("skip_confirm", "true") == "true",
1437
+ "silence": conf.get("silence", "true") == "true",
1438
+ "include_project_structure": conf.get("include_project_structure", "true")
1439
+ == "true",
1440
+ "exclude_files": memory.get("exclude_files", []),
1441
+ }
1442
+
1443
+ yaml_config["context"] = ""
1444
+ yaml_config["in_code_apply"] = is_apply
1445
+
1446
+ for key, value in conf.items():
1447
+ converted_value = convert_config_value(key, value)
1448
+ if converted_value is not None:
1449
+ yaml_config[key] = converted_value
1450
+
1451
+ yaml_config["urls"] = current_files + get_llm_friendly_package_docs(
1452
+ return_paths=True
1453
+ )
1454
+
1455
+ if conf.get("enable_global_memory", "true") in ["true", "True",True]:
1456
+ yaml_config["urls"] += get_global_memory_file_paths()
1457
+
1458
+ # handle image
1459
+ v = Image.convert_image_paths_from(query)
1460
+ yaml_config["query"] = v
1461
+
1462
+ # Add context for active groups and their query prefixes
1463
+ if current_groups:
1464
+ active_groups_context = "下面是对上面文件按分组给到的一些描述,当用户的需求正好匹配描述的时候,参考描述来做修改:\n"
1465
+ for group in current_groups:
1466
+ group_files = groups.get(group, [])
1467
+ query_prefix = groups_info.get(
1468
+ group, {}).get("query_prefix", "")
1469
+ active_groups_context += f"组名: {group}\n"
1470
+ active_groups_context += f"文件列表:\n"
1471
+ for file in group_files:
1472
+ active_groups_context += f"- {file}\n"
1473
+ active_groups_context += f"组描述: {query_prefix}\n\n"
1474
+
1475
+ yaml_config["context"] = active_groups_context + "\n"
1476
+
1477
+ if is_apply:
1478
+ memory_dir = os.path.join(".auto-coder", "memory")
1479
+ os.makedirs(memory_dir, exist_ok=True)
1480
+ memory_file = os.path.join(memory_dir, "chat_history.json")
1481
+
1482
+ def error_message():
1483
+ console.print(
1484
+ Panel(
1485
+ "No chat history found to apply.",
1486
+ title="Chat History",
1487
+ expand=False,
1488
+ border_style="yellow",
1489
+ )
1490
+ )
1491
+
1492
+ if not os.path.exists(memory_file):
1493
+ error_message()
1494
+ return
1495
+
1496
+ with open(memory_file, "r",encoding="utf-8") as f:
1497
+ chat_history = json.load(f)
1498
+
1499
+ if not chat_history["ask_conversation"]:
1500
+ error_message()
1501
+ return
1502
+
1503
+ conversations = chat_history["ask_conversation"]
1504
+
1505
+ yaml_config[
1506
+ "context"
1507
+ ] += f"下面是我们的历史对话,参考我们的历史对话从而更好的理解需求和修改代码: \n\n<history>\n"
1508
+ for conv in conversations:
1509
+ if conv["role"] == "user":
1510
+ yaml_config["context"] += f"用户: {conv['content']}\n"
1511
+ elif conv["role"] == "assistant":
1512
+ yaml_config["context"] += f"你: {conv['content']}\n"
1513
+ yaml_config["context"] += "</history>\n"
1514
+
1515
+ yaml_content = convert_yaml_config_to_str(yaml_config=yaml_config)
1516
+
1517
+ md5 = hashlib.md5(yaml_content.encode("utf-8")).hexdigest()
1518
+
1519
+ execute_file = os.path.join("actions", latest_yaml_file)
1520
+ with open(os.path.join(execute_file), "w",encoding="utf-8") as f:
1521
+ f.write(yaml_content)
1522
+
1523
+ def execute_chat():
1524
+ cmd = ["--file", execute_file]
1525
+ auto_coder_main(cmd)
1526
+ result_manager = ResultManager()
1527
+ result_manager.append(content="", meta={"commit_message": f"auto_coder_{latest_yaml_file}_{md5}","action": "coding", "input":{
1528
+ "query": query
1529
+ }})
1530
+
1531
+ execute_chat()
1532
+ else:
1533
+ print("Failed to create new YAML file.")
1534
+
1535
+ save_memory()
1536
+ completer.refresh_files()
1537
+
1538
+
1539
+ @byzerllm.prompt()
1540
+ def code_review(query: str) -> str:
1541
+ """
1542
+ 掐面提供了上下文,对代码进行review,参考如下检查点。
1543
+ 1. 有没有调用不符合方法,类的签名的调用,包括对第三方类,模块,方法的检查(如果上下文提供了这些信息)
1544
+ 2. 有没有未声明直接使用的变量,方法,类
1545
+ 3. 有没有明显的语法错误
1546
+ 4. 如果是python代码,检查有没有缩进方面的错误
1547
+ 5. 如果是python代码,检查是否 try 后面缺少 except 或者 finally
1548
+ {% if query %}
1549
+ 6. 用户的额外的检查需求:{{ query }}
1550
+ {% endif %}
1551
+
1552
+ 如果用户的需求包含了@一个文件名 或者 @@符号, 那么重点关注这些文件或者符号(函数,类)进行上述的review。
1553
+ review 过程中严格遵循上述的检查点,不要遗漏,没有发现异常的点直接跳过,只对发现的异常点,给出具体的修改后的代码。
1554
+ """
1555
+
1556
+
1557
+ @run_in_raw_thread()
1558
+ def chat(query: str):
1559
+ conf = memory.get("conf", {})
1560
+
1561
+ yaml_config = {
1562
+ "include_file": ["./base/base.yml"],
1563
+ "include_project_structure": conf.get("include_project_structure", "true")
1564
+ in ["true", "True"],
1565
+ "human_as_model": conf.get("human_as_model", "false") == "true",
1566
+ "skip_build_index": conf.get("skip_build_index", "true") == "true",
1567
+ "skip_confirm": conf.get("skip_confirm", "true") == "true",
1568
+ "silence": conf.get("silence", "true") == "true",
1569
+ "exclude_files": memory.get("exclude_files", []),
1570
+ }
1571
+
1572
+ current_files = memory["current_files"]["files"] + get_llm_friendly_package_docs(
1573
+ return_paths=True
1574
+ )
1575
+
1576
+ if conf.get("enable_global_memory", "true") in ["true", "True",True]:
1577
+ current_files += get_global_memory_file_paths()
1578
+
1579
+ yaml_config["urls"] = current_files
1580
+
1581
+ if "emb_model" in conf:
1582
+ yaml_config["emb_model"] = conf["emb_model"]
1583
+
1584
+ is_new = "/new" in query
1585
+ if is_new:
1586
+ query = query.replace("/new", "", 1).strip()
1587
+
1588
+ yaml_config["action"] = []
1589
+
1590
+ if "/mcp " in query:
1591
+ yaml_config["action"].append("mcp")
1592
+ query = query.replace("/mcp ", "", 1).strip()
1593
+
1594
+ if "/rag " in query:
1595
+ yaml_config["action"].append("rag")
1596
+ query = query.replace("/rag ", "", 1).strip()
1597
+
1598
+ if "/copy" in query:
1599
+ yaml_config["action"].append("copy")
1600
+ query = query.replace("/copy", "", 1).strip()
1601
+
1602
+ if "/save" in query:
1603
+ yaml_config["action"].append("save")
1604
+ query = query.replace("/save", "", 1).strip()
1605
+
1606
+ if "/review" in query and "/commit" in query:
1607
+ yaml_config["action"].append("review_commit")
1608
+ query = query.replace("/review", "", 1).replace("/commit", "", 1).strip()
1609
+ elif "/learn" in query:
1610
+ yaml_config["action"].append("learn_from_commit")
1611
+ query = query.replace("/learn", "", 1).strip()
1612
+ else:
1613
+ is_review = query.strip().startswith("/review")
1614
+ if is_review:
1615
+ query = query.replace("/review", "", 1).strip()
1616
+ if "prompt_review" in conf:
1617
+ query = format_str_jinja2(conf["prompt_review"], query=query)
1618
+ else:
1619
+ query = code_review.prompt(query)
1620
+
1621
+ is_no_context = "/no_context" in query.strip()
1622
+ if is_no_context:
1623
+ query = query.replace("/no_context", "", 1).strip()
1624
+ yaml_config["action"].append("no_context")
1625
+
1626
+ for key, value in conf.items():
1627
+ converted_value = convert_config_value(key, value)
1628
+ if converted_value is not None:
1629
+ yaml_config[key] = converted_value
1630
+
1631
+ query = Image.convert_image_paths_from(query)
1632
+
1633
+ yaml_config["query"] = query
1634
+
1635
+ yaml_content = convert_yaml_config_to_str(yaml_config=yaml_config)
1636
+
1637
+ execute_file = os.path.join("actions", f"{uuid.uuid4()}.yml")
1638
+
1639
+ with open(os.path.join(execute_file), "w",encoding="utf-8") as f:
1640
+ f.write(yaml_content)
1641
+
1642
+ def execute_ask():
1643
+ cmd = ["agent", "chat", "--file", execute_file]
1644
+ if is_new:
1645
+ cmd.append("--new_session")
1646
+ auto_coder_main(cmd)
1647
+
1648
+ try:
1649
+ execute_ask()
1650
+ finally:
1651
+ os.remove(execute_file)
1652
+
1653
+
1654
+ @run_in_raw_thread()
1655
+ def summon(query: str):
1656
+ conf = memory.get("conf", {})
1657
+ current_files = memory["current_files"]["files"]
1658
+
1659
+ file_contents = []
1660
+ for file in current_files:
1661
+ if os.path.exists(file):
1662
+ try:
1663
+ with open(file, "r",encoding="utf-8") as f:
1664
+ content = f.read()
1665
+ s = f"##File: {file}\n{content}\n\n"
1666
+ file_contents.append(s)
1667
+ except Exception as e:
1668
+ print(f"Failed to read file: {file}. Error: {str(e)}")
1669
+
1670
+ all_file_content = "".join(file_contents)
1671
+
1672
+ yaml_config = {
1673
+ "include_file": ["./base/base.yml"],
1674
+ }
1675
+ yaml_config["query"] = query
1676
+ yaml_config["context"] = json.dumps(
1677
+ {"file_content": all_file_content}, ensure_ascii=False
1678
+ )
1679
+
1680
+ if "emb_model" in conf:
1681
+ yaml_config["emb_model"] = conf["emb_model"]
1682
+
1683
+ if "vl_model" in conf:
1684
+ yaml_config["vl_model"] = conf["vl_model"]
1685
+
1686
+ if "code_model" in conf:
1687
+ yaml_config["code_model"] = conf["code_model"]
1688
+
1689
+ if "model" in conf:
1690
+ yaml_config["model"] = conf["model"]
1691
+
1692
+ if "product_mode" in conf:
1693
+ yaml_config["product_mode"] = conf["product_mode"]
1694
+
1695
+ yaml_content = convert_yaml_config_to_str(yaml_config=yaml_config)
1696
+
1697
+ execute_file = os.path.join("actions", f"{uuid.uuid4()}.yml")
1698
+
1699
+ with open(os.path.join(execute_file), "w",encoding="utf-8") as f:
1700
+ f.write(yaml_content)
1701
+
1702
+ def execute_summon():
1703
+ auto_coder_main(["agent", "auto_tool", "--file", execute_file])
1704
+
1705
+ try:
1706
+ execute_summon()
1707
+ finally:
1708
+ os.remove(execute_file)
1709
+
1710
+
1711
+ @run_in_raw_thread()
1712
+ def design(query: str):
1713
+
1714
+ conf = memory.get("conf", {})
1715
+ yaml_config = {
1716
+ "include_file": ["./base/base.yml"],
1717
+ }
1718
+
1719
+ if query.strip().startswith("/svg"):
1720
+ query = query.replace("/svg", "", 1).strip()
1721
+ yaml_config["agent_designer_mode"] = "svg"
1722
+ elif query.strip().startswith("/sd"):
1723
+ query = query.replace("/svg", "", 1).strip()
1724
+ yaml_config["agent_designer_mode"] = "sd"
1725
+ elif query.strip().startswith("/logo"):
1726
+ query = query.replace("/logo", "", 1).strip()
1727
+ yaml_config["agent_designer_mode"] = "logo"
1728
+ else:
1729
+ yaml_config["agent_designer_mode"] = "svg"
1730
+
1731
+ yaml_config["query"] = query
1732
+
1733
+ if "model" in conf:
1734
+ yaml_config["model"] = conf["model"]
1735
+
1736
+ if "designer_model" in conf:
1737
+ yaml_config["designer_model"] = conf["designer_model"]
1738
+
1739
+ if "sd_model" in conf:
1740
+ yaml_config["sd_model"] = conf["sd_model"]
1741
+
1742
+ yaml_content = convert_yaml_config_to_str(yaml_config=yaml_config)
1743
+
1744
+ execute_file = os.path.join("actions", f"{uuid.uuid4()}.yml")
1745
+
1746
+ with open(os.path.join(execute_file), "w",encoding="utf-8") as f:
1747
+ f.write(yaml_content)
1748
+
1749
+ def execute_design():
1750
+ auto_coder_main(["agent", "designer", "--file", execute_file])
1751
+
1752
+ try:
1753
+ execute_design()
1754
+ finally:
1755
+ os.remove(execute_file)
1756
+
1757
+
1758
+ def voice_input():
1759
+ conf = memory.get("conf", {})
1760
+ yaml_config = {
1761
+ "include_file": ["./base/base.yml"],
1762
+ }
1763
+
1764
+ if "voice2text_model" not in conf:
1765
+ print(
1766
+ "Please set voice2text_model in configuration. /conf voice2text_model:<model>"
1767
+ )
1768
+ return
1769
+
1770
+ yaml_config["voice2text_model"] = conf["voice2text_model"]
1771
+ yaml_content = convert_yaml_config_to_str(yaml_config=yaml_config)
1772
+
1773
+ execute_file = os.path.join("actions", f"{uuid.uuid4()}.yml")
1774
+
1775
+ with open(os.path.join(execute_file), "w",encoding="utf-8") as f:
1776
+ f.write(yaml_content)
1777
+
1778
+ def execute_voice2text_command():
1779
+ auto_coder_main(["agent", "voice2text", "--file", execute_file])
1780
+
1781
+ try:
1782
+ execute_voice2text_command()
1783
+ with open(os.path.join(".auto-coder", "exchange.txt"), "r",encoding="utf-8") as f:
1784
+ return f.read()
1785
+ finally:
1786
+ os.remove(execute_file)
1787
+
1788
+
1789
+ @run_in_raw_thread()
1790
+ def generate_shell_command(input_text):
1791
+ conf = memory.get("conf", {})
1792
+ yaml_config = {
1793
+ "include_file": ["./base/base.yml"],
1794
+ }
1795
+
1796
+ if "model" in conf:
1797
+ yaml_config["model"] = conf["model"]
1798
+
1799
+ yaml_config["query"] = input_text
1800
+
1801
+ yaml_content = convert_yaml_config_to_str(yaml_config=yaml_config)
1802
+
1803
+ execute_file = os.path.join("actions", f"{uuid.uuid4()}.yml")
1804
+
1805
+ with open(os.path.join(execute_file), "w",encoding="utf-8") as f:
1806
+ f.write(yaml_content)
1807
+
1808
+ try:
1809
+ auto_coder_main(["agent", "generate_command", "--file", execute_file])
1810
+ with open(os.path.join(".auto-coder", "exchange.txt"), "r",encoding="utf-8") as f:
1811
+ shell_script = f.read()
1812
+ result_manager = ResultManager()
1813
+ result_manager.add_result(content=shell_script,meta={
1814
+ "action": "generate_shell_command",
1815
+ "input": {
1816
+ "query": input_text
1817
+ }
1818
+ })
1819
+ return shell_script
1820
+ finally:
1821
+ os.remove(execute_file)
1822
+
1823
+ def manage_models(query: str):
1824
+ """
1825
+ Handle /models subcommands:
1826
+ /models /list - List all models (default + custom)
1827
+ /models /add <name> <api_key> - Add model with simplified params
1828
+ /models /add_model name=xxx base_url=xxx ... - Add model with custom params
1829
+ /models /remove <name> - Remove model by name
1830
+ """
1831
+ printer = Printer()
1832
+ console = Console()
1833
+
1834
+ product_mode = memory.get("product_mode", "lite")
1835
+ if product_mode != "lite":
1836
+ printer.print_in_terminal("models_lite_only", style="red")
1837
+ return
1838
+
1839
+ models_data = models_module.load_models()
1840
+ subcmd = ""
1841
+ if "/list" in query:
1842
+ subcmd = "/list"
1843
+ query = query.replace("/list", "", 1).strip()
1844
+
1845
+ if "/add_model" in query:
1846
+ subcmd = "/add_model"
1847
+ query = query.replace("/add_model", "", 1).strip()
1848
+
1849
+ if "/add" in query:
1850
+ subcmd = "/add"
1851
+ query = query.replace("/add", "", 1).strip()
1852
+
1853
+ # alias to /add
1854
+ if "/activate" in query:
1855
+ subcmd = "/add"
1856
+ query = query.replace("/activate", "", 1).strip()
1857
+
1858
+ if "/remove" in query:
1859
+ subcmd = "/remove"
1860
+ query = query.replace("/remove", "", 1).strip()
1861
+
1862
+ if "/speed-test" in query:
1863
+ subcmd = "/speed-test"
1864
+ query = query.replace("/speed-test", "", 1).strip()
1865
+
1866
+ if "/speed_test" in query:
1867
+ subcmd = "/speed-test"
1868
+ query = query.replace("/speed_test", "", 1).strip()
1869
+
1870
+ if "input_price" in query:
1871
+ subcmd = "/input_price"
1872
+ query = query.replace("/input_price", "", 1).strip()
1873
+
1874
+ if "output_price" in query:
1875
+ subcmd = "/output_price"
1876
+ query = query.replace("/output_price", "", 1).strip()
1877
+
1878
+ if "/speed" in query:
1879
+ subcmd = "/speed"
1880
+ query = query.replace("/speed", "", 1).strip()
1881
+
1882
+
1883
+
1884
+ if not subcmd:
1885
+ printer.print_in_terminal("models_usage")
1886
+
1887
+ result_manager = ResultManager()
1888
+ if subcmd == "/list":
1889
+ if models_data:
1890
+ # Sort models by speed (average_speed)
1891
+ sorted_models = sorted(models_data, key=lambda x: float(x.get('average_speed', 0)))
1892
+ sorted_models.reverse()
1893
+
1894
+ table = Table(
1895
+ title=printer.get_message_from_key("models_title"),
1896
+ expand=True,
1897
+ show_lines=True
1898
+ )
1899
+ table.add_column("Name", style="cyan", width=30, overflow="fold", no_wrap=False)
1900
+ table.add_column("Model Name", style="magenta", width=30, overflow="fold", no_wrap=False)
1901
+ table.add_column("Base URL", style="white", width=40, overflow="fold", no_wrap=False)
1902
+ table.add_column("Input Price (M)", style="magenta", width=15, overflow="fold", no_wrap=False)
1903
+ table.add_column("Output Price (M)", style="magenta", width=15, overflow="fold", no_wrap=False)
1904
+ table.add_column("Speed (s/req)", style="blue", width=15, overflow="fold", no_wrap=False)
1905
+ for m in sorted_models:
1906
+ # Check if api_key_path exists and file exists
1907
+ is_api_key_set = "api_key" in m
1908
+ name = m.get("name", "")
1909
+ if is_api_key_set:
1910
+ api_key = m.get("api_key", "").strip()
1911
+ if not api_key:
1912
+ printer.print_in_terminal("models_api_key_empty", style="yellow", name=name)
1913
+ name = f"{name} *"
1914
+
1915
+ table.add_row(
1916
+ name,
1917
+ m.get("model_name", ""),
1918
+ m.get("base_url", ""),
1919
+ f"{m.get('input_price', 0.0):.2f}",
1920
+ f"{m.get('output_price', 0.0):.2f}",
1921
+ f"{m.get('average_speed', 0.0):.3f}"
1922
+ )
1923
+ console.print(table)
1924
+ result_manager.add_result(content=json.dumps(sorted_models,ensure_ascii=False),meta={
1925
+ "action": "models",
1926
+ "input": {
1927
+ "query": query
1928
+ }
1929
+ })
1930
+
1931
+ else:
1932
+ printer.print_in_terminal("models_no_models", style="yellow")
1933
+ result_manager.add_result(content="No models found",meta={
1934
+ "action": "models",
1935
+ "input": {
1936
+ "query": query
1937
+ }
1938
+ })
1939
+
1940
+ elif subcmd == "/input_price":
1941
+ args = query.strip().split()
1942
+ if len(args) >= 2:
1943
+ name = args[0]
1944
+ try:
1945
+ price = float(args[1])
1946
+ if models_module.update_model_input_price(name, price):
1947
+ printer.print_in_terminal("models_input_price_updated", style="green", name=name, price=price)
1948
+ result_manager.add_result(content=f"models_input_price_updated: {name} {price}",meta={
1949
+ "action": "models",
1950
+ "input": {
1951
+ "query": query
1952
+ }
1953
+ })
1954
+ else:
1955
+ printer.print_in_terminal("models_not_found", style="red", name=name)
1956
+ result_manager.add_result(content=f"models_not_found: {name}",meta={
1957
+ "action": "models",
1958
+ "input": {
1959
+ "query": query
1960
+ }
1961
+ })
1962
+ except ValueError as e:
1963
+ result_manager.add_result(content=f"models_invalid_price: {str(e)}",meta={
1964
+ "action": "models",
1965
+ "input": {
1966
+ "query": query
1967
+ }
1968
+ })
1969
+ printer.print_in_terminal("models_invalid_price", style="red", error=str(e))
1970
+ else:
1971
+ result_manager.add_result(content=printer.get_message_from_key("models_input_price_usage"),meta={
1972
+ "action": "models",
1973
+ "input": {
1974
+ "query": query
1975
+ }
1976
+ })
1977
+ printer.print_in_terminal("models_input_price_usage", style="red")
1978
+
1979
+ elif subcmd == "/output_price":
1980
+ args = query.strip().split()
1981
+ if len(args) >= 2:
1982
+ name = args[0]
1983
+ try:
1984
+ price = float(args[1])
1985
+ if models_module.update_model_output_price(name, price):
1986
+ printer.print_in_terminal("models_output_price_updated", style="green", name=name, price=price)
1987
+ result_manager.add_result(content=f"models_output_price_updated: {name} {price}",meta={
1988
+ "action": "models",
1989
+ "input": {
1990
+ "query": query
1991
+ }
1992
+ })
1993
+ else:
1994
+ printer.print_in_terminal("models_not_found", style="red", name=name)
1995
+ result_manager.add_result(content=f"models_not_found: {name}",meta={
1996
+ "action": "models",
1997
+ "input": {
1998
+ "query": query
1999
+ }
2000
+ })
2001
+ except ValueError as e:
2002
+ printer.print_in_terminal("models_invalid_price", style="red", error=str(e))
2003
+ result_manager.add_result(content=f"models_invalid_price: {str(e)}",meta={
2004
+ "action": "models",
2005
+ "input": {
2006
+ "query": query
2007
+ }
2008
+ })
2009
+ else:
2010
+ result_manager.add_result(content=printer.get_message_from_key("models_output_price_usage"),meta={
2011
+ "action": "models",
2012
+ "input": {
2013
+ "query": query
2014
+ }
2015
+ })
2016
+ printer.print_in_terminal("models_output_price_usage", style="red")
2017
+
2018
+ elif subcmd == "/speed":
2019
+ args = query.strip().split()
2020
+ if len(args) >= 2:
2021
+ name = args[0]
2022
+ try:
2023
+ speed = float(args[1])
2024
+ if models_module.update_model_speed(name, speed):
2025
+ printer.print_in_terminal("models_speed_updated", style="green", name=name, speed=speed)
2026
+ result_manager.add_result(content=f"models_speed_updated: {name} {speed}",meta={
2027
+ "action": "models",
2028
+ "input": {
2029
+ "query": query
2030
+ }
2031
+ })
2032
+ else:
2033
+ printer.print_in_terminal("models_not_found", style="red", name=name)
2034
+ result_manager.add_result(content=f"models_not_found: {name}",meta={
2035
+ "action": "models",
2036
+ "input": {
2037
+ "query": query
2038
+ }
2039
+ })
2040
+ except ValueError as e:
2041
+ printer.print_in_terminal("models_invalid_speed", style="red", error=str(e))
2042
+ result_manager.add_result(content=f"models_invalid_speed: {str(e)}",meta={
2043
+ "action": "models",
2044
+ "input": {
2045
+ "query": query
2046
+ }
2047
+ })
2048
+ else:
2049
+ result_manager.add_result(content=printer.get_message_from_key("models_speed_usage"),meta={
2050
+ "action": "models",
2051
+ "input": {
2052
+ "query": query
2053
+ }
2054
+ })
2055
+ printer.print_in_terminal("models_speed_usage", style="red")
2056
+
2057
+ elif subcmd == "/speed-test":
2058
+ from autocoder.common.model_speed_test import render_speed_test_in_terminal
2059
+ test_rounds = 1 # 默认测试轮数
2060
+
2061
+ enable_long_context = False
2062
+ if "/long_context" in query:
2063
+ enable_long_context = True
2064
+ query = query.replace("/long_context", "", 1).strip()
2065
+
2066
+ if "/long-context" in query:
2067
+ enable_long_context = True
2068
+ query = query.replace("/long-context", "", 1).strip()
2069
+
2070
+ # 解析可选的测试轮数参数
2071
+ args = query.strip().split()
2072
+ if args and args[0].isdigit():
2073
+ test_rounds = int(args[0])
2074
+
2075
+ render_speed_test_in_terminal(product_mode, test_rounds,enable_long_context=enable_long_context)
2076
+ ## 等待优化,获取明细数据
2077
+ result_manager.add_result(content="models test success",meta={
2078
+ "action": "models",
2079
+ "input": {
2080
+ "query": query
2081
+ }
2082
+ })
2083
+
2084
+ elif subcmd == "/add":
2085
+ # Support both simplified and legacy formats
2086
+ args = query.strip().split(" ")
2087
+ if len(args) == 2:
2088
+ # Simplified: /models /add <name> <api_key>
2089
+ name, api_key = args[0], args[1]
2090
+ result = models_module.update_model_with_api_key(name, api_key)
2091
+ if result:
2092
+ result_manager.add_result(content=f"models_added: {name}",meta={
2093
+ "action": "models",
2094
+ "input": {
2095
+ "query": query
2096
+ }
2097
+ })
2098
+ printer.print_in_terminal("models_added", style="green", name=name)
2099
+ else:
2100
+ result_manager.add_result(content=f"models_add_failed: {name}",meta={
2101
+ "action": "models",
2102
+ "input": {
2103
+ "query": query
2104
+ }
2105
+ })
2106
+ printer.print_in_terminal("models_add_failed", style="red", name=name)
2107
+ else:
2108
+ printer.print_in_terminal("models_add_usage", style="red")
2109
+ result_manager.add_result(content=printer.get_message_from_key("models_add_usage"),meta={
2110
+ "action": "models",
2111
+ "input": {
2112
+ "query": query
2113
+ }
2114
+ })
2115
+
2116
+ elif subcmd == "/add_model":
2117
+ # Parse key=value pairs: /models /add_model name=abc base_url=http://xx ...
2118
+ # Collect key=value pairs
2119
+ kv_pairs = shlex.split(query)
2120
+ data_dict = {}
2121
+ for pair in kv_pairs:
2122
+ if '=' not in pair:
2123
+ printer.print_in_terminal("models_add_model_params", style="red")
2124
+ continue
2125
+ k, v = pair.split('=', 1)
2126
+ data_dict[k.strip()] = v.strip()
2127
+
2128
+ # Name is required
2129
+ if "name" not in data_dict:
2130
+ printer.print_in_terminal("models_add_model_name_required", style="red")
2131
+ return
2132
+
2133
+ # Check duplication
2134
+ if any(m["name"] == data_dict["name"] for m in models_data):
2135
+ printer.print_in_terminal("models_add_model_exists", style="yellow", name=data_dict["name"])
2136
+ result_manager.add_result(content=printer.get_message_from_key("models_add_model_exists",name=data_dict["name"]),meta={
2137
+ "action": "models",
2138
+ "input": {
2139
+ "query": query
2140
+ }
2141
+ })
2142
+ return
2143
+
2144
+ # Create model with defaults
2145
+ final_model = {
2146
+ "name": data_dict["name"],
2147
+ "model_type": data_dict.get("model_type", "saas/openai"),
2148
+ "model_name": data_dict.get("model_name", data_dict["name"]),
2149
+ "base_url": data_dict.get("base_url", "https://api.openai.com/v1"),
2150
+ "api_key_path": data_dict.get("api_key_path", "api.openai.com"),
2151
+ "description": data_dict.get("description", ""),
2152
+ "is_reasoning": data_dict.get("is_reasoning", "false") in ["true", "True", "TRUE", "1"]
2153
+ }
2154
+
2155
+ models_data.append(final_model)
2156
+ models_module.save_models(models_data)
2157
+ printer.print_in_terminal("models_add_model_success", style="green", name=data_dict["name"])
2158
+ result_manager.add_result(content=f"models_add_model_success: {data_dict['name']}",meta={
2159
+ "action": "models",
2160
+ "input": {
2161
+ "query": query
2162
+ }
2163
+ })
2164
+
2165
+ elif subcmd == "/remove":
2166
+ args = query.strip().split(" ")
2167
+ if len(args) < 1:
2168
+ printer.print_in_terminal("models_add_usage", style="red")
2169
+ result_manager.add_result(content=printer.get_message_from_key("models_add_usage"),meta={
2170
+ "action": "models",
2171
+ "input": {
2172
+ "query": query
2173
+ }
2174
+ })
2175
+ return
2176
+ name = args[0]
2177
+ filtered_models = [m for m in models_data if m["name"] != name]
2178
+ if len(filtered_models) == len(models_data):
2179
+ printer.print_in_terminal("models_add_model_remove", style="yellow", name=name)
2180
+ result_manager.add_result(content=printer.get_message_from_key("models_add_model_remove",name=name),meta={
2181
+ "action": "models",
2182
+ "input": {
2183
+ "query": query
2184
+ }
2185
+ })
2186
+ return
2187
+ models_module.save_models(filtered_models)
2188
+ printer.print_in_terminal("models_add_model_removed", style="green", name=name)
2189
+ result_manager.add_result(content=printer.get_message_from_key("models_add_model_removed",name=name),meta={
2190
+ "action": "models",
2191
+ "input": {
2192
+ "query": query
2193
+ }
2194
+ })
2195
+ else:
2196
+ printer.print_in_terminal("models_unknown_subcmd", style="yellow", subcmd=subcmd)
2197
+ result_manager.add_result(content=printer.get_message_from_key("models_unknown_subcmd",subcmd=subcmd),meta={
2198
+ "action": "models",
2199
+ "input": {
2200
+ "query": query
2201
+ }
2202
+ })
2203
+
2204
+ def exclude_dirs(dir_names: List[str]):
2205
+ new_dirs = dir_names
2206
+ existing_dirs = memory.get("exclude_dirs", [])
2207
+ dirs_to_add = [d for d in new_dirs if d not in existing_dirs]
2208
+
2209
+ if dirs_to_add:
2210
+ existing_dirs.extend(dirs_to_add)
2211
+ if "exclude_dirs" not in memory:
2212
+ memory["exclude_dirs"] = existing_dirs
2213
+ print(f"Added exclude dirs: {dirs_to_add}")
2214
+ exclude_files([f"regex://.*/{d}/*." for d in dirs_to_add])
2215
+ else:
2216
+ print("All specified dirs are already in the exclude list.")
2217
+ save_memory()
2218
+ completer.refresh_files()
2219
+
2220
+ def exclude_files(query: str):
2221
+ result_manager = ResultManager()
2222
+ printer = Printer()
2223
+ if "/list" in query:
2224
+ query = query.replace("/list", "", 1).strip()
2225
+ existing_file_patterns = memory.get("exclude_files", [])
2226
+ console = Console()
2227
+ # 打印表格
2228
+ table = Table(title="Exclude Files")
2229
+ table.add_column("File Pattern")
2230
+ for file_pattern in existing_file_patterns:
2231
+ table.add_row(file_pattern)
2232
+ console.print(table)
2233
+ result_manager.add_result(content=f"Exclude files: {existing_file_patterns}",meta={
2234
+ "action": "exclude_files",
2235
+ "input": {
2236
+ "query": query
2237
+ }
2238
+ })
2239
+ return
2240
+
2241
+ if "/drop" in query:
2242
+ query = query.replace("/drop", "", 1).strip()
2243
+ existing_file_patterns = memory.get("exclude_files", [])
2244
+ existing_file_patterns.remove(query.strip())
2245
+ memory["exclude_files"] = existing_file_patterns
2246
+ save_memory()
2247
+ completer.refresh_files()
2248
+ result_manager.add_result(content=f"Dropped exclude files: {query}",meta={
2249
+ "action": "exclude_files",
2250
+ "input": {
2251
+ "query": query
2252
+ }
2253
+ })
2254
+ return
2255
+
2256
+ new_file_patterns = query.strip().split(",")
2257
+
2258
+ existing_file_patterns = memory.get("exclude_files", [])
2259
+ file_patterns_to_add = [f for f in new_file_patterns if f not in existing_file_patterns]
2260
+
2261
+ for file_pattern in file_patterns_to_add:
2262
+ if not file_pattern.startswith("regex://"):
2263
+ result_manager.add_result(content=printer.get_message_from_key_with_format("invalid_file_pattern", file_pattern=file_pattern),meta={
2264
+ "action": "exclude_files",
2265
+ "input": {
2266
+ "query": file_pattern
2267
+ }
2268
+ })
2269
+ raise ValueError(printer.get_message_from_key_with_format("invalid_file_pattern", file_pattern=file_pattern))
2270
+
2271
+ if file_patterns_to_add:
2272
+ existing_file_patterns.extend(file_patterns_to_add)
2273
+ if "exclude_files" not in memory:
2274
+ memory["exclude_files"] = existing_file_patterns
2275
+
2276
+ result_manager.add_result(content=f"Added exclude files: {file_patterns_to_add}",meta={
2277
+ "action": "exclude_files",
2278
+ "input": {
2279
+ "query": file_patterns_to_add
2280
+ }
2281
+ })
2282
+ save_memory()
2283
+ print(f"Added exclude files: {file_patterns_to_add}")
2284
+ else:
2285
+ result_manager.add_result(content=f"All specified files are already in the exclude list.",meta={
2286
+ "action": "exclude_files",
2287
+ "input": {
2288
+ "query": file_patterns_to_add
2289
+ }
2290
+ })
2291
+ print("All specified files are already in the exclude list.")
2292
+
2293
+
2294
+
2295
+ @run_in_raw_thread()
2296
+ def index_build():
2297
+ conf = memory.get("conf", {})
2298
+ yaml_config = {
2299
+ "include_file": ["./base/base.yml"],
2300
+ "exclude_files": memory.get("exclude_files", []),
2301
+ }
2302
+
2303
+ for key, value in conf.items():
2304
+ converted_value = convert_config_value(key, value)
2305
+ if converted_value is not None:
2306
+ yaml_config[key] = converted_value
2307
+
2308
+ yaml_content = convert_yaml_config_to_str(yaml_config=yaml_config)
2309
+ yaml_file = os.path.join("actions", f"{uuid.uuid4()}.yml")
2310
+
2311
+ with open(yaml_file, "w",encoding="utf-8") as f:
2312
+ f.write(yaml_content)
2313
+ try:
2314
+ auto_coder_main(["index", "--file", yaml_file])
2315
+ completer.refresh_files()
2316
+ finally:
2317
+ os.remove(yaml_file)
2318
+
2319
+
2320
+ def get_final_config()->AutoCoderArgs:
2321
+ conf = memory.get("conf", {})
2322
+ yaml_config = {
2323
+ "include_file": ["./base/base.yml"],
2324
+ "auto_merge": conf.get("auto_merge", "editblock"),
2325
+ "human_as_model": conf.get("human_as_model", "false") == "true",
2326
+ "skip_build_index": conf.get("skip_build_index", "true") == "true",
2327
+ "skip_confirm": conf.get("skip_confirm", "true") == "true",
2328
+ "silence": conf.get("silence", "true") == "true",
2329
+ "include_project_structure": conf.get("include_project_structure", "true")
2330
+ == "true",
2331
+ "exclude_files": memory.get("exclude_files", []),
2332
+ }
2333
+ for key, value in conf.items():
2334
+ converted_value = convert_config_value(key, value)
2335
+ if converted_value is not None:
2336
+ yaml_config[key] = converted_value
2337
+
2338
+ temp_yaml = os.path.join("actions", f"{uuid.uuid4()}.yml")
2339
+ try:
2340
+ with open(temp_yaml, "w",encoding="utf-8") as f:
2341
+ f.write(convert_yaml_config_to_str(yaml_config=yaml_config))
2342
+ args = convert_yaml_to_config(temp_yaml)
2343
+ finally:
2344
+ if os.path.exists(temp_yaml):
2345
+ os.remove(temp_yaml)
2346
+ return args
2347
+
2348
+ def help(query: str):
2349
+ from autocoder.common.auto_configure import ConfigAutoTuner,MemoryConfig,AutoConfigRequest
2350
+ args = get_final_config()
2351
+ product_mode = memory.get("product_mode", "lite")
2352
+ llm = get_single_llm(args.chat_model or args.model, product_mode=product_mode)
2353
+ auto_config_tuner = ConfigAutoTuner(args=args, llm=llm, memory_config=MemoryConfig(memory=memory, save_memory_func=save_memory))
2354
+ auto_config_tuner.tune(AutoConfigRequest(query=query))
2355
+
2356
+ @run_in_raw_thread()
2357
+ def index_export(path: str):
2358
+ from autocoder.common.index_import_export import export_index
2359
+ from autocoder.common.printer import Printer
2360
+ printer = Printer()
2361
+ project_root = os.getcwd()
2362
+ if export_index(project_root, path):
2363
+ printer.print_in_terminal("index_export_success", path=path)
2364
+ else:
2365
+ printer.print_in_terminal("index_export_fail", path=path)
2366
+
2367
+ @run_in_raw_thread()
2368
+ def index_import(path: str):
2369
+ from autocoder.common.index_import_export import import_index
2370
+ from autocoder.common.printer import Printer
2371
+ printer = Printer()
2372
+ project_root = os.getcwd()
2373
+ if import_index(project_root, path):
2374
+ printer.print_in_terminal("index_import_success", path=path)
2375
+ else:
2376
+ printer.print_in_terminal("index_import_fail", path=path)
2377
+
2378
+ @run_in_raw_thread()
2379
+ def index_query(query: str):
2380
+ conf = memory.get("conf", {})
2381
+ yaml_config = {
2382
+ "include_file": ["./base/base.yml"],
2383
+ }
2384
+
2385
+ for key, value in conf.items():
2386
+ converted_value = convert_config_value(key, value)
2387
+ if converted_value is not None:
2388
+ yaml_config[key] = converted_value
2389
+
2390
+ yaml_config["query"] = query
2391
+
2392
+ yaml_content = convert_yaml_config_to_str(yaml_config=yaml_config)
2393
+ yaml_file = os.path.join("actions", f"{uuid.uuid4()}.yml")
2394
+
2395
+ with open(yaml_file, "w",encoding="utf-8") as f:
2396
+ f.write(yaml_content)
2397
+ try:
2398
+ auto_coder_main(["index-query", "--file", yaml_file])
2399
+ finally:
2400
+ os.remove(yaml_file)
2401
+
2402
+
2403
+ def list_files():
2404
+ console = Console()
2405
+ project_root = os.getcwd()
2406
+ current_files = memory["current_files"]["files"]
2407
+
2408
+ if current_files:
2409
+ table = Table(
2410
+ title="Current Files", show_header=True, header_style="bold magenta"
2411
+ )
2412
+ table.add_column("File", style="green")
2413
+ for file in current_files:
2414
+ table.add_row(os.path.relpath(file, project_root))
2415
+ console.print(Panel(table, border_style="blue"))
2416
+ else:
2417
+ console.print(
2418
+ Panel(
2419
+ "No files in the current session.",
2420
+ title="Current Files",
2421
+ border_style="yellow",
2422
+ )
2423
+ )
2424
+
2425
+
2426
+ def gen_and_exec_shell_command(query: str):
2427
+ from rich.prompt import Confirm
2428
+ from autocoder.common.printer import Printer
2429
+ from rich.console import Console
2430
+
2431
+ printer = Printer()
2432
+ console = Console()
2433
+ # Generate the shell script
2434
+ shell_script = generate_shell_command(query)
2435
+
2436
+ # Ask for confirmation using rich
2437
+ if Confirm.ask(
2438
+ printer.get_message_from_key("confirm_execute_shell_script"),
2439
+ default=False
2440
+ ):
2441
+ execute_shell_command(shell_script)
2442
+ else:
2443
+ console.print(
2444
+ Panel(
2445
+ printer.get_message_from_key("shell_script_not_executed"),
2446
+ border_style="yellow"
2447
+ )
2448
+ )
2449
+
2450
+
2451
+ def lib_command(args: List[str]):
2452
+ console = Console()
2453
+ lib_dir = os.path.join(".auto-coder", "libs")
2454
+ llm_friendly_packages_dir = os.path.join(lib_dir, "llm_friendly_packages")
2455
+
2456
+ if not os.path.exists(lib_dir):
2457
+ os.makedirs(lib_dir)
2458
+
2459
+ if "libs" not in memory:
2460
+ memory["libs"] = {}
2461
+
2462
+ if not args:
2463
+ console.print(
2464
+ "Please specify a subcommand: /add, /remove, /list, /set-proxy, /refresh, or /get"
2465
+ )
2466
+ return
2467
+
2468
+ subcommand = args[0]
2469
+
2470
+ if subcommand == "/add":
2471
+ if len(args) < 2:
2472
+ console.print("Please specify a library name to add")
2473
+ return
2474
+ lib_name = args[1].strip()
2475
+
2476
+ # Clone the repository if it doesn't exist
2477
+ if not os.path.exists(llm_friendly_packages_dir):
2478
+ console.print("Cloning llm_friendly_packages repository...")
2479
+ try:
2480
+ proxy_url = memory.get(
2481
+ "lib-proxy", "https://github.com/allwefantasy/llm_friendly_packages"
2482
+ )
2483
+ git.Repo.clone_from(
2484
+ proxy_url,
2485
+ llm_friendly_packages_dir,
2486
+ )
2487
+ console.print(
2488
+ "Successfully cloned llm_friendly_packages repository")
2489
+ except git.exc.GitCommandError as e:
2490
+ console.print(f"Error cloning repository: {e}")
2491
+
2492
+ if lib_name in memory["libs"]:
2493
+ console.print(f"Library {lib_name} is already added")
2494
+ else:
2495
+ memory["libs"][lib_name] = {}
2496
+ console.print(f"Added library: {lib_name}")
2497
+
2498
+ save_memory()
2499
+
2500
+ elif subcommand == "/remove":
2501
+ if len(args) < 2:
2502
+ console.print("Please specify a library name to remove")
2503
+ return
2504
+ lib_name = args[1].strip()
2505
+ if lib_name in memory["libs"]:
2506
+ del memory["libs"][lib_name]
2507
+ console.print(f"Removed library: {lib_name}")
2508
+ save_memory()
2509
+ else:
2510
+ console.print(f"Library {lib_name} is not in the list")
2511
+
2512
+ elif subcommand == "/list":
2513
+ if memory["libs"]:
2514
+ table = Table(title="Added Libraries")
2515
+ table.add_column("Library Name", style="cyan")
2516
+ for lib_name in memory["libs"]:
2517
+ table.add_row(lib_name)
2518
+ console.print(table)
2519
+ else:
2520
+ console.print("No libraries added yet")
2521
+
2522
+ elif subcommand == "/set-proxy":
2523
+ if len(args) == 1:
2524
+ current_proxy = memory.get("lib-proxy", "No proxy set")
2525
+ console.print(f"Current proxy: {current_proxy}")
2526
+ elif len(args) == 2:
2527
+ proxy_url = args[1]
2528
+ memory["lib-proxy"] = proxy_url
2529
+ console.print(f"Set proxy to: {proxy_url}")
2530
+ save_memory()
2531
+ else:
2532
+ console.print("Invalid number of arguments for /set-proxy")
2533
+
2534
+ elif subcommand == "/refresh":
2535
+ if os.path.exists(llm_friendly_packages_dir):
2536
+ try:
2537
+ repo = git.Repo(llm_friendly_packages_dir)
2538
+ origin = repo.remotes.origin
2539
+ proxy_url = memory.get("lib-proxy")
2540
+
2541
+ current_url = origin.url
2542
+
2543
+ if proxy_url and proxy_url != current_url:
2544
+ new_url = proxy_url
2545
+ origin.set_url(new_url)
2546
+ console.print(f"Updated remote URL to: {new_url}")
2547
+
2548
+ origin.pull()
2549
+ console.print(
2550
+ "Successfully updated llm_friendly_packages repository")
2551
+
2552
+ except git.exc.GitCommandError as e:
2553
+ console.print(f"Error updating repository: {e}")
2554
+ else:
2555
+ console.print(
2556
+ "llm_friendly_packages repository does not exist. Please run /lib /add <library_name> command first to clone it."
2557
+ )
2558
+
2559
+ elif subcommand == "/get":
2560
+ if len(args) < 2:
2561
+ console.print("Please specify a package name to get")
2562
+ return
2563
+ package_name = args[1].strip()
2564
+ docs = get_llm_friendly_package_docs(package_name, return_paths=True)
2565
+ if docs:
2566
+ table = Table(title=f"Markdown Files for {package_name}")
2567
+ table.add_column("File Path", style="cyan")
2568
+ for doc in docs:
2569
+ table.add_row(doc)
2570
+ console.print(table)
2571
+ else:
2572
+ console.print(
2573
+ f"No markdown files found for package: {package_name}")
2574
+
2575
+ else:
2576
+ console.print(f"Unknown subcommand: {subcommand}")
2577
+
2578
+
2579
+ def execute_shell_command(command: str):
2580
+ from autocoder.common.shells import execute_shell_command as shell_exec
2581
+ shell_exec(command)
2582
+
2583
+
2584
+ def conf_export(path: str):
2585
+ from autocoder.common.conf_import_export import export_conf
2586
+ export_conf(os.getcwd(), path)
2587
+
2588
+ def conf_import(path: str):
2589
+ from autocoder.common.conf_import_export import import_conf
2590
+ import_conf(os.getcwd(), path)
2591
+
2592
+ @run_in_raw_thread()
2593
+ def auto_command(params,query: str):
2594
+ """处理/auto指令"""
2595
+ from autocoder.commands.auto_command import CommandAutoTuner, AutoCommandRequest, CommandConfig, MemoryConfig
2596
+ args = get_final_config()
2597
+ # help(query)
2598
+
2599
+ # 准备请求参数
2600
+ request = AutoCommandRequest(
2601
+ user_input=query
2602
+ )
2603
+
2604
+ # 初始化调优器
2605
+ llm = get_single_llm(args.chat_model or args.model,product_mode=args.product_mode)
2606
+ tuner = CommandAutoTuner(llm,
2607
+ args=args,
2608
+ memory_config=MemoryConfig(memory=memory, save_memory_func=save_memory),
2609
+ command_config=CommandConfig(
2610
+ add_files=add_files,
2611
+ remove_files=remove_files,
2612
+ list_files=list_files,
2613
+ conf=configure,
2614
+ revert=revert,
2615
+ commit=commit,
2616
+ help=help,
2617
+ exclude_dirs=exclude_dirs,
2618
+ exclude_files=exclude_files,
2619
+ ask=ask,
2620
+ chat=chat,
2621
+ coding=coding,
2622
+ design=design,
2623
+ summon=summon,
2624
+ lib=lib_command,
2625
+ mcp=mcp,
2626
+ models=manage_models,
2627
+ index_build=index_build,
2628
+ index_query=index_query,
2629
+ execute_shell_command=execute_shell_command,
2630
+ generate_shell_command=generate_shell_command,
2631
+ conf_export=conf_export,
2632
+ conf_import=conf_import,
2633
+ index_export=index_export,
2634
+ index_import=index_import
2635
+ ))
2636
+
2637
+ # 生成建议
2638
+ response = tuner.analyze(request)
2639
+ printer = Printer()
2640
+ # 显示建议
2641
+ console = Console()
2642
+ console.print(Panel(
2643
+ Markdown(response.reasoning or ""),
2644
+ title=printer.get_message_from_key_with_format("auto_command_reasoning_title"),
2645
+ border_style="blue",
2646
+ padding=(1, 2)
2647
+ ))