Update Copilot sign-up URL based on verification domain (#44085)

Moritz Bitsch created

Use the url crate to extract the domain from the verification URI and
construct the appropriate Copilot sign-up URL for GitHub or GitHub
Enterprise.

Release Notes:

- Improved github enterprise (ghe) copilot sign in

Change summary

Cargo.lock                    |  1 
crates/copilot/Cargo.toml     |  1 
crates/copilot/src/sign_in.rs | 44 ++++++++++++++++++++++++++++++++++--
3 files changed, 43 insertions(+), 3 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3672,6 +3672,7 @@ dependencies = [
  "task",
  "theme",
  "ui",
+ "url",
  "util",
  "workspace",
  "zlog",

crates/copilot/Cargo.toml 🔗

@@ -52,6 +52,7 @@ ui.workspace = true
 util.workspace = true
 workspace.workspace = true
 itertools.workspace = true
+url.workspace = true
 
 [target.'cfg(windows)'.dependencies]
 async-std = { version = "1.12.0", features = ["unstable"] }

crates/copilot/src/sign_in.rs 🔗

@@ -6,6 +6,7 @@ use gpui::{
     Subscription, Window, WindowBounds, WindowOptions, div, point,
 };
 use ui::{ButtonLike, CommonAnimationExt, ConfiguredApiCard, Vector, VectorName, prelude::*};
+use url::Url;
 use util::ResultExt as _;
 use workspace::{Toast, Workspace, notifications::NotificationId};
 
@@ -152,6 +153,7 @@ pub struct CopilotCodeVerification {
     focus_handle: FocusHandle,
     copilot: Entity<Copilot>,
     _subscription: Subscription,
+    sign_up_url: Option<String>,
 }
 
 impl Focusable for CopilotCodeVerification {
@@ -183,11 +185,22 @@ impl CopilotCodeVerification {
         .detach();
 
         let status = copilot.read(cx).status();
+        // Determine sign-up URL based on verification_uri domain if available
+        let sign_up_url = if let Status::SigningIn {
+            prompt: Some(ref prompt),
+        } = status
+        {
+            // Extract domain from verification_uri to construct sign-up URL
+            Self::get_sign_up_url_from_verification(&prompt.verification_uri)
+        } else {
+            None
+        };
         Self {
             status,
             connect_clicked: false,
             focus_handle: cx.focus_handle(),
             copilot: copilot.clone(),
+            sign_up_url,
             _subscription: cx.observe(copilot, |this, copilot, cx| {
                 let status = copilot.read(cx).status();
                 match status {
@@ -201,10 +214,30 @@ impl CopilotCodeVerification {
     }
 
     pub fn set_status(&mut self, status: Status, cx: &mut Context<Self>) {
+        // Update sign-up URL if we have a new verification URI
+        if let Status::SigningIn {
+            prompt: Some(ref prompt),
+        } = status
+        {
+            self.sign_up_url = Self::get_sign_up_url_from_verification(&prompt.verification_uri);
+        }
         self.status = status;
         cx.notify();
     }
 
+    fn get_sign_up_url_from_verification(verification_uri: &str) -> Option<String> {
+        // Extract domain from verification URI using url crate
+        if let Ok(url) = Url::parse(verification_uri)
+            && let Some(host) = url.host_str()
+            && !host.contains("github.com")
+        {
+            // For GHE, construct URL from domain
+            Some(format!("https://{}/features/copilot", host))
+        } else {
+            None
+        }
+    }
+
     fn render_device_code(data: &PromptUserDeviceFlow, cx: &mut Context<Self>) -> impl IntoElement {
         let copied = cx
             .read_from_clipboard()
@@ -302,7 +335,12 @@ impl CopilotCodeVerification {
             )
     }
 
-    fn render_unauthorized_modal(cx: &mut Context<Self>) -> impl Element {
+    fn render_unauthorized_modal(&self, cx: &mut Context<Self>) -> impl Element {
+        let sign_up_url = self
+            .sign_up_url
+            .as_deref()
+            .unwrap_or(COPILOT_SIGN_UP_URL)
+            .to_owned();
         let description = "Enable Copilot by connecting your existing license once you have subscribed or renewed your subscription.";
 
         v_flex()
@@ -319,7 +357,7 @@ impl CopilotCodeVerification {
                     .full_width()
                     .style(ButtonStyle::Outlined)
                     .size(ButtonSize::Medium)
-                    .on_click(|_, _, cx| cx.open_url(COPILOT_SIGN_UP_URL)),
+                    .on_click(move |_, _, cx| cx.open_url(&sign_up_url)),
             )
             .child(
                 Button::new("copilot-subscribe-cancel-button", "Cancel")
@@ -374,7 +412,7 @@ impl Render for CopilotCodeVerification {
             } => Self::render_prompting_modal(self.connect_clicked, prompt, cx).into_any_element(),
             Status::Unauthorized => {
                 self.connect_clicked = false;
-                Self::render_unauthorized_modal(cx).into_any_element()
+                self.render_unauthorized_modal(cx).into_any_element()
             }
             Status::Authorized => {
                 self.connect_clicked = false;