django-agent-studio 0.1.5__py3-none-any.whl → 0.1.8__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/builder.py +737 -0
- django_agent_studio/api/urls.py +51 -0
- django_agent_studio/api/views.py +379 -0
- django_agent_studio/templates/django_agent_studio/builder.html +630 -2
- {django_agent_studio-0.1.5.dist-info → django_agent_studio-0.1.8.dist-info}/METADATA +1 -1
- {django_agent_studio-0.1.5.dist-info → django_agent_studio-0.1.8.dist-info}/RECORD +8 -8
- {django_agent_studio-0.1.5.dist-info → django_agent_studio-0.1.8.dist-info}/WHEEL +0 -0
- {django_agent_studio-0.1.5.dist-info → django_agent_studio-0.1.8.dist-info}/top_level.txt +0 -0
django_agent_studio/api/urls.py
CHANGED
|
@@ -242,4 +242,55 @@ urlpatterns = [
|
|
|
242
242
|
views.AgentSystemDiscoverView.as_view(),
|
|
243
243
|
name="discover_system",
|
|
244
244
|
),
|
|
245
|
+
|
|
246
|
+
# ==========================================================================
|
|
247
|
+
# Spec Document Endpoints
|
|
248
|
+
# ==========================================================================
|
|
249
|
+
|
|
250
|
+
# List and create spec documents
|
|
251
|
+
path(
|
|
252
|
+
"spec-documents/",
|
|
253
|
+
views.SpecDocumentListCreateView.as_view(),
|
|
254
|
+
name="spec_document_list",
|
|
255
|
+
),
|
|
256
|
+
path(
|
|
257
|
+
"spec-documents/<uuid:pk>/",
|
|
258
|
+
views.SpecDocumentDetailView.as_view(),
|
|
259
|
+
name="spec_document_detail",
|
|
260
|
+
),
|
|
261
|
+
|
|
262
|
+
# Link/unlink to agent
|
|
263
|
+
path(
|
|
264
|
+
"spec-documents/<uuid:pk>/link/",
|
|
265
|
+
views.SpecDocumentLinkView.as_view(),
|
|
266
|
+
name="spec_document_link",
|
|
267
|
+
),
|
|
268
|
+
|
|
269
|
+
# Version history
|
|
270
|
+
path(
|
|
271
|
+
"spec-documents/<uuid:pk>/history/",
|
|
272
|
+
views.SpecDocumentHistoryView.as_view(),
|
|
273
|
+
name="spec_document_history",
|
|
274
|
+
),
|
|
275
|
+
|
|
276
|
+
# Restore version
|
|
277
|
+
path(
|
|
278
|
+
"spec-documents/<uuid:pk>/restore/",
|
|
279
|
+
views.SpecDocumentRestoreView.as_view(),
|
|
280
|
+
name="spec_document_restore",
|
|
281
|
+
),
|
|
282
|
+
|
|
283
|
+
# Tree view
|
|
284
|
+
path(
|
|
285
|
+
"spec-documents/tree/",
|
|
286
|
+
views.SpecDocumentTreeView.as_view(),
|
|
287
|
+
name="spec_document_tree",
|
|
288
|
+
),
|
|
289
|
+
|
|
290
|
+
# Render as markdown
|
|
291
|
+
path(
|
|
292
|
+
"spec-documents/render/",
|
|
293
|
+
views.SpecDocumentRenderView.as_view(),
|
|
294
|
+
name="spec_document_render",
|
|
295
|
+
),
|
|
245
296
|
]
|
django_agent_studio/api/views.py
CHANGED
|
@@ -1546,3 +1546,382 @@ class AgentSystemDiscoverView(APIView):
|
|
|
1546
1546
|
],
|
|
1547
1547
|
'total_count': len(agents),
|
|
1548
1548
|
})
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
# =============================================================================
|
|
1552
|
+
# Spec Document Views
|
|
1553
|
+
# =============================================================================
|
|
1554
|
+
|
|
1555
|
+
class SpecDocumentListCreateView(APIView):
|
|
1556
|
+
"""List all spec documents or create a new one."""
|
|
1557
|
+
permission_classes = [IsAuthenticated]
|
|
1558
|
+
|
|
1559
|
+
def get(self, request):
|
|
1560
|
+
from django_agent_runtime.models import SpecDocument
|
|
1561
|
+
|
|
1562
|
+
root_only = request.query_params.get('root_only', 'false').lower() == 'true'
|
|
1563
|
+
linked_only = request.query_params.get('linked_only', 'false').lower() == 'true'
|
|
1564
|
+
|
|
1565
|
+
if root_only:
|
|
1566
|
+
docs = SpecDocument.objects.filter(parent__isnull=True).select_related('linked_agent').order_by('order', 'title')
|
|
1567
|
+
else:
|
|
1568
|
+
docs = SpecDocument.objects.all().select_related('linked_agent', 'parent').order_by('parent_id', 'order', 'title')
|
|
1569
|
+
|
|
1570
|
+
if linked_only:
|
|
1571
|
+
docs = docs.filter(linked_agent__isnull=False)
|
|
1572
|
+
|
|
1573
|
+
result = []
|
|
1574
|
+
for doc in docs:
|
|
1575
|
+
item = {
|
|
1576
|
+
'id': str(doc.id),
|
|
1577
|
+
'title': doc.title,
|
|
1578
|
+
'parent_id': str(doc.parent_id) if doc.parent_id else None,
|
|
1579
|
+
'order': doc.order,
|
|
1580
|
+
'current_version': doc.current_version,
|
|
1581
|
+
'has_content': bool(doc.content),
|
|
1582
|
+
'content_preview': doc.content[:100] + '...' if len(doc.content) > 100 else doc.content,
|
|
1583
|
+
'created_at': doc.created_at.isoformat(),
|
|
1584
|
+
'updated_at': doc.updated_at.isoformat(),
|
|
1585
|
+
}
|
|
1586
|
+
if doc.linked_agent:
|
|
1587
|
+
item['linked_agent'] = {
|
|
1588
|
+
'id': str(doc.linked_agent.id),
|
|
1589
|
+
'slug': doc.linked_agent.slug,
|
|
1590
|
+
'name': doc.linked_agent.name,
|
|
1591
|
+
}
|
|
1592
|
+
result.append(item)
|
|
1593
|
+
|
|
1594
|
+
return Response({'documents': result, 'count': len(result)})
|
|
1595
|
+
|
|
1596
|
+
def post(self, request):
|
|
1597
|
+
from django_agent_runtime.models import SpecDocument
|
|
1598
|
+
|
|
1599
|
+
title = request.data.get('title')
|
|
1600
|
+
if not title:
|
|
1601
|
+
return Response({'error': 'title is required'}, status=status.HTTP_400_BAD_REQUEST)
|
|
1602
|
+
|
|
1603
|
+
content = request.data.get('content', '')
|
|
1604
|
+
parent_id = request.data.get('parent_id')
|
|
1605
|
+
linked_agent_id = request.data.get('linked_agent_id')
|
|
1606
|
+
order = request.data.get('order', 0)
|
|
1607
|
+
|
|
1608
|
+
parent = None
|
|
1609
|
+
if parent_id:
|
|
1610
|
+
try:
|
|
1611
|
+
parent = SpecDocument.objects.get(id=parent_id)
|
|
1612
|
+
except SpecDocument.DoesNotExist:
|
|
1613
|
+
return Response({'error': f'Parent document not found: {parent_id}'}, status=status.HTTP_404_NOT_FOUND)
|
|
1614
|
+
|
|
1615
|
+
linked_agent = None
|
|
1616
|
+
if linked_agent_id:
|
|
1617
|
+
try:
|
|
1618
|
+
linked_agent = AgentDefinition.objects.get(id=linked_agent_id)
|
|
1619
|
+
except AgentDefinition.DoesNotExist:
|
|
1620
|
+
return Response({'error': f'Agent not found: {linked_agent_id}'}, status=status.HTTP_404_NOT_FOUND)
|
|
1621
|
+
|
|
1622
|
+
doc = SpecDocument.objects.create(
|
|
1623
|
+
title=title,
|
|
1624
|
+
content=content,
|
|
1625
|
+
parent=parent,
|
|
1626
|
+
linked_agent=linked_agent,
|
|
1627
|
+
order=order,
|
|
1628
|
+
owner=request.user,
|
|
1629
|
+
)
|
|
1630
|
+
|
|
1631
|
+
return Response({
|
|
1632
|
+
'id': str(doc.id),
|
|
1633
|
+
'title': doc.title,
|
|
1634
|
+
'current_version': doc.current_version,
|
|
1635
|
+
'message': f'Created spec document: {title}',
|
|
1636
|
+
}, status=status.HTTP_201_CREATED)
|
|
1637
|
+
|
|
1638
|
+
|
|
1639
|
+
class SpecDocumentDetailView(APIView):
|
|
1640
|
+
"""Get, update, or delete a spec document."""
|
|
1641
|
+
permission_classes = [IsAuthenticated]
|
|
1642
|
+
|
|
1643
|
+
def get(self, request, pk):
|
|
1644
|
+
from django_agent_runtime.models import SpecDocument
|
|
1645
|
+
|
|
1646
|
+
try:
|
|
1647
|
+
doc = SpecDocument.objects.select_related('linked_agent', 'parent').get(id=pk)
|
|
1648
|
+
except SpecDocument.DoesNotExist:
|
|
1649
|
+
return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
1650
|
+
|
|
1651
|
+
include_children = request.query_params.get('include_children', 'false').lower() == 'true'
|
|
1652
|
+
render_markdown = request.query_params.get('render_markdown', 'false').lower() == 'true'
|
|
1653
|
+
|
|
1654
|
+
result = {
|
|
1655
|
+
'id': str(doc.id),
|
|
1656
|
+
'title': doc.title,
|
|
1657
|
+
'content': doc.content,
|
|
1658
|
+
'parent_id': str(doc.parent_id) if doc.parent_id else None,
|
|
1659
|
+
'order': doc.order,
|
|
1660
|
+
'current_version': doc.current_version,
|
|
1661
|
+
'full_path': doc.get_full_path(),
|
|
1662
|
+
'created_at': doc.created_at.isoformat(),
|
|
1663
|
+
'updated_at': doc.updated_at.isoformat(),
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
if doc.linked_agent:
|
|
1667
|
+
result['linked_agent'] = {
|
|
1668
|
+
'id': str(doc.linked_agent.id),
|
|
1669
|
+
'slug': doc.linked_agent.slug,
|
|
1670
|
+
'name': doc.linked_agent.name,
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
if include_children:
|
|
1674
|
+
descendants = doc.get_descendants()
|
|
1675
|
+
result['children'] = [
|
|
1676
|
+
{
|
|
1677
|
+
'id': str(d.id),
|
|
1678
|
+
'title': d.title,
|
|
1679
|
+
'parent_id': str(d.parent_id) if d.parent_id else None,
|
|
1680
|
+
'has_content': bool(d.content),
|
|
1681
|
+
'linked_agent_slug': d.linked_agent.slug if d.linked_agent else None,
|
|
1682
|
+
}
|
|
1683
|
+
for d in descendants
|
|
1684
|
+
]
|
|
1685
|
+
|
|
1686
|
+
if render_markdown:
|
|
1687
|
+
result['rendered_markdown'] = doc.render_tree_as_markdown()
|
|
1688
|
+
|
|
1689
|
+
return Response(result)
|
|
1690
|
+
|
|
1691
|
+
def put(self, request, pk):
|
|
1692
|
+
from django_agent_runtime.models import SpecDocument
|
|
1693
|
+
|
|
1694
|
+
try:
|
|
1695
|
+
doc = SpecDocument.objects.get(id=pk)
|
|
1696
|
+
except SpecDocument.DoesNotExist:
|
|
1697
|
+
return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
1698
|
+
|
|
1699
|
+
changes = []
|
|
1700
|
+
if 'title' in request.data:
|
|
1701
|
+
doc.title = request.data['title']
|
|
1702
|
+
changes.append('title')
|
|
1703
|
+
if 'content' in request.data:
|
|
1704
|
+
doc.content = request.data['content']
|
|
1705
|
+
changes.append('content')
|
|
1706
|
+
if 'order' in request.data:
|
|
1707
|
+
doc.order = request.data['order']
|
|
1708
|
+
changes.append('order')
|
|
1709
|
+
if 'parent_id' in request.data:
|
|
1710
|
+
parent_id = request.data['parent_id']
|
|
1711
|
+
if parent_id:
|
|
1712
|
+
try:
|
|
1713
|
+
doc.parent = SpecDocument.objects.get(id=parent_id)
|
|
1714
|
+
except SpecDocument.DoesNotExist:
|
|
1715
|
+
return Response({'error': f'Parent not found: {parent_id}'}, status=status.HTTP_404_NOT_FOUND)
|
|
1716
|
+
else:
|
|
1717
|
+
doc.parent = None
|
|
1718
|
+
changes.append('parent')
|
|
1719
|
+
|
|
1720
|
+
if changes:
|
|
1721
|
+
doc.save()
|
|
1722
|
+
return Response({
|
|
1723
|
+
'id': str(doc.id),
|
|
1724
|
+
'title': doc.title,
|
|
1725
|
+
'current_version': doc.current_version,
|
|
1726
|
+
'changes': changes,
|
|
1727
|
+
'message': f'Updated: {", ".join(changes)}',
|
|
1728
|
+
})
|
|
1729
|
+
else:
|
|
1730
|
+
return Response({'message': 'No changes specified'})
|
|
1731
|
+
|
|
1732
|
+
def delete(self, request, pk):
|
|
1733
|
+
from django_agent_runtime.models import SpecDocument
|
|
1734
|
+
|
|
1735
|
+
try:
|
|
1736
|
+
doc = SpecDocument.objects.get(id=pk)
|
|
1737
|
+
except SpecDocument.DoesNotExist:
|
|
1738
|
+
return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
1739
|
+
|
|
1740
|
+
title = doc.title
|
|
1741
|
+
children_count = doc.children.count()
|
|
1742
|
+
doc.delete()
|
|
1743
|
+
|
|
1744
|
+
return Response({
|
|
1745
|
+
'message': f'Deleted document: {title}',
|
|
1746
|
+
'deleted_children': children_count,
|
|
1747
|
+
})
|
|
1748
|
+
|
|
1749
|
+
|
|
1750
|
+
class SpecDocumentLinkView(APIView):
|
|
1751
|
+
"""Link or unlink a spec document to/from an agent."""
|
|
1752
|
+
permission_classes = [IsAuthenticated]
|
|
1753
|
+
|
|
1754
|
+
def post(self, request, pk):
|
|
1755
|
+
from django_agent_runtime.models import SpecDocument
|
|
1756
|
+
|
|
1757
|
+
try:
|
|
1758
|
+
doc = SpecDocument.objects.get(id=pk)
|
|
1759
|
+
except SpecDocument.DoesNotExist:
|
|
1760
|
+
return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
1761
|
+
|
|
1762
|
+
agent_id = request.data.get('agent_id')
|
|
1763
|
+
if not agent_id:
|
|
1764
|
+
return Response({'error': 'agent_id is required'}, status=status.HTTP_400_BAD_REQUEST)
|
|
1765
|
+
|
|
1766
|
+
try:
|
|
1767
|
+
agent = AgentDefinition.objects.get(id=agent_id)
|
|
1768
|
+
except AgentDefinition.DoesNotExist:
|
|
1769
|
+
return Response({'error': f'Agent not found: {agent_id}'}, status=status.HTTP_404_NOT_FOUND)
|
|
1770
|
+
|
|
1771
|
+
doc.linked_agent = agent
|
|
1772
|
+
doc.save()
|
|
1773
|
+
|
|
1774
|
+
return Response({
|
|
1775
|
+
'message': f"Linked '{doc.title}' to agent '{agent.name}'",
|
|
1776
|
+
'document_id': str(doc.id),
|
|
1777
|
+
'agent_id': str(agent.id),
|
|
1778
|
+
'agent_slug': agent.slug,
|
|
1779
|
+
})
|
|
1780
|
+
|
|
1781
|
+
def delete(self, request, pk):
|
|
1782
|
+
from django_agent_runtime.models import SpecDocument
|
|
1783
|
+
|
|
1784
|
+
try:
|
|
1785
|
+
doc = SpecDocument.objects.select_related('linked_agent').get(id=pk)
|
|
1786
|
+
except SpecDocument.DoesNotExist:
|
|
1787
|
+
return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
1788
|
+
|
|
1789
|
+
if not doc.linked_agent:
|
|
1790
|
+
return Response({'message': 'Document is not linked to any agent'})
|
|
1791
|
+
|
|
1792
|
+
old_agent_name = doc.linked_agent.name
|
|
1793
|
+
doc.linked_agent = None
|
|
1794
|
+
doc.save()
|
|
1795
|
+
|
|
1796
|
+
return Response({
|
|
1797
|
+
'message': f"Unlinked '{doc.title}' from agent '{old_agent_name}'",
|
|
1798
|
+
'document_id': str(doc.id),
|
|
1799
|
+
})
|
|
1800
|
+
|
|
1801
|
+
|
|
1802
|
+
class SpecDocumentHistoryView(APIView):
|
|
1803
|
+
"""Get version history of a spec document."""
|
|
1804
|
+
permission_classes = [IsAuthenticated]
|
|
1805
|
+
|
|
1806
|
+
def get(self, request, pk):
|
|
1807
|
+
from django_agent_runtime.models import SpecDocument
|
|
1808
|
+
|
|
1809
|
+
try:
|
|
1810
|
+
doc = SpecDocument.objects.get(id=pk)
|
|
1811
|
+
except SpecDocument.DoesNotExist:
|
|
1812
|
+
return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
1813
|
+
|
|
1814
|
+
limit = int(request.query_params.get('limit', 20))
|
|
1815
|
+
versions = doc.versions.all()[:limit]
|
|
1816
|
+
|
|
1817
|
+
return Response({
|
|
1818
|
+
'document_id': str(doc.id),
|
|
1819
|
+
'document_title': doc.title,
|
|
1820
|
+
'current_version': doc.current_version,
|
|
1821
|
+
'versions': [
|
|
1822
|
+
{
|
|
1823
|
+
'version_number': v.version_number,
|
|
1824
|
+
'title': v.title,
|
|
1825
|
+
'content_preview': v.content[:200] + '...' if len(v.content) > 200 else v.content,
|
|
1826
|
+
'created_at': v.created_at.isoformat(),
|
|
1827
|
+
'change_summary': v.change_summary or '',
|
|
1828
|
+
}
|
|
1829
|
+
for v in versions
|
|
1830
|
+
],
|
|
1831
|
+
})
|
|
1832
|
+
|
|
1833
|
+
|
|
1834
|
+
class SpecDocumentRestoreView(APIView):
|
|
1835
|
+
"""Restore a spec document to a previous version."""
|
|
1836
|
+
permission_classes = [IsAuthenticated]
|
|
1837
|
+
|
|
1838
|
+
def post(self, request, pk):
|
|
1839
|
+
from django_agent_runtime.models import SpecDocument, SpecDocumentVersion
|
|
1840
|
+
|
|
1841
|
+
try:
|
|
1842
|
+
doc = SpecDocument.objects.get(id=pk)
|
|
1843
|
+
except SpecDocument.DoesNotExist:
|
|
1844
|
+
return Response({'error': 'Document not found'}, status=status.HTTP_404_NOT_FOUND)
|
|
1845
|
+
|
|
1846
|
+
version_number = request.data.get('version_number')
|
|
1847
|
+
if version_number is None:
|
|
1848
|
+
return Response({'error': 'version_number is required'}, status=status.HTTP_400_BAD_REQUEST)
|
|
1849
|
+
|
|
1850
|
+
try:
|
|
1851
|
+
version = doc.versions.get(version_number=version_number)
|
|
1852
|
+
except SpecDocumentVersion.DoesNotExist:
|
|
1853
|
+
return Response({'error': f'Version not found: {version_number}'}, status=status.HTTP_404_NOT_FOUND)
|
|
1854
|
+
|
|
1855
|
+
version.restore()
|
|
1856
|
+
doc.refresh_from_db()
|
|
1857
|
+
|
|
1858
|
+
return Response({
|
|
1859
|
+
'message': f'Restored document to version {version_number}',
|
|
1860
|
+
'document_id': str(doc.id),
|
|
1861
|
+
'restored_from_version': version_number,
|
|
1862
|
+
'new_version': doc.current_version,
|
|
1863
|
+
})
|
|
1864
|
+
|
|
1865
|
+
|
|
1866
|
+
class SpecDocumentTreeView(APIView):
|
|
1867
|
+
"""Get the full document tree structure."""
|
|
1868
|
+
permission_classes = [IsAuthenticated]
|
|
1869
|
+
|
|
1870
|
+
def get(self, request):
|
|
1871
|
+
from django_agent_runtime.models import SpecDocument
|
|
1872
|
+
|
|
1873
|
+
def build_tree(parent_id=None):
|
|
1874
|
+
if parent_id:
|
|
1875
|
+
docs = SpecDocument.objects.filter(parent_id=parent_id).select_related('linked_agent').order_by('order', 'title')
|
|
1876
|
+
else:
|
|
1877
|
+
docs = SpecDocument.objects.filter(parent__isnull=True).select_related('linked_agent').order_by('order', 'title')
|
|
1878
|
+
|
|
1879
|
+
result = []
|
|
1880
|
+
for doc in docs:
|
|
1881
|
+
node = {
|
|
1882
|
+
'id': str(doc.id),
|
|
1883
|
+
'title': doc.title,
|
|
1884
|
+
'has_content': bool(doc.content),
|
|
1885
|
+
'order': doc.order,
|
|
1886
|
+
'linked_agent': {
|
|
1887
|
+
'id': str(doc.linked_agent.id),
|
|
1888
|
+
'slug': doc.linked_agent.slug,
|
|
1889
|
+
'name': doc.linked_agent.name,
|
|
1890
|
+
} if doc.linked_agent else None,
|
|
1891
|
+
'children': build_tree(doc.id),
|
|
1892
|
+
}
|
|
1893
|
+
result.append(node)
|
|
1894
|
+
return result
|
|
1895
|
+
|
|
1896
|
+
tree = build_tree()
|
|
1897
|
+
return Response({'tree': tree})
|
|
1898
|
+
|
|
1899
|
+
|
|
1900
|
+
class SpecDocumentRenderView(APIView):
|
|
1901
|
+
"""Render all spec documents as a single markdown document."""
|
|
1902
|
+
permission_classes = [IsAuthenticated]
|
|
1903
|
+
|
|
1904
|
+
def get(self, request):
|
|
1905
|
+
from django_agent_runtime.models import SpecDocument
|
|
1906
|
+
|
|
1907
|
+
root_id = request.query_params.get('root_id')
|
|
1908
|
+
|
|
1909
|
+
if root_id:
|
|
1910
|
+
try:
|
|
1911
|
+
doc = SpecDocument.objects.get(id=root_id)
|
|
1912
|
+
markdown = doc.render_tree_as_markdown()
|
|
1913
|
+
return Response({
|
|
1914
|
+
'markdown': markdown,
|
|
1915
|
+
'root_document': doc.title,
|
|
1916
|
+
})
|
|
1917
|
+
except SpecDocument.DoesNotExist:
|
|
1918
|
+
return Response({'error': f'Document not found: {root_id}'}, status=status.HTTP_404_NOT_FOUND)
|
|
1919
|
+
else:
|
|
1920
|
+
roots = SpecDocument.objects.filter(parent__isnull=True).order_by('order', 'title')
|
|
1921
|
+
parts = [root.render_tree_as_markdown() for root in roots]
|
|
1922
|
+
|
|
1923
|
+
return Response({
|
|
1924
|
+
'markdown': '\n\n---\n\n'.join(parts),
|
|
1925
|
+
'root_count': len(parts),
|
|
1926
|
+
'roots': [{'id': str(r.id), 'title': r.title} for r in roots],
|
|
1927
|
+
})
|