Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion acp_adapter/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ def _make_agent(
default_model = ""
config_provider = None
if isinstance(model_cfg, dict):
default_model = str(model_cfg.get("default") or default_model)
default_model = str(model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or default_model)
config_provider = model_cfg.get("provider")
elif isinstance(model_cfg, str) and model_cfg.strip():
default_model = model_cfg.strip()
Expand Down
4 changes: 2 additions & 2 deletions agent/auxiliary_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1683,7 +1683,7 @@ def _read_main_model() -> str:
if isinstance(model_cfg, str) and model_cfg.strip():
return model_cfg.strip()
if isinstance(model_cfg, dict):
default = model_cfg.get("default", "")
default = model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or ""
if isinstance(default, str) and default.strip():
return default.strip()
except Exception:
Expand Down Expand Up @@ -2026,7 +2026,7 @@ def _try_azure_foundry(
return None, None

final_model = _normalize_resolved_model(
model or str(model_cfg.get("default") or ""),
model or str(model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or ""),
"azure-foundry",
)
if not final_model:
Expand Down
2 changes: 1 addition & 1 deletion cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3002,7 +3002,7 @@ def __init__(
# authoritative. This avoids conflicts in multi-agent setups where
# env vars would stomp each other.
_model_config = CLI_CONFIG.get("model", {})
_config_model = (_model_config.get("default") or _model_config.get("model") or "") if isinstance(_model_config, dict) else (_model_config or "")
_config_model = (_model_config.get("default") or _model_config.get("name") or _model_config.get("model") or "") if isinstance(_model_config, dict) else (_model_config or "")
_DEFAULT_CONFIG_MODEL = ""
self.model = model or _config_model or _DEFAULT_CONFIG_MODEL
# Auto-detect model from local server if still on default
Expand Down
2 changes: 1 addition & 1 deletion cron/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1499,7 +1499,7 @@ def _run_job_impl(job: dict) -> tuple[bool, str, str, Optional[str]]:
if isinstance(_model_cfg, str):
model = _model_cfg
elif isinstance(_model_cfg, dict):
model = _model_cfg.get("default", model)
model = _model_cfg.get("default") or _model_cfg.get("name") or _model_cfg.get("model") or model
except Exception as e:
logger.warning("Job '%s': failed to load config.yaml, using defaults: %s", job_id, e)

Expand Down
6 changes: 3 additions & 3 deletions gateway/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -1455,7 +1455,7 @@ def _resolve_gateway_model(config: dict | None = None) -> str:
if isinstance(model_cfg, str):
return model_cfg
elif isinstance(model_cfg, dict):
return model_cfg.get("default") or model_cfg.get("model") or ""
return model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or ""
return ""


Expand Down Expand Up @@ -8353,7 +8353,7 @@ async def _handle_message_with_agent(self, event, source, _quick_key: str, run_g
if isinstance(_model_cfg, str):
_hyg_model = _model_cfg
elif isinstance(_model_cfg, dict):
_hyg_model = _model_cfg.get("default") or _model_cfg.get("model") or _hyg_model
_hyg_model = _model_cfg.get("default") or _model_cfg.get("name") or _model_cfg.get("model") or _hyg_model
# Read explicit context_length override from model config
# (same as run_agent.py lines 995-1005)
_raw_ctx = _model_cfg.get("context_length")
Expand Down Expand Up @@ -10177,7 +10177,7 @@ async def _handle_model_command(self, event: MessageEvent) -> Optional[str]:
if cfg:
model_cfg = cfg.get("model", {})
if isinstance(model_cfg, dict):
current_model = model_cfg.get("default", "")
current_model = model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or ""
current_provider = model_cfg.get("provider", current_provider)
current_base_url = model_cfg.get("base_url", "")
user_provs = cfg.get("providers")
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ def run_doctor(args):
model_section = cfg.get("model") or {}
provider_raw = (model_section.get("provider") or "").strip()
provider = provider_raw.lower()
default_model = (model_section.get("default") or model_section.get("model") or "").strip()
default_model = (model_section.get("default") or model_section.get("name") or model_section.get("model") or "").strip()

known_providers: set = set()
try:
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def _get_model_and_provider(config: dict) -> tuple[str, str]:
"""Extract model and provider from config."""
model_cfg = config.get("model", "")
if isinstance(model_cfg, dict):
model = model_cfg.get("default") or model_cfg.get("model") or model_cfg.get("name") or "(not set)"
model = model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or "(not set)"
provider = model_cfg.get("provider") or "(auto)"
elif isinstance(model_cfg, str):
model = model_cfg or "(not set)"
Expand Down
4 changes: 2 additions & 2 deletions hermes_cli/fallback_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def _extract_fallback_from_model_cfg(model_cfg: Any) -> Optional[Dict[str, Any]]
return None
provider = (model_cfg.get("provider") or "").strip()
# The picker writes the selected model to ``model.default``.
model = (model_cfg.get("default") or model_cfg.get("model") or "").strip()
model = (model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or "").strip()
if not provider or not model:
return None
entry: Dict[str, Any] = {"provider": provider, "model": model}
Expand Down Expand Up @@ -137,7 +137,7 @@ def _describe_primary(config: Dict[str, Any]) -> Optional[str]:
model_cfg = config.get("model")
if isinstance(model_cfg, dict):
provider = (model_cfg.get("provider") or "?").strip() or "?"
model = (model_cfg.get("default") or model_cfg.get("model") or "?").strip() or "?"
model = (model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or "?").strip() or "?"
return f"{model} (via {provider})"
if isinstance(model_cfg, str) and model_cfg.strip():
return model_cfg.strip()
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def load_picker_context() -> ConfigContext:
cfg = load_config()
model_cfg = cfg.get("model", {})
if isinstance(model_cfg, dict):
current_model = model_cfg.get("default", model_cfg.get("name", "")) or ""
current_model = model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or ""
current_provider = model_cfg.get("provider", "") or ""
current_base_url = model_cfg.get("base_url", "") or ""
else:
Expand Down
4 changes: 2 additions & 2 deletions hermes_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ def _has_any_provider_configured() -> bool:
cfg = load_config()
model_cfg = cfg.get("model")
if isinstance(model_cfg, dict):
_model_name = (model_cfg.get("default") or "").strip()
_model_name = (model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or "").strip()
elif isinstance(model_cfg, str):
_model_name = model_cfg.strip()
else:
Expand Down Expand Up @@ -2125,7 +2125,7 @@ def select_provider_and_model(args=None):
config = load_config()
current_model = config.get("model")
if isinstance(current_model, dict):
current_model = current_model.get("default", "")
current_model = current_model.get("default") or current_model.get("name") or current_model.get("model") or ""
current_model = current_model or "(not set)"

# Read effective provider the same way the CLI does at startup:
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/oneshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ def _run_agent(
if isinstance(model_cfg, str):
cfg_model = model_cfg
else:
cfg_model = model_cfg.get("default") or model_cfg.get("model") or ""
cfg_model = model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or ""

env_model = os.getenv("HERMES_INFERENCE_MODEL", "").strip()
effective_model = (model or "").strip() or env_model or cfg_model
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ def _read_config_model(profile_dir: Path) -> tuple:
if isinstance(model_cfg, str):
return model_cfg, None
if isinstance(model_cfg, dict):
return model_cfg.get("default") or model_cfg.get("model"), model_cfg.get("provider")
return model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model"), model_cfg.get("provider")
return None, None
except Exception:
return None, None
Expand Down
10 changes: 5 additions & 5 deletions hermes_cli/runtime_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def _copilot_runtime_api_mode(model_cfg: Dict[str, Any], api_key: str) -> str:
if configured_mode and _provider_supports_explicit_api_mode("copilot", configured_provider):
return configured_mode

model_name = str(model_cfg.get("default") or "").strip()
model_name = str(model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or "").strip()
if not model_name:
return "chat_completions"

Expand Down Expand Up @@ -302,7 +302,7 @@ def _resolve_runtime_from_pool_entry(
# longer matches the model actually being used — the bug that caused
# opencode-zen /v1 to be stripped for chat_completions requests when
# config.default was still a Claude model.
effective_model = (target_model or model_cfg.get("default") or "")
effective_model = (target_model or model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or "")
base_url = (getattr(entry, "runtime_base_url", None) or getattr(entry, "base_url", None) or "").rstrip("/")
api_key = getattr(entry, "runtime_api_key", None) or getattr(entry, "access_token", "")
api_mode = "chat_completions"
Expand Down Expand Up @@ -929,7 +929,7 @@ def _resolve_azure_foundry_runtime(
# against them returns 400 "The requested operation is unsupported."
# Upgrade api_mode when the model name matches, unless the user has
# explicitly chosen anthropic_messages (Anthropic-style endpoint).
effective_model = str(target_model or model_cfg.get("default") or "").strip()
effective_model = str(target_model or model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or "").strip()
if effective_model and cfg_api_mode != "anthropic_messages":
try:
from hermes_cli.models import azure_foundry_model_api_mode
Expand Down Expand Up @@ -1570,7 +1570,7 @@ def resolve_runtime_provider(
# Dual-path routing: Claude models use AnthropicBedrock SDK for full
# feature parity (prompt caching, thinking budgets, adaptive thinking).
# Non-Claude models use the Converse API for multi-model support.
_current_model = str(model_cfg.get("default") or "").strip()
_current_model = str(model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or "").strip()
if is_anthropic_bedrock_model(_current_model):
# Claude on Bedrock → AnthropicBedrock SDK → anthropic_messages path
runtime = {
Expand Down Expand Up @@ -1630,7 +1630,7 @@ def resolve_runtime_provider(
# from base_url for chat_completions models and 404'ing.
# Refs #16878.
from hermes_cli.models import opencode_model_api_mode
_effective = target_model or model_cfg.get("default", "")
_effective = target_model or model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or ""
api_mode = opencode_model_api_mode(provider, _effective)
elif configured_mode and _provider_supports_explicit_api_mode(provider, configured_provider):
api_mode = configured_mode
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2720,7 +2720,7 @@ def _get_section_config_summary(config: dict, section_key: str) -> Optional[str]
if isinstance(model, str) and model.strip():
return model.strip()
if isinstance(model, dict):
return str(model.get("default") or model.get("model") or "configured")
return str(model.get("default") or model.get("name") or model.get("model") or "configured")
return "configured"

elif section_key == "terminal":
Expand Down
6 changes: 3 additions & 3 deletions hermes_cli/web_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@ def _normalize_config_for_web(config: Dict[str, Any]) -> Dict[str, Any]:
if isinstance(model_val, dict):
# Extract context_length before flattening the dict
ctx_len = model_val.get("context_length", 0)
config["model"] = model_val.get("default", model_val.get("name", ""))
config["model"] = model_val.get("default") or model_val.get("name") or model_val.get("model") or ""
config["model_context_length"] = ctx_len if isinstance(ctx_len, int) else 0
else:
config["model_context_length"] = 0
Expand Down Expand Up @@ -901,7 +901,7 @@ def get_model_info():

# Extract model name and provider from the config
if isinstance(model_cfg, dict):
model_name = model_cfg.get("default", model_cfg.get("name", ""))
model_name = model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or ""
provider = model_cfg.get("provider", "")
base_url = model_cfg.get("base_url", "")
config_ctx = model_cfg.get("context_length")
Expand Down Expand Up @@ -1038,7 +1038,7 @@ def get_auxiliary_models():
if isinstance(model_cfg, dict):
main = {
"provider": str(model_cfg.get("provider", "") or ""),
"model": str(model_cfg.get("default", model_cfg.get("name", "")) or ""),
"model": str(model_cfg.get("default") or model_cfg.get("name") or model_cfg.get("model") or ""),
}
else:
main = {"provider": "", "model": str(model_cfg) if model_cfg else ""}
Expand Down
37 changes: 37 additions & 0 deletions tests/cli/test_cli_provider_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,43 @@ def _runtime_resolve(**kwargs):
assert shell.model != "should-be-ignored"


def test_cli_uses_legacy_model_name_for_named_custom_provider(monkeypatch):
"""Legacy/custom-provider configs may use model.name instead of model.default.

Regression test for #34500: the CLI must pass that configured model through
to AIAgent so OpenAI-compatible custom backends do not receive model="".
"""
cli = _import_cli()

monkeypatch.setitem(cli.CLI_CONFIG, "model", {
"name": "claude-sonnet-4-20250514",
"provider": "my-litellm",
})

def _runtime_resolve(**kwargs):
return {
"provider": "my-litellm",
"api_mode": "chat_completions",
"base_url": "http://litellm-proxy:4000/v1",
"api_key": "test-key",
"source": "config/custom_providers",
}

class _DummyAgent:
def __init__(self, *args, **kwargs):
self.kwargs = kwargs

monkeypatch.setattr("hermes_cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
monkeypatch.setattr("hermes_cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
monkeypatch.setattr(cli, "AIAgent", _DummyAgent)

shell = cli.HermesCLI(compact=True, max_turns=1)

assert shell.model == "claude-sonnet-4-20250514"
assert shell._init_agent() is True
assert shell.agent.kwargs["model"] == "claude-sonnet-4-20250514"


def test_codex_config_model_not_replaced_by_normalization(monkeypatch):
"""When the user sets model.default in config.yaml to a specific codex
model, _normalize_model_for_provider must NOT replace it with the latest
Expand Down
2 changes: 1 addition & 1 deletion tui_gateway/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ def _resolve_model() -> str:
return env
m = _load_cfg().get("model", "")
if isinstance(m, dict):
return str(m.get("default", "") or "").strip()
return str(m.get("default") or m.get("name") or m.get("model") or "").strip()
if isinstance(m, str) and m:
return m.strip()
return "anthropic/claude-sonnet-4"
Expand Down