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:
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user