mirror of
https://github.com/aaif-goose/goose.git
synced 2026-07-03 14:10:03 +02:00
keep the order of keys in config.yaml (#5468)
This commit is contained in:
Generated
+23
-15
@@ -2633,6 +2633,7 @@ dependencies = [
|
||||
"fs2",
|
||||
"futures",
|
||||
"include_dir",
|
||||
"indexmap 2.12.0",
|
||||
"indoc",
|
||||
"insta",
|
||||
"jsonschema",
|
||||
@@ -2900,7 +2901,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.12.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -2919,7 +2920,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.2.0",
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.12.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -2963,6 +2964,12 @@ dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.8.4"
|
||||
@@ -3511,13 +3518,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.7.1"
|
||||
version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
|
||||
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
"hashbrown 0.16.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3969,7 +3977,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"encoding_rs",
|
||||
"flate2",
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.12.0",
|
||||
"itoa",
|
||||
"log",
|
||||
"md-5",
|
||||
@@ -4872,7 +4880,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.12.0",
|
||||
"quick-xml 0.32.0",
|
||||
"serde",
|
||||
"time",
|
||||
@@ -5025,7 +5033,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d35f4dc9988d1326b065b4def5e950c3ed727aa03e3151b86cc9e2aec6b03f54"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.12.0",
|
||||
"nix 0.29.0",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -5992,7 +6000,7 @@ version = "1.0.142"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
|
||||
dependencies = [
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.12.0",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
@@ -6041,7 +6049,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.12.0",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
@@ -6067,7 +6075,7 @@ version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.12.0",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
@@ -6314,7 +6322,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"hashlink",
|
||||
"hex",
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.12.0",
|
||||
"log",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
@@ -7078,7 +7086,7 @@ version = "0.22.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||
dependencies = [
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.12.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@@ -7562,7 +7570,7 @@ version = "4.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23"
|
||||
dependencies = [
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.12.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"utoipa-gen",
|
||||
@@ -8679,7 +8687,7 @@ dependencies = [
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"flate2",
|
||||
"indexmap 2.7.1",
|
||||
"indexmap 2.12.0",
|
||||
"memchr",
|
||||
"zopfli",
|
||||
]
|
||||
|
||||
@@ -40,26 +40,22 @@ pub fn handle_info(verbose: bool) -> Result<()> {
|
||||
// Print verbose info if requested
|
||||
if verbose {
|
||||
println!("\n{}", style("goose Configuration:").cyan().bold());
|
||||
match config.load_values() {
|
||||
Ok(values) => {
|
||||
if values.is_empty() {
|
||||
println!(" No configuration values set");
|
||||
println!(
|
||||
" Run '{}' to configure goose",
|
||||
style("goose configure").cyan()
|
||||
);
|
||||
} else {
|
||||
let sorted_values: std::collections::BTreeMap<_, _> =
|
||||
values.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
|
||||
let values = config.all_values()?;
|
||||
if values.is_empty() {
|
||||
println!(" No configuration values set");
|
||||
println!(
|
||||
" Run '{}' to configure goose",
|
||||
style("goose configure").cyan()
|
||||
);
|
||||
} else {
|
||||
let sorted_values: std::collections::BTreeMap<_, _> =
|
||||
values.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
|
||||
|
||||
if let Ok(yaml) = serde_yaml::to_string(&sorted_values) {
|
||||
for line in yaml.lines() {
|
||||
println!(" {}", line);
|
||||
}
|
||||
}
|
||||
if let Ok(yaml) = serde_yaml::to_string(&sorted_values) {
|
||||
for line in yaml.lines() {
|
||||
println!(" {}", line);
|
||||
}
|
||||
}
|
||||
Err(e) => println!(" Error loading configuration: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ pub async fn read_all_config() -> Result<Json<ConfigResponse>, StatusCode> {
|
||||
let config = Config::global();
|
||||
|
||||
let values = config
|
||||
.load_values()
|
||||
.all_values()
|
||||
.map_err(|_| StatusCode::UNPROCESSABLE_ENTITY)?;
|
||||
|
||||
Ok(Json(ConfigResponse { config: values }))
|
||||
@@ -509,7 +509,7 @@ pub async fn init_config() -> Result<Json<String>, StatusCode> {
|
||||
|
||||
// Use the shared function to load init-config.yaml
|
||||
match goose::config::base::load_init_config_from_workspace() {
|
||||
Ok(init_values) => match config.save_values(init_values) {
|
||||
Ok(init_values) => match config.initialize_if_empty(init_values) {
|
||||
Ok(_) => Ok(Json("Config initialized successfully".to_string())),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
},
|
||||
@@ -584,7 +584,7 @@ pub async fn recover_config() -> Result<Json<String>, StatusCode> {
|
||||
let config = Config::global();
|
||||
|
||||
// Force a reload which will trigger recovery if needed
|
||||
match config.load_values() {
|
||||
match config.all_values() {
|
||||
Ok(values) => {
|
||||
let recovered_keys: Vec<String> = values.keys().cloned().collect();
|
||||
if recovered_keys.is_empty() {
|
||||
|
||||
@@ -108,6 +108,7 @@ schemars = { version = "1.0.4", default-features = false, features = ["derive"]
|
||||
insta = "1.43.2"
|
||||
paste = "1.0.0"
|
||||
shellexpand = "3.1.1"
|
||||
indexmap = "2.12.0"
|
||||
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
|
||||
@@ -215,6 +215,8 @@ pub enum ExtensionConfig {
|
||||
Sse {
|
||||
/// The name used to identify this extension
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
#[schema(required)]
|
||||
description: String,
|
||||
uri: String,
|
||||
#[serde(default)]
|
||||
@@ -234,6 +236,8 @@ pub enum ExtensionConfig {
|
||||
Stdio {
|
||||
/// The name used to identify this extension
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
#[schema(required)]
|
||||
description: String,
|
||||
cmd: String,
|
||||
args: Vec<String>,
|
||||
@@ -252,6 +256,8 @@ pub enum ExtensionConfig {
|
||||
Builtin {
|
||||
/// The name used to identify this extension
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
#[schema(required)]
|
||||
description: String,
|
||||
display_name: Option<String>, // needed for the UI
|
||||
timeout: Option<u64>,
|
||||
@@ -265,6 +271,8 @@ pub enum ExtensionConfig {
|
||||
Platform {
|
||||
/// The name used to identify this extension
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
#[schema(required)]
|
||||
description: String,
|
||||
#[serde(default)]
|
||||
bundled: Option<bool>,
|
||||
@@ -276,6 +284,8 @@ pub enum ExtensionConfig {
|
||||
StreamableHttp {
|
||||
/// The name used to identify this extension
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
#[schema(required)]
|
||||
description: String,
|
||||
uri: String,
|
||||
#[serde(default)]
|
||||
@@ -297,6 +307,8 @@ pub enum ExtensionConfig {
|
||||
Frontend {
|
||||
/// The name used to identify this extension
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
#[schema(required)]
|
||||
description: String,
|
||||
/// The tools provided by the frontend
|
||||
tools: Vec<Tool>,
|
||||
@@ -312,6 +324,8 @@ pub enum ExtensionConfig {
|
||||
InlinePython {
|
||||
/// The name used to identify this extension
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
#[schema(required)]
|
||||
description: String,
|
||||
/// The Python code to execute
|
||||
code: String,
|
||||
|
||||
@@ -5,6 +5,7 @@ use keyring::Entry;
|
||||
use once_cell::sync::OnceCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use serde_yaml::Mapping;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs::OpenOptions;
|
||||
@@ -152,6 +153,10 @@ macro_rules! declare_param {
|
||||
};
|
||||
}
|
||||
|
||||
fn parse_yaml_content(content: &str) -> Result<Mapping, ConfigError> {
|
||||
serde_yaml::from_str(content).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Get the global configuration instance.
|
||||
///
|
||||
@@ -204,8 +209,7 @@ impl Config {
|
||||
self.config_path.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
// Load current values from the config file
|
||||
pub fn load_values(&self) -> Result<HashMap<String, Value>, ConfigError> {
|
||||
fn load(&self) -> Result<Mapping, ConfigError> {
|
||||
if self.config_path.exists() {
|
||||
self.load_values_with_recovery()
|
||||
} else {
|
||||
@@ -221,19 +225,27 @@ impl Config {
|
||||
tracing::info!("No backup found, creating default configuration");
|
||||
|
||||
// Try to load from init-config.yaml if it exists, otherwise use empty config
|
||||
let default_config = self
|
||||
.load_init_config_if_exists()
|
||||
.unwrap_or_else(|_| HashMap::new());
|
||||
let default_config = self.load_init_config_if_exists().unwrap_or_default();
|
||||
|
||||
self.create_and_save_default_config(default_config)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_values(&self) -> Result<HashMap<String, Value>, ConfigError> {
|
||||
self.load().map(|m| {
|
||||
HashMap::from_iter(m.into_iter().filter_map(|(k, v)| {
|
||||
k.as_str()
|
||||
.map(|k| k.to_string())
|
||||
.zip(serde_json::to_value(v).ok())
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
// Helper method to create and save default config with consistent logging
|
||||
fn create_and_save_default_config(
|
||||
&self,
|
||||
default_config: HashMap<String, Value>,
|
||||
) -> Result<HashMap<String, Value>, ConfigError> {
|
||||
default_config: Mapping,
|
||||
) -> Result<Mapping, ConfigError> {
|
||||
// Try to write the default config to disk
|
||||
match self.save_values(default_config.clone()) {
|
||||
Ok(_) => {
|
||||
@@ -255,12 +267,10 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
// Load values with automatic recovery from corruption
|
||||
fn load_values_with_recovery(&self) -> Result<HashMap<String, Value>, ConfigError> {
|
||||
fn load_values_with_recovery(&self) -> Result<Mapping, ConfigError> {
|
||||
let file_content = std::fs::read_to_string(&self.config_path)?;
|
||||
|
||||
// First attempt: try to parse the current config
|
||||
match self.parse_yaml_content(&file_content) {
|
||||
match parse_yaml_content(&file_content) {
|
||||
Ok(values) => Ok(values),
|
||||
Err(parse_error) => {
|
||||
tracing::warn!(
|
||||
@@ -277,40 +287,21 @@ impl Config {
|
||||
// Last resort: create a fresh default config file
|
||||
tracing::error!("Could not recover config file, creating fresh default configuration. Original error: {}", parse_error);
|
||||
|
||||
// Try to load from init-config.yaml if it exists, otherwise use empty config
|
||||
let default_config = self
|
||||
.load_init_config_if_exists()
|
||||
.unwrap_or_else(|_| HashMap::new());
|
||||
let default_config = self.load_init_config_if_exists().unwrap_or_default();
|
||||
|
||||
self.create_and_save_default_config(default_config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse YAML content into HashMap
|
||||
fn parse_yaml_content(&self, content: &str) -> Result<HashMap<String, Value>, ConfigError> {
|
||||
if content.trim().is_empty() {
|
||||
return Ok(HashMap::new());
|
||||
}
|
||||
|
||||
let yaml_value: serde_yaml::Value = serde_yaml::from_str(content)?;
|
||||
let json_value: Value = serde_json::to_value(yaml_value)?;
|
||||
|
||||
match json_value {
|
||||
Value::Object(map) => Ok(map.into_iter().collect()),
|
||||
_ => Ok(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
// Try to restore from backup file
|
||||
fn try_restore_from_backup(&self) -> Result<HashMap<String, Value>, ConfigError> {
|
||||
fn try_restore_from_backup(&self) -> Result<Mapping, ConfigError> {
|
||||
let backup_paths = self.get_backup_paths();
|
||||
|
||||
for backup_path in backup_paths {
|
||||
if backup_path.exists() {
|
||||
match std::fs::read_to_string(&backup_path) {
|
||||
Ok(backup_content) => {
|
||||
match self.parse_yaml_content(&backup_content) {
|
||||
match parse_yaml_content(&backup_content) {
|
||||
Ok(values) => {
|
||||
// Successfully parsed backup, restore it as the main config
|
||||
if let Err(e) = self.save_values(values.clone()) {
|
||||
@@ -370,13 +361,11 @@ impl Config {
|
||||
paths
|
||||
}
|
||||
|
||||
// Try to load init-config.yaml from workspace root if it exists
|
||||
fn load_init_config_if_exists(&self) -> Result<HashMap<String, Value>, ConfigError> {
|
||||
fn load_init_config_if_exists(&self) -> Result<Mapping, ConfigError> {
|
||||
load_init_config_from_workspace()
|
||||
}
|
||||
|
||||
// Save current values to the config file
|
||||
pub fn save_values(&self, values: HashMap<String, Value>) -> Result<(), ConfigError> {
|
||||
fn save_values(&self, values: Mapping) -> Result<(), ConfigError> {
|
||||
// Create backup before writing new config
|
||||
self.create_backup_if_needed()?;
|
||||
|
||||
@@ -416,6 +405,15 @@ impl Config {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn initialize_if_empty(&self, values: Mapping) -> Result<(), ConfigError> {
|
||||
let _guard = self.guard.lock().unwrap();
|
||||
if !self.exists() {
|
||||
self.save_values(values)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Create backup of current config file if it exists and is valid
|
||||
fn create_backup_if_needed(&self) -> Result<(), ConfigError> {
|
||||
if !self.config_path.exists() {
|
||||
@@ -424,7 +422,7 @@ impl Config {
|
||||
|
||||
// Check if current config is valid before backing it up
|
||||
let current_content = std::fs::read_to_string(&self.config_path)?;
|
||||
if self.parse_yaml_content(¤t_content).is_err() {
|
||||
if parse_yaml_content(¤t_content).is_err() {
|
||||
// Don't back up corrupted files
|
||||
return Ok(());
|
||||
}
|
||||
@@ -483,8 +481,7 @@ impl Config {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Load current secrets from the keyring
|
||||
pub fn load_secrets(&self) -> Result<HashMap<String, Value>, ConfigError> {
|
||||
pub fn all_secrets(&self) -> Result<HashMap<String, Value>, ConfigError> {
|
||||
match &self.secrets {
|
||||
SecretStorage::Keyring { service } => {
|
||||
let entry = Entry::new(service, KEYRING_USERNAME)?;
|
||||
@@ -585,21 +582,17 @@ impl Config {
|
||||
/// - The value cannot be deserialized into the requested type
|
||||
/// - There is an error reading the config file
|
||||
pub fn get_param<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Result<T, ConfigError> {
|
||||
// First check environment variables (convert to uppercase)
|
||||
let env_key = key.to_uppercase();
|
||||
if let Ok(val) = env::var(&env_key) {
|
||||
let value = Self::parse_env_value(&val)?;
|
||||
return Ok(serde_json::from_value(value)?);
|
||||
}
|
||||
|
||||
// Load current values from file
|
||||
let values = self.load_values()?;
|
||||
|
||||
// Then check our stored values
|
||||
let values = self.load()?;
|
||||
values
|
||||
.get(key)
|
||||
.ok_or_else(|| ConfigError::NotFound(key.to_string()))
|
||||
.and_then(|v| Ok(serde_json::from_value(v.clone())?))
|
||||
.and_then(|v| Ok(serde_yaml::from_value(v.clone())?))
|
||||
}
|
||||
|
||||
/// Set a configuration value in the config file (non-secret).
|
||||
@@ -617,8 +610,8 @@ impl Config {
|
||||
/// - There is an error serializing the value
|
||||
pub fn set_param<V: Serialize>(&self, key: &str, value: V) -> Result<(), ConfigError> {
|
||||
let _guard = self.guard.lock().unwrap();
|
||||
let mut values = self.load_values()?;
|
||||
values.insert(key.to_string(), serde_json::to_value(&value)?);
|
||||
let mut values = self.load()?;
|
||||
values.insert(serde_yaml::to_value(key)?, serde_yaml::to_value(value)?);
|
||||
self.save_values(values)
|
||||
}
|
||||
|
||||
@@ -639,8 +632,8 @@ impl Config {
|
||||
// Lock before reading to prevent race condition.
|
||||
let _guard = self.guard.lock().unwrap();
|
||||
|
||||
let mut values = self.load_values()?;
|
||||
values.remove(key);
|
||||
let mut values = self.load()?;
|
||||
values.shift_remove(key);
|
||||
|
||||
self.save_values(values)
|
||||
}
|
||||
@@ -670,7 +663,7 @@ impl Config {
|
||||
}
|
||||
|
||||
// Then check keyring
|
||||
let values = self.load_secrets()?;
|
||||
let values = self.all_secrets()?;
|
||||
values
|
||||
.get(key)
|
||||
.ok_or_else(|| ConfigError::NotFound(key.to_string()))
|
||||
@@ -698,7 +691,7 @@ impl Config {
|
||||
// Lock before reading to prevent race condition.
|
||||
let _guard = self.guard.lock().unwrap();
|
||||
|
||||
let mut values = self.load_secrets()?;
|
||||
let mut values = self.all_secrets()?;
|
||||
values.insert(key.to_string(), serde_json::to_value(value)?);
|
||||
|
||||
match &self.secrets {
|
||||
@@ -729,7 +722,7 @@ impl Config {
|
||||
// Lock before reading to prevent race condition.
|
||||
let _guard = self.guard.lock().unwrap();
|
||||
|
||||
let mut values = self.load_secrets()?;
|
||||
let mut values = self.all_secrets()?;
|
||||
values.remove(key);
|
||||
|
||||
match &self.secrets {
|
||||
@@ -754,7 +747,7 @@ impl Config {
|
||||
|
||||
/// Load init-config.yaml from workspace root if it exists.
|
||||
/// This function is shared between the config recovery and the init_config endpoint.
|
||||
pub fn load_init_config_from_workspace() -> Result<HashMap<String, Value>, ConfigError> {
|
||||
pub fn load_init_config_from_workspace() -> Result<Mapping, ConfigError> {
|
||||
let workspace_root = match std::env::current_exe() {
|
||||
Ok(mut exe_path) => {
|
||||
while let Some(parent) = exe_path.parent() {
|
||||
@@ -787,23 +780,7 @@ pub fn load_init_config_from_workspace() -> Result<HashMap<String, Value>, Confi
|
||||
}
|
||||
|
||||
let init_content = std::fs::read_to_string(&init_config_path)?;
|
||||
let init_values: HashMap<String, Value> =
|
||||
match serde_yaml::from_str::<serde_yaml::Value>(&init_content) {
|
||||
Ok(yaml_value) => {
|
||||
let json_value: Value = serde_json::to_value(yaml_value)?;
|
||||
match json_value {
|
||||
Value::Object(map) => map.into_iter().collect(),
|
||||
_ => HashMap::new(),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to parse init-config.yaml: {}", e);
|
||||
return Err(ConfigError::DeserializeError(e.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
tracing::info!("Loaded init-config.yaml with {} keys", init_values.len());
|
||||
Ok(init_values)
|
||||
parse_yaml_content(&init_content)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -902,7 +879,7 @@ mod tests {
|
||||
config.set_param("another_key", 42)?;
|
||||
config.set_param("third_key", true)?;
|
||||
|
||||
let _values = config.load_values()?;
|
||||
let _values = config.load()?;
|
||||
|
||||
let result: Result<String, ConfigError> = config.get_param("key");
|
||||
assert!(matches!(result, Err(ConfigError::NotFound(_))));
|
||||
@@ -994,11 +971,11 @@ mod tests {
|
||||
let temp_file = NamedTempFile::new().unwrap();
|
||||
let config = Arc::new(Config::new(temp_file.path(), TEST_KEYRING_SERVICE)?);
|
||||
let barrier = Arc::new(Barrier::new(3)); // For 3 concurrent threads
|
||||
let values = Arc::new(Mutex::new(HashMap::new()));
|
||||
let values = Arc::new(Mutex::new(Mapping::new()));
|
||||
let mut handles = vec![];
|
||||
|
||||
// Initialize with empty values
|
||||
config.save_values(HashMap::new())?;
|
||||
config.save_values(Default::default())?;
|
||||
|
||||
// Spawn 3 threads that will try to write simultaneously
|
||||
for i in 0..3 {
|
||||
@@ -1011,7 +988,10 @@ mod tests {
|
||||
|
||||
// Get the lock and update values
|
||||
let mut values = values.lock().unwrap();
|
||||
values.insert(format!("key{}", i), Value::String(format!("value{}", i)));
|
||||
values.insert(
|
||||
serde_yaml::to_value(format!("key{}", i)).unwrap(),
|
||||
serde_yaml::to_value(format!("value{}", i)).unwrap(),
|
||||
);
|
||||
|
||||
// Write all values
|
||||
config.save_values(values.clone())?;
|
||||
@@ -1026,7 +1006,7 @@ mod tests {
|
||||
}
|
||||
|
||||
// Verify all values were written correctly
|
||||
let final_values = config.load_values()?;
|
||||
let final_values = config.all_values()?;
|
||||
|
||||
// Print the final values for debugging
|
||||
println!("Final values: {:?}", final_values);
|
||||
@@ -1088,7 +1068,7 @@ mod tests {
|
||||
std::fs::write(temp_file.path(), "invalid: yaml: content: [unclosed")?;
|
||||
|
||||
// Try to load values - should recover from backup
|
||||
let recovered_values = config.load_values()?;
|
||||
let recovered_values = config.all_values()?;
|
||||
println!("Recovered values: {:?}", recovered_values);
|
||||
|
||||
// Should have recovered the data
|
||||
@@ -1109,7 +1089,7 @@ mod tests {
|
||||
std::fs::write(temp_file.path(), "invalid: yaml: content: [unclosed")?;
|
||||
|
||||
// Try to load values - should create a fresh default config
|
||||
let recovered_values = config.load_values()?;
|
||||
let recovered_values = config.all_values()?;
|
||||
|
||||
// Should return empty config
|
||||
assert_eq!(recovered_values.len(), 0);
|
||||
@@ -1122,7 +1102,7 @@ mod tests {
|
||||
assert!(parsed.is_mapping());
|
||||
|
||||
// Should be able to load it again without issues
|
||||
let reloaded_values = config.load_values()?;
|
||||
let reloaded_values = config.all_values()?;
|
||||
assert_eq!(reloaded_values.len(), 0);
|
||||
|
||||
Ok(())
|
||||
@@ -1140,7 +1120,7 @@ mod tests {
|
||||
let config = Config::new(config_path, TEST_KEYRING_SERVICE)?;
|
||||
|
||||
// Try to load values - should create a fresh default config file
|
||||
let values = config.load_values()?;
|
||||
let values = config.all_values()?;
|
||||
|
||||
// Should return empty config
|
||||
assert_eq!(values.len(), 0);
|
||||
@@ -1154,7 +1134,7 @@ mod tests {
|
||||
assert!(parsed.is_mapping());
|
||||
|
||||
// Should be able to load it again without issues
|
||||
let reloaded_values = config.load_values()?;
|
||||
let reloaded_values = config.all_values()?;
|
||||
assert_eq!(reloaded_values.len(), 0);
|
||||
|
||||
Ok(())
|
||||
@@ -1183,7 +1163,7 @@ mod tests {
|
||||
assert!(!config_path.exists());
|
||||
|
||||
// Try to load values - should recover from backup
|
||||
let recovered_values = config.load_values()?;
|
||||
let recovered_values = config.all_values()?;
|
||||
|
||||
// Should have recovered the data from backup
|
||||
assert!(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use super::base::Config;
|
||||
use crate::agents::extension::PLATFORM_EXTENSIONS;
|
||||
use crate::agents::ExtensionConfig;
|
||||
use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use serde_yaml::Mapping;
|
||||
use tracing::warn;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
@@ -27,56 +27,32 @@ pub fn name_to_key(name: &str) -> String {
|
||||
.to_lowercase()
|
||||
}
|
||||
|
||||
fn get_extensions_map() -> HashMap<String, ExtensionEntry> {
|
||||
let raw: Value = Config::global()
|
||||
.get_param::<Value>(EXTENSIONS_CONFIG_KEY)
|
||||
fn get_extensions_map() -> IndexMap<String, ExtensionEntry> {
|
||||
let raw: Mapping = Config::global()
|
||||
.get_param(EXTENSIONS_CONFIG_KEY)
|
||||
.unwrap_or_else(|err| {
|
||||
warn!(
|
||||
"Failed to load {}: {err}. Falling back to empty object.",
|
||||
EXTENSIONS_CONFIG_KEY
|
||||
);
|
||||
Value::Object(serde_json::Map::new())
|
||||
Default::default()
|
||||
});
|
||||
|
||||
let mut extensions_map: HashMap<String, ExtensionEntry> = match raw {
|
||||
Value::Object(obj) => {
|
||||
let mut m = HashMap::with_capacity(obj.len());
|
||||
for (k, mut v) in obj {
|
||||
if let Value::Object(ref mut inner) = v {
|
||||
match inner.get("description") {
|
||||
Some(Value::Null) | None => {
|
||||
inner.insert("description".to_string(), Value::String(String::new()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
match serde_json::from_value::<ExtensionEntry>(v.clone()) {
|
||||
Ok(entry) => {
|
||||
m.insert(k, entry);
|
||||
}
|
||||
Err(err) => {
|
||||
let bad_json = serde_json::to_string(&v).unwrap_or_else(|e| {
|
||||
format!("<failed to serialize malformed value: {e}>")
|
||||
});
|
||||
warn!(
|
||||
extension = %k,
|
||||
error = %err,
|
||||
bad_json = %bad_json,
|
||||
"Skipping malformed extension"
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut extensions_map = IndexMap::with_capacity(raw.len());
|
||||
for (k, v) in raw {
|
||||
match (k, serde_yaml::from_value::<ExtensionEntry>(v)) {
|
||||
(serde_yaml::Value::String(s), Ok(entry)) => {
|
||||
extensions_map.insert(s, entry);
|
||||
}
|
||||
(k, v) => {
|
||||
warn!(
|
||||
key = ?k,
|
||||
value = ?v,
|
||||
"Skipping malformed extension config entry"
|
||||
);
|
||||
}
|
||||
m
|
||||
}
|
||||
other => {
|
||||
warn!(
|
||||
"Expected object for {}, got {}. Using empty map.",
|
||||
EXTENSIONS_CONFIG_KEY, other
|
||||
);
|
||||
HashMap::new()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if !extensions_map.is_empty() {
|
||||
for (name, def) in PLATFORM_EXTENSIONS.iter() {
|
||||
@@ -99,7 +75,7 @@ fn get_extensions_map() -> HashMap<String, ExtensionEntry> {
|
||||
extensions_map
|
||||
}
|
||||
|
||||
fn save_extensions_map(extensions: HashMap<String, ExtensionEntry>) {
|
||||
fn save_extensions_map(extensions: IndexMap<String, ExtensionEntry>) {
|
||||
let config = Config::global();
|
||||
if let Err(e) = config.set_param(EXTENSIONS_CONFIG_KEY, &extensions) {
|
||||
// TODO(jack) why is this just a debug statement?
|
||||
@@ -124,7 +100,7 @@ pub fn set_extension(entry: ExtensionEntry) {
|
||||
|
||||
pub fn remove_extension(key: &str) {
|
||||
let mut extensions = get_extensions_map();
|
||||
extensions.remove(key);
|
||||
extensions.shift_remove(key);
|
||||
save_extensions_map(extensions);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,8 +61,8 @@ impl BedrockProvider {
|
||||
}
|
||||
};
|
||||
|
||||
set_aws_env_vars(config.load_values());
|
||||
set_aws_env_vars(config.load_secrets());
|
||||
set_aws_env_vars(config.all_values());
|
||||
set_aws_env_vars(config.all_secrets());
|
||||
|
||||
let sdk_config = aws_config::load_from_env().await;
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ impl SageMakerTgiProvider {
|
||||
}
|
||||
};
|
||||
|
||||
set_aws_env_vars(config.load_values());
|
||||
set_aws_env_vars(config.load_secrets());
|
||||
set_aws_env_vars(config.all_values());
|
||||
set_aws_env_vars(config.all_secrets());
|
||||
|
||||
let aws_config = aws_config::load_from_env().await;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user