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 assertExecutionNodeKinds,
10 assertRuntimeRequestCounts,
11 assertSemanticProjectionIntegrity,
12 assertTurnItemTypes,
13 assertUserMessagesInclude,
14 assertVisibleTurnItemsMirrorLocalTurnItems,
15 projectionFor,
16 WEB_SEARCH_PROMPT,
17} from "../shared.ts";
18
19const WEB_SEARCH_QUERY = "FIFA World Cup 2026 ticket prices official";
20
21export function assertWebSearchOutput(
22 result: OrchestratorV2ScenarioResult,
23 transcript: ProviderReplayTranscript,
24) {
25 assertBaseProjection({ result, transcript, runCount: 1, runStatuses: ["completed"] });
26
27 const projection = projectionFor(result, transcript.scenario);
28 assertSemanticProjectionIntegrity(projection);
29 assertVisibleTurnItemsMirrorLocalTurnItems(projection);
30 assertExecutionNodeKinds(projection, ["root_turn", "tool_call", "assistant_message"]);
31 // gpt-6 models announce the search in a commentary message before the final answer.
32 assertConversationMessageRoles(projection, ["user", "assistant", "assistant"]);
33 assertTurnItemTypes(projection, ["user_message", "web_search", "assistant_message"]);
34 assertRuntimeRequestCounts(projection, { total: 0 });
35 assertUserMessagesInclude(projection, [WEB_SEARCH_PROMPT]);
36 assertAssistantTextIncludes(projection, "web search fixture complete");
37
38 const webSearchItems = projection.turnItems.filter((item) => item.type === "web_search");
39 assert.lengthOf(webSearchItems, 1);
40 assert.equal(webSearchItems[0]?.status, "completed");
41 assert.include(webSearchItems[0]?.patterns ?? [], WEB_SEARCH_QUERY);
42}