Merge remote-tracking branch 'origin/pr/1192' into work/v0.8.34

# Conflicts:
#	crates/tui/src/commands/session.rs
#	crates/tui/src/session_manager.rs
#	crates/tui/src/tui/session_picker.rs
#	crates/tui/src/tui/sidebar.rs
#	crates/tui/src/tui/ui.rs
This commit is contained in:
Hunter Bown
2026-05-12 23:18:55 -05:00
8 changed files with 34 additions and 14 deletions
+5 -2
View File
@@ -74,7 +74,9 @@ pub fn tokens(app: &mut App) -> CommandResult {
.replace("{total}", &app.session.total_tokens.to_string())
.replace(
"{cost}",
&app.format_cost_amount_precise(app.session_cost_for_currency(app.cost_currency)),
&app.format_cost_amount_precise(
app.displayed_session_cost_for_currency(app.cost_currency),
),
)
.replace("{api_messages}", &message_count.to_string())
.replace("{chat_messages}", &chat_count.to_string())
@@ -84,9 +86,10 @@ pub fn tokens(app: &mut App) -> CommandResult {
/// Show session cost breakdown
pub fn cost(app: &mut App) -> CommandResult {
let total = app.displayed_session_cost_for_currency(app.cost_currency);
let report = tr(app.ui_locale, MessageId::CmdCostReport).replace(
"{cost}",
&app.format_cost_amount_precise(app.session_cost_for_currency(app.cost_currency)),
&app.format_cost_amount_precise(total),
);
CommandResult::message(report)
}
+1
View File
@@ -58,6 +58,7 @@ fn rename_with_manager(
u64::from(app.session.total_tokens),
app.system_prompt.as_ref(),
);
app.sync_cost_to_metadata(&mut session.metadata);
session.metadata.title = new_title.to_string();
match manager.save_session(&session) {
+1
View File
@@ -28,6 +28,7 @@ pub fn save(app: &mut App, path: Option<&str>) -> CommandResult {
app.system_prompt.as_ref(),
Some(app.mode.label()),
);
app.sync_cost_to_metadata(&mut session.metadata);
session.artifacts = app.session_artifacts.clone();
let sessions_dir = save_path
+2 -1
View File
@@ -3060,13 +3060,14 @@ fn fork_session(session_id: Option<String>, last: bool, workspace: &Path) -> Res
.system_prompt
.as_ref()
.map(|text| SystemPrompt::Text(text.clone()));
let forked = create_saved_session(
let mut forked = create_saved_session(
&saved.messages,
&saved.metadata.model,
&saved.metadata.workspace,
saved.metadata.total_tokens,
system_prompt.as_ref(),
);
forked.metadata.copy_cost_from(&saved.metadata);
manager.save_session(&forked)?;
let source_title = saved.metadata.title.trim();
+12
View File
@@ -1784,6 +1784,18 @@ impl App {
self.refresh_displayed_cost_high_water();
}
/// Copy current session/subagent cost accumulators into session metadata
/// for persistence.
pub fn sync_cost_to_metadata(&self, metadata: &mut crate::session_manager::SessionMetadata) {
metadata.cost.session_cost_usd = self.session.session_cost;
metadata.cost.session_cost_cny = self.session.session_cost_cny;
metadata.cost.subagent_cost_usd = self.session.subagent_cost;
metadata.cost.subagent_cost_cny = self.session.subagent_cost_cny;
metadata.cost.displayed_cost_high_water_usd = self.session.displayed_cost_high_water;
metadata.cost.displayed_cost_high_water_cny =
self.session.displayed_cost_high_water_cny;
}
/// Recompute the displayed cost high-water mark. Called any time a cost
/// counter is mutated; never decreases.
pub fn refresh_displayed_cost_high_water(&mut self) {
+3 -3
View File
@@ -17,8 +17,8 @@ use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use crate::palette;
use crate::session_manager::{
SavedSession, SessionManager, SessionMetadata, extract_title, extract_user_prompt,
strip_thinking_tags,
SavedSession, SessionCostSnapshot, SessionManager, SessionMetadata, extract_title,
extract_user_prompt, strip_thinking_tags,
};
use crate::tui::views::{ModalKind, ModalView, ViewAction, ViewEvent};
@@ -801,7 +801,7 @@ mod tests {
model: "deepseek-v4-pro".to_string(),
workspace: std::path::PathBuf::from("/tmp"),
mode: Some("agent".to_string()),
cost: crate::session_manager::SessionCostSnapshot::default(),
cost: SessionCostSnapshot::default(),
}
}
+4 -4
View File
@@ -1339,22 +1339,22 @@ fn render_context_panel(f: &mut Frame, area: Rect, app: &App) {
)));
// ── Session cost ─────────────────────────────────────────────
let total_cost = app.displayed_session_cost_for_currency(app.cost_currency);
let displayed_total = app.displayed_session_cost_for_currency(app.cost_currency);
let session_cost = app.session_cost_for_currency(app.cost_currency);
let agent_cost = app.subagent_cost_for_currency(app.cost_currency);
let real_total = session_cost + agent_cost;
// Only show the additive breakdown when it matches the displayed
// total; when the high-water mark is in effect (post-reconciliation),
// the breakdown would not sum to the displayed value (#244).
let cost_line = if (total_cost - real_total).abs() < COST_EQ_TOLERANCE {
let cost_line = if (displayed_total - real_total).abs() < COST_EQ_TOLERANCE {
format!(
"cost: {} (session {} + agents {})",
app.format_cost_amount(total_cost),
app.format_cost_amount(displayed_total),
app.format_cost_amount(session_cost),
app.format_cost_amount(agent_cost)
)
} else {
format!("cost: {}", app.format_cost_amount(total_cost),)
format!("cost: {}", app.format_cost_amount(displayed_total))
};
lines.push(Line::from(Span::styled(
cost_line,
+6 -4
View File
@@ -3481,6 +3481,7 @@ fn build_session_snapshot(app: &App, manager: &SessionManager) -> SavedSession {
app.system_prompt.as_ref(),
);
updated.metadata.mode = Some(app.mode.as_setting().to_string());
app.sync_cost_to_metadata(&mut updated.metadata);
updated.context_references = app.session_context_references.clone();
updated.artifacts = app.session_artifacts.clone();
updated
@@ -3505,6 +3506,7 @@ fn build_session_snapshot(app: &App, manager: &SessionManager) -> SavedSession {
Some(app.mode.as_setting()),
)
};
app.sync_cost_to_metadata(&mut session.metadata);
session.context_references = app.session_context_references.clone();
session.artifacts = app.session_artifacts.clone();
session
@@ -6874,10 +6876,10 @@ fn apply_loaded_session(app: &mut App, session: &SavedSession) -> bool {
app.workspace.clone_from(&session.metadata.workspace);
app.session.total_tokens = u32::try_from(session.metadata.total_tokens).unwrap_or(u32::MAX);
app.session.total_conversation_tokens = app.session.total_tokens;
app.session.session_cost = 0.0;
app.session.session_cost_cny = 0.0;
app.session.subagent_cost = 0.0;
app.session.subagent_cost_cny = 0.0;
app.session.session_cost = session.metadata.cost.session_cost_usd;
app.session.session_cost_cny = session.metadata.cost.session_cost_cny;
app.session.subagent_cost = session.metadata.cost.subagent_cost_usd;
app.session.subagent_cost_cny = session.metadata.cost.subagent_cost_cny;
app.session.subagent_cost_event_seqs.clear();
// Restore the high-water marks from persisted metadata so the
// monotonic cost guarantee (#244) survives session restarts.