From 5aeddb6c8591a62e2abccc3335adc60c1f36a2e4 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sun, 20 Sep 2026 15:05:36 -0700 Subject: [PATCH] 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 e5e7fbcd272c introduced for endpoints. Fixes #116668 --- hermes_cli/auth_plugin_providers.py | 33 +++++++++++++-- .../test_auth_registry_mid_discovery.py | 42 +++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/hermes_cli/auth_plugin_providers.py b/hermes_cli/auth_plugin_providers.py index dbb351dcc0..0fe96c1d94 100644 --- a/hermes_cli/auth_plugin_providers.py +++ b/hermes_cli/auth_plugin_providers.py @@ -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 diff --git a/tests/providers/test_auth_registry_mid_discovery.py b/tests/providers/test_auth_registry_mid_discovery.py index 1f876ca5a0..95f6fa44dc 100644 --- a/tests/providers/test_auth_registry_mid_discovery.py +++ b/tests/providers/test_auth_registry_mid_discovery.py @@ -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