fix(config): route plugin providers before config_provider bare-passthrough

Gate found the plugin-routing branch ran AFTER the provider==config_provider
early-return, so when an ACTIVE plugin provider was also the configured default,
model_with_provider_context returned a bare model and dropped the '@plugin:'
hint -> wrong backend. Moved the _is_plugin_model_provider() branch above the
config_provider passthrough. Regression test (GMI + anthropic/claude-sonnet-4.6
as the active plugin+configured provider) added. Co-authored-by: alexfoxtm.
This commit is contained in:
nesquena-hermes 2026-07-11 05:18:27 +00:00
parent 553ad8146e
commit 7176b3a756
2 changed files with 54 additions and 10 deletions

View file

@ -2960,6 +2960,16 @@ def model_with_provider_context(model_id: str, model_provider: str | None = None
if provider in _ACP_SUBPROCESS_PROVIDERS:
return f"@{provider}:{model}"
# Plugin-only model providers (e.g. 9router, and other model plugins whose
# slugs are not in the static provider tables) route through the plugin, not
# the default provider. This MUST come before the `provider == config_provider`
# bare-passthrough below: when a plugin provider is ALSO the configured
# provider, returning a bare model would drop the '@plugin:' hint and the model
# would be sent to the wrong backend. Emit the explicit hint so it stays
# routable to the plugin that surfaced it. (#5909 gate finding)
if _is_plugin_model_provider(provider):
return f"@{provider}:{model}"
# If the selected provider is already the configured provider, leaving the
# model bare preserves provider-specific base_url/proxy settings.
if provider == config_provider:
@ -2979,16 +2989,8 @@ def model_with_provider_context(model_id: str, model_provider: str | None = None
if isinstance(providers_cfg, dict) and provider in providers_cfg:
return f"@{provider}:{model}"
# Plugin-only model providers (e.g. 9router, and other model plugins whose
# slugs are not in the static provider tables) route through the plugin,
# not the default provider. A surfaced plugin-only model such as
# 'gh/claude-sonnet-4.5' carries a '/' in its ID, so without this branch it
# would fall through to the bare-passthrough below and inherit the default
# provider. Emit the explicit '@plugin:model' hint so the model stays
# routable to the plugin that surfaced it. This mirrors the explicit
# configured-provider handling above.
if _is_plugin_model_provider(provider):
return f"@{provider}:{model}"
# (Plugin-only provider routing handled above, before the config_provider
# bare-passthrough.)
# For non-OpenRouter slash IDs without an explicit configured provider,
# keep the ID intact so existing custom/proxy base_url routing and

View file

@ -430,3 +430,45 @@ class TestPluginModelProviderRouting:
config.cfg.clear()
config.cfg.update(old_cfg)
invalidate_plugin_model_provider_cache()
def test_plugin_provider_routes_even_when_it_is_the_configured_provider(self, monkeypatch):
# #5909 gate finding: the plugin-routing branch must run BEFORE the
# `provider == config_provider` bare-passthrough. When the ACTIVE plugin
# provider is also the configured default, returning a bare model would
# drop the '@plugin:' hint and route to the wrong backend.
profile = SimpleNamespace(
name="gmi",
display_name="GMI",
env_vars=("GMI_API_KEY",),
auth_type="api_key",
aliases=(),
fallback_models=("anthropic/claude-sonnet-4.6",),
)
def _fake_list_providers():
return [profile]
fake_providers = types.ModuleType("providers")
fake_providers.list_providers = _fake_list_providers
monkeypatch.setitem(sys.modules, "providers", fake_providers)
invalidate_plugin_model_provider_cache()
old_cfg = dict(config.cfg)
config.cfg.clear()
# The plugin IS the configured provider now.
config.cfg["model"] = {"provider": "gmi", "default": "anthropic/claude-sonnet-4.6"}
config.cfg["providers"] = {}
try:
assert config._is_plugin_model_provider("gmi")
routed = config.model_with_provider_context(
"anthropic/claude-sonnet-4.6", "gmi"
)
assert routed == "@gmi:anthropic/claude-sonnet-4.6", (
"an active plugin provider must keep its '@plugin:' routing hint "
"even when it equals the configured provider, got "
f"{routed!r}"
)
finally:
config.cfg.clear()
config.cfg.update(old_cfg)
invalidate_plugin_model_provider_cache()