using System.Net.Http.Headers; using System.Text.Json; namespace AnchorCli.Providers; /// /// Generic token extractor for any OpenAI-compatible endpoint. /// Tries common header names and JSON body parsing. /// internal sealed class GenericTokenExtractor : ITokenExtractor { public string ProviderName => "Generic"; public (int inputTokens, int outputTokens)? ExtractTokens(HttpResponseHeaders headers, string? responseBody) { // Try various common header names var headerNames = new[] { "x-total-tokens", "x-ai-response-tokens", "x-tokens", "x-prompt-tokens", "x-completion-tokens" }; foreach (var headerName in headerNames) { if (headers.TryGetValues(headerName, out var values)) { if (int.TryParse(values.FirstOrDefault(), out var tokens)) { // Assume all tokens are output if we can't determine split return (0, tokens); } } } // Fallback: try parsing from response body JSON if (!string.IsNullOrEmpty(responseBody)) { try { using var doc = JsonDocument.Parse(responseBody); var root = doc.RootElement; // Try standard OpenAI format: usage.prompt_tokens, usage.completion_tokens if (root.TryGetProperty("usage", out var usage)) { var prompt = usage.TryGetProperty("prompt_tokens", out var p) ? p.GetInt32() : 0; var completion = usage.TryGetProperty("completion_tokens", out var c) ? c.GetInt32() : 0; if (prompt > 0 || completion > 0) { return (prompt, completion); } } } catch { // Ignore parsing errors } } return null; } public int? ExtractLatency(HttpResponseHeaders headers) { // Try various common latency headers var headerNames = new[] { "x-response-time", "x-response-timing", "x-latency-ms", "x-duration-ms" }; foreach (var headerName in headerNames) { if (headers.TryGetValues(headerName, out var values)) { if (int.TryParse(values.FirstOrDefault(), out var latency)) { return latency; } } } return null; } }