fix(app): keep live assistant continuation with hydrated tail
This commit is contained in:
@@ -1099,6 +1099,48 @@ describe("processAgentStreamEvents", () => {
|
|||||||
expect(finalAssistantItems).toHaveLength(1);
|
expect(finalAssistantItems).toHaveLength(1);
|
||||||
expect(finalAssistantItems[0]?.text).toBe(`${seq186Text}${seq187Text}`);
|
expect(finalAssistantItems[0]?.text).toBe(`${seq186Text}${seq187Text}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not split an assistant sentence between hydrated tail and live head", () => {
|
||||||
|
const hydrated = processTimelineResponse({
|
||||||
|
...baseTimelineInput,
|
||||||
|
isInitializing: true,
|
||||||
|
hasActiveInitDeferred: true,
|
||||||
|
initRequestDirection: "tail",
|
||||||
|
payload: {
|
||||||
|
agentId: "agent-1",
|
||||||
|
direction: "tail",
|
||||||
|
reset: false,
|
||||||
|
epoch: "epoch-1",
|
||||||
|
startCursor: { seq: 10 },
|
||||||
|
endCursor: { seq: 10 },
|
||||||
|
entries: [makeTimelineEntry(10, "Call-site API — exactly one primitive. Not")],
|
||||||
|
error: null,
|
||||||
|
hasNewer: false,
|
||||||
|
hasOlder: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const liveContinuation = processAgentStreamEvent({
|
||||||
|
...baseStreamInput,
|
||||||
|
event: makeTimelineEvent(" gateValue, not filterEnum."),
|
||||||
|
seq: 11,
|
||||||
|
epoch: "epoch-1",
|
||||||
|
currentTail: hydrated.tail,
|
||||||
|
currentHead: hydrated.head,
|
||||||
|
currentCursor: hydrated.cursor ?? undefined,
|
||||||
|
timestamp: new Date("2026-05-02T10:00:00.011Z"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const finalAssistantItems = [...liveContinuation.tail, ...liveContinuation.head].filter(
|
||||||
|
(item): item is Extract<StreamItem, { kind: "assistant_message" }> =>
|
||||||
|
item.kind === "assistant_message",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(finalAssistantItems).toHaveLength(1);
|
||||||
|
expect(finalAssistantItems[0]?.text).toBe(
|
||||||
|
"Call-site API — exactly one primitive. Not gateValue, not filterEnum.",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("createAgentStreamReducerQueue", () => {
|
describe("createAgentStreamReducerQueue", () => {
|
||||||
|
|||||||
@@ -991,6 +991,16 @@ export function applyStreamEvent(params: {
|
|||||||
flushHead();
|
flushHead();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (incomingKind === "assistant_message" && nextHead.length === 0) {
|
||||||
|
const tailAssistant = nextTail.at(-1);
|
||||||
|
if (tailAssistant?.kind === "assistant_message") {
|
||||||
|
nextTail = nextTail.slice(0, -1);
|
||||||
|
nextHead = [tailAssistant];
|
||||||
|
changedTail = true;
|
||||||
|
changedHead = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For streamable kinds, apply to head
|
// For streamable kinds, apply to head
|
||||||
if (incomingKind !== null && isStreamableKind(incomingKind)) {
|
if (incomingKind !== null && isStreamableKind(incomingKind)) {
|
||||||
const reduced = reduceStreamUpdate(nextHead, event, timestamp, { source });
|
const reduced = reduceStreamUpdate(nextHead, event, timestamp, { source });
|
||||||
|
|||||||
Reference in New Issue
Block a user