

























@@ -1,6 +1,7 @@
11import { describe, expect, it, vi } from "vitest";
22import {
33createTextEndBlockReplyHarness,
4+extractTextPayloads,
45emitAssistantTextDelta,
56emitAssistantTextEnd,
67} from "./pi-embedded-subscribe.e2e-harness.js";
@@ -52,4 +53,187 @@ describe("subscribeEmbeddedPiSession", () => {
5253});
5354expect(subscription.assistantTexts).toEqual([expected]);
5455});
56+57+it("sends only the suffix when text_end block replies grow across assistant messages", async () => {
58+const onBlockReply = vi.fn();
59+const { emit, subscription } = createTextEndBlockReplyHarness({ onBlockReply });
60+61+const emitAssistantSnapshot = (content: string) => {
62+emit({ type: "message_start", message: { role: "assistant" } });
63+emitAssistantTextEnd({ emit, content });
64+};
65+const emitToolStart = (toolCallId: string) => {
66+emit({
67+type: "tool_execution_start",
68+toolName: "browser",
69+ toolCallId,
70+args: {},
71+});
72+};
73+74+emitAssistantSnapshot("Let me grab actual eBay prices:");
75+await vi.waitFor(() => {
76+expect(onBlockReply).toHaveBeenCalledTimes(1);
77+});
78+79+emitToolStart("tool-1");
80+await Promise.resolve();
81+emitAssistantSnapshot("Let me grab actual eBay prices:Let me grab actual prices from eBay:");
82+await vi.waitFor(() => {
83+expect(onBlockReply).toHaveBeenCalledTimes(2);
84+});
85+86+emitToolStart("tool-2");
87+await Promise.resolve();
88+emitAssistantSnapshot(
89+"Let me grab actual eBay prices:Let me grab actual prices from eBay:eBay blocks live pricing:",
90+);
91+await vi.waitFor(() => {
92+expect(onBlockReply).toHaveBeenCalledTimes(3);
93+});
94+95+expect(extractTextPayloads(onBlockReply.mock.calls)).toEqual([
96+"Let me grab actual eBay prices:",
97+"Let me grab actual prices from eBay:",
98+"eBay blocks live pricing:",
99+]);
100+expect(subscription.assistantTexts).toEqual([
101+"Let me grab actual eBay prices:",
102+"Let me grab actual prices from eBay:",
103+"eBay blocks live pricing:",
104+]);
105+});
106+107+it("keeps a full later reply that shares a prefix without an intervening tool call", async () => {
108+const onBlockReply = vi.fn();
109+const { emit, subscription } = createTextEndBlockReplyHarness({ onBlockReply });
110+111+const emitAssistantSnapshot = (content: string) => {
112+emit({ type: "message_start", message: { role: "assistant" } });
113+emitAssistantTextEnd({ emit, content });
114+};
115+116+emitAssistantSnapshot("OK");
117+await vi.waitFor(() => {
118+expect(onBlockReply).toHaveBeenCalledTimes(1);
119+});
120+121+emitAssistantSnapshot("OK, here's the detail");
122+await vi.waitFor(() => {
123+expect(onBlockReply).toHaveBeenCalledTimes(2);
124+});
125+126+expect(extractTextPayloads(onBlockReply.mock.calls)).toEqual(["OK", "OK, here's the detail"]);
127+expect(subscription.assistantTexts).toEqual(["OK", "OK, here's the detail"]);
128+});
129+130+it("keeps a full post-tool reply when the prior block is not a preamble", async () => {
131+const onBlockReply = vi.fn();
132+const { emit, subscription } = createTextEndBlockReplyHarness({ onBlockReply });
133+134+const emitAssistantSnapshot = (content: string) => {
135+emit({ type: "message_start", message: { role: "assistant" } });
136+emitAssistantTextEnd({ emit, content });
137+};
138+139+emitAssistantSnapshot("Checking...");
140+await vi.waitFor(() => {
141+expect(onBlockReply).toHaveBeenCalledTimes(1);
142+});
143+144+emit({
145+type: "tool_execution_start",
146+toolName: "browser",
147+toolCallId: "tool-post-check",
148+args: {},
149+});
150+await Promise.resolve();
151+152+emitAssistantSnapshot("Checking... found X");
153+await vi.waitFor(() => {
154+expect(onBlockReply).toHaveBeenCalledTimes(2);
155+});
156+157+expect(extractTextPayloads(onBlockReply.mock.calls)).toEqual([
158+"Checking...",
159+"Checking... found X",
160+]);
161+expect(subscription.assistantTexts).toEqual(["Checking...", "Checking... found X"]);
162+});
163+164+it("keeps a full post-tool reply when the shared prefix is whitespace-separated", async () => {
165+const onBlockReply = vi.fn();
166+const { emit, subscription } = createTextEndBlockReplyHarness({ onBlockReply });
167+168+const emitAssistantSnapshot = (content: string) => {
169+emit({ type: "message_start", message: { role: "assistant" } });
170+emitAssistantTextEnd({ emit, content });
171+};
172+173+emitAssistantSnapshot("Checking:");
174+await vi.waitFor(() => {
175+expect(onBlockReply).toHaveBeenCalledTimes(1);
176+});
177+178+emit({
179+type: "tool_execution_start",
180+toolName: "browser",
181+toolCallId: "tool-post-check-colon",
182+args: {},
183+});
184+await Promise.resolve();
185+186+emitAssistantSnapshot("Checking: found X");
187+await vi.waitFor(() => {
188+expect(onBlockReply).toHaveBeenCalledTimes(2);
189+});
190+191+expect(extractTextPayloads(onBlockReply.mock.calls)).toEqual([
192+"Checking:",
193+"Checking: found X",
194+]);
195+expect(subscription.assistantTexts).toEqual(["Checking:", "Checking: found X"]);
196+});
197+198+it("does not safety-send a cumulative text_end reply when the suffix was sent by a messaging tool", async () => {
199+const onBlockReply = vi.fn();
200+const { emit } = createTextEndBlockReplyHarness({ onBlockReply });
201+202+emit({ type: "message_start", message: { role: "assistant" } });
203+emitAssistantTextEnd({ emit, content: "Checking:" });
204+await vi.waitFor(() => {
205+expect(onBlockReply).toHaveBeenCalledTimes(1);
206+});
207+208+emit({
209+type: "tool_execution_start",
210+toolName: "message",
211+toolCallId: "message-tool-1",
212+args: { action: "send", to: "+1555", message: "Fetched prices" },
213+});
214+await Promise.resolve();
215+emit({
216+type: "tool_execution_end",
217+toolName: "message",
218+toolCallId: "message-tool-1",
219+isError: false,
220+result: "ok",
221+});
222+await Promise.resolve();
223+await new Promise((resolve) => setTimeout(resolve, 0));
224+225+emit({ type: "message_start", message: { role: "assistant" } });
226+emitAssistantTextEnd({ emit, content: "Checking: Fetched prices" });
227+await Promise.resolve();
228+emit({
229+type: "message_end",
230+message: {
231+role: "assistant",
232+content: [{ type: "text", text: "Checking: Fetched prices" }],
233+},
234+});
235+await Promise.resolve();
236+237+expect(extractTextPayloads(onBlockReply.mock.calls)).toEqual(["Checking:"]);
238+});
55239});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。