zcbot/core/external_systems/registry.py

93 lines
2.9 KiB
Python

"""外部系统 provider 注册表。
标准 OpenAPI 与 Streamable HTTP MCP 系统均通过数据库配置接入。
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from .auth import ExternalAuthError, get_auth_strategy
@dataclass(frozen=True)
class ProviderSpec:
provider: str
title: str
connector: str
default_auth_type: str
allowed_auth_types: tuple[str, ...]
defaults: dict[str, Any]
_PROVIDERS = {
"generic_openapi": ProviderSpec(
provider="generic_openapi",
title="通用 OpenAPI 系统",
connector="openapi",
default_auth_type="password_jwt",
allowed_auth_types=("password_jwt", "api_key", "bearer_token"),
defaults={
"login_path": "/api/auth/token/",
"username_field": "username",
"password_field": "password",
"token_field": "access",
"auth_header_name": "Authorization",
"auth_header_template": "Bearer {token}",
"query_guidance": "",
"recommended_operation_ids": [],
"operation_mode": "query",
"operation_policies": {},
},
),
"generic_mcp": ProviderSpec(
provider="generic_mcp",
title="通用 MCP 系统",
connector="mcp",
default_auth_type="password_jwt",
allowed_auth_types=("password_jwt", "api_key", "bearer_token"),
defaults={
"login_path": "/api/auth/token/",
"username_field": "username",
"password_field": "password",
"token_field": "access",
"auth_header_name": "Authorization",
"auth_header_template": "Bearer {token}",
"query_guidance": "",
"recommended_operation_ids": [],
"operation_mode": "upstream_managed",
"operation_policies": {},
},
),
}
def get_provider(provider: str) -> ProviderSpec:
result = _PROVIDERS.get((provider or "").strip())
if result is None:
raise ValueError(f"不支持的外部系统 provider: {provider}")
return result
def provider_specs() -> tuple[ProviderSpec, ...]:
return tuple(_PROVIDERS.values())
def merged_config(provider: str, config: dict[str, Any]) -> dict[str, Any]:
spec = get_provider(provider)
result = {**spec.defaults, **(config or {})}
auth_type = str(result.get("auth_type") or spec.default_auth_type).strip()
if auth_type not in spec.allowed_auth_types:
raise ValueError(f"{spec.title} 不支持认证方式 {auth_type}")
result["auth_type"] = auth_type
return result
def credential_fields(provider: str, config: dict[str, Any]) -> list[dict[str, Any]]:
merged = merged_config(provider, config)
try:
strategy = get_auth_strategy(merged["auth_type"])
except ExternalAuthError as exc:
raise ValueError(str(exc)) from exc
return [field.as_dict() for field in strategy.credential_fields]