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.
This commit is contained in:
lzt404 committed 2026-09-11 16:25:14 +08:00
1 parent ade2d50a69
commit 88ea6f1c9a
4 files changed
+106 -10

No files matched your search

+3
View File
@@ -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;
};
+18 -10
View File
@@ -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;
};
+1
View File
@@ -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;
@@ -0,0 +1,84 @@
use super::support::*;
use super::*;
fn json(content: &str) -> serde_json::Value {
serde_json::to_value(serde_norway::from_str::<serde_norway::Value>(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);
}