Detailed changes
@@ -306,6 +306,36 @@ impl NativeAgent {
}))
}
+ fn new_session(
+ &mut self,
+ project: Entity<Project>,
+ cx: &mut Context<Self>,
+ ) -> Entity<AcpThread> {
+ // Create Thread
+ // Fetch default model from registry settings
+ let registry = LanguageModelRegistry::read_global(cx);
+ // Log available models for debugging
+ let available_count = registry.available_models(cx).count();
+ log::debug!("Total available models: {}", available_count);
+
+ let default_model = registry.default_model().and_then(|default_model| {
+ self.models
+ .model_from_id(&LanguageModels::model_id(&default_model.model))
+ });
+ let thread = cx.new(|cx| {
+ Thread::new(
+ project.clone(),
+ self.project_context.clone(),
+ self.context_server_registry.clone(),
+ self.templates.clone(),
+ default_model,
+ cx,
+ )
+ });
+
+ self.register_session(thread, cx)
+ }
+
fn register_session(
&mut self,
thread_handle: Entity<Thread>,
@@ -1187,38 +1217,10 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
cwd: &Path,
cx: &mut App,
) -> Task<Result<Entity<acp_thread::AcpThread>>> {
- let agent = self.0.clone();
- log::debug!("Creating new thread for project at: {:?}", cwd);
-
- cx.spawn(async move |cx| {
- log::debug!("Starting thread creation in async context");
-
- // Create Thread
- let thread = agent.update(cx, |agent, cx| {
- // Fetch default model from registry settings
- let registry = LanguageModelRegistry::read_global(cx);
- // Log available models for debugging
- let available_count = registry.available_models(cx).count();
- log::debug!("Total available models: {}", available_count);
-
- let default_model = registry.default_model().and_then(|default_model| {
- agent
- .models
- .model_from_id(&LanguageModels::model_id(&default_model.model))
- });
- cx.new(|cx| {
- Thread::new(
- project.clone(),
- agent.project_context.clone(),
- agent.context_server_registry.clone(),
- agent.templates.clone(),
- default_model,
- cx,
- )
- })
- });
- Ok(agent.update(cx, |agent, cx| agent.register_session(thread, cx)))
- })
+ log::debug!("Creating new thread for project at: {cwd:?}");
+ Task::ready(Ok(self
+ .0
+ .update(cx, |agent, cx| agent.new_session(project, cx))))
}
fn supports_load_session(&self, _cx: &App) -> bool {
@@ -4293,19 +4293,8 @@ async fn test_subagent_tool_cancellation(cx: &mut TestAppContext) {
)
});
- let parent_tools: std::collections::BTreeMap<gpui::SharedString, Arc<dyn crate::AnyAgentTool>> =
- std::collections::BTreeMap::new();
-
#[allow(clippy::arc_with_non_send_sync)]
- let tool = Arc::new(SubagentTool::new(
- parent.downgrade(),
- project.clone(),
- project_context,
- context_server_registry,
- Templates::new(),
- 0,
- parent_tools,
- ));
+ let tool = Arc::new(SubagentTool::new(parent.downgrade(), 0));
let (event_stream, _rx, mut cancellation_tx) =
crate::ToolCallEventStream::test_with_cancellation();
@@ -4589,25 +4578,12 @@ async fn test_allowed_tools_rejects_unknown_tool(cx: &mut TestAppContext) {
thread
});
- let mut parent_tools: std::collections::BTreeMap<
- gpui::SharedString,
- Arc<dyn crate::AnyAgentTool>,
- > = std::collections::BTreeMap::new();
- parent_tools.insert("echo".into(), EchoTool.erase());
-
#[allow(clippy::arc_with_non_send_sync)]
- let tool = Arc::new(SubagentTool::new(
- parent.downgrade(),
- project,
- project_context,
- context_server_registry,
- Templates::new(),
- 0,
- parent_tools,
- ));
+ let tool = Arc::new(SubagentTool::new(parent.downgrade(), 0));
let allowed_tools = Some(vec!["nonexistent_tool".to_string()]);
- let result = tool.validate_allowed_tools(&allowed_tools);
+ let result = cx.read(|cx| tool.validate_allowed_tools(&allowed_tools, cx));
+
assert!(result.is_err(), "should reject unknown tool");
let err_msg = result.unwrap_err().to_string();
assert!(
@@ -4910,19 +4886,8 @@ async fn test_max_parallel_subagents_enforced(cx: &mut TestAppContext) {
);
});
- let parent_tools: std::collections::BTreeMap<gpui::SharedString, Arc<dyn crate::AnyAgentTool>> =
- std::collections::BTreeMap::new();
-
#[allow(clippy::arc_with_non_send_sync)]
- let tool = Arc::new(SubagentTool::new(
- parent.downgrade(),
- project.clone(),
- project_context,
- context_server_registry,
- Templates::new(),
- 0,
- parent_tools,
- ));
+ let tool = Arc::new(SubagentTool::new(parent.downgrade(), 0));
let (event_stream, _rx) = crate::ToolCallEventStream::test();
@@ -4983,22 +4948,8 @@ async fn test_subagent_tool_end_to_end(cx: &mut TestAppContext) {
thread
});
- let mut parent_tools: std::collections::BTreeMap<
- gpui::SharedString,
- Arc<dyn crate::AnyAgentTool>,
- > = std::collections::BTreeMap::new();
- parent_tools.insert("echo".into(), EchoTool.erase());
-
#[allow(clippy::arc_with_non_send_sync)]
- let tool = Arc::new(SubagentTool::new(
- parent.downgrade(),
- project.clone(),
- project_context,
- context_server_registry,
- Templates::new(),
- 0,
- parent_tools,
- ));
+ let tool = Arc::new(SubagentTool::new(parent.downgrade(), 0));
let (event_stream, _rx) = crate::ToolCallEventStream::test();
@@ -784,16 +784,16 @@ pub struct Thread {
/// Used to signal that the turn should end at the next message boundary.
has_queued_message: bool,
pending_message: Option<AgentMessage>,
- tools: BTreeMap<SharedString, Arc<dyn AnyAgentTool>>,
+ pub(crate) tools: BTreeMap<SharedString, Arc<dyn AnyAgentTool>>,
request_token_usage: HashMap<UserMessageId, language_model::TokenUsage>,
#[allow(unused)]
cumulative_token_usage: TokenUsage,
#[allow(unused)]
initial_project_snapshot: Shared<Task<Option<Arc<ProjectSnapshot>>>>,
- context_server_registry: Entity<ContextServerRegistry>,
+ pub(crate) context_server_registry: Entity<ContextServerRegistry>,
profile_id: AgentProfileId,
project_context: Entity<ProjectContext>,
- templates: Arc<Templates>,
+ pub(crate) templates: Arc<Templates>,
model: Option<Arc<dyn LanguageModel>>,
summarization_model: Option<Arc<dyn LanguageModel>>,
thinking_enabled: bool,
@@ -1280,16 +1280,7 @@ impl Thread {
self.add_tool(WebSearchTool);
if cx.has_flag::<SubagentsFeatureFlag>() && self.depth() < MAX_SUBAGENT_DEPTH {
- let parent_tools = self.tools.clone();
- self.add_tool(SubagentTool::new(
- cx.weak_entity(),
- self.project.clone(),
- self.project_context.clone(),
- self.context_server_registry.clone(),
- self.templates.clone(),
- self.depth(),
- parent_tools,
- ));
+ self.add_tool(SubagentTool::new(cx.weak_entity(), self.depth()));
}
}
@@ -7,7 +7,6 @@ use futures::{FutureExt, channel::mpsc};
use gpui::{App, AppContext, AsyncApp, Entity, SharedString, Task, WeakEntity};
use language_model::LanguageModelToolUseId;
use project::Project;
-use prompt_store::ProjectContext;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use smol::stream::StreamExt;
@@ -20,8 +19,8 @@ use util::ResultExt;
use watch;
use crate::{
- AgentTool, AnyAgentTool, ContextServerRegistry, MAX_PARALLEL_SUBAGENTS, MAX_SUBAGENT_DEPTH,
- SubagentContext, Templates, Thread, ThreadEvent, ToolCallAuthorization, ToolCallEventStream,
+ AgentTool, AnyAgentTool, MAX_PARALLEL_SUBAGENTS, MAX_SUBAGENT_DEPTH, SubagentContext, Thread,
+ ThreadEvent, ToolCallAuthorization, ToolCallEventStream,
};
/// When a subagent's remaining context window falls below this fraction (25%),
@@ -87,57 +86,38 @@ pub struct SubagentToolInput {
/// Tool that spawns a subagent thread to work on a task.
pub struct SubagentTool {
parent_thread: WeakEntity<Thread>,
- project: Entity<Project>,
- project_context: Entity<ProjectContext>,
- context_server_registry: Entity<ContextServerRegistry>,
- templates: Arc<Templates>,
current_depth: u8,
- /// The tools available to the parent thread, captured before SubagentTool was added.
- /// Subagents inherit from this set (or a subset via `allowed_tools` in the config).
- /// This is captured early so subagents don't get the subagent tool themselves.
- parent_tools: BTreeMap<SharedString, Arc<dyn AnyAgentTool>>,
}
impl SubagentTool {
- pub fn new(
- parent_thread: WeakEntity<Thread>,
- project: Entity<Project>,
- project_context: Entity<ProjectContext>,
- context_server_registry: Entity<ContextServerRegistry>,
- templates: Arc<Templates>,
- current_depth: u8,
- parent_tools: BTreeMap<SharedString, Arc<dyn AnyAgentTool>>,
- ) -> Self {
+ pub fn new(parent_thread: WeakEntity<Thread>, current_depth: u8) -> Self {
Self {
parent_thread,
- project,
- project_context,
- context_server_registry,
- templates,
current_depth,
- parent_tools,
}
}
- pub fn validate_allowed_tools(&self, allowed_tools: &Option<Vec<String>>) -> Result<()> {
- let Some(tools) = allowed_tools else {
+ pub fn validate_allowed_tools(
+ &self,
+ allowed_tools: &Option<Vec<String>>,
+ cx: &App,
+ ) -> Result<()> {
+ let Some(allowed_tools) = allowed_tools else {
return Ok(());
};
- let invalid_tools: Vec<&str> = tools
- .iter()
- .filter(|tool| !self.parent_tools.contains_key(tool.as_str()))
- .map(|s| s.as_str())
- .collect();
+ let invalid_tools: Vec<_> = self.parent_thread.read_with(cx, |thread, _cx| {
+ allowed_tools
+ .iter()
+ .filter(|tool| !thread.tools.contains_key(tool.as_str()))
+ .map(|s| format!("'{s}'"))
+ .collect()
+ })?;
if !invalid_tools.is_empty() {
return Err(anyhow!(
"The following tools do not exist: {}",
- invalid_tools
- .iter()
- .map(|t| format!("'{}'", t))
- .collect::<Vec<_>>()
- .join(", ")
+ invalid_tools.join(", ")
));
}
@@ -180,18 +160,19 @@ impl AgentTool for SubagentTool {
)));
}
- if let Err(e) = self.validate_allowed_tools(&input.allowed_tools) {
+ if let Err(e) = self.validate_allowed_tools(&input.allowed_tools, cx) {
return Task::ready(Err(e));
}
- let Some(parent_thread) = self.parent_thread.upgrade() else {
+ let Some(parent_thread_entity) = self.parent_thread.upgrade() else {
return Task::ready(Err(anyhow!(
"Parent thread no longer exists (subagent depth={})",
self.current_depth + 1
)));
};
+ let parent_thread = parent_thread_entity.read(cx);
- let running_count = parent_thread.read(cx).running_subagent_count();
+ let running_count = parent_thread.running_subagent_count();
if running_count >= MAX_PARALLEL_SUBAGENTS {
return Task::ready(Err(anyhow!(
"Maximum parallel subagents ({}) reached. Wait for existing subagents to complete.",
@@ -199,17 +180,17 @@ impl AgentTool for SubagentTool {
)));
}
- let parent_model = parent_thread.read(cx).model().cloned();
+ let parent_model = parent_thread.model().cloned();
let Some(model) = parent_model else {
return Task::ready(Err(anyhow!("No model configured")));
};
- let parent_thread_id = parent_thread.read(cx).id().clone();
- let project = self.project.clone();
- let project_context = self.project_context.clone();
- let context_server_registry = self.context_server_registry.clone();
- let templates = self.templates.clone();
- let parent_tools = self.parent_tools.clone();
+ let parent_thread_id = parent_thread.id().clone();
+ let project = parent_thread.project.clone();
+ let project_context = parent_thread.project_context().clone();
+ let context_server_registry = parent_thread.context_server_registry.clone();
+ let templates = parent_thread.templates.clone();
+ let parent_tools = parent_thread.tools.clone();
let current_depth = self.current_depth;
let parent_thread_weak = self.parent_thread.clone();
@@ -11,4 +11,4 @@ pub use mode_selector::ModeSelector;
pub use model_selector::AcpModelSelector;
pub use model_selector_popover::AcpModelSelectorPopover;
pub use thread_history::*;
-pub use thread_view::AcpThreadView;
+pub use thread_view::AcpServerView;
@@ -1,4 +1,4 @@
-use crate::acp::AcpThreadView;
+use crate::acp::AcpServerView;
use crate::{AgentPanel, RemoveHistory, RemoveSelectedThread};
use acp_thread::{AgentSessionInfo, AgentSessionList, AgentSessionListRequest, SessionListUpdate};
use agent_client_protocol as acp;
@@ -843,7 +843,7 @@ impl Render for AcpThreadHistory {
#[derive(IntoElement)]
pub struct AcpHistoryEntryElement {
entry: AgentSessionInfo,
- thread_view: WeakEntity<AcpThreadView>,
+ thread_view: WeakEntity<AcpServerView>,
selected: bool,
hovered: bool,
supports_delete: bool,
@@ -851,7 +851,7 @@ pub struct AcpHistoryEntryElement {
}
impl AcpHistoryEntryElement {
- pub fn new(entry: AgentSessionInfo, thread_view: WeakEntity<AcpThreadView>) -> Self {
+ pub fn new(entry: AgentSessionInfo, thread_view: WeakEntity<AcpServerView>) -> Self {
Self {
entry,
thread_view,
@@ -324,14 +324,14 @@ impl DiffStats {
}
}
-pub struct AcpThreadView {
+pub struct AcpServerView {
agent: Rc<dyn AgentServer>,
agent_server_store: Entity<AgentServerStore>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
thread_store: Option<Entity<ThreadStore>>,
prompt_store: Option<Entity<PromptStore>>,
- thread_state: ThreadState,
+ server_state: ServerState,
login: Option<task::SpawnInTerminal>, // is some <=> Active | Unauthenticated
recent_history_entries: Vec<AgentSessionInfo>,
history: Entity<AcpThreadHistory>,
@@ -343,34 +343,59 @@ pub struct AcpThreadView {
notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
slash_command_registry: Option<Entity<SlashCommandRegistry>>,
auth_task: Option<Task<()>>,
- _subscriptions: [Subscription; 4],
+ _subscriptions: Vec<Subscription>,
show_codex_windows_warning: bool,
in_flight_prompt: Option<Vec<acp::ContentBlock>>,
add_context_menu_handle: PopoverMenuHandle<ContextMenu>,
}
-impl AcpThreadView {
- pub fn as_active_thread(&self) -> Option<&ActiveThreadState> {
- match &self.thread_state {
- ThreadState::Active(active) => Some(active),
+impl AcpServerView {
+ pub fn as_active_thread(&self) -> Option<&AcpThreadView> {
+ match &self.server_state {
+ ServerState::Connected(connected) => Some(&connected.current),
_ => None,
}
}
- pub fn as_active_thread_mut(&mut self) -> Option<&mut ActiveThreadState> {
- match &mut self.thread_state {
- ThreadState::Active(active) => Some(active),
+ pub fn as_active_thread_mut(&mut self) -> Option<&mut AcpThreadView> {
+ match &mut self.server_state {
+ ServerState::Connected(connected) => Some(&mut connected.current),
+ _ => None,
+ }
+ }
+
+ pub fn as_connected(&self) -> Option<&ConnectedServerState> {
+ match &self.server_state {
+ ServerState::Connected(connected) => Some(connected),
+ _ => None,
+ }
+ }
+
+ pub fn as_connected_mut(&mut self) -> Option<&mut ConnectedServerState> {
+ match &mut self.server_state {
+ ServerState::Connected(connected) => Some(connected),
_ => None,
}
}
}
-enum ThreadState {
+enum ServerState {
Loading(Entity<LoadingView>),
- Active(ActiveThreadState),
LoadError(LoadError),
+ Connected(ConnectedServerState),
+}
+
+// current -> Entity
+// hashmap of threads, current becomes session_id
+pub struct ConnectedServerState {
+ auth_state: AuthState,
+ current: AcpThreadView,
+ connection: Rc<dyn AgentConnection>,
+}
+
+enum AuthState {
+ Ok,
Unauthenticated {
- connection: Rc<dyn AgentConnection>,
description: Option<Entity<Markdown>>,
configuration_view: Option<AnyView>,
pending_auth_method: Option<acp::AuthMethodId>,
@@ -378,13 +403,25 @@ enum ThreadState {
},
}
+impl AuthState {
+ pub fn is_ok(&self) -> bool {
+ matches!(self, Self::Ok)
+ }
+}
+
struct LoadingView {
title: SharedString,
_load_task: Task<()>,
_update_title_task: Task<anyhow::Result<()>>,
}
-impl AcpThreadView {
+impl ConnectedServerState {
+ pub fn has_thread_error(&self) -> bool {
+ self.current.thread_error.is_some()
+ }
+}
+
+impl AcpServerView {
pub fn new(
agent: Rc<dyn AgentServer>,
resume_thread: Option<AgentSessionInfo>,
@@ -447,7 +484,7 @@ impl AcpThreadView {
editor
});
- let subscriptions = [
+ let subscriptions = vec![
cx.observe_global_in::<SettingsStore>(window, Self::agent_ui_font_size_changed),
cx.observe_global_in::<AgentFontSize>(window, Self::agent_ui_font_size_changed),
cx.subscribe_in(&message_editor, window, Self::handle_message_editor_event),
@@ -524,7 +561,7 @@ impl AcpThreadView {
project: project.clone(),
thread_store,
prompt_store,
- thread_state: Self::initial_state(
+ server_state: Self::initial_state(
agent.clone(),
resume_thread,
workspace.clone(),
@@ -560,15 +597,9 @@ impl AcpThreadView {
let cached_user_commands = Rc::new(RefCell::new(collections::HashMap::default()));
let cached_user_command_errors = Rc::new(RefCell::new(Vec::new()));
- let resume_thread_metadata = if let ThreadState::Active(ActiveThreadState {
- resume_thread_metadata,
- ..
- }) = &self.thread_state
- {
- resume_thread_metadata.clone()
- } else {
- None
- };
+ let resume_thread_metadata = self
+ .as_active_thread()
+ .and_then(|thread| thread.resume_thread_metadata.clone());
self.message_editor.update(cx, |editor, cx| {
editor.set_command_state(
@@ -580,7 +611,7 @@ impl AcpThreadView {
);
});
- self.thread_state = Self::initial_state(
+ self.server_state = Self::initial_state(
self.agent.clone(),
resume_thread_metadata,
self.workspace.clone(),
@@ -608,11 +639,11 @@ impl AcpThreadView {
cached_user_command_errors: Rc<RefCell<Vec<CommandLoadError>>>,
window: &mut Window,
cx: &mut Context<Self>,
- ) -> ThreadState {
+ ) -> ServerState {
if project.read(cx).is_via_collab()
&& agent.clone().downcast::<NativeAgentServer>().is_none()
{
- return ThreadState::LoadError(LoadError::Other(
+ return ServerState::LoadError(LoadError::Other(
"External agents are not yet supported in shared projects.".into(),
));
}
@@ -715,7 +746,7 @@ impl AcpThreadView {
Err(e) => match e.downcast::<acp_thread::AuthRequired>() {
Ok(err) => {
cx.update(|window, cx| {
- Self::handle_auth_required(this, err, agent, connection, window, cx)
+ Self::handle_auth_required(this, err, agent.name(), window, cx)
})
.log_err();
return;
@@ -869,24 +900,29 @@ impl AcpThreadView {
})
});
- this.thread_state = ThreadState::Active(ActiveThreadState::new(
- thread,
- workspace.clone(),
- entry_view_state,
- title_editor,
- config_options_view,
- mode_selector,
- model_selector,
- profile_selector,
- list_state,
- prompt_capabilities,
- available_commands,
- cached_user_commands,
- cached_user_command_errors,
- resumed_without_history,
- resume_thread.clone(),
- subscriptions,
- ));
+ this.server_state = ServerState::Connected(ConnectedServerState {
+ connection,
+ auth_state: AuthState::Ok,
+ current: AcpThreadView::new(
+ thread,
+ workspace.clone(),
+ entry_view_state,
+ title_editor,
+ config_options_view,
+ mode_selector,
+ model_selector,
+ profile_selector,
+ list_state,
+ prompt_capabilities,
+ available_commands,
+ cached_user_commands,
+ cached_user_command_errors,
+ resumed_without_history,
+ resume_thread.clone(),
+ subscriptions,
+ cx,
+ ),
+ });
if this.focus_handle.contains_focused(window, cx) {
this.message_editor.focus_handle(cx).focus(window, cx);
@@ -906,12 +942,8 @@ impl AcpThreadView {
while let Ok(new_version) = new_version_available_rx.recv().await {
if let Some(new_version) = new_version {
this.update(cx, |this, cx| {
- if let ThreadState::Active(ActiveThreadState {
- new_server_version_available,
- ..
- }) = &mut this.thread_state
- {
- *new_server_version_available = Some(new_version.into());
+ if let Some(thread) = this.as_active_thread_mut() {
+ thread.new_server_version_available = Some(new_version.into());
}
cx.notify();
})
@@ -939,18 +971,16 @@ impl AcpThreadView {
}
});
- ThreadState::Loading(loading_view)
+ ServerState::Loading(loading_view)
}
fn handle_auth_required(
this: WeakEntity<Self>,
err: AuthRequired,
- agent: Rc<dyn AgentServer>,
- connection: Rc<dyn AgentConnection>,
+ agent_name: SharedString,
window: &mut Window,
cx: &mut App,
) {
- let agent_name = agent.name();
let (configuration_view, subscription) = if let Some(provider_id) = &err.provider_id {
let registry = LanguageModelRegistry::global(cx);
@@ -975,7 +1005,7 @@ impl AcpThreadView {
let view = registry.read(cx).provider(&provider_id).map(|provider| {
provider.configuration_view(
- language_model::ConfigurationViewTargetAgent::Other(agent_name.clone()),
+ language_model::ConfigurationViewTargetAgent::Other(agent_name),
window,
cx,
)
@@ -987,15 +1017,18 @@ impl AcpThreadView {
};
this.update(cx, |this, cx| {
- this.thread_state = ThreadState::Unauthenticated {
- pending_auth_method: None,
- connection,
- configuration_view,
- description: err
+ if let Some(connected) = this.as_connected_mut() {
+ let description = err
.description
- .map(|desc| cx.new(|cx| Markdown::new(desc.into(), None, None, cx))),
- _subscription: subscription,
- };
+ .map(|desc| cx.new(|cx| Markdown::new(desc.into(), None, None, cx)));
+
+ connected.auth_state = AuthState::Unauthenticated {
+ pending_auth_method: None,
+ configuration_view,
+ description,
+ _subscription: subscription,
+ };
+ }
if this.message_editor.focus_handle(cx).is_focused(window) {
this.focus_handle.focus(window, cx)
}
@@ -1011,10 +1044,10 @@ impl AcpThreadView {
cx: &mut Context<Self>,
) {
if let Some(load_err) = err.downcast_ref::<LoadError>() {
- self.thread_state = ThreadState::LoadError(load_err.clone());
+ self.server_state = ServerState::LoadError(load_err.clone());
} else {
- self.thread_state =
- ThreadState::LoadError(LoadError::Other(format!("{:#}", err).into()))
+ self.server_state =
+ ServerState::LoadError(LoadError::Other(format!("{:#}", err).into()))
}
if self.message_editor.focus_handle(cx).is_focused(window) {
self.focus_handle.focus(window, cx)
@@ -1032,13 +1065,12 @@ impl AcpThreadView {
// If we're in a LoadError state OR have a thread_error set (which can happen
// when agent.connect() fails during loading), retry loading the thread.
// This handles the case where a thread is restored before authentication completes.
- let should_retry = match &self.thread_state {
- ThreadState::LoadError(_)
- | ThreadState::Active(ActiveThreadState {
- thread_error: Some(_),
- ..
- }) => true,
- _ => false,
+ let should_retry = match &self.server_state {
+ ServerState::Loading(_) => false,
+ ServerState::LoadError(_) => true,
+ ServerState::Connected(connected) => {
+ connected.auth_state.is_ok() && connected.has_thread_error()
+ }
};
if should_retry {
@@ -1055,12 +1087,10 @@ impl AcpThreadView {
}
pub fn title(&self, cx: &App) -> SharedString {
- match &self.thread_state {
- ThreadState::Active(ActiveThreadState { .. }) | ThreadState::Unauthenticated { .. } => {
- "New Thread".into()
- }
- ThreadState::Loading(loading_view) => loading_view.read(cx).title.clone(),
- ThreadState::LoadError(error) => match error {
+ match &self.server_state {
+ ServerState::Connected(_) => "New Thread".into(),
+ ServerState::Loading(loading_view) => loading_view.read(cx).title.clone(),
+ ServerState::LoadError(error) => match error {
LoadError::Unsupported { .. } => format!("Upgrade {}", self.agent.name()).into(),
LoadError::FailedToInstall(_) => {
format!("Failed to Install {}", self.agent.name()).into()
@@ -1320,7 +1350,7 @@ impl AcpThreadView {
}
pub fn is_loading(&self) -> bool {
- matches!(self.thread_state, ThreadState::Loading { .. })
+ matches!(self.server_state, ServerState::Loading { .. })
}
fn retry_generation(&mut self, cx: &mut Context<Self>) {
@@ -1331,11 +1361,11 @@ impl AcpThreadView {
fn send(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let message_editor = self.message_editor.clone();
- let agent = self.agent.clone();
let login = self.login.clone();
+ let agent_name = self.agent.name();
if let Some(active) = self.as_active_thread_mut() {
- active.send(message_editor, agent, login, window, cx);
+ active.send(message_editor, agent_name, login, window, cx);
}
}
@@ -1390,28 +1420,16 @@ impl AcpThreadView {
)
});
- if let ThreadState::Active(ActiveThreadState {
- thread_error,
- thread_feedback,
- editing_message,
- ..
- }) = &mut self.thread_state
- {
- thread_error.take();
- thread_feedback.clear();
- editing_message.take();
- }
+ if let Some(thread) = self.as_active_thread_mut() {
+ thread.thread_error.take();
+ thread.thread_feedback.clear();
+ thread.editing_message.take();
- if let ThreadState::Active(ActiveThreadState {
- should_be_following: true,
- ..
- }) = &self.thread_state
- {
- self.workspace
- .update(cx, |workspace, cx| {
+ if thread.should_be_following {
+ let _ = self.workspace.update(cx, |workspace, cx| {
workspace.follow(CollaboratorId::Agent, window, cx);
- })
- .ok();
+ });
+ }
}
let contents_task = cx.spawn_in(window, async move |this, cx| {
@@ -1495,9 +1513,8 @@ impl AcpThreadView {
fn handle_thread_error(&mut self, error: anyhow::Error, cx: &mut Context<Self>) {
let error = ThreadError::from_err(error, &self.agent);
self.emit_thread_error_telemetry(&error, cx);
- if let ThreadState::Active(ActiveThreadState { thread_error, .. }) = &mut self.thread_state
- {
- *thread_error = Some(error);
+ if let Some(thread) = self.as_active_thread_mut() {
+ thread.thread_error = Some(error);
}
cx.notify();
}
@@ -1673,7 +1690,7 @@ impl AcpThreadView {
);
}
AcpThreadEvent::LoadError(error) => {
- self.thread_state = ThreadState::LoadError(error.clone());
+ self.server_state = ServerState::LoadError(error.clone());
if self.message_editor.focus_handle(cx).is_focused(window) {
self.focus_handle.focus(window, cx)
}
@@ -1752,126 +1769,133 @@ impl AcpThreadView {
window: &mut Window,
cx: &mut Context<Self>,
) {
- let ThreadState::Unauthenticated {
- connection,
- pending_auth_method,
+ let Some(connected) = self.as_connected_mut() else {
+ return;
+ };
+ let connection = connected.connection.clone();
+
+ let AuthState::Unauthenticated {
configuration_view,
+ pending_auth_method,
..
- } = &mut self.thread_state
+ } = &mut connected.auth_state
else {
return;
};
+
let agent_telemetry_id = connection.telemetry_id();
// Check for the experimental "terminal-auth" _meta field
let auth_method = connection.auth_methods().iter().find(|m| m.id == method);
- if let Some(auth_method) = auth_method {
- if let Some(meta) = &auth_method.meta {
- if let Some(terminal_auth) = meta.get("terminal-auth") {
- // Extract terminal auth details from meta
- if let (Some(command), Some(label)) = (
- terminal_auth.get("command").and_then(|v| v.as_str()),
- terminal_auth.get("label").and_then(|v| v.as_str()),
- ) {
- let args = terminal_auth
- .get("args")
- .and_then(|v| v.as_array())
- .map(|arr| {
- arr.iter()
- .filter_map(|v| v.as_str().map(String::from))
- .collect()
- })
- .unwrap_or_default();
-
- let env = terminal_auth
- .get("env")
- .and_then(|v| v.as_object())
- .map(|obj| {
- obj.iter()
- .filter_map(|(k, v)| {
- v.as_str().map(|val| (k.clone(), val.to_string()))
- })
- .collect::<HashMap<String, String>>()
- })
- .unwrap_or_default();
-
- // Run SpawnInTerminal in the same dir as the ACP server
- let cwd = connection
- .clone()
- .downcast::<agent_servers::AcpConnection>()
- .map(|acp_conn| acp_conn.root_dir().to_path_buf());
-
- // Build SpawnInTerminal from _meta
- let login = task::SpawnInTerminal {
- id: task::TaskId(format!("external-agent-{}-login", label)),
- full_label: label.to_string(),
- label: label.to_string(),
- command: Some(command.to_string()),
- args,
- command_label: label.to_string(),
- cwd,
- env,
- use_new_terminal: true,
- allow_concurrent_runs: true,
- hide: task::HideStrategy::Always,
- ..Default::default()
- };
+ if let Some(terminal_auth) = auth_method
+ .and_then(|a| a.meta.as_ref())
+ .and_then(|m| m.get("terminal-auth"))
+ {
+ // Extract terminal auth details from meta
+ if let (Some(command), Some(label)) = (
+ terminal_auth.get("command").and_then(|v| v.as_str()),
+ terminal_auth.get("label").and_then(|v| v.as_str()),
+ ) {
+ let args = terminal_auth
+ .get("args")
+ .and_then(|v| v.as_array())
+ .map(|arr| {
+ arr.iter()
+ .filter_map(|v| v.as_str().map(String::from))
+ .collect()
+ })
+ .unwrap_or_default();
+
+ let env = terminal_auth
+ .get("env")
+ .and_then(|v| v.as_object())
+ .map(|obj| {
+ obj.iter()
+ .filter_map(|(k, v)| v.as_str().map(|val| (k.clone(), val.to_string())))
+ .collect::<HashMap<String, String>>()
+ })
+ .unwrap_or_default();
+
+ // Run SpawnInTerminal in the same dir as the ACP server
+ let cwd = connected
+ .connection
+ .clone()
+ .downcast::<agent_servers::AcpConnection>()
+ .map(|acp_conn| acp_conn.root_dir().to_path_buf());
+
+ // Build SpawnInTerminal from _meta
+ let login = task::SpawnInTerminal {
+ id: task::TaskId(format!("external-agent-{}-login", label)),
+ full_label: label.to_string(),
+ label: label.to_string(),
+ command: Some(command.to_string()),
+ args,
+ command_label: label.to_string(),
+ cwd,
+ env,
+ use_new_terminal: true,
+ allow_concurrent_runs: true,
+ hide: task::HideStrategy::Always,
+ ..Default::default()
+ };
- configuration_view.take();
- pending_auth_method.replace(method.clone());
-
- if let Some(workspace) = self.workspace.upgrade() {
- let project = self.project.clone();
- let authenticate = Self::spawn_external_agent_login(
- login,
- workspace,
- project,
- method.clone(),
- false,
- window,
- cx,
- );
- cx.notify();
- self.auth_task = Some(cx.spawn_in(window, {
- async move |this, cx| {
- let result = authenticate.await;
-
- match &result {
- Ok(_) => telemetry::event!(
- "Authenticate Agent Succeeded",
- agent = agent_telemetry_id
- ),
- Err(_) => {
- telemetry::event!(
- "Authenticate Agent Failed",
- agent = agent_telemetry_id,
- )
- }
- }
+ configuration_view.take();
+ pending_auth_method.replace(method.clone());
+
+ if let Some(workspace) = self.workspace.upgrade() {
+ let project = self.project.clone();
+ let authenticate = Self::spawn_external_agent_login(
+ login,
+ workspace,
+ project,
+ method.clone(),
+ false,
+ window,
+ cx,
+ );
+ cx.notify();
+ self.auth_task = Some(cx.spawn_in(window, {
+ async move |this, cx| {
+ let result = authenticate.await;
+
+ match &result {
+ Ok(_) => telemetry::event!(
+ "Authenticate Agent Succeeded",
+ agent = agent_telemetry_id
+ ),
+ Err(_) => {
+ telemetry::event!(
+ "Authenticate Agent Failed",
+ agent = agent_telemetry_id,
+ )
+ }
+ }
- this.update_in(cx, |this, window, cx| {
- if let Err(err) = result {
- if let ThreadState::Unauthenticated {
+ this.update_in(cx, |this, window, cx| {
+ if let Err(err) = result {
+ if let Some(ConnectedServerState {
+ auth_state:
+ AuthState::Unauthenticated {
pending_auth_method,
..
- } = &mut this.thread_state
- {
- pending_auth_method.take();
- }
- this.handle_thread_error(err, cx);
- } else {
- this.reset(window, cx);
- }
- this.auth_task.take()
- })
- .ok();
+ },
+ ..
+ }) = this.as_connected_mut()
+ {
+ pending_auth_method.take();
+ }
+ this.handle_thread_error(err, cx);
+ } else {
+ this.reset(window, cx);
}
- }));
+ this.auth_task.take()
+ })
+ .ok();
}
- return;
- }
+ }));
}
+ return;
}
}
@@ -1883,8 +1907,7 @@ impl AcpThreadView {
.unwrap();
if !provider.is_authenticated(cx) {
let this = cx.weak_entity();
- let agent = self.agent.clone();
- let connection = connection.clone();
+ let agent_name = self.agent.name();
window.defer(cx, |window, cx| {
Self::handle_auth_required(
this,
@@ -1892,8 +1915,7 @@ impl AcpThreadView {
description: Some("GEMINI_API_KEY must be set".to_owned()),
provider_id: Some(language_model::GOOGLE_PROVIDER_ID),
},
- agent,
- connection,
+ agent_name,
window,
cx,
);
@@ -1906,8 +1928,7 @@ impl AcpThreadView {
|| (std::env::var("GOOGLE_CLOUD_PROJECT").is_err()))
{
let this = cx.weak_entity();
- let agent = self.agent.clone();
- let connection = connection.clone();
+ let agent_name = self.agent.name();
window.defer(cx, |window, cx| {
Self::handle_auth_required(
@@ -1919,8 +1940,7 @@ impl AcpThreadView {
),
provider_id: None,
},
- agent,
- connection,
+ agent_name,
window,
cx,
)
@@ -1965,10 +1985,14 @@ impl AcpThreadView {
this.update_in(cx, |this, window, cx| {
if let Err(err) = result {
- if let ThreadState::Unauthenticated {
- pending_auth_method,
+ if let Some(ConnectedServerState {
+ auth_state:
+ AuthState::Unauthenticated {
+ pending_auth_method,
+ ..
+ },
..
- } = &mut this.thread_state
+ }) = this.as_connected_mut()
{
pending_auth_method.take();
}
@@ -2282,7 +2306,7 @@ impl AcpThreadView {
.bg(cx.theme().colors().editor_background)
.overflow_hidden();
- let is_loading_contents = matches!(&self.thread_state, ThreadState::Active(ActiveThreadState { is_loading_contents: true, .. }));
+ let is_loading_contents = matches!(&self.server_state, ServerState::Connected(ConnectedServerState { current: AcpThreadView { is_loading_contents: true, .. }, ..}));
if message.id.is_some() {
this.child(
base_container
@@ -2653,7 +2677,7 @@ impl AcpThreadView {
let key = (entry_ix, chunk_ix);
- let is_open = matches!(&self.thread_state, ThreadState::Active(ActiveThreadState { expanded_thinking_blocks, .. }) if expanded_thinking_blocks.contains(&key));
+ let is_open = matches!(&self.server_state, ServerState::Connected(ConnectedServerState {current: AcpThreadView { expanded_thinking_blocks, .. }, ..}) if expanded_thinking_blocks.contains(&key));
let scroll_handle = self
.as_active_thread()
@@ -2798,8 +2822,14 @@ impl AcpThreadView {
let has_image_content = tool_call.content.iter().any(|c| c.image().is_some());
let is_collapsible = !tool_call.content.is_empty() && !needs_confirmation;
- let is_open = needs_confirmation
- || matches!(&self.thread_state, ThreadState::Active(ActiveThreadState { expanded_tool_calls, .. }) if expanded_tool_calls.contains(&tool_call.id));
+ let mut is_open = match &self.server_state {
+ ServerState::Connected(ConnectedServerState { current, .. }) => {
+ current.expanded_tool_calls.contains(&tool_call.id)
+ }
+ _ => false,
+ };
+
+ is_open |= needs_confirmation;
let should_show_raw_input = !is_terminal_tool && !is_edit && !has_image_content;
@@ -2837,7 +2867,7 @@ impl AcpThreadView {
)
.when(should_show_raw_input, |this| {
let is_raw_input_expanded =
- matches!(&self.thread_state, ThreadState::Active(ActiveThreadState { expanded_tool_call_raw_inputs, .. }) if expanded_tool_call_raw_inputs.contains(&tool_call.id));
+ matches!(&self.server_state, ServerState::Connected(ConnectedServerState {current: AcpThreadView { expanded_tool_call_raw_inputs, .. }, ..}) if expanded_tool_call_raw_inputs.contains(&tool_call.id));
let input_header = if is_raw_input_expanded {
"Raw Input:"
@@ -3076,7 +3106,7 @@ impl AcpThreadView {
})
.when_some(diff_for_discard, |this, diff| {
let tool_call_id = tool_call.id.clone();
- let is_discarded = matches!(&self.thread_state, ThreadState::Active(ActiveThreadState { discarded_partial_edits, .. }) if discarded_partial_edits.contains(&tool_call_id));
+ let is_discarded = matches!(&self.server_state, ServerState::Connected(ConnectedServerState{current: AcpThreadView { discarded_partial_edits, .. }, ..}) if discarded_partial_edits.contains(&tool_call_id));
this.when(!is_discarded, |this| {
this.child(
IconButton::new(
@@ -3577,19 +3607,17 @@ impl AcpThreadView {
}
});
- let scroll_handle = if let ThreadState::Active(ActiveThreadState {
- subagent_scroll_handles,
- ..
- }) = &self.thread_state
- {
- subagent_scroll_handles
- .borrow_mut()
- .entry(session_id.clone())
- .or_default()
- .clone()
- } else {
- ScrollHandle::default()
- };
+ let scroll_handle = self
+ .as_active_thread()
+ .map(|state| {
+ state
+ .subagent_scroll_handles
+ .borrow_mut()
+ .entry(session_id.clone())
+ .or_default()
+ .clone()
+ })
+ .unwrap_or_default();
scroll_handle.scroll_to_bottom();
let editor_bg = cx.theme().colors().editor_background;
@@ -3979,12 +4007,9 @@ impl AcpThreadView {
.map(|(i, choice)| (i, choice.label()))
.collect();
- let permission_dropdown_handle = match &self.thread_state {
- ThreadState::Active(ActiveThreadState {
- permission_dropdown_handle,
- ..
- }) => permission_dropdown_handle.clone(),
- _ => return div().into_any_element(),
+ let permission_dropdown_handle = match self.as_active_thread() {
+ Some(thread) => thread.permission_dropdown_handle.clone(),
+ None => return div().into_any_element(),
};
PopoverMenu::new(("permission-granularity", entry_ix))
@@ -4408,11 +4433,8 @@ impl AcpThreadView {
.map(|(i, choice)| (i, choice.label()))
.collect();
- let permission_dropdown_handle = match &self.thread_state {
- ThreadState::Active(ActiveThreadState {
- permission_dropdown_handle,
- ..
- }) => permission_dropdown_handle.clone(),
+ let permission_dropdown_handle = match self.as_active_thread() {
+ Some(thread) => thread.permission_dropdown_handle.clone(),
_ => return div().into_any_element(),
};
@@ -4671,7 +4693,9 @@ impl AcpThreadView {
let command_element =
self.render_collapsible_command(false, command_content, &tool_call.id, cx);
- let is_expanded = matches!(&self.thread_state, ThreadState::Active(ActiveThreadState { expanded_tool_calls, .. }) if expanded_tool_calls.contains(&tool_call.id));
+ let is_expanded = self
+ .as_connected()
+ .is_some_and(|c| c.current.expanded_tool_calls.contains(&tool_call.id));
let header = h_flex()
.id(header_id)
@@ -5896,10 +5920,12 @@ impl AcpThreadView {
.label_size(LabelSize::XSmall),
)
.when(
- !matches!(
- &self.thread_state,
- ThreadState::Active(ActiveThreadState { hovered_edited_file_buttons: Some(i), .. }) if *i == index
- ),
+ match self.as_active_thread() {
+ Some(thread) => {
+ thread.hovered_edited_file_buttons == Some(index)
+ }
+ None => false,
+ },
|this| {
let full_path = full_path.clone();
this.hover(|s| s.bg(cx.theme().colors().element_hover))
@@ -5991,13 +6017,10 @@ impl AcpThreadView {
let message_editor = self.message_editor.read(cx);
let focus_handle = message_editor.focus_handle(cx);
- let queued_message_editors = match &self.thread_state {
- ThreadState::Active(ActiveThreadState {
- queued_message_editors,
- ..
- }) => queued_message_editors.as_slice(),
- _ => &[],
- };
+ let queued_message_editors = self
+ .as_connected()
+ .map(|c| c.current.queued_message_editors.as_slice())
+ .unwrap_or(&[]);
let queue_len = queued_message_editors.len();
let can_fast_track = if let Some(active) = self.as_active_thread() {
@@ -6222,12 +6245,9 @@ impl AcpThreadView {
.opacity(0.8)
.block_mouse_except_scroll();
- let enable_editor = match self.thread_state {
- ThreadState::Active(ActiveThreadState { .. }) => true,
- ThreadState::Loading { .. }
- | ThreadState::Unauthenticated { .. }
- | ThreadState::LoadError(..) => false,
- };
+ let enable_editor = self
+ .as_connected()
+ .is_some_and(|conn| conn.auth_state.is_ok());
v_flex()
.on_action(cx.listener(Self::expand_message_editor))
@@ -6326,13 +6346,9 @@ impl AcpThreadView {
}
fn queued_messages_len(&self) -> usize {
- match &self.thread_state {
- ThreadState::Active(ActiveThreadState {
- local_queued_messages,
- ..
- }) => local_queued_messages.len(),
- _ => 0,
- }
+ self.as_active_thread()
+ .map(|thread| thread.local_queued_messages.len())
+ .unwrap_or_default()
}
fn has_queued_messages(&self) -> bool {
@@ -6376,18 +6392,15 @@ impl AcpThreadView {
tracked_buffers: Vec<Entity<Buffer>>,
_cx: &mut Context<Self>,
) -> bool {
- match &mut self.thread_state {
- ThreadState::Active(ActiveThreadState {
- local_queued_messages,
- ..
- }) if index < local_queued_messages.len() => {
- local_queued_messages[index] = QueuedMessage {
+ match self.as_active_thread_mut() {
+ Some(thread) if index < thread.local_queued_messages.len() => {
+ thread.local_queued_messages[index] = QueuedMessage {
content,
tracked_buffers,
};
true
}
- _ => false,
+ Some(_) | None => false,
}
}
@@ -6399,25 +6412,20 @@ impl AcpThreadView {
}
fn queued_message_contents(&self) -> Vec<Vec<acp::ContentBlock>> {
- match &self.thread_state {
- ThreadState::Active(ActiveThreadState {
- local_queued_messages,
- ..
- }) => local_queued_messages
+ match self.as_active_thread() {
+ None => Vec::new(),
+ Some(thread) => thread
+ .local_queued_messages
.iter()
.map(|q| q.content.clone())
.collect(),
- _ => Vec::new(),
}
}
fn save_queued_message_at_index(&mut self, index: usize, cx: &mut Context<Self>) {
- let editor = match &self.thread_state {
- ThreadState::Active(ActiveThreadState {
- queued_message_editors,
- ..
- }) => queued_message_editors.get(index).cloned(),
- _ => None,
+ let editor = match self.as_active_thread() {
+ Some(thread) => thread.queued_message_editors.get(index).cloned(),
+ None => None,
};
let Some(editor) = editor else {
return;
@@ -6444,31 +6452,31 @@ impl AcpThreadView {
let needed_count = self.queued_messages_len();
let queued_messages = self.queued_message_contents();
- let ThreadState::Active(ActiveThreadState {
- queued_message_editors,
- queued_message_editor_subscriptions,
- last_synced_queue_length,
- prompt_capabilities,
- available_commands,
- ..
- }) = &mut self.thread_state
- else {
+ let agent_name = self.agent.name();
+ let workspace = self.workspace.clone();
+ let project = self.project.downgrade();
+ let history = self.history.downgrade();
+ let message_editor = self.message_editor.clone();
+
+ let Some(thread) = self.as_active_thread_mut() else {
return;
};
- let prompt_capabilities = prompt_capabilities.clone();
- let available_commands = available_commands.clone();
+ let prompt_capabilities = thread.prompt_capabilities.clone();
+ let available_commands = thread.available_commands.clone();
- let current_count = queued_message_editors.len();
+ let current_count = thread.queued_message_editors.len();
- if current_count == needed_count && needed_count == *last_synced_queue_length {
+ if current_count == needed_count && needed_count == thread.last_synced_queue_length {
return;
}
if current_count > needed_count {
- queued_message_editors.truncate(needed_count);
- queued_message_editor_subscriptions.truncate(needed_count);
+ thread.queued_message_editors.truncate(needed_count);
+ thread
+ .queued_message_editor_subscriptions
+ .truncate(needed_count);
- for (index, editor) in queued_message_editors.iter().enumerate() {
+ for (index, editor) in thread.queued_message_editors.iter().enumerate() {
if let Some(content) = queued_messages.get(index) {
editor.update(cx, |editor, cx| {
editor.set_message(content.clone(), window, cx);
@@ -1,6 +1,7 @@
use super::*;
-pub struct ActiveThreadState {
+pub struct AcpThreadView {
+ pub id: acp::SessionId,
pub thread: Entity<AcpThread>,
pub workspace: WeakEntity<Workspace>,
pub entry_view_state: Entity<EntryViewState>,
@@ -66,7 +67,7 @@ pub struct TurnFields {
pub turn_tokens: Option<u64>,
}
-impl ActiveThreadState {
+impl AcpThreadView {
pub fn new(
thread: Entity<AcpThread>,
workspace: WeakEntity<Workspace>,
@@ -84,8 +85,11 @@ impl ActiveThreadState {
resumed_without_history: bool,
resume_thread_metadata: Option<AgentSessionInfo>,
subscriptions: Vec<Subscription>,
+ cx: &App,
) -> Self {
+ let id = thread.read(cx).session_id().clone();
Self {
+ id,
thread,
workspace,
entry_view_state,
@@ -179,7 +183,7 @@ impl ActiveThreadState {
// turns
- pub fn start_turn(&mut self, cx: &mut Context<AcpThreadView>) -> usize {
+ pub fn start_turn(&mut self, cx: &mut Context<AcpServerView>) -> usize {
self.turn_fields.turn_generation += 1;
let generation = self.turn_fields.turn_generation;
self.turn_fields.turn_started_at = Some(Instant::now());
@@ -223,10 +227,10 @@ impl ActiveThreadState {
pub fn send(
&mut self,
message_editor: Entity<MessageEditor>,
- agent: Rc<dyn AgentServer>,
+ agent_name: SharedString,
login: Option<task::SpawnInTerminal>,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
let thread = &self.thread;
@@ -269,13 +273,11 @@ impl ActiveThreadState {
message_editor.update(cx, |editor, cx| editor.clear(window, cx));
let this = cx.weak_entity();
- let agent = agent.clone();
window.defer(cx, |window, cx| {
- AcpThreadView::handle_auth_required(
+ AcpServerView::handle_auth_required(
this,
AuthRequired::new(),
- agent,
- connection,
+ agent_name,
window,
cx,
);
@@ -292,7 +294,7 @@ impl ActiveThreadState {
&mut self,
message_editor: Entity<MessageEditor>,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
let full_mention_content = self.as_native_thread(cx).is_some_and(|thread| {
// Include full contents when using minimal profile
@@ -349,7 +351,7 @@ impl ActiveThreadState {
&mut self,
contents_task: Task<anyhow::Result<Option<(Vec<acp::ContentBlock>, Vec<Entity<Buffer>>)>>>,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
let session_id = self.thread.read(cx).session_id().clone();
let agent_telemetry_id = self.thread.read(cx).connection().telemetry_id();
@@ -361,12 +363,8 @@ impl ActiveThreadState {
let mode_id = self.current_mode_id(cx);
let guard = cx.new(|_| ());
cx.observe_release(&guard, |this, _guard, cx| {
- if let ThreadState::Active(ActiveThreadState {
- is_loading_contents,
- ..
- }) = &mut this.thread_state
- {
- *is_loading_contents = false;
+ if let Some(thread) = this.as_active_thread_mut() {
+ thread.is_loading_contents = false;
}
cx.notify();
})
@@ -444,17 +442,14 @@ impl ActiveThreadState {
.ok();
} else {
this.update(cx, |this, cx| {
- if let ThreadState::Active(ActiveThreadState {
- should_be_following,
- ..
- }) = &mut this.thread_state
- {
- *should_be_following = this
- .workspace
- .update(cx, |workspace, _| {
- workspace.is_being_followed(CollaboratorId::Agent)
- })
- .unwrap_or_default();
+ let should_be_following = this
+ .workspace
+ .update(cx, |workspace, _| {
+ workspace.is_being_followed(CollaboratorId::Agent)
+ })
+ .unwrap_or_default();
+ if let Some(thread) = this.as_active_thread_mut() {
+ thread.should_be_following = should_be_following;
}
})
.ok();
@@ -467,7 +462,7 @@ impl ActiveThreadState {
&mut self,
message_editor: Entity<MessageEditor>,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
let thread = &self.thread;
@@ -486,7 +481,7 @@ impl ActiveThreadState {
pub fn stop_current_and_send_new_message(
&mut self,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
let thread = self.thread.clone();
self.skip_queue_processing_count = 0;
@@ -507,14 +502,14 @@ impl ActiveThreadState {
// generation
- pub fn cancel_generation(&mut self, cx: &mut Context<AcpThreadView>) {
+ pub fn cancel_generation(&mut self, cx: &mut Context<AcpServerView>) {
self.thread_retry_status.take();
self.thread_error.take();
self.user_interrupted_generation = true;
self._cancel_task = Some(self.thread.update(cx, |thread, cx| thread.cancel(cx)));
}
- pub fn retry_generation(&mut self, cx: &mut Context<AcpThreadView>) {
+ pub fn retry_generation(&mut self, cx: &mut Context<AcpServerView>) {
self.thread_error.take();
let thread = &self.thread;
@@ -540,7 +535,7 @@ impl ActiveThreadState {
entry_ix: usize,
message_editor: Entity<MessageEditor>,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
if self.is_loading_contents {
return;
@@ -594,7 +589,7 @@ impl ActiveThreadState {
&mut self,
message_editor: Entity<MessageEditor>,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
let is_idle = self.thread.read(cx).status() == acp_thread::ThreadStatus::Idle;
@@ -646,7 +641,7 @@ impl ActiveThreadState {
pub fn remove_from_queue(
&mut self,
index: usize,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) -> Option<QueuedMessage> {
if index < self.local_queued_messages.len() {
let removed = self.local_queued_messages.remove(index);
@@ -657,7 +652,7 @@ impl ActiveThreadState {
}
}
- pub fn sync_queue_flag_to_native_thread(&self, cx: &mut Context<AcpThreadView>) {
+ pub fn sync_queue_flag_to_native_thread(&self, cx: &mut Context<AcpServerView>) {
if let Some(native_thread) = self.as_native_thread(cx) {
let has_queued = self.has_queued_messages();
native_thread.update(cx, |thread, _| {
@@ -671,7 +666,7 @@ impl ActiveThreadState {
index: usize,
is_send_now: bool,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
let Some(queued) = self.remove_from_queue(index, cx) else {
return;
@@ -715,7 +710,7 @@ impl ActiveThreadState {
pub fn expand_message_editor(
&mut self,
message_editor: Entity<MessageEditor>,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
self.set_editor_is_expanded(!self.editor_expanded, message_editor, cx);
cx.stop_propagation();
@@ -726,7 +721,7 @@ impl ActiveThreadState {
&mut self,
is_expanded: bool,
message_editor: Entity<MessageEditor>,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
self.editor_expanded = is_expanded;
message_editor.update(cx, |editor, cx| {
@@ -758,7 +753,7 @@ impl ActiveThreadState {
title_editor: &Entity<Editor>,
event: &EditorEvent,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
let thread = &self.thread;
@@ -786,7 +781,7 @@ impl ActiveThreadState {
&mut self,
focus_handle: FocusHandle,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
if let Some(index) = self.editing_message.take()
&& let Some(editor) = &self
@@ -820,7 +815,7 @@ impl ActiveThreadState {
option_id: acp::PermissionOptionId,
option_kind: acp::PermissionOptionKind,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
let thread = &self.thread;
let agent_telemetry_id = thread.read(cx).connection().telemetry_id();
@@ -849,7 +844,7 @@ impl ActiveThreadState {
&mut self,
kind: acp::PermissionOptionKind,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) -> Option<()> {
let thread = self.thread.read(cx);
let tool_call = thread.first_tool_awaiting_confirmation()?;
@@ -872,7 +867,7 @@ impl ActiveThreadState {
pub fn handle_select_permission_granularity(
&mut self,
action: &SelectPermissionGranularity,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
let tool_call_id = acp::ToolCallId::new(action.tool_call_id.clone());
self.selected_permission_granularity
@@ -883,7 +878,7 @@ impl ActiveThreadState {
// edits
- pub fn keep_all(&mut self, cx: &mut Context<AcpThreadView>) {
+ pub fn keep_all(&mut self, cx: &mut Context<AcpServerView>) {
let thread = &self.thread;
let telemetry = ActionLogTelemetry::from(thread.read(cx));
let action_log = thread.read(cx).action_log().clone();
@@ -892,7 +887,7 @@ impl ActiveThreadState {
});
}
- pub fn reject_all(&mut self, cx: &mut Context<AcpThreadView>) {
+ pub fn reject_all(&mut self, cx: &mut Context<AcpServerView>) {
let thread = &self.thread;
let telemetry = ActionLogTelemetry::from(thread.read(cx));
let action_log = thread.read(cx).action_log().clone();
@@ -907,7 +902,7 @@ impl ActiveThreadState {
&mut self,
buffer: &Entity<Buffer>,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
let thread = &self.thread;
@@ -928,7 +923,7 @@ impl ActiveThreadState {
&mut self,
project: Entity<Project>,
window: &mut Window,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
if !self.is_imported_thread(cx) {
return;
@@ -972,11 +967,8 @@ impl ActiveThreadState {
};
this.update_in(cx, |this, window, cx| {
- if let ThreadState::Active(ActiveThreadState {
- resume_thread_metadata,
- ..
- }) = &mut this.thread_state
- {
+ if let Some(thread) = this.as_active_thread_mut() {
+ let resume_thread_metadata = &mut thread.resume_thread_metadata;
*resume_thread_metadata = Some(thread_metadata);
}
this.reset(window, cx);
@@ -1006,7 +998,7 @@ impl ActiveThreadState {
pub fn restore_checkpoint(
&mut self,
message_id: &UserMessageId,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) {
self.thread
.update(cx, |thread, cx| {
@@ -1015,7 +1007,7 @@ impl ActiveThreadState {
.detach_and_log_err(cx);
}
- pub fn clear_thread_error(&mut self, cx: &mut Context<AcpThreadView>) {
+ pub fn clear_thread_error(&mut self, cx: &mut Context<AcpServerView>) {
self.thread_error = None;
self.thread_error_markdown = None;
self.token_limit_callout_dismissed = true;
@@ -1051,7 +1043,7 @@ impl ActiveThreadState {
pub fn render_command_load_errors(
&self,
- cx: &mut Context<AcpThreadView>,
+ cx: &mut Context<AcpServerView>,
) -> Option<impl IntoElement> {
let errors = self.cached_user_command_errors.borrow();
@@ -1164,7 +1156,7 @@ impl ActiveThreadState {
)
}
- pub fn handle_open_rules(&mut self, window: &mut Window, cx: &mut Context<AcpThreadView>) {
+ pub fn handle_open_rules(&mut self, window: &mut Window, cx: &mut Context<AcpServerView>) {
let Some(thread) = self.as_native_thread(cx) else {
return;
};
@@ -21,7 +21,7 @@ use crate::{
LoadThreadFromClipboard, NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff,
OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell, ToggleNavigationMenu, ToggleNewThreadMenu,
ToggleOptionsMenu,
- acp::AcpThreadView,
+ acp::AcpServerView,
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
slash_command::SlashCommandCompletionProvider,
text_thread_editor::{AgentPanelDelegate, TextThreadEditor, make_lsp_adapter_delegate},
@@ -243,8 +243,8 @@ enum HistoryKind {
enum ActiveView {
Uninitialized,
- ExternalAgentThread {
- thread_view: Entity<AcpThreadView>,
+ AgentThread {
+ thread_view: Entity<AcpServerView>,
},
TextThread {
text_thread_editor: Entity<TextThreadEditor>,
@@ -316,7 +316,7 @@ impl ActiveView {
pub fn which_font_size_used(&self) -> WhichFontSize {
match self {
ActiveView::Uninitialized
- | ActiveView::ExternalAgentThread { .. }
+ | ActiveView::AgentThread { .. }
| ActiveView::History { .. } => WhichFontSize::AgentFont,
ActiveView::TextThread { .. } => WhichFontSize::BufferFont,
ActiveView::Configuration => WhichFontSize::None,
@@ -731,9 +731,9 @@ impl AgentPanel {
.unwrap_or(true)
}
- pub(crate) fn active_thread_view(&self) -> Option<&Entity<AcpThreadView>> {
+ pub(crate) fn active_thread_view(&self) -> Option<&Entity<AcpServerView>> {
match &self.active_view {
- ActiveView::ExternalAgentThread { thread_view, .. } => Some(thread_view),
+ ActiveView::AgentThread { thread_view, .. } => Some(thread_view),
ActiveView::Uninitialized
| ActiveView::TextThread { .. }
| ActiveView::History { .. }
@@ -1017,7 +1017,7 @@ impl AgentPanel {
self.active_view = previous_view;
match &self.active_view {
- ActiveView::ExternalAgentThread { thread_view } => {
+ ActiveView::AgentThread { thread_view } => {
thread_view.focus_handle(cx).focus(window, cx);
}
ActiveView::TextThread {
@@ -1190,7 +1190,7 @@ impl AgentPanel {
};
match &self.active_view {
- ActiveView::ExternalAgentThread { thread_view } => {
+ ActiveView::AgentThread { thread_view } => {
thread_view
.update(cx, |thread_view, cx| {
thread_view.open_thread_as_markdown(workspace, window, cx)
@@ -1418,7 +1418,7 @@ impl AgentPanel {
pub(crate) fn active_agent_thread(&self, cx: &App) -> Option<Entity<AcpThread>> {
match &self.active_view {
- ActiveView::ExternalAgentThread { thread_view, .. } => thread_view
+ ActiveView::AgentThread { thread_view, .. } => thread_view
.read(cx)
.as_active_thread()
.map(|r| r.thread.clone()),
@@ -1428,7 +1428,7 @@ impl AgentPanel {
pub(crate) fn active_native_agent_thread(&self, cx: &App) -> Option<Entity<agent::Thread>> {
match &self.active_view {
- ActiveView::ExternalAgentThread { thread_view, .. } => {
+ ActiveView::AgentThread { thread_view, .. } => {
thread_view.read(cx).as_native_thread(cx)
}
_ => None,
@@ -1702,7 +1702,7 @@ impl AgentPanel {
.then(|| self.thread_store.clone());
let thread_view = cx.new(|cx| {
- crate::acp::AcpThreadView::new(
+ crate::acp::AcpServerView::new(
server,
resume_thread,
initial_content,
@@ -1716,12 +1716,7 @@ impl AgentPanel {
)
});
- self.set_active_view(
- ActiveView::ExternalAgentThread { thread_view },
- true,
- window,
- cx,
- );
+ self.set_active_view(ActiveView::AgentThread { thread_view }, true, window, cx);
}
}
@@ -1729,7 +1724,7 @@ impl Focusable for AgentPanel {
fn focus_handle(&self, cx: &App) -> FocusHandle {
match &self.active_view {
ActiveView::Uninitialized => self.focus_handle.clone(),
- ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
+ ActiveView::AgentThread { thread_view, .. } => thread_view.focus_handle(cx),
ActiveView::History { kind } => match kind {
HistoryKind::AgentThreads => self.acp_history.focus_handle(cx),
HistoryKind::TextThreads => self.text_thread_history.focus_handle(cx),
@@ -1845,7 +1840,7 @@ impl AgentPanel {
const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summaryβ¦";
let content = match &self.active_view {
- ActiveView::ExternalAgentThread { thread_view } => {
+ ActiveView::AgentThread { thread_view } => {
let is_generating_title = thread_view
.read(cx)
.as_native_thread(cx)
@@ -1975,7 +1970,7 @@ impl AgentPanel {
.into_any()
}
- fn handle_regenerate_thread_title(thread_view: Entity<AcpThreadView>, cx: &mut App) {
+ fn handle_regenerate_thread_title(thread_view: Entity<AcpServerView>, cx: &mut App) {
thread_view.update(cx, |thread_view, cx| {
if let Some(thread) = thread_view.as_native_thread(cx) {
thread.update(cx, |thread, cx| {
@@ -2028,11 +2023,11 @@ impl AgentPanel {
};
let thread_view = match &self.active_view {
- ActiveView::ExternalAgentThread { thread_view } => Some(thread_view.clone()),
+ ActiveView::AgentThread { thread_view } => Some(thread_view.clone()),
_ => None,
};
let thread_with_messages = match &self.active_view {
- ActiveView::ExternalAgentThread { thread_view } => {
+ ActiveView::AgentThread { thread_view } => {
thread_view.read(cx).has_user_submitted_prompt(cx)
}
_ => false,
@@ -2196,9 +2191,7 @@ impl AgentPanel {
};
let active_thread = match &self.active_view {
- ActiveView::ExternalAgentThread { thread_view } => {
- thread_view.read(cx).as_native_thread(cx)
- }
+ ActiveView::AgentThread { thread_view } => thread_view.read(cx).as_native_thread(cx),
ActiveView::Uninitialized
| ActiveView::TextThread { .. }
| ActiveView::History { .. }
@@ -2600,7 +2593,7 @@ impl AgentPanel {
}
}
ActiveView::Uninitialized
- | ActiveView::ExternalAgentThread { .. }
+ | ActiveView::AgentThread { .. }
| ActiveView::History { .. }
| ActiveView::Configuration => return false,
}
@@ -2632,7 +2625,7 @@ impl AgentPanel {
ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {
false
}
- ActiveView::ExternalAgentThread { thread_view, .. }
+ ActiveView::AgentThread { thread_view, .. }
if thread_view.read(cx).as_native_thread(cx).is_none() =>
{
false
@@ -2878,7 +2871,7 @@ impl AgentPanel {
cx: &mut Context<Self>,
) {
match &self.active_view {
- ActiveView::ExternalAgentThread { thread_view } => {
+ ActiveView::AgentThread { thread_view } => {
thread_view.update(cx, |thread_view, cx| {
thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
});
@@ -2936,7 +2929,7 @@ impl AgentPanel {
let mut key_context = KeyContext::new_with_defaults();
key_context.add("AgentPanel");
match &self.active_view {
- ActiveView::ExternalAgentThread { .. } => key_context.add("acp_thread"),
+ ActiveView::AgentThread { .. } => key_context.add("acp_thread"),
ActiveView::TextThread { .. } => key_context.add("text_thread"),
ActiveView::Uninitialized | ActiveView::History { .. } | ActiveView::Configuration => {}
}
@@ -2988,7 +2981,7 @@ impl Render for AgentPanel {
.children(self.render_onboarding(window, cx))
.map(|parent| match &self.active_view {
ActiveView::Uninitialized => parent,
- ActiveView::ExternalAgentThread { thread_view, .. } => parent
+ ActiveView::AgentThread { thread_view, .. } => parent
.child(thread_view.clone())
.child(self.render_drag_target(cx)),
ActiveView::History { kind } => match kind {
@@ -3246,7 +3239,7 @@ impl AgentPanel {
///
/// This is a test-only accessor that exposes the private `active_thread_view()`
/// method for test assertions. Not compiled into production builds.
- pub fn active_thread_view_for_tests(&self) -> Option<&Entity<AcpThreadView>> {
+ pub fn active_thread_view_for_tests(&self) -> Option<&Entity<AcpServerView>> {
self.active_thread_view()
}
}
@@ -3,7 +3,7 @@ use agent::{NativeAgentServer, ThreadStore};
use agent_client_protocol as acp;
use agent_servers::AgentServer;
use agent_settings::AgentSettings;
-use agent_ui::acp::{AcpThreadHistory, AcpThreadView};
+use agent_ui::acp::{AcpServerView, AcpThreadHistory};
use fs::Fs;
use gpui::{
Entity, EventEmitter, Focusable, Pixels, SharedString, Subscription, WeakEntity, prelude::*,
@@ -51,7 +51,7 @@ impl EventEmitter<MinimizePane> for AgentThreadPane {}
impl EventEmitter<ClosePane> for AgentThreadPane {}
struct ActiveThreadView {
- view: Entity<AcpThreadView>,
+ view: Entity<AcpServerView>,
thread_id: acp::SessionId,
_notify: Subscription,
}
@@ -112,7 +112,7 @@ impl AgentThreadPane {
let history = self.history.clone();
let thread_view = cx.new(|cx| {
- AcpThreadView::new(
+ AcpServerView::new(
agent,
resume_thread,
None,
@@ -1951,7 +1951,7 @@ fn run_subagent_visual_tests(
)?;
// Expand the first subagent
- thread_view.update(cx, |view: &mut agent_ui::acp::AcpThreadView, cx| {
+ thread_view.update(cx, |view: &mut agent_ui::acp::AcpServerView, cx| {
view.expand_subagent(acp::SessionId::new("subagent-1"), cx);
});