using System; using System.Globalization; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Threading.Tasks; namespace Gott.Services { public class KeyCheckResult { public string Status { get; set; } // "ok", "invalid", "rate_limited", "quota_exceeded", "error" public string Message { get; set; } public string Plan { get; set; } // "Max", "Pro", "Pro/Max", "API Console", "Unknown" public bool IsSubscription { get; set; } public double? Usage7d { get; set; } public double? Usage5h { get; set; } public double? Overage { get; set; } public string Status7d { get; set; } public string Status5h { get; set; } public string StatusOverage { get; set; } } public static class AnthropicKeyChecker { public static async Task CheckKeyAsync(IHttpClientFactory httpFactory, string apiKey) { try { var client = httpFactory.CreateClient(); client.Timeout = TimeSpan.FromSeconds(30); var payload = JsonSerializer.Serialize(new { model = "claude-sonnet-4-20250514", max_tokens = 1, messages = new[] { new { role = "user", content = "hi" } } }); var request = new HttpRequestMessage(HttpMethod.Post, "https://api.anthropic.com/v1/messages"); request.Headers.Add("x-api-key", apiKey); request.Headers.Add("anthropic-version", "2023-06-01"); request.Content = new StringContent(payload, Encoding.UTF8, "application/json"); var resp = await client.SendAsync(request); var statusCode = (int)resp.StatusCode; if (statusCode == 401) return new KeyCheckResult { Status = "invalid", Message = "Ungültig oder abgelaufen (401)", Plan = "Unknown" }; if (statusCode == 429) return new KeyCheckResult { Status = "rate_limited", Message = "Rate Limited (429)", Plan = "Unknown" }; if (statusCode == 403) return new KeyCheckResult { Status = "quota_exceeded", Message = "Insufficient Quota (403)", Plan = "Unknown" }; if (statusCode >= 400) return new KeyCheckResult { Status = "error", Message = $"API-Fehler ({statusCode})", Plan = "Unknown" }; // Key gültig – Header auslesen var headers = resp.Headers; var d7Util = GetHeader(headers, "anthropic-ratelimit-unified-7d-utilization", "unified-7d-utilization"); var d5hUtil = GetHeader(headers, "anthropic-ratelimit-unified-5h-utilization", "unified-5h-utilization"); var overageUtil = GetHeader(headers, "anthropic-ratelimit-unified-overage-utilization", "unified-overage-utilization"); var d7Status = GetHeader(headers, "anthropic-ratelimit-unified-7d-status", "unified-7d-status"); var d5hStatus = GetHeader(headers, "anthropic-ratelimit-unified-5h-status", "unified-5h-status"); var overageStatus = GetHeader(headers, "anthropic-ratelimit-unified-overage-status", "unified-overage-status"); var fallbackPct = GetHeader(headers, "anthropic-ratelimit-unified-fallback-percentage", "unified-fallback-percentage"); var isSubscription = d7Util != null || d5hUtil != null; // Plan-Erkennung string plan; if (!isSubscription) plan = "API Console"; else if (overageUtil != null && double.TryParse(overageUtil, NumberStyles.Float, CultureInfo.InvariantCulture, out var ov) && ov > 0) plan = "Max"; else if (fallbackPct == null) plan = "Pro"; else plan = "Pro/Max"; var result = new KeyCheckResult { Status = "ok", Plan = plan, IsSubscription = isSubscription, Status7d = d7Status, Status5h = d5hStatus, StatusOverage = overageStatus }; if (d7Util != null && double.TryParse(d7Util, NumberStyles.Float, CultureInfo.InvariantCulture, out var d7)) result.Usage7d = Math.Round(d7 * 100, 1); if (d5hUtil != null && double.TryParse(d5hUtil, NumberStyles.Float, CultureInfo.InvariantCulture, out var d5)) result.Usage5h = Math.Round(d5 * 100, 1); if (overageUtil != null && double.TryParse(overageUtil, NumberStyles.Float, CultureInfo.InvariantCulture, out var ovVal)) result.Overage = Math.Round(ovVal * 100, 1); return result; } catch (Exception ex) { return new KeyCheckResult { Status = "error", Message = ex.Message, Plan = "Unknown" }; } } private static string GetHeader(HttpResponseHeaders headers, params string[] names) { foreach (var name in names) if (headers.TryGetValues(name, out var values)) return values.FirstOrDefault(); return null; } } }