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.
- django_agent_studio/agents/__init__.py +29 -0
- django_agent_studio/agents/builder.py +1918 -0
- django_agent_studio/agents/dynamic.py +376 -0
- django_agent_studio/migrations/0001_initial.py +63 -0
- django_agent_studio/migrations/__init__.py +0 -0
- django_agent_studio/models/__init__.py +18 -0
- django_agent_studio/models/permissions.py +191 -0
- django_agent_studio/services/__init__.py +14 -0
- django_agent_studio/services/permissions.py +228 -0
- django_agent_studio/static/agent-frontend/chat-widget.css +48 -0
- django_agent_studio/static/agent-frontend/chat-widget.js +119 -100
- {django_agent_studio-0.1.0.dist-info → django_agent_studio-0.1.1.dist-info}/METADATA +1 -1
- {django_agent_studio-0.1.0.dist-info → django_agent_studio-0.1.1.dist-info}/RECORD +15 -6
- {django_agent_studio-0.1.0.dist-info → django_agent_studio-0.1.1.dist-info}/WHEEL +1 -1
- {django_agent_studio-0.1.0.dist-info → django_agent_studio-0.1.1.dist-info}/top_level.txt +0 -0
|
@@ -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;
|