mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-04-10 05:00:17 +08:00
fix(provider): add backfill and Gemini security flags to switch function
The switch function was missing two important features after the SQLite migration: 1. Backfill mechanism: Before switching providers, read the current live config and save it back to the current provider. This preserves any manual edits users made to the live config file. 2. Gemini security flags: When switching to a Gemini provider, set the appropriate security.auth.selectedType: - PackyCode providers: "gemini-api-key" - Google OAuth providers: "oauth-personal" Also update tests to: - Use the new unified MCP structure (mcp.servers) instead of the legacy per-app structure (mcp.codex.servers) - Expect backfill behavior (was incorrectly marked as "no backfill") - Remove assertions for provider-specific file deletion (v3.7.0+ uses SSOT, no longer creates per-provider config files)
This commit is contained in:
@@ -1378,6 +1378,14 @@ impl ProviderService {
|
||||
}
|
||||
|
||||
/// 切换供应商
|
||||
///
|
||||
/// 切换流程:
|
||||
/// 1. 验证目标供应商存在
|
||||
/// 2. **回填机制**:将当前 live 配置回填到当前供应商,保护用户手动修改
|
||||
/// 3. 设置新的当前供应商
|
||||
/// 4. 将目标供应商配置写入 live 文件
|
||||
/// 5. Gemini 额外处理安全标志
|
||||
/// 6. 同步 MCP 配置
|
||||
pub fn switch(state: &AppState, app_type: AppType, id: &str) -> Result<(), AppError> {
|
||||
// Check if provider exists
|
||||
let providers = state.db.get_all_providers(app_type.as_str())?;
|
||||
@@ -1385,12 +1393,36 @@ impl ProviderService {
|
||||
.get(id)
|
||||
.ok_or_else(|| AppError::Message(format!("供应商 {id} 不存在")))?;
|
||||
|
||||
// Backfill: 将当前 live 配置回填到当前供应商
|
||||
if let Some(current_id) = state.db.get_current_provider(app_type.as_str())? {
|
||||
if current_id != id {
|
||||
// 只有在切换到不同供应商时才回填
|
||||
if let Ok(live_config) = Self::read_live_settings(app_type.clone()) {
|
||||
if let Some(mut current_provider) = providers.get(¤t_id).cloned() {
|
||||
current_provider.settings_config = live_config;
|
||||
// 忽略回填失败,不影响切换流程
|
||||
let _ = state.db.save_provider(app_type.as_str(), ¤t_provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set current
|
||||
state.db.set_current_provider(app_type.as_str(), id)?;
|
||||
|
||||
// Sync to live
|
||||
Self::write_live_snapshot(&app_type, provider)?;
|
||||
|
||||
// Gemini 需要额外处理安全标志(PackyCode 或 Google OAuth)
|
||||
if matches!(app_type, AppType::Gemini) {
|
||||
let auth_type = Self::detect_gemini_auth_type(provider);
|
||||
match auth_type {
|
||||
GeminiAuthType::GoogleOfficial => Self::ensure_google_oauth_security_flag(provider)?,
|
||||
GeminiAuthType::Packycode => Self::ensure_packycode_security_flag(provider)?,
|
||||
GeminiAuthType::Generic => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Sync MCP
|
||||
use crate::services::mcp::McpService;
|
||||
McpService::sync_all_enabled(state)?;
|
||||
|
||||
@@ -139,11 +139,11 @@ command = "say"
|
||||
.and_then(|v| v.get("OPENAI_API_KEY"))
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
// 注意:v3.7.0+ 的 switch 实现不再 backfill 旧供应商
|
||||
// 旧供应商保持其原始配置不变
|
||||
// 回填机制:切换前会将 live 配置回填到当前供应商
|
||||
// 这保护了用户在 live 文件中的手动修改
|
||||
assert_eq!(
|
||||
legacy_auth_value, "stale",
|
||||
"previous provider should retain its original auth (no backfill in v3.7.0+)"
|
||||
legacy_auth_value, "legacy-key",
|
||||
"previous provider should be backfilled with live auth"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -251,16 +251,11 @@ fn switch_provider_updates_claude_live_and_state() {
|
||||
let legacy_provider = providers
|
||||
.get("old-provider")
|
||||
.expect("legacy provider still exists");
|
||||
// 注意:v3.7.0+ 的 switch 实现不再 backfill 旧供应商
|
||||
// 旧供应商保持其原始配置不变
|
||||
// 回填机制:切换前会将 live 配置回填到当前供应商
|
||||
// 这保护了用户在 live 文件中的手动修改
|
||||
assert_eq!(
|
||||
legacy_provider
|
||||
.settings_config
|
||||
.get("env")
|
||||
.and_then(|env| env.get("ANTHROPIC_API_KEY"))
|
||||
.and_then(|key| key.as_str()),
|
||||
Some("stale-key"),
|
||||
"previous provider should retain its original config (no backfill in v3.7.0+)"
|
||||
legacy_provider.settings_config, legacy_live,
|
||||
"previous provider should be backfilled with live config"
|
||||
);
|
||||
|
||||
let new_provider = providers
|
||||
|
||||
@@ -2,7 +2,7 @@ use serde_json::json;
|
||||
|
||||
use cc_switch_lib::{
|
||||
get_claude_settings_path, read_json_file, write_codex_live_atomic, AppError, AppType,
|
||||
MultiAppConfig, Provider, ProviderMeta, ProviderService,
|
||||
McpApps, McpServer, MultiAppConfig, Provider, ProviderMeta, ProviderService,
|
||||
};
|
||||
|
||||
#[path = "support.rs"]
|
||||
@@ -68,16 +68,27 @@ command = "say"
|
||||
);
|
||||
}
|
||||
|
||||
initial_config.mcp.codex.servers.insert(
|
||||
// 使用新的统一 MCP 结构(v3.7.0+)
|
||||
let servers = initial_config.mcp.servers.get_or_insert_with(Default::default);
|
||||
servers.insert(
|
||||
"echo-server".into(),
|
||||
json!({
|
||||
"id": "echo-server",
|
||||
"enabled": true,
|
||||
"server": {
|
||||
McpServer {
|
||||
id: "echo-server".into(),
|
||||
name: "Echo Server".into(),
|
||||
server: json!({
|
||||
"type": "stdio",
|
||||
"command": "echo"
|
||||
}
|
||||
}),
|
||||
}),
|
||||
apps: McpApps {
|
||||
claude: false,
|
||||
codex: true,
|
||||
gemini: false,
|
||||
},
|
||||
description: None,
|
||||
homepage: None,
|
||||
docs: None,
|
||||
tags: Vec::new(),
|
||||
},
|
||||
);
|
||||
|
||||
let state = create_test_state_with_config(&initial_config).expect("create test state");
|
||||
@@ -115,9 +126,15 @@ command = "say"
|
||||
.get("config")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or_default();
|
||||
assert_eq!(
|
||||
new_config_text, config_text,
|
||||
"provider config snapshot should match live file"
|
||||
// provider 存储的是原始配置,不包含 MCP 同步后的内容
|
||||
assert!(
|
||||
new_config_text.contains("mcp_servers.latest"),
|
||||
"provider config should contain original MCP servers"
|
||||
);
|
||||
// live 文件额外包含同步的 MCP 服务器
|
||||
assert!(
|
||||
config_text.contains("mcp_servers.echo-server"),
|
||||
"live config should include synced MCP servers"
|
||||
);
|
||||
|
||||
let legacy = providers
|
||||
@@ -570,10 +587,8 @@ fn provider_service_delete_claude_removes_provider_files() {
|
||||
!providers.contains_key("delete"),
|
||||
"claude provider should be removed"
|
||||
);
|
||||
assert!(
|
||||
!by_name.exists() && !by_id.exists(),
|
||||
"provider config files should be deleted"
|
||||
);
|
||||
// v3.7.0+ 不再使用供应商特定文件(如 settings-*.json)
|
||||
// 删除供应商只影响数据库记录,不清理这些旧格式文件
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user