Support per-format provider key auth

This commit is contained in:
fawney19
2026-04-29 15:46:50 +08:00
parent 07a319259b
commit e751289dfb
67 changed files with 1244 additions and 420 deletions

View File

@@ -173,6 +173,7 @@ export interface ProviderKeyExport {
rate_multipliers?: Record<string, number> | null
internal_priority?: number
global_priority_by_format?: Record<string, number> | null
auth_type_by_format?: Record<string, 'api_key' | 'bearer'> | null
rpm_limit?: number | null
allowed_models?: string[] | null
capabilities?: Record<string, boolean>

View File

@@ -138,7 +138,8 @@ export async function addProviderKey(
data: {
api_formats: string[] // 支持的 API 格式列表(必填)
api_key: string
auth_type?: 'api_key' | 'service_account' | 'oauth' // 认证类型
auth_type?: 'api_key' | 'service_account' | 'oauth' | 'bearer' // 认证类型
auth_type_by_format?: Record<string, 'api_key' | 'bearer'> | null
auth_config?: Record<string, unknown> // 认证配置Vertex AI Service Account JSON
name: string
rate_multipliers?: Record<string, number> | null // 按 API 格式的成本倍率
@@ -167,6 +168,7 @@ export async function updateProviderKey(
api_formats: string[] // 支持的 API 格式列表
api_key: string
auth_type: 'api_key' | 'service_account' | 'oauth' | 'bearer' // 认证类型
auth_type_by_format: Record<string, 'api_key' | 'bearer'> | null
auth_config: Record<string, unknown> // 认证配置Vertex AI Service Account JSON
name: string
rate_multipliers: Record<string, number> | null // 按 API 格式的成本倍率

View File

@@ -104,8 +104,9 @@ export interface PoolKeyDetail {
key_name: string
is_active: boolean
auth_type: string
auth_type_by_format?: Record<string, 'api_key' | 'bearer'> | null
credential_kind?: 'raw_secret' | 'oauth_session' | 'service_account' | string | null
runtime_auth_kind?: 'api_key' | 'bearer' | 'service_account' | 'unknown' | string | null
runtime_auth_kind?: 'api_key' | 'bearer' | 'service_account' | 'mixed' | 'unknown' | string | null
oauth_managed?: boolean
can_refresh_oauth?: boolean
can_export_oauth?: boolean
@@ -206,8 +207,9 @@ export interface PoolKeySelectionItem {
key_id: string
key_name: string
auth_type: string
auth_type_by_format?: Record<string, 'api_key' | 'bearer'> | null
credential_kind?: 'raw_secret' | 'oauth_session' | 'service_account' | string | null
runtime_auth_kind?: 'api_key' | 'bearer' | 'service_account' | 'unknown' | string | null
runtime_auth_kind?: 'api_key' | 'bearer' | 'service_account' | 'mixed' | 'unknown' | string | null
oauth_managed?: boolean
can_refresh_oauth?: boolean
can_export_oauth?: boolean

View File

@@ -9,47 +9,51 @@ import {
sortApiFormats,
} from '@/api/endpoints/types'
const openaiAlias = (kind: string) => ['openai', kind].join(':')
const legacyEnumAlias = (kind: string) => ['OPENAI', kind].join('_')
describe('api format display helpers', () => {
it('maps historical OpenAI response aliases to current display names', () => {
expect(normalizeApiFormatAlias(openaiAlias('cli'))).toBe(API_FORMATS.OPENAI_RESPONSES)
expect(formatApiFormat(openaiAlias('cli'))).toBe('OpenAI Responses')
expect(formatApiFormatShort(openaiAlias('cli'))).toBe('OR')
expect(normalizeApiFormatAlias(openaiAlias('compact'))).toBe(API_FORMATS.OPENAI_RESPONSES_COMPACT)
expect(formatApiFormat(openaiAlias('compact'))).toBe('OpenAI Responses Compact')
expect(formatApiFormatShort(openaiAlias('compact'))).toBe('ORC')
it('normalizes current enum-style names to canonical api format ids', () => {
expect(normalizeApiFormatAlias('CLAUDE_MESSAGES')).toBe(API_FORMATS.CLAUDE_MESSAGES)
expect(normalizeApiFormatAlias('OPENAI_RESPONSES')).toBe(API_FORMATS.OPENAI_RESPONSES)
expect(normalizeApiFormatAlias('OPENAI_RESPONSES_COMPACT')).toBe(API_FORMATS.OPENAI_RESPONSES_COMPACT)
expect(normalizeApiFormatAlias('GEMINI_GENERATE_CONTENT')).toBe(API_FORMATS.GEMINI_GENERATE_CONTENT)
})
it('maps historical uppercase enum aliases to current display names', () => {
expect(normalizeApiFormatAlias(legacyEnumAlias('CLI'))).toBe(API_FORMATS.OPENAI_RESPONSES)
expect(formatApiFormat(legacyEnumAlias('CLI'))).toBe('OpenAI Responses')
expect(formatApiFormatShort(legacyEnumAlias('CLI'))).toBe('OR')
it('does not remap retired api format ids', () => {
expect(normalizeApiFormatAlias('openai:cli')).toBe('openai:cli')
expect(formatApiFormat('openai:cli')).toBe('openai:cli')
expect(formatApiFormatShort('openai:cli')).toBe('op')
expect(normalizeApiFormatAlias(legacyEnumAlias('COMPACT'))).toBe(API_FORMATS.OPENAI_RESPONSES_COMPACT)
expect(formatApiFormat(legacyEnumAlias('COMPACT'))).toBe('OpenAI Responses Compact')
expect(formatApiFormatShort(legacyEnumAlias('COMPACT'))).toBe('ORC')
expect(normalizeApiFormatAlias('openai:compact')).toBe('openai:compact')
expect(formatApiFormat('openai:compact')).toBe('openai:compact')
expect(formatApiFormatShort('openai:compact')).toBe('op')
})
it('sorts historical aliases in the same slot as their current formats', () => {
it('does not remap retired enum-style aliases', () => {
expect(normalizeApiFormatAlias('OPENAI_CLI')).toBe('openai_cli')
expect(formatApiFormat('OPENAI_CLI')).toBe('openai_cli')
expect(formatApiFormatShort('OPENAI_CLI')).toBe('op')
expect(normalizeApiFormatAlias('OPENAI_COMPACT')).toBe('openai_compact')
expect(formatApiFormat('OPENAI_COMPACT')).toBe('openai_compact')
expect(formatApiFormatShort('OPENAI_COMPACT')).toBe('op')
})
it('sorts only current canonical formats into known slots', () => {
expect(sortApiFormats([
openaiAlias('compact'),
'openai:compact',
API_FORMATS.OPENAI,
openaiAlias('cli'),
API_FORMATS.OPENAI_RESPONSES,
])).toEqual([
API_FORMATS.OPENAI,
openaiAlias('cli'),
openaiAlias('compact'),
API_FORMATS.OPENAI_RESPONSES,
'openai:compact',
])
})
it('groups uppercase historical aliases under OpenAI', () => {
expect(groupApiFormats([legacyEnumAlias('CLI')])).toEqual([{
family: 'openai',
label: 'OpenAI',
formats: [legacyEnumAlias('CLI')],
it('groups retired enum-style aliases as unknown raw families', () => {
expect(groupApiFormats(['OPENAI_CLI'])).toEqual([{
family: 'openai_cli',
label: 'openai_cli',
formats: ['OPENAI_CLI'],
}])
})
})

View File

@@ -16,7 +16,7 @@ export const API_FORMATS = {
export type APIFormat = typeof API_FORMATS[keyof typeof API_FORMATS]
// API 格式显示名称映射(按品牌分组Chat 在前CLI/Video 在后
// API 格式显示名称映射(按品牌分组)
export const API_FORMAT_LABELS: Record<string, string> = {
[API_FORMATS.CLAUDE_MESSAGES]: 'Claude Messages',
[API_FORMATS.OPENAI]: 'OpenAI Chat',
@@ -87,10 +87,8 @@ export const API_FORMAT_FAMILY_LABELS: Record<string, string> = {
// Kind 显示名称映射
export const API_FORMAT_KIND_LABELS: Record<string, string> = {
chat: 'Chat',
cli: 'CLI',
responses: 'Responses',
'responses:compact': 'Responses Compact',
compact: 'Compact',
messages: 'Messages',
generate_content: 'Generate Content',
image: 'Image',
@@ -110,6 +108,8 @@ export function parseApiFormat(format: string): { family: string; kind: string }
export function normalizeApiFormatAlias(format: string | null | undefined): string {
const raw = format?.trim() ?? ''
// Only normalize current enum-style frontend constants. Retired API format ids
// are migrated in the database and intentionally do not map at runtime.
switch (raw.toUpperCase()) {
case 'CLAUDE':
case 'CLAUDE_MESSAGES':

View File

@@ -230,8 +230,9 @@ export interface EndpointAPIKey {
api_key_masked: string
api_key_plain?: string | null
auth_type: 'api_key' | 'service_account' | 'oauth' | 'bearer' // 认证类型(必返回)
auth_type_by_format?: Record<string, 'api_key' | 'bearer'> | null
credential_kind?: 'raw_secret' | 'oauth_session' | 'service_account' | string | null
runtime_auth_kind?: 'api_key' | 'bearer' | 'service_account' | 'unknown' | string | null
runtime_auth_kind?: 'api_key' | 'bearer' | 'service_account' | 'mixed' | 'unknown' | string | null
oauth_managed?: boolean
can_refresh_oauth?: boolean
can_export_oauth?: boolean
@@ -384,7 +385,8 @@ export interface EndpointAPIKeyUpdate {
api_formats?: string[] // 支持的 API 格式列表
name?: string
api_key?: string // 仅在需要更新时提供
auth_type?: 'api_key' | 'service_account' | 'oauth' // 认证类型
auth_type?: 'api_key' | 'service_account' | 'oauth' | 'bearer' // 认证类型
auth_type_by_format?: Record<string, 'api_key' | 'bearer'> | null
auth_config?: Record<string, unknown> // 认证配置Vertex AI Service Account JSON
rate_multipliers?: Record<string, number> | null // 按 API 格式的成本倍率
internal_priority?: number