Skip to main content

acp_utils/
config_meta.rs

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