feat(worker): add OAuth usage client + poller
Adds Usage/ with ClaudeOAuthUsageClient (reads the access token Claude Code keeps fresh at ~/.claude/.credentials.json, calls the Anthropic OAuth usage endpoint, defensively parses buckets/limits), UsageState (threadsafe last-good-snapshot holder that never regresses on failure), and UsageMonitorService (BackgroundService polling on the new usage_poll_interval_seconds config, min 15s, one poll at startup, warns at most once per distinct error).
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
using ClaudeDo.Worker.Config;
|
||||
using ClaudeDo.Worker.Usage.Interfaces;
|
||||
|
||||
namespace ClaudeDo.Worker.Usage;
|
||||
|
||||
/// <summary>
|
||||
/// Polls <see cref="IUsageClient"/> on <see cref="WorkerConfig.UsagePollIntervalSeconds"/> and keeps
|
||||
/// <see cref="UsageState"/> current. Polls once immediately at startup. A failure is logged as a
|
||||
/// warning at most once per distinct error message, to avoid log spam on a persistent outage.
|
||||
/// </summary>
|
||||
public sealed class UsageMonitorService : BackgroundService
|
||||
{
|
||||
private readonly IUsageClient _client;
|
||||
private readonly UsageState _state;
|
||||
private readonly WorkerConfig _config;
|
||||
private readonly ILogger<UsageMonitorService> _logger;
|
||||
private string? _lastLoggedError;
|
||||
|
||||
public UsageMonitorService(
|
||||
IUsageClient client, UsageState state, WorkerConfig config, ILogger<UsageMonitorService> logger)
|
||||
{
|
||||
_client = client;
|
||||
_state = state;
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
await TickAsync(stoppingToken);
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(_config.UsagePollIntervalSeconds), stoppingToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task TickAsync(CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var snapshot = await _client.GetUsageAsync(ct);
|
||||
_state.ReportSuccess(snapshot);
|
||||
_lastLoggedError = null;
|
||||
}
|
||||
catch (OperationCanceledException) when (ct.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_state.ReportFailure(ex.Message, DateTime.UtcNow);
|
||||
|
||||
if (_lastLoggedError != ex.Message)
|
||||
{
|
||||
_logger.LogWarning(ex, "UsageMonitorService: failed to fetch usage: {Message}", ex.Message);
|
||||
_lastLoggedError = ex.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user