Coverage for agentos/plugins/registry.py: 0%
133 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 23:17 +0800
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 23:17 +0800
1"""
2AgentOS v0.70 — 插件系统: 注册中心。
3基因来源: Docker插件体系 + VSCode扩展市场
5插件清单格式:
6- manifest.json: 插件元数据
7- 入口点: Python类路径
8- 依赖声明: 插件间依赖
9"""
11from __future__ import annotations
13from collections.abc import Callable
14from dataclasses import dataclass, field
15from enum import StrEnum
16from typing import Any
19class PluginType(StrEnum):
20 """插件类型枚举。"""
22 PROVIDER = "provider" # 模型provider (如GeminiAdapter)
23 TOOL = "tool" # 工具扩展
24 MIDDLEWARE = "middleware" # 请求/响应拦截器
25 SINK = "sink" # 输出后端 (观测/日志/存储)
26 HOOK = "hook" # 生命周期钩子
27 CUSTOM = "custom" # 自定义
30class PluginStatus(StrEnum):
31 """插件状态。"""
33 LOADED = "loaded"
34 INITIALIZED = "initialized"
35 ACTIVE = "active"
36 STOPPING = "stopping"
37 STOPPED = "stopped"
38 ERROR = "error"
41@dataclass
42class PluginManifest:
43 """插件清单 — 描述插件能力与依赖。"""
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"
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 }
70@dataclass
71class RegisteredPlugin:
72 """已注册的插件实例。"""
74 manifest: PluginManifest
75 instance: Any = None
76 status: PluginStatus = PluginStatus.LOADED
77 load_time_ms: float = 0.0
78 error: str | None = None
81class PluginRegistry:
82 """
83 插件注册中心 — 统一管理所有已注册插件。
84 支持: CRUD、查询、按标签/类型检索、依赖解析。
85 """
87 def __init__(self):
88 self._plugins: dict[str, RegisteredPlugin] = {}
89 self._hooks: dict[str, list[Callable]] = {} # event → list of callbacks
91 # ── CRUD ─────────────────────────────────────
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
99 def unregister(self, name: str) -> bool:
100 if name in self._plugins:
101 del self._plugins[name]
102 return True
103 return False
105 def get(self, name: str) -> RegisteredPlugin | None:
106 return self._plugins.get(name)
108 def get_instance(self, name: str) -> Any:
109 """获取插件实例。"""
110 registered = self._plugins.get(name)
111 return registered.instance if registered else None
113 # ── Query ────────────────────────────────────
115 def list_all(self) -> list[RegisteredPlugin]:
116 return list(self._plugins.values())
118 def list_names(self) -> list[str]:
119 return list(self._plugins.keys())
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]
124 def by_tag(self, tag: str) -> list[RegisteredPlugin]:
125 return [p for p in self._plugins.values() if tag in p.manifest.tags]
127 def by_status(self, status: PluginStatus) -> list[RegisteredPlugin]:
128 return [p for p in self._plugins.values() if p.status == status]
130 # ── Dependency Resolution ────────────────────
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]
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
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)
156 if len(order) != len(names):
157 missing = set(names) - set(order)
158 raise DependencyCycleError(f"循环依赖或缺失依赖: {missing}")
160 return order
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
173 # ── Hook System ──────────────────────────────
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)
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
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
196 def hook_names(self) -> list[str]:
197 return list(self._hooks.keys())
199 # ── Info ─────────────────────────────────────
201 @property
202 def count(self) -> int:
203 return len(self._plugins)
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)
216class DependencyCycleError(Exception):
217 """插件依赖循环异常。"""