@langchain/langgraph-checkpoint 1.1.5 → 1.2.0
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/base.cjs +67 -1
- package/dist/base.cjs.map +1 -1
- package/dist/base.d.cts +47 -2
- package/dist/base.d.cts.map +1 -1
- package/dist/base.d.ts +47 -2
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +66 -2
- package/dist/base.js.map +1 -1
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/memory.cjs +47 -39
- package/dist/memory.cjs.map +1 -1
- package/dist/memory.d.cts +12 -2
- package/dist/memory.d.cts.map +1 -1
- package/dist/memory.d.ts +12 -2
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +48 -40
- package/dist/memory.js.map +1 -1
- package/dist/store/base.cjs +44 -11
- package/dist/store/base.cjs.map +1 -1
- package/dist/store/base.d.cts +15 -1
- package/dist/store/base.d.cts.map +1 -1
- package/dist/store/base.d.ts +15 -1
- package/dist/store/base.d.ts.map +1 -1
- package/dist/store/base.js +44 -12
- package/dist/store/base.js.map +1 -1
- package/dist/store/batch.cjs +13 -0
- package/dist/store/batch.cjs.map +1 -1
- package/dist/store/batch.d.cts.map +1 -1
- package/dist/store/batch.d.ts.map +1 -1
- package/dist/store/batch.js +14 -1
- package/dist/store/batch.js.map +1 -1
- package/dist/store/memory.cjs +8 -1
- package/dist/store/memory.cjs.map +1 -1
- package/dist/store/memory.d.cts.map +1 -1
- package/dist/store/memory.d.ts.map +1 -1
- package/dist/store/memory.js +9 -2
- package/dist/store/memory.js.map +1 -1
- package/package.json +2 -2
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
|
|
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(
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
309
|
-
|
|
310
|
-
|
|
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,
|
package/dist/memory.cjs.map
CHANGED
|
@@ -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
|
package/dist/memory.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.d.cts","names":[],"sources":["../src/memory.ts"],"mappings":";;;;;;
|
|
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
|
package/dist/memory.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.d.ts","names":[],"sources":["../src/memory.ts"],"mappings":";;;;;;
|
|
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
|
|
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(
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
309
|
-
|
|
310
|
-
|
|
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,
|