From 463c2ae2556b54c8581d0edd93758b84aacc072a Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Mon, 20 Jul 2026 00:09:56 -0700 Subject: [PATCH] fix: section-3 grouping follow-ups for salvaged PR #36998 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - extra_headers participates in the section-3 group identity (mirrors section 4 — header-routed tenants behind one proxy URL stay distinct) - model declarations go through _declared_model_ids() so models: [{id: ...}] rows keep working - gateway model-switch handler moved to gateway/slash_commands.py since the PR branched — re-applied the display-form edits there (both the legacy picker closure and the current typed path) - regression tests: same-endpoint fold, api_mode separation, header-routed separation, list-of-dict models, RID display stripping --- .../test_provider_section3_grouping.py | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 tests/hermes_cli/test_provider_section3_grouping.py diff --git a/tests/hermes_cli/test_provider_section3_grouping.py b/tests/hermes_cli/test_provider_section3_grouping.py new file mode 100644 index 000000000000..6f8c73934d7a --- /dev/null +++ b/tests/hermes_cli/test_provider_section3_grouping.py @@ -0,0 +1,149 @@ +"""Regression tests for section-3 (``providers:``) same-endpoint grouping in +``list_authenticated_providers`` and for ``format_model_for_display``. + +Salvaged with PR #36998 (@antydizajn): section 3 folds ``providers:`` entries +that share (api_url, credential, api_mode, extra_headers) into one picker row, +mirroring section 4's grouping for ``custom_providers:``. These are invariant +tests — grouping identity, header-routed separation, list-of-dict model +declarations, and display-only RID stripping. +""" + +import hermes_cli.providers as providers_mod +from hermes_cli.model_switch import ( + format_model_for_display, + list_authenticated_providers, +) + + +def _providers(monkeypatch, user_providers): + monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {}) + monkeypatch.setattr(providers_mod, "HERMES_OVERLAYS", {}) + monkeypatch.setattr("hermes_cli.models.fetch_api_models", lambda *a, **k: []) + return list_authenticated_providers( + user_providers=user_providers, + custom_providers=[], + max_models=50, + ) + + +def _user_rows(rows): + return [p for p in rows if p.get("source") == "user-config"] + + +def test_same_endpoint_same_credential_entries_fold_to_one_row(monkeypatch): + """Two providers: entries differing only by model id collapse into one + picker row carrying both models (the Palantir Foundry case).""" + rows = _user_rows(_providers(monkeypatch, { + "palantir-claude46": { + "name": "Palantir Claude 4.6 Opus", + "base_url": "https://foundry.example.com/anthropic", + "key_env": "PALANTIR_TOKEN", + "api_mode": "anthropic_messages", + "model": "ri.language-model-service..language-model.anthropic-claude-4-6-opus", + }, + "palantir-claude47": { + "name": "Palantir Claude 4.7 Opus", + "base_url": "https://foundry.example.com/anthropic", + "key_env": "PALANTIR_TOKEN", + "api_mode": "anthropic_messages", + "model": "ri.language-model-service..language-model.anthropic-claude-4-7-opus", + }, + })) + assert len(rows) == 1 + row = rows[0] + assert row["slug"] == "palantir-claude46" # first member's slug wins + assert row["name"] == "Palantir Claude" # version suffix stripped + assert len(row["models"]) == 2 + + +def test_different_api_mode_keeps_distinct_rows(monkeypatch): + """Same host + credential but a different wire protocol must not fold.""" + rows = _user_rows(_providers(monkeypatch, { + "proxy-claude": { + "name": "Proxy Claude", + "base_url": "https://proxy.example.com/v1", + "key_env": "PROXY_TOKEN", + "api_mode": "anthropic_messages", + "model": "claude-opus-4.6", + }, + "proxy-gpt": { + "name": "Proxy GPT", + "base_url": "https://proxy.example.com/v1", + "key_env": "PROXY_TOKEN", + "api_mode": "openai_chat", + "model": "gpt-5.4", + }, + })) + assert len(rows) == 2 + + +def test_different_extra_headers_keep_distinct_rows(monkeypatch): + """Header-routed tenants behind one proxy URL are distinct endpoints — + extra_headers is part of the group identity (mirrors section 4).""" + rows = _user_rows(_providers(monkeypatch, { + "tenant-a": { + "name": "Tenant A", + "base_url": "https://proxy.example.com/v1", + "key_env": "PROXY_TOKEN", + "api_mode": "openai_chat", + "extra_headers": {"X-Tenant": "a"}, + "model": "model-a", + }, + "tenant-b": { + "name": "Tenant B", + "base_url": "https://proxy.example.com/v1", + "key_env": "PROXY_TOKEN", + "api_mode": "openai_chat", + "extra_headers": {"X-Tenant": "b"}, + "model": "model-b", + }, + })) + assert len(rows) == 2 + + +def test_list_of_dict_model_declarations_are_honored(monkeypatch): + """``models: [{"id": ...}]`` rows go through _declared_model_ids — the + grouped path must not regress that contract.""" + rows = _user_rows(_providers(monkeypatch, { + "dictrows": { + "name": "Dict Rows", + "base_url": "https://dictrows.example.com/v1", + "key_env": "DICTROWS_TOKEN", + "models": [{"id": "model-x"}, {"id": "model-y"}], + }, + })) + assert len(rows) == 1 + assert rows[0]["models"] == ["model-x", "model-y"] + + +def test_single_word_group_name_not_over_trimmed(monkeypatch): + """Version-token stripping only applies when the prefix keeps >= 2 words.""" + rows = _user_rows(_providers(monkeypatch, { + "gpt54-a": { + "name": "GPT 5.4", + "base_url": "https://single.example.com/v1", + "key_env": "SINGLE_TOKEN", + "model": "gpt-5.4", + }, + })) + assert rows[0]["name"] == "GPT 5.4" + + +class TestFormatModelForDisplay: + def test_palantir_rid_stripped_to_trailing_slug(self): + rid = "ri.language-model-service..language-model.anthropic-claude-4-7-opus" + assert format_model_for_display(rid) == "anthropic-claude-4-7-opus" + + def test_plain_names_pass_through(self): + for name in ( + "claude-opus-4.6", + "gpt-5.4", + "meta-llama/Llama-3.3-70B-Instruct", + "some-model.gguf", + "", + ): + assert format_model_for_display(name) == name + + def test_prefix_only_edge_preserved(self): + """A bare prefix with no trailing slug must not become empty.""" + assert format_model_for_display("ri.language-model-service..language-model.") != ""