settings.rs

  1use std::path::PathBuf;
  2
  3use crate::AgentServerCommand;
  4use anyhow::Result;
  5use collections::HashMap;
  6use gpui::{App, SharedString};
  7use schemars::JsonSchema;
  8use serde::{Deserialize, Serialize};
  9use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
 10
 11pub fn init(cx: &mut App) {
 12    AllAgentServersSettings::register(cx);
 13}
 14
 15#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, SettingsUi, SettingsKey)]
 16#[settings_key(key = "agent_servers")]
 17pub struct AllAgentServersSettingsContent {
 18    gemini: Option<GeminiSettingsContent>,
 19    claude: Option<AgentServerCommand>,
 20    /// Custom agent servers configured by the user
 21    #[serde(flatten)]
 22    pub custom: HashMap<SharedString, AgentServerCommand>,
 23}
 24
 25#[derive(Clone, Debug, Default)]
 26pub struct AllAgentServersSettings {
 27    pub commands: HashMap<SharedString, AgentServerCommand>,
 28    pub gemini_is_system: bool,
 29}
 30
 31impl AllAgentServersSettings {
 32    pub fn is_system(this: &Self, name: &str) -> bool {
 33        if name == "gemini" {
 34            this.gemini_is_system
 35        } else {
 36            false
 37        }
 38    }
 39}
 40
 41impl std::ops::Deref for AllAgentServersSettings {
 42    type Target = HashMap<SharedString, AgentServerCommand>;
 43    fn deref(&self) -> &Self::Target {
 44        &self.commands
 45    }
 46}
 47
 48impl std::ops::DerefMut for AllAgentServersSettings {
 49    fn deref_mut(&mut self) -> &mut Self::Target {
 50        &mut self.commands
 51    }
 52}
 53
 54#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, PartialEq)]
 55pub struct GeminiSettingsContent {
 56    ignore_system_version: Option<bool>,
 57    #[serde(flatten)]
 58    inner: Option<AgentServerCommand>,
 59}
 60
 61#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)]
 62pub struct BuiltinAgentServerSettings {
 63    /// Absolute path to a binary to be used when launching this agent.
 64    ///
 65    /// This can be used to run a specific binary without automatic downloads or searching `$PATH`.
 66    #[serde(rename = "command")]
 67    pub path: Option<PathBuf>,
 68    /// If a binary is specified in `command`, it will be passed these arguments.
 69    pub args: Option<Vec<String>>,
 70    /// If a binary is specified in `command`, it will be passed these environment variables.
 71    pub env: Option<HashMap<String, String>>,
 72    /// Whether to skip searching `$PATH` for an agent server binary when
 73    /// launching this agent.
 74    ///
 75    /// This has no effect if a `command` is specified. Otherwise, when this is
 76    /// `false`, Zed will search `$PATH` for an agent server binary and, if one
 77    /// is found, use it for threads with this agent. If no agent binary is
 78    /// found on `$PATH`, Zed will automatically install and use its own binary.
 79    /// When this is `true`, Zed will not search `$PATH`, and will always use
 80    /// its own binary.
 81    ///
 82    /// Default: true
 83    pub ignore_system_version: Option<bool>,
 84}
 85
 86impl BuiltinAgentServerSettings {
 87    pub(crate) fn custom_command(self) -> Option<AgentServerCommand> {
 88        self.path.map(|path| AgentServerCommand {
 89            path,
 90            args: self.args.unwrap_or_default(),
 91            env: self.env,
 92        })
 93    }
 94}
 95
 96impl From<AgentServerCommand> for BuiltinAgentServerSettings {
 97    fn from(value: AgentServerCommand) -> Self {
 98        BuiltinAgentServerSettings {
 99            path: Some(value.path),
100            args: Some(value.args),
101            env: value.env,
102            ..Default::default()
103        }
104    }
105}
106
107#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)]
108pub struct AgentServerSettings {
109    #[serde(flatten)]
110    pub command: AgentServerCommand,
111}
112
113impl settings::Settings for AllAgentServersSettings {
114    type FileContent = AllAgentServersSettingsContent;
115
116    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
117        let mut settings = AllAgentServersSettings::default();
118
119        for AllAgentServersSettingsContent {
120            gemini,
121            claude,
122            custom,
123        } in sources.defaults_and_customizations()
124        {
125            if let Some(gemini) = gemini {
126                if let Some(ignore) = gemini.ignore_system_version {
127                    settings.gemini_is_system = !ignore;
128                }
129                if let Some(gemini) = gemini.inner.as_ref() {
130                    settings.insert("gemini".into(), gemini.clone());
131                }
132            }
133            if let Some(claude) = claude.clone() {
134                settings.insert("claude".into(), claude);
135            }
136
137            // Merge custom agents
138            for (name, command) in custom {
139                // Skip built-in agent names to avoid conflicts
140                if name != "gemini" && name != "claude" {
141                    settings.commands.insert(name.clone(), command.clone());
142                }
143            }
144        }
145
146        Ok(settings)
147    }
148
149    fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
150}
151
152#[cfg(test)]
153mod tests {
154    use serde_json::json;
155
156    use crate::{AgentServerCommand, GeminiSettingsContent};
157
158    #[test]
159    fn test_deserialization() {
160        let value = json!({
161            "command": "foo",
162            "args": ["bar"],
163            "ignore_system_version": false
164        });
165        let settings = serde_json::from_value::<GeminiSettingsContent>(value).unwrap();
166        assert_eq!(
167            settings,
168            GeminiSettingsContent {
169                ignore_system_version: Some(false),
170                inner: Some(AgentServerCommand {
171                    path: "foo".into(),
172                    args: vec!["bar".into()],
173                    env: Default::default(),
174                })
175            }
176        )
177    }
178}