Files
ai-agent/backend/schemas/notifications.py
Gabriel Vidal 2119af66d5 refactor(schemas): split backend/schemas.py into a schemas/ package
One module per API area (base, files, activity, skills, conversations,
notifications, forms, search, plans, projects, services, misc, diff, cron,
agents) on a shared base.Schema; __init__.py re-exports every model so
handlers keep writing schemas.<Name>. OpenAPI output is byte-identical.
PlanSummary lives in conversations.py to keep the module graph a DAG.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 23:55:47 +02:00

156 lines
4.4 KiB
Python

"""Notifications: the light feed behind the history + "new" badges, the
first-class webhooks + notify/ask log (``notify.py``) and the mail trigger
allowlist (``mail_trigger.py``)."""
from __future__ import annotations
from typing import Literal, Optional
from .base import Schema
from .conversations import ConvNotification
__all__ = [
"NotifConversation",
"NotificationsResponse",
"FeedNotification",
"UnreadNotificationsResponse",
"Webhook",
"WebhooksResponse",
"MailTriggerSendersResponse",
"DeliveryStatus",
"NotifyResult",
"AskRecord",
"NotifyLogEntry",
"NotifyLogResponse",
]
# ── notification feed (light source for the history + "new" badges) ──────────
class NotifConversation(Schema):
"""A conversation that recorded notify-done pushes, feed-relevant bits only."""
id: str
title: str
endedAt: Optional[str] = None
projects: list[str]
services: list[str]
notifications: list[ConvNotification]
class NotificationsResponse(Schema):
conversations: list[NotifConversation]
count: Optional[int] = None
class FeedNotification(Schema):
"""A flat notification feed item (the server-side mirror of the frontend's
FeedNotif), as returned by ``/api/notifications/unread``."""
id: str
convId: str
convTitle: str
title: str
body: str
type: str
url: str
image: Optional[str] = None
at: Optional[str] = None
projects: list[str]
services: list[str]
kind: Optional[str] = None
spoken: Optional[str] = None
class UnreadNotificationsResponse(Schema):
notifications: list[FeedNotification]
count: int # items returned (after the limit)
total: int # total unread (before the limit)
# ── first-class notifications: webhooks + notify/ask log (notify.py) ─────────
class Webhook(Schema):
id: str
name: str
url: str
events: list[Literal["notify", "ask"]]
enabled: bool
class WebhooksResponse(Schema):
webhooks: list[Webhook]
# ── mail trigger allowlist (mail_trigger.py) ─────────────────────────────────
class MailTriggerSendersResponse(Schema):
"""``emails`` are allowed to trigger; ``disabled`` are kept but switched off."""
emails: list[str]
disabled: list[str]
class DeliveryStatus(Schema):
"""One webhook's delivery outcome for a forwarded notification."""
webhook: str
ok: bool
status: int
class NotifyResult(Schema):
ok: bool
id: str
delivered: list[DeliveryStatus]
class AskRecord(Schema):
"""An ask (choice question) with its lifecycle state."""
id: str
kind: Literal["ask"]
title: str
body: str
type: str
url: str
# Image shown in the notification (notify-done `--image`): full URL or a
# `/local/<file>` path served from Home Assistant's www/ dir.
image: Optional[str] = None
at: str
sessionId: str
delivered: list[DeliveryStatus]
options: list[str]
status: Literal["pending", "answered", "expired"]
answer: Optional[str] = None
answerIndex: Optional[int] = None
answeredAt: Optional[str] = None
expiresAt: Optional[str] = None
class NotifyLogEntry(Schema):
"""One recorded notification (kind "notify") or ask (kind "ask")."""
id: str
kind: Literal["notify", "ask"]
title: str
body: str
type: str
url: str
# Image shown in the notification (notify-done `--image`): full URL or a
# `/local/<file>` path served from Home Assistant's www/ dir.
image: Optional[str] = None
at: str
sessionId: str
delivered: list[DeliveryStatus]
# The line the desk phone read aloud, kept so the UI can replay the sound.
spoken: Optional[str] = None
# ask-only lifecycle fields (absent on plain pushes)
options: Optional[list[str]] = None
status: Optional[Literal["pending", "answered", "expired",
"sent", "acted"]] = None
answer: Optional[str] = None
answerIndex: Optional[int] = None
answeredAt: Optional[str] = None
expiresAt: Optional[str] = None
# push-only: extra buttons (`notify send --action-cmd`) and the tap
actions: Optional[list[dict]] = None
actionIndex: Optional[int] = None
actionTitle: Optional[str] = None
actedAt: Optional[str] = None
class NotifyLogResponse(Schema):
notifications: list[NotifyLogEntry]
count: Optional[int] = None