from __future__ import annotations from fastapi import APIRouter, HTTPException, Request from pydantic import BaseModel, Field from backend.oracle.action_service import oracle_action_service from backend.oracle.natural_db_agent import natural_db_agent from backend.oracle.persona_service import persona_service from backend.services.mcp_registry import mcp_registry from backend.services.nemoclaw_runtime import nemoclaw_runtime from backend.services.runtime_llm_service import runtime_llm_service router = APIRouter() class WorkflowPreviewRequest(BaseModel): prompt: str = Field(..., min_length=1, max_length=4096) tenant_id: str = "tenant_velocity" actor_role: str = "sales_director" class MCPExecuteRequest(BaseModel): tool_name: str = Field(..., min_length=1, max_length=128) query: str = Field(..., min_length=1, max_length=1024) class OracleWritebackRequest(BaseModel): action_id: str tenant_id: str = "tenant_velocity" actor_id: str = "oracle_operator" target_entity_type: str = Field(..., min_length=1, max_length=64) target_entity_id: str = Field(..., min_length=1, max_length=128) action_type: str = Field(default="lead_writeback", min_length=1, max_length=128) writeback_payload: dict = Field(default_factory=dict) class OracleQueryRequest(BaseModel): prompt: str = Field(..., min_length=1, max_length=4096) row_limit: int = Field(default=100, ge=1, le=500) context: dict = Field(default_factory=dict) @router.get("/health") async def oracle_health() -> dict: return { "status": "ok", "persona": await persona_service.health(), "mcp_tools": mcp_registry.list_tools(), "runtime_llm": await runtime_llm_service.list_providers(), } @router.get("/data-health") async def oracle_data_health(request: Request) -> dict: pool = getattr(request.app.state, "db_pool", None) if pool is None: raise HTTPException(status_code=503, detail="Database unavailable.") async with pool.acquire() as conn: data = await natural_db_agent.data_health(conn) return { "status": "ok", "data": data, } @router.get("/schema-catalog") async def oracle_schema_catalog(request: Request) -> dict: pool = getattr(request.app.state, "db_pool", None) if pool is None: raise HTTPException(status_code=503, detail="Database unavailable.") async with pool.acquire() as conn: catalog = await natural_db_agent.schema_catalog(conn) return {"status": "ok", "data": catalog} @router.post("/query") async def oracle_query(request: Request, payload: OracleQueryRequest) -> dict: pool = getattr(request.app.state, "db_pool", None) if pool is None: raise HTTPException(status_code=503, detail="Database unavailable.") async with pool.acquire() as conn: result = await natural_db_agent.execute_prompt(payload.prompt, row_limit=payload.row_limit, conn=conn) return {"status": "ok", "data": result.as_dict()} @router.get("/mcp/tools") async def oracle_mcp_tools() -> dict: return {"status": "ok", "data": mcp_registry.list_tools()} @router.post("/mcp/execute") async def oracle_mcp_execute(request: Request, payload: MCPExecuteRequest) -> dict: pool = getattr(request.app.state, "db_pool", None) result = await mcp_registry.execute(payload.tool_name, payload.query, crm_pool=pool) return {"status": "ok", "data": result} @router.post("/workflow/preview") async def workflow_preview(payload: WorkflowPreviewRequest) -> dict: persona_plan = await persona_service.plan_for_prompt( prompt=payload.prompt, tenant_id=payload.tenant_id, actor_role=payload.actor_role, ) return { "status": "ok", "data": { "persona_plan": persona_plan, "workflow": nemoclaw_runtime.build_workflow_dispatch( prompt=payload.prompt, tenant_id=payload.tenant_id, actor_role=payload.actor_role, component_templates=persona_plan["recommendedTemplates"], ), }, } @router.get("/actions") async def list_oracle_actions(status: str | None = None, limit: int = 50) -> dict: actions = await oracle_action_service.list_actions(status=status, limit=limit) return {"status": "ok", "data": actions, "meta": {"count": len(actions)}} @router.get("/actions/{action_id}") async def get_oracle_action(action_id: str) -> dict: action = await oracle_action_service.get_action(action_id) if not action: raise HTTPException(status_code=404, detail=f"Oracle action '{action_id}' not found.") return {"status": "ok", "data": action} @router.post("/actions/writeback") async def apply_oracle_writeback(request: Request, payload: OracleWritebackRequest) -> dict: result = await oracle_action_service.apply_writeback(payload.model_dump()) if hasattr(request.app.state, "broadcast_crm_event"): await request.app.state.broadcast_crm_event( { "type": "oracle_writeback", "entity": payload.target_entity_type, "entity_id": payload.target_entity_id, "action_id": payload.action_id, "payload": result["resultPayload"], } ) return {"status": "ok", "data": result}