fix: stabilize telegram draft boundary previews (#33842) (thanks @ngutman)

This commit is contained in:
Ayaan Zaidi
2026-03-04 08:55:13 +05:30
committed by Ayaan Zaidi
parent 5ce53095c5
commit 575bd77196
8 changed files with 463 additions and 73 deletions

View File

@@ -62,6 +62,8 @@ export type TelegramDraftStream = {
lastDeliveredText?: () => string;
clear: () => Promise<void>;
stop: () => Promise<void>;
/** Convert the current draft preview into a permanent message (sendMessage). */
materialize?: () => Promise<number | undefined>;
/** Reset internal state so the next update creates a new message instead of editing. */
forceNewMessage: () => void;
};
@@ -137,6 +139,38 @@ export function createTelegramDraftStream(params: {
renderedParseMode: "HTML" | undefined;
sendGeneration: number;
};
const sendRenderedMessageWithThreadFallback = async (sendArgs: {
renderedText: string;
renderedParseMode: "HTML" | undefined;
fallbackWarnMessage: string;
}) => {
const sendParams = sendArgs.renderedParseMode
? {
...replyParams,
parse_mode: sendArgs.renderedParseMode,
}
: replyParams;
try {
return await params.api.sendMessage(chatId, sendArgs.renderedText, sendParams);
} catch (err) {
const hasThreadParam =
"message_thread_id" in (sendParams ?? {}) &&
typeof (sendParams as { message_thread_id?: unknown }).message_thread_id === "number";
if (!hasThreadParam || !THREAD_NOT_FOUND_RE.test(String(err))) {
throw err;
}
const threadlessParams = {
...(sendParams as Record<string, unknown>),
};
delete threadlessParams.message_thread_id;
params.warn?.(sendArgs.fallbackWarnMessage);
return await params.api.sendMessage(
chatId,
sendArgs.renderedText,
Object.keys(threadlessParams).length > 0 ? threadlessParams : undefined,
);
}
};
const sendMessageTransportPreview = async ({
renderedText,
renderedParseMode,
@@ -152,35 +186,12 @@ export function createTelegramDraftStream(params: {
}
return true;
}
const sendParams = renderedParseMode
? {
...replyParams,
parse_mode: renderedParseMode,
}
: replyParams;
let sent;
try {
sent = await params.api.sendMessage(chatId, renderedText, sendParams);
} catch (err) {
const hasThreadParam =
"message_thread_id" in (sendParams ?? {}) &&
typeof (sendParams as { message_thread_id?: unknown }).message_thread_id === "number";
if (!hasThreadParam || !THREAD_NOT_FOUND_RE.test(String(err))) {
throw err;
}
const threadlessParams = {
...(sendParams as Record<string, unknown>),
};
delete threadlessParams.message_thread_id;
params.warn?.(
const sent = await sendRenderedMessageWithThreadFallback({
renderedText,
renderedParseMode,
fallbackWarnMessage:
"telegram stream preview send failed with message_thread_id, retrying without thread",
);
sent = await params.api.sendMessage(
chatId,
renderedText,
Object.keys(threadlessParams).length > 0 ? threadlessParams : undefined,
);
}
});
const sentMessageId = sent?.message_id;
if (typeof sentMessageId !== "number" || !Number.isFinite(sentMessageId)) {
streamState.stopped = true;
@@ -324,6 +335,9 @@ export function createTelegramDraftStream(params: {
});
const forceNewMessage = () => {
// Boundary rotation may call stop() to finalize the previous draft.
// Re-open the stream lifecycle for the next assistant segment.
streamState.final = false;
generation += 1;
streamMessageId = undefined;
if (previewTransport === "draft") {
@@ -335,6 +349,45 @@ export function createTelegramDraftStream(params: {
loop.resetThrottleWindow();
};
/**
* Materialize the current draft into a permanent message.
* For draft transport: sends the accumulated text as a real sendMessage.
* For message transport: the message is already permanent (noop).
* Returns the permanent message id, or undefined if nothing to materialize.
*/
const materialize = async (): Promise<number | undefined> => {
await stop();
// If using message transport, the streamMessageId is already a real message.
if (previewTransport === "message" && typeof streamMessageId === "number") {
return streamMessageId;
}
// For draft transport, use the rendered snapshot first so parse_mode stays
// aligned with the text being materialized.
const renderedText = lastSentText || lastDeliveredText;
if (!renderedText) {
return undefined;
}
const renderedParseMode = lastSentText ? lastSentParseMode : undefined;
try {
const sent = await sendRenderedMessageWithThreadFallback({
renderedText,
renderedParseMode,
fallbackWarnMessage:
"telegram stream preview materialize send failed with message_thread_id, retrying without thread",
});
const sentId = sent?.message_id;
if (typeof sentId === "number" && Number.isFinite(sentId)) {
streamMessageId = Math.trunc(sentId);
return streamMessageId;
}
} catch (err) {
params.warn?.(
`telegram stream preview materialize failed: ${err instanceof Error ? err.message : String(err)}`,
);
}
return undefined;
};
params.log?.(`telegram stream preview ready (maxChars=${maxChars}, throttleMs=${throttleMs})`);
return {
@@ -346,6 +399,7 @@ export function createTelegramDraftStream(params: {
lastDeliveredText: () => lastDeliveredText,
clear,
stop,
materialize,
forceNewMessage,
};
}