import { BaseCheckpointSaver } from '@langchain/langgraph'; import type { Checkpoint, CheckpointMetadata, CheckpointTuple } from '@langchain/langgraph'; import type { RunnableConfig } from '@langchain/core/runnables'; import { type CheckpointStoreStats, type ReclaimSummary } from '#src/history/checkpointRetention.js'; /** * The abstract members' own parameter types, read off the base class rather than imported. * `@langchain/langgraph` re-exports `BaseCheckpointSaver` (and `Checkpoint`, `CheckpointMetadata`, * `CheckpointTuple`) but not `ChannelVersions`, `CheckpointListOptions` or `PendingWrite`, which * live only in `@langchain/langgraph-checkpoint` — a package this one does not depend on. Deriving * them keeps the signatures exactly the base class's without adding a dependency whose second * physical copy could disagree with the one LangGraph itself loads. `put`'s fourth parameter is * derived inline at its signature for the same reason, kept out of this list so the alias does not * surface as an undocumented type in the rendered API reference. */ type ListOptions = NonNullable[1]>; type PendingWrites = Parameters[1]; /** Options for {@link openCheckpointSaver}. */ export interface CheckpointSaverOptions { /** * Called when a `put` / `putWrites` could not reach the database. The handler's job is to tell * the user and to record that this conversation can no longer be resumed. Called ONCE, on the * first failed write: from then on the saver writes nothing to the database and the session runs * from memory, so there is no later failure to report. */ onWriteFailure?: (error: unknown) => void; } /** * A LangGraph `BaseCheckpointSaver` persisting to a `node:sqlite` database. * * Obtain one through {@link openCheckpointSaver}, which is fail-soft; the constructor is private so * there is no way to hold one whose tables were never created. */ export declare class GthSqliteSaver extends BaseCheckpointSaver { /** The store directory whose `threads/` this saver writes. */ private readonly storePath; /** GS2-121 — one connection per thread file this saver has opened, held until {@link close}. */ private readonly connections; /** Set by {@link close}; from then on every call that needs a file fails. */ private closed; private onWriteFailure; /** * GS2-107 — every thread this saver has written to, which is what automatic reclamation excludes. * * The thread a session writes to is not fixed at open: `clearConversation()` mints a fresh one on * `/clear`, `resetThread()` does the same before every turn on the conversational surfaces, and * `resumeConversation` rebinds onto a stored one — none of them tells this object. An * exclusion built from the id the session started with therefore names a thread nobody is writing * and misses the one that is — which on the `/clear` path is a thread no conversation row names, * i.e. exactly a reclamation candidate. Recording the ids as they arrive needs no notification at * all: whatever the runner rotated onto, the write came through here. * * GS2-117 — it also holds every thread {@link mirror} holds: a thread written after the cut (the * write reaches memory only) and a thread seeded into memory by a read. Protecting a thread is * the safe direction; reclaiming one this session still reads would pull its rows out from under * a later named read. */ private readonly writtenThreads; /** GS2-117 — what the running graph reads. See {@link CheckpointMirror}. */ private readonly mirror; /** * GS2-117 — set on the first failed durable write. From then on `put` and `putWrites` skip the * SQL and write memory only: every later write would fail the same way on a full or read-only * disk, and one that happened to land would extend a chain the handler has just marked * unresumable. */ private cut; private constructor(); /** * The connection to `threadId`'s file: the held one, or a new one. With `create` false a thread * that has no file answers `undefined` and no file is created — a read never materialises one. * Throws when the file cannot be opened or migrated; a writer turns that into a failed write. */ private connection; /** Release the held connection to one thread's file, if any. */ private release; /** * Stop writing to the database and report why, once. The reporting must never become the * failure itself: a handler that throws here would land back inside `agent.invoke()` and end the * turn — the exact outcome the degrade posture exists to avoid, arriving from the code that * implements it. */ private cutDurableWrites; /** * Open the saver over the store at `storePath`, creating the store (and splitting a single-file * store from an earlier release) as needed. Returns `null` on any failure — a store that cannot * be created, a split another process is running, a path that is not a store — so the caller can * fall back to a `MemorySaver` without a try/catch of its own. Thread files are opened lazily. */ static open(storePath: string, options?: CheckpointSaverOptions): GthSqliteSaver | null; /** * Close every held connection (fail-soft), and drop the in-memory copy with them, so a closed * saver answers no read from memory that it could not answer from disk. */ close(): void; /** The pending writes stored on disk for one checkpoint, in the order a tuple lists them. */ private readWriteRows; /** * Rebuild one stored checkpoint into the tuple LangGraph reads, pending writes and parent * included. Both sources — SQLite and memory — go through here, so a tuple read from memory * cannot differ in shape from the same checkpoint read from disk. */ private toTuple; getTuple(config: RunnableConfig): Promise; /** * When the caller named a checkpoint, echo their config back (they may carry other * `configurable` fields); when they asked for the latest, name the one actually found. */ private tupleConfig; /** * History, read from SQLite only. Nothing in this repo lists a thread's checkpoints * (`getStateHistory` is never called), and memory holds only each thread's latest checkpoint, so * after a cut this omits what reached memory alone. */ list(config: RunnableConfig, options?: ListOptions): AsyncGenerator; put(config: RunnableConfig, checkpoint: Checkpoint, metadata: CheckpointMetadata, _newVersions: Parameters[3]): Promise; putWrites(config: RunnableConfig, writes: PendingWrites, taskId: string): Promise; /** * Delete a thread's state from memory and from disk: its file, whole (see `removeThreadState`). */ deleteThread(threadId: string): Promise; /** * GS2-107 — remove the state of every thread no conversation row names, past the grace window. * The retention module owns the policy and the reasoning; this runs it over this saver's store, * releasing any connection this saver holds to a thread before its file is touched. * * **Every thread this saver wrote is excluded, always**, on top of whatever the caller names. A * caller cannot supply that set: the runner rotates threads without telling anyone, so the only * place that knows which thread the session ended on is the object the writes went through. The * union is the contract — `excludeThreadIds` adds to the protection and can never subtract from * it. */ reclaimUnresumableThreads(options?: { now?: number; graceMs?: number; excludeThreadIds?: readonly string[]; }): ReclaimSummary; /** GS2-107 — what this saver's store holds. */ storeStats(topN?: number): CheckpointStoreStats; } /** * Fail-soft open of the durable checkpoint saver over the store at `storePath`. Returns `null` * (never throws) when the store cannot be opened or created. */ export declare function openCheckpointSaver(storePath: string, options?: CheckpointSaverOptions): GthSqliteSaver | null; export {};