namespace AnchorCli.OpenRouter; /// /// Tracks token usage and calculates costs for the session. /// internal sealed class TokenTracker { private readonly ChatSession _session; public TokenTracker(ChatSession session) { _session = session; } public string Provider { get; set; } = "Unknown"; public long SessionInputTokens => _session.SessionInputTokens; public long SessionOutputTokens => _session.SessionOutputTokens; public int RequestCount => _session.RequestCount; /// Maximum context window for the model (tokens). 0 = unknown. public int ContextLength { get; set; } /// Input tokens from the most recent API response — approximates current context size. public int LastInputTokens { get; private set; } /// USD per input token. public decimal InputPrice { get; set; } /// USD per output token. public decimal OutputPrice { get; set; } /// Fixed USD per API request. public decimal RequestPrice { get; set; } /// /// Record usage from one response (may span multiple LLM rounds). /// public void AddUsage(int inputTokens, int outputTokens) { _session.SessionInputTokens += inputTokens; _session.SessionOutputTokens += outputTokens; LastInputTokens = inputTokens; _session.RequestCount++; } public void Reset() { _session.SessionInputTokens = 0; _session.SessionOutputTokens = 0; _session.RequestCount = 0; LastInputTokens = 0; } private const int MaxContextReserve = 150_000; /// /// Returns true if the context is getting too large and should be compacted. /// Triggers at min(75% of model context, 150K tokens). /// public bool ShouldCompact() { if (LastInputTokens <= 0) return false; int threshold = ContextLength > 0 ? Math.Min((int)(ContextLength * 0.75), MaxContextReserve) : MaxContextReserve; return LastInputTokens >= threshold; } /// Context usage as a percentage (0-100). Returns -1 if context length is unknown. public double ContextUsagePercent => ContextLength > 0 && LastInputTokens > 0 ? (double)LastInputTokens / ContextLength * 100.0 : -1; /// /// Calculate cost for a single response. /// public decimal CalculateCost(int inputTokens, int outputTokens) => inputTokens * InputPrice + outputTokens * OutputPrice + RequestPrice; /// /// Total session cost. /// public decimal SessionCost => SessionInputTokens * InputPrice + SessionOutputTokens * OutputPrice + RequestCount * RequestPrice; public static string FormatTokens(long count) => count >= 1_000 ? $"{count / 1_000.0:F1}k" : count.ToString("N0"); public static string FormatCost(decimal cost) => cost < 0.01m ? $"${cost:F4}" : $"${cost:F2}"; }