返回 DeepSeek-Reasonix
bridgeBenchFixtures.ts
根目录 / desktop / frontend / src / lib / bridgeBenchFixtures.ts
1 // bridgeBenchFixtures — deterministic heavyweight mock sessions for the
2 // real-DOM benchmark harness (desktop/frontend/bench), served by the dev mock
3 // when the page URL carries ?mock=bench. This module is imported lazily from
4 // bridge.ts so the generators never land in the eager production bundle.
5
6 import type { HistoryMessage, HistoryToolCall } from "./types";
7
8 // Soak tests need async hydration, but not simulated backend latency per cycle.
9 // Geometry/browser fixtures keep their delayed expansion contract by default.
10 export function benchHydrationDelay(search = window.location.search): number {
11 const params = new URLSearchParams(search);
12 return params.get("mock") === "bench" && params.get("bench") === "1"
13 && params.get("app-lifecycle-probe") === "1" && params.get("bench-hydration") === "soak" ? 0 : 1_500;
14 }
15
16 // ── Benchmark fixtures (?mock=bench, Phase F) ─────────────────────────────
17 // Fixed diagnostic sessions for the real-DOM performance harness
18 // (desktop/frontend/bench). Content is deterministic and generated once per
19 // page load. Shapes follow the plan's fixtures, mirrored from the Go-side
20 // history_slice tests: a tool-dense 38-turn session (~3.2k provider
21 // messages), a markdown-heavy 46-turn session (~600 messages incl. one
22 // ~500KiB answer with a big table and one oversized code block), a small
23 // 6-turn session, and a single turn with thousands of messages.
24 const benchFixtureCache = new Map<string, HistoryMessage[]>();
25 const benchFixture = (key: string, build: () => HistoryMessage[]): HistoryMessage[] => {
26 const cached = benchFixtureCache.get(key);
27 if (cached) return cached;
28 const built = build();
29 benchFixtureCache.set(key, built);
30 return built;
31 };
32 const benchToolOutput = (turn: number, index: number): string =>
33 [
34 `ok turn=${turn} call=${index}`,
35 "status: success",
36 `duration_ms: ${(turn * 7 + index * 13) % 420}`,
37 `detail: ${"x".repeat(120)}`,
38 ].join("\n");
39 const benchToolTurn = (turn: number, callCount: number, answer: string): HistoryMessage[] => {
40 const toolCalls: HistoryToolCall[] = [];
41 const results: HistoryMessage[] = [];
42 for (let k = 0; k < callCount; k += 1) {
43 const id = `bench-t${turn}-call-${k}`;
44 const readOnly = k % 3 !== 0;
45 toolCalls.push({
46 id,
47 name: readOnly ? "read_file" : "bash",
48 arguments: JSON.stringify(readOnly ? { path: `internal/bench/pkg-${k}/mod.go` } : { command: `go test ./internal/bench/pkg-${k}` }),
49 resolvedReadOnly: readOnly,
50 subject: readOnly ? `internal/bench/pkg-${k}/mod.go` : `go test pkg-${k}`,
51 });
52 results.push({ role: "tool", toolCallId: id, toolName: toolCalls[k].name, content: benchToolOutput(turn, k) });
53 }
54 return [
55 { role: "user", content: `bench turn ${turn}: run the verification batch and summarize per-package results.` },
56 { role: "assistant", content: "", reasoning: `planning verification batch ${turn}: ${callCount} checks.`, toolCalls },
57 ...results,
58 ...(answer ? [{ role: "assistant", content: answer, workDurationMs: 1200 }] : []),
59 ];
60 };
61 const benchToolDenseHistory = (): HistoryMessage[] => {
62 // 38 visible turns × 86 messages = 3268 provider messages (nominal 3255).
63 const messages: HistoryMessage[] = [];
64 for (let turn = 1; turn <= 38; turn += 1) messages.push(...benchToolTurn(turn, 42, turn % 4 === 0 ? `Batch ${turn} done: all checks green.` : ""));
65 return messages;
66 };
67 const benchMarkdownSection = (turn: number): string =>
68 [
69 `## Turn ${turn} summary`,
70 "",
71 "The verification sweep completed with all packages green. Key observations:",
72 "",
73 "- display-index hits stayed high across paged reads",
74 "- long tasks remained under the main-thread budget",
75 "- cache weights stayed within their declared byte budgets",
76 "",
77 "```ts",
78 "export function digest(values: number[]): number {",
79 " return values.reduce((acc, v) => (acc * 31 + v) | 0, 7);",
80 "}",
81 "```",
82 "",
83 "| package | tests | duration ms |",
84 "| --- | ---: | ---: |",
85 ...Array.from({ length: 12 }, (_, k) => `| pkg-${k} | ${20 + k * 3} | ${40 + ((turn * 17 + k * 29) % 300)} |`),
86 "",
87 ].join("\n");
88 const benchBigMarkdownAnswer = (): string => {
89 // ~500KiB answer: a giant table plus repeated prose/code sections.
90 const parts: string[] = ["# Full verification report", ""];
91 parts.push("| row | package | tests | duration ms | status |", "| ---: | --- | ---: | ---: | --- |");
92 for (let row = 0; row < 4000; row += 1) {
93 parts.push(`| ${row} | pkg-${row % 64} | ${(row * 7) % 90} | ${(row * 13) % 800} | ${row % 11 === 0 ? "flaky" : "green"} |`);
94 }
95 let body = parts.join("\n");
96 let section = 0;
97 while (body.length < 500 * 1024) {
98 section += 1;
99 body += `\n\n${benchMarkdownSection(1000 + section)}`;
100 }
101 return body;
102 };
103 const benchOversizedCodeBlock = (): string => {
104 // Single >64KiB code block (a content-ref candidate on the real backend).
105 const line = "const row = await db.query('select id, payload from bench where shard = $1', [shard]); // ";
106 const lines = Math.ceil((300 * 1024) / line.length);
107 return ["Here is the full generated migration:", "", "```sql", ...Array.from({ length: lines }, (_, k) => `-- ${k} ${line}`), "```"].join("\n");
108 };
109 const benchMarkdownHeavyHistory = (): HistoryMessage[] => {
110 // 46 visible turns; 13 messages per normal turn (10 tool pairs), plus the
111 // newest turn carrying the ~500KiB report and the oversized code block.
112 const messages: HistoryMessage[] = [];
113 for (let turn = 1; turn <= 45; turn += 1) messages.push(...benchToolTurn(turn, 5, benchMarkdownSection(turn)));
114 messages.push(
115 { role: "user", content: "bench final turn: produce the full verification report with the big table, then the migration SQL." },
116 { role: "assistant", content: benchBigMarkdownAnswer(), workDurationMs: 2400 },
117 { role: "assistant", content: benchOversizedCodeBlock(), workDurationMs: 800 },
118 );
119 return messages;
120 };
121 const benchSmallHistory = (): HistoryMessage[] => {
122 // 6 visible turns × 78 messages = 468 provider messages (nominal 473).
123 const messages: HistoryMessage[] = [];
124 for (let turn = 1; turn <= 6; turn += 1) {
125 const answer = turn === 6
126 ? [
127 "# Asynchronously hydrated verification appendix",
128 ...Array.from({ length: 1_200 }, (_, row) => `- package-${row % 42}: verified row ${row} with stable virtual measurements`),
129 "ASYNC LAYOUT EXPANSION COMPLETE",
130 ].join("\n")
131 : `Batch ${turn} summary.`;
132 messages.push(...benchToolTurn(turn, 38, answer));
133 }
134 return messages;
135 };
136 const benchGiantTurnHistory = (): HistoryMessage[] => {
137 // A single turn with thousands of messages (1000 tool pairs).
138 return benchToolTurn(1, 1000, "Single-turn sweep complete.");
139 };
140
141 const benchWindowedHistory = (): HistoryMessage[] => {
142 const messages: HistoryMessage[] = [];
143 for (let turn = 1; turn <= 1_000; turn += 1) {
144 messages.push(
145 { role: "user", content: `windowed turn ${turn}: verify the stable block anchor.` },
146 {
147 role: "assistant",
148 reasoning: turn === 950 ? Array.from({ length: 40 }, (_, line) => `Reasoning paragraph ${line + 1}: verify expanded cold history geometry.`).join("\n\n") : undefined,
149 content: turn % 25 === 0
150 ? `## Windowed turn ${turn}\n\n中文 English emoji ✅\n\n| turn | status |\n| ---: | --- |\n| ${turn} | stable |\n\n\`\`\`ts\nconst turn = ${turn};\n\`\`\``
151 : `Windowed result ${turn}: the block identity and native viewport geometry remain stable.`,
152 },
153 );
154 }
155 return messages;
156 };
157
158 const benchReportedLongTurnHistory = (): HistoryMessage[] => {
159 // Sanitized reproduction of the reported shape: one user turn, 70 tool
160 // results, and 44 separately measured assistant blocks. Keeping the height
161 // distribution matters; no user content or exported session data is used.
162 const messages: HistoryMessage[] = [
163 { role: "user", content: "Inspect the workspace, apply the changes, and verify the result." },
164 ];
165 for (let block = 0; block < 44; block += 1) {
166 const callCount = block < 26 ? 2 : 1;
167 const toolCalls: HistoryToolCall[] = [];
168 for (let call = 0; call < callCount; call += 1) {
169 const id = `reported-block-${block}-call-${call}`;
170 toolCalls.push({
171 id,
172 name: block % 3 === 0 ? "bash" : "read_file",
173 arguments: JSON.stringify(block % 3 === 0
174 ? { command: `pnpm test --filter reported-${block}-${call}` }
175 : { path: `src/reported/section-${block}-${call}.ts` }),
176 resolvedReadOnly: block % 3 !== 0,
177 subject: `reported section ${block + 1}.${call + 1}`,
178 });
179 }
180 messages.push({
181 role: "assistant",
182 content: [
183 `### Verification block ${block + 1}`,
184 "",
185 `Processed the deterministic fixture section ${block + 1}.`,
186 "",
187 ...Array.from({ length: 2 + (block % 5) }, (_, row) => `- check ${row + 1}: stable measurement ${block}-${row}`),
188 ].join("\n"),
189 reasoning: `Planning verification block ${block + 1}.`,
190 toolCalls,
191 });
192 messages.push(...toolCalls.map((toolCall, call) => ({
193 role: "tool" as const,
194 toolCallId: toolCall.id,
195 toolName: toolCall.name,
196 content: benchToolOutput(block + 1, call),
197 })));
198 }
199 messages.push({ role: "assistant", content: "Reported long turn complete." });
200 return messages;
201 };
202
203 const benchGeometryContractHistory = (): HistoryMessage[] => {
204 // One semantic turn with many heterogeneous rows. This verifies that row
205 // density does not change the block-level anchoring and mounting contract.
206 const messages: HistoryMessage[] = [
207 { role: "user", content: "验证首次向上遍历未访问的折叠过程行,记录逐帧几何和滚动方向。" },
208 ];
209 for (let block = 0; block < 60; block += 1) {
210 const id = `geometry-contract-${block}`;
211 const readOnly = block % 3 !== 0;
212 // Start the reasoning tail earlier so the reduced fixture still carries
213 // the 31 folded reasoning rows required by the contract gate.
214 const reasoningOrdinal = block - 29;
215 const reasoning = reasoningOrdinal >= 0
216 ? [
217 `第 ${reasoningOrdinal + 1} 段分析:验证折叠状态不读取完整正文。`,
218 ...Array.from(
219 { length: 8 + reasoningOrdinal * 5 },
220 (_, line) => `reasoning ${reasoningOrdinal + 1}.${line + 1} 中文 English ${"x".repeat(32 + (line % 4) * 16)}`,
221 ),
222 ].join("\n")
223 : undefined;
224 messages.push({
225 role: "assistant",
226 reasoning,
227 content: [
228 `### Geometry block ${block + 1}`,
229 "",
230 `折叠布局检查 ${block + 1} 完成,answer 保持内容感知估高。`,
231 "",
232 block % 5 === 0 ? "```ts\nconst stable = layoutVariant === 'reasoning-summary';\n```" : "- 中文换行\n- English wrapping",
233 ].join("\n"),
234 toolCalls: [
235 {
236 id,
237 name: readOnly ? "read_file" : "bash",
238 arguments: JSON.stringify(readOnly
239 ? { path: `src/geometry/section-${block}.ts` }
240 : { command: `pnpm test --filter geometry-${block}` }),
241 resolvedReadOnly: readOnly,
242 subject: `geometry section ${block + 1}`,
243 },
244 ...(block >= 31 && block < 55 ? [{
245 id: `${id}-stopped`,
246 name: "bash",
247 arguments: JSON.stringify({ command: `pnpm typecheck --filter geometry-${block}` }),
248 resolvedReadOnly: false,
249 subject: `geometry stopped section ${block + 1}`,
250 }] : []),
251 ],
252 });
253 messages.push({
254 role: "tool",
255 toolCallId: id,
256 toolName: readOnly ? "read_file" : "bash",
257 content: `ok block=${block + 1}\nstatus: success\n${"measurement stable ".repeat(4)}`,
258 });
259 }
260 messages.push({ role: "assistant", content: "Geometry contract fixture complete." });
261 return messages;
262 };
263
264 // Ref-resolution storm fixture (#8657): the newest page of a long session
265 // carries many ref-replaced fields, so opening the session fires a paced
266 // stream of history_items_patch invalidations — the exact load that used to
267 // remount the virtual list on every scroll idle and strand the view at
268 // estimate-based restore landings. The marker sits past the 4KiB preview
269 // cut, so it only appears in the DOM once the ref has resolved.
270 export const BENCH_STORM_MARKER = "BENCH STORM HYDRATION RESOLVED";
271 const benchStormAnswer = (turn: number): string => {
272 let body = [
273 `## Storm turn ${turn} verification report`,
274 "",
275 "Inline preview summary: the batch completed and per-package details follow.",
276 "",
277 ].join("\n");
278 let row = 0;
279 while (body.length < 9 * 1024) {
280 row += 1;
281 body += `\n- storm-${turn}-${row}: resolved payload ${"y".repeat(90)}`;
282 }
283 body += `\n- storm-${turn}-FINAL ${BENCH_STORM_MARKER}`;
284 return body;
285 };
286 const benchStormReasoning = (turn: number): string => {
287 let body = `planning storm turn ${turn}: gather results, then tabulate.\n`;
288 while (body.length < 6 * 1024) body += `reasoning fragment ${turn} ${"z".repeat(90)}\n`;
289 return `${body}${BENCH_STORM_MARKER}`;
290 };
291 const benchStormHistory = (): HistoryMessage[] => {
292 // 40 visible turns. The newest 12 turns (exactly the page a session opens
293 // with) carry ref-replaced answer+reasoning fields; turns 1-28 are small
294 // and eager. Opening the session resolves ~24 refs at a paced interval.
295 const messages: HistoryMessage[] = [];
296 for (let turn = 1; turn <= 40; turn += 1) {
297 if (turn <= 28) {
298 messages.push(...benchToolTurn(turn, 3, `Batch ${turn} summary.`));
299 } else {
300 messages.push(
301 { role: "user", content: `storm turn ${turn}: produce the verification report.` },
302 { role: "assistant", content: benchStormAnswer(turn), reasoning: benchStormReasoning(turn), workDurationMs: 900 },
303 );
304 }
305 }
306 return messages;
307 };
308
309 export const BENCH_SELECTION_TABLE_MARKER = "SELECTION REPAINT TARGET";
310
311 const benchSelectionTableHistory = (): HistoryMessage[] => {
312 const messages: HistoryMessage[] = [];
313 for (let turn = 1; turn <= 12; turn += 1) {
314 messages.push(
315 { role: "user", content: `selection fixture turn ${turn}: summarize the stable table inputs.` },
316 {
317 role: "assistant",
318 content: [
319 `## Selection fixture turn ${turn}`,
320 "",
321 "This deterministic paragraph keeps the virtual transcript away from its native top while all rows remain settled.",
322 "",
323 "- no streaming output",
324 "- no asynchronous content refs",
325 "- stable Markdown geometry",
326 ].join("\n"),
327 },
328 );
329 }
330 messages.push(
331 { role: "user", content: "Render the final selection regression table." },
332 {
333 role: "assistant",
334 content: [
335 "## WebView2 selection repaint regression",
336 "",
337 "| check | target | expected |",
338 "| --- | --- | --- |",
339 "| native multi-click | **SELECTION REPAINT TARGET** | transcript pixels remain stable |",
340 "| scroll geometry | fixed table row | no viewport movement |",
341 "| portal lifetime | one toolbar host | no mount churn |",
342 "",
343 "The table is the final settled row in this fixture.",
344 ].join("\n"),
345 },
346 );
347 return messages;
348 };
349
350 /** The bench session for a mock topic, or undefined for non-bench topics. */
351 export function benchTopicHistory(topicId: string): HistoryMessage[] | undefined {
352 switch (topicId) {
353 case "topic_bench_tools":
354 return benchFixture("tools", benchToolDenseHistory);
355 case "topic_bench_markdown":
356 return benchFixture("markdown", benchMarkdownHeavyHistory);
357 case "topic_bench_small":
358 return benchFixture("small", benchSmallHistory);
359 case "topic_bench_giant_turn":
360 return benchFixture("giant", benchGiantTurnHistory);
361 case "topic_bench_windowed":
362 return benchFixture("windowed", benchWindowedHistory);
363 case "topic_bench_reported_long_turn":
364 return benchFixture("reported-long-turn", benchReportedLongTurnHistory);
365 case "topic_bench_geometry_contract":
366 return benchFixture("geometry-contract", benchGeometryContractHistory);
367 case "topic_bench_storm":
368 return benchFixture("storm", benchStormHistory);
369 case "topic_bench_selection_table":
370 return benchFixture("selection-table", benchSelectionTableHistory);
371 default:
372 return undefined;
373 }
374 }
375
375 lines TYPESCRIPT