From 23d3db445f129d1096110f66c3a3d1144c2127c7 Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Tue, 28 Apr 2026 16:26:31 -0400 Subject: [PATCH] mergeable configs + cleanup (#8378) --- crates/goose-server/src/openapi.rs | 3 - .../src/routes/config_management.rs | 83 -- crates/goose-server/ui/desktop/openapi.json | 23 - crates/goose/src/config/base.rs | 928 +++++++++++------- crates/goose/src/config/mod.rs | 2 +- ui/desktop/openapi.json | 69 -- ui/desktop/src/App.test.tsx | 23 - ui/desktop/src/api/index.ts | 4 +- ui/desktop/src/api/sdk.gen.ts | 8 +- ui/desktop/src/api/types.gen.ts | 69 -- 10 files changed, 552 insertions(+), 660 deletions(-) diff --git a/crates/goose-server/src/openapi.rs b/crates/goose-server/src/openapi.rs index c5440dfe08..23aae67664 100644 --- a/crates/goose-server/src/openapi.rs +++ b/crates/goose-server/src/openapi.rs @@ -387,10 +387,7 @@ derive_utoipa!(IconTheme as IconThemeSchema); super::routes::status::system_info, super::routes::status::diagnostics, super::routes::mcp_ui_proxy::mcp_ui_proxy, - super::routes::config_management::backup_config, - super::routes::config_management::recover_config, super::routes::config_management::validate_config, - super::routes::config_management::init_config, super::routes::config_management::upsert_config, super::routes::config_management::remove_config, super::routes::config_management::read_config, diff --git a/crates/goose-server/src/routes/config_management.rs b/crates/goose-server/src/routes/config_management.rs index 600a74cbee..6047f88bb2 100644 --- a/crates/goose-server/src/routes/config_management.rs +++ b/crates/goose-server/src/routes/config_management.rs @@ -515,33 +515,6 @@ pub async fn get_canonical_model_info( }) } -#[utoipa::path( - post, - path = "/config/init", - responses( - (status = 200, description = "Config initialization check completed", body = String), - (status = 500, description = "Internal server error") - ) -)] -pub async fn init_config() -> Result, ErrorResponse> { - let config = Config::global(); - - if config.exists() { - return Ok(Json("Config already exists".to_string())); - } - - // Use the shared function to load init-config.yaml - match goose::config::base::load_init_config_from_workspace() { - Ok(init_values) => { - config.initialize_if_empty(init_values)?; - Ok(Json("Config initialized successfully".to_string())) - } - Err(_) => Ok(Json( - "No init-config.yaml found, using default configuration".to_string(), - )), - } -} - #[utoipa::path( post, path = "/config/permissions", @@ -566,59 +539,6 @@ pub async fn upsert_permissions( Ok(Json("Permissions updated successfully".to_string())) } -#[utoipa::path( - post, - path = "/config/backup", - responses( - (status = 200, description = "Config file backed up", body = String), - (status = 500, description = "Internal server error") - ) -)] -pub async fn backup_config() -> Result, ErrorResponse> { - let config_path = Paths::config_dir().join("config.yaml"); - - if !config_path.exists() { - return Err(ErrorResponse::not_found("Config file does not exist")); - } - - let file_name = config_path - .file_name() - .ok_or_else(|| ErrorResponse::internal("Invalid config file path"))?; - - let mut backup_name = file_name.to_os_string(); - backup_name.push(".bak"); - - let backup = config_path.with_file_name(backup_name); - std::fs::copy(&config_path, &backup)?; - Ok(Json(format!("Copied {:?} to {:?}", config_path, backup))) -} - -#[utoipa::path( - post, - path = "/config/recover", - responses( - (status = 200, description = "Config recovery attempted", body = String), - (status = 500, description = "Internal server error") - ) -)] -pub async fn recover_config() -> Result, ErrorResponse> { - let config = Config::global(); - - // Force a reload which will trigger recovery if needed - let values = config.all_values()?; - let recovered_keys: Vec = values.keys().cloned().collect(); - - if recovered_keys.is_empty() { - Ok(Json("Config recovery completed, but no data was recoverable. Starting with empty configuration.".to_string())) - } else { - Ok(Json(format!( - "Config recovery completed. Recovered {} keys: {}", - recovered_keys.len(), - recovered_keys.join(", ") - ))) - } -} - #[utoipa::path( get, path = "/config/validate", @@ -942,9 +862,6 @@ pub fn routes(state: Arc) -> Router { "/config/canonical-model-info", post(get_canonical_model_info), ) - .route("/config/init", post(init_config)) - .route("/config/backup", post(backup_config)) - .route("/config/recover", post(recover_config)) .route("/config/validate", get(validate_config)) .route("/config/permissions", post(upsert_permissions)) .route("/config/custom-providers", post(create_custom_provider)) diff --git a/crates/goose-server/ui/desktop/openapi.json b/crates/goose-server/ui/desktop/openapi.json index 7ef2609779..6f3c4c34da 100644 --- a/crates/goose-server/ui/desktop/openapi.json +++ b/crates/goose-server/ui/desktop/openapi.json @@ -77,29 +77,6 @@ } } }, - "/config/backup": { - "post": { - "tags": [ - "super::routes::config_management" - ], - "operationId": "backup_config", - "responses": { - "200": { - "description": "Config file backed up", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "500": { - "description": "Internal server error" - } - } - } - }, "/config/extensions": { "get": { "tags": [ diff --git a/crates/goose/src/config/base.rs b/crates/goose/src/config/base.rs index 56b2764323..34e6a6f952 100644 --- a/crates/goose/src/config/base.rs +++ b/crates/goose/src/config/base.rs @@ -121,7 +121,10 @@ impl From for ConfigError { /// /// For goose-specific configuration, consider prefixing with "goose_" to avoid conflicts. pub struct Config { - config_path: PathBuf, + /// Ordered list of config files to load and merge. + /// Later entries take precedence over earlier ones. + /// The last entry is where changes will be written. + config_paths: Vec, secrets: SecretStorage, guard: Mutex<()>, secrets_cache: Arc>>>, @@ -135,14 +138,52 @@ enum SecretStorage { // Global instance static GLOBAL_CONFIG: OnceCell = OnceCell::new(); +fn system_config_path() -> PathBuf { + #[cfg(unix)] + { + PathBuf::from("/etc/goose/config.yaml") + } + #[cfg(windows)] + { + env::var("PROGRAMDATA") + .map(|d| PathBuf::from(d).join("goose").join("config.yaml")) + .unwrap_or_else(|_| PathBuf::from(r"C:\ProgramData\goose\config.yaml")) + } +} + +fn bundled_defaults_path() -> Option { + let exe = std::env::current_exe().ok()?; + let path = exe.parent()?.join("defaults.yaml"); + if path.exists() { + Some(path) + } else { + None + } +} + impl Default for Config { fn default() -> Self { let config_dir = Paths::config_dir(); + let user_config_path = config_dir.join(CONFIG_YAML_NAME); - let config_path = config_dir.join(CONFIG_YAML_NAME); + let mut config_paths = vec![system_config_path()]; + if let Some(defaults) = bundled_defaults_path() { + config_paths.insert(0, defaults); + } + config_paths.push(user_config_path.clone()); - let secrets = if env::var("GOOSE_DISABLE_KEYRING").is_ok() - || keyring_disabled_in_config(&config_path) + let no_secrets_config = Self { + config_paths: config_paths.clone(), + secrets: SecretStorage::File { + path: Default::default(), + }, + guard: Mutex::new(()), + secrets_cache: Arc::new(Mutex::new(None)), + }; + + let secrets = if no_secrets_config + .get_param::("GOOSE_DISABLE_KEYRING") + .is_ok() { SecretStorage::File { path: config_dir.join("secrets.yaml"), @@ -152,8 +193,8 @@ impl Default for Config { service: KEYRING_SERVICE.to_string(), } }; - Config { - config_path, + Self { + config_paths, secrets, guard: Mutex::new(()), secrets_cache: Arc::new(Mutex::new(None)), @@ -240,20 +281,48 @@ fn parse_yaml_content(content: &str) -> Result { serde_yaml::from_str(content).map_err(|e| e.into()) } -/// Read the GOOSE_DISABLE_KEYRING flag from the config file. -/// -/// Called before Config is fully initialised, so we do a minimal raw read -/// rather than going through `get_param`. All errors are treated as `false` -/// (keyring stays enabled) so a missing/malformed file is never fatal here. -fn keyring_disabled_in_config(config_path: &Path) -> bool { - std::fs::read_to_string(config_path) - .ok() - .and_then(|s| parse_yaml_content(&s).ok()) - .and_then(|m| { - m.get("GOOSE_DISABLE_KEYRING") - .map(|v| v.as_bool().unwrap_or(false) || v.as_str().is_some_and(|s| s == "true")) - }) - .unwrap_or(false) +const EXTENSIONS_KEY: &str = "extensions"; + +pub fn merge_config_values(base: &mut Mapping, overlay: Mapping) { + let extensions_key = serde_yaml::Value::String(EXTENSIONS_KEY.to_string()); + + for (key, overlay_value) in overlay { + if key == extensions_key { + let base_ext = base + .entry(key.clone()) + .or_insert_with(|| serde_yaml::Value::Mapping(Mapping::new())); + if let (Some(base_map), Some(overlay_map)) = + (base_ext.as_mapping_mut(), overlay_value.as_mapping()) + { + merge_extensions(base_map, overlay_map); + } else { + base.insert(key, overlay_value); + } + } else { + base.insert(key, overlay_value); + } + } +} + +fn merge_extensions(base: &mut Mapping, overlay: &Mapping) { + for (ext_key, overlay_ext) in overlay { + match base.get_mut(ext_key) { + Some(base_ext) => { + if let (Some(base_map), Some(overlay_map)) = + (base_ext.as_mapping_mut(), overlay_ext.as_mapping()) + { + for (field_key, field_value) in overlay_map { + base_map.insert(field_key.clone(), field_value.clone()); + } + } else { + *base_ext = overlay_ext.clone(); + } + } + None => { + base.insert(ext_key.clone(), overlay_ext.clone()); + } + } + } } impl Config { @@ -271,7 +340,7 @@ impl Config { /// to manage multiple configuration files. pub fn new>(config_path: P, service: &str) -> Result { Ok(Config { - config_path: config_path.as_ref().to_path_buf(), + config_paths: vec![config_path.as_ref().to_path_buf()], secrets: SecretStorage::Keyring { service: service.to_string(), }, @@ -289,7 +358,7 @@ impl Config { secrets_path: P2, ) -> Result { Ok(Config { - config_path: config_path.as_ref().to_path_buf(), + config_paths: vec![config_path.as_ref().to_path_buf()], secrets: SecretStorage::File { path: secrets_path.as_ref().to_path_buf(), }, @@ -298,37 +367,54 @@ impl Config { }) } + pub fn new_with_config_paths>( + config_paths: Vec, + secrets_path: P1, + ) -> Result { + Ok(Config { + config_paths, + secrets: SecretStorage::File { + path: secrets_path.as_ref().to_path_buf(), + }, + guard: Mutex::new(()), + secrets_cache: Arc::new(Mutex::new(None)), + }) + } + + fn write_path(&self) -> &PathBuf { + self.config_paths + .last() + .expect("config_paths must not be empty") + } + pub fn exists(&self) -> bool { - self.config_path.exists() + self.config_paths.iter().any(|p| p.exists()) } pub fn clear(&self) -> Result<(), ConfigError> { - Ok(std::fs::remove_file(&self.config_path)?) + Ok(std::fs::remove_file(self.write_path())?) } pub fn path(&self) -> String { - self.config_path.to_string_lossy().to_string() + self.write_path().to_string_lossy().to_string() } - fn load_raw(&self) -> Result { - let mut values = if self.config_path.exists() { - self.load_values_with_recovery()? - } else { - // Config file doesn't exist, try to recover from backup first - tracing::info!("Config file doesn't exist, attempting recovery from backup"); + /// Load only the writable config file for read-modify-write operations. + /// Returns an empty mapping if the file doesn't exist or can't be parsed. + fn load_write_config(&self) -> Result { + if !self.write_path().exists() { + return Ok(Mapping::new()); + } + let content = std::fs::read_to_string(self.write_path())?; + let mut values = parse_yaml_content(&content).unwrap_or_else(|e| { + tracing::warn!( + "Config file {:?} is corrupt: {}. Starting fresh.", + self.write_path(), + e + ); + Mapping::new() + }); - if let Ok(backup_values) = self.try_restore_from_backup() { - tracing::info!("Successfully restored config from backup"); - backup_values - } else { - // No backup available, create a default config - tracing::info!("No backup found, creating default configuration"); - let default_config = self.load_init_config_if_exists().unwrap_or_default(); - self.create_and_save_default_config(default_config)? - } - }; - - // Run migrations on the loaded config if crate::config::migrations::run_migrations(&mut values) { if let Err(e) = self.save_values(&values) { tracing::warn!("Failed to save migrated config: {}", e); @@ -339,7 +425,29 @@ impl Config { } fn load(&self) -> Result { - self.load_raw() + let mut merged = Mapping::new(); + + for path in &self.config_paths { + if !path.exists() { + continue; + } + match std::fs::read_to_string(path) + .map_err(ConfigError::from) + .and_then(|content| parse_yaml_content(&content)) + { + Ok(layer) => { + tracing::debug!("Loading config from: {:?}", path); + merge_config_values(&mut merged, layer); + } + Err(e) => { + tracing::warn!("Failed to load config {:?}: {}. Skipping.", path, e); + } + } + } + + crate::config::migrations::run_migrations(&mut merged); + + Ok(merged) } pub fn all_values(&self) -> Result, ConfigError> { @@ -353,121 +461,8 @@ impl Config { ))) } - // Helper method to create and save default config with consistent logging - fn create_and_save_default_config( - &self, - default_config: Mapping, - ) -> Result { - // Try to write the default config to disk - match self.save_values(&default_config) { - Ok(_) => { - if default_config.is_empty() { - tracing::info!("Created fresh empty config file"); - } else { - tracing::info!( - "Created fresh config file from init-config.yaml with {} keys", - default_config.len() - ); - } - Ok(default_config) - } - Err(write_error) => { - tracing::error!("Failed to write default config file: {}", write_error); - // Even if we can't write to disk, return config so app can still run - Ok(default_config) - } - } - } - - fn load_values_with_recovery(&self) -> Result { - let file_content = std::fs::read_to_string(&self.config_path)?; - - match parse_yaml_content(&file_content) { - Ok(values) => Ok(values), - Err(parse_error) => { - tracing::warn!( - "Config file appears corrupted, attempting recovery: {}", - parse_error - ); - - // Try to recover from backup - if let Ok(backup_values) = self.try_restore_from_backup() { - tracing::info!("Successfully restored config from backup"); - return Ok(backup_values); - } - - // Last resort: create a fresh default config file - tracing::error!("Could not recover config file, creating fresh default configuration. Original error: {}", parse_error); - let default_config = self.load_init_config_if_exists().unwrap_or_default(); - self.create_and_save_default_config(default_config) - } - } - } - - fn try_restore_from_backup(&self) -> Result { - 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 parse_yaml_content(&backup_content) { - Ok(values) => { - // Successfully parsed backup, restore it as the main config - if let Err(e) = self.save_values(&values) { - tracing::warn!( - "Failed to restore backup as main config: {}", - e - ); - } else { - tracing::info!( - "Restored config from backup: {:?}", - backup_path - ); - } - return Ok(values); - } - Err(e) => { - tracing::warn!( - "Backup file {:?} is also corrupted: {}", - backup_path, - e - ); - continue; - } - } - } - Err(e) => { - tracing::warn!("Could not read backup file {:?}: {}", backup_path, e); - continue; - } - } - } - } - - Err(ConfigError::NotFound("No valid backup found".to_string())) - } - - // Get list of backup file paths in order of preference - fn get_backup_paths(&self) -> Vec { - let mut paths = Vec::new(); - - // Primary backup (created by backup_config endpoint) - if let Some(file_name) = self.config_path.file_name() { - let mut backup_name = file_name.to_os_string(); - backup_name.push(".bak"); - paths.push(self.config_path.with_file_name(backup_name)); - } - - paths - } - - fn load_init_config_if_exists(&self) -> Result { - load_init_config_from_workspace() - } - fn config_write_target_path(&self) -> Result { - let mut path = self.config_path.clone(); + let mut path = self.write_path().clone(); // Follow symlinks so we update the target file without replacing the link itself. const MAX_SYMLINK_HOPS: usize = 1; @@ -480,7 +475,7 @@ impl Config { std::io::ErrorKind::InvalidInput, format!( "Too many symlink levels (or a cycle) while resolving config path: {:?}", - self.config_path + self.write_path() ), ) .into()); @@ -502,9 +497,6 @@ impl Config { } fn save_values(&self, values: &Mapping) -> Result<(), ConfigError> { - // Create backup before writing new config - self.create_backup_if_needed()?; - let target_path = self.config_write_target_path()?; // Convert to YAML for storage @@ -551,36 +543,6 @@ impl Config { } } - // 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() { - return Ok(()); - } - - // Check if current config is valid before backing it up - let current_content = std::fs::read_to_string(&self.config_path)?; - if parse_yaml_content(¤t_content).is_err() { - // Don't back up corrupted files - return Ok(()); - } - - // Create new backup - if let Some(file_name) = self.config_path.file_name() { - let mut backup_name = file_name.to_os_string(); - backup_name.push(".bak"); - let backup_path = self.config_path.with_file_name(backup_name); - - if let Err(e) = std::fs::copy(&self.config_path, &backup_path) { - tracing::warn!("Failed to create config backup: {}", e); - // Don't fail the entire operation if backup fails - } else { - tracing::debug!("Created config backup: {:?}", backup_path); - } - } - - Ok(()) - } - pub fn all_secrets(&self) -> Result, ConfigError> { let mut cache = self.secrets_cache.lock().unwrap(); @@ -679,7 +641,7 @@ impl Config { /// /// This will attempt to get the value from (in order): /// 1. Environment variable with the uppercase key name - /// 2. Configuration file (~/.config/goose/config.yaml) + /// 2. Merged config from all config paths (system → user → local) /// /// The value will be deserialized into the requested type. This works with /// both simple types (String, i32, etc.) and complex types that implement @@ -720,7 +682,7 @@ impl Config { /// - There is an error serializing the value pub fn set_param(&self, key: &str, value: V) -> Result<(), ConfigError> { let _guard = self.guard.lock().unwrap(); - let mut values = self.load_raw()?; + let mut values = self.load_write_config()?; values.insert(serde_yaml::to_value(key)?, serde_yaml::to_value(value)?); self.save_values(&values) } @@ -742,7 +704,7 @@ impl Config { // Lock before reading to prevent race condition. let _guard = self.guard.lock().unwrap(); - let mut values = self.load_raw()?; + let mut values = self.load_write_config()?; values.shift_remove(key); self.save_values(&values) @@ -1016,45 +978,6 @@ config_value!(CLAUDE_THINKING_EFFORT, String); config_value!(CLAUDE_THINKING_BUDGET, i32); config_value!(GOOSE_DEFAULT_EXTENSION_TIMEOUT, u64); -fn find_workspace_or_exe_root() -> Option { - let exe = std::env::current_exe().ok()?; - let exe_dir = exe.parent()?.to_path_buf(); - - let mut path = exe; - while let Some(parent) = path.parent() { - let cargo_toml = parent.join("Cargo.toml"); - if cargo_toml.exists() { - if let Ok(content) = std::fs::read_to_string(&cargo_toml) { - if content.contains("[workspace]") { - return Some(parent.to_path_buf()); - } - } - } - path = parent.to_path_buf(); - } - - Some(exe_dir) -} - -pub fn load_init_config_from_workspace() -> Result { - let root = find_workspace_or_exe_root().ok_or_else(|| { - ConfigError::FileError(std::io::Error::new( - std::io::ErrorKind::NotFound, - "Could not determine executable path", - )) - })?; - - let init_config_path = root.join("init-config.yaml"); - if !init_config_path.exists() { - return Err(ConfigError::NotFound( - "init-config.yaml not found".to_string(), - )); - } - - let init_content = std::fs::read_to_string(&init_config_path)?; - parse_yaml_content(&init_content) -} - #[cfg(test)] mod tests { use super::*; @@ -1353,159 +1276,47 @@ mod tests { } #[test] - fn test_config_recovery_from_backup() -> Result<(), ConfigError> { + fn test_corrupt_config_skipped_on_read() -> Result<(), ConfigError> { let config_file = NamedTempFile::new().unwrap(); let secrets_file = NamedTempFile::new().unwrap(); let config = Config::new_with_file_secrets(config_file.path(), secrets_file.path())?; - // Create a valid config first - config.set_param("key1", "value1")?; - - // Verify the backup was created by the first write - let backup_paths = config.get_backup_paths(); - println!("Backup paths: {:?}", backup_paths); - for (i, path) in backup_paths.iter().enumerate() { - println!("Backup {} exists: {}", i, path.exists()); - } - - // Make another write to ensure backup is created - config.set_param("key2", 42)?; - - // Check again - for (i, path) in backup_paths.iter().enumerate() { - println!( - "After second write - Backup {} exists: {}", - i, - path.exists() - ); - } - - // Corrupt the main config file std::fs::write(config_file.path(), "invalid: yaml: content: [unclosed")?; - // Try to load values - should recover from backup - let recovered_values = config.all_values()?; - println!("Recovered values: {:?}", recovered_values); + // Reads skip corrupt files gracefully + let values = config.all_values()?; + assert!(values.is_empty() || !values.contains_key("key1")); - // Should have recovered the data - assert!( - !recovered_values.is_empty(), - "Should have recovered at least one key" - ); + // A write starts fresh (corrupt content is discarded) + config.set_param("recovery_key", "value")?; + let reloaded = config.all_values()?; + assert!(reloaded.contains_key("recovery_key")); Ok(()) } #[test] - fn test_config_recovery_creates_fresh_file() -> Result<(), ConfigError> { - let config_file = NamedTempFile::new().unwrap(); - let secrets_file = NamedTempFile::new().unwrap(); - let config = Config::new_with_file_secrets(config_file.path(), secrets_file.path())?; - - // Create a corrupted config file with no backup - std::fs::write(config_file.path(), "invalid: yaml: content: [unclosed")?; - - // Try to load values - should create a fresh default config - let recovered_values = config.all_values()?; - - // Note: migrations may add keys like "extensions", so we just verify - // that no user-defined keys exist (the config was reset) - assert!( - !recovered_values.contains_key("key1"), - "Should not have user keys after recovery" - ); - - // Verify that a clean config file was written to disk - let file_content = std::fs::read_to_string(config_file.path())?; - - // Should be valid YAML - let parsed: serde_yaml::Value = serde_yaml::from_str(&file_content)?; - assert!(parsed.is_mapping()); - - // Should be able to load it again without issues - let _reloaded_values = config.all_values()?; - - Ok(()) - } - - #[test] - fn test_config_file_creation_when_missing() -> Result<(), ConfigError> { + fn test_missing_config_created_on_write() -> Result<(), ConfigError> { let config_file = NamedTempFile::new().unwrap(); let secrets_file = NamedTempFile::new().unwrap(); let config_path = config_file.path().to_path_buf(); let config = Config::new_with_file_secrets(&config_path, secrets_file.path())?; - // Delete the file to simulate it not existing std::fs::remove_file(&config_path)?; assert!(!config_path.exists()); - // Try to load values - should create a fresh default config file + // Reads return empty when file is missing let values = config.all_values()?; + assert!(values.is_empty() || !values.contains_key("key1")); - // Note: migrations may add keys like "extensions", so we just verify - // that no user-defined keys exist (the config was freshly created) - assert!( - !values.contains_key("key1"), - "Should not have user keys in fresh config" - ); - - // Verify that the config file was created + // A write creates the file + config.set_param("new_key", "new_value")?; assert!(config_path.exists()); - // Verify that it's valid YAML let file_content = std::fs::read_to_string(&config_path)?; let parsed: serde_yaml::Value = serde_yaml::from_str(&file_content)?; assert!(parsed.is_mapping()); - // Should be able to load it again without issues - let _reloaded_values = config.all_values()?; - - Ok(()) - } - - #[test] - fn test_config_recovery_from_backup_when_missing() -> Result<(), ConfigError> { - let config_file = NamedTempFile::new().unwrap(); - let secrets_file = NamedTempFile::new().unwrap(); - let config_path = config_file.path().to_path_buf(); - let config = Config::new_with_file_secrets(&config_path, secrets_file.path())?; - - // First, create a config with some data - config.set_param("test_key_backup", "backup_value")?; - config.set_param("another_key", 42)?; - - // Verify the backup was created - let backup_paths = config.get_backup_paths(); - let primary_backup = &backup_paths[0]; // .bak file - - // Make sure we have a backup by doing another write - config.set_param("third_key", true)?; - assert!(primary_backup.exists(), "Backup should exist after writes"); - - // Now delete the main config file to simulate it being lost - std::fs::remove_file(&config_path)?; - assert!(!config_path.exists()); - - // Try to load values - should recover from backup - let recovered_values = config.all_values()?; - - // Should have recovered the data from backup - assert!( - !recovered_values.is_empty(), - "Should have recovered data from backup" - ); - - // Verify the main config file was restored - assert!(config_path.exists(), "Main config file should be restored"); - - // Verify we can load the data (using a key that won't conflict with env vars) - if let Ok(backup_value) = config.get_param::("test_key_backup") { - // If we recovered the key, great! - assert_eq!(backup_value, "backup_value"); - } - // Note: Due to back up rotation, we might not get the exact same data, - // but we should get some data back - Ok(()) } @@ -1530,27 +1341,6 @@ mod tests { Ok(()) } - #[test] - fn test_backup_rotation() -> Result<(), ConfigError> { - let config = new_test_config(); - - // Create multiple versions to test rotation - for i in 1..=7 { - config.set_param("version", i)?; - } - - let backup_paths = config.get_backup_paths(); - - // Should have backups but not more than our limit - let existing_backups: Vec<_> = backup_paths.iter().filter(|p| p.exists()).collect(); - assert!( - existing_backups.len() == 1, - "Should not exceed backup limit" - ); // .bak + .bak.1 through .bak.5 - - Ok(()) - } - #[test] fn test_env_var_parsing_strings() -> Result<(), ConfigError> { // Test unquoted strings @@ -1838,6 +1628,66 @@ mod tests { Config::new_with_file_secrets(config_file.path(), secrets_file.path()).unwrap() } + /// Create a test config where `base_content` is a lower-priority layer + /// and the actual writable config is a separate (initially empty) file. + fn new_test_config_with_base(base_content: &str) -> (Config, NamedTempFile) { + let base_file = NamedTempFile::new().unwrap(); + let config_file = NamedTempFile::new().unwrap(); + let secrets_file = NamedTempFile::new().unwrap(); + std::fs::write(base_file.path(), base_content).unwrap(); + let config = Config::new_with_config_paths( + vec![ + base_file.path().to_path_buf(), + config_file.path().to_path_buf(), + ], + secrets_file.path(), + ) + .unwrap(); + (config, base_file) + } + + #[test] + fn test_defaults_fallback_when_key_not_in_config() -> Result<(), ConfigError> { + let (config, _defaults) = + new_test_config_with_base("SECURITY_PROMPT_ENABLED: true\nsome_key: default_val"); + + // Key only in defaults → returns defaults value + let value: bool = config.get_param("SECURITY_PROMPT_ENABLED")?; + assert!(value); + + let value: String = config.get_param("some_key")?; + assert_eq!(value, "default_val"); + + Ok(()) + } + + #[test] + #[serial] + fn test_full_precedence_env_over_config_over_defaults() -> Result<(), ConfigError> { + let (config, _defaults) = new_test_config_with_base("my_key: from_defaults"); + + // Only defaults → returns defaults + let value: String = config.get_param("my_key")?; + assert_eq!(value, "from_defaults"); + + // Config file overrides defaults + config.set_param("my_key", "from_config")?; + let value: String = config.get_param("my_key")?; + assert_eq!(value, "from_config"); + + // Env var overrides config file (and defaults) + std::env::set_var("MY_KEY", "from_env"); + let value: String = config.get_param("my_key")?; + assert_eq!(value, "from_env"); + std::env::remove_var("MY_KEY"); + + // After removing env var, config file value is back + let value: String = config.get_param("my_key")?; + assert_eq!(value, "from_config"); + + Ok(()) + } + #[test] fn test_missing_key_returns_not_found() { let config = new_test_config(); @@ -1846,6 +1696,36 @@ mod tests { assert!(matches!(result, Err(ConfigError::NotFound(_)))); } + #[test] + fn test_lower_priority_values_not_persisted_on_write() -> Result<(), ConfigError> { + let (config, _base) = new_test_config_with_base("base_key: base_value"); + + // Read a value from the base layer (should work) + let value: String = config.get_param("base_key")?; + assert_eq!(value, "base_value"); + + // Write a different key to the user config + config.set_param("user_key", "user_value")?; + + // Read user config file directly - should NOT contain base_key + let config_path = PathBuf::from(config.path()); + let file_content = std::fs::read_to_string(&config_path)?; + assert!( + !file_content.contains("base_key"), + "Base layer values should not be persisted to user config on write" + ); + assert!( + file_content.contains("user_key"), + "User's key should be in config file" + ); + + // But reading via get_param should still return the base value + let value: String = config.get_param("base_key")?; + assert_eq!(value, "base_value"); + + Ok(()) + } + #[test] #[cfg(unix)] fn test_secrets_file_created_with_restricted_permissions() -> Result<(), ConfigError> { @@ -1863,4 +1743,292 @@ mod tests { Ok(()) } + + #[test] + fn test_merge_config_values_basic_override() { + let mut base = Mapping::new(); + base.insert( + serde_yaml::Value::String("key1".into()), + serde_yaml::Value::String("base_value".into()), + ); + base.insert( + serde_yaml::Value::String("key2".into()), + serde_yaml::Value::String("keep_me".into()), + ); + + let mut overlay = Mapping::new(); + overlay.insert( + serde_yaml::Value::String("key1".into()), + serde_yaml::Value::String("overlay_value".into()), + ); + overlay.insert( + serde_yaml::Value::String("key3".into()), + serde_yaml::Value::String("new_value".into()), + ); + + merge_config_values(&mut base, overlay); + + assert_eq!(base.get("key1").unwrap().as_str().unwrap(), "overlay_value"); + assert_eq!(base.get("key2").unwrap().as_str().unwrap(), "keep_me"); + assert_eq!(base.get("key3").unwrap().as_str().unwrap(), "new_value"); + } + + #[test] + fn test_merge_extensions_append_new() { + let mut base = Mapping::new(); + let mut base_ext = Mapping::new(); + let mut ext_a = Mapping::new(); + ext_a.insert( + serde_yaml::Value::String("enabled".into()), + serde_yaml::Value::Bool(true), + ); + ext_a.insert( + serde_yaml::Value::String("type".into()), + serde_yaml::Value::String("builtin".into()), + ); + base_ext.insert( + serde_yaml::Value::String("ext_a".into()), + serde_yaml::Value::Mapping(ext_a), + ); + base.insert( + serde_yaml::Value::String("extensions".into()), + serde_yaml::Value::Mapping(base_ext), + ); + + let mut overlay = Mapping::new(); + let mut overlay_ext = Mapping::new(); + let mut ext_b = Mapping::new(); + ext_b.insert( + serde_yaml::Value::String("enabled".into()), + serde_yaml::Value::Bool(true), + ); + ext_b.insert( + serde_yaml::Value::String("type".into()), + serde_yaml::Value::String("stdio".into()), + ); + overlay_ext.insert( + serde_yaml::Value::String("ext_b".into()), + serde_yaml::Value::Mapping(ext_b), + ); + overlay.insert( + serde_yaml::Value::String("extensions".into()), + serde_yaml::Value::Mapping(overlay_ext), + ); + + merge_config_values(&mut base, overlay); + + let extensions = base.get("extensions").unwrap().as_mapping().unwrap(); + assert!(extensions.contains_key("ext_a")); + assert!(extensions.contains_key("ext_b")); + // ext_a should be unchanged + let a = extensions.get("ext_a").unwrap().as_mapping().unwrap(); + assert!(a.get("enabled").unwrap().as_bool().unwrap()); + } + + #[test] + fn test_merge_extensions_partial_override() { + // Base has ext_a enabled with several fields + let mut base = Mapping::new(); + let mut base_ext = Mapping::new(); + let mut ext_a = Mapping::new(); + ext_a.insert( + serde_yaml::Value::String("enabled".into()), + serde_yaml::Value::Bool(true), + ); + ext_a.insert( + serde_yaml::Value::String("type".into()), + serde_yaml::Value::String("builtin".into()), + ); + ext_a.insert( + serde_yaml::Value::String("name".into()), + serde_yaml::Value::String("My Extension".into()), + ); + base_ext.insert( + serde_yaml::Value::String("my_ext".into()), + serde_yaml::Value::Mapping(ext_a), + ); + base.insert( + serde_yaml::Value::String("extensions".into()), + serde_yaml::Value::Mapping(base_ext), + ); + + // Overlay just disables it with a partial entry + let mut overlay = Mapping::new(); + let mut overlay_ext = Mapping::new(); + let mut ext_override = Mapping::new(); + ext_override.insert( + serde_yaml::Value::String("enabled".into()), + serde_yaml::Value::Bool(false), + ); + overlay_ext.insert( + serde_yaml::Value::String("my_ext".into()), + serde_yaml::Value::Mapping(ext_override), + ); + overlay.insert( + serde_yaml::Value::String("extensions".into()), + serde_yaml::Value::Mapping(overlay_ext), + ); + + merge_config_values(&mut base, overlay); + + let extensions = base.get("extensions").unwrap().as_mapping().unwrap(); + let my_ext = extensions.get("my_ext").unwrap().as_mapping().unwrap(); + + // enabled should be overridden to false + assert!(!my_ext.get("enabled").unwrap().as_bool().unwrap()); + // Other fields should be preserved + assert_eq!(my_ext.get("type").unwrap().as_str().unwrap(), "builtin"); + assert_eq!( + my_ext.get("name").unwrap().as_str().unwrap(), + "My Extension" + ); + } + + #[test] + fn test_multi_path_config_loading() -> Result<(), ConfigError> { + let base_file = NamedTempFile::new().unwrap(); + let user_file = NamedTempFile::new().unwrap(); + let secrets_file = NamedTempFile::new().unwrap(); + + // Base (system) config + std::fs::write( + base_file.path(), + "GOOSE_PROVIDER: openai\nGOOSE_MODEL: gpt-4\n", + ) + .unwrap(); + + // User config overrides model + std::fs::write(user_file.path(), "GOOSE_MODEL: gpt-4o\n").unwrap(); + + let config = Config::new_with_config_paths( + vec![ + base_file.path().to_path_buf(), + user_file.path().to_path_buf(), + ], + secrets_file.path(), + )?; + + // GOOSE_MODEL should be overridden by later config + let model: String = config.get_param("GOOSE_MODEL")?; + assert_eq!(model, "gpt-4o"); + + // GOOSE_PROVIDER should still come from base + let provider: String = config.get_param("GOOSE_PROVIDER")?; + assert_eq!(provider, "openai"); + + Ok(()) + } + + #[test] + fn test_extension_merge_across_configs() -> Result<(), ConfigError> { + let base_file = NamedTempFile::new().unwrap(); + let local_file = NamedTempFile::new().unwrap(); + let secrets_file = NamedTempFile::new().unwrap(); + + // System config (lower priority) has developer extension enabled + std::fs::write( + base_file.path(), + r#" +extensions: + developer: + enabled: true + type: builtin + name: Developer + description: "Core developer tools" +"#, + ) + .unwrap(); + + // User config (higher priority / write target) disables developer and adds a new extension + std::fs::write( + local_file.path(), + r#" +extensions: + developer: + enabled: false + my_custom_ext: + enabled: true + type: stdio + name: MyCustom + cmd: /usr/bin/my-ext +"#, + ) + .unwrap(); + + // local_file is last = write target and highest priority + let config = Config::new_with_config_paths( + vec![ + base_file.path().to_path_buf(), + local_file.path().to_path_buf(), + ], + secrets_file.path(), + )?; + + let values = config.load()?; + let extensions = values.get("extensions").unwrap().as_mapping().unwrap(); + + // developer should be disabled (user config overrides system) + let dev = extensions.get("developer").unwrap().as_mapping().unwrap(); + assert!(!dev.get("enabled").unwrap().as_bool().unwrap()); + // Fields from the system config should be preserved via merge + assert!(dev.get("name").is_some()); + + // my_custom_ext should be present from user config + let custom = extensions + .get("my_custom_ext") + .unwrap() + .as_mapping() + .unwrap(); + assert!(custom.get("enabled").unwrap().as_bool().unwrap()); + assert_eq!(custom.get("type").unwrap().as_str().unwrap(), "stdio"); + + Ok(()) + } + + #[test] + fn test_three_config_layers_ordered() -> Result<(), ConfigError> { + let system_file = NamedTempFile::new().unwrap(); + let user_file = NamedTempFile::new().unwrap(); + let local_file = NamedTempFile::new().unwrap(); + let secrets_file = NamedTempFile::new().unwrap(); + + std::fs::write(system_file.path(), "key: system\n").unwrap(); + std::fs::write(user_file.path(), "key: user\n").unwrap(); + std::fs::write(local_file.path(), "key: local\n").unwrap(); + + let config = Config::new_with_config_paths( + vec![ + system_file.path().to_path_buf(), + user_file.path().to_path_buf(), + local_file.path().to_path_buf(), + ], + secrets_file.path(), + )?; + + let value: String = config.get_param("key")?; + assert_eq!(value, "local"); + + Ok(()) + } + + #[test] + fn test_missing_config_path_is_skipped() -> Result<(), ConfigError> { + let config_file = NamedTempFile::new().unwrap(); + let secrets_file = NamedTempFile::new().unwrap(); + + std::fs::write(config_file.path(), "key: base\n").unwrap(); + + let config = Config::new_with_config_paths( + vec![ + PathBuf::from("/tmp/nonexistent_goose_config.yaml"), + config_file.path().to_path_buf(), + ], + secrets_file.path(), + )?; + + let value: String = config.get_param("key")?; + assert_eq!(value, "base"); + + Ok(()) + } } diff --git a/crates/goose/src/config/mod.rs b/crates/goose/src/config/mod.rs index 8fca0d134e..cd731c2ae3 100644 --- a/crates/goose/src/config/mod.rs +++ b/crates/goose/src/config/mod.rs @@ -12,7 +12,7 @@ pub mod signup_openrouter; pub mod signup_tetrate; pub use crate::agents::ExtensionConfig; -pub use base::{Config, ConfigError}; +pub use base::{merge_config_values, Config, ConfigError}; pub use declarative_providers::DeclarativeProviderConfig; pub use experiments::ExperimentManager; pub use extensions::{ diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index e01f2906ad..43f355fde7 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -747,29 +747,6 @@ } } }, - "/config/backup": { - "post": { - "tags": [ - "super::routes::config_management" - ], - "operationId": "backup_config", - "responses": { - "200": { - "description": "Config file backed up", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "500": { - "description": "Internal server error" - } - } - } - }, "/config/canonical-model-info": { "post": { "tags": [ @@ -1065,29 +1042,6 @@ } } }, - "/config/init": { - "post": { - "tags": [ - "super::routes::config_management" - ], - "operationId": "init_config", - "responses": { - "200": { - "description": "Config initialization check completed", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "500": { - "description": "Internal server error" - } - } - } - }, "/config/permissions": { "post": { "tags": [ @@ -1485,29 +1439,6 @@ } } }, - "/config/recover": { - "post": { - "tags": [ - "super::routes::config_management" - ], - "operationId": "recover_config", - "responses": { - "200": { - "description": "Config recovery attempted", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "500": { - "description": "Internal server error" - } - } - } - }, "/config/remove": { "post": { "tags": [ diff --git a/ui/desktop/src/App.test.tsx b/ui/desktop/src/App.test.tsx index f45e44588c..9383869932 100644 --- a/ui/desktop/src/App.test.tsx +++ b/ui/desktop/src/App.test.tsx @@ -273,29 +273,6 @@ describe('App Component - Brand New State', () => { expect(mockNavigate).not.toHaveBeenCalled(); }); - it('should handle config recovery gracefully', async () => { - // Mock config error that triggers recovery - const { readAllConfig, recoverConfig } = await import('./api'); - console.log(recoverConfig); - vi.mocked(readAllConfig).mockRejectedValueOnce(new Error('Config read error')); - - mockElectron.getConfig.mockReturnValue({ - GOOSE_DEFAULT_PROVIDER: null, - GOOSE_DEFAULT_MODEL: null, - GOOSE_ALLOWLIST_WARNING: false, - }); - - render(, { wrapper: IntlTestWrapper }); - - // Wait for initialization and recovery - await waitFor(() => { - expect(mockElectron.reactReady).toHaveBeenCalled(); - }); - - // App should still initialize without any navigation calls - expect(mockNavigate).not.toHaveBeenCalled(); - }); - it('should seed recipe sessions with the recipe prompt when no initial message is provided', () => { expect( resolveSessionInitialMessage( diff --git a/ui/desktop/src/api/index.ts b/ui/desktop/src/api/index.ts index 595b185bec..8850fe0db7 100644 --- a/ui/desktop/src/api/index.ts +++ b/ui/desktop/src/api/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { addExtension, agentAddExtension, agentRemoveExtension, backupConfig, callTool, cancelDownload, cancelLocalModelDownload, checkProvider, cleanupProviderCache, configureProviderOauth, confirmToolAction, createCustomProvider, createRecipe, createSchedule, decodeRecipe, deleteLocalModel, deleteModel, deleteRecipe, deleteSchedule, deleteSession, diagnostics, downloadHfModel, downloadModel, encodeRecipe, exportApp, exportSession, forkSession, getCanonicalModelInfo, getCustomProvider, getDictationConfig, getDownloadProgress, getExtensions, getFeatures, getLocalModelDownloadProgress, getModelSettings, getPrompt, getPrompts, getProviderCatalog, getProviderCatalogTemplate, getProviderModels, getRepoFiles, getSession, getSessionExtensions, getSessionInsights, getSlashCommands, getTools, getTunnelStatus, importApp, importSession, initConfig, inspectRunningJob, killRunningJob, listApps, listLocalModels, listModels, listRecipes, listSchedules, listSessions, mcpUiProxy, type Options, parseRecipe, pauseSchedule, providers, readAllConfig, readConfig, readResource, recipeToYaml, recoverConfig, removeConfig, removeCustomProvider, removeExtension, reply, resetPrompt, restartAgent, resumeAgent, runNowHandler, savePrompt, saveRecipe, scanRecipe, scheduleRecipe, searchHfModels, searchSessions, sendTelemetryEvent, sessionCancel, sessionEvents, sessionReply, sessionsHandler, setConfigProvider, setRecipeSlashCommand, startAgent, startNanogptSetup, startOpenrouterSetup, startTetrateSetup, startTunnel, status, stopAgent, stopTunnel, syncFeaturedModels, systemInfo, transcribeDictation, unpauseSchedule, updateAgentProvider, updateCustomProvider, updateFromSession, updateModelSettings, updateSchedule, updateSession, updateSessionName, updateSessionUserRecipeValues, updateWorkingDir, upsertConfig, upsertPermissions, validateConfig } from './sdk.gen'; -export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, AuthorRequest, BackupConfigData, BackupConfigErrors, BackupConfigResponse, BackupConfigResponses, CallToolData, CallToolErrors, CallToolRequest, CallToolResponse, CallToolResponse2, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CancelRequest, ChatRequest, CheckProviderData, CheckProviderRequest, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponse, CleanupProviderCacheResponses, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, ContentBlock, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponse2, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeRequest, CreateRecipeResponse, CreateRecipeResponse2, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponse, DiagnosticsResponses, DictationProvider, DictationProviderStatus, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponse, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelRequest, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, EnvVarConfig, ErrorResponse, ExportAppData, ExportAppError, ExportAppErrors, ExportAppResponse, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponse, ExportSessionResponses, ExtensionConfig, ExtensionData, ExtensionEntry, ExtensionLoadResult, ExtensionQuery, ExtensionResponse, FeaturesResponse, ForkRequest, ForkResponse, ForkSessionData, ForkSessionErrors, ForkSessionResponse, ForkSessionResponses, FrontendToolRequest, GetCanonicalModelInfoData, GetCanonicalModelInfoResponse, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponse, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponse, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponse, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponse, GetExtensionsResponses, GetFeaturesData, GetFeaturesResponse, GetFeaturesResponses, GetLocalModelDownloadProgressData, GetLocalModelDownloadProgressErrors, GetLocalModelDownloadProgressResponse, GetLocalModelDownloadProgressResponses, GetModelSettingsData, GetModelSettingsErrors, GetModelSettingsResponse, GetModelSettingsResponses, GetPromptData, GetPromptErrors, GetPromptResponse, GetPromptResponses, GetPromptsData, GetPromptsResponse, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponse, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponse, GetProviderCatalogTemplateResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponse, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponse, GetSessionInsightsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponse, GetTunnelStatusResponses, GooseApp, GooseMode, HfGgufFile, HfModelInfo, HfQuantVariant, Icon, IconTheme, ImageContent, ImportAppData, ImportAppError, ImportAppErrors, ImportAppRequest, ImportAppResponse, ImportAppResponse2, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionRequest, ImportSessionResponse, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponse, InitConfigResponses, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsError, ListAppsErrors, ListAppsRequest, ListAppsResponse, ListAppsResponse2, ListAppsResponses, ListLocalModelsData, ListLocalModelsResponse, ListLocalModelsResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponse, ListSessionsResponses, LoadedProvider, LocalModelResponse, McpAppResource, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, Message, MessageContent, MessageEvent, MessageMetadata, ModelCapabilities, ModelConfig, ModelDownloadStatus, ModelInfo, ModelInfoData, ModelInfoQuery, ModelInfoResponse, ModelSettings, ModelTemplate, ParseRecipeData, ParseRecipeError, ParseRecipeErrors, ParseRecipeRequest, ParseRecipeResponse, ParseRecipeResponse2, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponse, PauseScheduleResponses, Permission, PermissionLevel, PermissionsMetadata, PrincipalType, PromptContentResponse, PromptsListResponse, ProviderCatalogEntry, ProviderDetails, ProviderEngine, ProviderMetadata, ProvidersData, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderTemplate, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceRequest, ReadResourceResponse, ReadResourceResponse2, ReadResourceResponses, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponse, RecoverConfigResponses, RedactedThinkingContent, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponse, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponse, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionRequest, RemoveExtensionResponse, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponse, ReplyResponses, RepoVariantsResponse, ResetPromptData, ResetPromptErrors, ResetPromptResponse, ResetPromptResponses, ResourceContents, ResourceMetadata, Response, RestartAgentData, RestartAgentErrors, RestartAgentRequest, RestartAgentResponse, RestartAgentResponse2, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentRequest, ResumeAgentResponse, ResumeAgentResponse2, ResumeAgentResponses, RetryConfig, Role, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponse, RunNowHandlerResponses, RunNowResponse, SamplingConfig, SavePromptData, SavePromptErrors, SavePromptRequest, SavePromptResponse, SavePromptResponses, SaveRecipeData, SaveRecipeError, SaveRecipeErrors, SaveRecipeRequest, SaveRecipeResponse, SaveRecipeResponse2, SaveRecipeResponses, ScanRecipeData, ScanRecipeRequest, ScanRecipeResponse, ScanRecipeResponse2, ScanRecipeResponses, ScheduledJob, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeRequest, ScheduleRecipeResponses, SearchHfModelsData, SearchHfModelsErrors, SearchHfModelsResponse, SearchHfModelsResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponse, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionCancelData, SessionCancelResponses, SessionDisplayInfo, SessionEventsData, SessionEventsErrors, SessionEventsResponse, SessionEventsResponses, SessionExtensionsResponse, SessionInsights, SessionListResponse, SessionReplyData, SessionReplyErrors, SessionReplyRequest, SessionReplyResponse, SessionReplyResponse2, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SetupResponse, SlashCommand, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StartNanogptSetupData, StartNanogptSetupResponse, StartNanogptSetupResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponse, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponse, StartTetrateSetupResponses, StartTunnelData, StartTunnelError, StartTunnelErrors, StartTunnelResponse, StartTunnelResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, StopTunnelData, StopTunnelError, StopTunnelErrors, StopTunnelResponses, SubRecipe, SuccessCheck, SyncFeaturedModelsData, SyncFeaturedModelsResponses, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TaskSupport, TelemetryEventRequest, Template, TextContent, ThinkingContent, TokenState, Tool, ToolAnnotations, ToolConfirmationRequest, ToolExecution, ToolInfo, ToolPermission, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, TunnelInfo, TunnelState, UiMetadata, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponse, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderRequest, UpdateCustomProviderResponse, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionRequest, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponse, UpdateModelSettingsResponses, UpdateProviderRequest, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleRequest, UpdateScheduleResponse, UpdateScheduleResponses, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionRequest, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsQuery, UpsertPermissionsResponse, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponse, ValidateConfigResponses, WhisperModelResponse, WindowProps } from './types.gen'; +export { addExtension, agentAddExtension, agentRemoveExtension, callTool, cancelDownload, cancelLocalModelDownload, checkProvider, cleanupProviderCache, configureProviderOauth, confirmToolAction, createCustomProvider, createRecipe, createSchedule, decodeRecipe, deleteLocalModel, deleteModel, deleteRecipe, deleteSchedule, deleteSession, diagnostics, downloadHfModel, downloadModel, encodeRecipe, exportApp, exportSession, forkSession, getCanonicalModelInfo, getCustomProvider, getDictationConfig, getDownloadProgress, getExtensions, getFeatures, getLocalModelDownloadProgress, getModelSettings, getPrompt, getPrompts, getProviderCatalog, getProviderCatalogTemplate, getProviderModels, getRepoFiles, getSession, getSessionExtensions, getSessionInsights, getSlashCommands, getTools, getTunnelStatus, importApp, importSession, inspectRunningJob, killRunningJob, listApps, listLocalModels, listModels, listRecipes, listSchedules, listSessions, mcpUiProxy, type Options, parseRecipe, pauseSchedule, providers, readAllConfig, readConfig, readResource, recipeToYaml, removeConfig, removeCustomProvider, removeExtension, reply, resetPrompt, restartAgent, resumeAgent, runNowHandler, savePrompt, saveRecipe, scanRecipe, scheduleRecipe, searchHfModels, searchSessions, sendTelemetryEvent, sessionCancel, sessionEvents, sessionReply, sessionsHandler, setConfigProvider, setRecipeSlashCommand, startAgent, startNanogptSetup, startOpenrouterSetup, startTetrateSetup, startTunnel, status, stopAgent, stopTunnel, syncFeaturedModels, systemInfo, transcribeDictation, unpauseSchedule, updateAgentProvider, updateCustomProvider, updateFromSession, updateModelSettings, updateSchedule, updateSession, updateSessionName, updateSessionUserRecipeValues, updateWorkingDir, upsertConfig, upsertPermissions, validateConfig } from './sdk.gen'; +export type { ActionRequired, ActionRequiredData, AddExtensionData, AddExtensionErrors, AddExtensionRequest, AddExtensionResponse, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponse, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponse, AgentRemoveExtensionResponses, Annotations, Author, AuthorRequest, CallToolData, CallToolErrors, CallToolRequest, CallToolResponse, CallToolResponse2, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CancelRequest, ChatRequest, CheckProviderData, CheckProviderRequest, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponse, CleanupProviderCacheResponses, ClientOptions, CommandType, ConfigKey, ConfigKeyQuery, ConfigResponse, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionRequest, ConfirmToolActionResponses, Content, ContentBlock, Conversation, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponse, CreateCustomProviderResponse2, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeRequest, CreateRecipeResponse, CreateRecipeResponse2, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleRequest, CreateScheduleResponse, CreateScheduleResponses, CspMetadata, DeclarativeProviderConfig, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeRequest, DecodeRecipeResponse, DecodeRecipeResponse2, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeRequest, DeleteRecipeResponse, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponse, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponse, DiagnosticsResponses, DictationProvider, DictationProviderStatus, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponse, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelRequest, DownloadModelResponses, DownloadProgress, DownloadStatus, EmbeddedResource, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeRequest, EncodeRecipeResponse, EncodeRecipeResponse2, EncodeRecipeResponses, Envs, EnvVarConfig, ErrorResponse, ExportAppData, ExportAppError, ExportAppErrors, ExportAppResponse, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponse, ExportSessionResponses, ExtensionConfig, ExtensionData, ExtensionEntry, ExtensionLoadResult, ExtensionQuery, ExtensionResponse, FeaturesResponse, ForkRequest, ForkResponse, ForkSessionData, ForkSessionErrors, ForkSessionResponse, ForkSessionResponses, FrontendToolRequest, GetCanonicalModelInfoData, GetCanonicalModelInfoResponse, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponse, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponse, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponse, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponse, GetExtensionsResponses, GetFeaturesData, GetFeaturesResponse, GetFeaturesResponses, GetLocalModelDownloadProgressData, GetLocalModelDownloadProgressErrors, GetLocalModelDownloadProgressResponse, GetLocalModelDownloadProgressResponses, GetModelSettingsData, GetModelSettingsErrors, GetModelSettingsResponse, GetModelSettingsResponses, GetPromptData, GetPromptErrors, GetPromptResponse, GetPromptResponses, GetPromptsData, GetPromptsResponse, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponse, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponse, GetProviderCatalogTemplateResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponse, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponse, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponse, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponse, GetSessionInsightsResponses, GetSessionResponse, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponse, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsQuery, GetToolsResponse, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponse, GetTunnelStatusResponses, GooseApp, GooseMode, HfGgufFile, HfModelInfo, HfQuantVariant, Icon, IconTheme, ImageContent, ImportAppData, ImportAppError, ImportAppErrors, ImportAppRequest, ImportAppResponse, ImportAppResponse2, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionRequest, ImportSessionResponse, ImportSessionResponses, InspectJobResponse, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponse, InspectRunningJobResponses, JsonObject, KillJobResponse, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsError, ListAppsErrors, ListAppsRequest, ListAppsResponse, ListAppsResponse2, ListAppsResponses, ListLocalModelsData, ListLocalModelsResponse, ListLocalModelsResponses, ListModelsData, ListModelsResponse, ListModelsResponses, ListRecipeResponse, ListRecipesData, ListRecipesErrors, ListRecipesResponse, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponse, ListSchedulesResponse2, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponse, ListSessionsResponses, LoadedProvider, LocalModelResponse, McpAppResource, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, Message, MessageContent, MessageEvent, MessageMetadata, ModelCapabilities, ModelConfig, ModelDownloadStatus, ModelInfo, ModelInfoData, ModelInfoQuery, ModelInfoResponse, ModelSettings, ModelTemplate, ParseRecipeData, ParseRecipeError, ParseRecipeErrors, ParseRecipeRequest, ParseRecipeResponse, ParseRecipeResponse2, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponse, PauseScheduleResponses, Permission, PermissionLevel, PermissionsMetadata, PrincipalType, PromptContentResponse, PromptsListResponse, ProviderCatalogEntry, ProviderDetails, ProviderEngine, ProviderMetadata, ProvidersData, ProvidersResponse, ProvidersResponse2, ProvidersResponses, ProviderTemplate, ProviderType, RawAudioContent, RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ReadAllConfigData, ReadAllConfigResponse, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceRequest, ReadResourceResponse, ReadResourceResponse2, ReadResourceResponses, Recipe, RecipeManifest, RecipeParameter, RecipeParameterInputType, RecipeParameterRequirement, RecipeToYamlData, RecipeToYamlError, RecipeToYamlErrors, RecipeToYamlRequest, RecipeToYamlResponse, RecipeToYamlResponse2, RecipeToYamlResponses, RedactedThinkingContent, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponse, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponse, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionRequest, RemoveExtensionResponse, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponse, ReplyResponses, RepoVariantsResponse, ResetPromptData, ResetPromptErrors, ResetPromptResponse, ResetPromptResponses, ResourceContents, ResourceMetadata, Response, RestartAgentData, RestartAgentErrors, RestartAgentRequest, RestartAgentResponse, RestartAgentResponse2, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentRequest, ResumeAgentResponse, ResumeAgentResponse2, ResumeAgentResponses, RetryConfig, Role, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponse, RunNowHandlerResponses, RunNowResponse, SamplingConfig, SavePromptData, SavePromptErrors, SavePromptRequest, SavePromptResponse, SavePromptResponses, SaveRecipeData, SaveRecipeError, SaveRecipeErrors, SaveRecipeRequest, SaveRecipeResponse, SaveRecipeResponse2, SaveRecipeResponses, ScanRecipeData, ScanRecipeRequest, ScanRecipeResponse, ScanRecipeResponse2, ScanRecipeResponses, ScheduledJob, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeRequest, ScheduleRecipeResponses, SearchHfModelsData, SearchHfModelsErrors, SearchHfModelsResponse, SearchHfModelsResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponse, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, Session, SessionCancelData, SessionCancelResponses, SessionDisplayInfo, SessionEventsData, SessionEventsErrors, SessionEventsResponse, SessionEventsResponses, SessionExtensionsResponse, SessionInsights, SessionListResponse, SessionReplyData, SessionReplyErrors, SessionReplyRequest, SessionReplyResponse, SessionReplyResponse2, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponse, SessionsHandlerResponses, SessionsQuery, SessionType, SetConfigProviderData, SetProviderRequest, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, SetSlashCommandRequest, Settings, SetupResponse, SlashCommand, SlashCommandsResponse, StartAgentData, StartAgentError, StartAgentErrors, StartAgentRequest, StartAgentResponse, StartAgentResponses, StartNanogptSetupData, StartNanogptSetupResponse, StartNanogptSetupResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponse, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponse, StartTetrateSetupResponses, StartTunnelData, StartTunnelError, StartTunnelErrors, StartTunnelResponse, StartTunnelResponses, StatusData, StatusResponse, StatusResponses, StopAgentData, StopAgentErrors, StopAgentRequest, StopAgentResponse, StopAgentResponses, StopTunnelData, StopTunnelError, StopTunnelErrors, StopTunnelResponses, SubRecipe, SuccessCheck, SyncFeaturedModelsData, SyncFeaturedModelsResponses, SystemInfo, SystemInfoData, SystemInfoResponse, SystemInfoResponses, SystemNotificationContent, SystemNotificationType, TaskSupport, TelemetryEventRequest, Template, TextContent, ThinkingContent, TokenState, Tool, ToolAnnotations, ToolConfirmationRequest, ToolExecution, ToolInfo, ToolPermission, ToolRequest, ToolResponse, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponse, TranscribeDictationResponses, TranscribeRequest, TranscribeResponse, TunnelInfo, TunnelState, UiMetadata, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponse, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderRequest, UpdateCustomProviderResponse, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionRequest, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponse, UpdateModelSettingsResponses, UpdateProviderRequest, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleRequest, UpdateScheduleResponse, UpdateScheduleResponses, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameRequest, UpdateSessionNameResponses, UpdateSessionRequest, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesError, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesRequest, UpdateSessionUserRecipeValuesResponse, UpdateSessionUserRecipeValuesResponse2, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirRequest, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigQuery, UpsertConfigResponse, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsQuery, UpsertPermissionsResponse, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponse, ValidateConfigResponses, WhisperModelResponse, WindowProps } from './types.gen'; diff --git a/ui/desktop/src/api/sdk.gen.ts b/ui/desktop/src/api/sdk.gen.ts index abd3b48d72..920160ebf8 100644 --- a/ui/desktop/src/api/sdk.gen.ts +++ b/ui/desktop/src/api/sdk.gen.ts @@ -2,7 +2,7 @@ import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; -import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, BackupConfigData, BackupConfigErrors, BackupConfigResponses, CallToolData, CallToolErrors, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CheckProviderData, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponses, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportAppData, ExportAppErrors, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, ForkSessionData, ForkSessionErrors, ForkSessionResponses, GetCanonicalModelInfoData, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetFeaturesData, GetFeaturesResponses, GetLocalModelDownloadProgressData, GetLocalModelDownloadProgressErrors, GetLocalModelDownloadProgressResponses, GetModelSettingsData, GetModelSettingsErrors, GetModelSettingsResponses, GetPromptData, GetPromptErrors, GetPromptResponses, GetPromptsData, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportAppData, ImportAppErrors, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InitConfigData, InitConfigErrors, InitConfigResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsErrors, ListAppsResponses, ListLocalModelsData, ListLocalModelsResponses, ListModelsData, ListModelsResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceResponses, RecipeToYamlData, RecipeToYamlErrors, RecipeToYamlResponses, RecoverConfigData, RecoverConfigErrors, RecoverConfigResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResetPromptData, ResetPromptErrors, ResetPromptResponses, RestartAgentData, RestartAgentErrors, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SavePromptData, SavePromptErrors, SavePromptResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SearchHfModelsData, SearchHfModelsErrors, SearchHfModelsResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, SessionCancelData, SessionCancelResponses, SessionEventsData, SessionEventsErrors, SessionEventsResponses, SessionReplyData, SessionReplyErrors, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartNanogptSetupData, StartNanogptSetupResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopAgentData, StopAgentErrors, StopAgentResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, SyncFeaturedModelsData, SyncFeaturedModelsResponses, SystemInfoData, SystemInfoResponses, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen'; +import type { AddExtensionData, AddExtensionErrors, AddExtensionResponses, AgentAddExtensionData, AgentAddExtensionErrors, AgentAddExtensionResponses, AgentRemoveExtensionData, AgentRemoveExtensionErrors, AgentRemoveExtensionResponses, CallToolData, CallToolErrors, CallToolResponses, CancelDownloadData, CancelDownloadErrors, CancelDownloadResponses, CancelLocalModelDownloadData, CancelLocalModelDownloadErrors, CancelLocalModelDownloadResponses, CheckProviderData, CleanupProviderCacheData, CleanupProviderCacheErrors, CleanupProviderCacheResponses, ConfigureProviderOauthData, ConfigureProviderOauthErrors, ConfigureProviderOauthResponses, ConfirmToolActionData, ConfirmToolActionErrors, ConfirmToolActionResponses, CreateCustomProviderData, CreateCustomProviderErrors, CreateCustomProviderResponses, CreateRecipeData, CreateRecipeErrors, CreateRecipeResponses, CreateScheduleData, CreateScheduleErrors, CreateScheduleResponses, DecodeRecipeData, DecodeRecipeErrors, DecodeRecipeResponses, DeleteLocalModelData, DeleteLocalModelErrors, DeleteLocalModelResponses, DeleteModelData, DeleteModelErrors, DeleteModelResponses, DeleteRecipeData, DeleteRecipeErrors, DeleteRecipeResponses, DeleteScheduleData, DeleteScheduleErrors, DeleteScheduleResponses, DeleteSessionData, DeleteSessionErrors, DeleteSessionResponses, DiagnosticsData, DiagnosticsErrors, DiagnosticsResponses, DownloadHfModelData, DownloadHfModelErrors, DownloadHfModelResponses, DownloadModelData, DownloadModelErrors, DownloadModelResponses, EncodeRecipeData, EncodeRecipeErrors, EncodeRecipeResponses, ExportAppData, ExportAppErrors, ExportAppResponses, ExportSessionData, ExportSessionErrors, ExportSessionResponses, ForkSessionData, ForkSessionErrors, ForkSessionResponses, GetCanonicalModelInfoData, GetCanonicalModelInfoResponses, GetCustomProviderData, GetCustomProviderErrors, GetCustomProviderResponses, GetDictationConfigData, GetDictationConfigResponses, GetDownloadProgressData, GetDownloadProgressErrors, GetDownloadProgressResponses, GetExtensionsData, GetExtensionsErrors, GetExtensionsResponses, GetFeaturesData, GetFeaturesResponses, GetLocalModelDownloadProgressData, GetLocalModelDownloadProgressErrors, GetLocalModelDownloadProgressResponses, GetModelSettingsData, GetModelSettingsErrors, GetModelSettingsResponses, GetPromptData, GetPromptErrors, GetPromptResponses, GetPromptsData, GetPromptsResponses, GetProviderCatalogData, GetProviderCatalogErrors, GetProviderCatalogResponses, GetProviderCatalogTemplateData, GetProviderCatalogTemplateErrors, GetProviderCatalogTemplateResponses, GetProviderModelsData, GetProviderModelsErrors, GetProviderModelsResponses, GetRepoFilesData, GetRepoFilesResponses, GetSessionData, GetSessionErrors, GetSessionExtensionsData, GetSessionExtensionsErrors, GetSessionExtensionsResponses, GetSessionInsightsData, GetSessionInsightsErrors, GetSessionInsightsResponses, GetSessionResponses, GetSlashCommandsData, GetSlashCommandsResponses, GetToolsData, GetToolsErrors, GetToolsResponses, GetTunnelStatusData, GetTunnelStatusResponses, ImportAppData, ImportAppErrors, ImportAppResponses, ImportSessionData, ImportSessionErrors, ImportSessionResponses, InspectRunningJobData, InspectRunningJobErrors, InspectRunningJobResponses, KillRunningJobData, KillRunningJobResponses, ListAppsData, ListAppsErrors, ListAppsResponses, ListLocalModelsData, ListLocalModelsResponses, ListModelsData, ListModelsResponses, ListRecipesData, ListRecipesErrors, ListRecipesResponses, ListSchedulesData, ListSchedulesErrors, ListSchedulesResponses, ListSessionsData, ListSessionsErrors, ListSessionsResponses, McpUiProxyData, McpUiProxyErrors, McpUiProxyResponses, ParseRecipeData, ParseRecipeErrors, ParseRecipeResponses, PauseScheduleData, PauseScheduleErrors, PauseScheduleResponses, ProvidersData, ProvidersResponses, ReadAllConfigData, ReadAllConfigResponses, ReadConfigData, ReadConfigErrors, ReadConfigResponses, ReadResourceData, ReadResourceErrors, ReadResourceResponses, RecipeToYamlData, RecipeToYamlErrors, RecipeToYamlResponses, RemoveConfigData, RemoveConfigErrors, RemoveConfigResponses, RemoveCustomProviderData, RemoveCustomProviderErrors, RemoveCustomProviderResponses, RemoveExtensionData, RemoveExtensionErrors, RemoveExtensionResponses, ReplyData, ReplyErrors, ReplyResponses, ResetPromptData, ResetPromptErrors, ResetPromptResponses, RestartAgentData, RestartAgentErrors, RestartAgentResponses, ResumeAgentData, ResumeAgentErrors, ResumeAgentResponses, RunNowHandlerData, RunNowHandlerErrors, RunNowHandlerResponses, SavePromptData, SavePromptErrors, SavePromptResponses, SaveRecipeData, SaveRecipeErrors, SaveRecipeResponses, ScanRecipeData, ScanRecipeResponses, ScheduleRecipeData, ScheduleRecipeErrors, ScheduleRecipeResponses, SearchHfModelsData, SearchHfModelsErrors, SearchHfModelsResponses, SearchSessionsData, SearchSessionsErrors, SearchSessionsResponses, SendTelemetryEventData, SendTelemetryEventResponses, SessionCancelData, SessionCancelResponses, SessionEventsData, SessionEventsErrors, SessionEventsResponses, SessionReplyData, SessionReplyErrors, SessionReplyResponses, SessionsHandlerData, SessionsHandlerErrors, SessionsHandlerResponses, SetConfigProviderData, SetRecipeSlashCommandData, SetRecipeSlashCommandErrors, SetRecipeSlashCommandResponses, StartAgentData, StartAgentErrors, StartAgentResponses, StartNanogptSetupData, StartNanogptSetupResponses, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, StartTunnelData, StartTunnelErrors, StartTunnelResponses, StatusData, StatusResponses, StopAgentData, StopAgentErrors, StopAgentResponses, StopTunnelData, StopTunnelErrors, StopTunnelResponses, SyncFeaturedModelsData, SyncFeaturedModelsResponses, SystemInfoData, SystemInfoResponses, TranscribeDictationData, TranscribeDictationErrors, TranscribeDictationResponses, UnpauseScheduleData, UnpauseScheduleErrors, UnpauseScheduleResponses, UpdateAgentProviderData, UpdateAgentProviderErrors, UpdateAgentProviderResponses, UpdateCustomProviderData, UpdateCustomProviderErrors, UpdateCustomProviderResponses, UpdateFromSessionData, UpdateFromSessionErrors, UpdateFromSessionResponses, UpdateModelSettingsData, UpdateModelSettingsErrors, UpdateModelSettingsResponses, UpdateScheduleData, UpdateScheduleErrors, UpdateScheduleResponses, UpdateSessionData, UpdateSessionErrors, UpdateSessionNameData, UpdateSessionNameErrors, UpdateSessionNameResponses, UpdateSessionResponses, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesErrors, UpdateSessionUserRecipeValuesResponses, UpdateWorkingDirData, UpdateWorkingDirErrors, UpdateWorkingDirResponses, UpsertConfigData, UpsertConfigErrors, UpsertConfigResponses, UpsertPermissionsData, UpsertPermissionsErrors, UpsertPermissionsResponses, ValidateConfigData, ValidateConfigErrors, ValidateConfigResponses } from './types.gen'; export type Options = Options2 & { /** @@ -152,8 +152,6 @@ export const updateWorkingDir = (options: export const readAllConfig = (options?: Options) => (options?.client ?? client).get({ url: '/config', ...options }); -export const backupConfig = (options?: Options) => (options?.client ?? client).post({ url: '/config/backup', ...options }); - export const getCanonicalModelInfo = (options: Options) => (options.client ?? client).post({ url: '/config/canonical-model-info', ...options, @@ -207,8 +205,6 @@ export const addExtension = (options: Opti export const removeExtension = (options: Options) => (options.client ?? client).delete({ url: '/config/extensions/{name}', ...options }); -export const initConfig = (options?: Options) => (options?.client ?? client).post({ url: '/config/init', ...options }); - export const upsertPermissions = (options: Options) => (options.client ?? client).post({ url: '/config/permissions', ...options, @@ -254,8 +250,6 @@ export const readConfig = (options: Option } }); -export const recoverConfig = (options?: Options) => (options?.client ?? client).post({ url: '/config/recover', ...options }); - export const removeConfig = (options: Options) => (options.client ?? client).post({ url: '/config/remove', ...options, diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index 8a559e7489..1b71c9909c 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -2235,29 +2235,6 @@ export type ReadAllConfigResponses = { export type ReadAllConfigResponse = ReadAllConfigResponses[keyof ReadAllConfigResponses]; -export type BackupConfigData = { - body?: never; - path?: never; - query?: never; - url: '/config/backup'; -}; - -export type BackupConfigErrors = { - /** - * Internal server error - */ - 500: unknown; -}; - -export type BackupConfigResponses = { - /** - * Config file backed up - */ - 200: string; -}; - -export type BackupConfigResponse = BackupConfigResponses[keyof BackupConfigResponses]; - export type GetCanonicalModelInfoData = { body: ModelInfoQuery; path?: never; @@ -2478,29 +2455,6 @@ export type RemoveExtensionResponses = { export type RemoveExtensionResponse = RemoveExtensionResponses[keyof RemoveExtensionResponses]; -export type InitConfigData = { - body?: never; - path?: never; - query?: never; - url: '/config/init'; -}; - -export type InitConfigErrors = { - /** - * Internal server error - */ - 500: unknown; -}; - -export type InitConfigResponses = { - /** - * Config initialization check completed - */ - 200: string; -}; - -export type InitConfigResponse = InitConfigResponses[keyof InitConfigResponses]; - export type UpsertPermissionsData = { body: UpsertPermissionsQuery; path?: never; @@ -2815,29 +2769,6 @@ export type ReadConfigResponses = { 200: unknown; }; -export type RecoverConfigData = { - body?: never; - path?: never; - query?: never; - url: '/config/recover'; -}; - -export type RecoverConfigErrors = { - /** - * Internal server error - */ - 500: unknown; -}; - -export type RecoverConfigResponses = { - /** - * Config recovery attempted - */ - 200: string; -}; - -export type RecoverConfigResponse = RecoverConfigResponses[keyof RecoverConfigResponses]; - export type RemoveConfigData = { body: ConfigKeyQuery; path?: never;