collab: Pass down billing information in UpdatePlan
message (#29929)
This PR updates the `UpdatePlan` message to include some additional information about the user's billing subscription usage. Release Notes: - N/A
This commit is contained in:
parent
07b4480396
commit
10bdf39497
4 changed files with 137 additions and 5 deletions
|
@ -101,6 +101,8 @@ pub struct UserStore {
|
||||||
participant_indices: HashMap<u64, ParticipantIndex>,
|
participant_indices: HashMap<u64, ParticipantIndex>,
|
||||||
update_contacts_tx: mpsc::UnboundedSender<UpdateContacts>,
|
update_contacts_tx: mpsc::UnboundedSender<UpdateContacts>,
|
||||||
current_plan: Option<proto::Plan>,
|
current_plan: Option<proto::Plan>,
|
||||||
|
trial_started_at: Option<DateTime<Utc>>,
|
||||||
|
is_usage_based_billing_enabled: Option<bool>,
|
||||||
current_user: watch::Receiver<Option<Arc<User>>>,
|
current_user: watch::Receiver<Option<Arc<User>>>,
|
||||||
accepted_tos_at: Option<Option<DateTime<Utc>>>,
|
accepted_tos_at: Option<Option<DateTime<Utc>>>,
|
||||||
contacts: Vec<Arc<Contact>>,
|
contacts: Vec<Arc<Contact>>,
|
||||||
|
@ -160,6 +162,8 @@ impl UserStore {
|
||||||
by_github_login: Default::default(),
|
by_github_login: Default::default(),
|
||||||
current_user: current_user_rx,
|
current_user: current_user_rx,
|
||||||
current_plan: None,
|
current_plan: None,
|
||||||
|
trial_started_at: None,
|
||||||
|
is_usage_based_billing_enabled: None,
|
||||||
accepted_tos_at: None,
|
accepted_tos_at: None,
|
||||||
contacts: Default::default(),
|
contacts: Default::default(),
|
||||||
incoming_contact_requests: Default::default(),
|
incoming_contact_requests: Default::default(),
|
||||||
|
@ -321,6 +325,11 @@ impl UserStore {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.current_plan = Some(message.payload.plan());
|
this.current_plan = Some(message.payload.plan());
|
||||||
|
this.trial_started_at = message
|
||||||
|
.payload
|
||||||
|
.trial_started_at
|
||||||
|
.and_then(|trial_started_at| DateTime::from_timestamp(trial_started_at as i64, 0));
|
||||||
|
this.is_usage_based_billing_enabled = message.payload.is_usage_based_billing_enabled;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -482,7 +482,9 @@ CREATE TABLE IF NOT EXISTS billing_preferences (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
user_id INTEGER NOT NULL REFERENCES users (id),
|
user_id INTEGER NOT NULL REFERENCES users (id),
|
||||||
max_monthly_llm_usage_spending_in_cents INTEGER NOT NULL
|
max_monthly_llm_usage_spending_in_cents INTEGER NOT NULL,
|
||||||
|
model_request_overages_enabled bool NOT NULL DEFAULT FALSE,
|
||||||
|
model_request_overages_spend_limit_in_cents integer NOT NULL DEFAULT 0
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "uix_billing_preferences_on_user_id" ON billing_preferences (user_id);
|
CREATE UNIQUE INDEX "uix_billing_preferences_on_user_id" ON billing_preferences (user_id);
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod connection_pool;
|
||||||
|
|
||||||
use crate::api::{CloudflareIpCountryHeader, SystemIdHeader};
|
use crate::api::{CloudflareIpCountryHeader, SystemIdHeader};
|
||||||
use crate::db::billing_subscription::SubscriptionKind;
|
use crate::db::billing_subscription::SubscriptionKind;
|
||||||
use crate::llm::LlmTokenClaims;
|
use crate::llm::{AGENT_EXTENDED_TRIAL_FEATURE_FLAG, LlmTokenClaims};
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState, Error, Result, auth,
|
AppState, Error, Result, auth,
|
||||||
db::{
|
db::{
|
||||||
|
@ -37,6 +37,7 @@ use core::fmt::{self, Debug, Formatter};
|
||||||
use reqwest_client::ReqwestClient;
|
use reqwest_client::ReqwestClient;
|
||||||
use rpc::proto::split_repository_update;
|
use rpc::proto::split_repository_update;
|
||||||
use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi};
|
use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi};
|
||||||
|
use util::maybe;
|
||||||
|
|
||||||
use futures::{
|
use futures::{
|
||||||
FutureExt, SinkExt, StreamExt, TryStreamExt, channel::oneshot, future::BoxFuture,
|
FutureExt, SinkExt, StreamExt, TryStreamExt, channel::oneshot, future::BoxFuture,
|
||||||
|
@ -2701,14 +2702,111 @@ fn should_auto_subscribe_to_channels(version: ZedVersion) -> bool {
|
||||||
version.0.minor() < 139
|
version.0.minor() < 139
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_user_plan(_user_id: UserId, session: &Session) -> Result<()> {
|
async fn update_user_plan(user_id: UserId, session: &Session) -> Result<()> {
|
||||||
let plan = session.current_plan(&session.db().await).await?;
|
let db = session.db().await;
|
||||||
|
|
||||||
|
let feature_flags = db.get_user_flags(user_id).await?;
|
||||||
|
let plan = session.current_plan(&db).await?;
|
||||||
|
let billing_customer = db.get_billing_customer_by_user_id(user_id).await?;
|
||||||
|
let billing_preferences = db.get_billing_preferences(user_id).await?;
|
||||||
|
|
||||||
|
let usage = if let Some(llm_db) = session.app_state.llm_db.clone() {
|
||||||
|
let subscription = db.get_active_billing_subscription(user_id).await?;
|
||||||
|
|
||||||
|
let subscription_period = maybe!({
|
||||||
|
let subscription = subscription?;
|
||||||
|
let period_start_at = subscription.current_period_start_at()?;
|
||||||
|
let period_end_at = subscription.current_period_end_at()?;
|
||||||
|
|
||||||
|
Some((period_start_at, period_end_at))
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some((period_start_at, period_end_at)) = subscription_period {
|
||||||
|
llm_db
|
||||||
|
.get_subscription_usage_for_period(user_id, period_start_at, period_end_at)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
session
|
session
|
||||||
.peer
|
.peer
|
||||||
.send(
|
.send(
|
||||||
session.connection_id,
|
session.connection_id,
|
||||||
proto::UpdateUserPlan { plan: plan.into() },
|
proto::UpdateUserPlan {
|
||||||
|
plan: plan.into(),
|
||||||
|
trial_started_at: billing_customer
|
||||||
|
.and_then(|billing_customer| billing_customer.trial_started_at)
|
||||||
|
.map(|trial_started_at| trial_started_at.and_utc().timestamp() as u64),
|
||||||
|
is_usage_based_billing_enabled: billing_preferences
|
||||||
|
.map(|preferences| preferences.model_request_overages_enabled),
|
||||||
|
usage: usage.map(|usage| {
|
||||||
|
let plan = match plan {
|
||||||
|
proto::Plan::Free => zed_llm_client::Plan::Free,
|
||||||
|
proto::Plan::ZedPro => zed_llm_client::Plan::ZedPro,
|
||||||
|
proto::Plan::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
|
||||||
|
};
|
||||||
|
|
||||||
|
let model_requests_limit = match plan.model_requests_limit() {
|
||||||
|
zed_llm_client::UsageLimit::Limited(limit) => {
|
||||||
|
let limit = if plan == zed_llm_client::Plan::ZedProTrial
|
||||||
|
&& feature_flags
|
||||||
|
.iter()
|
||||||
|
.any(|flag| flag == AGENT_EXTENDED_TRIAL_FEATURE_FLAG)
|
||||||
|
{
|
||||||
|
1_000
|
||||||
|
} else {
|
||||||
|
limit
|
||||||
|
};
|
||||||
|
|
||||||
|
zed_llm_client::UsageLimit::Limited(limit)
|
||||||
|
}
|
||||||
|
zed_llm_client::UsageLimit::Unlimited => {
|
||||||
|
zed_llm_client::UsageLimit::Unlimited
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
proto::SubscriptionUsage {
|
||||||
|
model_requests_usage_amount: usage.model_requests as u32,
|
||||||
|
model_requests_usage_limit: Some(proto::UsageLimit {
|
||||||
|
variant: Some(match model_requests_limit {
|
||||||
|
zed_llm_client::UsageLimit::Limited(limit) => {
|
||||||
|
proto::usage_limit::Variant::Limited(
|
||||||
|
proto::usage_limit::Limited {
|
||||||
|
limit: limit as u32,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
zed_llm_client::UsageLimit::Unlimited => {
|
||||||
|
proto::usage_limit::Variant::Unlimited(
|
||||||
|
proto::usage_limit::Unlimited {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
edit_predictions_usage_amount: usage.edit_predictions as u32,
|
||||||
|
edit_predictions_usage_limit: Some(proto::UsageLimit {
|
||||||
|
variant: Some(match plan.edit_predictions_limit() {
|
||||||
|
zed_llm_client::UsageLimit::Limited(limit) => {
|
||||||
|
proto::usage_limit::Variant::Limited(
|
||||||
|
proto::usage_limit::Limited {
|
||||||
|
limit: limit as u32,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
zed_llm_client::UsageLimit::Unlimited => {
|
||||||
|
proto::usage_limit::Variant::Unlimited(
|
||||||
|
proto::usage_limit::Unlimited {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.trace_err();
|
.trace_err();
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,29 @@ enum Plan {
|
||||||
|
|
||||||
message UpdateUserPlan {
|
message UpdateUserPlan {
|
||||||
Plan plan = 1;
|
Plan plan = 1;
|
||||||
|
optional uint64 trial_started_at = 2;
|
||||||
|
optional bool is_usage_based_billing_enabled = 3;
|
||||||
|
optional SubscriptionUsage usage = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SubscriptionUsage {
|
||||||
|
uint32 model_requests_usage_amount = 1;
|
||||||
|
UsageLimit model_requests_usage_limit = 2;
|
||||||
|
uint32 edit_predictions_usage_amount = 3;
|
||||||
|
UsageLimit edit_predictions_usage_limit = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UsageLimit {
|
||||||
|
oneof variant {
|
||||||
|
Limited limited = 1;
|
||||||
|
Unlimited unlimited = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Limited {
|
||||||
|
uint32 limit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Unlimited {}
|
||||||
}
|
}
|
||||||
|
|
||||||
message AcceptTermsOfService {}
|
message AcceptTermsOfService {}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue