@langchain/langgraph-checkpoint 1.1.6 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/memory.cjs CHANGED
@@ -83,6 +83,41 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
83
83
  deseriablizableCheckpoint.channel_versions ??= {};
84
84
  deseriablizableCheckpoint.channel_versions[require_types.TASKS] = Object.keys(deseriablizableCheckpoint.channel_versions).length > 0 ? require_base.maxChannelVersion(...Object.values(deseriablizableCheckpoint.channel_versions)) : this.getNextVersion(void 0);
85
85
  }
86
+ /**
87
+ * Stored writes for one checkpoint, in `writesSortKey` order.
88
+ *
89
+ * `taskId` and `idx` come from the storage key, `taskPath` from the stored
90
+ * record (missing on records written before paths existed).
91
+ *
92
+ * @internal
93
+ */
94
+ _orderedWrites(threadId, checkpointNs, checkpointId) {
95
+ const stored = threadId === void 0 ? void 0 : this.writes[_generateKey(threadId, checkpointNs, checkpointId)];
96
+ if (stored === void 0) return [];
97
+ const keyed = Object.entries(stored).map(([keyStr, record]) => {
98
+ const comma = keyStr.lastIndexOf(",");
99
+ const taskId = keyStr.slice(0, comma);
100
+ const idx = Number(keyStr.slice(comma + 1));
101
+ return {
102
+ key: require_base.writesSortKey(record[3] ?? "", taskId, idx),
103
+ entry: [
104
+ taskId,
105
+ idx,
106
+ record
107
+ ]
108
+ };
109
+ });
110
+ keyed.sort((a, b) => require_base.compareWritesSortKeys(a.key, b.key));
111
+ return keyed.map(({ entry }) => entry);
112
+ }
113
+ /** Deserialize `_orderedWrites` records into public pending writes. */
114
+ async _deserializeOrderedWrites(ordered) {
115
+ return Promise.all(ordered.map(async ([, , [taskId, channel, value]]) => [
116
+ taskId,
117
+ channel,
118
+ await this.serde.loadsTyped("json", value)
119
+ ]));
120
+ }
86
121
  async getTuple(config) {
87
122
  const thread_id = config.configurable?.thread_id;
88
123
  const checkpoint_ns = config.configurable?.checkpoint_ns ?? "";
@@ -94,16 +129,9 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
94
129
  const saved = this.storage[thread_id]?.[checkpoint_ns]?.[checkpoint_id];
95
130
  if (saved !== void 0) {
96
131
  const [checkpoint, metadata, parentCheckpointId] = saved;
97
- const key = _generateKey(thread_id, checkpoint_ns, checkpoint_id);
98
132
  const deserializedCheckpoint = await this.serde.loadsTyped("json", checkpoint);
99
133
  if (deserializedCheckpoint.v < 4 && parentCheckpointId !== void 0) await this._migratePendingSends(deserializedCheckpoint, thread_id, checkpoint_ns, parentCheckpointId);
100
- const pendingWrites = await Promise.all(Object.values(this.writes[key] || {}).map(async ([taskId, channel, value]) => {
101
- return [
102
- taskId,
103
- channel,
104
- await this.serde.loadsTyped("json", value)
105
- ];
106
- }));
134
+ const pendingWrites = await this._deserializeOrderedWrites(this._orderedWrites(thread_id, checkpoint_ns, checkpoint_id));
107
135
  const checkpointTuple = {
108
136
  config,
109
137
  checkpoint: deserializedCheckpoint,
@@ -120,18 +148,11 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
120
148
  } else {
121
149
  const checkpoints = this.storage[thread_id]?.[checkpoint_ns];
122
150
  if (checkpoints !== void 0) {
123
- checkpoint_id = Object.keys(checkpoints).sort((a, b) => b.localeCompare(a))[0];
151
+ checkpoint_id = Object.keys(checkpoints).sort().reverse()[0];
124
152
  const [checkpoint, metadata, parentCheckpointId] = checkpoints[checkpoint_id];
125
- const key = _generateKey(thread_id, checkpoint_ns, checkpoint_id);
126
153
  const deserializedCheckpoint = await this.serde.loadsTyped("json", checkpoint);
127
154
  if (deserializedCheckpoint.v < 4 && parentCheckpointId !== void 0) await this._migratePendingSends(deserializedCheckpoint, thread_id, checkpoint_ns, parentCheckpointId);
128
- const pendingWrites = await Promise.all(Object.values(this.writes[key] || {}).map(async ([taskId, channel, value]) => {
129
- return [
130
- taskId,
131
- channel,
132
- await this.serde.loadsTyped("json", value)
133
- ];
134
- }));
155
+ const pendingWrites = await this._deserializeOrderedWrites(this._orderedWrites(thread_id, checkpoint_ns, checkpoint_id));
135
156
  const checkpointTuple = {
136
157
  config: { configurable: {
137
158
  thread_id,
@@ -163,7 +184,7 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
163
184
  for (const threadId of threadIds) for (const checkpointNamespace of Object.keys(this.storage[threadId] ?? {})) {
164
185
  if (configCheckpointNamespace !== void 0 && checkpointNamespace !== configCheckpointNamespace) continue;
165
186
  const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};
166
- const sortedCheckpoints = Object.entries(checkpoints).sort((a, b) => b[0].localeCompare(a[0]));
187
+ const sortedCheckpoints = Object.keys(checkpoints).sort().reverse().map((id) => [id, checkpoints[id]]);
167
188
  for (const [checkpointId, [checkpoint, metadataStr, parentCheckpointId]] of sortedCheckpoints) {
168
189
  if (configCheckpointId && checkpointId !== configCheckpointId) continue;
169
190
  if (before && before.configurable?.checkpoint_id && checkpointId >= before.configurable.checkpoint_id) continue;
@@ -173,15 +194,7 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
173
194
  if (limit <= 0) break;
174
195
  limit -= 1;
175
196
  }
176
- const key = _generateKey(threadId, checkpointNamespace, checkpointId);
177
- const writes = Object.values(this.writes[key] || {});
178
- const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
179
- return [
180
- taskId,
181
- channel,
182
- await this.serde.loadsTyped("json", value)
183
- ];
184
- }));
197
+ const pendingWrites = await this._deserializeOrderedWrites(this._orderedWrites(threadId, checkpointNamespace, checkpointId));
185
198
  const deserializedCheckpoint = await this.serde.loadsTyped("json", checkpoint);
186
199
  if (deserializedCheckpoint.v < 4 && parentCheckpointId !== void 0) await this._migratePendingSends(deserializedCheckpoint, threadId, checkpointNamespace, parentCheckpointId);
187
200
  const checkpointTuple = {
@@ -225,7 +238,7 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
225
238
  checkpoint_id: checkpoint.id
226
239
  } };
227
240
  }
228
- async putWrites(config, writes, taskId) {
241
+ async putWrites(config, writes, taskId, taskPath) {
229
242
  const threadId = config.configurable?.thread_id;
230
243
  const checkpointNamespace = config.configurable?.checkpoint_ns;
231
244
  const checkpointId = config.configurable?.checkpoint_id;
@@ -246,7 +259,8 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
246
259
  this.writes[outerKey][innerKeyStr] = [
247
260
  taskId,
248
261
  channel,
249
- serializedValue
262
+ serializedValue,
263
+ taskPath ?? ""
250
264
  ];
251
265
  }));
252
266
  }
@@ -277,10 +291,10 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
277
291
  if (channels.length === 0) return {};
278
292
  const threadId = config.configurable?.thread_id;
279
293
  const checkpointNs = config.configurable?.checkpoint_ns ?? "";
280
- const checkpointId = require_base.getCheckpointId(config);
281
294
  if (threadId !== void 0) assertSafeStorageKey("thread_id", threadId);
282
295
  assertSafeStorageKey("checkpoint_ns", checkpointNs, { allowEmpty: true });
283
296
  const nsStorage = this.storage[threadId]?.[checkpointNs] ?? {};
297
+ const checkpointId = require_base.getCheckpointId(config) || Object.keys(nsStorage).sort().reverse()[0];
284
298
  const chain = [];
285
299
  let current = (checkpointId ? nsStorage[checkpointId] : void 0)?.[2];
286
300
  while (current !== void 0) {
@@ -305,15 +319,9 @@ var MemorySaver = class extends require_base.BaseCheckpointSaver {
305
319
  terminatedHere.add(ch);
306
320
  }
307
321
  }
308
- const stepWritesKey = _generateKey(threadId, checkpointNs, cpId);
309
- const stepWrites = Object.entries(this.writes[stepWritesKey] ?? {});
310
- stepWrites.sort(([a], [b]) => {
311
- const [aTask, aIdx] = a.split(",");
312
- const [bTask, bIdx] = b.split(",");
313
- if (aTask !== bTask) return aTask < bTask ? 1 : -1;
314
- return Number(bIdx) - Number(aIdx);
315
- });
316
- for (const [, [tid, ch, serialized]] of stepWrites) {
322
+ const stepWrites = this._orderedWrites(threadId, checkpointNs, cpId);
323
+ for (let i = stepWrites.length - 1; i >= 0; i -= 1) {
324
+ const [tid, ch, serialized] = stepWrites[i][2];
317
325
  if (!remaining.has(ch)) continue;
318
326
  collectedByCh[ch].push([
319
327
  tid,
@@ -1 +1 @@
1
- {"version":3,"file":"memory.cjs","names":["BaseCheckpointSaver","TASKS","maxChannelVersion","getCheckpointId","copyCheckpoint","WRITES_IDX_MAP"],"sources":["../src/memory.ts"],"sourcesContent":["import type { RunnableConfig } from \"@langchain/core/runnables\";\nimport {\n BaseCheckpointSaver,\n Checkpoint,\n CheckpointListOptions,\n CheckpointTuple,\n copyCheckpoint,\n getCheckpointId,\n maxChannelVersion,\n WRITES_IDX_MAP,\n} from \"./base.js\";\nimport { SerializerProtocol } from \"./serde/base.js\";\nimport {\n CheckpointMetadata,\n CheckpointPendingWrite,\n DeltaChannelHistory,\n PendingWrite,\n} from \"./types.js\";\nimport { TASKS } from \"./serde/types.js\";\n\n/**\n * Keys that, when written into a plain JavaScript object via bracket\n * notation, traverse the prototype chain and mutate `Object.prototype`\n * (or the constructor) instead of creating a new own property. Any of\n * the three reaches `Object.prototype` and pollutes every object in\n * the running process. CWE-1321 (Prototype Pollution).\n */\nconst POLLUTION_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n/**\n * Asserts that a value sourced from {@link RunnableConfig.configurable} (or\n * any other caller-influenced position) is safe to use as a property key\n * on the in-memory checkpoint store.\n *\n * `MemorySaver` keeps state in two nested plain objects (`storage` and\n * `writes`) and writes to them with bracket notation:\n *\n * this.storage[threadId][checkpointNamespace][checkpoint.id] = ...\n *\n * Without this guard a `threadId` of `\"__proto__\"` (or `\"constructor\"`)\n * resolves through the prototype chain, and the subsequent assignment\n * mutates `Object.prototype`. From that point every plain object in the\n * process inherits the injected property: `for...in` loops over unrelated\n * objects iterate it, framework code that does `if (obj[x])` short-circuits\n * unexpectedly, and downstream serializers may emit it. In a Node.js\n * server this is a stepping stone to remote code execution.\n *\n * `MemorySaver` is the default saver used by every quickstart, every\n * tutorial, and most test fixtures, so this guard runs in the hot path\n * for the most common LangGraph configuration.\n *\n * @param field Name of the configurable field, used in the error message.\n * @param value Value to validate. Must be a non-empty string that is not\n * one of the three prototype-pollution keys.\n * @param options.allowEmpty When true the empty string is accepted, used\n * for the documented empty `checkpoint_ns`\n * default; otherwise an empty string is\n * rejected the same way as a non-string.\n */\nfunction assertSafeStorageKey(\n field: string,\n value: unknown,\n options: { allowEmpty?: boolean } = {}\n): asserts value is string {\n const { allowEmpty = false } = options;\n if (typeof value !== \"string\") {\n const observed =\n value === null\n ? \"null\"\n : value === undefined\n ? \"undefined\"\n : Array.isArray(value)\n ? \"array\"\n : typeof value;\n throw new Error(\n `Invalid configurable value for key \"${field}\": expected a string identifier (got ${observed}). This guard protects MemorySaver from prototype pollution.`\n );\n }\n if (!allowEmpty && value === \"\") {\n throw new Error(\n `Invalid configurable value for key \"${field}\": empty string is not permitted as an in-memory storage key.`\n );\n }\n if (POLLUTION_KEYS.has(value)) {\n throw new Error(\n `Invalid configurable value for key \"${field}\": value \"${value}\" is reserved (would mutate Object.prototype). This guard protects MemorySaver from prototype pollution.`\n );\n }\n}\n\nfunction _generateKey(\n threadId: string,\n checkpointNamespace: string,\n checkpointId: string\n) {\n return JSON.stringify([threadId, checkpointNamespace, checkpointId]);\n}\n\nfunction _parseKey(key: string) {\n const [threadId, checkpointNamespace, checkpointId] = JSON.parse(key);\n return { threadId, checkpointNamespace, checkpointId };\n}\n\nexport class MemorySaver extends BaseCheckpointSaver {\n // thread ID -> checkpoint namespace -> checkpoint ID -> checkpoint mapping\n //\n // Defense in depth against prototype pollution: the backing\n // objects (and every nested level created below) use a null prototype, so\n // even if a malicious key bypassed `assertSafeStorageKey` it could not reach\n // `Object.prototype`. The guard remains the primary control; this is the\n // structural safety net.\n storage: Record<\n string,\n Record<string, Record<string, [Uint8Array, Uint8Array, string | undefined]>>\n > = Object.create(null);\n\n writes: Record<string, Record<string, [string, string, Uint8Array]>> =\n Object.create(null);\n\n constructor(serde?: SerializerProtocol) {\n super(serde);\n }\n\n /** @internal */\n async _migratePendingSends(\n mutableCheckpoint: Checkpoint,\n threadId: string,\n checkpointNs: string,\n parentCheckpointId: string\n ) {\n const deseriablizableCheckpoint = mutableCheckpoint;\n const parentKey = _generateKey(threadId, checkpointNs, parentCheckpointId);\n\n const pendingSends = await Promise.all(\n Object.values(this.writes[parentKey] ?? {})\n .filter(([_taskId, channel]) => channel === TASKS)\n .map(\n async ([_taskId, _channel, writes]) =>\n await this.serde.loadsTyped(\"json\", writes)\n )\n );\n\n deseriablizableCheckpoint.channel_values ??= {};\n deseriablizableCheckpoint.channel_values[TASKS] = pendingSends;\n\n deseriablizableCheckpoint.channel_versions ??= {};\n deseriablizableCheckpoint.channel_versions[TASKS] =\n Object.keys(deseriablizableCheckpoint.channel_versions).length > 0\n ? maxChannelVersion(\n ...Object.values(deseriablizableCheckpoint.channel_versions)\n )\n : this.getNextVersion(undefined);\n }\n\n async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {\n const thread_id = config.configurable?.thread_id;\n const checkpoint_ns = config.configurable?.checkpoint_ns ?? \"\";\n let checkpoint_id = getCheckpointId(config);\n\n // Defense in depth: every public entry that mutates state already\n // validates these, but read paths must not return data sourced from\n // prototype-chain lookups when an attacker passes the magic keys.\n // `checkpoint_id` is intentionally allowed to be empty / undefined\n // here because the downstream `if (checkpoint_id)` branch treats\n // both as \"fetch the latest checkpoint\" rather than as a lookup key.\n if (thread_id !== undefined) {\n assertSafeStorageKey(\"thread_id\", thread_id);\n }\n assertSafeStorageKey(\"checkpoint_ns\", checkpoint_ns, { allowEmpty: true });\n if (checkpoint_id) {\n assertSafeStorageKey(\"checkpoint_id\", checkpoint_id);\n }\n\n if (checkpoint_id) {\n const saved = this.storage[thread_id]?.[checkpoint_ns]?.[checkpoint_id];\n if (saved !== undefined) {\n const [checkpoint, metadata, parentCheckpointId] = saved;\n const key = _generateKey(thread_id, checkpoint_ns, checkpoint_id);\n const deserializedCheckpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n checkpoint\n );\n\n if (deserializedCheckpoint.v < 4 && parentCheckpointId !== undefined) {\n await this._migratePendingSends(\n deserializedCheckpoint,\n thread_id,\n checkpoint_ns,\n parentCheckpointId\n );\n }\n\n const pendingWrites: CheckpointPendingWrite[] = await Promise.all(\n Object.values(this.writes[key] || {}).map(\n async ([taskId, channel, value]) => {\n return [\n taskId,\n channel,\n await this.serde.loadsTyped(\"json\", value),\n ];\n }\n )\n );\n const checkpointTuple: CheckpointTuple = {\n config,\n checkpoint: deserializedCheckpoint,\n metadata: (await this.serde.loadsTyped(\n \"json\",\n metadata\n )) as CheckpointMetadata,\n pendingWrites,\n };\n if (parentCheckpointId !== undefined) {\n checkpointTuple.parentConfig = {\n configurable: {\n thread_id,\n checkpoint_ns,\n checkpoint_id: parentCheckpointId,\n },\n };\n }\n return checkpointTuple;\n }\n } else {\n const checkpoints = this.storage[thread_id]?.[checkpoint_ns];\n if (checkpoints !== undefined) {\n // eslint-disable-next-line prefer-destructuring\n checkpoint_id = Object.keys(checkpoints).sort((a, b) =>\n b.localeCompare(a)\n )[0];\n const saved = checkpoints[checkpoint_id];\n const [checkpoint, metadata, parentCheckpointId] = saved;\n const key = _generateKey(thread_id, checkpoint_ns, checkpoint_id);\n const deserializedCheckpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n checkpoint\n );\n\n if (deserializedCheckpoint.v < 4 && parentCheckpointId !== undefined) {\n await this._migratePendingSends(\n deserializedCheckpoint,\n thread_id,\n checkpoint_ns,\n parentCheckpointId\n );\n }\n\n const pendingWrites: CheckpointPendingWrite[] = await Promise.all(\n Object.values(this.writes[key] || {}).map(\n async ([taskId, channel, value]) => {\n return [\n taskId,\n channel,\n await this.serde.loadsTyped(\"json\", value),\n ];\n }\n )\n );\n const checkpointTuple: CheckpointTuple = {\n config: {\n configurable: {\n thread_id,\n checkpoint_id,\n checkpoint_ns,\n },\n },\n checkpoint: deserializedCheckpoint,\n metadata: (await this.serde.loadsTyped(\n \"json\",\n metadata\n )) as CheckpointMetadata,\n pendingWrites,\n };\n if (parentCheckpointId !== undefined) {\n checkpointTuple.parentConfig = {\n configurable: {\n thread_id,\n checkpoint_ns,\n checkpoint_id: parentCheckpointId,\n },\n };\n }\n return checkpointTuple;\n }\n }\n\n return undefined;\n }\n\n async *list(\n config: RunnableConfig,\n options?: CheckpointListOptions\n ): AsyncGenerator<CheckpointTuple> {\n // eslint-disable-next-line prefer-const\n let { before, limit, filter } = options ?? {};\n if (config.configurable?.thread_id !== undefined) {\n assertSafeStorageKey(\"thread_id\", config.configurable.thread_id);\n }\n if (config.configurable?.checkpoint_ns !== undefined) {\n assertSafeStorageKey(\"checkpoint_ns\", config.configurable.checkpoint_ns, {\n allowEmpty: true,\n });\n }\n if (config.configurable?.checkpoint_id) {\n assertSafeStorageKey(\"checkpoint_id\", config.configurable.checkpoint_id);\n }\n if (before?.configurable?.checkpoint_id) {\n assertSafeStorageKey(\"checkpoint_id\", before.configurable.checkpoint_id);\n }\n const threadIds = config.configurable?.thread_id\n ? [config.configurable?.thread_id]\n : Object.keys(this.storage);\n const configCheckpointNamespace = config.configurable?.checkpoint_ns;\n const configCheckpointId = config.configurable?.checkpoint_id;\n\n for (const threadId of threadIds) {\n for (const checkpointNamespace of Object.keys(\n this.storage[threadId] ?? {}\n )) {\n if (\n configCheckpointNamespace !== undefined &&\n checkpointNamespace !== configCheckpointNamespace\n ) {\n continue;\n }\n const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};\n const sortedCheckpoints = Object.entries(checkpoints).sort((a, b) =>\n b[0].localeCompare(a[0])\n );\n\n for (const [\n checkpointId,\n [checkpoint, metadataStr, parentCheckpointId],\n ] of sortedCheckpoints) {\n // Filter by checkpoint ID from config\n if (configCheckpointId && checkpointId !== configCheckpointId) {\n continue;\n }\n\n // Filter by checkpoint ID from before config\n if (\n before &&\n before.configurable?.checkpoint_id &&\n checkpointId >= before.configurable.checkpoint_id\n ) {\n continue;\n }\n\n // Parse metadata\n const metadata = (await this.serde.loadsTyped(\n \"json\",\n metadataStr\n )) as CheckpointMetadata;\n\n if (\n filter &&\n !Object.entries(filter).every(\n ([key, value]) =>\n (metadata as unknown as Record<string, unknown>)[key] === value\n )\n ) {\n continue;\n }\n\n // Limit search results\n if (limit !== undefined) {\n if (limit <= 0) break;\n limit -= 1;\n }\n\n const key = _generateKey(threadId, checkpointNamespace, checkpointId);\n const writes = Object.values(this.writes[key] || {});\n\n const pendingWrites: CheckpointPendingWrite[] = await Promise.all(\n writes.map(async ([taskId, channel, value]) => {\n return [\n taskId,\n channel,\n await this.serde.loadsTyped(\"json\", value),\n ];\n })\n );\n\n const deserializedCheckpoint = await this.serde.loadsTyped(\n \"json\",\n checkpoint\n );\n\n if (\n deserializedCheckpoint.v < 4 &&\n parentCheckpointId !== undefined\n ) {\n await this._migratePendingSends(\n deserializedCheckpoint,\n threadId,\n checkpointNamespace,\n parentCheckpointId\n );\n }\n\n const checkpointTuple: CheckpointTuple = {\n config: {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNamespace,\n checkpoint_id: checkpointId,\n },\n },\n checkpoint: deserializedCheckpoint,\n metadata,\n pendingWrites,\n };\n if (parentCheckpointId !== undefined) {\n checkpointTuple.parentConfig = {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNamespace,\n checkpoint_id: parentCheckpointId,\n },\n };\n }\n yield checkpointTuple;\n }\n }\n }\n }\n\n async put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata\n ): Promise<RunnableConfig> {\n const preparedCheckpoint: Partial<Checkpoint> = copyCheckpoint(checkpoint);\n const threadId = config.configurable?.thread_id;\n const checkpointNamespace = config.configurable?.checkpoint_ns ?? \"\";\n if (threadId === undefined) {\n throw new Error(\n `Failed to put checkpoint. The passed RunnableConfig is missing a required \"thread_id\" field in its \"configurable\" property. ` +\n `When using a checkpointer, you must pass a \"thread_id\" so the checkpointer knows which conversation thread to persist state for. ` +\n `Example: graph.stream(input, { configurable: { thread_id: \"my-thread-id\" } })`\n );\n }\n\n assertSafeStorageKey(\"thread_id\", threadId);\n assertSafeStorageKey(\"checkpoint_ns\", checkpointNamespace, {\n allowEmpty: true,\n });\n assertSafeStorageKey(\"checkpoint_id\", checkpoint.id);\n\n if (!this.storage[threadId]) {\n this.storage[threadId] = Object.create(null);\n }\n if (!this.storage[threadId][checkpointNamespace]) {\n this.storage[threadId][checkpointNamespace] = Object.create(null);\n }\n\n const [[, serializedCheckpoint], [, serializedMetadata]] =\n await Promise.all([\n this.serde.dumpsTyped(preparedCheckpoint),\n this.serde.dumpsTyped(metadata),\n ]);\n\n this.storage[threadId][checkpointNamespace][checkpoint.id] = [\n serializedCheckpoint,\n serializedMetadata,\n config.configurable?.checkpoint_id, // parent\n ];\n\n return {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNamespace,\n checkpoint_id: checkpoint.id,\n },\n };\n }\n\n async putWrites(\n config: RunnableConfig,\n writes: PendingWrite[],\n taskId: string\n ): Promise<void> {\n const threadId = config.configurable?.thread_id;\n const checkpointNamespace = config.configurable?.checkpoint_ns;\n const checkpointId = config.configurable?.checkpoint_id;\n if (threadId === undefined) {\n throw new Error(\n `Failed to put writes. The passed RunnableConfig is missing a required \"thread_id\" field in its \"configurable\" property. ` +\n `When using a checkpointer, you must pass a \"thread_id\" so the checkpointer knows which conversation thread to persist state for. ` +\n `Example: graph.stream(input, { configurable: { thread_id: \"my-thread-id\" } })`\n );\n }\n if (checkpointId === undefined) {\n throw new Error(\n `Failed to put writes. The passed RunnableConfig is missing a required \"checkpoint_id\" field in its \"configurable\" property.`\n );\n }\n assertSafeStorageKey(\"thread_id\", threadId);\n assertSafeStorageKey(\"checkpoint_ns\", checkpointNamespace, {\n allowEmpty: true,\n });\n assertSafeStorageKey(\"checkpoint_id\", checkpointId);\n assertSafeStorageKey(\"task_id\", taskId);\n const outerKey = _generateKey(threadId, checkpointNamespace, checkpointId);\n const outerWrites_ = this.writes[outerKey];\n if (this.writes[outerKey] === undefined) {\n this.writes[outerKey] = Object.create(null);\n }\n\n await Promise.all(\n writes.map(async ([channel, value], idx) => {\n const [, serializedValue] = await this.serde.dumpsTyped(value);\n const innerKey: [string, number] = [\n taskId,\n WRITES_IDX_MAP[channel] || idx,\n ];\n const innerKeyStr = `${innerKey[0]},${innerKey[1]}`;\n if (innerKey[1] >= 0 && outerWrites_ && innerKeyStr in outerWrites_) {\n return;\n }\n this.writes[outerKey][innerKeyStr] = [taskId, channel, serializedValue];\n })\n );\n }\n\n async deleteThread(threadId: string): Promise<void> {\n assertSafeStorageKey(\"thread_id\", threadId);\n delete this.storage[threadId];\n for (const key of Object.keys(this.writes)) {\n if (_parseKey(key).threadId === threadId) delete this.writes[key];\n }\n }\n\n /**\n * Override: walk the parent chain ONCE for all requested channels using\n * direct storage access.\n *\n * Each channel terminates independently at the nearest ancestor whose\n * stored `channel_values[ch]` is populated. Other channels keep walking\n * until they find their own terminator or hit the root.\n *\n * The seed value (whether a `DeltaSnapshot` or a plain pre-delta migration\n * blob) is the value AT that ancestor, prior to its own pending writes that\n * produce the child. Those on-path writes — including the ones stored on the\n * terminating ancestor — are always collected and replayed on top of the\n * seed, so a thread migrated from a pre-delta channel does not drop the\n * writes saved under the migration boundary checkpoint.\n *\n * @remarks Beta. See {@link BaseCheckpointSaver.getDeltaChannelHistory}.\n */\n async getDeltaChannelHistory(options: {\n config: RunnableConfig;\n channels: string[];\n }): Promise<Record<string, DeltaChannelHistory>> {\n const { config, channels } = options;\n if (channels.length === 0) return {};\n\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? \"\";\n const checkpointId = getCheckpointId(config);\n\n if (threadId !== undefined) assertSafeStorageKey(\"thread_id\", threadId);\n assertSafeStorageKey(\"checkpoint_ns\", checkpointNs, { allowEmpty: true });\n\n const nsStorage = this.storage[threadId]?.[checkpointNs] ?? {};\n\n // Build the parent chain starting at the target's parent (the target's\n // own pending writes are for the next super-step and excluded).\n const chain: string[] = [];\n const targetEntry = checkpointId ? nsStorage[checkpointId] : undefined;\n let current: string | undefined = targetEntry?.[2];\n while (current !== undefined) {\n const entry = nsStorage[current];\n if (entry === undefined) break;\n chain.push(current);\n current = entry[2];\n }\n\n const collectedByCh: Record<string, CheckpointPendingWrite[]> = {};\n const seedByCh: Record<string, unknown> = {};\n const remaining = new Set(channels);\n for (const ch of channels) collectedByCh[ch] = [];\n\n for (const cpId of chain) {\n if (remaining.size === 0) break;\n const entry = nsStorage[cpId];\n const ckpt: Checkpoint | undefined =\n entry !== undefined\n ? await this.serde.loadsTyped(\"json\", entry[0])\n : undefined;\n\n const blobValueByCh: Record<string, unknown> = {};\n const terminatedHere = new Set<string>();\n if (ckpt !== undefined) {\n for (const ch of remaining) {\n if (\n Object.prototype.hasOwnProperty.call(ckpt.channel_values, ch) &&\n ckpt.channel_values[ch] !== undefined\n ) {\n blobValueByCh[ch] = ckpt.channel_values[ch];\n terminatedHere.add(ch);\n }\n }\n }\n\n const stepWritesKey = _generateKey(threadId, checkpointNs, cpId);\n const stepWrites = Object.entries(this.writes[stepWritesKey] ?? {});\n // Sort by [taskId, idx] descending to mirror the Python walk order;\n // the full list is reversed once at the end to get oldest→newest.\n stepWrites.sort(([a], [b]) => {\n const [aTask, aIdx] = a.split(\",\");\n const [bTask, bIdx] = b.split(\",\");\n if (aTask !== bTask) return aTask < bTask ? 1 : -1;\n return Number(bIdx) - Number(aIdx);\n });\n for (const [, [tid, ch, serialized]] of stepWrites) {\n if (!remaining.has(ch)) continue;\n // Collect on-path writes regardless of seed type. A plain (pre-delta\n // migration) blob is the settled value AT that ancestor; its own\n // pending writes produce the child and must still be replayed, just\n // like a `DeltaSnapshot` seed. Skipping them would drop post-migration\n // writes saved under the migration boundary checkpoint.\n collectedByCh[ch].push([\n tid,\n ch,\n await this.serde.loadsTyped(\"json\", serialized),\n ]);\n }\n\n for (const ch of terminatedHere) {\n seedByCh[ch] = blobValueByCh[ch];\n remaining.delete(ch);\n }\n }\n\n const result: Record<string, DeltaChannelHistory> = {};\n for (const ch of channels) {\n const entryH: DeltaChannelHistory = {\n writes: collectedByCh[ch].slice().reverse(),\n };\n if (Object.prototype.hasOwnProperty.call(seedByCh, ch)) {\n entryH.seed = seedByCh[ch];\n }\n result[ch] = entryH;\n }\n return result;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"memory.cjs","names":["BaseCheckpointSaver","TASKS","maxChannelVersion","writesSortKey","compareWritesSortKeys","getCheckpointId","copyCheckpoint","WRITES_IDX_MAP"],"sources":["../src/memory.ts"],"sourcesContent":["import type { RunnableConfig } from \"@langchain/core/runnables\";\nimport {\n BaseCheckpointSaver,\n Checkpoint,\n CheckpointListOptions,\n CheckpointTuple,\n compareWritesSortKeys,\n copyCheckpoint,\n getCheckpointId,\n maxChannelVersion,\n writesSortKey,\n WRITES_IDX_MAP,\n} from \"./base.js\";\nimport { SerializerProtocol } from \"./serde/base.js\";\nimport {\n CheckpointMetadata,\n CheckpointPendingWrite,\n DeltaChannelHistory,\n PendingWrite,\n} from \"./types.js\";\nimport { TASKS } from \"./serde/types.js\";\n\n/**\n * Keys that, when written into a plain JavaScript object via bracket\n * notation, traverse the prototype chain and mutate `Object.prototype`\n * (or the constructor) instead of creating a new own property. Any of\n * the three reaches `Object.prototype` and pollutes every object in\n * the running process. CWE-1321 (Prototype Pollution).\n */\nconst POLLUTION_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n/**\n * Asserts that a value sourced from {@link RunnableConfig.configurable} (or\n * any other caller-influenced position) is safe to use as a property key\n * on the in-memory checkpoint store.\n *\n * `MemorySaver` keeps state in two nested plain objects (`storage` and\n * `writes`) and writes to them with bracket notation:\n *\n * this.storage[threadId][checkpointNamespace][checkpoint.id] = ...\n *\n * Without this guard a `threadId` of `\"__proto__\"` (or `\"constructor\"`)\n * resolves through the prototype chain, and the subsequent assignment\n * mutates `Object.prototype`. From that point every plain object in the\n * process inherits the injected property: `for...in` loops over unrelated\n * objects iterate it, framework code that does `if (obj[x])` short-circuits\n * unexpectedly, and downstream serializers may emit it. In a Node.js\n * server this is a stepping stone to remote code execution.\n *\n * `MemorySaver` is the default saver used by every quickstart, every\n * tutorial, and most test fixtures, so this guard runs in the hot path\n * for the most common LangGraph configuration.\n *\n * @param field Name of the configurable field, used in the error message.\n * @param value Value to validate. Must be a non-empty string that is not\n * one of the three prototype-pollution keys.\n * @param options.allowEmpty When true the empty string is accepted, used\n * for the documented empty `checkpoint_ns`\n * default; otherwise an empty string is\n * rejected the same way as a non-string.\n */\nfunction assertSafeStorageKey(\n field: string,\n value: unknown,\n options: { allowEmpty?: boolean } = {}\n): asserts value is string {\n const { allowEmpty = false } = options;\n if (typeof value !== \"string\") {\n const observed =\n value === null\n ? \"null\"\n : value === undefined\n ? \"undefined\"\n : Array.isArray(value)\n ? \"array\"\n : typeof value;\n throw new Error(\n `Invalid configurable value for key \"${field}\": expected a string identifier (got ${observed}). This guard protects MemorySaver from prototype pollution.`\n );\n }\n if (!allowEmpty && value === \"\") {\n throw new Error(\n `Invalid configurable value for key \"${field}\": empty string is not permitted as an in-memory storage key.`\n );\n }\n if (POLLUTION_KEYS.has(value)) {\n throw new Error(\n `Invalid configurable value for key \"${field}\": value \"${value}\" is reserved (would mutate Object.prototype). This guard protects MemorySaver from prototype pollution.`\n );\n }\n}\n\nfunction _generateKey(\n threadId: string,\n checkpointNamespace: string,\n checkpointId: string\n) {\n return JSON.stringify([threadId, checkpointNamespace, checkpointId]);\n}\n\nfunction _parseKey(key: string) {\n const [threadId, checkpointNamespace, checkpointId] = JSON.parse(key);\n return { threadId, checkpointNamespace, checkpointId };\n}\n\nexport class MemorySaver extends BaseCheckpointSaver {\n // thread ID -> checkpoint namespace -> checkpoint ID -> checkpoint mapping\n //\n // Defense in depth against prototype pollution: the backing\n // objects (and every nested level created below) use a null prototype, so\n // even if a malicious key bypassed `assertSafeStorageKey` it could not reach\n // `Object.prototype`. The guard remains the primary control; this is the\n // structural safety net.\n storage: Record<\n string,\n Record<string, Record<string, [Uint8Array, Uint8Array, string | undefined]>>\n > = Object.create(null);\n\n // Each stored write is `[taskId, channel, serializedValue, taskPath?]`,\n // keyed by `${taskId},${idx}`. The optional 4th element is append-only:\n // records persisted before task paths existed (e.g. a `langgraph-api`\n // `.langgraphjs_api.checkpointer.json` file written by an older version)\n // have 3 elements and read back with path `\"\"`, which sorts first, by\n // task id.\n writes: Record<\n string,\n Record<string, [string, string, Uint8Array, string?]>\n > = Object.create(null);\n\n constructor(serde?: SerializerProtocol) {\n super(serde);\n }\n\n /** @internal */\n async _migratePendingSends(\n mutableCheckpoint: Checkpoint,\n threadId: string,\n checkpointNs: string,\n parentCheckpointId: string\n ) {\n const deseriablizableCheckpoint = mutableCheckpoint;\n const parentKey = _generateKey(threadId, checkpointNs, parentCheckpointId);\n\n const pendingSends = await Promise.all(\n Object.values(this.writes[parentKey] ?? {})\n .filter(([_taskId, channel]) => channel === TASKS)\n .map(\n async ([_taskId, _channel, writes]) =>\n await this.serde.loadsTyped(\"json\", writes)\n )\n );\n\n deseriablizableCheckpoint.channel_values ??= {};\n deseriablizableCheckpoint.channel_values[TASKS] = pendingSends;\n\n deseriablizableCheckpoint.channel_versions ??= {};\n deseriablizableCheckpoint.channel_versions[TASKS] =\n Object.keys(deseriablizableCheckpoint.channel_versions).length > 0\n ? maxChannelVersion(\n ...Object.values(deseriablizableCheckpoint.channel_versions)\n )\n : this.getNextVersion(undefined);\n }\n\n /**\n * Stored writes for one checkpoint, in `writesSortKey` order.\n *\n * `taskId` and `idx` come from the storage key, `taskPath` from the stored\n * record (missing on records written before paths existed).\n *\n * @internal\n */\n _orderedWrites(\n threadId: string | undefined,\n checkpointNs: string,\n checkpointId: string\n ): [string, number, [string, string, Uint8Array, string?]][] {\n const stored =\n threadId === undefined\n ? undefined\n : this.writes[_generateKey(threadId, checkpointNs, checkpointId)];\n if (stored === undefined) return [];\n // Compute each record's key once, then sort on the cached keys.\n const keyed = Object.entries(stored).map(([keyStr, record]) => {\n const comma = keyStr.lastIndexOf(\",\");\n const taskId = keyStr.slice(0, comma);\n const idx = Number(keyStr.slice(comma + 1));\n return {\n key: writesSortKey(record[3] ?? \"\", taskId, idx),\n entry: [taskId, idx, record] as [\n string,\n number,\n [string, string, Uint8Array, string?],\n ],\n };\n });\n keyed.sort((a, b) => compareWritesSortKeys(a.key, b.key));\n return keyed.map(({ entry }) => entry);\n }\n\n /** Deserialize `_orderedWrites` records into public pending writes. */\n private async _deserializeOrderedWrites(\n ordered: [string, number, [string, string, Uint8Array, string?]][]\n ): Promise<CheckpointPendingWrite[]> {\n return Promise.all(\n ordered.map(\n async ([, , [taskId, channel, value]]) =>\n [\n taskId,\n channel,\n await this.serde.loadsTyped(\"json\", value),\n ] as CheckpointPendingWrite\n )\n );\n }\n\n async getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined> {\n const thread_id = config.configurable?.thread_id;\n const checkpoint_ns = config.configurable?.checkpoint_ns ?? \"\";\n let checkpoint_id = getCheckpointId(config);\n\n // Defense in depth: every public entry that mutates state already\n // validates these, but read paths must not return data sourced from\n // prototype-chain lookups when an attacker passes the magic keys.\n // `checkpoint_id` is intentionally allowed to be empty / undefined\n // here because the downstream `if (checkpoint_id)` branch treats\n // both as \"fetch the latest checkpoint\" rather than as a lookup key.\n if (thread_id !== undefined) {\n assertSafeStorageKey(\"thread_id\", thread_id);\n }\n assertSafeStorageKey(\"checkpoint_ns\", checkpoint_ns, { allowEmpty: true });\n if (checkpoint_id) {\n assertSafeStorageKey(\"checkpoint_id\", checkpoint_id);\n }\n\n if (checkpoint_id) {\n const saved = this.storage[thread_id]?.[checkpoint_ns]?.[checkpoint_id];\n if (saved !== undefined) {\n const [checkpoint, metadata, parentCheckpointId] = saved;\n const deserializedCheckpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n checkpoint\n );\n\n if (deserializedCheckpoint.v < 4 && parentCheckpointId !== undefined) {\n await this._migratePendingSends(\n deserializedCheckpoint,\n thread_id,\n checkpoint_ns,\n parentCheckpointId\n );\n }\n\n const pendingWrites = await this._deserializeOrderedWrites(\n this._orderedWrites(thread_id, checkpoint_ns, checkpoint_id)\n );\n const checkpointTuple: CheckpointTuple = {\n config,\n checkpoint: deserializedCheckpoint,\n metadata: (await this.serde.loadsTyped(\n \"json\",\n metadata\n )) as CheckpointMetadata,\n pendingWrites,\n };\n if (parentCheckpointId !== undefined) {\n checkpointTuple.parentConfig = {\n configurable: {\n thread_id,\n checkpoint_ns,\n checkpoint_id: parentCheckpointId,\n },\n };\n }\n return checkpointTuple;\n }\n } else {\n const checkpoints = this.storage[thread_id]?.[checkpoint_ns];\n if (checkpoints !== undefined) {\n // eslint-disable-next-line prefer-destructuring\n checkpoint_id = Object.keys(checkpoints).sort().reverse()[0];\n const saved = checkpoints[checkpoint_id];\n const [checkpoint, metadata, parentCheckpointId] = saved;\n const deserializedCheckpoint: Checkpoint = await this.serde.loadsTyped(\n \"json\",\n checkpoint\n );\n\n if (deserializedCheckpoint.v < 4 && parentCheckpointId !== undefined) {\n await this._migratePendingSends(\n deserializedCheckpoint,\n thread_id,\n checkpoint_ns,\n parentCheckpointId\n );\n }\n\n const pendingWrites = await this._deserializeOrderedWrites(\n this._orderedWrites(thread_id, checkpoint_ns, checkpoint_id)\n );\n const checkpointTuple: CheckpointTuple = {\n config: {\n configurable: {\n thread_id,\n checkpoint_id,\n checkpoint_ns,\n },\n },\n checkpoint: deserializedCheckpoint,\n metadata: (await this.serde.loadsTyped(\n \"json\",\n metadata\n )) as CheckpointMetadata,\n pendingWrites,\n };\n if (parentCheckpointId !== undefined) {\n checkpointTuple.parentConfig = {\n configurable: {\n thread_id,\n checkpoint_ns,\n checkpoint_id: parentCheckpointId,\n },\n };\n }\n return checkpointTuple;\n }\n }\n\n return undefined;\n }\n\n async *list(\n config: RunnableConfig,\n options?: CheckpointListOptions\n ): AsyncGenerator<CheckpointTuple> {\n // eslint-disable-next-line prefer-const\n let { before, limit, filter } = options ?? {};\n if (config.configurable?.thread_id !== undefined) {\n assertSafeStorageKey(\"thread_id\", config.configurable.thread_id);\n }\n if (config.configurable?.checkpoint_ns !== undefined) {\n assertSafeStorageKey(\"checkpoint_ns\", config.configurable.checkpoint_ns, {\n allowEmpty: true,\n });\n }\n if (config.configurable?.checkpoint_id) {\n assertSafeStorageKey(\"checkpoint_id\", config.configurable.checkpoint_id);\n }\n if (before?.configurable?.checkpoint_id) {\n assertSafeStorageKey(\"checkpoint_id\", before.configurable.checkpoint_id);\n }\n const threadIds = config.configurable?.thread_id\n ? [config.configurable?.thread_id]\n : Object.keys(this.storage);\n const configCheckpointNamespace = config.configurable?.checkpoint_ns;\n const configCheckpointId = config.configurable?.checkpoint_id;\n\n for (const threadId of threadIds) {\n for (const checkpointNamespace of Object.keys(\n this.storage[threadId] ?? {}\n )) {\n if (\n configCheckpointNamespace !== undefined &&\n checkpointNamespace !== configCheckpointNamespace\n ) {\n continue;\n }\n const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};\n const sortedCheckpoints = Object.keys(checkpoints)\n .sort()\n .reverse()\n .map((id) => [id, checkpoints[id]] as const);\n\n for (const [\n checkpointId,\n [checkpoint, metadataStr, parentCheckpointId],\n ] of sortedCheckpoints) {\n // Filter by checkpoint ID from config\n if (configCheckpointId && checkpointId !== configCheckpointId) {\n continue;\n }\n\n // Filter by checkpoint ID from before config\n if (\n before &&\n before.configurable?.checkpoint_id &&\n checkpointId >= before.configurable.checkpoint_id\n ) {\n continue;\n }\n\n // Parse metadata\n const metadata = (await this.serde.loadsTyped(\n \"json\",\n metadataStr\n )) as CheckpointMetadata;\n\n if (\n filter &&\n !Object.entries(filter).every(\n ([key, value]) =>\n (metadata as unknown as Record<string, unknown>)[key] === value\n )\n ) {\n continue;\n }\n\n // Limit search results\n if (limit !== undefined) {\n if (limit <= 0) break;\n limit -= 1;\n }\n\n const pendingWrites = await this._deserializeOrderedWrites(\n this._orderedWrites(threadId, checkpointNamespace, checkpointId)\n );\n\n const deserializedCheckpoint = await this.serde.loadsTyped(\n \"json\",\n checkpoint\n );\n\n if (\n deserializedCheckpoint.v < 4 &&\n parentCheckpointId !== undefined\n ) {\n await this._migratePendingSends(\n deserializedCheckpoint,\n threadId,\n checkpointNamespace,\n parentCheckpointId\n );\n }\n\n const checkpointTuple: CheckpointTuple = {\n config: {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNamespace,\n checkpoint_id: checkpointId,\n },\n },\n checkpoint: deserializedCheckpoint,\n metadata,\n pendingWrites,\n };\n if (parentCheckpointId !== undefined) {\n checkpointTuple.parentConfig = {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNamespace,\n checkpoint_id: parentCheckpointId,\n },\n };\n }\n yield checkpointTuple;\n }\n }\n }\n }\n\n async put(\n config: RunnableConfig,\n checkpoint: Checkpoint,\n metadata: CheckpointMetadata\n ): Promise<RunnableConfig> {\n const preparedCheckpoint: Partial<Checkpoint> = copyCheckpoint(checkpoint);\n const threadId = config.configurable?.thread_id;\n const checkpointNamespace = config.configurable?.checkpoint_ns ?? \"\";\n if (threadId === undefined) {\n throw new Error(\n `Failed to put checkpoint. The passed RunnableConfig is missing a required \"thread_id\" field in its \"configurable\" property. ` +\n `When using a checkpointer, you must pass a \"thread_id\" so the checkpointer knows which conversation thread to persist state for. ` +\n `Example: graph.stream(input, { configurable: { thread_id: \"my-thread-id\" } })`\n );\n }\n\n assertSafeStorageKey(\"thread_id\", threadId);\n assertSafeStorageKey(\"checkpoint_ns\", checkpointNamespace, {\n allowEmpty: true,\n });\n assertSafeStorageKey(\"checkpoint_id\", checkpoint.id);\n\n if (!this.storage[threadId]) {\n this.storage[threadId] = Object.create(null);\n }\n if (!this.storage[threadId][checkpointNamespace]) {\n this.storage[threadId][checkpointNamespace] = Object.create(null);\n }\n\n const [[, serializedCheckpoint], [, serializedMetadata]] =\n await Promise.all([\n this.serde.dumpsTyped(preparedCheckpoint),\n this.serde.dumpsTyped(metadata),\n ]);\n\n this.storage[threadId][checkpointNamespace][checkpoint.id] = [\n serializedCheckpoint,\n serializedMetadata,\n config.configurable?.checkpoint_id, // parent\n ];\n\n return {\n configurable: {\n thread_id: threadId,\n checkpoint_ns: checkpointNamespace,\n checkpoint_id: checkpoint.id,\n },\n };\n }\n\n async putWrites(\n config: RunnableConfig,\n writes: PendingWrite[],\n taskId: string,\n taskPath?: string\n ): Promise<void> {\n const threadId = config.configurable?.thread_id;\n const checkpointNamespace = config.configurable?.checkpoint_ns;\n const checkpointId = config.configurable?.checkpoint_id;\n if (threadId === undefined) {\n throw new Error(\n `Failed to put writes. The passed RunnableConfig is missing a required \"thread_id\" field in its \"configurable\" property. ` +\n `When using a checkpointer, you must pass a \"thread_id\" so the checkpointer knows which conversation thread to persist state for. ` +\n `Example: graph.stream(input, { configurable: { thread_id: \"my-thread-id\" } })`\n );\n }\n if (checkpointId === undefined) {\n throw new Error(\n `Failed to put writes. The passed RunnableConfig is missing a required \"checkpoint_id\" field in its \"configurable\" property.`\n );\n }\n assertSafeStorageKey(\"thread_id\", threadId);\n assertSafeStorageKey(\"checkpoint_ns\", checkpointNamespace, {\n allowEmpty: true,\n });\n assertSafeStorageKey(\"checkpoint_id\", checkpointId);\n assertSafeStorageKey(\"task_id\", taskId);\n const outerKey = _generateKey(threadId, checkpointNamespace, checkpointId);\n const outerWrites_ = this.writes[outerKey];\n if (this.writes[outerKey] === undefined) {\n this.writes[outerKey] = Object.create(null);\n }\n\n await Promise.all(\n writes.map(async ([channel, value], idx) => {\n const [, serializedValue] = await this.serde.dumpsTyped(value);\n const innerKey: [string, number] = [\n taskId,\n WRITES_IDX_MAP[channel] || idx,\n ];\n const innerKeyStr = `${innerKey[0]},${innerKey[1]}`;\n if (innerKey[1] >= 0 && outerWrites_ && innerKeyStr in outerWrites_) {\n return;\n }\n this.writes[outerKey][innerKeyStr] = [\n taskId,\n channel,\n serializedValue,\n taskPath ?? \"\",\n ];\n })\n );\n }\n\n async deleteThread(threadId: string): Promise<void> {\n assertSafeStorageKey(\"thread_id\", threadId);\n delete this.storage[threadId];\n for (const key of Object.keys(this.writes)) {\n if (_parseKey(key).threadId === threadId) delete this.writes[key];\n }\n }\n\n /**\n * Override: walk the parent chain ONCE for all requested channels using\n * direct storage access.\n *\n * Each channel terminates independently at the nearest ancestor whose\n * stored `channel_values[ch]` is populated. Other channels keep walking\n * until they find their own terminator or hit the root.\n *\n * The seed value (whether a `DeltaSnapshot` or a plain pre-delta migration\n * blob) is the value AT that ancestor, prior to its own pending writes that\n * produce the child. Those on-path writes — including the ones stored on the\n * terminating ancestor — are always collected and replayed on top of the\n * seed, so a thread migrated from a pre-delta channel does not drop the\n * writes saved under the migration boundary checkpoint.\n *\n * @remarks Beta. See {@link BaseCheckpointSaver.getDeltaChannelHistory}.\n */\n async getDeltaChannelHistory(options: {\n config: RunnableConfig;\n channels: string[];\n }): Promise<Record<string, DeltaChannelHistory>> {\n const { config, channels } = options;\n if (channels.length === 0) return {};\n\n const threadId = config.configurable?.thread_id;\n const checkpointNs = config.configurable?.checkpoint_ns ?? \"\";\n\n if (threadId !== undefined) assertSafeStorageKey(\"thread_id\", threadId);\n assertSafeStorageKey(\"checkpoint_ns\", checkpointNs, { allowEmpty: true });\n\n const nsStorage = this.storage[threadId]?.[checkpointNs] ?? {};\n const checkpointId =\n getCheckpointId(config) || Object.keys(nsStorage).sort().reverse()[0];\n\n // Build the parent chain starting at the target's parent (the target's\n // own pending writes are for the next super-step and excluded).\n const chain: string[] = [];\n const targetEntry = checkpointId ? nsStorage[checkpointId] : undefined;\n let current: string | undefined = targetEntry?.[2];\n while (current !== undefined) {\n const entry = nsStorage[current];\n if (entry === undefined) break;\n chain.push(current);\n current = entry[2];\n }\n\n const collectedByCh: Record<string, CheckpointPendingWrite[]> = {};\n const seedByCh: Record<string, unknown> = {};\n const remaining = new Set(channels);\n for (const ch of channels) collectedByCh[ch] = [];\n\n for (const cpId of chain) {\n if (remaining.size === 0) break;\n const entry = nsStorage[cpId];\n const ckpt: Checkpoint | undefined =\n entry !== undefined\n ? await this.serde.loadsTyped(\"json\", entry[0])\n : undefined;\n\n const blobValueByCh: Record<string, unknown> = {};\n const terminatedHere = new Set<string>();\n if (ckpt !== undefined) {\n for (const ch of remaining) {\n if (\n Object.prototype.hasOwnProperty.call(ckpt.channel_values, ch) &&\n ckpt.channel_values[ch] !== undefined\n ) {\n blobValueByCh[ch] = ckpt.channel_values[ch];\n terminatedHere.add(ch);\n }\n }\n }\n\n // `writesSortKey` order — the order live execution applies a\n // superstep's writes in (the same order `getTuple` returns them in).\n // Each block is pushed reversed so the final `.reverse()` below yields\n // oldest→newest checkpoints with each checkpoint's writes ascending.\n const stepWrites = this._orderedWrites(threadId, checkpointNs, cpId);\n for (let i = stepWrites.length - 1; i >= 0; i -= 1) {\n const [tid, ch, serialized] = stepWrites[i][2];\n if (!remaining.has(ch)) continue;\n // Collect on-path writes regardless of seed type. A plain (pre-delta\n // migration) blob is the settled value AT that ancestor; its own\n // pending writes produce the child and must still be replayed, just\n // like a `DeltaSnapshot` seed. Skipping them would drop post-migration\n // writes saved under the migration boundary checkpoint.\n collectedByCh[ch].push([\n tid,\n ch,\n await this.serde.loadsTyped(\"json\", serialized),\n ]);\n }\n\n for (const ch of terminatedHere) {\n seedByCh[ch] = blobValueByCh[ch];\n remaining.delete(ch);\n }\n }\n\n const result: Record<string, DeltaChannelHistory> = {};\n for (const ch of channels) {\n const entryH: DeltaChannelHistory = {\n writes: collectedByCh[ch].slice().reverse(),\n };\n if (Object.prototype.hasOwnProperty.call(seedByCh, ch)) {\n entryH.seed = seedByCh[ch];\n }\n result[ch] = entryH;\n }\n return result;\n }\n}\n"],"mappings":"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"}
package/dist/memory.d.cts CHANGED
@@ -6,14 +6,24 @@ import { RunnableConfig } from "@langchain/core/runnables";
6
6
  //#region src/memory.d.ts
7
7
  declare class MemorySaver extends BaseCheckpointSaver {
8
8
  storage: Record<string, Record<string, Record<string, [Uint8Array, Uint8Array, string | undefined]>>>;
9
- writes: Record<string, Record<string, [string, string, Uint8Array]>>;
9
+ writes: Record<string, Record<string, [string, string, Uint8Array, string?]>>;
10
10
  constructor(serde?: SerializerProtocol);
11
11
  /** @internal */
12
12
  _migratePendingSends(mutableCheckpoint: Checkpoint, threadId: string, checkpointNs: string, parentCheckpointId: string): Promise<void>;
13
+ /**
14
+ * Stored writes for one checkpoint, in `writesSortKey` order.
15
+ *
16
+ * `taskId` and `idx` come from the storage key, `taskPath` from the stored
17
+ * record (missing on records written before paths existed).
18
+ *
19
+ * @internal
20
+ */
21
+ _orderedWrites(threadId: string | undefined, checkpointNs: string, checkpointId: string): [string, number, [string, string, Uint8Array, string?]][];
22
+ private _deserializeOrderedWrites;
13
23
  getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined>;
14
24
  list(config: RunnableConfig, options?: CheckpointListOptions): AsyncGenerator<CheckpointTuple>;
15
25
  put(config: RunnableConfig, checkpoint: Checkpoint, metadata: CheckpointMetadata): Promise<RunnableConfig>;
16
- putWrites(config: RunnableConfig, writes: PendingWrite[], taskId: string): Promise<void>;
26
+ putWrites(config: RunnableConfig, writes: PendingWrite[], taskId: string, taskPath?: string): Promise<void>;
17
27
  deleteThread(threadId: string): Promise<void>;
18
28
  /**
19
29
  * Override: walk the parent chain ONCE for all requested channels using
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.cts","names":[],"sources":["../src/memory.ts"],"mappings":";;;;;;cAuGa,WAAA,SAAoB,mBAAA;EAQ/B,OAAA,EAAS,MAAA,SAEP,MAAA,SAAe,MAAA,UAAgB,UAAA,EAAY,UAAA;EAG7C,MAAA,EAAQ,MAAA,SAAe,MAAA,0BAAgC,UAAA;EAGvD,WAAA,CAAY,KAAA,GAAQ,kBAAA;;EAKd,oBAAA,CACJ,iBAAA,EAAmB,UAAA,EACnB,QAAA,UACA,YAAA,UACA,kBAAA,WAA0B,OAAA;EA0BtB,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,eAAA;EAuIzC,IAAA,CACL,MAAA,EAAQ,cAAA,EACR,OAAA,GAAU,qBAAA,GACT,cAAA,CAAe,eAAA;EAuIZ,GAAA,CACJ,MAAA,EAAQ,cAAA,EACR,UAAA,EAAY,UAAA,EACZ,QAAA,EAAU,kBAAA,GACT,OAAA,CAAQ,cAAA;EA8CL,SAAA,CACJ,MAAA,EAAQ,cAAA,EACR,MAAA,EAAQ,YAAA,IACR,MAAA,WACC,OAAA;EA4CG,YAAA,CAAa,QAAA,WAAmB,OAAA;EAzZf;;;;;;;;;;;;;;;;;EAkbjB,sBAAA,CAAuB,OAAA;IAC3B,MAAA,EAAQ,cAAA;IACR,QAAA;EAAA,IACE,OAAA,CAAQ,MAAA,SAAe,mBAAA;AAAA"}
1
+ {"version":3,"file":"memory.d.cts","names":[],"sources":["../src/memory.ts"],"mappings":";;;;;;cAyGa,WAAA,SAAoB,mBAAA;EAQ/B,OAAA,EAAS,MAAA,SAEP,MAAA,SAAe,MAAA,UAAgB,UAAA,EAAY,UAAA;EAS7C,MAAA,EAAQ,MAAA,SAEN,MAAA,0BAAgC,UAAA;EAGlC,WAAA,CAAY,KAAA,GAAQ,kBAAA;;EAKd,oBAAA,CACJ,iBAAA,EAAmB,UAAA,EACnB,QAAA,UACA,YAAA,UACA,kBAAA,WAA0B,OAAA;EAvBiB;;;;;;;;EAyD7C,cAAA,CACE,QAAA,sBACA,YAAA,UACA,YAAA,6CACmC,UAAA;EAAA,QAyBvB,yBAAA;EAeR,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,eAAA;EAmHzC,IAAA,CACL,MAAA,EAAQ,cAAA,EACR,OAAA,GAAU,qBAAA,GACT,cAAA,CAAe,eAAA;EA+HZ,GAAA,CACJ,MAAA,EAAQ,cAAA,EACR,UAAA,EAAY,UAAA,EACZ,QAAA,EAAU,kBAAA,GACT,OAAA,CAAQ,cAAA;EA8CL,SAAA,CACJ,MAAA,EAAQ,cAAA,EACR,MAAA,EAAQ,YAAA,IACR,MAAA,UACA,QAAA,YACC,OAAA;EAiDG,YAAA,CAAa,QAAA,WAAmB,OAAA;EAxO1B;;;;;;;;;;;;;;;;;EAiQN,sBAAA,CAAuB,OAAA;IAC3B,MAAA,EAAQ,cAAA;IACR,QAAA;EAAA,IACE,OAAA,CAAQ,MAAA,SAAe,mBAAA;AAAA"}
package/dist/memory.d.ts CHANGED
@@ -6,14 +6,24 @@ import { RunnableConfig } from "@langchain/core/runnables";
6
6
  //#region src/memory.d.ts
7
7
  declare class MemorySaver extends BaseCheckpointSaver {
8
8
  storage: Record<string, Record<string, Record<string, [Uint8Array, Uint8Array, string | undefined]>>>;
9
- writes: Record<string, Record<string, [string, string, Uint8Array]>>;
9
+ writes: Record<string, Record<string, [string, string, Uint8Array, string?]>>;
10
10
  constructor(serde?: SerializerProtocol);
11
11
  /** @internal */
12
12
  _migratePendingSends(mutableCheckpoint: Checkpoint, threadId: string, checkpointNs: string, parentCheckpointId: string): Promise<void>;
13
+ /**
14
+ * Stored writes for one checkpoint, in `writesSortKey` order.
15
+ *
16
+ * `taskId` and `idx` come from the storage key, `taskPath` from the stored
17
+ * record (missing on records written before paths existed).
18
+ *
19
+ * @internal
20
+ */
21
+ _orderedWrites(threadId: string | undefined, checkpointNs: string, checkpointId: string): [string, number, [string, string, Uint8Array, string?]][];
22
+ private _deserializeOrderedWrites;
13
23
  getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined>;
14
24
  list(config: RunnableConfig, options?: CheckpointListOptions): AsyncGenerator<CheckpointTuple>;
15
25
  put(config: RunnableConfig, checkpoint: Checkpoint, metadata: CheckpointMetadata): Promise<RunnableConfig>;
16
- putWrites(config: RunnableConfig, writes: PendingWrite[], taskId: string): Promise<void>;
26
+ putWrites(config: RunnableConfig, writes: PendingWrite[], taskId: string, taskPath?: string): Promise<void>;
17
27
  deleteThread(threadId: string): Promise<void>;
18
28
  /**
19
29
  * Override: walk the parent chain ONCE for all requested channels using
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.ts","names":[],"sources":["../src/memory.ts"],"mappings":";;;;;;cAuGa,WAAA,SAAoB,mBAAA;EAQ/B,OAAA,EAAS,MAAA,SAEP,MAAA,SAAe,MAAA,UAAgB,UAAA,EAAY,UAAA;EAG7C,MAAA,EAAQ,MAAA,SAAe,MAAA,0BAAgC,UAAA;EAGvD,WAAA,CAAY,KAAA,GAAQ,kBAAA;;EAKd,oBAAA,CACJ,iBAAA,EAAmB,UAAA,EACnB,QAAA,UACA,YAAA,UACA,kBAAA,WAA0B,OAAA;EA0BtB,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,eAAA;EAuIzC,IAAA,CACL,MAAA,EAAQ,cAAA,EACR,OAAA,GAAU,qBAAA,GACT,cAAA,CAAe,eAAA;EAuIZ,GAAA,CACJ,MAAA,EAAQ,cAAA,EACR,UAAA,EAAY,UAAA,EACZ,QAAA,EAAU,kBAAA,GACT,OAAA,CAAQ,cAAA;EA8CL,SAAA,CACJ,MAAA,EAAQ,cAAA,EACR,MAAA,EAAQ,YAAA,IACR,MAAA,WACC,OAAA;EA4CG,YAAA,CAAa,QAAA,WAAmB,OAAA;EAzZf;;;;;;;;;;;;;;;;;EAkbjB,sBAAA,CAAuB,OAAA;IAC3B,MAAA,EAAQ,cAAA;IACR,QAAA;EAAA,IACE,OAAA,CAAQ,MAAA,SAAe,mBAAA;AAAA"}
1
+ {"version":3,"file":"memory.d.ts","names":[],"sources":["../src/memory.ts"],"mappings":";;;;;;cAyGa,WAAA,SAAoB,mBAAA;EAQ/B,OAAA,EAAS,MAAA,SAEP,MAAA,SAAe,MAAA,UAAgB,UAAA,EAAY,UAAA;EAS7C,MAAA,EAAQ,MAAA,SAEN,MAAA,0BAAgC,UAAA;EAGlC,WAAA,CAAY,KAAA,GAAQ,kBAAA;;EAKd,oBAAA,CACJ,iBAAA,EAAmB,UAAA,EACnB,QAAA,UACA,YAAA,UACA,kBAAA,WAA0B,OAAA;EAvBiB;;;;;;;;EAyD7C,cAAA,CACE,QAAA,sBACA,YAAA,UACA,YAAA,6CACmC,UAAA;EAAA,QAyBvB,yBAAA;EAeR,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,OAAA,CAAQ,eAAA;EAmHzC,IAAA,CACL,MAAA,EAAQ,cAAA,EACR,OAAA,GAAU,qBAAA,GACT,cAAA,CAAe,eAAA;EA+HZ,GAAA,CACJ,MAAA,EAAQ,cAAA,EACR,UAAA,EAAY,UAAA,EACZ,QAAA,EAAU,kBAAA,GACT,OAAA,CAAQ,cAAA;EA8CL,SAAA,CACJ,MAAA,EAAQ,cAAA,EACR,MAAA,EAAQ,YAAA,IACR,MAAA,UACA,QAAA,YACC,OAAA;EAiDG,YAAA,CAAa,QAAA,WAAmB,OAAA;EAxO1B;;;;;;;;;;;;;;;;;EAiQN,sBAAA,CAAuB,OAAA;IAC3B,MAAA,EAAQ,cAAA;IACR,QAAA;EAAA,IACE,OAAA,CAAQ,MAAA,SAAe,mBAAA;AAAA"}
package/dist/memory.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TASKS } from "./serde/types.js";
2
- import { BaseCheckpointSaver, WRITES_IDX_MAP, copyCheckpoint, getCheckpointId, maxChannelVersion } from "./base.js";
2
+ import { BaseCheckpointSaver, WRITES_IDX_MAP, compareWritesSortKeys, copyCheckpoint, getCheckpointId, maxChannelVersion, writesSortKey } from "./base.js";
3
3
  //#region src/memory.ts
4
4
  /**
5
5
  * Keys that, when written into a plain JavaScript object via bracket
@@ -83,6 +83,41 @@ var MemorySaver = class extends BaseCheckpointSaver {
83
83
  deseriablizableCheckpoint.channel_versions ??= {};
84
84
  deseriablizableCheckpoint.channel_versions[TASKS] = Object.keys(deseriablizableCheckpoint.channel_versions).length > 0 ? maxChannelVersion(...Object.values(deseriablizableCheckpoint.channel_versions)) : this.getNextVersion(void 0);
85
85
  }
86
+ /**
87
+ * Stored writes for one checkpoint, in `writesSortKey` order.
88
+ *
89
+ * `taskId` and `idx` come from the storage key, `taskPath` from the stored
90
+ * record (missing on records written before paths existed).
91
+ *
92
+ * @internal
93
+ */
94
+ _orderedWrites(threadId, checkpointNs, checkpointId) {
95
+ const stored = threadId === void 0 ? void 0 : this.writes[_generateKey(threadId, checkpointNs, checkpointId)];
96
+ if (stored === void 0) return [];
97
+ const keyed = Object.entries(stored).map(([keyStr, record]) => {
98
+ const comma = keyStr.lastIndexOf(",");
99
+ const taskId = keyStr.slice(0, comma);
100
+ const idx = Number(keyStr.slice(comma + 1));
101
+ return {
102
+ key: writesSortKey(record[3] ?? "", taskId, idx),
103
+ entry: [
104
+ taskId,
105
+ idx,
106
+ record
107
+ ]
108
+ };
109
+ });
110
+ keyed.sort((a, b) => compareWritesSortKeys(a.key, b.key));
111
+ return keyed.map(({ entry }) => entry);
112
+ }
113
+ /** Deserialize `_orderedWrites` records into public pending writes. */
114
+ async _deserializeOrderedWrites(ordered) {
115
+ return Promise.all(ordered.map(async ([, , [taskId, channel, value]]) => [
116
+ taskId,
117
+ channel,
118
+ await this.serde.loadsTyped("json", value)
119
+ ]));
120
+ }
86
121
  async getTuple(config) {
87
122
  const thread_id = config.configurable?.thread_id;
88
123
  const checkpoint_ns = config.configurable?.checkpoint_ns ?? "";
@@ -94,16 +129,9 @@ var MemorySaver = class extends BaseCheckpointSaver {
94
129
  const saved = this.storage[thread_id]?.[checkpoint_ns]?.[checkpoint_id];
95
130
  if (saved !== void 0) {
96
131
  const [checkpoint, metadata, parentCheckpointId] = saved;
97
- const key = _generateKey(thread_id, checkpoint_ns, checkpoint_id);
98
132
  const deserializedCheckpoint = await this.serde.loadsTyped("json", checkpoint);
99
133
  if (deserializedCheckpoint.v < 4 && parentCheckpointId !== void 0) await this._migratePendingSends(deserializedCheckpoint, thread_id, checkpoint_ns, parentCheckpointId);
100
- const pendingWrites = await Promise.all(Object.values(this.writes[key] || {}).map(async ([taskId, channel, value]) => {
101
- return [
102
- taskId,
103
- channel,
104
- await this.serde.loadsTyped("json", value)
105
- ];
106
- }));
134
+ const pendingWrites = await this._deserializeOrderedWrites(this._orderedWrites(thread_id, checkpoint_ns, checkpoint_id));
107
135
  const checkpointTuple = {
108
136
  config,
109
137
  checkpoint: deserializedCheckpoint,
@@ -120,18 +148,11 @@ var MemorySaver = class extends BaseCheckpointSaver {
120
148
  } else {
121
149
  const checkpoints = this.storage[thread_id]?.[checkpoint_ns];
122
150
  if (checkpoints !== void 0) {
123
- checkpoint_id = Object.keys(checkpoints).sort((a, b) => b.localeCompare(a))[0];
151
+ checkpoint_id = Object.keys(checkpoints).sort().reverse()[0];
124
152
  const [checkpoint, metadata, parentCheckpointId] = checkpoints[checkpoint_id];
125
- const key = _generateKey(thread_id, checkpoint_ns, checkpoint_id);
126
153
  const deserializedCheckpoint = await this.serde.loadsTyped("json", checkpoint);
127
154
  if (deserializedCheckpoint.v < 4 && parentCheckpointId !== void 0) await this._migratePendingSends(deserializedCheckpoint, thread_id, checkpoint_ns, parentCheckpointId);
128
- const pendingWrites = await Promise.all(Object.values(this.writes[key] || {}).map(async ([taskId, channel, value]) => {
129
- return [
130
- taskId,
131
- channel,
132
- await this.serde.loadsTyped("json", value)
133
- ];
134
- }));
155
+ const pendingWrites = await this._deserializeOrderedWrites(this._orderedWrites(thread_id, checkpoint_ns, checkpoint_id));
135
156
  const checkpointTuple = {
136
157
  config: { configurable: {
137
158
  thread_id,
@@ -163,7 +184,7 @@ var MemorySaver = class extends BaseCheckpointSaver {
163
184
  for (const threadId of threadIds) for (const checkpointNamespace of Object.keys(this.storage[threadId] ?? {})) {
164
185
  if (configCheckpointNamespace !== void 0 && checkpointNamespace !== configCheckpointNamespace) continue;
165
186
  const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};
166
- const sortedCheckpoints = Object.entries(checkpoints).sort((a, b) => b[0].localeCompare(a[0]));
187
+ const sortedCheckpoints = Object.keys(checkpoints).sort().reverse().map((id) => [id, checkpoints[id]]);
167
188
  for (const [checkpointId, [checkpoint, metadataStr, parentCheckpointId]] of sortedCheckpoints) {
168
189
  if (configCheckpointId && checkpointId !== configCheckpointId) continue;
169
190
  if (before && before.configurable?.checkpoint_id && checkpointId >= before.configurable.checkpoint_id) continue;
@@ -173,15 +194,7 @@ var MemorySaver = class extends BaseCheckpointSaver {
173
194
  if (limit <= 0) break;
174
195
  limit -= 1;
175
196
  }
176
- const key = _generateKey(threadId, checkpointNamespace, checkpointId);
177
- const writes = Object.values(this.writes[key] || {});
178
- const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
179
- return [
180
- taskId,
181
- channel,
182
- await this.serde.loadsTyped("json", value)
183
- ];
184
- }));
197
+ const pendingWrites = await this._deserializeOrderedWrites(this._orderedWrites(threadId, checkpointNamespace, checkpointId));
185
198
  const deserializedCheckpoint = await this.serde.loadsTyped("json", checkpoint);
186
199
  if (deserializedCheckpoint.v < 4 && parentCheckpointId !== void 0) await this._migratePendingSends(deserializedCheckpoint, threadId, checkpointNamespace, parentCheckpointId);
187
200
  const checkpointTuple = {
@@ -225,7 +238,7 @@ var MemorySaver = class extends BaseCheckpointSaver {
225
238
  checkpoint_id: checkpoint.id
226
239
  } };
227
240
  }
228
- async putWrites(config, writes, taskId) {
241
+ async putWrites(config, writes, taskId, taskPath) {
229
242
  const threadId = config.configurable?.thread_id;
230
243
  const checkpointNamespace = config.configurable?.checkpoint_ns;
231
244
  const checkpointId = config.configurable?.checkpoint_id;
@@ -246,7 +259,8 @@ var MemorySaver = class extends BaseCheckpointSaver {
246
259
  this.writes[outerKey][innerKeyStr] = [
247
260
  taskId,
248
261
  channel,
249
- serializedValue
262
+ serializedValue,
263
+ taskPath ?? ""
250
264
  ];
251
265
  }));
252
266
  }
@@ -277,10 +291,10 @@ var MemorySaver = class extends BaseCheckpointSaver {
277
291
  if (channels.length === 0) return {};
278
292
  const threadId = config.configurable?.thread_id;
279
293
  const checkpointNs = config.configurable?.checkpoint_ns ?? "";
280
- const checkpointId = getCheckpointId(config);
281
294
  if (threadId !== void 0) assertSafeStorageKey("thread_id", threadId);
282
295
  assertSafeStorageKey("checkpoint_ns", checkpointNs, { allowEmpty: true });
283
296
  const nsStorage = this.storage[threadId]?.[checkpointNs] ?? {};
297
+ const checkpointId = getCheckpointId(config) || Object.keys(nsStorage).sort().reverse()[0];
284
298
  const chain = [];
285
299
  let current = (checkpointId ? nsStorage[checkpointId] : void 0)?.[2];
286
300
  while (current !== void 0) {
@@ -305,15 +319,9 @@ var MemorySaver = class extends BaseCheckpointSaver {
305
319
  terminatedHere.add(ch);
306
320
  }
307
321
  }
308
- const stepWritesKey = _generateKey(threadId, checkpointNs, cpId);
309
- const stepWrites = Object.entries(this.writes[stepWritesKey] ?? {});
310
- stepWrites.sort(([a], [b]) => {
311
- const [aTask, aIdx] = a.split(",");
312
- const [bTask, bIdx] = b.split(",");
313
- if (aTask !== bTask) return aTask < bTask ? 1 : -1;
314
- return Number(bIdx) - Number(aIdx);
315
- });
316
- for (const [, [tid, ch, serialized]] of stepWrites) {
322
+ const stepWrites = this._orderedWrites(threadId, checkpointNs, cpId);
323
+ for (let i = stepWrites.length - 1; i >= 0; i -= 1) {
324
+ const [tid, ch, serialized] = stepWrites[i][2];
317
325
  if (!remaining.has(ch)) continue;
318
326
  collectedByCh[ch].push([
319
327
  tid,