Skip to main content

codineer_runtime/config/
types.rs

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}