Report the context a session holds, not what it has spent

The number on the status row was a running total of tokens spent, so it
could only ever climb: a session compacted from 128k down to 10k, or
cleared outright, went on reporting the larger figure, and disagreed with
the divider directly above it saying what the compaction had recovered.

It now reports what the model is holding -- prompt plus both cache
figures -- folded through `driver::context_after`, which is the one rule
the pump, the transcript and the phone all use: a turn sets it, a
compaction replaces it with what the compaction measured, and a clear
leaves it unmeasured. Unmeasured says so in words, because an empty
context and one nobody has counted used to look identical.

Taken from the turn's last assistant message rather than its `result`:
measured against CLI 2.1.237, a two-message turn reported a cache read of
40,211, being 14,259 and 25,952 -- the same conversation counted twice,
and no size the model ever held.
This commit is contained in:
iris committed 2026-08-30 01:53:43 -04:00
1 parent 81c8a57181
commit 5e11b9da80
12 files changed
+442 -130

No files matched your search

+9 -9
View File
@@ -21,7 +21,7 @@ use anyhow::{Context, Result};
use serde::Serialize;
use serde_json::Value;
use super::driver::Event;
use super::driver::{self, Event};
use super::transport::{Launch, Transport};
/// How much of a transcript's tail is replayed into the phone's view.
@@ -282,9 +282,9 @@ fn parse_row(line: &str) -> Option<Importable> {
/// The input tokens named in one `usage` object, added up.
///
/// Prompt plus cache creation plus cache read: all three are context the
/// model was given, and a cached token is cheaper but not free. Output is
/// deliberately left out -- it is what the turn produced, not what
/// continuing from here has to carry.
/// model was given -- the definition is [`driver::context_tokens`]; this
/// is the same three figures dug out of a raw line rather than a parsed
/// one, because these files reach tens of megabytes.
///
/// `None` for an empty blob, meaning no assistant turn has recorded usage.
/// Missing individual fields count as zero, which is what an absent
@@ -307,11 +307,11 @@ fn context_tokens(usage: &str) -> Option<u64> {
})
.unwrap_or(0)
};
Some(
field("input_tokens")
+ field("cache_creation_input_tokens")
+ field("cache_read_input_tokens"),
)
Some(driver::context_tokens(
field("input_tokens"),
field("cache_creation_input_tokens"),
field("cache_read_input_tokens"),
))
}
/// The first line of what a person typed, short enough for a list row.