auto-coder 0.1.348__py3-none-any.whl → 0.1.349__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 (35) hide show
  1. {auto_coder-0.1.348.dist-info → auto_coder-0.1.349.dist-info}/METADATA +1 -1
  2. {auto_coder-0.1.348.dist-info → auto_coder-0.1.349.dist-info}/RECORD +35 -26
  3. autocoder/auto_coder_runner.py +14 -10
  4. autocoder/chat_auto_coder_lang.py +5 -3
  5. autocoder/common/model_speed_tester.py +392 -0
  6. autocoder/common/printer.py +7 -8
  7. autocoder/common/run_cmd.py +247 -0
  8. autocoder/common/test_run_cmd.py +110 -0
  9. autocoder/common/v2/agent/agentic_edit.py +61 -11
  10. autocoder/common/v2/agent/agentic_edit_conversation.py +9 -0
  11. autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py +21 -36
  12. autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +4 -7
  13. autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +2 -5
  14. autocoder/helper/rag_doc_creator.py +141 -0
  15. autocoder/ignorefiles/__init__.py +4 -0
  16. autocoder/ignorefiles/ignore_file_utils.py +63 -0
  17. autocoder/ignorefiles/test_ignore_file_utils.py +91 -0
  18. autocoder/models.py +49 -9
  19. autocoder/rag/cache/byzer_storage_cache.py +10 -4
  20. autocoder/rag/cache/file_monitor_cache.py +27 -24
  21. autocoder/rag/cache/local_byzer_storage_cache.py +11 -5
  22. autocoder/rag/cache/local_duckdb_storage_cache.py +203 -128
  23. autocoder/rag/cache/simple_cache.py +56 -37
  24. autocoder/rag/loaders/filter_utils.py +106 -0
  25. autocoder/rag/loaders/image_loader.py +45 -23
  26. autocoder/rag/loaders/pdf_loader.py +3 -3
  27. autocoder/rag/loaders/test_image_loader.py +209 -0
  28. autocoder/rag/qa_conversation_strategy.py +3 -5
  29. autocoder/rag/utils.py +20 -9
  30. autocoder/utils/_markitdown.py +35 -0
  31. autocoder/version.py +1 -1
  32. {auto_coder-0.1.348.dist-info → auto_coder-0.1.349.dist-info}/LICENSE +0 -0
  33. {auto_coder-0.1.348.dist-info → auto_coder-0.1.349.dist-info}/WHEEL +0 -0
  34. {auto_coder-0.1.348.dist-info → auto_coder-0.1.349.dist-info}/entry_points.txt +0 -0
  35. {auto_coder-0.1.348.dist-info → auto_coder-0.1.349.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  import subprocess
2
2
  import os
3
3
  from typing import Dict, Any, Optional
4
+ from autocoder.common.run_cmd import run_cmd_subprocess
4
5
  from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
5
6
  from autocoder.common.v2.agent.agentic_edit_types import ExecuteCommandTool, ToolResult # Import ToolResult from types
6
7
  from autocoder.common import shells
@@ -8,7 +9,8 @@ from autocoder.common.printer import Printer
8
9
  from loguru import logger
9
10
  import typing
10
11
  from autocoder.common import AutoCoderArgs
11
-
12
+ from autocoder.events.event_manager_singleton import get_event_manager
13
+ from autocoder.run_context import get_run_context
12
14
  if typing.TYPE_CHECKING:
13
15
  from autocoder.common.v2.agent.agentic_edit import AgenticEdit
14
16
 
@@ -39,45 +41,28 @@ class ExecuteCommandToolResolver(BaseToolResolver):
39
41
  pass
40
42
 
41
43
  printer.print_str_in_terminal(f"Executing command: {command} in {os.path.abspath(source_dir)}")
42
- try:
43
- # Determine shell based on OS
44
- shell = True
45
- executable = None
46
- if shells.is_windows():
47
- # Decide between cmd and powershell if needed, default is cmd
48
- pass # shell=True uses default shell
49
- else:
50
- # Use bash or zsh? Default is usually fine.
51
- pass # shell=True uses default shell
52
-
53
- # Execute the command
54
- process = subprocess.Popen(
55
- command,
56
- shell=True,
57
- stdout=subprocess.PIPE,
58
- stderr=subprocess.PIPE,
59
- cwd=source_dir,
60
- text=True,
61
- encoding=shells.get_terminal_encoding(),
62
- errors='replace' # Handle potential decoding errors
63
- )
64
-
65
- stdout, stderr = process.communicate()
66
- returncode = process.returncode
44
+ try:
45
+ # 使用封装的run_cmd方法执行命令
46
+ if get_run_context().is_web():
47
+ answer = get_event_manager(
48
+ self.args.event_file).ask_user(prompt=f"Allow to execute the `{command}`?",options=["yes","no"])
49
+ if answer == "yes":
50
+ pass
51
+ else:
52
+ return ToolResult(success=False, message=f"Command '{command}' execution denied by user.")
53
+
54
+ exit_code, output = run_cmd_subprocess(command, verbose=True, cwd=source_dir)
67
55
 
68
56
  logger.info(f"Command executed: {command}")
69
- logger.info(f"Return Code: {returncode}")
70
- if stdout:
71
- logger.info(f"stdout:\n{stdout}")
72
- if stderr:
73
- logger.info(f"stderr:\n{stderr}")
74
-
57
+ logger.info(f"Return Code: {exit_code}")
58
+ if output:
59
+ logger.info(f"Output:\n{output}")
75
60
 
76
- if returncode == 0:
77
- return ToolResult(success=True, message="Command executed successfully.", content=stdout)
61
+ if exit_code == 0:
62
+ return ToolResult(success=True, message="Command executed successfully.", content=output)
78
63
  else:
79
- error_message = f"Command failed with return code {returncode}.\nStderr:\n{stderr}\nStdout:\n{stdout}"
80
- return ToolResult(success=False, message=error_message, content={"stdout": stdout, "stderr": stderr, "returncode": returncode})
64
+ error_message = f"Command failed with return code {exit_code}.\nOutput:\n{output}"
65
+ return ToolResult(success=False, message=error_message, content={"output": output, "returncode": exit_code})
81
66
 
82
67
  except FileNotFoundError:
83
68
  return ToolResult(success=False, message=f"Error: The command '{command.split()[0]}' was not found. Please ensure it is installed and in the system's PATH.")
@@ -6,7 +6,7 @@ from loguru import logger
6
6
  import typing
7
7
  from autocoder.common import AutoCoderArgs
8
8
 
9
- from autocoder.common.v2.agent.ignore_utils import load_ignore_spec, should_ignore, DEFAULT_IGNORED_DIRS
9
+ from autocoder.ignorefiles.ignore_file_utils import should_ignore
10
10
 
11
11
  if typing.TYPE_CHECKING:
12
12
  from autocoder.common.v2.agent.agentic_edit import AgenticEdit
@@ -25,9 +25,6 @@ class ListFilesToolResolver(BaseToolResolver):
25
25
  absolute_source_dir = os.path.abspath(source_dir)
26
26
  absolute_list_path = os.path.abspath(os.path.join(source_dir, list_path_str))
27
27
 
28
- # Load ignore spec from .autocoderignore if exists
29
- ignore_spec = load_ignore_spec(absolute_source_dir)
30
-
31
28
  # Security check: Allow listing outside source_dir IF the original path is outside?
32
29
  is_outside_source = not absolute_list_path.startswith(absolute_source_dir)
33
30
  if is_outside_source:
@@ -59,10 +56,10 @@ class ListFilesToolResolver(BaseToolResolver):
59
56
  if recursive:
60
57
  for root, dirs, files in os.walk(base_dir):
61
58
  # Modify dirs in-place to skip ignored dirs early
62
- dirs[:] = [d for d in dirs if not should_ignore(os.path.join(root, d), ignore_spec, DEFAULT_IGNORED_DIRS, absolute_source_dir)]
59
+ dirs[:] = [d for d in dirs if not should_ignore(os.path.join(root, d))]
63
60
  for name in files:
64
61
  full_path = os.path.join(root, name)
65
- if should_ignore(full_path, ignore_spec, DEFAULT_IGNORED_DIRS, absolute_source_dir):
62
+ if should_ignore(full_path):
66
63
  continue
67
64
  display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
68
65
  result.add(display_path)
@@ -73,7 +70,7 @@ class ListFilesToolResolver(BaseToolResolver):
73
70
  else:
74
71
  for item in os.listdir(base_dir):
75
72
  full_path = os.path.join(base_dir, item)
76
- if should_ignore(full_path, ignore_spec, DEFAULT_IGNORED_DIRS, absolute_source_dir):
73
+ if should_ignore(full_path):
77
74
  continue
78
75
  display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
79
76
  if os.path.isdir(full_path):
@@ -9,7 +9,7 @@ from loguru import logger
9
9
  from autocoder.common import AutoCoderArgs
10
10
  import typing
11
11
 
12
- from autocoder.common.v2.agent.ignore_utils import load_ignore_spec, should_ignore, DEFAULT_IGNORED_DIRS
12
+ from autocoder.ignorefiles.ignore_file_utils import should_ignore
13
13
 
14
14
  if typing.TYPE_CHECKING:
15
15
  from autocoder.common.v2.agent.agentic_edit import AgenticEdit
@@ -29,9 +29,6 @@ class SearchFilesToolResolver(BaseToolResolver):
29
29
  absolute_source_dir = os.path.abspath(source_dir)
30
30
  absolute_search_path = os.path.abspath(os.path.join(source_dir, search_path_str))
31
31
 
32
- # Load ignore spec from .autocoderignore if exists
33
- ignore_spec = load_ignore_spec(absolute_source_dir)
34
-
35
32
  # Security check
36
33
  if not absolute_search_path.startswith(absolute_source_dir):
37
34
  return ToolResult(success=False, message=f"Error: Access denied. Attempted to search outside the project directory: {search_path_str}")
@@ -65,7 +62,7 @@ class SearchFilesToolResolver(BaseToolResolver):
65
62
 
66
63
  for filepath in glob.glob(search_glob_pattern, recursive=True):
67
64
  abs_path = os.path.abspath(filepath)
68
- if should_ignore(abs_path, ignore_spec, DEFAULT_IGNORED_DIRS, absolute_source_dir):
65
+ if should_ignore(abs_path):
69
66
  continue
70
67
 
71
68
  if os.path.isfile(filepath):
@@ -0,0 +1,141 @@
1
+
2
+ import os
3
+ from loguru import logger
4
+
5
+ def create_sample_files(base_dir: str):
6
+ """创建示例代码文件用于演示"""
7
+ os.makedirs(base_dir, exist_ok=True)
8
+
9
+ calculator_content = """
10
+ class Calculator:
11
+ def __init__(self):
12
+ self.history = []
13
+
14
+ def add(self, a: int, b: int) -> int:
15
+ '''加法函数'''
16
+ result = a + b
17
+ self.history.append(f"{a} + {b} = {result}")
18
+ return result
19
+
20
+ def subtract(self, a: int, b: int) -> int:
21
+ '''减法函数'''
22
+ result = a - b
23
+ self.history.append(f"{a} - {b} = {result}")
24
+ return result
25
+
26
+ def multiply(self, a: int, b: int) -> int:
27
+ '''乘法函数'''
28
+ result = a * b
29
+ self.history.append(f"{a} * {b} = {result}")
30
+ return result
31
+
32
+ def divide(self, a: int, b: int) -> float:
33
+ '''除法函数'''
34
+ if b == 0:
35
+ raise ValueError("Cannot divide by zero")
36
+ result = a / b
37
+ self.history.append(f"{a} / {b} = {result}")
38
+ return result
39
+ """
40
+
41
+ string_processor_content = """
42
+ class StringProcessor:
43
+ @staticmethod
44
+ def reverse(text: str) -> str:
45
+ '''反转字符串'''
46
+ return text[::-1]
47
+
48
+ @staticmethod
49
+ def capitalize(text: str) -> str:
50
+ '''首字母大写'''
51
+ return text.capitalize()
52
+
53
+ @staticmethod
54
+ def count_words(text: str) -> int:
55
+ '''计算单词数量'''
56
+ return len(text.split())
57
+
58
+ @staticmethod
59
+ def format_name(first_name: str, last_name: str) -> str:
60
+ '''格式化姓名'''
61
+ return f"{last_name}, {first_name}"
62
+ """
63
+
64
+ data_processor_content = """
65
+ import statistics
66
+ from typing import List, Dict, Any
67
+
68
+ class DataProcessor:
69
+ @staticmethod
70
+ def calculate_average(numbers: List[float]) -> float:
71
+ '''计算平均值'''
72
+ return sum(numbers) / len(numbers)
73
+
74
+ @staticmethod
75
+ def find_median(numbers: List[float]) -> float:
76
+ '''计算中位数'''
77
+ sorted_numbers = sorted(numbers)
78
+ n = len(sorted_numbers)
79
+ if n % 2 == 0:
80
+ return (sorted_numbers[n//2-1] + sorted_numbers[n//2]) / 2
81
+ else:
82
+ return sorted_numbers[n//2]
83
+
84
+ @staticmethod
85
+ def find_mode(numbers: List[float]) -> List[float]:
86
+ '''查找众数'''
87
+ try:
88
+ return statistics.mode(numbers)
89
+ except statistics.StatisticsError:
90
+ # 没有唯一众数的情况
91
+ count_dict = {}
92
+ for num in numbers:
93
+ if num in count_dict:
94
+ count_dict[num] += 1
95
+ else:
96
+ count_dict[num] = 1
97
+ max_count = max(count_dict.values())
98
+ return [num for num, count in count_dict.items() if count == max_count]
99
+ """
100
+
101
+ with open(os.path.join(base_dir, "calculator.py"), "w", encoding="utf-8") as f:
102
+ f.write(calculator_content)
103
+
104
+ with open(os.path.join(base_dir, "string_processor.py"), "w", encoding="utf-8") as f:
105
+ f.write(string_processor_content)
106
+
107
+ with open(os.path.join(base_dir, "data_processor.py"), "w", encoding="utf-8") as f:
108
+ f.write(data_processor_content)
109
+
110
+ logger.info(f"示例代码文件已创建在: {base_dir}")
111
+
112
+
113
+ def update_sample_file(base_dir: str, filename: str, content: str):
114
+ """更新指定示例文件内容"""
115
+ file_path = os.path.join(base_dir, filename)
116
+ if not os.path.exists(file_path):
117
+ logger.warning(f"文件 {file_path} 不存在,无法更新")
118
+ return
119
+ with open(file_path, "w", encoding="utf-8") as f:
120
+ f.write(content)
121
+ logger.info(f"已更新文件: {file_path}")
122
+
123
+
124
+ def add_sample_file(base_dir: str, filename: str, content: str):
125
+ """新增示例文件,若存在则覆盖"""
126
+ file_path = os.path.join(base_dir, filename)
127
+ with open(file_path, "w", encoding="utf-8") as f:
128
+ f.write(content)
129
+ logger.info(f"已新增文件: {file_path}")
130
+
131
+
132
+ def delete_sample_file(base_dir: str, filename: str):
133
+ """删除指定示例文件"""
134
+ file_path = os.path.join(base_dir, filename)
135
+ try:
136
+ os.remove(file_path)
137
+ logger.info(f"已删除文件: {file_path}")
138
+ except FileNotFoundError:
139
+ logger.warning(f"文件 {file_path} 不存在,无法删除")
140
+ except Exception as e:
141
+ logger.error(f"删除文件 {file_path} 失败: {e}")
@@ -0,0 +1,4 @@
1
+
2
+ from .ignore_file_utils import should_ignore
3
+
4
+ __all__ = ["should_ignore"]
@@ -0,0 +1,63 @@
1
+
2
+ import os
3
+ from pathlib import Path
4
+ from threading import Lock
5
+ import pathspec
6
+
7
+ DEFAULT_EXCLUDES = [
8
+ '.git', '.auto-coder', 'node_modules', '.mvn', '.idea',
9
+ '__pycache__', '.venv', 'venv', 'dist', 'build', '.gradle',".next"
10
+ ]
11
+
12
+
13
+ class IgnoreFileManager:
14
+ _instance = None
15
+ _lock = Lock()
16
+
17
+ def __new__(cls):
18
+ if not cls._instance:
19
+ with cls._lock:
20
+ if not cls._instance:
21
+ cls._instance = super(IgnoreFileManager, cls).__new__(cls)
22
+ cls._instance._initialized = False
23
+ return cls._instance
24
+
25
+ def __init__(self):
26
+ if self._initialized:
27
+ return
28
+ self._initialized = True
29
+ self._spec = None
30
+ self._load_ignore_spec()
31
+
32
+ def _load_ignore_spec(self):
33
+ ignore_patterns = []
34
+ project_root = Path(os.getcwd())
35
+
36
+ ignore_file_paths = [
37
+ project_root / '.autocoderignore',
38
+ project_root / '.auto-coder' / '.autocoderignore'
39
+ ]
40
+
41
+ for ignore_file in ignore_file_paths:
42
+ if ignore_file.is_file():
43
+ with open(ignore_file, 'r', encoding='utf-8') as f:
44
+ ignore_patterns = f.read().splitlines()
45
+ break
46
+
47
+ # 添加默认排除目录
48
+ ignore_patterns.extend(DEFAULT_EXCLUDES)
49
+
50
+ self._spec = pathspec.PathSpec.from_lines('gitwildmatch', ignore_patterns)
51
+
52
+ def should_ignore(self, path: str) -> bool:
53
+ rel_path = os.path.relpath(path, os.getcwd())
54
+ # 标准化分隔符
55
+ rel_path = rel_path.replace(os.sep, '/')
56
+ return self._spec.match_file(rel_path)
57
+
58
+
59
+ # 对外提供单例
60
+ _ignore_manager = IgnoreFileManager()
61
+
62
+ def should_ignore(path: str) -> bool:
63
+ return _ignore_manager.should_ignore(path)
@@ -0,0 +1,91 @@
1
+
2
+ import os
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ from src.autocoder.ignorefiles import ignore_file_utils
9
+
10
+ @pytest.fixture(autouse=True)
11
+ def cleanup_ignore_manager(monkeypatch):
12
+ """
13
+ 在每个测试前后清理 IgnoreFileManager 的单例状态,保证测试隔离
14
+ """
15
+ # 备份原始实例
16
+ original_instance = ignore_file_utils._ignore_manager
17
+ # 强制重新加载忽略规则
18
+ def reset_ignore_manager():
19
+ ignore_file_utils.IgnoreFileManager._instance = None
20
+ return ignore_file_utils.IgnoreFileManager()
21
+
22
+ monkeypatch.setattr(ignore_file_utils, "_ignore_manager", reset_ignore_manager())
23
+ yield
24
+ # 恢复原始实例
25
+ ignore_file_utils._ignore_manager = original_instance
26
+
27
+
28
+ def test_default_excludes(tmp_path, monkeypatch):
29
+ # 切换当前工作目录
30
+ monkeypatch.chdir(tmp_path)
31
+
32
+ # 不创建任何 .autocoderignore 文件,使用默认排除规则
33
+ # 创建默认排除目录
34
+ for dirname in ignore_file_utils.DEFAULT_EXCLUDES:
35
+ (tmp_path / dirname).mkdir(parents=True, exist_ok=True)
36
+ # 应该被忽略
37
+ assert ignore_file_utils.should_ignore(str(tmp_path / dirname)) is True
38
+
39
+ # 创建不会被忽略的文件
40
+ normal_file = tmp_path / "myfile.txt"
41
+ normal_file.write_text("hello")
42
+ assert ignore_file_utils.should_ignore(str(normal_file)) is False
43
+
44
+
45
+ def test_custom_ignore_file(tmp_path, monkeypatch):
46
+ monkeypatch.chdir(tmp_path)
47
+
48
+ # 创建自定义忽略文件
49
+ ignore_file = tmp_path / ".autocoderignore"
50
+ ignore_file.write_text("data/**\nsecret.txt")
51
+
52
+ # 重新初始化忽略管理器以加载新规则
53
+ ignore_file_utils.IgnoreFileManager._instance = None
54
+ ignore_file_utils._ignore_manager = ignore_file_utils.IgnoreFileManager()
55
+
56
+ # 符合忽略规则的路径
57
+ ignored_dir = tmp_path / "data" / "subdir"
58
+ ignored_dir.mkdir(parents=True)
59
+ ignored_file = tmp_path / "secret.txt"
60
+ ignored_file.write_text("secret")
61
+
62
+ assert ignore_file_utils.should_ignore(str(ignored_dir)) is True
63
+ assert ignore_file_utils.should_ignore(str(ignored_file)) is True
64
+
65
+ # 不应被忽略的文件
66
+ normal_file = tmp_path / "keepme.txt"
67
+ normal_file.write_text("keep me")
68
+ assert ignore_file_utils.should_ignore(str(normal_file)) is False
69
+
70
+
71
+ def test_nested_ignore_file(tmp_path, monkeypatch):
72
+ monkeypatch.chdir(tmp_path)
73
+
74
+ # 没有根目录的.ignore,创建.auto-coder/.autocoderignore
75
+ nested_dir = tmp_path / ".auto-coder"
76
+ nested_dir.mkdir()
77
+
78
+ ignore_file = nested_dir / ".autocoderignore"
79
+ ignore_file.write_text("logs/**")
80
+
81
+ # 重新初始化忽略管理器以加载新规则
82
+ ignore_file_utils.IgnoreFileManager._instance = None
83
+ ignore_file_utils._ignore_manager = ignore_file_utils.IgnoreFileManager()
84
+
85
+ ignored_dir = tmp_path / "logs" / "2024"
86
+ ignored_dir.mkdir(parents=True)
87
+ assert ignore_file_utils.should_ignore(str(ignored_dir)) is True
88
+
89
+ normal_file = tmp_path / "main.py"
90
+ normal_file.write_text("# main")
91
+ assert ignore_file_utils.should_ignore(str(normal_file)) is False
autocoder/models.py CHANGED
@@ -8,7 +8,7 @@ MODELS_JSON = os.path.expanduser("~/.auto-coder/keys/models.json")
8
8
  # Default built-in models
9
9
  default_models_list = [
10
10
  {
11
- "name": "deepseek_r1_chat",
11
+ "name": "deepseek/r1",
12
12
  "description": "DeepSeek Reasoner is for design/review",
13
13
  "model_name": "deepseek-reasoner",
14
14
  "model_type": "saas/openai",
@@ -21,7 +21,7 @@ default_models_list = [
21
21
  "max_output_tokens": 8096
22
22
  },
23
23
  {
24
- "name": "deepseek_chat",
24
+ "name": "deepseek/v3",
25
25
  "description": "DeepSeek Chat is for coding",
26
26
  "model_name": "deepseek-chat",
27
27
  "model_type": "saas/openai",
@@ -34,16 +34,56 @@ default_models_list = [
34
34
  "max_output_tokens": 8096
35
35
  },
36
36
  {
37
- "name":"o1",
38
- "description": "o1 is for design/review",
39
- "model_name": "o1-2024-12-17",
37
+ "name": "ark/deepseek-v3-250324",
38
+ "description": "DeepSeek Chat is for coding",
39
+ "model_name": "deepseek-v3-250324",
40
40
  "model_type": "saas/openai",
41
- "base_url": "https://api.openai.com/v1",
41
+ "base_url": "https://ark.cn-beijing.volces.com/api/v3",
42
42
  "api_key_path": "",
43
- "is_reasoning": True,
43
+ "is_reasoning": False,
44
+ "input_price": 2.0,
45
+ "output_price": 8.0,
46
+ "average_speed": 0.0,
47
+ "max_output_tokens": 8096
48
+ },
49
+ {
50
+ "name": "ark/deepseek-v3-250324",
51
+ "description": "DeepSeek Chat is for coding",
52
+ "model_name": "deepseek-v3-250324",
53
+ "model_type": "saas/openai",
54
+ "base_url": "https://ark.cn-beijing.volces.com/api/v3",
55
+ "api_key_path": "",
56
+ "is_reasoning": False,
57
+ "input_price": 2.0,
58
+ "output_price": 8.0,
59
+ "average_speed": 0.0,
60
+ "max_output_tokens": 8096
61
+ },
62
+ {
63
+ "name": "openrouter/quasar-alpha",
64
+ "description": "",
65
+ "model_name": "openrouter/quasar-alpha",
66
+ "model_type": "saas/openai",
67
+ "base_url": "https://openrouter.ai/api/v1",
68
+ "api_key_path": "",
69
+ "is_reasoning": False,
44
70
  "input_price": 0.0,
45
71
  "output_price": 0.0,
46
- "average_speed": 0.0
72
+ "average_speed": 0.0,
73
+ "max_output_tokens": 8096*2
74
+ },
75
+ {
76
+ "name": "openrouter/google/gemini-2.5-pro-preview-03-25",
77
+ "description": "",
78
+ "model_name": "google/gemini-2.5-pro-preview-03-25",
79
+ "model_type": "saas/openai",
80
+ "base_url": "https://openrouter.ai/api/v1",
81
+ "api_key_path": "",
82
+ "is_reasoning": False,
83
+ "input_price": 0.0,
84
+ "output_price": 0.0,
85
+ "average_speed": 0.0,
86
+ "max_output_tokens": 8096*2
47
87
  }
48
88
  ]
49
89
 
@@ -263,7 +303,7 @@ def update_model_with_api_key(name: str, api_key: str) -> Dict:
263
303
  if not found_model:
264
304
  return None
265
305
 
266
- api_key_path = name
306
+ api_key_path = name.replace("/", "_") # 替换 / 为 _,保证文件名合法
267
307
  if api_key_path:
268
308
  found_model["api_key_path"] = api_key_path
269
309
 
@@ -1,3 +1,4 @@
1
+ from token import OP
1
2
  from autocoder.rag.cache.base_cache import (
2
3
  BaseCacheManager,
3
4
  DeleteEvent,
@@ -31,6 +32,8 @@ from pydantic import BaseModel
31
32
  from autocoder.rag.cache.cache_result_merge import CacheResultMerger, MergeStrategy
32
33
  from .failed_files_utils import save_failed_files, load_failed_files
33
34
  import time
35
+ from byzerllm import ByzerLLM, SimpleByzerLLM
36
+ from autocoder.utils.llms import get_llm_names
34
37
 
35
38
  if platform.system() != "Windows":
36
39
  import fcntl
@@ -66,8 +69,8 @@ class ByzerStorageCache(BaseCacheManager):
66
69
  ignore_spec,
67
70
  required_exts,
68
71
  extra_params: Optional[AutoCoderArgs] = None,
69
- args=None,
70
- llm=None,
72
+ args:Optional[AutoCoderArgs]=None,
73
+ llm:Optional[Union[ByzerLLM,SimpleByzerLLM,str]]=None,
71
74
  ):
72
75
  """
73
76
  初始化基于云端 Byzer Storage 的 RAG 缓存管理器。
@@ -78,6 +81,7 @@ class ByzerStorageCache(BaseCacheManager):
78
81
  self.extra_params = extra_params
79
82
  self.args = args
80
83
  self.llm = llm
84
+ self.product_mode = self.args.product_mode
81
85
  self.rag_build_name = extra_params.rag_build_name
82
86
  self.storage = ByzerStorage("byzerai_store", "rag", self.rag_build_name)
83
87
  self.queue = []
@@ -218,6 +222,8 @@ class ByzerStorageCache(BaseCacheManager):
218
222
  from autocoder.rag.token_counter import initialize_tokenizer
219
223
 
220
224
  logger.info("[BUILD CACHE] Starting parallel file processing...")
225
+ llm_name = get_llm_names(self.llm)[0] if self.llm else None
226
+ product_mode = self.product_mode
221
227
  start_time = time.time()
222
228
  with Pool(
223
229
  processes=os.cpu_count(),
@@ -227,7 +233,7 @@ class ByzerStorageCache(BaseCacheManager):
227
233
  target_files_to_process = []
228
234
  for file_info in files_to_process:
229
235
  target_files_to_process.append(self.fileinfo_to_tuple(file_info))
230
- results = pool.map(process_file_in_multi_process, target_files_to_process)
236
+ results = pool.map(process_file_in_multi_process, target_files_to_process, llm=llm_name, product_mode=product_mode)
231
237
  processing_time = time.time() - start_time
232
238
  logger.info(f"[BUILD CACHE] File processing completed, time elapsed: {processing_time:.2f}s")
233
239
 
@@ -417,7 +423,7 @@ class ByzerStorageCache(BaseCacheManager):
417
423
  for file_info in file_list.file_infos:
418
424
  logger.info(f"[QUEUE PROCESSING] Processing file update: {file_info.file_path}")
419
425
  try:
420
- content = process_file_local(self.fileinfo_to_tuple(file_info))
426
+ content = process_file_local(self.fileinfo_to_tuple(file_info), llm=self.llm, product_mode=self.product_mode)
421
427
  if content:
422
428
  self.cache[file_info.file_path] = CacheItem(
423
429
  file_path=file_info.file_path,