diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 370e070d7c6843fb373662790b7267e1691b6760..f8bc6c353bad4cca5cf6c5dbaa14f0e2927a1800 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -14,6 +14,7 @@ use arrayvec::ArrayVec; use audio::{Audio, Sound}; use buffer_diff::BufferDiff; use client::zed_urls; +use cloud_llm_client::PlanV1; use collections::{HashMap, HashSet}; use editor::scroll::Autoscroll; use editor::{Editor, EditorEvent, EditorMode, MultiBuffer, PathKey, SelectionEffects}; @@ -4875,7 +4876,9 @@ impl AcpThreadView { return None; } - let plan = user_store.plan().unwrap_or(cloud_llm_client::Plan::ZedFree); + let plan = user_store + .plan() + .unwrap_or(cloud_llm_client::Plan::V1(PlanV1::ZedFree)); let usage = user_store.model_request_usage()?; @@ -5134,13 +5137,12 @@ impl AcpThreadView { cx: &mut Context, ) -> Callout { let error_message = match plan { - cloud_llm_client::Plan::ZedPro => "Upgrade to usage-based billing for more prompts.", - cloud_llm_client::Plan::ZedProTrial | cloud_llm_client::Plan::ZedFree => { - "Upgrade to Zed Pro for more prompts." + cloud_llm_client::Plan::V1(PlanV1::ZedPro) => { + "Upgrade to usage-based billing for more prompts." } - cloud_llm_client::Plan::ZedProV2 - | cloud_llm_client::Plan::ZedProTrialV2 - | cloud_llm_client::Plan::ZedFreeV2 => "", + cloud_llm_client::Plan::V1(PlanV1::ZedProTrial) + | cloud_llm_client::Plan::V1(PlanV1::ZedFree) => "Upgrade to Zed Pro for more prompts.", + cloud_llm_client::Plan::V2(_) => "", }; Callout::new() diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index ad6ab13e9f3e742457cd5ee41f493c9718025a94..4ad5b2d8e84779aeaa928133a11dc1a10d9a02bc 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/crates/agent_ui/src/agent_configuration.rs @@ -8,7 +8,7 @@ use std::{ops::Range, sync::Arc}; use agent_settings::AgentSettings; use anyhow::Result; use assistant_tool::{ToolSource, ToolWorkingSet}; -use cloud_llm_client::Plan; +use cloud_llm_client::{Plan, PlanV1, PlanV2}; use collections::HashMap; use context_server::ContextServerId; use editor::{Editor, SelectionEffects, scroll::Autoscroll}; @@ -515,11 +515,15 @@ impl AgentConfiguration { .blend(cx.theme().colors().text_accent.opacity(0.2)); let (plan_name, label_color, bg_color) = match plan { - Plan::ZedFree | Plan::ZedFreeV2 => ("Free", Color::Default, free_chip_bg), - Plan::ZedProTrial | Plan::ZedProTrialV2 => { + Plan::V1(PlanV1::ZedFree) | Plan::V2(PlanV2::ZedFree) => { + ("Free", Color::Default, free_chip_bg) + } + Plan::V1(PlanV1::ZedProTrial) | Plan::V2(PlanV2::ZedProTrial) => { ("Pro Trial", Color::Accent, pro_chip_bg) } - Plan::ZedPro | Plan::ZedProV2 => ("Pro", Color::Accent, pro_chip_bg), + Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro) => { + ("Pro", Color::Accent, pro_chip_bg) + } }; Chip::new(plan_name.to_string()) diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 3cba74aeaeecc8a9681550dc5557a95030c6deb0..2ace7096c2588699939bc02b92841ea5b5db2e06 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -51,7 +51,7 @@ use assistant_context::{AssistantContext, ContextEvent, ContextSummary}; use assistant_slash_command::SlashCommandWorkingSet; use assistant_tool::ToolWorkingSet; use client::{UserStore, zed_urls}; -use cloud_llm_client::{CompletionIntent, Plan, UsageLimit}; +use cloud_llm_client::{CompletionIntent, Plan, PlanV1, PlanV2, UsageLimit}; use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer}; use feature_flags::{self, ClaudeCodeFeatureFlag, FeatureFlagAppExt, GeminiAndNativeFeatureFlag}; use fs::Fs; @@ -2976,7 +2976,10 @@ impl AgentPanel { let plan = self.user_store.read(cx).plan(); let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some(); - matches!(plan, Some(Plan::ZedFree)) && has_previous_trial + matches!( + plan, + Some(Plan::V1(PlanV1::ZedFree) | Plan::V2(PlanV2::ZedFree)) + ) && has_previous_trial } fn should_render_onboarding(&self, cx: &mut Context) -> bool { @@ -2988,7 +2991,7 @@ impl AgentPanel { if user_store .plan() - .is_some_and(|plan| matches!(plan, Plan::ZedPro)) + .is_some_and(|plan| matches!(plan, Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro))) && user_store .subscription_period() .and_then(|period| period.0.checked_add_days(chrono::Days::new(1))) @@ -3527,9 +3530,11 @@ impl AgentPanel { cx: &mut Context, ) -> AnyElement { let error_message = match plan { - Plan::ZedPro => "Upgrade to usage-based billing for more prompts.", - Plan::ZedProTrial | Plan::ZedFree => "Upgrade to Zed Pro for more prompts.", - Plan::ZedProV2 | Plan::ZedProTrialV2 | Plan::ZedFreeV2 => "", + Plan::V1(PlanV1::ZedPro) => "Upgrade to usage-based billing for more prompts.", + Plan::V1(PlanV1::ZedProTrial) | Plan::V1(PlanV1::ZedFree) => { + "Upgrade to Zed Pro for more prompts." + } + Plan::V2(_) => "", }; Callout::new() diff --git a/crates/agent_ui/src/debug.rs b/crates/agent_ui/src/debug.rs index bd34659210e933ad99357e7e1ceeedb6b53c5ee0..227528e8ae13dc861fe55da7698c86b485f8ae0a 100644 --- a/crates/agent_ui/src/debug.rs +++ b/crates/agent_ui/src/debug.rs @@ -1,7 +1,7 @@ #![allow(unused, dead_code)] use client::{ModelRequestUsage, RequestUsage}; -use cloud_llm_client::{Plan, UsageLimit}; +use cloud_llm_client::{Plan, PlanV1, UsageLimit}; use gpui::Global; use std::ops::{Deref, DerefMut}; use ui::prelude::*; @@ -75,7 +75,7 @@ impl Default for DebugAccountState { Self { enabled: false, trial_expired: false, - plan: Plan::ZedFree, + plan: Plan::V1(PlanV1::ZedFree), custom_prompt_usage: ModelRequestUsage(RequestUsage { limit: UsageLimit::Unlimited, amount: 0, diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index f08ff6f24b551d0eef5ba796c857a8f88eadac3d..e9a482c5f425f7559df2178f802b390cc53f2f61 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -17,7 +17,7 @@ use agent::{ use agent_settings::{AgentProfileId, AgentSettings, CompletionMode}; use ai_onboarding::ApiKeysWithProviders; use buffer_diff::BufferDiff; -use cloud_llm_client::CompletionIntent; +use cloud_llm_client::{CompletionIntent, PlanV1}; use collections::{HashMap, HashSet}; use editor::actions::{MoveUp, Paste}; use editor::display_map::CreaseId; @@ -1298,7 +1298,9 @@ impl MessageEditor { return None; } - let plan = user_store.plan().unwrap_or(cloud_llm_client::Plan::ZedFree); + let plan = user_store + .plan() + .unwrap_or(cloud_llm_client::Plan::V1(PlanV1::ZedFree)); let usage = user_store.model_request_usage()?; diff --git a/crates/agent_ui/src/ui/end_trial_upsell.rs b/crates/agent_ui/src/ui/end_trial_upsell.rs index e31334296fd7c83de450070c4b9059946ef285a7..4db9244469cf1ad7fab414874a64e45f3b97e377 100644 --- a/crates/agent_ui/src/ui/end_trial_upsell.rs +++ b/crates/agent_ui/src/ui/end_trial_upsell.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use ai_onboarding::{AgentPanelOnboardingCard, PlanDefinitions}; use client::zed_urls; -use cloud_llm_client::Plan; +use cloud_llm_client::{Plan, PlanV1}; use gpui::{AnyElement, App, IntoElement, RenderOnce, Window}; use ui::{Divider, Tooltip, prelude::*}; @@ -112,7 +112,7 @@ impl Component for EndTrialUpsell { Some( v_flex() .child(EndTrialUpsell { - plan: Plan::ZedFree, + plan: Plan::V1(PlanV1::ZedFree), dismiss_upsell: Arc::new(|_, _| {}), }) .into_any_element(), diff --git a/crates/agent_ui/src/ui/preview/usage_callouts.rs b/crates/agent_ui/src/ui/preview/usage_callouts.rs index 6f401712817148ffe96b89fd76c6abc1822d30a6..e31af9f49aca781e735a96384dbaddbd3b446ef7 100644 --- a/crates/agent_ui/src/ui/preview/usage_callouts.rs +++ b/crates/agent_ui/src/ui/preview/usage_callouts.rs @@ -1,5 +1,5 @@ use client::{ModelRequestUsage, RequestUsage, zed_urls}; -use cloud_llm_client::{Plan, UsageLimit}; +use cloud_llm_client::{Plan, PlanV1, PlanV2, UsageLimit}; use component::{empty_example, example_group_with_title, single_example}; use gpui::{AnyElement, App, IntoElement, RenderOnce, Window}; use ui::{Callout, prelude::*}; @@ -38,20 +38,20 @@ impl RenderOnce for UsageCallout { let (title, message, button_text, url) = if is_limit_reached { match self.plan { - Plan::ZedFree | Plan::ZedFreeV2 => ( + Plan::V1(PlanV1::ZedFree) | Plan::V2(PlanV2::ZedFree) => ( "Out of free prompts", "Upgrade to continue, wait for the next reset, or switch to API key." .to_string(), "Upgrade", zed_urls::account_url(cx), ), - Plan::ZedProTrial | Plan::ZedProTrialV2 => ( + Plan::V1(PlanV1::ZedProTrial) | Plan::V2(PlanV2::ZedProTrial) => ( "Out of trial prompts", "Upgrade to Zed Pro to continue, or switch to API key.".to_string(), "Upgrade", zed_urls::account_url(cx), ), - Plan::ZedPro | Plan::ZedProV2 => ( + Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro) => ( "Out of included prompts", "Enable usage-based billing to continue.".to_string(), "Manage", @@ -60,7 +60,7 @@ impl RenderOnce for UsageCallout { } } else { match self.plan { - Plan::ZedFree => ( + Plan::V1(PlanV1::ZedFree) => ( "Reaching free plan limit soon", format!( "{remaining} remaining - Upgrade to increase limit, or switch providers", @@ -68,7 +68,7 @@ impl RenderOnce for UsageCallout { "Upgrade", zed_urls::account_url(cx), ), - Plan::ZedProTrial => ( + Plan::V1(PlanV1::ZedProTrial) => ( "Reaching trial limit soon", format!( "{remaining} remaining - Upgrade to increase limit, or switch providers", @@ -76,7 +76,7 @@ impl RenderOnce for UsageCallout { "Upgrade", zed_urls::account_url(cx), ), - _ => return div().into_any_element(), + Plan::V1(PlanV1::ZedPro) | Plan::V2(_) => return div().into_any_element(), } }; @@ -119,7 +119,7 @@ impl Component for UsageCallout { single_example( "Approaching limit (90%)", UsageCallout::new( - Plan::ZedFree, + Plan::V1(PlanV1::ZedFree), ModelRequestUsage(RequestUsage { limit: UsageLimit::Limited(50), amount: 45, // 90% of limit @@ -130,7 +130,7 @@ impl Component for UsageCallout { single_example( "Limit reached (100%)", UsageCallout::new( - Plan::ZedFree, + Plan::V1(PlanV1::ZedFree), ModelRequestUsage(RequestUsage { limit: UsageLimit::Limited(50), amount: 50, // 100% of limit @@ -147,7 +147,7 @@ impl Component for UsageCallout { single_example( "Approaching limit (90%)", UsageCallout::new( - Plan::ZedProTrial, + Plan::V1(PlanV1::ZedProTrial), ModelRequestUsage(RequestUsage { limit: UsageLimit::Limited(150), amount: 135, // 90% of limit @@ -158,7 +158,7 @@ impl Component for UsageCallout { single_example( "Limit reached (100%)", UsageCallout::new( - Plan::ZedProTrial, + Plan::V1(PlanV1::ZedProTrial), ModelRequestUsage(RequestUsage { limit: UsageLimit::Limited(150), amount: 150, // 100% of limit @@ -175,7 +175,7 @@ impl Component for UsageCallout { single_example( "Limit reached (100%)", UsageCallout::new( - Plan::ZedPro, + Plan::V1(PlanV1::ZedPro), ModelRequestUsage(RequestUsage { limit: UsageLimit::Limited(500), amount: 500, // 100% of limit diff --git a/crates/ai_onboarding/src/agent_panel_onboarding_content.rs b/crates/ai_onboarding/src/agent_panel_onboarding_content.rs index 77f41d1a734afb4d21154cb536c19b4bd04632b1..3c8ffc1663e0660829698b5449a006de5b3c6009 100644 --- a/crates/ai_onboarding/src/agent_panel_onboarding_content.rs +++ b/crates/ai_onboarding/src/agent_panel_onboarding_content.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use client::{Client, UserStore}; -use cloud_llm_client::Plan; +use cloud_llm_client::{Plan, PlanV1, PlanV2}; use gpui::{Entity, IntoElement, ParentElement}; use language_model::{LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID}; use ui::prelude::*; @@ -57,8 +57,15 @@ impl AgentPanelOnboarding { impl Render for AgentPanelOnboarding { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let enrolled_in_trial = self.user_store.read(cx).plan() == Some(Plan::ZedProTrial); - let is_pro_user = self.user_store.read(cx).plan() == Some(Plan::ZedPro); + let enrolled_in_trial = self.user_store.read(cx).plan().is_some_and(|plan| { + matches!( + plan, + Plan::V1(PlanV1::ZedProTrial) | Plan::V2(PlanV2::ZedProTrial) + ) + }); + let is_pro_user = self.user_store.read(cx).plan().is_some_and(|plan| { + matches!(plan, Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro)) + }); AgentPanelOnboardingCard::new() .child( diff --git a/crates/ai_onboarding/src/ai_onboarding.rs b/crates/ai_onboarding/src/ai_onboarding.rs index d16969ba774e3c977f68da227d2a9f5792b38398..e131a60b12c2b330ff3a6c099713fa4a4a083358 100644 --- a/crates/ai_onboarding/src/ai_onboarding.rs +++ b/crates/ai_onboarding/src/ai_onboarding.rs @@ -10,7 +10,7 @@ pub use agent_api_keys_onboarding::{ApiKeysWithProviders, ApiKeysWithoutProvider pub use agent_panel_onboarding_card::AgentPanelOnboardingCard; pub use agent_panel_onboarding_content::AgentPanelOnboarding; pub use ai_upsell_card::AiUpsellCard; -use cloud_llm_client::Plan; +use cloud_llm_client::{Plan, PlanV1, PlanV2}; pub use edit_prediction_onboarding_content::EditPredictionOnboarding; pub use plan_definitions::PlanDefinitions; pub use young_account_banner::YoungAccountBanner; @@ -308,13 +308,13 @@ impl RenderOnce for ZedAiOnboarding { if matches!(self.sign_in_status, SignInStatus::SignedIn) { match self.plan { None => self.render_free_plan_state(cx.has_flag::(), cx), - Some(plan @ (Plan::ZedFree | Plan::ZedFreeV2)) => { + Some(plan @ (Plan::V1(PlanV1::ZedFree) | Plan::V2(PlanV2::ZedFree))) => { self.render_free_plan_state(plan.is_v2(), cx) } - Some(plan @ (Plan::ZedProTrial | Plan::ZedProTrialV2)) => { + Some(plan @ (Plan::V1(PlanV1::ZedProTrial) | Plan::V2(PlanV2::ZedProTrial))) => { self.render_trial_state(plan.is_v2(), cx) } - Some(plan @ (Plan::ZedPro | Plan::ZedProV2)) => { + Some(plan @ (Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro))) => { self.render_pro_plan_state(plan.is_v2(), cx) } } @@ -370,15 +370,27 @@ impl Component for ZedAiOnboarding { ), single_example( "Free Plan", - onboarding(SignInStatus::SignedIn, Some(Plan::ZedFree), false), + onboarding( + SignInStatus::SignedIn, + Some(Plan::V1(PlanV1::ZedFree)), + false, + ), ), single_example( "Pro Trial", - onboarding(SignInStatus::SignedIn, Some(Plan::ZedProTrial), false), + onboarding( + SignInStatus::SignedIn, + Some(Plan::V1(PlanV1::ZedProTrial)), + false, + ), ), single_example( "Pro Plan", - onboarding(SignInStatus::SignedIn, Some(Plan::ZedPro), false), + onboarding( + SignInStatus::SignedIn, + Some(Plan::V1(PlanV1::ZedPro)), + false, + ), ), ]) .into_any_element(), diff --git a/crates/ai_onboarding/src/ai_upsell_card.rs b/crates/ai_onboarding/src/ai_upsell_card.rs index 1e4fafd50d7a41c179c8c3a3ac6c9c86012038d0..51758dd9ac123b309450018bd254a2aae31d68af 100644 --- a/crates/ai_onboarding/src/ai_upsell_card.rs +++ b/crates/ai_onboarding/src/ai_upsell_card.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use client::{Client, UserStore, zed_urls}; -use cloud_llm_client::Plan; +use cloud_llm_client::{Plan, PlanV1, PlanV2}; use feature_flags::{BillingV2FeatureFlag, FeatureFlagAppExt}; use gpui::{AnyElement, App, Entity, IntoElement, RenderOnce, Window}; use ui::{CommonAnimationExt, Divider, Vector, VectorName, prelude::*}; @@ -171,7 +171,7 @@ impl RenderOnce for AiUpsellCard { match self.sign_in_status { SignInStatus::SignedIn => match self.user_plan { - None | Some(Plan::ZedFree | Plan::ZedFreeV2) => card + None | Some(Plan::V1(PlanV1::ZedFree) | Plan::V2(PlanV2::ZedFree)) => card .child(Label::new("Try Zed AI").size(LabelSize::Large)) .map(|this| { if self.account_too_young { @@ -237,16 +237,17 @@ impl RenderOnce for AiUpsellCard { ) } }), - Some(plan @ (Plan::ZedProTrial | Plan::ZedProTrialV2)) => card - .child(pro_trial_stamp) - .child(Label::new("You're in the Zed Pro Trial").size(LabelSize::Large)) - .child( - Label::new("Here's what you get for the next 14 days:") - .color(Color::Muted) - .mb_2(), - ) - .child(PlanDefinitions.pro_trial(plan.is_v2(), false)), - Some(plan @ (Plan::ZedPro | Plan::ZedProV2)) => card + Some(plan @ (Plan::V1(PlanV1::ZedProTrial) | Plan::V2(PlanV2::ZedProTrial))) => { + card.child(pro_trial_stamp) + .child(Label::new("You're in the Zed Pro Trial").size(LabelSize::Large)) + .child( + Label::new("Here's what you get for the next 14 days:") + .color(Color::Muted) + .mb_2(), + ) + .child(PlanDefinitions.pro_trial(plan.is_v2(), false)) + } + Some(plan @ (Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro))) => card .child(certified_user_stamp) .child(Label::new("You're in the Zed Pro plan").size(LabelSize::Large)) .child( @@ -326,7 +327,7 @@ impl Component for AiUpsellCard { sign_in_status: SignInStatus::SignedIn, sign_in: Arc::new(|_, _| {}), account_too_young: false, - user_plan: Some(Plan::ZedFree), + user_plan: Some(Plan::V1(PlanV1::ZedFree)), tab_index: Some(1), } .into_any_element(), @@ -337,7 +338,7 @@ impl Component for AiUpsellCard { sign_in_status: SignInStatus::SignedIn, sign_in: Arc::new(|_, _| {}), account_too_young: true, - user_plan: Some(Plan::ZedFree), + user_plan: Some(Plan::V1(PlanV1::ZedFree)), tab_index: Some(1), } .into_any_element(), @@ -348,7 +349,7 @@ impl Component for AiUpsellCard { sign_in_status: SignInStatus::SignedIn, sign_in: Arc::new(|_, _| {}), account_too_young: false, - user_plan: Some(Plan::ZedProTrial), + user_plan: Some(Plan::V1(PlanV1::ZedProTrial)), tab_index: Some(1), } .into_any_element(), @@ -359,7 +360,7 @@ impl Component for AiUpsellCard { sign_in_status: SignInStatus::SignedIn, sign_in: Arc::new(|_, _| {}), account_too_young: false, - user_plan: Some(Plan::ZedPro), + user_plan: Some(Plan::V1(PlanV1::ZedPro)), tab_index: Some(1), } .into_any_element(), diff --git a/crates/ai_onboarding/src/edit_prediction_onboarding_content.rs b/crates/ai_onboarding/src/edit_prediction_onboarding_content.rs index 50b729c37ee8cf98e188d66e716bdafa4ad11ca1..571f0f8e450ac2974cea2f4b2a7085069bc45c7c 100644 --- a/crates/ai_onboarding/src/edit_prediction_onboarding_content.rs +++ b/crates/ai_onboarding/src/edit_prediction_onboarding_content.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use client::{Client, UserStore}; -use cloud_llm_client::Plan; +use cloud_llm_client::{Plan, PlanV1, PlanV2}; use gpui::{Entity, IntoElement, ParentElement}; use ui::prelude::*; @@ -36,7 +36,9 @@ impl EditPredictionOnboarding { impl Render for EditPredictionOnboarding { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let is_free_plan = self.user_store.read(cx).plan() == Some(Plan::ZedFree); + let is_free_plan = self.user_store.read(cx).plan().is_some_and(|plan| { + matches!(plan, Plan::V1(PlanV1::ZedFree) | Plan::V2(PlanV2::ZedFree)) + }); let github_copilot = v_flex() .gap_1() diff --git a/crates/client/src/test.rs b/crates/client/src/test.rs index 3c451fcb015b5efb4fd860cce09cdb29a8f07379..da0e8a0f55a7256bd880b642a4d8cb2f744450d4 100644 --- a/crates/client/src/test.rs +++ b/crates/client/src/test.rs @@ -1,7 +1,7 @@ use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore}; use anyhow::{Context as _, Result, anyhow}; use cloud_api_client::{AuthenticatedUser, GetAuthenticatedUserResponse, PlanInfo}; -use cloud_llm_client::{CurrentUsage, Plan, UsageData, UsageLimit}; +use cloud_llm_client::{CurrentUsage, PlanV1, UsageData, UsageLimit}; use futures::{StreamExt, stream::BoxStream}; use gpui::{AppContext as _, BackgroundExecutor, Entity, TestAppContext}; use http_client::{AsyncBody, Method, Request, http}; @@ -269,7 +269,8 @@ pub fn make_get_authenticated_user_response( }, feature_flags: vec![], plan: PlanInfo { - plan: Plan::ZedPro, + plan: PlanV1::ZedPro, + plan_v2: None, subscription_period: None, usage: CurrentUsage { model_requests: UsageData { diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index a4c66e582c34e468432747d580e13c86b3ec33c8..63626e8ce1f3b25c742f227a56556545762367c3 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -5,7 +5,8 @@ use cloud_api_client::websocket_protocol::MessageToClient; use cloud_api_client::{GetAuthenticatedUserResponse, PlanInfo}; use cloud_llm_client::{ EDIT_PREDICTIONS_USAGE_AMOUNT_HEADER_NAME, EDIT_PREDICTIONS_USAGE_LIMIT_HEADER_NAME, - MODEL_REQUESTS_USAGE_AMOUNT_HEADER_NAME, MODEL_REQUESTS_USAGE_LIMIT_HEADER_NAME, UsageLimit, + MODEL_REQUESTS_USAGE_AMOUNT_HEADER_NAME, MODEL_REQUESTS_USAGE_LIMIT_HEADER_NAME, Plan, + UsageLimit, }; use collections::{HashMap, HashSet, hash_map::Entry}; use derive_more::Deref; @@ -692,20 +693,22 @@ impl UserStore { self.current_user.borrow().clone() } - pub fn plan(&self) -> Option { + pub fn plan(&self) -> Option { #[cfg(debug_assertions)] if let Ok(plan) = std::env::var("ZED_SIMULATE_PLAN").as_ref() { + use cloud_llm_client::PlanV1; + return match plan.as_str() { - "free" => Some(cloud_llm_client::Plan::ZedFree), - "trial" => Some(cloud_llm_client::Plan::ZedProTrial), - "pro" => Some(cloud_llm_client::Plan::ZedPro), + "free" => Some(Plan::V1(PlanV1::ZedFree)), + "trial" => Some(Plan::V1(PlanV1::ZedProTrial)), + "pro" => Some(Plan::V1(PlanV1::ZedPro)), _ => { panic!("ZED_SIMULATE_PLAN must be one of 'free', 'trial', or 'pro'"); } }; } - self.plan_info.as_ref().map(|info| info.plan) + self.plan_info.as_ref().map(|info| info.plan()) } pub fn subscription_period(&self) -> Option<(DateTime, DateTime)> { diff --git a/crates/cloud_api_types/src/cloud_api_types.rs b/crates/cloud_api_types/src/cloud_api_types.rs index fa189cd3b5ed7e87e2f3f2a6803d9095c6305105..fddf505f1075ae98f9b0260b2bc0391c7a008942 100644 --- a/crates/cloud_api_types/src/cloud_api_types.rs +++ b/crates/cloud_api_types/src/cloud_api_types.rs @@ -1,6 +1,7 @@ mod timestamp; pub mod websocket_protocol; +use cloud_llm_client::Plan; use serde::{Deserialize, Serialize}; pub use crate::timestamp::Timestamp; @@ -27,7 +28,9 @@ pub struct AuthenticatedUser { #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct PlanInfo { - pub plan: cloud_llm_client::Plan, + pub plan: cloud_llm_client::PlanV1, + #[serde(default)] + pub plan_v2: Option, pub subscription_period: Option, pub usage: cloud_llm_client::CurrentUsage, pub trial_started_at: Option, @@ -36,6 +39,12 @@ pub struct PlanInfo { pub has_overdue_invoices: bool, } +impl PlanInfo { + pub fn plan(&self) -> Plan { + self.plan_v2.map(Plan::V2).unwrap_or(Plan::V1(self.plan)) + } +} + #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)] pub struct SubscriptionPeriod { pub started_at: Timestamp, diff --git a/crates/cloud_llm_client/src/cloud_llm_client.rs b/crates/cloud_llm_client/src/cloud_llm_client.rs index 99f77f4ef3c3e30202faf19d427a7a014fc47f87..16267d86d806387140016dc0a25021ad92607ff2 100644 --- a/crates/cloud_llm_client/src/cloud_llm_client.rs +++ b/crates/cloud_llm_client/src/cloud_llm_client.rs @@ -74,38 +74,60 @@ impl FromStr for UsageLimit { } } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Plan { + V1(PlanV1), + V2(PlanV2), +} + +impl Plan { + pub fn is_v2(&self) -> bool { + matches!(self, Self::V2(_)) + } +} + #[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -pub enum Plan { +pub enum PlanV1 { #[default] #[serde(alias = "Free")] ZedFree, - ZedFreeV2, #[serde(alias = "ZedPro")] ZedPro, - ZedProV2, #[serde(alias = "ZedProTrial")] ZedProTrial, - ZedProTrialV2, } -impl Plan { - pub fn is_v2(&self) -> bool { - matches!(self, Plan::ZedFreeV2 | Plan::ZedProV2 | Plan::ZedProTrialV2) +impl FromStr for PlanV1 { + type Err = anyhow::Error; + + fn from_str(value: &str) -> Result { + match value { + "zed_free" => Ok(Self::ZedFree), + "zed_pro" => Ok(Self::ZedPro), + "zed_pro_trial" => Ok(Self::ZedProTrial), + plan => Err(anyhow::anyhow!("invalid plan: {plan:?}")), + } } } -impl FromStr for Plan { +#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PlanV2 { + #[default] + ZedFree, + ZedPro, + ZedProTrial, +} + +impl FromStr for PlanV2 { type Err = anyhow::Error; fn from_str(value: &str) -> Result { match value { - "zed_free" => Ok(Plan::ZedFree), - "zed_free_v2" => Ok(Plan::ZedFreeV2), - "zed_pro" => Ok(Plan::ZedPro), - "zed_pro_v2" => Ok(Plan::ZedProV2), - "zed_pro_trial" => Ok(Plan::ZedProTrial), - "zed_pro_trial_v2" => Ok(Plan::ZedProTrialV2), + "zed_free" => Ok(Self::ZedFree), + "zed_pro" => Ok(Self::ZedPro), + "zed_pro_trial" => Ok(Self::ZedProTrial), plan => Err(anyhow::anyhow!("invalid plan: {plan:?}")), } } @@ -306,7 +328,7 @@ pub struct ListModelsResponse { #[derive(Debug, Serialize, Deserialize)] pub struct GetSubscriptionResponse { - pub plan: Plan, + pub plan: PlanV1, pub usage: Option, } @@ -330,33 +352,39 @@ mod tests { use super::*; #[test] - fn test_plan_deserialize_snake_case() { - let plan = serde_json::from_value::(json!("zed_free")).unwrap(); - assert_eq!(plan, Plan::ZedFree); + fn test_plan_v1_deserialize_snake_case() { + let plan = serde_json::from_value::(json!("zed_free")).unwrap(); + assert_eq!(plan, PlanV1::ZedFree); - let plan = serde_json::from_value::(json!("zed_pro")).unwrap(); - assert_eq!(plan, Plan::ZedPro); + let plan = serde_json::from_value::(json!("zed_pro")).unwrap(); + assert_eq!(plan, PlanV1::ZedPro); - let plan = serde_json::from_value::(json!("zed_pro_trial")).unwrap(); - assert_eq!(plan, Plan::ZedProTrial); + let plan = serde_json::from_value::(json!("zed_pro_trial")).unwrap(); + assert_eq!(plan, PlanV1::ZedProTrial); + } + + #[test] + fn test_plan_v1_deserialize_aliases() { + let plan = serde_json::from_value::(json!("Free")).unwrap(); + assert_eq!(plan, PlanV1::ZedFree); - let plan = serde_json::from_value::(json!("zed_pro_v2")).unwrap(); - assert_eq!(plan, Plan::ZedProV2); + let plan = serde_json::from_value::(json!("ZedPro")).unwrap(); + assert_eq!(plan, PlanV1::ZedPro); - let plan = serde_json::from_value::(json!("zed_pro_trial_v2")).unwrap(); - assert_eq!(plan, Plan::ZedProTrialV2); + let plan = serde_json::from_value::(json!("ZedProTrial")).unwrap(); + assert_eq!(plan, PlanV1::ZedProTrial); } #[test] - fn test_plan_deserialize_aliases() { - let plan = serde_json::from_value::(json!("Free")).unwrap(); - assert_eq!(plan, Plan::ZedFree); + fn test_plan_v2_deserialize_snake_case() { + let plan = serde_json::from_value::(json!("zed_free")).unwrap(); + assert_eq!(plan, PlanV2::ZedFree); - let plan = serde_json::from_value::(json!("ZedPro")).unwrap(); - assert_eq!(plan, Plan::ZedPro); + let plan = serde_json::from_value::(json!("zed_pro")).unwrap(); + assert_eq!(plan, PlanV2::ZedPro); - let plan = serde_json::from_value::(json!("ZedProTrial")).unwrap(); - assert_eq!(plan, Plan::ZedProTrial); + let plan = serde_json::from_value::(json!("zed_pro_trial")).unwrap(); + assert_eq!(plan, PlanV2::ZedProTrial); } #[test] diff --git a/crates/language_model/src/model/cloud_model.rs b/crates/language_model/src/model/cloud_model.rs index 409bef2c828db2ea591fc2492d205b7b8429e9ed..e25ed0de50c4ddf03ff539dbce728dbc20def9b5 100644 --- a/crates/language_model/src/model/cloud_model.rs +++ b/crates/language_model/src/model/cloud_model.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use anyhow::Result; use client::Client; use cloud_api_types::websocket_protocol::MessageToClient; -use cloud_llm_client::Plan; +use cloud_llm_client::{Plan, PlanV1}; use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Global, ReadGlobal as _}; use smol::lock::{RwLock, RwLockUpgradableReadGuard, RwLockWriteGuard}; use thiserror::Error; @@ -29,16 +29,16 @@ pub struct ModelRequestLimitReachedError { impl fmt::Display for ModelRequestLimitReachedError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let message = match self.plan { - Plan::ZedFree => "Model request limit reached. Upgrade to Zed Pro for more requests.", - Plan::ZedPro => { + Plan::V1(PlanV1::ZedFree) => { + "Model request limit reached. Upgrade to Zed Pro for more requests." + } + Plan::V1(PlanV1::ZedPro) => { "Model request limit reached. Upgrade to usage-based billing for more requests." } - Plan::ZedProTrial => { + Plan::V1(PlanV1::ZedProTrial) => { "Model request limit reached. Upgrade to Zed Pro for more requests." } - Plan::ZedFreeV2 | Plan::ZedProV2 | Plan::ZedProTrialV2 => { - "Model request limit reached." - } + Plan::V2(_) => "Model request limit reached.", }; write!(f, "{message}") diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index b473d06357a7ac4a60749ef796642bee41a5a511..421e34e658fb604e21fdfc4af52b3c4c7874fd70 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -7,8 +7,9 @@ use cloud_llm_client::{ CLIENT_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, CURRENT_PLAN_HEADER_NAME, CompletionBody, CompletionEvent, CompletionRequestStatus, CountTokensBody, CountTokensResponse, EXPIRED_LLM_TOKEN_HEADER_NAME, ListModelsResponse, MODEL_REQUESTS_RESOURCE_HEADER_VALUE, Plan, - SERVER_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME, - TOOL_USE_LIMIT_REACHED_HEADER_NAME, ZED_VERSION_HEADER_NAME, + PlanV1, PlanV2, SERVER_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, + SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME, TOOL_USE_LIMIT_REACHED_HEADER_NAME, + ZED_VERSION_HEADER_NAME, }; use futures::{ AsyncBufReadExt, FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream, @@ -480,7 +481,8 @@ impl CloudLanguageModel { .headers() .get(CURRENT_PLAN_HEADER_NAME) .and_then(|plan| plan.to_str().ok()) - .and_then(|plan| cloud_llm_client::Plan::from_str(plan).ok()) + .and_then(|plan| cloud_llm_client::PlanV1::from_str(plan).ok()) + .map(Plan::V1) { return Err(anyhow!(ModelRequestLimitReachedError { plan })); } @@ -994,15 +996,17 @@ impl RenderOnce for ZedAiConfiguration { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { let young_account_banner = YoungAccountBanner; - let is_pro = self.plan == Some(Plan::ZedPro); + let is_pro = self.plan.is_some_and(|plan| { + matches!(plan, Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro)) + }); let subscription_text = match (self.plan, self.subscription_period) { - (Some(Plan::ZedPro), Some(_)) => { + (Some(Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro)), Some(_)) => { "You have access to Zed's hosted models through your Pro subscription." } - (Some(Plan::ZedProTrial), Some(_)) => { + (Some(Plan::V1(PlanV1::ZedProTrial) | Plan::V2(PlanV2::ZedProTrial)), Some(_)) => { "You have access to Zed's hosted models through your Pro trial." } - (Some(Plan::ZedFree), Some(_)) => { + (Some(Plan::V1(PlanV1::ZedFree) | Plan::V2(PlanV2::ZedFree)), Some(_)) => { "You have basic access to Zed's hosted models through the Free plan." } _ => { @@ -1161,15 +1165,15 @@ impl Component for ZedAiConfiguration { ), single_example( "Free Plan", - configuration(true, Some(Plan::ZedFree), true, false), + configuration(true, Some(Plan::V1(PlanV1::ZedFree)), true, false), ), single_example( "Zed Pro Trial Plan", - configuration(true, Some(Plan::ZedProTrial), true, false), + configuration(true, Some(Plan::V1(PlanV1::ZedProTrial)), true, false), ), single_example( "Zed Pro Plan", - configuration(true, Some(Plan::ZedPro), true, false), + configuration(true, Some(Plan::V1(PlanV1::ZedPro)), true, false), ), ]) .into_any_element(), diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 3e098b3660f57852aa3c208fb613cfbc3dcb1c70..4d451dd41f162d5ea41c10b7e1f85f825b9a7385 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -23,7 +23,7 @@ use crate::application_menu::{ use auto_update::AutoUpdateStatus; use call::ActiveCall; use client::{Client, UserStore, zed_urls}; -use cloud_llm_client::Plan; +use cloud_llm_client::{Plan, PlanV1, PlanV2}; use gpui::{ Action, AnyElement, App, Context, Corner, Element, Entity, Focusable, InteractiveElement, IntoElement, MouseButton, ParentElement, Render, StatefulInteractiveElement, Styled, @@ -668,13 +668,13 @@ impl TitleBar { let user_login = user.github_login.clone(); let (plan_name, label_color, bg_color) = match plan { - None | Some(Plan::ZedFree | Plan::ZedFreeV2) => { + None | Some(Plan::V1(PlanV1::ZedFree) | Plan::V2(PlanV2::ZedFree)) => { ("Free", Color::Default, free_chip_bg) } - Some(Plan::ZedProTrial | Plan::ZedProTrialV2) => { + Some(Plan::V1(PlanV1::ZedProTrial) | Plan::V2(PlanV2::ZedProTrial)) => { ("Pro Trial", Color::Accent, pro_chip_bg) } - Some(Plan::ZedPro | Plan::ZedProV2) => { + Some(Plan::V1(PlanV1::ZedPro) | Plan::V2(PlanV2::ZedPro)) => { ("Pro", Color::Accent, pro_chip_bg) } };