show token usage on per-agent web ui after each turn

This commit is contained in:
damocles 2026-05-17 02:25:25 +02:00
parent ca86bcf4bd
commit ce740483c6
6 changed files with 91 additions and 1 deletions

View file

@ -156,6 +156,23 @@ impl EventStore {
}
}
/// Token usage emitted by claude in the final `result` stream-json event.
/// All counts are in tokens. `None` fields mean the server didn't report them.
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct TokenUsage {
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_read_input_tokens: u64,
pub cache_creation_input_tokens: u64,
}
impl TokenUsage {
/// Total context consumed this turn (input + cache reads + cache writes).
pub fn context_tokens(&self) -> u64 {
self.input_tokens + self.cache_read_input_tokens + self.cache_creation_input_tokens
}
}
/// Authoritative turn-loop state. The harness owns it; the web UI
/// reads via `/api/state` and renders. Lives alongside the bus
/// because everyone who has a `Bus` already has the right handle to
@ -191,6 +208,12 @@ pub struct Bus {
/// Model name passed to `claude --model`. Default `haiku`; the
/// operator can override at runtime via `POST /api/model`.
model: Arc<Mutex<String>>,
/// Last token usage reported by claude (from the `result` stream-json
/// event). `None` until the first turn with usage data completes.
/// Updated on every turn; survives across turns within one harness
/// process lifetime (resets on container restart, which is fine —
/// it's a live indicator, not a cumulative counter).
last_usage: Arc<Mutex<Option<TokenUsage>>>,
/// One-shot: next `run_claude` call drops `--continue`, starting
/// a fresh claude session. Set by `POST /api/new-session` from
/// the per-agent web UI; consumed (cleared back to false) by the
@ -220,6 +243,7 @@ impl Bus {
store,
state: Arc::new(Mutex::new((TurnState::Idle, now_unix()))),
model: Arc::new(Mutex::new(initial_model)),
last_usage: Arc::new(Mutex::new(None)),
skip_continue_once: Arc::new(AtomicBool::new(false)),
}
}
@ -258,6 +282,17 @@ impl Bus {
}
}
/// Record the latest token usage from a completed turn.
pub fn record_usage(&self, usage: TokenUsage) {
*self.last_usage.lock().unwrap() = Some(usage);
}
/// Last known token usage, or `None` if no turn has completed yet.
#[must_use]
pub fn last_usage(&self) -> Option<TokenUsage> {
*self.last_usage.lock().unwrap()
}
/// Update the harness's authoritative turn-loop state. Records
/// the transition time so `state_snapshot` can return a since-age.
pub fn set_state(&self, next: TurnState) {