From 88ea6f1c9a95f350b7148d65b95aae7cdc446a2e Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Thu, 10 Sep 2026 21:02:09 +0800 Subject: [PATCH] fix(agents): rebuild desktop routes from enabled providers Do not treat managed aliases under disabled providers as usable Desktop routes. Match each route against its source on an enabled provider, skip disabled sources when creating aliases, and exclude disabled mappings when identifying direct aliases. Validation: four regression tests fail before the fix and pass afterward; cargo test --locked (456 passed); cargo check --locked. After migrating the two affected live routes to the existing enabled source, Haiku and Opus messages requests both returned HTTP 200. --- src-tauri/src/agents/commands.rs | 3 + src-tauri/src/core_config/aliases.rs | 28 ++++--- src-tauri/src/tests.rs | 1 + src-tauri/src/tests/desktop_alias_routing.rs | 84 ++++++++++++++++++++ 4 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 src-tauri/src/tests/desktop_alias_routing.rs diff --git a/src-tauri/src/agents/commands.rs b/src-tauri/src/agents/commands.rs index 85bc00e..9d4803b 100644 --- a/src-tauri/src/agents/commands.rs +++ b/src-tauri/src/agents/commands.rs @@ -1406,6 +1406,9 @@ pub(crate) fn mark_configured_agent_model_aliases( let Some(provider) = provider.as_mapping() else { continue; }; + if yaml_mapping_value(provider, "disabled") == Some(&serde_norway::Value::Bool(true)) { + continue; + } let Some(configured_models) = yaml_mapping_value(provider, "models") else { continue; }; diff --git a/src-tauri/src/core_config/aliases.rs b/src-tauri/src/core_config/aliases.rs index 0335d75..e760c05 100644 --- a/src-tauri/src/core_config/aliases.rs +++ b/src-tauri/src/core_config/aliases.rs @@ -528,20 +528,29 @@ pub(crate) fn is_managed_claude_model_alias(model: &serde_norway::Value, alias: .is_some_and(|value| value == expected_display_name) } -pub(crate) fn configured_managed_claude_alias_exists( +pub(crate) fn configured_managed_claude_alias_matches( root: &serde_norway::Mapping, alias: &str, + source_model: &str, ) -> bool { + let matches = |model: &serde_norway::Value| { + is_managed_claude_model_alias(model, alias) + && configured_model_identity(model) + .is_some_and(|(source, _, _)| source.eq_ignore_ascii_case(source_model)) + }; let configured_provider_alias = MODEL_ALIAS_CONFIG_SECTIONS.iter().any(|section| { yaml_mapping_value(root, section) .and_then(serde_norway::Value::as_sequence) .into_iter() .flatten() .filter_map(serde_norway::Value::as_mapping) + .filter(|provider| { + yaml_mapping_value(provider, "disabled") != Some(&serde_norway::Value::Bool(true)) + }) .filter_map(|provider| yaml_mapping_value(provider, "models")) .filter_map(serde_norway::Value::as_sequence) .flatten() - .any(|model| is_managed_claude_model_alias(model, alias)) + .any(matches) }); configured_provider_alias || yaml_mapping_value(root, "oauth-model-alias") @@ -550,7 +559,7 @@ pub(crate) fn configured_managed_claude_alias_exists( .flat_map(|channels| channels.values()) .filter_map(serde_norway::Value::as_sequence) .flatten() - .any(|model| is_managed_claude_model_alias(model, alias)) + .any(matches) } pub(crate) fn ensure_claude_desktop_model_alias( @@ -559,14 +568,10 @@ pub(crate) fn ensure_claude_desktop_model_alias( alias: &str, oauth_model_definitions: &[OAuthModelDefinitions], ) -> Result<(), String> { - if let Some((existing_source, _)) = configured_model_client_identity(root, alias) { - if existing_source.eq_ignore_ascii_case(source_model) - && configured_managed_claude_alias_exists(root, alias) - { - return Ok(()); - } - remove_existing_claude_model_alias(root, alias)?; + if configured_managed_claude_alias_matches(root, alias, source_model) { + return Ok(()); } + remove_existing_claude_model_alias(root, alias)?; if append_claude_desktop_model_alias(root, source_model, alias)? { return Ok(()); } @@ -657,6 +662,9 @@ pub(crate) fn append_claude_desktop_model_alias( let Some(provider) = provider.as_mapping_mut() else { continue; }; + if yaml_mapping_value(provider, "disabled") == Some(&serde_norway::Value::Bool(true)) { + continue; + } let Some(models) = yaml_mapping_value_mut(provider, "models") else { continue; }; diff --git a/src-tauri/src/tests.rs b/src-tauri/src/tests.rs index 2a5e902..0921fb9 100644 --- a/src-tauri/src/tests.rs +++ b/src-tauri/src/tests.rs @@ -12,6 +12,7 @@ mod app_settings; mod app_update; mod core_config; mod core_runtime; +mod desktop_alias_routing; #[cfg(windows)] mod file_replace; mod instance_lock; diff --git a/src-tauri/src/tests/desktop_alias_routing.rs b/src-tauri/src/tests/desktop_alias_routing.rs new file mode 100644 index 0000000..ebd9318 --- /dev/null +++ b/src-tauri/src/tests/desktop_alias_routing.rs @@ -0,0 +1,84 @@ +use super::support::*; +use super::*; + +fn json(content: &str) -> serde_json::Value { + serde_json::to_value(serde_norway::from_str::(content).unwrap()).unwrap() +} + +#[test] +fn desktop_routes_move_from_disabled_provider_to_enabled_source() { + let models = test_agent_models(&["grok-4.6"]); + let mappings = ClaudeDesktopModelMappings::all("grok-4.6"); + let initial = + "openai-compatibility:\n - name: disabled-provider\n models: [{name: grok-4.6}]\n"; + let configured = + ensure_claude_desktop_model_aliases_in_yaml(initial, &mappings, &models).unwrap(); + let disabled = configured.replacen( + "name: disabled-provider", + "name: disabled-provider\n disabled: true", + 1, + ); + let input = format!("{disabled}codex-api-key:\n - models: [{{name: grok-4.6}}]\n"); + let restored = ensure_claude_desktop_model_aliases_in_yaml(&input, &mappings, &models).unwrap(); + let after = json(&restored); + assert_eq!(after["openai-compatibility"][0]["disabled"], true); + assert_eq!( + after["openai-compatibility"][0]["models"], + serde_json::json!([{"name": "grok-4.6"}]) + ); + let active_models = after["codex-api-key"][0]["models"].as_array().unwrap(); + for route in [ + CLAUDE_DESKTOP_OPUS_MODEL_ID, + CLAUDE_DESKTOP_SONNET_MODEL_ID, + CLAUDE_DESKTOP_HAIKU_MODEL_ID, + ] { + assert!(active_models + .iter() + .any(|model| model["name"] == "grok-4.6" && model["alias"] == route)); + } + let repeated = + ensure_claude_desktop_model_aliases_in_yaml(&restored, &mappings, &models).unwrap(); + assert_eq!(json(&repeated), after); +} + +#[test] +fn desktop_routes_skip_disabled_sources_during_creation() { + let input = "openai-compatibility:\n - name: disabled-provider\n disabled: true\n models: [{name: model-a}]\n - name: enabled-provider\n models: [{name: model-a}]\n"; + let updated = ensure_claude_desktop_model_aliases_in_yaml( + input, + &ClaudeDesktopModelMappings::all("model-a"), + &test_agent_models(&["model-a"]), + ) + .unwrap(); + let after = json(&updated); + assert_eq!( + after["openai-compatibility"][0], + json(input)["openai-compatibility"][0] + ); + assert_eq!( + after["openai-compatibility"][1]["models"] + .as_array() + .unwrap() + .len(), + 4 + ); +} + +#[test] +fn desktop_routes_reject_sources_available_only_in_disabled_providers() { + let input = "openai-compatibility:\n - name: disabled-provider\n disabled: true\n models: [{name: model-a}]\n"; + let result = ensure_claude_desktop_model_aliases_in_yaml( + input, + &ClaudeDesktopModelMappings::all("model-a"), + &test_agent_models(&["model-a"]), + ); + assert!(result.is_err()); +} + +#[test] +fn disabled_provider_alias_does_not_mark_an_active_real_model_as_an_alias() { + let input = "openai-compatibility:\n - name: disabled-provider\n disabled: true\n models: [{name: other-model, alias: model-a}]\ncodex-api-key:\n - models: [{name: model-a}]\n"; + let mut models = test_agent_models(&["model-a"]); + mark_configured_agent_model_aliases(&mut models, input).unwrap(); + assert!(!models[0].is_alias); +}