diff --git a/apps/desktop-tauri/src-tauri/src/commands/provider_detail.rs b/apps/desktop-tauri/src-tauri/src/commands/provider_detail.rs index 418bd5c146..610746dd98 100644 --- a/apps/desktop-tauri/src-tauri/src/commands/provider_detail.rs +++ b/apps/desktop-tauri/src-tauri/src/commands/provider_detail.rs @@ -28,6 +28,13 @@ pub struct ProviderDetail { /// Locale key naming the tertiary lane when it carries a semantic label /// beyond "Tertiary" (upstream F5). Drives the settings metric picker. pub tertiary_label_key: Option<&'static str>, + /// Id of the extra rate window holding the provider's monthly plan + /// allowance (upstream 0.70.0 #4072). Offers the Monthly Plan metric in + /// the settings metric picker. + pub monthly_plan_window_id: Option<&'static str>, + /// Metric picker label for the primary lane when it is not a session + /// window (upstream `menuBarLayoutPrimaryLabel`). + pub primary_metric_label: Option<&'static str>, pub extra_rate_windows: Vec, pub usage_items: Vec, pub hidden_usage_item_ids: Vec, @@ -100,6 +107,8 @@ pub(crate) fn build_provider_detail( model_specific: None, tertiary: None, tertiary_label_key: metadata.tertiary_label_key, + monthly_plan_window_id: provider.monthly_plan_window_id(), + primary_metric_label: provider.menu_bar_primary_label(), extra_rate_windows: Vec::new(), usage_items: Vec::new(), hidden_usage_item_ids: settings.hidden_usage_item_ids(id), diff --git a/apps/desktop-tauri/src-tauri/src/tray_bridge.rs b/apps/desktop-tauri/src-tauri/src/tray_bridge.rs index 882573b666..be67b2369d 100644 --- a/apps/desktop-tauri/src-tauri/src/tray_bridge.rs +++ b/apps/desktop-tauri/src-tauri/src/tray_bridge.rs @@ -513,7 +513,7 @@ fn status_labels_for_settings( if settings.tray_icon_mode == TrayIconMode::PerProvider { return healthy .into_iter() - .map(|s| provider_status_label(s, lang)) + .map(|s| provider_status_label(s, settings, lang)) .collect::>(); } @@ -524,7 +524,7 @@ fn status_labels_for_settings( return vec![]; }; - let (_, label) = provider_status_label(selected, lang); + let (_, label) = provider_status_label(selected, settings, lang); vec![("status_summary".to_string(), label)] } @@ -554,25 +554,34 @@ fn ordered_snapshot_refs<'a>( fn provider_status_label( snapshot: &crate::commands::ProviderUsageSnapshot, + settings: &Settings, lang: codexbar::settings::Language, ) -> (String, String) { - // MonthlyPlan metric (PAYG spend, e.g. Mistral): show formatted cost. let provider = codexbar::core::ProviderId::from_cli_name(&snapshot.provider_id); let preference = provider - .map(|id| Settings::load().get_provider_metric(id)) + .map(|id| settings.get_provider_metric(id)) .unwrap_or_default(); - if preference == MetricPreference::MonthlyPlan - && let Some(cost) = snapshot.cost.as_ref() - { - let amount = if !cost.formatted_used.is_empty() { - cost.formatted_used.clone() - } else { - crate::commands::format_cost_amount(cost) - }; - return ( - snapshot.provider_id.clone(), - format!("{} {}", snapshot.display_name, amount), - ); + if preference == MetricPreference::MonthlyPlan { + // Upstream 0.70.0 (#4072): label the provider's plan allowance when + // it resolves, otherwise keep the current-month spend amount. + if let Some(plan) = crate::usage_metric::monthly_plan_window(snapshot, provider) { + let label = crate::commands::compact_tray_status_label(plan, lang); + return ( + snapshot.provider_id.clone(), + format!("{} {}", snapshot.display_name, label), + ); + } + if let Some(cost) = snapshot.cost.as_ref() { + let amount = if !cost.formatted_used.is_empty() { + cost.formatted_used.clone() + } else { + crate::commands::format_cost_amount(cost) + }; + return ( + snapshot.provider_id.clone(), + format!("{} {}", snapshot.display_name, amount), + ); + } } let label = crate::commands::compact_tray_status_label(headline_window(snapshot), lang); @@ -1315,14 +1324,42 @@ mod tests { "{japanese_tooltip}" ); + let settings = Settings::default(); let (_, english_label) = - provider_status_label(&claude, codexbar::settings::Language::English); + provider_status_label(&claude, &settings, codexbar::settings::Language::English); let (_, japanese_label) = - provider_status_label(&claude, codexbar::settings::Language::Japanese); + provider_status_label(&claude, &settings, codexbar::settings::Language::Japanese); assert!(english_label.contains("Resets in"), "{english_label}"); assert!(japanese_label.contains("リセットまで"), "{japanese_label}"); } + #[test] + fn mistral_monthly_plan_tray_label_shows_the_plan_then_the_spend() { + let mut settings = Settings::default(); + settings.set_provider_metric(ProviderId::Mistral, MetricPreference::MonthlyPlan); + settings.show_as_used = true; + let lang = codexbar::settings::Language::English; + let mut mistral = + fake_snapshot_with("mistral", "Mistral", 2.0, None, None, Some((12.5, 50.0))); + let mut plan = fake_extra_window(42.0); + plan.id = codexbar::providers::mistral::MONTHLY_PLAN_WINDOW_ID.to_string(); + plan.title = "Monthly Plan".to_string(); + mistral.extra_rate_windows = vec![plan]; + + let (_, label) = provider_status_label(&mistral, &settings, lang); + assert_eq!(label, "Mistral 42%"); + assert_eq!(selected_tray_percents(&mistral, &settings).0, 42.0); + + // Without a known plan allowance the label keeps the spend text. + mistral.extra_rate_windows[0].window.is_informational = true; + let (_, label) = provider_status_label(&mistral, &settings, lang); + assert_eq!(label, "Mistral $12.50"); + + // Automatic keeps the existing headline label. + let (_, label) = provider_status_label(&mistral, &Settings::default(), lang); + assert_eq!(label, "Mistral 2%"); + } + #[test] fn selected_tray_percent_uses_cursor_extra_usage_cost() { let mut settings = Settings::default(); diff --git a/apps/desktop-tauri/src-tauri/src/usage_metric.rs b/apps/desktop-tauri/src-tauri/src/usage_metric.rs index 65a2408a15..9ee7254a94 100644 --- a/apps/desktop-tauri/src-tauri/src/usage_metric.rs +++ b/apps/desktop-tauri/src-tauri/src/usage_metric.rs @@ -95,10 +95,44 @@ fn preferred_window( extra_usage_window(snapshot).or_else(|| cost_window(snapshot)) } MetricPreference::Average => average_window(snapshot), - MetricPreference::MonthlyPlan => cost_window(snapshot), + // Upstream 0.70.0 (#4072): the provider's plan allowance. A missing + // or unknown plan falls back to the primary (included API) allowance, + // never to a spend window. Providers without a plan window fall + // through to Automatic. + MetricPreference::MonthlyPlan => { + let window_id = provider.and_then(monthly_plan_window_id)?; + plan_window(snapshot, window_id) + .or_else(|| non_informational(Some(&snapshot.primary))) + .cloned() + } } } +/// The provider-declared monthly plan allowance, when the snapshot carries a +/// known value for it. Providers without a plan window return `None`. +pub(crate) fn monthly_plan_window( + snapshot: &ProviderUsageSnapshot, + provider: Option, +) -> Option<&RateWindowSnapshot> { + plan_window(snapshot, provider.and_then(monthly_plan_window_id)?) +} + +fn monthly_plan_window_id(provider: ProviderId) -> Option<&'static str> { + codexbar::core::instantiate_provider(provider).monthly_plan_window_id() +} + +fn plan_window<'a>( + snapshot: &'a ProviderUsageSnapshot, + window_id: &str, +) -> Option<&'a RateWindowSnapshot> { + snapshot + .extra_rate_windows + .iter() + .find(|extra| extra.id == window_id) + .map(|extra| &extra.window) + .filter(|window| !window.is_informational) +} + fn automatic_window( snapshot: &ProviderUsageSnapshot, provider: Option, @@ -606,6 +640,119 @@ mod tests { assert_eq!(selected.remaining_percent, 60.0); } + fn mistral_snapshot(plan: Option) -> ProviderUsageSnapshot { + let mut snapshot = snapshot(); + snapshot.provider_id = "mistral".to_string(); + snapshot.display_name = "Mistral".to_string(); + snapshot.primary = window(2.0); + snapshot.primary_label = Some("Included API".to_string()); + snapshot.secondary = None; + snapshot.extra_rate_windows = plan + .map(|window| crate::commands::NamedRateWindowSnapshot { + id: codexbar::providers::mistral::MONTHLY_PLAN_WINDOW_ID.to_string(), + title: "Monthly Plan".to_string(), + window, + fallback_lane: false, + }) + .into_iter() + .collect(); + snapshot + } + + fn metric_settings(provider: ProviderId, preference: MetricPreference) -> Settings { + let mut settings = Settings::default(); + settings.set_provider_metric(provider, preference); + settings + } + + #[test] + fn mistral_monthly_plan_selects_the_vibe_plan_window() { + let mut snapshot = mistral_snapshot(Some(window(42.0))); + snapshot.primary = window(80.0); + + let monthly_plan = metric_settings(ProviderId::Mistral, MetricPreference::MonthlyPlan); + assert_eq!( + selected_usage_window(&snapshot, &monthly_plan).used_percent, + 42.0 + ); + // Included API and Automatic stay separate choices (upstream #4072). + let included_api = metric_settings(ProviderId::Mistral, MetricPreference::Session); + assert_eq!( + selected_usage_window(&snapshot, &included_api).used_percent, + 80.0 + ); + assert_eq!( + selected_usage_window(&snapshot, &Settings::default()).used_percent, + 80.0 + ); + let presentation = + crate::commands::ProviderUsagePresentationSnapshot::new(snapshot, &monthly_plan); + assert_eq!(presentation.selected_metric.used_percent, 42.0); + } + + #[test] + fn mistral_monthly_plan_falls_back_to_included_api_without_a_known_plan() { + let settings = metric_settings(ProviderId::Mistral, MetricPreference::MonthlyPlan); + let unknown_plan = RateWindowSnapshot { + is_informational: true, + ..window(0.0) + }; + + for snapshot in [mistral_snapshot(None), mistral_snapshot(Some(unknown_plan))] { + let selected = selected_usage_window(&snapshot, &settings); + assert_eq!(selected.used_percent, 2.0); + assert!(!selected.is_informational); + } + } + + #[test] + fn mistral_monthly_plan_never_selects_a_spend_window() { + let mut snapshot = mistral_snapshot(None); + snapshot.cost = Some(crate::commands::CostSnapshotBridge { + used: 45.0, + limit: Some(50.0), + remaining: Some(5.0), + currency_code: "EUR".to_string(), + currency_symbol: Some("€".to_string()), + period: "month".to_string(), + resets_at: None, + formatted_used: "€45.00".to_string(), + formatted_limit: Some("€50.00".to_string()), + balance: None, + balance_updated_at: None, + account_id: None, + formatted_balance: None, + daily: Vec::new(), + always_visible: false, + }); + let settings = metric_settings(ProviderId::Mistral, MetricPreference::MonthlyPlan); + + assert_eq!( + selected_usage_window(&snapshot, &settings).used_percent, + 2.0 + ); + } + + #[test] + fn monthly_plan_without_a_provider_plan_window_falls_through_to_automatic() { + let mut snapshot = snapshot(); + snapshot.extra_rate_windows = vec![crate::commands::NamedRateWindowSnapshot { + id: codexbar::providers::mistral::MONTHLY_PLAN_WINDOW_ID.to_string(), + title: "Monthly Plan".to_string(), + window: window(90.0), + fallback_lane: false, + }]; + let settings = metric_settings(ProviderId::Codex, MetricPreference::MonthlyPlan); + + // Codex declares no plan window, so a same-named lane is not special + // and the choice resolves like Automatic (highest real window). + assert_eq!( + selected_usage_window(&snapshot, &settings).used_percent, + selected_usage_window(&snapshot, &Settings::default()).used_percent + ); + assert!(monthly_plan_window(&snapshot, Some(ProviderId::Codex)).is_none()); + } + #[test] fn presentation_payload_flattens_the_snapshot_and_selected_metric() { let presentation = crate::commands::ProviderUsagePresentationSnapshot::new( diff --git a/apps/desktop-tauri/src/i18n/keys.ts b/apps/desktop-tauri/src/i18n/keys.ts index 195620e3bf..fb9dc170d3 100644 --- a/apps/desktop-tauri/src/i18n/keys.ts +++ b/apps/desktop-tauri/src/i18n/keys.ts @@ -896,6 +896,8 @@ export const ALL_LOCALE_KEYS = [ // Mistral PAYG monthly spend (#2821, #2947) "MistralMonthlySpend", "MistralMonthlySpendHelper", + // Mistral Monthly Plan menu bar metric (upstream 0.70.0 #4072) + "MetricMonthlyPlan", // Menu cost-summary display style (#2976) "CostSummaryDisplayStyle", diff --git a/apps/desktop-tauri/src/surfaces/settings/providers/sections/MenuBarMetricSection.test.tsx b/apps/desktop-tauri/src/surfaces/settings/providers/sections/MenuBarMetricSection.test.tsx index 46f0e21cd2..a9dd63f3e8 100644 --- a/apps/desktop-tauri/src/surfaces/settings/providers/sections/MenuBarMetricSection.test.tsx +++ b/apps/desktop-tauri/src/surfaces/settings/providers/sections/MenuBarMetricSection.test.tsx @@ -36,6 +36,25 @@ function provider(extra = true): ProviderDetail { }; } +function mistral(observed = true): ProviderDetail { + return { + ...provider(false), + id: "mistral", + displayName: "Mistral", + primaryMetricLabel: "Included API", + monthlyPlanWindowId: "mistral-monthly-plan", + session: observed ? rateWindow(2) : null, + extraRateWindows: observed + ? [{ id: "mistral-monthly-plan", title: "Monthly Plan", window: rateWindow(42) }] + : [], + hasSnapshot: observed, + }; +} + +function optionNames() { + return screen.getAllByRole("option").map((option) => option.textContent); +} + function rateWindow(usedPercent: number) { return { usedPercent, @@ -122,5 +141,62 @@ describe("MenuBarMetricSection", () => { expect(onChange).toHaveBeenCalledWith({ providerMetrics: { copilot: "extraUsage" }, }); + expect(screen.queryByRole("option", { name: "MetricMonthlyPlan" })).toBeNull(); + }); + + it("offers Included API and Monthly Plan for a provider plan window", () => { + const onChange = vi.fn(); + render( + key} + onChange={onChange} + />, + ); + + // Upstream 0.70.0: Automatic, Included API and Monthly Plan only. The + // plan window does not add a separate Extra usage choice. + expect(optionNames()).toEqual(["Automatic", "Included API", "MetricMonthlyPlan"]); + fireEvent.change(screen.getByRole("combobox"), { target: { value: "monthlyPlan" } }); + expect(onChange).toHaveBeenCalledWith({ + providerMetrics: { mistral: "monthlyPlan" }, + }); + }); + + it("offers Monthly Plan before the first Mistral snapshot", () => { + render( + key} + onChange={vi.fn()} + />, + ); + + expect(optionNames()).toEqual(["Automatic", "Included API", "MetricMonthlyPlan"]); + expect(screen.getByRole("combobox")).toHaveValue("monthlyPlan"); + }); + + it("keeps a saved choice the provider no longer offers readable", () => { + render( + key} + onChange={vi.fn()} + />, + ); + + expect(optionNames()).toEqual([ + "Automatic", + "Included API", + "MetricMonthlyPlan", + "ExtraUsage", + ]); + expect(screen.getByRole("combobox")).toHaveValue("extraUsage"); }); }); diff --git a/apps/desktop-tauri/src/surfaces/settings/providers/sections/MenuBarMetricSection.tsx b/apps/desktop-tauri/src/surfaces/settings/providers/sections/MenuBarMetricSection.tsx index 7bc89e0d7d..e151249acd 100644 --- a/apps/desktop-tauri/src/surfaces/settings/providers/sections/MenuBarMetricSection.tsx +++ b/apps/desktop-tauri/src/surfaces/settings/providers/sections/MenuBarMetricSection.tsx @@ -78,7 +78,12 @@ function metricOptions( ): MetricOption[] { const options: MetricOption[] = [ { value: "automatic", label: t("Automatic") }, - { value: "session", label: t("ProviderSessionLabel") }, + { + value: "session", + // Upstream 0.70.0: a primary lane that is not a session window keeps + // its provider name in the picker (Mistral's "Included API"). + label: provider.primaryMetricLabel ?? t("ProviderSessionLabel"), + }, ]; if (provider.weekly) { @@ -95,21 +100,36 @@ function metricOptions( ), }); } - if (provider.id === "cursor" || provider.extraRateWindows.length > 0) { + // The monthly plan window has its own choice below, so it does not make + // Extra usage available on its own. + const extraWindows = provider.extraRateWindows.filter( + (extra) => extra.id !== provider.monthlyPlanWindowId, + ); + if (provider.id === "cursor" || extraWindows.length > 0) { options.push({ value: "extraUsage", label: t("ExtraUsage") }); } - if (provider.id === "mistral") { - options.push({ value: "monthlyPlan", label: t("MistralMonthlySpend") }); + // Upstream 0.70.0 (#4072): providers that publish a monthly plan + // allowance window offer it for the menu bar and widgets. + if (provider.monthlyPlanWindowId) { + options.push({ value: "monthlyPlan", label: t("MetricMonthlyPlan") }); } if (provider.id === "gemini" && provider.weekly) { options.push({ value: "average", label: t("Average") }); } if (!options.some((option) => option.value === selected)) { + const labelKey = SAVED_ONLY_LABEL_KEYS[selected]; options.push({ value: selected, - label: selected === "credits" ? t("CreditsLabel") : selected, + label: labelKey ? t(labelKey) : selected, }); } return options; } + +/** Labels for saved choices the provider no longer offers. */ +const SAVED_ONLY_LABEL_KEYS: Partial> = { + credits: "CreditsLabel", + extraUsage: "ExtraUsage", + monthlyPlan: "MetricMonthlyPlan", +}; diff --git a/apps/desktop-tauri/src/types/bridge.ts b/apps/desktop-tauri/src/types/bridge.ts index 53d8c843ea..197f3c337d 100644 --- a/apps/desktop-tauri/src/types/bridge.ts +++ b/apps/desktop-tauri/src/types/bridge.ts @@ -963,6 +963,10 @@ export interface ProviderDetail { tertiary: RateWindowSnapshot | null; /** Locale key for the tertiary metric lane when it carries a semantic label (upstream F5). */ tertiaryLabelKey?: string | null; + /** Extra rate window id holding the monthly plan allowance; offers the Monthly Plan metric (upstream 0.70.0). */ + monthlyPlanWindowId?: string | null; + /** Metric picker label for the primary lane when it is not a session window. */ + primaryMetricLabel?: string | null; extraRateWindows: Array<{ id: string; title: string; diff --git a/rust/src/core/provider.rs b/rust/src/core/provider.rs index cc954839f7..a2c8ca39f9 100755 --- a/rust/src/core/provider.rs +++ b/rust/src/core/provider.rs @@ -892,6 +892,21 @@ pub trait Provider: Send + Sync { true } + /// Id of the extra rate window that holds this provider's monthly plan + /// allowance. The `MonthlyPlan` menu bar metric selects that window + /// (upstream 0.70.0 #4072). `None` means the provider offers no Monthly + /// Plan metric. + fn monthly_plan_window_id(&self) -> Option<&'static str> { + None + } + + /// Label for the primary lane in the menu bar metric picker when the + /// lane is not a session window (upstream `menuBarLayoutPrimaryLabel`). + /// `None` keeps the generic session label. + fn menu_bar_primary_label(&self) -> Option<&'static str> { + None + } + /// Whether browser-cookie discovery/recovery is owned by the provider. fn owns_browser_cookie_resolution(&self) -> bool { false diff --git a/rust/src/locale.rs b/rust/src/locale.rs index e6ec918ac4..ebc3084476 100644 --- a/rust/src/locale.rs +++ b/rust/src/locale.rs @@ -1176,6 +1176,8 @@ locale_keys! { // Mistral PAYG monthly spend (#2821, #2947) MistralMonthlySpend, MistralMonthlySpendHelper, + // Mistral Monthly Plan menu bar metric (upstream 0.70.0 #4072) + MetricMonthlyPlan, // Menu cost-summary display style (#2976) CostSummaryDisplayStyle, diff --git a/rust/src/locale/en-US.ftl b/rust/src/locale/en-US.ftl index fb96ecd4b8..74d685987b 100644 --- a/rust/src/locale/en-US.ftl +++ b/rust/src/locale/en-US.ftl @@ -826,6 +826,7 @@ DeepSeekPricingEffective = Effective local time: DeepSeekPricingAdvice = Official schedule: peak 01:00-04:00 and 06:00-10:00 UTC; off-peak is half-price. MistralMonthlySpend = Monthly API spend MistralMonthlySpendHelper = Current-month API usage for pay-as-you-go Mistral accounts. +MetricMonthlyPlan = Monthly Plan CostSummaryDisplayStyle = Cost summary display CostSummaryDisplayStyleHelper = Choose how cost is shown on every provider card. CostSummaryStyleCompact = Compact diff --git a/rust/src/locale/ru-RU.ftl b/rust/src/locale/ru-RU.ftl index f978d540e7..3420e2ea1f 100644 --- a/rust/src/locale/ru-RU.ftl +++ b/rust/src/locale/ru-RU.ftl @@ -742,6 +742,7 @@ DeepSeekPricingEffective = Effective local time: DeepSeekPricingAdvice = Official schedule: peak 01:00-04:00 and 06:00-10:00 UTC; off-peak is half-price. MistralMonthlySpend = Расход на API за месяц MistralMonthlySpendHelper = Использование API за текущий месяц для аккаунтов Mistral с оплатой по мере использования. +MetricMonthlyPlan = Ежемесячный план CostSummaryDisplayStyle = Стиль отображения стоимости CostSummaryDisplayStyleHelper = Выберите, как стоимость отображается на каждой карте провайдера. CostSummaryStyleCompact = Компактный diff --git a/rust/src/locale/tr-TR.ftl b/rust/src/locale/tr-TR.ftl index 08de23e2e3..86e879ba52 100644 --- a/rust/src/locale/tr-TR.ftl +++ b/rust/src/locale/tr-TR.ftl @@ -753,6 +753,7 @@ Sub2ApiBaseUrlHelp = GET /v1/usage için dağıtım URL'si. Yerel geliştirme i PromoteTrayIconLabel = Görev Çubuğuna Sabitle (Windows 11) PromoteTrayIconHelper = CodexBar simgesini taşma şevronunun içinde değil, her zaman görev çubuğunda göster PromoteTrayIconUnsupportedHint = Bu işletim sisteminde kullanılamaz. Elle sabitlemek için simgeyi Windows Ayarlar > Görev Çubuğu'ndaki ^ taşma alanının dışına sürükleyin. +MetricMonthlyPlan = Aylık plan UsageSpendShare = Paylaşım kartını dışa aktar (PNG) UsageSpendShareEmpty = Henüz dışa aktarılacak bir şey yok. UsageSpendShareFailed = Paylaşım kartı görseli oluşturulamadı. diff --git a/rust/src/locale/zh-TW.ftl b/rust/src/locale/zh-TW.ftl index 31bb3bcf02..29d9b73366 100644 --- a/rust/src/locale/zh-TW.ftl +++ b/rust/src/locale/zh-TW.ftl @@ -763,6 +763,7 @@ DeepSeekPricingEffective = Effective local time: DeepSeekPricingAdvice = Official schedule: peak 01:00-04:00 and 06:00-10:00 UTC; off-peak is half-price. MistralMonthlySpend = 本月 API 花費 MistralMonthlySpendHelper = 按量付費 Mistral 帳戶的當月 API 用量。 +MetricMonthlyPlan = 月租方案 CostSummaryDisplayStyle = 費用摘要顯示方式 CostSummaryDisplayStyleHelper = 選擇在每個提供商卡片上如何顯示費用。 CostSummaryStyleCompact = 緊湊 diff --git a/rust/src/providers/mistral/mod.rs b/rust/src/providers/mistral/mod.rs index 33f82bc908..561a96c093 100644 --- a/rust/src/providers/mistral/mod.rs +++ b/rust/src/providers/mistral/mod.rs @@ -27,6 +27,12 @@ const CLIENT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); /// `/subscription` render can never stall the refresh; degraded enrichment is /// logged and skipped, never fatal. const SUBSCRIPTION_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(4); +/// Extra rate window holding the Vibe monthly plan allowance. The Monthly Plan +/// menu bar metric selects it (upstream 0.70.0 #4072). +pub const MONTHLY_PLAN_WINDOW_ID: &str = "mistral-monthly-plan"; +/// Picker label for the primary lane, the included API allowance (upstream +/// `menuBarLayoutPrimaryLabel`). +const INCLUDED_API_LABEL: &str = "Included API"; #[derive(Debug, Deserialize)] struct BillingResponse { @@ -417,11 +423,11 @@ impl MistralProvider { if let Some(budgets) = budgets { if let Some(api) = budgets.api { usage.primary = Self::budget_window(&api); - usage.primary_label = Some("Included API".to_string()); + usage.primary_label = Some(INCLUDED_API_LABEL.to_string()); } if let Some(vibe) = budgets.vibe { usage.extra_rate_windows.push(NamedRateWindow::new( - "mistral-monthly-plan", + MONTHLY_PLAN_WINDOW_ID, "Monthly Plan", Self::budget_window(&vibe), )); @@ -576,6 +582,14 @@ impl Provider for MistralProvider { fn supports_web(&self) -> bool { true } + + fn monthly_plan_window_id(&self) -> Option<&'static str> { + Some(MONTHLY_PLAN_WINDOW_ID) + } + + fn menu_bar_primary_label(&self) -> Option<&'static str> { + Some(INCLUDED_API_LABEL) + } } #[cfg(test)] @@ -627,6 +641,17 @@ mod tests { ); } + #[test] + fn monthly_plan_metric_targets_the_published_vibe_window() { + let provider = MistralProvider::new(); + + assert_eq!( + provider.monthly_plan_window_id(), + Some("mistral-monthly-plan") + ); + assert_eq!(provider.menu_bar_primary_label(), Some("Included API")); + } + #[test] fn attaches_subscription_allowances_without_replacing_billing_cost() { let summary = MistralUsageSummary { diff --git a/rust/src/settings/types.rs b/rust/src/settings/types.rs index 6113969b15..670012b167 100644 --- a/rust/src/settings/types.rs +++ b/rust/src/settings/types.rs @@ -280,8 +280,8 @@ pub enum MetricPreference { Credits, #[serde(rename = "extraUsage", alias = "extrausage")] ExtraUsage, - /// Current-month plan spend for PAYG providers (e.g. Mistral) that have - /// cost data but no rate-limit window (#2821, #2947). + /// The provider's monthly plan allowance window, such as Mistral's Vibe + /// plan (upstream 0.70.0 #4072). MonthlyPlan, Average, } @@ -312,7 +312,7 @@ impl MetricPreference { MetricPreference::Tertiary => "Tertiary", MetricPreference::Credits => "Credits", MetricPreference::ExtraUsage => "Extra usage", - MetricPreference::MonthlyPlan => "Monthly plan spend", + MetricPreference::MonthlyPlan => "Monthly plan", MetricPreference::Average => "Average", } } @@ -327,7 +327,7 @@ impl MetricPreference { MetricPreference::Tertiary => "Tertiary usage limit", MetricPreference::Credits => "Credit balance", MetricPreference::ExtraUsage => "On-demand or extra usage budget", - MetricPreference::MonthlyPlan => "Current-month plan spend (PAYG)", + MetricPreference::MonthlyPlan => "Monthly plan allowance", MetricPreference::Average => "Average across metrics", } }