1138 lines · 34.3 KB · Rust
RawDownload
use crate::{AcpThread, ElicitationStore};use agent_client_protocol::schema::v1 as acp;use anyhow::Result;use chrono::{DateTime, Utc};use collections::{HashMap, HashSet, IndexMap};use gpui::{Entity, SharedString, Task};use language_model::DisabledReason;use project::{AgentId, Project};use serde::{Deserialize, Serialize};use std::{any::Any, error::Error, fmt, path::PathBuf, rc::Rc};use task::{HideStrategy, SpawnInTerminal, TaskId};use ui::{App, IconName};use util::path_list::PathList;use uuid::Uuid; /// A user-message ID generated by Zed and passed to agents that support client IDs.#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]pub struct ClientUserMessageId(SharedString); impl ClientUserMessageId {    pub fn new() -> Self {        Self(Uuid::new_v4().to_string().into())    }} #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]pub struct AgentModelId(SharedString); impl AgentModelId {    pub fn new(id: impl Into<Self>) -> Self {        id.into()    }     pub fn as_str(&self) -> &str {        self.0.as_ref()    }} impl AsRef<str> for AgentModelId {    fn as_ref(&self) -> &str {        self.as_str()    }} impl fmt::Display for AgentModelId {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        self.0.fmt(f)    }} impl From<SharedString> for AgentModelId {    fn from(id: SharedString) -> Self {        Self(id)    }} impl From<String> for AgentModelId {    fn from(id: String) -> Self {        Self(SharedString::from(id))    }} impl From<&str> for AgentModelId {    fn from(id: &str) -> Self {        Self(SharedString::from(id.to_owned()))    }} pub fn build_terminal_auth_task(    id: String,    label: String,    command: String,    args: Vec<String>,    env: HashMap<String, String>,) -> SpawnInTerminal {    SpawnInTerminal {        id: TaskId(id),        full_label: label.clone(),        label: label.clone(),        command: Some(command),        args,        command_label: label,        env,        use_new_terminal: true,        allow_concurrent_runs: true,        hide: HideStrategy::Always,        ..Default::default()    }} pub trait AgentConnection {    fn agent_id(&self) -> AgentId;     fn telemetry_id(&self) -> SharedString;     fn agent_version(&self) -> Option<SharedString> {        None    }     fn new_session(        self: Rc<Self>,        project: Entity<Project>,        _work_dirs: PathList,        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_id: acp::SessionId,        _project: Entity<Project>,        _work_dirs: PathList,        _title: Option<SharedString>,        _cx: &mut App,    ) -> Task<Result<Entity<AcpThread>>> {        Task::ready(Err(anyhow::Error::msg("Loading sessions is not supported")))    }     /// Whether this agent supports closing existing sessions.    fn supports_close_session(&self) -> bool {        false    }     /// Close an existing session. Allows the agent to free the session from memory.    fn close_session(        self: Rc<Self>,        _session_id: &acp::SessionId,        _cx: &mut App,    ) -> Task<Result<()>> {        Task::ready(Err(anyhow::Error::msg("Closing sessions is not supported")))    }     /// Whether this agent supports resuming existing sessions without loading history.    fn supports_resume_session(&self) -> bool {        false    }     /// Resume an existing session by ID without replaying previous messages.    fn resume_session(        self: Rc<Self>,        _session_id: acp::SessionId,        _project: Entity<Project>,        _work_dirs: PathList,        _title: Option<SharedString>,        _cx: &mut App,    ) -> Task<Result<Entity<AcpThread>>> {        Task::ready(Err(anyhow::Error::msg(            "Resuming sessions is not supported",        )))    }     /// Whether this agent supports showing session history.    fn supports_session_history(&self) -> bool {        self.supports_load_session() || self.supports_resume_session()    }     /// Whether this agent supports additional session directories.    fn supports_session_additional_directories(&self) -> bool {        false    }     fn auth_methods(&self) -> &[acp::AuthMethod];     fn terminal_auth_task(        &self,        _method: &acp::AuthMethodId,        _cx: &App,    ) -> Option<Task<Result<SpawnInTerminal>>> {        None    }     fn authenticate(&self, method: acp::AuthMethodId, cx: &mut App) -> Task<Result<()>>;     fn supports_logout(&self) -> bool {        false    }     fn logout(&self, _cx: &mut App) -> Task<Result<()>> {        Task::ready(Err(anyhow::Error::msg("Logout is not supported")))    }     /// Returns a capability for agents that accept client-generated user message IDs.    fn client_user_message_ids(        &self,        _cx: &App,    ) -> Option<Rc<dyn AgentSessionClientUserMessageIds>> {        None    }     fn prompt(&self, params: acp::PromptRequest, cx: &mut App)    -> Task<Result<acp::PromptResponse>>;     fn retry(&self, _session_id: &acp::SessionId, _cx: &App) -> Option<Rc<dyn AgentSessionRetry>> {        None    }     fn cancel(&self, session_id: &acp::SessionId, cx: &mut App);     /// Request-scoped elicitations are connection-level because they can arrive before a session    /// thread exists. Session-scoped elicitations stay in the thread timeline, but use    /// `ElicitationStore` for shared processing.    fn request_elicitations(&self) -> Option<Entity<ElicitationStore>> {        None    }     fn truncate(        &self,        _session_id: &acp::SessionId,        _cx: &App,    ) -> Option<Rc<dyn AgentSessionTruncate>> {        None    }     fn set_title(        &self,        _session_id: &acp::SessionId,        _cx: &App,    ) -> Option<Rc<dyn AgentSessionSetTitle>> {        None    }     /// Returns this agent as an [Rc<dyn ModelSelector>] if the model selection capability is supported.    ///    /// If the agent does not support model selection, returns [None].    /// This allows sharing the selector in UI components.    fn model_selector(&self, _session_id: &acp::SessionId) -> Option<Rc<dyn AgentModelSelector>> {        None    }     fn telemetry(&self) -> Option<Rc<dyn AgentTelemetry>> {        None    }     fn session_modes(        &self,        _session_id: &acp::SessionId,        _cx: &App,    ) -> Option<Rc<dyn AgentSessionModes>> {        None    }     fn session_config_options(        &self,        _session_id: &acp::SessionId,        _cx: &App,    ) -> Option<Rc<dyn AgentSessionConfigOptions>> {        None    }     fn session_list(&self, _cx: &mut App) -> Option<Rc<dyn AgentSessionList>> {        None    }     fn into_any(self: Rc<Self>) -> Rc<dyn Any>;} impl dyn AgentConnection {    pub fn downcast<T: 'static + AgentConnection + Sized>(self: Rc<Self>) -> Option<Rc<T>> {        self.into_any().downcast().ok()    }} pub trait AgentSessionTruncate {    fn run(&self, client_user_message_id: ClientUserMessageId, cx: &mut App) -> Task<Result<()>>;} pub trait AgentSessionClientUserMessageIds {    fn new_id(&self) -> ClientUserMessageId {        ClientUserMessageId::new()    }     fn prompt(        &self,        client_user_message_id: ClientUserMessageId,        params: acp::PromptRequest,        cx: &mut App,    ) -> Task<Result<acp::PromptResponse>>;} pub trait AgentSessionRetry {    fn run(&self, cx: &mut App) -> Task<Result<acp::PromptResponse>>;} pub trait AgentSessionSetTitle {    fn run(&self, title: SharedString, cx: &mut App) -> Task<Result<()>>;} pub trait AgentTelemetry {    /// A representation of the current thread state that can be serialized for    /// storage with telemetry events.    fn thread_data(        &self,        session_id: &acp::SessionId,        cx: &mut App,    ) -> Task<Result<serde_json::Value>>;} pub trait AgentSessionModes {    fn current_mode(&self) -> acp::SessionModeId;     fn all_modes(&self) -> Vec<acp::SessionMode>;     fn set_mode(&self, mode: acp::SessionModeId, cx: &mut App) -> Task<Result<()>>;} pub trait AgentSessionConfigOptions {    /// Get all current config options with their state    fn config_options(&self) -> Vec<acp::SessionConfigOption>;     /// Set a config option value    /// Returns the full updated list of config options    fn set_config_option(        &self,        config_id: acp::SessionConfigId,        value: acp::SessionConfigOptionValue,        cx: &mut App,    ) -> Task<Result<Vec<acp::SessionConfigOption>>>;     /// Whenever the config options are updated the receiver will be notified.    /// Optional for agents that don't update their config options dynamically.    fn watch(&self, _cx: &mut App) -> Option<watch::Receiver<()>> {        None    }} #[derive(Debug, Clone, Default)]pub struct AgentSessionListRequest {    pub cwd: Option<PathBuf>,    pub cursor: Option<String>,    pub meta: Option<acp::Meta>,} #[derive(Debug, Clone)]pub struct AgentSessionListResponse {    pub sessions: Vec<AgentSessionInfo>,    pub next_cursor: Option<String>,    pub meta: Option<acp::Meta>,} impl AgentSessionListResponse {    pub fn new(sessions: Vec<AgentSessionInfo>) -> Self {        Self {            sessions,            next_cursor: None,            meta: None,        }    }} #[derive(Debug, Clone, PartialEq)]pub struct AgentSessionInfo {    pub session_id: acp::SessionId,    pub work_dirs: Option<PathList>,    pub title: Option<SharedString>,    pub updated_at: Option<DateTime<Utc>>,    pub created_at: Option<DateTime<Utc>>,    pub meta: Option<acp::Meta>,} impl AgentSessionInfo {    pub fn new(session_id: impl Into<acp::SessionId>) -> Self {        Self {            session_id: session_id.into(),            work_dirs: None,            title: None,            updated_at: None,            created_at: None,            meta: None,        }    }} #[derive(Debug, Clone)]pub enum SessionListUpdate {    Refresh,    SessionInfo {        session_id: acp::SessionId,        update: acp::SessionInfoUpdate,    },} pub trait AgentSessionList {    fn list_sessions(        &self,        request: AgentSessionListRequest,        cx: &mut App,    ) -> Task<Result<AgentSessionListResponse>>;     fn supports_delete(&self) -> bool {        false    }     fn delete_session(&self, _session_id: &acp::SessionId, _cx: &mut App) -> Task<Result<()>> {        Task::ready(Err(anyhow::anyhow!("delete_session not supported")))    }     fn delete_sessions(&self, _cx: &mut App) -> Task<Result<()>> {        Task::ready(Err(anyhow::anyhow!("delete_sessions not supported")))    }     fn watch(&self, _cx: &mut App) -> Option<async_channel::Receiver<SessionListUpdate>> {        None    }     fn notify_refresh(&self) {}     fn into_any(self: Rc<Self>) -> Rc<dyn Any>;} impl dyn AgentSessionList {    pub fn downcast<T: 'static + AgentSessionList + Sized>(self: Rc<Self>) -> Option<Rc<T>> {        self.into_any().downcast().ok()    }} #[derive(Debug)]pub struct AuthRequired {    pub description: Option<String>,} impl AuthRequired {    pub fn new() -> Self {        Self { description: None }    }     pub fn with_description(mut self, description: String) -> Self {        self.description = Some(description);        self    }} impl Error for AuthRequired {}impl fmt::Display for AuthRequired {    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {        write!(f, "Authentication required")    }} /// Trait for agents that support listing, selecting, and querying language models.////// This is an optional capability; agents indicate support via [AgentConnection::model_selector].pub trait AgentModelSelector: 'static {    /// Lists all available language models for this agent.    ///    /// # Parameters    /// - `cx`: The GPUI app context for async operations and global access.    ///    /// # Returns    /// A task resolving to the list of models or an error (e.g., if no models are configured).    fn list_models(&self, cx: &mut App) -> Task<Result<AgentModelList>>;     /// Selects a model for a specific session (thread).    ///    /// # Parameters    /// - `model_id`: The model to select (should be one from [list_models]).    /// - `cx`: The GPUI app context.    ///    /// # Returns    /// A task resolving to `Ok(())` on success or an error.    fn select_model(&self, model_id: AgentModelId, cx: &mut App) -> Task<Result<()>>;     /// Retrieves the currently selected model for a specific session (thread).    ///    /// # Parameters    /// - `cx`: The GPUI app context.    ///    /// # Returns    /// A task resolving to the selected model (always set) or an error (e.g., session not found).    fn selected_model(&self, cx: &mut App) -> Task<Result<AgentModelInfo>>;     fn favorite_model_ids(&self, _cx: &mut App) -> HashSet<AgentModelId> {        HashSet::default()    }     fn toggle_favorite_model(&self, _model_id: AgentModelId, _should_be_favorite: bool, _cx: &App) {    }     /// Whenever the model list is updated the receiver will be notified.    /// Optional for agents that don't update their model list.    fn watch(&self, _cx: &mut App) -> Option<watch::Receiver<()>> {        None    }     /// Returns whether the model picker should render a footer.    fn should_render_footer(&self) -> bool {        false    }} /// Icon for a model in the model selector.#[derive(Debug, Clone, PartialEq, Eq)]pub enum AgentModelIcon {    /// A built-in icon from Zed's icon set.    Named(IconName),    /// Path to a custom SVG icon file.

Lines 1–500 of 1138.