From b850a88e8b6df3f134bd541640c9d929379da2cd Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Tue, 2 Jun 2026 18:08:01 -0700 Subject: [PATCH] =?UTF-8?q?Release=20v0.51.223=20=E2=80=94=20Release=20GQ?= =?UTF-8?q?=20(stage-p5=20=E2=80=94=20openai-api=20first-class=20picker=20?= =?UTF-8?q?provider=20#3443=20+=20MiniMax-M3=20#3374)=20(#3453)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: upgrade MiniMax default model to M3 Add MiniMax-M3 as the new default and prune deprecated older versions (M2.5/M2.5-highspeed/M2.1/M2) from the model catalog. M2.7 (and M2.7-highspeed) is retained as the legacy compatible option for users who pin to it. Updated: - api/config.py: _FALLBACK_MODELS adds minimax/MiniMax-M3 (placed before M2.7 so the dropdown surfaces it first) - api/config.py: _PROVIDER_MODELS['minimax'] adds M3 first, removes M2.5/M2.5-highspeed/M2.1 - api/config.py: _PROVIDER_MODELS['minimax-cn'] adds M3 first, removes M2.5/M2.1/M2 - tests/test_minimax_provider.py: updated CN catalog assertions to match the new {M3, M2.7} list API URL and TTS configuration are unchanged. Co-Authored-By: Octopus * fix(models): register openai-api as first-class picker provider * fix(models): detect OPENAI_API_KEY as openai-api, not bare openai (#3443 Codex follow-up) Codex review found #3444 added the openai-api picker entry but the env-detection side still did detected_providers.add('openai') for OPENAI_API_KEY. The agent registry has only openai-api and openai-codex (no bare openai), so a env-only OPENAI_API_KEY setup emitted @openai: picker entries the agent can't resolve on the send path. Detect openai-api to match the registry. Adds a regression test. * docs(changelog): v0.51.223 — re-stamp keep-set (#3443 openai-api + #3374 MiniMax-M3); dropped #3289 + #3264 to hold per Codex --------- Co-authored-by: octo-patch Co-authored-by: Octopus Co-authored-by: Rod Boev Co-authored-by: nesquena-hermes <[email protected]> --- CHANGELOG.md | 8 +++ api/config.py | 23 ++++++--- tests/test_minimax_provider.py | 8 +-- tests/test_openai_api_provider_alias.py | 69 +++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 tests/test_openai_api_provider_alias.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1309c706..48aa4643 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ ## [Unreleased] +## [v0.51.223] — 2026-06-02 — Release GQ (stage-p5 — openai-api picker provider + MiniMax-M3) + +### Fixed +- GPT models now appear in the model picker when hermes-agent exposes its built-in OpenAI provider under the `openai-api` slug (the one activated by `OPENAI_API_KEY` / `OPENAI_BASE_URL`, distinct from `openai-codex`). `openai-api` is now a first-class picker provider in `_PROVIDER_DISPLAY` / `_PROVIDER_MODELS` rather than an alias of `openai` — an alias would have fixed the display but broken the send path, since the agent registry has `openai-api` and not `openai`. Env detection for `OPENAI_API_KEY` was also corrected to surface `openai-api` instead of a bare `openai` the agent registry can't resolve (#3443, @rodboev). + +### Changed +- MiniMax default model catalog upgraded to M3 in the model picker (#3374, @octo-patch). + ## [v0.51.222] — 2026-06-02 — Release GP (stage-p4 — backend bugfix batch: title language drift + orphaned CLI sidecar prune + pin-quota lineage) ### Fixed diff --git a/api/config.py b/api/config.py index ef96899a..b51e562b 100644 --- a/api/config.py +++ b/api/config.py @@ -679,6 +679,7 @@ _FALLBACK_MODELS = [ # Mistral {"provider": "Mistral", "id": "mistralai/mistral-large-latest", "label": "Mistral Large"}, # MiniMax + {"provider": "MiniMax", "id": "minimax/MiniMax-M3", "label": "MiniMax M3"}, {"provider": "MiniMax", "id": "minimax/MiniMax-M2.7", "label": "MiniMax M2.7"}, {"provider": "MiniMax", "id": "minimax/MiniMax-M2.7-highspeed", "label": "MiniMax M2.7 Highspeed"}, # Z.AI / GLM @@ -704,6 +705,7 @@ _PROVIDER_DISPLAY = { "openrouter": "OpenRouter", "anthropic": "Anthropic", "openai": "OpenAI", + "openai-api": "OpenAI API", "openai-codex": "OpenAI Codex", "xai-oauth": "xAI Grok OAuth", "copilot": "GitHub Copilot", @@ -1061,6 +1063,12 @@ _PROVIDER_MODELS = { {"id": "gpt-5.4-mini", "label": "GPT-5.4 Mini"}, {"id": "gpt-5.4", "label": "GPT-5.4"}, ], + "openai-api": [ + {"id": "gpt-5.5", "label": "GPT-5.5"}, + {"id": "gpt-5.5-mini", "label": "GPT-5.5 Mini"}, + {"id": "gpt-5.4-mini", "label": "GPT-5.4 Mini"}, + {"id": "gpt-5.4", "label": "GPT-5.4"}, + ], "openai-codex": [ {"id": "gpt-5.5", "label": "GPT-5.5"}, {"id": "gpt-5.5-mini", "label": "GPT-5.5 Mini"}, @@ -1107,17 +1115,13 @@ _PROVIDER_MODELS = { {"id": "kimi-k2.5", "label": "Kimi K2.5"}, ], "minimax": [ + {"id": "MiniMax-M3", "label": "MiniMax M3"}, {"id": "MiniMax-M2.7", "label": "MiniMax M2.7"}, {"id": "MiniMax-M2.7-highspeed", "label": "MiniMax M2.7 Highspeed"}, - {"id": "MiniMax-M2.5", "label": "MiniMax M2.5"}, - {"id": "MiniMax-M2.5-highspeed", "label": "MiniMax M2.5 Highspeed"}, - {"id": "MiniMax-M2.1", "label": "MiniMax M2.1"}, ], "minimax-cn": [ + {"id": "MiniMax-M3", "label": "MiniMax M3"}, {"id": "MiniMax-M2.7", "label": "MiniMax M2.7"}, - {"id": "MiniMax-M2.5", "label": "MiniMax M2.5"}, - {"id": "MiniMax-M2.1", "label": "MiniMax M2.1"}, - {"id": "MiniMax-M2", "label": "MiniMax M2"}, ], # GitHub Copilot — model IDs served via the Copilot API "copilot": [ @@ -3558,7 +3562,12 @@ def get_available_models() -> dict: if all_env.get("ANTHROPIC_API_KEY"): detected_providers.add("anthropic") if all_env.get("OPENAI_API_KEY"): - detected_providers.add("openai") + # hermes-agent registers its OPENAI_API_KEY/OPENAI_BASE_URL provider + # under the slug `openai-api` (there is no bare `openai` in the agent + # registry — only `openai-api` and `openai-codex`). Detecting `openai` + # here would emit `@openai:` picker entries the agent can't resolve on + # the send path, so detect `openai-api` to match the registry (#3443). + detected_providers.add("openai-api") # openai-codex uses ChatGPT OAuth (not OPENAI_API_KEY) for its default endpoint. # Detecting it here lets users who have both credentials configured find it in the # picker without a manual config.yaml edit. Users without Codex OAuth will see diff --git a/tests/test_minimax_provider.py b/tests/test_minimax_provider.py index 9893c7e2..834d1e3b 100644 --- a/tests/test_minimax_provider.py +++ b/tests/test_minimax_provider.py @@ -138,10 +138,8 @@ def test_minimax_cn_provider_models_match_hermes_agent_catalog(): models = config._PROVIDER_MODELS.get('minimax-cn', []) ids = [m['id'] for m in models] assert ids == [ + 'MiniMax-M3', 'MiniMax-M2.7', - 'MiniMax-M2.5', - 'MiniMax-M2.1', - 'MiniMax-M2', ] assert config._PROVIDER_DISPLAY.get('minimax-cn') == 'MiniMax (China)' @@ -199,10 +197,8 @@ def test_minimax_cn_detected_from_os_environ(monkeypatch, tmp_path): assert 'minimax-cn' in groups, f"minimax-cn group missing: {groups.keys()}" assert groups['minimax-cn']['provider'] == 'MiniMax (China)' assert {m['id'] for m in groups['minimax-cn']['models']} == { + 'MiniMax-M3', 'MiniMax-M2.7', - 'MiniMax-M2.5', - 'MiniMax-M2.1', - 'MiniMax-M2', } assert 'minimax' not in groups, ( "MINIMAX_CN_API_KEY must not be collapsed into the global minimax provider" diff --git a/tests/test_openai_api_provider_alias.py b/tests/test_openai_api_provider_alias.py new file mode 100644 index 00000000..d849fa0d --- /dev/null +++ b/tests/test_openai_api_provider_alias.py @@ -0,0 +1,69 @@ +"""openai-api as a first-class picker provider. + +hermes-agent registers its built-in OpenAI provider as ``openai-api`` in +``hermes_cli.auth.PROVIDER_REGISTRY``. The WebUI must recognise this +slug in ``_PROVIDER_DISPLAY`` and ``_PROVIDER_MODELS`` so that GPT +models appear in the picker AND the session ``model_provider`` stays +``openai-api`` on the send path (where the agent resolves it against +its own registry). + +An alias-only approach (``openai-api`` → ``openai``) fixes display but +breaks the send path because ``openai`` is not a registered provider in +the agent. See PR review discussion on #3444. +""" + +import api.config as cfg + + +class TestOpenaiApiPickerProvider: + """openai-api must be a first-class picker provider, not an alias.""" + + def test_provider_display_registered(self): + assert "openai-api" in cfg._PROVIDER_DISPLAY + + def test_provider_models_registered(self): + assert "openai-api" in cfg._PROVIDER_MODELS + assert len(cfg._PROVIDER_MODELS["openai-api"]) > 0 + + def test_not_aliased(self): + assert "openai-api" not in cfg._PROVIDER_ALIASES + + def test_canonicalise_preserves_slug(self): + assert cfg._canonicalise_provider_id("openai-api") == "openai-api" + + def test_openai_canonical_unchanged(self): + assert cfg._canonicalise_provider_id("openai") == "openai" + + def test_openai_codex_unchanged(self): + assert cfg._canonicalise_provider_id("openai-codex") == "openai-codex" + + +class TestOpenaiApiSendPath: + """The send path must preserve openai-api, not collapse it to openai.""" + + def test_resolve_model_provider_preserves_openai_api(self, monkeypatch): + monkeypatch.setattr(cfg, "cfg", { + "model": {"provider": "openai-api", "default": "gpt-5.5"}, + }) + _model, provider, _base_url = cfg.resolve_model_provider("gpt-5.5") + assert provider == "openai-api" + + def test_model_with_provider_context_preserves_openai_api(self, monkeypatch): + monkeypatch.setattr(cfg, "cfg", { + "model": {"provider": "openai-api", "default": "gpt-5.5"}, + }) + result = cfg.model_with_provider_context("gpt-5.5", "openai-api") + assert "openai" not in result or "openai-api" in result + + +class TestOpenaiApiEnvDetection: + """OPENAI_API_KEY env detection must surface `openai-api`, not a bare `openai` + the agent registry can't resolve (#3443 detection-side, Codex follow-up).""" + + def test_openai_api_key_detects_openai_api_not_bare_openai(self): + import inspect + src = inspect.getsource(cfg) + # The env-detection branch for OPENAI_API_KEY must add "openai-api". + assert 'detected_providers.add("openai-api")' in src + # And must NOT add a bare "openai" (no such slug in the agent registry). + assert 'detected_providers.add("openai")\n' not in src