@langgraph-js/pure-graph 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/fetch/index.js +2 -2
- package/dist/adapter/nextjs/index.js +1 -1
- package/dist/{createEndpoint-BViLxrhh.js → createEndpoint-YeQDXg5B.js} +2 -2
- package/dist/{createEndpoint-BViLxrhh.js.map → createEndpoint-YeQDXg5B.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/{queue-CtVch_az.js → queue-8-m4OzOj.js} +2 -2
- package/dist/{queue-CtVch_az.js.map → queue-8-m4OzOj.js.map} +1 -1
- package/dist/remote/index.js +1 -1
- package/dist/{sqlite-adapter-5PeLHaxe.js → sqlite-adapter-Bbonr5S5.js} +8 -4
- package/dist/sqlite-adapter-Bbonr5S5.js.map +1 -0
- package/dist/storage/index.d.ts +4 -2
- package/dist/storage/memory/checkpoint.d.ts +1 -0
- package/dist/storage/memory/shallow-memory.d.ts +54 -0
- package/dist/{stream-umoA6h4q.js → stream-3xunCZso.js} +300 -5
- package/dist/stream-3xunCZso.js.map +1 -0
- package/package.json +1 -1
- package/dist/sqlite-adapter-5PeLHaxe.js.map +0 -1
- package/dist/stream-umoA6h4q.js.map +0 -1
|
@@ -4,7 +4,7 @@ import { concat } from '@langchain/core/utils/stream';
|
|
|
4
4
|
import { EventEmitter } from 'eventemitter3';
|
|
5
5
|
import { load } from '@langchain/core/load';
|
|
6
6
|
import { v7 } from 'uuid';
|
|
7
|
-
import {
|
|
7
|
+
import { BaseCheckpointSaver, TASKS, maxChannelVersion, getCheckpointId, copyCheckpoint, uuid6, WRITES_IDX_MAP } from '@langchain/langgraph-checkpoint';
|
|
8
8
|
|
|
9
9
|
const getLangGraphCommand = (command) => {
|
|
10
10
|
let goto = command.goto != null && !Array.isArray(command.goto) ? [command.goto] : command.goto;
|
|
@@ -820,6 +820,301 @@ class KyselyThreadsManager {
|
|
|
820
820
|
}
|
|
821
821
|
}
|
|
822
822
|
|
|
823
|
+
function _parseShallowKey(key) {
|
|
824
|
+
const [threadId, checkpointNs] = key.split("::");
|
|
825
|
+
return { threadId, checkpointNs: checkpointNs ?? "" };
|
|
826
|
+
}
|
|
827
|
+
function _getWritesKey(threadId, checkpointNs, checkpointId) {
|
|
828
|
+
return `${threadId}::${checkpointNs}::${checkpointId}`;
|
|
829
|
+
}
|
|
830
|
+
function deterministicStringify(obj) {
|
|
831
|
+
if (obj === null || typeof obj !== "object") {
|
|
832
|
+
return JSON.stringify(obj);
|
|
833
|
+
}
|
|
834
|
+
if (Array.isArray(obj)) {
|
|
835
|
+
return JSON.stringify(obj.map((item) => deterministicStringify(item)));
|
|
836
|
+
}
|
|
837
|
+
const sortedObj = {};
|
|
838
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
839
|
+
for (const key of sortedKeys) {
|
|
840
|
+
sortedObj[key] = obj[key];
|
|
841
|
+
}
|
|
842
|
+
return JSON.stringify(sortedObj, (_, value) => {
|
|
843
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
844
|
+
const sorted = {};
|
|
845
|
+
const keys = Object.keys(value).sort();
|
|
846
|
+
for (const k of keys) {
|
|
847
|
+
sorted[k] = value[k];
|
|
848
|
+
}
|
|
849
|
+
return sorted;
|
|
850
|
+
}
|
|
851
|
+
return value;
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
class ShallowMemorySaver extends BaseCheckpointSaver {
|
|
855
|
+
// thread ID -> checkpoint namespace -> checkpoint data
|
|
856
|
+
// Each namespace only keeps ONE checkpoint
|
|
857
|
+
storage = {};
|
|
858
|
+
// writes storage: composite key -> writes data
|
|
859
|
+
// Key format: thread_id::checkpoint_ns::checkpoint_id
|
|
860
|
+
writes = {};
|
|
861
|
+
constructor(serde) {
|
|
862
|
+
super(serde);
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Get just the checkpoint without metadata
|
|
866
|
+
* Convenience method matching ShallowRedisSaver API
|
|
867
|
+
*/
|
|
868
|
+
async get(config) {
|
|
869
|
+
const tuple = await this.getTuple(config);
|
|
870
|
+
return tuple?.checkpoint;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Migrate pending sends for checkpoints with version < 4
|
|
874
|
+
* @internal
|
|
875
|
+
*/
|
|
876
|
+
async _migratePendingSends(mutableCheckpoint, threadId, checkpointNs, parentCheckpointId) {
|
|
877
|
+
const deseriablizableCheckpoint = mutableCheckpoint;
|
|
878
|
+
const parentKey = _getWritesKey(threadId, checkpointNs, parentCheckpointId);
|
|
879
|
+
const pendingSends = await Promise.all(
|
|
880
|
+
Object.values(this.writes[parentKey] ?? {}).filter(([_taskId, channel]) => channel === TASKS).map(async ([_taskId, _channel, writes]) => await this.serde.loadsTyped("json", writes))
|
|
881
|
+
);
|
|
882
|
+
deseriablizableCheckpoint.channel_values ??= {};
|
|
883
|
+
deseriablizableCheckpoint.channel_values[TASKS] = pendingSends;
|
|
884
|
+
deseriablizableCheckpoint.channel_versions ??= {};
|
|
885
|
+
deseriablizableCheckpoint.channel_versions[TASKS] = Object.keys(deseriablizableCheckpoint.channel_versions).length > 0 ? maxChannelVersion(...Object.values(deseriablizableCheckpoint.channel_versions)) : this.getNextVersion(void 0);
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Load pending writes for a specific checkpoint
|
|
889
|
+
*/
|
|
890
|
+
async _loadPendingWrites(threadId, checkpointNs, checkpointId) {
|
|
891
|
+
const key = _getWritesKey(threadId, checkpointNs, checkpointId);
|
|
892
|
+
const writes = this.writes[key] ?? {};
|
|
893
|
+
if (Object.keys(writes).length === 0) {
|
|
894
|
+
return [];
|
|
895
|
+
}
|
|
896
|
+
return await Promise.all(
|
|
897
|
+
Object.values(writes).map(async ([taskId, channel, value]) => {
|
|
898
|
+
return [taskId, channel, await this.serde.loadsTyped("json", value)];
|
|
899
|
+
})
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Clean up old writes for a checkpoint
|
|
904
|
+
*/
|
|
905
|
+
_cleanupOldWrites(threadId, checkpointNs, checkpointId) {
|
|
906
|
+
const key = _getWritesKey(threadId, checkpointNs, checkpointId);
|
|
907
|
+
delete this.writes[key];
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Check metadata filter matches (with deep comparison support)
|
|
911
|
+
* Matches ShallowRedisSaver behavior
|
|
912
|
+
*/
|
|
913
|
+
_checkMetadataFilterMatch(metadata, filter) {
|
|
914
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
915
|
+
const metadataValue = metadata?.[key];
|
|
916
|
+
if (value === null) {
|
|
917
|
+
if (!(key in (metadata || {})) || metadataValue !== null) {
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
} else if (typeof value === "object" && !Array.isArray(value)) {
|
|
921
|
+
if (typeof metadataValue !== "object" || metadataValue === null) {
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
if (deterministicStringify(value) !== deterministicStringify(metadataValue)) {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
} else if (metadataValue !== value) {
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return true;
|
|
932
|
+
}
|
|
933
|
+
async getTuple(config) {
|
|
934
|
+
const threadId = config.configurable?.thread_id;
|
|
935
|
+
const checkpointNs = config.configurable?.checkpoint_ns ?? "";
|
|
936
|
+
const requestedCheckpointId = getCheckpointId(config);
|
|
937
|
+
if (threadId === void 0) {
|
|
938
|
+
return void 0;
|
|
939
|
+
}
|
|
940
|
+
const namespaceData = this.storage[threadId]?.[checkpointNs];
|
|
941
|
+
if (namespaceData === void 0) {
|
|
942
|
+
return void 0;
|
|
943
|
+
}
|
|
944
|
+
const { checkpoint, metadata, checkpoint_id, parent_checkpoint_id } = namespaceData;
|
|
945
|
+
if (requestedCheckpointId && checkpoint_id !== requestedCheckpointId) {
|
|
946
|
+
return void 0;
|
|
947
|
+
}
|
|
948
|
+
const deserializedCheckpoint = await this.serde.loadsTyped("json", checkpoint);
|
|
949
|
+
if (deserializedCheckpoint.v < 4 && parent_checkpoint_id !== void 0) {
|
|
950
|
+
await this._migratePendingSends(deserializedCheckpoint, threadId, checkpointNs, parent_checkpoint_id);
|
|
951
|
+
}
|
|
952
|
+
const pendingWrites = await this._loadPendingWrites(threadId, checkpointNs, checkpoint_id);
|
|
953
|
+
const deserializedMetadata = await this.serde.loadsTyped("json", metadata);
|
|
954
|
+
const checkpointTuple = {
|
|
955
|
+
config: {
|
|
956
|
+
configurable: {
|
|
957
|
+
thread_id: threadId,
|
|
958
|
+
checkpoint_ns: checkpointNs,
|
|
959
|
+
checkpoint_id
|
|
960
|
+
}
|
|
961
|
+
},
|
|
962
|
+
checkpoint: deserializedCheckpoint,
|
|
963
|
+
metadata: deserializedMetadata,
|
|
964
|
+
pendingWrites
|
|
965
|
+
};
|
|
966
|
+
if (parent_checkpoint_id !== void 0) {
|
|
967
|
+
checkpointTuple.parentConfig = {
|
|
968
|
+
configurable: {
|
|
969
|
+
thread_id: threadId,
|
|
970
|
+
checkpoint_ns: checkpointNs,
|
|
971
|
+
checkpoint_id: parent_checkpoint_id
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
return checkpointTuple;
|
|
976
|
+
}
|
|
977
|
+
async *list(config, options) {
|
|
978
|
+
let { before, limit, filter } = options ?? {};
|
|
979
|
+
const threadIds = config.configurable?.thread_id ? [config.configurable.thread_id] : Object.keys(this.storage);
|
|
980
|
+
const configCheckpointNamespace = config.configurable?.checkpoint_ns;
|
|
981
|
+
const configCheckpointId = config.configurable?.checkpoint_id;
|
|
982
|
+
const matchingCheckpoints = [];
|
|
983
|
+
for (const threadId of threadIds) {
|
|
984
|
+
const namespaces = this.storage[threadId];
|
|
985
|
+
if (namespaces === void 0) continue;
|
|
986
|
+
for (const checkpointNs of Object.keys(namespaces)) {
|
|
987
|
+
if (configCheckpointNamespace !== void 0 && checkpointNs !== configCheckpointNamespace) {
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
const namespaceData = namespaces[checkpointNs];
|
|
991
|
+
if (namespaceData === void 0) continue;
|
|
992
|
+
const { checkpoint_id } = namespaceData;
|
|
993
|
+
if (configCheckpointId && checkpoint_id !== configCheckpointId) {
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
if (before?.configurable?.checkpoint_id && checkpoint_id >= before.configurable.checkpoint_id) {
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
const deserializedMetadata = await this.serde.loadsTyped(
|
|
1000
|
+
"json",
|
|
1001
|
+
namespaceData.metadata
|
|
1002
|
+
);
|
|
1003
|
+
if (filter && !this._checkMetadataFilterMatch(deserializedMetadata, filter)) {
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
matchingCheckpoints.push({ threadId, checkpointNs, namespaceData });
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
matchingCheckpoints.sort((a, b) => b.namespaceData.checkpoint_ts - a.namespaceData.checkpoint_ts);
|
|
1010
|
+
let count = 0;
|
|
1011
|
+
for (const { threadId, checkpointNs, namespaceData } of matchingCheckpoints) {
|
|
1012
|
+
if (limit !== void 0 && count >= limit) {
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const { checkpoint, metadata, checkpoint_id, parent_checkpoint_id } = namespaceData;
|
|
1016
|
+
const deserializedCheckpoint = await this.serde.loadsTyped("json", checkpoint);
|
|
1017
|
+
if (deserializedCheckpoint.v < 4 && parent_checkpoint_id !== void 0) {
|
|
1018
|
+
await this._migratePendingSends(deserializedCheckpoint, threadId, checkpointNs, parent_checkpoint_id);
|
|
1019
|
+
}
|
|
1020
|
+
const pendingWrites = await this._loadPendingWrites(threadId, checkpointNs, checkpoint_id);
|
|
1021
|
+
const deserializedMetadata = await this.serde.loadsTyped("json", metadata);
|
|
1022
|
+
const checkpointTuple = {
|
|
1023
|
+
config: {
|
|
1024
|
+
configurable: {
|
|
1025
|
+
thread_id: threadId,
|
|
1026
|
+
checkpoint_ns: checkpointNs,
|
|
1027
|
+
checkpoint_id
|
|
1028
|
+
}
|
|
1029
|
+
},
|
|
1030
|
+
checkpoint: deserializedCheckpoint,
|
|
1031
|
+
metadata: deserializedMetadata,
|
|
1032
|
+
pendingWrites
|
|
1033
|
+
};
|
|
1034
|
+
if (parent_checkpoint_id !== void 0) {
|
|
1035
|
+
checkpointTuple.parentConfig = {
|
|
1036
|
+
configurable: {
|
|
1037
|
+
thread_id: threadId,
|
|
1038
|
+
checkpoint_ns: checkpointNs,
|
|
1039
|
+
checkpoint_id: parent_checkpoint_id
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
count++;
|
|
1044
|
+
yield checkpointTuple;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
async put(config, checkpoint, metadata, _newVersions) {
|
|
1048
|
+
const preparedCheckpoint = copyCheckpoint(checkpoint);
|
|
1049
|
+
const threadId = config.configurable?.thread_id;
|
|
1050
|
+
const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
|
|
1051
|
+
const parentCheckpointId = config.configurable?.checkpoint_id;
|
|
1052
|
+
if (threadId === void 0) {
|
|
1053
|
+
throw new Error("thread_id is required");
|
|
1054
|
+
}
|
|
1055
|
+
const checkpointId = checkpoint.id || uuid6(0);
|
|
1056
|
+
if (!this.storage[threadId]) {
|
|
1057
|
+
this.storage[threadId] = {};
|
|
1058
|
+
}
|
|
1059
|
+
const oldNamespaceData = this.storage[threadId][checkpointNamespace];
|
|
1060
|
+
if (oldNamespaceData !== void 0 && oldNamespaceData.checkpoint_id !== checkpointId) {
|
|
1061
|
+
this._cleanupOldWrites(threadId, checkpointNamespace, oldNamespaceData.checkpoint_id);
|
|
1062
|
+
}
|
|
1063
|
+
const [[, serializedCheckpoint], [, serializedMetadata]] = await Promise.all([
|
|
1064
|
+
this.serde.dumpsTyped(preparedCheckpoint),
|
|
1065
|
+
this.serde.dumpsTyped(metadata)
|
|
1066
|
+
]);
|
|
1067
|
+
this.storage[threadId][checkpointNamespace] = {
|
|
1068
|
+
checkpoint: serializedCheckpoint,
|
|
1069
|
+
metadata: serializedMetadata,
|
|
1070
|
+
checkpoint_id: checkpointId,
|
|
1071
|
+
parent_checkpoint_id: parentCheckpointId,
|
|
1072
|
+
checkpoint_ts: Date.now()
|
|
1073
|
+
// Add timestamp for sorting
|
|
1074
|
+
};
|
|
1075
|
+
return {
|
|
1076
|
+
configurable: {
|
|
1077
|
+
thread_id: threadId,
|
|
1078
|
+
checkpoint_ns: checkpointNamespace,
|
|
1079
|
+
checkpoint_id: checkpointId
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
async putWrites(config, writes, taskId) {
|
|
1084
|
+
const threadId = config.configurable?.thread_id;
|
|
1085
|
+
const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
|
|
1086
|
+
const checkpointId = config.configurable?.checkpoint_id;
|
|
1087
|
+
if (!threadId || !checkpointId) {
|
|
1088
|
+
throw new Error("thread_id and checkpoint_id are required");
|
|
1089
|
+
}
|
|
1090
|
+
const outerKey = _getWritesKey(threadId, checkpointNamespace, checkpointId);
|
|
1091
|
+
const outerWrites_ = this.writes[outerKey];
|
|
1092
|
+
if (this.writes[outerKey] === void 0) {
|
|
1093
|
+
this.writes[outerKey] = {};
|
|
1094
|
+
}
|
|
1095
|
+
await Promise.all(
|
|
1096
|
+
writes.map(async ([channel, value], idx) => {
|
|
1097
|
+
const [, serializedValue] = await this.serde.dumpsTyped(value);
|
|
1098
|
+
const innerKey = [taskId, WRITES_IDX_MAP[channel] || idx];
|
|
1099
|
+
const innerKeyStr = `${innerKey[0]},${innerKey[1]}`;
|
|
1100
|
+
if (innerKey[1] >= 0 && outerWrites_ && innerKeyStr in outerWrites_) {
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
this.writes[outerKey][innerKeyStr] = [taskId, channel, serializedValue];
|
|
1104
|
+
})
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
async deleteThread(threadId) {
|
|
1108
|
+
delete this.storage[threadId];
|
|
1109
|
+
for (const key of Object.keys(this.writes)) {
|
|
1110
|
+
const { threadId: keyThreadId } = _parseShallowKey(key.split("::").slice(0, 2).join("::"));
|
|
1111
|
+
if (keyThreadId === threadId) {
|
|
1112
|
+
delete this.writes[key];
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
823
1118
|
class EventMessage {
|
|
824
1119
|
event;
|
|
825
1120
|
data;
|
|
@@ -1340,13 +1635,13 @@ const createCheckPointer = async () => {
|
|
|
1340
1635
|
"\x1B[33m%s\x1B[0m",
|
|
1341
1636
|
"LG | set DATABASE_URL=postgresql://user:pass@localhost:5432/db to your .env file to use \x1B[1mPostgreSQL\x1B[0m for prod!"
|
|
1342
1637
|
);
|
|
1343
|
-
return new
|
|
1638
|
+
return new ShallowMemorySaver();
|
|
1344
1639
|
};
|
|
1345
1640
|
const createMessageQueue = async () => {
|
|
1346
1641
|
let q;
|
|
1347
1642
|
if (process.env.REDIS_URL) {
|
|
1348
1643
|
console.debug("LG | Using redis as stream queue");
|
|
1349
|
-
const { RedisStreamQueue } = await import('./queue-
|
|
1644
|
+
const { RedisStreamQueue } = await import('./queue-8-m4OzOj.js');
|
|
1350
1645
|
q = RedisStreamQueue;
|
|
1351
1646
|
} else {
|
|
1352
1647
|
q = MemoryStreamQueue;
|
|
@@ -1386,7 +1681,7 @@ const createThreadManager = async (config) => {
|
|
|
1386
1681
|
}
|
|
1387
1682
|
if (process.env.SQLITE_DATABASE_URI && config.checkpointer) {
|
|
1388
1683
|
console.debug("LG | Using SQLite ThreadsManager");
|
|
1389
|
-
const { SQLiteAdapter } = await import('./sqlite-adapter-
|
|
1684
|
+
const { SQLiteAdapter } = await import('./sqlite-adapter-Bbonr5S5.js');
|
|
1390
1685
|
const database = config.checkpointer.db;
|
|
1391
1686
|
const threadsManager = new KyselyThreadsManager(new SQLiteAdapter(database));
|
|
1392
1687
|
await threadsManager.setup();
|
|
@@ -1584,4 +1879,4 @@ async function* streamState(threads, run, payload, options) {
|
|
|
1584
1879
|
}
|
|
1585
1880
|
|
|
1586
1881
|
export { BaseStreamQueue as B, CancelEventMessage as C, GRAPHS as G, KyselyThreadsManager as K, LangGraphGlobal as L, streamState as a, getGraph as g, registerGraph as r, serialiseAsDict as s };
|
|
1587
|
-
//# sourceMappingURL=stream-
|
|
1882
|
+
//# sourceMappingURL=stream-3xunCZso.js.map
|