assistant2: Add initial concept of profiles (#27123)

Marshall Bowers created

This PR adds the initial concept of agent profiles to Assistant 2.

Right now these are just collections of tools that can quickly be
enabled together:


https://github.com/user-attachments/assets/7c7f9cc8-a5e5-492f-96f7-79697bbf3d72

There are currently two profiles:

- `Read-only` - Consists only of tools that do not perform writes.
- `Code Writer` - Consists of all tools for writing code, with the
exception of the `lua-interpreter`.

Release Notes:

- N/A

Change summary

crates/assistant2/src/agent_profile.rs | 59 ++++++++++++++++++++++++++++
crates/assistant2/src/assistant.rs     |  1 
crates/assistant2/src/tool_selector.rs | 30 +++++++++++++
3 files changed, 89 insertions(+), 1 deletion(-)

Detailed changes

crates/assistant2/src/agent_profile.rs 🔗

@@ -0,0 +1,59 @@
+use std::sync::Arc;
+
+use collections::HashMap;
+use gpui::SharedString;
+
+/// A profile for the Zed Agent that controls its behavior.
+#[derive(Debug, Clone)]
+pub struct AgentProfile {
+    /// The name of the profile.
+    pub name: SharedString,
+    pub tools: HashMap<Arc<str>, bool>,
+    #[allow(dead_code)]
+    pub context_servers: HashMap<Arc<str>, ContextServerPreset>,
+}
+
+#[derive(Debug, Clone)]
+pub struct ContextServerPreset {
+    #[allow(dead_code)]
+    pub tools: HashMap<Arc<str>, bool>,
+}
+
+impl AgentProfile {
+    pub fn read_only() -> Self {
+        Self {
+            name: "Read-only".into(),
+            tools: HashMap::from_iter([
+                ("diagnostics".into(), true),
+                ("fetch".into(), true),
+                ("list-directory".into(), true),
+                ("now".into(), true),
+                ("path-search".into(), true),
+                ("read-file".into(), true),
+                ("regex-search".into(), true),
+                ("thinking".into(), true),
+            ]),
+            context_servers: HashMap::default(),
+        }
+    }
+
+    pub fn code_writer() -> Self {
+        Self {
+            name: "Code Writer".into(),
+            tools: HashMap::from_iter([
+                ("bash".into(), true),
+                ("delete-path".into(), true),
+                ("diagnostics".into(), true),
+                ("edit-files".into(), true),
+                ("fetch".into(), true),
+                ("list-directory".into(), true),
+                ("now".into(), true),
+                ("path-search".into(), true),
+                ("read-file".into(), true),
+                ("regex-search".into(), true),
+                ("thinking".into(), true),
+            ]),
+            context_servers: HashMap::default(),
+        }
+    }
+}

crates/assistant2/src/tool_selector.rs 🔗

@@ -5,13 +5,19 @@ use gpui::Entity;
 use scripting_tool::ScriptingTool;
 use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip};
 
+use crate::agent_profile::AgentProfile;
+
 pub struct ToolSelector {
+    profiles: Vec<AgentProfile>,
     tools: Arc<ToolWorkingSet>,
 }
 
 impl ToolSelector {
     pub fn new(tools: Arc<ToolWorkingSet>, _cx: &mut Context<Self>) -> Self {
-        Self { tools }
+        Self {
+            profiles: vec![AgentProfile::read_only(), AgentProfile::code_writer()],
+            tools,
+        }
     }
 
     fn build_context_menu(
@@ -19,9 +25,31 @@ impl ToolSelector {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Entity<ContextMenu> {
+        let profiles = self.profiles.clone();
         let tool_set = self.tools.clone();
         ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
             let icon_position = IconPosition::End;
+
+            menu = menu.header("Profiles");
+            for profile in profiles.clone() {
+                menu = menu.toggleable_entry(profile.name.clone(), false, icon_position, None, {
+                    let tools = tool_set.clone();
+                    move |_window, cx| {
+                        tools.disable_source(ToolSource::Native, cx);
+                        tools.enable(
+                            ToolSource::Native,
+                            &profile
+                                .tools
+                                .iter()
+                                .filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))
+                                .collect::<Vec<_>>(),
+                        );
+                    }
+                });
+            }
+
+            menu = menu.separator();
+
             let tools_by_source = tool_set.tools_by_source(cx);
 
             let all_tools_enabled = tool_set.are_all_tools_enabled();