agent.rs

  1use agent_client_protocol as acp;
  2use anyhow::Result;
  3use gpui::{App, AppContext, AsyncApp, Entity, Task};
  4use project::Project;
  5use std::collections::HashMap;
  6use std::path::Path;
  7use std::rc::Rc;
  8use std::sync::Arc;
  9
 10use crate::{templates::Templates, Thread};
 11
 12pub struct Agent {
 13    /// Session ID -> Thread entity mapping
 14    sessions: HashMap<acp::SessionId, Entity<Thread>>,
 15    /// Shared templates for all threads
 16    templates: Arc<Templates>,
 17}
 18
 19impl Agent {
 20    pub fn new(templates: Arc<Templates>) -> Self {
 21        Self {
 22            sessions: HashMap::new(),
 23            templates,
 24        }
 25    }
 26}
 27
 28/// Wrapper struct that implements the AgentConnection trait
 29pub struct AgentConnection(pub Entity<Agent>);
 30
 31impl acp_thread::AgentConnection for AgentConnection {
 32    fn new_thread(
 33        self: Rc<Self>,
 34        project: Entity<Project>,
 35        cwd: &Path,
 36        cx: &mut AsyncApp,
 37    ) -> Task<Result<Entity<acp_thread::AcpThread>>> {
 38        let _cwd = cwd.to_owned();
 39        let agent = self.0.clone();
 40
 41        cx.spawn(async move |cx| {
 42            // Create Thread and store in Agent
 43            let (session_id, _thread) =
 44                agent.update(cx, |agent, cx: &mut gpui::Context<Agent>| {
 45                    let thread = cx.new(|_| Thread::new(agent.templates.clone()));
 46                    let session_id = acp::SessionId(uuid::Uuid::new_v4().to_string().into());
 47                    agent.sessions.insert(session_id.clone(), thread.clone());
 48                    (session_id, thread)
 49                })?;
 50
 51            // Create AcpThread
 52            let acp_thread = cx.update(|cx| {
 53                cx.new(|cx| acp_thread::AcpThread::new("agent2", self, project, session_id, cx))
 54            })?;
 55
 56            Ok(acp_thread)
 57        })
 58    }
 59
 60    fn auth_methods(&self) -> &[acp::AuthMethod] {
 61        &[] // No auth for in-process
 62    }
 63
 64    fn authenticate(&self, _method: acp::AuthMethodId, _cx: &mut App) -> Task<Result<()>> {
 65        Task::ready(Ok(()))
 66    }
 67
 68    fn prompt(&self, params: acp::PromptRequest, cx: &mut App) -> Task<Result<()>> {
 69        let session_id = params.session_id.clone();
 70        let agent = self.0.clone();
 71
 72        cx.spawn(|cx| async move {
 73            // Get thread
 74            let thread: Entity<Thread> = agent
 75                .read_with(cx, |agent, _| agent.sessions.get(&session_id).cloned())?
 76                .ok_or_else(|| anyhow::anyhow!("Session not found"))?;
 77
 78            // Convert prompt to message
 79            let message = convert_prompt_to_message(params.prompt);
 80
 81            // TODO: Get model from somewhere - for now use a placeholder
 82            log::warn!("Model selection not implemented - need to get from UI context");
 83
 84            // Send to thread
 85            // thread.update(&mut cx, |thread, cx| {
 86            //     thread.send(model, message, cx)
 87            // })?;
 88
 89            Ok(())
 90        })
 91    }
 92
 93    fn cancel(&self, session_id: &acp::SessionId, cx: &mut App) {
 94        self.0.update(cx, |agent, _cx| {
 95            agent.sessions.remove(session_id);
 96        });
 97    }
 98}
 99
100/// Convert ACP content blocks to a message string
101fn convert_prompt_to_message(blocks: Vec<acp::ContentBlock>) -> String {
102    let mut message = String::new();
103
104    for block in blocks {
105        match block {
106            acp::ContentBlock::Text(text) => {
107                message.push_str(&text.text);
108            }
109            acp::ContentBlock::ResourceLink(link) => {
110                message.push_str(&format!(" @{} ", link.uri));
111            }
112            acp::ContentBlock::Image(_) => {
113                message.push_str(" [image] ");
114            }
115            acp::ContentBlock::Audio(_) => {
116                message.push_str(" [audio] ");
117            }
118            acp::ContentBlock::Resource(resource) => {
119                message.push_str(&format!(" [resource: {:?}] ", resource.resource));
120            }
121        }
122    }
123
124    message
125}