| """ |
| 插件数据模型 |
| 定义插件元数据规范和数据类 |
| """ |
|
|
| from typing import Dict, List, Optional, Any |
| from enum import Enum |
| from pydantic import BaseModel, Field, field_validator, model_validator, ValidationError |
| from datetime import datetime |
|
|
|
|
| class PluginStatus(str, Enum): |
| """插件状态枚举 |
| |
| 状态流转: |
| - discovered: 启动扫描发现,未加载 |
| - loaded: 加载完成,等待启用 |
| - enabled: 用户启用,可用 |
| - disabled: 用户禁用 |
| - error: 加载/运行错误 |
| - dependency_error: 依赖缺失或版本不匹配 |
| - incomplete: 元数据不完整(缺分类、能力标签等) |
| """ |
|
|
| |
| INSTALLED = "installed" |
| INSTALLING = "installing" |
| UNINSTALLING = "uninstalling" |
|
|
| |
| DISCOVERED = "discovered" |
| LOADED = "loaded" |
| ENABLED = "enabled" |
| DISABLED = "disabled" |
| ERROR = "error" |
| DEPENDENCY_ERROR = "dependency_error" |
| INCOMPLETE = "incomplete" |
|
|
|
|
| class PluginDependency(BaseModel): |
| """插件依赖""" |
|
|
| name: str |
| version: str |
| optional: bool = False |
|
|
|
|
| class PluginMetadata(BaseModel): |
| """插件元数据 - plugin.json 结构""" |
|
|
| name: str = Field(..., description="插件唯一标识符") |
| version: str = Field(..., description="插件版本") |
| description: str = Field(..., description="插件描述") |
| author: str = Field(..., description="插件作者") |
|
|
| |
| homepage: Optional[str] = Field(None, description="插件主页URL") |
| ui_path: Optional[str] = Field(None, description="插件UI路径") |
|
|
| |
| requirements: List[str] = Field(default_factory=list, description="Python依赖") |
| dependencies: List[PluginDependency] = Field( |
| default_factory=list, description="插件依赖" |
| ) |
| config_schema: Optional[Dict[str, Any]] = Field(None, description="配置模式") |
|
|
| @field_validator("name") |
| @classmethod |
| def validate_name(cls, v): |
| """验证插件名称""" |
| if not v.replace("_", "").isalnum(): |
| raise ValueError("插件名称只能包含字母、数字和下划线") |
| return v.lower() |
|
|
| @field_validator("version") |
| @classmethod |
| def validate_version(cls, v): |
| """验证版本格式""" |
| |
| if not all(part.isdigit() for part in v.split(".")[:3]): |
| raise ValueError("版本号格式应为 X.Y.Z") |
| return v |
|
|
| def __init__(self, **data): |
| """初始化 - 自动映射 homepage 到 ui_path""" |
| |
| if "ui_path" not in data and "homepage" in data: |
| data["ui_path"] = data["homepage"] |
| super().__init__(**data) |
|
|
|
|
| class PluginInfo(BaseModel): |
| """插件完整信息""" |
|
|
| |
| metadata: PluginMetadata |
|
|
| |
| status: PluginStatus = PluginStatus.INSTALLED |
| install_path: str |
| install_time: datetime = Field(default_factory=datetime.now) |
|
|
| |
| api_routes: List[str] = Field(default_factory=list) |
| ui_routes: List[str] = Field(default_factory=list) |
|
|
| |
| error_message: Optional[str] = None |
| last_error: Optional[str] = None |
|
|
| class Config: |
| json_encoders = {datetime: lambda v: v.isoformat()} |
|
|
|
|
| class PluginConfig(BaseModel): |
| """插件配置""" |
|
|
| enabled: bool = False |
| settings: Dict[str, Any] = Field(default_factory=dict) |
|
|
|
|
| class PluginOperationResult(BaseModel): |
| """插件操作结果""" |
|
|
| success: bool |
| message: str |
| plugin_name: Optional[str] = None |
| error: Optional[str] = None |
| plugin_info: Optional[Dict[str, Any]] = None |
|
|
| @classmethod |
| def success_result(cls, plugin_name: str, message: str) -> "PluginOperationResult": |
| return cls(success=True, plugin_name=plugin_name, message=message) |
|
|
| @classmethod |
| def error_result(cls, plugin_name: str, error: str) -> "PluginOperationResult": |
| return cls( |
| success=False, plugin_name=plugin_name, message="操作失败", error=error |
| ) |
|
|
|
|
| |
| |
| |
|
|
|
|
| class PluginErrorStage(str, Enum): |
| """错误发生阶段""" |
|
|
| STRUCTURE = "structure" |
| DEPENDENCY = "dependency" |
| LOAD = "load" |
| RUNTIME = "runtime" |
| MCP = "mcp" |
|
|
|
|
| class PluginErrorInfo(BaseModel): |
| """插件错误信息 |
| |
| 用于记录单个插件的结构化错误,支持页面展示和诊断。 |
| """ |
|
|
| error_code: str = Field(..., description="错误代码,如 MISSING_MAIN_PY") |
| message: str = Field(..., description="用户可读错误消息") |
| stage: PluginErrorStage = Field(..., description="错误发生阶段") |
| detail: Optional[str] = Field(None, description="技术细节,用于调试") |
| occurred_at: datetime = Field(default_factory=datetime.now, description="发生时间") |
|
|
|
|
| class DependencyCheckStatus(str, Enum): |
| """依赖检查状态""" |
|
|
| SATISFIED = "satisfied" |
| MISSING = "missing" |
| VERSION_MISMATCH = "version_mismatch" |
| CHECK_FAILED = "check_failed" |
|
|
|
|
| class DependencyItem(BaseModel): |
| """单个依赖项检查结果""" |
|
|
| name: str = Field(..., description="包名") |
| required: Optional[str] = Field(None, description="要求版本,如 >=1.0.0") |
| installed: Optional[str] = Field(None, description="已安装版本") |
| status: DependencyCheckStatus = Field(..., description="检查状态") |
| message: Optional[str] = Field(None, description="状态说明") |
|
|
|
|
| class DependencyCheckResult(BaseModel): |
| """依赖检查结果""" |
|
|
| plugin_name: str = Field(..., description="插件名") |
| overall_status: DependencyCheckStatus = Field(..., description="整体状态") |
| items: List[DependencyItem] = Field(default_factory=list, description="依赖项列表") |
| missing_count: int = Field(0, description="缺失数量") |
| mismatch_count: int = Field(0, description="版本不匹配数量") |
| checked_at: datetime = Field(default_factory=datetime.now, description="检查时间") |
|
|
|
|
| |
| |
| |
|
|
|
|
| class PluginCategory(str, Enum): |
| """插件分类""" |
|
|
| CONTENT_EXTRACTION = "content_extraction" |
| BROWSER = "browser" |
| SANDBOX = "sandbox" |
| UTILITY = "utility" |
| OTHER = "other" |
|
|
|
|
| class PluginGovernanceInfo(BaseModel): |
| """插件治理信息 |
| |
| 用于插件中心展示分类、能力标签、依赖风险和处理决策。 |
| """ |
|
|
| category: PluginCategory = Field( |
| default=PluginCategory.OTHER, description="插件分类" |
| ) |
| capabilities: List[str] = Field(default_factory=list, description="能力标签列表") |
| has_mcp_tools: bool = Field(False, description="是否声明 MCP 工具") |
| has_api_routes: bool = Field(False, description="是否提供 API 路由") |
| has_ui: bool = Field(False, description="是否有前端 UI") |
| dependency_risk: Optional[str] = Field(None, description="依赖风险说明") |
| handling_decision: Optional[str] = Field(None, description="处理决策说明") |
|
|
|
|
| |
| |
| |
|
|
|
|
| class PluginToolSummary(BaseModel): |
| """插件 Tool 摘要 |
| |
| 用于插件列表和详情页展示 Tool 状态。 |
| """ |
|
|
| total: int = Field(0, description="Tool 总数") |
| available: int = Field(0, description="可用数量") |
| incomplete: int = Field(0, description="元数据不完整数量") |
|
|
|
|
| class PluginMcpSummary(BaseModel): |
| """插件 MCP 摘要 |
| |
| 用于插件列表和详情页展示 MCP 工具状态。 |
| """ |
|
|
| total: int = Field(0, description="MCP 工具总数") |
| available: int = Field(0, description="可用数量") |
| unavailable: int = Field(0, description="不可用数量") |
|
|
|
|
| |
| |
| |
|
|
|
|
| class PluginUIType(str, Enum): |
| """插件 UI 类型""" |
|
|
| STATIC = "static" |
| SCHEMA = "schema" |
| NONE = "none" |
|
|
|
|
| class UISubmitContract(BaseModel): |
| """Schema UI 提交契约 |
| |
| 定义前端表单提交的目标和方式。 |
| """ |
|
|
| method: str = Field( |
| default="POST", description="HTTP 方法,只允许 POST" |
| ) |
| path: str = Field(..., description="提交路径,如 /plugins/{name}/api/run") |
| content_type: str = Field( |
| default="application/json", description="请求内容类型" |
| ) |
| success_path: Optional[str] = Field( |
| None, description="成功响应结果的 JSON 路径" |
| ) |
| error_path: Optional[str] = Field( |
| None, description="错误响应消息的 JSON 路径" |
| ) |
|
|
| @field_validator("method") |
| @classmethod |
| def validate_method(cls, v): |
| """只允许 POST 方法""" |
| if v.upper() != "POST": |
| raise ValueError("submit.method 只允许 POST") |
| return v.upper() |
|
|
| @field_validator("path") |
| @classmethod |
| def validate_path(cls, v): |
| """路径必须是相对路径""" |
| if v.startswith("http://") or v.startswith("https://"): |
| raise ValueError("submit.path 不能是外部 URL") |
| if not v.startswith("/"): |
| raise ValueError("submit.path 必须以 / 开头") |
| return v |
|
|
|
|
| class UIFieldDefinition(BaseModel): |
| """Schema UI 字段定义""" |
|
|
| name: str = Field(..., description="字段名") |
| label: str = Field(..., description="显示标签") |
| type: str = Field(default="string", description="字段类型:string/number/boolean/select") |
| required: bool = Field(default=False, description="是否必填") |
| default: Optional[Any] = Field(None, description="默认值") |
| placeholder: Optional[str] = Field(None, description="占位提示") |
| help_text: Optional[str] = Field(None, description="帮助文本") |
| validation: Optional[Dict[str, Any]] = Field(None, description="验证规则") |
|
|
|
|
| class UISchemaDefinition(BaseModel): |
| """Schema UI 定义""" |
|
|
| title: str = Field(..., description="表单标题") |
| description: Optional[str] = Field(None, description="表单描述") |
| fields: List[UIFieldDefinition] = Field( |
| default_factory=list, description="字段列表" |
| ) |
| result_schema: Optional[Dict[str, Any]] = Field( |
| None, description="结果展示 schema" |
| ) |
|
|
|
|
| class PluginUIEntry(BaseModel): |
| """插件 UI 入口 |
| |
| 定义插件运行页的 UI 类型和入口信息。 |
| """ |
|
|
| type: PluginUIType = Field(..., description="UI 类型") |
| entry_path: Optional[str] = Field(None, description="静态 UI 路径,type=static 时必填") |
| schema: Optional[UISchemaDefinition] = Field( |
| None, description="Schema UI 定义,type=schema 时必填" |
| ) |
| submit: Optional[UISubmitContract] = Field( |
| None, description="提交契约,type=schema 时必填" |
| ) |
| unavailable_reason: Optional[str] = Field( |
| None, description="UI 不可用原因" |
| ) |
|
|
| @field_validator("entry_path") |
| @classmethod |
| def validate_entry_path(cls, v, info): |
| """静态 UI 路径校验""" |
| |
| if v and (".." in v or v.startswith("http")): |
| raise ValueError("entry_path 不允许路径穿越或外部 URL") |
| return v |
|
|
| @model_validator(mode="after") |
| def validate_static_ui_entry_path(self): |
| """验证 type=static 时 entry_path 必填""" |
| if self.type == PluginUIType.STATIC and not self.entry_path: |
| raise ValueError("type=static 时 entry_path 必填") |
| return self |
|
|
|
|
| |
| |
| |
|
|
|
|
| class PluginStateEntry(BaseModel): |
| """插件状态文件中的单条记录 |
| |
| 保存用户启停状态,与 plugin.json 的 enabled 字段独立。 |
| """ |
|
|
| enabled: bool = Field(False, description="是否启用") |
| updated_at: datetime = Field( |
| default_factory=datetime.now, description="最后更新时间" |
| ) |
| updated_by: str = Field( |
| default="system", description="更新者:system/user" |
| ) |
|
|
|
|
| class PluginStateStore(BaseModel): |
| """插件状态文件模型 |
| |
| 事实源:data/plugin_state.json |
| 以插件名为 key 保存用户启停状态。 |
| """ |
|
|
| plugins: Dict[str, PluginStateEntry] = Field( |
| default_factory=dict, description="插件状态字典" |
| ) |
| version: int = Field(default=1, description="文件版本号") |
|
|
|
|
| |
| |
| |
|
|
|
|
| class PluginListItem(BaseModel): |
| """插件列表项 |
| |
| 用于 GET /api/plugins/ 响应。 |
| """ |
|
|
| metadata: PluginMetadata = Field(..., description="插件元数据") |
| status: PluginStatus = Field(..., description="插件状态") |
| dependency_summary: Optional[DependencyCheckResult] = Field( |
| None, description="依赖检查摘要" |
| ) |
| governance_summary: Optional[PluginGovernanceInfo] = Field( |
| None, description="治理信息摘要" |
| ) |
| tool_summary: PluginToolSummary = Field( |
| default_factory=PluginToolSummary, description="Tool 摘要" |
| ) |
| mcp_summary: PluginMcpSummary = Field( |
| default_factory=PluginMcpSummary, description="MCP 摘要" |
| ) |
| ui_entry: Optional[PluginUIEntry] = Field(None, description="UI 入口") |
| errors: List[PluginErrorInfo] = Field( |
| default_factory=list, description="错误列表" |
| ) |
|
|
|
|
| class PluginDetail(BaseModel): |
| """插件详情 |
| |
| 用于 GET /api/plugins/{plugin_name} 响应。 |
| """ |
|
|
| metadata: PluginMetadata = Field(..., description="插件元数据") |
| status: PluginStatus = Field(..., description="插件状态") |
| install_path: str = Field(..., description="安装路径") |
| install_time: Optional[datetime] = Field(None, description="安装时间") |
|
|
| |
| dependency_check: Optional[DependencyCheckResult] = Field( |
| None, description="依赖检查详情" |
| ) |
|
|
| |
| governance: Optional[PluginGovernanceInfo] = Field( |
| None, description="治理信息" |
| ) |
|
|
| |
| api_routes: List[str] = Field(default_factory=list, description="API 路由列表") |
|
|
| |
| tools: List[Dict[str, Any]] = Field( |
| default_factory=list, description="Tool 详情列表" |
| ) |
|
|
| |
| mcp_tools: List[Dict[str, Any]] = Field( |
| default_factory=list, description="MCP 工具列表" |
| ) |
|
|
| |
| ui_entry: Optional[PluginUIEntry] = Field(None, description="UI 入口") |
|
|
| |
| errors: List[PluginErrorInfo] = Field( |
| default_factory=list, description="错误列表" |
| ) |
|
|
| class Config: |
| json_encoders = {datetime: lambda v: v.isoformat()} |
|
|
|
|
| |
| |
| |
|
|
|
|
| class ToolAvailability(str, Enum): |
| """Tool 可用状态""" |
|
|
| AVAILABLE = "available" |
| UNAVAILABLE = "unavailable" |
| INCOMPLETE = "incomplete" |
|
|
|
|
| class ToolCatalogItem(BaseModel): |
| """Tool 目录项 |
| |
| 用于 GET /api/tools 和 GET /api/tools/{tool_name} 响应。 |
| 包含中文名称、用途、参数、示例、错误说明和可用状态。 |
| """ |
|
|
| name: str = Field(..., description="工具标识符") |
| display_name: str = Field(..., description="中文显示名称") |
| source: str = Field(..., description="来源:plugin/platform") |
| plugin_name: str = Field(..., description="所属插件名") |
| purpose: str = Field(..., description="用途说明") |
| applies_to: List[str] = Field( |
| default_factory=list, description="适用场景列表" |
| ) |
| not_for: List[str] = Field( |
| default_factory=list, description="不适用场景列表" |
| ) |
| parameters: Dict[str, Any] = Field( |
| default_factory=dict, description="参数 schema" |
| ) |
| input_examples: List[Dict[str, Any]] = Field( |
| default_factory=list, description="输入示例" |
| ) |
| output_examples: List[Dict[str, Any]] = Field( |
| default_factory=list, description="输出示例" |
| ) |
| common_errors: List[Dict[str, str]] = Field( |
| default_factory=list, description="常见错误列表" |
| ) |
| risk_level: str = Field( |
| default="low", description="风险等级:low/medium/high" |
| ) |
| availability: ToolAvailability = Field( |
| default=ToolAvailability.AVAILABLE, description="可用状态" |
| ) |
| unavailable_reason: Optional[str] = Field( |
| None, description="不可用原因" |
| ) |
| incomplete_reasons: Optional[List[str]] = Field( |
| None, description="元数据不完整原因列表" |
| ) |