Skip to content

fix(config): custom provider + :free/:beta/:thinking suffix mis-resolution (#1776) - #1783

Closed
Sanjays2402 wants to merge 1 commit into
nesquena:masterfrom
Sanjays2402:fix/1776-custom-provider-free-suffix
Closed

fix(config): custom provider + :free/:beta/:thinking suffix mis-resolution (#1776)#1783
Sanjays2402 wants to merge 1 commit into
nesquena:masterfrom
Sanjays2402:fix/1776-custom-provider-free-suffix

Conversation

@Sanjays2402

Copy link
Copy Markdown
Contributor

Summary

Closes #1776.

PR #1762 fixed the rsplit grammar collision for plain @openrouter:model:free qualifiers, but skipped the fallback whenever the provider hint started with custom: on the assumption that custom providers route directly. That left @custom:my-key:some-model:free broken — issue body and the maintainer's confirmation comment trace the exact failure mode.

Approach

Adopted the maintainer's recommended approach (no allowlist needed). Custom hints carry exactly one segment after custom: (constructed at api/config.py:1363 as \"custom:\" + entry_name.lower().replace(\" \", \"-\")), so any rsplit result of custom:<a>:<b> with bare model <c> has eaten a model segment. Peel it back with a second rsplit and prepend to the bare model.

provider_hint, bare_model = inner.rsplit(":", 1)
if provider_hint.startswith("custom:") and provider_hint.count(":") >= 2:
    provider_hint, extra = provider_hint.rsplit(":", 1)
    bare_model = f"{extra}:{bare_model}"
elif (provider_hint not in _PROVIDER_MODELS
        and provider_hint not in _PROVIDER_DISPLAY
        and not provider_hint.startswith("custom:")):
    provider_hint, bare_model = inner.split(":", 1)

Robust for :free / :beta / :thinking / :preview / any future OpenRouter suffix — no allowlist to keep in sync.

Resolution matrix (verified by tests)

Input provider model
@custom:my-key:some-model:free custom:my-key some-model:free
@custom:my-key:some-model:beta custom:my-key some-model:beta
@custom:my-key:some-model:thinking custom:my-key some-model:thinking
@custom:my-key:some-model:preview custom:my-key some-model:preview
@custom:my-key:org/model:free custom:my-key org/model:free
@custom:my-key:some-model (no suffix) custom:my-key some-model ✅ (existing test)
@openrouter:tencent/hy3-preview:free openrouter tencent/hy3-preview:free ✅ (existing test)
@anthropic:claude-sonnet-4.6 anthropic claude-sonnet-4.6 ✅ (existing test)

Tests

$ python -m pytest tests/test_resolve_model_provider_free_suffix.py tests/test_issue1228_model_picker_duplicate_ids.py -v
============================== 29 passed in 2.77s ==============================

Open question (left for separate issue)

Per the maintainer's comment: entry_name.lower().replace(\" \", \"-\") doesn't strip colons, so a user who literally names a custom provider \"foo:bar\" would still produce a custom:foo:bar slug that this fix would mis-handle. Probably worth a separate input-validation issue to forbid : in custom_provider names rather than complicating this resolver. Happy to file a follow-up if useful.

…ution (nesquena#1776)

PR nesquena#1762 fixed the rsplit grammar collision for plain @openrouter:model:free
qualifiers, but skipped the fallback whenever the provider hint started with
'custom:' on the assumption that custom providers route directly. That left
'@Custom:my-key:some-model:free' broken: rsplit yields
provider='custom:my-key:some-model', bare='free' → custom guard skips the
split-fallback → returns provider='custom:my-key:some-model', model='free'.

Detect the over-split structurally instead of using a known-suffix allowlist:
custom hints carry exactly one segment after 'custom:' (constructed at
api/config.py:1363 as 'custom:' + entry_name). So any rsplit result of
'custom:<a>:<b>' with bare model '<c>' has eaten one model segment — peel
it back with a second rsplit and prepend it to the bare model.

This is robust for :free / :beta / :thinking / :preview / any future
OpenRouter suffix without an allowlist to maintain.

Adds 5 regression tests covering the matrix (free/beta/thinking/preview/
slashed-model). All 7 existing nesquena#1744 tests still pass; nesquena#1228 tests
unaffected.

Co-authored-by: Cake <51058514+Sanjays2402@users.noreply.github.com>
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Thanks @Sanjays2402 — this shipped in v0.51.18 (commit a8de4e7) as part of a 5-PR full-sweep batch. Stage rebased your branch onto current master, ran the full pre-release gate (4733 pytest, browser tests, Opus advisor verdict SHIP all 5), and merged via release PR #1799.

GitHub didn't auto-close because the merge commit only references the squash-merged stage branch, not your fork's commit directly — closing manually for hygiene.

Live now on existing installs after git pull + restart.

Release notes: https://github.com/nesquena/hermes-webui/releases/tag/v0.51.18

pull Bot pushed a commit to soitun/hermes-webui that referenced this pull request May 7, 2026
pull Bot pushed a commit to soitun/hermes-webui that referenced this pull request May 7, 2026
…1789, nesquena#1790, nesquena#1791, nesquena#1794)

Constituent PRs:
- nesquena#1783 (@Sanjays2402) custom provider + :free/:beta/:thinking suffix fix. Closes nesquena#1776.
- nesquena#1789 (@Michaelyklam) preserve sidebar scrolling while streaming. Closes nesquena#1784.
- nesquena#1790 (@Michaelyklam) keep workspace open from preview breadcrumb. Closes nesquena#1785.
- nesquena#1791 (@Michaelyklam) keep assistant-only stream deltas on current turn. Closes nesquena#1787.
- nesquena#1794 (@nesquena-hermes, APPROVED by @nesquena) UX bundle: rail tooltip
  cascade fix, +new-conversation has-tooltip--bottom-right variant, context-menu
  hover-bg, rename pre-fill via setSelectionRange.

Tests: 4723 → 4747 collected (+24). 4733 passed, 0 failed in 149s.

Pre-release verification:
- All 5 PRs CI-green individually
- File overlaps (style.css + ui.js between nesquena#1789 + nesquena#1794) auto-merged cleanly
- node -c clean on all 4 changed JS files
- Browser API sanity 11/11 endpoints
- Pre-stamp re-fetch: all PR heads match local rebases
- Opus advisor: SHIP all 5, 0 MUST-FIX, 1 SHOULD-NOTE on test pattern divergence (acceptable)

Closes nesquena#1776, nesquena#1784, nesquena#1785, nesquena#1787.
Du7chManiac pushed a commit to TheCouchCoder-com/hermes-webui that referenced this pull request May 13, 2026
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
…1789, nesquena#1790, nesquena#1791, nesquena#1794)

Constituent PRs:
- nesquena#1783 (@Sanjays2402) custom provider + :free/:beta/:thinking suffix fix. Closes nesquena#1776.
- nesquena#1789 (@Michaelyklam) preserve sidebar scrolling while streaming. Closes nesquena#1784.
- nesquena#1790 (@Michaelyklam) keep workspace open from preview breadcrumb. Closes nesquena#1785.
- nesquena#1791 (@Michaelyklam) keep assistant-only stream deltas on current turn. Closes nesquena#1787.
- nesquena#1794 (@nesquena-hermes, APPROVED by @nesquena) UX bundle: rail tooltip
  cascade fix, +new-conversation has-tooltip--bottom-right variant, context-menu
  hover-bg, rename pre-fill via setSelectionRange.

Tests: 4723 → 4747 collected (+24). 4733 passed, 0 failed in 149s.

Pre-release verification:
- All 5 PRs CI-green individually
- File overlaps (style.css + ui.js between nesquena#1789 + nesquena#1794) auto-merged cleanly
- node -c clean on all 4 changed JS files
- Browser API sanity 11/11 endpoints
- Pre-stamp re-fetch: all PR heads match local rebases
- Opus advisor: SHIP all 5, 0 MUST-FIX, 1 SHOULD-NOTE on test pattern divergence (acceptable)

Closes nesquena#1776, nesquena#1784, nesquena#1785, nesquena#1787.
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
bernyforce pushed a commit to bernyforce/hermes-webui that referenced this pull request Jul 29, 2026
bernyforce pushed a commit to bernyforce/hermes-webui that referenced this pull request Jul 29, 2026
…1789, nesquena#1790, nesquena#1791, nesquena#1794)

Constituent PRs:
- nesquena#1783 (@Sanjays2402) custom provider + :free/:beta/:thinking suffix fix. Closes nesquena#1776.
- nesquena#1789 (@Michaelyklam) preserve sidebar scrolling while streaming. Closes nesquena#1784.
- nesquena#1790 (@Michaelyklam) keep workspace open from preview breadcrumb. Closes nesquena#1785.
- nesquena#1791 (@Michaelyklam) keep assistant-only stream deltas on current turn. Closes nesquena#1787.
- nesquena#1794 (@nesquena-hermes, APPROVED by @nesquena) UX bundle: rail tooltip
  cascade fix, +new-conversation has-tooltip--bottom-right variant, context-menu
  hover-bg, rename pre-fill via setSelectionRange.

Tests: 4723 → 4747 collected (+24). 4733 passed, 0 failed in 149s.

Pre-release verification:
- All 5 PRs CI-green individually
- File overlaps (style.css + ui.js between nesquena#1789 + nesquena#1794) auto-merged cleanly
- node -c clean on all 4 changed JS files
- Browser API sanity 11/11 endpoints
- Pre-stamp re-fetch: all PR heads match local rebases
- Opus advisor: SHIP all 5, 0 MUST-FIX, 1 SHOULD-NOTE on test pattern divergence (acceptable)

Closes nesquena#1776, nesquena#1784, nesquena#1785, nesquena#1787.
bernyforce pushed a commit to bernyforce/hermes-webui that referenced this pull request Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom provider + :free suffix mis-resolves in resolve_model_provider() (PR #1762 follow-up)

2 participants