Skip to content

fix(providers): treat custom:<name> as aggregator for vendor-prefixed slugs (#26578)#26594

Closed
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/custom-aggregator-recognition-26578
Closed

fix(providers): treat custom:<name> as aggregator for vendor-prefixed slugs (#26578)#26594
briandevans wants to merge 1 commit into
NousResearch:mainfrom
briandevans:fix/custom-aggregator-recognition-26578

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

Summary

Two places in the CLI treated custom:<name> providers as bare/unknown providers and broke vendor-prefixed model slugs (e.g. google/gemini-3.1-flash-lite, z-ai/glm-5.1) for users running through custom:zenmux, custom:litellm, etc.

The bug

custom:<name> providers front arbitrary upstream catalogs (LiteLLM, ZenMux, etc.) and commonly accept vendor-prefixed model slugs, just like openrouter. But:

  • hermes_cli/providers.py:is_aggregator() only returned True for providers registered in HERMES_OVERLAYS with is_aggregator=True. custom:zenmux fell through to get_provider(), which returns None for unknown slugs, so is_aggregator() returned False. This broke model-alias resolution in model_switch for vendor-prefixed model IDs when the current provider was custom:<name>.

  • hermes_cli/doctor.py vendor-prefix validation included bare "custom" in providers_accepting_vendor_slugs but not the custom:<name> namespace, producing a false-positive warning:

    model.default 'z-ai/glm-5.1' is vendor-prefixed but model.provider is 'custom:zenmux'. Either set model.provider to 'openrouter', or drop the vendor prefix.

    which is incorrect — custom:zenmux IS an aggregator.

The fix

Short-circuit on the custom: prefix in both call sites:

  • is_aggregator(provider) returns True when provider.startswith("custom:"), before falling through to the registry lookup.
  • run_doctor()'s vendor-prefix check skips when provider_for_policy.startswith("custom:").

The custom: special-case is intentionally narrow: it does NOT make every unknown provider an aggregator (covered by a regression test).

Test plan

  • Focused regression tests:
    • tests/hermes_cli/test_model_switch_custom_providers.py::test_is_aggregator_recognizes_custom_prefixcustom:zenmux, custom:ollama, custom:local-(127.0.0.1:4141) → all True
    • tests/hermes_cli/test_model_switch_custom_providers.py::test_is_aggregator_unknown_provider_still_false — the custom: fast-path must not turn every unknown provider into an aggregator
    • tests/hermes_cli/test_model_switch_custom_providers.py::test_is_aggregator_known_non_aggregator_unchangedanthropic still returns False
    • tests/hermes_cli/test_doctor.py::test_run_doctor_does_not_warn_vendor_prefix_for_custom_named_providerprovider: custom:zenmux + default: z-ai/glm-5.1 does not produce the vendor-prefix warning
  • Adjacent suite: test_doctor.py + test_model_switch_custom_providers.py + test_custom_provider_model_switch.py + test_custom_provider_context_length.py — 83 passed locally
  • Regression guard: with the production diff reverted (tests-only), the two new positive-case tests fail with AssertionError: assert False is True for is_aggregator("custom:zenmux") and the vendor-prefix substring still appears in doctor output; restoring the fix makes them pass.

Related

Copilot AI review requested due to automatic review settings May 15, 2026 21:15
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard labels May 15, 2026
@briandevans

Copy link
Copy Markdown
Contributor Author

CI audit — both failing checks are pre-existing baselines on clean origin/main (42070ecef). Zero failures are in touched code (hermes_cli/doctor.py, hermes_cli/providers.py, tests/hermes_cli/test_doctor.py, tests/hermes_cli/test_model_switch_custom_providers.py).

Check Failure Status on main
test 3 × tests/run_agent/test_provider_parity.pyTestDeveloperRoleSwap::test_developer_role_via_nous_portal, TestBuildApiKwargsNousPortal::test_uses_chat_completions_format, ::test_includes_nous_product_tags. ValueError: Model has a context window of 15,000 tokens, which is below the minimum 64,000. The Nous portal /v1/models endpoint is hit at AIAgent.__init__ (live network call, not mocked); the test helpers pass model=None so the single-iter heuristic matches the first portal entry, which currently returns 15K. Fix in flight: #26310 / #26312 (both pass model="gpt-5" explicitly). unmerged
e2e 4 × tests/e2e/test_discord_adapter.pyTestMentionStrippedCommandDispatch::test_mention_then_command, ::test_nickname_mention_then_command, ::test_text_before_command_not_detected, TestAutoThreadingPreservesCommand::test_command_detected_after_auto_thread. AttributeError: 'types.SimpleNamespace' object has no attribute 'history' and TypeError: catching classes that do not inherit from BaseException. The recent default-on history backfill in gateway/platforms/discord.py::_fetch_channel_context calls channel.history(...) on the test fixture's SimpleNamespace, then except discord.Forbidden: can't catch the resulting AttributeError because mocked discord.Forbidden isn't a real exception class. Fix in flight: #26301 / #26310 / #26312. unmerged

Nothing in hermes_cli/doctor.py, hermes_cli/providers.py, or the touched test files calls into run_agent.py, gateway/platforms/discord.py, or the Nous portal client. Happy to rebase once any of the in-flight CI fixes lands.

@briandevans briandevans force-pushed the fix/custom-aggregator-recognition-26578 branch 4 times, most recently from b70bf77 to 4c0740f Compare May 25, 2026 03:12
@briandevans briandevans force-pushed the fix/custom-aggregator-recognition-26578 branch 3 times, most recently from 3207fb2 to 3ea80bf Compare May 30, 2026 09:12
… slugs (NousResearch#26578)

`custom:<name>` providers front arbitrary upstream catalogs (LiteLLM,
ZenMux, etc.) and commonly accept vendor-prefixed model slugs like
`google/gemini-3.1-flash-lite` or `z-ai/glm-5.1`. Two places treated
them as bare/unknown providers and broke that pattern:

- `hermes_cli/providers.py:is_aggregator()` only returned True for
  providers registered in HERMES_OVERLAYS with `is_aggregator=True`.
  `custom:zenmux` fell through to `get_provider()` which returns None
  for unknown slugs, so `is_aggregator()` returned False — breaking
  model_switch alias resolution for vendor-prefixed model IDs.

- `hermes_cli/doctor.py` vendor-prefix validation included bare
  `"custom"` in `providers_accepting_vendor_slugs` but not the
  `custom:<name>` namespace, producing a false-positive warning:
  "model.default 'z-ai/glm-5.1' is vendor-prefixed but model.provider
  is 'custom:zenmux'. Either set model.provider to 'openrouter', or
  drop the vendor prefix." — which is incorrect for aggregators.

Fix: short-circuit on the `custom:` prefix in both call sites.
Regression-guarded: with the production change reverted, all three
new assertions fail (verified locally).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@briandevans briandevans force-pushed the fix/custom-aggregator-recognition-26578 branch from 3ea80bf to 93836ee Compare May 31, 2026 09:14
@briandevans

Copy link
Copy Markdown
Contributor Author

Housekeeping: closing to keep my open-PR set focused on actively-reviewed work. This has been open ~17d without maintainer review and the surrounding code has continued to move, so it's unlikely to land as-is. The underlying fix still stands — happy to reopen and rebase if it would be useful. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

custom:<name> providers not recognized as aggregators (custom:zenmux)

2 participants