From 6f2fd72af6639ab663af66b4a3a79cc0db51f776 Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Sat, 14 Jun 2025 17:27:21 +0800 Subject: [PATCH] feat(plugin): basic communication --- pkg/plugin/connector.py | 16 +++- pkg/plugin/context.py | 158 ++++++++++++++++++++-------------------- pkg/plugin/handler.py | 57 +++++++++++++++ pkg/utils/platform.py | 5 +- 4 files changed, 150 insertions(+), 86 deletions(-) diff --git a/pkg/plugin/connector.py b/pkg/plugin/connector.py index 5eadc900..f85d4be3 100644 --- a/pkg/plugin/connector.py +++ b/pkg/plugin/connector.py @@ -29,17 +29,23 @@ class PluginRuntimeConnector: async def initialize(self): async def new_connection_callback(connection: stdio_connection.StdioConnection): - self.ap.logger.info('Connected to plugin runtime.') - self.handler = handler.RuntimeConnectionHandler(connection) + self.handler = handler.RuntimeConnectionHandler(connection, self.ap) self.handler_task = asyncio.create_task(self.handler.run()) + _ = await self.handler.ping() + self.ap.logger.info('Connected to plugin runtime.') + await self.handler_task + + task: asyncio.Task | None = None if platform.get_platform() == 'docker': # use websocket + self.ap.logger.info('use websocket to connect to plugin runtime') ws_url = self.ap.instance_config.data['plugin']['runtime_ws_url'] ctrl = ws_client_controller.WebSocketClientController( ws_url=ws_url, ) - await ctrl.run(new_connection_callback) + task = ctrl.run(new_connection_callback) else: # stdio + self.ap.logger.info('use stdio to connect to plugin runtime') # cmd: lbp rt -s python_path = sys.executable env = os.environ.copy() @@ -48,7 +54,9 @@ class PluginRuntimeConnector: args=['-m', 'langbot_plugin.cli.__init__', 'rt', '-s'], env=env, ) - await ctrl.run(new_connection_callback) + task = ctrl.run(new_connection_callback) + + asyncio.create_task(task) async def run(self): pass diff --git a/pkg/plugin/context.py b/pkg/plugin/context.py index dfd691f3..86d940c4 100644 --- a/pkg/plugin/context.py +++ b/pkg/plugin/context.py @@ -2,13 +2,9 @@ from __future__ import annotations import typing import abc -import pydantic.v1 as pydantic -import enum from . import events -from ..provider.tools import entities as tools_entities from ..core import app -from ..discover import engine as discover_engine from ..platform.types import message as platform_message from ..platform import adapter as platform_adapter @@ -285,104 +281,104 @@ class EventContext: EventContext.eid += 1 -class RuntimeContainerStatus(enum.Enum): - """插件容器状态""" +# class RuntimeContainerStatus(enum.Enum): +# """插件容器状态""" - MOUNTED = 'mounted' - """已加载进内存,所有位于运行时记录中的 RuntimeContainer 至少是这个状态""" +# MOUNTED = 'mounted' +# """已加载进内存,所有位于运行时记录中的 RuntimeContainer 至少是这个状态""" - INITIALIZED = 'initialized' - """已初始化""" +# INITIALIZED = 'initialized' +# """已初始化""" -class RuntimeContainer(pydantic.BaseModel): - """运行时的插件容器 +# class RuntimeContainer(pydantic.BaseModel): +# """运行时的插件容器 - 运行期间存储单个插件的信息 - """ +# 运行期间存储单个插件的信息 +# """ - plugin_name: str - """插件名称""" +# plugin_name: str +# """插件名称""" - plugin_label: discover_engine.I18nString - """插件标签""" +# plugin_label: discover_engine.I18nString +# """插件标签""" - plugin_description: discover_engine.I18nString - """插件描述""" +# plugin_description: discover_engine.I18nString +# """插件描述""" - plugin_version: str - """插件版本""" +# plugin_version: str +# """插件版本""" - plugin_author: str - """插件作者""" +# plugin_author: str +# """插件作者""" - plugin_repository: str - """插件源码地址""" +# plugin_repository: str +# """插件源码地址""" - main_file: str - """插件主文件路径""" +# main_file: str +# """插件主文件路径""" - pkg_path: str - """插件包路径""" +# pkg_path: str +# """插件包路径""" - plugin_class: typing.Type[BasePlugin] = None - """插件类""" +# plugin_class: typing.Type[BasePlugin] = None +# """插件类""" - enabled: typing.Optional[bool] = True - """是否启用""" +# enabled: typing.Optional[bool] = True +# """是否启用""" - priority: typing.Optional[int] = 0 - """优先级""" +# priority: typing.Optional[int] = 0 +# """优先级""" - config_schema: typing.Optional[list[dict]] = [] - """插件配置模板""" +# config_schema: typing.Optional[list[dict]] = [] +# """插件配置模板""" - plugin_config: typing.Optional[dict] = {} - """插件配置""" +# plugin_config: typing.Optional[dict] = {} +# """插件配置""" - plugin_inst: typing.Optional[BasePlugin] = None - """插件实例""" +# plugin_inst: typing.Optional[BasePlugin] = None +# """插件实例""" - event_handlers: dict[ - typing.Type[events.BaseEventModel], - typing.Callable[[BasePlugin, EventContext], typing.Awaitable[None]], - ] = {} - """事件处理器""" +# event_handlers: dict[ +# typing.Type[events.BaseEventModel], +# typing.Callable[[BasePlugin, EventContext], typing.Awaitable[None]], +# ] = {} +# """事件处理器""" - tools: list[tools_entities.LLMFunction] = [] - """内容函数""" +# tools: list[tools_entities.LLMFunction] = [] +# """内容函数""" - status: RuntimeContainerStatus = RuntimeContainerStatus.MOUNTED - """插件状态""" +# status: RuntimeContainerStatus = RuntimeContainerStatus.MOUNTED +# """插件状态""" - class Config: - arbitrary_types_allowed = True +# class Config: +# arbitrary_types_allowed = True - def model_dump(self, *args, **kwargs): - return { - 'name': self.plugin_name, - 'label': self.plugin_label.to_dict(), - 'description': self.plugin_description.to_dict(), - 'version': self.plugin_version, - 'author': self.plugin_author, - 'repository': self.plugin_repository, - 'main_file': self.main_file, - 'pkg_path': self.pkg_path, - 'enabled': self.enabled, - 'priority': self.priority, - 'config_schema': self.config_schema, - 'event_handlers': { - event_name.__name__: handler.__name__ for event_name, handler in self.event_handlers.items() - }, - 'tools': [ - { - 'name': function.name, - 'human_desc': function.human_desc, - 'description': function.description, - 'parameters': function.parameters, - 'func': function.func.__name__, - } - for function in self.tools - ], - 'status': self.status.value, - } +# def model_dump(self, *args, **kwargs): +# return { +# 'name': self.plugin_name, +# 'label': self.plugin_label.to_dict(), +# 'description': self.plugin_description.to_dict(), +# 'version': self.plugin_version, +# 'author': self.plugin_author, +# 'repository': self.plugin_repository, +# 'main_file': self.main_file, +# 'pkg_path': self.pkg_path, +# 'enabled': self.enabled, +# 'priority': self.priority, +# 'config_schema': self.config_schema, +# 'event_handlers': { +# event_name.__name__: handler.__name__ for event_name, handler in self.event_handlers.items() +# }, +# 'tools': [ +# { +# 'name': function.name, +# 'human_desc': function.human_desc, +# 'description': function.description, +# 'parameters': function.parameters, +# 'func': function.func.__name__, +# } +# for function in self.tools +# ], +# 'status': self.status.value, +# } diff --git a/pkg/plugin/handler.py b/pkg/plugin/handler.py index 077c1649..d9360650 100644 --- a/pkg/plugin/handler.py +++ b/pkg/plugin/handler.py @@ -1,7 +1,64 @@ from __future__ import annotations +from typing import Any + +import sqlalchemy + from langbot_plugin.runtime.io import handler +from langbot_plugin.runtime.io.connection import Connection +from langbot_plugin.entities.io.actions.enums import ( + CommonAction, + RuntimeToLangBotAction, +) + +from ..entity.persistence import plugin as persistence_plugin + +from ..core import app class RuntimeConnectionHandler(handler.Handler): """Runtime connection handler""" + + ap: app.Application + + def __init__(self, connection: Connection, ap: app.Application): + super().__init__(connection) + self.ap = ap + + @self.action(RuntimeToLangBotAction.GET_PLUGIN_SETTINGS) + async def get_plugin_settings(data: dict[str, Any]) -> handler.ActionResponse: + """Get plugin settings""" + + plugin_author = data['plugin_author'] + plugin_name = data['plugin_name'] + + result = await self.ap.persistence_mgr.execute_async( + sqlalchemy.select(persistence_plugin.PluginSetting) + .where(persistence_plugin.PluginSetting.plugin_author == plugin_author) + .where(persistence_plugin.PluginSetting.plugin_name == plugin_name) + ) + + data = { + 'enabled': False, + 'priority': 0, + 'plugin_config': {}, + } + + setting = result.first() + + if setting is not None: + data['enabled'] = setting.enabled + data['priority'] = setting.priority + data['plugin_config'] = setting.config + + return handler.ActionResponse.success( + data=data, + ) + + async def ping(self) -> dict[str, Any]: + """Ping the runtime""" + return await self.call_action( + CommonAction.PING, + {}, + timeout=10, + ) diff --git a/pkg/utils/platform.py b/pkg/utils/platform.py index 0d4a1f26..0145081a 100644 --- a/pkg/utils/platform.py +++ b/pkg/utils/platform.py @@ -5,7 +5,10 @@ import sys def get_platform() -> str: """获取当前平台""" # 检查是不是在 docker 里 - if os.path.exists('/.dockerenv'): + + DOCKER_ENV = os.environ.get('DOCKER_ENV', 'false') + + if os.path.exists('/.dockerenv') or DOCKER_ENV == 'true': return 'docker' return sys.platform