Dash vs dot notation in Anthropic model IDs across providers
NousResearch/hermes-agent · cluster 6879 · agent
Data as of
3 open items. Fix merged in #11561. Check it covers your case.
The short answer
- Known?
- Yes. 8 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #11561. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #11561. That is separate from the fix event. - Still open
- 2 issues and 1 PR.
Next step: read #11561, then check whether your report is one of the 2 still open.
Read #11561Pick up a Lab taskMultiple providers (Copilot, OpenRouter, Nous) reject or misroute Anthropic model IDs when submitted in dash-notation (e.g. claude-opus-4-8) instead of dot-notation (e.g. claude-opus-4.8). The issues and accompanying fixes address normalization and aliasing of these ID formats across different provider integrations.
Open issues2
- Issue#45362nous provider: dash-style model ids (anthropic/claude-sonnet-4-6) 404 where Nous needs dot-style (…4.6); catalog also lists chat-uncallable models (claude-fable-5) — opaque 'Couldn't find that, sorry.'
- Issue#65152_find_openrouter_slug fails on Anthropic-native dash IDs (e.g. claude-opus-4-8), causing HTTP 400 not a valid model ID
Open PRs1
- PR#45532fix: nous provider dash-vs-dot model ID format and uncallable models
Fixes on record
-
#11561 fix(copilot): normalize vendor-prefixed and dash-notation model IDs (#6879)
files:
hermes_cli/model_normalize.py,hermes_cli/models.py,tests/hermes_cli/test_model_normalize.pyShow the diff
-
hermes_cli/model_normalize.py@@ -374,7 +374,26 @@ def normalize_model_for_provider(model_input: str, target_provider: str) -> str: return bare return _dots_to_hyphens(bare) - # --- Copilot: strip matching provider prefix, keep dots --- + # --- Copilot / Copilot ACP: delegate to the Copilot-specific + # normalizer. It knows about the alias table (vendor-prefix + # stripping for Anthropic/OpenAI, dash-to-dot repair for Claude) + # and live-catalog lookups. Without this, vendor-prefixed or + # dash-notation Claude IDs survive to the Copilot API and hit + # HTTP 400 "model_not_supported". See issue #6879. + if provider in {"copilot", "copilot-acp"}: + try: + from hermes_cli.models import normalize_copilot_model_id + + normalized = normalize_copilot_model_id(name) + if normalized: + return normalized + except Exception: + # Fall through to the generic strip-vendor behaviour below + # if the Copilot-specific path is unavailable for any reason. + pass + + # --- Copilot / Copilot ACP / openai-codex fallback: + # strip matching provider prefix, keep dots --- if provider in _STRIP_VENDOR_ONLY_PROVIDERS: stripped = _strip_matching_provider_prefix(name, provider) if stripped == name and name.startswith("openai/"): -
hermes_cli/models.py@@ -1488,6 +1488,19 @@ def _fetch_github_models(api_key: [redacted:secret-assignment] = None, timeout: float = 5.0) -> "anthropic/claude-sonnet-4.6": "claude-sonnet-4.6", "anthropic/claude-sonnet-4.5": "claude-sonnet-4.5", "anthropic/claude-haiku-4.5": "claude-haiku-4.5", + # Dash-notation fallbacks: Hermes' default Claude IDs elsewhere use + # hyphens (anthropic native format), but Copilot's API only accepts + # dot-notation. Accept both so users who configure copilot + a + # default hyphenated Claude model don't hit HTTP 400 + # "model_not_supported". See issue #6879. + "claude-opus-4-6": "claude-opus-4.6", + "claude-sonnet-4-6": "claude-sonnet-4.6", + "claude-sonnet-4-5": "claude-sonnet-4.5", + "claude-haiku-4-5": "claude-haiku-4.5", + "anthropic/claude-opus-4-6": "claude-opus-4.6", + "anthropic/claude-sonnet-4-6": "claude-sonnet-4.6", + "anthropic/claude-sonnet-4-5": "claude-sonnet-4.5", + "anthropic/claude-haiku-4-5": "claude-haiku-4.5", } -
tests/hermes_cli/test_model_normalize.py@@ -93,6 +93,59 @@ def test_copilot_preserves_dots(self, model, expected): assert result == expected +# ── Copilot model-name normalization (issue #6879 regression) ────────── + +class TestCopilotModelNormalization: + """Copilot requires bare dot-notation model IDs. + + Regression coverage for issue #6879 and the broken Copilot branch + that previously left vendor-prefixed Anthropic IDs (e.g. + ``anthropic/claude-sonnet-4.6``) and dash-notation Claude IDs (e.g. + ``claude-sonnet-4-6``) unchanged, causing the Copilot API to reject + the request with HTTP 400 "model_not_supported". + """ + + @pytest.mark.parametrize("model,expected", [ + # Vendor-prefixed Anthropic IDs — prefix must be stripped. + ("anthropic/claude-opus-4.6", "claude-opus-4.6"), + ("anthropic/claude-sonnet-4.6", "claude-sonnet-4.6"), + ("anthropic/claude-sonnet-4.5", "claude-sonnet-4.5"), + ("anthropic/claude-haiku-4.5", "claude-haiku-4.5"), + # Vendor-prefixed OpenAI IDs — prefix must be stripped. + ("openai/gpt-5.4", "gpt-5.4"), + ("openai/gpt-4o", "gpt-4o"), + ("openai/gpt-4o-mini", "gpt-4o-mini"), + # Dash-notation Claude IDs — must be converted to dot-notation. + ("claude-opus-4-6", "claude-opus-4.6"), + ("claude-sonnet-4-6", "claude-sonnet-4.6"), + ("claude-sonnet-4-5", "claude-sonnet-4.5"), + ("claude-haiku-4-5", "claude-haiku-4.5"), + # Combined: vendor-prefixed + dash-notation. + ("anthropic/claude-opus-4-6", "claude-opus-4.6"), + ("anthropic/claude-sonnet-4-6", "claude-sonnet-4.6"), + # Already-canonical inputs pass through unchanged. + ("claude-sonnet-4.6", "claude-sonnet-4.6"), + ("gpt-5.4", "gpt-5.4"), + ("gpt-5-mini", "gpt-5-mini"), + ]) + def test_copilot_normalization(self, model, expected): + assert normalize_model_for_provider(model, "copilot") == expected + + @pytest.mark.parametrize("model,expected", [ + ("anthropic/claude-sonnet-4.6", "claude-sonnet-4.6"), + ("claude-sonnet-4-6", "claude-sonnet-4.6"), + ("claude-opus-4-6", "claude-opus-4.6"), + ("openai/gpt-5.4", "gpt-5.4"), + ]) + def test_copilot_acp_normalization(self, model, expected): + """Copilot ACP shares the same API expectations as HTTP Copilot.""" + assert normalize_model_for_provider(model, "copilot-acp") == expected + + def test_openai_codex_still_strips_openai_prefix(self): + """Regression: openai-codex must still strip the openai/ prefix.""" + assert normalize_model_for_provider("openai/gpt-5.4", "openai-codex") == "gpt-5.4" + + # ── Aggregator providers (regression) ────────────────────────────────── class TestAggregatorProviders:
-
3 PRs closed without merging
From the close comments: done elsewhere 1 · duplicate 1 · merged via another change 1
- #6743 fix(copilot): add dash-notation aliases for Claude model IDs · merged via another change
-
#45405 fix(models): normalize Anthropic dash-style IDs to dot-style for aggregator providers · duplicate →
#45367 -
#65167 fix(models): resolve dash-form model IDs against dotted OpenRouter catalog (#65152) · done elsewhere →
148a625,#65152
Closed issues · 1
- Closed#6879[Bug]: Copilot provider rejects Claude model names in dash-notation (HTTP 400)
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.20: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- agent
- Merged PRs
- #11561
- Closed issues
- 1
- Attempts
- 3 PRs closed without merging
- Fix link checked
- 17 Apr 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.