"""FastAPI reference backend for the Nova Act Suite.""" from datetime import datetime, timezone from typing import Any from uuid import uuid4 from fastapi import Cookie, FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel, Field app = FastAPI(title="Nova Act Suite API", version="1.0.0", description="Shared auth, tab communication, Nova Act actions, and DataHub records.") app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) class TabEventIn(BaseModel): source: str = Field(..., examples=["usersync"]) target: str | None = Field(default="broadcast", examples=["graph"]) action: str = Field(..., examples=["view.changed"]) payload: dict[str, Any] = Field(default_factory=dict) class TabEvent(TabEventIn): id: str timestamp: datetime class NovaActSessionIn(BaseModel): target_url: str = "https://nova.amazon.com/" instruction: str = "Open the target and summarize the page state." class LimitationInjectionIn(BaseModel): limitation_ids: list[str] = Field(default_factory=list) prompt: str | None = None class OmniParserElementIn(BaseModel): id: str type: str = "panel" label: str bbox: list[float] = Field(default_factory=lambda: [0, 0, 100, 100]) interactable: bool = False description: str = "current application viewport" class OmniParserParseIn(BaseModel): screen_name: str = "Nova Act Suite" elements: list[OmniParserElementIn] = Field(default_factory=list) _events: list[TabEvent] = [] _mindwalk_limitations = [ {"id": "limit-safe-navigation", "label": "Safe navigation", "guardrail": "Only navigate, read, extract, and verify unless the active persona explicitly allows mutation.", "injectInto": ["nova-act", "graph"]}, {"id": "limit-sensitive-fields", "label": "Sensitive-field pause", "guardrail": "Pause and request supervisor review before entering passwords, tokens, payment data, or private identifiers.", "injectInto": ["nova-act", "usersync", "datahub"]}, {"id": "limit-evidence-first", "label": "Evidence-first output", "guardrail": "Return file, DOM, API, or screenshot evidence with every persona-facing recommendation.", "injectInto": ["dev", "graph", "oasis"]}, ] @app.get("/api/user") def get_user(hf_user: str | None = Cookie(default=None)) -> dict[str, Any]: if not hf_user: raise HTTPException(status_code=401, detail="Not authenticated") return {"shared_hf_cookie": True, "raw": hf_user} @app.post("/api/tabs/events", status_code=201) def publish_event(event: TabEventIn) -> TabEvent: saved = TabEvent(id=str(uuid4()), timestamp=datetime.now(timezone.utc), **event.model_dump()) _events.insert(0, saved) del _events[100:] return saved @app.get("/api/tabs/events") def list_events() -> list[TabEvent]: return _events @app.post("/api/nova-act/session", status_code=202) def create_nova_act_session(session: NovaActSessionIn, hf_user: str | None = Cookie(default=None)) -> dict[str, Any]: return {"id": str(uuid4()), "status": "queued", "sharedAuth": bool(hf_user), **session.model_dump()} @app.get("/api/datahub/records") def list_datahub_records() -> list[dict[str, Any]]: return [] @app.get("/api/mindwalk/graph") def get_mindwalk_graph_contract() -> dict[str, Any]: return {"source": "cosmtrek/mindwalk-inspired-adapter", "model": "tabs-subviews-personas-limitations", "touchStates": ["unvisited", "seen", "read", "edited", "limited"], "limitations": _mindwalk_limitations} @app.post("/api/nova-act/limitations", status_code=202) def inject_nova_act_limitations(injection: LimitationInjectionIn) -> dict[str, Any]: requested = set(injection.limitation_ids) selected = [item for item in _mindwalk_limitations if not requested or item["id"] in requested] return {"accepted": True, "selected": selected, "prompt": injection.prompt} @app.post("/api/omniparser/parse") def parse_ui_for_llm(payload: OmniParserParseIn) -> dict[str, Any]: elements = payload.elements or [OmniParserElementIn(id="viewport", label=payload.screen_name)] interactable = [element for element in elements if element.interactable] phrases = [f"Screen: {payload.screen_name}.", f"Detected {len(elements)} UI elements; {len(interactable)} are interactable."] phrases.extend([f"Use {element.type} \"{element.label}\" at bbox [{', '.join(map(str, element.bbox))}] to {element.description}." for element in interactable]) return {"source": "microsoft/OmniParser-adapter", "screenshotId": f"{payload.screen_name.lower().replace(' ', '-')}-synthetic", "elements": [element.model_dump() for element in elements], "llmLanguage": " ".join(phrases)}