fix(agent): validate OpenRouter provider sort before request dispatch

This commit is contained in:
konsisumer 2026-06-01 21:32:38 +02:00 committed by Teknium
parent 27322612b4
commit 1b6ebb24c0
4 changed files with 60 additions and 5 deletions

View file

@ -37,6 +37,7 @@ from tools.terminal_tool import is_persistent_env
from utils import base_url_host_matches, base_url_hostname, env_float, env_int
logger = logging.getLogger(__name__)
_OPENROUTER_PROVIDER_SORT_VALUES = {"throughput", "latency", "price"}
def _ra():
@ -115,6 +116,23 @@ def _is_openai_codex_backend(agent) -> bool:
)
def _validated_openrouter_provider_sort(raw_sort: Any) -> Optional[str]:
"""Return a normalized OpenRouter provider.sort value or None."""
if not isinstance(raw_sort, str):
return None
sort_value = raw_sort.strip().lower()
if not sort_value:
return None
if sort_value in _OPENROUTER_PROVIDER_SORT_VALUES:
return sort_value
logger.warning(
"Ignoring invalid OpenRouter provider.sort value %r (allowed: %s)",
raw_sort,
", ".join(sorted(_OPENROUTER_PROVIDER_SORT_VALUES)),
)
return None
def _env_float(name: str, default: float) -> float:
try:
return float(os.getenv(name, str(default)))
@ -698,8 +716,9 @@ def build_api_kwargs(agent, api_messages: list) -> dict:
_prefs["ignore"] = agent.providers_ignored
if agent.providers_order:
_prefs["order"] = agent.providers_order
if agent.provider_sort:
_prefs["sort"] = agent.provider_sort
_provider_sort = _validated_openrouter_provider_sort(agent.provider_sort)
if _provider_sort:
_prefs["sort"] = _provider_sort
if agent.provider_require_parameters:
_prefs["require_parameters"] = True
if agent.provider_data_collection:
@ -1476,8 +1495,9 @@ def handle_max_iterations(agent, messages: list, api_call_count: int) -> str:
provider_preferences["ignore"] = agent.providers_ignored
if agent.providers_order:
provider_preferences["order"] = agent.providers_order
if agent.provider_sort:
provider_preferences["sort"] = agent.provider_sort
_provider_sort = _validated_openrouter_provider_sort(agent.provider_sort)
if _provider_sort:
provider_preferences["sort"] = _provider_sort
if provider_preferences and (
(agent.provider or "").strip().lower() == "openrouter"
or agent._is_openrouter_url()

View file

@ -2335,7 +2335,7 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
max_iterations = _cfg.get("agent", {}).get("max_turns") or _cfg.get("max_turns") or 90
# Provider routing
pr = _cfg.get("provider_routing", {})
pr = _cfg.get("provider_routing") or {}
from hermes_cli.runtime_provider import (
resolve_runtime_provider,

View file

@ -0,0 +1,13 @@
from agent.chat_completion_helpers import _validated_openrouter_provider_sort
def test_validated_openrouter_provider_sort_accepts_valid_values():
assert _validated_openrouter_provider_sort("price") == "price"
assert _validated_openrouter_provider_sort(" latency ") == "latency"
assert _validated_openrouter_provider_sort("THROUGHPUT") == "throughput"
def test_validated_openrouter_provider_sort_rejects_invalid_values():
assert _validated_openrouter_provider_sort("intelligence") is None
assert _validated_openrouter_provider_sort("") is None
assert _validated_openrouter_provider_sort(None) is None

View file

@ -1676,6 +1676,14 @@ class TestBuildApiKwargs:
kwargs = agent._build_api_kwargs(messages)
assert kwargs["extra_body"]["provider"]["only"] == ["Anthropic"]
def test_provider_preferences_drop_invalid_sort(self, agent):
agent.provider = "openrouter"
agent.base_url = "https://openrouter.ai/api/v1"
agent.provider_sort = "intelligence"
messages = [{"role": "user", "content": "hi"}]
kwargs = agent._build_api_kwargs(messages)
assert "sort" not in kwargs.get("extra_body", {}).get("provider", {})
def test_reasoning_config_default_openrouter(self, agent):
"""Default reasoning config for OpenRouter should be medium."""
agent.provider = "openrouter"
@ -3492,6 +3500,20 @@ class TestHandleMaxIterations:
kwargs = agent.client.chat.completions.create.call_args.kwargs
assert kwargs["extra_body"]["provider"]["only"] == ["Anthropic"]
def test_summary_drops_invalid_provider_sort(self, agent):
agent.base_url = "https://openrouter.ai/api/v1"
agent._base_url_lower = agent.base_url.lower()
agent.provider = "openrouter"
agent.provider_sort = "intelligence"
agent.client.chat.completions.create.return_value = _mock_response(content="Summary")
agent._cached_system_prompt = "You are helpful."
result = agent._handle_max_iterations([{"role": "user", "content": "do stuff"}], 60)
assert result == "Summary"
kwargs = agent.client.chat.completions.create.call_args.kwargs
assert "sort" not in kwargs.get("extra_body", {}).get("provider", {})
def test_codex_summary_sanitizes_orphan_tool_results(self, agent):
agent.api_mode = "codex_responses"
agent.provider = "openai-codex"