package api // Request/response types of the daemon HTTP contract (binding fields). The // CLI and other consumers (MCP) use the same types: one source, no contract // drift between daemon and client. // SpeakRequest is the body of POST /speak. type SpeakRequest struct { // Text is the announcement text (required). Text string `json:"text"` // Priority: low | normal | high (default normal when empty). Priority string `json:"priority,omitempty"` // Provider pins the provider chain (--provider). Provider string `json:"provider,omitempty"` // Voice pins a voice ID (--voice). Voice string `json:"voice,omitempty"` // Lang pins the language de | en (empty = automatic detection). Lang string `json:"lang,omitempty"` // Now lets the announcement jump ahead of all waiting ones (--now). Now bool `json:"now,omitempty"` } // SpeakResponse is the response of POST /speak: 202 {"id":"..."}; with mute // active 202 {"id":"","muted":true} (announcement discarded; documented // behaviour, not an error). Discarded spam is reported just as // documented: 202 {"id":"","discarded":"rate_limit"|"quiet_hours"}, plus // "toast":true when the quiet-hours announcement was replaced by a // notification (quiet_mode = "toast"). type SpeakResponse struct { ID string `json:"id"` Muted bool `json:"muted,omitempty"` // Discarded is the discard reason ("rate_limit" | "quiet_hours"); empty // when the announcement was admitted. Discarded string `json:"discarded,omitempty"` // Toast reports the replacement by a notification. Toast bool `json:"toast,omitempty"` // Truncated reports that the text was cut to queue.max_text and // admitted that way (the law against read-aloud long answers). Truncated bool `json:"truncated,omitempty"` } // CurrentItem describes the running item in GET /status. type CurrentItem struct { ID string `json:"id"` Text string `json:"text"` Priority string `json:"priority"` Now bool `json:"now"` } // StatusResponse is the response of GET /status: queue length, current item // (null when idle), mute state, active provider, daemon version. QueueIDs // lists the waiting IDs in processing order; the CLI needs them for its // --wait polling. The law state (rate_limit, quiet_hours) and mcp/cors // (compat fields: MCP endpoint present, configured CORS origins) are // additive. 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 reports that the daemon offers the MCP endpoint (always true; the // field exists for an additive compat view of clients/browsers). MCP bool `json:"mcp"` // Cors is the configured CORS allow list of POST /speak (empty [] = // door closed). Always present (even when empty), not omitempty. Cors []string `json:"cors"` } // RateLimitState is the rate-limit state in GET /status. type RateLimitState struct { // Interval is the configured minimum spacing as a duration string // ("30s"; "0s" = unlimited). Interval string `json:"interval"` // AllowHighBurst reports whether priority=high may violate it. AllowHighBurst bool `json:"allow_high_burst"` // NextSlot is the next free announcement slot as an RFC3339 timestamp; // null when playback would be allowed immediately (or unlimited). NextSlot *string `json:"next_slot"` } // LimitsState holds the length and age laws in GET /status. type LimitsState struct { // MaxText is the upper bound of the announcement length in characters // (0 = unlimited). MaxText int `json:"max_text"` // MaxAge is the longest wait in the queue as a duration string ("2m0s"; // "0s" = unlimited). MaxAge string `json:"max_age"` } // QuietHoursState is the quiet-hours state in GET /status. type QuietHoursState struct { // Window is the configured window ("18:00-08:00"; "" = disabled). Window string `json:"window"` // Active reports whether local time is currently inside the window. Active bool `json:"active"` // Mode is the configured quiet-hours mode ("discard" | "toast"). Mode string `json:"mode"` // Until is the window end as an RFC3339 timestamp when active; // otherwise null. Until *string `json:"until"` } // PendingOrCurrent reports whether the announcement with the ID is still // waiting or currently playing; the basis of the CLI's --wait polling. If // it is neither waiting nor playing, it is done (or cancelled); in both // cases the caller has nothing left to wait for. 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 is the response of POST /cancel. type CancelResponse struct { Cancelled int `json:"cancelled"` } // MuteResponse is the response of POST /mute and POST /unmute. type MuteResponse struct { Muted bool `json:"muted"` } // ShutdownResponse is the response of POST /shutdown. type ShutdownResponse struct { OK bool `json:"ok"` }