feat(plugin): basic communication

This commit is contained in:
Junyan Qin
2025-06-14 17:27:21 +08:00
parent 2d06f1cadb
commit 6f2fd72af6
4 changed files with 150 additions and 86 deletions

View File

@@ -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

View File

@@ -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,
# }

View File

@@ -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,
)

View File

@@ -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