Skip to content

Commit 24ec637

Browse files
committed
test(client): add tests for reasoning message preservation after MESSAGES_SNAPSHOT
Follow-up to #1370 — adds regression tests to ensure reasoning messages survive snapshot merges.
1 parent 67fa09c commit 24ec637

1 file changed

Lines changed: 91 additions & 0 deletions

File tree

sdks/typescript/packages/client/src/apply/__tests__/default.activity.test.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,97 @@ describe("MESSAGES_SNAPSHOT preserves activity messages", () => {
567567
expect(msgs.map((m) => m.id)).toEqual(["m1", "act-1", "m2", "m3"]);
568568
});
569569

570+
it("preserves reasoning messages after MESSAGES_SNAPSHOT", async () => {
571+
const initial: Message[] = [
572+
{ id: "m1", role: "user", content: "hello" },
573+
{ id: "r1", role: "reasoning", content: "Let me think about this..." },
574+
{ id: "m2", role: "assistant", content: "hi there" },
575+
] as Message[];
576+
577+
const events$ = new Subject<BaseEvent>();
578+
const agent = createAgent(initial);
579+
const result$ = defaultApplyEvents(makeInput(initial), events$, agent, []);
580+
const updatesPromise = firstValueFrom(result$.pipe(toArray()));
581+
582+
// Snapshot does NOT contain the reasoning message (backends don't include them)
583+
events$.next({
584+
type: EventType.MESSAGES_SNAPSHOT,
585+
messages: [
586+
{ id: "m1", role: "user", content: "hello" },
587+
{ id: "m2", role: "assistant", content: "hi there" },
588+
],
589+
} as MessagesSnapshotEvent);
590+
591+
events$.complete();
592+
const updates = await updatesPromise;
593+
594+
const msgs = updates[0]?.messages!;
595+
expect(msgs.length).toBe(3);
596+
expect(msgs.map((m) => m.id)).toEqual(["m1", "r1", "m2"]);
597+
expect(msgs[1].role).toBe("reasoning");
598+
expect(msgs[1].content).toBe("Let me think about this...");
599+
});
600+
601+
it("preserves both activity and reasoning messages after MESSAGES_SNAPSHOT", async () => {
602+
const initial: Message[] = [
603+
{ id: "m1", role: "user", content: "explain this" },
604+
{ id: "act-1", role: "activity", activityType: "PLAN", content: { tasks: ["research"] } },
605+
{ id: "r1", role: "reasoning", content: "The user wants an explanation..." },
606+
{ id: "m2", role: "assistant", content: "Here is the explanation" },
607+
] as Message[];
608+
609+
const events$ = new Subject<BaseEvent>();
610+
const agent = createAgent(initial);
611+
const result$ = defaultApplyEvents(makeInput(initial), events$, agent, []);
612+
const updatesPromise = firstValueFrom(result$.pipe(toArray()));
613+
614+
events$.next({
615+
type: EventType.MESSAGES_SNAPSHOT,
616+
messages: [
617+
{ id: "m1", role: "user", content: "explain this" },
618+
{ id: "m2", role: "assistant", content: "Here is the explanation" },
619+
],
620+
} as MessagesSnapshotEvent);
621+
622+
events$.complete();
623+
const updates = await updatesPromise;
624+
625+
const msgs = updates[0]?.messages!;
626+
expect(msgs.length).toBe(4);
627+
expect(msgs.map((m) => m.id)).toEqual(["m1", "act-1", "r1", "m2"]);
628+
expect(msgs[1].role).toBe("activity");
629+
expect(msgs[2].role).toBe("reasoning");
630+
});
631+
632+
it("reasoning messages are not replaced by snapshot data", async () => {
633+
const initial: Message[] = [
634+
{ id: "m1", role: "user", content: "hello" },
635+
{ id: "r1", role: "reasoning", content: "original reasoning" },
636+
{ id: "m2", role: "assistant", content: "response" },
637+
] as Message[];
638+
639+
const events$ = new Subject<BaseEvent>();
640+
const agent = createAgent(initial);
641+
const result$ = defaultApplyEvents(makeInput(initial), events$, agent, []);
642+
const updatesPromise = firstValueFrom(result$.pipe(toArray()));
643+
644+
events$.next({
645+
type: EventType.MESSAGES_SNAPSHOT,
646+
messages: [
647+
{ id: "m1", role: "user", content: "hello" },
648+
{ id: "m2", role: "assistant", content: "response" },
649+
],
650+
} as MessagesSnapshotEvent);
651+
652+
events$.complete();
653+
const updates = await updatesPromise;
654+
655+
const msgs = updates[0]?.messages!;
656+
const reasoning = msgs.find((m) => m.id === "r1")!;
657+
expect(reasoning.role).toBe("reasoning");
658+
expect(reasoning.content).toBe("original reasoning");
659+
});
660+
570661
it("preserves activity position when a message ID changes in snapshot", async () => {
571662
// Simulates the real-world scenario: streaming creates a tool message with ID "tool-stream",
572663
// but MESSAGES_SNAPSHOT has the same tool message with a different canonical ID "tool-canon".

0 commit comments

Comments
 (0)