fix(delegate): resolve custom-endpoint subagent pools by endpoint identity (#41730)

Subagents delegated to a custom endpoint were misrouted when the parent
ran on a different custom endpoint. Both runtimes collapse to
provider="custom", so _resolve_child_credential_pool() treated them as
interchangeable and handed the child the parent's pool. Leasing from it
then overwrote the child's delegated base_url with the parent's endpoint
via _swap_credential() — the child sent the delegated model name to the
wrong endpoint.

Custom runtimes now resolve by endpoint identity (the custom:<name> pool
key derived from base_url). The parent pool is reused only when both
parent and child resolve to the same custom endpoint; unregistered raw
endpoints return None so the child keeps its fixed delegated credential.
Non-custom provider paths are unchanged.

Fixes #7833.
This commit is contained in:
Teknium 2026-06-07 22:05:14 -07:00 committed by GitHub
parent bddc5fd087
commit 48ae8029aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 124 additions and 3 deletions

View file

@ -1518,6 +1518,73 @@ class TestChildCredentialPoolResolution(unittest.TestCase):
self.assertIsNone(result)
# --- Custom-endpoint identity resolution (issue #7833) ---
def test_custom_different_endpoint_does_not_inherit_parent_pool(self):
"""A child on custom endpoint B must not inherit the parent's custom
endpoint A pool just because both normalize to provider='custom'."""
parent = _make_mock_parent()
parent.provider = "custom"
parent.base_url = "https://endpoint-a.example.com/v1"
parent._credential_pool = MagicMock(name="parent_custom_a_pool")
child_pool = MagicMock(name="endpoint_b_pool")
child_pool.has_credentials.return_value = True
def fake_key(base_url, provider_name=None):
return {
"https://endpoint-a.example.com/v1": "custom:endpoint-a",
"https://endpoint-b.example.com/v1": "custom:endpoint-b",
}.get(base_url)
with patch("agent.credential_pool.get_custom_provider_pool_key", side_effect=fake_key), \
patch("agent.credential_pool.load_pool", return_value=child_pool) as load_mock:
result = _resolve_child_credential_pool(
"custom", parent, "https://endpoint-b.example.com/v1"
)
# Loaded the child's OWN endpoint pool, not the parent's.
load_mock.assert_called_once_with("custom:endpoint-b")
self.assertIs(result, child_pool)
self.assertIsNot(result, parent._credential_pool)
def test_custom_same_endpoint_shares_parent_pool(self):
"""A child on the SAME custom endpoint as the parent reuses the parent's
pool so rotation/cooldown state stays synchronized."""
parent = _make_mock_parent()
parent.provider = "custom"
parent.base_url = "https://endpoint-a.example.com/v1"
parent._credential_pool = MagicMock(name="parent_custom_a_pool")
with patch(
"agent.credential_pool.get_custom_provider_pool_key",
return_value="custom:endpoint-a",
):
result = _resolve_child_credential_pool(
"custom", parent, "https://endpoint-a.example.com/v1"
)
self.assertIs(result, parent._credential_pool)
def test_custom_unregistered_endpoint_returns_none(self):
"""A raw delegation.base_url with no matching custom_providers entry
must NOT inherit the parent's pool — return None so the child keeps its
fixed delegated credential."""
parent = _make_mock_parent()
parent.provider = "custom"
parent.base_url = "https://endpoint-a.example.com/v1"
parent._credential_pool = MagicMock(name="parent_custom_a_pool")
with patch(
"agent.credential_pool.get_custom_provider_pool_key",
return_value=None,
):
result = _resolve_child_credential_pool(
"custom", parent, "https://raw-unregistered.example.com/v1"
)
self.assertIsNone(result)
def test_build_child_agent_assigns_parent_pool_when_shared(self):
parent = _make_mock_parent()
mock_pool = MagicMock()