Compost / backend /routers /tools.py
abc1181's picture
Refactor: use Composio Core SDK as backend engine
dc0fe62
Raw
History Blame Contribute Delete
5.4 kB
from typing import Optional, List
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from pydantic import BaseModel
from backend.database import get_db, Tool, Integration, ToolExecution
from backend.auth import get_current_user, User
from backend.composio_client import get_composio
from datetime import datetime
router = APIRouter()
class ToolResponse(BaseModel):
id: str
integration_id: str
name: str
description: str
input_schema: dict = {}
output_schema: Optional[dict] = None
category: Optional[str] = None
is_active: bool = True
class ToolExecutionRequest(BaseModel):
params: dict = {}
connection_id: Optional[str] = None
dry_run: bool = False
class ToolExecutionResponse(BaseModel):
execution_id: str
status: str
result: Optional[dict] = None
latency_ms: int = 0
executed_at: str = ""
error: Optional[str] = None
@router.get("", response_model=List[ToolResponse])
async def list_tools(
integration_id: Optional[str] = None,
category: Optional[str] = None,
search: Optional[str] = None,
db: Session = Depends(get_db)
):
composio = get_composio()
if composio and not integration_id and not category and search:
try:
raw = composio.tools.get_raw_composio_tools(search=search, limit=100)
return [_tool_to_response(t) for t in raw]
except Exception:
pass
query = db.query(Tool).filter(Tool.is_active == True)
if integration_id:
query = query.filter(Tool.integration_id == integration_id)
if category:
query = query.filter(Tool.category == category)
if search:
query = query.filter(
(Tool.name.ilike(f"%{search}%")) |
(Tool.description.ilike(f"%{search}%"))
)
tools = query.all()
return [ToolResponse(
id=t.id, integration_id=t.integration_id, name=t.name,
description=t.description, input_schema=t.input_schema,
output_schema=t.output_schema, category=t.category, is_active=t.is_active
) for t in tools]
def _tool_to_response(t):
t_d = t
if hasattr(t, "model_dump"):
t_d = t.model_dump(mode="json")
elif hasattr(t, "__dict__"):
t_d = {k: v for k, v in t.__dict__.items() if not k.startswith("_")}
toolkit = t_d.get("toolkit", {}) or {}
int_id = toolkit.get("slug", "") if isinstance(toolkit, dict) else ""
return ToolResponse(
id=t_d.get("slug", t_d.get("id", "")),
integration_id=int_id,
name=t_d.get("name", ""),
description=t_d.get("description", ""),
input_schema=t_d.get("input_parameters", {"type": "object", "properties": {}}),
output_schema=t_d.get("output_parameters"),
category=(t_d.get("tags") or ["General"])[0] if t_d.get("tags") else "General",
is_active=not t_d.get("is_deprecated", False)
)
@router.get("/{tool_id}", response_model=ToolResponse)
async def get_tool(tool_id: str, db: Session = Depends(get_db)):
composio = get_composio()
if composio:
try:
raw = composio.tools.get_raw_composio_tools(tools=[tool_id])
if raw:
return _tool_to_response(raw[0])
except Exception:
pass
tool = db.query(Tool).filter(Tool.id == tool_id).first()
if not tool:
raise HTTPException(status_code=404, detail="Tool not found")
return ToolResponse(
id=tool.id, integration_id=tool.integration_id, name=tool.name,
description=tool.description, input_schema=tool.input_schema,
output_schema=tool.output_schema, category=tool.category, is_active=tool.is_active
)
@router.post("/{tool_id}/execute", response_model=ToolExecutionResponse)
async def execute_tool(
tool_id: str,
request: ToolExecutionRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
from services.tool_executor import executor
result = await executor.execute(
tool_id=tool_id, params=request.params,
connection_id=request.connection_id,
user_id=current_user.id, source="api"
)
return ToolExecutionResponse(
execution_id=result["execution_id"], status=result["status"],
result=result["result"], latency_ms=result["latency_ms"],
executed_at=datetime.utcnow().isoformat(), error=result.get("error")
)
@router.get("/executions")
async def list_executions(
limit: int = 20,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
executions = db.query(ToolExecution).filter(
ToolExecution.user_id == current_user.id
).order_by(ToolExecution.executed_at.desc()).limit(limit).all()
return [{
"id": e.id, "tool_id": e.tool_id, "status": e.status,
"latency_ms": e.latency_ms,
"executed_at": e.executed_at.isoformat() if e.executed_at else "",
"source": e.source
} for e in executions]
@router.post("/sync")
async def sync_tools(
force: bool = Query(False),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
from integrations.registry import sync_from_composio
result = await sync_from_composio(force=force)
return {"message": "Tools synced", "source": result["source"], "toolkits": result["toolkits"], "tools": result["tools"]}