/** * The durable part of a streamed model response. Deliberately no text or * provider payload is accepted here: a checkpoint is a resume cursor, not a * second copy of the response. */ export type CheckpointState = "STREAMING" | "COMPLETED" | "FAILED"; export interface AtomicCheckpoint { state: CheckpointState; byteCount: number; chunkCount: number; sha256: string; } export interface AtomicCheckpointStore { writeTemporary(path: string, checkpoint: AtomicCheckpoint): Promise | void; renameTemporary(temporaryPath: string, targetPath: string): Promise | void; } const CHECKPOINT_KEYS = ["state", "byteCount", "chunkCount", "sha256"] as const; const HASH_PATTERN = /^[a-f0-9]{64}$/u; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } /** Validate and clone so callers cannot mutate a value after the atomic write. */ export function createAtomicCheckpoint(value: AtomicCheckpoint): AtomicCheckpoint { if (!isRecord(value)) throw new TypeError("CHECKPOINT_INVALID: checkpoint must be an object"); const keys = Object.keys(value).sort(); const expected = [...CHECKPOINT_KEYS].sort(); if (keys.length !== expected.length || keys.some((key, index) => key !== expected[index])) { throw new TypeError("CHECKPOINT_INVALID: checkpoint may contain only state, byteCount, chunkCount and sha256"); } if (!["STREAMING", "COMPLETED", "FAILED"].includes(value.state as string)) { throw new TypeError("CHECKPOINT_INVALID: state is invalid"); } if (!Number.isSafeInteger(value.byteCount) || value.byteCount < 0 || !Number.isSafeInteger(value.chunkCount) || value.chunkCount < 0) { throw new TypeError("CHECKPOINT_INVALID: counts must be non-negative safe integers"); } if (typeof value.sha256 !== "string" || !HASH_PATTERN.test(value.sha256)) { throw new TypeError("CHECKPOINT_INVALID: sha256 must be a lowercase SHA-256 digest"); } return Object.freeze({ state: value.state as CheckpointState, byteCount: value.byteCount, chunkCount: value.chunkCount, sha256: value.sha256, }); } /** * Write a checkpoint to a temporary key and publish it with one rename. A * failed write never replaces the previously committed checkpoint. */ export async function commitAtomicCheckpoint(store: AtomicCheckpointStore, targetPath: string, value: AtomicCheckpoint): Promise { if (!store || typeof store.writeTemporary !== "function" || typeof store.renameTemporary !== "function") { throw new TypeError("CHECKPOINT_STORE_INVALID: atomic write and rename are required"); } if (typeof targetPath !== "string" || targetPath.length === 0) throw new TypeError("CHECKPOINT_TARGET_INVALID: target path is required"); const checkpoint = createAtomicCheckpoint(value); const temporaryPath = `${targetPath}.tmp`; await store.writeTemporary(temporaryPath, checkpoint); await store.renameTemporary(temporaryPath, targetPath); return checkpoint; } /** In-memory store useful for recovery tests and callers without OPFS. */ export function createMemoryCheckpointStore(): AtomicCheckpointStore & { get(path: string): AtomicCheckpoint | undefined } { const files = new Map(); return { writeTemporary(path, checkpoint) { files.set(path, createAtomicCheckpoint(checkpoint)); }, renameTemporary(temporaryPath, targetPath) { const checkpoint = files.get(temporaryPath); if (!checkpoint) throw new Error("CHECKPOINT_RENAME_FAILED: temporary checkpoint is missing"); files.set(targetPath, checkpoint); files.delete(temporaryPath); }, get(path) { return files.get(path); }, }; } export const createCheckpoint = createAtomicCheckpoint; export const commitCheckpoint = commitAtomicCheckpoint;