suna/backend/mcp_service/template_api.py

269 lines
9.5 KiB
Python

"""
Template API endpoints
This module provides API endpoints for template management:
1. Creating agent templates from existing agents
2. Publishing/unpublishing templates to marketplace
3. Installing templates as agent instances
4. Browsing marketplace and user templates
"""
from fastapi import APIRouter, HTTPException, Depends
from typing import List, Optional, Dict, Any
from pydantic import BaseModel
from utils.logger import logger
from utils.auth_utils import get_current_user_id_from_jwt
from .template_manager import template_manager
router = APIRouter()
# =====================================================
# PYDANTIC MODELS
# =====================================================
class CreateTemplateRequest(BaseModel):
"""Request model for creating agent template"""
agent_id: str
make_public: bool = False
tags: Optional[List[str]] = None
class InstallTemplateRequest(BaseModel):
"""Request model for installing template"""
template_id: str
instance_name: Optional[str] = None
custom_system_prompt: Optional[str] = None
profile_mappings: Optional[Dict[str, str]] = None
custom_mcp_configs: Optional[Dict[str, Dict[str, Any]]] = None
class PublishTemplateRequest(BaseModel):
"""Request model for publishing template"""
tags: Optional[List[str]] = None
class TemplateResponse(BaseModel):
"""Response model for agent templates"""
template_id: str
name: str
description: Optional[str]
mcp_requirements: List[Dict[str, Any]]
agentpress_tools: Dict[str, Any]
tags: List[str]
is_public: bool
download_count: int
marketplace_published_at: Optional[str]
created_at: str
creator_name: Optional[str] = None
avatar: Optional[str]
avatar_color: Optional[str]
is_kortix_team: Optional[bool] = False
class InstallationResponse(BaseModel):
"""Response model for template installation"""
status: str # 'installed', 'configs_required'
instance_id: Optional[str] = None
missing_regular_credentials: Optional[List[Dict[str, Any]]] = None
missing_custom_configs: Optional[List[Dict[str, Any]]] = None
template: Optional[Dict[str, Any]] = None
# =====================================================
# TEMPLATE MANAGEMENT ENDPOINTS
# =====================================================
@router.post("", response_model=Dict[str, str])
async def create_agent_template(
request: CreateTemplateRequest,
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""Create an agent template from an existing agent"""
logger.info(f"Creating template from agent {request.agent_id} for user {user_id}")
try:
template_id = await template_manager.create_template_from_agent(
agent_id=request.agent_id,
creator_id=user_id,
make_public=request.make_public,
tags=request.tags
)
return {
"template_id": template_id,
"message": "Template created successfully"
}
except Exception as e:
logger.error(f"Error creating template: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to create template: {str(e)}")
@router.post("/{template_id}/publish")
async def publish_template(
template_id: str,
request: PublishTemplateRequest,
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""Publish a template to the marketplace"""
logger.info(f"Publishing template {template_id} for user {user_id}")
try:
success = await template_manager.publish_template(
template_id=template_id,
creator_id=user_id,
tags=request.tags
)
if not success:
raise HTTPException(status_code=404, detail="Template not found or access denied")
return {"message": "Template published to marketplace successfully"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error publishing template: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to publish template: {str(e)}")
@router.post("/{template_id}/unpublish")
async def unpublish_template(
template_id: str,
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""Unpublish a template from the marketplace"""
logger.info(f"Unpublishing template {template_id} for user {user_id}")
try:
success = await template_manager.unpublish_template(
template_id=template_id,
creator_id=user_id
)
if not success:
raise HTTPException(status_code=404, detail="Template not found or access denied")
return {"message": "Template unpublished from marketplace successfully"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error unpublishing template: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to unpublish template: {str(e)}")
@router.post("/install", response_model=InstallationResponse)
async def install_template(
request: InstallTemplateRequest,
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""Install a template as an agent instance"""
logger.info(f"Installing template {request.template_id} for user {user_id}")
try:
result = await template_manager.install_template(
template_id=request.template_id,
account_id=user_id,
instance_name=request.instance_name,
custom_system_prompt=request.custom_system_prompt,
profile_mappings=request.profile_mappings,
custom_mcp_configs=request.custom_mcp_configs
)
return InstallationResponse(**result)
except Exception as e:
logger.error(f"Error installing template: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to install template: {str(e)}")
@router.get("/marketplace", response_model=List[TemplateResponse])
async def get_marketplace_templates(
limit: int = 50,
offset: int = 0,
search: Optional[str] = None,
tags: Optional[str] = None, # Comma-separated tags
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""Get public templates from the marketplace"""
logger.info(f"Getting marketplace templates for user {user_id}")
try:
tag_list = None
if tags:
tag_list = [tag.strip() for tag in tags.split(',') if tag.strip()]
templates = await template_manager.get_marketplace_templates(
limit=limit,
offset=offset,
search=search,
tags=tag_list
)
print("templates", templates)
return [TemplateResponse(**template) for template in templates]
except Exception as e:
logger.error(f"Error getting marketplace templates: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get marketplace templates: {str(e)}")
@router.get("/my", response_model=List[TemplateResponse])
async def get_my_templates(
limit: int = 50,
offset: int = 0,
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""Get all templates created by the current user"""
logger.info(f"Getting user templates for user {user_id}")
try:
templates = await template_manager.get_user_templates(
creator_id=user_id,
limit=limit,
offset=offset
)
return [TemplateResponse(**template) for template in templates]
except Exception as e:
logger.error(f"Error getting user templates: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get user templates: {str(e)}")
@router.get("/{template_id}", response_model=TemplateResponse)
async def get_template_details(
template_id: str,
user_id: str = Depends(get_current_user_id_from_jwt)
):
"""Get detailed information about a specific template"""
logger.info(f"Getting template {template_id} details for user {user_id}")
try:
template = await template_manager.get_template(template_id)
if not template:
raise HTTPException(status_code=404, detail="Template not found")
# Check access permissions
if not template.is_public and template.creator_id != user_id:
raise HTTPException(status_code=403, detail="Access denied to private template")
return TemplateResponse(
template_id=template.template_id,
name=template.name,
description=template.description,
mcp_requirements=[
{
'qualified_name': req.qualified_name,
'display_name': req.display_name,
'enabled_tools': req.enabled_tools,
'required_config': req.required_config
}
for req in template.mcp_requirements
],
agentpress_tools=template.agentpress_tools,
tags=template.tags,
is_public=template.is_public,
download_count=template.download_count,
marketplace_published_at=template.marketplace_published_at.isoformat() if template.marketplace_published_at else None,
created_at=template.created_at.isoformat() if template.created_at else "",
avatar=template.avatar,
avatar_color=template.avatar_color
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting template details: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get template details: {str(e)}")