refactor(photon): reuse the /send-poll primitive from #43665 for poll clarify

Follow-up to the #48194 pick: it was written before #43665 landed and
re-added its own /send-poll sidecar route and poll import. Collapse the
duplicates:

- keep #43665's /send-poll route (>=2 trimmed string options) as the
  single sidecar implementation; drop #48194's variant
- drop the duplicated poll import in the sidecar destructure
- make adapter.send_poll() a thin wrapper over _sidecar_send_poll(), the
  one /send-poll client (shared with the poll-backed clarify path), and
  align its validation to the sidecar's >=2-options contract
This commit is contained in:
Teknium 2026-07-28 12:24:53 -07:00
parent fe95194c59
commit 324f310263
2 changed files with 11 additions and 43 deletions

View file

@ -1631,21 +1631,13 @@ class PhotonAdapter(BasePlatformAdapter):
options: list[str],
metadata: Optional[Dict[str, Any]] = None,
) -> SendResult:
"""Send a native iMessage poll through Photon Spectrum."""
choices = [str(option).strip() for option in options if str(option).strip()]
if len(choices) < 2:
return SendResult(
success=False,
error="Photon polls require at least two non-empty options",
)
try:
data = await self._sidecar_call(
"/send-poll",
{"spaceId": chat_id, "title": title.strip(), "options": choices},
)
except Exception as e:
return SendResult(success=False, error=str(e))
return SendResult(success=True, message_id=data.get("messageId"))
"""Send a native iMessage poll through Photon Spectrum.
Thin public wrapper over :meth:`_sidecar_send_poll`, the single
implementation of the sidecar's ``/send-poll`` primitive (also used
by the poll-backed clarify path).
"""
return await self._sidecar_send_poll(chat_id, title, list(options or []))
async def send_effect(
self,
@ -2022,8 +2014,8 @@ class PhotonAdapter(BasePlatformAdapter):
opts = [str(o).strip() for o in (options or []) if str(o).strip()]
if not title or not title.strip():
return SendResult(success=False, error="poll title is required")
if not opts:
return SendResult(success=False, error="poll needs at least one option")
if len(opts) < 2:
return SendResult(success=False, error="poll needs at least two options")
body: Dict[str, Any] = {
"spaceId": space_id,
"title": title.strip()[: self.MAX_MESSAGE_LENGTH],

View file

@ -33,14 +33,12 @@
// "reactionId": "..." | null (restart-recovery fallback)}
// - POST /send-poll -> {"ok": true, "messageId": "..."}
// body: {"spaceId": "...", "title": "...", "options": ["...", "..."]}
// Sends a native poll (orange iMessage poll bubble). A tap streams
// back inbound as a `poll_option` event ({title, selected}).
// - POST /send-effect -> {"ok": true, "messageId": "..."}
// body: {"spaceId": "...", "text": "...", "effect": "confetti" | ...}
// - POST /typing -> {"ok": true}
// body: {"spaceId": "...", "state": "start" | "stop"}
// - POST /send-poll -> {"ok": true, "messageId": "..."}
// body: {"spaceId": "...", "title": "...", "options": ["A", "B", ...]}
// Sends a native poll (orange iMessage poll bubble). A tap streams
// back inbound as a `poll_option` event ({title, selected}).
// - POST /shutdown -> {"ok": true}; then process exits
//
// On SIGINT/SIGTERM the sidecar calls `app.stop()` (3s graceful) before
@ -257,7 +255,6 @@ try {
text: spectrumText,
markdown: spectrumMarkdown,
typing: spectrumTyping,
poll: spectrumPoll,
} = await import("spectrum-ts"));
({ imessage, effect: imessageEffect } = await import("spectrum-ts/providers/imessage"));
} catch (e) {
@ -858,27 +855,6 @@ const server = http.createServer(async (req, res) => {
}
return ok(res, { messageId: result?.id || null });
}
if (req.url === "/send-poll") {
const { spaceId, title, options } = body || {};
if (!spaceId || typeof title !== "string" || !title) {
return badRequest(res, "spaceId and title are required");
}
if (!Array.isArray(options) || options.length < 1) {
return badRequest(res, "options must be a non-empty array");
}
// spectrum-ts' poll() builder accepts string options; it degrades to a
// numbered text list on platforms without native polls (so the gateway
// text-intercept still resolves the clarify there).
const opts = options
.map((o) => (typeof o === "string" ? o : o?.title))
.filter((o) => typeof o === "string" && o);
if (!opts.length) {
return badRequest(res, "options must contain at least one string");
}
const space = await resolveSpace(spaceId);
const result = await space.send(spectrumPoll(title, ...opts));
return ok(res, { messageId: result?.id || null });
}
if (req.url === "/react") {
const { spaceId, messageId, emoji } = body || {};
if (!spaceId || !messageId || typeof emoji !== "string" || !emoji) {