mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat(pool): 账号停用检测、OAuth refresh 失效标记与号池管理性能优化
- 新增 account_deactivated 关键词检测,覆盖 error_handler / health_policy / account_state / 前端 - 401 健康策略分级冷却:账号永久停用 1h,临时认证失败 60s - OAuth refresh token 失败时标记 oauth_invalid(不停用 key),成功时清除非账号级标记 - 号池管理 API 使用 load_only + SQL 聚合替代全量拉取,减少查询开销 - Redis 冷却统计改用 batch_count_provider_cooldowns (SCAN) 替代逐 key 检查 - 前端时间线移除 executableTimeline 过滤层,Provider 选择改为非阻塞加载
This commit is contained in:
@@ -642,10 +642,6 @@ const normalizeTimelineStatus = (value: unknown): CandidateRecord['status'] => {
|
||||
return 'failed'
|
||||
}
|
||||
|
||||
const isPlannedOnlyStatus = (status: CandidateRecord['status']): boolean => {
|
||||
return status === 'available'
|
||||
}
|
||||
|
||||
const extractPoolGroupId = (candidate: CandidateRecord): string | null => {
|
||||
const extra = candidate.extra_data
|
||||
if (!extra || typeof extra !== 'object' || Array.isArray(extra)) return null
|
||||
@@ -671,22 +667,6 @@ const rawTimeline = computed<CandidateRecord[]>(() => {
|
||||
})
|
||||
})
|
||||
|
||||
// 仅保留”已进入执行链路”的节点,过滤预创建但未执行的 available 占位记录。
|
||||
// unused 候选:代表因前序候选成功而未被执行的备选项。
|
||||
// - 号池内 unused key:不显示(号池只展示实际参与调度的 key)
|
||||
// - 非号池 unused:每个 candidate_index 保留 retry_index=0(代表该候选存在但未执行)
|
||||
const executableTimeline = computed<CandidateRecord[]>(() => {
|
||||
return rawTimeline.value.filter(candidate => {
|
||||
if (isPlannedOnlyStatus(candidate.status)) return false
|
||||
if (candidate.status === 'unused') {
|
||||
// 号池内 unused key 不需要展示
|
||||
if (extractPoolGroupId(candidate) !== null) return false
|
||||
// 非号池的 unused retry slot 只保留首个
|
||||
return candidate.retry_index === 0
|
||||
}
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
const schedulingAudit = computed<Record<string, unknown> | null>(() => {
|
||||
const metadata = props.requestMetadata
|
||||
@@ -698,7 +678,7 @@ const schedulingAudit = computed<Record<string, unknown> | null>(() => {
|
||||
|
||||
const poolAttemptCandidates = computed<CandidateRecord[]>(() => {
|
||||
// 新链路:优先使用后端写入的 extra_data.pool_group_id。
|
||||
const fromTrace = executableTimeline.value.filter((candidate) => extractPoolGroupId(candidate) !== null)
|
||||
const fromTrace = rawTimeline.value.filter((candidate) => extractPoolGroupId(candidate) !== null)
|
||||
if (fromTrace.length > 0) {
|
||||
return fromTrace
|
||||
}
|
||||
@@ -823,8 +803,8 @@ const poolAttemptKeySet = computed<Set<string>>(() => {
|
||||
})
|
||||
|
||||
const timeline = computed<CandidateRecord[]>(() => {
|
||||
if (poolAttemptCandidates.value.length === 0) return executableTimeline.value
|
||||
return executableTimeline.value.filter(
|
||||
if (poolAttemptCandidates.value.length === 0) return rawTimeline.value
|
||||
return rawTimeline.value.filter(
|
||||
(candidate) => !poolAttemptKeySet.value.has(makeAttemptKey(candidate.candidate_index, candidate.retry_index)),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -4,11 +4,15 @@ const ACCOUNT_BLOCK_REASON_KEYWORDS = [
|
||||
'account blocked',
|
||||
'account has been disabled',
|
||||
'account disabled',
|
||||
'account has been deactivated',
|
||||
'account_deactivated',
|
||||
'account deactivated',
|
||||
'organization has been disabled',
|
||||
'organization_disabled',
|
||||
'validation_required',
|
||||
'verify your account',
|
||||
'suspended',
|
||||
'deactivated',
|
||||
// Kiro quota refresher 写入的确切文本
|
||||
'账户已封禁',
|
||||
// Antigravity quota refresher 写入的确切文本
|
||||
|
||||
@@ -1426,7 +1426,10 @@ async function selectProvider(id: string) {
|
||||
clearTimeout(keysSearchDebounceTimer)
|
||||
keysSearchDebounceTimer = null
|
||||
}
|
||||
await Promise.all([loadKeys(), loadProviderData(id)])
|
||||
const keysTask = loadKeys()
|
||||
// Provider summary is non-blocking for key list rendering.
|
||||
void loadProviderData(id)
|
||||
await keysTask
|
||||
if (requestId !== selectProviderRequestId) return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user