1use crate::AcpThread;
2use agent_client_protocol::{self as acp};
3use anyhow::Result;
4use collections::IndexMap;
5use gpui::{AsyncApp, Entity, SharedString, Task};
6use project::Project;
7use std::{error::Error, fmt, path::Path, rc::Rc, sync::Arc};
8use ui::{App, IconName};
9use uuid::Uuid;
10
11#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct UserMessageId(Arc<str>);
13
14impl UserMessageId {
15 pub fn new() -> Self {
16 Self(Uuid::new_v4().to_string().into())
17 }
18}
19
20pub trait AgentConnection {
21 fn new_thread(
22 self: Rc<Self>,
23 project: Entity<Project>,
24 cwd: &Path,
25 cx: &mut AsyncApp,
26 ) -> Task<Result<Entity<AcpThread>>>;
27
28 fn auth_methods(&self) -> &[acp::AuthMethod];
29
30 fn authenticate(&self, method: acp::AuthMethodId, cx: &mut App) -> Task<Result<()>>;
31
32 fn prompt(
33 &self,
34 user_message_id: Option<UserMessageId>,
35 params: acp::PromptRequest,
36 cx: &mut App,
37 ) -> Task<Result<acp::PromptResponse>>;
38
39 fn cancel(&self, session_id: &acp::SessionId, cx: &mut App);
40
41 fn session_editor(
42 &self,
43 _session_id: &acp::SessionId,
44 _cx: &mut App,
45 ) -> Option<Rc<dyn AgentSessionEditor>> {
46 None
47 }
48
49 /// Returns this agent as an [Rc<dyn ModelSelector>] if the model selection capability is supported.
50 ///
51 /// If the agent does not support model selection, returns [None].
52 /// This allows sharing the selector in UI components.
53 fn model_selector(&self) -> Option<Rc<dyn AgentModelSelector>> {
54 None
55 }
56}
57
58pub trait AgentSessionEditor {
59 fn truncate(&self, message_id: UserMessageId, cx: &mut App) -> Task<Result<()>>;
60}
61
62#[derive(Debug)]
63pub struct AuthRequired;
64
65impl Error for AuthRequired {}
66impl fmt::Display for AuthRequired {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 write!(f, "AuthRequired")
69 }
70}
71
72/// Trait for agents that support listing, selecting, and querying language models.
73///
74/// This is an optional capability; agents indicate support via [AgentConnection::model_selector].
75pub trait AgentModelSelector: 'static {
76 /// Lists all available language models for this agent.
77 ///
78 /// # Parameters
79 /// - `cx`: The GPUI app context for async operations and global access.
80 ///
81 /// # Returns
82 /// A task resolving to the list of models or an error (e.g., if no models are configured).
83 fn list_models(&self, cx: &mut App) -> Task<Result<AgentModelList>>;
84
85 /// Selects a model for a specific session (thread).
86 ///
87 /// This sets the default model for future interactions in the session.
88 /// If the session doesn't exist or the model is invalid, it returns an error.
89 ///
90 /// # Parameters
91 /// - `session_id`: The ID of the session (thread) to apply the model to.
92 /// - `model`: The model to select (should be one from [list_models]).
93 /// - `cx`: The GPUI app context.
94 ///
95 /// # Returns
96 /// A task resolving to `Ok(())` on success or an error.
97 fn select_model(
98 &self,
99 session_id: acp::SessionId,
100 model_id: AgentModelId,
101 cx: &mut App,
102 ) -> Task<Result<()>>;
103
104 /// Retrieves the currently selected model for a specific session (thread).
105 ///
106 /// # Parameters
107 /// - `session_id`: The ID of the session (thread) to query.
108 /// - `cx`: The GPUI app context.
109 ///
110 /// # Returns
111 /// A task resolving to the selected model (always set) or an error (e.g., session not found).
112 fn selected_model(
113 &self,
114 session_id: &acp::SessionId,
115 cx: &mut App,
116 ) -> Task<Result<AgentModelInfo>>;
117
118 /// Whenever the model list is updated the receiver will be notified.
119 fn watch(&self, cx: &mut App) -> watch::Receiver<()>;
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Hash)]
123pub struct AgentModelId(pub SharedString);
124
125impl std::ops::Deref for AgentModelId {
126 type Target = SharedString;
127
128 fn deref(&self) -> &Self::Target {
129 &self.0
130 }
131}
132
133impl fmt::Display for AgentModelId {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 self.0.fmt(f)
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct AgentModelInfo {
141 pub id: AgentModelId,
142 pub name: SharedString,
143 pub icon: Option<IconName>,
144}
145
146#[derive(Debug, Clone, PartialEq, Eq, Hash)]
147pub struct AgentModelGroupName(pub SharedString);
148
149#[derive(Debug, Clone)]
150pub enum AgentModelList {
151 Flat(Vec<AgentModelInfo>),
152 Grouped(IndexMap<AgentModelGroupName, Vec<AgentModelInfo>>),
153}
154
155impl AgentModelList {
156 pub fn is_empty(&self) -> bool {
157 match self {
158 AgentModelList::Flat(models) => models.is_empty(),
159 AgentModelList::Grouped(groups) => groups.is_empty(),
160 }
161 }
162}