feat: allow custom endpoints to use responses API via api_mode override (#1651)

Add HERMES_API_MODE env var and model.api_mode config field to let
custom OpenAI-compatible endpoints opt into codex_responses mode
without requiring the OpenAI Codex OAuth provider path.

- _get_configured_api_mode() reads HERMES_API_MODE env (precedence)
  then model.api_mode from config.yaml; validates against whitelist
- Applied in both _resolve_openrouter_runtime() and
  _resolve_named_custom_runtime() (original PR only covered openrouter)
- Fix _dump_api_request_debug() to show /responses URL when in
  codex_responses mode instead of always showing /chat/completions
- Tests for config override, env override, invalid values, named
  custom providers, and debug dump URL for both API modes

Inspired by PR #1041 by @mxyhi.

Co-authored-by: mxyhi <mxyhi@users.noreply.github.com>
This commit is contained in:
Teknium 2026-03-17 02:04:36 -07:00 committed by GitHub
parent 68fbcdaa06
commit f2414bfd45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 131 additions and 4 deletions

View file

@ -33,6 +33,24 @@ def _get_model_config() -> Dict[str, Any]:
return {}
def _get_configured_api_mode(model_cfg: Optional[Dict[str, Any]] = None) -> Optional[str]:
"""Return an optional API mode override from env or config.
Allows custom OpenAI-compatible endpoints to opt into codex_responses
mode via HERMES_API_MODE env var or model.api_mode in config.yaml,
without requiring the OpenAI Codex OAuth provider path.
"""
candidate = os.getenv("HERMES_API_MODE", "").strip().lower()
if not candidate:
cfg = model_cfg if isinstance(model_cfg, dict) else _get_model_config()
raw = cfg.get("api_mode")
if isinstance(raw, str):
candidate = raw.strip().lower()
if candidate in {"chat_completions", "codex_responses"}:
return candidate
return None
def resolve_requested_provider(requested: Optional[str] = None) -> str:
"""Resolve provider request from explicit arg, config, then env."""
if requested and requested.strip():
@ -121,7 +139,7 @@ def _resolve_named_custom_runtime(
return {
"provider": "openrouter",
"api_mode": "chat_completions",
"api_mode": _get_configured_api_mode() or "chat_completions",
"base_url": base_url,
"api_key": api_key,
"source": f"custom_provider:{custom_provider.get('name', requested_provider)}",
@ -190,10 +208,11 @@ def _resolve_openrouter_runtime(
)
source = "explicit" if (explicit_api_key or explicit_base_url) else "env/config"
api_mode = _get_configured_api_mode(model_cfg) or "chat_completions"
return {
"provider": "openrouter",
"api_mode": "chat_completions",
"api_mode": api_mode,
"base_url": base_url,
"api_key": api_key,
"source": source,