feat: 支持固定类型 Provider OAuth 授权

- 新增 provider_type 字段区分自定义/预置 Provider 类型(claude_code/codex/gemini_cli/antigravity)
- 实现完整 OAuth 2.0 授权流程:start(生成授权 URL + PKCE)、complete(换取 token)、refresh
- 前端 KeyFormDialog 添加 OAuth 授权 UI,支持开始授权、粘贴回调 URL、完成授权、强制刷新
- 请求时自动检测 token 过期并刷新(120s 预留窗口 + Redis 分布式锁防并发)
- 固定类型 Provider 自动创建预置端点并锁定 base_url/custom_path
- 数据库迁移:添加 providers.provider_type,扩展 api_key 列为 TEXT
- 可选依赖 tls-client 用于 Claude token 请求的 TLS 指纹伪装
This commit is contained in:
AAEE86
2026-02-04 10:24:25 +08:00
parent f6dac1c38a
commit e4fdc65e52
25 changed files with 1818 additions and 36 deletions

View File

@@ -55,6 +55,11 @@ class CreateProviderRequest(BaseModel):
"""创建 Provider 请求"""
name: str = Field(..., min_length=1, max_length=100, description="提供商名称(唯一)")
provider_type: str | None = Field(
default="custom",
max_length=20,
description="Provider 类型custom/claude_code/codex/gemini_cli/antigravity",
)
description: str | None = Field(None, max_length=1000, description="描述")
website: str | None = Field(None, max_length=500, description="官网地址")
@@ -116,6 +121,17 @@ class CreateProviderRequest(BaseModel):
)
config: dict[str, Any] | None = Field(None, description="其他配置")
@field_validator("provider_type")
@classmethod
def validate_provider_type(cls, v: str | None) -> str | None:
if v is None:
return "custom"
v = v.strip()
allowed = {"custom", "claude_code", "codex", "gemini_cli", "antigravity"}
if v not in allowed:
raise ValueError(f"无效的 provider_type有效值为: {', '.join(sorted(allowed))}")
return v
@field_validator("name", "description")
@classmethod
def sanitize_text(cls, v: str | None) -> str | None:
@@ -171,6 +187,11 @@ class UpdateProviderRequest(BaseModel):
"""更新 Provider 请求"""
name: str | None = Field(None, min_length=1, max_length=100)
provider_type: str | None = Field(
None,
max_length=20,
description="Provider 类型custom/claude_code/codex/gemini_cli/antigravity",
)
description: str | None = Field(None, max_length=1000)
website: str | None = Field(None, max_length=500)
billing_type: str | None = None
@@ -201,6 +222,9 @@ class UpdateProviderRequest(BaseModel):
_validate_billing_type = field_validator("billing_type")(
CreateProviderRequest.validate_billing_type.__func__
)
_validate_provider_type = field_validator("provider_type")(
CreateProviderRequest.validate_provider_type.__func__
)
class CreateEndpointRequest(BaseModel):

View File

@@ -638,6 +638,11 @@ class Provider(Base):
description = Column(Text, nullable=True) # 提供商描述
website = Column(String(500), nullable=True) # 主站网站
# Provider 类型(用于模板化固定 Provider / 自定义 Provider
# - custom: 自定义
# - claude_code / codex / gemini_cli / antigravity: 固定类型
provider_type = Column(String(20), default="custom", nullable=False)
# 计费类型配置
billing_type = Column(
Enum(
@@ -1298,7 +1303,7 @@ class ProviderAPIKey(Base):
# API密钥加密存储
# - auth_type="api_key" 时:存储 API Key 字符串
# - auth_type="vertex_ai" 等:可为空,敏感凭证存在 auth_config 中
api_key = Column(String(500), nullable=False) # 保持 NOT NULL 兼容历史数据
api_key = Column(Text, nullable=False) # 使用 Text 支持加密后的 OAuth token
# 认证配置(加密存储)
# - auth_type="api_key" 时:可为空

View File

@@ -200,12 +200,16 @@ class EndpointAPIKeyCreate(BaseModel):
api_key: str = Field(
default="", max_length=500, description="API Key标准认证时必填将自动加密"
)
auth_type: Literal["api_key", "vertex_ai"] = Field(
auth_type: Literal["api_key", "vertex_ai", "oauth"] = Field(
default="api_key",
description="认证类型api_key标准 API Key vertex_aiVertex AI Service Account",
description="认证类型api_key标准 API Key/ vertex_aiVertex AI Service Account/ oauthOAuth access_token",
)
auth_config: dict[str, Any] | None = Field(
default=None, description="认证配置JSONvertex_ai 时存储完整 Service Account JSON"
default=None,
description=(
"认证配置JSONvertex_ai 时存储完整 Service Account JSON"
"oauth 时存储 token/refresh/expires_at 等(后端加密存储,不在响应中返回)"
),
)
name: str = Field(..., min_length=1, max_length=100, description="密钥名称(必填,用于识别)")
@@ -360,12 +364,16 @@ class EndpointAPIKeyUpdate(BaseModel):
max_length=500,
description="API Key标准认证时使用将自动加密",
)
auth_type: Literal["api_key", "vertex_ai"] | None = Field(
auth_type: Literal["api_key", "vertex_ai", "oauth"] | None = Field(
default=None,
description="认证类型api_key标准 API Key vertex_aiVertex AI Service Account",
description="认证类型api_key标准 API Key/ vertex_aiVertex AI Service Account/ oauthOAuth access_token",
)
auth_config: dict[str, Any] | None = Field(
default=None, description="认证配置JSONvertex_ai 时存储完整 Service Account JSON"
default=None,
description=(
"认证配置JSONvertex_ai 时存储完整 Service Account JSON"
"oauth 时存储 token/refresh/expires_at 等(后端加密存储,不在响应中返回)"
),
)
name: str | None = Field(default=None, min_length=1, max_length=100, description="密钥名称")
rate_multipliers: dict[str, float] | None = Field(
@@ -690,6 +698,7 @@ class ProviderWithEndpointsSummary(BaseModel):
# Provider 基本信息
id: str
name: str
provider_type: str | None = Field(default=None, description="Provider 类型custom/claude_code/codex/gemini_cli/antigravity")
description: str | None = None
website: str | None = None
provider_priority: int = Field(default=100, description="提供商优先级(数字越小越优先)")