修复 Codex 切换供应商后历史记录变化 (#2349)

* Keep Codex history stable across provider switches

* Restore template Codex provider id when backfilling live config

Backfill writes the current Codex live config back to the previous
provider's stored template after a switch. Because the live file now
carries a normalized stable model_provider id, the previous provider's
template would lose its own provider-specific id (and any matching
[profiles.*] references) on every subsequent switch.

Reverse the normalization at backfill time by rewriting model_provider,
the active model_providers section, and matching profile references back
to the template's original id.

---------

Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
SaladDay
2026-04-30 09:54:25 +08:00
committed by GitHub
parent eb304232b3
commit a1e6c3b65d
6 changed files with 1043 additions and 29 deletions
+87
View File
@@ -139,6 +139,93 @@ fn sync_codex_provider_writes_auth_and_config() {
assert_eq!(synced_cfg, toml_text);
}
#[test]
fn sync_codex_provider_preserves_live_model_provider_id_for_history() {
let _guard = test_mutex().lock().expect("acquire test mutex");
reset_test_fs();
let legacy_auth = json!({ "OPENAI_API_KEY": "rightcode-key" });
let legacy_config = r#"model_provider = "rightcode"
model = "gpt-5.4"
[model_providers.rightcode]
name = "RightCode"
base_url = "https://rightcode.example/v1"
wire_api = "responses"
requires_openai_auth = true
"#;
cc_switch_lib::write_codex_live_atomic(&legacy_auth, Some(legacy_config))
.expect("seed existing Codex live config");
let mut config = MultiAppConfig::default();
let provider_config = json!({
"auth": {
"OPENAI_API_KEY": "fresh-key"
},
"config": r#"model_provider = "aihubmix"
model = "gpt-5.4"
[model_providers.aihubmix]
name = "AiHubMix"
base_url = "https://aihubmix.example/v1"
wire_api = "responses"
requires_openai_auth = true
"#
});
let provider = Provider::with_id(
"codex-1".to_string(),
"Codex Test".to_string(),
provider_config,
None,
);
let manager = config
.get_manager_mut(&AppType::Codex)
.expect("codex manager");
manager.providers.insert("codex-1".to_string(), provider);
manager.current = "codex-1".to_string();
ConfigService::sync_current_providers_to_live(&mut config).expect("sync codex live");
let toml_text =
fs::read_to_string(cc_switch_lib::get_codex_config_path()).expect("read config.toml");
let parsed: toml::Value = toml::from_str(&toml_text).expect("parse config.toml");
assert_eq!(
parsed.get("model_provider").and_then(|v| v.as_str()),
Some("rightcode"),
"legacy ConfigService sync should use the stable live provider id"
);
let model_providers = parsed
.get("model_providers")
.and_then(|v| v.as_table())
.expect("model_providers should exist");
assert!(
model_providers.get("aihubmix").is_none(),
"provider-specific target id should not be written to live config"
);
assert_eq!(
model_providers
.get("rightcode")
.and_then(|v| v.get("base_url"))
.and_then(|v| v.as_str()),
Some("https://aihubmix.example/v1")
);
let synced_cfg = config
.get_manager(&AppType::Codex)
.and_then(|manager| manager.providers.get("codex-1"))
.and_then(|provider| provider.settings_config.get("config"))
.and_then(|v| v.as_str())
.expect("synced config string");
assert!(
synced_cfg.contains("[model_providers.rightcode]"),
"ConfigService keeps its existing behavior of syncing provider config from live"
);
}
#[test]
fn sync_enabled_to_codex_writes_enabled_servers() {
let _guard = test_mutex().lock().expect("acquire test mutex");