django-agent-studio 0.1.6__py3-none-any.whl → 0.1.7__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 +672 -3
- {django_agent_studio-0.1.6.dist-info → django_agent_studio-0.1.7.dist-info}/METADATA +1 -1
- {django_agent_studio-0.1.6.dist-info → django_agent_studio-0.1.7.dist-info}/RECORD +5 -5
- {django_agent_studio-0.1.6.dist-info → django_agent_studio-0.1.7.dist-info}/WHEEL +0 -0
- {django_agent_studio-0.1.6.dist-info → django_agent_studio-0.1.7.dist-info}/top_level.txt +0 -0
|
@@ -133,12 +133,43 @@ Every agent can have a **spec** - a human-readable description of its intended b
|
|
|
133
133
|
- Tone/personality: How should it communicate?
|
|
134
134
|
- Edge cases: How should it handle unusual situations?
|
|
135
135
|
|
|
136
|
-
**
|
|
137
|
-
- `get_agent_spec` - View the current spec
|
|
138
|
-
- `update_agent_spec` - Update the spec
|
|
136
|
+
**Simple spec tools (per-agent):**
|
|
137
|
+
- `get_agent_spec` - View the current agent's spec
|
|
138
|
+
- `update_agent_spec` - Update the current agent's spec
|
|
139
139
|
|
|
140
140
|
**Best practice:** When building an agent, start by writing or reviewing the spec, then craft the system prompt to implement that spec. This ensures alignment between intended and actual behavior.
|
|
141
141
|
|
|
142
|
+
## Spec Document System (Advanced)
|
|
143
|
+
|
|
144
|
+
For organizations managing multiple agents, there's a **Spec Document System** that provides:
|
|
145
|
+
- **Document tree structure**: Organize specs hierarchically (e.g., "Company Agents" → "Support" → "Billing Agent")
|
|
146
|
+
- **Agent linking**: Link any document to an agent - changes sync automatically to the agent's spec
|
|
147
|
+
- **Version history**: Every change creates a new version with full history and rollback
|
|
148
|
+
- **Unified view**: Render the entire document tree as a single markdown document for human review
|
|
149
|
+
|
|
150
|
+
**Spec Document Tools:**
|
|
151
|
+
- `list_spec_documents` - List all spec documents
|
|
152
|
+
- `get_spec_document` - Get a document with its content
|
|
153
|
+
- `create_spec_document` - Create a new document (root or child)
|
|
154
|
+
- `update_spec_document` - Update content (auto-versions)
|
|
155
|
+
- `link_spec_to_agent` - Link a document to an agent
|
|
156
|
+
- `unlink_spec_from_agent` - Remove the link
|
|
157
|
+
- `get_spec_document_history` - View version history
|
|
158
|
+
- `restore_spec_document_version` - Rollback to a previous version
|
|
159
|
+
- `delete_spec_document` - Delete a document (and children)
|
|
160
|
+
- `render_full_spec` - Render all documents as one markdown file
|
|
161
|
+
|
|
162
|
+
**Example structure:**
|
|
163
|
+
```
|
|
164
|
+
Company AI Agents (root document)
|
|
165
|
+
├── Customer Support
|
|
166
|
+
│ ├── Billing Support → linked to billing-agent
|
|
167
|
+
│ └── Technical Support → linked to tech-agent
|
|
168
|
+
├── Internal Tools
|
|
169
|
+
│ └── HR Assistant → linked to hr-agent
|
|
170
|
+
└── Guidelines (shared context for all agents)
|
|
171
|
+
```
|
|
172
|
+
|
|
142
173
|
When helping users:
|
|
143
174
|
- Ask clarifying questions to understand what they want their agent to do
|
|
144
175
|
- Suggest appropriate system prompts based on the agent's purpose
|
|
@@ -331,6 +362,231 @@ BUILDER_TOOLS = [
|
|
|
331
362
|
},
|
|
332
363
|
},
|
|
333
364
|
},
|
|
365
|
+
# Spec Document tools
|
|
366
|
+
{
|
|
367
|
+
"type": "function",
|
|
368
|
+
"function": {
|
|
369
|
+
"name": "list_spec_documents",
|
|
370
|
+
"description": "List all spec documents, optionally filtered. Returns the document tree structure.",
|
|
371
|
+
"parameters": {
|
|
372
|
+
"type": "object",
|
|
373
|
+
"properties": {
|
|
374
|
+
"root_only": {
|
|
375
|
+
"type": "boolean",
|
|
376
|
+
"description": "If true, only return root documents (no parent). Default: false",
|
|
377
|
+
},
|
|
378
|
+
"linked_only": {
|
|
379
|
+
"type": "boolean",
|
|
380
|
+
"description": "If true, only return documents linked to agents. Default: false",
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
"type": "function",
|
|
388
|
+
"function": {
|
|
389
|
+
"name": "get_spec_document",
|
|
390
|
+
"description": "Get a spec document by ID, including its content and metadata.",
|
|
391
|
+
"parameters": {
|
|
392
|
+
"type": "object",
|
|
393
|
+
"properties": {
|
|
394
|
+
"document_id": {
|
|
395
|
+
"type": "string",
|
|
396
|
+
"description": "The UUID of the document to retrieve",
|
|
397
|
+
},
|
|
398
|
+
"include_children": {
|
|
399
|
+
"type": "boolean",
|
|
400
|
+
"description": "If true, include all descendant documents. Default: false",
|
|
401
|
+
},
|
|
402
|
+
"render_as_markdown": {
|
|
403
|
+
"type": "boolean",
|
|
404
|
+
"description": "If true, render the document tree as a single markdown document. Default: false",
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
"required": ["document_id"],
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
"type": "function",
|
|
413
|
+
"function": {
|
|
414
|
+
"name": "create_spec_document",
|
|
415
|
+
"description": "Create a new spec document. Can be a root document or a child of an existing document.",
|
|
416
|
+
"parameters": {
|
|
417
|
+
"type": "object",
|
|
418
|
+
"properties": {
|
|
419
|
+
"title": {
|
|
420
|
+
"type": "string",
|
|
421
|
+
"description": "Document title",
|
|
422
|
+
},
|
|
423
|
+
"content": {
|
|
424
|
+
"type": "string",
|
|
425
|
+
"description": "Markdown content of the document",
|
|
426
|
+
},
|
|
427
|
+
"parent_id": {
|
|
428
|
+
"type": "string",
|
|
429
|
+
"description": "Optional: UUID of parent document. If not provided, creates a root document.",
|
|
430
|
+
},
|
|
431
|
+
"linked_agent_id": {
|
|
432
|
+
"type": "string",
|
|
433
|
+
"description": "Optional: UUID of agent to link this document to. The document content will sync to the agent's spec.",
|
|
434
|
+
},
|
|
435
|
+
"order": {
|
|
436
|
+
"type": "integer",
|
|
437
|
+
"description": "Optional: Order among siblings. Default: 0",
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
"required": ["title"],
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"type": "function",
|
|
446
|
+
"function": {
|
|
447
|
+
"name": "update_spec_document",
|
|
448
|
+
"description": "Update a spec document's content or metadata. Creates a new version automatically.",
|
|
449
|
+
"parameters": {
|
|
450
|
+
"type": "object",
|
|
451
|
+
"properties": {
|
|
452
|
+
"document_id": {
|
|
453
|
+
"type": "string",
|
|
454
|
+
"description": "The UUID of the document to update",
|
|
455
|
+
},
|
|
456
|
+
"title": {
|
|
457
|
+
"type": "string",
|
|
458
|
+
"description": "New title (optional)",
|
|
459
|
+
},
|
|
460
|
+
"content": {
|
|
461
|
+
"type": "string",
|
|
462
|
+
"description": "New markdown content (optional)",
|
|
463
|
+
},
|
|
464
|
+
"order": {
|
|
465
|
+
"type": "integer",
|
|
466
|
+
"description": "New order among siblings (optional)",
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
"required": ["document_id"],
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"type": "function",
|
|
475
|
+
"function": {
|
|
476
|
+
"name": "link_spec_to_agent",
|
|
477
|
+
"description": "Link a spec document to an agent. The document's content will sync to the agent's spec field.",
|
|
478
|
+
"parameters": {
|
|
479
|
+
"type": "object",
|
|
480
|
+
"properties": {
|
|
481
|
+
"document_id": {
|
|
482
|
+
"type": "string",
|
|
483
|
+
"description": "The UUID of the document",
|
|
484
|
+
},
|
|
485
|
+
"agent_id": {
|
|
486
|
+
"type": "string",
|
|
487
|
+
"description": "The UUID of the agent to link to. Use 'current' for the agent being edited.",
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
"required": ["document_id", "agent_id"],
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
"type": "function",
|
|
496
|
+
"function": {
|
|
497
|
+
"name": "unlink_spec_from_agent",
|
|
498
|
+
"description": "Remove the link between a spec document and its agent.",
|
|
499
|
+
"parameters": {
|
|
500
|
+
"type": "object",
|
|
501
|
+
"properties": {
|
|
502
|
+
"document_id": {
|
|
503
|
+
"type": "string",
|
|
504
|
+
"description": "The UUID of the document to unlink",
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
"required": ["document_id"],
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
"type": "function",
|
|
513
|
+
"function": {
|
|
514
|
+
"name": "get_spec_document_history",
|
|
515
|
+
"description": "Get the version history of a spec document.",
|
|
516
|
+
"parameters": {
|
|
517
|
+
"type": "object",
|
|
518
|
+
"properties": {
|
|
519
|
+
"document_id": {
|
|
520
|
+
"type": "string",
|
|
521
|
+
"description": "The UUID of the document",
|
|
522
|
+
},
|
|
523
|
+
"limit": {
|
|
524
|
+
"type": "integer",
|
|
525
|
+
"description": "Maximum number of versions to return. Default: 10",
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
"required": ["document_id"],
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
"type": "function",
|
|
534
|
+
"function": {
|
|
535
|
+
"name": "restore_spec_document_version",
|
|
536
|
+
"description": "Restore a spec document to a previous version. Creates a new version with the restored content.",
|
|
537
|
+
"parameters": {
|
|
538
|
+
"type": "object",
|
|
539
|
+
"properties": {
|
|
540
|
+
"document_id": {
|
|
541
|
+
"type": "string",
|
|
542
|
+
"description": "The UUID of the document",
|
|
543
|
+
},
|
|
544
|
+
"version_number": {
|
|
545
|
+
"type": "integer",
|
|
546
|
+
"description": "The version number to restore to",
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
"required": ["document_id", "version_number"],
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
"type": "function",
|
|
555
|
+
"function": {
|
|
556
|
+
"name": "delete_spec_document",
|
|
557
|
+
"description": "Delete a spec document. If it has children, they will also be deleted.",
|
|
558
|
+
"parameters": {
|
|
559
|
+
"type": "object",
|
|
560
|
+
"properties": {
|
|
561
|
+
"document_id": {
|
|
562
|
+
"type": "string",
|
|
563
|
+
"description": "The UUID of the document to delete",
|
|
564
|
+
},
|
|
565
|
+
"confirm": {
|
|
566
|
+
"type": "boolean",
|
|
567
|
+
"description": "Must be true to confirm deletion",
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
"required": ["document_id", "confirm"],
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
"type": "function",
|
|
576
|
+
"function": {
|
|
577
|
+
"name": "render_full_spec",
|
|
578
|
+
"description": "Render all spec documents (or a subtree) as a single unified markdown document for human review.",
|
|
579
|
+
"parameters": {
|
|
580
|
+
"type": "object",
|
|
581
|
+
"properties": {
|
|
582
|
+
"root_document_id": {
|
|
583
|
+
"type": "string",
|
|
584
|
+
"description": "Optional: Start from this document. If not provided, renders all root documents.",
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
},
|
|
589
|
+
},
|
|
334
590
|
{
|
|
335
591
|
"type": "function",
|
|
336
592
|
"function": {
|
|
@@ -1052,6 +1308,37 @@ Knowledge: {len(config.get('knowledge', []))} sources
|
|
|
1052
1308
|
}
|
|
1053
1309
|
return {"error": "No active version found"}
|
|
1054
1310
|
|
|
1311
|
+
# Spec Document tools
|
|
1312
|
+
elif tool_name == "list_spec_documents":
|
|
1313
|
+
return await self._list_spec_documents(agent, args, ctx)
|
|
1314
|
+
|
|
1315
|
+
elif tool_name == "get_spec_document":
|
|
1316
|
+
return await self._get_spec_document(agent, args, ctx)
|
|
1317
|
+
|
|
1318
|
+
elif tool_name == "create_spec_document":
|
|
1319
|
+
return await self._create_spec_document(agent, args, ctx)
|
|
1320
|
+
|
|
1321
|
+
elif tool_name == "update_spec_document":
|
|
1322
|
+
return await self._update_spec_document(agent, args, ctx)
|
|
1323
|
+
|
|
1324
|
+
elif tool_name == "link_spec_to_agent":
|
|
1325
|
+
return await self._link_spec_to_agent(agent, args, ctx)
|
|
1326
|
+
|
|
1327
|
+
elif tool_name == "unlink_spec_from_agent":
|
|
1328
|
+
return await self._unlink_spec_from_agent(agent, args, ctx)
|
|
1329
|
+
|
|
1330
|
+
elif tool_name == "get_spec_document_history":
|
|
1331
|
+
return await self._get_spec_document_history(agent, args, ctx)
|
|
1332
|
+
|
|
1333
|
+
elif tool_name == "restore_spec_document_version":
|
|
1334
|
+
return await self._restore_spec_document_version(agent, args, ctx)
|
|
1335
|
+
|
|
1336
|
+
elif tool_name == "delete_spec_document":
|
|
1337
|
+
return await self._delete_spec_document(agent, args, ctx)
|
|
1338
|
+
|
|
1339
|
+
elif tool_name == "render_full_spec":
|
|
1340
|
+
return await self._render_full_spec(agent, args, ctx)
|
|
1341
|
+
|
|
1055
1342
|
elif tool_name == "add_knowledge":
|
|
1056
1343
|
inclusion_mode = args.get("inclusion_mode", "always")
|
|
1057
1344
|
knowledge = await sync_to_async(AgentKnowledge.objects.create)(
|
|
@@ -2060,3 +2347,385 @@ Knowledge: {len(config.get('knowledge', []))} sources
|
|
|
2060
2347
|
except Exception as e:
|
|
2061
2348
|
logger.exception("Error getting system details")
|
|
2062
2349
|
return {"error": str(e)}
|
|
2350
|
+
|
|
2351
|
+
# ==================== Spec Document Methods ====================
|
|
2352
|
+
|
|
2353
|
+
async def _list_spec_documents(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
2354
|
+
"""List spec documents with optional filtering."""
|
|
2355
|
+
from django_agent_runtime.models import SpecDocument
|
|
2356
|
+
|
|
2357
|
+
try:
|
|
2358
|
+
root_only = args.get("root_only", False)
|
|
2359
|
+
linked_only = args.get("linked_only", False)
|
|
2360
|
+
|
|
2361
|
+
if root_only:
|
|
2362
|
+
docs = await sync_to_async(list)(
|
|
2363
|
+
SpecDocument.objects.filter(parent__isnull=True)
|
|
2364
|
+
.select_related("linked_agent")
|
|
2365
|
+
.order_by("order", "title")
|
|
2366
|
+
)
|
|
2367
|
+
else:
|
|
2368
|
+
docs = await sync_to_async(list)(
|
|
2369
|
+
SpecDocument.objects.all()
|
|
2370
|
+
.select_related("linked_agent", "parent")
|
|
2371
|
+
.order_by("parent_id", "order", "title")
|
|
2372
|
+
)
|
|
2373
|
+
|
|
2374
|
+
if linked_only:
|
|
2375
|
+
docs = [d for d in docs if d.linked_agent_id]
|
|
2376
|
+
|
|
2377
|
+
def format_doc(doc):
|
|
2378
|
+
result = {
|
|
2379
|
+
"id": str(doc.id),
|
|
2380
|
+
"title": doc.title,
|
|
2381
|
+
"parent_id": str(doc.parent_id) if doc.parent_id else None,
|
|
2382
|
+
"order": doc.order,
|
|
2383
|
+
"current_version": doc.current_version,
|
|
2384
|
+
"has_content": bool(doc.content),
|
|
2385
|
+
"content_preview": doc.content[:100] + "..." if len(doc.content) > 100 else doc.content,
|
|
2386
|
+
}
|
|
2387
|
+
if doc.linked_agent:
|
|
2388
|
+
result["linked_agent"] = {
|
|
2389
|
+
"id": str(doc.linked_agent.id),
|
|
2390
|
+
"slug": doc.linked_agent.slug,
|
|
2391
|
+
"name": doc.linked_agent.name,
|
|
2392
|
+
}
|
|
2393
|
+
return result
|
|
2394
|
+
|
|
2395
|
+
return {
|
|
2396
|
+
"documents": [format_doc(d) for d in docs],
|
|
2397
|
+
"count": len(docs),
|
|
2398
|
+
}
|
|
2399
|
+
except Exception as e:
|
|
2400
|
+
logger.exception("Error listing spec documents")
|
|
2401
|
+
return {"error": str(e)}
|
|
2402
|
+
|
|
2403
|
+
async def _get_spec_document(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
2404
|
+
"""Get a spec document by ID."""
|
|
2405
|
+
from django_agent_runtime.models import SpecDocument
|
|
2406
|
+
|
|
2407
|
+
try:
|
|
2408
|
+
doc_id = args["document_id"]
|
|
2409
|
+
include_children = args.get("include_children", False)
|
|
2410
|
+
render_markdown = args.get("render_as_markdown", False)
|
|
2411
|
+
|
|
2412
|
+
doc = await sync_to_async(
|
|
2413
|
+
SpecDocument.objects.select_related("linked_agent", "parent").get
|
|
2414
|
+
)(id=doc_id)
|
|
2415
|
+
|
|
2416
|
+
result = {
|
|
2417
|
+
"id": str(doc.id),
|
|
2418
|
+
"title": doc.title,
|
|
2419
|
+
"content": doc.content,
|
|
2420
|
+
"parent_id": str(doc.parent_id) if doc.parent_id else None,
|
|
2421
|
+
"order": doc.order,
|
|
2422
|
+
"current_version": doc.current_version,
|
|
2423
|
+
"full_path": await sync_to_async(doc.get_full_path)(),
|
|
2424
|
+
"created_at": doc.created_at.isoformat(),
|
|
2425
|
+
"updated_at": doc.updated_at.isoformat(),
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
if doc.linked_agent:
|
|
2429
|
+
result["linked_agent"] = {
|
|
2430
|
+
"id": str(doc.linked_agent.id),
|
|
2431
|
+
"slug": doc.linked_agent.slug,
|
|
2432
|
+
"name": doc.linked_agent.name,
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
if include_children:
|
|
2436
|
+
descendants = await sync_to_async(doc.get_descendants)()
|
|
2437
|
+
result["children"] = [
|
|
2438
|
+
{
|
|
2439
|
+
"id": str(d.id),
|
|
2440
|
+
"title": d.title,
|
|
2441
|
+
"parent_id": str(d.parent_id) if d.parent_id else None,
|
|
2442
|
+
"has_content": bool(d.content),
|
|
2443
|
+
"linked_agent_slug": d.linked_agent.slug if d.linked_agent else None,
|
|
2444
|
+
}
|
|
2445
|
+
for d in descendants
|
|
2446
|
+
]
|
|
2447
|
+
|
|
2448
|
+
if render_markdown:
|
|
2449
|
+
result["rendered_markdown"] = await sync_to_async(doc.render_tree_as_markdown)()
|
|
2450
|
+
|
|
2451
|
+
return result
|
|
2452
|
+
except SpecDocument.DoesNotExist:
|
|
2453
|
+
return {"error": f"Document not found: {args.get('document_id')}"}
|
|
2454
|
+
except Exception as e:
|
|
2455
|
+
logger.exception("Error getting spec document")
|
|
2456
|
+
return {"error": str(e)}
|
|
2457
|
+
|
|
2458
|
+
async def _create_spec_document(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
2459
|
+
"""Create a new spec document."""
|
|
2460
|
+
from django_agent_runtime.models import SpecDocument, AgentDefinition
|
|
2461
|
+
|
|
2462
|
+
try:
|
|
2463
|
+
title = args["title"]
|
|
2464
|
+
content = args.get("content", "")
|
|
2465
|
+
parent_id = args.get("parent_id")
|
|
2466
|
+
linked_agent_id = args.get("linked_agent_id")
|
|
2467
|
+
order = args.get("order", 0)
|
|
2468
|
+
|
|
2469
|
+
# Get parent if specified
|
|
2470
|
+
parent = None
|
|
2471
|
+
if parent_id:
|
|
2472
|
+
parent = await sync_to_async(SpecDocument.objects.get)(id=parent_id)
|
|
2473
|
+
|
|
2474
|
+
# Get linked agent if specified
|
|
2475
|
+
linked_agent = None
|
|
2476
|
+
if linked_agent_id:
|
|
2477
|
+
linked_agent = await sync_to_async(AgentDefinition.objects.get)(id=linked_agent_id)
|
|
2478
|
+
|
|
2479
|
+
# Create document
|
|
2480
|
+
doc = await sync_to_async(SpecDocument.objects.create)(
|
|
2481
|
+
title=title,
|
|
2482
|
+
content=content,
|
|
2483
|
+
parent=parent,
|
|
2484
|
+
linked_agent=linked_agent,
|
|
2485
|
+
order=order,
|
|
2486
|
+
owner=ctx.user if ctx.user and ctx.user.is_authenticated else None,
|
|
2487
|
+
)
|
|
2488
|
+
|
|
2489
|
+
return {
|
|
2490
|
+
"success": True,
|
|
2491
|
+
"message": f"Created spec document: {title}",
|
|
2492
|
+
"document_id": str(doc.id),
|
|
2493
|
+
"version": doc.current_version,
|
|
2494
|
+
"linked_agent": linked_agent.slug if linked_agent else None,
|
|
2495
|
+
}
|
|
2496
|
+
except SpecDocument.DoesNotExist:
|
|
2497
|
+
return {"error": f"Parent document not found: {parent_id}"}
|
|
2498
|
+
except AgentDefinition.DoesNotExist:
|
|
2499
|
+
return {"error": f"Agent not found: {linked_agent_id}"}
|
|
2500
|
+
except Exception as e:
|
|
2501
|
+
logger.exception("Error creating spec document")
|
|
2502
|
+
return {"error": str(e)}
|
|
2503
|
+
|
|
2504
|
+
async def _update_spec_document(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
2505
|
+
"""Update a spec document."""
|
|
2506
|
+
from django_agent_runtime.models import SpecDocument
|
|
2507
|
+
|
|
2508
|
+
try:
|
|
2509
|
+
doc_id = args["document_id"]
|
|
2510
|
+
doc = await sync_to_async(SpecDocument.objects.get)(id=doc_id)
|
|
2511
|
+
|
|
2512
|
+
changes = []
|
|
2513
|
+
if "title" in args:
|
|
2514
|
+
doc.title = args["title"]
|
|
2515
|
+
changes.append("title")
|
|
2516
|
+
if "content" in args:
|
|
2517
|
+
doc.content = args["content"]
|
|
2518
|
+
changes.append("content")
|
|
2519
|
+
if "order" in args:
|
|
2520
|
+
doc.order = args["order"]
|
|
2521
|
+
changes.append("order")
|
|
2522
|
+
|
|
2523
|
+
if changes:
|
|
2524
|
+
await sync_to_async(doc.save)()
|
|
2525
|
+
return {
|
|
2526
|
+
"success": True,
|
|
2527
|
+
"message": f"Updated spec document: {', '.join(changes)}",
|
|
2528
|
+
"document_id": str(doc.id),
|
|
2529
|
+
"new_version": doc.current_version,
|
|
2530
|
+
}
|
|
2531
|
+
else:
|
|
2532
|
+
return {"message": "No changes specified"}
|
|
2533
|
+
except SpecDocument.DoesNotExist:
|
|
2534
|
+
return {"error": f"Document not found: {args.get('document_id')}"}
|
|
2535
|
+
except Exception as e:
|
|
2536
|
+
logger.exception("Error updating spec document")
|
|
2537
|
+
return {"error": str(e)}
|
|
2538
|
+
|
|
2539
|
+
async def _link_spec_to_agent(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
2540
|
+
"""Link a spec document to an agent."""
|
|
2541
|
+
from django_agent_runtime.models import SpecDocument, AgentDefinition
|
|
2542
|
+
|
|
2543
|
+
try:
|
|
2544
|
+
doc_id = args["document_id"]
|
|
2545
|
+
agent_id = args["agent_id"]
|
|
2546
|
+
|
|
2547
|
+
doc = await sync_to_async(SpecDocument.objects.get)(id=doc_id)
|
|
2548
|
+
|
|
2549
|
+
# Handle 'current' as the agent being edited
|
|
2550
|
+
if agent_id == "current":
|
|
2551
|
+
target_agent = agent
|
|
2552
|
+
else:
|
|
2553
|
+
target_agent = await sync_to_async(AgentDefinition.objects.get)(id=agent_id)
|
|
2554
|
+
|
|
2555
|
+
doc.linked_agent = target_agent
|
|
2556
|
+
await sync_to_async(doc.save)()
|
|
2557
|
+
|
|
2558
|
+
return {
|
|
2559
|
+
"success": True,
|
|
2560
|
+
"message": f"Linked document '{doc.title}' to agent '{target_agent.name}'",
|
|
2561
|
+
"document_id": str(doc.id),
|
|
2562
|
+
"agent_id": str(target_agent.id),
|
|
2563
|
+
"agent_slug": target_agent.slug,
|
|
2564
|
+
}
|
|
2565
|
+
except SpecDocument.DoesNotExist:
|
|
2566
|
+
return {"error": f"Document not found: {args.get('document_id')}"}
|
|
2567
|
+
except AgentDefinition.DoesNotExist:
|
|
2568
|
+
return {"error": f"Agent not found: {args.get('agent_id')}"}
|
|
2569
|
+
except Exception as e:
|
|
2570
|
+
logger.exception("Error linking spec to agent")
|
|
2571
|
+
return {"error": str(e)}
|
|
2572
|
+
|
|
2573
|
+
async def _unlink_spec_from_agent(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
2574
|
+
"""Unlink a spec document from its agent."""
|
|
2575
|
+
from django_agent_runtime.models import SpecDocument
|
|
2576
|
+
|
|
2577
|
+
try:
|
|
2578
|
+
doc_id = args["document_id"]
|
|
2579
|
+
doc = await sync_to_async(
|
|
2580
|
+
SpecDocument.objects.select_related("linked_agent").get
|
|
2581
|
+
)(id=doc_id)
|
|
2582
|
+
|
|
2583
|
+
if not doc.linked_agent:
|
|
2584
|
+
return {"message": "Document is not linked to any agent"}
|
|
2585
|
+
|
|
2586
|
+
old_agent_name = doc.linked_agent.name
|
|
2587
|
+
doc.linked_agent = None
|
|
2588
|
+
await sync_to_async(doc.save)()
|
|
2589
|
+
|
|
2590
|
+
return {
|
|
2591
|
+
"success": True,
|
|
2592
|
+
"message": f"Unlinked document '{doc.title}' from agent '{old_agent_name}'",
|
|
2593
|
+
"document_id": str(doc.id),
|
|
2594
|
+
}
|
|
2595
|
+
except SpecDocument.DoesNotExist:
|
|
2596
|
+
return {"error": f"Document not found: {args.get('document_id')}"}
|
|
2597
|
+
except Exception as e:
|
|
2598
|
+
logger.exception("Error unlinking spec from agent")
|
|
2599
|
+
return {"error": str(e)}
|
|
2600
|
+
|
|
2601
|
+
async def _get_spec_document_history(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
2602
|
+
"""Get version history of a spec document."""
|
|
2603
|
+
from django_agent_runtime.models import SpecDocument, SpecDocumentVersion
|
|
2604
|
+
|
|
2605
|
+
try:
|
|
2606
|
+
doc_id = args["document_id"]
|
|
2607
|
+
limit = args.get("limit", 10)
|
|
2608
|
+
|
|
2609
|
+
doc = await sync_to_async(SpecDocument.objects.get)(id=doc_id)
|
|
2610
|
+
versions = await sync_to_async(list)(
|
|
2611
|
+
doc.versions.all()[:limit]
|
|
2612
|
+
)
|
|
2613
|
+
|
|
2614
|
+
return {
|
|
2615
|
+
"document_id": str(doc.id),
|
|
2616
|
+
"document_title": doc.title,
|
|
2617
|
+
"current_version": doc.current_version,
|
|
2618
|
+
"versions": [
|
|
2619
|
+
{
|
|
2620
|
+
"version_number": v.version_number,
|
|
2621
|
+
"title": v.title,
|
|
2622
|
+
"content_preview": v.content[:100] + "..." if len(v.content) > 100 else v.content,
|
|
2623
|
+
"created_at": v.created_at.isoformat(),
|
|
2624
|
+
"change_summary": v.change_summary or "",
|
|
2625
|
+
}
|
|
2626
|
+
for v in versions
|
|
2627
|
+
],
|
|
2628
|
+
}
|
|
2629
|
+
except SpecDocument.DoesNotExist:
|
|
2630
|
+
return {"error": f"Document not found: {args.get('document_id')}"}
|
|
2631
|
+
except Exception as e:
|
|
2632
|
+
logger.exception("Error getting spec document history")
|
|
2633
|
+
return {"error": str(e)}
|
|
2634
|
+
|
|
2635
|
+
async def _restore_spec_document_version(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
2636
|
+
"""Restore a spec document to a previous version."""
|
|
2637
|
+
from django_agent_runtime.models import SpecDocument, SpecDocumentVersion
|
|
2638
|
+
|
|
2639
|
+
try:
|
|
2640
|
+
doc_id = args["document_id"]
|
|
2641
|
+
version_number = args["version_number"]
|
|
2642
|
+
|
|
2643
|
+
doc = await sync_to_async(SpecDocument.objects.get)(id=doc_id)
|
|
2644
|
+
version = await sync_to_async(doc.versions.get)(version_number=version_number)
|
|
2645
|
+
|
|
2646
|
+
# Restore creates a new version
|
|
2647
|
+
await sync_to_async(version.restore)()
|
|
2648
|
+
|
|
2649
|
+
# Refresh doc to get new version number
|
|
2650
|
+
await sync_to_async(doc.refresh_from_db)()
|
|
2651
|
+
|
|
2652
|
+
return {
|
|
2653
|
+
"success": True,
|
|
2654
|
+
"message": f"Restored document to version {version_number}",
|
|
2655
|
+
"document_id": str(doc.id),
|
|
2656
|
+
"restored_from_version": version_number,
|
|
2657
|
+
"new_version": doc.current_version,
|
|
2658
|
+
}
|
|
2659
|
+
except SpecDocument.DoesNotExist:
|
|
2660
|
+
return {"error": f"Document not found: {args.get('document_id')}"}
|
|
2661
|
+
except SpecDocumentVersion.DoesNotExist:
|
|
2662
|
+
return {"error": f"Version not found: {args.get('version_number')}"}
|
|
2663
|
+
except Exception as e:
|
|
2664
|
+
logger.exception("Error restoring spec document version")
|
|
2665
|
+
return {"error": str(e)}
|
|
2666
|
+
|
|
2667
|
+
async def _delete_spec_document(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
2668
|
+
"""Delete a spec document."""
|
|
2669
|
+
from django_agent_runtime.models import SpecDocument
|
|
2670
|
+
|
|
2671
|
+
try:
|
|
2672
|
+
doc_id = args["document_id"]
|
|
2673
|
+
confirm = args.get("confirm", False)
|
|
2674
|
+
|
|
2675
|
+
if not confirm:
|
|
2676
|
+
return {"error": "Must set confirm=true to delete a document"}
|
|
2677
|
+
|
|
2678
|
+
doc = await sync_to_async(SpecDocument.objects.get)(id=doc_id)
|
|
2679
|
+
title = doc.title
|
|
2680
|
+
|
|
2681
|
+
# Count children that will be deleted
|
|
2682
|
+
children_count = await sync_to_async(doc.children.count)()
|
|
2683
|
+
|
|
2684
|
+
await sync_to_async(doc.delete)()
|
|
2685
|
+
|
|
2686
|
+
return {
|
|
2687
|
+
"success": True,
|
|
2688
|
+
"message": f"Deleted document '{title}'" + (f" and {children_count} children" if children_count else ""),
|
|
2689
|
+
"deleted_children": children_count,
|
|
2690
|
+
}
|
|
2691
|
+
except SpecDocument.DoesNotExist:
|
|
2692
|
+
return {"error": f"Document not found: {args.get('document_id')}"}
|
|
2693
|
+
except Exception as e:
|
|
2694
|
+
logger.exception("Error deleting spec document")
|
|
2695
|
+
return {"error": str(e)}
|
|
2696
|
+
|
|
2697
|
+
async def _render_full_spec(self, agent, args: dict, ctx: RunContext) -> dict:
|
|
2698
|
+
"""Render all spec documents as a single markdown document."""
|
|
2699
|
+
from django_agent_runtime.models import SpecDocument
|
|
2700
|
+
|
|
2701
|
+
try:
|
|
2702
|
+
root_id = args.get("root_document_id")
|
|
2703
|
+
|
|
2704
|
+
if root_id:
|
|
2705
|
+
# Render from specific root
|
|
2706
|
+
doc = await sync_to_async(SpecDocument.objects.get)(id=root_id)
|
|
2707
|
+
markdown = await sync_to_async(doc.render_tree_as_markdown)()
|
|
2708
|
+
return {
|
|
2709
|
+
"markdown": markdown,
|
|
2710
|
+
"root_document": doc.title,
|
|
2711
|
+
}
|
|
2712
|
+
else:
|
|
2713
|
+
# Render all root documents
|
|
2714
|
+
roots = await sync_to_async(list)(
|
|
2715
|
+
SpecDocument.objects.filter(parent__isnull=True).order_by("order", "title")
|
|
2716
|
+
)
|
|
2717
|
+
|
|
2718
|
+
parts = []
|
|
2719
|
+
for root in roots:
|
|
2720
|
+
parts.append(await sync_to_async(root.render_tree_as_markdown)())
|
|
2721
|
+
|
|
2722
|
+
return {
|
|
2723
|
+
"markdown": "\n\n---\n\n".join(parts),
|
|
2724
|
+
"root_count": len(roots),
|
|
2725
|
+
"roots": [{"id": str(r.id), "title": r.title} for r in roots],
|
|
2726
|
+
}
|
|
2727
|
+
except SpecDocument.DoesNotExist:
|
|
2728
|
+
return {"error": f"Document not found: {args.get('root_document_id')}"}
|
|
2729
|
+
except Exception as e:
|
|
2730
|
+
logger.exception("Error rendering full spec")
|
|
2731
|
+
return {"error": str(e)}
|
|
@@ -3,7 +3,7 @@ django_agent_studio/apps.py,sha256=L89QWn4XOvPBs2z6qHAhaE4uZQpasJntYD75aSd2p_k,6
|
|
|
3
3
|
django_agent_studio/urls.py,sha256=O_rrobaxpmg8gR0JmeUzQ0TI3E8mGdcz27p4OJlcI8s,743
|
|
4
4
|
django_agent_studio/views.py,sha256=bQxSIeL9-D4MzKwWkeHEFXrzo1CD911qicrt_A8t_6M,3153
|
|
5
5
|
django_agent_studio/agents/__init__.py,sha256=VYL_ato0DtggIo4BGRkyiz9cm1ARPXhhTQFzoG__NVM,800
|
|
6
|
-
django_agent_studio/agents/builder.py,sha256=
|
|
6
|
+
django_agent_studio/agents/builder.py,sha256=LwREdCELIYh5hMoDBQVGtpNZ5k7yMUcYsaqONQ79mEg,110635
|
|
7
7
|
django_agent_studio/agents/dynamic.py,sha256=KhmdAHkYUdfuuBgsKeY-Sf9sjDA-QiBVWHpw0NAgPX0,13939
|
|
8
8
|
django_agent_studio/api/__init__.py,sha256=vtBwuvBENyFFhFqCWyFsI6cYu4N9ZGqSMmHIRhr9a_U,45
|
|
9
9
|
django_agent_studio/api/permissions.py,sha256=MutmA8TxZb4ZwGfeEoolK-QI04Gbcxs7DPNzkXe_Bss,5302
|
|
@@ -26,7 +26,7 @@ django_agent_studio/templates/django_agent_studio/base.html,sha256=rpHmr7CAWGwRk
|
|
|
26
26
|
django_agent_studio/templates/django_agent_studio/builder.html,sha256=L2KX-RLVpnC2mmYsIl_aSeJIc6b63knTYVrDg8aaU1g,61425
|
|
27
27
|
django_agent_studio/templates/django_agent_studio/home.html,sha256=pgwKPjiQe9UxYYLGSsKT-6erEPDUdW9MZ5D-MOjIxK4,4385
|
|
28
28
|
django_agent_studio/templates/django_agent_studio/test.html,sha256=h9aTtTD1eYcgG-n410qMSryHdpXyKny0hjiKYAoGsic,3779
|
|
29
|
-
django_agent_studio-0.1.
|
|
30
|
-
django_agent_studio-0.1.
|
|
31
|
-
django_agent_studio-0.1.
|
|
32
|
-
django_agent_studio-0.1.
|
|
29
|
+
django_agent_studio-0.1.7.dist-info/METADATA,sha256=w8VyEVJK5oLHDLnxoRj1gDe34TH8Q9i4vbv37UNomAo,11294
|
|
30
|
+
django_agent_studio-0.1.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
31
|
+
django_agent_studio-0.1.7.dist-info/top_level.txt,sha256=O1kqZzXPOsJlqnPSAcB2fH5WpJNY8ZNfHEJzX9_SZ0A,20
|
|
32
|
+
django_agent_studio-0.1.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|