From e789d79cfe119d3fa7a7cd525269d55ed2f75bbe Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:14:12 -0700 Subject: [PATCH 1/4] =?UTF-8?q?refactor(hermes=5Fcli):=20provider=20cluste?= =?UTF-8?q?r=20=E2=80=94=20shared=20=5Foverlay=5Fpdef,=20pool-select=20gua?= =?UTF-8?q?rd=20collapse,=20docstring=20compaction=20(WHY=20kept)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hermes_cli/providers.py | 71 +++++++++++------------- hermes_cli/runtime_provider.py | 61 +++++++++----------- hermes_cli/runtime_provider_backends.py | 22 +++----- hermes_cli/runtime_provider_custom.py | 74 ++++++++++--------------- 4 files changed, 96 insertions(+), 132 deletions(-) diff --git a/hermes_cli/providers.py b/hermes_cli/providers.py index 06b3a6dc26..eccefc684b 100644 --- a/hermes_cli/providers.py +++ b/hermes_cli/providers.py @@ -177,12 +177,17 @@ def _models_dev_info(canonical: str, allow_network: bool = True): return None -def get_provider(name: str, *, allow_network: bool = True) -> Optional[ProviderDef]: - """Look up a built-in provider by id or alias. +def _overlay_pdef(canonical, ov: HermesOverlay, name, env_vars, base_url, doc, source) -> ProviderDef: + return ProviderDef( + id=canonical, name=name, transport=ov.transport, api_key_env_vars=env_vars, base_url=base_url, + base_url_env_var=ov.base_url_env_var, is_aggregator=ov.is_aggregator, auth_type=ov.auth_type, doc=doc, + source=source, + ) - Order: models.dev catalog merged with the Hermes overlay; Hermes-only overlay (nous, - openai-codex, …); plugin provider profiles with a concrete endpoint. - """ + +def get_provider(name: str, *, allow_network: bool = True) -> Optional[ProviderDef]: + """Look up a built-in provider by id or alias: models.dev catalog merged with the Hermes overlay; + Hermes-only overlay (nous, openai-codex, …); plugin provider profiles with a concrete endpoint.""" canonical = normalize_provider(name) mdev_info = _models_dev_info(canonical, allow_network) overlay = HERMES_OVERLAYS.get(canonical) @@ -192,17 +197,14 @@ def get_provider(name: str, *, allow_network: bool = True) -> Optional[ProviderD for ev in ov.extra_env_vars: if ev not in env_vars: env_vars.append(ev) - return ProviderDef( - id=canonical, name=mdev_info.name, transport=ov.transport, api_key_env_vars=tuple(env_vars), - base_url=ov.base_url_override or mdev_info.api, base_url_env_var=ov.base_url_env_var, - is_aggregator=ov.is_aggregator, auth_type=ov.auth_type, doc=mdev_info.doc, source="models.dev", + return _overlay_pdef( + canonical, ov, mdev_info.name, tuple(env_vars), ov.base_url_override or mdev_info.api, mdev_info.doc, + "models.dev", ) if overlay is not None: - return ProviderDef( - id=canonical, name=_LABEL_OVERRIDES.get(canonical, canonical), transport=overlay.transport, - api_key_env_vars=overlay.extra_env_vars, base_url=overlay.base_url_override, - base_url_env_var=overlay.base_url_env_var, is_aggregator=overlay.is_aggregator, - auth_type=overlay.auth_type, source="hermes", + return _overlay_pdef( + canonical, overlay, _LABEL_OVERRIDES.get(canonical, canonical), overlay.extra_env_vars, + overlay.base_url_override, "", "hermes", ) # Plugin-registered profiles (plugins/model-providers//) absent from models.dev and # HERMES_OVERLAYS would otherwise be "Unknown provider" in /model, --provider and model-switch @@ -264,12 +266,10 @@ def is_routing_aggregator(provider: str) -> bool: def is_official_openai_host(base_url: str) -> bool: - """True when *base_url* points at OpenAI's official API host family. - - Hostname-parsed matching only — never substring — so lookalike hosts - (``api.openai.com.attacker.test``) and path-segment spoofs (``proxy.test/api.openai.com/v1``) - are rejected. A genuine ``*.api.openai.com`` subdomain requires control of openai.com DNS. - """ + """True when *base_url* points at OpenAI's official API host family. Hostname-parsed matching + only — never substring — so lookalike hosts (``api.openai.com.attacker.test``) and path-segment + spoofs (``proxy.test/api.openai.com/v1``) are rejected; a genuine ``*.api.openai.com`` + subdomain requires control of openai.com DNS.""" return base_url_host_matches(base_url, "api.openai.com") @@ -281,14 +281,12 @@ _RESPONSES_NATIVE_HOSTS: frozenset[str] = frozenset({"api.meta.ai", "api.router. def host_mandated_api_mode(base_url: str = "") -> Optional[str]: - """Return the wire protocol a specific endpoint *requires*, or None. - - Some hosts accept exactly one API mode (api.openai.com 400s chat/completions for reasoning - models with tools). These are *mandatory*: a session carrying a stale api_mode (a /model switch - that kept the previous provider's ``chat_completions``) must be overridden, not merely filled - in when empty. Exact-hostname matching only — never substring — so lookalike hosts and - path-segment spoofs are not treated as the real endpoint. - """ + """Return the wire protocol a specific endpoint *requires*, or None. Some hosts accept exactly + one API mode (api.openai.com 400s chat/completions for reasoning models with tools); these are + *mandatory*: a session carrying a stale api_mode (a /model switch that kept the previous + provider's ``chat_completions``) must be overridden, not merely filled in when empty. + Exact-hostname matching only — never substring — so lookalike hosts and path-segment spoofs are + not treated as the real endpoint.""" if not base_url: return None url_lower = base_url.rstrip("/").lower() @@ -383,11 +381,9 @@ def custom_provider_aliases(display_name: str, provider_key: str = "") -> frozen def resolve_custom_provider(name: str, custom_providers: Optional[List[Dict[str, Any]]]) -> Optional[ProviderDef]: - """Resolve a provider from the user's config.yaml ``custom_providers`` list. - - A stored bare ``"custom"`` (corrupt state from a prior model-switch bug) falls back to the first - valid entry so existing configs self-heal. - """ + """Resolve a provider from the user's config.yaml ``custom_providers`` list. A stored bare + ``"custom"`` (corrupt state from a prior model-switch bug) falls back to the first valid entry + so existing configs self-heal.""" if not custom_providers or not isinstance(custom_providers, list): return None requested = (name or "").strip().lower() @@ -459,12 +455,9 @@ def resolve_provider_full( ) -> Optional[ProviderDef]: """Full resolution chain: user ``providers.`` -> lossy-alias registry id -> built-in (models.dev + overlays) -> user providers (canonical, then raw) -> ``custom_providers`` -> - managed llamacpp -> models.dev directly. - - User-defined ``providers.`` is tried FIRST on the raw (pre-alias) name: a configured - ``providers.openai`` pointing at api.openai.com must not be hijacked by the legacy - "openai" -> "openrouter" alias. - """ + managed llamacpp -> models.dev directly. User-defined ``providers.`` is tried FIRST on + the raw (pre-alias) name: a configured ``providers.openai`` pointing at api.openai.com must not + be hijacked by the legacy "openai" -> "openrouter" alias.""" canonical = normalize_provider(name) raw = name.strip().lower() if user_providers: diff --git a/hermes_cli/runtime_provider.py b/hermes_cli/runtime_provider.py index 3784c69d68..a1e3fa90b5 100644 --- a/hermes_cli/runtime_provider.py +++ b/hermes_cli/runtime_provider.py @@ -1,10 +1,9 @@ -"""Shared runtime provider resolution for CLI, gateway, cron, and helpers. - -This module owns the resolution ORDER (:func:`resolve_runtime_provider`), the api_mode / base_url -helpers and the pool / OAuth / explicit paths. Custom-provider lookup lives in -:mod:`hermes_cli.runtime_provider_custom`; Azure Foundry, OpenRouter/bare-custom, Bedrock and -external-process builders in :mod:`hermes_cli.runtime_provider_backends`. Both are re-exported -here so ``hermes_cli.runtime_provider.`` imports and test patches keep working.""" +"""Shared runtime provider resolution for CLI, gateway, cron, and helpers: the resolution ORDER +(:func:`resolve_runtime_provider`), api_mode / base_url helpers and the pool / OAuth / explicit paths. +Custom-provider lookup lives in :mod:`hermes_cli.runtime_provider_custom`; Azure Foundry, +OpenRouter/bare-custom, Bedrock and external-process builders in +:mod:`hermes_cli.runtime_provider_backends` — both re-exported here so +``hermes_cli.runtime_provider.`` imports and test patches keep working.""" from __future__ import annotations @@ -72,12 +71,11 @@ def _resolves_to_custom(name: str) -> bool: def _config_base_url_trustworthy_for_bare_custom(cfg_base_url: str, cfg_provider: str) -> bool: - """Whether ``model.base_url`` may back bare ``custom`` runtime resolution. - - The model picker can select Custom while ``model.provider`` still reflects a previous provider. - Non-loopback URLs are rejected unless the YAML provider is already ``custom`` or a local-server - alias (ollama/vllm/llamacpp — else a legit LAN ollama endpoint silently falls through to - OpenRouter), so a stale OpenRouter/Z.ai base_url cannot hijack local sessions.""" + """Whether ``model.base_url`` may back bare ``custom`` runtime resolution. The picker can select + Custom while ``model.provider`` still names a previous provider, so non-loopback URLs are rejected + unless the YAML provider is already ``custom`` or a local-server alias (ollama/vllm/llamacpp — + else a legit LAN ollama endpoint falls through to OpenRouter): a stale OpenRouter/Z.ai base_url + cannot hijack local sessions.""" cfg_provider_norm = (cfg_provider or "").strip().lower() bu = (cfg_base_url or "").strip() if not bu: @@ -105,11 +103,10 @@ _VALID_API_MODES = {"chat_completions", "codex_responses", "anthropic_messages", def _detect_api_mode_for_url(base_url: str) -> Optional[str]: - """Auto-detect api_mode from the resolved base URL, or None. - - Exact-hostname matches reject lookalike subdomains (api.anthropic.com.attacker.test) and - path-segment spoofing (proxy.test/api.anthropic.com/v1). Official OpenAI hosts (incl. the - data-residency us./eu. regional hosts) need Responses for GPT-5.x tool calls with reasoning.""" + """Auto-detect api_mode from the resolved base URL, or None. Exact-hostname matches reject + lookalike subdomains (api.anthropic.com.attacker.test) and path-segment spoofing + (proxy.test/api.anthropic.com/v1). Official OpenAI hosts (incl. us./eu. data-residency hosts) + need Responses for GPT-5.x tool calls with reasoning.""" normalized = (base_url or "").strip().lower().rstrip("/") hostname = base_url_hostname(base_url) mandated = _HOST_MANDATED_API_MODES.get(hostname) @@ -214,7 +211,6 @@ def _configured_or_fallback_api_mode( provider: str, model_cfg: Dict[str, Any], base_url: str, effective_model: Any, *, opencode_by_model: bool ) -> str: """Persisted ``model.api_mode`` when it belongs to this provider, else URL/transport fallback. - OpenCode Zen/Go serve both anthropic_messages and chat_completions models, so (when ``opencode_by_model``) their mode is always re-derived from the effective model.""" if opencode_by_model and _models.opencode_provider_family(provider) is not None: @@ -320,10 +316,9 @@ def _host_derived_api_key(base_url: str) -> str: def _host_gated_env_key_candidates(base_url: str, *, ollama: bool) -> list: """Env API keys gated on their authoritative hosts, then the host-derived ``_API_KEY``. - Sending OPENAI/OPENROUTER/OLLAMA keys to an unrelated endpoint leaks credentials - (GHSA-76xc-57q6-vm5m); match on HOST, not substring. ``_host_derived_api_key`` skips OLLAMA, - so callers that want it opt in via ``ollama``.""" + (GHSA-76xc-57q6-vm5m); match on HOST, not substring. ``_host_derived_api_key`` skips OLLAMA, so + callers that want it opt in via ``ollama``.""" is_openai = base_url_host_matches(base_url, "openai.com") or base_url_host_matches(base_url, "openai.azure.com") candidates = [] if ollama: @@ -570,14 +565,12 @@ def _resolve_from_pool( if not (pool and pool.has_credentials()): return None entry = pool.select() - pool_api_key = _pool_entry_api_key(entry) if entry is not None else "" - if provider == "nous" and entry is not None: + if entry is None: + return None + pool_api_key = _pool_entry_api_key(entry) + if provider == "nous": entry, pool_api_key = _refresh_nous_pool_entry(pool, entry, pool_api_key) - if ( - entry is not None - and pool_api_key - and credential_pool_matches_provider(pool, provider, base_url=_pool_entry_base_url(entry)) - ): + if pool_api_key and credential_pool_matches_provider(pool, provider, base_url=_pool_entry_base_url(entry)): return _resolve_runtime_from_pool_entry( provider=provider, entry=entry, requested_provider=requested_provider, model_cfg=model_cfg, pool=pool, target_model=target_model, @@ -939,9 +932,8 @@ def resolve_runtime_provider( *, requested: Optional[str] = None, explicit_api_key: Optional[str] = None, explicit_base_url: Optional[str] = None, target_model: Optional[str] = None, ) -> Dict[str, Any]: - """Resolve runtime provider credentials for agent execution. - - Ladder (order is behavior — each rung returns or raises, else falls to the next): + """Resolve runtime provider credentials for agent execution. Ladder (order is behavior — each + rung returns or raises, else falls to the next): 1. disabled-provider guard (``providers..enabled: false``) 2. requested-name shortcuts: moa, anthropic@azure, azure-foundry, vertex 3. named custom provider / llamacpp alias / bare-custom direct alias @@ -951,9 +943,8 @@ def resolve_runtime_provider( 7. OAuth specs (nous/codex/xai/qwen; "auto" swallows AuthError and logs) → minimax-oauth → external-process → anthropic env → bedrock → registry api_key providers 8. OpenRouter / bare-custom fallback - - target_model: overrides model_cfg["default"] when computing provider-specific api_mode - (e.g. OpenCode Zen/Go where different models route through different API surfaces).""" + target_model overrides model_cfg["default"] when computing provider-specific api_mode (e.g. + OpenCode Zen/Go where different models route through different API surfaces).""" requested_provider = resolve_requested_provider(requested) _raise_if_provider_disabled(requested_provider) runtime = _resolve_requested_shortcuts(requested_provider, explicit_api_key, explicit_base_url, target_model) diff --git a/hermes_cli/runtime_provider_backends.py b/hermes_cli/runtime_provider_backends.py index aba924a26e..bbe8c30acd 100644 --- a/hermes_cli/runtime_provider_backends.py +++ b/hermes_cli/runtime_provider_backends.py @@ -1,10 +1,8 @@ """Provider-specific runtime builders for :mod:`hermes_cli.runtime_provider`: Azure Foundry, the -OpenRouter / bare-custom fallback resolver, Bedrock, and external-process providers. - -Origin-internal collaborators are resolved on the origin module at call time via :func:`_rp` so -test patches on ``hermes_cli.runtime_provider.*`` (``_get_model_config``, ``load_config``, -``has_usable_secret``, ``_try_resolve_from_custom_pool``, …) still apply. -""" +OpenRouter / bare-custom fallback resolver, Bedrock, and external-process providers. Origin-internal +collaborators are resolved on the origin module at call time via :func:`_rp` so test patches on +``hermes_cli.runtime_provider.*`` (``_get_model_config``, ``load_config``, ``has_usable_secret``, +``_try_resolve_from_custom_pool``, …) still apply.""" from __future__ import annotations @@ -120,13 +118,11 @@ def _resolve_azure_foundry_runtime( def _resolve_openrouter_runtime( *, requested_provider: str, explicit_api_key: Optional[str] = None, explicit_base_url: Optional[str] = None ) -> Dict[str, Any]: - """Terminal resolver: OpenRouter, or a bare/aliased ``custom`` endpoint. - - base_url precedence: explicit > CUSTOM_BASE_URL > trusted ``model.base_url`` > OPENROUTER_BASE_URL - > default. OPENAI_BASE_URL is deliberately NOT consulted — config.yaml is the single source of - truth for endpoint URLs. OpenRouter contexts prefer OPENROUTER_API_KEY; custom endpoints never - receive the OpenRouter key and only get env keys gated on their authoritative hosts. - """ + """Terminal resolver: OpenRouter, or a bare/aliased ``custom`` endpoint. base_url precedence: + explicit > CUSTOM_BASE_URL > trusted ``model.base_url`` > OPENROUTER_BASE_URL > default. + OPENAI_BASE_URL is deliberately NOT consulted — config.yaml is the single source of truth for + endpoint URLs. OpenRouter contexts prefer OPENROUTER_API_KEY; custom endpoints never receive the + OpenRouter key and only get env keys gated on their authoritative hosts.""" rp = _rp() model_cfg = rp._get_model_config() cfg_base_url = model_cfg.get("base_url") if isinstance(model_cfg.get("base_url"), str) else "" diff --git a/hermes_cli/runtime_provider_custom.py b/hermes_cli/runtime_provider_custom.py index 0dd10f34b8..f1e04c1267 100644 --- a/hermes_cli/runtime_provider_custom.py +++ b/hermes_cli/runtime_provider_custom.py @@ -1,12 +1,9 @@ -"""Custom-provider resolution: ``providers:`` / ``custom_providers:`` lookup, identity -recovery, custom credential pools, and the named-custom runtime builder. - -Extracted from :mod:`hermes_cli.runtime_provider`; every name here is re-exported there. -Origin-internal collaborators (``load_config``, ``_get_model_config``, ``load_pool``, -``has_usable_secret``, ``custom_provider_pool_key_candidates``, …) are looked up on the origin -module AT CALL TIME via :func:`_rp` so ``monkeypatch.setattr(runtime_provider, name, …)`` keeps -working for moved bodies. -""" +"""Custom-provider resolution: ``providers:`` / ``custom_providers:`` lookup, identity recovery, +custom credential pools, and the named-custom runtime builder. Extracted from +:mod:`hermes_cli.runtime_provider` (every name re-exported there); origin-internal collaborators +(``load_config``, ``_get_model_config``, ``load_pool``, ``has_usable_secret``, …) are looked up on +the origin module AT CALL TIME via :func:`_rp` so ``monkeypatch.setattr(runtime_provider, name, …)`` +keeps working for moved bodies.""" from __future__ import annotations @@ -98,23 +95,19 @@ def _lift_common_custom_fields( if api_mode: result["api_mode"] = api_mode _lift_max_output_tokens(entry, result) - capabilities = _filter_capabilities(entry.get("capabilities")) - if capabilities: - result["capabilities"] = capabilities + _lift_model_capabilities(entry, None, result) # ── config lookup ────────────────────────────────────────────────────────────────────────── def _shadowed_by_builtin(requested_norm: str) -> bool: - """Raw names map to custom providers only when they are not canonical built-ins. - - Explicit ``custom:`` keys always target the saved entry, and bare ``custom`` is exempt: a - user may literally name a ``providers:`` entry "custom" (returning None before the config scan - made such cron jobs fail with ``auth_unavailable``). Defer to the built-in only when the raw - name IS the canonical provider (``nous``); an entry matching merely an alias (``kimi`` → - ``kimi-coding``) is the user's target. - """ + """Raw names map to custom providers only when they are not canonical built-ins. Explicit + ``custom:`` keys always target the saved entry, and bare ``custom`` is exempt: a user may + literally name a ``providers:`` entry "custom" (returning None before the config scan made such + cron jobs fail with ``auth_unavailable``). Defer to the built-in only when the raw name IS the + canonical provider (``nous``); an entry matching merely an alias (``kimi`` → ``kimi-coding``) + is the user's target.""" if requested_norm == "custom" or requested_norm.startswith("custom:"): return False rp = _rp() @@ -288,15 +281,13 @@ def find_custom_provider_identity_by_model(model: str) -> Optional[str]: def canonical_custom_identity( *, base_url: Optional[str] = None, config_provider: Optional[str] = None, model: Optional[str] = None ) -> Optional[str]: - """Recover a routable ``custom:`` identity for a bare custom provider. - - Every path that persists or restores a session's provider override must run the resolved - provider through this so a bare ``"custom"`` is upgraded back to its durable ``custom:`` - menu key. Sources in priority order: (1) ``base_url`` reverse lookup — the one fact that always - survives the round-trip when a URL was recorded; (2) ``model`` reverse lookup - (``model``/``default_model``/``models`` catalog); (3) the configured provider (arg, then - ``model.provider``, then ``HERMES_INFERENCE_PROVIDER``) when it names a real entry. - """ + """Recover a routable ``custom:`` identity for a bare custom provider. Every path that + persists or restores a session's provider override must run the resolved provider through this + so a bare ``"custom"`` is upgraded back to its durable menu key. Sources in priority order: + (1) ``base_url`` reverse lookup — the one fact that always survives the round-trip when a URL + was recorded; (2) ``model`` reverse lookup (``model``/``default_model``/``models`` catalog); + (3) the configured provider (arg, ``model.provider``, ``HERMES_INFERENCE_PROVIDER``) when it + names a real entry.""" rp = _rp() if base_url: identity = find_custom_provider_identity(base_url) @@ -336,14 +327,11 @@ def canonical_custom_identity( def is_routable_provider(provider: Optional[str]) -> bool: - """Whether a provider name currently resolves to a routable route. - - Empty/None/``auto`` is vacuously routable (agent build falls back to the configured default). - Bare ``custom`` is the resolved billing class shared by every named entry — not a routable - identity; restore paths must heal it (:func:`canonical_custom_identity`) or fall back. Anything - else is routable iff the full chain (built-in -> ``providers:`` -> ``custom_providers:`` -> - models.dev) resolves it. - """ + """Whether a provider name currently resolves to a routable route. Empty/None/``auto`` is + vacuously routable (agent build falls back to the configured default). Bare ``custom`` is the + resolved billing class shared by every named entry — not a routable identity; restore paths + must heal it (:func:`canonical_custom_identity`) or fall back. Anything else is routable iff the + full chain (built-in -> ``providers:`` -> ``custom_providers:`` -> models.dev) resolves it.""" name = str(provider or "").strip() if not name or name.lower() == "auto": return True @@ -426,11 +414,9 @@ def _apply_custom_provider_extras( def _resolve_llamacpp_runtime(requested_provider: str, explicit_api_key: Optional[str]) -> Dict[str, Any]: """Managed llama.cpp runtime: the supervised (or detected external) server, or a typed error. - No server => say so and stop; falling through to the generic custom path would surface "local server is off" as OpenRouter's baffling "401 Invalid API key". The switch's state picks the - message (server off → point at the switch; else the setup pane). - """ + message (server off → point at the switch; else the setup pane).""" rp = _rp() try: from hermes_cli.local_runtime.endpoint import resolve_llamacpp_endpoint @@ -506,11 +492,9 @@ def _resolve_named_custom_runtime( target_model: Optional[str] = None, ) -> Optional[Dict[str, Any]]: """Runtime for a llamacpp alias, a bare-custom direct alias, or a configured custom entry. - - Aliases resolving to "custom" (ollama, vllm, llamacpp, …) are treated like bare ``custom``. - A llamacpp alias with no explicit base_url resolves to the managed server first; an explicit - base_url always wins. - """ + Aliases resolving to "custom" (ollama, vllm, llamacpp, …) are treated like bare ``custom``. A + llamacpp alias with no explicit base_url resolves to the managed server first; an explicit + base_url always wins.""" rp = _rp() requested_norm = (requested_provider or "").strip().lower() if requested_norm in _LLAMACPP_ALIASES and not explicit_base_url: From 951ee86ae647207713bb7ba35ecfeebac43442f6 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:28:12 -0700 Subject: [PATCH 2/4] =?UTF-8?q?refactor(hermes=5Fcli):=20backends/custom?= =?UTF-8?q?=20=E2=80=94=20flatten=20entra=20branch,=20registry=20via=20ori?= =?UTF-8?q?gin,=20canonical=5Fcustom=5Fidentity=20tail=20via=20custom=5Fpr?= =?UTF-8?q?ovider=5Fslug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hermes_cli/runtime_provider_backends.py | 21 +++++++++---------- hermes_cli/runtime_provider_custom.py | 28 ++++++++++++------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/hermes_cli/runtime_provider_backends.py b/hermes_cli/runtime_provider_backends.py index bbe8c30acd..7115961c71 100644 --- a/hermes_cli/runtime_provider_backends.py +++ b/hermes_cli/runtime_provider_backends.py @@ -91,19 +91,18 @@ def _resolve_azure_foundry_runtime( if cfg_api_mode == "anthropic_messages": base_url = re.sub(r"/v1/?$", "", base_url) if cfg_auth_mode == "entra_id": + # --api-key on the CLI while config says entra_id: honour the explicit string (escape hatch + # for one-off testing). if explicit_api_key: - # --api-key on the CLI while config says entra_id: honour the explicit string - # (escape hatch for one-off testing). - api_key, source, auth_mode = explicit_api_key, "explicit", "api_key" + api_key, source, auth_mode, entra = explicit_api_key, "explicit", "api_key", {} else: - api_key, source, auth_mode = _azure_entra_credentials(cfg_entra), "entra_id", "entra_id" - clean_entra = {} - configured_scope = str(cfg_entra.get("scope") or "").strip() - if auth_mode == "entra_id" and configured_scope: - clean_entra["scope"] = configured_scope + scope = str(cfg_entra.get("scope") or "").strip() + api_key, source, auth_mode, entra = _azure_entra_credentials(cfg_entra), "entra_id", "entra_id", ( + {"scope": scope} if scope else {} + ) return rp._runtime( "azure-foundry", cfg_api_mode, base_url, api_key, - auth_mode=auth_mode, entra=clean_entra, source=source, requested_provider=requested_provider, + auth_mode=auth_mode, entra=entra, source=source, requested_provider=requested_provider, ) return rp._runtime( "azure-foundry", cfg_api_mode, base_url, _azure_foundry_api_key(rp, explicit_api_key), @@ -258,14 +257,14 @@ def _is_external_process_provider(provider: str) -> bool: if not name: return False try: - from hermes_cli.auth import PROVIDER_REGISTRY - pconfig = PROVIDER_REGISTRY.get(name) + pconfig = _rp().PROVIDER_REGISTRY.get(name) if pconfig is not None: return pconfig.auth_type == "external_process" except Exception: pass try: from providers import get_provider_profile + profile = get_provider_profile(name) except Exception: return False diff --git a/hermes_cli/runtime_provider_custom.py b/hermes_cli/runtime_provider_custom.py index f1e04c1267..442ab2eaba 100644 --- a/hermes_cli/runtime_provider_custom.py +++ b/hermes_cli/runtime_provider_custom.py @@ -136,10 +136,8 @@ def _match_new_style_provider(requested_norm: str, providers: Dict[str, Any]) -> if not base_url: continue result: Dict[str, Any] = { - "name": entry.get("name", ep_name), - "base_url": base_url.strip(), - "api_key": api_key or _clean(entry.get("api_key", "")), - "model": entry.get("default_model", ""), + "name": entry.get("name", ep_name), "base_url": base_url.strip(), + "api_key": api_key or _clean(entry.get("api_key", "")), "model": entry.get("default_model", ""), } # Command that PRINTS a short-lived credential; wrapped in a per-request token provider. key_cmd = _clean(entry.get("key_cmd", "")) @@ -310,20 +308,20 @@ def canonical_custom_identity( if not candidate_norm or candidate_norm in {"custom", "auto", "openrouter"}: return None # Only when it resolves to a configured entry — never invent a ``custom:`` resolution - # can't honor. + # can't honor. ``candidate`` may be the entry's DISPLAY NAME, not the durable identity of a + # keyed ``providers:`` entry — re-resolve via its endpoint so every path returns the same + # config-key slug. try: entry = rp._get_named_custom_provider(candidate) - if entry is not None: - # ``candidate`` may be the entry's DISPLAY NAME, not the durable identity of a keyed - # ``providers:`` entry — re-resolve via its endpoint so every path returns the same - # config-key slug. - identity = find_custom_provider_identity(str(entry.get("base_url") or "")) - if identity: - return identity - return candidate_norm if candidate_norm.startswith("custom:") else f"custom:{candidate_norm}" except Exception: - pass - return None + return None + if entry is None: + return None + try: + identity = find_custom_provider_identity(str(entry.get("base_url") or "")) + except Exception: + return None + return identity or custom_provider_slug(candidate_norm) def is_routable_provider(provider: Optional[str]) -> bool: From 3462d82bc348722136e1a4f585b765298ba477a3 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:37:08 -0700 Subject: [PATCH 3/4] =?UTF-8?q?refactor(hermes=5Fcli):=20runtime=5Fprovide?= =?UTF-8?q?r=20=E2=80=94=20inline=20single-use=20registry/pool-state=20wra?= =?UTF-8?q?ppers,=20compact=20call=20sites?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hermes_cli/runtime_provider.py | 140 ++++++++++++--------------------- 1 file changed, 50 insertions(+), 90 deletions(-) diff --git a/hermes_cli/runtime_provider.py b/hermes_cli/runtime_provider.py index a1e3fa90b5..e8862ff1b2 100644 --- a/hermes_cli/runtime_provider.py +++ b/hermes_cli/runtime_provider.py @@ -339,13 +339,8 @@ def _pool_entry_base_url(entry: Any) -> str: return getattr(entry, "runtime_base_url", None) or getattr(entry, "base_url", None) or "" -def _nous_pool_state(entry: Any) -> Dict[str, Any]: - return {k: getattr(entry, k, None) for k in ("agent_key", "agent_key_expires_at", "scope")} - - -def _registry_base_url(provider: str) -> str: - pconfig = PROVIDER_REGISTRY.get(provider) - return pconfig.inference_base_url if pconfig else "" +def _nous_entry_key_usable(entry: Any, min_ttl: int) -> bool: + return _agent_key_is_usable({k: getattr(entry, k, None) for k in ("agent_key", "agent_key_expires_at", "scope")}, min_ttl) def _nous_min_key_ttl() -> int: @@ -469,7 +464,8 @@ from hermes_cli.runtime_provider_backends import ( # noqa: E402,F401 _POOL_ENTRY_SIMPLE_MODES: Dict[str, tuple] = { "openai-codex": ("codex_responses", DEFAULT_CODEX_BASE_URL), "xai-oauth": ("codex_responses", DEFAULT_XAI_OAUTH_BASE_URL), "qwen-oauth": ("chat_completions", DEFAULT_QWEN_BASE_URL), "openrouter": ("chat_completions", OPENROUTER_BASE_URL), - "minimax-oauth": ("anthropic_messages", lambda: _registry_base_url("minimax-oauth")), "xai": ("codex_responses", ""), + "minimax-oauth": ("anthropic_messages", lambda: getattr(PROVIDER_REGISTRY.get("minimax-oauth"), "inference_base_url", "")), + "xai": ("codex_responses", ""), } @@ -507,15 +503,12 @@ def _resolve_runtime_from_pool_entry( pool: Optional[CredentialPool] = None, target_model: Optional[str] = None, ) -> Dict[str, Any]: model_cfg = model_cfg or _get_model_config() - api_mode, base_url = _pool_entry_mode_and_url( - provider, entry, model_cfg, _effective_model(model_cfg, target_model), _pool_entry_base_url(entry).rstrip("/") - ) + api_mode, base_url = _pool_entry_mode_and_url(provider, entry, model_cfg, _effective_model(model_cfg, target_model), + _pool_entry_base_url(entry).rstrip("/")) base_url = _finalize_base_url(provider, api_mode, base_url) api_mode = _maybe_apply_codex_app_server_runtime(provider=provider, api_mode=api_mode, model_cfg=model_cfg) - return _runtime( - provider, api_mode, base_url, _pool_entry_api_key(entry), - source=getattr(entry, "source", "pool"), credential_pool=pool, requested_provider=requested_provider, - ) + return _runtime(provider, api_mode, base_url, _pool_entry_api_key(entry), source=getattr(entry, "source", "pool"), + credential_pool=pool, requested_provider=requested_provider) def _openrouter_should_use_pool(requested_provider, model_cfg, explicit_api_key, explicit_base_url) -> bool: @@ -523,9 +516,7 @@ def _openrouter_should_use_pool(requested_provider, model_cfg, explicit_api_key, cfg_base_url = str(model_cfg.get("base_url") or "").strip() env_openai_base_url = _getenv("OPENAI_BASE_URL", "").strip() env_openrouter_base_url = _getenv("OPENROUTER_BASE_URL", "").strip() - has_custom_endpoint = bool(explicit_base_url or env_openai_base_url or env_openrouter_base_url) or bool( - cfg_base_url and _cfg_provider(model_cfg) in {"auto", "custom"} - ) + has_custom_endpoint = bool(explicit_base_url or env_openai_base_url or env_openrouter_base_url) or bool(cfg_base_url and _cfg_provider(model_cfg) in {"auto", "custom"}) return requested_provider in {"openrouter", "auto"} and not has_custom_endpoint and not bool(explicit_api_key or explicit_base_url) @@ -534,7 +525,7 @@ def _refresh_nous_pool_entry(pool: CredentialPool, entry: Any, pool_api_key: str selection (avoids network calls in `hermes auth list`); refresh here before falling back to singleton auth resolution. Returns (entry, pool_api_key) — key "" when still unusable.""" min_ttl = _nous_min_key_ttl() - if _agent_key_is_usable(_nous_pool_state(entry), min_ttl): + if _nous_entry_key_usable(entry, min_ttl): return entry, pool_api_key logger.debug("Nous pool entry agent_key expired/missing, refreshing selected pool entry") try: @@ -545,7 +536,7 @@ def _refresh_nous_pool_entry(pool: CredentialPool, entry: Any, pool_api_key: str if refreshed is not None: entry = refreshed pool_api_key = _pool_entry_api_key(entry) - if not pool_api_key or not _agent_key_is_usable(_nous_pool_state(entry), min_ttl): + if not pool_api_key or not _nous_entry_key_usable(entry, min_ttl): logger.debug("Nous pool entry agent_key still unavailable, falling through to runtime resolution") pool_api_key = "" return entry, pool_api_key @@ -555,9 +546,8 @@ def _resolve_from_pool( provider: str, requested_provider: str, model_cfg: Dict[str, Any], explicit_api_key, explicit_base_url, target_model ) -> Optional[Dict[str, Any]]: """Runtime from the provider's credential pool, or None to continue down the ladder.""" - should_use_pool = provider != "openrouter" or _openrouter_should_use_pool( - requested_provider, model_cfg, explicit_api_key, explicit_base_url - ) + should_use_pool = provider != "openrouter" or _openrouter_should_use_pool(requested_provider, model_cfg, explicit_api_key, + explicit_base_url) try: pool = load_pool(provider) if should_use_pool else None except Exception: @@ -571,10 +561,8 @@ def _resolve_from_pool( if provider == "nous": entry, pool_api_key = _refresh_nous_pool_entry(pool, entry, pool_api_key) if pool_api_key and credential_pool_matches_provider(pool, provider, base_url=_pool_entry_base_url(entry)): - return _resolve_runtime_from_pool_entry( - provider=provider, entry=entry, requested_provider=requested_provider, - model_cfg=model_cfg, pool=pool, target_model=target_model, - ) + return _resolve_runtime_from_pool_entry(provider=provider, entry=entry, requested_provider=requested_provider, + model_cfg=model_cfg, pool=pool, target_model=target_model) return None @@ -597,14 +585,10 @@ def _creds_fallback(api_key, explicit_base_url, base_url, expiry, expiry_key, re def _explicit_codex(requested_provider, model_cfg, api_key, explicit_base_url, target_model): - api_key, base_url, last_refresh = _creds_fallback( - api_key, explicit_base_url, explicit_base_url or DEFAULT_CODEX_BASE_URL, None, "last_refresh", - resolve_codex_runtime_credentials, - ) - return _runtime( - "openai-codex", "codex_responses", base_url, api_key, - source="explicit", last_refresh=last_refresh, requested_provider=requested_provider, - ) + api_key, base_url, last_refresh = _creds_fallback(api_key, explicit_base_url, explicit_base_url or DEFAULT_CODEX_BASE_URL, + None, "last_refresh", resolve_codex_runtime_credentials) + return _runtime("openai-codex", "codex_responses", base_url, api_key, source="explicit", last_refresh=last_refresh, + requested_provider=requested_provider) def _explicit_nous(requested_provider, model_cfg, api_key, explicit_base_url, target_model): @@ -616,23 +600,17 @@ def _explicit_nous(requested_provider, model_cfg, api_key, explicit_base_url, ta ) # The agent_key compatibility field is used for inference only when it holds a NAS invoke JWT; # raw OAuth access_token fallback is handled by resolve_nous_runtime_credentials(). - api_key = api_key or ( - str(state.get("agent_key") or "").strip() if _agent_key_is_usable(state, _nous_min_key_ttl()) else "" - ) - api_key, base_url, expires_at = _creds_fallback( - api_key, explicit_base_url, base_url, state.get("agent_key_expires_at") or state.get("expires_at"), "expires_at", - _resolve_nous_creds, - ) - return _runtime( - "nous", nous_api_mode(_effective_model(model_cfg, target_model)), base_url, api_key, - source="explicit", expires_at=expires_at, requested_provider=requested_provider, - ) + api_key = api_key or (str(state.get("agent_key") or "").strip() if _agent_key_is_usable(state, _nous_min_key_ttl()) else "") + api_key, base_url, expires_at = _creds_fallback(api_key, explicit_base_url, base_url, + state.get("agent_key_expires_at") or state.get("expires_at"), "expires_at", + _resolve_nous_creds) + return _runtime("nous", nous_api_mode(_effective_model(model_cfg, target_model)), base_url, api_key, source="explicit", + expires_at=expires_at, requested_provider=requested_provider) def _explicit_azure_foundry(requested_provider, model_cfg, api_key, base_url, target_model): - return _resolve_azure_foundry_runtime( - requested_provider=requested_provider, model_cfg=model_cfg, explicit_api_key=api_key, explicit_base_url=base_url - ) + return _resolve_azure_foundry_runtime(requested_provider=requested_provider, model_cfg=model_cfg, explicit_api_key=api_key, + explicit_base_url=base_url) def _actual_local_key(provider: str, api_key: str, base_url: str) -> str: @@ -658,9 +636,8 @@ def _explicit_api_key_provider(provider, pconfig, requested_provider, model_cfg, base_url = creds.get("base_url", "").rstrip("/") if provider == "actual": base_url = normalize_actual_base_url(base_url) - api_mode = _api_key_provider_api_mode( - provider, model_cfg, api_key, base_url, target_model or model_cfg.get("default", ""), opencode_by_model=False - ) + api_mode = _api_key_provider_api_mode(provider, model_cfg, api_key, base_url, target_model or model_cfg.get("default", ""), + opencode_by_model=False) api_key = _actual_local_key(provider, api_key, base_url) return _runtime(provider, api_mode, base_url.rstrip("/"), api_key, source="explicit", requested_provider=requested_provider) @@ -686,9 +663,8 @@ def _resolve_explicit_runtime( return resolver(requested_provider, model_cfg, explicit_api_key, explicit_base_url, target_model) pconfig = PROVIDER_REGISTRY.get(provider) if pconfig and pconfig.auth_type == "api_key": - return _explicit_api_key_provider( - provider, pconfig, requested_provider, model_cfg, explicit_api_key, explicit_base_url, target_model - ) + return _explicit_api_key_provider(provider, pconfig, requested_provider, model_cfg, explicit_api_key, + explicit_base_url, target_model) return None @@ -749,10 +725,8 @@ def _minimax_oauth_runtime(provider, requested_provider) -> Optional[Dict[str, A if not (pconfig and pconfig.auth_type == "oauth_minimax"): return None creds = auth_mod.resolve_minimax_oauth_runtime_credentials() - return _runtime( - provider, "anthropic_messages", creds["base_url"], creds["api_key"], - source=creds.get("source", "oauth"), requested_provider=requested_provider, - ) + return _runtime(provider, "anthropic_messages", creds["base_url"], creds["api_key"], source=creds.get("source", "oauth"), + requested_provider=requested_provider) # ── env/config paths for anthropic and registry api_key providers ────────────────────────── @@ -815,9 +789,8 @@ def _api_key_provider_runtime(provider, pconfig, requested_provider, model_cfg, base_url = _config_base_url_for_provider(model_cfg, provider) or creds.get("base_url", "").rstrip("/") if provider == "actual": base_url = normalize_actual_base_url(base_url) - api_mode = _api_key_provider_api_mode( - provider, model_cfg, creds.get("api_key", ""), base_url, target_model or model_cfg.get("default", ""), opencode_by_model=True - ) + api_mode = _api_key_provider_api_mode(provider, model_cfg, creds.get("api_key", ""), base_url, + target_model or model_cfg.get("default", ""), opencode_by_model=True) base_url = _finalize_base_url(provider, api_mode, base_url) api_key = _actual_local_key(provider, creds.get("api_key", ""), base_url) return _runtime(provider, api_mode, base_url, api_key, source=creds.get("source", "env"), requested_provider=requested_provider) @@ -864,30 +837,21 @@ def _resolve_vertex_runtime(requested_provider: str) -> Dict[str, Any]: def _resolve_requested_shortcuts(requested_provider, explicit_api_key, explicit_base_url, target_model) -> Optional[Dict[str, Any]]: """Providers decided on the REQUESTED name alone, before custom / pool / generic paths.""" if requested_provider == "moa": - return _runtime( - "moa", "chat_completions", "moa://local", "moa-virtual-provider", - source="moa-virtual-provider", requested_provider=requested_provider, - ) + return _runtime("moa", "chat_completions", "moa://local", "moa-virtual-provider", source="moa-virtual-provider", + requested_provider=requested_provider) # Azure Anthropic short-circuit: an explicit Azure endpoint with provider="anthropic" must # bypass _resolve_named_custom_runtime (which would yield custom/chat_completions/no key). eff_base = (explicit_base_url or "").strip() if requested_provider == "anthropic" and base_url_host_matches(eff_base, "azure.com"): - azure_key = ( - (explicit_api_key or "").strip() - or _getenv("AZURE_ANTHROPIC_KEY", "").strip() - or _getenv("ANTHROPIC_API_KEY", "").strip() - ) - return _runtime( - "anthropic", "anthropic_messages", eff_base.rstrip("/"), azure_key, - source="azure-explicit", requested_provider=requested_provider, - ) + azure_key = (explicit_api_key or "").strip() or _getenv("AZURE_ANTHROPIC_KEY", "").strip() or _getenv("ANTHROPIC_API_KEY", "").strip() + return _runtime("anthropic", "anthropic_messages", eff_base.rstrip("/"), azure_key, source="azure-explicit", + requested_provider=requested_provider) # Azure Foundry resolves before the custom-runtime / pool / generic paths so its config is # always picked up from model.base_url + model.api_mode, with or without explicit_* args. if requested_provider == "azure-foundry": - return _resolve_azure_foundry_runtime( - requested_provider=requested_provider, model_cfg=_get_model_config(), - explicit_api_key=explicit_api_key, explicit_base_url=explicit_base_url, target_model=target_model, - ) + return _resolve_azure_foundry_runtime(requested_provider=requested_provider, model_cfg=_get_model_config(), + explicit_api_key=explicit_api_key, explicit_base_url=explicit_base_url, + target_model=target_model) if requested_provider in _VERTEX_NAMES: return _resolve_vertex_runtime(requested_provider) return None @@ -908,9 +872,8 @@ def _local_endpoint_bypass(requested_provider: str, explicit_api_key, explicit_b def _openrouter_fallback(requested_provider, explicit_api_key, explicit_base_url) -> Dict[str, Any]: - runtime = _resolve_openrouter_runtime( - requested_provider=requested_provider, explicit_api_key=explicit_api_key, explicit_base_url=explicit_base_url - ) + runtime = _resolve_openrouter_runtime(requested_provider=requested_provider, explicit_api_key=explicit_api_key, + explicit_base_url=explicit_base_url) runtime["requested_provider"] = requested_provider return runtime @@ -950,10 +913,8 @@ def resolve_runtime_provider( runtime = _resolve_requested_shortcuts(requested_provider, explicit_api_key, explicit_base_url, target_model) if runtime: return runtime - runtime = _resolve_named_custom_runtime( - requested_provider=requested_provider, explicit_api_key=explicit_api_key, - explicit_base_url=explicit_base_url, target_model=target_model, - ) + runtime = _resolve_named_custom_runtime(requested_provider=requested_provider, explicit_api_key=explicit_api_key, + explicit_base_url=explicit_base_url, target_model=target_model) if runtime: runtime["requested_provider"] = requested_provider return runtime @@ -970,10 +931,9 @@ def _provider_rungs(provider, requested_provider, explicit_api_key, explicit_bas returned nothing; the last rung (OpenRouter / bare-custom fallback) always yields a runtime.""" model_cfg = _get_model_config() yield _opencode_free_runtime(provider, requested_provider, model_cfg, target_model) - yield _resolve_explicit_runtime( - provider=provider, requested_provider=requested_provider, model_cfg=model_cfg, - explicit_api_key=explicit_api_key, explicit_base_url=explicit_base_url, target_model=target_model, - ) + yield _resolve_explicit_runtime(provider=provider, requested_provider=requested_provider, model_cfg=model_cfg, + explicit_api_key=explicit_api_key, explicit_base_url=explicit_base_url, + target_model=target_model) yield _resolve_from_pool(provider, requested_provider, model_cfg, explicit_api_key, explicit_base_url, target_model) if provider in _OAUTH_RUNTIME_PROVIDERS: yield _resolve_oauth_runtime(provider, requested_provider, model_cfg, target_model) From 465c6dd4e98d4955e555114eb4c1b85d11a802ff Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 22:39:49 -0700 Subject: [PATCH 4/4] =?UTF-8?q?refactor(hermes=5Fcli):=20provider=20cluste?= =?UTF-8?q?r=20=E2=80=94=20hanging-indent=20repack=20of=20exploded=20call?= =?UTF-8?q?=20sites=20(AST-identical)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hermes_cli/providers.py | 77 +++++++++---------------- hermes_cli/route_identity.py | 6 +- hermes_cli/runtime_provider.py | 47 ++++++--------- hermes_cli/runtime_provider_backends.py | 54 +++++++---------- hermes_cli/runtime_provider_custom.py | 63 ++++++++------------ 5 files changed, 91 insertions(+), 156 deletions(-) diff --git a/hermes_cli/providers.py b/hermes_cli/providers.py index eccefc684b..e394dc8f69 100644 --- a/hermes_cli/providers.py +++ b/hermes_cli/providers.py @@ -178,11 +178,9 @@ def _models_dev_info(canonical: str, allow_network: bool = True): def _overlay_pdef(canonical, ov: HermesOverlay, name, env_vars, base_url, doc, source) -> ProviderDef: - return ProviderDef( - id=canonical, name=name, transport=ov.transport, api_key_env_vars=env_vars, base_url=base_url, - base_url_env_var=ov.base_url_env_var, is_aggregator=ov.is_aggregator, auth_type=ov.auth_type, doc=doc, - source=source, - ) + return ProviderDef(id=canonical, name=name, transport=ov.transport, api_key_env_vars=env_vars, base_url=base_url, + base_url_env_var=ov.base_url_env_var, is_aggregator=ov.is_aggregator, auth_type=ov.auth_type, doc=doc, + source=source) def get_provider(name: str, *, allow_network: bool = True) -> Optional[ProviderDef]: @@ -197,15 +195,11 @@ def get_provider(name: str, *, allow_network: bool = True) -> Optional[ProviderD for ev in ov.extra_env_vars: if ev not in env_vars: env_vars.append(ev) - return _overlay_pdef( - canonical, ov, mdev_info.name, tuple(env_vars), ov.base_url_override or mdev_info.api, mdev_info.doc, - "models.dev", - ) + return _overlay_pdef(canonical, ov, mdev_info.name, tuple(env_vars), ov.base_url_override or mdev_info.api, + mdev_info.doc, "models.dev") if overlay is not None: - return _overlay_pdef( - canonical, overlay, _LABEL_OVERRIDES.get(canonical, canonical), overlay.extra_env_vars, - overlay.base_url_override, "", "hermes", - ) + return _overlay_pdef(canonical, overlay, _LABEL_OVERRIDES.get(canonical, canonical), overlay.extra_env_vars, + overlay.base_url_override, "", "hermes") # Plugin-registered profiles (plugins/model-providers//) absent from models.dev and # HERMES_OVERLAYS would otherwise be "Unknown provider" in /model, --provider and model-switch # even though the picker lists them. Only profiles with a concrete endpoint resolve here: @@ -217,12 +211,10 @@ def get_provider(name: str, *, allow_network: bool = True) -> Optional[ProviderD _prof = _profile(canonical) if _prof is not None and (_prof.base_url or "").strip(): _api_mode_to_transport = {v: k for k, v in TRANSPORT_TO_API_MODE.items()} - return ProviderDef( - id=canonical, name=_prof.display_name or _prof.name or canonical, - transport=_api_mode_to_transport.get(_prof.api_mode, "openai_chat"), - api_key_env_vars=tuple(_prof.env_vars or ()), base_url=_prof.base_url or "", - auth_type=_prof.auth_type or "api_key", source="plugin-profile", - ) + return ProviderDef(id=canonical, name=_prof.display_name or _prof.name or canonical, + transport=_api_mode_to_transport.get(_prof.api_mode, "openai_chat"), + api_key_env_vars=tuple(_prof.env_vars or ()), base_url=_prof.base_url or "", + auth_type=_prof.auth_type or "api_key", source="plugin-profile") except Exception: pass return None @@ -334,10 +326,8 @@ def determine_api_mode(provider: str, base_url: str = "", model: str = "") -> st def _user_pdef(pid: str, name: str, base_url: str, key_env: str, transport: str = "openai_chat") -> ProviderDef: """``source="user-config"`` ProviderDef shared by ``providers:`` and ``custom_providers:`` entries.""" - return ProviderDef( - id=pid, name=name, transport=transport, api_key_env_vars=(key_env,) if key_env else (), - base_url=base_url, is_aggregator=False, auth_type="api_key", source="user-config", - ) + return ProviderDef(id=pid, name=name, transport=transport, api_key_env_vars=(key_env,) if key_env else (), + base_url=base_url, is_aggregator=False, auth_type="api_key", source="user-config") def resolve_user_provider(name: str, user_config: Dict[str, Any]) -> Optional[ProviderDef]: @@ -347,12 +337,10 @@ def resolve_user_provider(name: str, user_config: Dict[str, Any]) -> Optional[Pr entry = user_config.get(name) if not isinstance(entry, dict): return None - return _user_pdef( - name, entry.get("name", "") or name, - entry.get("api", "") or entry.get("url", "") or entry.get("base_url", "") or "", - entry.get("key_env") or entry.get("api_key_env") or "", - entry.get("transport", "openai_chat") or "openai_chat", - ) + return _user_pdef(name, entry.get("name", "") or name, + entry.get("api", "") or entry.get("url", "") or entry.get("base_url", "") or "", + entry.get("key_env") or entry.get("api_key_env") or "", + entry.get("transport", "openai_chat") or "openai_chat") def custom_provider_slug(display_name: str, provider_key: str = "") -> str: @@ -398,9 +386,8 @@ def resolve_custom_provider(name: str, custom_providers: Optional[List[Dict[str, if not display_name or not api_url: continue provider_key = (entry.get("provider_key") or "").strip() - pdef = _user_pdef( - custom_provider_slug(display_name, provider_key), display_name, api_url, (entry.get("key_env") or "").strip() - ) + pdef = _user_pdef(custom_provider_slug(display_name, provider_key), display_name, api_url, + (entry.get("key_env") or "").strip()) if first_valid is None: first_valid = pdef if requested in custom_provider_aliases(display_name, provider_key): @@ -422,11 +409,9 @@ def _lossy_alias_registry_pdef(raw: str, canonical: str) -> Optional[ProviderDef if _pcfg is None: return None if sum(1 for _rid in _AUTH_PROVIDER_REGISTRY if normalize_provider(_rid) == canonical) > 1: - return ProviderDef( - id=_pcfg.id, name=_pcfg.name, transport="openai_chat", - api_key_env_vars=tuple(_pcfg.api_key_env_vars or ()), base_url=_pcfg.inference_base_url or "", - source="hermes-auth-registry", - ) + return ProviderDef(id=_pcfg.id, name=_pcfg.name, transport="openai_chat", + api_key_env_vars=tuple(_pcfg.api_key_env_vars or ()), base_url=_pcfg.inference_base_url or "", + source="hermes-auth-registry") except Exception: pass return None @@ -443,16 +428,12 @@ def _llamacpp_pdef() -> Optional[ProviderDef]: endpoint = None if not endpoint: return None - return ProviderDef( - id="llamacpp", name="Local", transport="openai_chat", api_key_env_vars=(), base_url=endpoint["base_url"], - source="local-runtime", - ) + return ProviderDef(id="llamacpp", name="Local", transport="openai_chat", api_key_env_vars=(), base_url=endpoint["base_url"], + source="local-runtime") -def resolve_provider_full( - name: str, user_providers: Optional[Dict[str, Any]] = None, - custom_providers: Optional[List[Dict[str, Any]]] = None, -) -> Optional[ProviderDef]: +def resolve_provider_full(name: str, user_providers: Optional[Dict[str, Any]] = None, + custom_providers: Optional[List[Dict[str, Any]]] = None) -> Optional[ProviderDef]: """Full resolution chain: user ``providers.`` -> lossy-alias registry id -> built-in (models.dev + overlays) -> user providers (canonical, then raw) -> ``custom_providers`` -> managed llamacpp -> models.dev directly. User-defined ``providers.`` is tried FIRST on @@ -486,10 +467,8 @@ def resolve_provider_full( try: mdev_info = _models_dev_info(canonical) if mdev_info is not None: - return ProviderDef( - id=canonical, name=mdev_info.name, transport="openai_chat", api_key_env_vars=mdev_info.env, - base_url=mdev_info.api, source="models.dev", - ) + return ProviderDef(id=canonical, name=mdev_info.name, transport="openai_chat", api_key_env_vars=mdev_info.env, + base_url=mdev_info.api, source="models.dev") except Exception: pass return None diff --git a/hermes_cli/route_identity.py b/hermes_cli/route_identity.py index 3732874d72..6ada71c1d0 100644 --- a/hermes_cli/route_identity.py +++ b/hermes_cli/route_identity.py @@ -44,10 +44,8 @@ def normalize_route_base_url(base_url: Any) -> str: return normalized -def should_clear_context_pin( - configured_model: Any, active_model: Any, configured_base_url: Any, active_base_url: Any, - configured_provider: Any, active_provider: Any, -) -> bool: +def should_clear_context_pin(configured_model: Any, active_model: Any, configured_base_url: Any, active_base_url: Any, + configured_provider: Any, active_provider: Any) -> bool: """True when a configured ``model.context_length`` pin no longer matches its runtime route. Fail-closed: any error during route comparison returns ``True`` (drop the pin) so a stale window never silently inflates the compression threshold.""" diff --git a/hermes_cli/runtime_provider.py b/hermes_cli/runtime_provider.py index e8862ff1b2..8bb2a7b0b3 100644 --- a/hermes_cli/runtime_provider.py +++ b/hermes_cli/runtime_provider.py @@ -147,9 +147,7 @@ def _resolve_plain_custom_api_mode(model_cfg: Dict[str, Any], base_url: str) -> configured_mode = _parse_api_mode(model_cfg.get("api_mode")) detected_mode = _detect_api_mode_for_url(base_url) if configured_mode == "codex_responses" and detected_mode != "codex_responses": - logger.info( - "Ignoring persisted custom api_mode=codex_responses for non-OpenAI endpoint %s", base_url or "(unknown)", - ) + logger.info("Ignoring persisted custom api_mode=codex_responses for non-OpenAI endpoint %s", base_url or "(unknown)") configured_mode = None return configured_mode or detected_mode or "chat_completions" @@ -207,9 +205,8 @@ def _azure_inferred_api_mode(effective_model: str, api_mode: str) -> str: return inferred or api_mode -def _configured_or_fallback_api_mode( - provider: str, model_cfg: Dict[str, Any], base_url: str, effective_model: Any, *, opencode_by_model: bool -) -> str: +def _configured_or_fallback_api_mode(provider: str, model_cfg: Dict[str, Any], base_url: str, effective_model: Any, *, + opencode_by_model: bool) -> str: """Persisted ``model.api_mode`` when it belongs to this provider, else URL/transport fallback. OpenCode Zen/Go serve both anthropic_messages and chat_completions models, so (when ``opencode_by_model``) their mode is always re-derived from the effective model.""" @@ -218,9 +215,8 @@ def _configured_or_fallback_api_mode( return _configured_api_mode(provider, model_cfg) or _fallback_api_mode(provider, base_url, effective_model) -def _api_key_provider_api_mode( - provider: str, model_cfg: Dict[str, Any], api_key: str, base_url: str, effective_model: Any, *, opencode_by_model: bool -) -> str: +def _api_key_provider_api_mode(provider: str, model_cfg: Dict[str, Any], api_key: str, base_url: str, effective_model: Any, *, + opencode_by_model: bool) -> str: """api_mode for a registry ``api_key`` provider (explicit and env/config paths).""" if provider == "copilot": return _copilot_runtime_api_mode(model_cfg, api_key, target_model=effective_model) @@ -498,10 +494,9 @@ def _pool_entry_mode_and_url(provider, entry, model_cfg, effective_model, base_u return _configured_or_fallback_api_mode(provider, model_cfg, base_url, effective_model, opencode_by_model=True), base_url -def _resolve_runtime_from_pool_entry( - *, provider: str, entry: PooledCredential, requested_provider: str, model_cfg: Optional[Dict[str, Any]] = None, - pool: Optional[CredentialPool] = None, target_model: Optional[str] = None, -) -> Dict[str, Any]: +def _resolve_runtime_from_pool_entry(*, provider: str, entry: PooledCredential, requested_provider: str, + model_cfg: Optional[Dict[str, Any]] = None, pool: Optional[CredentialPool] = None, + target_model: Optional[str] = None) -> Dict[str, Any]: model_cfg = model_cfg or _get_model_config() api_mode, base_url = _pool_entry_mode_and_url(provider, entry, model_cfg, _effective_model(model_cfg, target_model), _pool_entry_base_url(entry).rstrip("/")) @@ -542,9 +537,8 @@ def _refresh_nous_pool_entry(pool: CredentialPool, entry: Any, pool_api_key: str return entry, pool_api_key -def _resolve_from_pool( - provider: str, requested_provider: str, model_cfg: Dict[str, Any], explicit_api_key, explicit_base_url, target_model -) -> Optional[Dict[str, Any]]: +def _resolve_from_pool(provider: str, requested_provider: str, model_cfg: Dict[str, Any], explicit_api_key, explicit_base_url, + target_model) -> Optional[Dict[str, Any]]: """Runtime from the provider's credential pool, or None to continue down the ladder.""" should_use_pool = provider != "openrouter" or _openrouter_should_use_pool(requested_provider, model_cfg, explicit_api_key, explicit_base_url) @@ -650,10 +644,9 @@ _EXPLICIT_RESOLVERS: Dict[str, Callable[..., Dict[str, Any]]] = { } -def _resolve_explicit_runtime( - *, provider: str, requested_provider: str, model_cfg: Dict[str, Any], explicit_api_key: Optional[str] = None, - explicit_base_url: Optional[str] = None, target_model: Optional[str] = None, -) -> Optional[Dict[str, Any]]: +def _resolve_explicit_runtime(*, provider: str, requested_provider: str, model_cfg: Dict[str, Any], + explicit_api_key: Optional[str] = None, explicit_base_url: Optional[str] = None, + target_model: Optional[str] = None) -> Optional[Dict[str, Any]]: explicit_api_key = str(explicit_api_key or "").strip() explicit_base_url = str(explicit_base_url or "").strip().rstrip("/") if not explicit_api_key and not explicit_base_url: @@ -713,11 +706,9 @@ def _resolve_oauth_runtime(provider, requested_provider, model_cfg, target_model api_mode = spec.api_mode if callable(api_mode): api_mode = api_mode(_effective_model(model_cfg, target_model)) - return _runtime( - provider, api_mode, (creds.get("base_url") or "").rstrip("/") or spec.default_base_url, - creds.get("api_key", ""), source=creds.get("source", spec.default_source), - **{spec.expiry_key: creds.get(spec.expiry_key)}, requested_provider=requested_provider, - ) + return _runtime(provider, api_mode, (creds.get("base_url") or "").rstrip("/") or spec.default_base_url, + creds.get("api_key", ""), source=creds.get("source", spec.default_source), + **{spec.expiry_key: creds.get(spec.expiry_key)}, requested_provider=requested_provider) def _minimax_oauth_runtime(provider, requested_provider) -> Optional[Dict[str, Any]]: @@ -891,10 +882,8 @@ def _opencode_free_runtime(provider, requested_provider, model_cfg, target_model return free_runtime -def resolve_runtime_provider( - *, requested: Optional[str] = None, explicit_api_key: Optional[str] = None, - explicit_base_url: Optional[str] = None, target_model: Optional[str] = None, -) -> Dict[str, Any]: +def resolve_runtime_provider(*, requested: Optional[str] = None, explicit_api_key: Optional[str] = None, + explicit_base_url: Optional[str] = None, target_model: Optional[str] = None) -> Dict[str, Any]: """Resolve runtime provider credentials for agent execution. Ladder (order is behavior — each rung returns or raises, else falls to the next): 1. disabled-provider guard (``providers..enabled: false``) diff --git a/hermes_cli/runtime_provider_backends.py b/hermes_cli/runtime_provider_backends.py index 7115961c71..a69baa083e 100644 --- a/hermes_cli/runtime_provider_backends.py +++ b/hermes_cli/runtime_provider_backends.py @@ -61,10 +61,9 @@ def _azure_foundry_api_key(rp, explicit_api_key: str) -> str: return api_key -def _resolve_azure_foundry_runtime( - *, requested_provider: str, model_cfg: Dict[str, Any], explicit_api_key: Optional[str] = None, - explicit_base_url: Optional[str] = None, target_model: Optional[str] = None, -) -> Dict[str, Any]: +def _resolve_azure_foundry_runtime(*, requested_provider: str, model_cfg: Dict[str, Any], + explicit_api_key: Optional[str] = None, explicit_base_url: Optional[str] = None, + target_model: Optional[str] = None) -> Dict[str, Any]: """Azure Foundry: ``model.base_url`` + ``model.api_mode`` (or explicit overrides), API key from ``.env``/env or a per-request Entra ID token, trailing ``/v1`` stripped for Anthropic-style endpoints (the Anthropic SDK appends /v1/messages itself).""" @@ -100,15 +99,11 @@ def _resolve_azure_foundry_runtime( api_key, source, auth_mode, entra = _azure_entra_credentials(cfg_entra), "entra_id", "entra_id", ( {"scope": scope} if scope else {} ) - return rp._runtime( - "azure-foundry", cfg_api_mode, base_url, api_key, - auth_mode=auth_mode, entra=entra, source=source, requested_provider=requested_provider, - ) - return rp._runtime( - "azure-foundry", cfg_api_mode, base_url, _azure_foundry_api_key(rp, explicit_api_key), - auth_mode="api_key", source="explicit" if (explicit_api_key or explicit_base_url) else "config", - requested_provider=requested_provider, - ) + return rp._runtime("azure-foundry", cfg_api_mode, base_url, api_key, auth_mode=auth_mode, entra=entra, source=source, + requested_provider=requested_provider) + return rp._runtime("azure-foundry", cfg_api_mode, base_url, _azure_foundry_api_key(rp, explicit_api_key), + auth_mode="api_key", source="explicit" if (explicit_api_key or explicit_base_url) else "config", + requested_provider=requested_provider) # ── OpenRouter / bare custom fallback ────────────────────────────────────────────────────── @@ -168,10 +163,8 @@ def _resolve_openrouter_runtime( cfg_api_mode = rp._parse_api_mode(model_cfg.get("api_mode")) # Explicit "custom" stays "custom" rather than relabeling to "openrouter". if requested_norm != "custom": - return rp._runtime( - "openrouter", cfg_api_mode or rp._detect_api_mode_for_url(base_url) or "chat_completions", - base_url, api_key, source=source, - ) + return rp._runtime("openrouter", cfg_api_mode or rp._detect_api_mode_for_url(base_url) or "chat_completions", base_url, + api_key, source=source) if base_url: # provider_name makes pool lookup prefer name match over base_url (fixes credential # mix-ups when multiple custom providers share a base_url). @@ -205,10 +198,9 @@ def _resolve_bedrock_runtime(requested_provider: str, model_cfg: Dict[str, Any], Claude → AnthropicBedrock SDK (prompt caching, thinking budgets); others → Converse API. AWS_BEARER_TOKEN_BEDROCK auth is unsupported by AnthropicBedrock (SigV4 only), so bearer users go through Converse regardless of model.""" - from agent.bedrock_adapter import ( - bedrock_openai_base_url, has_aws_credentials, is_anthropic_bedrock_model, is_openai_bedrock_model, - resolve_aws_auth_env_var, resolve_bedrock_bearer_token, resolve_bedrock_runtime_region, - ) + from agent.bedrock_adapter import (bedrock_openai_base_url, has_aws_credentials, is_anthropic_bedrock_model, + is_openai_bedrock_model, resolve_aws_auth_env_var, resolve_bedrock_bearer_token, + resolve_bedrock_runtime_region) from hermes_cli.config import load_config # direct (not the origin delegate), as before rp = _rp() # Explicitly selected bedrock trusts boto3's credential chain (IMDS, ECS/Lambda roles, SSO) @@ -230,16 +222,12 @@ def _resolve_bedrock_runtime(requested_provider: str, model_cfg: Dict[str, Any], guardrail_config = _bedrock_guardrail_config(bedrock_cfg) current_model = str(target_model or model_cfg.get("default") or "").strip() has_bearer_token = bool(os.environ.get("AWS_BEARER_TOKEN_BEDROCK", "").strip()) - runtime = rp._runtime( - "bedrock", "bedrock_converse", f"https://bedrock-runtime.{region}.amazonaws.com", "aws-sdk", - source=auth_source, region=region, requested_provider=requested_provider, - ) + runtime = rp._runtime("bedrock", "bedrock_converse", f"https://bedrock-runtime.{region}.amazonaws.com", "aws-sdk", + source=auth_source, region=region, requested_provider=requested_provider) if is_openai_bedrock_model(current_model): bearer = resolve_bedrock_bearer_token() - runtime.update( - api_mode="codex_responses", base_url=bedrock_openai_base_url(region), api_key=bearer or "aws-sdk", - source="AWS_BEARER_TOKEN_BEDROCK" if bearer else auth_source, model=current_model, bedrock_openai=True, - ) + runtime.update(api_mode="codex_responses", base_url=bedrock_openai_base_url(region), api_key=bearer or "aws-sdk", + source="AWS_BEARER_TOKEN_BEDROCK" if bearer else auth_source, model=current_model, bedrock_openai=True) elif is_anthropic_bedrock_model(current_model) and not has_bearer_token: runtime.update(api_mode="anthropic_messages", bedrock_anthropic=True) if guardrail_config: @@ -274,8 +262,6 @@ def _is_external_process_provider(provider: str) -> bool: def _resolve_external_process_runtime(provider: str, requested_provider: str) -> Dict[str, Any]: rp = _rp() creds = rp.resolve_external_process_provider_credentials(provider) - return rp._runtime( - provider, "chat_completions", creds.get("base_url", "").rstrip("/"), creds.get("api_key", ""), - command=creds.get("command", ""), args=list(creds.get("args") or []), - source=creds.get("source", "process"), requested_provider=requested_provider, - ) + return rp._runtime(provider, "chat_completions", creds.get("base_url", "").rstrip("/"), creds.get("api_key", ""), + command=creds.get("command", ""), args=list(creds.get("args") or []), + source=creds.get("source", "process"), requested_provider=requested_provider) diff --git a/hermes_cli/runtime_provider_custom.py b/hermes_cli/runtime_provider_custom.py index 442ab2eaba..125ab9a639 100644 --- a/hermes_cli/runtime_provider_custom.py +++ b/hermes_cli/runtime_provider_custom.py @@ -80,9 +80,8 @@ def _lift_extra_headers(entry: Dict[str, Any], result: Dict[str, Any]) -> None: result["extra_headers"] = extra_headers -def _lift_common_custom_fields( - entry: Dict[str, Any], result: Dict[str, Any], *, provider_key: str, key_env: str, api_mode: Optional[str] -) -> None: +def _lift_common_custom_fields(entry: Dict[str, Any], result: Dict[str, Any], *, provider_key: str, key_env: str, + api_mode: Optional[str]) -> None: """Copy the optional fields shared by ``providers:`` and legacy ``custom_providers:`` entries.""" if key_env: result["key_env"] = key_env @@ -168,10 +167,8 @@ def _match_legacy_custom_provider(requested_norm: str, custom_providers) -> Opti model_name = _clean(entry.get("model", "")) if model_name: result["model"] = model_name - _lift_common_custom_fields( - entry, result, provider_key=provider_key, key_env=_clean(entry.get("key_env", "")), - api_mode=_rp()._parse_api_mode(entry.get("api_mode")), - ) + _lift_common_custom_fields(entry, result, provider_key=provider_key, key_env=_clean(entry.get("key_env", "")), + api_mode=_rp()._parse_api_mode(entry.get("api_mode"))) return result return None @@ -276,9 +273,8 @@ def find_custom_provider_identity_by_model(model: str) -> Optional[str]: return _find_custom_identity(_entry_serves_model) -def canonical_custom_identity( - *, base_url: Optional[str] = None, config_provider: Optional[str] = None, model: Optional[str] = None -) -> Optional[str]: +def canonical_custom_identity(*, base_url: Optional[str] = None, config_provider: Optional[str] = None, + model: Optional[str] = None) -> Optional[str]: """Recover a routable ``custom:`` identity for a bare custom provider. Every path that persists or restores a session's provider override must run the resolved provider through this so a bare ``"custom"`` is upgraded back to its durable menu key. Sources in priority order: @@ -374,10 +370,8 @@ def _try_resolve_from_custom_pool( # services; has_usable_secret's 4-char floor rejects them. Every other path # substitutes "no-key-required" for a loopback endpoint — this was the one gap. pool_api_key = "no-key-required" - return rp._runtime( - provider_label, api_mode_override or rp._detect_api_mode_for_url(base_url) or "chat_completions", - base_url, pool_api_key, source=f"pool:{pool_key}", credential_pool=pool, - ) + return rp._runtime(provider_label, api_mode_override or rp._detect_api_mode_for_url(base_url) or "chat_completions", + base_url, pool_api_key, source=f"pool:{pool_key}", credential_pool=pool) except Exception: continue return None @@ -390,9 +384,7 @@ def _custom_provider_request_overrides(custom_provider: Dict[str, Any]) -> Dict[ return {"extra_body": dict(extra_body)} -def _apply_custom_provider_extras( - custom_provider: Dict[str, Any], target_model: Optional[str], result: Dict[str, Any] -) -> None: +def _apply_custom_provider_extras(custom_provider: Dict[str, Any], target_model: Optional[str], result: Dict[str, Any]) -> None: """Copy model / capabilities / max_output_tokens / extra_headers / request_overrides onto a resolved custom runtime. An explicit ``target_model`` wins over the provider's configured default (auxiliary slots / background-review resolve a concrete model and must not fall back to @@ -422,11 +414,9 @@ def _resolve_llamacpp_runtime(requested_provider: str, explicit_api_key: Optiona except Exception: # noqa: BLE001 — resolution is best-effort endpoint = None if endpoint: - return rp._runtime( - "custom", "chat_completions", endpoint["base_url"], - (explicit_api_key or "").strip() or endpoint["api_key"] or "no-key-required", - source="local-runtime", requested_provider=requested_provider, - ) + return rp._runtime("custom", "chat_completions", endpoint["base_url"], + (explicit_api_key or "").strip() or endpoint["api_key"] or "no-key-required", source="local-runtime", + requested_provider=requested_provider) try: enabled = bool((rp.load_config().get("local_runtime") or {}).get("enabled")) except Exception: # noqa: BLE001 @@ -446,15 +436,12 @@ def _resolve_llamacpp_runtime(requested_provider: str, explicit_api_key: Optiona def _custom_runtime(rp, base_url: str, api_key: Any, api_mode: Optional[str], **extra: Any) -> Dict[str, Any]: """``custom`` runtime dict with URL-detected api_mode fallback and the no-auth placeholder.""" - return rp._runtime( - "custom", api_mode or rp._detect_api_mode_for_url(base_url) or "chat_completions", base_url, - api_key or "no-key-required", **extra, - ) + return rp._runtime("custom", api_mode or rp._detect_api_mode_for_url(base_url) or "chat_completions", base_url, + api_key or "no-key-required", **extra) -def _resolve_direct_alias_runtime( - requested_provider: str, explicit_api_key: Optional[str], explicit_base_url: str -) -> Dict[str, Any]: +def _resolve_direct_alias_runtime(requested_provider: str, explicit_api_key: Optional[str], + explicit_base_url: str) -> Dict[str, Any]: """Bare ``custom`` + explicit base_url (e.g. a ``model_aliases:`` direct alias).""" rp = _rp() base_url = explicit_base_url.strip().rstrip("/") @@ -485,10 +472,9 @@ def _opencode_family_for_custom(requested_provider: str, base_url: str) -> Optio return None -def _resolve_named_custom_runtime( - *, requested_provider: str, explicit_api_key: Optional[str] = None, explicit_base_url: Optional[str] = None, - target_model: Optional[str] = None, -) -> Optional[Dict[str, Any]]: +def _resolve_named_custom_runtime(*, requested_provider: str, explicit_api_key: Optional[str] = None, + explicit_base_url: Optional[str] = None, + target_model: Optional[str] = None) -> Optional[Dict[str, Any]]: """Runtime for a llamacpp alias, a bare-custom direct alias, or a configured custom entry. Aliases resolving to "custom" (ollama, vllm, llamacpp, …) are treated like bare ``custom``. A llamacpp alias with no explicit base_url resolves to the managed server first; an explicit @@ -529,15 +515,12 @@ def _resolve_named_custom_runtime( key_cmd = _clean(custom_provider.get("key_cmd", "")) if key_cmd and not rp.has_usable_secret(explicit_key): from agent.command_token_source import build_command_token_provider - token_provider = build_command_token_provider( - key_cmd, str(custom_provider.get("name", requested_provider) or "custom") - ) + token_provider = build_command_token_provider(key_cmd, str(custom_provider.get("name", requested_provider) or "custom")) if token_provider is not None: api_key = token_provider - result = _custom_runtime( - rp, base_url, api_key, custom_provider.get("api_mode"), - source=f"custom_provider:{custom_provider.get('name', requested_provider)}", requested_provider=requested_provider, - ) + result = _custom_runtime(rp, base_url, api_key, custom_provider.get("api_mode"), + source=f"custom_provider:{custom_provider.get('name', requested_provider)}", + requested_provider=requested_provider) _apply_custom_provider_extras(custom_provider, target_model, result) # OpenCode-family custom providers (opencode-go/zen names, or opencode.ai hosts) serve models # on different API surfaces — a static api_mode 503s for /v1/responses-only models. Re-derive