bamboo_server/
config_manager.rs1use serde_json::{Map, Value};
21
22use crate::error::AppError;
23use bamboo_infrastructure::Config;
24
25pub use bamboo_infrastructure::patch::{
28 deep_merge_json, domains_for_root_patch, effects_for_root_patch, is_masked_api_key,
29 preserve_masked_provider_api_keys, provider_api_key_intents, sanitize_root_patch,
30 DomainChanges, PatchEffects, ReloadMode,
31};
32
33pub fn sync_provider_api_keys_encrypted_for_patch(
34 config: &mut Config,
35 providers: &std::collections::BTreeSet<String>,
36) -> Result<(), AppError> {
37 for name in providers.iter() {
38 match name.as_str() {
39 "openai" => {
40 if let Some(openai) = config.providers.openai.as_mut() {
41 let api_key = openai.api_key.trim();
42 openai.api_key_encrypted = if api_key.is_empty() {
43 None
44 } else {
45 Some(
46 bamboo_infrastructure::encryption::encrypt(api_key).map_err(|e| {
47 AppError::InternalError(anyhow::anyhow!(
48 "Failed to encrypt OpenAI api_key: {e}"
49 ))
50 })?,
51 )
52 };
53 }
54 }
55 "anthropic" => {
56 if let Some(anthropic) = config.providers.anthropic.as_mut() {
57 let api_key = anthropic.api_key.trim();
58 anthropic.api_key_encrypted = if api_key.is_empty() {
59 None
60 } else {
61 Some(
62 bamboo_infrastructure::encryption::encrypt(api_key).map_err(|e| {
63 AppError::InternalError(anyhow::anyhow!(
64 "Failed to encrypt Anthropic api_key: {e}"
65 ))
66 })?,
67 )
68 };
69 }
70 }
71 "gemini" => {
72 if let Some(gemini) = config.providers.gemini.as_mut() {
73 let api_key = gemini.api_key.trim();
74 gemini.api_key_encrypted = if api_key.is_empty() {
75 None
76 } else {
77 Some(
78 bamboo_infrastructure::encryption::encrypt(api_key).map_err(|e| {
79 AppError::InternalError(anyhow::anyhow!(
80 "Failed to encrypt Gemini api_key: {e}"
81 ))
82 })?,
83 )
84 };
85 }
86 }
87 _ => {}
88 }
89 }
90
91 Ok(())
92}
93
94pub fn assert_json_object(value: Value) -> Result<Map<String, Value>, AppError> {
95 match value {
96 Value::Object(map) => Ok(map),
97 _ => Err(AppError::BadRequest(
98 "config.json must be a JSON object".to_string(),
99 )),
100 }
101}
102
103pub fn build_merged_config(
104 current: &Config,
105 patch_obj: Map<String, Value>,
106) -> Result<Config, AppError> {
107 let mut merged = serde_json::to_value(current)
108 .map_err(|e| AppError::InternalError(anyhow::anyhow!("Failed to serialize config: {e}")))?;
109
110 deep_merge_json(&mut merged, Value::Object(patch_obj));
111
112 let mut new_config: Config = serde_json::from_value(merged)
113 .map_err(|e| AppError::BadRequest(format!("Invalid configuration JSON: {e}")))?;
114 new_config.hydrate_proxy_auth_from_encrypted();
115 new_config.hydrate_provider_api_keys_from_encrypted();
116 new_config.hydrate_mcp_secrets_from_encrypted();
117 new_config.hydrate_env_vars_from_encrypted();
118 new_config.normalize_tool_settings();
119 new_config.normalize_skill_settings();
120
121 Ok(new_config)
122}