Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/anthropic_endpoints/claude_code_endpoints/claude_code_skill_access.py: 83%
40 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1"""
2Claude Code marketplace visibility: enabled plugins are public, disabled plugins
3are private and resolve only for proxy admins or keys granted them via
4``object_permission.skills``.
5"""
7from dataclasses import dataclass
8from typing import TYPE_CHECKING, Final, Protocol
10from litellm.proxy._types import LiteLLM_ObjectPermissionTable, UserAPIKeyAuth
11from litellm.proxy.common_utils.resource_ownership import is_proxy_admin
13if TYPE_CHECKING: 13 ↛ 14line 13 didn't jump to line 14 because the condition on line 13 was never true
14 from prisma.types import LiteLLM_ClaudeCodePluginTableWhereInput
17class _SkillRecord(Protocol):
18 name: str
19 enabled: bool
22def _skills_of(permission: LiteLLM_ObjectPermissionTable | None) -> frozenset[str]:
23 return frozenset(permission.skills or ()) if permission is not None else frozenset()
26def granted_skills(user_api_key_dict: UserAPIKeyAuth) -> frozenset[str]:
27 """Key grant intersected with the team grant when both are non-empty; either alone applies as is.
29 An empty list is the Prisma column default for every object-permission row, so it means
30 "no private grants configured here" and defers to the other scope, same as the agents check.
31 """
32 key_skills: Final = _skills_of(user_api_key_dict.object_permission)
33 team_skills: Final = _skills_of(user_api_key_dict.team_object_permission)
34 match (bool(key_skills), bool(team_skills)):
35 case (True, True): 35 ↛ 36line 35 didn't jump to line 36 because the pattern on line 35 never matched
36 return key_skills & team_skills
37 case (True, False): 37 ↛ 38line 37 didn't jump to line 38 because the pattern on line 37 never matched
38 return key_skills
39 case _:
40 return team_skills
43@dataclass(frozen=True, slots=True)
44class SkillVisibility:
45 granted: frozenset[str]
46 sees_private: bool
48 def allows(self, skill: _SkillRecord) -> bool:
49 return skill.enabled or self.sees_private or skill.name in self.granted
51 def where(self) -> "LiteLLM_ClaudeCodePluginTableWhereInput":
52 if self.sees_private:
53 return {}
54 if not self.granted: 54 ↛ 56line 54 didn't jump to line 56 because the condition on line 54 was always true
55 return {"enabled": True}
56 return {"OR": [{"enabled": True}, {"name": {"in": sorted(self.granted)}}]}
59PUBLIC_ONLY: Final = SkillVisibility(granted=frozenset(), sees_private=False)
62def skill_visibility(user_api_key_dict: UserAPIKeyAuth | None) -> SkillVisibility:
63 if user_api_key_dict is None:
64 return PUBLIC_ONLY
65 if is_proxy_admin(user_api_key_dict):
66 return SkillVisibility(granted=frozenset(), sees_private=True)
67 return SkillVisibility(granted=granted_skills(user_api_key_dict), sees_private=False)