Files
tbphp d4699dd2b1 feat(health): 支持凭据按模型冷却与统一恢复 (#599)
* feat(health): 支持凭据按模型冷却与统一恢复

* fix(health): 修复模型冷却的错误契约与恢复边界

* fix(web): 修复模型冷却展示与重置反馈

* fix(web): 保持分组健康与恢复反馈一致

* fix(health): 修复停用分组的模型冷却身份展示
2026-09-09 08:05:43 +08:00

93 lines
2.5 KiB
Go

package state
import (
"sort"
"time"
)
type CredentialRuntimeView struct {
ID uint
GroupID uint
Version uint64
IdentityGeneration uint64
WeightManual *int
Status CredentialStatus
AuthState CredentialAuthState
CooldownUntil time.Time
ModelCooldowns map[string]time.Time
Blacklisted bool
FailureCount int
QuotaRemaining *float64
QuotaResetAt time.Time
}
func (view CredentialRuntimeView) AuthReady() bool {
return view.AuthState.normalize() == CredentialAuthStateReady
}
func (view CredentialRuntimeView) ObservedQuotaRemaining() *float64 {
return cloneFloat(view.QuotaRemaining)
}
type CredentialRuntimeState string
const (
CredentialRuntimeAvailable CredentialRuntimeState = "available"
CredentialRuntimeDisabled CredentialRuntimeState = "disabled"
CredentialRuntimeBlacklisted CredentialRuntimeState = "blacklisted"
CredentialRuntimeCooldown CredentialRuntimeState = "cooldown"
)
func (view CredentialRuntimeView) RuntimeState(now time.Time) CredentialRuntimeState {
if view.Status != CredentialStatusActive {
return CredentialRuntimeDisabled
}
if view.Blacklisted {
return CredentialRuntimeBlacklisted
}
if view.CooldownUntil.After(now) {
return CredentialRuntimeCooldown
}
return CredentialRuntimeAvailable
}
func runtimeView(entry *CredentialEntry) CredentialRuntimeView {
return CredentialRuntimeView{
ID: entry.ID,
GroupID: entry.GroupID,
Version: entry.Version,
IdentityGeneration: entry.IdentityGeneration,
WeightManual: cloneWeight(entry.WeightManual),
Status: entry.Status,
AuthState: entry.AuthState.normalize(),
CooldownUntil: entry.CooldownUntil,
ModelCooldowns: cloneModelCooldowns(entry.ModelCooldowns),
Blacklisted: entry.Blacklisted,
FailureCount: entry.FailureCount,
QuotaRemaining: cloneFloat(entry.quotaRemaining),
QuotaResetAt: entry.quotaResetAt,
}
}
func sortRuntimeViews(views []CredentialRuntimeView) {
sort.Slice(views, func(i, j int) bool {
if views[i].GroupID != views[j].GroupID {
return views[i].GroupID < views[j].GroupID
}
return views[i].ID < views[j].ID
})
}
func (r *CredentialRegistry) Snapshot() []CredentialRuntimeView {
r.mu.RLock()
views := make([]CredentialRuntimeView, 0, len(r.credentialGroups))
for _, bucket := range r.buckets {
for _, entry := range bucket {
views = append(views, runtimeView(entry))
}
}
r.mu.RUnlock()
sortRuntimeViews(views)
return views
}