acp: Add button to configure custom agent in the configuration view (#36923)

Bennet Bo Fenner created

Release Notes:

- N/A

Change summary

crates/agent_ui/src/agent_configuration.rs | 107 ++++++++++++++++++++++-
1 file changed, 102 insertions(+), 5 deletions(-)

Detailed changes

crates/agent_ui/src/agent_configuration.rs 🔗

@@ -5,18 +5,21 @@ mod tool_picker;
 
 use std::{sync::Arc, time::Duration};
 
-use agent_servers::{AgentServerCommand, AllAgentServersSettings, Gemini};
+use agent_servers::{AgentServerCommand, AgentServerSettings, AllAgentServersSettings, Gemini};
 use agent_settings::AgentSettings;
+use anyhow::Result;
 use assistant_tool::{ToolSource, ToolWorkingSet};
 use cloud_llm_client::Plan;
 use collections::HashMap;
 use context_server::ContextServerId;
+use editor::{Editor, SelectionEffects, scroll::Autoscroll};
 use extension::ExtensionManifest;
 use extension_host::ExtensionStore;
 use fs::Fs;
 use gpui::{
-    Action, Animation, AnimationExt as _, AnyView, App, Corner, Entity, EventEmitter, FocusHandle,
-    Focusable, Hsla, ScrollHandle, Subscription, Task, Transformation, WeakEntity, percentage,
+    Action, Animation, AnimationExt as _, AnyView, App, AsyncWindowContext, Corner, Entity,
+    EventEmitter, FocusHandle, Focusable, Hsla, ScrollHandle, Subscription, Task, Transformation,
+    WeakEntity, percentage,
 };
 use language::LanguageRegistry;
 use language_model::{
@@ -34,7 +37,7 @@ use ui::{
     Scrollbar, ScrollbarState, Switch, SwitchColor, SwitchField, Tooltip, prelude::*,
 };
 use util::ResultExt as _;
-use workspace::Workspace;
+use workspace::{Workspace, create_and_open_local_file};
 use zed_actions::ExtensionCategoryFilter;
 
 pub(crate) use configure_context_server_modal::ConfigureContextServerModal;
@@ -1058,7 +1061,36 @@ impl AgentConfiguration {
                     .child(
                         v_flex()
                             .gap_0p5()
-                            .child(Headline::new("External Agents"))
+                            .child(
+                                h_flex()
+                                    .w_full()
+                                    .gap_2()
+                                    .justify_between()
+                                    .child(Headline::new("External Agents"))
+                                    .child(
+                                        Button::new("add-agent", "Add Agent")
+                                            .icon_position(IconPosition::Start)
+                                            .icon(IconName::Plus)
+                                            .icon_size(IconSize::Small)
+                                            .icon_color(Color::Muted)
+                                            .label_size(LabelSize::Small)
+                                            .on_click(
+                                                move |_, window, cx| {
+                                                    if let Some(workspace) = window.root().flatten() {
+                                                        let workspace = workspace.downgrade();
+                                                        window
+                                                            .spawn(cx, async |cx| {
+                                                                open_new_agent_servers_entry_in_settings_editor(
+                                                                    workspace,
+                                                                    cx,
+                                                                ).await
+                                                            })
+                                                            .detach_and_log_err(cx);
+                                                    }
+                                                }
+                                            ),
+                                    )
+                            )
                             .child(
                                 Label::new(
                                     "Use the full power of Zed's UI with your favorite agent, connected via the Agent Client Protocol.",
@@ -1324,3 +1356,68 @@ fn show_unable_to_uninstall_extension_with_context_server(
 
     workspace.toggle_status_toast(status_toast, cx);
 }
+
+async fn open_new_agent_servers_entry_in_settings_editor(
+    workspace: WeakEntity<Workspace>,
+    cx: &mut AsyncWindowContext,
+) -> Result<()> {
+    let settings_editor = workspace
+        .update_in(cx, |_, window, cx| {
+            create_and_open_local_file(paths::settings_file(), window, cx, || {
+                settings::initial_user_settings_content().as_ref().into()
+            })
+        })?
+        .await?
+        .downcast::<Editor>()
+        .unwrap();
+
+    settings_editor
+        .downgrade()
+        .update_in(cx, |item, window, cx| {
+            let text = item.buffer().read(cx).snapshot(cx).text();
+
+            let settings = cx.global::<SettingsStore>();
+
+            let edits = settings.edits_for_update::<AllAgentServersSettings>(&text, |file| {
+                let unique_server_name = (0..u8::MAX)
+                    .map(|i| {
+                        if i == 0 {
+                            "your_agent".into()
+                        } else {
+                            format!("your_agent_{}", i).into()
+                        }
+                    })
+                    .find(|name| !file.custom.contains_key(name));
+                if let Some(server_name) = unique_server_name {
+                    file.custom.insert(
+                        server_name,
+                        AgentServerSettings {
+                            command: AgentServerCommand {
+                                path: "path_to_executable".into(),
+                                args: vec![],
+                                env: Some(HashMap::default()),
+                            },
+                        },
+                    );
+                }
+            });
+
+            if !edits.is_empty() {
+                let ranges = edits
+                    .iter()
+                    .map(|(range, _)| range.clone())
+                    .collect::<Vec<_>>();
+
+                item.edit(edits, cx);
+
+                item.change_selections(
+                    SelectionEffects::scroll(Autoscroll::newest()),
+                    window,
+                    cx,
+                    |selections| {
+                        selections.select_ranges(ranges);
+                    },
+                );
+            }
+        })
+}