""" Tool settings router. Routes: GET /settings/tools → tool risk policy page POST /settings/tools → save max_risk + per-tool overrides """ import html as _html import json import logging from pathlib import Path import jwt from fastapi import APIRouter, Form, Request from fastapi.responses import HTMLResponse, RedirectResponse from auth_utils import COOKIE_NAME, decode_token, get_tool_policy, save_tool_policy from persona import list_user_personas from tools import TOOL_CATEGORIES, TOOL_RISK logger = logging.getLogger(__name__) router = APIRouter() _STATIC = Path(__file__).parent.parent / "static" _LAST_PERSONA_COOKIE = "cx_last_persona" def _get_session_user(request: Request) -> str | None: token = request.cookies.get(COOKIE_NAME) if not token: return None try: return decode_token(token) except jwt.InvalidTokenError: return None def _preferred_persona(request: Request, username: str) -> str: names = list_user_personas(username) if not names: return "" cookie_val = request.cookies.get(_LAST_PERSONA_COOKIE, "") return cookie_val if cookie_val in names else (names[0] if names else "") def _build_tool_table(policy: dict) -> str: """Generate the per-tool override table rows grouped by category.""" whitelist = set(policy.get("whitelist") or []) blacklist = set(policy.get("blacklist") or []) rows: list[str] = [] for category, tools in TOOL_CATEGORIES.items(): # Category header spanning all columns escaped_cat = _html.escape(category) rows.append( f'
| Tool | Risk | Auto status | Override | ' '
|---|
{success}
') if error: html = html.replace("", f'{error}
') return html @router.get("/settings/tools", include_in_schema=False) async def tools_page(request: Request): username = _get_session_user(request) if not username: return RedirectResponse("/login", status_code=302) back_persona = _preferred_persona(request, username) return HTMLResponse(_tools_page(username, back_persona)) @router.post("/settings/tools", include_in_schema=False) async def save_tools(request: Request): username = _get_session_user(request) if not username: return RedirectResponse("/login", status_code=302) back_persona = _preferred_persona(request, username) form = await request.form() max_risk = (form.get("max_risk") or "").strip() if max_risk not in ("", "low", "medium", "high"): max_risk = "" whitelist: list[str] = [] blacklist: list[str] = [] all_tools = [t for tools in TOOL_CATEGORIES.values() for t in tools] for tool in all_tools: val = (form.get(f"override_{tool}") or "").strip() if val == "whitelist": whitelist.append(tool) elif val == "blacklist": blacklist.append(tool) # Merge into existing policy (preserve allow/deny confirmation-gate fields) policy = get_tool_policy(username) if max_risk: policy["max_risk"] = max_risk else: policy.pop("max_risk", None) policy["whitelist"] = whitelist policy["blacklist"] = blacklist save_tool_policy(username, policy) logger.info( "tool policy saved for %s: max_risk=%s whitelist=%d blacklist=%d", username, max_risk or "none", len(whitelist), len(blacklist), ) return HTMLResponse(_tools_page( username, back_persona, success=f"Tool policy saved — max risk: {max_risk or 'none'}, " f"{len(whitelist)} whitelisted, {len(blacklist)} blacklisted.", ))