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

133 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-02 09:59 +0800

1""" 

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

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

4 

5插件清单格式: 

6- manifest.json: 插件元数据 

7- 入口点: Python类路径 

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

9""" 

10 

11from __future__ import annotations 

12 

13from dataclasses import dataclass, field 

14from enum import Enum 

15from typing import Any, Callable 

16 

17 

18class PluginType(str, Enum): 

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

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

21 TOOL = "tool" # 工具扩展 

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

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

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

25 CUSTOM = "custom" # 自定义 

26 

27 

28class PluginStatus(str, Enum): 

29 

30 """插件状态。""" 

31 

32 LOADED = "loaded" 

33 INITIALIZED = "initialized" 

34 ACTIVE = "active" 

35 STOPPING = "stopping" 

36 STOPPED = "stopped" 

37 ERROR = "error" 

38 

39 

40@dataclass 

41class PluginManifest: 

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

43 

44 name: str 

45 version: str 

46 description: str = "" 

47 author: str = "" 

48 plugin_type: PluginType = PluginType.CUSTOM 

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

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

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

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

53 config_schema: dict = field(default_factory=dict) 

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

55 homepage: str = "" 

56 license: str = "MIT" 

57 

58 def to_dict(self) -> dict: 

59 return { 

60 "name": self.name, 

61 "version": self.version, 

62 "plugin_type": self.plugin_type.value, 

63 "entry_point": self.entry_point, 

64 "dependencies": self.dependencies, 

65 "tags": self.tags, 

66 } 

67 

68 

69@dataclass 

70class RegisteredPlugin: 

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

72 

73 manifest: PluginManifest 

74 instance: Any = None 

75 status: PluginStatus = PluginStatus.LOADED 

76 load_time_ms: float = 0.0 

77 error: str | None = None 

78 

79 

80class PluginRegistry: 

81 """ 

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

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

84 """ 

85 

86 def __init__(self): 

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

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

89 

90 # ── CRUD ───────────────────────────────────── 

91 

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

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

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

95 self._plugins[manifest.name] = registered 

96 return registered 

97 

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

99 if name in self._plugins: 

100 del self._plugins[name] 

101 return True 

102 return False 

103 

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

105 return self._plugins.get(name) 

106 

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

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

109 registered = self._plugins.get(name) 

110 return registered.instance if registered else None 

111 

112 # ── Query ──────────────────────────────────── 

113 

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

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

116 

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

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

119 

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

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

122 

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

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

125 

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

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

128 

129 # ── Dependency Resolution ──────────────────── 

130 

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

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

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

134 for name in names: 

135 p = self._plugins.get(name) 

136 if p: 

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

138 

139 # Kahn's algorithm 

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

141 for deps in adj.values(): 

142 for d in deps: 

143 in_degree[d] += 1 

144 

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

146 order = [] 

147 while queue: 

148 n = queue.pop(0) 

149 order.append(n) 

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

151 in_degree[dep] -= 1 

152 if in_degree[dep] == 0: 

153 queue.append(dep) 

154 

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

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

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

158 

159 return order 

160 

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

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

163 p = self._plugins.get(name) 

164 if not p: 

165 return [name] 

166 missing = [] 

167 for dep in p.manifest.dependencies: 

168 if dep not in self._plugins: 

169 missing.append(dep) 

170 return missing 

171 

172 # ── Hook System ────────────────────────────── 

173 

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

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

176 if event not in self._hooks: 

177 self._hooks[event] = [] 

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

179 

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

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

182 results = [] 

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

184 try: 

185 import asyncio 

186 if asyncio.iscoroutinefunction(cb): 

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

188 else: 

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

190 except Exception as e: 

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

192 return results 

193 

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

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

196 

197 # ── Info ───────────────────────────────────── 

198 

199 @property 

200 def count(self) -> int: 

201 return len(self._plugins) 

202 

203 def summary(self) -> str: 

204 by_type = {} 

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

206 t = p.manifest.plugin_type.value 

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

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

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

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

211 return "\n".join(lines) 

212 

213 

214class DependencyCycleError(Exception): 

215 

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

217 

218 pass