Ctrl K
ComunidadGuías…

scout/plugins/__init__.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""插件系统 - 允许用户扩展 Scout 功能"""

from typing import Any, Dict, Optional, Callable, Awaitable
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
import json


class EventType(Enum):
    """事件类型"""
    # 消息事件
    BEFORE_CHAT = "before_chat"      # 对话前
    AFTER_CHAT = "after_chat"        # 对话后
    ON_MESSAGE = "on_message"        # 收到消息
    
    # 工具事件
    BEFORE_TOOL = "before_tool"      # 工具执行前
    AFTER_TOOL = "after_tool"        # 工具执行后
    
    # 系统事件
    ON_STARTUP = "on_startup"        # 启动时
    ON_SHUTDOWN = "on_shutdown"      # 关闭时
    ON_ERROR = "on_error"            # 错误时


@dataclass
class EventContext:
    """事件上下文 - 在插件间传递数据"""
    event_type: EventType
    data: Dict[str, Any] = field(default_factory=dict)
    stop_propagation: bool = False
    result: Optional[Any] = None
    
    def stop(self):
        """停止事件传播"""
        self.stop_propagation = True
    
    def get(self, key: str, default: Any = None) -> Any:
        return self.data.get(key, default)
    
    def set(self, key: str, value: Any):
        self.data[key] = value


class Plugin:
    """插件基类 - 所有插件必须继承此类"""
    
    # 子类必须设置这些属性
    name: str = ""
    version: str = "1.0.0"
    author: str = ""
    description: str = ""
    
    # 可选属性
    enabled: bool = True
    priority: int = 100  # 数字越小优先级越高
    # SPI 声明(2026-08-27):插件可提供核心组件替代实现,如 ["llm", "storage"]。
    # 需配套实现 provide(kind)。加载时由 PluginManager 自动注册进 SPIRegistry。
    provides: list = []
    
    def __init__(self, plugin_dir: Path = None):
        self.plugin_dir = plugin_dir or Path.cwd()
        self.data_dir = self.plugin_dir / "data"
        self.data_dir.mkdir(exist_ok=True)
        self.config: Dict[str, Any] = {}
        self._config_file = self.plugin_dir / "config.json"
        self.load_config()
    
    def load_config(self):
        """加载插件配置"""
        if self._config_file.exists():
            with open(self._config_file, 'r', encoding='utf-8') as f:
                self.config = json.load(f)
        else:
            self.config = {}
    
    def save_config(self):
        """保存插件配置"""
        with open(self._config_file, 'w', encoding='utf-8') as f:
            json.dump(self.config, f, indent=2, ensure_ascii=False)
    
    # 生命周期钩子(可选实现)
    async def on_load(self) -> None:
        """插件加载时调用"""
        pass
    
    async def on_unload(self) -> None:
        """插件卸载时调用"""
        pass
    
    async def on_enable(self) -> None:
        """插件启用时调用"""
        pass
    
    async def on_disable(self) -> None:
        """插件禁用时调用"""
        pass
    
    # SPI 提供(2026-08-27):返回 kind 对应的实现对象;不提供返回 None。
    # 同步方法:对象构造在加载期完成(连接/初始化交给应用侧异步进行)。
    # 示例:
    #   def provide(self, kind: str) -> Any:
    #       if kind == "llm":
    #           return MyLLMClient  # 传类或工厂,应用侧按 impl(**kwargs) 调用
    #       return None
    def provide(self, kind: str) -> Optional[Any]:
        """提供 SPI 实现(与 provides 声明配合,应用侧 get_provider 优先取用)"""
        return None

    # 事件处理器(可选实现,返回 True 表示已处理)
    async def on_event(self, ctx: EventContext) -> bool:
        """通用事件处理器"""
        return False
    
    # 便捷方法
    async def before_chat(self, message: str, session_id: str) -> Optional[str]:
        """对话前处理 - 返回非 None 则替换用户消息"""
        return None
    
    async def after_chat(self, message: str, response: str, session_id: str) -> Optional[str]:
        """对话后处理 - 返回非 None 则替换助手回复"""
        return None
    
    async def on_message(self, role: str, content: str, session_id: str) -> None:
        """消息记录"""
        pass
    
    def get_help(self) -> str:
        """获取帮助信息"""
        return f"{self.name} v{self.version}\n{self.description}"