1
0
Files
AnchorCli/Providers/GenericTokenExtractor.cs

90 lines
2.6 KiB
C#

using System.Net.Http.Headers;
using System.Text.Json;
namespace AnchorCli.Providers;
/// <summary>
/// Generic token extractor for any OpenAI-compatible endpoint.
/// Tries common header names and JSON body parsing.
/// </summary>
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;
}
}