mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +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)?;
|
||||
|
||||
+26
-1
@@ -61,7 +61,10 @@ import { UniversalProviderPanel } from "@/components/universal";
|
||||
import { McpIcon } from "@/components/BrandIcons";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SessionManagerPage } from "@/components/sessions/SessionManagerPage";
|
||||
import { useDisableCurrentOmo } from "@/lib/query/omo";
|
||||
import {
|
||||
useDisableCurrentOmo,
|
||||
useDisableCurrentOmoSlim,
|
||||
} from "@/lib/query/omo";
|
||||
import WorkspaceFilesPanel from "@/components/workspace/WorkspaceFilesPanel";
|
||||
import EnvPanel from "@/components/openclaw/EnvPanel";
|
||||
import ToolsPanel from "@/components/openclaw/ToolsPanel";
|
||||
@@ -248,6 +251,23 @@ function App() {
|
||||
});
|
||||
};
|
||||
|
||||
const disableOmoSlimMutation = useDisableCurrentOmoSlim();
|
||||
const handleDisableOmoSlim = () => {
|
||||
disableOmoSlimMutation.mutate(undefined, {
|
||||
onSuccess: () => {
|
||||
toast.success(t("omo.disabled", { defaultValue: "OMO 已停用" }));
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
toast.error(
|
||||
t("omo.disableFailed", {
|
||||
defaultValue: "停用 OMO 失败: {{error}}",
|
||||
error: extractErrorMessage(error),
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let unsubscribe: (() => void) | undefined;
|
||||
|
||||
@@ -746,6 +766,11 @@ function App() {
|
||||
onDisableOmo={
|
||||
activeApp === "opencode" ? handleDisableOmo : undefined
|
||||
}
|
||||
onDisableOmoSlim={
|
||||
activeApp === "opencode"
|
||||
? handleDisableOmoSlim
|
||||
: undefined
|
||||
}
|
||||
onDuplicate={handleDuplicateProvider}
|
||||
onConfigureUsage={setUsageProvider}
|
||||
onOpenWebsite={handleOpenWebsite}
|
||||
|
||||
@@ -29,11 +29,14 @@ interface ProviderCardProps {
|
||||
isInConfig?: boolean; // OpenCode: 是否已添加到 opencode.json
|
||||
isOmo?: boolean;
|
||||
isLastOmo?: boolean;
|
||||
isOmoSlim?: boolean;
|
||||
isLastOmoSlim?: boolean;
|
||||
onSwitch: (provider: Provider) => void;
|
||||
onEdit: (provider: Provider) => void;
|
||||
onDelete: (provider: Provider) => void;
|
||||
onRemoveFromConfig?: (provider: Provider) => void;
|
||||
onDisableOmo?: () => void;
|
||||
onDisableOmoSlim?: () => void;
|
||||
onConfigureUsage: (provider: Provider) => void;
|
||||
onOpenWebsite: (url: string) => void;
|
||||
onDuplicate: (provider: Provider) => void;
|
||||
@@ -92,11 +95,14 @@ export function ProviderCard({
|
||||
isInConfig = true,
|
||||
isOmo = false,
|
||||
isLastOmo = false,
|
||||
isOmoSlim = false,
|
||||
isLastOmoSlim = false,
|
||||
onSwitch,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onRemoveFromConfig,
|
||||
onDisableOmo,
|
||||
onDisableOmoSlim,
|
||||
onConfigureUsage,
|
||||
onOpenWebsite,
|
||||
onDuplicate,
|
||||
@@ -117,6 +123,11 @@ export function ProviderCard({
|
||||
}: ProviderCardProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// OMO and OMO Slim share the same card behavior
|
||||
const isAnyOmo = isOmo || isOmoSlim;
|
||||
const isLastAnyOmo = isOmo ? isLastOmo : isLastOmoSlim;
|
||||
const handleDisableAnyOmo = isOmoSlim ? onDisableOmoSlim : onDisableOmo;
|
||||
|
||||
const { data: health } = useProviderHealth(provider.id, appId);
|
||||
|
||||
const fallbackUrlText = t("provider.notConfigured", {
|
||||
@@ -186,11 +197,11 @@ export function ProviderCard({
|
||||
};
|
||||
|
||||
// 判断是否是"当前使用中"的供应商
|
||||
// - OMO 供应商:使用 isCurrent
|
||||
// - OMO/OMO Slim 供应商:使用 isCurrent
|
||||
// - 累加模式应用(OpenCode 非 OMO / OpenClaw):不存在"当前"概念,始终返回 false
|
||||
// - 故障转移模式:代理实际使用的供应商(activeProviderId)
|
||||
// - 普通模式:isCurrent
|
||||
const isActiveProvider = isOmo
|
||||
const isActiveProvider = isAnyOmo
|
||||
? isCurrent
|
||||
: appId === "opencode" || appId === "openclaw"
|
||||
? false
|
||||
@@ -198,10 +209,10 @@ export function ProviderCard({
|
||||
? activeProviderId === provider.id
|
||||
: isCurrent;
|
||||
|
||||
const shouldUseGreen = !isOmo && isProxyTakeover && isActiveProvider;
|
||||
const shouldUseGreen = !isAnyOmo && isProxyTakeover && isActiveProvider;
|
||||
const shouldUseBlue =
|
||||
(isOmo && isActiveProvider) ||
|
||||
(!isOmo && !isProxyTakeover && isActiveProvider);
|
||||
(isAnyOmo && isActiveProvider) ||
|
||||
(!isAnyOmo && !isProxyTakeover && isActiveProvider);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -265,6 +276,12 @@ export function ProviderCard({
|
||||
</span>
|
||||
)}
|
||||
|
||||
{isOmoSlim && (
|
||||
<span className="inline-flex items-center rounded-md bg-indigo-100 px-1.5 py-0.5 text-[10px] font-semibold text-indigo-700 dark:bg-indigo-900/40 dark:text-indigo-300">
|
||||
Slim
|
||||
</span>
|
||||
)}
|
||||
|
||||
{isProxyRunning && isInFailoverQueue && health && (
|
||||
<ProviderHealthBadge
|
||||
consecutiveFailures={health.consecutive_failures}
|
||||
@@ -372,8 +389,8 @@ export function ProviderCard({
|
||||
isInConfig={isInConfig}
|
||||
isTesting={isTesting}
|
||||
isProxyTakeover={isProxyTakeover}
|
||||
isOmo={isOmo}
|
||||
isLastOmo={isLastOmo}
|
||||
isOmo={isAnyOmo}
|
||||
isLastOmo={isLastAnyOmo}
|
||||
onSwitch={() => onSwitch(provider)}
|
||||
onEdit={() => onEdit(provider)}
|
||||
onDuplicate={() => onDuplicate(provider)}
|
||||
@@ -385,7 +402,7 @@ export function ProviderCard({
|
||||
? () => onRemoveFromConfig(provider)
|
||||
: undefined
|
||||
}
|
||||
onDisableOmo={onDisableOmo}
|
||||
onDisableOmo={handleDisableAnyOmo}
|
||||
onOpenTerminal={
|
||||
onOpenTerminal ? () => onOpenTerminal(provider) : undefined
|
||||
}
|
||||
|
||||
@@ -33,7 +33,12 @@ import {
|
||||
useAddToFailoverQueue,
|
||||
useRemoveFromFailoverQueue,
|
||||
} from "@/lib/query/failover";
|
||||
import { useCurrentOmoProviderId, useOmoProviderCount } from "@/lib/query/omo";
|
||||
import {
|
||||
useCurrentOmoProviderId,
|
||||
useOmoProviderCount,
|
||||
useCurrentOmoSlimProviderId,
|
||||
useOmoSlimProviderCount,
|
||||
} from "@/lib/query/omo";
|
||||
import { useCallback } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -47,6 +52,7 @@ interface ProviderListProps {
|
||||
onDelete: (provider: Provider) => void;
|
||||
onRemoveFromConfig?: (provider: Provider) => void;
|
||||
onDisableOmo?: () => void;
|
||||
onDisableOmoSlim?: () => void;
|
||||
onDuplicate: (provider: Provider) => void;
|
||||
onConfigureUsage?: (provider: Provider) => void;
|
||||
onOpenWebsite: (url: string) => void;
|
||||
@@ -68,6 +74,7 @@ export function ProviderList({
|
||||
onDelete,
|
||||
onRemoveFromConfig,
|
||||
onDisableOmo,
|
||||
onDisableOmoSlim,
|
||||
onDuplicate,
|
||||
onConfigureUsage,
|
||||
onOpenWebsite,
|
||||
@@ -135,6 +142,8 @@ export function ProviderList({
|
||||
const isOpenCode = appId === "opencode";
|
||||
const { data: currentOmoId } = useCurrentOmoProviderId(isOpenCode);
|
||||
const { data: omoProviderCount } = useOmoProviderCount(isOpenCode);
|
||||
const { data: currentOmoSlimId } = useCurrentOmoSlimProviderId(isOpenCode);
|
||||
const { data: omoSlimProviderCount } = useOmoSlimProviderCount(isOpenCode);
|
||||
|
||||
const getFailoverPriority = useCallback(
|
||||
(providerId: string): number | undefined => {
|
||||
@@ -239,13 +248,20 @@ export function ProviderList({
|
||||
<div className="space-y-3">
|
||||
{filteredProviders.map((provider) => {
|
||||
const isOmo = provider.category === "omo";
|
||||
const isOmoSlim = provider.category === "omo-slim";
|
||||
const isOmoCurrent = isOmo && provider.id === (currentOmoId || "");
|
||||
const isOmoSlimCurrent =
|
||||
isOmoSlim && provider.id === (currentOmoSlimId || "");
|
||||
return (
|
||||
<SortableProviderCard
|
||||
key={provider.id}
|
||||
provider={provider}
|
||||
isCurrent={
|
||||
isOmo ? isOmoCurrent : provider.id === currentProviderId
|
||||
isOmo
|
||||
? isOmoCurrent
|
||||
: isOmoSlim
|
||||
? isOmoSlimCurrent
|
||||
: provider.id === currentProviderId
|
||||
}
|
||||
appId={appId}
|
||||
isInConfig={isProviderInConfig(provider.id)}
|
||||
@@ -253,11 +269,18 @@ export function ProviderList({
|
||||
isLastOmo={
|
||||
isOmo && (omoProviderCount ?? 0) <= 1 && isOmoCurrent
|
||||
}
|
||||
isOmoSlim={isOmoSlim}
|
||||
isLastOmoSlim={
|
||||
isOmoSlim &&
|
||||
(omoSlimProviderCount ?? 0) <= 1 &&
|
||||
isOmoSlimCurrent
|
||||
}
|
||||
onSwitch={onSwitch}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
onRemoveFromConfig={onRemoveFromConfig}
|
||||
onDisableOmo={onDisableOmo}
|
||||
onDisableOmoSlim={onDisableOmoSlim}
|
||||
onDuplicate={onDuplicate}
|
||||
onConfigureUsage={onConfigureUsage}
|
||||
onOpenWebsite={onOpenWebsite}
|
||||
@@ -371,11 +394,14 @@ interface SortableProviderCardProps {
|
||||
isInConfig: boolean;
|
||||
isOmo: boolean;
|
||||
isLastOmo: boolean;
|
||||
isOmoSlim: boolean;
|
||||
isLastOmoSlim: boolean;
|
||||
onSwitch: (provider: Provider) => void;
|
||||
onEdit: (provider: Provider) => void;
|
||||
onDelete: (provider: Provider) => void;
|
||||
onRemoveFromConfig?: (provider: Provider) => void;
|
||||
onDisableOmo?: () => void;
|
||||
onDisableOmoSlim?: () => void;
|
||||
onDuplicate: (provider: Provider) => void;
|
||||
onConfigureUsage?: (provider: Provider) => void;
|
||||
onOpenWebsite: (url: string) => void;
|
||||
@@ -401,11 +427,14 @@ function SortableProviderCard({
|
||||
isInConfig,
|
||||
isOmo,
|
||||
isLastOmo,
|
||||
isOmoSlim,
|
||||
isLastOmoSlim,
|
||||
onSwitch,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onRemoveFromConfig,
|
||||
onDisableOmo,
|
||||
onDisableOmoSlim,
|
||||
onDuplicate,
|
||||
onConfigureUsage,
|
||||
onOpenWebsite,
|
||||
@@ -445,11 +474,14 @@ function SortableProviderCard({
|
||||
isInConfig={isInConfig}
|
||||
isOmo={isOmo}
|
||||
isLastOmo={isLastOmo}
|
||||
isOmoSlim={isOmoSlim}
|
||||
isLastOmoSlim={isLastOmoSlim}
|
||||
onSwitch={onSwitch}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
onRemoveFromConfig={onRemoveFromConfig}
|
||||
onDisableOmo={onDisableOmo}
|
||||
onDisableOmoSlim={onDisableOmoSlim}
|
||||
onDuplicate={onDuplicate}
|
||||
onConfigureUsage={
|
||||
onConfigureUsage ? (item) => onConfigureUsage(item) : () => undefined
|
||||
|
||||
@@ -23,6 +23,7 @@ interface OmoCommonConfigEditorProps {
|
||||
onGlobalConfigStateChange: (config: OmoGlobalConfig) => void;
|
||||
globalConfigRef: React.RefObject<OmoGlobalConfigFieldsRef | null>;
|
||||
fieldsKey: number;
|
||||
isSlim?: boolean;
|
||||
}
|
||||
|
||||
export function OmoCommonConfigEditor({
|
||||
@@ -37,6 +38,7 @@ export function OmoCommonConfigEditor({
|
||||
onGlobalConfigStateChange,
|
||||
globalConfigRef,
|
||||
fieldsKey,
|
||||
isSlim = false,
|
||||
}: OmoCommonConfigEditorProps) {
|
||||
const { t } = useTranslation();
|
||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||
@@ -153,6 +155,7 @@ export function OmoCommonConfigEditor({
|
||||
ref={globalConfigRef as React.Ref<OmoGlobalConfigFieldsRef>}
|
||||
onStateChange={onGlobalConfigStateChange}
|
||||
hideSaveButtons
|
||||
isSlim={isSlim}
|
||||
/>
|
||||
</div>
|
||||
</FullScreenPanel>
|
||||
|
||||
@@ -41,10 +41,11 @@ import {
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { toast } from "sonner";
|
||||
import { useReadOmoLocalFile } from "@/lib/query/omo";
|
||||
import { useReadOmoLocalFile, useReadOmoSlimLocalFile } from "@/lib/query/omo";
|
||||
import {
|
||||
OMO_BUILTIN_AGENTS,
|
||||
OMO_BUILTIN_CATEGORIES,
|
||||
OMO_SLIM_BUILTIN_AGENTS,
|
||||
type OmoAgentDef,
|
||||
type OmoCategoryDef,
|
||||
} from "@/types/omo";
|
||||
@@ -69,12 +70,13 @@ interface OmoFormFieldsProps {
|
||||
>;
|
||||
agents: Record<string, Record<string, unknown>>;
|
||||
onAgentsChange: (agents: Record<string, Record<string, unknown>>) => void;
|
||||
categories: Record<string, Record<string, unknown>>;
|
||||
onCategoriesChange: (
|
||||
categories?: Record<string, Record<string, unknown>>;
|
||||
onCategoriesChange?: (
|
||||
categories: Record<string, Record<string, unknown>>,
|
||||
) => void;
|
||||
otherFieldsStr: string;
|
||||
onOtherFieldsStrChange: (value: string) => void;
|
||||
isSlim?: boolean;
|
||||
}
|
||||
|
||||
export type CustomModelItem = {
|
||||
@@ -121,6 +123,9 @@ function DeferredKeyInput({
|
||||
}
|
||||
|
||||
const BUILTIN_AGENT_KEYS = new Set(OMO_BUILTIN_AGENTS.map((a) => a.key));
|
||||
const BUILTIN_AGENT_KEYS_SLIM = new Set(
|
||||
OMO_SLIM_BUILTIN_AGENTS.map((a) => a.key),
|
||||
);
|
||||
const BUILTIN_CATEGORY_KEYS = new Set(OMO_BUILTIN_CATEGORIES.map((c) => c.key));
|
||||
const EMPTY_VARIANT_VALUE = "__cc_switch_omo_variant_empty__";
|
||||
|
||||
@@ -303,13 +308,21 @@ export function OmoFormFields({
|
||||
presetMetaMap: _presetMetaMap = {},
|
||||
agents,
|
||||
onAgentsChange,
|
||||
categories,
|
||||
categories = {},
|
||||
onCategoriesChange,
|
||||
otherFieldsStr,
|
||||
onOtherFieldsStrChange,
|
||||
isSlim = false,
|
||||
}: OmoFormFieldsProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const builtinAgentDefs = isSlim
|
||||
? OMO_SLIM_BUILTIN_AGENTS
|
||||
: OMO_BUILTIN_AGENTS;
|
||||
const builtinAgentKeys = isSlim
|
||||
? BUILTIN_AGENT_KEYS_SLIM
|
||||
: BUILTIN_AGENT_KEYS;
|
||||
|
||||
const [mainAgentsOpen, setMainAgentsOpen] = useState(true);
|
||||
const [subAgentsOpen, setSubAgentsOpen] = useState(true);
|
||||
const [categoriesOpen, setCategoriesOpen] = useState(true);
|
||||
@@ -329,7 +342,7 @@ export function OmoFormFields({
|
||||
>({});
|
||||
|
||||
const [customAgents, setCustomAgents] = useState<CustomModelItem[]>(() =>
|
||||
collectCustomModels(agents, BUILTIN_AGENT_KEYS),
|
||||
collectCustomModels(agents, builtinAgentKeys),
|
||||
);
|
||||
|
||||
const [customCategories, setCustomCategories] = useState<CustomModelItem[]>(
|
||||
@@ -337,7 +350,7 @@ export function OmoFormFields({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setCustomAgents(collectCustomModels(agents, BUILTIN_AGENT_KEYS));
|
||||
setCustomAgents(collectCustomModels(agents, builtinAgentKeys));
|
||||
}, [agents]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -349,17 +362,18 @@ export function OmoFormFields({
|
||||
onAgentsChange(
|
||||
mergeCustomModelsIntoStore(
|
||||
agents,
|
||||
BUILTIN_AGENT_KEYS,
|
||||
builtinAgentKeys,
|
||||
customs,
|
||||
modelVariantsMap,
|
||||
),
|
||||
);
|
||||
},
|
||||
[agents, onAgentsChange, modelVariantsMap],
|
||||
[agents, onAgentsChange, modelVariantsMap, builtinAgentKeys],
|
||||
);
|
||||
|
||||
const syncCustomCategories = useCallback(
|
||||
(customs: CustomModelItem[]) => {
|
||||
if (!onCategoriesChange) return;
|
||||
onCategoriesChange(
|
||||
mergeCustomModelsIntoStore(
|
||||
categories,
|
||||
@@ -709,7 +723,7 @@ export function OmoFormFields({
|
||||
}
|
||||
|
||||
const updatedAgents = { ...agents };
|
||||
for (const agentDef of OMO_BUILTIN_AGENTS) {
|
||||
for (const agentDef of builtinAgentDefs) {
|
||||
const recommendedValue = resolveRecommendedModel(agentDef.recommended);
|
||||
if (recommendedValue && !updatedAgents[agentDef.key]?.model) {
|
||||
updatedAgents[agentDef.key] = {
|
||||
@@ -720,30 +734,35 @@ export function OmoFormFields({
|
||||
}
|
||||
onAgentsChange(updatedAgents);
|
||||
|
||||
const updatedCategories = { ...categories };
|
||||
for (const catDef of OMO_BUILTIN_CATEGORIES) {
|
||||
const recommendedValue = resolveRecommendedModel(catDef.recommended);
|
||||
if (recommendedValue && !updatedCategories[catDef.key]?.model) {
|
||||
updatedCategories[catDef.key] = {
|
||||
...updatedCategories[catDef.key],
|
||||
model: recommendedValue,
|
||||
};
|
||||
if (!isSlim && onCategoriesChange) {
|
||||
const updatedCategories = { ...categories };
|
||||
for (const catDef of OMO_BUILTIN_CATEGORIES) {
|
||||
const recommendedValue = resolveRecommendedModel(catDef.recommended);
|
||||
if (recommendedValue && !updatedCategories[catDef.key]?.model) {
|
||||
updatedCategories[catDef.key] = {
|
||||
...updatedCategories[catDef.key],
|
||||
model: recommendedValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
onCategoriesChange(updatedCategories);
|
||||
}
|
||||
onCategoriesChange(updatedCategories);
|
||||
};
|
||||
|
||||
const configuredAgentCount = Object.keys(agents).length;
|
||||
const configuredCategoryCount = Object.keys(categories).length;
|
||||
const mainAgents = OMO_BUILTIN_AGENTS.filter((a) => a.group === "main");
|
||||
const subAgents = OMO_BUILTIN_AGENTS.filter((a) => a.group === "sub");
|
||||
const configuredCategoryCount = isSlim ? 0 : Object.keys(categories).length;
|
||||
const mainAgents = builtinAgentDefs.filter((a) => a.group === "main");
|
||||
const subAgents = builtinAgentDefs.filter((a) => a.group === "sub");
|
||||
|
||||
const readLocalFile = useReadOmoLocalFile();
|
||||
const readSlimLocalFile = useReadOmoSlimLocalFile();
|
||||
const [localFilePath, setLocalFilePath] = useState<string | null>(null);
|
||||
|
||||
const handleImportFromLocal = useCallback(async () => {
|
||||
try {
|
||||
const data = await readLocalFile.mutateAsync();
|
||||
const data = isSlim
|
||||
? await readSlimLocalFile.mutateAsync()
|
||||
: await readLocalFile.mutateAsync();
|
||||
const importedAgents =
|
||||
(data.agents as Record<string, Record<string, unknown>> | undefined) ||
|
||||
{};
|
||||
@@ -753,16 +772,20 @@ export function OmoFormFields({
|
||||
| undefined) || {};
|
||||
|
||||
onAgentsChange(importedAgents);
|
||||
onCategoriesChange(importedCategories);
|
||||
if (!isSlim && onCategoriesChange) {
|
||||
onCategoriesChange(importedCategories);
|
||||
}
|
||||
onOtherFieldsStrChange(
|
||||
data.otherFields ? JSON.stringify(data.otherFields, null, 2) : "",
|
||||
);
|
||||
setAgentAdvancedDrafts({});
|
||||
setCategoryAdvancedDrafts({});
|
||||
setCustomAgents(collectCustomModels(importedAgents, BUILTIN_AGENT_KEYS));
|
||||
setCustomCategories(
|
||||
collectCustomModels(importedCategories, BUILTIN_CATEGORY_KEYS),
|
||||
);
|
||||
setCustomAgents(collectCustomModels(importedAgents, builtinAgentKeys));
|
||||
if (!isSlim) {
|
||||
setCustomCategories(
|
||||
collectCustomModels(importedCategories, BUILTIN_CATEGORY_KEYS),
|
||||
);
|
||||
}
|
||||
setLocalFilePath(data.filePath);
|
||||
toast.success(
|
||||
t("omo.importLocalReplaceSuccess", {
|
||||
@@ -792,7 +815,7 @@ export function OmoFormFields({
|
||||
) => {
|
||||
const isAgent = scope === "agent";
|
||||
const store = isAgent ? agents : categories;
|
||||
const setter = isAgent ? onAgentsChange : onCategoriesChange;
|
||||
const setter = isAgent ? onAgentsChange : onCategoriesChange!;
|
||||
const drafts = isAgent ? agentAdvancedDrafts : categoryAdvancedDrafts;
|
||||
const expanded = isAgent ? expandedAgents : expandedCategories;
|
||||
|
||||
@@ -866,7 +889,7 @@ export function OmoFormFields({
|
||||
) => {
|
||||
const isAgent = scope === "agent";
|
||||
const store = isAgent ? agents : categories;
|
||||
const setter = isAgent ? onAgentsChange : onCategoriesChange;
|
||||
const setter = isAgent ? onAgentsChange : onCategoriesChange!;
|
||||
const drafts = isAgent ? agentAdvancedDrafts : categoryAdvancedDrafts;
|
||||
const expanded = isAgent ? expandedAgents : expandedCategories;
|
||||
const customs = isAgent ? customAgents : customCategories;
|
||||
@@ -1153,30 +1176,31 @@ export function OmoFormFields({
|
||||
),
|
||||
})}
|
||||
|
||||
{renderModelSection({
|
||||
title: t("omo.categories", { defaultValue: "Categories" }),
|
||||
isOpen: categoriesOpen,
|
||||
onToggle: () => setCategoriesOpen(!categoriesOpen),
|
||||
badge: `${OMO_BUILTIN_CATEGORIES.length + customCategories.length}`,
|
||||
action: renderCustomAddButton(() => addCustomModel("category")),
|
||||
children: (
|
||||
<>
|
||||
{OMO_BUILTIN_CATEGORIES.map(renderCategoryRow)}
|
||||
{customCategories.length > 0 && (
|
||||
<>
|
||||
{renderCustomDivider(
|
||||
t("omo.customCategories", {
|
||||
defaultValue: "Custom Categories",
|
||||
}),
|
||||
)}
|
||||
{customCategories.map((c, i) =>
|
||||
renderCustomModelRow("category", c, i),
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
})}
|
||||
{!isSlim &&
|
||||
renderModelSection({
|
||||
title: t("omo.categories", { defaultValue: "Categories" }),
|
||||
isOpen: categoriesOpen,
|
||||
onToggle: () => setCategoriesOpen(!categoriesOpen),
|
||||
badge: `${OMO_BUILTIN_CATEGORIES.length + customCategories.length}`,
|
||||
action: renderCustomAddButton(() => addCustomModel("category")),
|
||||
children: (
|
||||
<>
|
||||
{OMO_BUILTIN_CATEGORIES.map(renderCategoryRow)}
|
||||
{customCategories.length > 0 && (
|
||||
<>
|
||||
{renderCustomDivider(
|
||||
t("omo.customCategories", {
|
||||
defaultValue: "Custom Categories",
|
||||
}),
|
||||
)}
|
||||
{customCategories.map((c, i) =>
|
||||
renderCustomModelRow("category", c, i),
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
})}
|
||||
|
||||
{renderModelSection({
|
||||
title: t("omo.otherFieldsJson", {
|
||||
|
||||
@@ -40,11 +40,18 @@ import {
|
||||
OMO_BACKGROUND_TASK_PLACEHOLDER,
|
||||
OMO_BROWSER_AUTOMATION_PLACEHOLDER,
|
||||
OMO_CLAUDE_CODE_PLACEHOLDER,
|
||||
OMO_SLIM_DISABLEABLE_AGENTS,
|
||||
OMO_SLIM_DISABLEABLE_MCPS,
|
||||
OMO_SLIM_DISABLEABLE_HOOKS,
|
||||
OMO_SLIM_DEFAULT_SCHEMA_URL,
|
||||
} from "@/types/omo";
|
||||
import {
|
||||
useOmoGlobalConfig,
|
||||
useSaveOmoGlobalConfig,
|
||||
useReadOmoLocalFile,
|
||||
useOmoSlimGlobalConfig,
|
||||
useSaveOmoSlimGlobalConfig,
|
||||
useReadOmoSlimLocalFile,
|
||||
} from "@/lib/query/omo";
|
||||
|
||||
interface PresetOption {
|
||||
@@ -61,6 +68,7 @@ export interface OmoGlobalConfigFieldsRef {
|
||||
interface OmoGlobalConfigFieldsProps {
|
||||
onStateChange?: (config: OmoGlobalConfig) => void;
|
||||
hideSaveButtons?: boolean;
|
||||
isSlim?: boolean;
|
||||
}
|
||||
|
||||
type OmoAdvancedFieldKey =
|
||||
@@ -114,6 +122,11 @@ const OMO_ADVANCED_JSON_FIELDS: ReadonlyArray<{
|
||||
},
|
||||
];
|
||||
|
||||
const OMO_SLIM_ADVANCED_KEYS: ReadonlySet<OmoAdvancedFieldKey> = new Set([
|
||||
"lspStr",
|
||||
"experimentalStr",
|
||||
]);
|
||||
|
||||
function TagListEditor({
|
||||
label,
|
||||
values,
|
||||
@@ -310,12 +323,25 @@ function JsonTextareaField({
|
||||
export const OmoGlobalConfigFields = forwardRef<
|
||||
OmoGlobalConfigFieldsRef,
|
||||
OmoGlobalConfigFieldsProps
|
||||
>(function OmoGlobalConfigFields({ onStateChange, hideSaveButtons }, ref) {
|
||||
>(function OmoGlobalConfigFields(
|
||||
{ onStateChange, hideSaveButtons, isSlim = false },
|
||||
ref,
|
||||
) {
|
||||
const { t } = useTranslation();
|
||||
const { data: config } = useOmoGlobalConfig();
|
||||
const saveMutation = useSaveOmoGlobalConfig();
|
||||
const { data: standardConfig } = useOmoGlobalConfig(!isSlim);
|
||||
const { data: slimConfig } = useOmoSlimGlobalConfig(isSlim);
|
||||
const config = isSlim ? slimConfig : standardConfig;
|
||||
const standardSaveMutation = useSaveOmoGlobalConfig();
|
||||
const slimSaveMutation = useSaveOmoSlimGlobalConfig();
|
||||
const saveMutation = isSlim ? slimSaveMutation : standardSaveMutation;
|
||||
const standardReadLocal = useReadOmoLocalFile();
|
||||
const slimReadLocal = useReadOmoSlimLocalFile();
|
||||
|
||||
const [schemaUrl, setSchemaUrl] = useState(OMO_DEFAULT_SCHEMA_URL);
|
||||
const defaultSchemaUrl = isSlim
|
||||
? OMO_SLIM_DEFAULT_SCHEMA_URL
|
||||
: OMO_DEFAULT_SCHEMA_URL;
|
||||
|
||||
const [schemaUrl, setSchemaUrl] = useState(defaultSchemaUrl);
|
||||
const [sisyphusAgentStr, setSisyphusAgentStr] = useState("");
|
||||
const [disabledAgents, setDisabledAgents] = useState<string[]>([]);
|
||||
const [disabledMcps, setDisabledMcps] = useState<string[]>([]);
|
||||
@@ -330,7 +356,7 @@ export const OmoGlobalConfigFields = forwardRef<
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
const applyGlobalState = useCallback((global: OmoGlobalConfig) => {
|
||||
setSchemaUrl(global.schemaUrl || OMO_DEFAULT_SCHEMA_URL);
|
||||
setSchemaUrl(global.schemaUrl || defaultSchemaUrl);
|
||||
setSisyphusAgentStr(
|
||||
global.sisyphusAgent ? JSON.stringify(global.sisyphusAgent, null, 2) : "",
|
||||
);
|
||||
@@ -545,7 +571,7 @@ export const OmoGlobalConfigFields = forwardRef<
|
||||
placeholder: t("omo.disabledAgentsPlaceholder", {
|
||||
defaultValue: "Disabled Agents",
|
||||
}),
|
||||
presets: OMO_DISABLEABLE_AGENTS,
|
||||
presets: isSlim ? OMO_SLIM_DISABLEABLE_AGENTS : OMO_DISABLEABLE_AGENTS,
|
||||
},
|
||||
{
|
||||
key: "mcps",
|
||||
@@ -555,7 +581,7 @@ export const OmoGlobalConfigFields = forwardRef<
|
||||
placeholder: t("omo.disabledMcpsPlaceholder", {
|
||||
defaultValue: "Disabled MCPs",
|
||||
}),
|
||||
presets: OMO_DISABLEABLE_MCPS,
|
||||
presets: isSlim ? OMO_SLIM_DISABLEABLE_MCPS : OMO_DISABLEABLE_MCPS,
|
||||
},
|
||||
{
|
||||
key: "hooks",
|
||||
@@ -565,21 +591,25 @@ export const OmoGlobalConfigFields = forwardRef<
|
||||
placeholder: t("omo.disabledHooksPlaceholder", {
|
||||
defaultValue: "Disabled Hooks",
|
||||
}),
|
||||
presets: OMO_DISABLEABLE_HOOKS,
|
||||
presets: isSlim ? OMO_SLIM_DISABLEABLE_HOOKS : OMO_DISABLEABLE_HOOKS,
|
||||
},
|
||||
{
|
||||
key: "skills",
|
||||
label: t("omo.disabledSkills", { defaultValue: "Skills" }),
|
||||
values: disabledSkills,
|
||||
onChange: setDisabledSkills,
|
||||
placeholder: t("omo.disabledSkillsPlaceholder", {
|
||||
defaultValue: "Disabled Skills",
|
||||
}),
|
||||
presets: OMO_DISABLEABLE_SKILLS,
|
||||
},
|
||||
] as const;
|
||||
...(!isSlim
|
||||
? [
|
||||
{
|
||||
key: "skills" as const,
|
||||
label: t("omo.disabledSkills", { defaultValue: "Skills" }),
|
||||
values: disabledSkills,
|
||||
onChange: setDisabledSkills,
|
||||
placeholder: t("omo.disabledSkillsPlaceholder", {
|
||||
defaultValue: "Disabled Skills",
|
||||
}),
|
||||
presets: OMO_DISABLEABLE_SKILLS,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
const readLocalFile = useReadOmoLocalFile();
|
||||
const readLocalFile = isSlim ? slimReadLocal : standardReadLocal;
|
||||
|
||||
const handleImportGlobalFromLocal = useCallback(async () => {
|
||||
try {
|
||||
@@ -668,25 +698,27 @@ export const OmoGlobalConfigFields = forwardRef<
|
||||
<Input
|
||||
value={schemaUrl}
|
||||
onChange={(e) => setSchemaUrl(e.target.value)}
|
||||
placeholder={OMO_DEFAULT_SCHEMA_URL}
|
||||
placeholder={defaultSchemaUrl}
|
||||
className="text-sm h-8"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="rounded-md border border-border/40 bg-muted/10 p-2 space-y-2">
|
||||
<Label className="text-sm font-semibold">
|
||||
{t("omo.sisyphusAgentConfig", {
|
||||
defaultValue: "Sisyphus Agent",
|
||||
})}
|
||||
</Label>
|
||||
<Textarea
|
||||
value={sisyphusAgentStr}
|
||||
onChange={(e) => setSisyphusAgentStr(e.target.value)}
|
||||
placeholder={OMO_SISYPHUS_AGENT_PLACEHOLDER}
|
||||
className="font-mono text-sm"
|
||||
style={{ minHeight: "140px" }}
|
||||
/>
|
||||
</div>
|
||||
{!isSlim && (
|
||||
<div className="rounded-md border border-border/40 bg-muted/10 p-2 space-y-2">
|
||||
<Label className="text-sm font-semibold">
|
||||
{t("omo.sisyphusAgentConfig", {
|
||||
defaultValue: "Sisyphus Agent",
|
||||
})}
|
||||
</Label>
|
||||
<Textarea
|
||||
value={sisyphusAgentStr}
|
||||
onChange={(e) => setSisyphusAgentStr(e.target.value)}
|
||||
placeholder={OMO_SISYPHUS_AGENT_PLACEHOLDER}
|
||||
className="font-mono text-sm"
|
||||
style={{ minHeight: "140px" }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="rounded-md border border-border/40 bg-muted/10 p-2 space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -715,7 +747,9 @@ export const OmoGlobalConfigFields = forwardRef<
|
||||
<Label className="text-sm font-semibold">
|
||||
{t("omo.advanced", { defaultValue: "Advanced Settings" })}
|
||||
</Label>
|
||||
{OMO_ADVANCED_JSON_FIELDS.map((field) => (
|
||||
{OMO_ADVANCED_JSON_FIELDS.filter(
|
||||
(field) => !isSlim || OMO_SLIM_ADVANCED_KEYS.has(field.key),
|
||||
).map((field) => (
|
||||
<JsonTextareaField
|
||||
key={field.key}
|
||||
label={t(field.labelKey, { defaultValue: field.defaultLabel })}
|
||||
|
||||
@@ -78,7 +78,7 @@ import {
|
||||
useOmoDraftState,
|
||||
useOpenclawFormState,
|
||||
} from "./hooks";
|
||||
import { useOmoGlobalConfig } from "@/lib/query/omo";
|
||||
import { useOmoGlobalConfig, useOmoSlimGlobalConfig } from "@/lib/query/omo";
|
||||
import {
|
||||
CLAUDE_DEFAULT_CONFIG,
|
||||
CODEX_DEFAULT_CONFIG,
|
||||
@@ -184,7 +184,15 @@ export function ProviderForm({
|
||||
initialCategory: initialData?.category,
|
||||
});
|
||||
const isOmoCategory = appId === "opencode" && category === "omo";
|
||||
const { data: queriedOmoGlobalConfig } = useOmoGlobalConfig(isOmoCategory);
|
||||
const isOmoSlimCategory = appId === "opencode" && category === "omo-slim";
|
||||
const isAnyOmoCategory = isOmoCategory || isOmoSlimCategory;
|
||||
const { data: queriedStandardOmoGlobalConfig } =
|
||||
useOmoGlobalConfig(isOmoCategory);
|
||||
const { data: queriedSlimOmoGlobalConfig } =
|
||||
useOmoSlimGlobalConfig(isOmoSlimCategory);
|
||||
const queriedOmoGlobalConfig = isOmoSlimCategory
|
||||
? queriedSlimOmoGlobalConfig
|
||||
: queriedStandardOmoGlobalConfig;
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedPresetId(initialData ? null : "custom");
|
||||
@@ -495,7 +503,7 @@ export function ProviderForm({
|
||||
omoModelVariantsMap,
|
||||
omoPresetMetaMap,
|
||||
existingOpencodeKeys,
|
||||
} = useOmoModelSource({ isOmoCategory, providerId });
|
||||
} = useOmoModelSource({ isOmoCategory: isAnyOmoCategory, providerId });
|
||||
|
||||
const opencodeForm = useOpencodeFormState({
|
||||
initialData,
|
||||
@@ -506,7 +514,8 @@ export function ProviderForm({
|
||||
});
|
||||
|
||||
const initialOmoSettings =
|
||||
appId === "opencode" && initialData?.category === "omo"
|
||||
appId === "opencode" &&
|
||||
(initialData?.category === "omo" || initialData?.category === "omo-slim")
|
||||
? (initialData.settingsConfig as Record<string, unknown> | undefined)
|
||||
: undefined;
|
||||
|
||||
@@ -677,13 +686,19 @@ export function ProviderForm({
|
||||
} catch (err) {
|
||||
settingsConfig = values.settingsConfig.trim();
|
||||
}
|
||||
} else if (appId === "opencode" && category === "omo") {
|
||||
} else if (
|
||||
appId === "opencode" &&
|
||||
(category === "omo" || category === "omo-slim")
|
||||
) {
|
||||
const omoConfig: Record<string, unknown> = {};
|
||||
omoConfig.useCommonConfig = omoDraft.useOmoCommonConfig;
|
||||
if (Object.keys(omoDraft.omoAgents).length > 0) {
|
||||
omoConfig.agents = omoDraft.omoAgents;
|
||||
}
|
||||
if (Object.keys(omoDraft.omoCategories).length > 0) {
|
||||
if (
|
||||
category === "omo" &&
|
||||
Object.keys(omoDraft.omoCategories).length > 0
|
||||
) {
|
||||
omoConfig.categories = omoDraft.omoCategories;
|
||||
}
|
||||
if (omoDraft.omoOtherFieldsStr.trim()) {
|
||||
@@ -1334,19 +1349,25 @@ export function ProviderForm({
|
||||
/>
|
||||
)}
|
||||
|
||||
{appId === "opencode" && category === "omo" && (
|
||||
<OmoFormFields
|
||||
modelOptions={omoModelOptions}
|
||||
modelVariantsMap={omoModelVariantsMap}
|
||||
presetMetaMap={omoPresetMetaMap}
|
||||
agents={omoDraft.omoAgents}
|
||||
onAgentsChange={omoDraft.setOmoAgents}
|
||||
categories={omoDraft.omoCategories}
|
||||
onCategoriesChange={omoDraft.setOmoCategories}
|
||||
otherFieldsStr={omoDraft.omoOtherFieldsStr}
|
||||
onOtherFieldsStrChange={omoDraft.setOmoOtherFieldsStr}
|
||||
/>
|
||||
)}
|
||||
{appId === "opencode" &&
|
||||
(category === "omo" || category === "omo-slim") && (
|
||||
<OmoFormFields
|
||||
modelOptions={omoModelOptions}
|
||||
modelVariantsMap={omoModelVariantsMap}
|
||||
presetMetaMap={omoPresetMetaMap}
|
||||
agents={omoDraft.omoAgents}
|
||||
onAgentsChange={omoDraft.setOmoAgents}
|
||||
categories={
|
||||
category === "omo" ? omoDraft.omoCategories : undefined
|
||||
}
|
||||
onCategoriesChange={
|
||||
category === "omo" ? omoDraft.setOmoCategories : undefined
|
||||
}
|
||||
otherFieldsStr={omoDraft.omoOtherFieldsStr}
|
||||
onOtherFieldsStrChange={omoDraft.setOmoOtherFieldsStr}
|
||||
isSlim={category === "omo-slim"}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* OpenClaw 专属字段 */}
|
||||
{appId === "openclaw" && (
|
||||
@@ -1408,7 +1429,8 @@ export function ProviderForm({
|
||||
/>
|
||||
{settingsConfigErrorField}
|
||||
</>
|
||||
) : appId === "opencode" && category === "omo" ? (
|
||||
) : appId === "opencode" &&
|
||||
(category === "omo" || category === "omo-slim") ? (
|
||||
<OmoCommonConfigEditor
|
||||
previewValue={omoDraft.mergedOmoJsonPreview}
|
||||
useCommonConfig={omoDraft.useOmoCommonConfig}
|
||||
@@ -1421,8 +1443,11 @@ export function ProviderForm({
|
||||
onGlobalConfigStateChange={omoDraft.setOmoGlobalState}
|
||||
globalConfigRef={omoDraft.omoGlobalConfigRef}
|
||||
fieldsKey={omoDraft.omoFieldsKey}
|
||||
isSlim={category === "omo-slim"}
|
||||
/>
|
||||
) : appId === "opencode" && category !== "omo" ? (
|
||||
) : appId === "opencode" &&
|
||||
category !== "omo" &&
|
||||
category !== "omo-slim" ? (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="settingsConfig">{t("provider.configJson")}</Label>
|
||||
|
||||
@@ -2,7 +2,11 @@ import { useState, useCallback, useEffect, useRef, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import type { OmoGlobalConfig } from "@/types/omo";
|
||||
import { mergeOmoConfigPreview } from "@/types/omo";
|
||||
import {
|
||||
mergeOmoConfigPreview,
|
||||
mergeOmoSlimConfigPreview,
|
||||
buildOmoSlimProfilePreview,
|
||||
} from "@/types/omo";
|
||||
import { type OmoGlobalConfigFieldsRef } from "../OmoGlobalConfigFields";
|
||||
import * as configApi from "@/lib/api/config";
|
||||
import {
|
||||
@@ -54,6 +58,8 @@ export function useOmoDraftState({
|
||||
category,
|
||||
}: UseOmoDraftStateParams): OmoDraftState {
|
||||
const { t } = useTranslation();
|
||||
const isSlim = category === "omo-slim";
|
||||
const commonConfigKey = isSlim ? "omo_slim" : "omo";
|
||||
|
||||
const [omoAgents, setOmoAgents] = useState<
|
||||
Record<string, Record<string, unknown>>
|
||||
@@ -93,6 +99,14 @@ export function useOmoDraftState({
|
||||
|
||||
const mergedOmoJsonPreview = useMemo(() => {
|
||||
if (useOmoCommonConfig) {
|
||||
if (isSlim) {
|
||||
const merged = mergeOmoSlimConfigPreview(
|
||||
effectiveOmoGlobalConfig,
|
||||
omoAgents,
|
||||
omoOtherFieldsStr,
|
||||
);
|
||||
return JSON.stringify(merged, null, 2);
|
||||
}
|
||||
const merged = mergeOmoConfigPreview(
|
||||
effectiveOmoGlobalConfig,
|
||||
omoAgents,
|
||||
@@ -101,6 +115,13 @@ export function useOmoDraftState({
|
||||
);
|
||||
return JSON.stringify(merged, null, 2);
|
||||
} else {
|
||||
if (isSlim) {
|
||||
return JSON.stringify(
|
||||
buildOmoSlimProfilePreview(omoAgents, omoOtherFieldsStr),
|
||||
null,
|
||||
2,
|
||||
);
|
||||
}
|
||||
return JSON.stringify(
|
||||
buildOmoProfilePreview(omoAgents, omoCategories, omoOtherFieldsStr),
|
||||
null,
|
||||
@@ -113,16 +134,22 @@ export function useOmoDraftState({
|
||||
omoAgents,
|
||||
omoCategories,
|
||||
omoOtherFieldsStr,
|
||||
isSlim,
|
||||
]);
|
||||
|
||||
// Auto-detect whether common config has content for new OMO profiles
|
||||
// Auto-detect whether common config has content for new OMO/OMO Slim profiles
|
||||
useEffect(() => {
|
||||
if (appId !== "opencode" || category !== "omo" || isEditMode) return;
|
||||
if (
|
||||
appId !== "opencode" ||
|
||||
(category !== "omo" && category !== "omo-slim") ||
|
||||
isEditMode
|
||||
)
|
||||
return;
|
||||
let active = true;
|
||||
(async () => {
|
||||
let next = false;
|
||||
try {
|
||||
const raw = await configApi.getCommonConfigSnippet("omo");
|
||||
const raw = await configApi.getCommonConfigSnippet(commonConfigKey);
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
||||
next = Object.keys(parsed).some(
|
||||
@@ -135,14 +162,17 @@ export function useOmoDraftState({
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [appId, category, isEditMode]);
|
||||
}, [appId, category, isEditMode, commonConfigKey]);
|
||||
|
||||
const handleOmoGlobalConfigSave = useCallback(async () => {
|
||||
if (!omoGlobalConfigRef.current) return;
|
||||
setIsOmoSaving(true);
|
||||
try {
|
||||
const config = omoGlobalConfigRef.current.buildCurrentConfigStrict();
|
||||
await configApi.setCommonConfigSnippet("omo", JSON.stringify(config));
|
||||
await configApi.setCommonConfigSnippet(
|
||||
commonConfigKey,
|
||||
JSON.stringify(config),
|
||||
);
|
||||
setIsOmoConfigModalOpen(false);
|
||||
toast.success(
|
||||
t("omo.globalConfigSaved", { defaultValue: "Global config saved" }),
|
||||
@@ -152,7 +182,7 @@ export function useOmoDraftState({
|
||||
} finally {
|
||||
setIsOmoSaving(false);
|
||||
}
|
||||
}, [t]);
|
||||
}, [t, commonConfigKey]);
|
||||
|
||||
const handleOmoEditClick = useCallback(() => {
|
||||
setOmoFieldsKey((k) => k + 1);
|
||||
|
||||
@@ -133,7 +133,7 @@ export function useOmoModelSource({
|
||||
const parseFailedProviders: string[] = [];
|
||||
|
||||
for (const [providerKey, provider] of Object.entries(allProviders)) {
|
||||
if (provider.category === "omo") {
|
||||
if (provider.category === "omo" || provider.category === "omo-slim") {
|
||||
continue;
|
||||
}
|
||||
if (liveSet && !liveSet.has(providerKey)) {
|
||||
|
||||
@@ -1129,4 +1129,17 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
|
||||
iconColor: "#8B5CF6",
|
||||
isCustomTemplate: true,
|
||||
},
|
||||
{
|
||||
name: "Oh My OpenCode Slim",
|
||||
websiteUrl: "https://github.com/alvinunreal/oh-my-opencode-slim",
|
||||
settingsConfig: {
|
||||
npm: "",
|
||||
options: {},
|
||||
models: {},
|
||||
},
|
||||
category: "omo-slim" as ProviderCategory,
|
||||
icon: "opencode",
|
||||
iconColor: "#6366F1",
|
||||
isCustomTemplate: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1858,6 +1858,22 @@
|
||||
"unspecifiedLow": "Uncategorized low-effort task category for tasks that don't fit other categories with small workload. Defaults to Claude Sonnet 4.5.",
|
||||
"unspecifiedHigh": "Uncategorized high-effort task category for tasks that don't fit other categories with large workload. Defaults to Claude Opus 4.6 max variant.",
|
||||
"writing": "Writing category for documentation, prose and technical writing. Defaults to Gemini 3 Flash fast generation model."
|
||||
},
|
||||
"slimAgentDesc": {
|
||||
"orchestrator": "Orchestrator",
|
||||
"oracle": "Oracle",
|
||||
"librarian": "Librarian",
|
||||
"explorer": "Explorer",
|
||||
"designer": "Designer",
|
||||
"fixer": "Fixer"
|
||||
},
|
||||
"slimAgentTooltip": {
|
||||
"orchestrator": "Writes executable code, orchestrates multi-agent workflow, summons experts",
|
||||
"oracle": "Root cause analysis, architecture review, debugging guidance (read-only)",
|
||||
"librarian": "Documentation lookup, GitHub code search (read-only)",
|
||||
"explorer": "Regex search, AST pattern matching, file discovery (read-only)",
|
||||
"designer": "Modern responsive design, CSS/Tailwind expertise",
|
||||
"fixer": "Code implementation, refactoring, testing, verification"
|
||||
}
|
||||
},
|
||||
"openclawConfig": {
|
||||
|
||||
@@ -1839,6 +1839,22 @@
|
||||
"unspecifiedLow": "未分類の低作業量タスクカテゴリ。他のカテゴリに該当せず作業量が小さいタスクに適用。デフォルトで Claude Sonnet 4.5 を使用。",
|
||||
"unspecifiedHigh": "未分類の高作業量タスクカテゴリ。他のカテゴリに該当せず作業量が大きいタスクに適用。デフォルトで Claude Opus 4.6 の max バリアントを使用。",
|
||||
"writing": "ライティングカテゴリ。ドキュメント、散文、技術文書に特化。デフォルトで Gemini 3 Flash 高速生成モデルを使用。"
|
||||
},
|
||||
"slimAgentDesc": {
|
||||
"orchestrator": "オーケストレーター",
|
||||
"oracle": "オラクル",
|
||||
"librarian": "ライブラリアン",
|
||||
"explorer": "エクスプローラー",
|
||||
"designer": "デザイナー",
|
||||
"fixer": "フィクサー"
|
||||
},
|
||||
"slimAgentTooltip": {
|
||||
"orchestrator": "実行コードの作成、マルチエージェントワークフローの調整、エキスパートの召喚",
|
||||
"oracle": "根本原因分析、アーキテクチャレビュー、デバッグガイダンス(読み取り専用)",
|
||||
"librarian": "ドキュメント検索、GitHubコード検索(読み取り専用)",
|
||||
"explorer": "正規表現検索、ASTパターンマッチング、ファイル検出(読み取り専用)",
|
||||
"designer": "モダンなレスポンシブデザイン、CSS/Tailwindの専門知識",
|
||||
"fixer": "コード実装、リファクタリング、テスト、検証"
|
||||
}
|
||||
},
|
||||
"openclawConfig": {
|
||||
|
||||
@@ -1858,6 +1858,22 @@
|
||||
"unspecifiedLow": "未归类低工作量任务类别,适用于不适合其他类别且工作量较小的任务,默认使用 Claude Sonnet 4.5。",
|
||||
"unspecifiedHigh": "未归类高工作量任务类别,适用于不适合其他类别且工作量较大的任务,默认使用 Claude Opus 4.6 的最大变体。",
|
||||
"writing": "写作类别,专注于文档、散文和技术写作,默认使用 Gemini 3 Flash 快速生成模型。"
|
||||
},
|
||||
"slimAgentDesc": {
|
||||
"orchestrator": "编排者",
|
||||
"oracle": "神谕者",
|
||||
"librarian": "图书管理员",
|
||||
"explorer": "探索者",
|
||||
"designer": "设计师",
|
||||
"fixer": "修复者"
|
||||
},
|
||||
"slimAgentTooltip": {
|
||||
"orchestrator": "编写执行代码,编排多代理工作流,召唤专家",
|
||||
"oracle": "根本原因分析、架构审查、调试指导(只读)",
|
||||
"librarian": "文档查询、GitHub 代码搜索(只读)",
|
||||
"explorer": "正则搜索、AST 模式匹配、文件发现(只读)",
|
||||
"designer": "现代响应式设计、CSS/Tailwind 精通",
|
||||
"fixer": "代码实现、重构、测试、验证"
|
||||
}
|
||||
},
|
||||
"openclawConfig": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 配置相关 API
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
export type AppType = "claude" | "codex" | "gemini" | "omo";
|
||||
export type AppType = "claude" | "codex" | "gemini" | "omo" | "omo_slim";
|
||||
|
||||
/**
|
||||
* 获取 Claude 通用配置片段(已废弃,使用 getCommonConfigSnippet)
|
||||
|
||||
@@ -8,3 +8,13 @@ export const omoApi = {
|
||||
getOmoProviderCount: (): Promise<number> => invoke("get_omo_provider_count"),
|
||||
disableCurrentOmo: (): Promise<void> => invoke("disable_current_omo"),
|
||||
};
|
||||
|
||||
export const omoSlimApi = {
|
||||
readLocalFile: (): Promise<OmoLocalFileData> =>
|
||||
invoke("read_omo_slim_local_file"),
|
||||
getCurrentProviderId: (): Promise<string> =>
|
||||
invoke("get_current_omo_slim_provider_id"),
|
||||
getProviderCount: (): Promise<number> =>
|
||||
invoke("get_omo_slim_provider_count"),
|
||||
disableCurrent: (): Promise<void> => invoke("disable_current_omo_slim"),
|
||||
};
|
||||
|
||||
+100
-1
@@ -1,5 +1,5 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { omoApi } from "@/lib/api/omo";
|
||||
import { omoApi, omoSlimApi } from "@/lib/api/omo";
|
||||
import * as configApi from "@/lib/api/config";
|
||||
import type { OmoGlobalConfig } from "@/types/omo";
|
||||
|
||||
@@ -93,3 +93,102 @@ export function useDisableCurrentOmo() {
|
||||
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>,
|
||||
) {
|
||||
queryClient.invalidateQueries({ queryKey: omoSlimKeys.globalConfig() });
|
||||
queryClient.invalidateQueries({ queryKey: ["providers"] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: omoSlimKeys.currentProviderId(),
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: omoSlimKeys.providerCount() });
|
||||
}
|
||||
|
||||
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(),
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCurrentOmoSlimProviderId(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: omoSlimKeys.currentProviderId(),
|
||||
queryFn: omoSlimApi.getCurrentProviderId,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useOmoSlimProviderCount(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: omoSlimKeys.providerCount(),
|
||||
queryFn: omoSlimApi.getProviderCount,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
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 function useReadOmoSlimLocalFile() {
|
||||
return useMutation({
|
||||
mutationFn: () => omoSlimApi.readLocalFile(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useDisableCurrentOmoSlim() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: () => omoSlimApi.disableCurrent(),
|
||||
onSuccess: () => invalidateOmoSlimQueries(queryClient),
|
||||
});
|
||||
}
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@ export type ProviderCategory =
|
||||
| "aggregator" // 聚合网站
|
||||
| "third_party" // 第三方供应商
|
||||
| "custom" // 自定义
|
||||
| "omo"; // Oh My OpenCode
|
||||
| "omo" // Oh My OpenCode
|
||||
| "omo-slim"; // Oh My OpenCode Slim
|
||||
|
||||
export interface Provider {
|
||||
id: string;
|
||||
|
||||
@@ -327,6 +327,142 @@ export function parseOmoOtherFieldsObject(
|
||||
return parsed as Record<string, unknown>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OMO Slim (oh-my-opencode-slim) definitions
|
||||
// ============================================================================
|
||||
|
||||
export const OMO_SLIM_BUILTIN_AGENTS: OmoAgentDef[] = [
|
||||
{
|
||||
key: "orchestrator",
|
||||
display: "Orchestrator",
|
||||
descKey: "omo.slimAgentDesc.orchestrator",
|
||||
tooltipKey: "omo.slimAgentTooltip.orchestrator",
|
||||
recommended: "kimi-for-coding/k2p5",
|
||||
group: "main",
|
||||
},
|
||||
{
|
||||
key: "oracle",
|
||||
display: "Oracle",
|
||||
descKey: "omo.slimAgentDesc.oracle",
|
||||
tooltipKey: "omo.slimAgentTooltip.oracle",
|
||||
recommended: "openai/gpt-5.2-codex",
|
||||
group: "sub",
|
||||
},
|
||||
{
|
||||
key: "librarian",
|
||||
display: "Librarian",
|
||||
descKey: "omo.slimAgentDesc.librarian",
|
||||
tooltipKey: "omo.slimAgentTooltip.librarian",
|
||||
recommended: "openai/gpt-5.1-codex-mini",
|
||||
group: "sub",
|
||||
},
|
||||
{
|
||||
key: "explorer",
|
||||
display: "Explorer",
|
||||
descKey: "omo.slimAgentDesc.explorer",
|
||||
tooltipKey: "omo.slimAgentTooltip.explorer",
|
||||
recommended: "openai/gpt-5.1-codex-mini",
|
||||
group: "sub",
|
||||
},
|
||||
{
|
||||
key: "designer",
|
||||
display: "Designer",
|
||||
descKey: "omo.slimAgentDesc.designer",
|
||||
tooltipKey: "omo.slimAgentTooltip.designer",
|
||||
recommended: "kimi-for-coding/k2p5",
|
||||
group: "sub",
|
||||
},
|
||||
{
|
||||
key: "fixer",
|
||||
display: "Fixer",
|
||||
descKey: "omo.slimAgentDesc.fixer",
|
||||
tooltipKey: "omo.slimAgentTooltip.fixer",
|
||||
recommended: "openai/gpt-5.1-codex-mini",
|
||||
group: "sub",
|
||||
},
|
||||
];
|
||||
|
||||
export const OMO_SLIM_DISABLEABLE_AGENTS = [
|
||||
{ value: "orchestrator", label: "Orchestrator" },
|
||||
{ value: "oracle", label: "Oracle" },
|
||||
{ value: "librarian", label: "Librarian" },
|
||||
{ value: "explorer", label: "Explorer" },
|
||||
{ value: "designer", label: "Designer" },
|
||||
{ value: "fixer", label: "Fixer" },
|
||||
] as const;
|
||||
|
||||
export const OMO_SLIM_DISABLEABLE_MCPS = [
|
||||
{ value: "context7", label: "context7" },
|
||||
{ value: "grep_app", label: "grep_app" },
|
||||
{ value: "websearch", label: "websearch" },
|
||||
] as const;
|
||||
|
||||
export const OMO_SLIM_DISABLEABLE_HOOKS = [
|
||||
{ value: "auto-update-checker", label: "auto-update-checker" },
|
||||
{ value: "phase-reminder", label: "phase-reminder" },
|
||||
{ value: "post-read-nudge", label: "post-read-nudge" },
|
||||
] as const;
|
||||
|
||||
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(
|
||||
global: OmoGlobalConfig | undefined,
|
||||
agents: Record<string, Record<string, unknown>>,
|
||||
otherFieldsStr: string,
|
||||
): Record<string, unknown> {
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
if (global) {
|
||||
if (global.schemaUrl) result["$schema"] = global.schemaUrl;
|
||||
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.otherFields) {
|
||||
for (const [k, v] of Object.entries(global.otherFields)) {
|
||||
result[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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>>,
|
||||
|
||||
Reference in New Issue
Block a user