From 436204eb2b614f04490700126de39fb05e5be6e4 Mon Sep 17 00:00:00 2001 From: Tryanks Date: Tue, 13 Oct 2026 09:07:12 +0800 Subject: [PATCH] perf(protocol): send usage changes without full session status --- crates/protocol/src/event.rs | 4 ++++ crates/protocol/src/lib.rs | 2 ++ crates/runtime/src/app/snapshots.rs | 17 +++++++++++++++-- crates/ui/src/store/mod.rs | 24 ++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/crates/protocol/src/event.rs b/crates/protocol/src/event.rs index 96673acf1..df7c41dc8 100644 --- a/crates/protocol/src/event.rs +++ b/crates/protocol/src/event.rs @@ -68,6 +68,10 @@ pub enum ServerEvent { }, SessionEvent(StoredEvent), SessionStatusReplaced(Box), + SessionUsageChanged { + usage: Option, + context_window: Option, + }, SessionPlanReplaced(SessionPlan), ProvidersReplaced(ProvidersStatus), GitStatusReplaced(GitStatusStatus), diff --git a/crates/protocol/src/lib.rs b/crates/protocol/src/lib.rs index 883533adb..f306fd084 100644 --- a/crates/protocol/src/lib.rs +++ b/crates/protocol/src/lib.rs @@ -150,6 +150,8 @@ pub use wire::{ // host then sends the text as an ordinary turn. Index subscriptions may carry // known_index; matching content receives IndexUnchanged instead of a snapshot. // ReadFilePreview rejects files larger than 512 KiB before transferring bytes. +// SessionUsageChanged carries usage and meter capacity when the rest of the +// session status is unchanged; subscription replies remain full statuses. pub const PROTOCOL_VERSION: u32 = 12; #[cfg(test)] diff --git a/crates/runtime/src/app/snapshots.rs b/crates/runtime/src/app/snapshots.rs index 4250ac99d..197f3b1ab 100644 --- a/crates/runtime/src/app/snapshots.rs +++ b/crates/runtime/src/app/snapshots.rs @@ -109,12 +109,25 @@ impl DomainDiff { .collect(); changed_ids.sort_unstable(); for id in changed_ids { - let status = session_statuses[id].clone(); + let status = &session_statuses[id]; + let usage_only = self.session_statuses.get_mut(id).is_some_and(|previous| { + previous.usage = status.usage; + previous.context_window = status.context_window; + previous == status + }); + let event = if usage_only { + ServerEvent::SessionUsageChanged { + usage: status.usage, + context_window: status.context_window, + } + } else { + ServerEvent::SessionStatusReplaced(Box::new(status.clone())) + }; emit_replacement( Topic::SessionStatus { session_id: id.to_string(), }, - ServerEvent::SessionStatusReplaced(Box::new(status)), + event, cx, ); } diff --git a/crates/ui/src/store/mod.rs b/crates/ui/src/store/mod.rs index c0dfa8ff5..05f069c72 100644 --- a/crates/ui/src/store/mod.rs +++ b/crates/ui/src/store/mod.rs @@ -1342,6 +1342,30 @@ impl WorkspaceStore { self.sync_active_conversation_ui(); } } + ( + Topic::SessionStatus { session_id }, + ServerEvent::SessionUsageChanged { + usage, + context_window, + }, + ) if self.baseline_topics.contains(&envelope.topic) => { + if let Some(status) = self + .threads + .get_mut(session_id) + .and_then(|thread| thread.status.as_mut()) + { + status.usage = *usage; + status.context_window = *context_window; + } + if let Some(status) = self + .session_status_replica + .as_mut() + .filter(|status| status.session_id == *session_id) + { + status.usage = *usage; + status.context_window = *context_window; + } + } (Topic::SessionPlan { session_id }, ServerEvent::SessionPlanReplaced(plan)) if plan.session_id == *session_id => {