refactor(app): unify assistant fork boundary resolution (#2091)
This commit is contained in:
@@ -1,9 +1,6 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import type { StreamItem } from "@/types/stream";
|
import type { StreamItem } from "@/types/stream";
|
||||||
import {
|
import { resolveAssistantTurnForkBoundary } from "./turn-boundary";
|
||||||
resolveAssistantTurnBoundaryMessageId,
|
|
||||||
resolveAssistantTurnForkBoundary,
|
|
||||||
} from "./turn-boundary";
|
|
||||||
|
|
||||||
function timestamp(seed: number): Date {
|
function timestamp(seed: number): Date {
|
||||||
return new Date(`2026-01-01T00:00:${seed.toString().padStart(2, "0")}.000Z`);
|
return new Date(`2026-01-01T00:00:${seed.toString().padStart(2, "0")}.000Z`);
|
||||||
@@ -32,40 +29,6 @@ function assistantMessage(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("resolveAssistantTurnBoundaryMessageId", () => {
|
|
||||||
it("uses the selected assistant message id", () => {
|
|
||||||
const selected = assistantMessage("assistant-1", 2, "msg-assistant-1");
|
|
||||||
|
|
||||||
expect(
|
|
||||||
resolveAssistantTurnBoundaryMessageId({
|
|
||||||
items: [userMessage("user-1", 1), selected],
|
|
||||||
startIndex: 1,
|
|
||||||
}),
|
|
||||||
).toBe("msg-assistant-1");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not borrow a boundary id from another assistant in the same turn", () => {
|
|
||||||
const first = assistantMessage("assistant-1", 2, "msg-assistant-1");
|
|
||||||
const selected = assistantMessage("assistant-2", 3);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
resolveAssistantTurnBoundaryMessageId({
|
|
||||||
items: [userMessage("user-1", 1), first, selected],
|
|
||||||
startIndex: 2,
|
|
||||||
}),
|
|
||||||
).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("requires the selected item to be an assistant message", () => {
|
|
||||||
expect(
|
|
||||||
resolveAssistantTurnBoundaryMessageId({
|
|
||||||
items: [userMessage("user-1", 1), assistantMessage("assistant-1", 2, "msg-assistant-1")],
|
|
||||||
startIndex: 0,
|
|
||||||
}),
|
|
||||||
).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("resolveAssistantTurnForkBoundary", () => {
|
describe("resolveAssistantTurnForkBoundary", () => {
|
||||||
it("forks a failed assistant turn from its Paseo timeline cursor without a provider message id", () => {
|
it("forks a failed assistant turn from its Paseo timeline cursor without a provider message id", () => {
|
||||||
const failedTurn = {
|
const failedTurn = {
|
||||||
@@ -117,6 +80,29 @@ describe("resolveAssistantTurnForkBoundary", () => {
|
|||||||
).toEqual({ boundaryMessageId: "msg-assistant-1" });
|
).toEqual({ boundaryMessageId: "msg-assistant-1" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not borrow a provider message id from another assistant in the same turn", () => {
|
||||||
|
const first = assistantMessage("assistant-1", 2, "msg-assistant-1");
|
||||||
|
const selected = assistantMessage("assistant-2", 3);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveAssistantTurnForkBoundary({
|
||||||
|
items: [userMessage("user-1", 1), first, selected],
|
||||||
|
startIndex: 2,
|
||||||
|
supportsTimelineCursor: false,
|
||||||
|
}),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requires the selected item to be an assistant message", () => {
|
||||||
|
expect(
|
||||||
|
resolveAssistantTurnForkBoundary({
|
||||||
|
items: [userMessage("user-1", 1), assistantMessage("assistant-1", 2, "msg-assistant-1")],
|
||||||
|
startIndex: 0,
|
||||||
|
supportsTimelineCursor: false,
|
||||||
|
}),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("does not offer an unavailable boundary", () => {
|
it("does not offer an unavailable boundary", () => {
|
||||||
expect(
|
expect(
|
||||||
resolveAssistantTurnForkBoundary({
|
resolveAssistantTurnForkBoundary({
|
||||||
|
|||||||
@@ -4,18 +4,6 @@ export type AssistantTurnForkBoundary =
|
|||||||
| { boundaryCursor: TimelinePosition; boundaryMessageId?: string }
|
| { boundaryCursor: TimelinePosition; boundaryMessageId?: string }
|
||||||
| { boundaryCursor?: undefined; boundaryMessageId: string };
|
| { boundaryCursor?: undefined; boundaryMessageId: string };
|
||||||
|
|
||||||
export function resolveAssistantTurnBoundaryMessageId(input: {
|
|
||||||
items: readonly StreamItem[];
|
|
||||||
startIndex: number;
|
|
||||||
}): string | undefined {
|
|
||||||
const item = input.items[input.startIndex];
|
|
||||||
if (item?.kind !== "assistant_message") {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
// Forking without the selected assistant's durable message id would send the wrong slice.
|
|
||||||
return item.messageId || undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveAssistantTurnForkBoundary(input: {
|
export function resolveAssistantTurnForkBoundary(input: {
|
||||||
items: readonly StreamItem[];
|
items: readonly StreamItem[];
|
||||||
startIndex: number;
|
startIndex: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user