fix(fallback): named custom providers keep their configured identity after automatic fallback (#98739)

resolve_runtime_provider returns the bare billing class 'custom' for every
named providers:/custom_providers: entry; the configured id only survives in
requested_provider. All three fallback resolvers (gateway, TUI/desktop, cron)
persisted runtime['provider'] as the agent identity, so an automatic fallback
labeled the session 'custom' in the UI and billing rows, while a manual
/model switch to the same provider showed the configured name.

New shared helper hermes_cli.fallback_config.effective_runtime_provider()
upgrades the bare class back to the entry's configured identity (ad-hoc
provider: custom entries stay unchanged), applied at all three sites —
same class as the delegate_tool fix.
This commit is contained in:
Teknium
2026-08-30 11:22:32 -07:00
parent 2aad4035a5
commit f72e79a111
5 changed files with 73 additions and 4 deletions
+32
View File
@@ -28,6 +28,38 @@ def resolve_entry_api_key(entry: dict[str, Any] | None) -> str | None:
return None
def effective_runtime_provider(
entry: dict[str, Any] | None, runtime: dict[str, Any] | None
) -> str:
"""Provider identity to persist/display for a resolved fallback entry.
``resolve_runtime_provider`` returns the bare billing class ``"custom"``
for every named ``providers:`` / ``custom_providers:`` entry; the entry's
configured id only survives in ``requested_provider``. Fallback resolvers
that persist ``runtime["provider"]`` as the agent identity therefore label
sessions/billing rows ``custom`` instead of the configured provider name —
while the manual ``/model`` switch path correctly persists the named id
(#98739). Same class as the delegation fix in ``tools/delegate_tool.py``.
Returns the entry's requested identity when the resolved provider is the
bare ``custom`` class; a genuinely ad-hoc endpoint (requested provider IS
``custom``) keeps the bare class unchanged.
"""
runtime = runtime or {}
resolved = str(runtime.get("provider") or "").strip()
if resolved.lower() != "custom":
return resolved
requested = str(
runtime.get("requested_provider")
or (entry or {}).get("provider")
or ""
).strip()
if requested and requested.lower() != "custom":
return requested
return resolved
def _iter_fallback_entries(raw: Any) -> list[dict[str, Any]]:
candidates = [raw] if isinstance(raw, dict) else raw if isinstance(raw, list) else []
entries: list[dict[str, Any]] = []