feat(plugin): add virtual plugin instances

This commit is contained in:
jxxghp
2026-08-22 09:40:38 +08:00
parent 4061868d25
commit 7b9499fc5b
21 changed files with 891 additions and 114 deletions
+4 -2
View File
@@ -246,11 +246,13 @@ SCHEMA_EXPORTS = {
'Path': ('app.schemas.transfer', 'Path'),
'PerformanceSnapshot': ('app.schemas.monitoring', 'PerformanceSnapshot'),
'Plugin': ('app.schemas.plugin', 'Plugin'),
'PluginCloneRequest': ('app.schemas.plugin', 'PluginCloneRequest'),
'PluginDashboard': ('app.schemas.plugin', 'PluginDashboard'),
'PluginDashboardMetaItem': ('app.schemas.plugin', 'PluginDashboardMetaItem'),
'PluginDataResetEventData': ('app.schemas.event', 'PluginDataResetEventData'),
'PluginFolderConfigData': ('app.schemas.plugin', 'PluginFolderConfigData'),
'PluginFoldersData': ('app.schemas.plugin', 'PluginFoldersData'),
'PluginInstance': ('app.schemas.plugin', 'PluginInstance'),
'PluginMarketSyncData': ('app.schemas.system', 'PluginMarketSyncData'),
'PluginMarketSyncRequest': ('app.schemas.system', 'PluginMarketSyncRequest'),
'PluginMemoryInfo': ('app.schemas.plugin', 'PluginMemoryInfo'),
@@ -423,7 +425,7 @@ SCHEMA_CONFLICTS = {
'FilterRuleGroup': ['app.schemas.rule', 'app.schemas.system'],
'JsonData': ['app.schemas.agent', 'app.schemas.common', 'app.schemas.context', 'app.schemas.dashboard', 'app.schemas.event', 'app.schemas.history', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.mfa', 'app.schemas.music', 'app.schemas.plugin', 'app.schemas.search', 'app.schemas.openai', 'app.schemas.servcookie', 'app.schemas.site', 'app.schemas.user', 'app.schemas.workflow', 'app.schemas.mcp'],
'List': ['app.schemas.agent', 'app.schemas.context', 'app.schemas.event', 'app.schemas.history', 'app.schemas.mediaserver', 'app.schemas.message', 'app.schemas.monitoring', 'app.schemas.plugin', 'app.schemas.openai', 'app.schemas.subscribe', 'app.schemas.transfer', 'app.schemas.workflow'],
'Literal': ['app.schemas.agent', 'app.schemas.music', 'app.schemas.search', 'app.schemas.servcookie', 'app.schemas.mcp'],
'Literal': ['app.schemas.agent', 'app.schemas.music', 'app.schemas.plugin', 'app.schemas.search', 'app.schemas.servcookie', 'app.schemas.mcp'],
'LocaleHelper': ['app.schemas.dashboard', 'app.schemas.response'],
'MediaInfo': ['app.schemas.context', 'app.schemas.system', 'app.schemas.transfer', 'app.schemas.workflow'],
'MediaSource': ['app.schemas.cache', 'app.schemas.context', 'app.schemas.event', 'app.schemas.history', 'app.schemas.mediaserver', 'app.schemas.music', 'app.schemas.subscribe', 'app.schemas.transfer'],
@@ -448,6 +450,6 @@ SCHEMA_CONFLICTS = {
'UserPermissions': ['app.schemas.token', 'app.schemas.user'],
'dataclass': ['app.schemas.notification', 'app.schemas.system'],
'datetime': ['app.schemas.agent', 'app.schemas.monitoring'],
'field_validator': ['app.schemas.event', 'app.schemas.message', 'app.schemas.music', 'app.schemas.response', 'app.schemas.subscribe', 'app.schemas.system'],
'field_validator': ['app.schemas.event', 'app.schemas.message', 'app.schemas.music', 'app.schemas.plugin', 'app.schemas.response', 'app.schemas.subscribe', 'app.schemas.system'],
'model_validator': ['app.schemas.dashboard', 'app.schemas.event', 'app.schemas.mediaserver', 'app.schemas.subscribe'],
}
+48 -2
View File
@@ -1,7 +1,7 @@
from enum import Enum as _Enum
from typing import Optional, List, Dict, Union
from typing import Literal, Optional, List, Dict, Union
from pydantic import BaseModel, Field, RootModel
from pydantic import BaseModel, Field, RootModel, field_validator
from app.schemas.common import JsonData
@@ -17,6 +17,27 @@ class PluginRuntimeStatus(str, _Enum):
LOAD_FAILED = "load_failed"
class PluginInstance(BaseModel):
"""持久化一个共享源码插件的独立运行实例。"""
instance_id: str = Field(description="运行实例 ID,也是配置、数据和路由命名空间")
source_plugin_id: str = Field(description="提供代码与前端资源的源插件 ID")
plugin_name: Optional[str] = Field(default=None, description="实例展示名称")
plugin_desc: Optional[str] = Field(default=None, description="实例展示描述")
plugin_icon: Optional[str] = Field(default=None, description="实例展示图标")
mode: Literal["virtual"] = Field(default="virtual", description="实例实现模式")
@field_validator("instance_id", "source_plugin_id")
@classmethod
def validate_plugin_id(cls, value: str) -> str:
"""限制实例标识为可安全用作 Python 类名和路由段的格式。"""
if not value or not value[0].isalpha() or not value.isalnum():
raise ValueError("插件 ID 必须以字母开头且只能包含字母和数字")
if len(value) > 128:
raise ValueError("插件 ID 长度不能超过 128 个字符")
return value
class Plugin(BaseModel):
"""
插件信息
@@ -72,6 +93,12 @@ class Plugin(BaseModel):
add_time: Optional[int] = 0
# 插件公钥
plugin_public_key: Optional[str] = None
# 共享代码与前端资源的源插件 ID;普通插件为空
source_plugin_id: Optional[str] = None
# 是否为共享源码的虚拟实例
is_instance: Optional[bool] = False
# 实例实现模式;存量物理分身为空
instance_mode: Optional[str] = None
class PluginRuntimeSummary(BaseModel):
@@ -83,6 +110,24 @@ class PluginRuntimeSummary(BaseModel):
failed_count: int = Field(description="加载失败或被策略阻止的插件数量")
class PluginCloneRequest(BaseModel):
"""创建虚拟插件分身的请求参数。"""
suffix: str = Field(
min_length=1,
max_length=20,
pattern=r"^[A-Za-z0-9]+$",
description="追加到当前插件 ID 后的 ASCII 字母或数字后缀",
)
name: str = Field(default="", description="分身展示名称")
description: str = Field(default="", description="分身展示描述")
icon: Optional[str] = Field(default=None, description="分身展示图标")
version: Optional[str] = Field(
default=None,
description="兼容旧客户端保留,虚拟分身始终跟随源插件版本",
)
class PluginDashboard(Plugin):
"""
插件仪表盘
@@ -164,6 +209,7 @@ class PluginRemoteInfo(BaseModel):
id: str
url: str
name: str
source_plugin_id: Optional[str] = None
class PluginReleaseItem(BaseModel):
+2
View File
@@ -392,6 +392,8 @@ class SystemConfigKey(Enum):
UserCustomCSS = "UserCustomCSS"
# 用户已安装的插件
UserInstalledPlugins = "UserInstalledPlugins"
# 共享源码插件的虚拟运行实例
PluginInstances = "PluginInstances"
# 插件文件夹分组配置
PluginFolders = "PluginFolders"
# 默认电影订阅规则