1use agent_client_protocol as acp;
2use std::path::PathBuf;
3
4use crate::AgentServerCommand;
5use anyhow::Result;
6use collections::HashMap;
7use gpui::{App, SharedString};
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
11
12pub fn init(cx: &mut App) {
13 AllAgentServersSettings::register(cx);
14}
15
16#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, SettingsUi, SettingsKey)]
17#[settings_key(key = "agent_servers")]
18pub struct AllAgentServersSettings {
19 pub gemini: Option<BuiltinAgentServerSettings>,
20 pub claude: Option<BuiltinAgentServerSettings>,
21
22 /// Custom agent servers configured by the user
23 #[serde(flatten)]
24 pub custom: HashMap<SharedString, CustomAgentServerSettings>,
25}
26
27#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)]
28pub struct BuiltinAgentServerSettings {
29 /// Absolute path to a binary to be used when launching this agent.
30 ///
31 /// This can be used to run a specific binary without automatic downloads or searching `$PATH`.
32 #[serde(rename = "command")]
33 pub path: Option<PathBuf>,
34 /// If a binary is specified in `command`, it will be passed these arguments.
35 pub args: Option<Vec<String>>,
36 /// If a binary is specified in `command`, it will be passed these environment variables.
37 pub env: Option<HashMap<String, String>>,
38 /// Whether to skip searching `$PATH` for an agent server binary when
39 /// launching this agent.
40 ///
41 /// This has no effect if a `command` is specified. Otherwise, when this is
42 /// `false`, Zed will search `$PATH` for an agent server binary and, if one
43 /// is found, use it for threads with this agent. If no agent binary is
44 /// found on `$PATH`, Zed will automatically install and use its own binary.
45 /// When this is `true`, Zed will not search `$PATH`, and will always use
46 /// its own binary.
47 ///
48 /// Default: true
49 pub ignore_system_version: Option<bool>,
50 /// The default mode for new threads.
51 ///
52 /// Note: Not all agents support modes.
53 ///
54 /// Default: None
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub default_mode: Option<acp::SessionModeId>,
57}
58
59impl BuiltinAgentServerSettings {
60 pub(crate) fn custom_command(self) -> Option<AgentServerCommand> {
61 self.path.map(|path| AgentServerCommand {
62 path,
63 args: self.args.unwrap_or_default(),
64 env: self.env,
65 })
66 }
67}
68
69impl From<AgentServerCommand> for BuiltinAgentServerSettings {
70 fn from(value: AgentServerCommand) -> Self {
71 BuiltinAgentServerSettings {
72 path: Some(value.path),
73 args: Some(value.args),
74 env: value.env,
75 ..Default::default()
76 }
77 }
78}
79
80#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)]
81pub struct CustomAgentServerSettings {
82 #[serde(flatten)]
83 pub command: AgentServerCommand,
84 /// The default mode for new threads.
85 ///
86 /// Note: Not all agents support modes.
87 ///
88 /// Default: None
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub default_mode: Option<acp::SessionModeId>,
91}
92
93impl settings::Settings for AllAgentServersSettings {
94 type FileContent = Self;
95
96 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
97 let mut settings = AllAgentServersSettings::default();
98
99 for AllAgentServersSettings {
100 gemini,
101 claude,
102 custom,
103 } in sources.defaults_and_customizations()
104 {
105 if gemini.is_some() {
106 settings.gemini = gemini.clone();
107 }
108 if claude.is_some() {
109 settings.claude = claude.clone();
110 }
111
112 // Merge custom agents
113 for (name, config) in custom {
114 // Skip built-in agent names to avoid conflicts
115 if name != "gemini" && name != "claude" {
116 settings.custom.insert(name.clone(), config.clone());
117 }
118 }
119 }
120
121 Ok(settings)
122 }
123
124 fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
125}