collab: Remove `GET /billing/monthly_spend` endpoint (#31123)

Marshall Bowers created

This PR removes the `GET /billing/monthly_spend` endpoint, as it is no
longer used.

Release Notes:

- N/A

Change summary

crates/collab/src/api/billing.rs                 | 55 --------------
crates/collab/src/llm.rs                         |  4 -
crates/collab/src/llm/db/queries/usages.rs       | 68 ------------------
crates/collab/src/llm/db/tables.rs               |  1 
crates/collab/src/llm/db/tables/monthly_usage.rs | 22 -----
5 files changed, 2 insertions(+), 148 deletions(-)

Detailed changes

crates/collab/src/api/billing.rs 🔗

@@ -27,11 +27,9 @@ use crate::db::billing_subscription::{
     StripeCancellationReason, StripeSubscriptionStatus, SubscriptionKind,
 };
 use crate::llm::db::subscription_usage_meter::CompletionMode;
-use crate::llm::{
-    AGENT_EXTENDED_TRIAL_FEATURE_FLAG, DEFAULT_MAX_MONTHLY_SPEND, FREE_TIER_MONTHLY_SPENDING_LIMIT,
-};
+use crate::llm::{AGENT_EXTENDED_TRIAL_FEATURE_FLAG, DEFAULT_MAX_MONTHLY_SPEND};
 use crate::rpc::{ResultExt as _, Server};
-use crate::{AppState, Cents, Error, Result};
+use crate::{AppState, Error, Result};
 use crate::{db::UserId, llm::db::LlmDatabase};
 use crate::{
     db::{
@@ -64,7 +62,6 @@ pub fn router() -> Router {
             "/billing/subscriptions/sync",
             post(sync_billing_subscription),
         )
-        .route("/billing/monthly_spend", get(get_monthly_spend))
         .route("/billing/usage", get(get_current_usage))
 }
 
@@ -1223,54 +1220,6 @@ async fn handle_customer_subscription_event(
     Ok(())
 }
 
-#[derive(Debug, Deserialize)]
-struct GetMonthlySpendParams {
-    github_user_id: i32,
-}
-
-#[derive(Debug, Serialize)]
-struct GetMonthlySpendResponse {
-    monthly_free_tier_spend_in_cents: u32,
-    monthly_free_tier_allowance_in_cents: u32,
-    monthly_spend_in_cents: u32,
-}
-
-async fn get_monthly_spend(
-    Extension(app): Extension<Arc<AppState>>,
-    Query(params): Query<GetMonthlySpendParams>,
-) -> Result<Json<GetMonthlySpendResponse>> {
-    let user = app
-        .db
-        .get_user_by_github_user_id(params.github_user_id)
-        .await?
-        .context("user not found")?;
-
-    let Some(llm_db) = app.llm_db.clone() else {
-        return Err(Error::http(
-            StatusCode::NOT_IMPLEMENTED,
-            "LLM database not available".into(),
-        ));
-    };
-
-    let free_tier = user
-        .custom_llm_monthly_allowance_in_cents
-        .map(|allowance| Cents(allowance as u32))
-        .unwrap_or(FREE_TIER_MONTHLY_SPENDING_LIMIT);
-
-    let spending_for_month = llm_db
-        .get_user_spending_for_month(user.id, Utc::now())
-        .await?;
-
-    let free_tier_spend = Cents::min(spending_for_month, free_tier);
-    let monthly_spend = spending_for_month.saturating_sub(free_tier);
-
-    Ok(Json(GetMonthlySpendResponse {
-        monthly_free_tier_spend_in_cents: free_tier_spend.0,
-        monthly_free_tier_allowance_in_cents: free_tier.0,
-        monthly_spend_in_cents: monthly_spend.0,
-    }))
-}
-
 #[derive(Debug, Deserialize)]
 struct GetCurrentUsageParams {
     github_user_id: i32,

crates/collab/src/llm.rs 🔗

@@ -7,10 +7,6 @@ pub use token::*;
 
 pub const AGENT_EXTENDED_TRIAL_FEATURE_FLAG: &str = "agent-extended-trial";
 
-/// The maximum monthly spending an individual user can reach on the free tier
-/// before they have to pay.
-pub const FREE_TIER_MONTHLY_SPENDING_LIMIT: Cents = Cents::from_dollars(10);
-
 /// The default value to use for maximum spend per month if the user did not
 /// explicitly set a maximum spend.
 ///

crates/collab/src/llm/db/queries/usages.rs 🔗

@@ -1,7 +1,3 @@
-use crate::db::UserId;
-use crate::llm::Cents;
-use chrono::Datelike;
-use futures::StreamExt as _;
 use std::str::FromStr;
 use strum::IntoEnumIterator as _;
 
@@ -45,68 +41,4 @@ impl LlmDatabase {
             .collect();
         Ok(())
     }
-
-    pub async fn get_user_spending_for_month(
-        &self,
-        user_id: UserId,
-        now: DateTimeUtc,
-    ) -> Result<Cents> {
-        self.transaction(|tx| async move {
-            let month = now.date_naive().month() as i32;
-            let year = now.date_naive().year();
-
-            let mut monthly_usages = monthly_usage::Entity::find()
-                .filter(
-                    monthly_usage::Column::UserId
-                        .eq(user_id)
-                        .and(monthly_usage::Column::Month.eq(month))
-                        .and(monthly_usage::Column::Year.eq(year)),
-                )
-                .stream(&*tx)
-                .await?;
-            let mut monthly_spending = Cents::ZERO;
-
-            while let Some(usage) = monthly_usages.next().await {
-                let usage = usage?;
-                let Ok(model) = self.model_by_id(usage.model_id) else {
-                    continue;
-                };
-
-                monthly_spending += calculate_spending(
-                    model,
-                    usage.input_tokens as usize,
-                    usage.cache_creation_input_tokens as usize,
-                    usage.cache_read_input_tokens as usize,
-                    usage.output_tokens as usize,
-                );
-            }
-
-            Ok(monthly_spending)
-        })
-        .await
-    }
-}
-
-fn calculate_spending(
-    model: &model::Model,
-    input_tokens_this_month: usize,
-    cache_creation_input_tokens_this_month: usize,
-    cache_read_input_tokens_this_month: usize,
-    output_tokens_this_month: usize,
-) -> Cents {
-    let input_token_cost =
-        input_tokens_this_month * model.price_per_million_input_tokens as usize / 1_000_000;
-    let cache_creation_input_token_cost = cache_creation_input_tokens_this_month
-        * model.price_per_million_cache_creation_input_tokens as usize
-        / 1_000_000;
-    let cache_read_input_token_cost = cache_read_input_tokens_this_month
-        * model.price_per_million_cache_read_input_tokens as usize
-        / 1_000_000;
-    let output_token_cost =
-        output_tokens_this_month * model.price_per_million_output_tokens as usize / 1_000_000;
-    let spending = input_token_cost
-        + cache_creation_input_token_cost
-        + cache_read_input_token_cost
-        + output_token_cost;
-    Cents::new(spending as u32)
 }

crates/collab/src/llm/db/tables/monthly_usage.rs 🔗

@@ -1,22 +0,0 @@
-use crate::{db::UserId, llm::db::ModelId};
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
-#[sea_orm(table_name = "monthly_usages")]
-pub struct Model {
-    #[sea_orm(primary_key)]
-    pub id: i32,
-    pub user_id: UserId,
-    pub model_id: ModelId,
-    pub month: i32,
-    pub year: i32,
-    pub input_tokens: i64,
-    pub cache_creation_input_tokens: i64,
-    pub cache_read_input_tokens: i64,
-    pub output_tokens: i64,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}