diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 25a1ed8670..23f0b3915a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -800,7 +800,8 @@ jobs:
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
- if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) && env.RELEASE_CHANNEL == 'preview' }} # upload only preview
+ # Re-enable when we are ready to publish windows preview releases
+ if: false && ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) && env.RELEASE_CHANNEL == 'preview' }} # upload only preview
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
diff --git a/Cargo.lock b/Cargo.lock
index 38bb7819ca..7da1b6064b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3043,6 +3043,7 @@ dependencies = [
"context_server",
"ctor",
"dap",
+ "dap-types",
"dap_adapters",
"dashmap 6.1.0",
"debugger_ui",
@@ -9000,6 +9001,7 @@ dependencies = [
"util",
"vercel",
"workspace-hack",
+ "x_ai",
"zed_llm_client",
]
@@ -19731,6 +19733,17 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
+[[package]]
+name = "x_ai"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "schemars",
+ "serde",
+ "strum 0.27.1",
+ "workspace-hack",
+]
+
[[package]]
name = "xattr"
version = "0.2.3"
@@ -19972,7 +19985,7 @@ dependencies = [
[[package]]
name = "zed"
-version = "0.195.0"
+version = "0.195.2"
dependencies = [
"activity_indicator",
"agent",
diff --git a/Cargo.toml b/Cargo.toml
index a4d8b3cb95..d1042c499c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -177,6 +177,7 @@ members = [
"crates/welcome",
"crates/workspace",
"crates/worktree",
+ "crates/x_ai",
"crates/zed",
"crates/zed_actions",
"crates/zeta",
@@ -390,6 +391,7 @@ web_search_providers = { path = "crates/web_search_providers" }
welcome = { path = "crates/welcome" }
workspace = { path = "crates/workspace" }
worktree = { path = "crates/worktree" }
+x_ai = { path = "crates/x_ai" }
zed = { path = "crates/zed" }
zed_actions = { path = "crates/zed_actions" }
zeta = { path = "crates/zeta" }
diff --git a/assets/icons/ai_x_ai.svg b/assets/icons/ai_x_ai.svg
new file mode 100644
index 0000000000..289525c8ef
--- /dev/null
+++ b/assets/icons/ai_x_ai.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs
index 8bfdd50761..579331c9ac 100644
--- a/crates/agent_ui/src/agent_configuration.rs
+++ b/crates/agent_ui/src/agent_configuration.rs
@@ -491,6 +491,7 @@ impl AgentConfiguration {
category_filter: Some(
ExtensionCategoryFilter::ContextServers,
),
+ id: None,
}
.boxed_clone(),
cx,
diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs
index 5f58e0bd8d..47adcd859e 100644
--- a/crates/agent_ui/src/agent_panel.rs
+++ b/crates/agent_ui/src/agent_panel.rs
@@ -1778,6 +1778,7 @@ impl AgentPanel {
category_filter: Some(
zed_actions::ExtensionCategoryFilter::ContextServers,
),
+ id: None,
}),
)
.action("Add Custom Server…", Box::new(AddContextServer))
diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml
index 55c15cac5a..7b536a2d24 100644
--- a/crates/collab/Cargo.toml
+++ b/crates/collab/Cargo.toml
@@ -94,6 +94,7 @@ context_server.workspace = true
ctor.workspace = true
dap = { workspace = true, features = ["test-support"] }
dap_adapters = { workspace = true, features = ["test-support"] }
+dap-types.workspace = true
debugger_ui = { workspace = true, features = ["test-support"] }
editor = { workspace = true, features = ["test-support"] }
extension.workspace = true
diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs
index 7aeb381c02..8ab6e6910c 100644
--- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs
+++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs
@@ -2,6 +2,7 @@ use crate::tests::TestServer;
use call::ActiveCall;
use collections::{HashMap, HashSet};
+use dap::{Capabilities, adapters::DebugTaskDefinition, transport::RequestHandling};
use debugger_ui::debugger_panel::DebugPanel;
use extension::ExtensionHostProxy;
use fs::{FakeFs, Fs as _, RemoveOptions};
@@ -22,6 +23,7 @@ use language::{
use node_runtime::NodeRuntime;
use project::{
ProjectPath,
+ debugger::session::ThreadId,
lsp_store::{FormatTrigger, LspFormatTarget},
};
use remote::SshRemoteClient;
@@ -29,7 +31,11 @@ use remote_server::{HeadlessAppState, HeadlessProject};
use rpc::proto;
use serde_json::json;
use settings::SettingsStore;
-use std::{path::Path, sync::Arc};
+use std::{
+ path::Path,
+ sync::{Arc, atomic::AtomicUsize},
+};
+use task::TcpArgumentsTemplate;
use util::path;
#[gpui::test(iterations = 10)]
@@ -688,3 +694,162 @@ async fn test_remote_server_debugger(
shutdown_session.await.unwrap();
}
+
+#[gpui::test]
+async fn test_slow_adapter_startup_retries(
+ cx_a: &mut TestAppContext,
+ server_cx: &mut TestAppContext,
+ executor: BackgroundExecutor,
+) {
+ cx_a.update(|cx| {
+ release_channel::init(SemanticVersion::default(), cx);
+ command_palette_hooks::init(cx);
+ zlog::init_test();
+ dap_adapters::init(cx);
+ });
+ server_cx.update(|cx| {
+ release_channel::init(SemanticVersion::default(), cx);
+ dap_adapters::init(cx);
+ });
+ let (opts, server_ssh) = SshRemoteClient::fake_server(cx_a, server_cx);
+ let remote_fs = FakeFs::new(server_cx.executor());
+ remote_fs
+ .insert_tree(
+ path!("/code"),
+ json!({
+ "lib.rs": "fn one() -> usize { 1 }"
+ }),
+ )
+ .await;
+
+ // User A connects to the remote project via SSH.
+ server_cx.update(HeadlessProject::init);
+ let remote_http_client = Arc::new(BlockedHttpClient);
+ let node = NodeRuntime::unavailable();
+ let languages = Arc::new(LanguageRegistry::new(server_cx.executor()));
+ let _headless_project = server_cx.new(|cx| {
+ client::init_settings(cx);
+ HeadlessProject::new(
+ HeadlessAppState {
+ session: server_ssh,
+ fs: remote_fs.clone(),
+ http_client: remote_http_client,
+ node_runtime: node,
+ languages,
+ extension_host_proxy: Arc::new(ExtensionHostProxy::new()),
+ },
+ cx,
+ )
+ });
+
+ let client_ssh = SshRemoteClient::fake_client(opts, cx_a).await;
+ let mut server = TestServer::start(server_cx.executor()).await;
+ let client_a = server.create_client(cx_a, "user_a").await;
+ cx_a.update(|cx| {
+ debugger_ui::init(cx);
+ command_palette_hooks::init(cx);
+ });
+ let (project_a, _) = client_a
+ .build_ssh_project(path!("/code"), client_ssh.clone(), cx_a)
+ .await;
+
+ let (workspace, cx_a) = client_a.build_workspace(&project_a, cx_a);
+
+ let debugger_panel = workspace
+ .update_in(cx_a, |_workspace, window, cx| {
+ cx.spawn_in(window, DebugPanel::load)
+ })
+ .await
+ .unwrap();
+
+ workspace.update_in(cx_a, |workspace, window, cx| {
+ workspace.add_panel(debugger_panel, window, cx);
+ });
+
+ cx_a.run_until_parked();
+ let debug_panel = workspace
+ .update(cx_a, |workspace, cx| workspace.panel::(cx))
+ .unwrap();
+
+ let workspace_window = cx_a
+ .window_handle()
+ .downcast::()
+ .unwrap();
+
+ let count = Arc::new(AtomicUsize::new(0));
+ let session = debugger_ui::tests::start_debug_session_with(
+ &workspace_window,
+ cx_a,
+ DebugTaskDefinition {
+ adapter: "fake-adapter".into(),
+ label: "test".into(),
+ config: json!({
+ "request": "launch"
+ }),
+ tcp_connection: Some(TcpArgumentsTemplate {
+ port: None,
+ host: None,
+ timeout: None,
+ }),
+ },
+ move |client| {
+ let count = count.clone();
+ client.on_request_ext::(move |_seq, _request| {
+ if count.fetch_add(1, std::sync::atomic::Ordering::SeqCst) < 5 {
+ return RequestHandling::Exit;
+ }
+ RequestHandling::Respond(Ok(Capabilities::default()))
+ });
+ },
+ )
+ .unwrap();
+ cx_a.run_until_parked();
+
+ let client = session.update(cx_a, |session, _| session.adapter_client().unwrap());
+ client
+ .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
+ reason: dap::StoppedEventReason::Pause,
+ description: None,
+ thread_id: Some(1),
+ preserve_focus_hint: None,
+ text: None,
+ all_threads_stopped: None,
+ hit_breakpoint_ids: None,
+ }))
+ .await;
+
+ cx_a.run_until_parked();
+
+ let active_session = debug_panel
+ .update(cx_a, |this, _| this.active_session())
+ .unwrap();
+
+ let running_state = active_session.update(cx_a, |active_session, _| {
+ active_session.running_state().clone()
+ });
+
+ assert_eq!(
+ client.id(),
+ running_state.read_with(cx_a, |running_state, _| running_state.session_id())
+ );
+ assert_eq!(
+ ThreadId(1),
+ running_state.read_with(cx_a, |running_state, _| running_state
+ .selected_thread_id()
+ .unwrap())
+ );
+
+ let shutdown_session = workspace.update(cx_a, |workspace, cx| {
+ workspace.project().update(cx, |project, cx| {
+ project.dap_store().update(cx, |dap_store, cx| {
+ dap_store.shutdown_session(session.read(cx).session_id(), cx)
+ })
+ })
+ });
+
+ client_ssh.update(cx_a, |a, _| {
+ a.shutdown_processes(Some(proto::ShutdownRemoteServer {}), executor)
+ });
+
+ shutdown_session.await.unwrap();
+}
diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs
index d9f26b3b34..bd36b07387 100644
--- a/crates/dap/src/adapters.rs
+++ b/crates/dap/src/adapters.rs
@@ -442,10 +442,18 @@ impl DebugAdapter for FakeAdapter {
_: Option>,
_: &mut AsyncApp,
) -> Result {
+ let connection = task_definition
+ .tcp_connection
+ .as_ref()
+ .map(|connection| TcpArguments {
+ host: connection.host(),
+ port: connection.port.unwrap_or(17),
+ timeout: connection.timeout,
+ });
Ok(DebugAdapterBinary {
command: Some("command".into()),
arguments: vec![],
- connection: None,
+ connection,
envs: HashMap::default(),
cwd: None,
request_args: StartDebuggingRequestArguments {
diff --git a/crates/dap/src/client.rs b/crates/dap/src/client.rs
index ff082e3b76..86a15b2d8a 100644
--- a/crates/dap/src/client.rs
+++ b/crates/dap/src/client.rs
@@ -2,7 +2,7 @@ use crate::{
adapters::DebugAdapterBinary,
transport::{IoKind, LogKind, TransportDelegate},
};
-use anyhow::{Context as _, Result};
+use anyhow::Result;
use dap_types::{
messages::{Message, Response},
requests::Request,
@@ -110,9 +110,7 @@ impl DebugAdapterClient {
self.transport_delegate
.pending_requests
.lock()
- .as_mut()
- .context("client is closed")?
- .insert(sequence_id, callback_tx);
+ .insert(sequence_id, callback_tx)?;
log::debug!(
"Client {} send `{}` request with sequence_id: {}",
@@ -170,6 +168,7 @@ impl DebugAdapterClient {
pub fn kill(&self) {
log::debug!("Killing DAP process");
self.transport_delegate.transport.lock().kill();
+ self.transport_delegate.pending_requests.lock().shutdown();
}
pub fn has_adapter_logs(&self) -> bool {
@@ -184,11 +183,34 @@ impl DebugAdapterClient {
}
#[cfg(any(test, feature = "test-support"))]
- pub fn on_request(&self, handler: F)
+ pub fn on_request(&self, mut handler: F)
where
F: 'static
+ Send
+ FnMut(u64, R::Arguments) -> Result,
+ {
+ use crate::transport::RequestHandling;
+
+ self.transport_delegate
+ .transport
+ .lock()
+ .as_fake()
+ .on_request::(move |seq, request| {
+ RequestHandling::Respond(handler(seq, request))
+ });
+ }
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn on_request_ext(&self, handler: F)
+ where
+ F: 'static
+ + Send
+ + FnMut(
+ u64,
+ R::Arguments,
+ ) -> crate::transport::RequestHandling<
+ Result,
+ >,
{
self.transport_delegate
.transport
diff --git a/crates/dap/src/transport.rs b/crates/dap/src/transport.rs
index 14370f66e4..6dadf1cf35 100644
--- a/crates/dap/src/transport.rs
+++ b/crates/dap/src/transport.rs
@@ -49,6 +49,12 @@ pub enum IoKind {
StdErr,
}
+#[cfg(any(test, feature = "test-support"))]
+pub enum RequestHandling {
+ Respond(T),
+ Exit,
+}
+
type LogHandlers = Arc>>;
pub trait Transport: Send + Sync {
@@ -76,7 +82,11 @@ async fn start(
) -> Result> {
#[cfg(any(test, feature = "test-support"))]
if cfg!(any(test, feature = "test-support")) {
- return Ok(Box::new(FakeTransport::start(cx).await?));
+ if let Some(connection) = binary.connection.clone() {
+ return Ok(Box::new(FakeTransport::start_tcp(connection, cx).await?));
+ } else {
+ return Ok(Box::new(FakeTransport::start_stdio(cx).await?));
+ }
}
if binary.connection.is_some() {
@@ -90,11 +100,57 @@ async fn start(
}
}
+pub(crate) struct PendingRequests {
+ inner: Option>>>,
+}
+
+impl PendingRequests {
+ fn new() -> Self {
+ Self {
+ inner: Some(HashMap::default()),
+ }
+ }
+
+ fn flush(&mut self, e: anyhow::Error) {
+ let Some(inner) = self.inner.as_mut() else {
+ return;
+ };
+ for (_, sender) in inner.drain() {
+ sender.send(Err(e.cloned())).ok();
+ }
+ }
+
+ pub(crate) fn insert(
+ &mut self,
+ sequence_id: u64,
+ callback_tx: oneshot::Sender>,
+ ) -> anyhow::Result<()> {
+ let Some(inner) = self.inner.as_mut() else {
+ bail!("client is closed")
+ };
+ inner.insert(sequence_id, callback_tx);
+ Ok(())
+ }
+
+ pub(crate) fn remove(
+ &mut self,
+ sequence_id: u64,
+ ) -> anyhow::Result