Ctrl K
ComunidadGuías…

src/plugin_sdk/bridge_runtime.py

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

Archivo original ↗Preguntar a KiBot
"""Synchronous SDK for DiceFrame ``bot-extension`` process plugins."""

from __future__ import annotations

import json
import sys
from dataclasses import dataclass
from typing import Any, Callable, TextIO

from .contracts import BridgeHandler, JsonObject


@dataclass(frozen=True)
class _BridgeExtension:
    name: str
    title: str
    description: str
    stages: tuple[str, ...]
    priority: int
    timeout_sec: float
    platforms: tuple[str, ...]
    kinds: tuple[str, ...]
    handler: BridgeHandler


class BridgeExtensionRuntime:
    """Registers Bridge hooks/renderers and serves JSON-RPC over stdio."""

    protocol_version = 1

    def __init__(self, *, stdin: TextIO | None = None, stdout: TextIO | None = None) -> None:
        self.stdin = stdin or sys.stdin
        self.stdout = stdout or sys.stdout
        self._extensions: dict[str, _BridgeExtension] = {}

    def extension(
        self,
        *,
        name: str,
        title: str,
        description: str,
        stages: list[str] | tuple[str, ...],
        priority: int = 0,
        timeout_sec: float = 5,
        platforms: list[str] | tuple[str, ...] = (),
        kinds: list[str] | tuple[str, ...] = (),
    ) -> Callable[[BridgeHandler], BridgeHandler]:
        def register(handler: BridgeHandler) -> BridgeHandler:
            if name in self._extensions:
                raise ValueError(f"Bot Bridge 扩展名称重复:{name}")
            self._extensions[name] = _BridgeExtension(
                name=name,
                title=title,
                description=description,
                stages=tuple(stages),
                priority=int(priority),
                timeout_sec=float(timeout_sec),
                platforms=tuple(platforms),
                kinds=tuple(kinds),
                handler=handler,
            )
            return handler
        return register

    def run(self) -> None:
        for line in self.stdin:
            if not line.strip():
                continue
            request_id: Any = None
            try:
                request = json.loads(line)
                if not isinstance(request, dict) or request.get("jsonrpc") != "2.0":
                    raise ValueError("无效 JSON-RPC 请求")
                request_id = request.get("id")
                result = self._dispatch(str(request.get("method") or ""), request.get("params"))
                response = {"jsonrpc": "2.0", "id": request_id, "result": result}
            except Exception as exc:
                response = {
                    "jsonrpc": "2.0",
                    "id": request_id,
                    "error": {"code": -32000, "message": str(exc)},
                }
            self.stdout.write(json.dumps(response, ensure_ascii=False, separators=(",", ":")) + "\n")
            self.stdout.flush()

    def _dispatch(self, method: str, raw_params: Any) -> JsonObject:
        params = raw_params if isinstance(raw_params, dict) else {}
        if method == "initialize":
            requested = int(params.get("protocol_version") or 0)
            if requested != self.protocol_version:
                raise ValueError(f"不支持的协议版本:{requested}")
            return {
                "protocol_version": self.protocol_version,
                "bridge_extensions": [
                    {
                        "name": item.name,
                        "title": item.title,
                        "description": item.description,
                        "stages": list(item.stages),
                        "priority": item.priority,
                        "timeout_sec": item.timeout_sec,
                        "platforms": list(item.platforms),
                        "kinds": list(item.kinds),
                    }
                    for item in self._extensions.values()
                ],
            }
        if method == "bridge.apply":
            name = str(params.get("name") or "")
            stage = str(params.get("stage") or "")
            payload = params.get("payload")
            extension = self._extensions.get(name)
            if not extension:
                raise ValueError(f"Bot Bridge 扩展不存在:{name}")
            if stage not in extension.stages:
                raise ValueError(f"扩展 {name} 不支持阶段:{stage}")
            if not isinstance(payload, dict):
                raise ValueError("payload 必须是对象")
            result = extension.handler(stage, payload)
            if not isinstance(result, dict):
                raise ValueError("Bot Bridge 扩展必须返回 JSON 对象")
            return result
        raise ValueError(f"不支持的 RPC 方法:{method}")