125 lines
5.3 KiB
Python
125 lines
5.3 KiB
Python
|
|
"""IntegrationOperationRegistry:厂商操作分派注册表。
|
|||
|
|
|
|||
|
|
实现 ``core/contracts.py::IntegrationOperationRegistry`` 端口(结构化子类型)。
|
|||
|
|
|
|||
|
|
按 ``(operation, source_type)`` 二元组分派 handler。支持 discover / preview_tools /
|
|||
|
|
create_tools 三类操作。
|
|||
|
|
|
|||
|
|
handler 签名(``OperationHandler``):``(source_type: str, system_config: dict[str, Any]) -> Awaitable[Any]``,
|
|||
|
|
2 参数纯 dict,不依赖 ORM/DB。Token/解密/DB 由 use_cases 层预处理后注入 ``system_config``。
|
|||
|
|
|
|||
|
|
source_type 解析职责由 use_cases 层完成(端口注释 L842-L844),本注册表只接收
|
|||
|
|
已解析的 source_type。草稿持久化职责由 use_cases 层统一处理(端口注释 L846-L847)。
|
|||
|
|
|
|||
|
|
键冲突策略:同一 ``(operation, source_type)`` 重复注册且 handler 不同时抛
|
|||
|
|
``DomainValidationError``,与 framework 层注册表策略一致。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
from collections.abc import Awaitable, Callable
|
|||
|
|
from typing import Any
|
|||
|
|
|
|||
|
|
from yuxi.external_systems.exceptions import (
|
|||
|
|
DomainValidationError,
|
|||
|
|
IntegrationOperationNotRegisteredError,
|
|||
|
|
)
|
|||
|
|
from yuxi.external_systems.integrations.schemas import GeneratedToolsDraft
|
|||
|
|
|
|||
|
|
# handler 类型别名:与端口签名一致,2 参数 + Awaitable 返回
|
|||
|
|
OperationHandler = Callable[
|
|||
|
|
[str, dict[str, Any]],
|
|||
|
|
Awaitable[Any],
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# 允许的 operation 集合
|
|||
|
|
_ALLOWED_OPERATIONS = frozenset({"discover", "preview_tools", "create_tools"})
|
|||
|
|
|
|||
|
|
|
|||
|
|
class IntegrationOperationRegistry:
|
|||
|
|
"""厂商操作分派注册表,实现 core/contracts.py::IntegrationOperationRegistry 端口。
|
|||
|
|
|
|||
|
|
实现 Protocol 的 3 个分派方法(discover / preview_tools / create_tools),
|
|||
|
|
并额外提供 register / get / list_operations / list_source_types / clear
|
|||
|
|
供框架内部使用(与 AdapterRegistry 模式一致)。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
_handlers: dict[tuple[str, str], OperationHandler] = {}
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def register(cls, operation: str, source_type: str, handler: OperationHandler) -> None:
|
|||
|
|
"""注册 (operation, source_type) 对应的 handler。
|
|||
|
|
|
|||
|
|
键冲突抛 DomainValidationError。
|
|||
|
|
operation 限定为 discover / preview_tools / create_tools 三者之一。
|
|||
|
|
"""
|
|||
|
|
if operation not in _ALLOWED_OPERATIONS:
|
|||
|
|
raise DomainValidationError(f"不支持的集成操作: {operation},仅允许 {_ALLOWED_OPERATIONS}")
|
|||
|
|
key = (operation, source_type)
|
|||
|
|
existing = cls._handlers.get(key)
|
|||
|
|
if existing is not None and existing is not handler:
|
|||
|
|
raise DomainValidationError(
|
|||
|
|
f"集成操作 handler 冲突: (operation={operation}, source_type={source_type}) "
|
|||
|
|
f"已注册 {getattr(existing, '__name__', existing)},"
|
|||
|
|
f"尝试注册 {getattr(handler, '__name__', handler)}"
|
|||
|
|
)
|
|||
|
|
cls._handlers[key] = handler
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def get(cls, operation: str, source_type: str) -> OperationHandler:
|
|||
|
|
"""获取 handler,未注册抛 IntegrationOperationNotRegisteredError。"""
|
|||
|
|
handler = cls._handlers.get((operation, source_type))
|
|||
|
|
if handler is None:
|
|||
|
|
raise IntegrationOperationNotRegisteredError(
|
|||
|
|
f"集成操作未注册: (operation={operation}, source_type={source_type})"
|
|||
|
|
)
|
|||
|
|
return handler
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
async def discover(cls, source_type: str, system_config: dict[str, Any]) -> list[dict[str, Any]]:
|
|||
|
|
"""分派 discover 操作(实现端口)。"""
|
|||
|
|
handler = cls.get("discover", source_type)
|
|||
|
|
return await handler(source_type, system_config)
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
async def preview_tools(cls, source_type: str, system_config: dict[str, Any]) -> list[dict[str, Any]]:
|
|||
|
|
"""分派 preview_tools 操作(实现端口)。"""
|
|||
|
|
handler = cls.get("preview_tools", source_type)
|
|||
|
|
return await handler(source_type, system_config)
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
async def create_tools(cls, source_type: str, system_config: dict[str, Any]) -> GeneratedToolsDraft:
|
|||
|
|
"""分派 create_tools 操作(实现端口)。
|
|||
|
|
|
|||
|
|
返回 GeneratedToolsDraft,由 use_cases 层统一持久化。
|
|||
|
|
"""
|
|||
|
|
handler = cls.get("create_tools", source_type)
|
|||
|
|
result = await handler(source_type, system_config)
|
|||
|
|
if isinstance(result, GeneratedToolsDraft):
|
|||
|
|
return result
|
|||
|
|
# 容错:handler 返回 dict 时构造 GeneratedToolsDraft(便于渐进迁移)
|
|||
|
|
if isinstance(result, dict):
|
|||
|
|
return GeneratedToolsDraft.model_validate(result)
|
|||
|
|
raise DomainValidationError(
|
|||
|
|
f"create_tools handler 返回类型非法: 期望 GeneratedToolsDraft / dict,实际 {type(result).__name__}"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def list_operations(cls, source_type: str) -> list[str]:
|
|||
|
|
"""列出指定 source_type 已注册的操作(供调试与前端展示)。"""
|
|||
|
|
return sorted(op for (op, st) in cls._handlers if st == source_type)
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def list_source_types(cls) -> list[str]:
|
|||
|
|
"""列出所有已注册 handler 涉及的 source_type(去重)。"""
|
|||
|
|
seen: dict[str, None] = {}
|
|||
|
|
for _op, st in cls._handlers:
|
|||
|
|
if st not in seen:
|
|||
|
|
seen[st] = None
|
|||
|
|
return list(seen.keys())
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def clear(cls) -> None:
|
|||
|
|
"""清空注册表,仅供测试使用。"""
|
|||
|
|
cls._handlers.clear()
|