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

Files changed (36) hide show
  1. {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/METADATA +1 -1
  2. {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/RECORD +36 -27
  3. autocoder/agent/auto_review_commit.py +51 -24
  4. autocoder/auto_coder.py +24 -1
  5. autocoder/chat_auto_coder.py +377 -399
  6. autocoder/chat_auto_coder_lang.py +20 -0
  7. autocoder/commands/__init__.py +0 -0
  8. autocoder/commands/auto_command.py +1174 -0
  9. autocoder/commands/tools.py +533 -0
  10. autocoder/common/__init__.py +8 -0
  11. autocoder/common/auto_coder_lang.py +61 -8
  12. autocoder/common/auto_configure.py +304 -0
  13. autocoder/common/code_auto_merge.py +2 -2
  14. autocoder/common/code_auto_merge_diff.py +2 -2
  15. autocoder/common/code_auto_merge_editblock.py +2 -2
  16. autocoder/common/code_auto_merge_strict_diff.py +2 -2
  17. autocoder/common/code_modification_ranker.py +8 -7
  18. autocoder/common/command_completer.py +557 -0
  19. autocoder/common/conf_validator.py +245 -0
  20. autocoder/common/conversation_pruner.py +131 -0
  21. autocoder/common/git_utils.py +82 -1
  22. autocoder/common/index_import_export.py +101 -0
  23. autocoder/common/result_manager.py +115 -0
  24. autocoder/common/shells.py +22 -6
  25. autocoder/common/utils_code_auto_generate.py +2 -2
  26. autocoder/dispacher/actions/action.py +45 -4
  27. autocoder/dispacher/actions/plugins/action_regex_project.py +13 -1
  28. autocoder/index/filter/quick_filter.py +22 -7
  29. autocoder/utils/auto_coder_utils/chat_stream_out.py +13 -6
  30. autocoder/utils/project_structure.py +15 -0
  31. autocoder/utils/thread_utils.py +4 -0
  32. autocoder/version.py +1 -1
  33. {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/LICENSE +0 -0
  34. {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/WHEEL +0 -0
  35. {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/entry_points.txt +0 -0
  36. {auto_coder-0.1.259.dist-info → auto_coder-0.1.261.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,9 @@
1
1
  from prompt_toolkit.completion import Completer, Completion, CompleteEvent
2
2
  from prompt_toolkit.document import Document
3
3
  import pydantic
4
+ from typing import Callable,Dict,Any
5
+ from pydantic import BaseModel,SkipValidation
6
+ from autocoder.common import AutoCoderArgs
4
7
  import os
5
8
 
6
9
  COMMANDS = {
@@ -38,6 +41,8 @@ COMMANDS = {
38
41
  "/speed-test": "",
39
42
  "/input_price": "",
40
43
  "/output_price": "",
44
+ },
45
+ "/auto": {
41
46
  }
42
47
  }
43
48
 
@@ -48,6 +53,24 @@ class Tag(pydantic.BaseModel):
48
53
  end_tag: str
49
54
 
50
55
 
56
+ class FileSystemModel(pydantic.BaseModel):
57
+ project_root: str
58
+ get_all_file_names_in_project: SkipValidation[Callable]
59
+ get_all_file_in_project: SkipValidation[Callable]
60
+ get_all_dir_names_in_project: SkipValidation[Callable]
61
+ get_all_file_in_project_with_dot: SkipValidation[Callable]
62
+ get_symbol_list: SkipValidation[Callable]
63
+
64
+ class MemoryConfig(BaseModel):
65
+ """
66
+ A model to encapsulate memory configuration and operations.
67
+ """
68
+ memory: Dict[str, Any]
69
+ save_memory_func: SkipValidation[Callable]
70
+
71
+ class Config:
72
+ arbitrary_types_allowed = True
73
+
51
74
  class CommandTextParser:
52
75
  def __init__(self, text: str, command: str):
53
76
  self.text = text
@@ -322,3 +345,537 @@ class CommandTextParser:
322
345
  self.consume_tag()
323
346
  else:
324
347
  self.consume_coding_value()
348
+
349
+
350
+ class CommandCompleter(Completer):
351
+ def __init__(self, commands, file_system_model: FileSystemModel, memory_model: MemoryConfig):
352
+ self.commands = commands
353
+ self.file_system_model = file_system_model
354
+ self.memory_model = memory_model
355
+ self.all_file_names = file_system_model.get_all_file_names_in_project()
356
+ self.all_files = file_system_model.get_all_file_in_project()
357
+ self.all_dir_names = file_system_model.get_all_dir_names_in_project()
358
+ self.all_files_with_dot = file_system_model.get_all_file_in_project_with_dot()
359
+ self.symbol_list = file_system_model.get_symbol_list()
360
+ self.current_file_names = []
361
+
362
+ def get_completions(self, document, complete_event):
363
+ text = document.text_before_cursor
364
+ words = text.split()
365
+
366
+ if len(words) > 0:
367
+ if words[0] == "/mode":
368
+ left_word = text[len("/mode"):]
369
+ for mode in ["normal", "auto_detect", "voice_input"]:
370
+ if mode.startswith(left_word.strip()):
371
+ yield Completion(mode, start_position=-len(left_word.strip()))
372
+
373
+ if words[0] == "/add_files":
374
+ new_text = text[len("/add_files"):]
375
+ parser = CommandTextParser(new_text, words[0])
376
+ parser.add_files()
377
+ current_word = parser.current_word()
378
+
379
+ if parser.last_sub_command() == "/refresh":
380
+ return
381
+
382
+ for command in parser.get_sub_commands():
383
+ if command.startswith(current_word):
384
+ yield Completion(command, start_position=-len(current_word))
385
+
386
+ if parser.first_sub_command() == "/group" and (
387
+ parser.last_sub_command() == "/group"
388
+ or parser.last_sub_command() == "/drop"
389
+ ):
390
+ group_names = self.memory_model.memory["current_files"]["groups"].keys()
391
+ if "," in current_word:
392
+ current_word = current_word.split(",")[-1]
393
+
394
+ for group_name in group_names:
395
+ if group_name.startswith(current_word):
396
+ yield Completion(
397
+ group_name, start_position=-len(current_word)
398
+ )
399
+
400
+ if parser.first_sub_command() != "/group":
401
+ if current_word and current_word.startswith("."):
402
+ for file_name in self.all_files_with_dot:
403
+ if file_name.startswith(current_word):
404
+ yield Completion(
405
+ file_name, start_position=-
406
+ len(current_word)
407
+ )
408
+ else:
409
+ for file_name in self.all_file_names:
410
+ if file_name.startswith(current_word):
411
+ yield Completion(
412
+ file_name, start_position=-
413
+ len(current_word)
414
+ )
415
+ for file_name in self.all_files:
416
+ if current_word and current_word in file_name:
417
+ yield Completion(
418
+ file_name, start_position=-
419
+ len(current_word)
420
+ )
421
+ elif words[0] == "/remove_files":
422
+ new_words = text[len("/remove_files"):].strip().split(",")
423
+
424
+ is_at_space = text[-1] == " "
425
+ last_word = new_words[-2] if len(new_words) > 1 else ""
426
+ current_word = new_words[-1] if new_words else ""
427
+
428
+ if is_at_space:
429
+ last_word = current_word
430
+ current_word = ""
431
+
432
+ # /remove_files /all [cursor] or /remove_files /all p[cursor]
433
+ if not last_word and not current_word:
434
+ if "/all".startswith(current_word):
435
+ yield Completion("/all", start_position=-len(current_word))
436
+ for file_name in self.current_file_names:
437
+ yield Completion(file_name, start_position=-len(current_word))
438
+
439
+ # /remove_files /a[cursor] or /remove_files p[cursor]
440
+ if current_word:
441
+ if "/all".startswith(current_word):
442
+ yield Completion("/all", start_position=-len(current_word))
443
+ for file_name in self.current_file_names:
444
+ if current_word and current_word in file_name:
445
+ yield Completion(
446
+ file_name, start_position=-len(current_word)
447
+ )
448
+ elif words[0] == "/exclude_dirs":
449
+ new_words = text[len("/exclude_dirs"):].strip().split(",")
450
+ current_word = new_words[-1]
451
+
452
+ for file_name in self.all_dir_names:
453
+ if current_word and current_word in file_name:
454
+ yield Completion(file_name, start_position=-len(current_word))
455
+
456
+ elif words[0] == "/lib":
457
+ new_text = text[len("/lib"):]
458
+ parser = CommandTextParser(new_text, words[0])
459
+ parser.lib()
460
+ current_word = parser.current_word()
461
+
462
+ for command in parser.get_sub_commands():
463
+ if command.startswith(current_word):
464
+ yield Completion(command, start_position=-len(current_word))
465
+
466
+ if parser.last_sub_command() in ["/add", "/remove", "/get"]:
467
+ for lib_name in self.memory_model.memory.get("libs", {}).keys():
468
+ if lib_name.startswith(current_word):
469
+ yield Completion(
470
+ lib_name, start_position=-len(current_word)
471
+ )
472
+ elif words[0] == "/mcp":
473
+ new_text = text[len("/mcp"):]
474
+ parser = CommandTextParser(new_text, words[0])
475
+ parser.lib()
476
+ current_word = parser.current_word()
477
+ for command in parser.get_sub_commands():
478
+ if command.startswith(current_word):
479
+ yield Completion(command, start_position=-len(current_word))
480
+ elif words[0] == "/models":
481
+ new_text = text[len("/models"):]
482
+ parser = CommandTextParser(new_text, words[0])
483
+ parser.lib()
484
+ current_word = parser.current_word()
485
+ for command in parser.get_sub_commands():
486
+ if command.startswith(current_word):
487
+ yield Completion(command, start_position=-len(current_word))
488
+
489
+ elif words[0] == "/conf":
490
+ new_words = text[len("/conf"):].strip().split()
491
+ is_at_space = text[-1] == " "
492
+ last_word = new_words[-2] if len(new_words) > 1 else ""
493
+ current_word = new_words[-1] if new_words else ""
494
+ completions = []
495
+
496
+ if is_at_space:
497
+ last_word = current_word
498
+ current_word = ""
499
+
500
+ # /conf /drop [curor] or /conf /drop p[cursor]
501
+ if last_word == "/drop":
502
+ completions = [
503
+ field_name
504
+ for field_name in self.memory_model.memory["conf"].keys()
505
+ if field_name.startswith(current_word)
506
+ ]
507
+ # /conf [curosr]
508
+ elif not last_word and not current_word:
509
+ completions = [
510
+ "/drop"] if "/drop".startswith(current_word) else []
511
+ completions += [
512
+ field_name + ":"
513
+ for field_name in AutoCoderArgs.model_fields.keys()
514
+ if field_name.startswith(current_word)
515
+ ]
516
+ # /conf p[cursor]
517
+ elif not last_word and current_word:
518
+ completions = [
519
+ "/drop"] if "/drop".startswith(current_word) else []
520
+ completions += [
521
+ field_name + ":"
522
+ for field_name in AutoCoderArgs.model_fields.keys()
523
+ if field_name.startswith(current_word)
524
+ ]
525
+
526
+ for completion in completions:
527
+ yield Completion(completion, start_position=-len(current_word))
528
+ elif words[0] in ["/chat", "/coding","/auto"]:
529
+ image_extensions = (
530
+ ".png",
531
+ ".jpg",
532
+ ".jpeg",
533
+ ".gif",
534
+ ".bmp",
535
+ ".tiff",
536
+ ".tif",
537
+ ".webp",
538
+ ".svg",
539
+ ".ico",
540
+ ".heic",
541
+ ".heif",
542
+ ".raw",
543
+ ".cr2",
544
+ ".nef",
545
+ ".arw",
546
+ ".dng",
547
+ ".orf",
548
+ ".rw2",
549
+ ".pef",
550
+ ".srw",
551
+ ".eps",
552
+ ".ai",
553
+ ".psd",
554
+ ".xcf",
555
+ )
556
+ new_text = text[len(words[0]):]
557
+ parser = CommandTextParser(new_text, words[0])
558
+
559
+ parser.coding()
560
+ current_word = parser.current_word()
561
+
562
+ if len(new_text.strip()) == 0 or new_text.strip() == "/":
563
+ for command in parser.get_sub_commands():
564
+ if command.startswith(current_word):
565
+ yield Completion(command, start_position=-len(current_word))
566
+
567
+ all_tags = parser.tags
568
+
569
+ if current_word.startswith("@"):
570
+ name = current_word[1:]
571
+ target_set = set()
572
+
573
+ for file_name in self.current_file_names:
574
+ base_file_name = os.path.basename(file_name)
575
+ if name in base_file_name:
576
+ target_set.add(base_file_name)
577
+ path_parts = file_name.split(os.sep)
578
+ display_name = (
579
+ os.sep.join(path_parts[-3:])
580
+ if len(path_parts) > 3
581
+ else file_name
582
+ )
583
+ relative_path = os.path.relpath(
584
+ file_name, self.file_system_model.project_root)
585
+ yield Completion(
586
+ relative_path,
587
+ start_position=-len(name),
588
+ display=f"{display_name} (in active files)",
589
+ )
590
+
591
+ for file_name in self.all_file_names:
592
+ if file_name.startswith(name) and file_name not in target_set:
593
+ target_set.add(file_name)
594
+
595
+ path_parts = file_name.split(os.sep)
596
+ display_name = (
597
+ os.sep.join(path_parts[-3:])
598
+ if len(path_parts) > 3
599
+ else file_name
600
+ )
601
+ relative_path = os.path.relpath(
602
+ file_name, self.file_system_model.project_root)
603
+
604
+ yield Completion(
605
+ relative_path,
606
+ start_position=-len(name),
607
+ display=f"{display_name}",
608
+ )
609
+
610
+ for file_name in self.all_files:
611
+ if name in file_name and file_name not in target_set:
612
+ path_parts = file_name.split(os.sep)
613
+ display_name = (
614
+ os.sep.join(path_parts[-3:])
615
+ if len(path_parts) > 3
616
+ else file_name
617
+ )
618
+ relative_path = os.path.relpath(
619
+ file_name, self.file_system_model.project_root)
620
+ yield Completion(
621
+ relative_path,
622
+ start_position=-len(name),
623
+ display=f"{display_name}",
624
+ )
625
+
626
+ if current_word.startswith("@@"):
627
+ name = current_word[2:]
628
+ for symbol in self.symbol_list:
629
+ if name in symbol.symbol_name:
630
+ file_name = symbol.file_name
631
+ path_parts = file_name.split(os.sep)
632
+ display_name = (
633
+ os.sep.join(path_parts[-3:])
634
+ if len(path_parts) > 3
635
+ else symbol.symbol_name
636
+ )
637
+ relative_path = os.path.relpath(
638
+ file_name, self.file_system_model.project_root)
639
+ yield Completion(
640
+ f"{symbol.symbol_name}(location: {relative_path})",
641
+ start_position=-len(name),
642
+ display=f"{symbol.symbol_name} ({display_name}/{symbol.symbol_type})",
643
+ )
644
+
645
+ tags = [tag for tag in parser.tags]
646
+
647
+ if current_word.startswith("<"):
648
+ name = current_word[1:]
649
+ for tag in ["<img>", "</img>"]:
650
+ if all_tags and all_tags[-1].start_tag == "<img>":
651
+ if tag.startswith(name):
652
+ yield Completion(
653
+ "</img>", start_position=-len(current_word)
654
+ )
655
+ elif tag.startswith(name):
656
+ yield Completion(tag, start_position=-len(current_word))
657
+
658
+ if tags and tags[-1].start_tag == "<img>" and tags[-1].end_tag == "":
659
+ raw_file_name = tags[0].content
660
+ file_name = raw_file_name.strip()
661
+ parent_dir = os.path.dirname(file_name)
662
+ file_basename = os.path.basename(file_name)
663
+ search_dir = parent_dir if parent_dir else "."
664
+ for root, dirs, files in os.walk(search_dir):
665
+ # 只处理直接子目录
666
+ if root != search_dir:
667
+ continue
668
+
669
+ # 补全子目录
670
+ for dir in dirs:
671
+ full_path = os.path.join(root, dir)
672
+ if full_path.startswith(file_name):
673
+ relative_path = os.path.relpath(
674
+ full_path, search_dir)
675
+ yield Completion(
676
+ relative_path,
677
+ start_position=-len(file_basename),
678
+ )
679
+
680
+ # 补全文件
681
+ for file in files:
682
+ if file.lower().endswith(
683
+ image_extensions
684
+ ) and file.startswith(file_basename):
685
+ full_path = os.path.join(root, file)
686
+ relative_path = os.path.relpath(
687
+ full_path, search_dir)
688
+ yield Completion(
689
+ relative_path,
690
+ start_position=-len(file_basename),
691
+ )
692
+
693
+ # 只处理一层子目录,然后退出循环
694
+ break
695
+
696
+ elif not words[0].startswith("/"):
697
+ image_extensions = (
698
+ ".png",
699
+ ".jpg",
700
+ ".jpeg",
701
+ ".gif",
702
+ ".bmp",
703
+ ".tiff",
704
+ ".tif",
705
+ ".webp",
706
+ ".svg",
707
+ ".ico",
708
+ ".heic",
709
+ ".heif",
710
+ ".raw",
711
+ ".cr2",
712
+ ".nef",
713
+ ".arw",
714
+ ".dng",
715
+ ".orf",
716
+ ".rw2",
717
+ ".pef",
718
+ ".srw",
719
+ ".eps",
720
+ ".ai",
721
+ ".psd",
722
+ ".xcf",
723
+ )
724
+ new_text = text
725
+ parser = CommandTextParser(new_text, "/auto")
726
+
727
+ parser.coding()
728
+ current_word = parser.current_word()
729
+
730
+ if len(new_text.strip()) == 0 or new_text.strip() == "/":
731
+ for command in parser.get_sub_commands():
732
+ if command.startswith(current_word):
733
+ yield Completion(command, start_position=-len(current_word))
734
+
735
+ all_tags = parser.tags
736
+
737
+ if current_word.startswith("@"):
738
+ name = current_word[1:]
739
+ target_set = set()
740
+
741
+ for file_name in self.current_file_names:
742
+ base_file_name = os.path.basename(file_name)
743
+ if name in base_file_name:
744
+ target_set.add(base_file_name)
745
+ path_parts = file_name.split(os.sep)
746
+ display_name = (
747
+ os.sep.join(path_parts[-3:])
748
+ if len(path_parts) > 3
749
+ else file_name
750
+ )
751
+ relative_path = os.path.relpath(
752
+ file_name, self.file_system_model.project_root)
753
+ yield Completion(
754
+ relative_path,
755
+ start_position=-len(name),
756
+ display=f"{display_name} (in active files)",
757
+ )
758
+
759
+ for file_name in self.all_file_names:
760
+ if file_name.startswith(name) and file_name not in target_set:
761
+ target_set.add(file_name)
762
+
763
+ path_parts = file_name.split(os.sep)
764
+ display_name = (
765
+ os.sep.join(path_parts[-3:])
766
+ if len(path_parts) > 3
767
+ else file_name
768
+ )
769
+ relative_path = os.path.relpath(
770
+ file_name, self.file_system_model.project_root)
771
+
772
+ yield Completion(
773
+ relative_path,
774
+ start_position=-len(name),
775
+ display=f"{display_name}",
776
+ )
777
+
778
+ for file_name in self.all_files:
779
+ if name in file_name and file_name not in target_set:
780
+ path_parts = file_name.split(os.sep)
781
+ display_name = (
782
+ os.sep.join(path_parts[-3:])
783
+ if len(path_parts) > 3
784
+ else file_name
785
+ )
786
+ relative_path = os.path.relpath(
787
+ file_name, self.file_system_model.project_root)
788
+ yield Completion(
789
+ relative_path,
790
+ start_position=-len(name),
791
+ display=f"{display_name}",
792
+ )
793
+
794
+ if current_word.startswith("@@"):
795
+ name = current_word[2:]
796
+ for symbol in self.symbol_list:
797
+ if name in symbol.symbol_name:
798
+ file_name = symbol.file_name
799
+ path_parts = file_name.split(os.sep)
800
+ display_name = (
801
+ os.sep.join(path_parts[-3:])
802
+ if len(path_parts) > 3
803
+ else symbol.symbol_name
804
+ )
805
+ relative_path = os.path.relpath(
806
+ file_name, self.file_system_model.project_root)
807
+ yield Completion(
808
+ f"{symbol.symbol_name}(location: {relative_path})",
809
+ start_position=-len(name),
810
+ display=f"{symbol.symbol_name} ({display_name}/{symbol.symbol_type})",
811
+ )
812
+
813
+ tags = [tag for tag in parser.tags]
814
+
815
+ if current_word.startswith("<"):
816
+ name = current_word[1:]
817
+ for tag in ["<img>", "</img>"]:
818
+ if all_tags and all_tags[-1].start_tag == "<img>":
819
+ if tag.startswith(name):
820
+ yield Completion(
821
+ "</img>", start_position=-len(current_word)
822
+ )
823
+ elif tag.startswith(name):
824
+ yield Completion(tag, start_position=-len(current_word))
825
+
826
+ if tags and tags[-1].start_tag == "<img>" and tags[-1].end_tag == "":
827
+ raw_file_name = tags[0].content
828
+ file_name = raw_file_name.strip()
829
+ parent_dir = os.path.dirname(file_name)
830
+ file_basename = os.path.basename(file_name)
831
+ search_dir = parent_dir if parent_dir else "."
832
+ for root, dirs, files in os.walk(search_dir):
833
+ # 只处理直接子目录
834
+ if root != search_dir:
835
+ continue
836
+
837
+ # 补全子目录
838
+ for dir in dirs:
839
+ full_path = os.path.join(root, dir)
840
+ if full_path.startswith(file_name):
841
+ relative_path = os.path.relpath(
842
+ full_path, search_dir)
843
+ yield Completion(
844
+ relative_path,
845
+ start_position=-len(file_basename),
846
+ )
847
+
848
+ # 补全文件
849
+ for file in files:
850
+ if file.lower().endswith(
851
+ image_extensions
852
+ ) and file.startswith(file_basename):
853
+ full_path = os.path.join(root, file)
854
+ relative_path = os.path.relpath(
855
+ full_path, search_dir)
856
+ yield Completion(
857
+ relative_path,
858
+ start_position=-len(file_basename),
859
+ )
860
+
861
+ # 只处理一层子目录,然后退出循环
862
+ break
863
+ else:
864
+ for command in self.commands:
865
+ if command.startswith(text):
866
+ yield Completion(command, start_position=-len(text))
867
+
868
+ else:
869
+ for command in self.commands:
870
+ if command.startswith(text):
871
+ yield Completion(command, start_position=-len(text))
872
+
873
+ def update_current_files(self, files):
874
+ self.current_file_names = [f for f in files]
875
+
876
+ def refresh_files(self):
877
+ self.all_file_names = self.file_system_model.get_all_file_names_in_project()
878
+ self.all_files = self.file_system_model.get_all_file_in_project()
879
+ self.all_dir_names = self.file_system_model.get_all_dir_names_in_project()
880
+ self.all_files_with_dot = self.file_system_model.get_all_file_in_project_with_dot()
881
+ self.symbol_list = self.file_system_model.get_symbol_list()