fix(delegate): key a named custom child's credential pool by its inherited identity
Two named custom providers may share one gateway URL with different credentials. `_resolve_child_credential_pool` matched custom pools by URL only, so a child inherited whichever pool was registered first for that endpoint. Pass the child's `requested_provider` (and the parent's) into `get_custom_provider_pool_key(provider_name=...)`, which already prefers a name match over URL order. Second half of the class fixed by #117839; grafted from PR #89021 onto the split `delegate_tool_config` layout (#45763).
This commit is contained in:
@@ -1303,6 +1303,25 @@ class TestChildCredentialPoolResolution(unittest.TestCase):
|
||||
|
||||
# --- Custom-endpoint identity resolution (issue #7833) ---
|
||||
|
||||
def test_named_custom_child_pool_follows_requested_provider_not_endpoint_order(self):
|
||||
"""#45763 (salvage #89021): two named custom providers on one gateway URL keep separate pools; the child
|
||||
leases the pool of the identity it inherited, not the first entry registered for that URL."""
|
||||
from hermes_constants import get_hermes_home
|
||||
|
||||
url = "https://gateway.invalid/v1"
|
||||
get_hermes_home().joinpath("config.yaml").write_text(
|
||||
f"providers:\n claude-ai:\n api: {url}\n open-ai:\n api: {url}\n", encoding="utf-8",
|
||||
)
|
||||
parent = _make_mock_parent()
|
||||
parent.provider, parent.base_url, parent.requested_provider = "custom", url, "custom:open-ai"
|
||||
parent._credential_pool = None
|
||||
|
||||
with patch("tools.delegate_tool_config._loaded_pool", side_effect=lambda key: key) as loaded:
|
||||
key = _resolve_child_credential_pool("custom", parent, url, effective_requested_provider="custom:open-ai")
|
||||
loaded.assert_called_once()
|
||||
self.assertIn("open-ai", key)
|
||||
self.assertNotIn("claude", key)
|
||||
|
||||
|
||||
@patch(
|
||||
"tools.delegate_tool._load_config",
|
||||
|
||||
@@ -275,7 +275,9 @@ def _build_child_agent(
|
||||
if parent_sid and getattr(child, "_session_init_model_config", None) is not None:
|
||||
child._session_init_model_config["_delegate_from"] = parent_sid
|
||||
# Shared pool lets children rotate credentials on rate limits.
|
||||
child_pool = _resolve_child_credential_pool(rt["provider"], parent_agent, rt["base_url"])
|
||||
child_pool = _resolve_child_credential_pool(
|
||||
rt["provider"], parent_agent, rt["base_url"], effective_requested_provider=rt.get("requested_provider"),
|
||||
)
|
||||
if child_pool is not None:
|
||||
child._credential_pool = child_pool
|
||||
|
||||
|
||||
@@ -234,6 +234,7 @@ def _pool_serves_endpoint(pool: Any, provider: Optional[str], base_url: Optional
|
||||
|
||||
def _resolve_child_credential_pool(
|
||||
effective_provider: Optional[str], parent_agent, effective_base_url: Optional[str] = None,
|
||||
effective_requested_provider: Optional[str] = None,
|
||||
):
|
||||
"""Credential pool for the child: parent's pool (same provider), that provider's own pool, or None (child keeps
|
||||
its fixed credential). Custom endpoints all collapse to ``provider="custom"``, so they are matched by endpoint
|
||||
@@ -246,6 +247,10 @@ def _resolve_child_credential_pool(
|
||||
interchangeable and let the child inherit the parent's pool. We therefore resolve custom runtimes by
|
||||
endpoint identity (the ``custom:<name>`` pool key derived from the base_url) and only share the parent's
|
||||
pool when both resolve to the *same* custom endpoint. See #7833.
|
||||
|
||||
Named custom providers may share one gateway URL with different credentials, so the inherited
|
||||
``requested_provider`` identity takes precedence over URL-only matching (#45763): the child must not
|
||||
lease the first pool registered for the shared endpoint.
|
||||
"""
|
||||
parent_pool = getattr(parent_agent, "_credential_pool", None)
|
||||
if not effective_provider:
|
||||
@@ -254,10 +259,12 @@ def _resolve_child_credential_pool(
|
||||
try:
|
||||
if effective_provider == "custom":
|
||||
from agent.credential_pool import get_custom_provider_pool_key
|
||||
child_key = get_custom_provider_pool_key(effective_base_url)
|
||||
child_key = get_custom_provider_pool_key(effective_base_url, provider_name=effective_requested_provider)
|
||||
if child_key is None:
|
||||
return None
|
||||
parent_key = get_custom_provider_pool_key(getattr(parent_agent, "base_url", None))
|
||||
parent_key = get_custom_provider_pool_key(
|
||||
getattr(parent_agent, "base_url", None), provider_name=getattr(parent_agent, "requested_provider", None),
|
||||
)
|
||||
if parent_pool is not None and parent_provider == "custom" and parent_key is not None and parent_key == child_key:
|
||||
return parent_pool
|
||||
return _loaded_pool(child_key)
|
||||
|
||||
Reference in New Issue
Block a user