Gin + GORM + pure-Go SQLite. Users/auth (JWT), API key management with quotas, proxy gateway with weighted channel failover and health checks, usage/billing ledger, cross-protocol conversion (Anthropic Messages / OpenAI Chat Completions / OpenAI Responses), and channel/model admin API. Channels declare native API formats and auto-convert the rest. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
54 lines
1.4 KiB
Go
54 lines
1.4 KiB
Go
package channel
|
|
|
|
import (
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"openteam/server/internal/store"
|
|
)
|
|
|
|
// StartHealthCheck runs the periodic health-check loop in a goroutine.
|
|
// Only channels that are enabled and have a bound test model are checked.
|
|
func (s *Service) StartHealthCheck() {
|
|
go func() {
|
|
ticker := time.NewTicker(s.cfg.HealthCheck.Interval)
|
|
defer ticker.Stop()
|
|
s.runHealthCheck()
|
|
for range ticker.C {
|
|
s.runHealthCheck()
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (s *Service) runHealthCheck() {
|
|
var channels []store.Channel
|
|
if err := s.db.Where("enabled = ?", true).Find(&channels).Error; err != nil {
|
|
s.log.Warn("health check query failed", zap.Error(err))
|
|
return
|
|
}
|
|
for i := range channels {
|
|
ch := &channels[i]
|
|
// Skip channels already in cooldown until the cooldown elapses.
|
|
if ch.HealthStatus == "cooldown" {
|
|
continue
|
|
}
|
|
ok, _, _, err := s.Test(ch)
|
|
if err != nil || !ok {
|
|
s.MarkFailure(ch.ID)
|
|
continue
|
|
}
|
|
s.MarkHealthy(ch.ID, "healthy")
|
|
}
|
|
}
|
|
|
|
// RecoverCooldown moves channels back from cooldown to degraded after the
|
|
// cooldown window, giving them another chance to pass the health check.
|
|
// Called periodically; keeps cooldown bounded.
|
|
func (s *Service) RecoverCooldown() {
|
|
cutoff := time.Now().Add(-s.cfg.HealthCheck.Cooldown)
|
|
s.db.Model(&store.Channel{}).
|
|
Where("health_status = ? AND updated_at < ?", "cooldown", cutoff).
|
|
Updates(map[string]any{"health_status": "degraded", "health_failures": 0})
|
|
}
|