返回 DeepSeek-Reasonix
mockHistorySlice.ts
根目录 / desktop / frontend / src / __tests__ / mockHistorySlice.ts
1 // Shared HistorySliceForTab mock helper for controller tests: windows a full
2 // HistoryMessage[] the same way the bridge dev mock does (turn-budgeted
3 // suffix pages, opaque base64 cursor, stable mock entry ids). Tests that mock
4 // the desktop host stub build their HistorySliceForTab from their HistoryForTab
5 // with this.
6 //
7 // HistorySliceForTab: async (tabID, req) =>
8 // historySliceFromMessages(tabID, await appStubTable.HistoryForTab(tabID), req),
9
10 import type { HistorySlice, HistorySliceRequest } from "../lib/types";
11 import type { HistoryMessage } from "../lib/types";
12
13 export function historySliceFromMessages(
14 tabID: string,
15 messages: HistoryMessage[],
16 req: HistorySliceRequest,
17 identity: { revision?: number; digest?: string } = {},
18 ): HistorySlice {
19 const turnsOf: number[] = [];
20 let turn = 0;
21 for (const message of messages) {
22 if (message.role === "user") turn += 1;
23 turnsOf.push(turn);
24 }
25 let before = messages.length;
26 if (req.cursor) {
27 try {
28 const decoded = JSON.parse(atob(req.cursor)) as { before?: number };
29 if (typeof decoded.before === "number" && decoded.before >= 0 && decoded.before < before) before = decoded.before;
30 } catch { /* unknown cursor: serve the latest page */ }
31 }
32 const revision = identity.revision ?? 0;
33 const digest = identity.digest ?? "";
34 const empty: HistorySlice = { entries: [], nextCursor: "", hasOlder: false, totalTurns: turn, startTurn: 0, endTurn: 0, stale: false, revision, revisionKnown: revision > 0, digest };
35 if (before <= 0 || messages.length === 0) return empty;
36 const turns = Math.max(1, Math.floor(req.turns || 12));
37 const newestTurn = turnsOf[before - 1];
38 const oldestTurn = newestTurn > 0 ? Math.max(newestTurn - turns + 1, 1) : 0;
39 let lo = 0;
40 if (oldestTurn > 1) {
41 lo = before;
42 for (let i = 0; i < before; i += 1) {
43 if (turnsOf[i] >= oldestTurn) { lo = i; break; }
44 }
45 }
46 const entries = messages.slice(lo, before).map((message, index) => ({
47 entryId: `smock-${tabID}:r0:m${lo + index}:o0`,
48 turn: turnsOf[lo + index],
49 order: lo + index,
50 message,
51 refs: [],
52 }));
53 const visibleTurns = entries.map((entry) => entry.turn).filter((value) => value > 0);
54 return {
55 entries,
56 nextCursor: lo > 0 ? btoa(JSON.stringify({ v: 1, before: lo })) : "",
57 hasOlder: lo > 0,
58 totalTurns: turn,
59 startTurn: visibleTurns.length > 0 ? Math.min(...visibleTurns) : 0,
60 endTurn: visibleTurns.length > 0 ? Math.max(...visibleTurns) : 0,
61 stale: false,
62 revision,
63 revisionKnown: revision > 0,
64 digest,
65 };
66 }
67
67 lines TYPESCRIPT