1use std::collections::BTreeMap;
2use std::fmt::{Display, Formatter};
3use std::path::PathBuf;
4
5use crate::json::JsonValue;
6use crate::sandbox::SandboxConfig;
7
8pub const CODINEER_SETTINGS_SCHEMA_NAME: &str = "SettingsSchema";
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
11pub enum ConfigSource {
12 User,
13 Project,
14 Local,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ResolvedPermissionMode {
19 ReadOnly,
20 WorkspaceWrite,
21 DangerFullAccess,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct ConfigEntry {
26 pub source: ConfigSource,
27 pub path: PathBuf,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct RuntimeConfig {
32 merged: BTreeMap<String, JsonValue>,
33 loaded_entries: Vec<ConfigEntry>,
34 feature_config: RuntimeFeatureConfig,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Default)]
38pub struct RuntimePluginConfig {
39 pub(crate) enabled_plugins: BTreeMap<String, bool>,
40 pub(crate) external_directories: Vec<String>,
41 pub(crate) install_root: Option<String>,
42 pub(crate) registry_path: Option<String>,
43 pub(crate) bundled_root: Option<String>,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Default)]
47pub struct RuntimeFeatureConfig {
48 pub(crate) hooks: RuntimeHookConfig,
49 pub(crate) plugins: RuntimePluginConfig,
50 pub(crate) mcp: McpConfigCollection,
51 pub(crate) oauth: Option<OAuthConfig>,
52 pub(crate) model: Option<String>,
53 pub(crate) permission_mode: Option<ResolvedPermissionMode>,
54 pub(crate) sandbox: SandboxConfig,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Default)]
58pub struct RuntimeHookConfig {
59 pub(crate) pre_tool_use: Vec<String>,
60 pub(crate) post_tool_use: Vec<String>,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Default)]
64pub struct McpConfigCollection {
65 pub(crate) servers: BTreeMap<String, ScopedMcpServerConfig>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct ScopedMcpServerConfig {
70 pub scope: ConfigSource,
71 pub config: McpServerConfig,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum McpTransport {
76 Stdio,
77 Sse,
78 Http,
79 Ws,
80 Sdk,
81 ManagedProxy,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum McpServerConfig {
86 Stdio(McpStdioServerConfig),
87 Sse(McpRemoteServerConfig),
88 Http(McpRemoteServerConfig),
89 Ws(McpWebSocketServerConfig),
90 Sdk(McpSdkServerConfig),
91 ManagedProxy(McpManagedProxyServerConfig),
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct McpStdioServerConfig {
96 pub command: String,
97 pub args: Vec<String>,
98 pub env: BTreeMap<String, String>,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct McpRemoteServerConfig {
103 pub url: String,
104 pub headers: BTreeMap<String, String>,
105 pub headers_helper: Option<String>,
106 pub oauth: Option<McpOAuthConfig>,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
110pub struct McpWebSocketServerConfig {
111 pub url: String,
112 pub headers: BTreeMap<String, String>,
113 pub headers_helper: Option<String>,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub struct McpSdkServerConfig {
118 pub name: String,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct McpManagedProxyServerConfig {
123 pub url: String,
124 pub id: String,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct McpOAuthConfig {
129 pub client_id: Option<String>,
130 pub callback_port: Option<u16>,
131 pub auth_server_metadata_url: Option<String>,
132 pub xaa: Option<bool>,
133}
134
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct OAuthConfig {
137 pub client_id: String,
138 pub authorize_url: String,
139 pub token_url: String,
140 pub callback_port: Option<u16>,
141 pub manual_redirect_url: Option<String>,
142 pub scopes: Vec<String>,
143}
144
145#[derive(Debug)]
146pub enum ConfigError {
147 Io(std::io::Error),
148 Parse(String),
149}
150
151impl Display for ConfigError {
152 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
153 match self {
154 Self::Io(error) => write!(f, "{error}"),
155 Self::Parse(error) => write!(f, "{error}"),
156 }
157 }
158}
159
160impl std::error::Error for ConfigError {}
161
162impl From<std::io::Error> for ConfigError {
163 fn from(value: std::io::Error) -> Self {
164 Self::Io(value)
165 }
166}
167
168impl RuntimeConfig {
169 #[must_use]
170 pub(crate) fn new(
171 merged: BTreeMap<String, JsonValue>,
172 loaded_entries: Vec<ConfigEntry>,
173 feature_config: RuntimeFeatureConfig,
174 ) -> Self {
175 Self {
176 merged,
177 loaded_entries,
178 feature_config,
179 }
180 }
181
182 #[must_use]
183 pub fn empty() -> Self {
184 Self::new(BTreeMap::new(), Vec::new(), RuntimeFeatureConfig::default())
185 }
186
187 #[must_use]
188 pub fn merged(&self) -> &BTreeMap<String, JsonValue> {
189 &self.merged
190 }
191
192 #[must_use]
193 pub fn loaded_entries(&self) -> &[ConfigEntry] {
194 &self.loaded_entries
195 }
196
197 #[must_use]
198 pub fn get(&self, key: &str) -> Option<&JsonValue> {
199 self.merged.get(key)
200 }
201
202 #[must_use]
203 pub fn as_json(&self) -> JsonValue {
204 JsonValue::Object(self.merged.clone())
205 }
206
207 #[must_use]
208 pub fn feature_config(&self) -> &RuntimeFeatureConfig {
209 &self.feature_config
210 }
211
212 #[must_use]
213 pub fn mcp(&self) -> &McpConfigCollection {
214 &self.feature_config.mcp
215 }
216
217 #[must_use]
218 pub fn hooks(&self) -> &RuntimeHookConfig {
219 &self.feature_config.hooks
220 }
221
222 #[must_use]
223 pub fn plugins(&self) -> &RuntimePluginConfig {
224 &self.feature_config.plugins
225 }
226
227 #[must_use]
228 pub fn oauth(&self) -> Option<&OAuthConfig> {
229 self.feature_config.oauth.as_ref()
230 }
231
232 #[must_use]
233 pub fn model(&self) -> Option<&str> {
234 self.feature_config.model.as_deref()
235 }
236
237 #[must_use]
238 pub fn permission_mode(&self) -> Option<ResolvedPermissionMode> {
239 self.feature_config.permission_mode
240 }
241
242 #[must_use]
243 pub fn sandbox(&self) -> &SandboxConfig {
244 &self.feature_config.sandbox
245 }
246}
247
248impl RuntimeFeatureConfig {
249 #[must_use]
250 pub fn with_hooks(mut self, hooks: RuntimeHookConfig) -> Self {
251 self.hooks = hooks;
252 self
253 }
254
255 #[must_use]
256 pub fn with_plugins(mut self, plugins: RuntimePluginConfig) -> Self {
257 self.plugins = plugins;
258 self
259 }
260
261 #[must_use]
262 pub fn hooks(&self) -> &RuntimeHookConfig {
263 &self.hooks
264 }
265
266 #[must_use]
267 pub fn plugins(&self) -> &RuntimePluginConfig {
268 &self.plugins
269 }
270
271 #[must_use]
272 pub fn mcp(&self) -> &McpConfigCollection {
273 &self.mcp
274 }
275
276 #[must_use]
277 pub fn oauth(&self) -> Option<&OAuthConfig> {
278 self.oauth.as_ref()
279 }
280
281 #[must_use]
282 pub fn model(&self) -> Option<&str> {
283 self.model.as_deref()
284 }
285
286 #[must_use]
287 pub fn permission_mode(&self) -> Option<ResolvedPermissionMode> {
288 self.permission_mode
289 }
290
291 #[must_use]
292 pub fn sandbox(&self) -> &SandboxConfig {
293 &self.sandbox
294 }
295}
296
297impl RuntimePluginConfig {
298 #[must_use]
299 pub fn enabled_plugins(&self) -> &BTreeMap<String, bool> {
300 &self.enabled_plugins
301 }
302
303 #[must_use]
304 pub fn external_directories(&self) -> &[String] {
305 &self.external_directories
306 }
307
308 #[must_use]
309 pub fn install_root(&self) -> Option<&str> {
310 self.install_root.as_deref()
311 }
312
313 #[must_use]
314 pub fn registry_path(&self) -> Option<&str> {
315 self.registry_path.as_deref()
316 }
317
318 #[must_use]
319 pub fn bundled_root(&self) -> Option<&str> {
320 self.bundled_root.as_deref()
321 }
322
323 pub fn set_plugin_state(&mut self, plugin_id: String, enabled: bool) {
324 self.enabled_plugins.insert(plugin_id, enabled);
325 }
326
327 #[must_use]
328 pub fn state_for(&self, plugin_id: &str, default_enabled: bool) -> bool {
329 self.enabled_plugins
330 .get(plugin_id)
331 .copied()
332 .unwrap_or(default_enabled)
333 }
334}
335
336impl RuntimeHookConfig {
337 #[must_use]
338 pub fn new(pre_tool_use: Vec<String>, post_tool_use: Vec<String>) -> Self {
339 Self {
340 pre_tool_use,
341 post_tool_use,
342 }
343 }
344
345 #[must_use]
346 pub fn pre_tool_use(&self) -> &[String] {
347 &self.pre_tool_use
348 }
349
350 #[must_use]
351 pub fn post_tool_use(&self) -> &[String] {
352 &self.post_tool_use
353 }
354
355 #[must_use]
356 pub fn merged(&self, other: &Self) -> Self {
357 let mut merged = self.clone();
358 merged.extend(other);
359 merged
360 }
361
362 pub fn extend(&mut self, other: &Self) {
363 extend_unique(&mut self.pre_tool_use, other.pre_tool_use());
364 extend_unique(&mut self.post_tool_use, other.post_tool_use());
365 }
366}
367
368impl McpConfigCollection {
369 #[must_use]
370 pub fn servers(&self) -> &BTreeMap<String, ScopedMcpServerConfig> {
371 &self.servers
372 }
373
374 #[must_use]
375 pub fn get(&self, name: &str) -> Option<&ScopedMcpServerConfig> {
376 self.servers.get(name)
377 }
378}
379
380impl ScopedMcpServerConfig {
381 #[must_use]
382 pub fn transport(&self) -> McpTransport {
383 self.config.transport()
384 }
385}
386
387impl McpServerConfig {
388 #[must_use]
389 pub fn transport(&self) -> McpTransport {
390 match self {
391 Self::Stdio(_) => McpTransport::Stdio,
392 Self::Sse(_) => McpTransport::Sse,
393 Self::Http(_) => McpTransport::Http,
394 Self::Ws(_) => McpTransport::Ws,
395 Self::Sdk(_) => McpTransport::Sdk,
396 Self::ManagedProxy(_) => McpTransport::ManagedProxy,
397 }
398 }
399}
400
401fn extend_unique(target: &mut Vec<String>, values: &[String]) {
402 for value in values {
403 push_unique(target, value.clone());
404 }
405}
406
407fn push_unique(target: &mut Vec<String>, value: String) {
408 if !target.iter().any(|existing| existing == &value) {
409 target.push(value);
410 }
411}