feat: add Vertex AI authentication support for provider API keys

- Add auth_type field to ProviderAPIKey model (api_key or vertex_ai)
- Implement Vertex AI OAuth token generation with service account
- Update transport layer to handle Vertex AI authentication
- Add Vertex AI endpoint URL generation in request builder
- Update frontend KeyFormDialog to support auth_type selection
- Add migration for auth_type column in provider_api_keys table
This commit is contained in:
fawney19
2026-01-30 02:43:50 +08:00
parent 3e75bc8964
commit 32b293ef3e
16 changed files with 956 additions and 65 deletions

View File

@@ -1111,8 +1111,22 @@ class ProviderAPIKey(Base):
# None 表示支持所有格式(兼容历史数据),空列表 [] 表示不支持任何格式
api_formats = Column(JSON, nullable=True, default=list) # ["CLAUDE", "CLAUDE_CLI"]
# API密钥信息
api_key = Column(String(500), nullable=False) # API密钥加密存储
# 认证类型
# - "api_key": 标准 API Key 认证(默认
# - "vertex_ai": Google Vertex AI 认证Service Account JSON
# - 未来可扩展oauth2, azure_ad, aws_iam 等
auth_type = Column(String(20), default="api_key", nullable=False)
# API密钥加密存储
# - auth_type="api_key" 时:存储 API Key 字符串
# - auth_type="vertex_ai" 等:可为空,敏感凭证存在 auth_config 中
api_key = Column(String(500), nullable=False) # 保持 NOT NULL 兼容历史数据
# 认证配置(加密存储)
# - auth_type="api_key" 时:可为空
# - auth_type="vertex_ai" 时:存储加密后的 Service Account JSON
# - auth_type="oauth2" 时:存储加密后的 {client_id, client_secret, token_url, scope}
auth_config = Column(Text, nullable=True)
name = Column(String(100), nullable=False) # 密钥名称(必填,用于识别)
note = Column(String(500), nullable=True) # 备注说明(可选)

View File

@@ -4,7 +4,7 @@ ProviderEndpoint 相关的 API 模型定义
import re
from datetime import datetime
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Literal, Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
@@ -166,7 +166,15 @@ class EndpointAPIKeyCreate(BaseModel):
default=None, min_length=1, description="支持的 API 格式列表(必填,路由层校验)"
)
api_key: str = Field(..., min_length=3, max_length=500, description="API Key将自动加密")
api_key: str = Field(default="", max_length=500, description="API Key标准认证时必填,将自动加密)")
auth_type: Literal["api_key", "vertex_ai"] = Field(
default="api_key",
description="认证类型api_key标准 API Key或 vertex_aiVertex AI Service Account"
)
auth_config: Optional[Dict[str, Any]] = Field(
default=None,
description="认证配置JSONvertex_ai 时存储完整 Service Account JSON"
)
name: str = Field(..., min_length=1, max_length=100, description="密钥名称(必填,用于识别)")
# 成本计算
@@ -313,7 +321,15 @@ class EndpointAPIKeyUpdate(BaseModel):
)
api_key: Optional[str] = Field(
default=None, min_length=3, max_length=500, description="API Key将自动加密"
default=None, min_length=3, max_length=500, description="API Key标准认证时使用,将自动加密)"
)
auth_type: Optional[Literal["api_key", "vertex_ai"]] = Field(
default=None,
description="认证类型api_key标准 API Key或 vertex_aiVertex AI Service Account"
)
auth_config: Optional[Dict[str, Any]] = Field(
default=None,
description="认证配置JSONvertex_ai 时存储完整 Service Account JSON"
)
name: Optional[str] = Field(default=None, min_length=1, max_length=100, description="密钥名称")
rate_multipliers: Optional[Dict[str, float]] = Field(
@@ -445,6 +461,8 @@ class EndpointAPIKeyResponse(BaseModel):
# Key 信息(脱敏)
api_key_masked: str = Field(..., description="脱敏后的 Key")
api_key_plain: Optional[str] = Field(default=None, description="完整的 Key")
auth_type: str = Field(default="api_key", description="认证类型api_key 或 vertex_ai")
# auth_config 不在响应中返回(包含敏感信息),前端通过 auth_type 判断类型
name: str = Field(..., description="密钥名称")
# 成本计算

View File

@@ -74,6 +74,87 @@ class GeminiUsageMetadata(BaseModelWithExtras):
total_token_count: int = Field(default=0, alias="totalTokenCount")
# ---------------------------------------------------------------------------
# 文件 API 模型
# ---------------------------------------------------------------------------
class GeminiFileMetadata(BaseModelWithExtras):
"""
Gemini 文件元数据
用于上传文件时指定的元数据信息
"""
display_name: Optional[str] = Field(default=None, alias="displayName")
class GeminiFileUploadRequest(BaseModelWithExtras):
"""
Gemini 文件上传请求
用于 media.upload API 的请求体
"""
file: Optional[GeminiFileMetadata] = None
class GeminiFile(BaseModelWithExtras):
"""
Gemini 文件资源
表示已上传到 Gemini API 的文件
"""
name: Optional[str] = None # 文件名格式files/xxx
display_name: Optional[str] = Field(default=None, alias="displayName")
mime_type: Optional[str] = Field(default=None, alias="mimeType")
size_bytes: Optional[str] = Field(default=None, alias="sizeBytes")
create_time: Optional[str] = Field(default=None, alias="createTime")
update_time: Optional[str] = Field(default=None, alias="updateTime")
expiration_time: Optional[str] = Field(default=None, alias="expirationTime")
sha256_hash: Optional[str] = Field(default=None, alias="sha256Hash")
uri: Optional[str] = None # 文件 URI用于在请求中引用
download_uri: Optional[str] = Field(default=None, alias="downloadUri")
state: Optional[str] = None # PROCESSING, ACTIVE, FAILED
error: Optional[Dict[str, Any]] = None
# 视频文件元数据
video_metadata: Optional[Dict[str, Any]] = Field(default=None, alias="videoMetadata")
class GeminiFileListResponse(BaseModelWithExtras):
"""
Gemini 文件列表响应
用于 files.list API 的响应体
"""
files: Optional[List["GeminiFile"]] = None
next_page_token: Optional[str] = Field(default=None, alias="nextPageToken")
class GeminiFileUploadResponse(BaseModelWithExtras):
"""
Gemini 文件上传响应
用于 media.upload API 的响应体
"""
file: Optional[GeminiFile] = None
class GeminiFilePart(BaseModelWithExtras):
"""
Gemini 文件引用部分
用于在请求内容中引用已上传的文件
使用 file_data 字段引用文件 URI
"""
file_data: Optional[Dict[str, Any]] = Field(default=None, alias="fileData")
# fileData 格式:{"mimeType": "...", "fileUri": "..."}
# ---------------------------------------------------------------------------
# Thought Signature 常量
# ---------------------------------------------------------------------------