mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-12 23:12:54 +00:00
fix: stabilize telegram draft boundary previews (#33842) (thanks @ngutman)
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user