Set User Agent for Jupyter websocket connections (#21910)

Kyle Kelley created

Some VPN configurations require that websockets present a user agent.
This adds it in directly for the repl usage. I wish there was a way to
reuse the user agent from the `cx.http_client`, but I'm not seeing a
simple way to do that for the moment since it's not on the `HttpClient`
trait.

No release notes since this feature hasn't been announced/exposed.

Release Notes:

- N/A

Change summary

Cargo.lock                                |  1 
crates/repl/Cargo.toml                    |  1 
crates/repl/src/kernels/remote_kernels.rs | 35 ++++++++++++++++++++++--
3 files changed, 34 insertions(+), 3 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -10363,6 +10363,7 @@ dependencies = [
  "alacritty_terminal",
  "anyhow",
  "async-dispatcher",
+ "async-tungstenite 0.28.1",
  "base64 0.22.1",
  "client",
  "collections",

crates/repl/Cargo.toml 🔗

@@ -16,6 +16,7 @@ doctest = false
 alacritty_terminal.workspace = true
 anyhow.workspace = true
 async-dispatcher.workspace = true
+async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] }
 base64.workspace = true
 client.workspace = true
 collections.workspace = true

crates/repl/src/kernels/remote_kernels.rs 🔗

@@ -3,6 +3,11 @@ use gpui::{Task, View, WindowContext};
 use http_client::{AsyncBody, HttpClient, Request};
 use jupyter_protocol::{ExecutionState, JupyterKernelspec, JupyterMessage, KernelInfoReply};
 
+use async_tungstenite::{
+    async_std::connect_async,
+    tungstenite::{client::IntoClientRequest, http::HeaderValue},
+};
+
 use futures::StreamExt;
 use smol::io::AsyncReadExt as _;
 
@@ -11,8 +16,8 @@ use crate::Session;
 use super::RunningKernel;
 use anyhow::Result;
 use jupyter_websocket_client::{
-    JupyterWebSocketReader, JupyterWebSocketWriter, KernelLaunchRequest, KernelSpecsResponse,
-    RemoteServer,
+    JupyterWebSocket, JupyterWebSocketReader, JupyterWebSocketWriter, KernelLaunchRequest,
+    KernelSpecsResponse, RemoteServer,
 };
 use std::{fmt::Debug, sync::Arc};
 
@@ -151,7 +156,31 @@ impl RemoteRunningKernel {
             )
             .await?;
 
-            let (kernel_socket, _response) = remote_server.connect_to_kernel(&kernel_id).await?;
+            let ws_url = format!(
+                "{}/api/kernels/{}/channels?token={}",
+                remote_server.base_url.replace("http", "ws"),
+                kernel_id,
+                remote_server.token
+            );
+
+            let mut req: Request<()> = ws_url.into_client_request()?;
+            let headers = req.headers_mut();
+
+            headers.insert(
+                "User-Agent",
+                HeaderValue::from_str(&format!(
+                    "Zed/{} ({}; {})",
+                    "repl",
+                    std::env::consts::OS,
+                    std::env::consts::ARCH
+                ))?,
+            );
+
+            let response = connect_async(req).await;
+
+            let (ws_stream, _response) = response?;
+
+            let kernel_socket = JupyterWebSocket { inner: ws_stream };
 
             let (mut w, mut r): (JupyterWebSocketWriter, JupyterWebSocketReader) =
                 kernel_socket.split();