@langchain/langgraph-checkpoint 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2024 LangChain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # @langchain/langgraph-checkpoint
2
+
3
+ This library defines the base interface for [LangGraph.js](https://github.com/langchain-ai/langgraphjs) checkpointers. Checkpointers provide persistence layer for LangGraph. They allow you to interact with and manage the graph's state. When you use a graph with a checkpointer, the checkpointer saves a _checkpoint_ of the graph state at every superstep, enabling several powerful capabilities like human-in-the-loop, "memory" between interactions and more.
4
+
5
+ ## Key concepts
6
+
7
+ ### Checkpoint
8
+
9
+ Checkpoint is a snapshot of the graph state at a given point in time. Checkpoint tuple refers to an object containing checkpoint and the associated config, metadata and pending writes.
10
+
11
+ ### Thread
12
+
13
+ Threads enable the checkpointing of multiple different runs, making them essential for multi-tenant chat applications and other scenarios where maintaining separate states is necessary. A thread is a unique ID assigned to a series of checkpoints saved by a checkpointer. When using a checkpointer, you must specify a `thread_id` and optionally `checkpoint_id` when running the graph.
14
+
15
+ - `thread_id` is simply the ID of a thread. This is always required
16
+ - `checkpoint_id` can optionally be passed. This identifier refers to a specific checkpoint within a thread. This can be used to kick of a run of a graph from some point halfway through a thread.
17
+
18
+ You must pass these when invoking the graph as part of the configurable part of the config, e.g.
19
+
20
+ ```ts
21
+ { configurable: { thread_id: "1" } } // valid config
22
+ { configurable: { thread_id: "1", checkpoint_id: "0c62ca34-ac19-445d-bbb0-5b4984975b2a" } } // also valid config
23
+ ```
24
+
25
+ ### Serde
26
+
27
+ `@langchain/langgraph-checkpoint` also defines protocol for serialization/deserialization (serde) and provides an default implementation that handles a range of types.
28
+
29
+ ### Pending writes
30
+
31
+ When a graph node fails mid-execution at a given superstep, LangGraph stores pending checkpoint writes from any other nodes that completed successfully at that superstep, so that whenever we resume graph execution from that superstep we don't re-run the successful nodes.
32
+
33
+ ## Interface
34
+
35
+ Each checkpointer should conform to `BaseCheckpointSaver` interface and must implement the following methods:
36
+
37
+ - `.put` - Store a checkpoint with its configuration and metadata.
38
+ - `.putWrites` - Store intermediate writes linked to a checkpoint (i.e. pending writes).
39
+ - `.getTuple` - Fetch a checkpoint tuple using for a given configuration (`thread_id` and `thread_ts`).
40
+ - `.list` - List checkpoints that match a given configuration and filter criteria.
41
+
42
+ ## Usage
43
+
44
+ ```ts
45
+ import { MemorySaver } from "@langchain/langgraph-checkpoint";
46
+
47
+ const writeConfig = {
48
+ configurable: {
49
+ thread_id: "1",
50
+ checkpoint_ns: ""
51
+ }
52
+ };
53
+ const readConfig = {
54
+ configurable: {
55
+ thread_id: "1"
56
+ }
57
+ };
58
+
59
+ const checkpointer = new MemorySaver();
60
+ const checkpoint = {
61
+ v: 1,
62
+ ts: "2024-07-31T20:14:19.804150+00:00",
63
+ id: "1ef4f797-8335-6428-8001-8a1503f9b875",
64
+ channel_values: {
65
+ my_key: "meow",
66
+ node: "node"
67
+ },
68
+ channel_versions: {
69
+ __start__: 2,
70
+ my_key: 3,
71
+ start:node: 3,
72
+ node: 3
73
+ },
74
+ versions_seen: {
75
+ __input__: {},
76
+ __start__: {
77
+ __start__: 1
78
+ },
79
+ node: {
80
+ start:node: 2
81
+ }
82
+ },
83
+ pending_sends: [],
84
+ current_tasks: {}
85
+ }
86
+
87
+ // store checkpoint
88
+ await checkpointer.put(writeConfig, checkpoint, {}, {})
89
+
90
+ // load checkpoint
91
+ await checkpointer.get(readConfig)
92
+
93
+ // list checkpoints
94
+ for await (const checkpoint of checkpointer.list(readConfig)) {
95
+ console.log(checkpoint);
96
+ }
97
+ ```
package/dist/base.cjs ADDED
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseCheckpointSaver = exports.copyCheckpoint = exports.emptyCheckpoint = exports.deepCopy = void 0;
4
+ const base_js_1 = require("./serde/base.cjs");
5
+ const id_js_1 = require("./id.cjs");
6
+ function deepCopy(obj) {
7
+ if (typeof obj !== "object" || obj === null) {
8
+ return obj;
9
+ }
10
+ const newObj = Array.isArray(obj) ? [] : {};
11
+ for (const key in obj) {
12
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
13
+ newObj[key] = deepCopy(obj[key]);
14
+ }
15
+ }
16
+ return newObj;
17
+ }
18
+ exports.deepCopy = deepCopy;
19
+ function emptyCheckpoint() {
20
+ return {
21
+ v: 1,
22
+ id: (0, id_js_1.uuid6)(-2),
23
+ ts: new Date().toISOString(),
24
+ channel_values: {},
25
+ channel_versions: {},
26
+ versions_seen: {},
27
+ pending_sends: [],
28
+ };
29
+ }
30
+ exports.emptyCheckpoint = emptyCheckpoint;
31
+ function copyCheckpoint(checkpoint) {
32
+ return {
33
+ v: checkpoint.v,
34
+ id: checkpoint.id,
35
+ ts: checkpoint.ts,
36
+ channel_values: { ...checkpoint.channel_values },
37
+ channel_versions: { ...checkpoint.channel_versions },
38
+ versions_seen: deepCopy(checkpoint.versions_seen),
39
+ pending_sends: [...checkpoint.pending_sends],
40
+ };
41
+ }
42
+ exports.copyCheckpoint = copyCheckpoint;
43
+ class BaseCheckpointSaver {
44
+ constructor(serde) {
45
+ Object.defineProperty(this, "serde", {
46
+ enumerable: true,
47
+ configurable: true,
48
+ writable: true,
49
+ value: base_js_1.DefaultSerializer
50
+ });
51
+ this.serde = serde || this.serde;
52
+ }
53
+ async get(config) {
54
+ const value = await this.getTuple(config);
55
+ return value ? value.checkpoint : undefined;
56
+ }
57
+ /**
58
+ * Generate the next version ID for a channel.
59
+ *
60
+ * Default is to use integer versions, incrementing by 1. If you override, you can use str/int/float versions,
61
+ * as long as they are monotonically increasing.
62
+ */
63
+ getNextVersion(current, _channel) {
64
+ return current !== undefined ? current + 1 : 1;
65
+ }
66
+ }
67
+ exports.BaseCheckpointSaver = BaseCheckpointSaver;
package/dist/base.d.ts ADDED
@@ -0,0 +1,75 @@
1
+ import type { RunnableConfig } from "@langchain/core/runnables";
2
+ import { SerializerProtocol } from "./serde/base.js";
3
+ import type { PendingWrite, CheckpointPendingWrite, CheckpointMetadata } from "./types.js";
4
+ import type { ChannelProtocol, SendProtocol } from "./serde/types.js";
5
+ export type ChannelVersions = Record<string, string | number>;
6
+ export interface Checkpoint<N extends string = string, C extends string = string> {
7
+ /**
8
+ * Version number
9
+ */
10
+ v: number;
11
+ /**
12
+ * Checkpoint ID {uuid6}
13
+ */
14
+ id: string;
15
+ /**
16
+ * Timestamp {new Date().toISOString()}
17
+ */
18
+ ts: string;
19
+ /**
20
+ * @default {}
21
+ */
22
+ channel_values: Record<C, unknown>;
23
+ /**
24
+ * @default {}
25
+ */
26
+ channel_versions: Record<C, number>;
27
+ /**
28
+ * @default {}
29
+ */
30
+ versions_seen: Record<N, Record<C, number>>;
31
+ /**
32
+ * List of packets sent to nodes but not yet processed.
33
+ * Cleared by the next checkpoint.
34
+ */
35
+ pending_sends: SendProtocol[];
36
+ }
37
+ export interface ReadonlyCheckpoint extends Readonly<Checkpoint> {
38
+ readonly channel_values: Readonly<Record<string, unknown>>;
39
+ readonly channel_versions: Readonly<Record<string, number>>;
40
+ readonly versions_seen: Readonly<Record<string, Readonly<Record<string, number>>>>;
41
+ }
42
+ export declare function deepCopy<T>(obj: T): T;
43
+ export declare function emptyCheckpoint(): Checkpoint;
44
+ export declare function copyCheckpoint(checkpoint: ReadonlyCheckpoint): Checkpoint;
45
+ export interface CheckpointTuple {
46
+ config: RunnableConfig;
47
+ checkpoint: Checkpoint;
48
+ metadata?: CheckpointMetadata;
49
+ parentConfig?: RunnableConfig;
50
+ pendingWrites?: CheckpointPendingWrite[];
51
+ }
52
+ export type CheckpointListOptions = {
53
+ limit?: number;
54
+ before?: RunnableConfig;
55
+ filter?: Record<string, any>;
56
+ };
57
+ export declare abstract class BaseCheckpointSaver<V = number> {
58
+ serde: SerializerProtocol<unknown>;
59
+ constructor(serde?: SerializerProtocol<unknown>);
60
+ get(config: RunnableConfig): Promise<Checkpoint | undefined>;
61
+ abstract getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined>;
62
+ abstract list(config: RunnableConfig, options?: CheckpointListOptions): AsyncGenerator<CheckpointTuple>;
63
+ abstract put(config: RunnableConfig, checkpoint: Checkpoint, metadata: CheckpointMetadata, newVersions: ChannelVersions): Promise<RunnableConfig>;
64
+ /**
65
+ * Store intermediate writes linked to a checkpoint.
66
+ */
67
+ abstract putWrites(config: RunnableConfig, writes: PendingWrite[], taskId: string): Promise<void>;
68
+ /**
69
+ * Generate the next version ID for a channel.
70
+ *
71
+ * Default is to use integer versions, incrementing by 1. If you override, you can use str/int/float versions,
72
+ * as long as they are monotonically increasing.
73
+ */
74
+ getNextVersion(current: V | undefined, _channel: ChannelProtocol): number;
75
+ }
package/dist/base.js ADDED
@@ -0,0 +1,60 @@
1
+ import { DefaultSerializer } from "./serde/base.js";
2
+ import { uuid6 } from "./id.js";
3
+ export function deepCopy(obj) {
4
+ if (typeof obj !== "object" || obj === null) {
5
+ return obj;
6
+ }
7
+ const newObj = Array.isArray(obj) ? [] : {};
8
+ for (const key in obj) {
9
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
10
+ newObj[key] = deepCopy(obj[key]);
11
+ }
12
+ }
13
+ return newObj;
14
+ }
15
+ export function emptyCheckpoint() {
16
+ return {
17
+ v: 1,
18
+ id: uuid6(-2),
19
+ ts: new Date().toISOString(),
20
+ channel_values: {},
21
+ channel_versions: {},
22
+ versions_seen: {},
23
+ pending_sends: [],
24
+ };
25
+ }
26
+ export function copyCheckpoint(checkpoint) {
27
+ return {
28
+ v: checkpoint.v,
29
+ id: checkpoint.id,
30
+ ts: checkpoint.ts,
31
+ channel_values: { ...checkpoint.channel_values },
32
+ channel_versions: { ...checkpoint.channel_versions },
33
+ versions_seen: deepCopy(checkpoint.versions_seen),
34
+ pending_sends: [...checkpoint.pending_sends],
35
+ };
36
+ }
37
+ export class BaseCheckpointSaver {
38
+ constructor(serde) {
39
+ Object.defineProperty(this, "serde", {
40
+ enumerable: true,
41
+ configurable: true,
42
+ writable: true,
43
+ value: DefaultSerializer
44
+ });
45
+ this.serde = serde || this.serde;
46
+ }
47
+ async get(config) {
48
+ const value = await this.getTuple(config);
49
+ return value ? value.checkpoint : undefined;
50
+ }
51
+ /**
52
+ * Generate the next version ID for a channel.
53
+ *
54
+ * Default is to use integer versions, incrementing by 1. If you override, you can use str/int/float versions,
55
+ * as long as they are monotonically increasing.
56
+ */
57
+ getNextVersion(current, _channel) {
58
+ return current !== undefined ? current + 1 : 1;
59
+ }
60
+ }
package/dist/id.cjs ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uuid5 = exports.uuid6 = void 0;
4
+ const uuid_1 = require("uuid");
5
+ function uuid6(clockseq) {
6
+ return (0, uuid_1.v6)({ clockseq });
7
+ }
8
+ exports.uuid6 = uuid6;
9
+ // Skip UUID validation check, since UUID6s
10
+ // generated with negative clockseq are not
11
+ // technically compliant, but still work.
12
+ // See: https://github.com/uuidjs/uuid/issues/511
13
+ function uuid5(name, namespace) {
14
+ const namespaceBytes = namespace
15
+ .replace(/-/g, "")
16
+ .match(/.{2}/g)
17
+ .map((byte) => parseInt(byte, 16));
18
+ return (0, uuid_1.v5)(name, new Uint8Array(namespaceBytes));
19
+ }
20
+ exports.uuid5 = uuid5;
package/dist/id.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function uuid6(clockseq: number): string;
2
+ export declare function uuid5(name: string, namespace: string): string;
package/dist/id.js ADDED
@@ -0,0 +1,15 @@
1
+ import { v5, v6 } from "uuid";
2
+ export function uuid6(clockseq) {
3
+ return v6({ clockseq });
4
+ }
5
+ // Skip UUID validation check, since UUID6s
6
+ // generated with negative clockseq are not
7
+ // technically compliant, but still work.
8
+ // See: https://github.com/uuidjs/uuid/issues/511
9
+ export function uuid5(name, namespace) {
10
+ const namespaceBytes = namespace
11
+ .replace(/-/g, "")
12
+ .match(/.{2}/g)
13
+ .map((byte) => parseInt(byte, 16));
14
+ return v5(name, new Uint8Array(namespaceBytes));
15
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.MemorySaver = void 0;
18
+ var memory_js_1 = require("./memory.cjs");
19
+ Object.defineProperty(exports, "MemorySaver", { enumerable: true, get: function () { return memory_js_1.MemorySaver; } });
20
+ __exportStar(require("./base.cjs"), exports);
21
+ __exportStar(require("./id.cjs"), exports);
22
+ __exportStar(require("./types.cjs"), exports);
23
+ __exportStar(require("./serde/base.cjs"), exports);
24
+ __exportStar(require("./serde/types.cjs"), exports);
@@ -0,0 +1,6 @@
1
+ export { MemorySaver } from "./memory.js";
2
+ export * from "./base.js";
3
+ export * from "./id.js";
4
+ export * from "./types.js";
5
+ export * from "./serde/base.js";
6
+ export * from "./serde/types.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { MemorySaver } from "./memory.js";
2
+ export * from "./base.js";
3
+ export * from "./id.js";
4
+ export * from "./types.js";
5
+ export * from "./serde/base.js";
6
+ export * from "./serde/types.js";
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemorySaver = void 0;
4
+ const base_js_1 = require("./base.cjs");
5
+ function _generateKey(threadId, checkpointNamespace, checkpointId) {
6
+ return JSON.stringify([threadId, checkpointNamespace, checkpointId]);
7
+ }
8
+ class MemorySaver extends base_js_1.BaseCheckpointSaver {
9
+ constructor(serde) {
10
+ super(serde);
11
+ // thread ID -> checkpoint namespace -> checkpoint ID -> checkpoint mapping
12
+ Object.defineProperty(this, "storage", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: {}
17
+ });
18
+ Object.defineProperty(this, "writes", {
19
+ enumerable: true,
20
+ configurable: true,
21
+ writable: true,
22
+ value: {}
23
+ });
24
+ }
25
+ async getTuple(config) {
26
+ const thread_id = config.configurable?.thread_id;
27
+ const checkpoint_ns = config.configurable?.checkpoint_ns ?? "";
28
+ let checkpoint_id = config.configurable?.checkpoint_id;
29
+ if (checkpoint_id) {
30
+ const saved = this.storage[thread_id]?.[checkpoint_ns]?.[checkpoint_id];
31
+ if (saved !== undefined) {
32
+ const [checkpoint, metadata, parentCheckpointId] = saved;
33
+ const writes = this.writes[_generateKey(thread_id, checkpoint_ns, checkpoint_id)] ??
34
+ [];
35
+ const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
36
+ return [taskId, channel, await this.serde.parse(value)];
37
+ }));
38
+ const parentConfig = parentCheckpointId !== undefined
39
+ ? {
40
+ configurable: {
41
+ thread_id,
42
+ checkpoint_ns,
43
+ checkpoint_id,
44
+ },
45
+ }
46
+ : undefined;
47
+ return {
48
+ config,
49
+ checkpoint: (await this.serde.parse(checkpoint)),
50
+ metadata: (await this.serde.parse(metadata)),
51
+ pendingWrites,
52
+ parentConfig,
53
+ };
54
+ }
55
+ }
56
+ else {
57
+ const checkpoints = this.storage[thread_id]?.[checkpoint_ns];
58
+ if (checkpoints !== undefined) {
59
+ // eslint-disable-next-line prefer-destructuring
60
+ checkpoint_id = Object.keys(checkpoints).sort((a, b) => b.localeCompare(a))[0];
61
+ const saved = checkpoints[checkpoint_id];
62
+ const [checkpoint, metadata, parentCheckpointId] = saved;
63
+ const writes = this.writes[_generateKey(thread_id, checkpoint_ns, checkpoint_id)] ??
64
+ [];
65
+ const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
66
+ return [taskId, channel, await this.serde.parse(value)];
67
+ }));
68
+ const parentConfig = parentCheckpointId !== undefined
69
+ ? {
70
+ configurable: {
71
+ thread_id,
72
+ checkpoint_ns,
73
+ checkpoint_id: parentCheckpointId,
74
+ },
75
+ }
76
+ : undefined;
77
+ return {
78
+ config: {
79
+ configurable: {
80
+ thread_id,
81
+ checkpoint_id,
82
+ checkpoint_ns,
83
+ },
84
+ },
85
+ checkpoint: (await this.serde.parse(checkpoint)),
86
+ metadata: (await this.serde.parse(metadata)),
87
+ pendingWrites,
88
+ parentConfig,
89
+ };
90
+ }
91
+ }
92
+ return undefined;
93
+ }
94
+ async *list(config, options) {
95
+ // eslint-disable-next-line prefer-const
96
+ let { before, limit } = options ?? {};
97
+ const threadIds = config.configurable?.thread_id
98
+ ? [config.configurable?.thread_id]
99
+ : Object.keys(this.storage);
100
+ const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
101
+ for (const threadId of threadIds) {
102
+ const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};
103
+ const sortedCheckpoints = Object.entries(checkpoints).sort((a, b) => b[0].localeCompare(a[0]));
104
+ for (const [checkpointId, [checkpoint, metadataStr, parentCheckpointId],] of sortedCheckpoints) {
105
+ // Filter by checkpoint ID
106
+ if (before &&
107
+ before.configurable?.checkpoint_id &&
108
+ checkpointId >= before.configurable.checkpoint_id) {
109
+ continue;
110
+ }
111
+ // Parse metadata
112
+ const metadata = (await this.serde.parse(metadataStr));
113
+ // Limit search results
114
+ if (limit !== undefined) {
115
+ if (limit <= 0)
116
+ break;
117
+ // eslint-disable-next-line no-param-reassign
118
+ limit -= 1;
119
+ }
120
+ const writes = this.writes[_generateKey(threadId, checkpointNamespace, checkpointId)] ?? [];
121
+ const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
122
+ return [taskId, channel, await this.serde.parse(value)];
123
+ }));
124
+ yield {
125
+ config: {
126
+ configurable: {
127
+ thread_id: threadId,
128
+ checkpoint_ns: checkpointNamespace,
129
+ checkpoint_id: checkpointId,
130
+ },
131
+ },
132
+ checkpoint: (await this.serde.parse(checkpoint)),
133
+ metadata,
134
+ pendingWrites,
135
+ parentConfig: parentCheckpointId
136
+ ? {
137
+ configurable: {
138
+ thread_id: threadId,
139
+ checkpoint_ns: checkpointNamespace,
140
+ checkpoint_id: parentCheckpointId,
141
+ },
142
+ }
143
+ : undefined,
144
+ };
145
+ }
146
+ }
147
+ }
148
+ async put(config, checkpoint, metadata) {
149
+ const threadId = config.configurable?.thread_id;
150
+ const checkpointNamespace = config.configurable?.checkpoint_ns;
151
+ if (threadId === undefined) {
152
+ throw new Error(`Failed to put checkpoint. The passed RunnableConfig is missing a required "thread_id" field in its "configurable" property.`);
153
+ }
154
+ if (checkpointNamespace === undefined) {
155
+ throw new Error(`Failed to put checkpoint. The passed RunnableConfig is missing a required "checkpoint_ns" field in its "configurable" property.`);
156
+ }
157
+ if (!this.storage[threadId]) {
158
+ this.storage[threadId] = {};
159
+ }
160
+ if (!this.storage[threadId][checkpointNamespace]) {
161
+ this.storage[threadId][checkpointNamespace] = {};
162
+ }
163
+ this.storage[threadId][checkpointNamespace][checkpoint.id] = [
164
+ this.serde.stringify(checkpoint),
165
+ this.serde.stringify(metadata),
166
+ config.configurable?.checkpoint_id, // parent
167
+ ];
168
+ return {
169
+ configurable: {
170
+ thread_id: threadId,
171
+ checkpoint_ns: checkpointNamespace,
172
+ checkpoint_id: checkpoint.id,
173
+ },
174
+ };
175
+ }
176
+ async putWrites(config, writes, taskId) {
177
+ const threadId = config.configurable?.thread_id;
178
+ const checkpointNamespace = config.configurable?.checkpoint_ns;
179
+ const checkpointId = config.configurable?.checkpoint_id;
180
+ if (threadId === undefined) {
181
+ throw new Error(`Failed to put writes. The passed RunnableConfig is missing a required "thread_id" field in its "configurable" property`);
182
+ }
183
+ if (checkpointId === undefined) {
184
+ throw new Error(`Failed to put writes. The passed RunnableConfig is missing a required "checkpoint_id" field in its "configurable" property.`);
185
+ }
186
+ const key = _generateKey(threadId, checkpointNamespace, checkpointId);
187
+ if (this.writes[key] === undefined) {
188
+ this.writes[key] = [];
189
+ }
190
+ const pendingWrites = writes.map(([channel, value]) => {
191
+ return [taskId, channel, this.serde.stringify(value)];
192
+ });
193
+ this.writes[key].push(...pendingWrites);
194
+ }
195
+ }
196
+ exports.MemorySaver = MemorySaver;
@@ -0,0 +1,13 @@
1
+ import type { RunnableConfig } from "@langchain/core/runnables";
2
+ import { BaseCheckpointSaver, Checkpoint, CheckpointListOptions, CheckpointTuple } from "./base.js";
3
+ import { SerializerProtocol } from "./serde/base.js";
4
+ import { CheckpointMetadata, CheckpointPendingWrite, PendingWrite } from "./types.js";
5
+ export declare class MemorySaver extends BaseCheckpointSaver {
6
+ storage: Record<string, Record<string, Record<string, [string, string, string | undefined]>>>;
7
+ writes: Record<string, CheckpointPendingWrite[]>;
8
+ constructor(serde?: SerializerProtocol<unknown>);
9
+ getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined>;
10
+ list(config: RunnableConfig, options?: CheckpointListOptions): AsyncGenerator<CheckpointTuple>;
11
+ put(config: RunnableConfig, checkpoint: Checkpoint, metadata: CheckpointMetadata): Promise<RunnableConfig>;
12
+ putWrites(config: RunnableConfig, writes: PendingWrite[], taskId: string): Promise<void>;
13
+ }
package/dist/memory.js ADDED
@@ -0,0 +1,192 @@
1
+ import { BaseCheckpointSaver, } from "./base.js";
2
+ function _generateKey(threadId, checkpointNamespace, checkpointId) {
3
+ return JSON.stringify([threadId, checkpointNamespace, checkpointId]);
4
+ }
5
+ export class MemorySaver extends BaseCheckpointSaver {
6
+ constructor(serde) {
7
+ super(serde);
8
+ // thread ID -> checkpoint namespace -> checkpoint ID -> checkpoint mapping
9
+ Object.defineProperty(this, "storage", {
10
+ enumerable: true,
11
+ configurable: true,
12
+ writable: true,
13
+ value: {}
14
+ });
15
+ Object.defineProperty(this, "writes", {
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true,
19
+ value: {}
20
+ });
21
+ }
22
+ async getTuple(config) {
23
+ const thread_id = config.configurable?.thread_id;
24
+ const checkpoint_ns = config.configurable?.checkpoint_ns ?? "";
25
+ let checkpoint_id = config.configurable?.checkpoint_id;
26
+ if (checkpoint_id) {
27
+ const saved = this.storage[thread_id]?.[checkpoint_ns]?.[checkpoint_id];
28
+ if (saved !== undefined) {
29
+ const [checkpoint, metadata, parentCheckpointId] = saved;
30
+ const writes = this.writes[_generateKey(thread_id, checkpoint_ns, checkpoint_id)] ??
31
+ [];
32
+ const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
33
+ return [taskId, channel, await this.serde.parse(value)];
34
+ }));
35
+ const parentConfig = parentCheckpointId !== undefined
36
+ ? {
37
+ configurable: {
38
+ thread_id,
39
+ checkpoint_ns,
40
+ checkpoint_id,
41
+ },
42
+ }
43
+ : undefined;
44
+ return {
45
+ config,
46
+ checkpoint: (await this.serde.parse(checkpoint)),
47
+ metadata: (await this.serde.parse(metadata)),
48
+ pendingWrites,
49
+ parentConfig,
50
+ };
51
+ }
52
+ }
53
+ else {
54
+ const checkpoints = this.storage[thread_id]?.[checkpoint_ns];
55
+ if (checkpoints !== undefined) {
56
+ // eslint-disable-next-line prefer-destructuring
57
+ checkpoint_id = Object.keys(checkpoints).sort((a, b) => b.localeCompare(a))[0];
58
+ const saved = checkpoints[checkpoint_id];
59
+ const [checkpoint, metadata, parentCheckpointId] = saved;
60
+ const writes = this.writes[_generateKey(thread_id, checkpoint_ns, checkpoint_id)] ??
61
+ [];
62
+ const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
63
+ return [taskId, channel, await this.serde.parse(value)];
64
+ }));
65
+ const parentConfig = parentCheckpointId !== undefined
66
+ ? {
67
+ configurable: {
68
+ thread_id,
69
+ checkpoint_ns,
70
+ checkpoint_id: parentCheckpointId,
71
+ },
72
+ }
73
+ : undefined;
74
+ return {
75
+ config: {
76
+ configurable: {
77
+ thread_id,
78
+ checkpoint_id,
79
+ checkpoint_ns,
80
+ },
81
+ },
82
+ checkpoint: (await this.serde.parse(checkpoint)),
83
+ metadata: (await this.serde.parse(metadata)),
84
+ pendingWrites,
85
+ parentConfig,
86
+ };
87
+ }
88
+ }
89
+ return undefined;
90
+ }
91
+ async *list(config, options) {
92
+ // eslint-disable-next-line prefer-const
93
+ let { before, limit } = options ?? {};
94
+ const threadIds = config.configurable?.thread_id
95
+ ? [config.configurable?.thread_id]
96
+ : Object.keys(this.storage);
97
+ const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
98
+ for (const threadId of threadIds) {
99
+ const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};
100
+ const sortedCheckpoints = Object.entries(checkpoints).sort((a, b) => b[0].localeCompare(a[0]));
101
+ for (const [checkpointId, [checkpoint, metadataStr, parentCheckpointId],] of sortedCheckpoints) {
102
+ // Filter by checkpoint ID
103
+ if (before &&
104
+ before.configurable?.checkpoint_id &&
105
+ checkpointId >= before.configurable.checkpoint_id) {
106
+ continue;
107
+ }
108
+ // Parse metadata
109
+ const metadata = (await this.serde.parse(metadataStr));
110
+ // Limit search results
111
+ if (limit !== undefined) {
112
+ if (limit <= 0)
113
+ break;
114
+ // eslint-disable-next-line no-param-reassign
115
+ limit -= 1;
116
+ }
117
+ const writes = this.writes[_generateKey(threadId, checkpointNamespace, checkpointId)] ?? [];
118
+ const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
119
+ return [taskId, channel, await this.serde.parse(value)];
120
+ }));
121
+ yield {
122
+ config: {
123
+ configurable: {
124
+ thread_id: threadId,
125
+ checkpoint_ns: checkpointNamespace,
126
+ checkpoint_id: checkpointId,
127
+ },
128
+ },
129
+ checkpoint: (await this.serde.parse(checkpoint)),
130
+ metadata,
131
+ pendingWrites,
132
+ parentConfig: parentCheckpointId
133
+ ? {
134
+ configurable: {
135
+ thread_id: threadId,
136
+ checkpoint_ns: checkpointNamespace,
137
+ checkpoint_id: parentCheckpointId,
138
+ },
139
+ }
140
+ : undefined,
141
+ };
142
+ }
143
+ }
144
+ }
145
+ async put(config, checkpoint, metadata) {
146
+ const threadId = config.configurable?.thread_id;
147
+ const checkpointNamespace = config.configurable?.checkpoint_ns;
148
+ if (threadId === undefined) {
149
+ throw new Error(`Failed to put checkpoint. The passed RunnableConfig is missing a required "thread_id" field in its "configurable" property.`);
150
+ }
151
+ if (checkpointNamespace === undefined) {
152
+ throw new Error(`Failed to put checkpoint. The passed RunnableConfig is missing a required "checkpoint_ns" field in its "configurable" property.`);
153
+ }
154
+ if (!this.storage[threadId]) {
155
+ this.storage[threadId] = {};
156
+ }
157
+ if (!this.storage[threadId][checkpointNamespace]) {
158
+ this.storage[threadId][checkpointNamespace] = {};
159
+ }
160
+ this.storage[threadId][checkpointNamespace][checkpoint.id] = [
161
+ this.serde.stringify(checkpoint),
162
+ this.serde.stringify(metadata),
163
+ config.configurable?.checkpoint_id, // parent
164
+ ];
165
+ return {
166
+ configurable: {
167
+ thread_id: threadId,
168
+ checkpoint_ns: checkpointNamespace,
169
+ checkpoint_id: checkpoint.id,
170
+ },
171
+ };
172
+ }
173
+ async putWrites(config, writes, taskId) {
174
+ const threadId = config.configurable?.thread_id;
175
+ const checkpointNamespace = config.configurable?.checkpoint_ns;
176
+ const checkpointId = config.configurable?.checkpoint_id;
177
+ if (threadId === undefined) {
178
+ throw new Error(`Failed to put writes. The passed RunnableConfig is missing a required "thread_id" field in its "configurable" property`);
179
+ }
180
+ if (checkpointId === undefined) {
181
+ throw new Error(`Failed to put writes. The passed RunnableConfig is missing a required "checkpoint_id" field in its "configurable" property.`);
182
+ }
183
+ const key = _generateKey(threadId, checkpointNamespace, checkpointId);
184
+ if (this.writes[key] === undefined) {
185
+ this.writes[key] = [];
186
+ }
187
+ const pendingWrites = writes.map(([channel, value]) => {
188
+ return [taskId, channel, this.serde.stringify(value)];
189
+ });
190
+ this.writes[key].push(...pendingWrites);
191
+ }
192
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultSerializer = void 0;
4
+ const load_1 = require("@langchain/core/load");
5
+ exports.DefaultSerializer = {
6
+ stringify: JSON.stringify,
7
+ parse: load_1.load,
8
+ };
@@ -0,0 +1,12 @@
1
+ import { load } from "@langchain/core/load";
2
+ export interface SerializerProtocol<D> {
3
+ stringify(obj: D): string;
4
+ parse(data: string): Promise<D>;
5
+ }
6
+ export declare const DefaultSerializer: {
7
+ stringify: {
8
+ (value: any, replacer?: ((this: any, key: string, value: any) => any) | undefined, space?: string | number | undefined): string;
9
+ (value: any, replacer?: (string | number)[] | null | undefined, space?: string | number | undefined): string;
10
+ };
11
+ parse: typeof load;
12
+ };
@@ -0,0 +1,5 @@
1
+ import { load } from "@langchain/core/load";
2
+ export const DefaultSerializer = {
3
+ stringify: JSON.stringify,
4
+ parse: load,
5
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,44 @@
1
+ export interface ChannelProtocol<ValueType = unknown, UpdateType = unknown, CheckpointType = unknown> {
2
+ ValueType: ValueType;
3
+ UpdateType: UpdateType;
4
+ /**
5
+ * The name of the channel.
6
+ */
7
+ lc_graph_name: string;
8
+ /**
9
+ * Return a new identical channel, optionally initialized from a checkpoint.
10
+ * Can be thought of as a "restoration" from a checkpoint which is a "snapshot" of the channel's state.
11
+ *
12
+ * @param {CheckpointType | undefined} checkpoint
13
+ * @param {CheckpointType | undefined} initialValue
14
+ * @returns {this}
15
+ */
16
+ fromCheckpoint(checkpoint?: CheckpointType): this;
17
+ /**
18
+ * Update the channel's value with the given sequence of updates.
19
+ * The order of the updates in the sequence is arbitrary.
20
+ *
21
+ * @throws {InvalidUpdateError} if the sequence of updates is invalid.
22
+ * @param {Array<UpdateType>} values
23
+ * @returns {void}
24
+ */
25
+ update(values: UpdateType[]): void;
26
+ /**
27
+ * Return the current value of the channel.
28
+ *
29
+ * @throws {EmptyChannelError} if the channel is empty (never updated yet).
30
+ * @returns {ValueType}
31
+ */
32
+ get(): ValueType;
33
+ /**
34
+ * Return a string representation of the channel's current state.
35
+ *
36
+ * @throws {EmptyChannelError} if the channel is empty (never updated yet), or doesn't support checkpoints.
37
+ * @returns {CheckpointType | undefined}
38
+ */
39
+ checkpoint(): CheckpointType | undefined;
40
+ }
41
+ export interface SendProtocol {
42
+ node: string;
43
+ args: any;
44
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/types.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,28 @@
1
+ export type All = "*";
2
+ export type PendingWriteValue = unknown;
3
+ export type PendingWrite<Channel = string> = [Channel, PendingWriteValue];
4
+ export type CheckpointPendingWrite<TaskId = string> = [
5
+ TaskId,
6
+ ...PendingWrite<string>
7
+ ];
8
+ export interface CheckpointMetadata {
9
+ /**
10
+ * The source of the checkpoint.
11
+ * - "input": The checkpoint was created from an input to invoke/stream/batch.
12
+ * - "loop": The checkpoint was created from inside the pregel loop.
13
+ * - "update": The checkpoint was created from a manual state update.
14
+ */
15
+ source: "input" | "loop" | "update";
16
+ /**
17
+ * The step number of the checkpoint.
18
+ * -1 for the first "input" checkpoint.
19
+ * 0 for the first "loop" checkpoint.
20
+ * ... for the nth checkpoint afterwards.
21
+ */
22
+ step: number;
23
+ /**
24
+ * The writes that were made between the previous checkpoint and this one.
25
+ * Mapping from node name to writes emitted by that node.
26
+ */
27
+ writes: Record<string, unknown> | null;
28
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/index.cjs ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./dist/index.cjs');
package/index.d.cts ADDED
@@ -0,0 +1 @@
1
+ export * from './dist/index.js'
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './dist/index.js'
package/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './dist/index.js'
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@langchain/langgraph-checkpoint",
3
+ "version": "0.0.0",
4
+ "description": "Library with base interfaces for LangGraph checkpoint savers.",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=18"
8
+ },
9
+ "main": "./index.js",
10
+ "types": "./index.d.ts",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git@github.com:langchain-ai/langgraphjs.git"
14
+ },
15
+ "scripts": {
16
+ "build": "yarn turbo:command build:internal --filter=@langchain/langgraph-checkpoint",
17
+ "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking",
18
+ "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/",
19
+ "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts",
20
+ "lint": "yarn lint:eslint && yarn lint:dpdm",
21
+ "lint:fix": "yarn lint:eslint --fix && yarn lint:dpdm",
22
+ "prepack": "yarn build",
23
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts --testTimeout 30000 --maxWorkers=50%",
24
+ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch --testPathIgnorePatterns=\\.int\\.test.ts",
25
+ "test:single": "NODE_OPTIONS=--experimental-vm-modules yarn run jest --config jest.config.cjs --testTimeout 100000",
26
+ "test:int": "NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern=\\.int\\.test.ts --testTimeout 100000 --maxWorkers=50%",
27
+ "format": "prettier --config .prettierrc --write \"src\"",
28
+ "format:check": "prettier --config .prettierrc --check \"src\""
29
+ },
30
+ "author": "LangChain",
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "uuid": "^10.0.0"
34
+ },
35
+ "peerDependencies": {
36
+ "@langchain/core": ">=0.2.20 <0.3.0"
37
+ },
38
+ "devDependencies": {
39
+ "@jest/globals": "^29.5.0",
40
+ "@langchain/scripts": "^0.0.22",
41
+ "@swc/core": "^1.3.90",
42
+ "@swc/jest": "^0.2.29",
43
+ "@tsconfig/recommended": "^1.0.3",
44
+ "@types/uuid": "^10",
45
+ "@typescript-eslint/eslint-plugin": "^6.12.0",
46
+ "@typescript-eslint/parser": "^6.12.0",
47
+ "dotenv": "^16.3.1",
48
+ "dpdm": "^3.12.0",
49
+ "eslint": "^8.33.0",
50
+ "eslint-config-airbnb-base": "^15.0.0",
51
+ "eslint-config-prettier": "^8.6.0",
52
+ "eslint-plugin-import": "^2.29.1",
53
+ "eslint-plugin-jest": "^28.8.0",
54
+ "eslint-plugin-no-instanceof": "^1.0.1",
55
+ "eslint-plugin-prettier": "^4.2.1",
56
+ "jest": "^29.5.0",
57
+ "jest-environment-node": "^29.6.4",
58
+ "prettier": "^2.8.3",
59
+ "release-it": "^17.6.0",
60
+ "rollup": "^4.5.2",
61
+ "ts-jest": "^29.1.0",
62
+ "tsx": "^4.7.0",
63
+ "typescript": "^4.9.5 || ^5.4.5"
64
+ },
65
+ "publishConfig": {
66
+ "access": "public",
67
+ "registry": "https://registry.npmjs.org/"
68
+ },
69
+ "exports": {
70
+ ".": {
71
+ "types": {
72
+ "import": "./index.d.ts",
73
+ "require": "./index.d.cts",
74
+ "default": "./index.d.ts"
75
+ },
76
+ "import": "./index.js",
77
+ "require": "./index.cjs"
78
+ },
79
+ "./package.json": "./package.json"
80
+ },
81
+ "files": [
82
+ "dist/",
83
+ "index.cjs",
84
+ "index.js",
85
+ "index.d.ts",
86
+ "index.d.cts"
87
+ ]
88
+ }