fix(providers): pin all Actual routes to chat completions (E-1047)

This commit is contained in:
Justin Bennington
2026-09-10 14:56:55 -04:00
parent d7b0a72c2a
commit 8b2b83a906
13 changed files with 352 additions and 29 deletions
+32 -2
View File
@@ -936,6 +936,9 @@ def _to_openai_base_url(base_url: str) -> str:
without it). Anthropic-only gateways keep their path.
"""
url = str(base_url or "").strip().rstrip("/")
if base_url_hostname(url) == "api.actual.inc":
from hermes_cli.auth import normalize_actual_base_url
return normalize_actual_base_url(url)
if url.endswith("/anthropic"):
if base_url_host_matches(url, "open.bigmodel.cn") or base_url_host_matches(url, "api.z.ai"):
rewritten = url[: -len("/anthropic")] + "/coding/paas/v4"
@@ -4469,12 +4472,32 @@ def _log_once_debug(seen: set, key: Any, msg: str, *args: Any) -> None:
logger.debug(msg, *args)
def _is_actual_auxiliary_route(req: _ResolveRequest, base_url: str) -> bool:
from hermes_cli.auth import normalize_actual_base_url
from hermes_cli.providers import is_actual_route
from hermes_cli.route_identity import normalize_route_base_url
if is_actual_route(req.provider, base_url):
return True
runtime = _normalize_main_runtime(req.main_runtime)
return bool(
base_url
and is_actual_route(runtime.get("provider", ""), runtime.get("base_url", ""))
and normalize_route_base_url(normalize_actual_base_url(base_url))
== normalize_route_base_url(
normalize_actual_base_url(runtime.get("base_url", ""))
)
)
def _wrap_transport(req: _ResolveRequest, client_obj: Any, final_model_str: str,
base_url_str: str = "", api_key_str: str = ""):
"""Wrap a plain OpenAI client in the right transport adapter; specialized wrappers pass through.
Codex (Responses API): explicit ``api_mode=codex_responses``, else — with no
explicit api_mode — api.openai.com + codex model. Anthropic (Messages): ``api_mode=anthropic_messages``,
any ``/anthropic`` suffix, ``api.kimi.com/coding``, or ``api.anthropic.com``."""
if _is_actual_auxiliary_route(req, base_url_str):
return client_obj._real_client if isinstance(client_obj, CodexAuxiliaryClient) else client_obj
needs_codex = not (isinstance(client_obj, CodexAuxiliaryClient) or req.raw_codex) and (
req.api_mode == "codex_responses"
or (not req.api_mode and base_url_hostname(base_url_str) == "api.openai.com"
@@ -4616,6 +4639,9 @@ def _resolve_custom_branch(req: _ResolveRequest) -> _ResolveResult:
if _main_base and _main_key:
custom_base, custom_key = _main_base, _main_key
if custom_base and custom_key:
if _is_actual_auxiliary_route(req, custom_base):
from hermes_cli.auth import normalize_actual_base_url
custom_base = normalize_actual_base_url(custom_base)
final_model = _normalize_resolved_model(
model or (main_runtime.get("model") if main_runtime else None) or "gpt-4o-mini", provider,
)
@@ -4678,8 +4704,12 @@ def _resolve_named_custom_branch(req: _ResolveRequest) -> Optional[_ResolveResul
logger.warning("resolve_provider_client: named custom provider %r has no resolvable "
"api_key — request will be sent with placeholder no-key-required "
"and will 401 on auth-required endpoints", custom_entry.get("name") or provider)
# Explicit per-task api_mode override wins over the provider entry's.
# Actual's wire protocol takes precedence over persisted task/provider modes.
entry_api_mode = (req.api_mode or custom_entry.get("api_mode") or "").strip()
if _is_actual_auxiliary_route(req, custom_base):
from hermes_cli.auth import normalize_actual_base_url
custom_base = normalize_actual_base_url(custom_base)
entry_api_mode = "chat_completions"
if not custom_base:
logger.warning("resolve_provider_client: named custom provider %r has no base_url", provider)
return None, None
@@ -4766,7 +4796,7 @@ def _resolve_api_key_branch(req: _ResolveRequest, pconfig: Any, resolve_creds: C
return None, None
base_url = _to_openai_base_url(raw_base_url)
# Explicit base_url override: a fallback_model/custom_providers entry pointing a built-in name elsewhere.
if req.explicit_base_url:
if req.explicit_base_url and provider != "actual":
base_url = _to_openai_base_url(req.explicit_base_url.strip().rstrip("/"))
final_model = _normalize_resolved_model(req.model or _get_aux_model_for_provider(provider), provider)
if provider == "gemini":