Skip to main content

vtcode_config/
acp.rs

1use crate::constants::env::acp::AgentClientProtocolEnvKey;
2use serde::{Deserialize, Serialize};
3
4fn parse_env_bool(key: AgentClientProtocolEnvKey, default: bool) -> bool {
5    std::env::var(key.as_str())
6        .ok()
7        .and_then(|value| {
8            let normalized = value.trim().to_ascii_lowercase();
9            match normalized.as_str() {
10                "1" | "true" | "yes" | "on" => Some(true),
11                "0" | "false" | "no" | "off" => Some(false),
12                _ => None,
13            }
14        })
15        .unwrap_or(default)
16}
17
18fn default_enabled() -> bool {
19    parse_env_bool(AgentClientProtocolEnvKey::Enabled, false)
20}
21
22fn default_zed_enabled() -> bool {
23    parse_env_bool(AgentClientProtocolEnvKey::ZedEnabled, default_enabled())
24}
25
26fn default_zed_tools_read_file_enabled() -> bool {
27    parse_env_bool(AgentClientProtocolEnvKey::ZedToolsReadFileEnabled, true)
28}
29
30fn default_zed_tools_list_files_enabled() -> bool {
31    parse_env_bool(AgentClientProtocolEnvKey::ZedToolsListFilesEnabled, true)
32}
33
34fn parse_env_trust_mode(
35    key: AgentClientProtocolEnvKey,
36    default: AgentClientProtocolZedWorkspaceTrustMode,
37) -> AgentClientProtocolZedWorkspaceTrustMode {
38    std::env::var(key.as_str())
39        .ok()
40        .and_then(|value| AgentClientProtocolZedWorkspaceTrustMode::from_env_value(&value))
41        .unwrap_or(default)
42}
43
44fn default_zed_workspace_trust_mode() -> AgentClientProtocolZedWorkspaceTrustMode {
45    parse_env_trust_mode(
46        AgentClientProtocolEnvKey::ZedWorkspaceTrust,
47        AgentClientProtocolZedWorkspaceTrustMode::FullAuto,
48    )
49}
50
51fn default_transport() -> AgentClientProtocolTransport {
52    AgentClientProtocolTransport::Stdio
53}
54
55/// Agent Client Protocol configuration root
56#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
57#[derive(Debug, Clone, Deserialize, Serialize)]
58pub struct AgentClientProtocolConfig {
59    /// Globally enable the ACP bridge
60    #[serde(default = "default_enabled")]
61    pub enabled: bool,
62
63    /// Agent Client Protocol settings
64    #[serde(default)]
65    pub zed: AgentClientProtocolZedConfig,
66}
67
68impl Default for AgentClientProtocolConfig {
69    fn default() -> Self {
70        Self {
71            enabled: default_enabled(),
72            zed: AgentClientProtocolZedConfig::default(),
73        }
74    }
75}
76
77/// Transport options supported by the ACP bridge
78#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
80#[serde(rename_all = "snake_case")]
81pub enum AgentClientProtocolTransport {
82    /// Communicate over stdio (spawned process model)
83    Stdio,
84}
85
86impl Default for AgentClientProtocolTransport {
87    fn default() -> Self {
88        default_transport()
89    }
90}
91
92/// Zed-specific configuration
93#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
94#[derive(Debug, Clone, Deserialize, Serialize)]
95pub struct AgentClientProtocolZedConfig {
96    /// Enable Zed integration
97    #[serde(default = "default_zed_enabled")]
98    pub enabled: bool,
99
100    /// Transport used to communicate with the Zed client
101    #[serde(default = "default_transport")]
102    pub transport: AgentClientProtocolTransport,
103
104    /// Tool toggles exposed through the Zed bridge
105    #[serde(default)]
106    pub tools: AgentClientProtocolZedToolsConfig,
107
108    /// Desired workspace trust level when running under ACP
109    #[serde(default = "default_zed_workspace_trust_mode")]
110    pub workspace_trust: AgentClientProtocolZedWorkspaceTrustMode,
111
112    /// Authentication method configuration for ACP
113    #[serde(default)]
114    pub auth: AcpAuthConfig,
115}
116
117impl Default for AgentClientProtocolZedConfig {
118    fn default() -> Self {
119        Self {
120            enabled: default_zed_enabled(),
121            transport: default_transport(),
122            tools: AgentClientProtocolZedToolsConfig::default(),
123            workspace_trust: default_zed_workspace_trust_mode(),
124            auth: AcpAuthConfig::default(),
125        }
126    }
127}
128
129/// Zed bridge tool configuration flags
130#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
131#[derive(Debug, Clone, Deserialize, Serialize)]
132pub struct AgentClientProtocolZedToolsConfig {
133    /// Toggle the read_file function bridge
134    #[serde(default = "default_zed_tools_read_file_enabled")]
135    pub read_file: bool,
136
137    /// Toggle the list_files function bridge
138    #[serde(default = "default_zed_tools_list_files_enabled")]
139    pub list_files: bool,
140}
141
142impl Default for AgentClientProtocolZedToolsConfig {
143    fn default() -> Self {
144        Self {
145            read_file: default_zed_tools_read_file_enabled(),
146            list_files: default_zed_tools_list_files_enabled(),
147        }
148    }
149}
150
151/// Workspace trust configuration for the Zed ACP bridge
152#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
153#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
154#[serde(rename_all = "snake_case")]
155pub enum AgentClientProtocolZedWorkspaceTrustMode {
156    /// Maintain full automation trust
157    FullAuto,
158    /// Restrict to tools policy safeguards
159    ToolsPolicy,
160}
161
162impl AgentClientProtocolZedWorkspaceTrustMode {
163    fn from_env_value(value: &str) -> Option<Self> {
164        let normalized = value.trim().to_ascii_lowercase();
165        match normalized.as_str() {
166            "full_auto" | "full-auto" | "full" => Some(Self::FullAuto),
167            "tools_policy" | "tools-policy" | "tools" => Some(Self::ToolsPolicy),
168            _ => None,
169        }
170    }
171
172    /// Resolve the workspace trust level represented by this configuration.
173    pub fn to_workspace_trust_level(self) -> WorkspaceTrustLevel {
174        match self {
175            Self::FullAuto => WorkspaceTrustLevel::FullAuto,
176            Self::ToolsPolicy => WorkspaceTrustLevel::ToolsPolicy,
177        }
178    }
179}
180
181/// Workspace trust levels exposed through the Agent Client Protocol configuration.
182#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
183#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
184#[serde(rename_all = "snake_case")]
185#[derive(Default)]
186pub enum WorkspaceTrustLevel {
187    #[default]
188    ToolsPolicy,
189    FullAuto,
190}
191
192impl std::fmt::Display for WorkspaceTrustLevel {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        match self {
195            WorkspaceTrustLevel::ToolsPolicy => write!(f, "tools policy"),
196            WorkspaceTrustLevel::FullAuto => write!(f, "full auto"),
197        }
198    }
199}
200
201/// ACP authentication configuration
202#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
203#[derive(Debug, Clone, Deserialize, Serialize)]
204pub struct AcpAuthConfig {
205    /// Default authentication method for ACP agents
206    /// Options: "agent" (default - agent handles auth), "env_var", "terminal"
207    #[serde(default = "default_auth_method")]
208    pub default_method: String,
209
210    /// Environment variable name for auth (used when default_method is "env_var")
211    /// Examples: "OPENAI_API_KEY", "ANTHROPIC_API_KEY"
212    #[serde(default)]
213    pub env_var_name: Option<String>,
214
215    /// URL where users can get their API key (optional, for UI display)
216    #[serde(default)]
217    pub auth_url: Option<String>,
218}
219
220fn default_auth_method() -> String {
221    "agent".to_string()
222}
223
224impl Default for AcpAuthConfig {
225    fn default() -> Self {
226        Self {
227            default_method: default_auth_method(),
228            env_var_name: None,
229            auth_url: None,
230        }
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    #[test]
239    fn defaults_use_stdio_transport() {
240        let cfg = AgentClientProtocolConfig::default();
241        assert!(matches!(
242            cfg.zed.transport,
243            AgentClientProtocolTransport::Stdio
244        ));
245        assert!(cfg.zed.tools.read_file);
246        assert!(cfg.zed.tools.list_files);
247        assert!(matches!(
248            cfg.zed.workspace_trust,
249            AgentClientProtocolZedWorkspaceTrustMode::FullAuto
250        ));
251    }
252
253    #[test]
254    fn test_acp_auth_config_defaults() {
255        let auth = AcpAuthConfig::default();
256        assert_eq!(auth.default_method, "agent");
257        assert!(auth.env_var_name.is_none());
258        assert!(auth.auth_url.is_none());
259    }
260
261    #[test]
262    fn test_acp_auth_config_in_zed_config() {
263        let cfg = AgentClientProtocolZedConfig::default();
264        assert_eq!(cfg.auth.default_method, "agent");
265    }
266
267    #[test]
268    fn test_acp_auth_config_deserialization() {
269        let toml_str = r#"
270default_method = "env_var"
271env_var_name = "OPENAI_API_KEY"
272auth_url = "https://platform.openai.com/api-keys"
273"#;
274        let auth: AcpAuthConfig = toml::from_str(toml_str).unwrap();
275        assert_eq!(auth.default_method, "env_var");
276        assert_eq!(auth.env_var_name, Some("OPENAI_API_KEY".to_string()));
277        assert_eq!(
278            auth.auth_url,
279            Some("https://platform.openai.com/api-keys".to_string())
280        );
281    }
282}