From 86ce4ef3ab0930cf04fda15ae5bc8dd2d27a5fb0 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Thu, 16 Oct 2025 12:37:37 +0200 Subject: [PATCH] acp: Add nicer WSL warning for Codex (#40354) Moves the Codex warning into the thread so that we can render it nicer, as well as provide an option to open the folder in WSL. Release Notes: - N/A --------- Co-authored-by: Bennet Bo Fenner --- crates/agent_ui/src/acp/thread_view.rs | 61 ++++++++++++++++++++++++++ crates/agent_ui/src/agent_panel.rs | 29 ++++-------- crates/agent_ui/src/agent_ui.rs | 19 ++++---- 3 files changed, 79 insertions(+), 30 deletions(-) diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 0e51c061f17c33c48f6ff5e4474a1ca9b5ca8e96..b43d7dd277e0a2ca4fbc8ecae2c4ba8a1ae90999 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -292,6 +292,8 @@ pub struct AcpThreadView { resume_thread_metadata: Option, _cancel_task: Option>, _subscriptions: [Subscription; 5], + #[cfg(target_os = "windows")] + show_codex_windows_warning: bool, } enum ThreadState { @@ -397,6 +399,10 @@ impl AcpThreadView { ), ]; + #[cfg(target_os = "windows")] + let show_codex_windows_warning = crate::ExternalAgent::parse_built_in(agent.as_ref()) + == Some(crate::ExternalAgent::Codex); + Self { agent: agent.clone(), workspace: workspace.clone(), @@ -439,6 +445,8 @@ impl AcpThreadView { focus_handle: cx.focus_handle(), new_server_version_available: None, resume_thread_metadata: resume_thread, + #[cfg(target_os = "windows")] + show_codex_windows_warning, } } @@ -5028,6 +5036,49 @@ impl AcpThreadView { ) } + #[cfg(target_os = "windows")] + fn render_codex_windows_warning(&self, cx: &mut Context) -> Option { + if self.show_codex_windows_warning { + Some( + Callout::new() + .icon(IconName::Warning) + .severity(Severity::Warning) + .title("Codex on Windows") + .description( + "For best performance, run Codex in Windows Subsystem for Linux (WSL2)", + ) + .actions_slot( + Button::new("open-wsl-modal", "Open in WSL") + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .on_click(cx.listener({ + move |_, _, window, cx| { + window.dispatch_action( + zed_actions::wsl_actions::OpenWsl::default().boxed_clone(), + cx, + ); + cx.notify(); + } + })), + ) + .dismiss_action( + IconButton::new("dismiss", IconName::Close) + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .tooltip(Tooltip::text("Dismiss Warning")) + .on_click(cx.listener({ + move |this, _, _, cx| { + this.show_codex_windows_warning = false; + cx.notify(); + } + })), + ), + ) + } else { + None + } + } + fn render_thread_error(&self, window: &mut Window, cx: &mut Context) -> Option
{ let content = match self.thread_error.as_ref()? { ThreadError::Other(error) => self.render_any_thread_error(error.clone(), cx), @@ -5515,6 +5566,16 @@ impl Render for AcpThreadView { _ => this, }) .children(self.render_thread_retry_status_callout(window, cx)) + .children({ + #[cfg(target_os = "windows")] + { + self.render_codex_windows_warning(cx) + } + #[cfg(not(target_os = "windows"))] + { + Vec::::new() + } + }) .children(self.render_thread_error(window, cx)) .when_some( self.new_server_version_available.as_ref().filter(|_| { diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index d71279528fa6b1be36ed3b4b6b1f46b4f787751b..bcba02e3cf2056a27b58b53ab6947b8775e4bfda 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -222,12 +222,11 @@ enum WhichFontSize { #[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] pub enum AgentType { #[default] - Zed, + NativeAgent, TextThread, Gemini, ClaudeCode, Codex, - NativeAgent, Custom { name: SharedString, command: AgentServerCommand, @@ -237,8 +236,7 @@ pub enum AgentType { impl AgentType { fn label(&self) -> SharedString { match self { - Self::Zed | Self::TextThread => "Zed Agent".into(), - Self::NativeAgent => "Agent 2".into(), + Self::NativeAgent | Self::TextThread => "Zed Agent".into(), Self::Gemini => "Gemini CLI".into(), Self::ClaudeCode => "Claude Code".into(), Self::Codex => "Codex".into(), @@ -248,7 +246,7 @@ impl AgentType { fn icon(&self) -> Option { match self { - Self::Zed | Self::NativeAgent | Self::TextThread => None, + Self::NativeAgent | Self::TextThread => None, Self::Gemini => Some(IconName::AiGemini), Self::ClaudeCode => Some(IconName::AiClaude), Self::Codex => Some(IconName::AiOpenAi), @@ -813,7 +811,7 @@ impl AgentPanel { const LAST_USED_EXTERNAL_AGENT_KEY: &str = "agent_panel__last_used_external_agent"; - #[derive(Default, Serialize, Deserialize)] + #[derive(Serialize, Deserialize)] struct LastUsedExternalAgent { agent: crate::ExternalAgent, } @@ -854,18 +852,18 @@ impl AgentPanel { .and_then(|value| { serde_json::from_str::(&value).log_err() }) - .unwrap_or_default() - .agent + .map(|agent| agent.agent) + .unwrap_or(ExternalAgent::NativeAgent) } } }; + let server = ext_agent.server(fs, history); + if !loading { - telemetry::event!("Agent Thread Started", agent = ext_agent.name()); + telemetry::event!("Agent Thread Started", agent = server.telemetry_id()); } - let server = ext_agent.server(fs, history); - this.update_in(cx, |this, window, cx| { let selected_agent = ext_agent.into(); if this.selected_agent != selected_agent { @@ -1345,15 +1343,6 @@ impl AgentPanel { cx: &mut Context, ) { match agent { - AgentType::Zed => { - window.dispatch_action( - NewThread { - from_thread_id: None, - } - .boxed_clone(), - cx, - ); - } AgentType::TextThread => { window.dispatch_action(NewTextThread.boxed_clone(), cx); } diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 2c439a725456976f090ddc4cb754664c4953d626..26d37378776b52be5fb88f3dad820986fb812d07 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -161,10 +161,9 @@ pub struct NewNativeAgentThreadFromSummary { } // TODO unify this with AgentType -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] -enum ExternalAgent { - #[default] +pub enum ExternalAgent { Gemini, ClaudeCode, Codex, @@ -184,13 +183,13 @@ fn placeholder_command() -> AgentServerCommand { } impl ExternalAgent { - fn name(&self) -> &'static str { - match self { - Self::NativeAgent => "zed", - Self::Gemini => "gemini-cli", - Self::ClaudeCode => "claude-code", - Self::Codex => "codex", - Self::Custom { .. } => "custom", + pub fn parse_built_in(server: &dyn agent_servers::AgentServer) -> Option { + match server.telemetry_id() { + "gemini-cli" => Some(Self::Gemini), + "claude-code" => Some(Self::ClaudeCode), + "codex" => Some(Self::Codex), + "zed" => Some(Self::NativeAgent), + _ => None, } }