feat(pool,scheduling): 号池调度维度、配额冷却机制与管理后台重构

- 新增 scheduling_dimensions 模块,为每个 Key 计算多维调度状态(手动/冷却/熔断/成本/健康)
- 新增 quota_cooldown 模块,统一判定 Key 的有效冷却原因
- Pool 管理后台 API 扩展 Key 详情字段(调度状态/维度/配额/OAuth 信息)
- 前端 Pool 管理页面重写,支持调度状态展示、批量清理封禁 Key
- Handler 基类增加请求调度元数据采集,stream telemetry 增强
- 请求时间线组件增强,支持 attempted 候选展示
- Kiro OAuth 凭证导入解析改进
- 新增 usage 表 provider_key 索引迁移
- 补充调度维度、配额冷却、候选枚举等单元测试

Closes #197

Co-authored-by: AAEE86 <33052466+AAEE86@users.noreply.github.com>
This commit is contained in:
fawney19
2026-03-03 09:22:20 +08:00
parent f787b1b02a
commit 11997c024e
40 changed files with 4419 additions and 823 deletions

View File

@@ -1,4 +1,5 @@
import client from '../client'
import type { AllowedModels, ProxyConfig } from './types/provider'
export interface PoolKeyStatus {
key_id: string
@@ -78,15 +79,80 @@ export interface PoolKeyDetail {
key_name: string
is_active: boolean
auth_type: string
oauth_expires_at?: number | null
oauth_invalid_at?: number | null
oauth_invalid_reason?: string | null
oauth_plan_type?: string | null
quota_updated_at?: number | null
health_score?: number
circuit_breaker_open?: boolean
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
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: number
sticky_sessions: number
lru_score: number | null
created_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[]
scheduling_score?: number
candidate_eligible?: boolean
scheduling_blocked_count?: number
scheduling_degraded_count?: number
scheduling_dimensions?: PoolSchedulingDimension[]
}
export interface PoolSchedulingReason {
code: string
label: string
blocking: boolean
source: 'manual' | 'pool' | 'health' | 'policy' | string
ttl_seconds?: number | null
detail?: string | null
}
export interface PoolSchedulingDimension {
code: string
label: string
status: 'ok' | 'degraded' | 'blocked' | string
blocking: boolean
source: 'manual' | 'pool' | 'health' | 'policy' | string
weight: number
score: number
ttl_seconds?: number | null
detail?: string | null
}
export interface PoolKeysPageResponse {
@@ -103,18 +169,6 @@ export interface PoolKeysQuery {
status?: 'all' | 'active' | 'cooldown' | 'inactive'
}
export interface PoolKeyImportItem {
name: string
api_key: string
auth_type?: string
}
export interface BatchImportResponse {
imported: number
skipped: number
errors: { index: number; reason: string }[]
}
export interface PoolBatchAction {
key_ids: string[]
action: 'enable' | 'disable' | 'delete' | 'clear_cooldown' | 'reset_cost'
@@ -133,14 +187,6 @@ export async function listPoolKeys(
return response.data
}
export async function batchImportPoolKeys(
providerId: string,
keys: PoolKeyImportItem[],
): Promise<BatchImportResponse> {
const response = await client.post(`/api/admin/pool/${providerId}/keys/batch-import`, { keys })
return response.data
}
export async function batchActionPoolKeys(
providerId: string,
body: PoolBatchAction,
@@ -148,3 +194,10 @@ export async function batchActionPoolKeys(
const response = await client.post(`/api/admin/pool/${providerId}/keys/batch-action`, body)
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`)
return response.data
}