Fix custom provider identity loss in session persistence
_runtime_model_config persisted the live agent's RESOLVED provider into the session row's model_config JSON. For any named providers:/ custom_providers: entry, agent.provider is the literal string "custom", so the entry name was lost (and the api_key is deliberately never persisted). On session.resume or _reset_session_agent the stored provider="custom" fed resolve_runtime_provider(requested="custom"), which cannot match a named entry — the rebuild either raised "No LLM provider configured" or silently resolved placeholder credentials against the patched-back base_url. Persist the REQUESTED/entry identity instead: a new reverse lookup find_custom_provider_identity(base_url) maps the endpoint URL back to the canonical custom:<name> menu key. _runtime_model_config stores that key; _make_agent performs the same recovery for rows persisted before the fix, falling back to passing the stored base_url as explicit_base_url so the direct-alias branch still targets the session's endpoint when no entry matches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
committed by
Teknium
co-authored by
Claude Fable 5
parent
e256f4aae4
commit
643dc82793
@@ -660,6 +660,61 @@ def has_named_custom_provider(requested_provider: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def find_custom_provider_identity(base_url: str) -> Optional[str]:
|
||||
"""Map an endpoint URL back to its canonical ``custom:<name>`` menu key.
|
||||
|
||||
Returns the ``custom:<normalized-name>`` slug of the first ``providers:``
|
||||
/ ``custom_providers:`` entry whose base_url matches, or ``None`` when no
|
||||
entry owns the URL.
|
||||
|
||||
Session persistence stores the agent's *resolved* provider, and for every
|
||||
named custom endpoint that is the literal string ``"custom"`` — the entry
|
||||
name is lost, and the api_key is deliberately never persisted. The
|
||||
endpoint URL is the one durable fact that survives the round-trip, so
|
||||
this reverse lookup lets persist/rebuild paths recover the entry identity
|
||||
(and with it key_env/api_key/api_mode resolution via
|
||||
:func:`_get_named_custom_provider`) instead of failing with
|
||||
``auth_unavailable`` or silently rebuilding with placeholder credentials.
|
||||
"""
|
||||
target = _normalize_base_url_for_match(base_url)
|
||||
if not target:
|
||||
return None
|
||||
try:
|
||||
config = load_config()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
providers = config.get("providers")
|
||||
if isinstance(providers, dict):
|
||||
for ep_name, entry in providers.items():
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
entry_url = (
|
||||
entry.get("api") or entry.get("url") or entry.get("base_url") or ""
|
||||
)
|
||||
if _normalize_base_url_for_match(entry_url) == target:
|
||||
return f"custom:{_normalize_custom_provider_name(str(ep_name))}"
|
||||
|
||||
try:
|
||||
custom_providers = get_compatible_custom_providers(config)
|
||||
except Exception:
|
||||
custom_providers = None
|
||||
for entry in custom_providers or []:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
name = entry.get("name")
|
||||
if not isinstance(name, str) or not name.strip():
|
||||
continue
|
||||
if _normalize_base_url_for_match(entry.get("base_url")) == target:
|
||||
return f"custom:{_normalize_custom_provider_name(name)}"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _normalize_base_url_for_match(value) -> str:
|
||||
return str(value or "").strip().rstrip("/").lower()
|
||||
|
||||
|
||||
def _custom_provider_request_overrides(custom_provider: Dict[str, Any]) -> Dict[str, Any]:
|
||||
extra_body = custom_provider.get("extra_body")
|
||||
if not isinstance(extra_body, dict) or not extra_body:
|
||||
|
||||
Reference in New Issue
Block a user