agent: Highlight latest models available through the Zed provider (#48614)

Marshall Bowers created

This PR updates the model selector to highlight the latest models that
are available through the Zed provider:

<img width="388" height="477" alt="Screenshot 2026-02-06 at 1 46 41 PM"
src="https://github.com/user-attachments/assets/70760399-ecf6-46e3-80a7-cb998216c192"
/>

Closes CLO-205.

Release Notes:

- Added a "Latest" indicator to highlight the latest models available
through the Zed provider.

Change summary

crates/acp_thread/src/connection.rs                 |  3 +++
crates/agent/src/agent.rs                           |  2 ++
crates/agent_ui/src/acp/model_selector.rs           |  6 ++++++
crates/agent_ui/src/language_model_selector.rs      |  1 +
crates/agent_ui/src/ui/model_selector_components.rs | 12 ++++++++++--
crates/cloud_llm_client/src/cloud_llm_client.rs     |  2 ++
crates/language_model/src/language_model.rs         |  5 +++++
crates/language_models/src/provider/cloud.rs        |  4 ++++
8 files changed, 33 insertions(+), 2 deletions(-)

Detailed changes

crates/acp_thread/src/connection.rs 🔗

@@ -382,6 +382,7 @@ pub struct AgentModelInfo {
     pub name: SharedString,
     pub description: Option<SharedString>,
     pub icon: Option<AgentModelIcon>,
+    pub is_latest: bool,
 }
 
 impl From<acp::ModelInfo> for AgentModelInfo {
@@ -391,6 +392,7 @@ impl From<acp::ModelInfo> for AgentModelInfo {
             name: info.name.into(),
             description: info.description.map(|desc| desc.into()),
             icon: None,
+            is_latest: false,
         }
     }
 }
@@ -744,6 +746,7 @@ mod test_support {
                     name: "Visual Test Model".into(),
                     description: Some("A stub model for visual testing".into()),
                     icon: Some(AgentModelIcon::Named(ui::IconName::ZedAssistant)),
+                    is_latest: false,
                 })),
             }
         }

crates/agent/src/agent.rs 🔗

@@ -165,6 +165,7 @@ impl LanguageModels {
                 IconOrSvg::Svg(path) => acp_thread::AgentModelIcon::Path(path),
                 IconOrSvg::Icon(name) => acp_thread::AgentModelIcon::Named(name),
             }),
+            is_latest: model.is_latest(),
         }
     }
 
@@ -1768,6 +1769,7 @@ mod internal_tests {
                     icon: Some(acp_thread::AgentModelIcon::Named(
                         ui::IconName::ZedAssistant
                     )),
+                    is_latest: false,
                 }]
             )])
         );

crates/agent_ui/src/acp/model_selector.rs 🔗

@@ -367,6 +367,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
                                 })
                                 .is_selected(is_selected)
                                 .is_focused(selected)
+                                .is_latest(model_info.is_latest)
                                 .is_favorite(is_favorite)
                                 .on_toggle_favorite(handle_action_click),
                         )
@@ -552,6 +553,7 @@ mod tests {
                             name: model.to_string().into(),
                             description: None,
                             icon: None,
+                            is_latest: false,
                         })
                         .collect::<Vec<_>>(),
                 )
@@ -774,12 +776,14 @@ mod tests {
                 name: "Claude".into(),
                 description: None,
                 icon: None,
+                is_latest: false,
             },
             acp_thread::AgentModelInfo {
                 id: acp::ModelId::new("zed/gemini".to_string()),
                 name: "Gemini".into(),
                 description: None,
                 icon: None,
+                is_latest: false,
             },
         ]);
         let favorites = create_favorites(vec!["zed/gemini"]);
@@ -820,12 +824,14 @@ mod tests {
                 name: "Favorite".into(),
                 description: None,
                 icon: None,
+                is_latest: false,
             },
             acp_thread::AgentModelInfo {
                 id: acp::ModelId::new("regular-model".to_string()),
                 name: "Regular".into(),
                 description: None,
                 icon: None,
+                is_latest: false,
             },
         ]);
         let favorites = create_favorites(vec!["favorite-model"]);

crates/agent_ui/src/language_model_selector.rs 🔗

@@ -589,6 +589,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
                         })
                         .is_selected(is_selected)
                         .is_focused(selected)
+                        .is_latest(model_info.model.is_latest())
                         .is_favorite(is_favorite)
                         .on_toggle_favorite(handle_action_click)
                         .into_any_element(),

crates/agent_ui/src/ui/model_selector_components.rs 🔗

@@ -1,5 +1,5 @@
 use gpui::{Action, ClickEvent, FocusHandle, prelude::*};
-use ui::{ElevationIndex, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
+use ui::{Chip, ElevationIndex, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
 use zed_actions::agent::ToggleModelSelector;
 
 use crate::CycleFavoriteModels;
@@ -50,6 +50,7 @@ pub struct ModelSelectorListItem {
     icon: Option<ModelIcon>,
     is_selected: bool,
     is_focused: bool,
+    is_latest: bool,
     is_favorite: bool,
     on_toggle_favorite: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
 }
@@ -62,6 +63,7 @@ impl ModelSelectorListItem {
             icon: None,
             is_selected: false,
             is_focused: false,
+            is_latest: false,
             is_favorite: false,
             on_toggle_favorite: None,
         }
@@ -87,6 +89,11 @@ impl ModelSelectorListItem {
         self
     }
 
+    pub fn is_latest(mut self, is_latest: bool) -> Self {
+        self.is_latest = is_latest;
+        self
+    }
+
     pub fn is_favorite(mut self, is_favorite: bool) -> Self {
         self.is_favorite = is_favorite;
         self
@@ -129,7 +136,8 @@ impl RenderOnce for ModelSelectorListItem {
                             .size(IconSize::Small),
                         )
                     })
-                    .child(Label::new(self.title).truncate()),
+                    .child(Label::new(self.title).truncate())
+                    .when(self.is_latest, |parent| parent.child(Chip::new("Latest"))),
             )
             .end_slot(div().pr_2().when(self.is_selected, |this| {
                 this.child(Icon::new(IconName::Check).color(Color::Accent))

crates/cloud_llm_client/src/cloud_llm_client.rs 🔗

@@ -291,6 +291,8 @@ pub struct LanguageModel {
     pub provider: LanguageModelProvider,
     pub id: LanguageModelId,
     pub display_name: String,
+    #[serde(default)]
+    pub is_latest: bool,
     pub max_token_count: usize,
     pub max_token_count_in_max_mode: Option<usize>,
     pub max_output_tokens: usize,

crates/language_model/src/language_model.rs 🔗

@@ -592,6 +592,11 @@ pub trait LanguageModel: Send + Sync {
         self.provider_name()
     }
 
+    /// Returns whether this model is the "latest", so we can highlight it in the UI.
+    fn is_latest(&self) -> bool {
+        false
+    }
+
     fn telemetry_id(&self) -> String;
 
     fn api_key(&self, _cx: &App) -> Option<String> {

crates/language_models/src/provider/cloud.rs 🔗

@@ -566,6 +566,10 @@ impl LanguageModel for CloudLanguageModel {
         }
     }
 
+    fn is_latest(&self) -> bool {
+        self.model.is_latest
+    }
+
     fn supports_tools(&self) -> bool {
         self.model.supports_tools
     }