From 35cff949248feea53bd59e05c4f6dad5f86c5741 Mon Sep 17 00:00:00 2001 From: yassin Date: Mon, 14 Sep 2026 19:34:14 +0000 Subject: [PATCH] fix(proxy): resolve the agent registry lazily in JWT auth to break the import cycle Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/auth/handle_jwt.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/auth/handle_jwt.py b/litellm/proxy/auth/handle_jwt.py index 0e09fce268c..fcbcf35dba9 100644 --- a/litellm/proxy/auth/handle_jwt.py +++ b/litellm/proxy/auth/handle_jwt.py @@ -51,7 +51,6 @@ from litellm.proxy._types import ( TeamMemberAddRequest, UserAPIKeyAuth, ) -from litellm.proxy.agent_endpoints.agent_registry import AgentRegistry, global_agent_registry from litellm.proxy.auth.auth_checks import can_team_access_model from litellm.proxy.auth.resolvers.grants import GrantResolver, UserLookup, canonical_user_id from litellm.proxy.auth.route_checks import RouteChecks @@ -62,6 +61,7 @@ from litellm.proxy.common_utils.user_api_key_cache import ( ) from litellm.proxy.utils import PrismaClient, ProxyLogging from litellm.repositories.user_repository import UserRepository +from litellm.types.agents import AgentResponse from .auth_checks import ( _allowed_routes_check, @@ -128,6 +128,20 @@ class _UserInfoResponse(Protocol): def json(self) -> dict[str, object]: ... +class AgentLookup(Protocol): + """The registered-agent lookups a JWT agent claim is matched against.""" + + def get_agent_by_id(self, agent_id: str) -> AgentResponse | None: ... + + def get_agent_by_name(self, agent_name: str) -> AgentResponse | None: ... + + +def _global_agent_lookup() -> AgentLookup: + from litellm.proxy.agent_endpoints.agent_registry import global_agent_registry + + return global_agent_registry + + def _discovery_document(response: _OIDCDiscoveryResponse) -> _OIDCDiscoveryBody: """Decode an OIDC discovery response body.""" return response.json() @@ -1424,7 +1438,7 @@ class JWTAuthManager: def resolve_agent_id( jwt_handler: JWTHandler, jwt_valid_token: Mapping[str, object], - agent_registry: AgentRegistry, + agent_registry: AgentLookup, ) -> str | None: agent_claim: Final = jwt_handler.get_agent_claim(token=jwt_valid_token) if agent_claim is None: @@ -2237,7 +2251,7 @@ class JWTAuthManager: proxy_logging_obj: ProxyLogging, request_headers: dict | None = None, request_method: str | None = None, - agent_registry: AgentRegistry = global_agent_registry, + agent_registry: AgentLookup | None = None, ) -> JWTAuthBuilderResult: """Main authentication and authorization builder""" # Check if OIDC UserInfo endpoint is enabled, but fall back to standard @@ -2298,7 +2312,9 @@ class JWTAuthManager: user_id = object_id agent_id: Final = JWTAuthManager.resolve_agent_id( - jwt_handler=jwt_handler, jwt_valid_token=jwt_valid_token, agent_registry=agent_registry + jwt_handler=jwt_handler, + jwt_valid_token=jwt_valid_token, + agent_registry=agent_registry if agent_registry is not None else _global_agent_lookup(), ) # Check admin access