agent: Move thread loading to the AgentConnection (#46985)

Ben Brandt created

Preps for external agents being able to load a thread

Release Notes:

- N/A

Change summary

crates/acp_thread/src/connection.rs    | 16 +++++++++
crates/agent/src/agent.rs              | 15 +++++++++
crates/agent_ui/src/acp/thread_view.rs | 46 +++++++++++++++++++--------
crates/agent_ui_v2/src/agents_panel.rs |  4 +
4 files changed, 66 insertions(+), 15 deletions(-)

Detailed changes

crates/acp_thread/src/connection.rs πŸ”—

@@ -37,6 +37,22 @@ pub trait AgentConnection {
         cx: &mut App,
     ) -> Task<Result<Entity<AcpThread>>>;
 
+    /// Whether this agent supports loading existing sessions.
+    fn supports_load_session(&self) -> bool {
+        false
+    }
+
+    /// Load an existing session by ID.
+    fn load_session(
+        self: Rc<Self>,
+        _session: AgentSessionInfo,
+        _project: Entity<Project>,
+        _cwd: &Path,
+        _cx: &mut App,
+    ) -> Task<Result<Entity<AcpThread>>> {
+        Task::ready(Err(anyhow::Error::msg("Loading sessions is not supported")))
+    }
+
     fn auth_methods(&self) -> &[acp::AuthMethod];
 
     fn authenticate(&self, method: acp::AuthMethodId, cx: &mut App) -> Task<Result<()>>;

crates/agent/src/agent.rs πŸ”—

@@ -1220,6 +1220,21 @@ impl acp_thread::AgentConnection for NativeAgentConnection {
         })
     }
 
+    fn supports_load_session(&self) -> bool {
+        true
+    }
+
+    fn load_session(
+        self: Rc<Self>,
+        session: AgentSessionInfo,
+        _project: Entity<Project>,
+        _cwd: &Path,
+        cx: &mut App,
+    ) -> Task<Result<Entity<acp_thread::AcpThread>>> {
+        self.0
+            .update(cx, |agent, cx| agent.open_thread(session.session_id, cx))
+    }
+
     fn auth_methods(&self) -> &[acp::AuthMethod] {
         &[] // No auth for in-process
     }

crates/agent_ui/src/acp/thread_view.rs πŸ”—

@@ -613,6 +613,9 @@ impl AcpThreadView {
                 }
             })
             .next();
+        let fallback_cwd = root_dir
+            .clone()
+            .unwrap_or_else(|| paths::home_dir().as_path().into());
         let (status_tx, mut status_rx) = watch::channel("Loading…".into());
         let (new_version_available_tx, mut new_version_available_rx) = watch::channel(None);
         let delegate = AgentServerDelegate::new(
@@ -647,23 +650,34 @@ impl AcpThreadView {
                 telemetry::event!("Agent Thread Started", agent = connection.telemetry_id());
             }
 
-            let result = if let Some(native_agent) = connection
-                .clone()
-                .downcast::<agent::NativeAgentConnection>()
-                && let Some(resume) = resume_thread.clone()
-            {
-                cx.update(|_, cx| {
-                    native_agent
-                        .0
-                        .update(cx, |agent, cx| agent.open_thread(resume.session_id, cx))
-                })
-                .log_err()
+            let result = if let Some(resume) = resume_thread.clone() {
+                if connection.supports_load_session() {
+                    let session_cwd = resume
+                        .cwd
+                        .clone()
+                        .unwrap_or_else(|| fallback_cwd.as_ref().to_path_buf());
+                    cx.update(|_, cx| {
+                        connection.clone().load_session(
+                            resume,
+                            project.clone(),
+                            session_cwd.as_path(),
+                            cx,
+                        )
+                    })
+                    .log_err()
+                } else {
+                    cx.update(|_, _| {
+                        Task::ready(Err(anyhow!(LoadError::Other(
+                            "Loading sessions is not supported by this agent.".into()
+                        ))))
+                    })
+                    .log_err()
+                }
             } else {
-                let root_dir = root_dir.unwrap_or(paths::home_dir().as_path().into());
                 cx.update(|_, cx| {
                     connection
                         .clone()
-                        .new_thread(project.clone(), &root_dir, cx)
+                        .new_thread(project.clone(), fallback_cwd.as_ref(), cx)
                 })
                 .log_err()
             };
@@ -709,7 +723,11 @@ impl AcpThreadView {
 
                         let connection = thread.read(cx).connection().clone();
                         let session_id = thread.read(cx).session_id().clone();
-                        let session_list = connection.session_list(cx);
+                        let session_list = if connection.supports_load_session() {
+                            connection.session_list(cx)
+                        } else {
+                            None
+                        };
                         this.history.update(cx, |history, cx| {
                             history.set_session_list(session_list, cx);
                         });

crates/agent_ui_v2/src/agents_panel.rs πŸ”—

@@ -151,7 +151,9 @@ impl AgentsPanel {
             };
 
             cx.update(|cx| {
-                if let Some(session_list) = connection.session_list(cx) {
+                if connection.supports_load_session()
+                    && let Some(session_list) = connection.session_list(cx)
+                {
                     history_handle.update(cx, |history, cx| {
                         history.set_session_list(Some(session_list), cx);
                     });