/** * Telegram message-draft painter — the lifecycle primitive under * `bot/llm`'s `streamChatReply`, dependency-light on purpose (gramio types + * `@gramio/format` types only) so a bot with its own rendering pipeline pulls * no markdown machinery into its bundle. * * `createDraftPreview(ctx, { render, throttleMs? })` owns the whole draft * lifecycle: throttled FULL-FRAME repaints (drafts replace, never append), * the anti-expiry keepalive under Telegram's ~30s draft TTL, serialized * sends, reset, quiesce, and {@link streamForensics} (render-regression + * upstream-reset warnings, also exported standalone for edit-based viewers). * `render` is required: a painter doesn't know markdown — it paints whatever * frame you render, plain (`sendMessageDraft`) or rich (`sendRichMessageDraft`). * Previews exist in PRIVATE chats only; elsewhere every method no-ops. */ import type { FormattableString } from "@gramio/format"; import type { Bot, MessageContext } from "gramio"; /** * Jitter forensics for a streamed preview: a healthy stream only GROWS. A * render that shrinks while the accumulated markdown grew means the * partial-markdown tail parsed differently between frames (the renderer); * accumulated text shrinking is an upstream provider reset. Neither warning * firing while a user still sees back-and-forth points at their CLIENT's own * draft rendering. Built into {@link createDraftPreview}; exported standalone * for edit-based viewers that repaint outside the painter. */ export declare function streamForensics(label: string): { /** Call per outgoing frame with the accumulated-markdown and rendered lengths. */ frame(accLen: number, renderedLen: number): void; /** Call from the upstream reset path with the length being dropped. */ reset(droppedChars: number): void; }; /** * One rendered preview frame. `text` goes out via `sendMessageDraft` (a * FormattableString carries entities; a raw string may carry `parseMode`); * `rich` goes out via `sendRichMessageDraft` (Telegram's rich-message HTML). */ export type DraftFrame = { kind: "text"; text: string | FormattableString; parseMode?: "HTML" | "MarkdownV2" | "Markdown"; } | { kind: "rich"; html: string; }; export interface DraftPreviewOptions { /** Ms between repaints. Default 1000. */ throttleMs?: number; /** * Render one FULL frame from the accumulated markdown — drafts replace, * never append, which is what makes resets and phase switches one cheap * frame. Return null to skip (nothing renderable yet). */ render: (markdown: string) => DraftFrame | null; /** Forensics label. Default `draft `. */ label?: string; } /** * A live message-draft preview: the push-driven primitive under * {@link streamChatReply}, exposed for callers whose producer pushes deltas * (rather than handing over an event iterable) or whose render/persist paths * are their own. Owns the whole draft lifecycle: throttled full-frame * repaints, the anti-expiry keepalive, serialized sends, reset, and quiesce. * Previews exist in PRIVATE chats only — elsewhere every method no-ops and * the caller's persist path is all that happens. */ export interface DraftPreview { /** Accumulated markdown of the current attempt. */ readonly text: string; /** Append a delta and schedule a repaint. */ append(text: string): void; /** Replace the buffer (phase switches); schedules a repaint. */ set(markdown: string): void; /** Upstream failover: drop the buffer with a forensics note; the next tokens repaint from scratch. */ reset(): void; /** Force a repaint of the current frame (render inputs changed, e.g. a cover arrived). */ repaint(): void; /** * Paint one last complete frame and stop. Call right before persisting: * the preview shows the finished text (not a throttle-stale partial) and * its ~30s TTL resets, so the handoff to the real message can't blank. */ finish(markdown?: string): Promise; /** Stop painting without a final frame; the ephemeral draft expires on its own. */ abort(): Promise; } export declare function createDraftPreview(ctx: MessageContext, opts: DraftPreviewOptions): DraftPreview; //# sourceMappingURL=draft.d.ts.map