| 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"]} |
|
|