mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-06-17 14:27:39 +08:00
feat(omo): add OMO Slim (oh-my-opencode-slim) support
Implement full OMO Slim profile management to align with ai-toolbox: - Backend: Slim service methods, DAO, Tauri commands, plugin conflict handling - Frontend: types, API, query hooks, form integration with isSlim parameterization - Slim variant: 6 agents (no categories), separate config file and plugin name - Mutual exclusion: standard OMO and Slim cannot coexist as plugins - i18n: zh/en/ja translations for all Slim agent descriptions
This commit is contained in:
@@ -215,7 +215,7 @@ pub async fn set_common_config_snippet(
|
||||
) -> Result<(), String> {
|
||||
if !snippet.trim().is_empty() {
|
||||
match app_type.as_str() {
|
||||
"claude" | "gemini" | "omo" => {
|
||||
"claude" | "gemini" | "omo" | "omo-slim" => {
|
||||
serde_json::from_str::<serde_json::Value>(&snippet)
|
||||
.map_err(invalid_json_format_error)?;
|
||||
}
|
||||
@@ -245,6 +245,16 @@ pub async fn set_common_config_snippet(
|
||||
crate::services::OmoService::write_config_to_file(state.inner())
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
if app_type == "omo-slim"
|
||||
&& state
|
||||
.db
|
||||
.get_current_omo_slim_provider("opencode")
|
||||
.map_err(|e| e.to_string())?
|
||||
.is_some()
|
||||
{
|
||||
crate::services::OmoService::write_config_to_file_slim(state.inner())
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -48,3 +48,52 @@ pub async fn get_omo_provider_count(state: State<'_, AppState>) -> Result<usize,
|
||||
.count();
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
// ── OMO Slim commands ───────────────────────────────────────
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn read_omo_slim_local_file() -> Result<OmoLocalFileData, String> {
|
||||
OmoService::read_local_file_slim().map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_current_omo_slim_provider_id(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<String, String> {
|
||||
let provider = state
|
||||
.db
|
||||
.get_current_omo_slim_provider("opencode")
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(provider.map(|p| p.id).unwrap_or_default())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn disable_current_omo_slim(state: State<'_, AppState>) -> Result<(), String> {
|
||||
let providers = state
|
||||
.db
|
||||
.get_all_providers("opencode")
|
||||
.map_err(|e| e.to_string())?;
|
||||
for (id, p) in &providers {
|
||||
if p.category.as_deref() == Some("omo-slim") {
|
||||
state
|
||||
.db
|
||||
.clear_omo_slim_provider_current("opencode", id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
}
|
||||
OmoService::delete_config_file_slim().map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_omo_slim_provider_count(state: State<'_, AppState>) -> Result<usize, String> {
|
||||
let providers = state
|
||||
.db
|
||||
.get_all_providers("opencode")
|
||||
.map_err(|e| e.to_string())?;
|
||||
let count = providers
|
||||
.values()
|
||||
.filter(|p| p.category.as_deref() == Some("omo-slim"))
|
||||
.count();
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
@@ -70,4 +70,23 @@ impl Database {
|
||||
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)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,4 +481,131 @@ impl Database {
|
||||
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(
|
||||
&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-slim' 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 Slim 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 Slim 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,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
+34
-1
@@ -526,7 +526,36 @@ pub fn run() {
|
||||
}
|
||||
}
|
||||
|
||||
// 2.3 OpenClaw 供应商导入(累加式模式,需特殊处理)
|
||||
// 2.3 OMO Slim config import (when no omo-slim provider in DB, import from local)
|
||||
{
|
||||
let has_omo_slim = app_state
|
||||
.db
|
||||
.get_all_providers("opencode")
|
||||
.map(|providers| {
|
||||
providers
|
||||
.values()
|
||||
.any(|p| p.category.as_deref() == Some("omo-slim"))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if !has_omo_slim {
|
||||
match crate::services::OmoService::import_from_local_slim(&app_state) {
|
||||
Ok(provider) => {
|
||||
log::info!(
|
||||
"✓ Imported OMO Slim config from local as provider '{}'",
|
||||
provider.name
|
||||
);
|
||||
}
|
||||
Err(AppError::OmoConfigNotFound) => {
|
||||
log::debug!("○ No OMO Slim config to import");
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("✗ Failed to import OMO Slim config from local: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2.4 OpenClaw 供应商导入(累加式模式,需特殊处理)
|
||||
// OpenClaw 与 OpenCode 类似:配置文件中可同时存在多个供应商
|
||||
// 需要遍历 models.providers 字段下的每个供应商并导入
|
||||
match crate::services::provider::import_openclaw_providers_from_live(&app_state) {
|
||||
@@ -1035,6 +1064,10 @@ pub fn run() {
|
||||
commands::get_current_omo_provider_id,
|
||||
commands::get_omo_provider_count,
|
||||
commands::disable_current_omo,
|
||||
commands::read_omo_slim_local_file,
|
||||
commands::get_current_omo_slim_provider_id,
|
||||
commands::get_omo_slim_provider_count,
|
||||
commands::disable_current_omo_slim,
|
||||
// Workspace files (OpenClaw)
|
||||
commands::read_workspace_file,
|
||||
commands::write_workspace_file,
|
||||
|
||||
@@ -145,14 +145,25 @@ pub fn add_plugin(plugin_name: &str) -> Result<(), AppError> {
|
||||
|
||||
match plugins {
|
||||
Some(arr) => {
|
||||
// Mutual exclusion: standard OMO and OMO Slim cannot coexist as plugins
|
||||
if plugin_name.starts_with("oh-my-opencode")
|
||||
&& !plugin_name.starts_with("oh-my-opencode-slim")
|
||||
{
|
||||
// Adding standard OMO -> remove all Slim variants
|
||||
arr.retain(|v| {
|
||||
v.as_str()
|
||||
.map(|s| !s.starts_with("oh-my-opencode-slim"))
|
||||
.unwrap_or(true)
|
||||
});
|
||||
} else if plugin_name.starts_with("oh-my-opencode-slim") {
|
||||
// Adding Slim -> remove all standard OMO variants (but keep slim)
|
||||
arr.retain(|v| {
|
||||
v.as_str()
|
||||
.map(|s| {
|
||||
!s.starts_with("oh-my-opencode") || s.starts_with("oh-my-opencode-slim")
|
||||
})
|
||||
.unwrap_or(true)
|
||||
});
|
||||
}
|
||||
|
||||
let already_exists = arr.iter().any(|v| v.as_str() == Some(plugin_name));
|
||||
|
||||
+224
-17
@@ -52,26 +52,48 @@ impl OmoService {
|
||||
.ok_or_else(|| AppError::Config("Expected JSON object".to_string()))
|
||||
}
|
||||
|
||||
fn extract_other_fields(obj: &Map<String, Value>) -> Map<String, Value> {
|
||||
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: [&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)
|
||||
}
|
||||
|
||||
fn extract_other_fields_with_keys(
|
||||
obj: &Map<String, Value>,
|
||||
known: &[&str],
|
||||
) -> Map<String, Value> {
|
||||
let mut other = Map::new();
|
||||
for (k, v) in obj {
|
||||
if !KNOWN_KEYS.contains(&k.as_str()) {
|
||||
if !known.contains(&k.as_str()) {
|
||||
other.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
@@ -142,6 +164,24 @@ impl OmoService {
|
||||
}
|
||||
}
|
||||
|
||||
fn config_path_slim() -> PathBuf {
|
||||
get_opencode_dir().join("oh-my-opencode-slim.jsonc")
|
||||
}
|
||||
|
||||
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();
|
||||
if config_path.exists() {
|
||||
@@ -152,6 +192,16 @@ impl OmoService {
|
||||
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")?;
|
||||
@@ -183,6 +233,36 @@ impl OmoService {
|
||||
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 {
|
||||
let mut result = Map::new();
|
||||
let use_common_config = profile_data.map(|(_, _, _, v)| *v).unwrap_or(true);
|
||||
@@ -219,6 +299,36 @@ impl OmoService {
|
||||
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()));
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -277,6 +387,58 @@ impl OmoService {
|
||||
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 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
|
||||
settings.insert("useCommonConfig".to_string(), Value::Bool(true));
|
||||
|
||||
let other = Self::extract_other_fields_slim(&obj);
|
||||
if !other.is_empty() {
|
||||
settings.insert("otherFields".to_string(), Value::Object(other));
|
||||
}
|
||||
|
||||
let mut global = state.db.get_omo_slim_global_config()?;
|
||||
Self::merge_global_from_obj(&obj, &mut global);
|
||||
global.updated_at = chrono::Utc::now().to_rfc3339();
|
||||
state.db.save_omo_slim_global_config(&global)?;
|
||||
|
||||
let provider_id = format!("omo-slim-{}", uuid::Uuid::new_v4());
|
||||
let name = format!(
|
||||
"Imported Slim {}",
|
||||
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-slim".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_slim_provider_current("opencode", &provider.id)?;
|
||||
Self::write_config_to_file_slim(state)?;
|
||||
Ok(provider)
|
||||
}
|
||||
|
||||
pub fn read_local_file() -> Result<OmoLocalFileData, AppError> {
|
||||
let actual_path = Self::resolve_local_config_path()?;
|
||||
let metadata = std::fs::metadata(&actual_path).ok();
|
||||
@@ -293,6 +455,22 @@ impl OmoService {
|
||||
))
|
||||
}
|
||||
|
||||
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(
|
||||
obj: &Map<String, Value>,
|
||||
file_path: String,
|
||||
@@ -322,6 +500,35 @@ 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();
|
||||
|
||||
@@ -167,8 +167,7 @@ impl ProviderService {
|
||||
// Additive mode apps (OpenCode, OpenClaw) - always write to live config
|
||||
if app_type.is_additive_mode() {
|
||||
// OMO providers use exclusive mode and write to dedicated config file.
|
||||
if matches!(app_type, AppType::OpenCode)
|
||||
&& provider.category.as_deref() == Some("omo")
|
||||
if matches!(app_type, AppType::OpenCode) && provider.category.as_deref() == Some("omo")
|
||||
{
|
||||
// Do not auto-enable newly added OMO providers.
|
||||
// Users must explicitly switch/apply an OMO provider to activate it.
|
||||
@@ -207,8 +206,7 @@ impl ProviderService {
|
||||
|
||||
// Additive mode apps (OpenCode, OpenClaw) - always update in live config
|
||||
if app_type.is_additive_mode() {
|
||||
if matches!(app_type, AppType::OpenCode)
|
||||
&& provider.category.as_deref() == Some("omo")
|
||||
if matches!(app_type, AppType::OpenCode) && provider.category.as_deref() == Some("omo")
|
||||
{
|
||||
let is_omo_current = state
|
||||
.db
|
||||
@@ -218,6 +216,17 @@ impl ProviderService {
|
||||
}
|
||||
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)?;
|
||||
if is_current {
|
||||
crate::services::OmoService::write_config_to_file_slim(state)?;
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
write_live_snapshot(&app_type, &provider)?;
|
||||
return Ok(true);
|
||||
}
|
||||
@@ -264,14 +273,12 @@ impl ProviderService {
|
||||
// Additive mode apps - no current provider concept
|
||||
if app_type.is_additive_mode() {
|
||||
if matches!(app_type, AppType::OpenCode) {
|
||||
let is_omo = state
|
||||
let provider_category = state
|
||||
.db
|
||||
.get_provider_by_id(id, app_type.as_str())?
|
||||
.and_then(|p| p.category)
|
||||
.as_deref()
|
||||
== Some("omo");
|
||||
.and_then(|p| p.category);
|
||||
|
||||
if is_omo {
|
||||
if provider_category.as_deref() == Some("omo") {
|
||||
let was_current = state.db.is_omo_provider_current(app_type.as_str(), id)?;
|
||||
let omo_count = state
|
||||
.db
|
||||
@@ -292,6 +299,30 @@ impl ProviderService {
|
||||
}
|
||||
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 slim_count = state
|
||||
.db
|
||||
.get_all_providers(app_type.as_str())?
|
||||
.values()
|
||||
.filter(|p| p.category.as_deref() == Some("omo-slim"))
|
||||
.count();
|
||||
|
||||
if slim_count <= 1 && was_current {
|
||||
return Err(AppError::Message(
|
||||
"无法删除当前启用的最后一个 OMO Slim 配置,请先停用".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
state.db.delete_provider(app_type.as_str(), id)?;
|
||||
if was_current {
|
||||
crate::services::OmoService::delete_config_file_slim()?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// Remove from database
|
||||
state.db.delete_provider(app_type.as_str(), id)?;
|
||||
@@ -329,14 +360,12 @@ impl ProviderService {
|
||||
) -> Result<(), AppError> {
|
||||
match app_type {
|
||||
AppType::OpenCode => {
|
||||
let is_omo = state
|
||||
let provider_category = state
|
||||
.db
|
||||
.get_provider_by_id(id, app_type.as_str())?
|
||||
.and_then(|p| p.category)
|
||||
.as_deref()
|
||||
== Some("omo");
|
||||
.and_then(|p| p.category);
|
||||
|
||||
if is_omo {
|
||||
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();
|
||||
@@ -345,6 +374,19 @@ impl ProviderService {
|
||||
} else {
|
||||
crate::services::OmoService::delete_config_file()?;
|
||||
}
|
||||
} else if provider_category.as_deref() == Some("omo-slim") {
|
||||
state
|
||||
.db
|
||||
.clear_omo_slim_provider_current(app_type.as_str(), id)?;
|
||||
let still_has_current = state
|
||||
.db
|
||||
.get_current_omo_slim_provider("opencode")?
|
||||
.is_some();
|
||||
if still_has_current {
|
||||
crate::services::OmoService::write_config_to_file_slim(state)?;
|
||||
} else {
|
||||
crate::services::OmoService::delete_config_file_slim()?;
|
||||
}
|
||||
} else {
|
||||
remove_opencode_provider_from_live(id)?;
|
||||
}
|
||||
@@ -386,6 +428,13 @@ impl ProviderService {
|
||||
return Self::switch_normal(state, app_type, id, &providers);
|
||||
}
|
||||
|
||||
// OMO Slim providers are switched through their own exclusive path.
|
||||
if matches!(app_type, AppType::OpenCode)
|
||||
&& _provider.category.as_deref() == Some("omo-slim")
|
||||
{
|
||||
return Self::switch_normal(state, app_type, id, &providers);
|
||||
}
|
||||
|
||||
// Check if proxy takeover mode is active AND proxy server is actually running
|
||||
// Both conditions must be true to use hot-switch mode
|
||||
// Use blocking wait since this is a sync function
|
||||
@@ -463,6 +512,15 @@ impl ProviderService {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if matches!(app_type, AppType::OpenCode) && provider.category.as_deref() == Some("omo-slim")
|
||||
{
|
||||
state
|
||||
.db
|
||||
.set_omo_slim_provider_current(app_type.as_str(), id)?;
|
||||
crate::services::OmoService::write_config_to_file_slim(state)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Backfill: Backfill current live config to current provider
|
||||
// Use effective current provider (validated existence) to ensure backfill targets valid provider
|
||||
let current_id = crate::settings::get_effective_current_provider(&state.db, &app_type)?;
|
||||
|
||||
Reference in New Issue
Block a user