mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-05-25 07:20:41 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 687a73a88f |
@@ -307,9 +307,12 @@ pub async fn testUsageScript(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn read_live_provider_settings(app: String) -> Result<serde_json::Value, String> {
|
pub fn read_live_provider_settings(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
app: String,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?;
|
let app_type = AppType::from_str(&app).map_err(|e| e.to_string())?;
|
||||||
ProviderService::read_live_settings(app_type).map_err(|e| e.to_string())
|
ProviderService::read_live_settings(&state, app_type).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|||||||
@@ -1348,6 +1348,7 @@ fn initialize_common_config_snippets(state: &store::AppState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let settings = match crate::services::provider::ProviderService::read_live_settings(
|
let settings = match crate::services::provider::ProviderService::read_live_settings(
|
||||||
|
state,
|
||||||
app_type.clone(),
|
app_type.clone(),
|
||||||
) {
|
) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
|
|||||||
@@ -851,6 +851,26 @@ pub(crate) fn sync_current_provider_for_app_to_live(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_codex_live_settings_with_auth_fallback(
|
||||||
|
fallback_auth: Option<Value>,
|
||||||
|
) -> Result<Value, AppError> {
|
||||||
|
let auth_path = get_codex_auth_path();
|
||||||
|
let auth = if auth_path.exists() {
|
||||||
|
read_json_file(&auth_path)?
|
||||||
|
} else if let Some(auth) = fallback_auth {
|
||||||
|
auth
|
||||||
|
} else {
|
||||||
|
return Err(AppError::localized(
|
||||||
|
"codex.auth.missing",
|
||||||
|
"Codex 配置文件不存在:缺少 auth.json",
|
||||||
|
"Codex configuration missing: auth.json not found",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let cfg_text = crate::codex_config::read_and_validate_codex_config_text()?;
|
||||||
|
Ok(json!({ "auth": auth, "config": cfg_text }))
|
||||||
|
}
|
||||||
|
|
||||||
/// Sync current provider to live configuration
|
/// Sync current provider to live configuration
|
||||||
///
|
///
|
||||||
/// 使用有效的当前供应商 ID(验证过存在性)。
|
/// 使用有效的当前供应商 ID(验证过存在性)。
|
||||||
@@ -895,22 +915,20 @@ pub fn sync_current_to_live(state: &AppState) -> Result<(), AppError> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn read_live_settings_with_auth_fallback(
|
||||||
|
app_type: AppType,
|
||||||
|
fallback_auth: Option<Value>,
|
||||||
|
) -> Result<Value, AppError> {
|
||||||
|
match app_type {
|
||||||
|
AppType::Codex => read_codex_live_settings_with_auth_fallback(fallback_auth),
|
||||||
|
_ => read_live_settings(app_type),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Read current live settings for an app type
|
/// Read current live settings for an app type
|
||||||
pub fn read_live_settings(app_type: AppType) -> Result<Value, AppError> {
|
pub fn read_live_settings(app_type: AppType) -> Result<Value, AppError> {
|
||||||
match app_type {
|
match app_type {
|
||||||
AppType::Codex => {
|
AppType::Codex => read_codex_live_settings_with_auth_fallback(None),
|
||||||
let auth_path = get_codex_auth_path();
|
|
||||||
if !auth_path.exists() {
|
|
||||||
return Err(AppError::localized(
|
|
||||||
"codex.auth.missing",
|
|
||||||
"Codex 配置文件不存在:缺少 auth.json",
|
|
||||||
"Codex configuration missing: auth.json not found",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let auth: Value = read_json_file(&auth_path)?;
|
|
||||||
let cfg_text = crate::codex_config::read_and_validate_codex_config_text()?;
|
|
||||||
Ok(json!({ "auth": auth, "config": cfg_text }))
|
|
||||||
}
|
|
||||||
AppType::Claude => {
|
AppType::Claude => {
|
||||||
let path = get_claude_settings_path();
|
let path = get_claude_settings_path();
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
|
|||||||
@@ -22,15 +22,16 @@ use crate::store::AppState;
|
|||||||
// Re-export sub-module functions for external access
|
// Re-export sub-module functions for external access
|
||||||
pub use live::{
|
pub use live::{
|
||||||
import_default_config, import_openclaw_providers_from_live,
|
import_default_config, import_openclaw_providers_from_live,
|
||||||
import_opencode_providers_from_live, read_live_settings, sync_current_to_live,
|
import_opencode_providers_from_live, sync_current_to_live,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Internal re-exports (pub(crate))
|
// Internal re-exports (pub(crate))
|
||||||
pub(crate) use live::sanitize_claude_settings_for_live;
|
pub(crate) use live::sanitize_claude_settings_for_live;
|
||||||
pub(crate) use live::{
|
pub(crate) use live::{
|
||||||
build_effective_settings_with_common_config, normalize_provider_common_config_for_storage,
|
build_effective_settings_with_common_config, normalize_provider_common_config_for_storage,
|
||||||
provider_exists_in_live_config, strip_common_config_from_live_settings,
|
provider_exists_in_live_config, read_live_settings_with_auth_fallback,
|
||||||
sync_current_provider_for_app_to_live, write_live_with_common_config,
|
strip_common_config_from_live_settings, sync_current_provider_for_app_to_live,
|
||||||
|
write_live_with_common_config,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Internal re-exports
|
// Internal re-exports
|
||||||
@@ -1473,8 +1474,16 @@ impl ProviderService {
|
|||||||
// no backfill needed (backfill is for exclusive mode apps like Claude/Codex/Gemini)
|
// no backfill needed (backfill is for exclusive mode apps like Claude/Codex/Gemini)
|
||||||
if !app_type.is_additive_mode() {
|
if !app_type.is_additive_mode() {
|
||||||
// Only backfill when switching to a different provider
|
// Only backfill when switching to a different provider
|
||||||
if let Ok(live_config) = read_live_settings(app_type.clone()) {
|
if let Some(mut current_provider) = providers.get(¤t_id).cloned() {
|
||||||
if let Some(mut current_provider) = providers.get(¤t_id).cloned() {
|
let fallback_auth = if matches!(app_type, AppType::Codex) {
|
||||||
|
current_provider.settings_config.get("auth").cloned()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(live_config) =
|
||||||
|
read_live_settings_with_auth_fallback(app_type.clone(), fallback_auth)
|
||||||
|
{
|
||||||
current_provider.settings_config =
|
current_provider.settings_config =
|
||||||
strip_common_config_from_live_settings(
|
strip_common_config_from_live_settings(
|
||||||
state.db.as_ref(),
|
state.db.as_ref(),
|
||||||
@@ -1897,8 +1906,21 @@ impl ProviderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read current live settings (re-export)
|
/// Read current live settings (re-export)
|
||||||
pub fn read_live_settings(app_type: AppType) -> Result<Value, AppError> {
|
pub fn read_live_settings(state: &AppState, app_type: AppType) -> Result<Value, AppError> {
|
||||||
read_live_settings(app_type)
|
let fallback_auth = if matches!(app_type, AppType::Codex) {
|
||||||
|
let current_id = crate::settings::get_effective_current_provider(&state.db, &app_type)?;
|
||||||
|
match current_id {
|
||||||
|
Some(current_id) => state
|
||||||
|
.db
|
||||||
|
.get_provider_by_id(¤t_id, app_type.as_str())?
|
||||||
|
.and_then(|provider| provider.settings_config.get("auth").cloned()),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
read_live_settings_with_auth_fallback(app_type, fallback_auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get custom endpoints list (re-export)
|
/// Get custom endpoints list (re-export)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use cc_switch_lib::{
|
use cc_switch_lib::{
|
||||||
get_claude_settings_path, read_json_file, write_codex_live_atomic, AppError, AppType, McpApps,
|
get_claude_settings_path, get_codex_config_path, read_json_file, write_codex_live_atomic,
|
||||||
McpServer, MultiAppConfig, Provider, ProviderMeta, ProviderService,
|
AppError, AppType, McpApps, McpServer, MultiAppConfig, Provider, ProviderMeta, ProviderService,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "support.rs"]
|
#[path = "support.rs"]
|
||||||
@@ -238,6 +238,145 @@ command = "say"
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn provider_service_switch_codex_backfills_current_provider_when_auth_json_missing() {
|
||||||
|
let _guard = test_mutex().lock().expect("acquire test mutex");
|
||||||
|
reset_test_fs();
|
||||||
|
let _home = ensure_test_home();
|
||||||
|
|
||||||
|
let live_config = r#"[mcp_servers.legacy]
|
||||||
|
type = "stdio"
|
||||||
|
command = "echo"
|
||||||
|
"#;
|
||||||
|
let config_path = get_codex_config_path();
|
||||||
|
if let Some(parent) = config_path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).expect("create codex dir");
|
||||||
|
}
|
||||||
|
std::fs::write(&config_path, live_config).expect("seed codex config without auth.json");
|
||||||
|
|
||||||
|
let mut initial_config = MultiAppConfig::default();
|
||||||
|
{
|
||||||
|
let manager = initial_config
|
||||||
|
.get_manager_mut(&AppType::Codex)
|
||||||
|
.expect("codex manager");
|
||||||
|
manager.current = "old-provider".to_string();
|
||||||
|
manager.providers.insert(
|
||||||
|
"old-provider".to_string(),
|
||||||
|
Provider::with_id(
|
||||||
|
"old-provider".to_string(),
|
||||||
|
"Legacy".to_string(),
|
||||||
|
json!({
|
||||||
|
"auth": {"OPENAI_API_KEY": "db-key"},
|
||||||
|
"config": "stale-config"
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
manager.providers.insert(
|
||||||
|
"new-provider".to_string(),
|
||||||
|
Provider::with_id(
|
||||||
|
"new-provider".to_string(),
|
||||||
|
"Latest".to_string(),
|
||||||
|
json!({
|
||||||
|
"auth": {"OPENAI_API_KEY": "fresh-key"},
|
||||||
|
"config": r#"[mcp_servers.latest]
|
||||||
|
type = "stdio"
|
||||||
|
command = "say"
|
||||||
|
"#
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = create_test_state_with_config(&initial_config).expect("create test state");
|
||||||
|
|
||||||
|
ProviderService::switch(&state, AppType::Codex, "new-provider")
|
||||||
|
.expect("switch provider should succeed without auth.json");
|
||||||
|
|
||||||
|
let providers = state
|
||||||
|
.db
|
||||||
|
.get_all_providers(AppType::Codex.as_str())
|
||||||
|
.expect("read providers after switch");
|
||||||
|
let legacy = providers
|
||||||
|
.get("old-provider")
|
||||||
|
.expect("legacy provider should still exist");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
legacy
|
||||||
|
.settings_config
|
||||||
|
.get("auth")
|
||||||
|
.and_then(|v| v.get("OPENAI_API_KEY"))
|
||||||
|
.and_then(|v| v.as_str()),
|
||||||
|
Some("db-key"),
|
||||||
|
"missing auth.json should fall back to the provider's stored auth during backfill"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
legacy
|
||||||
|
.settings_config
|
||||||
|
.get("config")
|
||||||
|
.and_then(|v| v.as_str()),
|
||||||
|
Some(live_config),
|
||||||
|
"backfill should still capture the current live config.toml when auth.json is missing"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn provider_service_read_live_settings_uses_current_provider_auth_when_auth_json_missing() {
|
||||||
|
let _guard = test_mutex().lock().expect("acquire test mutex");
|
||||||
|
reset_test_fs();
|
||||||
|
let _home = ensure_test_home();
|
||||||
|
|
||||||
|
let live_config = r#"[mcp_servers.current]
|
||||||
|
type = "stdio"
|
||||||
|
command = "echo"
|
||||||
|
"#;
|
||||||
|
let config_path = get_codex_config_path();
|
||||||
|
if let Some(parent) = config_path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).expect("create codex dir");
|
||||||
|
}
|
||||||
|
std::fs::write(&config_path, live_config).expect("seed codex config without auth.json");
|
||||||
|
|
||||||
|
let mut initial_config = MultiAppConfig::default();
|
||||||
|
{
|
||||||
|
let manager = initial_config
|
||||||
|
.get_manager_mut(&AppType::Codex)
|
||||||
|
.expect("codex manager");
|
||||||
|
manager.current = "current-provider".to_string();
|
||||||
|
manager.providers.insert(
|
||||||
|
"current-provider".to_string(),
|
||||||
|
Provider::with_id(
|
||||||
|
"current-provider".to_string(),
|
||||||
|
"Current".to_string(),
|
||||||
|
json!({
|
||||||
|
"auth": {"OPENAI_API_KEY": "db-key"},
|
||||||
|
"config": "provider-config"
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = create_test_state_with_config(&initial_config).expect("create test state");
|
||||||
|
|
||||||
|
let settings = ProviderService::read_live_settings(&state, AppType::Codex)
|
||||||
|
.expect("should recover codex live settings from provider auth");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
settings
|
||||||
|
.get("auth")
|
||||||
|
.and_then(|v| v.get("OPENAI_API_KEY"))
|
||||||
|
.and_then(|v| v.as_str()),
|
||||||
|
Some("db-key"),
|
||||||
|
"live settings should reuse stored provider auth when auth.json is missing"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
settings.get("config").and_then(|v| v.as_str()),
|
||||||
|
Some(live_config),
|
||||||
|
"live settings should still read config.toml from disk"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sync_current_provider_for_app_keeps_live_takeover_and_updates_restore_backup() {
|
fn sync_current_provider_for_app_keeps_live_takeover_and_updates_restore_backup() {
|
||||||
let _guard = test_mutex().lock().expect("acquire test mutex");
|
let _guard = test_mutex().lock().expect("acquire test mutex");
|
||||||
|
|||||||
Reference in New Issue
Block a user