fix(telegram): draft stream preview not threaded when replyToMode is on (#17880) (#17928)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: cfd4181a23
Co-authored-by: yinghaosang <261132136+yinghaosang@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
This commit is contained in:
yinghaosang
2026-02-16 20:40:24 +08:00
committed by GitHub
parent b2aa6e094d
commit 244ed9db39
5 changed files with 211 additions and 33 deletions

View File

@@ -114,13 +114,14 @@ describe("dispatchTelegramMessage draft streaming", () => {
context: TelegramMessageContext;
telegramCfg?: Parameters<typeof dispatchTelegramMessage>[0]["telegramCfg"];
streamMode?: Parameters<typeof dispatchTelegramMessage>[0]["streamMode"];
replyToMode?: Parameters<typeof dispatchTelegramMessage>[0]["replyToMode"];
}) {
await dispatchTelegramMessage({
context: params.context,
bot: createBot(),
cfg: {},
runtime: createRuntime(),
replyToMode: "first",
replyToMode: params.replyToMode ?? "first",
streamMode: params.streamMode ?? "partial",
textLimit: 4096,
telegramCfg: params.telegramCfg ?? {},
@@ -151,6 +152,7 @@ describe("dispatchTelegramMessage draft streaming", () => {
expect.objectContaining({
chatId: 123,
thread: { id: 777, scope: "dm" },
replyToMessageId: 456,
}),
);
expect(draftStream.update).toHaveBeenCalledWith("Hello");
@@ -215,6 +217,52 @@ describe("dispatchTelegramMessage draft streaming", () => {
expect(draftStream.stop).toHaveBeenCalled();
});
it("uses only the latest final payload when multiple finals are emitted", async () => {
const draftStream = createDraftStream(999);
createTelegramDraftStream.mockReturnValue(draftStream);
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
async ({ dispatcherOptions, replyOptions }) => {
await replyOptions?.onPartialReply?.({ text: "Okay." });
await dispatcherOptions.deliver({ text: "Ok" }, { kind: "final" });
await dispatcherOptions.deliver({ text: "Okay." }, { kind: "final" });
return { queuedFinal: true };
},
);
deliverReplies.mockResolvedValue({ delivered: true });
editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "999" });
await dispatchWithContext({ context: createContext() });
expect(editMessageTelegram).toHaveBeenCalledTimes(1);
expect(editMessageTelegram).toHaveBeenCalledWith(123, 999, "Okay.", expect.any(Object));
expect(deliverReplies).not.toHaveBeenCalled();
expect(draftStream.clear).not.toHaveBeenCalled();
expect(draftStream.stop).toHaveBeenCalled();
});
it("ignores transient shorter partial prefixes to avoid preview punctuation flicker", async () => {
const draftStream = createDraftStream(999);
createTelegramDraftStream.mockReturnValue(draftStream);
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
async ({ dispatcherOptions, replyOptions }) => {
await replyOptions?.onPartialReply?.({ text: "Sure." });
await replyOptions?.onPartialReply?.({ text: "Sure" });
await replyOptions?.onPartialReply?.({ text: "Sure." });
await dispatcherOptions.deliver({ text: "Sure." }, { kind: "final" });
return { queuedFinal: true };
},
);
deliverReplies.mockResolvedValue({ delivered: true });
editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "999" });
await dispatchWithContext({ context: createContext() });
expect(draftStream.update).toHaveBeenCalledTimes(1);
expect(draftStream.update).toHaveBeenCalledWith("Sure.");
expect(editMessageTelegram).toHaveBeenCalledTimes(1);
expect(editMessageTelegram).toHaveBeenCalledWith(123, 999, "Sure.", expect.any(Object));
});
it("falls back to normal delivery when preview final is too long to edit", async () => {
const draftStream = createDraftStream(999);
createTelegramDraftStream.mockReturnValue(draftStream);
@@ -259,4 +307,26 @@ describe("dispatchTelegramMessage draft streaming", () => {
}),
);
});
it("omits replyToMessageId from draft stream when replyToMode is off", async () => {
const draftStream = createDraftStream();
createTelegramDraftStream.mockReturnValue(draftStream);
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => {
await dispatcherOptions.deliver({ text: "Hello" }, { kind: "final" });
return { queuedFinal: true };
});
deliverReplies.mockResolvedValue({ delivered: true });
await dispatchWithContext({
context: createContext(),
replyToMode: "off",
});
expect(createTelegramDraftStream).toHaveBeenCalledWith(
expect.objectContaining({
chatId: 123,
replyToMessageId: undefined,
}),
);
});
});