acp: Add agent server extension deprecation banner (#47817)

This PR adds a banner to communicate the deprecation of agent server
extensions in favor of the ACP registry:

<img width="600" height="1986" alt="Screenshot 2026-01-27 at 8  37@2x"
src="https://github.com/user-attachments/assets/8c1b658f-d170-4009-a93b-336b785f4be9"
/>

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2026-01-28 06:28:43 -03:00 committed by GitHub
parent ade8749537
commit 0e1802a596
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 89 additions and 7 deletions

View file

@ -1,5 +1,6 @@
use std::ops::Range;
use client::zed_urls;
use collections::HashMap;
use editor::{Editor, EditorElement, EditorStyle};
use fs::Fs;
@ -525,8 +526,6 @@ impl AgentRegistryPage {
impl Render for AgentRegistryPage {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let learn_more_url = "https://zed.dev/blog/acp-registry";
v_flex()
.size_full()
.bg(cx.theme().colors().editor_background)
@ -548,7 +547,8 @@ impl Render for AgentRegistryPage {
.child(Headline::new("ACP Registry").size(HeadlineSize::Large))
.child(Chip::new("Beta"))
.hoverable_tooltip({
let learn_more_url = learn_more_url.to_string();
let learn_more_url: SharedString =
zed_urls::acp_registry_blog(cx).into();
let tooltip_fn = Tooltip::element(move |_, _| {
v_flex()
.gap_1()
@ -571,7 +571,9 @@ impl Render for AgentRegistryPage {
.icon(IconName::ArrowUpRight)
.icon_color(Color::Muted)
.icon_size(IconSize::Small)
.on_click(move |_, _, cx| cx.open_url(learn_more_url)),
.on_click(move |_, _, cx| {
cx.open_url(&zed_urls::acp_registry_blog(cx))
}),
),
)
.child(

View file

@ -68,6 +68,11 @@ pub fn edit_prediction_docs(cx: &App) -> String {
)
}
/// Returns the URL to Zed's ACP registry blog post.
pub fn acp_registry_blog(cx: &App) -> String {
format!("{server_url}/blog/acp-registy", server_url = server_url(cx))
}
pub fn shared_agent_thread_url(session_id: &str) -> String {
format!("zed://agent/shared/{}", session_id)
}

View file

@ -7,7 +7,7 @@ use std::time::Duration;
use std::{ops::Range, sync::Arc};
use anyhow::Context as _;
use client::{ExtensionMetadata, ExtensionProvides};
use client::{ExtensionMetadata, ExtensionProvides, zed_urls};
use collections::{BTreeMap, BTreeSet};
use editor::{Editor, EditorElement, EditorStyle};
use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore};
@ -287,6 +287,19 @@ fn keywords_by_feature() -> &'static BTreeMap<Feature, Vec<&'static str>> {
})
}
fn acp_registry_upsell_keywords() -> &'static [&'static str] {
&[
"opencode",
"mistral",
"auggie",
"stakpak",
"codebuddy",
"autohand",
"factory droid",
"corust",
]
}
fn extension_button_id(extension_id: &Arc<str>, operation: ExtensionOperation) -> ElementId {
(SharedString::from(extension_id.clone()), operation as usize).into()
}
@ -312,6 +325,7 @@ pub struct ExtensionsPage {
_subscriptions: [gpui::Subscription; 2],
extension_fetch_task: Option<Task<()>>,
upsells: BTreeSet<Feature>,
show_acp_registry_upsell: bool,
}
impl ExtensionsPage {
@ -373,6 +387,7 @@ impl ExtensionsPage {
_subscriptions: subscriptions,
query_editor,
upsells: BTreeSet::default(),
show_acp_registry_upsell: false,
};
this.fetch_extensions(
this.search_query(cx),
@ -1375,11 +1390,13 @@ impl ExtensionsPage {
fn refresh_feature_upsells(&mut self, cx: &mut Context<Self>) {
let Some(search) = self.search_query(cx) else {
self.upsells.clear();
self.show_acp_registry_upsell = false;
return;
};
if let Some(id) = search.strip_prefix("id:") {
self.upsells.clear();
self.show_acp_registry_upsell = false;
let upsell = match id.to_lowercase().as_str() {
"ruff" => Some(Feature::ExtensionRuff),
@ -1411,6 +1428,60 @@ impl ExtensionsPage {
self.upsells.remove(feature);
}
}
self.show_acp_registry_upsell = acp_registry_upsell_keywords()
.iter()
.any(|keyword| search_terms.iter().any(|term| keyword.contains(term)));
}
fn render_acp_registry_upsell(&self, cx: &mut Context<Self>) -> impl IntoElement {
let registry_url = zed_urls::acp_registry_blog(cx);
let view_registry = Button::new("view_registry", "View Registry")
.style(ButtonStyle::Tinted(ui::TintColor::Warning))
.on_click({
let registry_url = registry_url.clone();
move |_, window, cx| {
telemetry::event!(
"ACP Registry Opened from Extensions",
source = "ACP Registry Upsell",
url = registry_url,
);
window.dispatch_action(Box::new(zed_actions::AcpRegistry), cx)
}
});
let open_registry_button = Button::new("open_registry", "Learn More")
.icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small)
.icon_position(IconPosition::End)
.icon_color(Color::Muted)
.on_click({
move |_event, _window, cx| {
telemetry::event!(
"ACP Registry Viewed",
source = "ACP Registry Upsell",
url = registry_url,
);
cx.open_url(&registry_url)
}
});
div().pt_4().px_4().child(
Banner::new()
.severity(Severity::Warning)
.child(
Label::new(
"Agent Server extensions will be deprecated in favor of the ACP registry.",
)
.mt_0p5(),
)
.action_slot(
h_flex()
.gap_1()
.child(open_registry_button)
.child(view_registry),
),
)
}
fn render_feature_upsell_banner(
@ -1712,8 +1783,7 @@ impl Render for ExtensionsPage {
)
.children(ExtensionProvides::iter().filter_map(|provides| {
match provides {
ExtensionProvides::AgentServers
| ExtensionProvides::SlashCommands
ExtensionProvides::SlashCommands
| ExtensionProvides::IndexedDocsProviders => return None,
_ => {}
}
@ -1737,6 +1807,11 @@ impl Render for ExtensionsPage {
)
})),
)
.when(
self.provides_filter == Some(ExtensionProvides::AgentServers)
|| self.show_acp_registry_upsell,
|this| this.child(self.render_acp_registry_upsell(cx)),
)
.child(self.render_feature_upsells(cx))
.child(v_flex().px_4().size_full().overflow_y_hidden().map(|this| {
let mut count = self.filtered_remote_extension_indices.len();