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.
This commit is contained in:
Jason
2026-02-19 21:11:58 +08:00
parent 8e219b5eb1
commit 1b71dc721c
10 changed files with 493 additions and 741 deletions
+12 -6
View File
@@ -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(())
}
+9 -9
View File
@@ -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<OmoLocalFileData, String> {
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<String, String> {
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<usize,
#[tauri::command]
pub async fn read_omo_slim_local_file() -> Result<OmoLocalFileData, String> {
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<String, String> {
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(())
}
+9 -24
View File
@@ -55,38 +55,23 @@ impl Default for OmoGlobalConfig {
}
impl Database {
pub fn get_omo_global_config(&self) -> Result<OmoGlobalConfig, AppError> {
let json_str = self.get_setting("common_config_omo")?;
pub fn get_omo_global_config(&self, key: &str) -> Result<OmoGlobalConfig, AppError> {
let json_str = self.get_setting(key)?;
match json_str {
Some(s) => serde_json::from_str::<OmoGlobalConfig>(&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<OmoGlobalConfig, AppError> {
let json_str = self.get_setting("common_config_omo_slim")?;
match json_str {
Some(s) => serde_json::from_str::<OmoGlobalConfig>(&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(())
}
}
+21 -141
View File
@@ -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<bool, AppError> {
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<Option<Provider>, AppError> {
let conn = lock_conn!(self.conn);
let row_data: Result<OmoProviderRow, rusqlite::Error> = 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<bool, AppError> {
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<Option<Provider>, AppError> {
let conn = lock_conn!(self.conn);
let row_data: Result<OmoProviderRow, rusqlite::Error> = 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,
+2 -2
View File
@@ -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 '{}'",
+180 -271
View File
@@ -20,15 +20,83 @@ pub struct OmoLocalFileData {
type OmoProfileData = (Option<Value>, Option<Value>, Option<Value>, 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<PathBuf, AppError> {
let config_path = Self::config_path();
fn resolve_local_config_path(v: &OmoVariant) -> Result<PathBuf, AppError> {
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<String, Value>) -> Map<String, Value> {
Self::extract_other_fields_with_keys(obj, &Self::KNOWN_KEYS)
}
fn extract_other_fields_slim(obj: &Map<String, Value>) -> Map<String, Value> {
Self::extract_other_fields_with_keys(obj, &Self::KNOWN_KEYS_SLIM)
}
// ── Field extraction ───────────────────────────────────
fn extract_other_fields_with_keys(
obj: &Map<String, Value>,
@@ -141,6 +176,8 @@ impl OmoService {
}
}
// ── Merge helpers ──────────────────────────────────────
fn insert_opt_value(result: &mut Map<String, Value>, key: &str, value: &Option<Value>) {
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<PathBuf, AppError> {
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<crate::provider::Provider, AppError> {
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<crate::provider::Provider, AppError> {
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<crate::provider::Provider, AppError> {
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<OmoLocalFileData, AppError> {
let actual_path = Self::resolve_local_config_path()?;
pub fn read_local_file(v: &OmoVariant) -> Result<OmoLocalFileData, AppError> {
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<OmoLocalFileData, AppError> {
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::<chrono::Utc>::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<String, Value>,
file_path: String,
last_modified: Option<String>,
) -> 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<String, Value>,
file_path: String,
last_modified: Option<String>,
) -> 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"));
}
}
+63 -27
View File
@@ -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(());
}
@@ -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<string, Record<string, unknown>>,
categories: Record<string, Record<string, unknown>>,
otherFieldsStr: string,
): Record<string, unknown> {
const profileOnly: Record<string, unknown> = {};
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,
+134 -178
View File
@@ -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<typeof useQueryClient>) {
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<OmoGlobalConfig> => {
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<typeof useQueryClient>,
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<typeof useQueryClient>) {
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<OmoGlobalConfig> => {
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<OmoGlobalConfig> => {
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;
+62 -60
View File
@@ -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<string, Record<string, unknown>>,
categories: Record<string, Record<string, unknown>> | undefined,
otherFieldsStr: string,
options?: { slim?: boolean },
): Record<string, unknown> {
const result: Record<string, unknown> = {};
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<string, Record<string, unknown>>,
otherFieldsStr: string,
): Record<string, unknown> {
return mergeOmoConfigPreview(global, agents, undefined, otherFieldsStr, {
slim: true,
});
}
export function buildOmoProfilePreview(
agents: Record<string, Record<string, unknown>>,
categories: Record<string, Record<string, unknown>> | undefined,
otherFieldsStr: string,
options?: { slim?: boolean },
): Record<string, unknown> {
const result: Record<string, unknown> = {};
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<string, Record<string, unknown>>,
otherFieldsStr: string,
): Record<string, unknown> {
const result: Record<string, unknown> = {};
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<string, Record<string, unknown>>,
categories: Record<string, Record<string, unknown>>,
otherFieldsStr: string,
): Record<string, unknown> {
const result: Record<string, unknown> = {};
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,
});
}