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:
parent
553ad8146e
commit
7176b3a756
2 changed files with 54 additions and 10 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue