feat: v6.6.0 — Agent phase 5 : API publique agent (/api/v2/agents, run synchrone JSON) + marketplace skills (export/import portable + galerie de 6 presets, palette / du panneau) + webhooks agent.run.started/failed · 764 tests verts
This commit is contained in:
@@ -14,6 +14,7 @@ from fastapi.responses import StreamingResponse
|
||||
from app.auth.session import get_current_user
|
||||
from app.config import settings
|
||||
from app.db import get_conn
|
||||
from app.services import skill_gallery
|
||||
from app.services.agent_engine import AgentEngine, undo_action
|
||||
from app.services.llm_client import PROVIDER_MODELS, PROVIDERS, LLMClient
|
||||
from app.services.llm_config import (
|
||||
@@ -524,6 +525,81 @@ async def apply_skill(request: Request, skill_id: int):
|
||||
return {"conversation_id": cur.lastrowid, "skill": skill["name"], "status": "ready"}
|
||||
|
||||
|
||||
# ── Skill marketplace (v6.6.0, Agent phase 5) ──
|
||||
# Galerie de presets + export/import portable — même implémentation que
|
||||
# l'API publique (/api/v2/skills/*), via app.services.skill_gallery.
|
||||
|
||||
|
||||
@router.get("/skills/gallery")
|
||||
async def skills_gallery(request: Request):
|
||||
presets = skill_gallery.list_gallery()
|
||||
return {"gallery": presets, "total": len(presets),
|
||||
"install": "POST /api/agent/skills/gallery/{slug}/install"}
|
||||
|
||||
|
||||
@router.post("/skills/gallery/{slug}/install")
|
||||
async def install_gallery_skill(request: Request, slug: str):
|
||||
user_id = await _current_user_id(request)
|
||||
ws = await _workspace_id(request)
|
||||
preset = skill_gallery.get_gallery(slug)
|
||||
if not preset:
|
||||
raise HTTPException(status_code=404, detail=f"Skill inconnue dans la galerie: {slug}")
|
||||
body = await request.json() if request.headers.get("content-type") else {}
|
||||
try:
|
||||
row, created = skill_gallery.upsert_skill(
|
||||
skill_gallery.parse_payload(preset),
|
||||
workspace_id=ws, created_by=user_id,
|
||||
overwrite=bool(body.get("overwrite", True)),
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
||||
return {"slug": slug, "id": row.get("id"), "name": row.get("name"),
|
||||
"status": "installed" if created else "updated", "skill": row}
|
||||
|
||||
|
||||
@router.post("/skills/import")
|
||||
async def import_skill(request: Request):
|
||||
"""Importe un skill portable (JSON exporté depuis une autre instance)."""
|
||||
user_id = await _current_user_id(request)
|
||||
ws = await _workspace_id(request)
|
||||
body = await request.json() if request.headers.get("content-type") else {}
|
||||
payload = body.get("payload") if isinstance(body.get("payload"), dict) else body
|
||||
try:
|
||||
fields = skill_gallery.parse_payload(payload)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
try:
|
||||
row, created = skill_gallery.upsert_skill(
|
||||
fields, workspace_id=ws, created_by=user_id,
|
||||
overwrite=bool(body.get("overwrite")),
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
||||
return {"id": row.get("id"), "name": fields["name"],
|
||||
"status": "imported" if created else "updated", "skill": row}
|
||||
|
||||
|
||||
@router.get("/skills/{skill_id}/export")
|
||||
async def export_skill(request: Request, skill_id: int):
|
||||
"""Document JSON portable — à rejouer sur /api/agent/skills/import."""
|
||||
with get_conn() as conn:
|
||||
row = conn.execute("SELECT * FROM agent_skills WHERE id=?", (skill_id,)).fetchone()
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Skill introuvable")
|
||||
return skill_gallery.export_skill(row)
|
||||
|
||||
|
||||
@router.delete("/skills/{skill_id}")
|
||||
async def delete_skill(request: Request, skill_id: int):
|
||||
with get_conn() as conn:
|
||||
row = conn.execute("SELECT name FROM agent_skills WHERE id=?", (skill_id,)).fetchone()
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Skill introuvable")
|
||||
conn.execute("DELETE FROM agent_skills WHERE id=?", (skill_id,))
|
||||
conn.commit()
|
||||
return {"id": skill_id, "status": "deleted"}
|
||||
|
||||
|
||||
# ── Mentions (commande @ / +) & feedback (boutons 👍 / 👎) ──
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user