fix(worker): record real raw token usage per run, not the uncached remainder
task_runs.tokens_in only ever held the API's uncached "input_tokens" field (off by a factor of ~400,000 on a resumed session), and tokens_out summed only the last result event instead of the whole session. TaskRunner now reads each run's cache-read/cache-write/input/output totals from the session transcript via a new ITranscriptUsageReader.ReadSessionTotalsAsync, storing the delta against prior runs on the same session so a --resume doesn't double-count. New task_runs.cache_read_tokens/cache_write_tokens columns; the Session tab now shows the raw total (what actually counts against the 5h/7d usage limit) with a breakdown tooltip.
This commit is contained in:
@@ -117,4 +117,58 @@ public sealed class UsageGateAndRunModelTests : IDisposable
|
||||
|
||||
Assert.Null(reloaded.Model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TaskRun_cache_token_fields_roundtrip_when_set()
|
||||
{
|
||||
var list = new ListEntity { Id = "l3", Name = "Test", CreatedAt = DateTime.UtcNow };
|
||||
var task = new TaskEntity { Id = "t3", ListId = "l3", Title = "T", Status = TaskStatus.Idle, CreatedAt = DateTime.UtcNow };
|
||||
var run = new TaskRunEntity
|
||||
{
|
||||
Id = "r3",
|
||||
TaskId = "t3",
|
||||
RunNumber = 1,
|
||||
IsRetry = false,
|
||||
Prompt = "do the thing",
|
||||
CacheReadTokens = 12345,
|
||||
CacheWriteTokens = 678,
|
||||
};
|
||||
_ctx.Lists.Add(list);
|
||||
_ctx.Tasks.Add(task);
|
||||
_ctx.TaskRuns.Add(run);
|
||||
await _ctx.SaveChangesAsync();
|
||||
|
||||
await using var freshCtx = new ClaudeDoDbContext(
|
||||
new DbContextOptionsBuilder<ClaudeDoDbContext>().UseSqlite($"Data Source={_dbPath}").Options);
|
||||
var reloaded = await freshCtx.TaskRuns.AsNoTracking().FirstAsync(r => r.Id == "r3");
|
||||
|
||||
Assert.Equal(12345, reloaded.CacheReadTokens);
|
||||
Assert.Equal(678, reloaded.CacheWriteTokens);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TaskRun_cache_token_fields_stay_null_when_not_set()
|
||||
{
|
||||
var list = new ListEntity { Id = "l4", Name = "Test", CreatedAt = DateTime.UtcNow };
|
||||
var task = new TaskEntity { Id = "t4", ListId = "l4", Title = "T", Status = TaskStatus.Idle, CreatedAt = DateTime.UtcNow };
|
||||
var run = new TaskRunEntity
|
||||
{
|
||||
Id = "r4",
|
||||
TaskId = "t4",
|
||||
RunNumber = 1,
|
||||
IsRetry = false,
|
||||
Prompt = "do the thing",
|
||||
};
|
||||
_ctx.Lists.Add(list);
|
||||
_ctx.Tasks.Add(task);
|
||||
_ctx.TaskRuns.Add(run);
|
||||
await _ctx.SaveChangesAsync();
|
||||
|
||||
await using var freshCtx = new ClaudeDoDbContext(
|
||||
new DbContextOptionsBuilder<ClaudeDoDbContext>().UseSqlite($"Data Source={_dbPath}").Options);
|
||||
var reloaded = await freshCtx.TaskRuns.AsNoTracking().FirstAsync(r => r.Id == "r4");
|
||||
|
||||
Assert.Null(reloaded.CacheReadTokens);
|
||||
Assert.Null(reloaded.CacheWriteTokens);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user