| 1 | import type { Action, State } from "./useController"; |
| 2 | import type { SessionTranscript } from "./transcriptStoreTypes"; |
| 3 | import type { TranscriptRecord } from "./transcriptRecordProjection"; |
| 4 | import { recordBytes } from "./transcriptRecordBytes"; |
| 5 | |
| 6 | /** Pins retain the live edge, not invisible expanded copies of durable data. */ |
| 7 | export function reclaimInvisibleBodies( |
| 8 | sessions: Iterable<SessionTranscript>, visible: (tabId: string) => boolean, |
| 9 | budget: number, total: number, publish: (session: SessionTranscript, record: TranscriptRecord) => void, |
| 10 | ): number { |
| 11 | for (const session of sessions) { |
| 12 | if (total <= budget) break; |
| 13 | if (visible(session.tabId)) continue; |
| 14 | for (const rec of session.records) { |
| 15 | if (total <= budget) break; |
| 16 | if (!rec.previewMessage) continue; |
| 17 | const before = rec.bytes; |
| 18 | rec.message = rec.previewMessage; |
| 19 | rec.previewMessage = undefined; |
| 20 | rec.resolved = undefined; |
| 21 | rec.bytes = recordBytes(rec.message); |
| 22 | session.bodyBytes += rec.bytes - before; |
| 23 | total += rec.bytes - before; |
| 24 | publish(session, rec); |
| 25 | } |
| 26 | } |
| 27 | return total; |
| 28 | } |
| 29 | |
| 30 | export function releaseCachedHistory(s: State): State { |
| 31 | return { ...s, items: s.items.slice(s.historyPrefixCount), historyPrefixCount: 0, |
| 32 | hydratePlaceholderItems: undefined, hydrateHistoryLoaded: false, |
| 33 | historyRevision: undefined, historyDigest: undefined, transcriptItemOrder: {}, |
| 34 | offscreenItems: undefined, historyStartTurn: 0, historyEndTurn: 0, historyTotalTurns: 0, |
| 35 | historyHasOlder: false, historyHasNewer: false, historyOlderLoading: false, historyNewerLoading: false, |
| 36 | historyOlderError: undefined, historyNewerError: undefined, |
| 37 | historyMutation: { seq: s.historyMutation.seq + 1, kind: "replace" } }; |
| 38 | } |
| 39 | |
| 40 | export function isIsolatedStreamDelta(action: Action, prev: State, next: State): boolean { |
| 41 | return (action.type === "stream_batch" || |
| 42 | (action.type === "event" && (action.e.kind === "text" || action.e.kind === "reasoning"))) && |
| 43 | prev.items === next.items && prev.currentAssistant === next.currentAssistant && |
| 44 | prev.pendingUser === next.pendingUser && prev.retry === next.retry; |
| 45 | } |
| 46 |