2024-01-29 21:22:27 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import typing
|
|
|
|
|
import pkgutil
|
|
|
|
|
import importlib
|
|
|
|
|
import traceback
|
|
|
|
|
|
2024-10-19 18:38:01 +08:00
|
|
|
from .. import loader, events, context, models
|
2024-01-29 21:22:27 +08:00
|
|
|
from ...core import entities as core_entities
|
|
|
|
|
from ...provider.tools import entities as tools_entities
|
2024-03-20 15:09:47 +08:00
|
|
|
from ...utils import funcschema
|
2025-04-12 15:37:15 +08:00
|
|
|
from ...discover import engine as discover_engine
|
2025-04-13 22:51:21 +08:00
|
|
|
|
2024-01-29 21:22:27 +08:00
|
|
|
|
|
|
|
|
class PluginLoader(loader.PluginLoader):
|
|
|
|
|
"""加载 plugins/ 目录下的插件"""
|
|
|
|
|
|
|
|
|
|
_current_pkg_path = ''
|
|
|
|
|
|
|
|
|
|
_current_module_path = ''
|
|
|
|
|
|
|
|
|
|
_current_container: context.RuntimeContainer = None
|
|
|
|
|
|
2024-11-16 16:13:02 +08:00
|
|
|
plugins: list[context.RuntimeContainer] = []
|
|
|
|
|
|
|
|
|
|
def __init__(self, ap):
|
|
|
|
|
self.ap = ap
|
|
|
|
|
self.plugins = []
|
|
|
|
|
self._current_pkg_path = ''
|
|
|
|
|
self._current_module_path = ''
|
|
|
|
|
self._current_container = None
|
2024-01-29 21:22:27 +08:00
|
|
|
|
|
|
|
|
async def initialize(self):
|
|
|
|
|
"""初始化"""
|
2024-03-20 15:09:47 +08:00
|
|
|
|
2024-01-29 21:22:27 +08:00
|
|
|
def register(
|
2025-04-29 17:24:07 +08:00
|
|
|
self, name: str, description: str, version: str, author: str
|
2025-05-10 18:04:58 +08:00
|
|
|
) -> typing.Callable[[typing.Type[context.BasePlugin]], typing.Type[context.BasePlugin]]:
|
2024-01-29 21:22:27 +08:00
|
|
|
self.ap.logger.debug(f'注册插件 {name} {version} by {author}')
|
|
|
|
|
container = context.RuntimeContainer(
|
|
|
|
|
plugin_name=name,
|
2025-05-14 16:44:48 +08:00
|
|
|
plugin_label=discover_engine.I18nString(en_US=name, zh_Hans=name),
|
|
|
|
|
plugin_description=discover_engine.I18nString(en_US=description, zh_Hans=description),
|
2024-01-29 21:22:27 +08:00
|
|
|
plugin_version=version,
|
|
|
|
|
plugin_author=author,
|
2025-04-12 15:37:15 +08:00
|
|
|
plugin_repository='',
|
2024-01-29 21:22:27 +08:00
|
|
|
pkg_path=self._current_pkg_path,
|
|
|
|
|
main_file=self._current_module_path,
|
|
|
|
|
event_handlers={},
|
2025-04-12 15:37:15 +08:00
|
|
|
tools=[],
|
2024-01-29 21:22:27 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self._current_container = container
|
|
|
|
|
|
|
|
|
|
def wrapper(cls: context.BasePlugin) -> typing.Type[context.BasePlugin]:
|
|
|
|
|
container.plugin_class = cls
|
|
|
|
|
return cls
|
2025-04-29 17:24:07 +08:00
|
|
|
|
2024-01-29 21:22:27 +08:00
|
|
|
return wrapper
|
|
|
|
|
|
2024-03-20 15:09:47 +08:00
|
|
|
# 过时
|
|
|
|
|
# 最早将于 v3.4 版本移除
|
2025-05-10 18:04:58 +08:00
|
|
|
def on(self, event: typing.Type[events.BaseEventModel]) -> typing.Callable[[typing.Callable], typing.Callable]:
|
2024-01-29 21:22:27 +08:00
|
|
|
"""注册过时的事件处理器"""
|
|
|
|
|
self.ap.logger.debug(f'注册事件处理器 {event.__name__}')
|
2025-04-29 17:24:07 +08:00
|
|
|
|
2024-01-29 21:22:27 +08:00
|
|
|
def wrapper(func: typing.Callable) -> typing.Callable:
|
2025-05-10 18:04:58 +08:00
|
|
|
async def handler(plugin: context.BasePlugin, ctx: context.EventContext) -> None:
|
2024-01-29 21:22:27 +08:00
|
|
|
args = {
|
|
|
|
|
'host': ctx.host,
|
|
|
|
|
'event': ctx,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 把 ctx.event 所有的属性都放到 args 里
|
2024-11-16 16:13:02 +08:00
|
|
|
# for k, v in ctx.event.dict().items():
|
2025-04-29 17:24:07 +08:00
|
|
|
# args[k] = v
|
2024-11-16 16:13:02 +08:00
|
|
|
for attr_name in ctx.event.__dict__.keys():
|
|
|
|
|
args[attr_name] = getattr(ctx.event, attr_name)
|
2024-01-29 21:22:27 +08:00
|
|
|
|
2024-01-30 21:45:17 +08:00
|
|
|
func(plugin, **args)
|
2025-04-29 17:24:07 +08:00
|
|
|
|
2024-01-29 21:22:27 +08:00
|
|
|
self._current_container.event_handlers[event] = handler
|
|
|
|
|
|
|
|
|
|
return func
|
|
|
|
|
|
|
|
|
|
return wrapper
|
|
|
|
|
|
2024-03-20 15:09:47 +08:00
|
|
|
# 过时
|
|
|
|
|
# 最早将于 v3.4 版本移除
|
2024-01-29 21:22:27 +08:00
|
|
|
def func(
|
|
|
|
|
self,
|
2025-04-29 17:24:07 +08:00
|
|
|
name: str = None,
|
2024-01-29 21:22:27 +08:00
|
|
|
) -> typing.Callable:
|
|
|
|
|
"""注册过时的内容函数"""
|
|
|
|
|
self.ap.logger.debug(f'注册内容函数 {name}')
|
2025-04-29 17:24:07 +08:00
|
|
|
|
2024-01-29 21:22:27 +08:00
|
|
|
def wrapper(func: typing.Callable) -> typing.Callable:
|
2024-03-20 15:09:47 +08:00
|
|
|
function_schema = funcschema.get_func_schema(func)
|
2025-05-10 18:04:58 +08:00
|
|
|
function_name = self._current_container.plugin_name + '-' + (func.__name__ if name is None else name)
|
2024-01-29 21:22:27 +08:00
|
|
|
|
2025-05-10 18:04:58 +08:00
|
|
|
async def handler(plugin: context.BasePlugin, query: core_entities.Query, *args, **kwargs):
|
2024-01-29 21:22:27 +08:00
|
|
|
return func(*args, **kwargs)
|
|
|
|
|
|
|
|
|
|
llm_function = tools_entities.LLMFunction(
|
|
|
|
|
name=function_name,
|
|
|
|
|
human_desc='',
|
|
|
|
|
description=function_schema['description'],
|
|
|
|
|
parameters=function_schema['parameters'],
|
|
|
|
|
func=handler,
|
|
|
|
|
)
|
|
|
|
|
|
2025-04-12 15:37:15 +08:00
|
|
|
self._current_container.tools.append(llm_function)
|
2024-01-29 21:22:27 +08:00
|
|
|
|
|
|
|
|
return func
|
2025-04-29 17:24:07 +08:00
|
|
|
|
2024-01-29 21:22:27 +08:00
|
|
|
return wrapper
|
2025-04-29 17:24:07 +08:00
|
|
|
|
2025-05-10 18:04:58 +08:00
|
|
|
def handler(self, event: typing.Type[events.BaseEventModel]) -> typing.Callable[[typing.Callable], typing.Callable]:
|
2024-03-20 15:09:47 +08:00
|
|
|
"""注册事件处理器"""
|
|
|
|
|
self.ap.logger.debug(f'注册事件处理器 {event.__name__}')
|
2025-04-12 20:21:43 +08:00
|
|
|
|
2025-04-29 17:24:07 +08:00
|
|
|
def wrapper(func: typing.Callable) -> typing.Callable:
|
|
|
|
|
if (
|
|
|
|
|
self._current_container is None
|
|
|
|
|
): # None indicates this plugin is registered through manifest, so ignore it here
|
2025-04-12 20:21:43 +08:00
|
|
|
return func
|
|
|
|
|
|
2024-03-20 15:09:47 +08:00
|
|
|
self._current_container.event_handlers[event] = func
|
|
|
|
|
|
|
|
|
|
return func
|
|
|
|
|
|
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
|
def llm_func(
|
|
|
|
|
self,
|
2025-04-29 17:24:07 +08:00
|
|
|
name: str = None,
|
2024-03-20 15:09:47 +08:00
|
|
|
) -> typing.Callable:
|
|
|
|
|
"""注册内容函数"""
|
|
|
|
|
self.ap.logger.debug(f'注册内容函数 {name}')
|
2025-04-29 17:24:07 +08:00
|
|
|
|
2024-03-20 15:09:47 +08:00
|
|
|
def wrapper(func: typing.Callable) -> typing.Callable:
|
2025-04-29 17:24:07 +08:00
|
|
|
if (
|
|
|
|
|
self._current_container is None
|
|
|
|
|
): # None indicates this plugin is registered through manifest, so ignore it here
|
2025-04-12 20:21:43 +08:00
|
|
|
return func
|
2025-04-29 17:24:07 +08:00
|
|
|
|
2024-03-20 15:09:47 +08:00
|
|
|
function_schema = funcschema.get_func_schema(func)
|
2025-05-10 18:04:58 +08:00
|
|
|
function_name = self._current_container.plugin_name + '-' + (func.__name__ if name is None else name)
|
2024-03-20 15:09:47 +08:00
|
|
|
|
|
|
|
|
llm_function = tools_entities.LLMFunction(
|
|
|
|
|
name=function_name,
|
|
|
|
|
human_desc='',
|
|
|
|
|
description=function_schema['description'],
|
|
|
|
|
parameters=function_schema['parameters'],
|
|
|
|
|
func=func,
|
|
|
|
|
)
|
|
|
|
|
|
2025-04-12 15:37:15 +08:00
|
|
|
self._current_container.tools.append(llm_function)
|
2024-03-20 15:09:47 +08:00
|
|
|
|
|
|
|
|
return func
|
2025-04-29 17:24:07 +08:00
|
|
|
|
2024-03-20 15:09:47 +08:00
|
|
|
return wrapper
|
2025-04-29 17:24:07 +08:00
|
|
|
|
|
|
|
|
async def _walk_plugin_path(self, module, prefix='', path_prefix=''):
|
|
|
|
|
"""遍历插件路径"""
|
2024-01-29 21:22:27 +08:00
|
|
|
for item in pkgutil.iter_modules(module.__path__):
|
|
|
|
|
if item.ispkg:
|
|
|
|
|
await self._walk_plugin_path(
|
2025-04-29 17:24:07 +08:00
|
|
|
__import__(module.__name__ + '.' + item.name, fromlist=['']),
|
|
|
|
|
prefix + item.name + '.',
|
|
|
|
|
path_prefix + item.name + '/',
|
2024-01-29 21:22:27 +08:00
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
try:
|
2025-04-29 17:24:07 +08:00
|
|
|
self._current_pkg_path = 'plugins/' + path_prefix
|
2025-05-10 18:04:58 +08:00
|
|
|
self._current_module_path = 'plugins/' + path_prefix + item.name + '.py'
|
2024-01-29 21:22:27 +08:00
|
|
|
|
|
|
|
|
self._current_container = None
|
|
|
|
|
|
2025-04-29 17:24:07 +08:00
|
|
|
importlib.import_module(module.__name__ + '.' + item.name)
|
2024-01-29 21:22:27 +08:00
|
|
|
|
|
|
|
|
if self._current_container is not None:
|
2024-11-16 16:13:02 +08:00
|
|
|
self.plugins.append(self._current_container)
|
2024-01-29 21:22:27 +08:00
|
|
|
self.ap.logger.debug(f'插件 {self._current_container} 已加载')
|
2025-04-29 17:24:07 +08:00
|
|
|
except Exception:
|
2025-05-10 18:04:58 +08:00
|
|
|
self.ap.logger.error(f'加载插件模块 {prefix + item.name} 时发生错误')
|
2024-01-29 21:22:27 +08:00
|
|
|
traceback.print_exc()
|
|
|
|
|
|
2024-11-16 16:13:02 +08:00
|
|
|
async def load_plugins(self):
|
2025-04-29 17:24:07 +08:00
|
|
|
"""加载插件"""
|
2025-04-12 15:37:15 +08:00
|
|
|
setattr(models, 'register', self.register)
|
|
|
|
|
setattr(models, 'on', self.on)
|
|
|
|
|
setattr(models, 'func', self.func)
|
|
|
|
|
|
|
|
|
|
setattr(context, 'register', self.register)
|
|
|
|
|
setattr(context, 'handler', self.handler)
|
|
|
|
|
setattr(context, 'llm_func', self.llm_func)
|
2025-04-29 17:24:07 +08:00
|
|
|
await self._walk_plugin_path(__import__('plugins', fromlist=['']))
|