package api // Request/Response-Typen des Daemon-HTTP-Vertrags (Task-04-Brief, bindende // Felder). Die CLI und spätere Konsumenten (MCP, Task 06) benutzen dieselben // Typen — eine Quelle, kein Contract-Drift zwischen Daemon und Client. // SpeakRequest ist der Body von POST /speak. type SpeakRequest struct { // Text ist der Ansage-Text (Pflicht). Text string `json:"text"` // Priority: low | normal | high (Default normal, wenn leer). Priority string `json:"priority,omitempty"` // Provider pinnt die Provider-Kette (--provider). Provider string `json:"provider,omitempty"` // Voice pinnt eine Voice-ID (--voice). Voice string `json:"voice,omitempty"` // Lang pinnt die Sprache de | en (leer = automatische Erkennung). Lang string `json:"lang,omitempty"` // Now lässt die Ansage vor allen wartenden springen (--now). Now bool `json:"now,omitempty"` } // SpeakResponse ist die Antwort von POST /speak: 202 {"id":"…"} — bei // aktivem Mute 202 {"id":"","muted":true} (Ansage verworfen; dokumentiertes // Verhalten, kein Fehler). Seit Task 05 meldet verworfenen Spam ebenso // dokumentiert: 202 {"id":"","discarded":"rate_limit"|"quiet_hours"} — // plus "toast":true, wenn die Ruhezeit-Ansage durch eine Benachrichtigung // ersetzt wurde (quiet_mode = "toast"). type SpeakResponse struct { ID string `json:"id"` Muted bool `json:"muted,omitempty"` // Discarded ist der Verwurfs-Grund ("rate_limit" | "quiet_hours"); // leer, wenn die Ansage aufgenommen wurde. Discarded string `json:"discarded,omitempty"` // Toast meldet die Ersetzung durch eine Benachrichtigung. Toast bool `json:"toast,omitempty"` // Truncated meldet, dass der Text auf queue.max_text gekürzt und so // aufgenommen wurde (Gesetz gegen vorgelesene Langantworten). Truncated bool `json:"truncated,omitempty"` } // CurrentItem beschreibt das laufende Item in GET /status. type CurrentItem struct { ID string `json:"id"` Text string `json:"text"` Priority string `json:"priority"` Now bool `json:"now"` } // StatusResponse ist die Antwort von GET /status: Queue-Länge, aktuelles // Item (null im Leerlauf), Mute-State, aktiver Provider, Daemon-Version. // QueueIDs ist die Ergänzung der wartenden IDs in Abarbeitungs-Reihenfolge // — die CLI braucht sie für ihr --wait-Polling. Task 05 ergänzt additiv // den Gesetz-State (rate_limit, quiet_hours); Task 06 additiv mcp/cors // (Compat-Felder: MCP-Endpoint vorhanden, konfigurierte CORS-Origins). type StatusResponse struct { Version string `json:"version"` Queue int `json:"queue"` QueueIDs []string `json:"queue_ids,omitempty"` Current *CurrentItem `json:"current"` Muted bool `json:"muted"` Provider string `json:"provider"` RateLimit *RateLimitState `json:"rate_limit,omitempty"` QuietHours *QuietHoursState `json:"quiet_hours,omitempty"` Limits *LimitsState `json:"limits,omitempty"` // MCP meldet, dass der Daemon den MCP-Endpoint anbietet (immer true; // Feld existiert für additive Compat-Sicht von Clients/Browsern). MCP bool `json:"mcp"` // Cors ist die konfigurierte CORS-Erlaubnisliste von POST /speak // (leeres [] = Tür zu). Immer vorhanden (auch leer), nicht omitempty. Cors []string `json:"cors"` } // RateLimitState ist der Rate-Limit-State in GET /status (Task 05). type RateLimitState struct { // Interval ist der konfigurierte Mindestabstand als Dauer-String // ("30s"; "0s" = unbegrenzt). Interval string `json:"interval"` // AllowHighBurst meldet, ob priority=high verstoßen darf. AllowHighBurst bool `json:"allow_high_burst"` // NextSlot ist der nächste freie Ansage-Slot als RFC3339-Zeitstempel; // null, wenn sofort gespielt werden dürfte (oder unbegrenzt). NextSlot *string `json:"next_slot"` } // LimitsState sind die Längen- und Alters-Gesetze in GET /status. type LimitsState struct { // MaxText ist die Obergrenze der Ansage-Länge in Zeichen (0 = unbegrenzt). MaxText int `json:"max_text"` // MaxAge ist die längste Wartezeit in der Queue als Dauer-String // ("2m0s"; "0s" = unbegrenzt). MaxAge string `json:"max_age"` } // QuietHoursState ist der Ruhezeit-State in GET /status (Task 05). type QuietHoursState struct { // Window ist das konfigurierte Fenster ("18:00-08:00"; "" = deaktiviert). Window string `json:"window"` // Active meldet, ob die lokale Zeit gerade im Fenster liegt. Active bool `json:"active"` // Mode ist der konfigurierte Ruhezeit-Modus ("discard" | "toast"). Mode string `json:"mode"` // Until ist das Fenster-Ende als RFC3339-Zeitstempel, wenn aktiv; // sonst null. Until *string `json:"until"` } // PendingOrCurrent meldet, ob die Ansage mit der ID noch wartet oder // gerade läuft — Grundlage des --wait-Pollings der CLI. Ist die Ansage // weder wartend noch laufend, ist sie fertig (oder abgebrochen) — in // beiden Fällen hat der Aufrufer nichts mehr zu warten. func (s StatusResponse) PendingOrCurrent(id string) bool { if s.Current != nil && s.Current.ID == id { return true } for _, qid := range s.QueueIDs { if qid == id { return true } } return false } // CancelResponse ist die Antwort von POST /cancel. type CancelResponse struct { Cancelled int `json:"cancelled"` } // MuteResponse ist die Antwort von POST /mute und POST /unmute. type MuteResponse struct { Muted bool `json:"muted"` } // ShutdownResponse ist die Antwort von POST /shutdown. type ShutdownResponse struct { OK bool `json:"ok"` }