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:
1 parent
ade2d50a69
commit
88ea6f1c9a
4 files changed
+106
-10
No files matched your search
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user