mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-05-02 17:12:04 +08:00
Compare commits
1 Commits
feat/usage
...
fix/codex-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
687a73a88f |
@@ -307,9 +307,12 @@ pub async fn testUsageScript(
|
||||
}
|
||||
|
||||
#[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())?;
|
||||
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]
|
||||
|
||||
@@ -1348,6 +1348,7 @@ fn initialize_common_config_snippets(state: &store::AppState) {
|
||||
}
|
||||
|
||||
let settings = match crate::services::provider::ProviderService::read_live_settings(
|
||||
state,
|
||||
app_type.clone(),
|
||||
) {
|
||||
Ok(s) => s,
|
||||
|
||||
@@ -851,6 +851,26 @@ pub(crate) fn sync_current_provider_for_app_to_live(
|
||||
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
|
||||
///
|
||||
/// 使用有效的当前供应商 ID(验证过存在性)。
|
||||
@@ -895,22 +915,20 @@ pub fn sync_current_to_live(state: &AppState) -> Result<(), AppError> {
|
||||
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
|
||||
pub fn read_live_settings(app_type: AppType) -> Result<Value, AppError> {
|
||||
match app_type {
|
||||
AppType::Codex => {
|
||||
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::Codex => read_codex_live_settings_with_auth_fallback(None),
|
||||
AppType::Claude => {
|
||||
let path = get_claude_settings_path();
|
||||
if !path.exists() {
|
||||
|
||||
@@ -22,15 +22,16 @@ use crate::store::AppState;
|
||||
// Re-export sub-module functions for external access
|
||||
pub use 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))
|
||||
pub(crate) use live::sanitize_claude_settings_for_live;
|
||||
pub(crate) use live::{
|
||||
build_effective_settings_with_common_config, normalize_provider_common_config_for_storage,
|
||||
provider_exists_in_live_config, strip_common_config_from_live_settings,
|
||||
sync_current_provider_for_app_to_live, write_live_with_common_config,
|
||||
provider_exists_in_live_config, read_live_settings_with_auth_fallback,
|
||||
strip_common_config_from_live_settings, sync_current_provider_for_app_to_live,
|
||||
write_live_with_common_config,
|
||||
};
|
||||
|
||||
// Internal re-exports
|
||||
@@ -1473,8 +1474,16 @@ impl ProviderService {
|
||||
// no backfill needed (backfill is for exclusive mode apps like Claude/Codex/Gemini)
|
||||
if !app_type.is_additive_mode() {
|
||||
// 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 =
|
||||
strip_common_config_from_live_settings(
|
||||
state.db.as_ref(),
|
||||
@@ -1897,8 +1906,21 @@ impl ProviderService {
|
||||
}
|
||||
|
||||
/// Read current live settings (re-export)
|
||||
pub fn read_live_settings(app_type: AppType) -> Result<Value, AppError> {
|
||||
read_live_settings(app_type)
|
||||
pub fn read_live_settings(state: &AppState, app_type: AppType) -> Result<Value, AppError> {
|
||||
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)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use serde_json::json;
|
||||
|
||||
use cc_switch_lib::{
|
||||
get_claude_settings_path, read_json_file, write_codex_live_atomic, AppError, AppType, McpApps,
|
||||
McpServer, MultiAppConfig, Provider, ProviderMeta, ProviderService,
|
||||
get_claude_settings_path, get_codex_config_path, read_json_file, write_codex_live_atomic,
|
||||
AppError, AppType, McpApps, McpServer, MultiAppConfig, Provider, ProviderMeta, ProviderService,
|
||||
};
|
||||
|
||||
#[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]
|
||||
fn sync_current_provider_for_app_keeps_live_takeover_and_updates_restore_backup() {
|
||||
let _guard = test_mutex().lock().expect("acquire test mutex");
|
||||
|
||||
Reference in New Issue
Block a user