2026-01-17 19:50:35 +08:00
|
|
|
|
"""
|
|
|
|
|
|
Provider 架构抽象基类
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from abc import ABC, abstractmethod
|
2026-02-15 16:32:23 +08:00
|
|
|
|
from collections.abc import AsyncIterator, Callable
|
2026-01-17 19:50:35 +08:00
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
from datetime import datetime, timezone
|
2026-01-30 03:10:21 +08:00
|
|
|
|
from typing import Any
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
import httpx
|
|
|
|
|
|
|
|
|
|
|
|
from src.services.provider_ops.actions.base import ProviderAction
|
|
|
|
|
|
from src.services.provider_ops.types import (
|
|
|
|
|
|
ConnectorAuthType,
|
|
|
|
|
|
ConnectorState,
|
|
|
|
|
|
ConnectorStatus,
|
|
|
|
|
|
ProviderActionType,
|
|
|
|
|
|
)
|
2026-02-01 17:28:00 +08:00
|
|
|
|
from src.utils.ssl_utils import get_ssl_context
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
# ==================== 连接器基类 ====================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProviderConnector(ABC):
|
|
|
|
|
|
"""
|
|
|
|
|
|
提供商连接器基类
|
|
|
|
|
|
|
|
|
|
|
|
负责建立与提供商的认证连接,管理凭据状态。
|
|
|
|
|
|
每个架构应在自己的文件中实现对应的连接器子类。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
# 子类需要定义的类属性
|
|
|
|
|
|
auth_type: ConnectorAuthType = ConnectorAuthType.NONE
|
|
|
|
|
|
display_name: str = "Base Connector"
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def __init__(self, base_url: str, config: dict[str, Any] | None = None):
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""
|
|
|
|
|
|
初始化连接器
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
base_url: 提供商 API 基础 URL
|
|
|
|
|
|
config: 连接器配置
|
|
|
|
|
|
"""
|
|
|
|
|
|
self.base_url = base_url.rstrip("/")
|
|
|
|
|
|
self.config = config or {}
|
|
|
|
|
|
self._status = ConnectorStatus.DISCONNECTED
|
2026-01-30 03:10:21 +08:00
|
|
|
|
self._connected_at: datetime | None = None
|
|
|
|
|
|
self._expires_at: datetime | None = None
|
|
|
|
|
|
self._last_error: str | None = None
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
2026-02-26 12:25:23 +08:00
|
|
|
|
# 代理配置(支持 proxy_node_id、tunnel 和旧的 proxy URL)
|
|
|
|
|
|
from src.services.proxy_node.resolver import resolve_ops_proxy_config
|
2026-02-07 14:30:46 +08:00
|
|
|
|
|
2026-02-26 12:25:23 +08:00
|
|
|
|
self._proxy: str | httpx.Proxy | None
|
|
|
|
|
|
self._tunnel_node_id: str | None
|
|
|
|
|
|
self._proxy, self._tunnel_node_id = resolve_ops_proxy_config(self.config)
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
# HTTP 客户端配置
|
|
|
|
|
|
self._timeout = self.config.get("timeout", 30)
|
2026-01-30 03:10:21 +08:00
|
|
|
|
self._headers: dict[str, str] = {}
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
2026-02-15 16:32:23 +08:00
|
|
|
|
# 凭据更新回调(Token Rotation 等场景需要持久化新凭据)
|
|
|
|
|
|
self._on_credentials_updated: Callable[[dict[str, Any]], None] | None = None
|
|
|
|
|
|
|
2026-01-17 19:50:35 +08:00
|
|
|
|
@abstractmethod
|
2026-01-30 03:10:21 +08:00
|
|
|
|
async def connect(self, credentials: dict[str, Any]) -> bool:
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""
|
|
|
|
|
|
建立认证连接
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
credentials: 凭据信息(如用户名密码、API Key 等)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
是否连接成功
|
|
|
|
|
|
"""
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
|
async def disconnect(self) -> None:
|
|
|
|
|
|
"""断开连接,清理状态"""
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
|
async def is_authenticated(self) -> bool:
|
|
|
|
|
|
"""检查当前是否已认证"""
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
|
def _apply_auth(self, request: httpx.Request) -> httpx.Request:
|
|
|
|
|
|
"""
|
|
|
|
|
|
为请求应用认证信息
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
request: 原始请求
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
添加认证信息后的请求
|
|
|
|
|
|
"""
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
async def refresh_auth(self, credentials: dict[str, Any]) -> bool:
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""
|
|
|
|
|
|
刷新认证(如 Token 过期)
|
|
|
|
|
|
|
|
|
|
|
|
默认实现:重新连接
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
credentials: 凭据信息
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
是否刷新成功
|
|
|
|
|
|
"""
|
|
|
|
|
|
return await self.connect(credentials)
|
|
|
|
|
|
|
|
|
|
|
|
@asynccontextmanager
|
|
|
|
|
|
async def get_client(self) -> AsyncIterator[httpx.AsyncClient]:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取已认证的 HTTP 客户端
|
|
|
|
|
|
|
2026-02-25 21:59:29 +08:00
|
|
|
|
使用 context manager 确保资源正确释放。
|
|
|
|
|
|
tunnel 模式下使用 TunnelTransport 替代 proxy transport。
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
Yields:
|
|
|
|
|
|
已配置认证信息的 AsyncClient
|
|
|
|
|
|
"""
|
|
|
|
|
|
transport = None
|
2026-02-25 21:59:29 +08:00
|
|
|
|
if self._tunnel_node_id:
|
2026-03-02 02:43:14 +08:00
|
|
|
|
from src.services.proxy_node.tunnel_transport import create_tunnel_transport
|
2026-02-25 21:59:29 +08:00
|
|
|
|
|
2026-03-02 02:43:14 +08:00
|
|
|
|
transport = create_tunnel_transport(self._tunnel_node_id, timeout=self._timeout)
|
2026-02-25 21:59:29 +08:00
|
|
|
|
elif self._proxy:
|
2026-01-17 19:50:35 +08:00
|
|
|
|
transport = httpx.AsyncHTTPTransport(proxy=self._proxy)
|
|
|
|
|
|
|
|
|
|
|
|
async with httpx.AsyncClient(
|
|
|
|
|
|
base_url=self.base_url,
|
|
|
|
|
|
timeout=self._timeout,
|
|
|
|
|
|
transport=transport,
|
|
|
|
|
|
event_hooks={"request": [self._auth_hook]},
|
2026-01-19 17:56:02 +08:00
|
|
|
|
verify=get_ssl_context(),
|
2026-01-17 19:50:35 +08:00
|
|
|
|
) as client:
|
|
|
|
|
|
yield client
|
|
|
|
|
|
|
|
|
|
|
|
async def _auth_hook(self, request: httpx.Request) -> None:
|
|
|
|
|
|
"""请求钩子:应用认证信息"""
|
|
|
|
|
|
self._apply_auth(request)
|
|
|
|
|
|
|
|
|
|
|
|
def get_state(self) -> ConnectorState:
|
|
|
|
|
|
"""获取连接器当前状态"""
|
|
|
|
|
|
return ConnectorState(
|
|
|
|
|
|
status=self._status,
|
|
|
|
|
|
auth_type=self.auth_type,
|
|
|
|
|
|
connected_at=self._connected_at,
|
|
|
|
|
|
expires_at=self._expires_at,
|
|
|
|
|
|
last_error=self._last_error,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def _set_connected(self, expires_at: datetime | None = None) -> None:
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""设置为已连接状态"""
|
|
|
|
|
|
self._status = ConnectorStatus.CONNECTED
|
|
|
|
|
|
self._connected_at = datetime.now(timezone.utc)
|
|
|
|
|
|
self._expires_at = expires_at
|
|
|
|
|
|
self._last_error = None
|
|
|
|
|
|
|
|
|
|
|
|
def _set_error(self, error: str) -> None:
|
|
|
|
|
|
"""设置错误状态"""
|
|
|
|
|
|
self._status = ConnectorStatus.ERROR
|
|
|
|
|
|
self._last_error = error
|
|
|
|
|
|
|
|
|
|
|
|
def _set_disconnected(self) -> None:
|
|
|
|
|
|
"""设置为断开状态"""
|
|
|
|
|
|
self._status = ConnectorStatus.DISCONNECTED
|
|
|
|
|
|
self._connected_at = None
|
|
|
|
|
|
self._expires_at = None
|
|
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def get_credentials_schema(cls) -> dict[str, Any]:
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""
|
|
|
|
|
|
获取凭据配置 JSON Schema(用于前端表单生成)
|
|
|
|
|
|
|
|
|
|
|
|
子类应重写此方法
|
|
|
|
|
|
"""
|
|
|
|
|
|
return {"type": "object", "properties": {}, "required": []}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== 验证结果 ====================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class VerifyResult:
|
|
|
|
|
|
"""认证验证结果"""
|
|
|
|
|
|
|
|
|
|
|
|
success: bool
|
2026-01-30 03:10:21 +08:00
|
|
|
|
message: str | None = None
|
|
|
|
|
|
username: str | None = None
|
|
|
|
|
|
display_name: str | None = None
|
|
|
|
|
|
email: str | None = None
|
|
|
|
|
|
quota: float | None = None
|
|
|
|
|
|
used_quota: float | None = None
|
|
|
|
|
|
request_count: int | None = None
|
|
|
|
|
|
extra: dict[str, Any] | None = None
|
|
|
|
|
|
|
|
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""转换为字典"""
|
|
|
|
|
|
if not self.success:
|
|
|
|
|
|
return {"success": False, "message": self.message}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"username": self.username,
|
|
|
|
|
|
"display_name": self.display_name or self.username,
|
|
|
|
|
|
"email": self.email,
|
|
|
|
|
|
"quota": self.quota,
|
|
|
|
|
|
"used_quota": self.used_quota,
|
|
|
|
|
|
"request_count": self.request_count,
|
|
|
|
|
|
"extra": self.extra or {},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== 架构基类 ====================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProviderArchitecture(ABC):
|
|
|
|
|
|
"""
|
2026-01-20 00:55:52 +08:00
|
|
|
|
提供商架构抽象基类
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
架构 = Connector(鉴权方式) + Actions(支持的操作)
|
|
|
|
|
|
|
|
|
|
|
|
一个架构可以被多个 Provider 复用。
|
|
|
|
|
|
例如:generic_api 架构可用于各种中转站。
|
|
|
|
|
|
|
|
|
|
|
|
## 添加新认证模板的步骤
|
|
|
|
|
|
|
|
|
|
|
|
1. 在 architectures/ 目录创建新文件
|
|
|
|
|
|
2. 继承 ProviderArchitecture 和 ProviderConnector
|
|
|
|
|
|
3. 定义类属性:architecture_id, display_name, description
|
|
|
|
|
|
4. 实现连接器子类和架构类
|
2026-01-20 00:55:52 +08:00
|
|
|
|
5. 实现认证相关的抽象方法:
|
|
|
|
|
|
- get_credentials_schema(): 返回凭据字段定义
|
2026-01-17 19:50:35 +08:00
|
|
|
|
- get_verify_endpoint(): 返回验证端点
|
|
|
|
|
|
- build_verify_headers(): 构建验证请求 headers
|
|
|
|
|
|
- parse_verify_response(): 解析验证响应
|
|
|
|
|
|
6. 在 registry.py 的 _register_builtin_architectures() 中注册
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
# 子类需要定义的类属性
|
|
|
|
|
|
architecture_id: str = ""
|
|
|
|
|
|
display_name: str = ""
|
|
|
|
|
|
description: str = ""
|
|
|
|
|
|
|
2026-02-13 16:41:30 +08:00
|
|
|
|
# 设为 True 时不在架构列表 API 中返回(内部使用的架构)
|
|
|
|
|
|
hidden: bool = False
|
|
|
|
|
|
|
2026-01-17 19:50:35 +08:00
|
|
|
|
# 支持的 Connector 类型列表(按优先级排序)
|
2026-01-30 03:10:21 +08:00
|
|
|
|
supported_connectors: list[type[ProviderConnector]] = []
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
# 支持的 Action 类型列表
|
2026-01-30 03:10:21 +08:00
|
|
|
|
supported_actions: list[type[ProviderAction]] = []
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
# 默认操作配置
|
2026-01-30 03:10:21 +08:00
|
|
|
|
default_action_configs: dict[ProviderActionType, dict[str, Any]] = {}
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def __init__(self, config: dict[str, Any] | None = None):
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""
|
|
|
|
|
|
初始化架构
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
config: 架构配置
|
|
|
|
|
|
"""
|
|
|
|
|
|
self.config = config or {}
|
|
|
|
|
|
|
2026-02-13 16:41:30 +08:00
|
|
|
|
# ==================== 认证验证相关方法 ====================
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
2026-01-20 00:55:52 +08:00
|
|
|
|
@abstractmethod
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def get_credentials_schema(self) -> dict[str, Any]:
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""
|
|
|
|
|
|
获取凭据字段定义(JSON Schema 格式)
|
|
|
|
|
|
|
2026-01-20 00:55:52 +08:00
|
|
|
|
子类必须实现此方法定义需要的凭据字段。
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
JSON Schema 格式的字段定义
|
|
|
|
|
|
"""
|
2026-01-20 00:55:52 +08:00
|
|
|
|
pass
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
2026-01-20 00:55:52 +08:00
|
|
|
|
@abstractmethod
|
2026-01-17 19:50:35 +08:00
|
|
|
|
def get_verify_endpoint(self) -> str:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取认证验证端点
|
|
|
|
|
|
|
2026-01-20 00:55:52 +08:00
|
|
|
|
子类必须实现此方法返回验证端点。
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
Returns:
|
2026-01-20 00:55:52 +08:00
|
|
|
|
验证端点路径(如 /api/user/self, /api/v1/auth/profile)
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""
|
2026-01-20 00:55:52 +08:00
|
|
|
|
pass
|
2026-01-19 17:18:03 +08:00
|
|
|
|
|
2026-01-20 00:55:52 +08:00
|
|
|
|
@abstractmethod
|
2026-01-17 19:50:35 +08:00
|
|
|
|
def build_verify_headers(
|
|
|
|
|
|
self,
|
2026-01-30 03:10:21 +08:00
|
|
|
|
config: dict[str, Any],
|
|
|
|
|
|
credentials: dict[str, Any],
|
|
|
|
|
|
) -> dict[str, str]:
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""
|
|
|
|
|
|
构建认证验证请求的 Headers
|
|
|
|
|
|
|
2026-01-20 00:55:52 +08:00
|
|
|
|
子类必须实现此方法构建认证 Headers。
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
Args:
|
2026-01-20 00:55:52 +08:00
|
|
|
|
config: 连接器配置(可能包含 prepare_verify_config 返回的额外配置)
|
2026-01-17 19:50:35 +08:00
|
|
|
|
credentials: 凭据信息
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Headers 字典
|
|
|
|
|
|
"""
|
2026-01-20 00:55:52 +08:00
|
|
|
|
pass
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
def parse_verify_response(
|
|
|
|
|
|
self,
|
|
|
|
|
|
status_code: int,
|
2026-01-30 03:10:21 +08:00
|
|
|
|
data: dict[str, Any],
|
2026-01-17 19:50:35 +08:00
|
|
|
|
) -> VerifyResult:
|
|
|
|
|
|
"""
|
|
|
|
|
|
解析认证验证响应
|
|
|
|
|
|
|
2026-02-13 16:41:30 +08:00
|
|
|
|
默认实现处理通用的 {"success": bool, "data": {...}} 格式。
|
|
|
|
|
|
子类可重写 _auth_fail_message() 和 _build_verify_result() 进行自定义。
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
status_code: HTTP 状态码
|
|
|
|
|
|
data: 响应 JSON 数据
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
验证结果
|
|
|
|
|
|
"""
|
2026-02-13 16:41:30 +08:00
|
|
|
|
if status_code == 401:
|
|
|
|
|
|
return VerifyResult(success=False, message=self._auth_fail_message(401))
|
|
|
|
|
|
if status_code == 403:
|
|
|
|
|
|
return VerifyResult(success=False, message=self._auth_fail_message(403))
|
|
|
|
|
|
if status_code != 200:
|
|
|
|
|
|
return VerifyResult(success=False, message=f"验证失败:HTTP {status_code}")
|
|
|
|
|
|
|
|
|
|
|
|
# 解析通用响应格式
|
|
|
|
|
|
if data.get("success") is True and "data" in data:
|
|
|
|
|
|
user_data = data["data"]
|
|
|
|
|
|
elif data.get("success") is False:
|
|
|
|
|
|
message = data.get("message", "验证失败")
|
|
|
|
|
|
return VerifyResult(success=False, message=message)
|
|
|
|
|
|
else:
|
|
|
|
|
|
user_data = data
|
|
|
|
|
|
|
|
|
|
|
|
return self._build_verify_result(user_data, data)
|
|
|
|
|
|
|
|
|
|
|
|
def _auth_fail_message(self, status_code: int) -> str:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取认证失败消息
|
|
|
|
|
|
|
|
|
|
|
|
子类可重写以提供自定义消息(如 Cookie 认证场景)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
if status_code == 401:
|
|
|
|
|
|
return "认证失败:无效的凭据"
|
|
|
|
|
|
return "认证失败:权限不足"
|
|
|
|
|
|
|
|
|
|
|
|
def _build_verify_result(
|
|
|
|
|
|
self, user_data: dict[str, Any], raw_data: dict[str, Any] | None = None
|
|
|
|
|
|
) -> VerifyResult:
|
|
|
|
|
|
"""
|
|
|
|
|
|
从用户数据构建验证结果
|
|
|
|
|
|
|
|
|
|
|
|
默认实现提取 username, display_name, email, quota, used_quota, request_count。
|
|
|
|
|
|
子类可重写以自定义字段提取。
|
|
|
|
|
|
"""
|
|
|
|
|
|
known_fields = (
|
|
|
|
|
|
"username",
|
|
|
|
|
|
"display_name",
|
|
|
|
|
|
"email",
|
|
|
|
|
|
"quota",
|
|
|
|
|
|
"used_quota",
|
|
|
|
|
|
"request_count",
|
|
|
|
|
|
)
|
|
|
|
|
|
return VerifyResult(
|
|
|
|
|
|
success=True,
|
|
|
|
|
|
username=user_data.get("username"),
|
|
|
|
|
|
display_name=user_data.get("display_name") or user_data.get("username"),
|
|
|
|
|
|
email=user_data.get("email"),
|
|
|
|
|
|
quota=user_data.get("quota"),
|
|
|
|
|
|
used_quota=user_data.get("used_quota"),
|
|
|
|
|
|
request_count=user_data.get("request_count"),
|
|
|
|
|
|
extra={k: v for k, v in user_data.items() if k not in known_fields},
|
|
|
|
|
|
)
|
2026-01-20 00:55:52 +08:00
|
|
|
|
|
|
|
|
|
|
# ==================== 可选的钩子方法 ====================
|
|
|
|
|
|
|
|
|
|
|
|
async def prepare_verify_config(
|
|
|
|
|
|
self,
|
|
|
|
|
|
base_url: str,
|
2026-01-30 03:10:21 +08:00
|
|
|
|
config: dict[str, Any],
|
|
|
|
|
|
credentials: dict[str, Any],
|
2026-02-15 16:32:23 +08:00
|
|
|
|
) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]:
|
2026-01-20 00:55:52 +08:00
|
|
|
|
"""
|
|
|
|
|
|
验证前的异步预处理(可选)
|
|
|
|
|
|
|
2026-02-15 16:32:23 +08:00
|
|
|
|
子类可重写以执行异步操作(如获取动态 Cookie、登录获取 Token)。
|
2026-01-20 00:55:52 +08:00
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
base_url: API 基础地址
|
|
|
|
|
|
config: 连接器配置
|
|
|
|
|
|
credentials: 凭据信息
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
2026-02-15 16:32:23 +08:00
|
|
|
|
- dict: 额外配置(会与原 config 合并传递给 build_verify_headers)
|
|
|
|
|
|
- tuple[dict, dict]: (额外配置, 需持久化的凭据更新)
|
|
|
|
|
|
当预处理过程中凭据发生变更时(如 Token Rotation),
|
|
|
|
|
|
通过第二个 dict 显式返回需要持久化的字段。
|
2026-01-20 00:55:52 +08:00
|
|
|
|
"""
|
|
|
|
|
|
return {}
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
# ==================== 连接器和操作相关方法 ====================
|
|
|
|
|
|
|
|
|
|
|
|
def get_connector(
|
|
|
|
|
|
self,
|
|
|
|
|
|
base_url: str,
|
2026-01-30 03:10:21 +08:00
|
|
|
|
auth_type: ConnectorAuthType | None = None,
|
|
|
|
|
|
config: dict[str, Any] | None = None,
|
2026-01-17 19:50:35 +08:00
|
|
|
|
) -> ProviderConnector:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取连接器实例
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
base_url: 提供商 API 基础 URL
|
|
|
|
|
|
auth_type: 指定的认证类型,None 则使用默认
|
|
|
|
|
|
config: 连接器配置
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
连接器实例
|
|
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
|
ValueError: 不支持的认证类型
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not self.supported_connectors:
|
|
|
|
|
|
raise ValueError(f"架构 {self.architecture_id} 未配置支持的连接器")
|
|
|
|
|
|
|
|
|
|
|
|
# 查找匹配的连接器
|
2026-01-30 03:10:21 +08:00
|
|
|
|
connector_cls: type[ProviderConnector] | None = None
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
if auth_type:
|
|
|
|
|
|
for cls in self.supported_connectors:
|
|
|
|
|
|
if cls.auth_type == auth_type:
|
|
|
|
|
|
connector_cls = cls
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
if not connector_cls:
|
|
|
|
|
|
supported = [c.auth_type.value for c in self.supported_connectors]
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
f"架构 {self.architecture_id} 不支持 {auth_type.value} 认证,"
|
|
|
|
|
|
f"支持的类型: {supported}"
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 使用第一个(默认)连接器
|
|
|
|
|
|
connector_cls = self.supported_connectors[0]
|
|
|
|
|
|
|
|
|
|
|
|
return connector_cls(base_url, config)
|
|
|
|
|
|
|
|
|
|
|
|
def get_action(
|
|
|
|
|
|
self,
|
|
|
|
|
|
action_type: ProviderActionType,
|
2026-01-30 03:10:21 +08:00
|
|
|
|
config: dict[str, Any] | None = None,
|
2026-01-17 19:50:35 +08:00
|
|
|
|
) -> ProviderAction:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取操作实例
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
action_type: 操作类型
|
|
|
|
|
|
config: 操作配置(会与默认配置合并)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
操作实例
|
|
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
|
ValueError: 不支持的操作类型
|
|
|
|
|
|
"""
|
2026-01-30 03:10:21 +08:00
|
|
|
|
action_cls: type[ProviderAction] | None = None
|
2026-01-17 19:50:35 +08:00
|
|
|
|
|
|
|
|
|
|
for cls in self.supported_actions:
|
|
|
|
|
|
if cls.action_type == action_type:
|
|
|
|
|
|
action_cls = cls
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
if not action_cls:
|
|
|
|
|
|
supported = [a.action_type.value for a in self.supported_actions]
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
f"架构 {self.architecture_id} 不支持 {action_type.value} 操作,"
|
|
|
|
|
|
f"支持的操作: {supported}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 合并默认配置和用户配置
|
|
|
|
|
|
merged_config = dict(self.default_action_configs.get(action_type, {}))
|
|
|
|
|
|
if config:
|
|
|
|
|
|
merged_config.update(config)
|
|
|
|
|
|
|
|
|
|
|
|
return action_cls(merged_config)
|
|
|
|
|
|
|
|
|
|
|
|
def supports_action(self, action_type: ProviderActionType) -> bool:
|
|
|
|
|
|
"""检查是否支持指定操作"""
|
|
|
|
|
|
return any(a.action_type == action_type for a in self.supported_actions)
|
|
|
|
|
|
|
|
|
|
|
|
def supports_auth_type(self, auth_type: ConnectorAuthType) -> bool:
|
|
|
|
|
|
"""检查是否支持指定认证类型"""
|
|
|
|
|
|
return any(c.auth_type == auth_type for c in self.supported_connectors)
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def get_supported_auth_types(self) -> list[ConnectorAuthType]:
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""获取支持的认证类型列表"""
|
|
|
|
|
|
return [c.auth_type for c in self.supported_connectors]
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def get_supported_action_types(self) -> list[ProviderActionType]:
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""获取支持的操作类型列表"""
|
|
|
|
|
|
return [a.action_type for a in self.supported_actions]
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
2026-01-17 19:50:35 +08:00
|
|
|
|
"""转换为字典(用于 API 响应)"""
|
|
|
|
|
|
return {
|
|
|
|
|
|
"architecture_id": self.architecture_id,
|
|
|
|
|
|
"display_name": self.display_name,
|
|
|
|
|
|
"description": self.description,
|
|
|
|
|
|
"credentials_schema": self.get_credentials_schema(),
|
|
|
|
|
|
"verify_endpoint": self.get_verify_endpoint(),
|
|
|
|
|
|
"supported_auth_types": [
|
2026-02-15 16:32:23 +08:00
|
|
|
|
{
|
|
|
|
|
|
"type": c.auth_type.value,
|
|
|
|
|
|
"display_name": c.display_name,
|
|
|
|
|
|
"credentials_schema": c.get_credentials_schema(),
|
|
|
|
|
|
}
|
2026-01-17 19:50:35 +08:00
|
|
|
|
for c in self.supported_connectors
|
|
|
|
|
|
],
|
|
|
|
|
|
"supported_actions": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"type": a.action_type.value,
|
|
|
|
|
|
"display_name": a.display_name,
|
|
|
|
|
|
"description": a.description,
|
|
|
|
|
|
"config_schema": a.get_config_schema(),
|
|
|
|
|
|
}
|
|
|
|
|
|
for a in self.supported_actions
|
|
|
|
|
|
],
|
|
|
|
|
|
"default_connector": (
|
2026-02-01 17:28:00 +08:00
|
|
|
|
self.supported_connectors[0].auth_type.value if self.supported_connectors else None
|
2026-01-17 19:50:35 +08:00
|
|
|
|
),
|
|
|
|
|
|
}
|