package channel import ( "context" "fmt" "opencatd-open/internal/dao" "opencatd-open/internal/store" "opencatd-open/internal/pkg/crypto" "net/http" "time" ) type HealthChecker struct { channelDAO *dao.ChannelDAO service *Service client *http.Client } func NewHealthChecker(channelDAO *dao.ChannelDAO, service *Service) *HealthChecker { return &HealthChecker{ channelDAO: channelDAO, service: service, client: &http.Client{ Timeout: 10 * time.Second, }, } } // CheckChannel performs a health check on a channel func (hc *HealthChecker) CheckChannel(ctx context.Context, channel *store.Channel) error { apiKey, err := crypto.Decrypt(channel.APIKeyEnc) if err != nil { return fmt.Errorf("failed to decrypt API key: %w", err) } // Simple health check: try to list models var url string switch channel.Provider { case store.ChannelProviderOpenAI: url = channel.UpstreamURL("chat", "/models") case store.ChannelProviderAnthropic: url = "https://api.anthropic.com/v1/models" default: url = channel.UpstreamURL("chat", "/models") } req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return fmt.Errorf("failed to create request: %w", err) } // Set headers based on provider switch channel.Provider { case store.ChannelProviderOpenAI, store.ChannelProviderCompatible: req.Header.Set("Authorization", "Bearer "+apiKey) case store.ChannelProviderAnthropic: req.Header.Set("x-api-key", apiKey) req.Header.Set("anthropic-version", "2023-06-01") } req.Header.Set("Content-Type", "application/json") resp, err := hc.client.Do(req) if err != nil { hc.service.RecordFailure(channel.ID) return fmt.Errorf("health check failed: %w", err) } defer resp.Body.Close() if resp.StatusCode == http.StatusOK { hc.service.RecordSuccess(channel.ID) return nil } hc.service.RecordFailure(channel.ID) return fmt.Errorf("health check returned status %d", resp.StatusCode) } // CheckAllChannels checks health of all enabled channels func (hc *HealthChecker) CheckAllChannels(ctx context.Context) error { channels, err := hc.channelDAO.ListEnabled() if err != nil { return err } for _, ch := range channels { if err := hc.CheckChannel(ctx, ch); err != nil { fmt.Printf("Channel %s health check failed: %v\n", ch.Name, err) } } return nil } // StartPeriodicCheck starts periodic health checks func (hc *HealthChecker) StartPeriodicCheck(ctx context.Context, interval time.Duration) { ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: if err := hc.CheckAllChannels(ctx); err != nil { fmt.Printf("Periodic health check error: %v\n", err) } } } }