acp: Automatically install gemini under Zed's data dir (#37054)
Closes: https://github.com/zed-industries/zed/issues/37089 Instead of looking for the gemini command on `$PATH`, by default we'll install our own copy on demand under our data dir, as we already do for language servers and debug adapters. This also means we can handle keeping the binary up to date instead of prompting the user to upgrade. Notes: - The download is only triggered if you open a new Gemini thread - Custom commands from `agent_servers.gemini` in settings are respected as before - A new `agent_servers.gemini.ignore_system_version` setting is added, similar to the existing settings for language servers. It's `true` by default, and setting it to `false` disables the automatic download and makes Zed search `$PATH` as before. - If `agent_servers.gemini.ignore_system_version` is `false` and no binary is found on `$PATH`, we'll fall back to automatic installation. If it's `false` and a binary is found, but the version is older than v0.2.1, we'll show an error. Release Notes: - acp: By default, Zed will now download and use a private copy of the Gemini CLI binary, instead of searching your `$PATH`. To make Zed search your `$PATH` for Gemini CLI before attempting to download it, use the following setting: ``` { "agent_servers": { "gemini": { "ignore_system_version": false } } } ```
This commit is contained in:
@@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use acp_thread::{MentionUri, selection_name};
|
||||
use agent_client_protocol as acp;
|
||||
use agent_servers::AgentServer;
|
||||
use agent_servers::{AgentServer, AgentServerDelegate};
|
||||
use agent2::HistoryStore;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_slash_commands::codeblock_fence_for_path;
|
||||
@@ -645,7 +645,8 @@ impl MessageEditor {
|
||||
self.project.read(cx).fs().clone(),
|
||||
self.history_store.clone(),
|
||||
));
|
||||
let connection = server.connect(Path::new(""), &self.project, cx);
|
||||
let delegate = AgentServerDelegate::new(self.project.clone(), watch::channel("".into()).0);
|
||||
let connection = server.connect(Path::new(""), delegate, cx);
|
||||
cx.spawn(async move |_, cx| {
|
||||
let agent = connection.await?;
|
||||
let agent = agent.downcast::<agent2::NativeAgentConnection>().unwrap();
|
||||
|
||||
@@ -6,7 +6,7 @@ use acp_thread::{
|
||||
use acp_thread::{AgentConnection, Plan};
|
||||
use action_log::ActionLog;
|
||||
use agent_client_protocol::{self as acp, PromptCapabilities};
|
||||
use agent_servers::{AgentServer, ClaudeCode};
|
||||
use agent_servers::{AgentServer, AgentServerDelegate, ClaudeCode};
|
||||
use agent_settings::{AgentProfileId, AgentSettings, CompletionMode, NotifyWhenAgentWaiting};
|
||||
use agent2::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore};
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
@@ -46,7 +46,7 @@ use text::Anchor;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
Callout, Disclosure, Divider, DividerColor, ElevationIndex, KeyBinding, PopoverMenuHandle,
|
||||
Scrollbar, ScrollbarState, SpinnerLabel, TintColor, Tooltip, prelude::*,
|
||||
Scrollbar, ScrollbarState, SpinnerLabel, Tooltip, prelude::*,
|
||||
};
|
||||
use util::{ResultExt, size::format_file_size, time::duration_alt_display};
|
||||
use workspace::{CollaboratorId, Workspace};
|
||||
@@ -285,15 +285,12 @@ pub struct AcpThreadView {
|
||||
editing_message: Option<usize>,
|
||||
prompt_capabilities: Rc<Cell<PromptCapabilities>>,
|
||||
is_loading_contents: bool,
|
||||
install_command_markdown: Entity<Markdown>,
|
||||
_cancel_task: Option<Task<()>>,
|
||||
_subscriptions: [Subscription; 3],
|
||||
}
|
||||
|
||||
enum ThreadState {
|
||||
Loading {
|
||||
_task: Task<()>,
|
||||
},
|
||||
Loading(Entity<LoadingView>),
|
||||
Ready {
|
||||
thread: Entity<AcpThread>,
|
||||
title_editor: Option<Entity<Editor>>,
|
||||
@@ -309,6 +306,12 @@ enum ThreadState {
|
||||
},
|
||||
}
|
||||
|
||||
struct LoadingView {
|
||||
title: SharedString,
|
||||
_load_task: Task<()>,
|
||||
_update_title_task: Task<anyhow::Result<()>>,
|
||||
}
|
||||
|
||||
impl AcpThreadView {
|
||||
pub fn new(
|
||||
agent: Rc<dyn AgentServer>,
|
||||
@@ -399,7 +402,6 @@ impl AcpThreadView {
|
||||
hovered_recent_history_item: None,
|
||||
prompt_capabilities,
|
||||
is_loading_contents: false,
|
||||
install_command_markdown: cx.new(|cx| Markdown::new("".into(), None, None, cx)),
|
||||
_subscriptions: subscriptions,
|
||||
_cancel_task: None,
|
||||
focus_handle: cx.focus_handle(),
|
||||
@@ -420,8 +422,10 @@ impl AcpThreadView {
|
||||
.next()
|
||||
.map(|worktree| worktree.read(cx).abs_path())
|
||||
.unwrap_or_else(|| paths::home_dir().as_path().into());
|
||||
let (tx, mut rx) = watch::channel("Loading…".into());
|
||||
let delegate = AgentServerDelegate::new(project.clone(), tx);
|
||||
|
||||
let connect_task = agent.connect(&root_dir, &project, cx);
|
||||
let connect_task = agent.connect(&root_dir, delegate, cx);
|
||||
let load_task = cx.spawn_in(window, async move |this, cx| {
|
||||
let connection = match connect_task.await {
|
||||
Ok(connection) => connection,
|
||||
@@ -574,7 +578,25 @@ impl AcpThreadView {
|
||||
.log_err();
|
||||
});
|
||||
|
||||
ThreadState::Loading { _task: load_task }
|
||||
let loading_view = cx.new(|cx| {
|
||||
let update_title_task = cx.spawn(async move |this, cx| {
|
||||
loop {
|
||||
let status = rx.recv().await?;
|
||||
this.update(cx, |this: &mut LoadingView, cx| {
|
||||
this.title = status;
|
||||
cx.notify();
|
||||
})?;
|
||||
}
|
||||
});
|
||||
|
||||
LoadingView {
|
||||
title: "Loading…".into(),
|
||||
_load_task: load_task,
|
||||
_update_title_task: update_title_task,
|
||||
}
|
||||
});
|
||||
|
||||
ThreadState::Loading(loading_view)
|
||||
}
|
||||
|
||||
fn handle_auth_required(
|
||||
@@ -674,13 +696,15 @@ impl AcpThreadView {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title(&self) -> SharedString {
|
||||
pub fn title(&self, cx: &App) -> SharedString {
|
||||
match &self.thread_state {
|
||||
ThreadState::Ready { .. } | ThreadState::Unauthenticated { .. } => "New Thread".into(),
|
||||
ThreadState::Loading { .. } => "Loading…".into(),
|
||||
ThreadState::Loading(loading_view) => loading_view.read(cx).title.clone(),
|
||||
ThreadState::LoadError(error) => match error {
|
||||
LoadError::NotInstalled { .. } => format!("Install {}", self.agent.name()).into(),
|
||||
LoadError::Unsupported { .. } => format!("Upgrade {}", self.agent.name()).into(),
|
||||
LoadError::FailedToInstall(_) => {
|
||||
format!("Failed to Install {}", self.agent.name()).into()
|
||||
}
|
||||
LoadError::Exited { .. } => format!("{} Exited", self.agent.name()).into(),
|
||||
LoadError::Other(_) => format!("Error Loading {}", self.agent.name()).into(),
|
||||
},
|
||||
@@ -2950,18 +2974,26 @@ impl AcpThreadView {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
let (message, action_slot): (SharedString, _) = match e {
|
||||
LoadError::NotInstalled => {
|
||||
return self.render_not_installed(None, window, cx);
|
||||
}
|
||||
let (title, message, action_slot): (_, SharedString, _) = match e {
|
||||
LoadError::Unsupported {
|
||||
command: path,
|
||||
current_version,
|
||||
minimum_version,
|
||||
} => {
|
||||
return self.render_not_installed(Some((path, current_version)), window, cx);
|
||||
return self.render_unsupported(path, current_version, minimum_version, window, cx);
|
||||
}
|
||||
LoadError::Exited { .. } => ("Server exited with status {status}".into(), None),
|
||||
LoadError::FailedToInstall(msg) => (
|
||||
"Failed to Install",
|
||||
msg.into(),
|
||||
Some(self.create_copy_button(msg.to_string()).into_any_element()),
|
||||
),
|
||||
LoadError::Exited { status } => (
|
||||
"Failed to Launch",
|
||||
format!("Server exited with status {status}").into(),
|
||||
None,
|
||||
),
|
||||
LoadError::Other(msg) => (
|
||||
"Failed to Launch",
|
||||
msg.into(),
|
||||
Some(self.create_copy_button(msg.to_string()).into_any_element()),
|
||||
),
|
||||
@@ -2970,95 +3002,34 @@ impl AcpThreadView {
|
||||
Callout::new()
|
||||
.severity(Severity::Error)
|
||||
.icon(IconName::XCircleFilled)
|
||||
.title("Failed to Launch")
|
||||
.title(title)
|
||||
.description(message)
|
||||
.actions_slot(div().children(action_slot))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn install_agent(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
telemetry::event!("Agent Install CLI", agent = self.agent.telemetry_id());
|
||||
let Some(install_command) = self.agent.install_command().map(|s| s.to_owned()) else {
|
||||
return;
|
||||
};
|
||||
let task = self
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let project = workspace.project().read(cx);
|
||||
let cwd = project.first_project_directory(cx);
|
||||
let shell = project.terminal_settings(&cwd, cx).shell.clone();
|
||||
let spawn_in_terminal = task::SpawnInTerminal {
|
||||
id: task::TaskId(install_command.clone()),
|
||||
full_label: install_command.clone(),
|
||||
label: install_command.clone(),
|
||||
command: Some(install_command.clone()),
|
||||
args: Vec::new(),
|
||||
command_label: install_command.clone(),
|
||||
cwd,
|
||||
env: Default::default(),
|
||||
use_new_terminal: true,
|
||||
allow_concurrent_runs: true,
|
||||
reveal: Default::default(),
|
||||
reveal_target: Default::default(),
|
||||
hide: Default::default(),
|
||||
shell,
|
||||
show_summary: true,
|
||||
show_command: true,
|
||||
show_rerun: false,
|
||||
};
|
||||
workspace.spawn_in_terminal(spawn_in_terminal, window, cx)
|
||||
})
|
||||
.ok();
|
||||
let Some(task) = task else { return };
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
if let Some(Ok(_)) = task.await {
|
||||
this.update_in(cx, |this, window, cx| {
|
||||
this.reset(window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
|
||||
fn render_not_installed(
|
||||
fn render_unsupported(
|
||||
&self,
|
||||
existing_version: Option<(&SharedString, &SharedString)>,
|
||||
window: &mut Window,
|
||||
path: &SharedString,
|
||||
version: &SharedString,
|
||||
minimum_version: &SharedString,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
let install_command = self.agent.install_command().unwrap_or_default();
|
||||
|
||||
self.install_command_markdown.update(cx, |markdown, cx| {
|
||||
if !markdown.source().contains(&install_command) {
|
||||
markdown.replace(format!("```\n{}\n```", install_command), cx);
|
||||
}
|
||||
});
|
||||
|
||||
let (heading_label, description_label, button_label) =
|
||||
if let Some((path, version)) = existing_version {
|
||||
(
|
||||
format!("Upgrade {} to work with Zed", self.agent.name()),
|
||||
if version.is_empty() {
|
||||
format!(
|
||||
"Currently using {}, which does not report a valid --version",
|
||||
path,
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Currently using {}, which is only version {}",
|
||||
path, version
|
||||
)
|
||||
},
|
||||
format!("Upgrade {}", self.agent.name()),
|
||||
let (heading_label, description_label) = (
|
||||
format!("Upgrade {} to work with Zed", self.agent.name()),
|
||||
if version.is_empty() {
|
||||
format!(
|
||||
"Currently using {}, which does not report a valid --version",
|
||||
path,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
format!("Get Started with {} in Zed", self.agent.name()),
|
||||
"Use Google's new coding agent directly in Zed.".to_string(),
|
||||
format!("Install {}", self.agent.name()),
|
||||
format!(
|
||||
"Currently using {}, which is only version {} (need at least {minimum_version})",
|
||||
path, version
|
||||
)
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
v_flex()
|
||||
.w_full()
|
||||
@@ -3078,34 +3049,6 @@ impl AcpThreadView {
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Button::new("install_gemini", button_label)
|
||||
.full_width()
|
||||
.size(ButtonSize::Medium)
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.label_size(LabelSize::Small)
|
||||
.icon(IconName::TerminalGhost)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(cx.listener(|this, _, window, cx| this.install_agent(window, cx))),
|
||||
)
|
||||
.child(
|
||||
Label::new("Or, run the following command in your terminal:")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(MarkdownElement::new(
|
||||
self.install_command_markdown.clone(),
|
||||
default_markdown_style(false, false, window, cx),
|
||||
))
|
||||
.when_some(existing_version, |el, (path, _)| {
|
||||
el.child(
|
||||
Label::new(format!("If this does not work you will need to upgrade manually, or uninstall your existing version from {}", path))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -4994,18 +4937,6 @@ impl AcpThreadView {
|
||||
}))
|
||||
}
|
||||
|
||||
fn reset(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.thread_state = Self::initial_state(
|
||||
self.agent.clone(),
|
||||
None,
|
||||
self.workspace.clone(),
|
||||
self.project.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn delete_history_entry(&mut self, entry: HistoryEntry, cx: &mut Context<Self>) {
|
||||
let task = match entry {
|
||||
HistoryEntry::AcpThread(thread) => self.history_store.update(cx, |history, cx| {
|
||||
@@ -5534,22 +5465,10 @@ pub(crate) mod tests {
|
||||
"Test".into()
|
||||
}
|
||||
|
||||
fn empty_state_headline(&self) -> SharedString {
|
||||
"Test".into()
|
||||
}
|
||||
|
||||
fn empty_state_message(&self) -> SharedString {
|
||||
"Test".into()
|
||||
}
|
||||
|
||||
fn install_command(&self) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn connect(
|
||||
&self,
|
||||
_root_dir: &Path,
|
||||
_project: &Entity<Project>,
|
||||
_delegate: AgentServerDelegate,
|
||||
_cx: &mut App,
|
||||
) -> Task<gpui::Result<Rc<dyn AgentConnection>>> {
|
||||
Task::ready(Ok(Rc::new(self.connection.clone())))
|
||||
|
||||
@@ -5,7 +5,7 @@ mod tool_picker;
|
||||
|
||||
use std::{ops::Range, sync::Arc, time::Duration};
|
||||
|
||||
use agent_servers::{AgentServerCommand, AgentServerSettings, AllAgentServersSettings, Gemini};
|
||||
use agent_servers::{AgentServerCommand, AllAgentServersSettings, CustomAgentServerSettings};
|
||||
use agent_settings::AgentSettings;
|
||||
use anyhow::Result;
|
||||
use assistant_tool::{ToolSource, ToolWorkingSet};
|
||||
@@ -27,7 +27,6 @@ use language_model::{
|
||||
};
|
||||
use notifications::status_toast::{StatusToast, ToastIcon};
|
||||
use project::{
|
||||
Project,
|
||||
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
|
||||
project_settings::{ContextServerSettings, ProjectSettings},
|
||||
};
|
||||
@@ -52,7 +51,6 @@ pub struct AgentConfiguration {
|
||||
fs: Arc<dyn Fs>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: WeakEntity<Project>,
|
||||
focus_handle: FocusHandle,
|
||||
configuration_views_by_provider: HashMap<LanguageModelProviderId, AnyView>,
|
||||
context_server_store: Entity<ContextServerStore>,
|
||||
@@ -62,7 +60,6 @@ pub struct AgentConfiguration {
|
||||
_registry_subscription: Subscription,
|
||||
scroll_handle: ScrollHandle,
|
||||
scrollbar_state: ScrollbarState,
|
||||
gemini_is_installed: bool,
|
||||
_check_for_gemini: Task<()>,
|
||||
}
|
||||
|
||||
@@ -73,7 +70,6 @@ impl AgentConfiguration {
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: WeakEntity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
@@ -98,11 +94,6 @@ impl AgentConfiguration {
|
||||
|
||||
cx.subscribe(&context_server_store, |_, _, _, cx| cx.notify())
|
||||
.detach();
|
||||
cx.observe_global_in::<SettingsStore>(window, |this, _, cx| {
|
||||
this.check_for_gemini(cx);
|
||||
cx.notify();
|
||||
})
|
||||
.detach();
|
||||
|
||||
let scroll_handle = ScrollHandle::new();
|
||||
let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
|
||||
@@ -111,7 +102,6 @@ impl AgentConfiguration {
|
||||
fs,
|
||||
language_registry,
|
||||
workspace,
|
||||
project,
|
||||
focus_handle,
|
||||
configuration_views_by_provider: HashMap::default(),
|
||||
context_server_store,
|
||||
@@ -121,11 +111,9 @@ impl AgentConfiguration {
|
||||
_registry_subscription: registry_subscription,
|
||||
scroll_handle,
|
||||
scrollbar_state,
|
||||
gemini_is_installed: false,
|
||||
_check_for_gemini: Task::ready(()),
|
||||
};
|
||||
this.build_provider_configuration_views(window, cx);
|
||||
this.check_for_gemini(cx);
|
||||
this
|
||||
}
|
||||
|
||||
@@ -155,34 +143,6 @@ impl AgentConfiguration {
|
||||
self.configuration_views_by_provider
|
||||
.insert(provider.id(), configuration_view);
|
||||
}
|
||||
|
||||
fn check_for_gemini(&mut self, cx: &mut Context<Self>) {
|
||||
let project = self.project.clone();
|
||||
let settings = AllAgentServersSettings::get_global(cx).clone();
|
||||
self._check_for_gemini = cx.spawn({
|
||||
async move |this, cx| {
|
||||
let Some(project) = project.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let gemini_is_installed = AgentServerCommand::resolve(
|
||||
Gemini::binary_name(),
|
||||
&[],
|
||||
// TODO expose fallback path from the Gemini/CC types so we don't have to hardcode it again here
|
||||
None,
|
||||
settings.gemini,
|
||||
&project,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
.is_some();
|
||||
this.update(cx, |this, cx| {
|
||||
this.gemini_is_installed = gemini_is_installed;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for AgentConfiguration {
|
||||
@@ -1041,9 +1001,8 @@ impl AgentConfiguration {
|
||||
name.clone(),
|
||||
ExternalAgent::Custom {
|
||||
name: name.clone(),
|
||||
settings: settings.clone(),
|
||||
command: settings.command.clone(),
|
||||
},
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
.into_any_element()
|
||||
@@ -1102,7 +1061,6 @@ impl AgentConfiguration {
|
||||
IconName::AiGemini,
|
||||
"Gemini CLI",
|
||||
ExternalAgent::Gemini,
|
||||
(!self.gemini_is_installed).then_some(Gemini::install_command().into()),
|
||||
cx,
|
||||
))
|
||||
// TODO add CC
|
||||
@@ -1115,7 +1073,6 @@ impl AgentConfiguration {
|
||||
icon: IconName,
|
||||
name: impl Into<SharedString>,
|
||||
agent: ExternalAgent,
|
||||
install_command: Option<SharedString>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let name = name.into();
|
||||
@@ -1135,88 +1092,28 @@ impl AgentConfiguration {
|
||||
.child(Icon::new(icon).size(IconSize::Small).color(Color::Muted))
|
||||
.child(Label::new(name.clone())),
|
||||
)
|
||||
.map(|this| {
|
||||
if let Some(install_command) = install_command {
|
||||
this.child(
|
||||
Button::new(
|
||||
SharedString::from(format!("install_external_agent-{name}")),
|
||||
"Install Agent",
|
||||
)
|
||||
.label_size(LabelSize::Small)
|
||||
.icon(IconName::Plus)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(Tooltip::text(install_command.clone()))
|
||||
.on_click(cx.listener(
|
||||
move |this, _, window, cx| {
|
||||
let Some(project) = this.project.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(workspace) = this.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let cwd = project.read(cx).first_project_directory(cx);
|
||||
let shell =
|
||||
project.read(cx).terminal_settings(&cwd, cx).shell.clone();
|
||||
let spawn_in_terminal = task::SpawnInTerminal {
|
||||
id: task::TaskId(install_command.to_string()),
|
||||
full_label: install_command.to_string(),
|
||||
label: install_command.to_string(),
|
||||
command: Some(install_command.to_string()),
|
||||
args: Vec::new(),
|
||||
command_label: install_command.to_string(),
|
||||
cwd,
|
||||
env: Default::default(),
|
||||
use_new_terminal: true,
|
||||
allow_concurrent_runs: true,
|
||||
reveal: Default::default(),
|
||||
reveal_target: Default::default(),
|
||||
hide: Default::default(),
|
||||
shell,
|
||||
show_summary: true,
|
||||
show_command: true,
|
||||
show_rerun: false,
|
||||
};
|
||||
let task = workspace.update(cx, |workspace, cx| {
|
||||
workspace.spawn_in_terminal(spawn_in_terminal, window, cx)
|
||||
});
|
||||
cx.spawn(async move |this, cx| {
|
||||
task.await;
|
||||
this.update(cx, |this, cx| {
|
||||
this.check_for_gemini(cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
},
|
||||
)),
|
||||
.child(
|
||||
h_flex().gap_1().child(
|
||||
Button::new(
|
||||
SharedString::from(format!("start_acp_thread-{name}")),
|
||||
"Start New Thread",
|
||||
)
|
||||
} else {
|
||||
this.child(
|
||||
h_flex().gap_1().child(
|
||||
Button::new(
|
||||
SharedString::from(format!("start_acp_thread-{name}")),
|
||||
"Start New Thread",
|
||||
)
|
||||
.label_size(LabelSize::Small)
|
||||
.icon(IconName::Thread)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(
|
||||
NewExternalAgentThread {
|
||||
agent: Some(agent.clone()),
|
||||
}
|
||||
.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
.label_size(LabelSize::Small)
|
||||
.icon(IconName::Thread)
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(move |_, window, cx| {
|
||||
window.dispatch_action(
|
||||
NewExternalAgentThread {
|
||||
agent: Some(agent.clone()),
|
||||
}
|
||||
.boxed_clone(),
|
||||
cx,
|
||||
);
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1393,7 +1290,7 @@ async fn open_new_agent_servers_entry_in_settings_editor(
|
||||
unique_server_name = Some(server_name.clone());
|
||||
file.custom.insert(
|
||||
server_name,
|
||||
AgentServerSettings {
|
||||
CustomAgentServerSettings {
|
||||
command: AgentServerCommand {
|
||||
path: "path_to_executable".into(),
|
||||
args: vec![],
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use acp_thread::AcpThread;
|
||||
use agent_servers::AgentServerSettings;
|
||||
use agent_servers::AgentServerCommand;
|
||||
use agent2::{DbThreadMetadata, HistoryEntry};
|
||||
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -259,7 +259,7 @@ pub enum AgentType {
|
||||
NativeAgent,
|
||||
Custom {
|
||||
name: SharedString,
|
||||
settings: AgentServerSettings,
|
||||
command: AgentServerCommand,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1479,7 +1479,6 @@ impl AgentPanel {
|
||||
tools,
|
||||
self.language_registry.clone(),
|
||||
self.workspace.clone(),
|
||||
self.project.downgrade(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
@@ -1896,8 +1895,8 @@ impl AgentPanel {
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
AgentType::Custom { name, settings } => self.external_thread(
|
||||
Some(crate::ExternalAgent::Custom { name, settings }),
|
||||
AgentType::Custom { name, command } => self.external_thread(
|
||||
Some(crate::ExternalAgent::Custom { name, command }),
|
||||
None,
|
||||
None,
|
||||
window,
|
||||
@@ -2115,7 +2114,7 @@ impl AgentPanel {
|
||||
.child(title_editor)
|
||||
.into_any_element()
|
||||
} else {
|
||||
Label::new(thread_view.read(cx).title())
|
||||
Label::new(thread_view.read(cx).title(cx))
|
||||
.color(Color::Muted)
|
||||
.truncate()
|
||||
.into_any_element()
|
||||
@@ -2664,9 +2663,9 @@ impl AgentPanel {
|
||||
AgentType::Custom {
|
||||
name: agent_name
|
||||
.clone(),
|
||||
settings:
|
||||
agent_settings
|
||||
.clone(),
|
||||
command: agent_settings
|
||||
.command
|
||||
.clone(),
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
|
||||
@@ -28,7 +28,7 @@ use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use agent::{Thread, ThreadId};
|
||||
use agent_servers::AgentServerSettings;
|
||||
use agent_servers::AgentServerCommand;
|
||||
use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use client::Client;
|
||||
@@ -170,7 +170,7 @@ enum ExternalAgent {
|
||||
NativeAgent,
|
||||
Custom {
|
||||
name: SharedString,
|
||||
settings: AgentServerSettings,
|
||||
command: AgentServerCommand,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -193,9 +193,9 @@ impl ExternalAgent {
|
||||
Self::Gemini => Rc::new(agent_servers::Gemini),
|
||||
Self::ClaudeCode => Rc::new(agent_servers::ClaudeCode),
|
||||
Self::NativeAgent => Rc::new(agent2::NativeAgentServer::new(fs, history)),
|
||||
Self::Custom { name, settings } => Rc::new(agent_servers::CustomAgentServer::new(
|
||||
Self::Custom { name, command } => Rc::new(agent_servers::CustomAgentServer::new(
|
||||
name.clone(),
|
||||
settings,
|
||||
command.clone(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user