fix(auth): user provider plugin owns its aliases and display name in the auth registry

register_plugin_provider setdefault-ed aliases regardless of provenance, so a
$HERMES_HOME plugin alias colliding with an existing row kept resolving to the
old row while providers.get_provider_profile already followed the user profile;
override_registry_row left the display name untouched on a same-name
replacement. mirror_aliases applies the provider_source ownership rule that
e5e7fbcd27 introduced for endpoints.

Fixes #116668
This commit is contained in:
teknium1
2026-09-20 15:05:36 -07:00
committed by Teknium
parent a294a28e2b
commit 5aeddb6c85
2 changed files with 72 additions and 3 deletions

View File

@@ -60,16 +60,42 @@ def register_plugin_provider(pp: Any) -> None:
pconfig = ProviderConfig(pp.name, pp.display_name or pp.name, pp.auth_type, inference_base_url=pp.base_url)
PROVIDER_REGISTRY[pp.name] = pconfig
PLUGIN_MIRRORED_PROVIDERS.add(pp.name)
for alias in pp.aliases: # so resolve_provider() resolves them too
PROVIDER_REGISTRY.setdefault(alias, pconfig)
mirror_aliases(pconfig, pp)
def _user_owns_alias(pp: Any, alias: str) -> bool:
"""True when *alias* resolves to the ``$HERMES_HOME`` plugin *pp* in the ``providers`` layer."""
try:
from providers import get_provider_profile, provider_source
except Exception:
return False
owner = get_provider_profile(alias)
return owner is not None and owner.name == pp.name and provider_source(pp.name) == "user"
def mirror_aliases(pconfig: Any, pp: Any) -> None:
"""Point ``pp.aliases`` at *pconfig* so ``resolve_provider()`` resolves them too.
A bundled plugin never steals an alias another row already holds; a ``$HERMES_HOME`` plugin
whose alias the ``providers`` layer already resolves to it does — the same ownership rule as
:func:`override_registry_row`, otherwise the alias kept resolving to the built-in row while
``providers.get_provider_profile`` followed the user's profile (#116668).
"""
from hermes_cli.auth import PROVIDER_REGISTRY
for alias in pp.aliases:
if alias not in PROVIDER_REGISTRY or _user_owns_alias(pp, alias):
PROVIDER_REGISTRY[alias] = pconfig
def override_registry_row(pconfig: Any, pp: Any) -> None:
"""A ``$HERMES_HOME`` plugin re-registering a name that already has a row wins for the fields
it declares — ``base_url`` and, on api-key rows, ``env_vars`` (#48450). ``register_provider()``
it declares — ``display_name``, ``base_url`` and, on api-key rows, ``env_vars`` (#48450, #116668). ``register_provider()``
is last-writer-wins for the profile; without this the runtime kept reading the built-in
endpoint. In place, so alias rows sharing the object follow; idempotent, so re-sync is free.
"""
if pp.display_name:
pconfig.name = pp.display_name
if pp.base_url:
pconfig.inference_base_url = pp.base_url
if pp.auth_type == "api_key" == pconfig.auth_type and pp.env_vars:
@@ -104,6 +130,7 @@ def sync_plugin_provider_registry() -> int:
core_row = pp.name in BUILTIN_PROVIDER_IDS or pp.name in PLUGIN_MIRRORED_PROVIDERS
if core_row and pp.name not in _REGISTRY_PLUGIN_SKIP and provider_source(pp.name) == "user":
override_registry_row(PROVIDER_REGISTRY[pp.name], pp)
mirror_aliases(PROVIDER_REGISTRY[pp.name], pp)
continue
register_plugin_provider(pp)
added += pp.name in PROVIDER_REGISTRY

View File

@@ -173,3 +173,45 @@ def test_post_discovery_registration_is_mirrored(_isolated_registries, monkeypat
)
assert LATE in auth_mod.PROVIDER_REGISTRY
assert auth_mod.PROVIDER_REGISTRY[LATE_ALIAS] is auth_mod.PROVIDER_REGISTRY[LATE]
def test_user_plugin_alias_repoints_and_display_name_follows(_isolated_registries, monkeypatch, tmp_path):
"""A $HERMES_HOME plugin owns the aliases it declares and the display name of a same-name row.
Ownership split (#116668): ``providers.get_provider_profile`` already followed the user's
profile for the alias, while the auth registry kept the alias on whichever row got there first
and kept the bundled display name on a same-name replacement.
"""
monkeypatch.setattr(providers, "_discover_entry_point_providers", lambda: None)
monkeypatch.setattr(providers, "_BUNDLED_PLUGINS_DIR", tmp_path)
monkeypatch.setattr(providers, "_user_plugins_dir", lambda: None)
monkeypatch.setattr(providers, "_installed_plugins_dir", lambda: None)
providers._discover_providers()
taken_alias = "probe-116668-alias"
monkeypatch.setattr(providers, "_current_source", "bundled")
providers.register_provider(ProviderProfile(
name="probe-116668-bundled", display_name="Bundled", base_url="https://bundled.example/v1",
env_vars=("PROBE_116668_BUNDLED_KEY",), aliases=(taken_alias,)))
bundled_row = auth_mod.PROVIDER_REGISTRY["probe-116668-bundled"]
assert auth_mod.PROVIDER_REGISTRY[taken_alias] is bundled_row
# A second bundled plugin claiming the same alias does not steal it.
providers.register_provider(ProviderProfile(
name="probe-116668-other", display_name="Other", base_url="https://other.example/v1",
env_vars=("PROBE_116668_OTHER_KEY",), aliases=(taken_alias,)))
assert auth_mod.PROVIDER_REGISTRY[taken_alias] is bundled_row
# The user's plugin does, and its same-name replacement rewrites the display name in place.
monkeypatch.setattr(providers, "_current_source", "user")
providers.register_provider(ProviderProfile(
name="probe-116668-user", display_name="Mine", base_url="https://mine.example/v1",
env_vars=("PROBE_116668_USER_KEY",), aliases=(taken_alias,)))
assert auth_mod.PROVIDER_REGISTRY[taken_alias] is auth_mod.PROVIDER_REGISTRY["probe-116668-user"]
providers.register_provider(ProviderProfile(
name="probe-116668-bundled", display_name="Bundled (mine)", base_url="https://mine.example/v2",
env_vars=("PROBE_116668_BUNDLED_KEY",), aliases=(taken_alias,)))
assert bundled_row.name == "Bundled (mine)"
assert bundled_row.inference_base_url == "https://mine.example/v2"
assert auth_mod.PROVIDER_REGISTRY[taken_alias] is bundled_row