Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src-tauri/src/services/provider/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,7 @@ impl ProviderService {
action.app_type.as_str(),
&action.provider,
action.previous_provider.as_ref(),
action.common_config_snippet.as_deref(),
))
.map_err(AppError::Message)?;
PreparedPostCommitEffect::ProxyLiveBackup(backup_snapshot)
Expand Down Expand Up @@ -822,6 +823,16 @@ impl ProviderService {
.save_live_backup_snapshot(prepared.action.app_type.as_str(), snapshot),
)
.map_err(AppError::Message)?;

// Refresh active live settings under takeover
if matches!(prepared.action.app_type, AppType::Claude) {
futures::executor::block_on(
state.proxy_service.sync_claude_live_from_provider_while_proxy_active(
&prepared.action.provider,
),
)
.map_err(AppError::Message)?;
}
}
}

Expand Down
25 changes: 16 additions & 9 deletions src-tauri/src/services/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1591,7 +1591,7 @@ impl ProxyService {
original_live: &Value,
provider: &Provider,
) -> Result<Value, String> {
let mut provider_snapshot = self.build_live_snapshot_from_provider(app_type, provider)?;
let mut provider_snapshot = self.build_live_snapshot_from_provider(app_type, provider, None)?;
Self::apply_codex_unified_session_bucket_to_backup(
app_type,
provider,
Expand Down Expand Up @@ -2086,7 +2086,7 @@ impl ProxyService {
) -> Result<(), String> {
let app_type_enum = Self::takeover_app_from_str(app_type)?;
let mut backup_snapshot =
self.build_live_snapshot_from_provider(&app_type_enum, provider)?;
self.build_live_snapshot_from_provider(&app_type_enum, provider, None)?;
Self::apply_codex_unified_session_bucket_to_backup(
&app_type_enum,
provider,
Expand All @@ -2108,17 +2108,20 @@ impl ProxyService {
app_type: &str,
provider: &Provider,
previous_provider: Option<&Provider>,
snippet_override: Option<&str>,
) -> Result<Value, String> {
let app_type = Self::takeover_app_from_str(app_type)?;
let mut backup_snapshot = self.build_live_snapshot_from_provider(&app_type, provider)?;
let mut backup_snapshot =
self.build_live_snapshot_from_provider(&app_type, provider, snippet_override)?;
Self::apply_codex_unified_session_bucket_to_backup(
&app_type,
provider,
&mut backup_snapshot,
)?;
let previous_backup_snapshot = previous_provider
.map(|provider| {
let mut snapshot = self.build_live_snapshot_from_provider(&app_type, provider)?;
let mut snapshot =
self.build_live_snapshot_from_provider(&app_type, provider, snippet_override)?;
Self::apply_codex_unified_session_bucket_to_backup(
&app_type,
provider,
Expand Down Expand Up @@ -2382,6 +2385,7 @@ impl ProxyService {
app_type_enum.as_str(),
&provider,
previous_provider.as_ref(),
None,
)
.await?;
self.save_live_backup_snapshot(app_type_enum.as_str(), &backup_snapshot)
Expand All @@ -2407,7 +2411,7 @@ impl ProxyService {
) -> Result<(), String> {
let mut effective_provider = provider.clone();
effective_provider.settings_config =
self.build_live_snapshot_from_provider(&AppType::Claude, provider)?;
self.build_live_snapshot_from_provider(&AppType::Claude, provider, None)?;
let mut effective_settings = effective_provider.settings_config.clone();
let (proxy_url, _) = self.build_proxy_urls_for_app(&AppType::Claude).await?;

Expand Down Expand Up @@ -3099,7 +3103,7 @@ impl ProxyService {
if let Some(provider) = provider_for_write {
let mut effective_provider = provider.clone();
effective_provider.settings_config =
self.build_live_snapshot_from_provider(app_type, provider)?;
self.build_live_snapshot_from_provider(app_type, provider, None)?;
Self::apply_claude_takeover_fields_for_provider(
live,
proxy_url,
Expand Down Expand Up @@ -3457,16 +3461,19 @@ impl ProxyService {
&self,
app_type: &AppType,
provider: &Provider,
snippet_override: Option<&str>,
) -> Result<Value, String> {
let common_config_snippet =
self.db
let common_config_snippet = match snippet_override {
Some(override_snippet) => Some(override_snippet.to_string()),
None => self.db
.get_config_snippet(app_type.as_str())
.map_err(|error| {
format!(
"load common config snippet for {} failed: {error}",
app_type.as_str()
)
})?;
})?,
};
let apply_common_config =
crate::services::provider::ProviderService::provider_uses_common_config_for_app(
app_type,
Expand Down
139 changes: 139 additions & 0 deletions src-tauri/tests/provider_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2035,6 +2035,145 @@ async fn switch_provider_under_takeover_refreshes_claude_restore_backup_to_selec
);
}

#[tokio::test]
#[serial]
async fn updating_claude_common_config_under_takeover_refreshes_active_live_settings() {
let _guard = lock_test_mutex();
reset_test_fs();
let _home = ensure_test_home();

let settings_path = get_claude_settings_path();
if let Some(parent) = settings_path.parent() {
std::fs::create_dir_all(parent).expect("create claude settings dir");
}

let provider_live = json!({
"env": {
"ANTHROPIC_API_KEY": "provider-key"
},
"workspace": {
"path": "/tmp/provider-workspace"
}
});
std::fs::write(
&settings_path,
serde_json::to_string_pretty(&provider_live).expect("serialize provider live"),
)
.expect("seed claude live config");

let mut config = MultiAppConfig::default();
{
let manager = config
.get_manager_mut(&AppType::Claude)
.expect("claude manager");
manager.current = "glm-provider".to_string();
let mut provider = Provider::with_id(
"glm-provider".to_string(),
"GLM Claude".to_string(),
json!({
"env": { "ANTHROPIC_API_KEY": "provider-key" },
"workspace": { "path": "/tmp/provider-workspace" }
}),
None,
);
provider.meta = Some(cc_switch_lib::ProviderMeta {
apply_common_config: Some(true),
..Default::default()
});
manager.providers.insert("glm-provider".to_string(), provider);
}

let state = state_from_config(config);
state.save().expect("persist provider state to db");

let proxy_port = find_free_port();
state
.db
.set_app_proxy_preferred_port("claude", proxy_port)
.expect("update claude proxy port");

state
.proxy_service
.set_takeover_for_app("claude", true)
.await
.expect("enable claude takeover");

let snippet = serde_json::json!({
"env": {
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
},
"statusLine": {
"type": "command",
"command": "~/.claude/statusline.sh"
}
})
.to_string();

ProviderService::set_common_config_snippet(&state, AppType::Claude, Some(snippet))
.expect("set common config under takeover");

let live_during_takeover: serde_json::Value =
read_json_file(&settings_path).expect("read claude live settings under takeover");
assert_eq!(
live_during_takeover
.get("env")
.and_then(|env| env.get("ANTHROPIC_BASE_URL"))
.and_then(|value| value.as_str()),
Some(format!("http://127.0.0.1:{proxy_port}").as_str()),
"common config apply under takeover should keep Claude live config pointed at the local proxy"
);
assert_eq!(
live_during_takeover
.get("env")
.and_then(|env| env.get("ANTHROPIC_API_KEY"))
.and_then(|value| value.as_str()),
Some("PROXY_MANAGED"),
"common config apply under takeover should keep the managed placeholder in Claude live config"
);
assert_eq!(
live_during_takeover
.get("env")
.and_then(|env| env.get("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"))
.and_then(|value| value.as_str()),
Some("1"),
"common config apply under takeover should refresh active live config with snippet-owned env keys"
);
assert_eq!(
live_during_takeover
.get("statusLine")
.and_then(|value| value.get("command"))
.and_then(|value| value.as_str()),
Some("~/.claude/statusline.sh"),
"common config apply under takeover should refresh active live config with snippet-owned nested keys"
);

let backup_after_apply = state
.db
.get_live_backup("claude")
.await
.expect("read claude live backup after common config apply")
.expect("claude takeover backup should exist after common config apply");
let backup_after_apply: serde_json::Value =
serde_json::from_str(&backup_after_apply.original_config)
.expect("parse claude live backup after common config apply");
assert_eq!(
backup_after_apply
.get("env")
.and_then(|env| env.get("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"))
.and_then(|value| value.as_str()),
Some("1"),
"common config apply under takeover should refresh the restore backup too"
);
assert_eq!(
backup_after_apply
.get("statusLine")
.and_then(|value| value.get("command"))
.and_then(|value| value.as_str()),
Some("~/.claude/statusline.sh"),
"common config apply under takeover should store nested snippet keys in the restore backup"
);
}

fn seed_claude_provider(config: &mut MultiAppConfig, id: &str, name: &str) {
let manager = config
.get_manager_mut(&AppType::Claude)
Expand Down