/** * Pure rendering of threaded-session frames into Telegram send specs. * * The daemon receives the additive `ServerMessage` frames (identity_header, * context_update, turn_stream, image_attachment, config_update) over the session * WS and must turn each into a Bot API call scoped to the session's forum topic * (`message_thread_id`), throttled through the shared rate-limit pool. This * module is the pure frame→send mapping (including the priority lane and live- * edit coalesce key), so rendering is unit-testable without a live Bot API. */ import type { RateLimitLane } from "./rate-limit-pool"; /** A Telegram send derived from a threaded frame (topic id is applied by the daemon). */ export interface ThreadedSend { method: "sendMessage" | "sendPhoto" | "sendDocument"; /** Rate-limit lane for prioritisation/fairness. */ lane: RateLimitLane; /** Message text (sendMessage) or media caption (sendPhoto/sendDocument). */ text?: string; /** Base64 image bytes for sendPhoto. */ photoBase64?: string; /** Base64 file bytes for sendDocument. */ documentBase64?: string; /** Uploaded media MIME type. */ mime?: string; /** Suggested document filename. */ fileName?: string; /** Coalesce key for live edits (same key collapses to the latest). */ coalesceKey?: string; /** True for the one-time identity header (the daemon pins it once). */ identity?: boolean; /** * When true the daemon may deliver this as an in-place edit of the message * previously sent under the same `(sessionId, coalesceKey)` instead of a new * message. Set for streamed turn frames so live + finalized share one message. */ editable?: boolean; /** Rich message class metadata. Only finalized final-answer sends are ever * rich-promoted; the daemon gate (`shouldPromoteRich`) requires exactly this. */ richClass?: "final"; /** Rich final-answer markdown (raw). Delivery marker derived ONLY from a frame's `finalAnswer` bit; never inferred from `phase`. */ richMarkdown?: string; /** Live-turn raw markdown for opt-in draft streaming (set ONLY on non-finalized turn frames; never triggers rich-final promotion, which requires `lane === "finalized"`). */ richDraftMarkdown?: string; /** * True for a terminal tool_activity frame (phase completed/failed/cancelled/ * unknown). The daemon evicts the tool's `tool:` live-message entry * after delivering it, so completed tools do not accumulate unbounded across a * long session (concurrent in-flight `started` entries are retained). */ terminal?: boolean; } interface ThreadedFrame { type?: unknown; sessionId?: unknown; repo?: unknown; branch?: unknown; machine?: unknown; title?: unknown; telegramTopicsEnabled?: unknown; lastMessage?: unknown; task?: unknown; goal?: unknown; tokenUsage?: unknown; model?: unknown; diff?: unknown; cwd?: unknown; phase?: unknown; finalAnswer?: boolean; text?: unknown; messageRef?: unknown; turnRef?: unknown; toolCallId?: unknown; toolName?: unknown; argsSummary?: unknown; resultSummary?: unknown; isError?: unknown; source?: unknown; data?: unknown; mime?: unknown; caption?: unknown; name?: unknown; verbosity?: unknown; redact?: unknown; status?: unknown; message?: unknown; } export declare function supportsTelegramPhotoUpload(mime: string | undefined): boolean; /** Format the one-time identity header as pinned bullets. */ export declare function formatIdentityHeader(frame: { repo?: unknown; branch?: unknown; machine?: unknown; sessionId?: unknown; title?: unknown; }): string; /** Format a streamed context update into a compact block (omitting empty fields). */ export declare function formatContextUpdate(frame: ThreadedFrame): string | undefined; /** * Map a threaded frame to a Telegram send spec, or `undefined` when there is * nothing to send (e.g. an empty context update or an unknown frame type). */ export declare function renderThreadedFrame(frame: ThreadedFrame): ThreadedSend | undefined; export {};