From 1b71dc721cf93c5d0320395d296aaf7c90970ff8 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 19 Feb 2026 21:11:58 +0800 Subject: [PATCH] refactor(omo): deduplicate OMO/OMO Slim via OmoVariant parameterization Introduce OmoVariant struct with STANDARD/SLIM constants to eliminate ~250 lines of copy-pasted code across DAO, service, commands, and frontend layers. Adding a new OMO variant now requires only a single const declaration instead of duplicating ~400 lines. --- src-tauri/src/commands/config.rs | 18 +- src-tauri/src/commands/omo.rs | 18 +- src-tauri/src/database/dao/omo.rs | 33 +- src-tauri/src/database/dao/providers.rs | 162 +------ src-tauri/src/lib.rs | 4 +- src-tauri/src/services/omo.rs | 451 +++++++----------- src-tauri/src/services/provider/mod.rs | 90 ++-- .../forms/helpers/opencodeFormUtils.ts | 24 +- src/lib/query/omo.ts | 312 ++++++------ src/types/omo.ts | 122 ++--- 10 files changed, 493 insertions(+), 741 deletions(-) diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index 6fd8c4dad..8edf15617 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -238,22 +238,28 @@ pub async fn set_common_config_snippet( if app_type == "omo" && state .db - .get_current_omo_provider("opencode") + .get_current_omo_provider("opencode", "omo") .map_err(|e| e.to_string())? .is_some() { - crate::services::OmoService::write_config_to_file(state.inner()) - .map_err(|e| e.to_string())?; + crate::services::OmoService::write_config_to_file( + state.inner(), + &crate::services::omo::STANDARD, + ) + .map_err(|e| e.to_string())?; } if app_type == "omo-slim" && state .db - .get_current_omo_slim_provider("opencode") + .get_current_omo_provider("opencode", "omo-slim") .map_err(|e| e.to_string())? .is_some() { - crate::services::OmoService::write_config_to_file_slim(state.inner()) - .map_err(|e| e.to_string())?; + crate::services::OmoService::write_config_to_file( + state.inner(), + &crate::services::omo::SLIM, + ) + .map_err(|e| e.to_string())?; } Ok(()) } diff --git a/src-tauri/src/commands/omo.rs b/src-tauri/src/commands/omo.rs index a2b91794d..d976e8b94 100644 --- a/src-tauri/src/commands/omo.rs +++ b/src-tauri/src/commands/omo.rs @@ -1,19 +1,19 @@ use tauri::State; -use crate::services::omo::OmoLocalFileData; +use crate::services::omo::{OmoLocalFileData, SLIM, STANDARD}; use crate::services::OmoService; use crate::store::AppState; #[tauri::command] pub async fn read_omo_local_file() -> Result { - OmoService::read_local_file().map_err(|e| e.to_string()) + OmoService::read_local_file(&STANDARD).map_err(|e| e.to_string()) } #[tauri::command] pub async fn get_current_omo_provider_id(state: State<'_, AppState>) -> Result { let provider = state .db - .get_current_omo_provider("opencode") + .get_current_omo_provider("opencode", "omo") .map_err(|e| e.to_string())?; Ok(provider.map(|p| p.id).unwrap_or_default()) } @@ -28,11 +28,11 @@ pub async fn disable_current_omo(state: State<'_, AppState>) -> Result<(), Strin if p.category.as_deref() == Some("omo") { state .db - .clear_omo_provider_current("opencode", id) + .clear_omo_provider_current("opencode", id, "omo") .map_err(|e| e.to_string())?; } } - OmoService::delete_config_file().map_err(|e| e.to_string())?; + OmoService::delete_config_file(&STANDARD).map_err(|e| e.to_string())?; Ok(()) } @@ -53,7 +53,7 @@ pub async fn get_omo_provider_count(state: State<'_, AppState>) -> Result Result { - OmoService::read_local_file_slim().map_err(|e| e.to_string()) + OmoService::read_local_file(&SLIM).map_err(|e| e.to_string()) } #[tauri::command] @@ -62,7 +62,7 @@ pub async fn get_current_omo_slim_provider_id( ) -> Result { let provider = state .db - .get_current_omo_slim_provider("opencode") + .get_current_omo_provider("opencode", "omo-slim") .map_err(|e| e.to_string())?; Ok(provider.map(|p| p.id).unwrap_or_default()) } @@ -77,11 +77,11 @@ pub async fn disable_current_omo_slim(state: State<'_, AppState>) -> Result<(), if p.category.as_deref() == Some("omo-slim") { state .db - .clear_omo_slim_provider_current("opencode", id) + .clear_omo_provider_current("opencode", id, "omo-slim") .map_err(|e| e.to_string())?; } } - OmoService::delete_config_file_slim().map_err(|e| e.to_string())?; + OmoService::delete_config_file(&SLIM).map_err(|e| e.to_string())?; Ok(()) } diff --git a/src-tauri/src/database/dao/omo.rs b/src-tauri/src/database/dao/omo.rs index fc01631fc..85be1ab22 100644 --- a/src-tauri/src/database/dao/omo.rs +++ b/src-tauri/src/database/dao/omo.rs @@ -55,38 +55,23 @@ impl Default for OmoGlobalConfig { } impl Database { - pub fn get_omo_global_config(&self) -> Result { - let json_str = self.get_setting("common_config_omo")?; + pub fn get_omo_global_config(&self, key: &str) -> Result { + let json_str = self.get_setting(key)?; match json_str { Some(s) => serde_json::from_str::(&s) - .map_err(|e| AppError::Config(format!("Failed to parse common_config_omo: {e}"))), + .map_err(|e| AppError::Config(format!("Failed to parse {key}: {e}"))), None => Ok(OmoGlobalConfig::default()), } } - pub fn save_omo_global_config(&self, config: &OmoGlobalConfig) -> Result<(), AppError> { + pub fn save_omo_global_config( + &self, + key: &str, + config: &OmoGlobalConfig, + ) -> Result<(), AppError> { let json_str = serde_json::to_string(config) .map_err(|e| AppError::Config(format!("JSON serialization failed: {e}")))?; - self.set_setting("common_config_omo", &json_str)?; - Ok(()) - } - - // ── OMO Slim global config ────────────────────────────────── - - pub fn get_omo_slim_global_config(&self) -> Result { - let json_str = self.get_setting("common_config_omo_slim")?; - match json_str { - Some(s) => serde_json::from_str::(&s).map_err(|e| { - AppError::Config(format!("Failed to parse common_config_omo_slim: {e}")) - }), - None => Ok(OmoGlobalConfig::default()), - } - } - - pub fn save_omo_slim_global_config(&self, config: &OmoGlobalConfig) -> Result<(), AppError> { - let json_str = serde_json::to_string(config) - .map_err(|e| AppError::Config(format!("JSON serialization failed: {e}")))?; - self.set_setting("common_config_omo_slim", &json_str)?; + self.set_setting(key, &json_str)?; Ok(()) } } diff --git a/src-tauri/src/database/dao/providers.rs b/src-tauri/src/database/dao/providers.rs index c514b3661..4f4259e95 100644 --- a/src-tauri/src/database/dao/providers.rs +++ b/src-tauri/src/database/dao/providers.rs @@ -364,25 +364,26 @@ impl Database { &self, app_type: &str, provider_id: &str, + category: &str, ) -> Result<(), AppError> { let mut conn = lock_conn!(self.conn); let tx = conn .transaction() .map_err(|e| AppError::Database(e.to_string()))?; tx.execute( - "UPDATE providers SET is_current = 0 WHERE app_type = ?1 AND category = 'omo'", - params![app_type], + "UPDATE providers SET is_current = 0 WHERE app_type = ?1 AND category = ?2", + params![app_type, category], ) .map_err(|e| AppError::Database(e.to_string()))?; let updated = tx .execute( - "UPDATE providers SET is_current = 1 WHERE id = ?1 AND app_type = ?2 AND category = 'omo'", - params![provider_id, app_type], - ) + "UPDATE providers SET is_current = 1 WHERE id = ?1 AND app_type = ?2 AND category = ?3", + params![provider_id, app_type, category], + ) .map_err(|e| AppError::Database(e.to_string()))?; if updated != 1 { return Err(AppError::Database(format!( - "Failed to set OMO provider current: provider '{provider_id}' not found in app '{app_type}'" + "Failed to set {category} provider current: provider '{provider_id}' not found in app '{app_type}'" ))); } tx.commit().map_err(|e| AppError::Database(e.to_string()))?; @@ -393,12 +394,13 @@ impl Database { &self, app_type: &str, provider_id: &str, + category: &str, ) -> Result { let conn = lock_conn!(self.conn); match conn.query_row( "SELECT is_current FROM providers - WHERE id = ?1 AND app_type = ?2 AND category = 'omo'", - params![provider_id, app_type], + WHERE id = ?1 AND app_type = ?2 AND category = ?3", + params![provider_id, app_type, category], |row| row.get(0), ) { Ok(is_current) => Ok(is_current), @@ -411,152 +413,30 @@ impl Database { &self, app_type: &str, provider_id: &str, + category: &str, ) -> Result<(), AppError> { let conn = lock_conn!(self.conn); conn.execute( "UPDATE providers SET is_current = 0 - WHERE id = ?1 AND app_type = ?2 AND category = 'omo'", - params![provider_id, app_type], + WHERE id = ?1 AND app_type = ?2 AND category = ?3", + params![provider_id, app_type, category], ) .map_err(|e| AppError::Database(e.to_string()))?; Ok(()) } - pub fn get_current_omo_provider(&self, app_type: &str) -> Result, AppError> { - let conn = lock_conn!(self.conn); - let row_data: Result = conn.query_row( - "SELECT id, name, settings_config, category, created_at, sort_index, notes, meta - FROM providers - WHERE app_type = ?1 AND category = 'omo' AND is_current = 1 - LIMIT 1", - params![app_type], - |row| { - Ok(( - row.get(0)?, - row.get(1)?, - row.get(2)?, - row.get(3)?, - row.get(4)?, - row.get(5)?, - row.get(6)?, - row.get(7)?, - )) - }, - ); - - let (id, name, settings_config_str, category, created_at, sort_index, notes, meta_str) = - match row_data { - Ok(v) => v, - Err(rusqlite::Error::QueryReturnedNoRows) => return Ok(None), - Err(e) => return Err(AppError::Database(e.to_string())), - }; - - let settings_config = serde_json::from_str(&settings_config_str).map_err(|e| { - AppError::Database(format!( - "Failed to parse OMO provider settings_config (provider_id={id}): {e}" - )) - })?; - let meta: crate::provider::ProviderMeta = if meta_str.trim().is_empty() { - crate::provider::ProviderMeta::default() - } else { - serde_json::from_str(&meta_str).map_err(|e| { - AppError::Database(format!( - "Failed to parse OMO provider meta (provider_id={id}): {e}" - )) - })? - }; - - Ok(Some(Provider { - id, - name, - settings_config, - website_url: None, - category, - created_at, - sort_index, - notes, - meta: Some(meta), - icon: None, - icon_color: None, - in_failover_queue: false, - })) - } - - // ── OMO Slim provider management ──────────────────────────── - - pub fn set_omo_slim_provider_current( - &self, - app_type: &str, - provider_id: &str, - ) -> Result<(), AppError> { - let mut conn = lock_conn!(self.conn); - let tx = conn - .transaction() - .map_err(|e| AppError::Database(e.to_string()))?; - tx.execute( - "UPDATE providers SET is_current = 0 WHERE app_type = ?1 AND category = 'omo-slim'", - params![app_type], - ) - .map_err(|e| AppError::Database(e.to_string()))?; - let updated = tx - .execute( - "UPDATE providers SET is_current = 1 WHERE id = ?1 AND app_type = ?2 AND category = 'omo-slim'", - params![provider_id, app_type], - ) - .map_err(|e| AppError::Database(e.to_string()))?; - if updated != 1 { - return Err(AppError::Database(format!( - "Failed to set OMO Slim provider current: provider '{provider_id}' not found in app '{app_type}'" - ))); - } - tx.commit().map_err(|e| AppError::Database(e.to_string()))?; - Ok(()) - } - - pub fn is_omo_slim_provider_current( - &self, - app_type: &str, - provider_id: &str, - ) -> Result { - let conn = lock_conn!(self.conn); - match conn.query_row( - "SELECT is_current FROM providers - WHERE id = ?1 AND app_type = ?2 AND category = 'omo-slim'", - params![provider_id, app_type], - |row| row.get(0), - ) { - Ok(is_current) => Ok(is_current), - Err(rusqlite::Error::QueryReturnedNoRows) => Ok(false), - Err(e) => Err(AppError::Database(e.to_string())), - } - } - - pub fn clear_omo_slim_provider_current( - &self, - app_type: &str, - provider_id: &str, - ) -> Result<(), AppError> { - let conn = lock_conn!(self.conn); - conn.execute( - "UPDATE providers SET is_current = 0 - WHERE id = ?1 AND app_type = ?2 AND category = 'omo-slim'", - params![provider_id, app_type], - ) - .map_err(|e| AppError::Database(e.to_string()))?; - Ok(()) - } - - pub fn get_current_omo_slim_provider( + pub fn get_current_omo_provider( &self, app_type: &str, + category: &str, ) -> Result, AppError> { let conn = lock_conn!(self.conn); let row_data: Result = conn.query_row( "SELECT id, name, settings_config, category, created_at, sort_index, notes, meta FROM providers - WHERE app_type = ?1 AND category = 'omo-slim' AND is_current = 1 + WHERE app_type = ?1 AND category = ?2 AND is_current = 1 LIMIT 1", - params![app_type], + params![app_type, category], |row| { Ok(( row.get(0)?, @@ -571,7 +451,7 @@ impl Database { }, ); - let (id, name, settings_config_str, category, created_at, sort_index, notes, meta_str) = + let (id, name, settings_config_str, _row_category, created_at, sort_index, notes, meta_str) = match row_data { Ok(v) => v, Err(rusqlite::Error::QueryReturnedNoRows) => return Ok(None), @@ -580,7 +460,7 @@ impl Database { let settings_config = serde_json::from_str(&settings_config_str).map_err(|e| { AppError::Database(format!( - "Failed to parse OMO Slim provider settings_config (provider_id={id}): {e}" + "Failed to parse {category} provider settings_config (provider_id={id}): {e}" )) })?; let meta: crate::provider::ProviderMeta = if meta_str.trim().is_empty() { @@ -588,7 +468,7 @@ impl Database { } else { serde_json::from_str(&meta_str).map_err(|e| { AppError::Database(format!( - "Failed to parse OMO Slim provider meta (provider_id={id}): {e}" + "Failed to parse {category} provider meta (provider_id={id}): {e}" )) })? }; @@ -598,7 +478,7 @@ impl Database { name, settings_config, website_url: None, - category, + category: Some(category.to_string()), created_at, sort_index, notes, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 11f0abecc..c82c879f6 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -512,7 +512,7 @@ pub fn run() { .map(|providers| providers.values().any(|p| p.category.as_deref() == Some("omo"))) .unwrap_or(false); if !has_omo { - match crate::services::OmoService::import_from_local(&app_state) { + match crate::services::OmoService::import_from_local(&app_state, &crate::services::omo::STANDARD) { Ok(provider) => { log::info!("✓ Imported OMO config from local as provider '{}'", provider.name); } @@ -538,7 +538,7 @@ pub fn run() { }) .unwrap_or(false); if !has_omo_slim { - match crate::services::OmoService::import_from_local_slim(&app_state) { + match crate::services::OmoService::import_from_local(&app_state, &crate::services::omo::SLIM) { Ok(provider) => { log::info!( "✓ Imported OMO Slim config from local as provider '{}'", diff --git a/src-tauri/src/services/omo.rs b/src-tauri/src/services/omo.rs index 3e963b3f9..b62ac26ff 100644 --- a/src-tauri/src/services/omo.rs +++ b/src-tauri/src/services/omo.rs @@ -20,15 +20,83 @@ pub struct OmoLocalFileData { type OmoProfileData = (Option, Option, Option, bool); +// ── Variant descriptor ───────────────────────────────────────── + +pub struct OmoVariant { + pub filename: &'static str, + pub category: &'static str, + pub provider_prefix: &'static str, + pub plugin_name: &'static str, + pub plugin_prefix: &'static str, + pub known_keys: &'static [&'static str], + pub has_categories: bool, + pub config_key: &'static str, + pub label: &'static str, + pub import_label: &'static str, +} + +pub const STANDARD: OmoVariant = OmoVariant { + filename: "oh-my-opencode.jsonc", + category: "omo", + provider_prefix: "omo-", + plugin_name: "oh-my-opencode@latest", + plugin_prefix: "oh-my-opencode", + known_keys: &[ + "$schema", + "agents", + "categories", + "sisyphus_agent", + "disabled_agents", + "disabled_mcps", + "disabled_hooks", + "disabled_skills", + "lsp", + "experimental", + "background_task", + "browser_automation_engine", + "claude_code", + ], + has_categories: true, + config_key: "common_config_omo", + label: "OMO", + import_label: "Imported", +}; + +pub const SLIM: OmoVariant = OmoVariant { + filename: "oh-my-opencode-slim.jsonc", + category: "omo-slim", + provider_prefix: "omo-slim-", + plugin_name: "oh-my-opencode-slim@latest", + plugin_prefix: "oh-my-opencode-slim", + known_keys: &[ + "$schema", + "agents", + "sisyphus_agent", + "disabled_agents", + "disabled_mcps", + "disabled_hooks", + "lsp", + "experimental", + ], + has_categories: false, + config_key: "common_config_omo_slim", + label: "OMO Slim", + import_label: "Imported Slim", +}; + +// ── Service ──────────────────────────────────────────────────── + pub struct OmoService; impl OmoService { - fn config_path() -> PathBuf { - get_opencode_dir().join("oh-my-opencode.jsonc") + // ── Path helpers ──────────────────────────────────────── + + fn config_path(v: &OmoVariant) -> PathBuf { + get_opencode_dir().join(v.filename) } - fn resolve_local_config_path() -> Result { - let config_path = Self::config_path(); + fn resolve_local_config_path(v: &OmoVariant) -> Result { + let config_path = Self::config_path(v); if config_path.exists() { return Ok(config_path); } @@ -52,40 +120,7 @@ impl OmoService { .ok_or_else(|| AppError::Config("Expected JSON object".to_string())) } - const KNOWN_KEYS: [&str; 13] = [ - "$schema", - "agents", - "categories", - "sisyphus_agent", - "disabled_agents", - "disabled_mcps", - "disabled_hooks", - "disabled_skills", - "lsp", - "experimental", - "background_task", - "browser_automation_engine", - "claude_code", - ]; - - const KNOWN_KEYS_SLIM: [&str; 8] = [ - "$schema", - "agents", - "sisyphus_agent", - "disabled_agents", - "disabled_mcps", - "disabled_hooks", - "lsp", - "experimental", - ]; - - fn extract_other_fields(obj: &Map) -> Map { - Self::extract_other_fields_with_keys(obj, &Self::KNOWN_KEYS) - } - - fn extract_other_fields_slim(obj: &Map) -> Map { - Self::extract_other_fields_with_keys(obj, &Self::KNOWN_KEYS_SLIM) - } + // ── Field extraction ─────────────────────────────────── fn extract_other_fields_with_keys( obj: &Map, @@ -141,6 +176,8 @@ impl OmoService { } } + // ── Merge helpers ────────────────────────────────────── + fn insert_opt_value(result: &mut Map, key: &str, value: &Option) { if let Some(v) = value { result.insert(key.to_string(), v.clone()); @@ -164,62 +201,40 @@ impl OmoService { } } - fn config_path_slim() -> PathBuf { - get_opencode_dir().join("oh-my-opencode-slim.jsonc") - } + // ── Public API (variant-parameterized) ───────────────── - fn resolve_local_config_path_slim() -> Result { - let config_path = Self::config_path_slim(); - if config_path.exists() { - return Ok(config_path); - } - - let json_path = config_path.with_extension("json"); - if json_path.exists() { - return Ok(json_path); - } - - Err(AppError::OmoConfigNotFound) - } - - pub fn delete_config_file() -> Result<(), AppError> { - let config_path = Self::config_path(); + pub fn delete_config_file(v: &OmoVariant) -> Result<(), AppError> { + let config_path = Self::config_path(v); if config_path.exists() { std::fs::remove_file(&config_path).map_err(|e| AppError::io(&config_path, e))?; - log::info!("OMO config file deleted: {config_path:?}"); + log::info!("{} config file deleted: {config_path:?}", v.label); } - crate::opencode_config::remove_plugin_by_prefix("oh-my-opencode")?; + crate::opencode_config::remove_plugin_by_prefix(v.plugin_prefix)?; Ok(()) } - pub fn delete_config_file_slim() -> Result<(), AppError> { - let config_path = Self::config_path_slim(); - if config_path.exists() { - std::fs::remove_file(&config_path).map_err(|e| AppError::io(&config_path, e))?; - log::info!("OMO Slim config file deleted: {config_path:?}"); - } - crate::opencode_config::remove_plugin_by_prefix("oh-my-opencode-slim")?; - Ok(()) - } - - pub fn write_config_to_file(state: &AppState) -> Result<(), AppError> { - let global = state.db.get_omo_global_config()?; - let current_omo = state.db.get_current_omo_provider("opencode")?; + pub fn write_config_to_file(state: &AppState, v: &OmoVariant) -> Result<(), AppError> { + let global = state.db.get_omo_global_config(v.config_key)?; + let current_omo = state.db.get_current_omo_provider("opencode", v.category)?; let profile_data = current_omo.as_ref().map(|p| { let agents = p.settings_config.get("agents").cloned(); - let categories = p.settings_config.get("categories").cloned(); + let categories = if v.has_categories { + p.settings_config.get("categories").cloned() + } else { + None + }; let other_fields = p.settings_config.get("otherFields").cloned(); let use_common_config = p .settings_config .get("useCommonConfig") - .and_then(|v| v.as_bool()) + .and_then(|val| val.as_bool()) .unwrap_or(true); (agents, categories, other_fields, use_common_config) }); - let merged = Self::merge_config(&global, profile_data.as_ref()); - let config_path = Self::config_path(); + let merged = Self::merge_config(v, &global, profile_data.as_ref()); + let config_path = Self::config_path(v); if let Some(parent) = config_path.parent() { std::fs::create_dir_all(parent).map_err(|e| AppError::io(parent, e))?; @@ -227,45 +242,19 @@ impl OmoService { write_json_file(&config_path, &merged)?; - crate::opencode_config::add_plugin("oh-my-opencode@latest")?; + crate::opencode_config::add_plugin(v.plugin_name)?; - log::info!("OMO config written to {config_path:?}"); + log::info!("{} config written to {config_path:?}", v.label); Ok(()) } - pub fn write_config_to_file_slim(state: &AppState) -> Result<(), AppError> { - let global = state.db.get_omo_slim_global_config()?; - let current_omo = state.db.get_current_omo_slim_provider("opencode")?; - - let profile_data = current_omo.as_ref().map(|p| { - let agents = p.settings_config.get("agents").cloned(); - let other_fields = p.settings_config.get("otherFields").cloned(); - let use_common_config = p - .settings_config - .get("useCommonConfig") - .and_then(|v| v.as_bool()) - .unwrap_or(true); - (agents, None, other_fields, use_common_config) - }); - - let merged = Self::merge_config_slim(&global, profile_data.as_ref()); - let config_path = Self::config_path_slim(); - - if let Some(parent) = config_path.parent() { - std::fs::create_dir_all(parent).map_err(|e| AppError::io(parent, e))?; - } - - write_json_file(&config_path, &merged)?; - - crate::opencode_config::add_plugin("oh-my-opencode-slim@latest")?; - - log::info!("OMO Slim config written to {config_path:?}"); - Ok(()) - } - - fn merge_config(global: &OmoGlobalConfig, profile_data: Option<&OmoProfileData>) -> Value { + fn merge_config( + v: &OmoVariant, + global: &OmoGlobalConfig, + profile_data: Option<&OmoProfileData>, + ) -> Value { let mut result = Map::new(); - let use_common_config = profile_data.map(|(_, _, _, v)| *v).unwrap_or(true); + let use_common_config = profile_data.map(|(_, _, _, uc)| *uc).unwrap_or(true); if use_common_config { if let Some(url) = &global.schema_url { @@ -276,141 +265,67 @@ impl OmoService { Self::insert_string_array(&mut result, "disabled_agents", &global.disabled_agents); Self::insert_string_array(&mut result, "disabled_mcps", &global.disabled_mcps); Self::insert_string_array(&mut result, "disabled_hooks", &global.disabled_hooks); - Self::insert_string_array(&mut result, "disabled_skills", &global.disabled_skills); + + if v.has_categories { + Self::insert_string_array(&mut result, "disabled_skills", &global.disabled_skills); + Self::insert_opt_value(&mut result, "background_task", &global.background_task); + Self::insert_opt_value( + &mut result, + "browser_automation_engine", + &global.browser_automation_engine, + ); + Self::insert_opt_value(&mut result, "claude_code", &global.claude_code); + } + Self::insert_opt_value(&mut result, "lsp", &global.lsp); Self::insert_opt_value(&mut result, "experimental", &global.experimental); - Self::insert_opt_value(&mut result, "background_task", &global.background_task); - Self::insert_opt_value( - &mut result, - "browser_automation_engine", - &global.browser_automation_engine, - ); - Self::insert_opt_value(&mut result, "claude_code", &global.claude_code); Self::insert_object_entries(&mut result, global.other_fields.as_ref()); } if let Some((agents, categories, other_fields, _)) = profile_data { Self::insert_opt_value(&mut result, "agents", agents); - Self::insert_opt_value(&mut result, "categories", categories); - Self::insert_object_entries(&mut result, other_fields.as_ref()); - } - - Value::Object(result) - } - - /// Merge config for Slim variant (no categories, no disabled_skills, no background_task, etc.) - fn merge_config_slim(global: &OmoGlobalConfig, profile_data: Option<&OmoProfileData>) -> Value { - let mut result = Map::new(); - let use_common_config = profile_data.map(|(_, _, _, v)| *v).unwrap_or(true); - - if use_common_config { - if let Some(url) = &global.schema_url { - result.insert("$schema".to_string(), Value::String(url.clone())); + if v.has_categories { + Self::insert_opt_value(&mut result, "categories", categories); } - - Self::insert_opt_value(&mut result, "sisyphus_agent", &global.sisyphus_agent); - Self::insert_string_array(&mut result, "disabled_agents", &global.disabled_agents); - Self::insert_string_array(&mut result, "disabled_mcps", &global.disabled_mcps); - Self::insert_string_array(&mut result, "disabled_hooks", &global.disabled_hooks); - // Slim does NOT support: disabled_skills, background_task, browser_automation_engine, claude_code - Self::insert_opt_value(&mut result, "lsp", &global.lsp); - Self::insert_opt_value(&mut result, "experimental", &global.experimental); - - Self::insert_object_entries(&mut result, global.other_fields.as_ref()); - } - - if let Some((agents, _categories, other_fields, _)) = profile_data { - Self::insert_opt_value(&mut result, "agents", agents); - // Slim does NOT have categories Self::insert_object_entries(&mut result, other_fields.as_ref()); } Value::Object(result) } - pub fn import_from_local(state: &AppState) -> Result { - let actual_path = Self::resolve_local_config_path()?; - Self::import_from_path(state, &actual_path) - } - - fn import_from_path( + pub fn import_from_local( state: &AppState, - path: &std::path::Path, + v: &OmoVariant, ) -> Result { - let obj = Self::read_jsonc_object(path)?; - - let mut settings = Map::new(); - if let Some(agents) = obj.get("agents") { - settings.insert("agents".to_string(), agents.clone()); - } - if let Some(categories) = obj.get("categories") { - settings.insert("categories".to_string(), categories.clone()); - } - settings.insert("useCommonConfig".to_string(), Value::Bool(true)); - - let other = Self::extract_other_fields(&obj); - if !other.is_empty() { - settings.insert("otherFields".to_string(), Value::Object(other)); - } - - let mut global = state.db.get_omo_global_config()?; - Self::merge_global_from_obj(&obj, &mut global); - global.updated_at = chrono::Utc::now().to_rfc3339(); - state.db.save_omo_global_config(&global)?; - - let provider_id = format!("omo-{}", uuid::Uuid::new_v4()); - let name = format!("Imported {}", chrono::Local::now().format("%Y-%m-%d %H:%M")); - let settings_config = - serde_json::to_value(&settings).unwrap_or_else(|_| serde_json::json!({})); - - let provider = crate::provider::Provider { - id: provider_id, - name, - settings_config, - website_url: None, - category: Some("omo".to_string()), - created_at: Some(chrono::Utc::now().timestamp_millis()), - sort_index: None, - notes: None, - meta: None, - icon: None, - icon_color: None, - in_failover_queue: false, - }; - - state.db.save_provider("opencode", &provider)?; - state - .db - .set_omo_provider_current("opencode", &provider.id)?; - Self::write_config_to_file(state)?; - Ok(provider) - } - - pub fn import_from_local_slim(state: &AppState) -> Result { - let actual_path = Self::resolve_local_config_path_slim()?; + let actual_path = Self::resolve_local_config_path(v)?; let obj = Self::read_jsonc_object(&actual_path)?; let mut settings = Map::new(); if let Some(agents) = obj.get("agents") { settings.insert("agents".to_string(), agents.clone()); } - // Slim has no categories + if v.has_categories { + if let Some(categories) = obj.get("categories") { + settings.insert("categories".to_string(), categories.clone()); + } + } settings.insert("useCommonConfig".to_string(), Value::Bool(true)); - let other = Self::extract_other_fields_slim(&obj); + let other = Self::extract_other_fields_with_keys(&obj, v.known_keys); if !other.is_empty() { settings.insert("otherFields".to_string(), Value::Object(other)); } - let mut global = state.db.get_omo_slim_global_config()?; + let mut global = state.db.get_omo_global_config(v.config_key)?; Self::merge_global_from_obj(&obj, &mut global); global.updated_at = chrono::Utc::now().to_rfc3339(); - state.db.save_omo_slim_global_config(&global)?; + state.db.save_omo_global_config(v.config_key, &global)?; - let provider_id = format!("omo-slim-{}", uuid::Uuid::new_v4()); + let provider_id = format!("{}{}", v.provider_prefix, uuid::Uuid::new_v4()); let name = format!( - "Imported Slim {}", + "{} {}", + v.import_label, chrono::Local::now().format("%Y-%m-%d %H:%M") ); let settings_config = @@ -421,7 +336,7 @@ impl OmoService { name, settings_config, website_url: None, - category: Some("omo-slim".to_string()), + category: Some(v.category.to_string()), created_at: Some(chrono::Utc::now().timestamp_millis()), sort_index: None, notes: None, @@ -434,13 +349,13 @@ impl OmoService { state.db.save_provider("opencode", &provider)?; state .db - .set_omo_slim_provider_current("opencode", &provider.id)?; - Self::write_config_to_file_slim(state)?; + .set_omo_provider_current("opencode", &provider.id, v.category)?; + Self::write_config_to_file(state, v)?; Ok(provider) } - pub fn read_local_file() -> Result { - let actual_path = Self::resolve_local_config_path()?; + pub fn read_local_file(v: &OmoVariant) -> Result { + let actual_path = Self::resolve_local_config_path(v)?; let metadata = std::fs::metadata(&actual_path).ok(); let last_modified = metadata .and_then(|m| m.modified().ok()) @@ -448,38 +363,28 @@ impl OmoService { let obj = Self::read_jsonc_object(&actual_path)?; - Ok(Self::build_local_file_data_from_obj( + Ok(Self::build_local_file_data( + v, &obj, actual_path.to_string_lossy().to_string(), last_modified, )) } - pub fn read_local_file_slim() -> Result { - let actual_path = Self::resolve_local_config_path_slim()?; - let metadata = std::fs::metadata(&actual_path).ok(); - let last_modified = metadata - .and_then(|m| m.modified().ok()) - .map(|t| chrono::DateTime::::from(t).to_rfc3339()); - - let obj = Self::read_jsonc_object(&actual_path)?; - - Ok(Self::build_local_file_data_slim_from_obj( - &obj, - actual_path.to_string_lossy().to_string(), - last_modified, - )) - } - - fn build_local_file_data_from_obj( + fn build_local_file_data( + v: &OmoVariant, obj: &Map, file_path: String, last_modified: Option, ) -> OmoLocalFileData { let agents = obj.get("agents").cloned(); - let categories = obj.get("categories").cloned(); + let categories = if v.has_categories { + obj.get("categories").cloned() + } else { + None + }; - let other = Self::extract_other_fields(obj); + let other = Self::extract_other_fields_with_keys(obj, v.known_keys); let other_fields = if other.is_empty() { None } else { @@ -500,35 +405,6 @@ impl OmoService { } } - fn build_local_file_data_slim_from_obj( - obj: &Map, - file_path: String, - last_modified: Option, - ) -> OmoLocalFileData { - let agents = obj.get("agents").cloned(); - // Slim has no categories - - let other = Self::extract_other_fields_slim(obj); - let other_fields = if other.is_empty() { - None - } else { - Some(Value::Object(other)) - }; - - let mut global = OmoGlobalConfig::default(); - Self::merge_global_from_obj(obj, &mut global); - global.other_fields = other_fields.clone(); - - OmoLocalFileData { - agents, - categories: None, - other_fields, - global, - file_path, - last_modified, - } - } - fn strip_jsonc_comments(input: &str) -> String { let mut result = String::with_capacity(input.len()); let mut chars = input.chars().peekable(); @@ -608,7 +484,7 @@ mod tests { #[test] fn test_merge_config_empty() { let global = OmoGlobalConfig::default(); - let merged = OmoService::merge_config(&global, None); + let merged = OmoService::merge_config(&STANDARD, &global, None); assert!(merged.is_object()); } @@ -625,7 +501,7 @@ mod tests { let categories = None; let other_fields = None; let profile_data = (agents, categories, other_fields, true); - let merged = OmoService::merge_config(&global, Some(&profile_data)); + let merged = OmoService::merge_config(&STANDARD, &global, Some(&profile_data)); let obj = merged.as_object().unwrap(); assert_eq!(obj["$schema"], "https://example.com/schema.json"); @@ -647,7 +523,7 @@ mod tests { let categories = None; let other_fields = None; let profile_data = (agents, categories, other_fields, false); - let merged = OmoService::merge_config(&global, Some(&profile_data)); + let merged = OmoService::merge_config(&STANDARD, &global, Some(&profile_data)); let obj = merged.as_object().unwrap(); assert!(!obj.contains_key("$schema")); @@ -672,7 +548,8 @@ mod tests { }); let obj_map = obj.as_object().unwrap().clone(); - let data = OmoService::build_local_file_data_from_obj( + let data = OmoService::build_local_file_data( + &STANDARD, &obj_map, "/tmp/oh-my-opencode.jsonc".to_string(), None, @@ -704,11 +581,43 @@ mod tests { let other_fields = Some(serde_json::json!("profile_non_object")); let profile_data = (agents, categories, other_fields, true); - let merged = OmoService::merge_config(&global, Some(&profile_data)); + let merged = OmoService::merge_config(&STANDARD, &global, Some(&profile_data)); let obj = merged.as_object().unwrap(); assert!(!obj.contains_key("0")); assert!(!obj.contains_key("global_non_object")); assert!(!obj.contains_key("profile_non_object")); } + + #[test] + fn test_merge_config_slim_excludes_categories_and_extra_fields() { + let global = OmoGlobalConfig { + schema_url: Some("https://slim.schema".to_string()), + disabled_agents: vec!["oracle".to_string()], + disabled_skills: vec!["playwright".to_string()], + background_task: Some(serde_json::json!({"key": "val"})), + browser_automation_engine: Some(serde_json::json!({"provider": "pw"})), + claude_code: Some(serde_json::json!({"mcp": true})), + ..Default::default() + }; + let agents = Some(serde_json::json!({"orchestrator": {"model": "k2"}})); + let categories = Some(serde_json::json!({"code": {"model": "gpt"}})); + let other_fields = None; + let profile_data = (agents, categories, other_fields, true); + + let merged = OmoService::merge_config(&SLIM, &global, Some(&profile_data)); + let obj = merged.as_object().unwrap(); + + // Slim should NOT include these + assert!(!obj.contains_key("disabled_skills")); + assert!(!obj.contains_key("background_task")); + assert!(!obj.contains_key("browser_automation_engine")); + assert!(!obj.contains_key("claude_code")); + assert!(!obj.contains_key("categories")); + + // Slim SHOULD include these + assert_eq!(obj["$schema"], "https://slim.schema"); + assert!(obj.contains_key("agents")); + assert!(obj.contains_key("disabled_agents")); + } } diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs index 3327428d2..a7324066c 100644 --- a/src-tauri/src/services/provider/mod.rs +++ b/src-tauri/src/services/provider/mod.rs @@ -208,22 +208,31 @@ impl ProviderService { if app_type.is_additive_mode() { if matches!(app_type, AppType::OpenCode) && provider.category.as_deref() == Some("omo") { - let is_omo_current = state - .db - .is_omo_provider_current(app_type.as_str(), &provider.id)?; + let is_omo_current = + state + .db + .is_omo_provider_current(app_type.as_str(), &provider.id, "omo")?; if is_omo_current { - crate::services::OmoService::write_config_to_file(state)?; + crate::services::OmoService::write_config_to_file( + state, + &crate::services::omo::STANDARD, + )?; } return Ok(true); } if matches!(app_type, AppType::OpenCode) && provider.category.as_deref() == Some("omo-slim") { - let is_current = state - .db - .is_omo_slim_provider_current(app_type.as_str(), &provider.id)?; + let is_current = state.db.is_omo_provider_current( + app_type.as_str(), + &provider.id, + "omo-slim", + )?; if is_current { - crate::services::OmoService::write_config_to_file_slim(state)?; + crate::services::OmoService::write_config_to_file( + state, + &crate::services::omo::SLIM, + )?; } return Ok(true); } @@ -279,7 +288,10 @@ impl ProviderService { .and_then(|p| p.category); if provider_category.as_deref() == Some("omo") { - let was_current = state.db.is_omo_provider_current(app_type.as_str(), id)?; + let was_current = + state + .db + .is_omo_provider_current(app_type.as_str(), id, "omo")?; let omo_count = state .db .get_all_providers(app_type.as_str())? @@ -295,15 +307,18 @@ impl ProviderService { state.db.delete_provider(app_type.as_str(), id)?; if was_current { - crate::services::OmoService::delete_config_file()?; + crate::services::OmoService::delete_config_file( + &crate::services::omo::STANDARD, + )?; } return Ok(()); } if provider_category.as_deref() == Some("omo-slim") { - let was_current = state - .db - .is_omo_slim_provider_current(app_type.as_str(), id)?; + let was_current = + state + .db + .is_omo_provider_current(app_type.as_str(), id, "omo-slim")?; let slim_count = state .db .get_all_providers(app_type.as_str())? @@ -319,7 +334,9 @@ impl ProviderService { state.db.delete_provider(app_type.as_str(), id)?; if was_current { - crate::services::OmoService::delete_config_file_slim()?; + crate::services::OmoService::delete_config_file( + &crate::services::omo::SLIM, + )?; } return Ok(()); } @@ -366,26 +383,40 @@ impl ProviderService { .and_then(|p| p.category); if provider_category.as_deref() == Some("omo") { - state.db.clear_omo_provider_current(app_type.as_str(), id)?; - let still_has_current = - state.db.get_current_omo_provider("opencode")?.is_some(); + state + .db + .clear_omo_provider_current(app_type.as_str(), id, "omo")?; + let still_has_current = state + .db + .get_current_omo_provider("opencode", "omo")? + .is_some(); if still_has_current { - crate::services::OmoService::write_config_to_file(state)?; + crate::services::OmoService::write_config_to_file( + state, + &crate::services::omo::STANDARD, + )?; } else { - crate::services::OmoService::delete_config_file()?; + crate::services::OmoService::delete_config_file( + &crate::services::omo::STANDARD, + )?; } } else if provider_category.as_deref() == Some("omo-slim") { state .db - .clear_omo_slim_provider_current(app_type.as_str(), id)?; + .clear_omo_provider_current(app_type.as_str(), id, "omo-slim")?; let still_has_current = state .db - .get_current_omo_slim_provider("opencode")? + .get_current_omo_provider("opencode", "omo-slim")? .is_some(); if still_has_current { - crate::services::OmoService::write_config_to_file_slim(state)?; + crate::services::OmoService::write_config_to_file( + state, + &crate::services::omo::SLIM, + )?; } else { - crate::services::OmoService::delete_config_file_slim()?; + crate::services::OmoService::delete_config_file( + &crate::services::omo::SLIM, + )?; } } else { remove_opencode_provider_from_live(id)?; @@ -507,8 +538,13 @@ impl ProviderService { .ok_or_else(|| AppError::Message(format!("供应商 {id} 不存在")))?; if matches!(app_type, AppType::OpenCode) && provider.category.as_deref() == Some("omo") { - state.db.set_omo_provider_current(app_type.as_str(), id)?; - crate::services::OmoService::write_config_to_file(state)?; + state + .db + .set_omo_provider_current(app_type.as_str(), id, "omo")?; + crate::services::OmoService::write_config_to_file( + state, + &crate::services::omo::STANDARD, + )?; return Ok(()); } @@ -516,8 +552,8 @@ impl ProviderService { { state .db - .set_omo_slim_provider_current(app_type.as_str(), id)?; - crate::services::OmoService::write_config_to_file_slim(state)?; + .set_omo_provider_current(app_type.as_str(), id, "omo-slim")?; + crate::services::OmoService::write_config_to_file(state, &crate::services::omo::SLIM)?; return Ok(()); } diff --git a/src/components/providers/forms/helpers/opencodeFormUtils.ts b/src/components/providers/forms/helpers/opencodeFormUtils.ts index a87ae69a0..c9d855898 100644 --- a/src/components/providers/forms/helpers/opencodeFormUtils.ts +++ b/src/components/providers/forms/helpers/opencodeFormUtils.ts @@ -1,6 +1,5 @@ import type { OpenCodeModel, OpenCodeProviderConfig } from "@/types"; import type { OmoGlobalConfig } from "@/types/omo"; -import { parseOmoOtherFieldsObject } from "@/types/omo"; import type { PricingModelSourceOption } from "../ProviderAdvancedConfig"; // ── Default configs ────────────────────────────────────────────────── @@ -132,28 +131,7 @@ export function toOpencodeExtraOptions( return extra; } -export function buildOmoProfilePreview( - agents: Record>, - categories: Record>, - otherFieldsStr: string, -): Record { - const profileOnly: Record = {}; - if (Object.keys(agents).length > 0) { - profileOnly.agents = agents; - } - if (Object.keys(categories).length > 0) { - profileOnly.categories = categories; - } - if (otherFieldsStr.trim()) { - try { - const other = parseOmoOtherFieldsObject(otherFieldsStr); - if (other) { - Object.assign(profileOnly, other); - } - } catch {} - } - return profileOnly; -} +export { buildOmoProfilePreview } from "@/types/omo"; export const normalizePricingSource = ( value?: string, diff --git a/src/lib/query/omo.ts b/src/lib/query/omo.ts index eeee26285..d85bb48d8 100644 --- a/src/lib/query/omo.ts +++ b/src/lib/query/omo.ts @@ -3,192 +3,148 @@ import { omoApi, omoSlimApi } from "@/lib/api/omo"; import * as configApi from "@/lib/api/config"; import type { OmoGlobalConfig } from "@/types/omo"; -export const omoKeys = { - all: ["omo"] as const, - globalConfig: () => [...omoKeys.all, "global-config"] as const, - currentProviderId: () => [...omoKeys.all, "current-provider-id"] as const, - providerCount: () => [...omoKeys.all, "provider-count"] as const, -}; +// ── Factory ──────────────────────────────────────────────────── -function invalidateOmoQueries(queryClient: ReturnType) { - queryClient.invalidateQueries({ queryKey: omoKeys.globalConfig() }); - queryClient.invalidateQueries({ queryKey: ["providers"] }); - queryClient.invalidateQueries({ queryKey: omoKeys.currentProviderId() }); - queryClient.invalidateQueries({ queryKey: omoKeys.providerCount() }); +function createOmoQueryKeys(prefix: string) { + return { + all: [prefix] as const, + globalConfig: () => [prefix, "global-config"] as const, + currentProviderId: () => [prefix, "current-provider-id"] as const, + providerCount: () => [prefix, "provider-count"] as const, + }; } -export function useOmoGlobalConfig(enabled = true) { - return useQuery({ - queryKey: omoKeys.globalConfig(), - enabled, - queryFn: async (): Promise => { - const raw = await configApi.getCommonConfigSnippet("omo"); - if (!raw) { - return { - id: "global", - disabledAgents: [], - disabledMcps: [], - disabledHooks: [], - disabledSkills: [], - updatedAt: new Date().toISOString(), - }; - } - try { - return JSON.parse(raw) as OmoGlobalConfig; - } catch (error) { - console.warn( - "[omo] invalid global config json, fallback to defaults", - error, - ); - return { - id: "global", - disabledAgents: [], - disabledMcps: [], - disabledHooks: [], - disabledSkills: [], - updatedAt: new Date().toISOString(), - }; - } - }, - }); -} - -export function useCurrentOmoProviderId(enabled = true) { - return useQuery({ - queryKey: omoKeys.currentProviderId(), - queryFn: omoApi.getCurrentOmoProviderId, - enabled, - }); -} - -export function useOmoProviderCount(enabled = true) { - return useQuery({ - queryKey: omoKeys.providerCount(), - queryFn: omoApi.getOmoProviderCount, - enabled, - }); -} - -export function useSaveOmoGlobalConfig() { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: async (input: OmoGlobalConfig) => { - const jsonStr = JSON.stringify(input); - await configApi.setCommonConfigSnippet("omo", jsonStr); - }, - onSuccess: () => invalidateOmoQueries(queryClient), - }); -} - -export function useReadOmoLocalFile() { - return useMutation({ - mutationFn: () => omoApi.readLocalFile(), - }); -} - -export function useDisableCurrentOmo() { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: () => omoApi.disableCurrentOmo(), - onSuccess: () => invalidateOmoQueries(queryClient), - }); -} - -// ============================================================================ -// OMO Slim query hooks -// ============================================================================ - -export const omoSlimKeys = { - all: ["omo-slim"] as const, - globalConfig: () => [...omoSlimKeys.all, "global-config"] as const, - currentProviderId: () => [...omoSlimKeys.all, "current-provider-id"] as const, - providerCount: () => [...omoSlimKeys.all, "provider-count"] as const, -}; - -function invalidateOmoSlimQueries( - queryClient: ReturnType, +function createOmoQueryHooks( + variant: "omo" | "omo-slim", + api: typeof omoApi | typeof omoSlimApi, ) { - queryClient.invalidateQueries({ queryKey: omoSlimKeys.globalConfig() }); - queryClient.invalidateQueries({ queryKey: ["providers"] }); - queryClient.invalidateQueries({ - queryKey: omoSlimKeys.currentProviderId(), - }); - queryClient.invalidateQueries({ queryKey: omoSlimKeys.providerCount() }); + const keys = createOmoQueryKeys(variant); + const snippetKey = variant === "omo" ? "omo" : "omo_slim"; + + function invalidateAll(queryClient: ReturnType) { + queryClient.invalidateQueries({ queryKey: keys.globalConfig() }); + queryClient.invalidateQueries({ queryKey: ["providers"] }); + queryClient.invalidateQueries({ queryKey: keys.currentProviderId() }); + queryClient.invalidateQueries({ queryKey: keys.providerCount() }); + } + + function useGlobalConfig(enabled = true) { + return useQuery({ + queryKey: keys.globalConfig(), + enabled, + queryFn: async (): Promise => { + const raw = await configApi.getCommonConfigSnippet(snippetKey); + if (!raw) { + return { + id: "global", + disabledAgents: [], + disabledMcps: [], + disabledHooks: [], + disabledSkills: [], + updatedAt: new Date().toISOString(), + }; + } + try { + return JSON.parse(raw) as OmoGlobalConfig; + } catch (error) { + console.warn( + `[${variant}] invalid global config json, fallback to defaults`, + error, + ); + return { + id: "global", + disabledAgents: [], + disabledMcps: [], + disabledHooks: [], + disabledSkills: [], + updatedAt: new Date().toISOString(), + }; + } + }, + }); + } + + function useCurrentProviderId(enabled = true) { + return useQuery({ + queryKey: keys.currentProviderId(), + queryFn: + "getCurrentOmoProviderId" in api + ? (api as typeof omoApi).getCurrentOmoProviderId + : (api as typeof omoSlimApi).getCurrentProviderId, + enabled, + }); + } + + function useProviderCount(enabled = true) { + return useQuery({ + queryKey: keys.providerCount(), + queryFn: + "getOmoProviderCount" in api + ? (api as typeof omoApi).getOmoProviderCount + : (api as typeof omoSlimApi).getProviderCount, + enabled, + }); + } + + function useSaveGlobalConfig() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (input: OmoGlobalConfig) => { + const jsonStr = JSON.stringify(input); + await configApi.setCommonConfigSnippet(snippetKey, jsonStr); + }, + onSuccess: () => invalidateAll(queryClient), + }); + } + + function useReadLocalFile() { + return useMutation({ + mutationFn: () => api.readLocalFile(), + }); + } + + function useDisableCurrent() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: + "disableCurrentOmo" in api + ? (api as typeof omoApi).disableCurrentOmo + : (api as typeof omoSlimApi).disableCurrent, + onSuccess: () => invalidateAll(queryClient), + }); + } + + return { + keys, + useGlobalConfig, + useCurrentProviderId, + useProviderCount, + useSaveGlobalConfig, + useReadLocalFile, + useDisableCurrent, + }; } -export function useOmoSlimGlobalConfig(enabled = true) { - return useQuery({ - queryKey: omoSlimKeys.globalConfig(), - enabled, - queryFn: async (): Promise => { - const raw = await configApi.getCommonConfigSnippet("omo_slim"); - if (!raw) { - return { - id: "global", - disabledAgents: [], - disabledMcps: [], - disabledHooks: [], - disabledSkills: [], - updatedAt: new Date().toISOString(), - }; - } - try { - return JSON.parse(raw) as OmoGlobalConfig; - } catch (error) { - console.warn( - "[omo-slim] invalid global config json, fallback to defaults", - error, - ); - return { - id: "global", - disabledAgents: [], - disabledMcps: [], - disabledHooks: [], - disabledSkills: [], - updatedAt: new Date().toISOString(), - }; - } - }, - }); -} +// ── Instances ────────────────────────────────────────────────── -export function useCurrentOmoSlimProviderId(enabled = true) { - return useQuery({ - queryKey: omoSlimKeys.currentProviderId(), - queryFn: omoSlimApi.getCurrentProviderId, - enabled, - }); -} +const omoHooks = createOmoQueryHooks("omo", omoApi); +const omoSlimHooks = createOmoQueryHooks("omo-slim", omoSlimApi); -export function useOmoSlimProviderCount(enabled = true) { - return useQuery({ - queryKey: omoSlimKeys.providerCount(), - queryFn: omoSlimApi.getProviderCount, - enabled, - }); -} +// ── Backward-compatible exports ──────────────────────────────── -export function useSaveOmoSlimGlobalConfig() { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: async (input: OmoGlobalConfig) => { - const jsonStr = JSON.stringify(input); - await configApi.setCommonConfigSnippet("omo_slim", jsonStr); - }, - onSuccess: () => invalidateOmoSlimQueries(queryClient), - }); -} +export const omoKeys = omoHooks.keys; +export const omoSlimKeys = omoSlimHooks.keys; -export function useReadOmoSlimLocalFile() { - return useMutation({ - mutationFn: () => omoSlimApi.readLocalFile(), - }); -} +export const useOmoGlobalConfig = omoHooks.useGlobalConfig; +export const useCurrentOmoProviderId = omoHooks.useCurrentProviderId; +export const useOmoProviderCount = omoHooks.useProviderCount; +export const useSaveOmoGlobalConfig = omoHooks.useSaveGlobalConfig; +export const useReadOmoLocalFile = omoHooks.useReadLocalFile; +export const useDisableCurrentOmo = omoHooks.useDisableCurrent; -export function useDisableCurrentOmoSlim() { - const queryClient = useQueryClient(); - return useMutation({ - mutationFn: () => omoSlimApi.disableCurrent(), - onSuccess: () => invalidateOmoSlimQueries(queryClient), - }); -} +export const useOmoSlimGlobalConfig = omoSlimHooks.useGlobalConfig; +export const useCurrentOmoSlimProviderId = omoSlimHooks.useCurrentProviderId; +export const useOmoSlimProviderCount = omoSlimHooks.useProviderCount; +export const useSaveOmoSlimGlobalConfig = omoSlimHooks.useSaveGlobalConfig; +export const useReadOmoSlimLocalFile = omoSlimHooks.useReadLocalFile; +export const useDisableCurrentOmoSlim = omoSlimHooks.useDisableCurrent; diff --git a/src/types/omo.ts b/src/types/omo.ts index da6e23c61..fa5515112 100644 --- a/src/types/omo.ts +++ b/src/types/omo.ts @@ -406,15 +406,22 @@ export const OMO_SLIM_DISABLEABLE_HOOKS = [ export const OMO_SLIM_DEFAULT_SCHEMA_URL = "https://raw.githubusercontent.com/alvinunreal/oh-my-opencode-slim/master/assets/oh-my-opencode-slim.schema.json"; -export function mergeOmoSlimConfigPreview( +export function mergeOmoConfigPreview( global: OmoGlobalConfig | undefined, agents: Record>, + categories: Record> | undefined, otherFieldsStr: string, + options?: { slim?: boolean }, ): Record { const result: Record = {}; + const isSlim = options?.slim ?? false; if (global) { if (global.schemaUrl) result["$schema"] = global.schemaUrl; + + if (!isSlim) { + if (global.sisyphusAgent) result["sisyphus_agent"] = global.sisyphusAgent; + } if (global.disabledAgents?.length) result["disabled_agents"] = global.disabledAgents; if (global.disabledMcps?.length) @@ -422,6 +429,18 @@ export function mergeOmoSlimConfigPreview( if (global.disabledHooks?.length) result["disabled_hooks"] = global.disabledHooks; + if (!isSlim) { + if (global.disabledSkills?.length) + result["disabled_skills"] = global.disabledSkills; + if (global.lsp) result["lsp"] = global.lsp; + if (global.experimental) result["experimental"] = global.experimental; + if (global.backgroundTask) + result["background_task"] = global.backgroundTask; + if (global.browserAutomationEngine) + result["browser_automation_engine"] = global.browserAutomationEngine; + if (global.claudeCode) result["claude_code"] = global.claudeCode; + } + if (global.otherFields) { for (const [k, v] of Object.entries(global.otherFields)) { result[k] = v; @@ -430,6 +449,8 @@ export function mergeOmoSlimConfigPreview( } if (Object.keys(agents).length > 0) result["agents"] = agents; + if (!isSlim && categories && Object.keys(categories).length > 0) + result["categories"] = categories; try { const other = parseOmoOtherFieldsObject(otherFieldsStr); @@ -443,67 +464,48 @@ export function mergeOmoSlimConfigPreview( return result; } +/** @deprecated Use mergeOmoConfigPreview with options.slim=true */ +export function mergeOmoSlimConfigPreview( + global: OmoGlobalConfig | undefined, + agents: Record>, + otherFieldsStr: string, +): Record { + return mergeOmoConfigPreview(global, agents, undefined, otherFieldsStr, { + slim: true, + }); +} + +export function buildOmoProfilePreview( + agents: Record>, + categories: Record> | undefined, + otherFieldsStr: string, + options?: { slim?: boolean }, +): Record { + const result: Record = {}; + const isSlim = options?.slim ?? false; + + if (Object.keys(agents).length > 0) result["agents"] = agents; + if (!isSlim && categories && Object.keys(categories).length > 0) + result["categories"] = categories; + + try { + const other = parseOmoOtherFieldsObject(otherFieldsStr); + if (other) { + for (const [k, v] of Object.entries(other)) { + result[k] = v; + } + } + } catch {} + + return result; +} + +/** @deprecated Use buildOmoProfilePreview with options.slim=true */ export function buildOmoSlimProfilePreview( agents: Record>, otherFieldsStr: string, ): Record { - const result: Record = {}; - - if (Object.keys(agents).length > 0) result["agents"] = agents; - - try { - const other = parseOmoOtherFieldsObject(otherFieldsStr); - if (other) { - for (const [k, v] of Object.entries(other)) { - result[k] = v; - } - } - } catch {} - - return result; -} - -export function mergeOmoConfigPreview( - global: OmoGlobalConfig, - agents: Record>, - categories: Record>, - otherFieldsStr: string, -): Record { - const result: Record = {}; - - if (global.schemaUrl) result["$schema"] = global.schemaUrl; - - if (global.sisyphusAgent) result["sisyphus_agent"] = global.sisyphusAgent; - if (global.disabledAgents?.length) - result["disabled_agents"] = global.disabledAgents; - if (global.disabledMcps?.length) - result["disabled_mcps"] = global.disabledMcps; - if (global.disabledHooks?.length) - result["disabled_hooks"] = global.disabledHooks; - if (global.disabledSkills?.length) - result["disabled_skills"] = global.disabledSkills; - if (global.lsp) result["lsp"] = global.lsp; - if (global.experimental) result["experimental"] = global.experimental; - if (global.backgroundTask) result["background_task"] = global.backgroundTask; - if (global.browserAutomationEngine) - result["browser_automation_engine"] = global.browserAutomationEngine; - if (global.claudeCode) result["claude_code"] = global.claudeCode; - - if (global.otherFields) { - for (const [k, v] of Object.entries(global.otherFields)) { - result[k] = v; - } - } - - if (Object.keys(agents).length > 0) result["agents"] = agents; - if (Object.keys(categories).length > 0) result["categories"] = categories; - try { - const other = parseOmoOtherFieldsObject(otherFieldsStr); - if (!other) return result; - for (const [k, v] of Object.entries(other)) { - result[k] = v; - } - } catch {} - - return result; + return buildOmoProfilePreview(agents, undefined, otherFieldsStr, { + slim: true, + }); }