Coverage for agentos/plugins/registry.py: 0%

133 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-06 19:15 +0800

1""" 

2AgentOS v0.70 — 插件系统: 注册中心。 

3基因来源: Docker插件体系 + VSCode扩展市场 

4 

5插件清单格式: 

6- manifest.json: 插件元数据 

7- 入口点: Python类路径 

8- 依赖声明: 插件间依赖 

9""" 

10 

11from __future__ import annotations 

12 

13from collections.abc import Callable 

14from dataclasses import dataclass, field 

15from enum import StrEnum 

16from typing import Any 

17 

18 

19class PluginType(StrEnum): 

20 """插件类型枚举。""" 

21 

22 PROVIDER = "provider" # 模型provider (如GeminiAdapter) 

23 TOOL = "tool" # 工具扩展 

24 MIDDLEWARE = "middleware" # 请求/响应拦截器 

25 SINK = "sink" # 输出后端 (观测/日志/存储) 

26 HOOK = "hook" # 生命周期钩子 

27 CUSTOM = "custom" # 自定义 

28 

29 

30class PluginStatus(StrEnum): 

31 """插件状态。""" 

32 

33 LOADED = "loaded" 

34 INITIALIZED = "initialized" 

35 ACTIVE = "active" 

36 STOPPING = "stopping" 

37 STOPPED = "stopped" 

38 ERROR = "error" 

39 

40 

41@dataclass 

42class PluginManifest: 

43 """插件清单 — 描述插件能力与依赖。""" 

44 

45 name: str 

46 version: str 

47 description: str = "" 

48 author: str = "" 

49 plugin_type: PluginType = PluginType.CUSTOM 

50 entry_point: str = "" # fully qualified class path 

51 dependencies: list[str] = field(default_factory=list) 

52 optional_dependencies: list[str] = field(default_factory=list) 

53 tags: list[str] = field(default_factory=list) 

54 config_schema: dict = field(default_factory=dict) 

55 priority: int = 50 # 0=最高, 100=最低 

56 homepage: str = "" 

57 license: str = "MIT" 

58 

59 def to_dict(self) -> dict: 

60 return { 

61 "name": self.name, 

62 "version": self.version, 

63 "plugin_type": self.plugin_type.value, 

64 "entry_point": self.entry_point, 

65 "dependencies": self.dependencies, 

66 "tags": self.tags, 

67 } 

68 

69 

70@dataclass 

71class RegisteredPlugin: 

72 """已注册的插件实例。""" 

73 

74 manifest: PluginManifest 

75 instance: Any = None 

76 status: PluginStatus = PluginStatus.LOADED 

77 load_time_ms: float = 0.0 

78 error: str | None = None 

79 

80 

81class PluginRegistry: 

82 """ 

83 插件注册中心 — 统一管理所有已注册插件。 

84 支持: CRUD、查询、按标签/类型检索、依赖解析。 

85 """ 

86 

87 def __init__(self): 

88 self._plugins: dict[str, RegisteredPlugin] = {} 

89 self._hooks: dict[str, list[Callable]] = {} # event → list of callbacks 

90 

91 # ── CRUD ───────────────────────────────────── 

92 

93 def register(self, manifest: PluginManifest, instance: Any = None) -> RegisteredPlugin: 

94 """注册插件(覆盖已有同名插件)。""" 

95 registered = RegisteredPlugin(manifest=manifest, instance=instance) 

96 self._plugins[manifest.name] = registered 

97 return registered 

98 

99 def unregister(self, name: str) -> bool: 

100 if name in self._plugins: 

101 del self._plugins[name] 

102 return True 

103 return False 

104 

105 def get(self, name: str) -> RegisteredPlugin | None: 

106 return self._plugins.get(name) 

107 

108 def get_instance(self, name: str) -> Any: 

109 """获取插件实例。""" 

110 registered = self._plugins.get(name) 

111 return registered.instance if registered else None 

112 

113 # ── Query ──────────────────────────────────── 

114 

115 def list_all(self) -> list[RegisteredPlugin]: 

116 return list(self._plugins.values()) 

117 

118 def list_names(self) -> list[str]: 

119 return list(self._plugins.keys()) 

120 

121 def by_type(self, plugin_type: PluginType) -> list[RegisteredPlugin]: 

122 return [p for p in self._plugins.values() if p.manifest.plugin_type == plugin_type] 

123 

124 def by_tag(self, tag: str) -> list[RegisteredPlugin]: 

125 return [p for p in self._plugins.values() if tag in p.manifest.tags] 

126 

127 def by_status(self, status: PluginStatus) -> list[RegisteredPlugin]: 

128 return [p for p in self._plugins.values() if p.status == status] 

129 

130 # ── Dependency Resolution ──────────────────── 

131 

132 def resolve_order(self, names: list[str]) -> list[str]: 

133 """拓扑排序解析插件加载顺序。""" 

134 adj: dict[str, list[str]] = {n: [] for n in names} 

135 for name in names: 

136 p = self._plugins.get(name) 

137 if p: 

138 adj[name] = [d for d in p.manifest.dependencies if d in names] 

139 

140 # Kahn's algorithm 

141 in_degree = {n: 0 for n in names} 

142 for deps in adj.values(): 

143 for d in deps: 

144 in_degree[d] += 1 

145 

146 queue = [n for n in names if in_degree[n] == 0] 

147 order = [] 

148 while queue: 

149 n = queue.pop(0) 

150 order.append(n) 

151 for dep in adj.get(n, []): 

152 in_degree[dep] -= 1 

153 if in_degree[dep] == 0: 

154 queue.append(dep) 

155 

156 if len(order) != len(names): 

157 missing = set(names) - set(order) 

158 raise DependencyCycleError(f"循环依赖或缺失依赖: {missing}") 

159 

160 return order 

161 

162 def check_requirements(self, name: str) -> list[str]: 

163 """检查某插件的依赖是否满足。返回缺失的依赖列表。""" 

164 p = self._plugins.get(name) 

165 if not p: 

166 return [name] 

167 missing = [] 

168 for dep in p.manifest.dependencies: 

169 if dep not in self._plugins: 

170 missing.append(dep) 

171 return missing 

172 

173 # ── Hook System ────────────────────────────── 

174 

175 def register_hook(self, event: str, callback: Callable): 

176 """注册事件钩子。""" 

177 if event not in self._hooks: 

178 self._hooks[event] = [] 

179 self._hooks[event].append(callback) 

180 

181 async def emit_hook(self, event: str, **kwargs) -> list[Any]: 

182 """触发事件钩子,返回所有回调结果。""" 

183 results = [] 

184 for cb in self._hooks.get(event, []): 

185 try: 

186 import asyncio 

187 

188 if asyncio.iscoroutinefunction(cb): 

189 results.append(await cb(**kwargs)) 

190 else: 

191 results.append(cb(**kwargs)) 

192 except Exception as e: 

193 results.append({"error": str(e)}) 

194 return results 

195 

196 def hook_names(self) -> list[str]: 

197 return list(self._hooks.keys()) 

198 

199 # ── Info ───────────────────────────────────── 

200 

201 @property 

202 def count(self) -> int: 

203 return len(self._plugins) 

204 

205 def summary(self) -> str: 

206 by_type = {} 

207 for p in self._plugins.values(): 

208 t = p.manifest.plugin_type.value 

209 by_type[t] = by_type.get(t, 0) + 1 

210 lines = [f"共 {self.count} 个插件"] 

211 for t, c in sorted(by_type.items()): 

212 lines.append(f" {t}: {c}") 

213 return "\n".join(lines) 

214 

215 

216class DependencyCycleError(Exception): 

217 """插件依赖循环异常。""" 

218