fix(tui): persist displayed cost high-water mark across sessions

The high-water mark prevents the footer cost from reversing during API
reconciliation (#244), but it was not persisted in SessionMetadata so
session restarts could still show a lower cost than before the restart.

Store displayed_cost_high_water_usd/cny in session metadata and restore
via max(saved_high_water, current_total) so old sessions fall back to the
restored total with no regression.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
lbcheng
2026-05-08 19:22:47 +08:00
parent b6b1599b8c
commit 53076e23f2
5 changed files with 30 additions and 4 deletions
+15
View File
@@ -108,6 +108,13 @@ pub struct SessionMetadata {
/// Accumulated sub-agent/background LLM cost in CNY (for persisted billing).
#[serde(default)]
pub subagent_cost_cny: f64,
/// Max-ever displayed session+subagent cost in USD (preserves #244
/// monotonic guarantee across session restarts).
#[serde(default)]
pub displayed_cost_high_water_usd: f64,
/// Max-ever displayed session+subagent cost in CNY.
#[serde(default)]
pub displayed_cost_high_water_cny: f64,
}
impl SessionMetadata {
@@ -117,6 +124,8 @@ impl SessionMetadata {
self.session_cost_cny = other.session_cost_cny;
self.subagent_cost_usd = other.subagent_cost_usd;
self.subagent_cost_cny = other.subagent_cost_cny;
self.displayed_cost_high_water_usd = other.displayed_cost_high_water_usd;
self.displayed_cost_high_water_cny = other.displayed_cost_high_water_cny;
}
}
@@ -606,6 +615,8 @@ pub fn create_saved_session_with_mode(
session_cost_cny: 0.0,
subagent_cost_usd: 0.0,
subagent_cost_cny: 0.0,
displayed_cost_high_water_usd: 0.0,
displayed_cost_high_water_cny: 0.0,
},
messages: capped_messages,
system_prompt: merge_truncation_note(
@@ -877,6 +888,8 @@ mod tests {
session_cost_cny: 0.0,
subagent_cost_usd: 0.0,
subagent_cost_cny: 0.0,
displayed_cost_high_water_usd: 0.0,
displayed_cost_high_water_cny: 0.0,
},
system_prompt: None,
context_references: Vec::new(),
@@ -907,6 +920,8 @@ mod tests {
session_cost_cny: 0.0,
subagent_cost_usd: 0.0,
subagent_cost_cny: 0.0,
displayed_cost_high_water_usd: 0.0,
displayed_cost_high_water_cny: 0.0,
},
system_prompt: None,
context_references: Vec::new(),
+2
View File
@@ -1655,6 +1655,8 @@ impl App {
metadata.session_cost_cny = self.session.session_cost_cny;
metadata.subagent_cost_usd = self.session.subagent_cost;
metadata.subagent_cost_cny = self.session.subagent_cost_cny;
metadata.displayed_cost_high_water_usd = self.session.displayed_cost_high_water;
metadata.displayed_cost_high_water_cny = self.session.displayed_cost_high_water_cny;
}
/// Recompute the displayed cost high-water mark. Called any time a cost
+2
View File
@@ -615,6 +615,8 @@ mod tests {
session_cost_cny: 0.0,
subagent_cost_usd: 0.0,
subagent_cost_cny: 0.0,
displayed_cost_high_water_usd: 0.0,
displayed_cost_high_water_cny: 0.0,
}
}
+9 -4
View File
@@ -6068,12 +6068,17 @@ fn apply_loaded_session(app: &mut App, session: &SavedSession) -> bool {
app.session.subagent_cost = session.metadata.subagent_cost_usd;
app.session.subagent_cost_cny = session.metadata.subagent_cost_cny;
app.session.subagent_cost_event_seqs.clear();
// Set high-water marks to the restored totals so the footer never
// reverses when reconciliation events fire on a resumed session.
// Restore the high-water marks from persisted metadata so the
// monotonic cost guarantee (#244) survives session restarts.
// Take the max with the current totals — old sessions without
// persisted high-water fields deserialise to 0.0 and fall back to
// the restored total with no regression.
let total_restored_usd = app.session.session_cost + app.session.subagent_cost;
let total_restored_cny = app.session.session_cost_cny + app.session.subagent_cost_cny;
app.session.displayed_cost_high_water = total_restored_usd;
app.session.displayed_cost_high_water_cny = total_restored_cny;
app.session.displayed_cost_high_water =
session.metadata.displayed_cost_high_water_usd.max(total_restored_usd);
app.session.displayed_cost_high_water_cny =
session.metadata.displayed_cost_high_water_cny.max(total_restored_cny);
app.session.last_prompt_tokens = None;
app.session.last_completion_tokens = None;
app.session.last_prompt_cache_hit_tokens = None;
+2
View File
@@ -699,6 +699,8 @@ fn saved_session_with_messages(messages: Vec<Message>) -> SavedSession {
session_cost_cny: 0.0,
subagent_cost_usd: 0.0,
subagent_cost_cny: 0.0,
displayed_cost_high_water_usd: 0.0,
displayed_cost_high_water_cny: 0.0,
},
messages,
system_prompt: None,