opendev_runtime/
session_model.rs1use serde::{Deserialize, Serialize};
11use std::collections::{HashMap, HashSet};
12
13pub static SESSION_MODEL_FIELDS: &[&str] = &[
15 "model",
16 "model_provider",
17 "model_thinking",
18 "model_thinking_provider",
19 "model_vlm",
20 "model_vlm_provider",
21];
22
23pub type SessionOverlay = HashMap<String, String>;
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct SessionModelManager {
33 originals: HashMap<String, String>,
35 active_overlay: Option<SessionOverlay>,
37}
38
39impl SessionModelManager {
40 pub fn new() -> Self {
42 Self {
43 originals: HashMap::new(),
44 active_overlay: None,
45 }
46 }
47
48 pub fn is_active(&self) -> bool {
50 self.active_overlay.as_ref().is_some_and(|o| !o.is_empty())
51 }
52
53 pub fn apply<G, S>(
58 &mut self,
59 overlay: &SessionOverlay,
60 get_config_value: G,
61 set_config_value: S,
62 ) where
63 G: Fn(&str) -> Option<String>,
64 S: Fn(&str, &str),
65 {
66 if overlay.is_empty() {
67 return;
68 }
69
70 let valid_fields: HashSet<&str> = SESSION_MODEL_FIELDS.iter().copied().collect();
71
72 self.active_overlay = Some(overlay.clone());
73 self.originals.clear();
74
75 for (key, value) in overlay {
76 if !valid_fields.contains(key.as_str()) {
77 continue;
78 }
79 if let Some(original) = get_config_value(key) {
80 self.originals.insert(key.clone(), original);
81 }
82 set_config_value(key, value);
83 }
84 }
85
86 pub fn restore<S>(&mut self, set_config_value: S)
90 where
91 S: Fn(&str, &str),
92 {
93 for (key, value) in &self.originals {
94 set_config_value(key, value);
95 }
96 self.originals.clear();
97 self.active_overlay = None;
98 }
99
100 pub fn get_overlay(&self) -> Option<&SessionOverlay> {
102 self.active_overlay.as_ref()
103 }
104}
105
106impl Default for SessionModelManager {
107 fn default() -> Self {
108 Self::new()
109 }
110}
111
112pub fn get_session_model(metadata: &serde_json::Value) -> Option<SessionOverlay> {
114 metadata
115 .get("session_model")
116 .and_then(|v| serde_json::from_value::<SessionOverlay>(v.clone()).ok())
117}
118
119pub fn set_session_model(metadata: &mut serde_json::Value, overlay: &SessionOverlay) {
121 if let Some(obj) = metadata.as_object_mut() {
122 obj.insert(
123 "session_model".to_string(),
124 serde_json::to_value(overlay).unwrap_or_default(),
125 );
126 }
127}
128
129pub fn clear_session_model(metadata: &mut serde_json::Value) {
131 if let Some(obj) = metadata.as_object_mut() {
132 obj.remove("session_model");
133 }
134}
135
136pub fn validate_session_model(overlay: &SessionOverlay) -> (SessionOverlay, Vec<String>) {
140 if overlay.is_empty() {
141 return (HashMap::new(), Vec::new());
142 }
143
144 let valid_fields: HashSet<&str> = SESSION_MODEL_FIELDS.iter().copied().collect();
145 let mut valid = HashMap::new();
146 let mut warnings = Vec::new();
147
148 for (key, value) in overlay {
149 if valid_fields.contains(key.as_str()) {
150 valid.insert(key.clone(), value.clone());
151 } else {
152 warnings.push(format!("Unknown session-model field '{}', ignored", key));
153 }
154 }
155
156 (valid, warnings)
157}
158
159#[cfg(test)]
160#[path = "session_model_tests.rs"]
161mod tests;