"""插件 DTO。 定义插件相关的枚举、不可变值对象与 Protocol,包括权限枚举、环境变量、 领域事件、事件处理 Protocol、控制 RPC 处理 Protocol、配置加载 Protocol 与配置解密 Protocol。所有枚举继承 ``str, Enum`` 以支持 JSON 序列化, DTO 均为 ``dataclass(frozen=True)``,Protocol 使用 ``@runtime_checkable`` 装饰以支持 ``isinstance`` 检查。仅依赖标准库,用于插件生命周期、事件 分发、控制 RPC 与配置加载解密。 """ from __future__ import annotations from dataclasses import dataclass from datetime import datetime from enum import StrEnum from typing import TYPE_CHECKING, Any, Literal, Protocol, runtime_checkable from yuxi.channels.contract.dtos.common import BatchOperationFailure, Operator from yuxi.channels.contract.errors import ValidationError if TYPE_CHECKING: from yuxi.channels.contract.dtos.capability import ChannelCapabilities from yuxi.channels.contract.plugin.manifest import PluginManifest # 插件生命周期状态字面量类型(对齐 LifecycleState 枚举值)。 # 供 PluginSummary / PluginCatalogItem / PluginDetail 的 state 字段与 # Router 层 state 查询参数共用,避免 11 个取值在多处重复声明。 PluginStateLiteral = Literal[ "discovered", "resolved", "loaded", "initialized", "started", "paused", "stopped", "unloaded", "failed", "not_installed", "installed", ] class Permission(StrEnum): """权限等级。 标识插件或操作所需的用户权限等级,用于权限校验与审计。继承 ``str, Enum`` 以支持 JSON 序列化与字符串比较。 取值: REQUIRED_USER: 普通需求用户。 ADMIN_USER: 管理员用户。 SUPERADMIN_USER: 超级管理员用户。 """ REQUIRED_USER = "required_user" ADMIN_USER = "admin_user" SUPERADMIN_USER = "superadmin_user" @dataclass(frozen=True) class EnvVar: """环境变量声明。 描述插件所需的环境变量元信息,包括名称、描述、是否必填、默认值与 是否敏感,用于插件安装向导的环境变量校验与脱敏渲染。 字段: name: 环境变量名称。 description: 环境变量描述。 required: 是否必填(默认 True)。 default: 默认值(可选)。 sensitive: 是否敏感(需脱敏,默认 False)。 """ name: str description: str required: bool = True default: str | None = None sensitive: bool = False @dataclass(frozen=True) class DomainEvent: """领域事件。 描述插件生命周期或业务流程中产生的领域事件,包括事件 ID、事件类型、 负载、时间戳与链路追踪 ID,用于事件总线分发与审计。 字段: event_id: 事件 ID。 event_type: 事件类型(PluginLoaded | PairingApproved | ConfigChanged | ...)。 payload: 事件负载。 timestamp: 事件时间戳。 trace_id: 链路追踪 ID(可选)。 """ event_id: str event_type: str payload: dict[str, Any] timestamp: datetime trace_id: str | None = None @runtime_checkable class EventHandler(Protocol): """事件处理 Protocol。 由插件实现,用于订阅并处理领域事件。使用 ``@runtime_checkable`` 装饰以支持 ``isinstance`` 检查。事件处理为异步方法,避免阻塞 事件总线分发线程。 """ async def handle(self, event: DomainEvent) -> None: """处理领域事件。 参数: event: 领域事件。 """ ... @runtime_checkable class ConfigLoader(Protocol): """配置加载 Protocol。 由插件实现,用于加载插件配置并返回配置字典。使用 ``@runtime_checkable`` 装饰以支持 ``isinstance`` 检查。配置加载 为同步方法,适用于启动期一次性加载场景。 """ def load(self) -> dict[str, Any]: """加载插件配置。 返回: 配置字典。 """ ... @runtime_checkable class ConfigDecryptor(Protocol): """配置解密 Protocol。 由插件实现,用于解密敏感配置值。使用 ``@runtime_checkable`` 装饰 以支持 ``isinstance`` 检查。解密为同步方法,适用于配置加载后 的就地解密场景。 """ def decrypt(self, value: str) -> str: """解密配置值。 参数: value: 待解密的配置值。 返回: 解密后的明文配置值。 """ ... @dataclass(frozen=True) class PluginSummary: """插件摘要(PLG-QUERY-01 列表项)。 字段: plugin_id: 插件唯一标识(manifest.manifest.id)。 name: 插件显示名称(manifest.manifest.name)。 version: 插件版本(manifest.manifest.version)。 channel_type: 绑定渠道类型(manifest.manifest.channel_type)。 state: 当前生命周期状态(LifecycleState.value)。 """ plugin_id: str name: str version: str channel_type: str state: PluginStateLiteral @dataclass(frozen=True) class PluginCatalogItem: """插件目录项(PLG-CATALOG 卡片视图数据)。 与 ``PluginSummary`` 的区别在于携带 ``capabilities`` 与 ``icon`` 字段, 供前端卡片网格展示能力摘要与渠道图标。``capabilities`` 为 ``ChannelCapabilities`` dataclass,由 Router 层 ``serialize_control_data`` 递归序列化。 字段: plugin_id: 插件唯一标识。 name: 插件显示名称。 version: 插件版本。 channel_type: 绑定渠道类型。 state: 当前生命周期状态。 capabilities: 渠道能力集合(静态声明)。 icon: 渠道展示图标名(lucide 图标名,缺失时前端回退默认图标)。 来源为 ``ChannelManifest.icon``,供前端卡片/筛选/标签等场景 统一渲染渠道图标,避免在前端硬编码渠道清单。 """ plugin_id: str name: str version: str channel_type: str state: PluginStateLiteral capabilities: ChannelCapabilities icon: str | None = None @dataclass(frozen=True) class PluginDetail: """插件详情(PLG-QUERY-02 响应)。 字段: plugin_id: 插件唯一标识。 name: 插件显示名称。 version: 插件版本。 channel_type: 绑定渠道类型。 state: 当前生命周期状态。 manifest: 完整 PluginManifest(含内嵌 ChannelManifest 与 adapters / stages / event_subscriptions / config_sources)。 ChannelManifest 含 capabilities / provides / depends / config_schema / failure_policy / resource_quota 等字段。 last_error: 最近一次生命周期失败错误信息(可选),供详情页排障展示。 """ plugin_id: str name: str version: str channel_type: str state: PluginStateLiteral manifest: PluginManifest last_error: str | None = None @dataclass(frozen=True) class InstallPluginCmd: """安装插件命令(PLG-INSTALL)。 由 ``PluginManagementPort.installPlugin`` 引用,从来源安装插件,需记录 操作人以满足审计要求(超级管理员)。 字段: operator: 操作人(审计用)。 source_type: 来源类型(``path`` / ``url`` / ``registry``)。 source: 来源路径或 URL。 version: 指定版本(可选)。 force: 是否强制覆盖已存在插件(默认 False)。 """ operator: Operator source_type: Literal["path", "url", "registry"] source: str version: str | None = None force: bool = False def __post_init__(self) -> None: """校验必填字段非空与 source_type 取值(PLG-INSTALL)。 ``source`` 必须非空,``source_type`` 必须为 ``path`` / ``url`` / ``registry`` 之一,在构造时即抛出 ``ValidationError``,adapter 不再做 该校验(INV-8)。 """ if not self.source: raise ValidationError("source", "must not be empty") if self.source_type not in ("path", "url", "registry"): raise ValidationError( "source_type", "must be one of: path, url, registry", ) @dataclass(frozen=True) class InstallPluginResult: """安装插件结果(PLG-INSTALL)。 字段: plugin_id: 插件 ID。 version: 安装的插件版本。 installed_at: 安装时间戳。 requires_load: 是否需要手动加载(默认 True)。 """ plugin_id: str version: str installed_at: datetime requires_load: bool = True @dataclass(frozen=True) class UninstallPluginResult: """卸载插件结果(PLG-UNINSTALL-FILE)。 字段: plugin_id: 插件 ID。 uninstalled_at: 卸载时间戳。 files_removed: 已删除的文件列表。 """ plugin_id: str uninstalled_at: datetime files_removed: tuple[str, ...] @dataclass(frozen=True) class PluginConfigResult: """插件配置查询结果(PLG-CONFIG)。 字段: plugin_id: 插件 ID。 config: 当前配置值。 schema: 配置 schema(从 PluginManifest 获取)。 requires_restart: 是否需要重启生效。 updated_at: 配置最后更新时间(可选)。 """ plugin_id: str config: dict[str, Any] schema: dict[str, Any] requires_restart: bool updated_at: datetime | None = None @dataclass(frozen=True) class UpdatePluginConfigCmd: """更新插件配置命令(PLG-CONFIG-PUT)。 由 ``PluginManagementPort.updatePluginConfig`` 引用,按 ``apply_mode`` 判断是否需要重启,需记录操作人以满足审计要求。 字段: plugin_id: 插件 ID。 operator: 操作人(审计用)。 config: 配置值。 apply_mode: 应用模式(``hot`` / ``restart_required``)。 """ plugin_id: str operator: Operator config: dict[str, Any] apply_mode: Literal["hot", "restart_required"] def __post_init__(self) -> None: """校验必填字段非空与 apply_mode 取值(PLG-CONFIG-PUT)。 ``plugin_id`` 必须非空,``config`` 必须非空字典,``apply_mode`` 必须为 ``hot`` / ``restart_required`` 之一,在构造时即抛出 ``ValidationError``,adapter 不再做该校验(INV-8)。 """ if not self.plugin_id: raise ValidationError("plugin_id", "must not be empty") if not self.config: raise ValidationError("config", "must not be empty") if self.apply_mode not in ("hot", "restart_required"): raise ValidationError( "apply_mode", "must be one of: hot, restart_required", ) @dataclass(frozen=True) class BatchPluginLifecycleSuccessItem: """批量插件生命周期成功条目(PLG-BATCH-LIFE)。 字段: plugin_id: 插件 ID。 new_state: 新生命周期状态。 """ plugin_id: str new_state: str @dataclass(frozen=True) class BatchPluginLifecycleCmd: """批量插件生命周期命令(PLG-BATCH-LIFE)。 逐条独立事务执行批量启动/停止(模式 D),``plugin_ids`` 与 ``filter`` 二选一:不可同时为空、不可同时提供(由 ``__post_init__`` 校验)。 字段: operator: 操作人(审计用)。 plugin_ids: 显式插件 ID 元组(默认空元组)。 filter: 筛选条件(含 channel_type / state,可选)。 """ operator: Operator plugin_ids: tuple[str, ...] = () filter: dict[str, Any] | None = None def __post_init__(self) -> None: """校验 plugin_ids 与 filter 互斥性与非空(PLG-BATCH-LIFE)。 ``plugin_ids`` 与 ``filter`` 二选一:不可同时为空、不可同时提供, ``plugin_ids`` 各项不得为空字符串。在构造时即抛出 ``ValidationError``, 使非法输入在 Router 层即被拒绝(INV-8)。 """ has_ids = bool(self.plugin_ids) has_filter = self.filter is not None if not has_ids and not has_filter: raise ValidationError( "plugin_ids", "either plugin_ids or filter must be provided", ) if has_ids and has_filter: raise ValidationError( "plugin_ids", "plugin_ids and filter are mutually exclusive", ) if has_ids and not all(pid for pid in self.plugin_ids): raise ValidationError( "plugin_ids", "plugin_ids must not contain empty strings", ) @dataclass(frozen=True) class BatchPluginLifecycleResult: """批量插件生命周期结果(PLG-BATCH-LIFE)。 描述逐条独立事务执行批量启动/停止的执行结果,``failed`` 使用通用 ``BatchOperationFailure``(``id`` 字段承载 plugin_id)。 字段: total: 待操作插件总数。 succeeded: 成功条目元组。 failed: 失败条目元组。 """ total: int succeeded: tuple[BatchPluginLifecycleSuccessItem, ...] failed: tuple[BatchOperationFailure, ...]