mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-22 17:49:48 +02:00
80 lines
2.6 KiB
TypeScript
80 lines
2.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { TaskMessagePayload } from "@multica/core/types/events";
|
|
import { appendTimelineItem, buildTimeline, coalesceTimelineItems, type TimelineItem } from "./build-timeline";
|
|
|
|
function message(seq: number, type: TaskMessagePayload["type"], content?: string): TaskMessagePayload {
|
|
return {
|
|
task_id: "task-1",
|
|
issue_id: "issue-1",
|
|
seq,
|
|
type,
|
|
content,
|
|
};
|
|
}
|
|
|
|
describe("task transcript timeline", () => {
|
|
it("merges adjacent text and thinking fragments split by streaming flushes", () => {
|
|
const items = buildTimeline([
|
|
message(2, "text", "world"),
|
|
message(1, "text", "hello "),
|
|
message(3, "thinking", "step "),
|
|
message(4, "thinking", "one"),
|
|
]);
|
|
|
|
expect(items).toEqual([
|
|
expect.objectContaining({ seq: 1, type: "text", content: "hello world" }),
|
|
expect.objectContaining({ seq: 3, type: "thinking", content: "step one" }),
|
|
]);
|
|
});
|
|
|
|
it("does not merge across tool or error boundaries", () => {
|
|
const items = coalesceTimelineItems([
|
|
{ seq: 1, type: "text", content: "before" },
|
|
{ seq: 2, type: "tool_use", tool: "bash" },
|
|
{ seq: 3, type: "text", content: "after" },
|
|
{ seq: 4, type: "error", content: "failed" },
|
|
{ seq: 5, type: "text", content: "done" },
|
|
]);
|
|
|
|
expect(items.map((item) => item.content ?? item.tool)).toEqual([
|
|
"before",
|
|
"bash",
|
|
"after",
|
|
"failed",
|
|
"done",
|
|
]);
|
|
});
|
|
|
|
it("coalesces newly appended live text with the previous text item", () => {
|
|
const existing: TimelineItem[] = [{ seq: 1, type: "text", content: "hello" }];
|
|
const items = appendTimelineItem(existing, { seq: 2, type: "text", content: " world" });
|
|
|
|
expect(items).toEqual([
|
|
expect.objectContaining({ seq: 1, type: "text", content: "hello world" }),
|
|
]);
|
|
});
|
|
|
|
it("coalesces out-of-order raw text by sequence", () => {
|
|
const existing: TimelineItem[] = [
|
|
{ seq: 1, type: "text", content: "A" },
|
|
{ seq: 3, type: "text", content: "C" },
|
|
];
|
|
const items = appendTimelineItem(existing, { seq: 2, type: "text", content: "B" });
|
|
|
|
expect(items).toEqual([
|
|
expect.objectContaining({ seq: 1, type: "text", content: "ABC" }),
|
|
]);
|
|
});
|
|
|
|
it("redacts secrets after adjacent chunks are coalesced", () => {
|
|
const items = buildTimeline([
|
|
message(1, "text", "Authorization: Bearer abc123xyz."),
|
|
message(2, "text", "def456"),
|
|
]);
|
|
|
|
expect(items[0]?.content).toBe("Authorization: Bearer [REDACTED]");
|
|
expect(items[0]?.content).not.toContain("abc123xyz");
|
|
expect(items[0]?.content).not.toContain("def456");
|
|
});
|
|
});
|