agent: Add newtype for profile IDs (#27939)

Marshall Bowers created

This PR adds an `AgentProfileId` newtype for profile IDs that we can use
instead of `Arc<str>` everywhere.

Release Notes:

- N/A

Change summary

crates/agent/src/assistant.rs                                     |  6 
crates/agent/src/assistant_configuration/manage_profiles_modal.rs | 25 
crates/agent/src/assistant_configuration/tool_picker.rs           |  6 
crates/agent/src/profile_selector.rs                              |  6 
crates/agent/src/thread_store.rs                                  |  4 
crates/assistant_settings/src/agent_profile.rs                    | 23 
crates/assistant_settings/src/assistant_settings.rs               | 16 
7 files changed, 59 insertions(+), 27 deletions(-)

Detailed changes

crates/agent/src/assistant.rs 🔗

@@ -23,7 +23,7 @@ mod ui;
 
 use std::sync::Arc;
 
-use assistant_settings::AssistantSettings;
+use assistant_settings::{AgentProfileId, AssistantSettings};
 use client::Client;
 use command_palette_hooks::CommandPaletteFilter;
 use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt};
@@ -82,11 +82,11 @@ pub struct NewThread {
 #[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
 pub struct ManageProfiles {
     #[serde(default)]
-    pub customize_tools: Option<Arc<str>>,
+    pub customize_tools: Option<AgentProfileId>,
 }
 
 impl ManageProfiles {
-    pub fn customize_tools(profile_id: Arc<str>) -> Self {
+    pub fn customize_tools(profile_id: AgentProfileId) -> Self {
         Self {
             customize_tools: Some(profile_id),
         }

crates/agent/src/assistant_configuration/manage_profiles_modal.rs 🔗

@@ -2,7 +2,7 @@ mod profile_modal_header;
 
 use std::sync::Arc;
 
-use assistant_settings::{AgentProfile, AssistantSettings};
+use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings};
 use assistant_tool::ToolWorkingSet;
 use convert_case::{Case, Casing as _};
 use editor::Editor;
@@ -27,7 +27,7 @@ enum Mode {
     NewProfile(NewProfileMode),
     ViewProfile(ViewProfileMode),
     ConfigureTools {
-        profile_id: Arc<str>,
+        profile_id: AgentProfileId,
         tool_picker: Entity<ToolPicker>,
         _subscription: Subscription,
     },
@@ -58,7 +58,7 @@ impl Mode {
 
 #[derive(Clone)]
 struct ProfileEntry {
-    pub id: Arc<str>,
+    pub id: AgentProfileId,
     pub name: SharedString,
     pub navigation: NavigableEntry,
 }
@@ -71,7 +71,7 @@ pub struct ChooseProfileMode {
 
 #[derive(Clone)]
 pub struct ViewProfileMode {
-    profile_id: Arc<str>,
+    profile_id: AgentProfileId,
     fork_profile: NavigableEntry,
     configure_tools: NavigableEntry,
 }
@@ -79,7 +79,7 @@ pub struct ViewProfileMode {
 #[derive(Clone)]
 pub struct NewProfileMode {
     name_editor: Entity<Editor>,
-    base_profile_id: Option<Arc<str>>,
+    base_profile_id: Option<AgentProfileId>,
 }
 
 pub struct ManageProfilesModal {
@@ -140,7 +140,7 @@ impl ManageProfilesModal {
 
     fn new_profile(
         &mut self,
-        base_profile_id: Option<Arc<str>>,
+        base_profile_id: Option<AgentProfileId>,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -158,7 +158,7 @@ impl ManageProfilesModal {
 
     pub fn view_profile(
         &mut self,
-        profile_id: Arc<str>,
+        profile_id: AgentProfileId,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -172,7 +172,7 @@ impl ManageProfilesModal {
 
     fn configure_tools(
         &mut self,
-        profile_id: Arc<str>,
+        profile_id: AgentProfileId,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) {
@@ -219,7 +219,7 @@ impl ManageProfilesModal {
                     .and_then(|profile_id| settings.profiles.get(profile_id).cloned());
 
                 let name = mode.name_editor.read(cx).text(cx);
-                let profile_id: Arc<str> = name.to_case(Case::Kebab).into();
+                let profile_id = AgentProfileId(name.to_case(Case::Kebab).into());
 
                 let profile = AgentProfile {
                     name: name.into(),
@@ -261,7 +261,12 @@ impl ManageProfilesModal {
         }
     }
 
-    fn create_profile(&self, profile_id: Arc<str>, profile: AgentProfile, cx: &mut Context<Self>) {
+    fn create_profile(
+        &self,
+        profile_id: AgentProfileId,
+        profile: AgentProfile,
+        cx: &mut Context<Self>,
+    ) {
         update_settings_file::<AssistantSettings>(self.fs.clone(), cx, {
             move |settings, _cx| {
                 settings.create_profile(profile_id, profile).log_err();

crates/agent/src/assistant_configuration/tool_picker.rs 🔗

@@ -1,7 +1,7 @@
 use std::sync::Arc;
 
 use assistant_settings::{
-    AgentProfile, AgentProfileContent, AssistantSettings, AssistantSettingsContent,
+    AgentProfile, AgentProfileContent, AgentProfileId, AssistantSettings, AssistantSettingsContent,
     ContextServerPresetContent, VersionedAssistantSettingsContent,
 };
 use assistant_tool::{ToolSource, ToolWorkingSet};
@@ -51,7 +51,7 @@ pub struct ToolPickerDelegate {
     thread_store: WeakEntity<ThreadStore>,
     fs: Arc<dyn Fs>,
     tools: Vec<ToolEntry>,
-    profile_id: Arc<str>,
+    profile_id: AgentProfileId,
     profile: AgentProfile,
     matches: Vec<StringMatch>,
     selected_index: usize,
@@ -62,7 +62,7 @@ impl ToolPickerDelegate {
         fs: Arc<dyn Fs>,
         tool_set: Arc<ToolWorkingSet>,
         thread_store: WeakEntity<ThreadStore>,
-        profile_id: Arc<str>,
+        profile_id: AgentProfileId,
         profile: AgentProfile,
         cx: &mut Context<ToolPicker>,
     ) -> Self {

crates/agent/src/profile_selector.rs 🔗

@@ -1,6 +1,6 @@
 use std::sync::Arc;
 
-use assistant_settings::{AgentProfile, AssistantSettings};
+use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings};
 use fs::Fs;
 use gpui::{Action, Entity, FocusHandle, Subscription, WeakEntity, prelude::*};
 use indexmap::IndexMap;
@@ -15,7 +15,7 @@ use util::ResultExt as _;
 use crate::{ManageProfiles, ThreadStore, ToggleProfileSelector};
 
 pub struct ProfileSelector {
-    profiles: IndexMap<Arc<str>, AgentProfile>,
+    profiles: IndexMap<AgentProfileId, AgentProfile>,
     fs: Arc<dyn Fs>,
     thread_store: WeakEntity<ThreadStore>,
     focus_handle: FocusHandle,
@@ -133,7 +133,7 @@ impl Render for ProfileSelector {
             .active_model()
             .map_or(false, |model| model.supports_tools());
 
-        let icon = match profile_id.as_ref() {
+        let icon = match profile_id.as_str() {
             "write" => IconName::Pencil,
             "ask" => IconName::MessageBubbles,
             _ => IconName::UserRoundPen,

crates/agent/src/thread_store.rs 🔗

@@ -3,7 +3,7 @@ use std::path::PathBuf;
 use std::sync::Arc;
 
 use anyhow::{Result, anyhow};
-use assistant_settings::{AgentProfile, AssistantSettings};
+use assistant_settings::{AgentProfile, AgentProfileId, AssistantSettings};
 use assistant_tool::{ToolId, ToolSource, ToolWorkingSet};
 use chrono::{DateTime, Utc};
 use collections::HashMap;
@@ -202,7 +202,7 @@ impl ThreadStore {
         self.load_profile_by_id(&assistant_settings.default_profile, cx);
     }
 
-    pub fn load_profile_by_id(&self, profile_id: &Arc<str>, cx: &Context<Self>) {
+    pub fn load_profile_by_id(&self, profile_id: &AgentProfileId, cx: &Context<Self>) {
         let assistant_settings = AssistantSettings::get_global(cx);
 
         if let Some(profile) = assistant_settings.profiles.get(profile_id) {

crates/assistant_settings/src/agent_profile.rs 🔗

@@ -2,6 +2,29 @@ use std::sync::Arc;
 
 use gpui::SharedString;
 use indexmap::IndexMap;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
+pub struct AgentProfileId(pub Arc<str>);
+
+impl AgentProfileId {
+    pub fn as_str(&self) -> &str {
+        &self.0
+    }
+}
+
+impl std::fmt::Display for AgentProfileId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl Default for AgentProfileId {
+    fn default() -> Self {
+        Self("write".into())
+    }
+}
 
 /// A profile for the Zed Agent that controls its behavior.
 #[derive(Debug, Clone)]

crates/assistant_settings/src/assistant_settings.rs 🔗

@@ -81,8 +81,8 @@ pub struct AssistantSettings {
     pub inline_alternatives: Vec<LanguageModelSelection>,
     pub using_outdated_settings_version: bool,
     pub enable_experimental_live_diffs: bool,
-    pub default_profile: Arc<str>,
-    pub profiles: IndexMap<Arc<str>, AgentProfile>,
+    pub default_profile: AgentProfileId,
+    pub profiles: IndexMap<AgentProfileId, AgentProfile>,
     pub always_allow_tool_actions: bool,
     pub notify_when_agent_waiting: NotifyWhenAgentWaiting,
 }
@@ -325,7 +325,7 @@ impl AssistantSettingsContent {
         }
     }
 
-    pub fn set_profile(&mut self, profile_id: Arc<str>) {
+    pub fn set_profile(&mut self, profile_id: AgentProfileId) {
         let AssistantSettingsContent::Versioned(VersionedAssistantSettingsContent::V2(settings)) =
             self
         else {
@@ -335,7 +335,11 @@ impl AssistantSettingsContent {
         settings.default_profile = Some(profile_id);
     }
 
-    pub fn create_profile(&mut self, profile_id: Arc<str>, profile: AgentProfile) -> Result<()> {
+    pub fn create_profile(
+        &mut self,
+        profile_id: AgentProfileId,
+        profile: AgentProfile,
+    ) -> Result<()> {
         let AssistantSettingsContent::Versioned(VersionedAssistantSettingsContent::V2(settings)) =
             self
         else {
@@ -436,9 +440,9 @@ pub struct AssistantSettingsContentV2 {
     /// The default profile to use in the Agent.
     ///
     /// Default: write
-    default_profile: Option<Arc<str>>,
+    default_profile: Option<AgentProfileId>,
     /// The available agent profiles.
-    pub profiles: Option<IndexMap<Arc<str>, AgentProfileContent>>,
+    pub profiles: Option<IndexMap<AgentProfileId, AgentProfileContent>>,
     /// Whenever a tool action would normally wait for your confirmation
     /// that you allow it, always choose to allow it.
     ///