{"version":3,"sources":["../../src/session-store/firestore.ts"],"sourcesContent":["/**\n * Copyright 2026 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n  Firestore,\n  type CollectionReference,\n  type DocumentReference,\n  type DocumentSnapshot,\n  type Transaction,\n} from '@google-cloud/firestore';\nimport {\n  GenkitError,\n  applyPatch,\n  diff,\n  type JsonPatch,\n  type SessionSnapshot,\n  type SessionState,\n  type SessionStore,\n  type SessionStoreOptions,\n  type SnapshotMutator,\n} from 'genkit/beta';\nimport { logger } from 'genkit/logging';\n\n/**\n * Default number of turns between full-state checkpoints.\n *\n * Chosen to favor the common chat / `useChat` workload, where per-turn state is\n * small and read cost dominates. Per-save reconstruction reads grow ~linearly\n * with the interval while checkpoint write/storage cost shrinks with it, so the\n * op-cost optimum is roughly `sqrt(6 * checkpointShardCount)` (≈small for tiny\n * state); 25 sits near that optimum for chat while staying conservative for\n * larger states. Raise it (e.g. 50-100) for large per-turn state retained\n * long-term; lower it (e.g. 10) for small-state, read-heavy sessions.\n */\nconst DEFAULT_CHECKPOINT_INTERVAL = 25;\n\n/**\n * Default maximum size (in bytes) of a single shard / diff document. Kept well\n * under Firestore's 1 MiB per-document limit so that no individual write can be\n * rejected for being too large.\n */\nconst DEFAULT_SHARD_SIZE = 512 * 1024;\n\n/**\n * Fallback prefix used when no {@link FirestoreSessionStoreOptions.snapshotPathPrefix}\n * is configured.\n */\nconst DEFAULT_PREFIX = 'global';\n\n/**\n * Options for {@link FirestoreSessionStore}.\n */\nexport interface FirestoreSessionStoreOptions {\n  /**\n   * An explicit Firestore instance. Defaults to a new {@link Firestore}\n   * instance (which picks up Application Default Credentials and the\n   * `FIRESTORE_EMULATOR_HOST` environment variable).\n   */\n  db?: Firestore;\n  /**\n   * The collection where snapshot documents are stored (keyed by\n   * `snapshotId`). Defaults to `\"genkit-sessions\"`. Two companion collections\n   * are derived from it: `\"<collection>-pointers\"` holds one pointer document\n   * per session and `\"<collection>-shards\"` holds the sharded checkpoint\n   * state.\n   */\n  collection?: string;\n  /**\n   * Number of turns between full-state checkpoints. A larger value stores\n   * fewer (but reconstructs over more) diffs; a smaller value reconstructs\n   * faster at the cost of more frequent full-state writes. Defaults to\n   * {@link DEFAULT_CHECKPOINT_INTERVAL}.\n   *\n   * Cost tuning: per-save reconstruction reads grow ~linearly with this value\n   * (so per-interval read work is ~quadratic in it), while checkpoint write and\n   * storage cost shrink with it. Lower it (e.g. 10) for small-state, read-heavy\n   * sessions; raise it (e.g. 50-100) for large per-turn state retained for a\n   * long time, where checkpoint write/storage amplification dominates.\n   */\n  checkpointInterval?: number;\n\n  /**\n   * Maximum size in bytes of a single shard / diff document. Checkpoint state\n   * is split into chunks of this size, and any diff exceeding it is promoted to\n   * a (sharded) checkpoint so that no document approaches Firestore's 1 MiB\n   * limit. Defaults to {@link DEFAULT_SHARD_SIZE}.\n   */\n  shardSize?: number;\n\n  /**\n   * Returns a per-tenant prefix derived from the call's\n   * {@link SessionStoreOptions} (e.g. the authenticated user id from\n   * `options.context`). When set, all snapshot, pointer and shard documents are\n   * nested under a tenant-scoped subcollection keyed by this prefix, so reads\n   * and writes are isolated per tenant: one tenant can never see (or even\n   * address) another's snapshots, even if they get hold of a `snapshotId` -\n   * resolving it still requires the matching, auth-derived prefix. Defaults to\n   * `\"global\"`.\n   *\n   * The prefix is used only to build document paths; the stored ids\n   * (`snapshotId`, `parentId`, `checkpointId`, ...) remain prefix-agnostic.\n   */\n  snapshotPathPrefix?: (options?: SessionStoreOptions) => string;\n}\n\n/**\n * The persisted shape of a snapshot document.\n *\n * A session's history is stored as a chain of per-turn documents. To keep\n * reads and document sizes bounded regardless of how long a session grows,\n * documents come in two `kind`s:\n *\n * - `checkpoint` - a full materialization of the session state at that turn.\n *   The state itself is stored *out of band*, sharded across the shards\n *   collection (see {@link ShardDoc}), so a checkpoint never approaches the\n *   1 MiB document limit. Written for the session root, every\n *   `checkpointInterval` turns, and whenever a single turn's diff would be too\n *   large.\n * - `diff` - only the {@link JsonPatch} (`statePatch`) that transforms its\n *   parent's state into its own.\n *\n * Every document carries the metadata needed to reconstruct it with a single\n * batched, strongly-consistent `getAll` (no queries / secondary indexes):\n * `checkpointId` (the nearest checkpoint ancestor), `checkpointShardCount`, and\n * `segmentPath` (the ordered diff IDs from that checkpoint down to this\n * document). Because `segmentPath` resets at every checkpoint, the *number of\n * diff documents* read per reconstruction is bounded by `checkpointInterval`,\n * not by total session length. (The number of shard documents still scales\n * with the state's size - i.e. with session length - since each checkpoint\n * stores the full accumulated state.)\n */\ninterface SnapshotDoc {\n  snapshotId: string;\n  sessionId: string;\n  parentId?: string;\n  createdAt: string;\n  updatedAt?: string;\n  status?: SessionSnapshot['status'];\n  /** Heartbeat timestamp (RFC 3339) for an in-flight detached turn. */\n  heartbeatAt?: SessionSnapshot['heartbeatAt'];\n  finishReason?: SessionSnapshot['finishReason'];\n  error?: SessionSnapshot['error'];\n  /** `checkpoint` stores full state in shards; `diff` stores `statePatch`. */\n  kind: 'diff' | 'checkpoint';\n  /** Nearest checkpoint ancestor (equals `snapshotId` when a checkpoint). */\n  checkpointId: string;\n  /** Shard count of the checkpoint identified by `checkpointId`. */\n  checkpointShardCount: number;\n  /**\n   * Ordered diff IDs from the checkpoint (exclusive) to this document\n   * (inclusive). Empty for a checkpoint. Applying these patches in order onto\n   * the checkpoint's state materializes this document's state.\n   */\n  segmentPath: string[];\n  /** RFC 6902 patch from the parent's state. Only set for `kind: 'diff'`. */\n  statePatch?: JsonPatch;\n}\n\n/**\n * One shard of a checkpoint's materialized state. The full state is\n * JSON-serialized to UTF-8 and split into byte-bounded chunks stored at\n * `<checkpointId>_<index>`; concatenating the chunks and parsing the result\n * yields the original state.\n */\ninterface ShardDoc {\n  chunk: Buffer;\n}\n\n/**\n * The per-session pointer document. Tracks the current leaf snapshot and the\n * metadata needed to reconstruct it (its checkpoint, shard count and segment\n * path) so the common `sessionId` lookup is a single pointer read followed by\n * one batched `getAll`. It deliberately does *not* cache the full state, so it\n * can never approach the 1 MiB document limit no matter how long the session\n * grows.\n */\ninterface PointerDoc {\n  currentSnapshotId: string;\n  checkpointId: string;\n  checkpointShardCount: number;\n  segmentPath: string[];\n  updatedAt: string;\n}\n\n/**\n * Chain metadata about a parent snapshot needed to extend the chain - the\n * nearest checkpoint, its shard count and the diff segment leading to the\n * parent. Deliberately excludes the parent's (potentially large) state so it\n * can be resolved without a full reconstruction.\n */\ninterface ParentChainMeta {\n  checkpointId: string;\n  checkpointShardCount: number;\n  segmentPath: string[];\n}\n\n/**\n * A minimal batched read interface so reconstruction can run identically\n * against a transaction or the bare Firestore instance. Both `get` and\n * `getAll` are document-ID lookups, which Firestore serves with strong\n * consistency (unlike queries), keeping reconstruction deterministic.\n */\ninterface Reader {\n  get(ref: DocumentReference): Promise<DocumentSnapshot>;\n  getAll(refs: DocumentReference[]): Promise<DocumentSnapshot[]>;\n}\n\n/**\n * Strips `undefined` members (Firestore rejects them) while preserving JSON\n * semantics - matching how snapshot state is diffed and reconstructed.\n */\nfunction sanitize<T>(value: T): T {\n  return JSON.parse(JSON.stringify(value ?? null));\n}\n\n/**\n * A Firestore-backed {@link SessionStore} that persists session snapshots as\n * incremental JSON Patch diffs anchored to periodic, sharded full-state\n * checkpoints.\n *\n * Storage layout (the `<prefix>` segment is the per-tenant prefix returned by\n * {@link FirestoreSessionStoreOptions.snapshotPathPrefix}, or `\"global\"` when\n * none is configured):\n *\n * - `<collection>/<prefix>/snapshots/<snapshotId>` - one document per snapshot.\n *   A `diff` document holds the patch from its parent (`statePatch`); a\n *   `checkpoint` document holds a full-state materialization (sharded out of\n *   band).\n * - `<collection>-shards/<prefix>/shards/<checkpointId>_<index>` - the sharded\n *   full state for a checkpoint.\n * - `<collection>-pointers/<prefix>/pointers/<sessionId>` - one document per\n *   session pointing at the latest leaf snapshot and the metadata needed to\n *   reconstruct it.\n *\n * Reconstruction uses only document-ID lookups (`getAll`), so it needs no\n * secondary indexes and is strongly consistent. No single document approaches\n * the 1 MiB limit (state is sharded by `shardSize`), and the number of *diff*\n * documents touched per read/write is bounded by `checkpointInterval` rather\n * than total session length - so the store scales to arbitrarily long sessions\n * (e.g. coding agents, long-lived chatbots). Note that checkpoints still store\n * the full accumulated state, so checkpoint shard count (and the bytes written\n * per checkpoint) grow with the state's size; tune `checkpointInterval` to\n * trade per-save diff reads against checkpoint write amplification.\n */\nexport class FirestoreSessionStore<S = unknown> implements SessionStore<S> {\n  protected db: Firestore;\n  protected collection: string;\n  protected checkpointInterval: number;\n  protected shardSize: number;\n  protected snapshotPathPrefix?: (options?: SessionStoreOptions) => string;\n\n  constructor(opts?: FirestoreSessionStoreOptions) {\n    this.db = opts?.db ?? new Firestore();\n    this.collection = opts?.collection ?? 'genkit-sessions';\n    this.checkpointInterval =\n      opts?.checkpointInterval ?? DEFAULT_CHECKPOINT_INTERVAL;\n    this.shardSize = opts?.shardSize ?? DEFAULT_SHARD_SIZE;\n    this.snapshotPathPrefix = opts?.snapshotPathPrefix;\n  }\n\n  /** Resolves the (per-tenant) prefix for the given call options. */\n  private prefixFor(options?: SessionStoreOptions): string {\n    return this.snapshotPathPrefix\n      ? this.snapshotPathPrefix(options)\n      : DEFAULT_PREFIX;\n  }\n\n  /** The (per-tenant) snapshots subcollection. */\n  private snapshotsCol(options?: SessionStoreOptions): CollectionReference {\n    return this.db\n      .collection(this.collection)\n      .doc(this.prefixFor(options))\n      .collection('snapshots');\n  }\n\n  /** The (per-tenant) pointers subcollection. */\n  private pointersCol(options?: SessionStoreOptions): CollectionReference {\n    return this.db\n      .collection(`${this.collection}-pointers`)\n      .doc(this.prefixFor(options))\n      .collection('pointers');\n  }\n\n  /** The (per-tenant) shards subcollection. */\n  private shardsCol(options?: SessionStoreOptions): CollectionReference {\n    return this.db\n      .collection(`${this.collection}-shards`)\n      .doc(this.prefixFor(options))\n      .collection('shards');\n  }\n\n  async getSnapshot(opts: {\n    snapshotId?: string;\n    sessionId?: string;\n    context?: SessionStoreOptions['context'];\n  }): Promise<SessionSnapshot<S> | undefined> {\n    const { snapshotId, sessionId } = this.normalize(opts);\n    const options: SessionStoreOptions = { context: opts.context };\n\n    // Reconstruct inside a read-only transaction so the pointer read and the\n    // batched shard/diff reads all observe a single, consistent point in time.\n    // Without this, a concurrent checkpoint write - which overwrites a\n    // checkpoint's shards in place and may delete now-stale trailing shards\n    // (see `writeShards`) - could let a reader stitch together a mix of old and\n    // new chunks, yielding a `DATA_LOSS` (missing shard) error or a corrupt\n    // `JSON.parse`. A read-only transaction also avoids the contention/retries\n    // of a read-write one.\n    return this.db.runTransaction(\n      async (tx) => {\n        const reader = this.reader(tx);\n\n        if (sessionId) {\n          const pointerSnap = await tx.get(\n            this.pointersCol(options).doc(sessionId)\n          );\n          if (!pointerSnap.exists) return undefined;\n          const pointer = pointerSnap.data() as PointerDoc;\n          // Reconstruct straight from the pointer's checkpoint metadata - one\n          // batched round-trip, no extra read of the leaf document.\n          const reconstructed = await this.reconstructFrom(\n            reader,\n            pointer.checkpointId,\n            pointer.checkpointShardCount,\n            pointer.segmentPath,\n            pointer.currentSnapshotId,\n            options\n          );\n          if (!reconstructed) return undefined;\n          return this.toSnapshot(reconstructed.doc, reconstructed.state);\n        }\n\n        const reconstructed = await this.reconstruct(\n          reader,\n          snapshotId!,\n          options\n        );\n        if (!reconstructed) return undefined;\n        return this.toSnapshot(reconstructed.doc, reconstructed.state);\n      },\n      { readOnly: true }\n    );\n  }\n\n  async saveSnapshot(\n    snapshotId: string | undefined,\n    mutator: SnapshotMutator<S>,\n    options?: SessionStoreOptions\n  ): Promise<string | null> {\n    return this.db.runTransaction(async (tx) => {\n      const reader = this.reader(tx);\n\n      // Reads phase 1: load the existing snapshot (if any) so the mutator can\n      // inspect the current full state.\n      let existing: { doc: SnapshotDoc; state: SessionState<S> } | undefined;\n      if (snapshotId) {\n        existing = await this.reconstruct(reader, snapshotId, options);\n      }\n      const current = existing\n        ? this.toSnapshot(existing.doc, existing.state)\n        : undefined;\n\n      const result = mutator(current);\n      if (result === null) return null;\n\n      const id =\n        snapshotId || result.snapshotId || globalThis.crypto.randomUUID();\n      // Prefer the snapshot's top-level `sessionId`; fall back to the id carried\n      // in its state for rows written before snapshot-level ids existed.\n      const sessionId = result.sessionId ?? result.state?.sessionId;\n      if (!sessionId) {\n        throw new GenkitError({\n          status: 'INVALID_ARGUMENT',\n          message: `FirestoreSessionStore requires 'sessionId' to be set on the snapshot.`,\n        });\n      }\n      const newState = (result.state ?? {}) as SessionState<S>;\n\n      // Reads phase 2: the per-session pointer (current leaf metadata).\n      const pointerRef = this.pointersCol(options).doc(sessionId);\n      const pointerSnap = await tx.get(pointerRef);\n      const pointer = pointerSnap.exists\n        ? (pointerSnap.data() as PointerDoc)\n        : undefined;\n\n      let kind: 'diff' | 'checkpoint';\n      let checkpointId: string;\n      let checkpointShardCount: number;\n      let segmentPath: string[];\n      let statePatch: JsonPatch | undefined;\n\n      if (existing) {\n        // Upsert: preserve the document's role and chain position; only the\n        // state/metadata change. Callers must only upsert the *leaf* -\n        // rewriting a non-leaf snapshot's state would invalidate its\n        // descendants' diffs.\n        if (existing.doc.kind === 'checkpoint') {\n          ({\n            kind,\n            checkpointId,\n            checkpointShardCount,\n            segmentPath,\n            statePatch,\n          } = this.writeCheckpoint(\n            tx,\n            id,\n            newState,\n            options,\n            existing.doc.checkpointShardCount\n          ));\n        } else {\n          // Reads phase 3 (diff upsert): resolve parent state for the patch.\n          const parentState = existing.doc.parentId\n            ? (await this.reconstruct(reader, existing.doc.parentId, options))\n                ?.state\n            : undefined;\n          const candidatePatch = diff(parentState, newState);\n          // Promote an oversized diff to a (sharded) checkpoint so even an\n          // in-place leaf rewrite can never push the document past the 1 MiB\n          // limit. Safe because callers only upsert the leaf, which has no\n          // descendants depending on its chain position.\n          if (this.byteLength(candidatePatch) > this.shardSize) {\n            ({\n              kind,\n              checkpointId,\n              checkpointShardCount,\n              segmentPath,\n              statePatch,\n            } = this.writeCheckpoint(tx, id, newState, options));\n          } else {\n            kind = 'diff';\n            checkpointId = existing.doc.checkpointId;\n            checkpointShardCount = existing.doc.checkpointShardCount;\n            segmentPath = existing.doc.segmentPath;\n            statePatch = candidatePatch;\n          }\n        }\n      } else {\n        // New snapshot: resolve the parent's *chain metadata* (no state) to\n        // decide diff vs checkpoint. Materializing the parent's full state is\n        // deferred until we know we actually need a diff - so the expensive\n        // reconstruction is skipped on every checkpoint-boundary turn (which\n        // would rewrite the whole state regardless).\n        let parentMeta: ParentChainMeta | undefined;\n        if (result.parentId) {\n          parentMeta = await this.loadParentChainMeta(\n            reader,\n            result.parentId,\n            pointer,\n            options\n          );\n        }\n\n        if (\n          !result.parentId ||\n          !parentMeta ||\n          parentMeta.segmentPath.length + 1 >= this.checkpointInterval\n        ) {\n          // Write a full checkpoint without ever reconstructing the parent's\n          // state, for any of: a session root, an orphaned parent, or reaching\n          // the checkpoint interval (whose final segment is exactly the longest,\n          // costliest one we'd otherwise pay to reconstruct here).\n          ({\n            kind,\n            checkpointId,\n            checkpointShardCount,\n            segmentPath,\n            statePatch,\n          } = this.writeCheckpoint(tx, id, newState, options));\n        } else {\n          // Diff candidate: now we must materialize the parent's state to\n          // compute the patch.\n          const parentState = (\n            await this.reconstructFrom(\n              reader,\n              parentMeta.checkpointId,\n              parentMeta.checkpointShardCount,\n              parentMeta.segmentPath,\n              result.parentId,\n              options\n            )\n          )?.state;\n          const candidatePatch = diff(parentState, newState);\n          // Promote oversized diffs to checkpoints so a single large turn is\n          // sharded rather than rejected by the 1 MiB limit.\n          if (this.byteLength(candidatePatch) > this.shardSize) {\n            ({\n              kind,\n              checkpointId,\n              checkpointShardCount,\n              segmentPath,\n              statePatch,\n            } = this.writeCheckpoint(tx, id, newState, options));\n          } else {\n            kind = 'diff';\n            checkpointId = parentMeta.checkpointId;\n            checkpointShardCount = parentMeta.checkpointShardCount;\n            segmentPath = [...parentMeta.segmentPath, id];\n            statePatch = candidatePatch;\n          }\n        }\n      }\n\n      // Writes phase.\n      const doc: SnapshotDoc = {\n        snapshotId: id,\n        sessionId,\n        parentId: result.parentId,\n        createdAt: result.createdAt,\n        updatedAt: result.updatedAt ?? result.createdAt,\n        status: result.status,\n        heartbeatAt: result.heartbeatAt,\n        finishReason: result.finishReason,\n        error: result.error,\n        kind,\n        checkpointId,\n        checkpointShardCount,\n        segmentPath,\n        statePatch,\n      };\n      tx.set(this.snapshotsCol(options).doc(id), sanitize(doc));\n\n      // Advance the pointer when this is a new leaf, or refresh it when we just\n      // rewrote the current leaf. Upserts of older, non-leaf snapshots leave\n      // the pointer untouched.\n      const isNew = !existing;\n      if (isNew || !pointer || pointer.currentSnapshotId === id) {\n        tx.set(\n          pointerRef,\n          sanitize<PointerDoc>({\n            currentSnapshotId:\n              isNew || !pointer ? id : pointer.currentSnapshotId,\n            checkpointId,\n            checkpointShardCount,\n            segmentPath,\n            updatedAt: new Date().toISOString(),\n          })\n        );\n      }\n\n      return id;\n    });\n  }\n\n  onSnapshotStateChange(\n    snapshotId: string,\n    callback: (snapshot: SessionSnapshot<S>) => void,\n    options?: SessionStoreOptions\n  ): void | (() => void) {\n    const ref = this.snapshotsCol(options).doc(snapshotId);\n    return ref.onSnapshot(async (docSnap) => {\n      if (!docSnap.exists) return;\n      try {\n        const snapshot = await this.getSnapshot({\n          snapshotId,\n          context: options?.context,\n        });\n        if (snapshot) callback(snapshot);\n      } catch (err) {\n        // Swallow errors so a transient read failure (network / permission)\n        // doesn't surface as an unhandled promise rejection and crash the\n        // process. The next snapshot event will retry.\n        logger.error(\n          `FirestoreSessionStore.watch failed to load snapshot ${snapshotId}`,\n          err\n        );\n      }\n    });\n  }\n\n  /**\n   * Validates that exactly one of `snapshotId` / `sessionId` is provided, and\n   * that the provided one is a non-blank string. Blank / whitespace-only ids\n   * are rejected up front (rather than silently treated as \"absent\") so callers\n   * get a clear `INVALID_ARGUMENT` instead of an unusable document key.\n   */\n  private normalize(opts: { snapshotId?: string; sessionId?: string }): {\n    snapshotId?: string;\n    sessionId?: string;\n  } {\n    const snapshotId = opts.snapshotId?.trim() ? opts.snapshotId : undefined;\n    const sessionId = opts.sessionId?.trim() ? opts.sessionId : undefined;\n    if (!!snapshotId === !!sessionId) {\n      throw new GenkitError({\n        status: 'INVALID_ARGUMENT',\n        message:\n          `getSnapshot requires exactly one non-empty 'snapshotId' or ` +\n          `'sessionId' (got ${snapshotId ? 'snapshotId' : opts.snapshotId !== undefined ? 'blank snapshotId' : 'neither'}` +\n          `${sessionId ? ' and sessionId' : opts.sessionId !== undefined ? ' and blank sessionId' : ''}).`,\n      });\n    }\n    return { snapshotId, sessionId };\n  }\n\n  /** Builds a {@link Reader} bound to a transaction or the bare instance. */\n  private reader(tx?: Transaction): Reader {\n    if (tx) {\n      return {\n        get: (ref) => tx.get(ref),\n        getAll: (refs) =>\n          refs.length ? tx.getAll(...refs) : Promise.resolve([]),\n      };\n    }\n    return {\n      get: (ref) => ref.get(),\n      getAll: (refs) =>\n        refs.length ? this.db.getAll(...refs) : Promise.resolve([]),\n    };\n  }\n\n  /**\n   * Resolves a parent's chain metadata (nearest checkpoint, shard count and\n   * segment path) *without* materializing its - potentially large - state.\n   *\n   * In the common linear case the parent is the session's current leaf, so the\n   * metadata is read straight off the pointer and this performs *zero*\n   * document reads. Otherwise it reads the single parent document. Crucially,\n   * resolving only the metadata lets `saveSnapshot` decide diff-vs-checkpoint\n   * (which only needs `segmentPath.length`) before paying for a full state\n   * reconstruction - so checkpoint-boundary turns, which would rewrite the\n   * whole state anyway, skip reconstruction entirely.\n   */\n  private async loadParentChainMeta(\n    reader: Reader,\n    parentId: string,\n    pointer: PointerDoc | undefined,\n    options?: SessionStoreOptions\n  ): Promise<ParentChainMeta | undefined> {\n    if (pointer && pointer.currentSnapshotId === parentId) {\n      return {\n        checkpointId: pointer.checkpointId,\n        checkpointShardCount: pointer.checkpointShardCount,\n        segmentPath: pointer.segmentPath,\n      };\n    }\n    const snap = await reader.get(this.snapshotsCol(options).doc(parentId));\n    if (!snap.exists) return undefined;\n    const d = snap.data() as SnapshotDoc;\n    return {\n      checkpointId: d.checkpointId,\n      checkpointShardCount: d.checkpointShardCount,\n      segmentPath: d.segmentPath,\n    };\n  }\n\n  /**\n   * Reconstructs the state of `id` by reading its document to learn its\n   * checkpoint and segment path, then materializing from that checkpoint.\n   * Returns `undefined` when the snapshot does not exist.\n   */\n  private async reconstruct(\n    reader: Reader,\n    id: string,\n    options?: SessionStoreOptions\n  ): Promise<{ doc: SnapshotDoc; state: SessionState<S> } | undefined> {\n    const snap = await reader.get(this.snapshotsCol(options).doc(id));\n    if (!snap.exists) return undefined;\n    const d = snap.data() as SnapshotDoc;\n    return this.reconstructFrom(\n      reader,\n      d.checkpointId,\n      d.checkpointShardCount,\n      d.segmentPath,\n      id,\n      options\n    );\n  }\n\n  /**\n   * Materializes the state of `targetId` from a known checkpoint using a single\n   * batched, strongly-consistent `getAll`: the checkpoint's shards, the\n   * (bounded) segment of diff documents along `segmentPath`, and - only when\n   * the target *is* the checkpoint - the checkpoint document itself. The diffs\n   * are then applied in order onto the checkpoint's state. Cost is bounded by\n   * `checkpointInterval` + shard count, independent of total session length.\n   *\n   * Note: when `segmentPath` is non-empty the state comes entirely from the\n   * shards and the target's metadata from the last segment document, so the\n   * checkpoint *document* is not read - saving one read on the common path.\n   */\n  private async reconstructFrom(\n    reader: Reader,\n    checkpointId: string,\n    shardCount: number,\n    segmentPath: string[],\n    targetId: string,\n    options?: SessionStoreOptions\n  ): Promise<{ doc: SnapshotDoc; state: SessionState<S> } | undefined> {\n    const targetIsCheckpoint = segmentPath.length === 0;\n    const snapshotsCol = this.snapshotsCol(options);\n    const shardsCol = this.shardsCol(options);\n    const checkpointRef = snapshotsCol.doc(checkpointId);\n    const shardRefs = Array.from({ length: shardCount }, (_, i) =>\n      shardsCol.doc(`${checkpointId}_${i}`)\n    );\n    const segRefs = segmentPath.map((sid) => snapshotsCol.doc(sid));\n\n    const snaps = await reader.getAll([\n      // The checkpoint document is only needed when it is itself the target;\n      // otherwise the last segment document carries the target metadata.\n      ...(targetIsCheckpoint ? [checkpointRef] : []),\n      ...shardRefs,\n      ...segRefs,\n    ]);\n\n    // `getAll` does not guarantee result order matches request order, so index\n    // the snapshots by their (fully-qualified) path and look each up explicitly.\n    const byPath = new Map<string, DocumentSnapshot>();\n    for (const s of snaps) byPath.set(s.ref.path, s);\n\n    const shardSnaps = shardRefs.map((ref) => byPath.get(ref.path)!);\n    let state = this.stitch(shardSnaps) as SessionState<S> | undefined;\n\n    if (targetIsCheckpoint) {\n      const checkpointSnap = byPath.get(checkpointRef.path);\n      if (!checkpointSnap?.exists) return undefined;\n      const checkpointDoc = checkpointSnap.data() as SnapshotDoc;\n      if (checkpointDoc.snapshotId !== targetId) return undefined;\n      return { doc: checkpointDoc, state: (state ?? {}) as SessionState<S> };\n    }\n\n    let targetDoc: SnapshotDoc | undefined;\n    for (const ref of segRefs) {\n      const segSnap = byPath.get(ref.path);\n      if (!segSnap?.exists) return undefined; // Missing diff: corrupt chain.\n      const segDoc = segSnap.data() as SnapshotDoc;\n      state = applyPatch(state, segDoc.statePatch ?? []);\n      targetDoc = segDoc;\n    }\n\n    if (!targetDoc || targetDoc.snapshotId !== targetId) return undefined;\n    return { doc: targetDoc, state: (state ?? {}) as SessionState<S> };\n  }\n\n  /**\n   * Serializes `state` to UTF-8, splits it into `shardSize`-byte chunks, and\n   * writes them at `<checkpointId>_<index>`. When `oldShardCount` exceeds the\n   * new count (a shrinking re-checkpoint), the now-stale trailing shards are\n   * deleted. Returns the number of shards written.\n   */\n  private writeShards(\n    tx: Transaction,\n    checkpointId: string,\n    state: SessionState<S>,\n    options?: SessionStoreOptions,\n    oldShardCount = 0\n  ): number {\n    const shardsCol = this.shardsCol(options);\n    // `JSON.stringify` already drops `undefined` members, so it produces the\n    // same bytes as `sanitize(state)` without the extra parse+stringify round\n    // trip - a meaningful saving when checkpointing large states.\n    const buf = Buffer.from(JSON.stringify(state ?? null), 'utf8');\n    const count = Math.max(1, Math.ceil(buf.length / this.shardSize));\n    for (let i = 0; i < count; i++) {\n      // Copy the slice into its own buffer. `subarray` returns a view sharing\n      // the parent's underlying ArrayBuffer, which the Firestore serializer can\n      // persist in full rather than just the sliced range.\n      const chunk = Buffer.from(\n        buf.subarray(i * this.shardSize, (i + 1) * this.shardSize)\n      );\n      tx.set(shardsCol.doc(`${checkpointId}_${i}`), {\n        chunk,\n      } satisfies ShardDoc);\n    }\n    for (let i = count; i < oldShardCount; i++) {\n      tx.delete(shardsCol.doc(`${checkpointId}_${i}`));\n    }\n    return count;\n  }\n\n  /**\n   * Writes a full-state checkpoint at `id` (sharding the state via\n   * {@link writeShards}) and returns the snapshot metadata describing it: a\n   * checkpoint anchors itself (`checkpointId === id`), has an empty\n   * `segmentPath`, and carries no `statePatch`.\n   *\n   * This is the shared promotion path used whenever a snapshot must be a\n   * checkpoint rather than a diff - the session root, an orphaned parent, a\n   * checkpoint-interval boundary, an in-place checkpoint rewrite, and the\n   * promotion of an oversized diff (whether new turn or leaf upsert) so that no\n   * single document approaches Firestore's 1 MiB limit. Pass `oldShardCount`\n   * when re-checkpointing an existing checkpoint so stale trailing shards are\n   * pruned.\n   */\n  private writeCheckpoint(\n    tx: Transaction,\n    id: string,\n    state: SessionState<S>,\n    options?: SessionStoreOptions,\n    oldShardCount = 0\n  ): {\n    kind: 'checkpoint';\n    checkpointId: string;\n    checkpointShardCount: number;\n    segmentPath: string[];\n    statePatch: undefined;\n  } {\n    return {\n      kind: 'checkpoint',\n      checkpointId: id,\n      checkpointShardCount: this.writeShards(\n        tx,\n        id,\n        state,\n        options,\n        oldShardCount\n      ),\n      segmentPath: [],\n      statePatch: undefined,\n    };\n  }\n\n  /** Concatenates ordered shard documents and parses the materialized state. */\n  private stitch(shardSnaps: DocumentSnapshot[]): unknown {\n    if (shardSnaps.length === 0) return undefined;\n    const buffers: Buffer[] = [];\n    for (const s of shardSnaps) {\n      if (!s.exists) {\n        throw new GenkitError({\n          status: 'DATA_LOSS',\n          message: `FirestoreSessionStore: missing checkpoint shard '${s.id}'.`,\n        });\n      }\n      buffers.push((s.data() as ShardDoc).chunk);\n    }\n    return JSON.parse(Buffer.concat(buffers).toString('utf8'));\n  }\n\n  /** UTF-8 byte length of a JSON-serializable value. */\n  private byteLength(value: unknown): number {\n    return Buffer.byteLength(JSON.stringify(value ?? null), 'utf8');\n  }\n\n  /** Assembles a {@link SessionSnapshot} from a document and its state. */\n  private toSnapshot(\n    doc: SnapshotDoc,\n    state: SessionState<S>\n  ): SessionSnapshot<S> {\n    const snapshot: SessionSnapshot<S> = {\n      snapshotId: doc.snapshotId,\n      sessionId: doc.sessionId,\n      createdAt: doc.createdAt,\n      // Normalize to plain objects: values reconstructed from Firestore\n      // documents (e.g. patch operands) can carry non-plain prototypes.\n      state: sanitize(state),\n    };\n\n    if (doc.parentId !== undefined) snapshot.parentId = doc.parentId;\n    if (doc.updatedAt !== undefined) snapshot.updatedAt = doc.updatedAt;\n    if (doc.status !== undefined) snapshot.status = doc.status;\n    if (doc.heartbeatAt !== undefined) snapshot.heartbeatAt = doc.heartbeatAt;\n    if (doc.finishReason !== undefined)\n      snapshot.finishReason = doc.finishReason;\n    if (doc.error !== undefined) snapshot.error = doc.error;\n    return snapshot;\n  }\n}\n"],"mappings":"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","names":["reconstructed"]}