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 <bennetbo@gmx.de>
This commit is contained in:
Ben Brandt
2025-10-16 12:37:37 +02:00
committed by GitHub
co-authored by Bennet Bo Fenner
parent 9948778e96
commit 86ce4ef3ab
3 changed files with 80 additions and 31 deletions
+61
View File
@@ -292,6 +292,8 @@ pub struct AcpThreadView {
resume_thread_metadata: Option<DbThreadMetadata>,
_cancel_task: Option<Task<()>>,
_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<Self>) -> Option<Callout> {
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<Self>) -> Option<Div> {
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::<Empty>::new()
}
})
.children(self.render_thread_error(window, cx))
.when_some(
self.new_server_version_available.as_ref().filter(|_| {
+10 -21
View File
@@ -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<IconName> {
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::<LastUsedExternalAgent>(&value).log_err()
})
.unwrap_or_default()
.agent
.map(|agent| agent.agent)
.unwrap_or(ExternalAgent::NativeAgent)
}
}
};
if !loading {
telemetry::event!("Agent Thread Started", agent = ext_agent.name());
}
let server = ext_agent.server(fs, history);
if !loading {
telemetry::event!("Agent Thread Started", agent = server.telemetry_id());
}
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<Self>,
) {
match agent {
AgentType::Zed => {
window.dispatch_action(
NewThread {
from_thread_id: None,
}
.boxed_clone(),
cx,
);
}
AgentType::TextThread => {
window.dispatch_action(NewTextThread.boxed_clone(), cx);
}
+9 -10
View File
@@ -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<Self> {
match server.telemetry_id() {
"gemini-cli" => Some(Self::Gemini),
"claude-code" => Some(Self::ClaudeCode),
"codex" => Some(Self::Codex),
"zed" => Some(Self::NativeAgent),
_ => None,
}
}