Files
Aether/frontend/src/api/endpoints/keys.ts
T

389 lines
11 KiB
TypeScript
Raw Normal View History

2025-12-10 20:52:44 +08:00
import client from '../client'
import type { EndpointAPIKey, AllowedModels } from './types'
import type { QuotaStatusSnapshot } from './types'
2025-12-10 20:52:44 +08:00
// Re-export types for convenience
export type { EndpointAPIKey, AllowedModels }
interface KeyRequestOptions {
timeout?: number
}
2025-12-10 20:52:44 +08:00
/**
* 能力定义类型
*/
export interface CapabilityDefinition {
name: string
display_name: string
description: string
match_mode: 'exclusive' | 'compatible'
config_mode?: 'user_configurable' | 'auto_detect' | 'request_param'
short_name?: string
}
/**
* 模型支持的能力响应类型
*/
export interface ModelCapabilitiesResponse {
model: string
global_model_id?: string
global_model_name?: string
supported_capabilities: string[]
capability_details: CapabilityDefinition[]
error?: string
}
/**
* 获取所有能力定义
*/
export async function getAllCapabilities(): Promise<CapabilityDefinition[]> {
const response = await client.get('/api/capabilities')
return response.data.capabilities
}
/**
* 获取用户可配置的能力列表
*/
export async function getUserConfigurableCapabilities(): Promise<CapabilityDefinition[]> {
const response = await client.get('/api/capabilities/user-configurable')
return response.data.capabilities
}
/**
* 获取指定模型支持的能力列表
*/
export async function getModelCapabilities(modelName: string): Promise<ModelCapabilitiesResponse> {
const response = await client.get(`/api/capabilities/model/${encodeURIComponent(modelName)}`)
return response.data
}
/**
* 获取完整的 API Key(用于查看和复制)
*/
export interface RevealKeyResult {
auth_type: 'api_key' | 'service_account' | 'oauth' | 'bearer'
api_key?: string
refresh_token?: string
auth_config?: string | Record<string, unknown>
}
export async function revealEndpointKey(keyId: string): Promise<RevealKeyResult> {
const response = await client.get(`/api/admin/endpoints/keys/${keyId}/reveal`)
return response.data
}
/**
* 导出 OAuth Key 凭据(扁平 JSON,用于跨实例迁移)
*/
export async function exportKey(keyId: string): Promise<Record<string, unknown>> {
const response = await client.get(`/api/admin/endpoints/keys/${keyId}/export`)
return response.data
}
2025-12-10 20:52:44 +08:00
/**
* 删除 Key
2025-12-10 20:52:44 +08:00
*/
export async function deleteEndpointKey(keyId: string): Promise<{ message: string }> {
const response = await client.delete(`/api/admin/endpoints/keys/${keyId}`)
return response.data
}
/**
* 批量删除 Keys
*/
export interface BatchDeleteKeysResult {
success_count: number
failed_count: number
failed: Array<{ id: string; error: string }>
}
export async function batchDeleteEndpointKeys(ids: string[]): Promise<BatchDeleteKeysResult> {
const response = await client.post('/api/admin/endpoints/keys/batch-delete', { ids })
return response.data
}
// ========== Provider 级别的 Keys API ==========
2025-12-10 20:52:44 +08:00
/**
* 获取 Provider 的所有 Keys
2025-12-10 20:52:44 +08:00
*/
2026-05-16 12:52:49 +08:00
export interface ProviderKeysPageResponse {
total: number
page: number
page_size: number
keys: EndpointAPIKey[]
}
export interface ProviderKeysPageQuery {
page?: number
page_size?: number
}
type ProviderKeysPagePayload = ProviderKeysPageResponse | EndpointAPIKey[]
function normalizeProviderKeysPage(
value: ProviderKeysPagePayload,
page: number,
pageSize: number,
): ProviderKeysPageResponse {
if (Array.isArray(value)) {
const start = value.length > pageSize ? (page - 1) * pageSize : 0
const keys = value.slice(start, start + pageSize)
return {
total: value.length,
page,
page_size: pageSize,
keys,
}
}
const keys = Array.isArray(value.keys) ? value.keys : []
return {
total: typeof value.total === 'number' && Number.isFinite(value.total)
? value.total
: keys.length,
page: typeof value.page === 'number' && Number.isFinite(value.page)
? value.page
: page,
page_size: typeof value.page_size === 'number' && Number.isFinite(value.page_size)
? value.page_size
: pageSize,
keys,
}
}
2026-05-16 12:52:49 +08:00
export async function getProviderKeysPage(
providerId: string,
params: ProviderKeysPageQuery = {},
): Promise<ProviderKeysPageResponse> {
const page = params.page ?? 1
const pageSize = params.page_size ?? 20
const response = await client.get<ProviderKeysPagePayload>(
2026-05-16 12:52:49 +08:00
`/api/admin/endpoints/providers/${providerId}/keys`,
{ params: { page, page_size: pageSize } },
)
return normalizeProviderKeysPage(response.data, page, pageSize)
2026-05-16 12:52:49 +08:00
}
export async function getProviderKeys(providerId: string): Promise<EndpointAPIKey[]> {
// 后端默认 limit=100,这里主动分页拉取,避免账号数 >100 时前端被截断
const pageSize = 1000
let skip = 0
const allKeys: EndpointAPIKey[] = []
while (true) {
const response = await client.get(`/api/admin/endpoints/providers/${providerId}/keys`, {
params: { skip, limit: pageSize },
})
const batch = Array.isArray(response.data) ? (response.data as EndpointAPIKey[]) : []
allKeys.push(...batch)
if (batch.length < pageSize) break
skip += pageSize
}
return allKeys
}
/**
* 为 Provider 添加 Key
*/
export async function addProviderKey(
providerId: string,
data: {
api_formats: string[] // 支持的 API 格式列表(必填)
api_key: string
2026-04-29 15:46:50 +08:00
auth_type?: 'api_key' | 'service_account' | 'oauth' | 'bearer' // 认证类型
auth_type_by_format?: Record<string, 'api_key' | 'bearer'> | null
allow_auth_channel_mismatch_formats?: string[] | null
auth_config?: Record<string, unknown> // 认证配置(Vertex AI Service Account JSON
name: string
rate_multipliers?: Record<string, number> | null // 按 API 格式的成本倍率
internal_priority?: number
rpm_limit?: number | null // RPM 限制(留空=自适应模式)
concurrent_limit?: number | null // 并发请求上限(留空或 0=不限制)
cache_ttl_minutes?: number
max_probe_interval_minutes?: number
allowed_models?: AllowedModels
capabilities?: Record<string, boolean>
note?: string
auto_fetch_models?: boolean // 是否启用自动获取模型
model_include_patterns?: string[] // 模型包含规则
model_exclude_patterns?: string[] // 模型排除规则
}
): Promise<EndpointAPIKey> {
const response = await client.post(`/api/admin/endpoints/providers/${providerId}/keys`, data)
return response.data
}
/**
* 更新 Key
*/
export async function updateProviderKey(
keyId: string,
data: Partial<{
api_formats: string[] // 支持的 API 格式列表
api_key: string
auth_type: 'api_key' | 'service_account' | 'oauth' | 'bearer' // 认证类型
2026-04-29 15:46:50 +08:00
auth_type_by_format: Record<string, 'api_key' | 'bearer'> | null
allow_auth_channel_mismatch_formats: string[] | null
auth_config: Record<string, unknown> // 认证配置(Vertex AI Service Account JSON
name: string
rate_multipliers: Record<string, number> | null // 按 API 格式的成本倍率
internal_priority: number
global_priority_by_format: Record<string, number> | null // 按 API 格式的全局优先级
rpm_limit: number | null // RPM 限制(留空=自适应模式)
concurrent_limit: number | null // 并发请求上限(留空或 0=不限制)
cache_ttl_minutes: number
max_probe_interval_minutes: number
allowed_models: AllowedModels
locked_models: string[] // 被锁定的模型列表
capabilities: Record<string, boolean> | null
is_active: boolean
note: string
auto_fetch_models: boolean // 是否启用自动获取模型
model_include_patterns: string[] // 模型包含规则
model_exclude_patterns: string[] // 模型排除规则
proxy: import('./types').ProxyConfig | null // Key 级别代理配置
}>,
requestOptions?: KeyRequestOptions,
): Promise<EndpointAPIKey> {
const response = await client.put(
`/api/admin/endpoints/keys/${keyId}`,
data,
requestOptions,
)
2025-12-10 20:52:44 +08:00
return response.data
}
/**
* 清除 Key 的 OAuth 失效标记
*/
export async function clearOAuthInvalid(keyId: string): Promise<{ message: string }> {
const response = await client.post(`/api/admin/endpoints/keys/${keyId}/clear-oauth-invalid`)
return response.data
}
2026-05-07 03:23:20 +08:00
/**
* 重置 Key 的当前周期统计起点(Codex 号池)
*/
export async function resetProviderKeyCycleStats(keyId: string): Promise<{
message: string
reset_at: number
windows: number
}> {
const response = await client.post(`/api/admin/endpoints/keys/${keyId}/reset-cycle-stats`)
return response.data
}
/**
* 刷新 Provider 的所有 Key 限额信息(Codex / Antigravity
*/
export interface RefreshQuotaResult {
success: number
failed: number
total: number
results: Array<{
key_id: string
key_name: string
status:
| 'success'
| 'no_metadata'
| 'quota_exhausted'
| 'workspace_deactivated'
| 'auth_invalid'
| 'forbidden'
| 'banned'
| 'error'
// provider 级 bucket 数据;前端应按当前 provider_type 包装回 upstream_metadata.<provider_type>
metadata?: Record<string, unknown>
quota_snapshot?: QuotaStatusSnapshot
message?: string
status_code?: number
}>
}
export async function refreshProviderQuota(
providerId: string,
keyIds?: string[],
): Promise<RefreshQuotaResult> {
const body = keyIds && keyIds.length > 0 ? { key_ids: keyIds } : undefined
const response = await client.post(
`/api/admin/endpoints/providers/${providerId}/refresh-quota`,
body,
{ timeout: 5 * 60 * 1000 },
)
return response.data
}
2026-07-04 06:10:53 +08:00
export interface ConsumeCodexResetCreditPayload {
idempotency_key: string
}
export interface ConsumeCodexResetCreditResult {
key_id: string
status: 'success' | 'noop' | 'unknown' | 'error' | string
outcome:
| 'reset'
| 'already_redeemed'
| 'nothing_to_reset'
| 'no_credit'
| 'unknown'
| 'error'
| string
idempotency_key: string
refresh_status?: 'success' | 'failed' | string
refresh_error?: string | null
metadata?: Record<string, unknown>
quota_snapshot?: QuotaStatusSnapshot
message?: string
status_code?: number
}
export async function consumeCodexResetCredit(
keyId: string,
payload: ConsumeCodexResetCreditPayload,
): Promise<ConsumeCodexResetCreditResult> {
const response = await client.post(
`/api/admin/endpoints/keys/${keyId}/codex-reset-credit/consume`,
payload,
{ timeout: 5 * 60 * 1000 },
)
return response.data
}
/**
* 批量导入 OAuth 凭据(通用)
* 支持的 Provider 类型:Codex、Antigravity、GeminiCli、ClaudeCode、Kiro
*/
export interface BatchImportResultItem {
index: number
status: 'success' | 'error'
key_id?: string
key_name?: string
auth_method?: string
error?: string
}
export interface BatchImportResult {
total: number
success: number
failed: number
results: BatchImportResultItem[]
}
export async function batchImportOAuth(
providerId: string,
credentials: string,
proxyNodeId?: string
): Promise<BatchImportResult> {
const response = await client.post(`/api/admin/provider-oauth/providers/${providerId}/batch-import`, {
credentials,
proxy_node_id: proxyNodeId || undefined,
})
return response.data
}