namespace AnchorCli.OpenRouter; /// /// Tracks token usage and calculates costs for the session. /// internal sealed class TokenTracker { public long SessionInputTokens { get; private set; } public long SessionOutputTokens { get; private set; } public int RequestCount { get; private set; } /// 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) { SessionInputTokens += inputTokens; SessionOutputTokens += outputTokens; LastInputTokens = inputTokens; RequestCount++; } 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}"; }