django-agent-studio 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,228 @@
1
+ """
2
+ Permission service for Dynamic Tool access control.
3
+
4
+ Provides centralized permission checking for all dynamic tool operations.
5
+ """
6
+
7
+ import logging
8
+ from typing import Optional, TYPE_CHECKING
9
+
10
+ from django.contrib.auth import get_user_model
11
+ from django.db.models import QuerySet
12
+
13
+ from django_agent_studio.models.permissions import (
14
+ DynamicToolAccessLevel,
15
+ UserDynamicToolAccess,
16
+ ToolApprovalRequest,
17
+ ACCESS_LEVEL_HIERARCHY,
18
+ )
19
+
20
+ if TYPE_CHECKING:
21
+ from django_agent_runtime.models import AgentDefinition, DynamicTool
22
+
23
+ logger = logging.getLogger(__name__)
24
+ User = get_user_model()
25
+
26
+
27
+ class DynamicToolPermissionService:
28
+ """
29
+ Service for checking and managing dynamic tool permissions.
30
+
31
+ Usage:
32
+ service = DynamicToolPermissionService()
33
+
34
+ # Check if user can scan
35
+ if service.can_scan(user, agent):
36
+ # do scan
37
+
38
+ # Check if user can create tools directly or needs approval
39
+ if service.can_create_tool(user, agent):
40
+ # create directly
41
+ elif service.can_request_tool(user, agent):
42
+ # create approval request
43
+ """
44
+
45
+ def get_user_access_level(
46
+ self,
47
+ user,
48
+ agent: Optional['AgentDefinition'] = None,
49
+ ) -> str:
50
+ """
51
+ Get the effective access level for a user.
52
+
53
+ Args:
54
+ user: The user to check
55
+ agent: Optional agent to check agent-specific restrictions
56
+
57
+ Returns:
58
+ The access level string
59
+ """
60
+ # Superusers always have admin access
61
+ if user.is_superuser:
62
+ return DynamicToolAccessLevel.ADMIN
63
+
64
+ # Check for user-specific access
65
+ try:
66
+ access = UserDynamicToolAccess.objects.get(user=user)
67
+
68
+ # If restricted to specific agents, check if this agent is included
69
+ if agent and access.restricted_to_agents.exists():
70
+ if not access.restricted_to_agents.filter(id=agent.id).exists():
71
+ return DynamicToolAccessLevel.NONE
72
+
73
+ return access.access_level
74
+ except UserDynamicToolAccess.DoesNotExist:
75
+ return DynamicToolAccessLevel.NONE
76
+
77
+ def has_level(
78
+ self,
79
+ user,
80
+ required_level: str,
81
+ agent: Optional['AgentDefinition'] = None,
82
+ ) -> bool:
83
+ """
84
+ Check if user has at least the required access level.
85
+
86
+ Args:
87
+ user: The user to check
88
+ required_level: The minimum required level
89
+ agent: Optional agent for agent-specific checks
90
+
91
+ Returns:
92
+ True if user has sufficient access
93
+ """
94
+ user_level = self.get_user_access_level(user, agent)
95
+ user_rank = ACCESS_LEVEL_HIERARCHY.get(user_level, 0)
96
+ required_rank = ACCESS_LEVEL_HIERARCHY.get(required_level, 0)
97
+ return user_rank >= required_rank
98
+
99
+ # Convenience methods for specific operations
100
+
101
+ def can_view(self, user, agent: Optional['AgentDefinition'] = None) -> bool:
102
+ """Can user view discovered functions and tools?"""
103
+ return self.has_level(user, DynamicToolAccessLevel.VIEWER, agent)
104
+
105
+ def can_scan(self, user, agent: Optional['AgentDefinition'] = None) -> bool:
106
+ """Can user scan project for functions?"""
107
+ return self.has_level(user, DynamicToolAccessLevel.SCANNER, agent)
108
+
109
+ def can_request_tool(self, user, agent: Optional['AgentDefinition'] = None) -> bool:
110
+ """Can user request tool creation (with approval)?"""
111
+ return self.has_level(user, DynamicToolAccessLevel.REQUESTER, agent)
112
+
113
+ def can_create_tool(self, user, agent: Optional['AgentDefinition'] = None) -> bool:
114
+ """Can user create tools directly without approval?"""
115
+ return self.has_level(user, DynamicToolAccessLevel.CREATOR, agent)
116
+
117
+ def can_approve(self, user, agent: Optional['AgentDefinition'] = None) -> bool:
118
+ """Can user approve/reject tool requests?"""
119
+ return self.has_level(user, DynamicToolAccessLevel.ADMIN, agent)
120
+
121
+ def can_manage_permissions(self, user) -> bool:
122
+ """Can user manage other users' access levels?"""
123
+ return self.has_level(user, DynamicToolAccessLevel.ADMIN)
124
+
125
+ def can_modify_tool(
126
+ self,
127
+ user,
128
+ tool: 'DynamicTool',
129
+ ) -> bool:
130
+ """Can user modify an existing tool?"""
131
+ return self.has_level(user, DynamicToolAccessLevel.CREATOR, tool.agent)
132
+
133
+ def can_delete_tool(
134
+ self,
135
+ user,
136
+ tool: 'DynamicTool',
137
+ ) -> bool:
138
+ """Can user delete a tool?"""
139
+ return self.has_level(user, DynamicToolAccessLevel.ADMIN, tool.agent)
140
+
141
+ # Multi-agent specific permissions
142
+
143
+ def can_add_sub_agent(self, user, agent: Optional['AgentDefinition'] = None) -> bool:
144
+ """Can user add sub-agent tools to an agent?"""
145
+ return self.has_level(user, DynamicToolAccessLevel.CREATOR, agent)
146
+
147
+ def can_remove_sub_agent(self, user, agent: Optional['AgentDefinition'] = None) -> bool:
148
+ """Can user remove sub-agent tools from an agent?"""
149
+ return self.has_level(user, DynamicToolAccessLevel.CREATOR, agent)
150
+
151
+ def can_create_agent_system(self, user) -> bool:
152
+ """Can user create multi-agent systems?"""
153
+ return self.has_level(user, DynamicToolAccessLevel.CREATOR)
154
+
155
+ def can_publish_system_version(self, user) -> bool:
156
+ """Can user publish agent system versions?"""
157
+ return self.has_level(user, DynamicToolAccessLevel.ADMIN)
158
+
159
+ def can_deploy_system_version(self, user) -> bool:
160
+ """Can user deploy (activate) agent system versions?"""
161
+ return self.has_level(user, DynamicToolAccessLevel.ADMIN)
162
+
163
+ # Access management methods
164
+
165
+ def grant_access(
166
+ self,
167
+ user,
168
+ access_level: str,
169
+ granted_by,
170
+ agents: Optional[QuerySet] = None,
171
+ notes: str = '',
172
+ ) -> UserDynamicToolAccess:
173
+ """
174
+ Grant or update access level for a user.
175
+
176
+ Args:
177
+ user: User to grant access to
178
+ access_level: The access level to grant
179
+ granted_by: User granting the access
180
+ agents: Optional queryset of agents to restrict access to
181
+ notes: Optional notes about why access was granted
182
+
183
+ Returns:
184
+ The created/updated access record
185
+ """
186
+ access, created = UserDynamicToolAccess.objects.update_or_create(
187
+ user=user,
188
+ defaults={
189
+ 'access_level': access_level,
190
+ 'granted_by': granted_by,
191
+ 'notes': notes,
192
+ }
193
+ )
194
+
195
+ if agents is not None:
196
+ access.restricted_to_agents.set(agents)
197
+
198
+ action = 'Granted' if created else 'Updated'
199
+ logger.info(
200
+ f"{action} {access_level} access to {user} by {granted_by}"
201
+ )
202
+
203
+ return access
204
+
205
+ def revoke_access(self, user) -> bool:
206
+ """
207
+ Revoke all dynamic tool access for a user.
208
+
209
+ Returns:
210
+ True if access was revoked, False if user had no access
211
+ """
212
+ deleted, _ = UserDynamicToolAccess.objects.filter(user=user).delete()
213
+ if deleted:
214
+ logger.info(f"Revoked dynamic tool access for {user}")
215
+ return deleted > 0
216
+
217
+
218
+ # Singleton instance
219
+ _permission_service: Optional[DynamicToolPermissionService] = None
220
+
221
+
222
+ def get_permission_service() -> DynamicToolPermissionService:
223
+ """Get the singleton permission service instance."""
224
+ global _permission_service
225
+ if _permission_service is None:
226
+ _permission_service = DynamicToolPermissionService()
227
+ return _permission_service
228
+
@@ -580,6 +580,54 @@
580
580
  background-color: #dc2626 !important;
581
581
  }
582
582
 
583
+ /* Voice input button */
584
+ .cw-voice-btn {
585
+ width: 40px;
586
+ height: 40px;
587
+ border: 1px solid var(--cw-border);
588
+ border-radius: var(--cw-radius-sm);
589
+ cursor: pointer;
590
+ display: flex;
591
+ align-items: center;
592
+ justify-content: center;
593
+ background: var(--cw-bg);
594
+ color: var(--cw-text-muted);
595
+ transition: all 0.15s ease;
596
+ }
597
+
598
+ .cw-voice-btn:hover:not(:disabled) {
599
+ background: var(--cw-bg-muted);
600
+ color: var(--cw-text);
601
+ border-color: var(--cw-primary);
602
+ }
603
+
604
+ .cw-voice-btn:disabled {
605
+ opacity: 0.5;
606
+ cursor: not-allowed;
607
+ }
608
+
609
+ /* Recording state */
610
+ .cw-voice-btn.cw-voice-btn-recording {
611
+ background: #dc2626;
612
+ border-color: #dc2626;
613
+ color: white;
614
+ animation: cw-pulse 1.5s ease-in-out infinite;
615
+ }
616
+
617
+ .cw-voice-btn.cw-voice-btn-recording:hover {
618
+ background: #b91c1c;
619
+ border-color: #b91c1c;
620
+ }
621
+
622
+ @keyframes cw-pulse {
623
+ 0%, 100% {
624
+ box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.4);
625
+ }
626
+ 50% {
627
+ box-shadow: 0 0 0 8px rgba(220, 38, 38, 0);
628
+ }
629
+ }
630
+
583
631
  /* Dropdown */
584
632
  .cw-dropdown {
585
633
  position: relative;