fix(agent): keep primary runtime restore on matching credential pool (#56374)

This commit is contained in:
openhands 2026-07-01 12:43:38 +00:00 committed by kshitij
parent fb403a3a73
commit 820a052575
2 changed files with 61 additions and 1 deletions

View file

@ -1184,11 +1184,23 @@ def restore_primary_runtime(agent) -> bool:
if pool is not None and pool.has_available():
entry = pool.select()
if entry is not None:
entry_provider = str(getattr(entry, "provider", "") or "").strip().lower()
primary_provider = str(rt.get("provider") or "").strip().lower()
entry_matches_primary = entry_provider == primary_provider
if primary_provider == "custom" and entry_provider.startswith("custom:"):
primary_base_url = str(rt.get("base_url") or "").strip().rstrip("/").lower()
entry_base_url = str(
getattr(entry, "runtime_base_url", None)
or getattr(entry, "base_url", None)
or ""
).strip().rstrip("/").lower()
entry_matches_primary = bool(primary_base_url and entry_base_url == primary_base_url)
entry_key = (
getattr(entry, "runtime_api_key", None)
or getattr(entry, "access_token", "")
)
if entry_key:
if entry_key and entry_matches_primary:
# ``_swap_credential`` rebuilds the OpenAI/Anthropic client,
# reapplies base-url-scoped headers, and carries the
# accumulated base_url / OAuth-detection fixes (#33163).
@ -1198,6 +1210,14 @@ def restore_primary_runtime(agent) -> bool:
getattr(entry, "id", "?"),
getattr(entry, "label", "?"),
)
elif entry_key:
logger.info(
"Restore skipped pool entry %s (%s): provider %s does not match primary provider %s",
getattr(entry, "id", "?"),
getattr(entry, "label", "?"),
entry_provider or "?",
primary_provider or "?",
)
# ── Reset fallback chain for the new turn ──
agent._fallback_activated = False

View file

@ -201,6 +201,46 @@ class TestRestorePrimaryRuntime:
assert agent._use_prompt_caching == original_caching
def test_restore_skips_cross_provider_pool_entry(self):
"""Restore must not swap in a fallback provider credential for the primary runtime."""
class _Entry:
provider = "openrouter"
id = "fallback-entry"
label = "fallback"
runtime_api_key = "fallback-key"
runtime_base_url = "https://openrouter.ai/api/v1"
access_token = "fallback-key"
class _Pool:
provider = "openrouter"
def has_available(self):
return True
def select(self):
return _Entry()
agent = _make_agent(
provider="custom",
base_url="https://primary.example.com/v1",
fallback_model={"provider": "openrouter", "model": "anthropic/claude-sonnet-4"},
)
original_base_url = agent.base_url
mock_client = _mock_resolve()
with patch("agent.auxiliary_client.resolve_provider_client", return_value=(mock_client, None)):
agent._try_activate_fallback()
agent._credential_pool = _Pool()
agent._swap_credential = MagicMock()
with patch("run_agent.OpenAI", return_value=MagicMock()):
result = agent._restore_primary_runtime()
assert result is True
assert agent.provider == "custom"
assert agent.base_url == original_base_url
agent._swap_credential.assert_not_called()
def test_restore_survives_exception(self):
"""If client rebuild fails, the method returns False gracefully."""
agent = _make_agent()