Files
Aether/frontend/src/api/endpoints/pool.ts
T
MMEXA ec1783a35c Merge remote-tracking branch 'origin/main' into codex/pool-key-bulk-management-20260714
# Conflicts:
#	apps/aether-gateway/src/handlers/admin/request/provider/tasks.rs
#	frontend/src/api/endpoints/pool.ts
2026-07-16 23:43:04 +08:00

565 lines
15 KiB
TypeScript

import client from '../client'
import { buildCacheKey, cachedRequest } from '@/utils/cache'
import type {
AllowedModels,
OAuthOrganizationInfo,
ProxyConfig,
UpstreamMetadata,
} from './types/provider'
import type { ProviderKeyStatusSnapshot } from './types/statusSnapshot'
const POOL_BATCH_ACTION_TIMEOUT_MS = 5 * 60 * 1000
export interface PoolKeyStatus {
key_id: string
key_name: string
is_active: boolean
cooldown_reason: string | null
cooldown_ttl_seconds: number | null
cost_window_usage: number
cost_limit: number | null
sticky_sessions: number
lru_score: number | null
}
export interface PoolStatusResponse {
provider_id: string
provider_name: string
pool_enabled: boolean
total_keys: number
total_sticky_sessions: number
provider_hot_count: number
provider_desired_hot: number
provider_in_flight: number
provider_ema_in_flight: number
provider_burst_pending: boolean
keys: PoolKeyStatus[]
}
/**
* 获取 Provider 的号池状态
*/
export async function getPoolStatus(providerId: string): Promise<PoolStatusResponse> {
const response = await client.get<PoolStatusResponse>(`/api/admin/providers/${providerId}/pool-status`)
return response.data
}
/**
* 清除指定 Key 的号池冷却状态
*/
export async function clearPoolCooldown(
providerId: string,
keyId: string,
): Promise<{ message: string }> {
const response = await client.post<{ message: string }>(
`/api/admin/providers/${providerId}/pool/clear-cooldown/${keyId}`,
)
return response.data
}
/**
* 重置指定 Key 的号池成本窗口
*/
export async function resetPoolCost(
providerId: string,
keyId: string,
): Promise<{ message: string }> {
const response = await client.post<{ message: string }>(
`/api/admin/providers/${providerId}/pool/reset-cost/${keyId}`,
)
return response.data
}
// ---------------------------------------------------------------------------
// Pool management API (standalone page)
// ---------------------------------------------------------------------------
export interface PoolOverviewItem {
provider_id: string
provider_name: string
provider_type: string
total_keys: number
active_keys: number
cooldown_count: number
pool_enabled: boolean
provider_hot_count?: number
provider_desired_hot?: number
provider_in_flight?: number
provider_ema_in_flight?: number
provider_burst_pending?: boolean
}
export interface PoolOverviewResponse {
items: PoolOverviewItem[]
}
export interface PoolPresetModeMeta {
value: string
label: string
}
export interface PoolPresetMeta {
name: string
label: string
description: string
providers: string[]
modes?: PoolPresetModeMeta[] | null
default_mode?: string | null
mutex_group?: string | null
evidence_hint?: string | null
}
export interface PoolKeyDetail {
key_id: string
key_name: string
provider_type?: string | null
is_active: boolean
auth_type: string
auth_type_by_format?: Record<string, 'api_key' | 'bearer'> | null
allow_auth_channel_mismatch_formats?: string[] | null
credential_kind?: 'raw_secret' | 'oauth_session' | 'service_account' | string | null
runtime_auth_kind?: 'api_key' | 'bearer' | 'service_account' | 'mixed' | 'unknown' | string | null
oauth_managed?: boolean
oauth_header_auth?: boolean
can_refresh_oauth?: boolean
can_export_oauth?: boolean
can_edit_oauth?: boolean
oauth_expires_at?: number | null
oauth_invalid_at?: number | null // 兼容字段;优先使用 status_snapshot.oauth
oauth_invalid_reason?: string | null // 兼容字段;优先使用 status_snapshot.oauth
oauth_plan_type?: string | null
oauth_account_id?: string | null
oauth_account_user_id?: string | null
oauth_account_name?: string | null
oauth_organizations?: OAuthOrganizationInfo[] | null
oauth_temporary?: boolean | null
account_status_code?: string | null // 兼容字段;优先使用 status_snapshot.account
account_status_label?: string | null // 兼容字段;优先使用 status_snapshot.account
account_status_reason?: string | null // 兼容字段;优先使用 status_snapshot.account
account_status_blocked?: boolean // 兼容字段;优先使用 status_snapshot.account
account_status_recoverable?: boolean // 兼容字段;优先使用 status_snapshot.account
account_status_source?: string | null // 兼容字段;优先使用 status_snapshot.account
status_snapshot?: ProviderKeyStatusSnapshot | null
upstream_metadata?: UpstreamMetadata | null
quota_updated_at?: number | null
health_score?: number
circuit_breaker_open?: boolean
pool_score?: PoolKeyScoreDetail | null
api_formats?: string[]
rate_multipliers?: Record<string, number> | null
internal_priority?: number
rpm_limit?: number | null
cache_ttl_minutes?: number
max_probe_interval_minutes?: number
note?: string | null
allowed_models?: AllowedModels
capabilities?: Record<string, boolean> | null
auto_fetch_models?: boolean
locked_models?: string[] | null
model_include_patterns?: string[] | null
model_exclude_patterns?: string[] | null
proxy?: ProxyConfig | null
account_quota: string | null // compatibility only; UI should prefer status_snapshot.quota
cooldown_reason: string | null
cooldown_ttl_seconds: number | null
cost_window_usage: number
cost_limit: number | null
request_count: number
total_tokens: number
total_cost_usd: string
sticky_sessions: number
lru_score: number | null
created_at: string | null
imported_at?: string | null
last_used_at: string | null
scheduling_status?: 'available' | 'degraded' | 'blocked'
scheduling_reason?:
| 'available'
| 'manual_disabled'
| 'cooldown'
| 'circuit_open'
| 'cost_exhausted'
| 'cost_soft'
| 'cost'
| 'health_low'
| 'health_degraded'
| 'health'
| string
scheduling_label?: string
scheduling_reasons?: PoolSchedulingReason[]
}
export interface PoolSchedulingReason {
code: string
label: string
blocking: boolean
source: 'manual' | 'pool' | 'health' | 'policy' | string
ttl_seconds?: number | null
detail?: string | null
}
export interface PoolKeysPageResponse {
total: number
page: number
page_size: number
keys: PoolKeyDetail[]
}
export interface PoolKeyScoreDetail {
id: string
capability: string
scope_kind: string
scope_id: string | null
score: number
hard_state: PoolScoreHardState
score_version: number
score_reason: Record<string, unknown> | null
last_ranked_at: number | null
last_scheduled_at: number | null
last_success_at: number | null
last_failure_at: number | null
failure_count: number
last_probe_attempt_at: number | null
last_probe_success_at: number | null
last_probe_failure_at: number | null
probe_failure_count: number
probe_status: PoolScoreProbeStatus
updated_at: number
}
export type PoolScoreHardState =
| 'available'
| 'unknown'
| 'cooldown'
| 'quota_exhausted'
| 'auth_invalid'
| 'banned'
| 'inactive'
export type PoolScoreProbeStatus = 'never' | 'ok' | 'failed' | 'stale' | 'in_progress'
export interface PoolScoreKeySummary {
id: string
name: string
auth_type: string
is_active: boolean
internal_priority: number
last_used_at: number | null
}
export interface PoolMemberScoreItem extends PoolKeyScoreDetail {
pool_kind: string
pool_id: string
member_kind: string
member_id: string
key?: PoolScoreKeySummary | null
}
export interface PoolScoresResponse {
provider_id: string
page: number
page_size: number
filters: {
api_format?: string | null
model_id?: string | null
hard_state?: string | null
probe_status?: string | null
}
items: PoolMemberScoreItem[]
}
export interface PoolKeysQuery {
page?: number
page_size?: number
search?: string
status?:
| 'all'
| 'available'
| 'cooldown'
| 'inactive'
| 'invalid'
| 'expired'
| 'account_banned'
| 'quota_exhausted'
| 'account_forbidden'
| 'account_disabled'
| 'workspace_deactivated'
| 'account_verification'
| 'account_quarantined'
| 'account_blocked'
| 'rate_limited'
| 'cost_exhausted'
quick_selectors?: string[]
search_scope?: 'name' | 'full'
sort_by?: 'imported_at' | 'last_used_at' | 'score'
sort_order?: 'asc' | 'desc'
}
export interface PoolScoresQuery {
page?: number
page_size?: number
api_format?: string
model_id?: string
hard_state?: string
probe_status?: string
}
export interface PoolKeySelectionRequest {
search?: string
quick_selectors?: string[]
}
export interface PoolKeySelectionItem {
key_id: string
key_name: string
auth_type: string
auth_type_by_format?: Record<string, 'api_key' | 'bearer'> | null
allow_auth_channel_mismatch_formats?: string[] | null
credential_kind?: 'raw_secret' | 'oauth_session' | 'service_account' | string | null
runtime_auth_kind?: 'api_key' | 'bearer' | 'service_account' | 'mixed' | 'unknown' | string | null
oauth_managed?: boolean
oauth_header_auth?: boolean
can_refresh_oauth?: boolean
can_export_oauth?: boolean
can_edit_oauth?: boolean
}
export interface PoolKeySelectionResponse {
total: number
items: PoolKeySelectionItem[]
}
export interface PoolBatchAction {
key_ids: string[]
action:
| 'enable'
| 'disable'
| 'delete'
| 'clear_proxy'
| 'set_proxy'
| 'update_settings'
payload?: Record<string, unknown> | null
}
export interface PoolKeySharedSettingsPatch {
internal_priority?: number
rpm_limit?: number | null
concurrent_limit?: number | null
cache_ttl_minutes?: number
max_probe_interval_minutes?: number
is_active?: boolean
note?: string | null
}
export interface PoolKeyBatchUpdatePatch extends PoolKeySharedSettingsPatch {
api_formats?: string[]
auth_type_by_format?: Record<string, 'api_key' | 'bearer'> | null
allow_auth_channel_mismatch_formats?: string[] | null
rate_multipliers?: Record<string, number> | null
global_priority_by_format?: Record<string, number> | null
allowed_models?: AllowedModels
capabilities?: Record<string, boolean> | null
auto_fetch_models?: boolean
locked_models?: string[]
model_include_patterns?: string[]
model_exclude_patterns?: string[]
proxy?: ProxyConfig | null
}
export interface PoolKeyBatchUpdateRequest {
key_ids: string[]
patch: PoolKeyBatchUpdatePatch
}
export interface PoolKeyBatchModelSyncResult {
requested: number
attempted: number
succeeded: number
failed: number
skipped: number
error?: string
}
export interface PoolKeyBatchUpdateResponse {
affected: number
message: string
model_sync: PoolKeyBatchModelSyncResult | null
}
export interface PoolKeySettingsPatch extends PoolKeySharedSettingsPatch {
proxy_node_id?: string | null
}
export interface PoolBatchImportRequest {
keys: Array<{
name: string
api_key: string
auth_type: 'api_key' | 'bearer'
api_formats?: string[]
settings?: PoolKeySettingsPatch
}>
api_formats?: string[]
settings?: PoolKeySettingsPatch
}
export interface PoolBatchImportResult {
imported: number
skipped: number
errors: Array<{ index: number; reason: string }>
}
interface PoolReadOptions {
cacheTtlMs?: number
}
export async function getPoolOverview(
options: PoolReadOptions = {},
): Promise<PoolOverviewResponse> {
const cacheTtlMs = options.cacheTtlMs ?? 0
return cachedRequest(
'pool:overview',
async () => {
const response = await client.get<PoolOverviewResponse>('/api/admin/pool/overview')
return response.data
},
cacheTtlMs,
)
}
export async function getPoolSchedulingPresets(
options: PoolReadOptions = {},
): Promise<PoolPresetMeta[]> {
const cacheTtlMs = options.cacheTtlMs ?? 0
return cachedRequest(
'pool:scheduling-presets',
async () => {
const response = await client.get<PoolPresetMeta[]>('/api/admin/pool/scheduling-presets')
return response.data
},
cacheTtlMs,
)
}
export async function listPoolKeys(
providerId: string,
params: PoolKeysQuery = {},
options: PoolReadOptions = {},
): Promise<PoolKeysPageResponse> {
const normalizedParams = {
...params,
quick_selectors: params.quick_selectors?.length ? params.quick_selectors.join(',') : undefined,
}
const cacheKey = buildCacheKey(
`pool:keys:${providerId}`,
normalizedParams as Record<string, unknown>,
)
return cachedRequest(
cacheKey,
async () => {
const response = await client.get<PoolKeysPageResponse>(`/api/admin/pool/${providerId}/keys`, { params: normalizedParams })
return response.data
},
options.cacheTtlMs ?? 0,
)
}
export async function listPoolScores(
providerId: string,
params: PoolScoresQuery = {},
options: PoolReadOptions = {},
): Promise<PoolScoresResponse> {
const normalizedParams = { ...params }
const cacheKey = buildCacheKey(
`pool:scores:${providerId}`,
normalizedParams as Record<string, unknown>,
)
return cachedRequest(
cacheKey,
async () => {
const response = await client.get<PoolScoresResponse>(
`/api/admin/pool/${providerId}/scores`,
{ params: normalizedParams },
)
return response.data
},
options.cacheTtlMs ?? 0,
)
}
export async function resolvePoolKeySelection(
providerId: string,
body: PoolKeySelectionRequest,
): Promise<PoolKeySelectionResponse> {
const response = await client.post<PoolKeySelectionResponse>(
`/api/admin/pool/${providerId}/keys/resolve-selection`,
body,
{ timeout: POOL_BATCH_ACTION_TIMEOUT_MS },
)
return response.data
}
export async function batchActionPoolKeys(
providerId: string,
body: PoolBatchAction,
): Promise<{ affected: number; message: string; task_id?: string }> {
const response = await client.post(
`/api/admin/pool/${providerId}/keys/batch-action`,
body,
{ timeout: POOL_BATCH_ACTION_TIMEOUT_MS },
)
return response.data
}
export async function batchUpdatePoolKeys(
providerId: string,
body: PoolKeyBatchUpdateRequest,
): Promise<PoolKeyBatchUpdateResponse> {
const response = await client.patch<PoolKeyBatchUpdateResponse>(
`/api/admin/pool/${providerId}/keys/batch-update`,
body,
{ timeout: POOL_BATCH_ACTION_TIMEOUT_MS },
)
return response.data
}
export async function batchImportPoolKeys(
providerId: string,
body: PoolBatchImportRequest,
): Promise<PoolBatchImportResult> {
const response = await client.post<PoolBatchImportResult>(
`/api/admin/pool/${providerId}/keys/batch-import`,
body,
{ timeout: POOL_BATCH_ACTION_TIMEOUT_MS },
)
return response.data
}
export interface BatchDeleteTaskStatus {
task_id: string
status: 'pending' | 'running' | 'completed' | 'failed'
total: number
deleted: number
message: string
}
export async function getPoolBatchDeleteTask(
providerId: string,
taskId: string,
): Promise<BatchDeleteTaskStatus> {
const response = await client.get<BatchDeleteTaskStatus>(
`/api/admin/pool/${providerId}/keys/batch-delete-task/${taskId}`,
)
return response.data
}
export async function cleanupBannedPoolKeys(
providerId: string,
): Promise<{ affected: number; message: string }> {
const response = await client.post(
`/api/admin/pool/${providerId}/keys/cleanup-banned`,
undefined,
{ timeout: POOL_BATCH_ACTION_TIMEOUT_MS },
)
return response.data
}