diff --git a/src-tauri/src/services/provider/live.rs b/src-tauri/src/services/provider/live.rs index 7599b0ab7..0030c12a9 100644 --- a/src-tauri/src/services/provider/live.rs +++ b/src-tauri/src/services/provider/live.rs @@ -121,9 +121,32 @@ pub(crate) fn write_live_snapshot(app_type: &AppType, provider: &Provider) -> Re write_gemini_live(provider)?; } AppType::OpenCode => { - // OpenCode uses additive mode - providers are written directly to config - // This will be fully implemented in Phase 3 - log::debug!("OpenCode live snapshot not needed (additive mode)"); + // OpenCode uses additive mode - write provider to config + use crate::opencode_config; + use crate::provider::OpenCodeProviderConfig; + + // Convert settings_config to OpenCodeProviderConfig + let opencode_config_result = + serde_json::from_value::(provider.settings_config.clone()); + + match opencode_config_result { + Ok(config) => { + opencode_config::set_typed_provider(&provider.id, &config)?; + log::info!( + "OpenCode provider '{}' written to live config", + provider.id + ); + } + Err(e) => { + log::warn!( + "Failed to parse OpenCode provider config for '{}': {}", + provider.id, + e + ); + // Still try to write as raw JSON + opencode_config::set_provider(&provider.id, provider.settings_config.clone())?; + } + } } } Ok(()) @@ -437,3 +460,81 @@ pub(crate) fn write_gemini_live(provider: &Provider) -> Result<(), AppError> { Ok(()) } + +/// Remove an OpenCode provider from the live configuration +/// +/// This is specific to OpenCode's additive mode - removing a provider +/// from the opencode.json file. +pub(crate) fn remove_opencode_provider_from_live(provider_id: &str) -> Result<(), AppError> { + use crate::opencode_config; + + // Check if OpenCode config directory exists + if !opencode_config::get_opencode_dir().exists() { + log::debug!( + "OpenCode config directory doesn't exist, skipping removal of '{}'", + provider_id + ); + return Ok(()); + } + + opencode_config::remove_provider(provider_id)?; + log::info!( + "OpenCode provider '{}' removed from live config", + provider_id + ); + + Ok(()) +} + +/// Import all providers from OpenCode live config to database +/// +/// This imports existing providers from ~/.config/opencode/opencode.json +/// into the CC Switch database. Each provider found will be added to the +/// database with is_current set to false. +pub fn import_opencode_providers_from_live(state: &AppState) -> Result { + use crate::opencode_config; + + let providers = opencode_config::get_typed_providers()?; + if providers.is_empty() { + return Ok(0); + } + + let mut imported = 0; + let existing = state.db.get_all_providers("opencode")?; + + for (id, config) in providers { + // Skip if already exists in database + if existing.contains_key(&id) { + log::debug!("OpenCode provider '{}' already exists in database, skipping", id); + continue; + } + + // Convert to Value for settings_config + let settings_config = match serde_json::to_value(&config) { + Ok(v) => v, + Err(e) => { + log::warn!("Failed to serialize OpenCode provider '{}': {}", id, e); + continue; + } + }; + + // Create provider + let provider = Provider::with_id( + id.clone(), + config.name.clone().unwrap_or_else(|| id.clone()), + settings_config, + None, + ); + + // Save to database + if let Err(e) = state.db.save_provider("opencode", &provider) { + log::warn!("Failed to import OpenCode provider '{}': {}", id, e); + continue; + } + + imported += 1; + log::info!("Imported OpenCode provider '{}' from live config", id); + } + + Ok(imported) +} diff --git a/src-tauri/src/services/provider/mod.rs b/src-tauri/src/services/provider/mod.rs index 0c48ed437..a749e7700 100644 --- a/src-tauri/src/services/provider/mod.rs +++ b/src-tauri/src/services/provider/mod.rs @@ -20,13 +20,16 @@ use crate::settings::CustomEndpoint; use crate::store::AppState; // Re-export sub-module functions for external access -pub use live::{import_default_config, read_live_settings, sync_current_to_live}; +pub use live::{ + import_default_config, import_opencode_providers_from_live, read_live_settings, + sync_current_to_live, +}; // Internal re-exports (pub(crate)) pub(crate) use live::write_live_snapshot; // Internal re-exports -use live::write_gemini_live; +use live::{remove_opencode_provider_from_live, write_gemini_live}; use usage::validate_usage_script; /// Provider business logic service @@ -152,7 +155,13 @@ impl ProviderService { // Save to database state.db.save_provider(app_type.as_str(), &provider)?; - // Check if sync is needed (if this is current provider, or no current provider) + // OpenCode uses additive mode - always write to live config + if matches!(app_type, AppType::OpenCode) { + write_live_snapshot(&app_type, &provider)?; + return Ok(true); + } + + // For other apps: Check if sync is needed (if this is current provider, or no current provider) let current = state.db.get_current_provider(app_type.as_str())?; if current.is_none() { // No current provider, set as current and sync @@ -176,14 +185,20 @@ impl ProviderService { Self::normalize_provider_if_claude(&app_type, &mut provider); Self::validate_provider_settings(&app_type, &provider)?; - // Check if this is current provider (use effective current, not just DB) + // Save to database + state.db.save_provider(app_type.as_str(), &provider)?; + + // OpenCode uses additive mode - always update in live config + if matches!(app_type, AppType::OpenCode) { + write_live_snapshot(&app_type, &provider)?; + return Ok(true); + } + + // For other apps: Check if this is current provider (use effective current, not just DB) let effective_current = crate::settings::get_effective_current_provider(&state.db, &app_type)?; let is_current = effective_current.as_deref() == Some(provider.id.as_str()); - // Save to database - state.db.save_provider(app_type.as_str(), &provider)?; - if is_current { // 如果代理接管模式处于激活状态,并且代理服务正在运行: // - 不写 Live 配置(否则会破坏接管) @@ -216,8 +231,18 @@ impl ProviderService { /// Delete a provider /// /// 同时检查本地 settings 和数据库的当前供应商,防止删除任一端正在使用的供应商。 + /// 对于 OpenCode(累加模式),可以随时删除任意供应商,同时从 live 配置中移除。 pub fn delete(state: &AppState, app_type: AppType, id: &str) -> Result<(), AppError> { - // Check both local settings and database + // OpenCode uses additive mode - no current provider concept + if matches!(app_type, AppType::OpenCode) { + // Remove from database + state.db.delete_provider(app_type.as_str(), id)?; + // Also remove from live config + remove_opencode_provider_from_live(id)?; + return Ok(()); + } + + // For other apps: Check both local settings and database let local_current = crate::settings::get_current_provider(&app_type); let db_current = state.db.get_current_provider(app_type.as_str())?;