mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-07 22:09:57 +00:00
fix(telegram): dedupe reasoning block preview sends
This commit is contained in:
@@ -687,6 +687,49 @@ describe("dispatchTelegramMessage draft streaming", () => {
|
||||
expect(deliverReplies).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates reasoning preview for reasoning block payloads instead of sending duplicates", async () => {
|
||||
setupDraftStreams({ answerMessageId: 999, reasoningMessageId: 111 });
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
|
||||
async ({ dispatcherOptions, replyOptions }) => {
|
||||
await replyOptions?.onReasoningStream?.({
|
||||
text: "Reasoning:\nIf I count r in strawberry, I see positions 3, 8, and",
|
||||
});
|
||||
await replyOptions?.onReasoningEnd?.();
|
||||
await replyOptions?.onPartialReply?.({ text: "3" });
|
||||
await dispatcherOptions.deliver({ text: "3" }, { kind: "final" });
|
||||
await dispatcherOptions.deliver(
|
||||
{
|
||||
text: "Reasoning:\nIf I count r in strawberry, I see positions 3, 8, and 9. So the total is 3.",
|
||||
},
|
||||
{ kind: "block" },
|
||||
);
|
||||
return { queuedFinal: true };
|
||||
},
|
||||
);
|
||||
deliverReplies.mockResolvedValue({ delivered: true });
|
||||
editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "999" });
|
||||
|
||||
await dispatchWithContext({ context: createContext(), streamMode: "partial" });
|
||||
|
||||
expect(editMessageTelegram).toHaveBeenNthCalledWith(1, 123, 999, "3", expect.any(Object));
|
||||
expect(editMessageTelegram).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
123,
|
||||
111,
|
||||
"Reasoning:\nIf I count r in strawberry, I see positions 3, 8, and 9. So the total is 3.",
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(deliverReplies).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replies: [
|
||||
expect.objectContaining({
|
||||
text: expect.stringContaining("Reasoning:\nIf I count r in strawberry"),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("splits reasoning preview only when next reasoning block starts in partial mode", async () => {
|
||||
const { reasoningDraftStream } = setupDraftStreams({
|
||||
answerMessageId: 999,
|
||||
|
||||
@@ -399,6 +399,49 @@ export const dispatchTelegramMessage = async ({
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const tryEditExistingPreviewForLane = async (params: {
|
||||
lane: DraftLaneState;
|
||||
laneName: "answer" | "reasoning";
|
||||
finalText: string;
|
||||
previewButtons?: TelegramInlineButtons;
|
||||
}): Promise<boolean> => {
|
||||
const { lane, laneName, finalText, previewButtons } = params;
|
||||
if (!lane.stream) {
|
||||
return false;
|
||||
}
|
||||
const previewMessageId = lane.stream.messageId();
|
||||
if (typeof previewMessageId !== "number") {
|
||||
return false;
|
||||
}
|
||||
const currentPreviewText = streamMode === "block" ? lane.draftText : lane.lastPartialText;
|
||||
if (
|
||||
currentPreviewText &&
|
||||
currentPreviewText.startsWith(finalText) &&
|
||||
finalText.length < currentPreviewText.length
|
||||
) {
|
||||
// Avoid regressive punctuation/wording flicker from occasional shorter finals.
|
||||
deliveryState.delivered = true;
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
await editMessageTelegram(chatId, previewMessageId, finalText, {
|
||||
api: bot.api,
|
||||
cfg,
|
||||
accountId: route.accountId,
|
||||
linkPreview: telegramCfg.linkPreview,
|
||||
buttons: previewButtons,
|
||||
});
|
||||
lane.lastPartialText = finalText;
|
||||
lane.draftText = finalText;
|
||||
deliveryState.delivered = true;
|
||||
return true;
|
||||
} catch (err) {
|
||||
logVerbose(
|
||||
`telegram: ${laneName} preview update failed; falling back to standard send (${String(err)})`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let queuedFinal = false;
|
||||
try {
|
||||
@@ -408,21 +451,21 @@ export const dispatchTelegramMessage = async ({
|
||||
dispatcherOptions: {
|
||||
...prefixOptions,
|
||||
deliver: async (payload, info) => {
|
||||
const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
|
||||
const finalText = payload.text;
|
||||
const reasoningMessage = isReasoningMessage(finalText);
|
||||
const previewButtons = (
|
||||
payload.channelData?.telegram as { buttons?: TelegramInlineButtons } | undefined
|
||||
)?.buttons;
|
||||
const canFinalizeViaPreviewEdit =
|
||||
!hasMedia &&
|
||||
typeof finalText === "string" &&
|
||||
finalText.length > 0 &&
|
||||
finalText.length <= draftMaxChars &&
|
||||
!payload.isError;
|
||||
if (info.kind === "final") {
|
||||
await flushDraftLane(answerLane);
|
||||
await flushDraftLane(reasoningLane);
|
||||
const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
|
||||
const finalText = payload.text;
|
||||
const reasoningMessage = isReasoningMessage(finalText);
|
||||
const previewButtons = (
|
||||
payload.channelData?.telegram as { buttons?: TelegramInlineButtons } | undefined
|
||||
)?.buttons;
|
||||
const canFinalizeViaPreviewEdit =
|
||||
!hasMedia &&
|
||||
typeof finalText === "string" &&
|
||||
finalText.length > 0 &&
|
||||
finalText.length <= draftMaxChars &&
|
||||
!payload.isError;
|
||||
if (canFinalizeViaPreviewEdit && reasoningMessage) {
|
||||
const finalizedReasoning = await tryFinalizePreviewForLane({
|
||||
lane: reasoningLane,
|
||||
@@ -459,6 +502,17 @@ export const dispatchTelegramMessage = async ({
|
||||
await answerLane.stream?.stop();
|
||||
await reasoningLane.stream?.stop();
|
||||
}
|
||||
if (info.kind !== "final" && canFinalizeViaPreviewEdit && reasoningMessage) {
|
||||
const updatedReasoning = await tryEditExistingPreviewForLane({
|
||||
lane: reasoningLane,
|
||||
laneName: "reasoning",
|
||||
finalText,
|
||||
previewButtons,
|
||||
});
|
||||
if (updatedReasoning) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const result = await deliverReplies({
|
||||
...deliveryBaseOptions,
|
||||
replies: [payload],
|
||||
|
||||
Reference in New Issue
Block a user