fix: keep nested model.default provider paired through HermesCLI

The prior dict coercion converted a dict-valued model.default to a plain
model string but dropped the nested provider. On the interactive CLI path
requested_provider then fell back to the outer merged model.provider
(typically "auto", authoritative at runtime resolution), so the model
could be routed through the wrong active provider.

Canonicalize both halves at the shared boundary: _split_model_config_default
flattens a dict-valued default into (model, provider) and HermesCLI.__init__
feeds the nested provider into the requested_provider chain (still below an
explicit --provider argument). new_session reuses the same helper.

Adds regression coverage asserting the nested provider stays paired with the
model, that flat string defaults keep the outer provider behavior, and that
an explicit provider argument still wins.
This commit is contained in:
mariobgsp
2026-08-12 00:30:25 +07:00
committed by kshitij
parent cb4daf23f3
commit 86054eff62
2 changed files with 86 additions and 8 deletions
+28 -8
View File
@@ -4381,6 +4381,26 @@ def _normalize_moa_model(model: Optional[str]) -> tuple[Optional[str], Optional[
return "moa", preset
return None, model
def _split_model_config_default(raw_default: Any) -> tuple[str, str]:
"""Canonicalize a config ``model.default``/``model.model`` value.
A dict-valued default (``model.default: {provider: ..., model: ...}``)
pairs the model string with the provider it must be routed through. The
dict is flattened here at the shared boundary so both halves stay
together through ``HermesCLI`` construction: the model becomes a plain
string and the provider is returned explicitly instead of being lost to
the outer merged ``model.provider`` default (often ``"auto"``, which
runtime resolution treats as authoritative and would otherwise route the
model through the wrong active provider).
Returns ``(model, provider)``; both are ``""`` when nothing is usable.
"""
if isinstance(raw_default, dict):
provider = str(raw_default.get("provider") or "").strip()
model = raw_default.get("model") or raw_default.get("default")
return (str(model or "").strip(), provider)
return (str(raw_default or "").strip(), "")
class _VoiceInputMessage:
"""Sentinel wrapper for voice-transcribed messages in ``_pending_input``.
@@ -4566,10 +4586,12 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
# env vars would stomp each other.
_model_config = CLI_CONFIG.get("model", {})
_raw_default = (_model_config.get("default") or _model_config.get("model") or "") if isinstance(_model_config, dict) else (_model_config or "")
if isinstance(_raw_default, dict):
_config_model = str(_raw_default.get("model") or _raw_default.get("default") or _raw_default.get("provider") or "")
else:
_config_model = str(_raw_default or "")
# A dict-valued default (``model.default: {provider: ..., model: ...}``)
# carries its own provider; flatten it here so the nested provider is
# available when ``requested_provider`` is constructed below instead of
# being discarded and replaced by the outer merged ``model.provider``
# (typically ``"auto"``, which is authoritative at runtime resolution).
_config_model, _nested_provider = _split_model_config_default(_raw_default)
_DEFAULT_CONFIG_MODEL = ""
# Track whether the user passed -m / --model so resume knows not to
# clobber an explicit override with the session's stored model.
@@ -4618,6 +4640,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
self.requested_provider = (
_moa_provider_override
or provider
or _nested_provider
or CLI_CONFIG["model"].get("provider")
or os.getenv("HERMES_INFERENCE_PROVIDER")
or "auto"
@@ -8981,10 +9004,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
)
_model_config = CLI_CONFIG.get("model", {})
_raw_default2 = (_model_config.get("default") or _model_config.get("model") or "") if isinstance(_model_config, dict) else (_model_config or "")
if isinstance(_raw_default2, dict):
_config_model = str(_raw_default2.get("model") or _raw_default2.get("default") or _raw_default2.get("provider") or "")
else:
_config_model = str(_raw_default2 or "")
_config_model, _ = _split_model_config_default(_raw_default2)
if _config_model and _config_model != getattr(self, "model", None):
_config_provider = (
_model_config.get("provider", "")