auto-coder 0.1.397__py3-none-any.whl → 0.1.399__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of auto-coder might be problematic. Click here for more details.
- auto_coder-0.1.399.dist-info/METADATA +396 -0
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.399.dist-info}/RECORD +81 -28
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.399.dist-info}/WHEEL +1 -1
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.399.dist-info}/entry_points.txt +2 -0
- autocoder/agent/base_agentic/base_agent.py +2 -2
- autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +1 -1
- autocoder/agent/entry_command_agent/__init__.py +29 -0
- autocoder/agent/entry_command_agent/auto_tool.py +61 -0
- autocoder/agent/entry_command_agent/chat.py +475 -0
- autocoder/agent/entry_command_agent/designer.py +53 -0
- autocoder/agent/entry_command_agent/generate_command.py +50 -0
- autocoder/agent/entry_command_agent/project_reader.py +58 -0
- autocoder/agent/entry_command_agent/voice2text.py +71 -0
- autocoder/auto_coder.py +23 -548
- autocoder/auto_coder_rag.py +1 -0
- autocoder/auto_coder_runner.py +510 -8
- autocoder/chat/rules_command.py +1 -1
- autocoder/chat_auto_coder.py +8 -0
- autocoder/common/ac_style_command_parser/__init__.py +15 -0
- autocoder/common/ac_style_command_parser/example.py +7 -0
- autocoder/{command_parser.py → common/ac_style_command_parser/parser.py} +1 -33
- autocoder/common/ac_style_command_parser/test_parser.py +516 -0
- autocoder/common/command_completer_v2.py +1 -1
- autocoder/common/command_file_manager/examples.py +22 -8
- autocoder/common/command_file_manager/manager.py +37 -6
- autocoder/common/conversations/__init__.py +84 -39
- autocoder/common/conversations/backup/__init__.py +14 -0
- autocoder/common/conversations/backup/backup_manager.py +564 -0
- autocoder/common/conversations/backup/restore_manager.py +546 -0
- autocoder/common/conversations/cache/__init__.py +16 -0
- autocoder/common/conversations/cache/base_cache.py +89 -0
- autocoder/common/conversations/cache/cache_manager.py +368 -0
- autocoder/common/conversations/cache/memory_cache.py +224 -0
- autocoder/common/conversations/config.py +195 -0
- autocoder/common/conversations/exceptions.py +72 -0
- autocoder/common/conversations/file_locker.py +145 -0
- autocoder/common/conversations/get_conversation_manager.py +143 -0
- autocoder/common/conversations/manager.py +1028 -0
- autocoder/common/conversations/models.py +154 -0
- autocoder/common/conversations/search/__init__.py +15 -0
- autocoder/common/conversations/search/filter_manager.py +431 -0
- autocoder/common/conversations/search/text_searcher.py +366 -0
- autocoder/common/conversations/storage/__init__.py +16 -0
- autocoder/common/conversations/storage/base_storage.py +82 -0
- autocoder/common/conversations/storage/file_storage.py +267 -0
- autocoder/common/conversations/storage/index_manager.py +406 -0
- autocoder/common/v2/agent/agentic_edit.py +131 -18
- autocoder/common/v2/agent/agentic_edit_types.py +10 -0
- autocoder/common/v2/code_auto_generate_editblock.py +10 -2
- autocoder/dispacher/__init__.py +10 -0
- autocoder/rags.py +73 -50
- autocoder/run_context.py +1 -0
- autocoder/sdk/__init__.py +188 -0
- autocoder/sdk/cli/__init__.py +15 -0
- autocoder/sdk/cli/__main__.py +26 -0
- autocoder/sdk/cli/completion_wrapper.py +38 -0
- autocoder/sdk/cli/formatters.py +211 -0
- autocoder/sdk/cli/handlers.py +174 -0
- autocoder/sdk/cli/install_completion.py +301 -0
- autocoder/sdk/cli/main.py +284 -0
- autocoder/sdk/cli/options.py +72 -0
- autocoder/sdk/constants.py +102 -0
- autocoder/sdk/core/__init__.py +20 -0
- autocoder/sdk/core/auto_coder_core.py +867 -0
- autocoder/sdk/core/bridge.py +497 -0
- autocoder/sdk/example.py +0 -0
- autocoder/sdk/exceptions.py +72 -0
- autocoder/sdk/models/__init__.py +19 -0
- autocoder/sdk/models/messages.py +209 -0
- autocoder/sdk/models/options.py +194 -0
- autocoder/sdk/models/responses.py +311 -0
- autocoder/sdk/session/__init__.py +32 -0
- autocoder/sdk/session/session.py +106 -0
- autocoder/sdk/session/session_manager.py +56 -0
- autocoder/sdk/utils/__init__.py +24 -0
- autocoder/sdk/utils/formatters.py +216 -0
- autocoder/sdk/utils/io_utils.py +302 -0
- autocoder/sdk/utils/validators.py +287 -0
- autocoder/version.py +2 -1
- auto_coder-0.1.397.dist-info/METADATA +0 -111
- autocoder/common/conversations/compatibility.py +0 -303
- autocoder/common/conversations/conversation_manager.py +0 -502
- autocoder/common/conversations/example.py +0 -152
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.399.dist-info/licenses}/LICENSE +0 -0
- {auto_coder-0.1.397.dist-info → auto_coder-0.1.399.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PersistConversationManager 配置类定义
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Optional, Dict, Any
|
|
9
|
+
from copy import deepcopy
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ConversationManagerConfig:
|
|
14
|
+
"""对话管理器配置类"""
|
|
15
|
+
|
|
16
|
+
storage_path: str = "./.auto-coder/conversations"
|
|
17
|
+
max_cache_size: int = 100
|
|
18
|
+
cache_ttl: float = 300.0
|
|
19
|
+
lock_timeout: float = 10.0
|
|
20
|
+
backup_enabled: bool = True
|
|
21
|
+
backup_interval: float = 3600.0
|
|
22
|
+
max_backups: int = 10
|
|
23
|
+
enable_compression: bool = False
|
|
24
|
+
log_level: str = "INFO"
|
|
25
|
+
|
|
26
|
+
def __post_init__(self):
|
|
27
|
+
"""配置验证"""
|
|
28
|
+
self._validate()
|
|
29
|
+
|
|
30
|
+
def _validate(self):
|
|
31
|
+
"""验证配置数据"""
|
|
32
|
+
# 验证存储路径
|
|
33
|
+
if not self.storage_path or not isinstance(self.storage_path, str):
|
|
34
|
+
raise ValueError("存储路径不能为空")
|
|
35
|
+
|
|
36
|
+
# 验证缓存大小
|
|
37
|
+
if not isinstance(self.max_cache_size, int) or self.max_cache_size <= 0:
|
|
38
|
+
raise ValueError("缓存大小必须是正整数")
|
|
39
|
+
|
|
40
|
+
# 验证缓存TTL
|
|
41
|
+
if not isinstance(self.cache_ttl, (int, float)) or self.cache_ttl <= 0:
|
|
42
|
+
raise ValueError("缓存TTL必须是正数")
|
|
43
|
+
|
|
44
|
+
# 验证锁超时
|
|
45
|
+
if not isinstance(self.lock_timeout, (int, float)) or self.lock_timeout <= 0:
|
|
46
|
+
raise ValueError("锁超时时间必须是正数")
|
|
47
|
+
|
|
48
|
+
# 验证备份间隔
|
|
49
|
+
if not isinstance(self.backup_interval, (int, float)) or self.backup_interval <= 0:
|
|
50
|
+
raise ValueError("备份间隔必须是正数")
|
|
51
|
+
|
|
52
|
+
# 验证最大备份数
|
|
53
|
+
if not isinstance(self.max_backups, int) or self.max_backups <= 0:
|
|
54
|
+
raise ValueError("最大备份数必须是正整数")
|
|
55
|
+
|
|
56
|
+
# 验证日志级别
|
|
57
|
+
valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
58
|
+
if self.log_level not in valid_levels:
|
|
59
|
+
raise ValueError(f"无效的日志级别: {self.log_level},有效级别: {valid_levels}")
|
|
60
|
+
|
|
61
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
62
|
+
"""转换为字典"""
|
|
63
|
+
return {
|
|
64
|
+
"storage_path": self.storage_path,
|
|
65
|
+
"max_cache_size": self.max_cache_size,
|
|
66
|
+
"cache_ttl": self.cache_ttl,
|
|
67
|
+
"lock_timeout": self.lock_timeout,
|
|
68
|
+
"backup_enabled": self.backup_enabled,
|
|
69
|
+
"backup_interval": self.backup_interval,
|
|
70
|
+
"max_backups": self.max_backups,
|
|
71
|
+
"enable_compression": self.enable_compression,
|
|
72
|
+
"log_level": self.log_level
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ConversationManagerConfig":
|
|
77
|
+
"""从字典创建配置"""
|
|
78
|
+
# 创建默认配置
|
|
79
|
+
config = cls()
|
|
80
|
+
|
|
81
|
+
# 更新配置字段
|
|
82
|
+
for key, value in data.items():
|
|
83
|
+
if hasattr(config, key):
|
|
84
|
+
setattr(config, key, value)
|
|
85
|
+
|
|
86
|
+
# 重新验证
|
|
87
|
+
config._validate()
|
|
88
|
+
|
|
89
|
+
return config
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_env(cls, prefix: str = "CONVERSATION_") -> "ConversationManagerConfig":
|
|
93
|
+
"""从环境变量创建配置"""
|
|
94
|
+
config = cls()
|
|
95
|
+
|
|
96
|
+
# 环境变量映射
|
|
97
|
+
env_mapping = {
|
|
98
|
+
f"{prefix}STORAGE_PATH": "storage_path",
|
|
99
|
+
f"{prefix}MAX_CACHE_SIZE": "max_cache_size",
|
|
100
|
+
f"{prefix}CACHE_TTL": "cache_ttl",
|
|
101
|
+
f"{prefix}LOCK_TIMEOUT": "lock_timeout",
|
|
102
|
+
f"{prefix}BACKUP_ENABLED": "backup_enabled",
|
|
103
|
+
f"{prefix}BACKUP_INTERVAL": "backup_interval",
|
|
104
|
+
f"{prefix}MAX_BACKUPS": "max_backups",
|
|
105
|
+
f"{prefix}ENABLE_COMPRESSION": "enable_compression",
|
|
106
|
+
f"{prefix}LOG_LEVEL": "log_level"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for env_key, attr_name in env_mapping.items():
|
|
110
|
+
env_value = os.environ.get(env_key)
|
|
111
|
+
if env_value is not None:
|
|
112
|
+
# 类型转换
|
|
113
|
+
try:
|
|
114
|
+
if attr_name in ["max_cache_size", "max_backups"]:
|
|
115
|
+
value = int(env_value)
|
|
116
|
+
elif attr_name in ["cache_ttl", "lock_timeout", "backup_interval"]:
|
|
117
|
+
value = float(env_value)
|
|
118
|
+
elif attr_name in ["backup_enabled", "enable_compression"]:
|
|
119
|
+
value = env_value.lower() in ["true", "1", "yes", "on"]
|
|
120
|
+
else:
|
|
121
|
+
value = env_value
|
|
122
|
+
|
|
123
|
+
setattr(config, attr_name, value)
|
|
124
|
+
except (ValueError, TypeError) as e:
|
|
125
|
+
raise ValueError(f"环境变量 {env_key} 的值 '{env_value}' 无效: {e}")
|
|
126
|
+
|
|
127
|
+
# 重新验证
|
|
128
|
+
config._validate()
|
|
129
|
+
|
|
130
|
+
return config
|
|
131
|
+
|
|
132
|
+
def save_to_file(self, file_path: str):
|
|
133
|
+
"""保存配置到文件"""
|
|
134
|
+
# 确保目录存在
|
|
135
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
136
|
+
|
|
137
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
138
|
+
json.dump(self.to_dict(), f, indent=2, ensure_ascii=False)
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def load_from_file(cls, file_path: str) -> "ConversationManagerConfig":
|
|
142
|
+
"""从文件加载配置"""
|
|
143
|
+
if not os.path.exists(file_path):
|
|
144
|
+
raise FileNotFoundError(f"配置文件不存在: {file_path}")
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
148
|
+
data = json.load(f)
|
|
149
|
+
|
|
150
|
+
return cls.from_dict(data)
|
|
151
|
+
except json.JSONDecodeError as e:
|
|
152
|
+
raise ValueError(f"配置文件格式错误: {e}")
|
|
153
|
+
|
|
154
|
+
def copy(self) -> "ConversationManagerConfig":
|
|
155
|
+
"""创建配置的深拷贝"""
|
|
156
|
+
return ConversationManagerConfig.from_dict(self.to_dict())
|
|
157
|
+
|
|
158
|
+
def update(self, **kwargs):
|
|
159
|
+
"""更新配置字段"""
|
|
160
|
+
# 先备份当前配置
|
|
161
|
+
backup_values = {}
|
|
162
|
+
for key in kwargs.keys():
|
|
163
|
+
if hasattr(self, key):
|
|
164
|
+
backup_values[key] = getattr(self, key)
|
|
165
|
+
else:
|
|
166
|
+
raise AttributeError(f"配置类没有属性: {key}")
|
|
167
|
+
|
|
168
|
+
# 尝试更新
|
|
169
|
+
try:
|
|
170
|
+
for key, value in kwargs.items():
|
|
171
|
+
setattr(self, key, value)
|
|
172
|
+
# 重新验证
|
|
173
|
+
self._validate()
|
|
174
|
+
except Exception:
|
|
175
|
+
# 如果验证失败,恢复原值
|
|
176
|
+
for key, value in backup_values.items():
|
|
177
|
+
setattr(self, key, value)
|
|
178
|
+
raise
|
|
179
|
+
|
|
180
|
+
def __repr__(self) -> str:
|
|
181
|
+
"""字符串表示"""
|
|
182
|
+
return (f"ConversationManagerConfig("
|
|
183
|
+
f"storage_path='{self.storage_path}', "
|
|
184
|
+
f"max_cache_size={self.max_cache_size}, "
|
|
185
|
+
f"cache_ttl={self.cache_ttl}, "
|
|
186
|
+
f"lock_timeout={self.lock_timeout}, "
|
|
187
|
+
f"backup_enabled={self.backup_enabled}, "
|
|
188
|
+
f"log_level='{self.log_level}')")
|
|
189
|
+
|
|
190
|
+
def __eq__(self, other) -> bool:
|
|
191
|
+
"""相等性比较"""
|
|
192
|
+
if not isinstance(other, ConversationManagerConfig):
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
return self.to_dict() == other.to_dict()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PersistConversationManager 异常类定义
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConversationManagerError(Exception):
|
|
7
|
+
"""对话管理器基础异常类"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message="对话管理器发生错误", error_code="GENERAL_ERROR"):
|
|
10
|
+
super().__init__(message)
|
|
11
|
+
self.error_code = error_code
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConversationNotFoundError(ConversationManagerError):
|
|
15
|
+
"""对话不存在异常"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, conversation_id):
|
|
18
|
+
# 检查是否是类似ID的字符串(不包含中文等)
|
|
19
|
+
if (isinstance(conversation_id, str) and len(conversation_id) > 0 and
|
|
20
|
+
not any(ord(c) > 127 or c.isspace() for c in conversation_id)):
|
|
21
|
+
message = f"对话未找到: {conversation_id}"
|
|
22
|
+
else:
|
|
23
|
+
message = conversation_id # 自定义消息
|
|
24
|
+
super().__init__(message, error_code="CONVERSATION_NOT_FOUND")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MessageNotFoundError(ConversationManagerError):
|
|
28
|
+
"""消息不存在异常"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, message_id):
|
|
31
|
+
# 检查是否是类似ID的字符串(不包含中文等)
|
|
32
|
+
if (isinstance(message_id, str) and len(message_id) > 0 and
|
|
33
|
+
not any(ord(c) > 127 or c.isspace() for c in message_id)):
|
|
34
|
+
message = f"消息未找到: {message_id}"
|
|
35
|
+
else:
|
|
36
|
+
message = message_id # 自定义消息
|
|
37
|
+
super().__init__(message, error_code="MESSAGE_NOT_FOUND")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ConcurrencyError(ConversationManagerError):
|
|
41
|
+
"""并发访问异常"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, message="并发访问冲突"):
|
|
44
|
+
super().__init__(message, error_code="CONCURRENCY_ERROR")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DataIntegrityError(ConversationManagerError):
|
|
48
|
+
"""数据完整性异常"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, message="数据完整性检查失败"):
|
|
51
|
+
super().__init__(message, error_code="DATA_INTEGRITY_ERROR")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class LockTimeoutError(ConversationManagerError):
|
|
55
|
+
"""锁超时异常"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, message="锁获取超时"):
|
|
58
|
+
super().__init__(message, error_code="LOCK_TIMEOUT_ERROR")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BackupError(ConversationManagerError):
|
|
62
|
+
"""备份操作异常"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, message="备份操作失败"):
|
|
65
|
+
super().__init__(message, error_code="BACKUP_ERROR")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class RestoreError(ConversationManagerError):
|
|
69
|
+
"""恢复操作异常"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, message="恢复操作失败"):
|
|
72
|
+
super().__init__(message, error_code="RESTORE_ERROR")
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PersistConversationManager 跨平台文件锁实现
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
import contextlib
|
|
9
|
+
from typing import Generator
|
|
10
|
+
from .exceptions import LockTimeoutError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 跨平台文件锁实现
|
|
14
|
+
if sys.platform == "win32":
|
|
15
|
+
import msvcrt
|
|
16
|
+
|
|
17
|
+
class FileLocker:
|
|
18
|
+
"""Windows 平台文件锁实现"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, lock_file: str, timeout: float = 10.0):
|
|
21
|
+
self.lock_file = lock_file
|
|
22
|
+
self.timeout = timeout
|
|
23
|
+
self.lock_fd = None
|
|
24
|
+
|
|
25
|
+
@contextlib.contextmanager
|
|
26
|
+
def acquire_read_lock(self) -> Generator[None, None, None]:
|
|
27
|
+
"""获取读锁(共享锁)- Windows 实现"""
|
|
28
|
+
self._acquire_lock(shared=True)
|
|
29
|
+
try:
|
|
30
|
+
yield
|
|
31
|
+
finally:
|
|
32
|
+
self._release_lock()
|
|
33
|
+
|
|
34
|
+
@contextlib.contextmanager
|
|
35
|
+
def acquire_write_lock(self) -> Generator[None, None, None]:
|
|
36
|
+
"""获取写锁(排他锁)- Windows 实现"""
|
|
37
|
+
self._acquire_lock(shared=False)
|
|
38
|
+
try:
|
|
39
|
+
yield
|
|
40
|
+
finally:
|
|
41
|
+
self._release_lock()
|
|
42
|
+
|
|
43
|
+
def _acquire_lock(self, shared: bool = False):
|
|
44
|
+
"""Windows 文件锁实现"""
|
|
45
|
+
start_time = time.time()
|
|
46
|
+
while True:
|
|
47
|
+
try:
|
|
48
|
+
# 确保锁文件目录存在
|
|
49
|
+
os.makedirs(os.path.dirname(self.lock_file), exist_ok=True)
|
|
50
|
+
|
|
51
|
+
# 打开文件用于锁定
|
|
52
|
+
self.lock_fd = open(self.lock_file, 'w+')
|
|
53
|
+
|
|
54
|
+
# Windows 下使用 msvcrt.locking
|
|
55
|
+
# 注意:Windows 不直接支持共享锁,这里简化处理
|
|
56
|
+
msvcrt.locking(self.lock_fd.fileno(), msvcrt.LK_NBLCK, 1)
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
except (IOError, OSError):
|
|
60
|
+
if self.lock_fd:
|
|
61
|
+
self.lock_fd.close()
|
|
62
|
+
self.lock_fd = None
|
|
63
|
+
|
|
64
|
+
if time.time() - start_time > self.timeout:
|
|
65
|
+
raise LockTimeoutError(f"Failed to acquire lock on {self.lock_file} within {self.timeout}s")
|
|
66
|
+
|
|
67
|
+
time.sleep(0.1)
|
|
68
|
+
|
|
69
|
+
def _release_lock(self):
|
|
70
|
+
"""释放锁"""
|
|
71
|
+
if self.lock_fd:
|
|
72
|
+
try:
|
|
73
|
+
msvcrt.locking(self.lock_fd.fileno(), msvcrt.LK_UNLCK, 1)
|
|
74
|
+
self.lock_fd.close()
|
|
75
|
+
except:
|
|
76
|
+
# 忽略释放时的错误
|
|
77
|
+
pass
|
|
78
|
+
finally:
|
|
79
|
+
self.lock_fd = None
|
|
80
|
+
|
|
81
|
+
else:
|
|
82
|
+
# Unix/Linux/Mac 系统使用 fcntl
|
|
83
|
+
import fcntl
|
|
84
|
+
|
|
85
|
+
class FileLocker:
|
|
86
|
+
"""Unix/Linux/Mac 平台文件锁实现"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, lock_file: str, timeout: float = 10.0):
|
|
89
|
+
self.lock_file = lock_file
|
|
90
|
+
self.timeout = timeout
|
|
91
|
+
self.lock_fd = None
|
|
92
|
+
|
|
93
|
+
@contextlib.contextmanager
|
|
94
|
+
def acquire_read_lock(self) -> Generator[None, None, None]:
|
|
95
|
+
"""获取读锁(共享锁)- Unix/Linux/Mac 实现"""
|
|
96
|
+
self._acquire_lock(fcntl.LOCK_SH)
|
|
97
|
+
try:
|
|
98
|
+
yield
|
|
99
|
+
finally:
|
|
100
|
+
self._release_lock()
|
|
101
|
+
|
|
102
|
+
@contextlib.contextmanager
|
|
103
|
+
def acquire_write_lock(self) -> Generator[None, None, None]:
|
|
104
|
+
"""获取写锁(排他锁)- Unix/Linux/Mac 实现"""
|
|
105
|
+
self._acquire_lock(fcntl.LOCK_EX)
|
|
106
|
+
try:
|
|
107
|
+
yield
|
|
108
|
+
finally:
|
|
109
|
+
self._release_lock()
|
|
110
|
+
|
|
111
|
+
def _acquire_lock(self, lock_type: int):
|
|
112
|
+
"""Unix/Linux/Mac 文件锁实现"""
|
|
113
|
+
start_time = time.time()
|
|
114
|
+
|
|
115
|
+
# 确保锁文件目录存在
|
|
116
|
+
os.makedirs(os.path.dirname(self.lock_file), exist_ok=True)
|
|
117
|
+
|
|
118
|
+
# 打开锁文件
|
|
119
|
+
self.lock_fd = open(self.lock_file, 'w+')
|
|
120
|
+
|
|
121
|
+
while True:
|
|
122
|
+
try:
|
|
123
|
+
# 尝试获取非阻塞锁
|
|
124
|
+
fcntl.flock(self.lock_fd.fileno(), lock_type | fcntl.LOCK_NB)
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
except (IOError, OSError):
|
|
128
|
+
if time.time() - start_time > self.timeout:
|
|
129
|
+
self.lock_fd.close()
|
|
130
|
+
self.lock_fd = None
|
|
131
|
+
raise LockTimeoutError(f"Failed to acquire lock on {self.lock_file} within {self.timeout}s")
|
|
132
|
+
|
|
133
|
+
time.sleep(0.1)
|
|
134
|
+
|
|
135
|
+
def _release_lock(self):
|
|
136
|
+
"""释放锁"""
|
|
137
|
+
if self.lock_fd:
|
|
138
|
+
try:
|
|
139
|
+
fcntl.flock(self.lock_fd.fileno(), fcntl.LOCK_UN)
|
|
140
|
+
self.lock_fd.close()
|
|
141
|
+
except:
|
|
142
|
+
# 忽略释放时的错误
|
|
143
|
+
pass
|
|
144
|
+
finally:
|
|
145
|
+
self.lock_fd = None
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import threading
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from .manager import PersistConversationManager
|
|
5
|
+
from .config import ConversationManagerConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConversationManagerSingleton:
|
|
9
|
+
"""对话管理器的单例类,确保全局只有一个实例"""
|
|
10
|
+
|
|
11
|
+
_instance: Optional[PersistConversationManager] = None
|
|
12
|
+
_lock = threading.Lock()
|
|
13
|
+
_config: Optional[ConversationManagerConfig] = None
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def get_instance(cls, config: Optional[ConversationManagerConfig] = None) -> PersistConversationManager:
|
|
17
|
+
"""
|
|
18
|
+
获取对话管理器实例
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
config: 配置对象,如果为None则使用默认配置
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
PersistConversationManager实例
|
|
25
|
+
"""
|
|
26
|
+
if cls._instance is None:
|
|
27
|
+
with cls._lock:
|
|
28
|
+
if cls._instance is None:
|
|
29
|
+
if config is None:
|
|
30
|
+
config = cls._get_default_config()
|
|
31
|
+
cls._config = config
|
|
32
|
+
cls._instance = PersistConversationManager(config)
|
|
33
|
+
return cls._instance
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def reset_instance(cls, config: Optional[ConversationManagerConfig] = None):
|
|
37
|
+
"""
|
|
38
|
+
重置实例,用于测试或配置更改时
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
config: 新的配置对象
|
|
42
|
+
"""
|
|
43
|
+
with cls._lock:
|
|
44
|
+
cls._instance = None
|
|
45
|
+
cls._config = None
|
|
46
|
+
if config is not None:
|
|
47
|
+
cls._instance = PersistConversationManager(config)
|
|
48
|
+
cls._config = config
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def _get_default_config(cls) -> ConversationManagerConfig:
|
|
52
|
+
"""获取默认配置"""
|
|
53
|
+
# 默认存储路径为当前工作目录下的 .auto-coder/conversations
|
|
54
|
+
default_storage_path = os.path.join(os.getcwd(), ".auto-coder", "conversations")
|
|
55
|
+
|
|
56
|
+
return ConversationManagerConfig(
|
|
57
|
+
storage_path=default_storage_path,
|
|
58
|
+
max_cache_size=100,
|
|
59
|
+
cache_ttl=300.0,
|
|
60
|
+
lock_timeout=10.0,
|
|
61
|
+
backup_enabled=True,
|
|
62
|
+
backup_interval=3600.0,
|
|
63
|
+
max_backups=10
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def get_config(cls) -> Optional[ConversationManagerConfig]:
|
|
68
|
+
"""获取当前使用的配置"""
|
|
69
|
+
return cls._config
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_conversation_manager(config: Optional[ConversationManagerConfig] = None) -> PersistConversationManager:
|
|
73
|
+
"""
|
|
74
|
+
获取全局对话管理器实例
|
|
75
|
+
|
|
76
|
+
这是一个便捷函数,内部使用单例模式确保全局只有一个实例。
|
|
77
|
+
首次调用时会创建实例,后续调用会返回同一个实例。
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
config: 可选的配置对象。如果为None,将使用默认配置。
|
|
81
|
+
注意:只有在首次调用时,config参数才会生效。
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
PersistConversationManager: 对话管理器实例
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
```python
|
|
88
|
+
# 使用默认配置
|
|
89
|
+
manager = get_conversation_manager()
|
|
90
|
+
|
|
91
|
+
# 使用自定义配置(仅在首次调用时生效)
|
|
92
|
+
config = ConversationManagerConfig(
|
|
93
|
+
storage_path="./my_conversations",
|
|
94
|
+
max_cache_size=200
|
|
95
|
+
)
|
|
96
|
+
manager = get_conversation_manager(config)
|
|
97
|
+
|
|
98
|
+
# 创建对话
|
|
99
|
+
conv_id = manager.create_conversation(
|
|
100
|
+
name="测试对话",
|
|
101
|
+
description="这是一个测试对话"
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
"""
|
|
105
|
+
return ConversationManagerSingleton.get_instance(config)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def reset_conversation_manager(config: Optional[ConversationManagerConfig] = None):
|
|
109
|
+
"""
|
|
110
|
+
重置全局对话管理器实例
|
|
111
|
+
|
|
112
|
+
用于测试或需要更改配置时重置实例。
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
config: 新的配置对象,如果为None则在下次调用get_conversation_manager时使用默认配置
|
|
116
|
+
|
|
117
|
+
Example:
|
|
118
|
+
```python
|
|
119
|
+
# 重置为默认配置
|
|
120
|
+
reset_conversation_manager()
|
|
121
|
+
|
|
122
|
+
# 重置为新配置
|
|
123
|
+
new_config = ConversationManagerConfig(storage_path="./new_path")
|
|
124
|
+
reset_conversation_manager(new_config)
|
|
125
|
+
```
|
|
126
|
+
"""
|
|
127
|
+
ConversationManagerSingleton.reset_instance(config)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_conversation_manager_config() -> Optional[ConversationManagerConfig]:
|
|
131
|
+
"""
|
|
132
|
+
获取当前对话管理器使用的配置
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
当前配置对象,如果还未初始化则返回None
|
|
136
|
+
"""
|
|
137
|
+
return ConversationManagerSingleton.get_config()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# 便捷别名
|
|
141
|
+
get_manager = get_conversation_manager
|
|
142
|
+
reset_manager = reset_conversation_manager
|
|
143
|
+
get_manager_config = get_conversation_manager_config
|