collab: Remove Supermaven API key issuance (#46044)

Marshall Bowers created

This PR removes the ability to retrieve a Supermaven API key from
Collab.

This was staff-gated (at least, on the server), and judging by the logs,
no one is using it.

Release Notes:

- N/A

Change summary

Cargo.lock                                  |   2 
crates/collab/Cargo.toml                    |   2 
crates/collab/src/rpc.rs                    |  60 -------
crates/proto/proto/app.proto                |   6 
crates/proto/proto/zed.proto                |   4 
crates/proto/src/proto.rs                   |   3 
crates/supermaven/src/messages.rs           |   7 
crates/supermaven/src/supermaven.rs         |  25 ---
crates/supermaven_api/src/supermaven_api.rs | 170 ----------------------
9 files changed, 4 insertions(+), 275 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3354,7 +3354,6 @@ dependencies = [
  "remote",
  "remote_server",
  "reqwest 0.11.27",
- "reqwest_client",
  "rpc",
  "scrypt",
  "sea-orm",
@@ -3369,7 +3368,6 @@ dependencies = [
  "sqlx",
  "strum 0.27.2",
  "subtle",
- "supermaven_api",
  "task",
  "telemetry_events",
  "text",

crates/collab/Cargo.toml 🔗

@@ -44,7 +44,6 @@ prometheus = "0.14"
 prost.workspace = true
 rand.workspace = true
 reqwest = { version = "0.11", features = ["json"] }
-reqwest_client.workspace = true
 rpc.workspace = true
 scrypt = "0.11"
 # sea-orm and sea-orm-macros versions must match exactly.
@@ -57,7 +56,6 @@ sha2.workspace = true
 sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "json", "time", "uuid", "any"] }
 strum.workspace = true
 subtle.workspace = true
-supermaven_api.workspace = true
 telemetry_events.workspace = true
 text.workspace = true
 time.workspace = true

crates/collab/src/rpc.rs 🔗

@@ -32,9 +32,7 @@ use collections::{HashMap, HashSet};
 pub use connection_pool::{ConnectionPool, ZedVersion};
 use core::fmt::{self, Debug, Formatter};
 use futures::TryFutureExt as _;
-use reqwest_client::ReqwestClient;
 use rpc::proto::split_repository_update;
-use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi};
 use tracing::Span;
 use util::paths::PathStyle;
 
@@ -195,7 +193,6 @@ struct Session {
     peer: Arc<Peer>,
     connection_pool: Arc<parking_lot::Mutex<ConnectionPool>>,
     app_state: Arc<AppState>,
-    supermaven_client: Option<Arc<SupermavenAdminApi>>,
     /// The GeoIP country code for the user.
     #[allow(unused)]
     geoip_country_code: Option<String>,
@@ -224,6 +221,7 @@ impl Session {
         }
     }
 
+    #[expect(dead_code)]
     fn is_staff(&self) -> bool {
         match &self.principal {
             Principal::User(user) => user.admin,
@@ -237,13 +235,6 @@ impl Session {
             Principal::Impersonated { user, .. } => user.id,
         }
     }
-
-    pub fn email(&self) -> Option<String> {
-        match &self.principal {
-            Principal::User(user) => user.email_address.clone(),
-            Principal::Impersonated { user, .. } => user.email_address.clone(),
-        }
-    }
 }
 
 impl Debug for Session {
@@ -443,7 +434,6 @@ impl Server {
             .add_message_handler(update_followers)
             .add_message_handler(acknowledge_channel_message)
             .add_message_handler(acknowledge_buffer_version)
-            .add_request_handler(get_supermaven_api_key)
             .add_request_handler(forward_mutating_project_request::<proto::OpenContext>)
             .add_request_handler(forward_mutating_project_request::<proto::CreateContext>)
             .add_request_handler(forward_mutating_project_request::<proto::SynchronizeContexts>)
@@ -815,24 +805,6 @@ impl Server {
 
             tracing::info!("connection opened");
 
-            let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION"));
-            let http_client = match ReqwestClient::user_agent(&user_agent) {
-                Ok(http_client) => Arc::new(http_client),
-                Err(error) => {
-                    tracing::error!(?error, "failed to create HTTP client");
-                    return;
-                }
-            };
-
-            let supermaven_client = this.app_state.config.supermaven_admin_api_key.clone().map(
-                |supermaven_admin_api_key| {
-                    Arc::new(SupermavenAdminApi::new(
-                        supermaven_admin_api_key.to_string(),
-                        http_client.clone(),
-                    ))
-                },
-            );
-
             let session = Session {
                 principal: principal.clone(),
                 connection_id,
@@ -843,7 +815,6 @@ impl Server {
                 geoip_country_code,
                 system_id,
                 _executor: executor.clone(),
-                supermaven_client,
             };
 
             if let Err(error) = this
@@ -3654,35 +3625,6 @@ async fn acknowledge_buffer_version(
     Ok(())
 }
 
-/// Get a Supermaven API key for the user
-async fn get_supermaven_api_key(
-    _request: proto::GetSupermavenApiKey,
-    response: Response<proto::GetSupermavenApiKey>,
-    session: MessageContext,
-) -> Result<()> {
-    let user_id: String = session.user_id().to_string();
-    if !session.is_staff() {
-        return Err(anyhow!("supermaven not enabled for this account"))?;
-    }
-
-    let email = session.email().context("user must have an email")?;
-
-    let supermaven_admin_api = session
-        .supermaven_client
-        .as_ref()
-        .context("supermaven not configured")?;
-
-    let result = supermaven_admin_api
-        .try_get_or_create_user(CreateExternalUserRequest { id: user_id, email })
-        .await?;
-
-    response.send(proto::GetSupermavenApiKeyResponse {
-        api_key: result.api_key,
-    })?;
-
-    Ok(())
-}
-
 /// Start receiving chat updates for a channel
 async fn join_channel_chat(
     _request: proto::JoinChannelChat,

crates/proto/proto/app.proto 🔗

@@ -68,9 +68,3 @@ message AskPassRequest {
 message AskPassResponse {
     string response = 1;
 }
-
-message GetSupermavenApiKey {}
-
-message GetSupermavenApiKeyResponse {
-    string api_key = 1;
-}

crates/proto/proto/zed.proto 🔗

@@ -218,9 +218,6 @@ message Envelope {
 
         OpenNewBuffer open_new_buffer = 196;
 
-        GetSupermavenApiKey get_supermaven_api_key = 198;
-        GetSupermavenApiKeyResponse get_supermaven_api_key_response = 199;
-
         TaskContextForLocation task_context_for_location = 203;
         TaskContext task_context = 204;
 
@@ -464,6 +461,7 @@ message Envelope {
     reserved 189 to 192;
     reserved 193 to 195;
     reserved 197;
+    reserved 198 to 199;
     reserved 200 to 202;
     reserved 205 to 206;
     reserved 221;

crates/proto/src/proto.rs 🔗

@@ -115,8 +115,6 @@ messages!(
     (GetReferencesResponse, Background),
     (GetSignatureHelp, Background),
     (GetSignatureHelpResponse, Background),
-    (GetSupermavenApiKey, Background),
-    (GetSupermavenApiKeyResponse, Background),
     (GetTypeDefinition, Background),
     (GetTypeDefinitionResponse, Background),
     (GetImplementation, Background),
@@ -388,7 +386,6 @@ request_messages!(
     (GetSignatureHelp, GetSignatureHelpResponse),
     (OpenUnstagedDiff, OpenUnstagedDiffResponse),
     (OpenUncommittedDiff, OpenUncommittedDiffResponse),
-    (GetSupermavenApiKey, GetSupermavenApiKeyResponse),
     (GetTypeDefinition, GetTypeDefinitionResponse),
     (LinkedEditingRange, LinkedEditingRangeResponse),
     (ListRemoteDirectory, ListRemoteDirectoryResponse),

crates/supermaven/src/messages.rs 🔗

@@ -1,16 +1,9 @@
 use serde::{Deserialize, Serialize};
 
-#[derive(Debug, Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SetApiKey {
-    pub api_key: String,
-}
-
 // Outbound messages
 #[derive(Debug, Serialize)]
 #[serde(tag = "kind", rename_all = "snake_case")]
 pub enum OutboundMessage {
-    SetApiKey(SetApiKey),
     StateUpdate(StateUpdateMessage),
     #[allow(dead_code)]
     UseFreeVersion,

crates/supermaven/src/supermaven.rs 🔗

@@ -291,31 +291,6 @@ impl SupermavenAgent {
 
         let (outgoing_tx, outgoing_rx) = mpsc::unbounded();
 
-        cx.spawn({
-            let client = client.clone();
-            let outgoing_tx = outgoing_tx.clone();
-            async move |this, cx| {
-                let mut status = client.status();
-                while let Some(status) = status.next().await {
-                    if status.is_connected() {
-                        let api_key = client.request(proto::GetSupermavenApiKey {}).await?.api_key;
-                        outgoing_tx
-                            .unbounded_send(OutboundMessage::SetApiKey(SetApiKey { api_key }))
-                            .ok();
-                        this.update(cx, |this, cx| {
-                            if let Supermaven::Spawned(this) = this {
-                                this.account_status = AccountStatus::Ready;
-                                cx.notify();
-                            }
-                        })?;
-                        break;
-                    }
-                }
-                anyhow::Ok(())
-            }
-        })
-        .detach();
-
         Ok(Self {
             _process: process,
             next_state_id: SupermavenCompletionStateId::default(),

crates/supermaven_api/src/supermaven_api.rs 🔗

@@ -1,50 +1,20 @@
-use anyhow::{Context as _, Result, anyhow};
+use anyhow::{Context as _, Result};
 use futures::AsyncReadExt;
 use futures::io::BufReader;
 use http_client::{AsyncBody, HttpClient, Request as HttpRequest};
 use paths::supermaven_dir;
-use serde::{Deserialize, Serialize};
+use serde::Deserialize;
 use smol::fs::{self, File};
 use std::path::{Path, PathBuf};
 use std::sync::Arc;
 
 use util::fs::{make_file_executable, remove_matching};
 
-#[derive(Serialize)]
-pub struct GetExternalUserRequest {
-    pub id: String,
-}
-
-#[derive(Serialize)]
-pub struct CreateExternalUserRequest {
-    pub id: String,
-    pub email: String,
-}
-
-#[derive(Serialize)]
-pub struct DeleteExternalUserRequest {
-    pub id: String,
-}
-
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct CreateExternalUserResponse {
-    pub api_key: String,
-}
-
 #[derive(Deserialize)]
 pub struct SupermavenApiError {
     pub message: String,
 }
 
-pub struct SupermavenBinary {}
-
-pub struct SupermavenAdminApi {
-    admin_api_key: String,
-    api_url: String,
-    http_client: Arc<dyn HttpClient>,
-}
-
 #[derive(Debug, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct SupermavenDownloadResponse {
@@ -53,142 +23,6 @@ pub struct SupermavenDownloadResponse {
     pub sha256_hash: String,
 }
 
-#[derive(Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SupermavenUser {
-    #[expect(
-        unused,
-        reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove"
-    )]
-    id: String,
-    #[expect(
-        unused,
-        reason = "This field was found to be unused with serde library bump; it's left as is due to insufficient context on PO's side, but it *may* be fine to remove"
-    )]
-    email: String,
-    api_key: String,
-}
-
-impl SupermavenAdminApi {
-    pub fn new(admin_api_key: String, http_client: Arc<dyn HttpClient>) -> Self {
-        Self {
-            admin_api_key,
-            api_url: "https://supermaven.com/api/".to_string(),
-            http_client,
-        }
-    }
-
-    pub async fn try_get_user(
-        &self,
-        request: GetExternalUserRequest,
-    ) -> Result<Option<SupermavenUser>> {
-        let uri = format!("{}external-user/{}", &self.api_url, &request.id);
-
-        let request = HttpRequest::get(&uri).header("Authorization", self.admin_api_key.clone());
-
-        let mut response = self
-            .http_client
-            .send(request.body(AsyncBody::default())?)
-            .await
-            .with_context(|| "Unable to get Supermaven API Key".to_string())?;
-
-        let mut body = Vec::new();
-        response.body_mut().read_to_end(&mut body).await?;
-
-        if response.status().is_client_error() {
-            let error: SupermavenApiError = serde_json::from_slice(&body)?;
-            if error.message == "User not found" {
-                return Ok(None);
-            } else {
-                anyhow::bail!("Supermaven API error: {}", error.message);
-            }
-        } else if response.status().is_server_error() {
-            let error: SupermavenApiError = serde_json::from_slice(&body)?;
-            return Err(anyhow!("Supermaven API server error").context(error.message));
-        }
-
-        let body_str = std::str::from_utf8(&body)?;
-
-        Ok(Some(
-            serde_json::from_str::<SupermavenUser>(body_str)
-                .with_context(|| "Unable to parse Supermaven user response".to_string())?,
-        ))
-    }
-
-    pub async fn try_create_user(
-        &self,
-        request: CreateExternalUserRequest,
-    ) -> Result<CreateExternalUserResponse> {
-        let uri = format!("{}external-user", &self.api_url);
-
-        let request = HttpRequest::post(&uri)
-            .header("Authorization", self.admin_api_key.clone())
-            .body(AsyncBody::from(serde_json::to_vec(&request)?))?;
-
-        let mut response = self
-            .http_client
-            .send(request)
-            .await
-            .with_context(|| "Unable to create Supermaven API Key".to_string())?;
-
-        let mut body = Vec::new();
-        response.body_mut().read_to_end(&mut body).await?;
-
-        let body_str = std::str::from_utf8(&body)?;
-
-        if !response.status().is_success() {
-            let error: SupermavenApiError = serde_json::from_slice(&body)?;
-            return Err(anyhow!("Supermaven API server error").context(error.message));
-        }
-
-        serde_json::from_str::<CreateExternalUserResponse>(body_str)
-            .with_context(|| "Unable to parse Supermaven API Key response".to_string())
-    }
-
-    pub async fn try_delete_user(&self, request: DeleteExternalUserRequest) -> Result<()> {
-        let uri = format!("{}external-user/{}", &self.api_url, &request.id);
-
-        let request = HttpRequest::delete(&uri).header("Authorization", self.admin_api_key.clone());
-
-        let mut response = self
-            .http_client
-            .send(request.body(AsyncBody::default())?)
-            .await
-            .with_context(|| "Unable to delete Supermaven User".to_string())?;
-
-        let mut body = Vec::new();
-        response.body_mut().read_to_end(&mut body).await?;
-
-        if response.status().is_client_error() {
-            let error: SupermavenApiError = serde_json::from_slice(&body)?;
-            if error.message == "User not found" {
-                return Ok(());
-            } else {
-                anyhow::bail!("Supermaven API error: {}", error.message);
-            }
-        } else if response.status().is_server_error() {
-            let error: SupermavenApiError = serde_json::from_slice(&body)?;
-            return Err(anyhow!("Supermaven API server error").context(error.message));
-        }
-
-        Ok(())
-    }
-
-    pub async fn try_get_or_create_user(
-        &self,
-        request: CreateExternalUserRequest,
-    ) -> Result<CreateExternalUserResponse> {
-        let get_user_request = GetExternalUserRequest {
-            id: request.id.clone(),
-        };
-
-        match self.try_get_user(get_user_request).await? {
-            None => self.try_create_user(request).await,
-            Some(SupermavenUser { api_key, .. }) => Ok(CreateExternalUserResponse { api_key }),
-        }
-    }
-}
-
 pub async fn latest_release(
     client: Arc<dyn HttpClient>,
     platform: &str,