Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,18 @@ members = [
]
default-members = ["apps/desktop-tauri/src-tauri"]
resolver = "3"

# Cargo only reads profiles from the workspace root. These lived in
# `rust/Cargo.toml`, where cargo ignored them and said so on every build, so
# released binaries were built unoptimized-for-size: no LTO, no symbol
# stripping, and 16 codegen units.
#
# `panic = "abort"` was in that ignored block and is deliberately not carried
# over. It has never actually applied to a shipped build, and turning it on
# would convert a panic in any background refresh task into an immediate
# process kill — the failure 1.5.21 moved away from.
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
21 changes: 21 additions & 0 deletions apps/desktop-tauri/src-tauri/src/capacity_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,27 @@ fn observed_windows(
window,
);
}
// The model lane shares a cadence with the weekly window (Claude's 7-day
// Opus pool is also 10080 minutes), so a cadence-derived id would land on
// "weekly" and one window would silently replace the other in this map.
if let Some(window) = snapshot.model_specific.as_ref()
&& let Some(mut observed) = to_observed_window("Model", window)
{
observed.id = "model".to_string();
windows.insert(observed.id.clone(), observed);
}
// Same hazard for the third window: most are monthly, but a provider whose
// tertiary matches its secondary cadence must not overwrite it. Core slots
// are already in the map, so an id they hold means this one needs its own.
if let Some(window) = snapshot.tertiary.as_ref() {
let label = snapshot.tertiary_label.as_deref().unwrap_or("Extra");
if let Some(mut observed) = to_observed_window(label, window) {
if windows.contains_key(&observed.id) {
observed.id = "tertiary".to_string();
}
windows.insert(observed.id.clone(), observed);
}
}
for extra in &snapshot.extra_rate_windows {
if ignored_capacity_window(snapshot, &extra.id, &extra.title) {
continue;
Expand Down
137 changes: 114 additions & 23 deletions apps/desktop-tauri/src-tauri/src/commands/providers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -833,27 +833,75 @@ fn window_notify_key(
}
}

/// Plan per-window threshold alerts for a snapshot's primary/secondary windows.
/// Plan per-window threshold alerts for every measured window on a snapshot.
///
/// Returns stable, cadence-based window keys (so a primary/secondary swap cannot
/// masquerade as a new crossing) and the used-percent of the true 5-hour session
/// window *only when it is actually present this refresh* — a promoted weekly is
/// never reported as the session.
///
/// The third and model windows are real subscription ceilings (OpenCode Go
/// Monthly, Claude's Opus pool), so they are offered to the notifier too — which
/// keeps its own policy about what deserves a toast and still declines monthly
/// keys. The model window carries its own key rather than a cadence one:
/// Claude's is 7 days like the weekly window, and a shared key would let
/// whichever came second be dropped as a duplicate.
/// The measured windows of one snapshot, borrowed for alert planning. Grouped
/// so adding a slot does not grow every signature along the way.
struct ThresholdWindows<'a> {
primary_label: Option<&'a str>,
primary: &'a RateWindowSnapshot,
secondary_label: Option<&'a str>,
secondary: Option<&'a RateWindowSnapshot>,
tertiary_label: Option<&'a str>,
tertiary: Option<&'a RateWindowSnapshot>,
model_specific: Option<&'a RateWindowSnapshot>,
}

impl<'a> ThresholdWindows<'a> {
fn of(snapshot: &'a ProviderUsageSnapshot) -> Self {
Self {
primary_label: snapshot.primary_label.as_deref(),
primary: &snapshot.primary,
secondary_label: snapshot.secondary_label.as_deref(),
secondary: snapshot.secondary.as_ref(),
tertiary_label: snapshot.tertiary_label.as_deref(),
tertiary: snapshot.tertiary.as_ref(),
model_specific: snapshot.model_specific.as_ref(),
}
}
}

fn plan_threshold_alerts(
provider: ProviderId,
primary_label: Option<&str>,
primary: &RateWindowSnapshot,
secondary_label: Option<&str>,
secondary: Option<&RateWindowSnapshot>,
windows: ThresholdWindows<'_>,
) -> (Vec<(&'static str, f64)>, Option<f64>) {
let mut alerts: Vec<(&'static str, f64)> = Vec::new();
let mut session_percent: Option<f64> = None;
let mut seen: std::collections::HashSet<&'static str> = std::collections::HashSet::new();

for (label, window) in std::iter::once((primary_label, primary))
.chain(secondary.map(|window| (secondary_label, window)))
for (key, window) in std::iter::once((
window_notify_key(
provider,
windows.primary_label,
windows.primary.window_minutes,
),
windows.primary,
))
.chain(windows.secondary.map(|window| {
(
window_notify_key(provider, windows.secondary_label, window.window_minutes),
window,
)
}))
.chain(windows.tertiary.map(|window| {
(
window_notify_key(provider, windows.tertiary_label, window.window_minutes),
window,
)
}))
.chain(windows.model_specific.map(|window| ("model", window)))
{
let key = window_notify_key(provider, label, window.window_minutes);
if key == "session" {
session_percent = Some(window.used_percent);
}
Expand All @@ -877,13 +925,8 @@ fn notify_usage_thresholds(
if snapshot.error.is_none()
&& let Some(&provider) = cli_map.get(snapshot.provider_id.as_str())
{
let (alerts, _session_percent) = plan_threshold_alerts(
provider,
snapshot.primary_label.as_deref(),
&snapshot.primary,
snapshot.secondary_label.as_deref(),
snapshot.secondary.as_ref(),
);
let (alerts, _session_percent) =
plan_threshold_alerts(provider, ThresholdWindows::of(snapshot));
for (window_key, used_percent) in alerts {
guard.notification_manager.check_and_notify(
provider,
Expand Down Expand Up @@ -1122,10 +1165,15 @@ mod predictive_warning_tests {
// 5-hour present: primary = 5h (100%), secondary = weekly (75%).
let (alerts, session) = plan_threshold_alerts(
ProviderId::Codex,
Some("Session"),
&rw(Some(300), 100.0),
Some("Weekly"),
Some(&rw(Some(10_080), 75.0)),
ThresholdWindows {
primary_label: Some("Session"),
primary: &rw(Some(300), 100.0),
secondary_label: Some("Weekly"),
secondary: Some(&rw(Some(10_080), 75.0)),
tertiary_label: None,
tertiary: None,
model_specific: None,
},
);
assert_eq!(session, Some(100.0));
assert!(alerts.contains(&("session", 100.0)));
Expand All @@ -1135,10 +1183,15 @@ mod predictive_warning_tests {
// It must NOT be reported as the session, and it keeps the "weekly" key.
let (alerts, session) = plan_threshold_alerts(
ProviderId::Codex,
Some("Weekly"),
&rw(Some(10_080), 75.0),
None,
None,
ThresholdWindows {
primary_label: Some("Weekly"),
primary: &rw(Some(10_080), 75.0),
secondary_label: None,
secondary: None,
tertiary_label: None,
tertiary: None,
model_specific: None,
},
);
assert_eq!(
session, None,
Expand All @@ -1147,6 +1200,44 @@ mod predictive_warning_tests {
assert_eq!(alerts, vec![("weekly", 75.0)]);
}

/// The third and model windows are real ceilings. They were never handed to
/// the notifier, so an OpenCode Go monthly pool or a Claude Opus pool could
/// sit at 99% without a word.
#[test]
fn third_and_model_windows_alert_too() {
let (alerts, _) = plan_threshold_alerts(
ProviderId::OpenCodeGo,
ThresholdWindows {
primary_label: Some("Rolling (5h)"),
primary: &rw(Some(300), 34.0),
secondary_label: Some("Weekly"),
secondary: Some(&rw(Some(10_080), 32.0)),
tertiary_label: Some("Monthly"),
tertiary: Some(&rw(Some(43_200), 97.0)),
model_specific: None,
},
);
assert!(alerts.contains(&("monthly", 97.0)));

// Claude's model pool runs on the same 7-day cadence as its weekly
// window, so a cadence key would collide and the second one would be
// dropped as a duplicate. The model window carries its own key.
let (alerts, _) = plan_threshold_alerts(
ProviderId::Claude,
ThresholdWindows {
primary_label: Some("Session (5h)"),
primary: &rw(Some(300), 4.0),
secondary_label: Some("Weekly"),
secondary: Some(&rw(Some(10_080), 20.0)),
tertiary_label: None,
tertiary: None,
model_specific: Some(&rw(Some(10_080), 96.0)),
},
);
assert!(alerts.contains(&("weekly", 20.0)));
assert!(alerts.contains(&("model", 96.0)));
}

/// A payload for eligibility checks only; the numbers do not matter.
#[cfg(test)]
fn eligibility_event(
Expand Down
63 changes: 62 additions & 1 deletion apps/desktop-tauri/src-tauri/src/quota_run_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,15 @@ fn live_windows(snapshot: &ProviderUsageSnapshot) -> Vec<LiveWindow> {
push_live(&mut windows, snapshot, "model", Some("Model"), window);
}
if let Some(window) = snapshot.tertiary.as_ref() {
push_live(&mut windows, snapshot, "tertiary", Some("API"), window);
// Same fallback as usage history: use the window's own label when it has
// one, so an OpenCode Go monthly run is not filed as an API run.
push_live(
&mut windows,
snapshot,
"tertiary",
snapshot.tertiary_label.as_deref().or(Some("API")),
window,
);
}
for extra in &snapshot.extra_rate_windows {
push_live(
Expand Down Expand Up @@ -699,6 +707,16 @@ fn push_live(
if id.is_empty() {
return;
}
// Cadence alone does not separate every slot: Claude's model pool is 7 days
// like its weekly window, so both resolved to "weekly" and the model reading
// took over the weekly run — same open key, wrong numbers and label. Slots
// are pushed core-first, so an id already taken belongs to the core window
// and this one falls back to its own slot name.
let id = if windows.iter().any(|existing| existing.id == id) {
fallback_id.to_string()
} else {
id
};
windows.push(LiveWindow {
id,
label: label.to_string(),
Expand Down Expand Up @@ -1041,6 +1059,49 @@ mod tests {
assert!(list_runs(provider, Some("me@job.test"), Some("acct-personal")).is_empty());
}

#[test]
fn a_model_window_does_not_take_over_the_weekly_run() {
// Claude's model pool is 7 days, the same cadence as its weekly window,
// so both resolved to the "weekly" id. Sharing one open key meant the
// model reading overwrote the weekly run's numbers and its label.
let now = Utc::now();
let reset = now + Duration::hours(6);
let mut snap = snapshot("claude", "person@example.com", now, 10.0, reset);
snap.secondary = Some(rate(20.0, 10_080, reset));
snap.secondary_label = Some("Weekly".into());
snap.model_specific = Some(rate(96.0, 10_080, reset));

let windows = live_windows(&snap);
let weekly = windows
.iter()
.find(|window| window.id == "weekly")
.expect("weekly window");
assert_eq!(weekly.used_percent, 20.0);
assert_eq!(weekly.label, "Weekly");
let model = windows
.iter()
.find(|window| window.id == "model")
.expect("model window keeps its own run");
assert_eq!(model.used_percent, 96.0);
}

#[test]
fn a_named_third_window_runs_under_its_own_name() {
let now = Utc::now();
let reset = now + Duration::hours(6);
let mut snap = snapshot("opencodego", "person@example.com", now, 34.0, reset);
snap.tertiary = Some(rate(57.0, 43_200, reset));
snap.tertiary_label = Some("Monthly".into());

let windows = live_windows(&snap);
let monthly = windows
.iter()
.find(|window| window.id == "monthly")
.expect("monthly window");
assert_eq!(monthly.label, "Monthly");
assert_eq!(monthly.used_percent, 57.0);
}

#[test]
fn tokens_per_percent_and_projection_require_enough_peak() {
assert!(tokens_per_percent(Some(1_000_000), 4.0).is_none());
Expand Down
Loading
Loading