vtcode_config/loader/
merge.rs1use crate::loader::layers::ConfigLayerMetadata;
2use hashbrown::HashMap;
3
4pub fn merge_toml_values(base: &mut toml::Value, overlay: &toml::Value) {
9 match (base, overlay) {
10 (toml::Value::Table(base_table), toml::Value::Table(overlay_table)) => {
11 for (key, value) in overlay_table {
12 if let Some(base_value) = base_table.get_mut(key) {
13 merge_toml_values(base_value, value);
14 } else {
15 base_table.insert(key.clone(), value.clone());
16 }
17 }
18 }
19 (base, overlay) => {
20 *base = overlay.clone();
21 }
22 }
23}
24
25pub fn merge_toml_values_with_origins(
27 base: &mut toml::Value,
28 overlay: &toml::Value,
29 origins: &mut HashMap<String, ConfigLayerMetadata>,
30 layer: &ConfigLayerMetadata,
31) {
32 merge_with_origins(base, overlay, "", origins, layer);
33}
34
35fn merge_with_origins(
36 base: &mut toml::Value,
37 overlay: &toml::Value,
38 path: &str,
39 origins: &mut HashMap<String, ConfigLayerMetadata>,
40 layer: &ConfigLayerMetadata,
41) {
42 match (base, overlay) {
43 (toml::Value::Table(base_table), toml::Value::Table(overlay_table)) => {
44 for (key, value) in overlay_table {
45 let child_path = if path.is_empty() {
46 key.clone()
47 } else {
48 format!("{path}.{key}")
49 };
50
51 if let Some(base_value) = base_table.get_mut(key) {
52 if base_value.is_table() && value.is_table() {
53 merge_with_origins(base_value, value, &child_path, origins, layer);
54 } else {
55 *base_value = value.clone();
56 assign_origins(value, &child_path, origins, layer);
57 }
58 } else {
59 base_table.insert(key.clone(), value.clone());
60 assign_origins(value, &child_path, origins, layer);
61 }
62 }
63 }
64 (base, overlay) => {
65 *base = overlay.clone();
66 if !path.is_empty() {
67 assign_origins(overlay, path, origins, layer);
68 }
69 }
70 }
71}
72
73fn assign_origins(
74 value: &toml::Value,
75 path: &str,
76 origins: &mut HashMap<String, ConfigLayerMetadata>,
77 layer: &ConfigLayerMetadata,
78) {
79 match value {
80 toml::Value::Table(table) => {
81 for (key, child) in table {
82 let child_path = if path.is_empty() {
83 key.clone()
84 } else {
85 format!("{path}.{key}")
86 };
87 assign_origins(child, &child_path, origins, layer);
88 }
89 }
90 _ => {
91 origins.insert(path.to_string(), layer.clone());
92 }
93 }
94}
95
96pub(crate) fn normalize_legacy_top_level_provider_aliases(value: &mut toml::Value) {
108 let Some(table) = value.as_table_mut() else {
109 return;
110 };
111
112 let legacy_provider = table
113 .get("default_provider")
114 .and_then(|v| v.as_str())
115 .map(str::trim)
116 .filter(|s| !s.is_empty())
117 .map(ToOwned::to_owned);
118 let legacy_model = table
119 .get("default_model")
120 .and_then(|v| v.as_str())
121 .map(str::trim)
122 .filter(|s| !s.is_empty())
123 .map(ToOwned::to_owned);
124
125 if legacy_provider.is_none() && legacy_model.is_none() {
126 return;
127 }
128
129 let agent_entry = table
130 .entry("agent".to_string())
131 .or_insert(toml::Value::Table(toml::Table::new()));
132 let Some(agent_table) = agent_entry.as_table_mut() else {
133 return;
134 };
135
136 if let Some(provider) = legacy_provider
137 && !agent_table.contains_key("provider")
138 {
139 agent_table.insert("provider".to_string(), toml::Value::String(provider));
140 }
141 if let Some(model) = legacy_model
142 && !agent_table.contains_key("default_model")
143 {
144 agent_table.insert("default_model".to_string(), toml::Value::String(model));
145 }
146}