cloud_llm_client: Move `Plan` type into `cloud_api_types` (#47778)

Marshall Bowers created

This PR moves the `Plan` type out of `cloud_llm_client` and into
`cloud_api_types`.

Release Notes:

- N/A

Change summary

Cargo.lock                                                     |  7 
crates/agent/Cargo.toml                                        |  1 
crates/agent/src/thread.rs                                     |  3 
crates/agent_ui/Cargo.toml                                     |  1 
crates/agent_ui/src/agent_configuration.rs                     |  2 
crates/agent_ui/src/agent_panel.rs                             |  2 
crates/ai_onboarding/Cargo.toml                                |  2 
crates/ai_onboarding/src/agent_panel_onboarding_content.rs     |  2 
crates/ai_onboarding/src/ai_onboarding.rs                      |  2 
crates/ai_onboarding/src/ai_upsell_card.rs                     |  2 
crates/ai_onboarding/src/edit_prediction_onboarding_content.rs |  2 
crates/client/src/test.rs                                      |  4 
crates/client/src/user.rs                                      |  7 
crates/cloud_api_types/src/cloud_api_types.rs                  | 25 -
crates/cloud_api_types/src/plan.rs                             | 59 ++++
crates/cloud_llm_client/src/cloud_llm_client.rs                | 29 -
crates/language_models/Cargo.toml                              |  1 
crates/language_models/src/provider/cloud.rs                   |  3 
crates/title_bar/Cargo.toml                                    |  2 
crates/title_bar/src/title_bar.rs                              |  2 
20 files changed, 87 insertions(+), 71 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -164,6 +164,7 @@ dependencies = [
  "chrono",
  "client",
  "clock",
+ "cloud_api_types",
  "cloud_llm_client",
  "collections",
  "context_server",
@@ -351,6 +352,7 @@ dependencies = [
  "chrono",
  "client",
  "clock",
+ "cloud_api_types",
  "cloud_llm_client",
  "collections",
  "command_palette_hooks",
@@ -497,7 +499,7 @@ name = "ai_onboarding"
 version = "0.1.0"
 dependencies = [
  "client",
- "cloud_llm_client",
+ "cloud_api_types",
  "component",
  "gpui",
  "language_model",
@@ -9069,6 +9071,7 @@ dependencies = [
  "bedrock",
  "chrono",
  "client",
+ "cloud_api_types",
  "cloud_llm_client",
  "collections",
  "component",
@@ -17100,7 +17103,7 @@ dependencies = [
  "channel",
  "chrono",
  "client",
- "cloud_llm_client",
+ "cloud_api_types",
  "collections",
  "db",
  "git_ui",

crates/agent/Cargo.toml 🔗

@@ -26,6 +26,7 @@ agent_settings.workspace = true
 anyhow.workspace = true
 chrono.workspace = true
 client.workspace = true
+cloud_api_types.workspace = true
 cloud_llm_client.workspace = true
 collections.workspace = true
 context_server.workspace = true

crates/agent/src/thread.rs 🔗

@@ -18,7 +18,8 @@ use agent_settings::{
 use anyhow::{Context as _, Result, anyhow};
 use chrono::{DateTime, Utc};
 use client::UserStore;
-use cloud_llm_client::{CompletionIntent, Plan};
+use cloud_api_types::Plan;
+use cloud_llm_client::CompletionIntent;
 use collections::{HashMap, HashSet, IndexMap};
 use fs::Fs;
 use futures::stream;

crates/agent_ui/Cargo.toml 🔗

@@ -43,6 +43,7 @@ audio.workspace = true
 buffer_diff.workspace = true
 chrono.workspace = true
 client.workspace = true
+cloud_api_types.workspace = true
 cloud_llm_client.workspace = true
 collections.workspace = true
 command_palette_hooks.workspace = true

crates/agent_ui/src/agent_configuration.rs 🔗

@@ -9,7 +9,7 @@ use std::{ops::Range, sync::Arc};
 use agent::ContextServerRegistry;
 use anyhow::Result;
 use client::zed_urls;
-use cloud_llm_client::{Plan, PlanV2};
+use cloud_api_types::{Plan, PlanV2};
 use collections::HashMap;
 use context_server::ContextServerId;
 use editor::{Editor, MultiBufferOffset, SelectionEffects, scroll::Autoscroll};

crates/agent_ui/src/agent_panel.rs 🔗

@@ -37,7 +37,7 @@ use anyhow::{Result, anyhow};
 use assistant_slash_command::SlashCommandWorkingSet;
 use assistant_text_thread::{TextThread, TextThreadEvent, TextThreadSummary};
 use client::UserStore;
-use cloud_llm_client::{Plan, PlanV2};
+use cloud_api_types::{Plan, PlanV2};
 use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
 use extension::ExtensionEvents;
 use extension_host::ExtensionStore;

crates/ai_onboarding/Cargo.toml 🔗

@@ -16,7 +16,7 @@ default = []
 
 [dependencies]
 client.workspace = true
-cloud_llm_client.workspace = true
+cloud_api_types.workspace = true
 component.workspace = true
 gpui.workspace = true
 language_model.workspace = true

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, PlanV2};
+use cloud_api_types::{Plan, PlanV2};
 use gpui::{Entity, IntoElement, ParentElement};
 use language_model::{LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID};
 use ui::prelude::*;

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, PlanV2};
+use cloud_api_types::{Plan, PlanV2};
 pub use edit_prediction_onboarding_content::EditPredictionOnboarding;
 pub use plan_definitions::PlanDefinitions;
 pub use young_account_banner::YoungAccountBanner;

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, PlanV2};
+use cloud_api_types::{Plan, PlanV2};
 use gpui::{AnyElement, App, Entity, IntoElement, RenderOnce, Window};
 use ui::{CommonAnimationExt, Divider, Vector, VectorName, prelude::*};
 

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, PlanV2, UsageData, UsageLimit};
+use cloud_api_client::{AuthenticatedUser, GetAuthenticatedUserResponse, PlanInfo, PlanV2};
+use cloud_llm_client::{CurrentUsage, UsageData, UsageLimit};
 use futures::{StreamExt, stream::BoxStream};
 use gpui::{AppContext as _, Entity, TestAppContext};
 use http_client::{AsyncBody, Method, Request, http};

crates/client/src/user.rs 🔗

@@ -2,10 +2,9 @@ use super::{Client, Status, TypedEnvelope, proto};
 use anyhow::{Context as _, Result};
 use chrono::{DateTime, Utc};
 use cloud_api_client::websocket_protocol::MessageToClient;
-use cloud_api_client::{GetAuthenticatedUserResponse, PlanInfo};
+use cloud_api_client::{GetAuthenticatedUserResponse, Plan, PlanInfo};
 use cloud_llm_client::{
-    EDIT_PREDICTIONS_USAGE_AMOUNT_HEADER_NAME, EDIT_PREDICTIONS_USAGE_LIMIT_HEADER_NAME, Plan,
-    UsageLimit,
+    EDIT_PREDICTIONS_USAGE_AMOUNT_HEADER_NAME, EDIT_PREDICTIONS_USAGE_LIMIT_HEADER_NAME, UsageLimit,
 };
 use collections::{HashMap, HashSet, hash_map::Entry};
 use derive_more::Deref;
@@ -669,7 +668,7 @@ impl UserStore {
     pub fn plan(&self) -> Option<Plan> {
         #[cfg(debug_assertions)]
         if let Ok(plan) = std::env::var("ZED_SIMULATE_PLAN").as_ref() {
-            use cloud_llm_client::PlanV2;
+            use cloud_api_client::PlanV2;
 
             return match plan.as_str() {
                 "free" => Some(Plan::V2(PlanV2::ZedFree)),

crates/cloud_api_types/src/cloud_api_types.rs 🔗

@@ -1,9 +1,10 @@
+mod plan;
 mod timestamp;
 pub mod websocket_protocol;
 
-use cloud_llm_client::Plan;
 use serde::{Deserialize, Serialize};
 
+pub use crate::plan::*;
 pub use crate::timestamp::Timestamp;
 
 pub const ZED_SYSTEM_ID_HEADER_NAME: &str = "x-zed-system-id";
@@ -26,28 +27,6 @@ pub struct AuthenticatedUser {
     pub accepted_tos_at: Option<Timestamp>,
 }
 
-#[derive(Debug, PartialEq, Serialize, Deserialize)]
-pub struct PlanInfo {
-    pub plan_v2: cloud_llm_client::PlanV2,
-    pub subscription_period: Option<SubscriptionPeriod>,
-    pub usage: cloud_llm_client::CurrentUsage,
-    pub trial_started_at: Option<Timestamp>,
-    pub is_account_too_young: bool,
-    pub has_overdue_invoices: bool,
-}
-
-impl PlanInfo {
-    pub fn plan(&self) -> Plan {
-        Plan::V2(self.plan_v2)
-    }
-}
-
-#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
-pub struct SubscriptionPeriod {
-    pub started_at: Timestamp,
-    pub ended_at: Timestamp,
-}
-
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct AcceptTermsOfServiceResponse {
     pub user: AuthenticatedUser,

crates/cloud_api_types/src/plan.rs 🔗

@@ -0,0 +1,59 @@
+use serde::{Deserialize, Serialize};
+
+use crate::Timestamp;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Plan {
+    V2(PlanV2),
+}
+
+#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum PlanV2 {
+    #[default]
+    ZedFree,
+    ZedPro,
+    ZedProTrial,
+}
+
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct PlanInfo {
+    pub plan_v2: PlanV2,
+    pub subscription_period: Option<SubscriptionPeriod>,
+    pub usage: cloud_llm_client::CurrentUsage,
+    pub trial_started_at: Option<Timestamp>,
+    pub is_account_too_young: bool,
+    pub has_overdue_invoices: bool,
+}
+
+impl PlanInfo {
+    pub fn plan(&self) -> Plan {
+        Plan::V2(self.plan_v2)
+    }
+}
+
+#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
+pub struct SubscriptionPeriod {
+    pub started_at: Timestamp,
+    pub ended_at: Timestamp,
+}
+
+#[cfg(test)]
+mod tests {
+    use pretty_assertions::assert_eq;
+    use serde_json::json;
+
+    use super::*;
+
+    #[test]
+    fn test_plan_v2_deserialize_snake_case() {
+        let plan = serde_json::from_value::<PlanV2>(json!("zed_free")).unwrap();
+        assert_eq!(plan, PlanV2::ZedFree);
+
+        let plan = serde_json::from_value::<PlanV2>(json!("zed_pro")).unwrap();
+        assert_eq!(plan, PlanV2::ZedPro);
+
+        let plan = serde_json::from_value::<PlanV2>(json!("zed_pro_trial")).unwrap();
+        assert_eq!(plan, PlanV2::ZedProTrial);
+    }
+}

crates/cloud_llm_client/src/cloud_llm_client.rs 🔗

@@ -74,20 +74,6 @@ impl FromStr for UsageLimit {
     }
 }
 
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum Plan {
-    V2(PlanV2),
-}
-
-#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
-#[serde(rename_all = "snake_case")]
-pub enum PlanV2 {
-    #[default]
-    ZedFree,
-    ZedPro,
-    ZedProTrial,
-}
-
 #[derive(
     Debug, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize, EnumString, EnumIter, Display,
 )]
@@ -337,23 +323,8 @@ pub struct UsageData {
 
 #[cfg(test)]
 mod tests {
-    use pretty_assertions::assert_eq;
-    use serde_json::json;
-
     use super::*;
 
-    #[test]
-    fn test_plan_v2_deserialize_snake_case() {
-        let plan = serde_json::from_value::<PlanV2>(json!("zed_free")).unwrap();
-        assert_eq!(plan, PlanV2::ZedFree);
-
-        let plan = serde_json::from_value::<PlanV2>(json!("zed_pro")).unwrap();
-        assert_eq!(plan, PlanV2::ZedPro);
-
-        let plan = serde_json::from_value::<PlanV2>(json!("zed_pro_trial")).unwrap();
-        assert_eq!(plan, PlanV2::ZedProTrial);
-    }
-
     #[test]
     fn test_usage_limit_from_str() {
         let limit = UsageLimit::from_str("unlimited").unwrap();

crates/language_models/Cargo.toml 🔗

@@ -21,6 +21,7 @@ aws_http_client.workspace = true
 bedrock.workspace = true
 chrono.workspace = true
 client.workspace = true
+cloud_api_types.workspace = true
 cloud_llm_client.workspace = true
 collections.workspace = true
 component.workspace = true

crates/language_models/src/provider/cloud.rs 🔗

@@ -3,9 +3,10 @@ use anthropic::AnthropicModelMode;
 use anyhow::{Context as _, Result, anyhow};
 use chrono::{DateTime, Utc};
 use client::{Client, UserStore, zed_urls};
+use cloud_api_types::{Plan, PlanV2};
 use cloud_llm_client::{
     CLIENT_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, CLIENT_SUPPORTS_X_AI_HEADER_NAME, CompletionBody,
-    CompletionEvent, CountTokensBody, CountTokensResponse, ListModelsResponse, Plan, PlanV2,
+    CompletionEvent, CountTokensBody, CountTokensResponse, ListModelsResponse,
     SERVER_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, ZED_VERSION_HEADER_NAME,
 };
 use feature_flags::{CloudThinkingToggleFeatureFlag, FeatureFlagAppExt as _};

crates/title_bar/Cargo.toml 🔗

@@ -36,7 +36,7 @@ call.workspace = true
 channel.workspace = true
 chrono.workspace = true
 client.workspace = true
-cloud_llm_client.workspace = true
+cloud_api_types.workspace = true
 db.workspace = true
 git_ui.workspace = true
 gpui = { workspace = true, features = ["screen-capture"] }

crates/title_bar/src/title_bar.rs 🔗

@@ -21,7 +21,7 @@ use crate::application_menu::{
 use auto_update::AutoUpdateStatus;
 use call::ActiveCall;
 use client::{Client, UserStore, zed_urls};
-use cloud_llm_client::{Plan, PlanV2};
+use cloud_api_types::{Plan, PlanV2};
 use gpui::{
     Action, AnyElement, App, Context, Corner, Element, Entity, FocusHandle, Focusable,
     InteractiveElement, IntoElement, MouseButton, ParentElement, Render,