Specs stay at repo root (cross-VM). Move deploy and code into logical projects with README per domain, updated manifest.yaml, and symlinks at legacy paths for VM122 backward compatibility.
63 lines
2.9 KiB
Python
63 lines
2.9 KiB
Python
"""Agentic API — Spec 029."""
|
|
from __future__ import annotations
|
|
from datetime import datetime, timezone
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from app import auth
|
|
from app.agents import llm_client, runner, store
|
|
|
|
router = APIRouter(prefix="/api/v1/agents", tags=["agents"])
|
|
|
|
def _db():
|
|
conn = auth.db()
|
|
try: yield conn
|
|
finally: conn.close()
|
|
|
|
def _ops_view(user):
|
|
if user.role not in ("super_admin","ops_lead","technician","noc","agentic_operator"):
|
|
raise HTTPException(403, "insufficient permissions")
|
|
|
|
@router.get("/health")
|
|
def agents_health():
|
|
return {"status":"ok","tier":"t1" if llm_client.AGENTIC_LLM_ENABLED else "t0",
|
|
"ollama": llm_client.ollama_available(), "ollama_url": llm_client.OLLAMA_BASE_URL,
|
|
"model": llm_client.AGENTIC_LLM_MODEL}
|
|
|
|
@router.get("/scenarios")
|
|
def list_scenarios(user=Depends(auth.get_current_user), conn=Depends(_db)):
|
|
_ops_view(user); runner.sync_registry(conn); conn.commit()
|
|
return {"scenarios": store.list_scenarios(conn)}
|
|
|
|
@router.get("/findings")
|
|
def list_findings(user=Depends(auth.get_current_user), conn=Depends(_db), severity: str|None=None, limit: int=Query(50, ge=1, le=200), open_only: bool=True):
|
|
_ops_view(user)
|
|
return {"findings": store.list_findings(conn, severity=severity, limit=limit, open_only=open_only)}
|
|
|
|
@router.post("/findings/{finding_id}/ack")
|
|
def ack_finding(finding_id: int, user=Depends(auth.get_current_user), conn=Depends(_db)):
|
|
_ops_view(user)
|
|
if not conn.execute("SELECT id FROM agent_findings WHERE id=?", (finding_id,)).fetchone():
|
|
raise HTTPException(404, "not found")
|
|
now = datetime.now(timezone.utc).isoformat()
|
|
conn.execute("UPDATE agent_findings SET acknowledged_at=?, acknowledged_by=? WHERE id=?", (now, user.username, finding_id))
|
|
store.log_event(conn, event_type="finding.ack", message=f"#{finding_id}", payload={"by": user.username})
|
|
conn.commit()
|
|
return {"ok": True, "id": finding_id}
|
|
|
|
@router.get("/action-log")
|
|
def action_log(user=Depends(auth.get_current_user), conn=Depends(_db), limit: int=Query(100, ge=1, le=500)):
|
|
_ops_view(user)
|
|
return {"events": store.list_action_log(conn, limit=limit)}
|
|
|
|
@router.post("/runs/{scenario_id}")
|
|
def trigger_run(scenario_id: str, user=Depends(auth.get_current_user), conn=Depends(_db)):
|
|
if user.role not in ("super_admin","ops_lead"): raise HTTPException(403, "insufficient permissions")
|
|
r = runner.run_scenario(conn, scenario_id, trigger=f"manual:{user.username}")
|
|
conn.commit(); return r
|
|
|
|
@router.post("/internal/tick")
|
|
def internal_tick(user=Depends(auth.require_internal_or_user), conn=Depends(_db)):
|
|
kb = runner.index_specs_kb(conn)
|
|
result = runner.run_all_enabled(conn, trigger="cron")
|
|
store.log_event(conn, event_type="tick.complete", message=f"kb={kb} runs={result['total']}", payload={"kb": kb, **result})
|
|
conn.commit()
|
|
return {"ok": True, "kb_indexed": kb, **result}
|