Skip to main content

acp_utils/
config_meta.rs

1use serde::{Deserialize, Serialize};
2use utils::ReasoningEffort;
3
4type Meta = serde_json::Map<String, serde_json::Value>;
5
6/// Meta for a top-level `SessionConfigOption` (e.g. the "model" config).
7#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
8pub struct ConfigOptionMeta {
9    #[serde(default, skip_serializing_if = "is_false")]
10    pub multi_select: bool,
11}
12
13#[allow(clippy::trivially_copy_pass_by_ref)] // serde requires &T
14fn is_false(b: &bool) -> bool {
15    !b
16}
17
18/// Meta for an individual `SessionConfigSelectOption` (e.g. one model choice).
19#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
20pub struct SelectOptionMeta {
21    #[serde(default, skip_serializing_if = "Vec::is_empty")]
22    pub reasoning_levels: Vec<ReasoningEffort>,
23    #[serde(default, skip_serializing_if = "is_false")]
24    pub supports_image: bool,
25    #[serde(default, skip_serializing_if = "is_false")]
26    pub supports_audio: bool,
27}
28
29impl SelectOptionMeta {
30    pub fn supports_reasoning(&self) -> bool {
31        !self.reasoning_levels.is_empty()
32    }
33}
34
35impl ConfigOptionMeta {
36    pub fn into_meta(self) -> Option<Meta> {
37        if self == Self::default() {
38            return None;
39        }
40        match serde_json::to_value(self).expect("ConfigOptionMeta should serialize") {
41            serde_json::Value::Object(map) => Some(map),
42            _ => unreachable!(),
43        }
44    }
45
46    pub fn from_meta(meta: Option<&Meta>) -> Self {
47        meta.and_then(|m| serde_json::from_value(serde_json::Value::Object(m.clone())).ok()).unwrap_or_default()
48    }
49}
50
51impl SelectOptionMeta {
52    pub fn into_meta(self) -> Option<Meta> {
53        if self == Self::default() {
54            return None;
55        }
56        match serde_json::to_value(self).expect("SelectOptionMeta should serialize") {
57            serde_json::Value::Object(map) => Some(map),
58            _ => unreachable!(),
59        }
60    }
61
62    pub fn from_meta(meta: Option<&Meta>) -> Self {
63        meta.and_then(|m| serde_json::from_value(serde_json::Value::Object(m.clone())).ok()).unwrap_or_default()
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn config_option_meta_roundtrip() {
73        let original = ConfigOptionMeta { multi_select: true };
74        let meta = original.clone().into_meta();
75        assert!(meta.is_some());
76        let restored = ConfigOptionMeta::from_meta(meta.as_ref());
77        assert_eq!(restored, original);
78    }
79
80    #[test]
81    fn select_option_meta_roundtrip() {
82        let original = SelectOptionMeta {
83            reasoning_levels: vec![ReasoningEffort::Low, ReasoningEffort::Medium, ReasoningEffort::High],
84            supports_image: true,
85            supports_audio: false,
86        };
87        let meta = original.clone().into_meta();
88        assert!(meta.is_some());
89        let restored = SelectOptionMeta::from_meta(meta.as_ref());
90        assert_eq!(restored, original);
91    }
92
93    #[test]
94    fn default_produces_none() {
95        assert!(ConfigOptionMeta::default().into_meta().is_none());
96        assert!(SelectOptionMeta::default().into_meta().is_none());
97    }
98
99    #[test]
100    fn from_meta_none_returns_default() {
101        assert_eq!(ConfigOptionMeta::from_meta(None), ConfigOptionMeta::default());
102        assert_eq!(SelectOptionMeta::from_meta(None), SelectOptionMeta::default());
103    }
104
105    #[test]
106    fn unknown_keys_are_ignored() {
107        let mut map = serde_json::Map::new();
108        map.insert("multi_select".to_string(), serde_json::Value::Bool(true));
109        map.insert("unknown_field".to_string(), serde_json::Value::String("hello".to_string()));
110        let parsed = ConfigOptionMeta::from_meta(Some(&map));
111        assert_eq!(parsed, ConfigOptionMeta { multi_select: true });
112    }
113
114    #[test]
115    fn false_fields_omitted_from_serialized_output() {
116        let meta = ConfigOptionMeta { multi_select: false };
117        let value = serde_json::to_value(&meta).unwrap();
118        let obj = value.as_object().unwrap();
119        assert!(!obj.contains_key("multi_select"));
120
121        let meta = SelectOptionMeta::default();
122        let value = serde_json::to_value(&meta).unwrap();
123        let obj = value.as_object().unwrap();
124        assert!(!obj.contains_key("reasoning_levels"));
125        assert!(!obj.contains_key("supports_image"));
126        assert!(!obj.contains_key("supports_audio"));
127    }
128
129    #[test]
130    fn supports_reasoning_convenience() {
131        assert!(!SelectOptionMeta::default().supports_reasoning());
132        let meta = SelectOptionMeta {
133            reasoning_levels: vec![ReasoningEffort::Low, ReasoningEffort::High],
134            supports_image: false,
135            supports_audio: false,
136        };
137        assert!(meta.supports_reasoning());
138    }
139}