Report live billing totals for in-progress runs

Run summaries previously populated billing only from the terminal
conclusion event, so the web UI's size chip showed dollar amounts only
after a run completed — even though the size letter was already derived
from live per-stage usage. Derive billing from the same projected total
the size uses. projected_billing already prefers the conclusion's
billing once a run concludes, so completed runs still report the
authoritative final total.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-08-06 11:54:32 -04:00
parent 0abf2297c0
commit a4db43a889
No known key found for this signature in database

View file

@ -1360,8 +1360,7 @@ pub(crate) fn build_summary(state: &RunProjection, run_id: &RunId) -> Run {
.conclusion
.as_ref()
.map(|conclusion| conclusion.timing);
let terminal_total = terminal_total_usd_micros(state);
let current_total = projected_billing(state).total_usd_micros;
let total_usd_micros = projected_billing(state).total_usd_micros;
Run {
id: *run_id,
@ -1405,10 +1404,10 @@ pub(crate) fn build_summary(state: &RunProjection, run_id: &RunId) -> Run {
completed_at,
},
timing: run_timing,
billing: terminal_total.map(|total_usd_micros| RunBillingSummary {
billing: total_usd_micros.map(|total_usd_micros| RunBillingSummary {
total_usd_micros: Some(total_usd_micros),
}),
size: RunSize::from_total_usd_micros(current_total),
size: RunSize::from_total_usd_micros(total_usd_micros),
ask_fabro: AskFabro::default(),
diff: diff_summary,
pull_request: state.pull_request.clone(),
@ -1421,14 +1420,6 @@ pub(crate) fn build_summary(state: &RunProjection, run_id: &RunId) -> Run {
}
}
fn terminal_total_usd_micros(state: &RunProjection) -> Option<i64> {
state
.conclusion
.as_ref()
.and_then(|conclusion| conclusion.billing.as_ref())
.and_then(|billing| billing.total_usd_micros)
}
pub(crate) fn projected_billing(state: &RunProjection) -> BilledTokenCounts {
if let Some(billing) = state
.conclusion
@ -1693,8 +1684,8 @@ mod tests {
BilledTokenCounts, BlockedReason, Checkpoint, CheckpointRecord, CommandTermination,
EventBody, FailureCategory, FailureDetail, FailureReason, Graph, McpServerStatus, Node,
Outcome, ParallelBranchId, PendingReason, PermissionLevel, PullRequestCreationStatus,
PullRequestLink, QuestionType, ReasoningEffort, RunApprovalState, RunBlobId,
RunControlAction, RunDiff, RunEvent, RunSize, RunSpec, RunStatus, Speed,
PullRequestLink, QuestionType, ReasoningEffort, RunApprovalState, RunBillingSummary,
RunBlobId, RunControlAction, RunDiff, RunEvent, RunSize, RunSpec, RunStatus, Speed,
StageContextWindowBreakdownItem, StageContextWindowCategory, StageContextWindowCountMethod,
StageContextWindowProjection, StageContextWindowStaleness, StageContextWindowWarning,
StageHandler, StageModelUsage, StageOutcome, StageState, StageTiming, SubAgentStatus,
@ -5284,7 +5275,12 @@ mod tests {
let summary = build_summary(&state, &fixtures::RUN_1);
assert_eq!(summary.size, RunSize::S);
assert_eq!(summary.billing, None);
assert_eq!(
summary.billing,
Some(RunBillingSummary {
total_usd_micros: Some(20_000_001),
})
);
}
#[test]