1import { assert } from "@effect/vitest";
2import type { ProviderReplayTranscript } from "@t3tools/contracts";
3
4import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts";
5import {
6 assertAssistantTextIncludes,
7 assertBaseProjection,
8 assertConversationMessageRoles,
9 assertRunOrdinals,
10 assertSemanticProjectionIntegrity,
11 assertUserMessagesInclude,
12 assertVisibleTurnItemsMirrorLocalTurnItems,
13 MULTI_TURN_FIRST_PROMPT,
14 MULTI_TURN_SECOND_PROMPT,
15 projectionFor,
16} from "../shared.ts";
17
18/**
19 * Two turns in one Muse session. Muse streams reasoning beside each reply, and
20 * its per-step skill-reminder child never shows as a row.
21 */
22export function assertMuseMultiTurnOutput(
23 result: OrchestratorV2ScenarioResult,
24 transcript: ProviderReplayTranscript,
25) {
26 assertBaseProjection({
27 result,
28 transcript,
29 runCount: 2,
30 runStatuses: ["completed", "completed"],
31 });
32 const projection = projectionFor(result, transcript.scenario);
33 assertSemanticProjectionIntegrity(projection);
34 assertVisibleTurnItemsMirrorLocalTurnItems(projection);
35 assertRunOrdinals(projection, [1, 2]);
36 assertConversationMessageRoles(projection, ["user", "assistant", "user", "assistant"]);
37 assertUserMessagesInclude(projection, [MULTI_TURN_FIRST_PROMPT, MULTI_TURN_SECOND_PROMPT]);
38 assertAssistantTextIncludes(projection, "first fixture turn complete");
39 assertAssistantTextIncludes(projection, "second fixture turn complete");
40 assert.isTrue(
41 projection.turnItems.every((item) =>
42 ["user_message", "assistant_message", "reasoning", "checkpoint"].includes(item.type),
43 ),
44 `unexpected rows: ${projection.turnItems.map((item) => item.type).join(", ")}`,
45 );
46 // Both turns ran on one native session.
47 assert.lengthOf(projection.providerThreads, 1);
48}