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}