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
127 changes: 124 additions & 3 deletions src-tauri/src/services/provider/claude.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
use super::common::merge_json_values;
use super::*;

impl ProviderService {
const CLAUDE_PROVIDER_ENV_KEYS: &'static [&'static str] = &[
"ANTHROPIC_AUTH_TOKEN",
"ANTHROPIC_API_KEY",
"ANTHROPIC_BASE_URL",
"ANTHROPIC_MODEL",
"ANTHROPIC_REASONING_MODEL",
"ANTHROPIC_SMALL_FAST_MODEL",
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
"ANTHROPIC_DEFAULT_SONNET_MODEL",
"ANTHROPIC_DEFAULT_OPUS_MODEL",
];

pub(super) fn parse_common_claude_config_snippet(snippet: &str) -> Result<Value, AppError> {
let value: Value = serde_json::from_str(snippet).map_err(|e| {
AppError::localized(
Expand Down Expand Up @@ -119,6 +132,104 @@ impl ProviderService {
)
}

fn strip_claude_provider_owned_live_keys(settings: &mut Value) {
let Some(env) = settings.get_mut("env").and_then(Value::as_object_mut) else {
return;
};

for key in Self::CLAUDE_PROVIDER_ENV_KEYS {
env.remove(*key);
}
}

pub(super) fn merge_claude_provider_owned_values_from_live(snapshot: &mut Value, live: &Value) {
let Some(live_env) = live.get("env").and_then(Value::as_object) else {
return;
};

if !snapshot.is_object() {
*snapshot = json!({});
}

let snapshot_obj = snapshot
.as_object_mut()
.expect("snapshot should be object after normalization");
let env_value = snapshot_obj
.entry("env".to_string())
.or_insert_with(|| json!({}));
if !env_value.is_object() {
*env_value = json!({});
}
let snapshot_env = env_value
.as_object_mut()
.expect("env should be object after normalization");

for key in Self::CLAUDE_PROVIDER_ENV_KEYS {
snapshot_env.remove(*key);
if let Some(value) = live_env.get(*key) {
snapshot_env.insert((*key).to_string(), value.clone());
}
}
}

pub(super) fn build_claude_live_snapshot_for_write(
provider: &Provider,
common_config_snippet: Option<&str>,
previous_common_config_snippet: Option<&str>,
apply_common_config: bool,
existing_live_settings: Option<Value>,
) -> Result<Value, AppError> {
let apply_common_config = Self::resolve_live_apply_common_config(
&AppType::Claude,
provider,
common_config_snippet,
apply_common_config,
);

let mut live_base = existing_live_settings.unwrap_or_else(|| json!({}));
if !live_base.is_object() {
live_base = json!({});
}

Self::strip_claude_provider_owned_live_keys(&mut live_base);

if let Some(snippet) = previous_common_config_snippet.map(str::trim) {
if !snippet.is_empty() {
match common_config::remove_common_config_from_settings(
&AppType::Claude,
&live_base,
snippet,
) {
Ok(settings) => live_base = settings,
Err(err)
if Self::should_skip_common_config_migration_error(
&AppType::Claude,
&err,
) =>
{
log::warn!(
"skip stripping invalid stored Claude common config snippet from live base: {err}"
);
live_base = json!({});
}
Err(err) => return Err(err),
}
}
}

let mut provider_content = common_config::build_effective_settings_with_common_config(
&AppType::Claude,
provider,
common_config_snippet,
apply_common_config,
)?;
let _ = Self::normalize_claude_models_in_value(&mut provider_content);
merge_json_values(&mut live_base, &provider_content);
let _ = Self::normalize_claude_models_in_value(&mut live_base);

Ok(live_base)
}

pub(super) fn prepare_switch_claude(
config: &mut MultiAppConfig,
provider_id: &str,
Expand Down Expand Up @@ -180,7 +291,10 @@ impl ProviderService {
);
if let Some(manager) = config.get_manager_mut(&AppType::Claude) {
if let Some(current) = manager.providers.get_mut(current_id) {
current.settings_config = live;
Self::merge_claude_provider_owned_values_from_live(
&mut current.settings_config,
&live,
);
}
}

Expand Down Expand Up @@ -214,18 +328,25 @@ impl ProviderService {
pub(super) fn write_claude_live(
provider: &Provider,
common_config_snippet: Option<&str>,
previous_common_config_snippet: Option<&str>,
apply_common_config: bool,
) -> Result<(), AppError> {
if !crate::sync_policy::should_sync_live(&AppType::Claude) {
return Ok(());
}

let settings_path = get_claude_settings_path();
let content_to_write = Self::build_effective_live_snapshot(
&AppType::Claude,
let existing_live_settings = if settings_path.exists() {
Some(read_json_file::<Value>(&settings_path)?)
} else {
None
};
let content_to_write = Self::build_claude_live_snapshot_for_write(
provider,
common_config_snippet,
previous_common_config_snippet,
apply_common_config,
existing_live_settings,
)?;

write_json_file(&settings_path, &content_to_write)?;
Expand Down
28 changes: 0 additions & 28 deletions src-tauri/src/services/provider/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,31 +160,3 @@ pub(super) fn merge_json_values(base: &mut Value, overlay: &Value) {
}
}
}

pub(super) fn strip_common_values(target: &mut Value, common: &Value) {
match (target, common) {
(Value::Object(target_map), Value::Object(common_map)) => {
for (key, common_value) in common_map {
let should_remove = match target_map.get_mut(key) {
Some(target_value) => match target_value {
Value::Object(_) if matches!(common_value, Value::Object(_)) => {
strip_common_values(target_value, common_value);
target_value.as_object().is_some_and(|m| m.is_empty())
}
_ => target_value == common_value,
},
None => false,
};

if should_remove {
target_map.remove(key);
}
}
}
(target_value, common_value) => {
if target_value == common_value {
*target_value = Value::Null;
}
}
}
}
37 changes: 31 additions & 6 deletions src-tauri/src/services/provider/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ struct PostCommitAction {
sync_mcp: bool,
refresh_snapshot: bool,
common_config_snippet: Option<String>,
previous_common_config_snippet: Option<String>,
takeover_active: bool,
}

Expand Down Expand Up @@ -135,7 +136,13 @@ impl ProviderService {
};

for provider in &providers {
Self::write_live_snapshot(&AppType::OpenClaw, provider, snippet.as_deref(), true)?;
Self::write_live_snapshot(
&AppType::OpenClaw,
provider,
snippet.as_deref(),
None,
true,
)?;
}

Ok(())
Expand Down Expand Up @@ -389,6 +396,7 @@ impl ProviderService {
&action.app_type,
&action.provider,
action.common_config_snippet.as_deref(),
action.previous_common_config_snippet.as_deref(),
apply_common_config,
)?;
}
Expand Down Expand Up @@ -456,7 +464,10 @@ impl ProviderService {
let mut guard = state.config.write().map_err(AppError::from)?;
if let Some(manager) = guard.get_manager_mut(app_type) {
if let Some(target) = manager.providers.get_mut(provider_id) {
target.settings_config = live_after;
Self::merge_claude_provider_owned_values_from_live(
&mut target.settings_config,
&live_after,
);
}
}
}
Expand Down Expand Up @@ -729,6 +740,7 @@ impl ProviderService {
config: &MultiAppConfig,
app_type: &AppType,
current_provider_id: Option<&str>,
previous_common_config_snippet: Option<String>,
takeover_active: bool,
) -> Result<Option<PostCommitAction>, AppError> {
if app_type.is_additive_mode() {
Expand All @@ -743,6 +755,7 @@ impl ProviderService {
config,
app_type,
&current_provider_id,
previous_common_config_snippet,
takeover_active,
)
}
Expand All @@ -751,6 +764,7 @@ impl ProviderService {
config: &MultiAppConfig,
app_type: &AppType,
current_provider_id: &str,
previous_common_config_snippet: Option<String>,
takeover_active: bool,
) -> Result<Option<PostCommitAction>, AppError> {
let provider = config
Expand All @@ -768,6 +782,7 @@ impl ProviderService {
sync_mcp: matches!(app_type, AppType::Codex) && !takeover_active,
refresh_snapshot: false,
common_config_snippet: config.common_config_snippets.get(app_type).cloned(),
previous_common_config_snippet,
takeover_active,
}))
}
Expand Down Expand Up @@ -1026,6 +1041,7 @@ impl ProviderService {
config,
&app_type_clone,
effective_current_provider.as_deref(),
old_snippet,
takeover_active,
)?;
Ok(((), action))
Expand Down Expand Up @@ -1134,6 +1150,7 @@ impl ProviderService {
sync_mcp: matches!(&app_type_clone, AppType::Codex),
refresh_snapshot: false,
common_config_snippet,
previous_common_config_snippet: None,
takeover_active: false,
})
} else {
Expand Down Expand Up @@ -1247,6 +1264,7 @@ impl ProviderService {
sync_mcp: matches!(&app_type_clone, AppType::Codex),
refresh_snapshot: false,
common_config_snippet,
previous_common_config_snippet: None,
takeover_active: false,
})
} else {
Expand Down Expand Up @@ -1672,7 +1690,8 @@ impl ProviderService {
continue;
}

if let Err(e) = Self::write_live_snapshot(app_type, provider, snippet.as_deref(), true)
if let Err(e) =
Self::write_live_snapshot(app_type, provider, snippet.as_deref(), None, true)
{
log::warn!("sync_current_to_live: 写入 {app_type} live 配置失败: {e}");
}
Expand Down Expand Up @@ -1774,6 +1793,7 @@ impl ProviderService {
.common_config_snippets
.get(&app_type_clone)
.cloned(),
previous_common_config_snippet: None,
takeover_active: false,
};

Expand Down Expand Up @@ -1808,6 +1828,7 @@ impl ProviderService {
sync_mcp: true, // v3.7.0: 所有应用切换时都同步 MCP,防止配置丢失
refresh_snapshot: true,
common_config_snippet: config.common_config_snippets.get(&app_type_clone).cloned(),
previous_common_config_snippet: None,
takeover_active: false,
};

Expand All @@ -1825,6 +1846,7 @@ impl ProviderService {
app_type: &AppType,
provider: &Provider,
common_config_snippet: Option<&str>,
previous_common_config_snippet: Option<&str>,
apply_common_config: bool,
) -> Result<(), AppError> {
let apply_common_config = Self::resolve_live_apply_common_config(
Expand All @@ -1838,9 +1860,12 @@ impl ProviderService {
AppType::Codex => {
Self::write_codex_live(provider, common_config_snippet, apply_common_config)
}
AppType::Claude => {
Self::write_claude_live(provider, common_config_snippet, apply_common_config)
}
AppType::Claude => Self::write_claude_live(
provider,
common_config_snippet,
previous_common_config_snippet,
apply_common_config,
),
AppType::Gemini => Self::write_gemini_live(
provider,
if apply_common_config {
Expand Down
Loading