| 1 | from fastapi import APIRouter |
| 2 | from fastapi import Depends |
| 3 | from fastapi import Security |
| 4 | from loguru import logger |
| 5 | from sqlalchemy.ext.asyncio import AsyncSession |
| 6 | from starlette.responses import StreamingResponse |
| 7 | |
| 8 | from app.auth.utils import AuthHandler |
| 9 | from app.connectors.talon.schema.talon import TalonInvestigateRequest |
| 10 | from app.connectors.talon.schema.talon import TalonInvestigateResponse |
| 11 | from app.connectors.talon.schema.talon import TalonJobResponse |
| 12 | from app.connectors.talon.schema.talon import TalonMessageRequest |
| 13 | from app.connectors.talon.schema.talon import TalonStatusResponse |
| 14 | from app.connectors.talon.schema.talon import TalonTemplatesResponse |
| 15 | from app.connectors.talon.services.talon import get_talon_job |
| 16 | from app.connectors.talon.services.talon import get_talon_status |
| 17 | from app.connectors.talon.services.talon import investigate_alert |
| 18 | from app.connectors.talon.services.talon import list_talon_templates |
| 19 | from app.connectors.talon.services.talon import stream_talon_message |
| 20 | from app.db.db_session import get_db |
| 21 | |
| 22 | talon_router = APIRouter() |
| 23 | |
| 24 | |
| 25 | @talon_router.post( |
| 26 | "/message", |
| 27 | response_class=StreamingResponse, |
| 28 | description="Send a message to Talon and stream the SSE response", |
| 29 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 30 | ) |
| 31 | async def send_message(request: TalonMessageRequest) -> StreamingResponse: |
| 32 | """Send a message to Talon and stream the response as SSE.""" |
| 33 | logger.info(f"Sending message to Talon: {request.message}") |
| 34 | return StreamingResponse( |
| 35 | stream_talon_message(request), |
| 36 | media_type="text/event-stream", |
| 37 | headers={ |
| 38 | "Cache-Control": "no-cache", |
| 39 | "Connection": "keep-alive", |
| 40 | "X-Accel-Buffering": "no", |
| 41 | }, |
| 42 | ) |
| 43 | |
| 44 | |
| 45 | @talon_router.post( |
| 46 | "/investigate", |
| 47 | response_model=TalonInvestigateResponse, |
| 48 | description="Trigger a Talon investigation for a specific alert", |
| 49 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 50 | ) |
| 51 | async def trigger_investigation(request: TalonInvestigateRequest) -> TalonInvestigateResponse: |
| 52 | """Trigger an investigation for a specific alert.""" |
| 53 | logger.info(f"Triggering investigation for alert ID: {request.alert_id}") |
| 54 | return await investigate_alert(request) |
| 55 | |
| 56 | |
| 57 | @talon_router.get( |
| 58 | "/status", |
| 59 | response_model=TalonStatusResponse, |
| 60 | description="Get the current Talon service status", |
| 61 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 62 | ) |
| 63 | async def get_status() -> TalonStatusResponse: |
| 64 | """Get the current status of the Talon service.""" |
| 65 | logger.info("Fetching Talon status") |
| 66 | return await get_talon_status() |
| 67 | |
| 68 | |
| 69 | @talon_router.get( |
| 70 | "/templates", |
| 71 | response_model=TalonTemplatesResponse, |
| 72 | description="List the prompt templates available in NanoClaw's CoPilot group (for replay picker)", |
| 73 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 74 | ) |
| 75 | async def get_templates() -> TalonTemplatesResponse: |
| 76 | """Proxy NanoClaw GET /templates — read-only template metadata, no bodies.""" |
| 77 | logger.info("Fetching Talon templates list") |
| 78 | return await list_talon_templates() |
| 79 | |
| 80 | |
| 81 | @talon_router.get( |
| 82 | "/jobs/{alert_id}", |
| 83 | response_model=TalonJobResponse, |
| 84 | description="Get the Talon job status and report for a specific alert", |
| 85 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 86 | ) |
| 87 | async def get_job(alert_id: int, db: AsyncSession = Depends(get_db)) -> TalonJobResponse: |
| 88 | """Get the job status for a specific alert.""" |
| 89 | logger.info(f"Fetching Talon job for alert ID: {alert_id}") |
| 90 | return await get_talon_job(alert_id, db) |