connection.rs

  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}