

























@@ -784,27 +784,24 @@ describe("dispatchTelegramMessage draft streaming", () => {
784784);
785785});
786786787-it.each(["block", "partial"] as const)(
788-"forces new message when assistant message restarts (%s mode)",
789-async (streamMode) => {
790-const draftStream = createDraftStream(999);
791-createTelegramDraftStream.mockReturnValue(draftStream);
792-dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
793-async ({ dispatcherOptions, replyOptions }) => {
794-await replyOptions?.onPartialReply?.({ text: "First response" });
795-await replyOptions?.onAssistantMessageStart?.();
796-await replyOptions?.onPartialReply?.({ text: "After tool call" });
797-await dispatcherOptions.deliver({ text: "After tool call" }, { kind: "final" });
798-return { queuedFinal: true };
799-},
800-);
801-deliverReplies.mockResolvedValue({ delivered: true });
787+it("forces new message when assistant message restarts", async () => {
788+const draftStream = createDraftStream(999);
789+createTelegramDraftStream.mockReturnValue(draftStream);
790+dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
791+async ({ dispatcherOptions, replyOptions }) => {
792+await replyOptions?.onPartialReply?.({ text: "First response" });
793+await replyOptions?.onAssistantMessageStart?.();
794+await replyOptions?.onPartialReply?.({ text: "After tool call" });
795+await dispatcherOptions.deliver({ text: "After tool call" }, { kind: "final" });
796+return { queuedFinal: true };
797+},
798+);
799+deliverReplies.mockResolvedValue({ delivered: true });
802800803- await dispatchWithContext({ context: createContext(), streamMode });
801+await dispatchWithContext({ context: createContext(), streamMode: "partial" });
804802805-expect(draftStream.forceNewMessage).toHaveBeenCalledTimes(1);
806-},
807-);
803+expect(draftStream.forceNewMessage).toHaveBeenCalledTimes(1);
804+});
808805809806it("materializes boundary preview and keeps it when no matching final arrives", async () => {
810807const answerDraftStream = createDraftStream(999);
@@ -1758,51 +1755,48 @@ describe("dispatchTelegramMessage draft streaming", () => {
17581755expect(answerDraftStream.clear).toHaveBeenCalledTimes(1);
17591756});
176017571761-it.each(["partial", "block"] as const)(
1762-"keeps finalized text preview when the next assistant message is media-only (%s mode)",
1763-async (streamMode) => {
1764-let answerMessageId: number | undefined = 1001;
1765-const answerDraftStream = {
1766-update: vi.fn(),
1767-flush: vi.fn().mockResolvedValue(undefined),
1768-messageId: vi.fn().mockImplementation(() => answerMessageId),
1769-clear: vi.fn().mockResolvedValue(undefined),
1770-stop: vi.fn().mockResolvedValue(undefined),
1771-forceNewMessage: vi.fn().mockImplementation(() => {
1772-answerMessageId = undefined;
1773-}),
1774-};
1775-const reasoningDraftStream = createDraftStream();
1776-createTelegramDraftStream
1777-.mockImplementationOnce(() => answerDraftStream)
1778-.mockImplementationOnce(() => reasoningDraftStream);
1779-dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
1780-async ({ dispatcherOptions, replyOptions }) => {
1781-await replyOptions?.onPartialReply?.({ text: "First message preview" });
1782-await dispatcherOptions.deliver({ text: "First message final" }, { kind: "final" });
1783-await replyOptions?.onAssistantMessageStart?.();
1784-await dispatcherOptions.deliver({ mediaUrl: "file:///tmp/voice.ogg" }, { kind: "final" });
1785-return { queuedFinal: true };
1786-},
1787-);
1788-deliverReplies.mockResolvedValue({ delivered: true });
1789-editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "1001" });
1790-const bot = createBot();
1758+it("keeps finalized text preview when the next assistant message is media-only", async () => {
1759+let answerMessageId: number | undefined = 1001;
1760+const answerDraftStream = {
1761+update: vi.fn(),
1762+flush: vi.fn().mockResolvedValue(undefined),
1763+messageId: vi.fn().mockImplementation(() => answerMessageId),
1764+clear: vi.fn().mockResolvedValue(undefined),
1765+stop: vi.fn().mockResolvedValue(undefined),
1766+forceNewMessage: vi.fn().mockImplementation(() => {
1767+answerMessageId = undefined;
1768+}),
1769+};
1770+const reasoningDraftStream = createDraftStream();
1771+createTelegramDraftStream
1772+.mockImplementationOnce(() => answerDraftStream)
1773+.mockImplementationOnce(() => reasoningDraftStream);
1774+dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
1775+async ({ dispatcherOptions, replyOptions }) => {
1776+await replyOptions?.onPartialReply?.({ text: "First message preview" });
1777+await dispatcherOptions.deliver({ text: "First message final" }, { kind: "final" });
1778+await replyOptions?.onAssistantMessageStart?.();
1779+await dispatcherOptions.deliver({ mediaUrl: "file:///tmp/voice.ogg" }, { kind: "final" });
1780+return { queuedFinal: true };
1781+},
1782+);
1783+deliverReplies.mockResolvedValue({ delivered: true });
1784+editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "1001" });
1785+const bot = createBot();
179117861792- await dispatchWithContext({ context: createContext(), streamMode, bot });
1787+await dispatchWithContext({ context: createContext(), streamMode: "partial", bot });
179317881794-expect(editMessageTelegram).toHaveBeenCalledWith(
1795-123,
1796-1001,
1797-"First message final",
1798-expect.any(Object),
1799-);
1800-const deleteMessageCalls = (
1801-bot.api as unknown as { deleteMessage: { mock: { calls: unknown[][] } } }
1802-).deleteMessage.mock.calls;
1803-expect(deleteMessageCalls).not.toContainEqual([123, 1001]);
1804-},
1805-);
1789+expect(editMessageTelegram).toHaveBeenCalledWith(
1790+123,
1791+1001,
1792+"First message final",
1793+expect.any(Object),
1794+);
1795+const deleteMessageCalls = (
1796+bot.api as unknown as { deleteMessage: { mock: { calls: unknown[][] } } }
1797+).deleteMessage.mock.calls;
1798+expect(deleteMessageCalls).not.toContainEqual([123, 1001]);
1799+});
1806180018071801it("maps finals correctly when archived preview id arrives during final flush", async () => {
18081802let answerMessageId: number | undefined;
@@ -1873,32 +1867,29 @@ describe("dispatchTelegramMessage draft streaming", () => {
18731867expect(deliverReplies).not.toHaveBeenCalled();
18741868});
187518691876-it.each(["block", "partial"] as const)(
1877-"splits reasoning lane only when a later reasoning block starts (%s mode)",
1878-async (streamMode) => {
1879-const { reasoningDraftStream } = setupDraftStreams({
1880-answerMessageId: 999,
1881-reasoningMessageId: 111,
1882-});
1883-dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
1884-async ({ dispatcherOptions, replyOptions }) => {
1885-await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_first block_" });
1886-await replyOptions?.onReasoningEnd?.();
1887-expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
1888-await replyOptions?.onPartialReply?.({ text: "checking files..." });
1889-await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_second block_" });
1890-await dispatcherOptions.deliver({ text: "Done" }, { kind: "final" });
1891-return { queuedFinal: true };
1892-},
1893-);
1894-deliverReplies.mockResolvedValue({ delivered: true });
1895-editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "999" });
1870+it("splits reasoning lane only when a later reasoning block starts", async () => {
1871+const { reasoningDraftStream } = setupDraftStreams({
1872+answerMessageId: 999,
1873+reasoningMessageId: 111,
1874+});
1875+dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
1876+async ({ dispatcherOptions, replyOptions }) => {
1877+await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_first block_" });
1878+await replyOptions?.onReasoningEnd?.();
1879+expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
1880+await replyOptions?.onPartialReply?.({ text: "checking files..." });
1881+await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_second block_" });
1882+await dispatcherOptions.deliver({ text: "Done" }, { kind: "final" });
1883+return { queuedFinal: true };
1884+},
1885+);
1886+deliverReplies.mockResolvedValue({ delivered: true });
1887+editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "999" });
189618881897- await dispatchWithContext({ context: createReasoningStreamContext(), streamMode });
1889+await dispatchWithContext({ context: createReasoningStreamContext(), streamMode: "partial" });
189818901899-expect(reasoningDraftStream.forceNewMessage).toHaveBeenCalledTimes(1);
1900-},
1901-);
1891+expect(reasoningDraftStream.forceNewMessage).toHaveBeenCalledTimes(1);
1892+});
1902189319031894it("queues reasoning-end split decisions behind queued reasoning deltas", async () => {
19041895const { reasoningDraftStream } = setupDraftStreams({
@@ -1970,29 +1961,26 @@ describe("dispatchTelegramMessage draft streaming", () => {
19701961expect(deleteMessageCalls).toContainEqual([123, 4444]);
19711962});
197219631973-it.each(["block", "partial"] as const)(
1974-"does not split reasoning lane on reasoning end without a later reasoning block (%s mode)",
1975-async (streamMode) => {
1976-const { reasoningDraftStream } = setupDraftStreams({
1977-answerMessageId: 999,
1978-reasoningMessageId: 111,
1979-});
1980-dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
1981-async ({ dispatcherOptions, replyOptions }) => {
1982-await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_first block_" });
1983-await replyOptions?.onReasoningEnd?.();
1984-await replyOptions?.onPartialReply?.({ text: "Here's the answer" });
1985-await dispatcherOptions.deliver({ text: "Here's the answer" }, { kind: "final" });
1986-return { queuedFinal: true };
1987-},
1988-);
1989-deliverReplies.mockResolvedValue({ delivered: true });
1964+it("does not split reasoning lane on reasoning end without a later reasoning block", async () => {
1965+const { reasoningDraftStream } = setupDraftStreams({
1966+answerMessageId: 999,
1967+reasoningMessageId: 111,
1968+});
1969+dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
1970+async ({ dispatcherOptions, replyOptions }) => {
1971+await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_first block_" });
1972+await replyOptions?.onReasoningEnd?.();
1973+await replyOptions?.onPartialReply?.({ text: "Here's the answer" });
1974+await dispatcherOptions.deliver({ text: "Here's the answer" }, { kind: "final" });
1975+return { queuedFinal: true };
1976+},
1977+);
1978+deliverReplies.mockResolvedValue({ delivered: true });
199019791991- await dispatchWithContext({ context: createReasoningStreamContext(), streamMode });
1980+await dispatchWithContext({ context: createReasoningStreamContext(), streamMode: "partial" });
199219811993-expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
1994-},
1995-);
1982+expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
1983+});
1996198419971985it("suppresses reasoning-only final payloads when reasoning level is off", async () => {
19981986setupDraftStreams({ answerMessageId: 999 });
@@ -2175,50 +2163,47 @@ describe("dispatchTelegramMessage draft streaming", () => {
21752163);
21762164});
217721652178-it.each(["partial", "block"] as const)(
2179-"does not duplicate reasoning final after reasoning end (%s mode)",
2180-async (streamMode) => {
2181-let reasoningMessageId: number | undefined = 111;
2182-const reasoningDraftStream = {
2183-update: vi.fn(),
2184-flush: vi.fn().mockResolvedValue(undefined),
2185-messageId: vi.fn().mockImplementation(() => reasoningMessageId),
2186-clear: vi.fn().mockResolvedValue(undefined),
2187-stop: vi.fn().mockResolvedValue(undefined),
2188-forceNewMessage: vi.fn().mockImplementation(() => {
2189-reasoningMessageId = undefined;
2190-}),
2191-};
2192-const answerDraftStream = createDraftStream(999);
2193-createTelegramDraftStream
2194-.mockImplementationOnce(() => answerDraftStream)
2195-.mockImplementationOnce(() => reasoningDraftStream);
2196-dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
2197-async ({ dispatcherOptions, replyOptions }) => {
2198-await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_step one_" });
2199-await replyOptions?.onReasoningEnd?.();
2200-await dispatcherOptions.deliver(
2201-{ text: "Reasoning:\n_step one expanded_" },
2202-{ kind: "final" },
2203-);
2204-return { queuedFinal: true };
2205-},
2206-);
2207-deliverReplies.mockResolvedValue({ delivered: true });
2208-editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "111" });
2166+it("does not duplicate reasoning final after reasoning end", async () => {
2167+let reasoningMessageId: number | undefined = 111;
2168+const reasoningDraftStream = {
2169+update: vi.fn(),
2170+flush: vi.fn().mockResolvedValue(undefined),
2171+messageId: vi.fn().mockImplementation(() => reasoningMessageId),
2172+clear: vi.fn().mockResolvedValue(undefined),
2173+stop: vi.fn().mockResolvedValue(undefined),
2174+forceNewMessage: vi.fn().mockImplementation(() => {
2175+reasoningMessageId = undefined;
2176+}),
2177+};
2178+const answerDraftStream = createDraftStream(999);
2179+createTelegramDraftStream
2180+.mockImplementationOnce(() => answerDraftStream)
2181+.mockImplementationOnce(() => reasoningDraftStream);
2182+dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
2183+async ({ dispatcherOptions, replyOptions }) => {
2184+await replyOptions?.onReasoningStream?.({ text: "Reasoning:\n_step one_" });
2185+await replyOptions?.onReasoningEnd?.();
2186+await dispatcherOptions.deliver(
2187+{ text: "Reasoning:\n_step one expanded_" },
2188+{ kind: "final" },
2189+);
2190+return { queuedFinal: true };
2191+},
2192+);
2193+deliverReplies.mockResolvedValue({ delivered: true });
2194+editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "111" });
220921952210- await dispatchWithContext({ context: createReasoningStreamContext(), streamMode });
2196+await dispatchWithContext({ context: createReasoningStreamContext(), streamMode: "partial" });
221121972212-expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
2213-expect(editMessageTelegram).toHaveBeenCalledWith(
2214-123,
2215-111,
2216-"Reasoning:\n_step one expanded_",
2217-expect.any(Object),
2218-);
2219-expect(deliverReplies).not.toHaveBeenCalled();
2220-},
2221-);
2198+expect(reasoningDraftStream.forceNewMessage).not.toHaveBeenCalled();
2199+expect(editMessageTelegram).toHaveBeenCalledWith(
2200+123,
2201+111,
2202+"Reasoning:\n_step one expanded_",
2203+expect.any(Object),
2204+);
2205+expect(deliverReplies).not.toHaveBeenCalled();
2206+});
2222220722232208it("updates reasoning preview for reasoning block payloads instead of sending duplicates", async () => {
22242209setupDraftStreams({ answerMessageId: 999, reasoningMessageId: 111 });
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。