mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 01:21:43 +00:00
fix: remove OpenRouter '/' format enforcement — let API probe be the authority
Not all providers require 'provider/model' format. Removing the rigid format check lets the live API probe handle all validation uniformly. If someone types 'gpt-5.4' on OpenRouter, the probe won't find it and will suggest 'openai/gpt-5.4' — better UX than a format rejection.
This commit is contained in:
parent
245d174359
commit
8c734f2f27
3 changed files with 15 additions and 32 deletions
|
|
@ -177,23 +177,6 @@ def validate_requested_model(
|
||||||
"message": "Model names cannot contain spaces.",
|
"message": "Model names cannot contain spaces.",
|
||||||
}
|
}
|
||||||
|
|
||||||
# OpenRouter requires provider/model format
|
|
||||||
if normalized == "openrouter":
|
|
||||||
if "/" not in requested or requested.startswith("/") or requested.endswith("/"):
|
|
||||||
known_models = provider_model_ids(normalized)
|
|
||||||
suggestion = get_close_matches(requested, known_models, n=1, cutoff=0.6)
|
|
||||||
suggestion_text = f" Did you mean `{suggestion[0]}`?" if suggestion else ""
|
|
||||||
return {
|
|
||||||
"accepted": False,
|
|
||||||
"persist": False,
|
|
||||||
"recognized": False,
|
|
||||||
"message": (
|
|
||||||
"OpenRouter model IDs should use the `provider/model` format "
|
|
||||||
"(for example `anthropic/claude-opus-4.6`)."
|
|
||||||
f"{suggestion_text}"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Probe the live API to check if the model actually exists
|
# Probe the live API to check if the model actually exists
|
||||||
api_models = fetch_api_models(api_key, base_url)
|
api_models = fetch_api_models(api_key, base_url)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,18 +103,16 @@ class TestValidateFormatChecks:
|
||||||
assert result["accepted"] is False
|
assert result["accepted"] is False
|
||||||
assert "spaces" in result["message"].lower()
|
assert "spaces" in result["message"].lower()
|
||||||
|
|
||||||
def test_openrouter_requires_slash(self):
|
def test_no_slash_model_still_probes_api(self):
|
||||||
result = _validate("claude-opus-4.6")
|
"""Models without '/' should still be checked via API (not all providers need it)."""
|
||||||
assert result["accepted"] is False
|
result = _validate("gpt-5.4", api_models=["gpt-5.4", "gpt-5.4-pro"])
|
||||||
assert "provider/model" in result["message"]
|
assert result["accepted"] is True
|
||||||
|
assert result["persist"] is True
|
||||||
|
|
||||||
def test_openrouter_rejects_leading_slash(self):
|
def test_no_slash_model_rejected_if_not_in_api(self):
|
||||||
result = _validate("/claude-opus-4.6")
|
result = _validate("gpt-5.4", api_models=["openai/gpt-5.4"])
|
||||||
assert result["accepted"] is False
|
|
||||||
|
|
||||||
def test_openrouter_rejects_trailing_slash(self):
|
|
||||||
result = _validate("anthropic/")
|
|
||||||
assert result["accepted"] is False
|
assert result["accepted"] is False
|
||||||
|
assert "not a valid model" in result["message"]
|
||||||
|
|
||||||
|
|
||||||
# -- validate_requested_model — API probe found model ------------------------
|
# -- validate_requested_model — API probe found model ------------------------
|
||||||
|
|
|
||||||
|
|
@ -62,18 +62,20 @@ class TestModelCommand:
|
||||||
assert cli_obj.agent is None
|
assert cli_obj.agent is None
|
||||||
save_mock.assert_not_called()
|
save_mock.assert_not_called()
|
||||||
|
|
||||||
def test_bad_format_rejected_without_api_call(self, capsys):
|
def test_no_slash_model_probes_api_and_rejects(self, capsys):
|
||||||
|
"""Model without '/' is still probed via API — not rejected on format alone."""
|
||||||
cli_obj = self._make_cli()
|
cli_obj = self._make_cli()
|
||||||
|
|
||||||
with patch("hermes_cli.auth.resolve_provider", return_value="openrouter"), \
|
with patch("hermes_cli.auth.resolve_provider", return_value="openrouter"), \
|
||||||
patch("hermes_cli.models.fetch_api_models") as fetch_mock, \
|
patch("hermes_cli.models.fetch_api_models",
|
||||||
|
return_value=["openai/gpt-5.4"]) as fetch_mock, \
|
||||||
patch("cli.save_config_value") as save_mock:
|
patch("cli.save_config_value") as save_mock:
|
||||||
cli_obj.process_command("/model invalid-no-slash")
|
cli_obj.process_command("/model gpt-5.4")
|
||||||
|
|
||||||
output = capsys.readouterr().out
|
output = capsys.readouterr().out
|
||||||
assert "provider/model" in output
|
assert "not a valid model" in output
|
||||||
assert cli_obj.model == "anthropic/claude-opus-4.6" # unchanged
|
assert cli_obj.model == "anthropic/claude-opus-4.6" # unchanged
|
||||||
fetch_mock.assert_not_called() # no API call for format errors
|
fetch_mock.assert_called_once() # API was probed
|
||||||
save_mock.assert_not_called()
|
save_mock.assert_not_called()
|
||||||
|
|
||||||
def test_validation_crash_falls_back_to_save(self, capsys):
|
def test_validation_crash_falls_back_to_save(self, capsys):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue