Skip to main content

atman_runtime/
settings_catalog.rs

1use std::fmt;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum SettingSource {
5    Default,
6    Global,
7    Project,
8    Session,
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Activation {
13    Immediate,
14    NextSession,
15    RestartRequired,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct SettingDescriptor {
20    pub key: &'static str,
21    pub title: &'static str,
22    pub description: &'static str,
23    pub source: SettingSource,
24    pub activation: Activation,
25    pub sensitive: bool,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct SettingValue {
30    pub descriptor: SettingDescriptor,
31    pub value: String,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum SettingMutationError {
36    EmptyValue,
37    UnsupportedKey(String),
38}
39
40impl fmt::Display for SettingMutationError {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            Self::EmptyValue => write!(f, "setting value cannot be empty"),
44            Self::UnsupportedKey(key) => write!(f, "unsupported setting: {key}"),
45        }
46    }
47}
48
49impl std::error::Error for SettingMutationError {}
50
51pub fn catalog() -> Vec<SettingDescriptor> {
52    vec![
53        SettingDescriptor {
54            key: "session.context_budget",
55            title: "Context budget",
56            description: "Maximum provider context window budget.",
57            source: SettingSource::Global,
58            activation: Activation::NextSession,
59            sensitive: false,
60        },
61        SettingDescriptor {
62            key: "tools.output.max_bytes",
63            title: "Tool output bytes",
64            description: "Maximum inline tool output before continuation is offered.",
65            source: SettingSource::Global,
66            activation: Activation::Immediate,
67            sensitive: false,
68        },
69        SettingDescriptor {
70            key: "trust.mode",
71            title: "Trust mode",
72            description: "Controls approval behavior for tool execution.",
73            source: SettingSource::Project,
74            activation: Activation::NextSession,
75            sensitive: false,
76        },
77        SettingDescriptor {
78            key: "provider.api_key",
79            title: "Provider API key",
80            description: "Credential used by the selected provider.",
81            source: SettingSource::Global,
82            activation: Activation::Immediate,
83            sensitive: true,
84        },
85    ]
86}
87
88pub fn descriptor(key: &str) -> Option<SettingDescriptor> {
89    catalog().into_iter().find(|item| item.key == key)
90}
91
92pub fn validate_mutation(key: &str, value: &str) -> Result<(), SettingMutationError> {
93    if descriptor(key).is_none() {
94        return Err(SettingMutationError::UnsupportedKey(key.to_owned()));
95    }
96    if value.trim().is_empty() {
97        return Err(SettingMutationError::EmptyValue);
98    }
99    Ok(())
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn catalog_exposes_source_activation_and_sensitive_metadata() {
108        let item = descriptor("provider.api_key").unwrap();
109        assert_eq!(item.source, SettingSource::Global);
110        assert_eq!(item.activation, Activation::Immediate);
111        assert!(item.sensitive);
112    }
113
114    #[test]
115    fn mutation_validation_rejects_unknown_and_empty_values() {
116        assert_eq!(
117            validate_mutation("unknown", "x"),
118            Err(SettingMutationError::UnsupportedKey("unknown".into()))
119        );
120        assert_eq!(
121            validate_mutation("trust.mode", " "),
122            Err(SettingMutationError::EmptyValue)
123        );
124    }
125}