Files
taiji-AI-PAD/services/mcp-server/app/routes/frontend_integration.py
T
2025-12-24 12:33:54 +00:00

940 lines
33 KiB
Python

"""Frontend integration endpoints required by BACKEND_INTEGRATION_CHECKLIST.
All handlers return lightweight, mostly in-memory data so the frontend can
render flows before the full business services are ready. Where possible, we
hydrate responses from existing tables (Agents, Tools) to keep values realistic.
"""
from __future__ import annotations
import uuid
from datetime import datetime, timedelta
from typing import Any, Dict, List
from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from database import get_db
from models import (
Agent,
Balance,
Channel,
ChannelAgentQuota,
DataTemplate,
Execution,
Billing,
GatewayAPI,
ProviderModel,
Tenant,
Tool,
)
from monitoring import system_monitor
from ..auth import create_access_token, ensure_user, get_password_hash, verify_password
router = APIRouter(prefix="/api", tags=["frontend-integration"])
class _Store:
"""Simple in-memory store backing the checklist endpoints."""
def __init__(self) -> None:
self.gateway_type: str | None = None
self.gateway_apis: List[Dict[str, Any]] = []
self.data_templates: List[Dict[str, Any]] = []
self.deployments: List[Dict[str, Any]] = []
self.workflows: Dict[str, Dict[str, Any]] = {}
self.billing_balance: float = 1200.0
self.billing_history: List[Dict[str, Any]] = []
self.tenants: Dict[str, Dict[str, Any]] = {}
self.channels: Dict[str, Dict[str, Any]] = {}
self.channel_admins: Dict[str, Dict[str, Any]] = {}
self.resource_applications: Dict[str, Dict[str, Any]] = {}
self.provider_models: Dict[str, Dict[str, Any]] = {}
self.provider_data: List[Dict[str, Any]] = []
store = _Store()
def _now() -> str:
return datetime.utcnow().isoformat()
async def _get_principal_user_id(request: Request, db: AsyncSession) -> uuid.UUID:
principal = getattr(request.state, "principal", None) or {}
email = principal.get("email") or "dev@taiji-ai.com"
user = await ensure_user(email, "temp-pass", db)
return user.id
async def _get_balance(db: AsyncSession, user_id: uuid.UUID) -> Balance:
result = await db.execute(select(Balance).where(Balance.user_id == user_id))
balance = result.scalar_one_or_none()
if balance is None:
balance = Balance(user_id=user_id, eu_balance=0.0)
db.add(balance)
await db.commit()
await db.refresh(balance)
return balance
# ----- User Dashboard -----
@router.get("/user/dashboard/stats")
async def user_dashboard_stats(request: Request, db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
agent_count = (await db.execute(select(func.count(Agent.id)))).scalar() or 0
total_requests = (
await db.execute(select(func.coalesce(func.sum(Agent.total_executions), 0)))
).scalar() or 0
health = await system_monitor.get_system_health()
balance = await _get_balance(db, await _get_principal_user_id(request, db))
return {
"activeAgents": agent_count,
"totalRequests": total_requests,
"euBalance": round(balance.eu_balance, 2),
"systemHealth": health.get("score", 100),
}
@router.get("/user/agents/activity")
async def user_agents_activity(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
query = (
select(Execution)
.order_by(Execution.started_at.desc())
.limit(20)
)
rows = (await db.execute(query)).scalars().all()
activity = [
{
"executionId": row.execution_id,
"agentId": str(row.agent_id),
"status": row.status,
"startedAt": row.started_at.isoformat(),
"duration": row.execution_time or 0.0,
"eu": row.eu_consumed or 0.0,
}
for row in rows
]
return {"items": activity, "count": len(activity)}
@router.get("/user/resources/usage")
async def user_resources_usage(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
agent_count = (await db.execute(select(func.count(Agent.id)))).scalar() or 0
execution_stats = (
await db.execute(
select(
func.coalesce(func.sum(Execution.eu_consumed), 0),
func.coalesce(func.sum(Execution.cpu_usage), 0),
func.coalesce(func.sum(Execution.memory_usage), 0),
)
)
).one()
return {
"agents": agent_count,
"euConsumed": float(execution_stats[0] or 0),
"cpuSeconds": float(execution_stats[1] or 0),
"memoryMb": float(execution_stats[2] or 0),
}
# ----- Service Gateway -----
@router.post("/gateway/select")
async def select_gateway(payload: Dict[str, str]) -> Dict[str, Any]:
gateway_type = payload.get("gatewayType")
if gateway_type not in {"MCP", "A2A", "API"}:
raise HTTPException(status_code=400, detail="gatewayType must be MCP | A2A | API")
store.gateway_type = gateway_type
return {"selected": gateway_type, "updatedAt": _now()}
@router.post("/gateway/api/create")
async def create_gateway_api(
request: Request, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)
) -> Dict[str, Any]:
if not payload.get("name") or not payload.get("content"):
raise HTTPException(status_code=400, detail="name and content are required")
owner_id = await _get_principal_user_id(request, db)
gateway_api = GatewayAPI(
name=payload["name"],
method=payload.get("method", "json"),
content=payload.get("content", ""),
owner_id=owner_id,
)
db.add(gateway_api)
await db.commit()
await db.refresh(gateway_api)
item = {
"id": str(gateway_api.id),
"name": gateway_api.name,
"method": gateway_api.method,
"content": gateway_api.content,
"createdAt": gateway_api.created_at.isoformat(),
}
return item
@router.get("/gateway/apis")
async def list_gateway_apis(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
apis = (await db.execute(select(GatewayAPI).order_by(GatewayAPI.created_at.desc()))).scalars().all()
items = [
{
"id": str(api.id),
"name": api.name,
"method": api.method,
"content": api.content,
"createdAt": api.created_at.isoformat() if api.created_at else None,
}
for api in apis
]
return {"items": items, "count": len(items)}
@router.get("/gateway/monitoring")
async def gateway_monitoring(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
total = (await db.execute(select(func.count(GatewayAPI.id)))).scalar() or 0
return {
"selected": store.gateway_type,
"totalApis": total,
"lastUpdated": _now(),
"throughput": {
"rpm": 120,
"errorRate": 0.01,
},
}
# ----- Data & Tools -----
@router.post("/tools/generate")
async def generate_tool(payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
name = payload.get("name")
description = payload.get("description", "")
framework_template = payload.get("frameworkTemplate", "API")
if not name:
raise HTTPException(status_code=400, detail="name is required")
tool = Tool(
name=name,
description=description,
category=framework_template.lower(),
schema={
"type": "object",
"properties": payload.get("config", {}),
},
method="POST",
endpoint=payload.get("gateway", ""),
rate_limit=payload.get("maxScale", 100),
cost_per_call=0.0,
is_public=True,
)
db.add(tool)
await db.commit()
await db.refresh(tool)
return {
"id": str(tool.id),
"name": tool.name,
"description": tool.description,
"category": tool.category,
"endpoint": tool.endpoint,
"createdAt": _now(),
}
@router.get("/tools/list")
async def list_tools(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
tools = (await db.execute(select(Tool).order_by(Tool.created_at.desc()).limit(100))).scalars().all()
items = [
{
"id": str(tool.id),
"name": tool.name,
"description": tool.description,
"category": tool.category,
"endpoint": tool.endpoint,
"method": tool.method,
"isActive": tool.is_active,
}
for tool in tools
]
return {"items": items, "count": len(items)}
@router.post("/data-templates/create")
async def create_data_template(request: Request, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
if not payload.get("name") or not payload.get("type"):
raise HTTPException(status_code=400, detail="name and type are required")
template = DataTemplate(
name=payload["name"],
type=payload.get("type", "json_api"),
config=payload.get("config", {}),
owner_id=await _get_principal_user_id(request, db),
)
db.add(template)
await db.commit()
await db.refresh(template)
return {"id": str(template.id), "status": "created", "createdAt": template.created_at.isoformat()}
# ----- Agent Factory -----
@router.get("/agents/platform")
async def platform_agents(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
agents = (await db.execute(select(Agent).order_by(Agent.created_at.desc()).limit(50))).scalars().all()
items = [
{
"id": str(agent.id),
"name": agent.name,
"description": agent.description,
"status": agent.status,
"version": agent.version,
}
for agent in agents
]
return {"items": items, "count": len(items)}
@router.post("/agents/deploy")
async def deploy_agent(payload: Dict[str, Any]) -> Dict[str, Any]:
if not payload.get("agentId"):
raise HTTPException(status_code=400, detail="agentId is required")
deployment = {
"deploymentId": str(uuid.uuid4()),
"agentId": payload["agentId"],
"instances": payload.get("instances", 1),
"model": payload.get("model", ""),
"gateway": payload.get("gateway", "MCP"),
"createdAt": _now(),
}
store.deployments.append(deployment)
return deployment
@router.get("/agents/deployed")
async def deployed_agents() -> Dict[str, Any]:
return {"items": store.deployments, "count": len(store.deployments)}
# ----- Workflows -----
@router.post("/workflows/create")
async def create_workflow(payload: Dict[str, Any]) -> Dict[str, Any]:
if not payload.get("name"):
raise HTTPException(status_code=400, detail="name is required")
workflow_id = str(uuid.uuid4())
workflow = {"id": workflow_id, **payload, "createdAt": _now()}
store.workflows[workflow_id] = workflow
return workflow
@router.get("/workflows/list")
async def list_workflows() -> Dict[str, Any]:
items = list(store.workflows.values())
return {"items": items, "count": len(items)}
@router.put("/workflows/{workflow_id}")
async def update_workflow(workflow_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
if workflow_id not in store.workflows:
raise HTTPException(status_code=404, detail="workflow not found")
store.workflows[workflow_id].update(payload)
store.workflows[workflow_id]["updatedAt"] = _now()
return store.workflows[workflow_id]
@router.delete("/workflows/{workflow_id}")
async def delete_workflow(workflow_id: str) -> Dict[str, Any]:
if workflow_id not in store.workflows:
raise HTTPException(status_code=404, detail="workflow not found")
store.workflows.pop(workflow_id)
return {"deleted": True, "id": workflow_id}
# ----- Billing & Resources -----
@router.get("/billing/balance")
async def billing_balance(request: Request, db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
balance = await _get_balance(db, await _get_principal_user_id(request, db))
return {"balance": round(balance.eu_balance, 2), "currency": balance.currency}
@router.get("/billing/history")
async def billing_history(request: Request, db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
user_id = await _get_principal_user_id(request, db)
result = await db.execute(
select(Execution.execution_id, Execution.agent_id, Billing.eu_consumed, Billing.cost, Billing.created_at)
.select_from(Billing)
.join(Execution, Billing.execution_id == Execution.id, isouter=True)
.where(Billing.user_id == user_id)
.order_by(Billing.created_at.desc())
.limit(100)
)
records = [
{
"executionId": row.execution_id,
"agentId": str(row.agent_id) if row.agent_id else None,
"eu": float(row.eu_consumed or 0),
"cost": float(row.cost or 0),
"timestamp": row.created_at.isoformat() if row.created_at else _now(),
}
for row in result.all()
]
return {"records": records, "count": len(records)}
@router.post("/billing/recharge")
async def billing_recharge(request: Request, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
amount = float(payload.get("amount", 0))
if amount <= 0:
raise HTTPException(status_code=400, detail="amount must be positive")
user_id = await _get_principal_user_id(request, db)
balance = await _get_balance(db, user_id)
balance.eu_balance += amount
db.add(balance)
# 记录充值交易
billing = Billing(
execution_id=None,
eu_consumed=0.0,
cost=amount,
currency="EU",
cpu_time=0.0,
memory_max=0.0,
network_io=0.0,
storage_io=0.0,
user_id=user_id,
)
db.add(billing)
await db.commit()
return {"balance": round(balance.eu_balance, 2)}
# ----- Channel Partner -----
@router.post("/channel/auth/login")
async def channel_login(payload: Dict[str, str], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
email = payload.get("email")
password = payload.get("password") or "temp-pass"
if not email:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="email is required")
user = await ensure_user(email, password, db)
if user.hashed_password and not verify_password(password, user.hashed_password):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid credentials")
token = create_access_token({"sub": str(user.id), "email": email, "role": "channel_admin"})
return {"token": token, "tokenType": "bearer", "email": email, "expiresIn": 60 * 60}
@router.get("/channel/dashboard/stats")
async def channel_dashboard_stats(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
tenants_count = (await db.execute(select(func.count(Tenant.id)))).scalar() or 0
deployments = len(store.deployments)
balances = (await db.execute(select(func.coalesce(func.sum(Balance.eu_balance), 0)))).scalar() or 0
return {
"tenants": tenants_count,
"agents": deployments,
"eu": round(float(balances), 2),
}
@router.get("/channel/agents/available")
async def channel_agents_available(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
agents = (await db.execute(select(Agent).limit(50))).scalars().all()
return {
"items": [
{"agentId": str(a.id), "name": a.name, "quantity": 10, "status": a.status}
for a in agents
],
"count": len(agents),
}
@router.get("/channel/tenants")
async def channel_tenants(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
tenants = (await db.execute(select(Tenant))).scalars().all()
items = [
{
"id": str(t.id),
"name": t.name,
"subscriptionTier": t.subscription_tier,
"discount": t.discount,
"channelId": str(t.channel_id) if t.channel_id else None,
}
for t in tenants
]
return {"items": items, "count": len(items)}
@router.post("/channel/tenants/create")
async def channel_create_tenant(payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
if not payload.get("name"):
raise HTTPException(status_code=400, detail="name is required")
tenant = Tenant(
name=payload["name"],
subscription_tier=payload.get("subscriptionTier", "free"),
discount=payload.get("discount", 0),
channel_id=uuid.UUID(payload["channelId"]) if payload.get("channelId") else None,
)
db.add(tenant)
await db.commit()
await db.refresh(tenant)
return {
"id": str(tenant.id),
"name": tenant.name,
"subscriptionTier": tenant.subscription_tier,
"discount": tenant.discount,
}
@router.put("/channel/tenants/{tenant_id}/resources")
async def channel_update_tenant_resources(tenant_id: str, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
tenant = await db.get(Tenant, uuid.UUID(tenant_id)) if tenant_id else None
if not tenant:
raise HTTPException(status_code=404, detail="tenant not found")
tenant.subscription_tier = payload.get("subscriptionTier", tenant.subscription_tier)
tenant.discount = payload.get("discount", tenant.discount)
db.add(tenant)
await db.commit()
await db.refresh(tenant)
return {
"id": str(tenant.id),
"name": tenant.name,
"subscriptionTier": tenant.subscription_tier,
"discount": tenant.discount,
}
@router.put("/channel/tenants/{tenant_id}/billing")
async def channel_update_tenant_billing(tenant_id: str, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
tenant = await db.get(Tenant, uuid.UUID(tenant_id)) if tenant_id else None
if not tenant:
raise HTTPException(status_code=404, detail="tenant not found")
tenant.subscription_tier = payload.get("subscriptionTier", tenant.subscription_tier)
tenant.discount = payload.get("discount", tenant.discount)
db.add(tenant)
await db.commit()
await db.refresh(tenant)
return {
"id": str(tenant.id),
"subscriptionTier": tenant.subscription_tier,
"discount": tenant.discount,
}
@router.get("/channel/resources/agents")
async def channel_resources_agents(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
quotas = (await db.execute(select(ChannelAgentQuota))).scalars().all()
if quotas:
items = [
{
"agentId": str(q.agent_id),
"channelId": str(q.channel_id),
"quota": q.quantity,
"cpu": q.cpu,
"memory": q.memory,
}
for q in quotas
]
else:
agents = (await db.execute(select(Agent))).scalars().all()
items = [{"agentId": str(a.id), "quota": 10} for a in agents]
return {"items": items}
@router.get("/channel/resources/models")
async def channel_resources_models(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
models = (await db.execute(select(ProviderModel))).scalars().all()
return {
"items": [
{
"modelId": str(m.id),
"name": m.name,
"supportedModels": m.supported_models,
"rpm": m.rpm,
"tpm": m.tpm,
}
for m in models
]
}
@router.post("/channel/resources/apply")
async def channel_resources_apply(payload: Dict[str, Any]) -> Dict[str, Any]:
request_id = str(uuid.uuid4())
store.resource_applications[request_id] = {"id": request_id, **payload, "status": "pending"}
return store.resource_applications[request_id]
@router.get("/channel/billing/stats")
async def channel_billing_stats() -> Dict[str, Any]:
return {
"totalEU": sum(rec.get("eu", 0) for rec in store.billing_history),
"totalCost": sum(rec.get("cost", 0) for rec in store.billing_history),
"records": store.billing_history,
}
@router.get("/channel/admins")
async def channel_admins() -> Dict[str, Any]:
return {"items": list(store.channel_admins.values())}
@router.post("/channel/admins/create")
async def channel_admins_create(payload: Dict[str, Any]) -> Dict[str, Any]:
admin_id = str(uuid.uuid4())
admin = {"id": admin_id, **payload, "createdAt": _now()}
store.channel_admins[admin_id] = admin
return admin
@router.put("/channel/admins/{admin_id}/permissions")
async def channel_admins_permissions(admin_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
if admin_id not in store.channel_admins:
raise HTTPException(status_code=404, detail="admin not found")
store.channel_admins[admin_id]["permissions"] = payload.get("permissions", [])
return store.channel_admins[admin_id]
# ----- Super Admin -----
@router.post("/admin/auth/login")
async def admin_login(payload: Dict[str, str], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
email = payload.get("email")
password = payload.get("password") or "temp-pass"
if not email:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="email is required")
user = await ensure_user(email, password, db)
if user.hashed_password and not verify_password(password, user.hashed_password):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid credentials")
user.is_admin = True
db.add(user)
await db.commit()
token = create_access_token({"sub": str(user.id), "email": email, "role": "super_admin"})
return {"token": token, "tokenType": "bearer", "email": email, "expiresIn": 60 * 60}
@router.get("/admin/dashboard/stats")
async def admin_dashboard_stats(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
agent_count = (await db.execute(select(func.count(Agent.id)))).scalar() or 0
channels_count = (await db.execute(select(func.count(Channel.id)))).scalar() or 0
balances = (await db.execute(select(func.coalesce(func.sum(Balance.eu_balance), 0)))).scalar() or 0
return {
"channels": channels_count,
"agents": agent_count,
"eu": round(float(balances), 2),
}
@router.get("/admin/channels")
async def admin_channels(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
channels = (await db.execute(select(Channel))).scalars().all()
items = [
{
"id": str(ch.id),
"name": ch.name,
"email": ch.email,
"commissionRate": ch.commission_rate,
"monthlyQuota": ch.monthly_quota,
"monthlyBudget": ch.monthly_budget,
}
for ch in channels
]
return {"items": items, "count": len(items)}
@router.post("/admin/channels/create")
async def admin_create_channel(payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
required = {"name", "email"}
missing = [k for k in required if not payload.get(k)]
if missing:
raise HTTPException(status_code=400, detail=f"Missing fields: {', '.join(missing)}")
channel = Channel(
name=payload["name"],
email=payload["email"],
commission_rate=payload.get("commissionRate", 0.0),
monthly_quota=payload.get("monthlyQuota", 0),
monthly_budget=payload.get("monthlyBudget", 0),
)
db.add(channel)
await db.commit()
await db.refresh(channel)
return {
"id": str(channel.id),
"name": channel.name,
"email": channel.email,
"commissionRate": channel.commission_rate,
"monthlyQuota": channel.monthly_quota,
"monthlyBudget": channel.monthly_budget,
}
@router.put("/admin/channels/{channel_id}/commission")
async def admin_update_channel_commission(channel_id: str, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
channel = await db.get(Channel, uuid.UUID(channel_id)) if channel_id else None
if not channel:
raise HTTPException(status_code=404, detail="channel not found")
channel.commission_rate = payload.get("commissionRate", channel.commission_rate)
db.add(channel)
await db.commit()
await db.refresh(channel)
return {
"id": str(channel.id),
"name": channel.name,
"commissionRate": channel.commission_rate,
}
@router.put("/admin/channels/{channel_id}/resources")
async def admin_update_channel_resources(channel_id: str, payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
channel = await db.get(Channel, uuid.UUID(channel_id)) if channel_id else None
if not channel:
raise HTTPException(status_code=404, detail="channel not found")
# 简化为保存配额到 ChannelAgentQuota
if payload.get("agents"):
for agent in payload["agents"]:
agent_id = uuid.UUID(agent.get("agentId"))
quota = ChannelAgentQuota(channel_id=channel.id, agent_id=agent_id, quantity=agent.get("quantity", 0))
db.add(quota)
db.add(channel)
await db.commit()
return {"id": str(channel.id), "updatedAt": _now()}
@router.get("/admin/channels/applications")
async def admin_channel_applications() -> Dict[str, Any]:
return {"items": list(store.resource_applications.values())}
@router.put("/admin/channels/applications/{request_id}/approve")
async def admin_channel_applications_approve(request_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
if request_id not in store.resource_applications:
raise HTTPException(status_code=404, detail="application not found")
store.resource_applications[request_id]["status"] = "approved" if payload.get("approved") else "rejected"
store.resource_applications[request_id]["reason"] = payload.get("reason", "")
return store.resource_applications[request_id]
@router.get("/admin/resources/models")
async def admin_resources_models(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
models = (await db.execute(select(ProviderModel).order_by(ProviderModel.created_at.desc()))).scalars().all()
items = [
{
"id": str(m.id),
"name": m.name,
"apiUrl": m.api_url,
"supportedModels": m.supported_models,
"rpm": m.rpm,
"tpm": m.tpm,
"isActive": m.is_active,
}
for m in models
]
return {"items": items}
@router.post("/admin/resources/models/add")
async def admin_resources_models_add(payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
required = {"name", "apiUrl", "apiKey", "supportedModels"}
missing = [k for k in required if not payload.get(k)]
if missing:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Missing fields: {', '.join(missing)}")
provider_model = ProviderModel(
name=payload["name"],
api_url=payload["apiUrl"],
api_key=payload["apiKey"],
supported_models=payload.get("supportedModels", []),
rpm=payload.get("rpm", 0),
tpm=payload.get("tpm", 0),
)
db.add(provider_model)
await db.commit()
await db.refresh(provider_model)
return {
"id": str(provider_model.id),
"name": provider_model.name,
"apiUrl": provider_model.api_url,
"supportedModels": provider_model.supported_models,
"rpm": provider_model.rpm,
"tpm": provider_model.tpm,
}
@router.get("/admin/resources/agents")
async def admin_resources_agents(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
agents = (await db.execute(select(Agent))).scalars().all()
return {"items": [{"id": str(a.id), "cpu": 1, "memory": 512, "maxInstances": 5} for a in agents]}
@router.put("/admin/resources/agents/{agent_id}")
async def admin_resources_agents_update(agent_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
return {"id": agent_id, **payload, "updatedAt": _now()}
@router.get("/admin/monitoring/agents")
async def admin_monitoring_agents() -> Dict[str, Any]:
metrics = await system_monitor.get_system_metrics()
return {"health": metrics, "updatedAt": _now()}
@router.get("/admin/billing/overview")
async def admin_billing_overview(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
billing_rows = (
await db.execute(
select(Billing.cost, Billing.eu_consumed, Billing.created_at, Billing.channel_id, Billing.tenant_id)
.order_by(Billing.created_at.desc())
.limit(200)
)
).all()
total_eu = sum(float(row.eu_consumed or 0) for row in billing_rows)
total_cost = sum(float(row.cost or 0) for row in billing_rows)
channels = [
{
"channelName": str(row.channel_id) if row.channel_id else "",
"calls": 1,
"totalEU": float(row.eu_consumed or 0),
"totalCost": float(row.cost or 0),
}
for row in billing_rows
]
tenants = [
{
"tenantName": str(row.tenant_id) if row.tenant_id else "",
"channelName": str(row.channel_id) if row.channel_id else "",
"calls": 1,
"totalEU": float(row.eu_consumed or 0),
"totalCost": float(row.cost or 0),
}
for row in billing_rows
]
call_records = [
{
"timestamp": row.created_at.isoformat() if row.created_at else _now(),
"channelName": str(row.channel_id) if row.channel_id else "",
"tenantName": str(row.tenant_id) if row.tenant_id else "",
"agentName": "",
"duration": 0,
"eu": float(row.eu_consumed or 0),
"cost": float(row.cost or 0),
}
for row in billing_rows
]
return {"channels": channels, "tenants": tenants, "callRecords": call_records, "totalEU": total_eu, "totalCost": total_cost}
@router.get("/admin/roles")
async def admin_roles() -> Dict[str, Any]:
return {"items": ["billing_admin", "operations_admin", "super_admin"]}
@router.post("/admin/admins/create")
async def admin_admins_create(payload: Dict[str, Any]) -> Dict[str, Any]:
admin_id = str(uuid.uuid4())
admin = {"id": admin_id, **payload, "createdAt": _now()}
store.channel_admins[admin_id] = admin
return admin
@router.get("/admin/providers/stats")
async def admin_providers_stats(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
models = (await db.execute(select(ProviderModel))).scalars().all()
return {
"providers": len(models),
"models": [
{
"id": str(m.id),
"name": m.name,
"apiUrl": m.api_url,
"supportedModels": m.supported_models,
}
for m in models
],
}
@router.get("/admin/channels/backend/stats")
async def admin_channels_backend_stats() -> Dict[str, Any]:
return {"channels": len(store.channels), "applications": len(store.resource_applications)}
# ----- Provider Management -----
@router.post("/providers/auth/login")
async def providers_login(payload: Dict[str, str], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
email = payload.get("email")
password = payload.get("password") or "temp-pass"
if not email:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="email is required")
user = await ensure_user(email, password, db)
if user.hashed_password and not verify_password(password, user.hashed_password):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid credentials")
token = create_access_token({"sub": str(user.id), "email": email, "role": "provider_admin"})
return {"token": token, "tokenType": "bearer", "email": email, "expiresIn": 60 * 60}
@router.get("/providers/models")
async def providers_models(db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
models = (await db.execute(select(ProviderModel))).scalars().all()
return {
"items": [
{
"id": str(m.id),
"name": m.name,
"apiUrl": m.api_url,
"supportedModels": m.supported_models,
"rpm": m.rpm,
"tpm": m.tpm,
}
for m in models
]
}
@router.post("/providers/models/add")
async def providers_models_add(payload: Dict[str, Any], db: AsyncSession = Depends(get_db)) -> Dict[str, Any]:
required = {"name", "apiUrl", "apiKey"}
missing = [k for k in required if not payload.get(k)]
if missing:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Missing fields: {', '.join(missing)}")
provider_model = ProviderModel(
name=payload["name"],
api_url=payload["apiUrl"],
api_key=payload["apiKey"],
supported_models=payload.get("supportedModels", []),
rpm=payload.get("rpm", 0),
tpm=payload.get("tpm", 0),
)
db.add(provider_model)
await db.commit()
await db.refresh(provider_model)
return {
"id": str(provider_model.id),
"name": provider_model.name,
"apiUrl": provider_model.api_url,
"supportedModels": provider_model.supported_models,
}
@router.get("/providers/data")
async def providers_data() -> Dict[str, Any]:
if not store.provider_data:
store.provider_data.append(
{
"id": str(uuid.uuid4()),
"name": "RapidAPI",
"category": "api",
"endpoints": len(store.gateway_apis),
}
)
return {"items": store.provider_data}