Skip to content

Fix _normalizeConfiguredModelKey in frontend to match backend behavior#1474

Merged
1 commit merged intonesquena:masterfrom
happy5318:fix-frontend-model-id-normalization
May 2, 2026
Merged

Fix _normalizeConfiguredModelKey in frontend to match backend behavior#1474
1 commit merged intonesquena:masterfrom
happy5318:fix-frontend-model-id-normalization

Conversation

@happy5318
Copy link
Copy Markdown
Contributor

Summary

This PR fixes the same bug in the JavaScript frontend that was fixed in the Python backend in commit #d6164cd.

Problem

The JavaScript _normalizeConfiguredModelKey function used substring(indexOf(:)+1) which only removes the first colon-separated segment, leaving provider names in the normalized model ID.

For example, @custom:jingdong:GLM-5 became jingdong:glm.5 instead of glm.5.

This caused:

  • Duplicate Primary badges appearing in the model dropdown
  • Configured model badge injection check failing for custom providers

Solution

Replace substring(indexOf(:)+1) with split(:).pop() to match the Python backend behavior and strip all colon prefixes.

Bonus

Added provider name to badge label for clarity (e.g., "Primary (jingdong)" instead of just "Primary").

Testing

  • Verified model dropdown shows only one Primary badge
  • Verified badge displays provider name correctly
  • Tested with @custom:jingdong:GLM-5 format model IDs

Related

  • Follow-up to commit d6164cd which fixed the same issue in Python backend

The JavaScript _normalizeConfiguredModelKey function had the same bug as the
Python _norm_model_id function that was fixed in commit d6164cd. It used
substring(indexOf(':')+1) which only removes the first colon-separated segment,
leaving provider names in the normalized model ID.

For example, '@Custom:jingdong:GLM-5' became 'jingdong:glm.5' instead of 'glm.5'.

This caused duplicate Primary badges to appear in the model dropdown when using
custom providers with @Provider:model ID format.

Changes:
- Replace substring(indexOf(':')+1) with split(':').pop() to strip all colon prefixes
- Add provider name to badge label for clarity (e.g., 'Primary (jingdong)')
@nesquena-hermes
Copy link
Copy Markdown
Collaborator

Thanks for the matching frontend follow-up — the diagnosis is correct and the JS/Python normalizers should stay in lock-step.

One concern with the proposed fix that mirrors the same issue I just flagged on the backend PR (#1454): split(':').pop() is too aggressive for model IDs that legitimately contain a colon inside the model name. The most common case is OpenRouter's free-tier suffix:

@openrouter:meta-llama/llama-3.1-405b:free
  • Old code (substring(s.indexOf(':')+1)) → meta-llama/llama-3.1-405b:free → after / split + -.llama.3.1.405b:free
  • New code (split(':').pop()) → freefree

That collapses every :free variant to the same normalized key ("free"), which would silently dedupe distinct OpenRouter free models against each other. That is a worse failure mode than the duplicate Primary badge this PR is trying to fix — and it directly conflicts with #1426 (OpenRouter free-tier visibility).

A more conservative shape that handles @custom:provider:model without breaking @provider:model:variant:

if(s.startsWith('@')&&s.includes(':')){
  const parts=s.split(':');
  // @custom:<provider>:<model...> → strip both prefix segments
  if(parts.length>=3 && parts[0]==='@custom'){
    s=parts.slice(2).join(':');
  } else {
    // @<provider>:<model...> → strip only the @provider segment
    s=parts.length===2 ? parts[1] : parts.slice(1).join(':');
  }
}

This needs to land together with the backend fix in #1454 — they have to use the exact same algorithm, or the badge map keys (built server-side) won't match the lookup keys (built client-side). Whichever shape we converge on in #1454, mirror it here verbatim.

Bonus badge label change

The Primary (jingdong) enrichment is reasonable and surgical — I'd keep that part as-is. Two minor notes:

  1. Empty-provider safety: m.badge.provider exists in the current map shape, but if a future code path ever sets it to "" you'd render Primary (). Cheap guard: if(m.badge&&m.badge.provider&&m.badge.provider.trim()){...}.
  2. The replace(/^custom:/,'').split('/')[0] collapses custom:openrouteropenrouter correctly, but for a raw provider like openrouter/sonoma it gives openrouter, which loses the sub-provider. Probably the right tradeoff for a compact badge — just confirming intent.

Tests

This is renderer-pipeline glue + a normalizer tweak — both should have a targeted Python/JS test:

  • tests/test_norm_model_id_javascript.py (or similar) that scrapes _normalizeConfiguredModelKey from static/ui.js and asserts the OpenRouter :free case + the @custom:provider:model case both produce sensible keys.

Labelling bug + frontend + needs-rework. Please rebase against #1454 once that lands and we converge on the algorithm.

cc: @nesquena for the algorithm decision in #1454.

@nesquena-hermes nesquena-hermes closed this pull request by merging all changes into nesquena:master in 5650d11 May 2, 2026
Thanatos-Z pushed a commit to Thanatos-Z/hermes-webui that referenced this pull request May 2, 2026
Thanatos-Z pushed a commit to Thanatos-Z/hermes-webui that referenced this pull request May 2, 2026
…w-up

- CHANGELOG.md: v0.50.267 entry detailing nesquena#1454/nesquena#1474/nesquena#1461/nesquena#1465/nesquena#1467/nesquena#1460/nesquena#1473
  + Opus advisor SHOULD-FIX trailing-empty guard for _norm_model_id
- ROADMAP.md: bump to v0.50.267, 3776 tests collected
- TESTING.md: bump header + total to 3776
- api/config.py: trailing-empty fallback in _norm_model_id (parts[-1] or s)
- static/ui.js: mirror trailing-empty fallback in _normalizeConfiguredModelKey
- tests/test_norm_model_id_trailing_empty_guard.py: 5 regression tests
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.

2 participants