refactor(hermes_cli): provider cluster — hanging-indent repack of exploded call sites (AST-identical)
This commit is contained in:
@@ -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/<name>/) 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.<raw name>`` -> lossy-alias registry id -> built-in
|
||||
(models.dev + overlays) -> user providers (canonical, then raw) -> ``custom_providers`` ->
|
||||
managed llamacpp -> models.dev directly. User-defined ``providers.<name>`` 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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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.<name>.enabled: false``)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:<name>`` 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
|
||||
|
||||
Reference in New Issue
Block a user