acp: Support session modes (e.g. CC plan mode) (#37632)
Adds support for [ACP session modes](https://github.com/zed-industries/agent-client-protocol/pull/67) enabling plan and other permission modes in CC: https://github.com/user-attachments/assets/dea18d82-4da6-465e-983b-02b77c6dcf15 Release Notes: - Claude Code: Add support for plan mode, and all other permission modes --------- Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de> Co-authored-by: Richard Feldman <oss@rtfeldman.com> Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
co-authored by
Bennet Bo Fenner
Richard Feldman
Danilo Leal
parent
ad02f6b9e3
commit
5e397e85b1
@@ -1,11 +1,13 @@
|
||||
mod completion_provider;
|
||||
mod entry_view_state;
|
||||
mod message_editor;
|
||||
mod mode_selector;
|
||||
mod model_selector;
|
||||
mod model_selector_popover;
|
||||
mod thread_history;
|
||||
mod thread_view;
|
||||
|
||||
pub use mode_selector::ModeSelector;
|
||||
pub use model_selector::AcpModelSelector;
|
||||
pub use model_selector_popover::AcpModelSelectorPopover;
|
||||
pub use thread_history::*;
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
use acp_thread::AgentSessionModes;
|
||||
use agent_client_protocol as acp;
|
||||
use agent_servers::AgentServer;
|
||||
use fs::Fs;
|
||||
use gpui::{Context, Entity, FocusHandle, WeakEntity, Window, prelude::*};
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
use ui::{
|
||||
Button, ContextMenu, ContextMenuEntry, KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip,
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use crate::{CycleModeSelector, ToggleProfileSelector};
|
||||
|
||||
pub struct ModeSelector {
|
||||
connection: Rc<dyn AgentSessionModes>,
|
||||
agent_server: Rc<dyn AgentServer>,
|
||||
menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
focus_handle: FocusHandle,
|
||||
fs: Arc<dyn Fs>,
|
||||
setting_mode: bool,
|
||||
}
|
||||
|
||||
impl ModeSelector {
|
||||
pub fn new(
|
||||
session_modes: Rc<dyn AgentSessionModes>,
|
||||
agent_server: Rc<dyn AgentServer>,
|
||||
fs: Arc<dyn Fs>,
|
||||
focus_handle: FocusHandle,
|
||||
) -> Self {
|
||||
Self {
|
||||
connection: session_modes,
|
||||
agent_server,
|
||||
menu_handle: PopoverMenuHandle::default(),
|
||||
fs,
|
||||
setting_mode: false,
|
||||
focus_handle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn menu_handle(&self) -> PopoverMenuHandle<ContextMenu> {
|
||||
self.menu_handle.clone()
|
||||
}
|
||||
|
||||
pub fn cycle_mode(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let all_modes = self.connection.all_modes();
|
||||
let current_mode = self.connection.current_mode();
|
||||
|
||||
let current_index = all_modes
|
||||
.iter()
|
||||
.position(|mode| mode.id.0 == current_mode.0)
|
||||
.unwrap_or(0);
|
||||
|
||||
let next_index = (current_index + 1) % all_modes.len();
|
||||
self.set_mode(all_modes[next_index].id.clone(), cx);
|
||||
}
|
||||
|
||||
pub fn set_mode(&mut self, mode: acp::SessionModeId, cx: &mut Context<Self>) {
|
||||
let task = self.connection.set_mode(mode, cx);
|
||||
self.setting_mode = true;
|
||||
cx.notify();
|
||||
|
||||
cx.spawn(async move |this: WeakEntity<ModeSelector>, cx| {
|
||||
if let Err(err) = task.await {
|
||||
log::error!("Failed to set session mode: {:?}", err);
|
||||
}
|
||||
this.update(cx, |this, cx| {
|
||||
this.setting_mode = false;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn build_context_menu(
|
||||
&self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Entity<ContextMenu> {
|
||||
let weak_self = cx.weak_entity();
|
||||
|
||||
ContextMenu::build(window, cx, move |mut menu, _window, cx| {
|
||||
let all_modes = self.connection.all_modes();
|
||||
let current_mode = self.connection.current_mode();
|
||||
let default_mode = self.agent_server.default_mode(cx);
|
||||
|
||||
for mode in all_modes {
|
||||
let is_selected = &mode.id == ¤t_mode;
|
||||
let is_default = Some(&mode.id) == default_mode.as_ref();
|
||||
let entry = ContextMenuEntry::new(mode.name.clone())
|
||||
.toggleable(IconPosition::End, is_selected);
|
||||
|
||||
let entry = if let Some(description) = &mode.description {
|
||||
entry.documentation_aside(ui::DocumentationSide::Left, {
|
||||
let description = description.clone();
|
||||
|
||||
move |cx| {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(Label::new(description.clone()))
|
||||
.child(
|
||||
h_flex()
|
||||
.pt_1()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.gap_0p5()
|
||||
.text_sm()
|
||||
.text_color(Color::Muted.color(cx))
|
||||
.child("Hold")
|
||||
.child(div().pt_0p5().children(ui::render_modifiers(
|
||||
&gpui::Modifiers::secondary_key(),
|
||||
PlatformStyle::platform(),
|
||||
None,
|
||||
Some(ui::TextSize::Default.rems(cx).into()),
|
||||
true,
|
||||
)))
|
||||
.child(div().map(|this| {
|
||||
if is_default {
|
||||
this.child("to also unset as default")
|
||||
} else {
|
||||
this.child("to also set as default")
|
||||
}
|
||||
})),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
entry
|
||||
};
|
||||
|
||||
menu.push_item(entry.handler({
|
||||
let mode_id = mode.id.clone();
|
||||
let weak_self = weak_self.clone();
|
||||
move |window, cx| {
|
||||
weak_self
|
||||
.update(cx, |this, cx| {
|
||||
if window.modifiers().secondary() {
|
||||
this.agent_server.set_default_mode(
|
||||
if is_default {
|
||||
None
|
||||
} else {
|
||||
Some(mode_id.clone())
|
||||
},
|
||||
this.fs.clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
this.set_mode(mode_id.clone(), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
menu.key_context("ModeSelector")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ModeSelector {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let current_mode_id = self.connection.current_mode();
|
||||
let current_mode_name = self
|
||||
.connection
|
||||
.all_modes()
|
||||
.iter()
|
||||
.find(|mode| mode.id == current_mode_id)
|
||||
.map(|mode| mode.name.clone())
|
||||
.unwrap_or_else(|| "Unknown".into());
|
||||
|
||||
let this = cx.entity();
|
||||
|
||||
let trigger_button = Button::new("mode-selector-trigger", current_mode_name)
|
||||
.label_size(LabelSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.color(Color::Muted)
|
||||
.icon(IconName::ChevronDown)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_position(IconPosition::End)
|
||||
.icon_color(Color::Muted)
|
||||
.disabled(self.setting_mode);
|
||||
|
||||
PopoverMenu::new("mode-selector")
|
||||
.trigger_with_tooltip(
|
||||
trigger_button,
|
||||
Tooltip::element({
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
move |window, cx| {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.pb_1()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(Label::new("Cycle Through Modes"))
|
||||
.children(KeyBinding::for_action_in(
|
||||
&CycleModeSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(Label::new("Toggle Mode Menu"))
|
||||
.children(KeyBinding::for_action_in(
|
||||
&ToggleProfileSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
}),
|
||||
)
|
||||
.anchor(gpui::Corner::BottomRight)
|
||||
.with_handle(self.menu_handle.clone())
|
||||
.menu(move |window, cx| {
|
||||
Some(this.update(cx, |this, cx| this.build_context_menu(window, cx)))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ use zed_actions::assistant::OpenRulesLibrary;
|
||||
|
||||
use super::entry_view_state::EntryViewState;
|
||||
use crate::acp::AcpModelSelectorPopover;
|
||||
use crate::acp::ModeSelector;
|
||||
use crate::acp::entry_view_state::{EntryViewEvent, ViewEvent};
|
||||
use crate::acp::message_editor::{MessageEditor, MessageEditorEvent};
|
||||
use crate::agent_diff::AgentDiff;
|
||||
@@ -64,8 +65,9 @@ use crate::ui::{
|
||||
AgentNotification, AgentNotificationEvent, BurnModeTooltip, UnavailableEditingTooltip,
|
||||
};
|
||||
use crate::{
|
||||
AgentDiffPane, AgentPanel, ContinueThread, ContinueWithBurnMode, ExpandMessageEditor, Follow,
|
||||
KeepAll, OpenAgentDiff, OpenHistory, RejectAll, ToggleBurnMode, ToggleProfileSelector,
|
||||
AgentDiffPane, AgentPanel, ContinueThread, ContinueWithBurnMode, CycleModeSelector,
|
||||
ExpandMessageEditor, Follow, KeepAll, OpenAgentDiff, OpenHistory, RejectAll, ToggleBurnMode,
|
||||
ToggleProfileSelector,
|
||||
};
|
||||
|
||||
pub const MIN_EDITOR_LINES: usize = 4;
|
||||
@@ -298,6 +300,7 @@ enum ThreadState {
|
||||
Ready {
|
||||
thread: Entity<AcpThread>,
|
||||
title_editor: Option<Entity<Editor>>,
|
||||
mode_selector: Option<Entity<ModeSelector>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
},
|
||||
LoadError(LoadError),
|
||||
@@ -396,6 +399,7 @@ impl AcpThreadView {
|
||||
message_editor,
|
||||
model_selector: None,
|
||||
profile_selector: None,
|
||||
|
||||
notifications: Vec::new(),
|
||||
notification_subscriptions: HashMap::default(),
|
||||
list_state: list_state.clone(),
|
||||
@@ -594,6 +598,23 @@ impl AcpThreadView {
|
||||
})
|
||||
});
|
||||
|
||||
let mode_selector = thread
|
||||
.read(cx)
|
||||
.connection()
|
||||
.session_modes(thread.read(cx).session_id(), cx)
|
||||
.map(|session_modes| {
|
||||
let fs = this.project.read(cx).fs().clone();
|
||||
let focus_handle = this.focus_handle(cx);
|
||||
cx.new(|_cx| {
|
||||
ModeSelector::new(
|
||||
session_modes,
|
||||
this.agent.clone(),
|
||||
fs,
|
||||
focus_handle,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
let mut subscriptions = vec![
|
||||
cx.subscribe_in(&thread, window, Self::handle_thread_event),
|
||||
cx.observe(&action_log, |_, _, cx| cx.notify()),
|
||||
@@ -615,9 +636,11 @@ impl AcpThreadView {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
this.thread_state = ThreadState::Ready {
|
||||
thread,
|
||||
title_editor,
|
||||
mode_selector,
|
||||
_subscriptions: subscriptions,
|
||||
};
|
||||
this.message_editor.focus_handle(cx).focus(window);
|
||||
@@ -770,6 +793,15 @@ impl AcpThreadView {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mode_selector(&self) -> Option<&Entity<ModeSelector>> {
|
||||
match &self.thread_state {
|
||||
ThreadState::Ready { mode_selector, .. } => mode_selector.as_ref(),
|
||||
ThreadState::Unauthenticated { .. }
|
||||
| ThreadState::Loading { .. }
|
||||
| ThreadState::LoadError { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title(&self, cx: &App) -> SharedString {
|
||||
match &self.thread_state {
|
||||
ThreadState::Ready { .. } | ThreadState::Unauthenticated { .. } => "New Thread".into(),
|
||||
@@ -1365,6 +1397,10 @@ impl AcpThreadView {
|
||||
|
||||
self.available_commands.replace(available_commands);
|
||||
}
|
||||
AcpThreadEvent::ModeUpdated(_mode) => {
|
||||
// The connection keeps track of the mode
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
@@ -2055,6 +2091,7 @@ impl AcpThreadView {
|
||||
acp::ToolKind::Execute => IconName::ToolTerminal,
|
||||
acp::ToolKind::Think => IconName::ToolThink,
|
||||
acp::ToolKind::Fetch => IconName::ToolWeb,
|
||||
acp::ToolKind::SwitchMode => IconName::ArrowRightLeft,
|
||||
acp::ToolKind::Other => IconName::ToolHammer,
|
||||
})
|
||||
}
|
||||
@@ -2105,59 +2142,67 @@ impl AcpThreadView {
|
||||
})
|
||||
};
|
||||
|
||||
let tool_output_display = if is_open {
|
||||
match &tool_call.status {
|
||||
ToolCallStatus::WaitingForConfirmation { options, .. } => v_flex()
|
||||
.w_full()
|
||||
.children(tool_call.content.iter().map(|content| {
|
||||
div()
|
||||
.child(self.render_tool_call_content(
|
||||
entry_ix,
|
||||
content,
|
||||
tool_call,
|
||||
use_card_layout,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.into_any_element()
|
||||
}))
|
||||
.child(self.render_permission_buttons(
|
||||
options,
|
||||
entry_ix,
|
||||
tool_call.id.clone(),
|
||||
cx,
|
||||
))
|
||||
.into_any(),
|
||||
ToolCallStatus::Pending | ToolCallStatus::InProgress
|
||||
if is_edit
|
||||
&& tool_call.content.is_empty()
|
||||
&& self.as_native_connection(cx).is_some() =>
|
||||
{
|
||||
self.render_diff_loading(cx).into_any()
|
||||
}
|
||||
ToolCallStatus::Pending
|
||||
| ToolCallStatus::InProgress
|
||||
| ToolCallStatus::Completed
|
||||
| ToolCallStatus::Failed
|
||||
| ToolCallStatus::Canceled => v_flex()
|
||||
.w_full()
|
||||
.children(tool_call.content.iter().map(|content| {
|
||||
div().child(self.render_tool_call_content(
|
||||
let tool_output_display =
|
||||
if is_open {
|
||||
match &tool_call.status {
|
||||
ToolCallStatus::WaitingForConfirmation { options, .. } => v_flex()
|
||||
.w_full()
|
||||
.children(tool_call.content.iter().enumerate().map(
|
||||
|(content_ix, content)| {
|
||||
div()
|
||||
.child(self.render_tool_call_content(
|
||||
entry_ix,
|
||||
content,
|
||||
content_ix,
|
||||
tool_call,
|
||||
use_card_layout,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.into_any_element()
|
||||
},
|
||||
))
|
||||
.child(self.render_permission_buttons(
|
||||
tool_call.kind,
|
||||
options,
|
||||
entry_ix,
|
||||
content,
|
||||
tool_call,
|
||||
use_card_layout,
|
||||
window,
|
||||
tool_call.id.clone(),
|
||||
cx,
|
||||
))
|
||||
}))
|
||||
.into_any(),
|
||||
ToolCallStatus::Rejected => Empty.into_any(),
|
||||
}
|
||||
.into()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
.into_any(),
|
||||
ToolCallStatus::Pending | ToolCallStatus::InProgress
|
||||
if is_edit
|
||||
&& tool_call.content.is_empty()
|
||||
&& self.as_native_connection(cx).is_some() =>
|
||||
{
|
||||
self.render_diff_loading(cx).into_any()
|
||||
}
|
||||
ToolCallStatus::Pending
|
||||
| ToolCallStatus::InProgress
|
||||
| ToolCallStatus::Completed
|
||||
| ToolCallStatus::Failed
|
||||
| ToolCallStatus::Canceled => v_flex()
|
||||
.w_full()
|
||||
.children(tool_call.content.iter().enumerate().map(
|
||||
|(content_ix, content)| {
|
||||
div().child(self.render_tool_call_content(
|
||||
entry_ix,
|
||||
content,
|
||||
content_ix,
|
||||
tool_call,
|
||||
use_card_layout,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
},
|
||||
))
|
||||
.into_any(),
|
||||
ToolCallStatus::Rejected => Empty.into_any(),
|
||||
}
|
||||
.into()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.map(|this| {
|
||||
@@ -2282,6 +2327,7 @@ impl AcpThreadView {
|
||||
&self,
|
||||
entry_ix: usize,
|
||||
content: &ToolCallContent,
|
||||
context_ix: usize,
|
||||
tool_call: &ToolCall,
|
||||
card_layout: bool,
|
||||
window: &Window,
|
||||
@@ -2295,6 +2341,7 @@ impl AcpThreadView {
|
||||
self.render_markdown_output(
|
||||
markdown.clone(),
|
||||
tool_call.id.clone(),
|
||||
context_ix,
|
||||
card_layout,
|
||||
window,
|
||||
cx,
|
||||
@@ -2314,6 +2361,7 @@ impl AcpThreadView {
|
||||
&self,
|
||||
markdown: Entity<Markdown>,
|
||||
tool_call_id: acp::ToolCallId,
|
||||
context_ix: usize,
|
||||
card_layout: bool,
|
||||
window: &Window,
|
||||
cx: &Context<Self>,
|
||||
@@ -2330,11 +2378,13 @@ impl AcpThreadView {
|
||||
.border_color(self.tool_card_border_color(cx))
|
||||
})
|
||||
.when(card_layout, |this| {
|
||||
this.p_2()
|
||||
.border_t_1()
|
||||
.border_color(self.tool_card_border_color(cx))
|
||||
this.px_2().pb_2().when(context_ix > 0, |this| {
|
||||
this.border_t_1()
|
||||
.pt_2()
|
||||
.border_color(self.tool_card_border_color(cx))
|
||||
})
|
||||
})
|
||||
.text_sm()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.child(self.render_markdown(markdown, default_markdown_style(false, false, window, cx)))
|
||||
.when(!card_layout, |this| {
|
||||
@@ -2415,6 +2465,7 @@ impl AcpThreadView {
|
||||
|
||||
fn render_permission_buttons(
|
||||
&self,
|
||||
kind: acp::ToolKind,
|
||||
options: &[acp::PermissionOption],
|
||||
entry_ix: usize,
|
||||
tool_call_id: acp::ToolCallId,
|
||||
@@ -2422,53 +2473,65 @@ impl AcpThreadView {
|
||||
) -> Div {
|
||||
h_flex()
|
||||
.py_1()
|
||||
.pl_2()
|
||||
.pr_1()
|
||||
.px_1()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.flex_wrap()
|
||||
.border_t_1()
|
||||
.border_color(self.tool_card_border_color(cx))
|
||||
.child(
|
||||
.when(kind != acp::ToolKind::SwitchMode, |this| {
|
||||
this.pl_2().child(
|
||||
div().min_w(rems_from_px(145.)).child(
|
||||
LoadingLabel::new("Waiting for Confirmation").size(LabelSize::Small),
|
||||
),
|
||||
)
|
||||
})
|
||||
.child({
|
||||
div()
|
||||
.min_w(rems_from_px(145.))
|
||||
.child(LoadingLabel::new("Waiting for Confirmation").size(LabelSize::Small)),
|
||||
)
|
||||
.child(h_flex().gap_0p5().children(options.iter().map(|option| {
|
||||
let option_id = SharedString::from(option.id.0.clone());
|
||||
Button::new((option_id, entry_ix), option.name.clone())
|
||||
.map(|this| match option.kind {
|
||||
acp::PermissionOptionKind::AllowOnce => {
|
||||
this.icon(IconName::Check).icon_color(Color::Success)
|
||||
}
|
||||
acp::PermissionOptionKind::AllowAlways => {
|
||||
this.icon(IconName::CheckDouble).icon_color(Color::Success)
|
||||
}
|
||||
acp::PermissionOptionKind::RejectOnce => {
|
||||
this.icon(IconName::Close).icon_color(Color::Error)
|
||||
}
|
||||
acp::PermissionOptionKind::RejectAlways => {
|
||||
this.icon(IconName::Close).icon_color(Color::Error)
|
||||
.map(|this| {
|
||||
if kind == acp::ToolKind::SwitchMode {
|
||||
this.w_full().v_flex()
|
||||
} else {
|
||||
this.h_flex()
|
||||
}
|
||||
})
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener({
|
||||
let tool_call_id = tool_call_id.clone();
|
||||
let option_id = option.id.clone();
|
||||
let option_kind = option.kind;
|
||||
move |this, _, window, cx| {
|
||||
this.authorize_tool_call(
|
||||
tool_call_id.clone(),
|
||||
option_id.clone(),
|
||||
option_kind,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
.gap_0p5()
|
||||
.children(options.iter().map(|option| {
|
||||
let option_id = SharedString::from(option.id.0.clone());
|
||||
Button::new((option_id, entry_ix), option.name.clone())
|
||||
.map(|this| match option.kind {
|
||||
acp::PermissionOptionKind::AllowOnce => {
|
||||
this.icon(IconName::Check).icon_color(Color::Success)
|
||||
}
|
||||
acp::PermissionOptionKind::AllowAlways => {
|
||||
this.icon(IconName::CheckDouble).icon_color(Color::Success)
|
||||
}
|
||||
acp::PermissionOptionKind::RejectOnce => {
|
||||
this.icon(IconName::Close).icon_color(Color::Error)
|
||||
}
|
||||
acp::PermissionOptionKind::RejectAlways => {
|
||||
this.icon(IconName::Close).icon_color(Color::Error)
|
||||
}
|
||||
})
|
||||
.icon_position(IconPosition::Start)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener({
|
||||
let tool_call_id = tool_call_id.clone();
|
||||
let option_id = option.id.clone();
|
||||
let option_kind = option.kind;
|
||||
move |this, _, window, cx| {
|
||||
this.authorize_tool_call(
|
||||
tool_call_id.clone(),
|
||||
option_id.clone(),
|
||||
option_kind,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}))
|
||||
}))
|
||||
})))
|
||||
})
|
||||
}
|
||||
|
||||
fn render_diff_loading(&self, cx: &Context<Self>) -> AnyElement {
|
||||
@@ -3729,6 +3792,15 @@ impl AcpThreadView {
|
||||
.on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {
|
||||
if let Some(profile_selector) = this.profile_selector.as_ref() {
|
||||
profile_selector.read(cx).menu_handle().toggle(window, cx);
|
||||
} else if let Some(mode_selector) = this.mode_selector() {
|
||||
mode_selector.read(cx).menu_handle().toggle(window, cx);
|
||||
}
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &CycleModeSelector, window, cx| {
|
||||
if let Some(mode_selector) = this.mode_selector() {
|
||||
mode_selector.update(cx, |mode_selector, cx| {
|
||||
mode_selector.cycle_mode(window, cx);
|
||||
});
|
||||
}
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {
|
||||
@@ -3795,6 +3867,7 @@ impl AcpThreadView {
|
||||
.gap_1()
|
||||
.children(self.render_token_usage(cx))
|
||||
.children(self.profile_selector.clone())
|
||||
.children(self.mode_selector().cloned())
|
||||
.children(self.model_selector.clone())
|
||||
.child(self.render_send_button(cx)),
|
||||
),
|
||||
|
||||
@@ -1322,6 +1322,7 @@ async fn open_new_agent_servers_entry_in_settings_editor(
|
||||
args: vec![],
|
||||
env: Some(HashMap::default()),
|
||||
},
|
||||
default_mode: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1529,7 +1529,8 @@ impl AgentDiff {
|
||||
| AcpThreadEvent::ToolAuthorizationRequired
|
||||
| AcpThreadEvent::PromptCapabilitiesUpdated
|
||||
| AcpThreadEvent::AvailableCommandsUpdated(_)
|
||||
| AcpThreadEvent::Retry(_) => {}
|
||||
| AcpThreadEvent::Retry(_)
|
||||
| AcpThreadEvent::ModeUpdated(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2717,10 +2717,13 @@ impl AgentPanel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.new_agent_thread(
|
||||
AgentType::Custom {
|
||||
name: agent_name
|
||||
.clone()
|
||||
.into(),
|
||||
command: custom_settings.get(&agent_name.0).map(|settings| settings.command.clone()).unwrap_or(placeholder_command())
|
||||
name: agent_name.clone().into(),
|
||||
command: custom_settings
|
||||
.get(&agent_name.0)
|
||||
.map(|settings| {
|
||||
settings.command.clone()
|
||||
})
|
||||
.unwrap_or(placeholder_command()),
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
|
||||
@@ -72,8 +72,10 @@ actions!(
|
||||
ToggleOptionsMenu,
|
||||
/// Deletes the recently opened thread from history.
|
||||
DeleteRecentlyOpenThread,
|
||||
/// Toggles the profile selector for switching between agent profiles.
|
||||
/// Toggles the profile or mode selector for switching between agent profiles.
|
||||
ToggleProfileSelector,
|
||||
/// Cycles through available session modes.
|
||||
CycleModeSelector,
|
||||
/// Removes all added context from the current conversation.
|
||||
RemoveAllContext,
|
||||
/// Expands the message editor to full size.
|
||||
|
||||
Reference in New Issue
Block a user