@langchain/langgraph-checkpoint 0.0.12 → 0.0.14
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 +7 -1
- package/dist/base.d.ts +1 -0
- package/dist/base.js +6 -1
- package/dist/memory.cjs +29 -17
- package/dist/memory.d.ts +2 -2
- package/dist/memory.js +30 -18
- package/dist/serde/types.cjs +3 -1
- package/dist/serde/types.d.ts +2 -0
- package/dist/serde/types.js +2 -0
- package/dist/store/base.cjs +144 -58
- package/dist/store/base.d.ts +262 -54
- package/dist/store/base.js +141 -57
- package/dist/store/batch.cjs +11 -0
- package/dist/store/batch.d.ts +10 -0
- package/dist/store/batch.js +11 -0
- package/dist/store/memory.cjs +273 -28
- package/dist/store/memory.d.ts +48 -3
- package/dist/store/memory.js +273 -28
- package/dist/store/utils.cjs +261 -0
- package/dist/store/utils.d.ts +26 -0
- package/dist/store/utils.js +254 -0
- package/dist/types.d.ts +2 -1
- package/package.json +2 -2
package/dist/base.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WRITES_IDX_MAP = exports.maxChannelVersion = exports.compareChannelVersions = exports.BaseCheckpointSaver = exports.copyCheckpoint = exports.emptyCheckpoint = exports.deepCopy = void 0;
|
|
3
|
+
exports.getCheckpointId = exports.WRITES_IDX_MAP = exports.maxChannelVersion = exports.compareChannelVersions = exports.BaseCheckpointSaver = exports.copyCheckpoint = exports.emptyCheckpoint = exports.deepCopy = void 0;
|
|
4
4
|
const id_js_1 = require("./id.cjs");
|
|
5
5
|
const types_js_1 = require("./serde/types.cjs");
|
|
6
6
|
const jsonplus_js_1 = require("./serde/jsonplus.cjs");
|
|
@@ -96,4 +96,10 @@ exports.maxChannelVersion = maxChannelVersion;
|
|
|
96
96
|
exports.WRITES_IDX_MAP = {
|
|
97
97
|
[types_js_1.ERROR]: -1,
|
|
98
98
|
[types_js_1.SCHEDULED]: -2,
|
|
99
|
+
[types_js_1.INTERRUPT]: -3,
|
|
100
|
+
[types_js_1.RESUME]: -4,
|
|
99
101
|
};
|
|
102
|
+
function getCheckpointId(config) {
|
|
103
|
+
return (config.configurable?.checkpoint_id || config.configurable?.thread_ts || "");
|
|
104
|
+
}
|
|
105
|
+
exports.getCheckpointId = getCheckpointId;
|
package/dist/base.d.ts
CHANGED
|
@@ -86,4 +86,5 @@ export declare function maxChannelVersion(...versions: ChannelVersion[]): Channe
|
|
|
86
86
|
* Each Checkpointer implementation should use this mapping in put_writes.
|
|
87
87
|
*/
|
|
88
88
|
export declare const WRITES_IDX_MAP: Record<string, number>;
|
|
89
|
+
export declare function getCheckpointId(config: RunnableConfig): string;
|
|
89
90
|
export {};
|
package/dist/base.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { uuid6 } from "./id.js";
|
|
2
|
-
import { ERROR, SCHEDULED, } from "./serde/types.js";
|
|
2
|
+
import { ERROR, INTERRUPT, RESUME, SCHEDULED, } from "./serde/types.js";
|
|
3
3
|
import { JsonPlusSerializer } from "./serde/jsonplus.js";
|
|
4
4
|
export function deepCopy(obj) {
|
|
5
5
|
if (typeof obj !== "object" || obj === null) {
|
|
@@ -87,4 +87,9 @@ export function maxChannelVersion(...versions) {
|
|
|
87
87
|
export const WRITES_IDX_MAP = {
|
|
88
88
|
[ERROR]: -1,
|
|
89
89
|
[SCHEDULED]: -2,
|
|
90
|
+
[INTERRUPT]: -3,
|
|
91
|
+
[RESUME]: -4,
|
|
90
92
|
};
|
|
93
|
+
export function getCheckpointId(config) {
|
|
94
|
+
return (config.configurable?.checkpoint_id || config.configurable?.thread_ts || "");
|
|
95
|
+
}
|
package/dist/memory.cjs
CHANGED
|
@@ -26,7 +26,8 @@ class MemorySaver extends base_js_1.BaseCheckpointSaver {
|
|
|
26
26
|
async _getPendingSends(threadId, checkpointNs, parentCheckpointId) {
|
|
27
27
|
let pendingSends = [];
|
|
28
28
|
if (parentCheckpointId !== undefined) {
|
|
29
|
-
|
|
29
|
+
const key = _generateKey(threadId, checkpointNs, parentCheckpointId);
|
|
30
|
+
pendingSends = await Promise.all(Object.values(this.writes[key] || {})
|
|
30
31
|
?.filter(([_taskId, channel]) => {
|
|
31
32
|
return channel === types_js_1.TASKS;
|
|
32
33
|
})
|
|
@@ -39,19 +40,18 @@ class MemorySaver extends base_js_1.BaseCheckpointSaver {
|
|
|
39
40
|
async getTuple(config) {
|
|
40
41
|
const thread_id = config.configurable?.thread_id;
|
|
41
42
|
const checkpoint_ns = config.configurable?.checkpoint_ns ?? "";
|
|
42
|
-
let checkpoint_id = config
|
|
43
|
+
let checkpoint_id = (0, base_js_1.getCheckpointId)(config);
|
|
43
44
|
if (checkpoint_id) {
|
|
44
45
|
const saved = this.storage[thread_id]?.[checkpoint_ns]?.[checkpoint_id];
|
|
45
46
|
if (saved !== undefined) {
|
|
46
47
|
const [checkpoint, metadata, parentCheckpointId] = saved;
|
|
47
|
-
const
|
|
48
|
-
[];
|
|
48
|
+
const key = _generateKey(thread_id, checkpoint_ns, checkpoint_id);
|
|
49
49
|
const pending_sends = await this._getPendingSends(thread_id, checkpoint_ns, parentCheckpointId);
|
|
50
50
|
const deserializedCheckpoint = {
|
|
51
51
|
...(await this.serde.loadsTyped("json", checkpoint)),
|
|
52
52
|
pending_sends,
|
|
53
53
|
};
|
|
54
|
-
const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
|
|
54
|
+
const pendingWrites = await Promise.all(Object.values(this.writes[key] || {}).map(async ([taskId, channel, value]) => {
|
|
55
55
|
return [
|
|
56
56
|
taskId,
|
|
57
57
|
channel,
|
|
@@ -83,14 +83,13 @@ class MemorySaver extends base_js_1.BaseCheckpointSaver {
|
|
|
83
83
|
checkpoint_id = Object.keys(checkpoints).sort((a, b) => b.localeCompare(a))[0];
|
|
84
84
|
const saved = checkpoints[checkpoint_id];
|
|
85
85
|
const [checkpoint, metadata, parentCheckpointId] = saved;
|
|
86
|
-
const
|
|
87
|
-
[];
|
|
86
|
+
const key = _generateKey(thread_id, checkpoint_ns, checkpoint_id);
|
|
88
87
|
const pending_sends = await this._getPendingSends(thread_id, checkpoint_ns, parentCheckpointId);
|
|
89
88
|
const deserializedCheckpoint = {
|
|
90
89
|
...(await this.serde.loadsTyped("json", checkpoint)),
|
|
91
90
|
pending_sends,
|
|
92
91
|
};
|
|
93
|
-
const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
|
|
92
|
+
const pendingWrites = await Promise.all(Object.values(this.writes[key] || {}).map(async ([taskId, channel, value]) => {
|
|
94
93
|
return [
|
|
95
94
|
taskId,
|
|
96
95
|
channel,
|
|
@@ -130,6 +129,7 @@ class MemorySaver extends base_js_1.BaseCheckpointSaver {
|
|
|
130
129
|
? [config.configurable?.thread_id]
|
|
131
130
|
: Object.keys(this.storage);
|
|
132
131
|
const configCheckpointNamespace = config.configurable?.checkpoint_ns;
|
|
132
|
+
const configCheckpointId = config.configurable?.checkpoint_id;
|
|
133
133
|
for (const threadId of threadIds) {
|
|
134
134
|
for (const checkpointNamespace of Object.keys(this.storage[threadId] ?? {})) {
|
|
135
135
|
if (configCheckpointNamespace !== undefined &&
|
|
@@ -139,7 +139,11 @@ class MemorySaver extends base_js_1.BaseCheckpointSaver {
|
|
|
139
139
|
const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};
|
|
140
140
|
const sortedCheckpoints = Object.entries(checkpoints).sort((a, b) => b[0].localeCompare(a[0]));
|
|
141
141
|
for (const [checkpointId, [checkpoint, metadataStr, parentCheckpointId],] of sortedCheckpoints) {
|
|
142
|
-
// Filter by checkpoint ID
|
|
142
|
+
// Filter by checkpoint ID from config
|
|
143
|
+
if (configCheckpointId && checkpointId !== configCheckpointId) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
// Filter by checkpoint ID from before config
|
|
143
147
|
if (before &&
|
|
144
148
|
before.configurable?.checkpoint_id &&
|
|
145
149
|
checkpointId >= before.configurable.checkpoint_id) {
|
|
@@ -155,10 +159,10 @@ class MemorySaver extends base_js_1.BaseCheckpointSaver {
|
|
|
155
159
|
if (limit !== undefined) {
|
|
156
160
|
if (limit <= 0)
|
|
157
161
|
break;
|
|
158
|
-
// eslint-disable-next-line no-param-reassign
|
|
159
162
|
limit -= 1;
|
|
160
163
|
}
|
|
161
|
-
const
|
|
164
|
+
const key = _generateKey(threadId, checkpointNamespace, checkpointId);
|
|
165
|
+
const writes = Object.values(this.writes[key] || {});
|
|
162
166
|
const pending_sends = await this._getPendingSends(threadId, checkpointNamespace, parentCheckpointId);
|
|
163
167
|
const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
|
|
164
168
|
return [
|
|
@@ -236,15 +240,23 @@ class MemorySaver extends base_js_1.BaseCheckpointSaver {
|
|
|
236
240
|
if (checkpointId === undefined) {
|
|
237
241
|
throw new Error(`Failed to put writes. The passed RunnableConfig is missing a required "checkpoint_id" field in its "configurable" property.`);
|
|
238
242
|
}
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
243
|
+
const outerKey = _generateKey(threadId, checkpointNamespace, checkpointId);
|
|
244
|
+
const outerWrites_ = this.writes[outerKey];
|
|
245
|
+
if (this.writes[outerKey] === undefined) {
|
|
246
|
+
this.writes[outerKey] = {};
|
|
242
247
|
}
|
|
243
|
-
|
|
248
|
+
writes.forEach(([channel, value], idx) => {
|
|
244
249
|
const [, serializedValue] = this.serde.dumpsTyped(value);
|
|
245
|
-
|
|
250
|
+
const innerKey = [
|
|
251
|
+
taskId,
|
|
252
|
+
base_js_1.WRITES_IDX_MAP[channel] || idx,
|
|
253
|
+
];
|
|
254
|
+
const innerKeyStr = `${innerKey[0]},${innerKey[1]}`;
|
|
255
|
+
if (innerKey[1] >= 0 && outerWrites_ && innerKeyStr in outerWrites_) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
this.writes[outerKey][innerKeyStr] = [taskId, channel, serializedValue];
|
|
246
259
|
});
|
|
247
|
-
this.writes[key].push(...pendingWrites);
|
|
248
260
|
}
|
|
249
261
|
}
|
|
250
262
|
exports.MemorySaver = MemorySaver;
|
package/dist/memory.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { RunnableConfig } from "@langchain/core/runnables";
|
|
2
2
|
import { BaseCheckpointSaver, Checkpoint, CheckpointListOptions, CheckpointTuple } from "./base.js";
|
|
3
3
|
import { SerializerProtocol } from "./serde/base.js";
|
|
4
|
-
import { CheckpointMetadata,
|
|
4
|
+
import { CheckpointMetadata, PendingWrite } from "./types.js";
|
|
5
5
|
import { SendProtocol } from "./serde/types.js";
|
|
6
6
|
export declare class MemorySaver extends BaseCheckpointSaver {
|
|
7
7
|
storage: Record<string, Record<string, Record<string, [Uint8Array, Uint8Array, string | undefined]>>>;
|
|
8
|
-
writes: Record<string,
|
|
8
|
+
writes: Record<string, Record<string, [string, string, Uint8Array]>>;
|
|
9
9
|
constructor(serde?: SerializerProtocol);
|
|
10
10
|
_getPendingSends(threadId: string, checkpointNs: string, parentCheckpointId?: string): Promise<SendProtocol[]>;
|
|
11
11
|
getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined>;
|
package/dist/memory.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseCheckpointSaver, copyCheckpoint, } from "./base.js";
|
|
1
|
+
import { BaseCheckpointSaver, copyCheckpoint, getCheckpointId, WRITES_IDX_MAP, } from "./base.js";
|
|
2
2
|
import { TASKS } from "./serde/types.js";
|
|
3
3
|
function _generateKey(threadId, checkpointNamespace, checkpointId) {
|
|
4
4
|
return JSON.stringify([threadId, checkpointNamespace, checkpointId]);
|
|
@@ -23,7 +23,8 @@ export class MemorySaver extends BaseCheckpointSaver {
|
|
|
23
23
|
async _getPendingSends(threadId, checkpointNs, parentCheckpointId) {
|
|
24
24
|
let pendingSends = [];
|
|
25
25
|
if (parentCheckpointId !== undefined) {
|
|
26
|
-
|
|
26
|
+
const key = _generateKey(threadId, checkpointNs, parentCheckpointId);
|
|
27
|
+
pendingSends = await Promise.all(Object.values(this.writes[key] || {})
|
|
27
28
|
?.filter(([_taskId, channel]) => {
|
|
28
29
|
return channel === TASKS;
|
|
29
30
|
})
|
|
@@ -36,19 +37,18 @@ export class MemorySaver extends BaseCheckpointSaver {
|
|
|
36
37
|
async getTuple(config) {
|
|
37
38
|
const thread_id = config.configurable?.thread_id;
|
|
38
39
|
const checkpoint_ns = config.configurable?.checkpoint_ns ?? "";
|
|
39
|
-
let checkpoint_id = config
|
|
40
|
+
let checkpoint_id = getCheckpointId(config);
|
|
40
41
|
if (checkpoint_id) {
|
|
41
42
|
const saved = this.storage[thread_id]?.[checkpoint_ns]?.[checkpoint_id];
|
|
42
43
|
if (saved !== undefined) {
|
|
43
44
|
const [checkpoint, metadata, parentCheckpointId] = saved;
|
|
44
|
-
const
|
|
45
|
-
[];
|
|
45
|
+
const key = _generateKey(thread_id, checkpoint_ns, checkpoint_id);
|
|
46
46
|
const pending_sends = await this._getPendingSends(thread_id, checkpoint_ns, parentCheckpointId);
|
|
47
47
|
const deserializedCheckpoint = {
|
|
48
48
|
...(await this.serde.loadsTyped("json", checkpoint)),
|
|
49
49
|
pending_sends,
|
|
50
50
|
};
|
|
51
|
-
const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
|
|
51
|
+
const pendingWrites = await Promise.all(Object.values(this.writes[key] || {}).map(async ([taskId, channel, value]) => {
|
|
52
52
|
return [
|
|
53
53
|
taskId,
|
|
54
54
|
channel,
|
|
@@ -80,14 +80,13 @@ export class MemorySaver extends BaseCheckpointSaver {
|
|
|
80
80
|
checkpoint_id = Object.keys(checkpoints).sort((a, b) => b.localeCompare(a))[0];
|
|
81
81
|
const saved = checkpoints[checkpoint_id];
|
|
82
82
|
const [checkpoint, metadata, parentCheckpointId] = saved;
|
|
83
|
-
const
|
|
84
|
-
[];
|
|
83
|
+
const key = _generateKey(thread_id, checkpoint_ns, checkpoint_id);
|
|
85
84
|
const pending_sends = await this._getPendingSends(thread_id, checkpoint_ns, parentCheckpointId);
|
|
86
85
|
const deserializedCheckpoint = {
|
|
87
86
|
...(await this.serde.loadsTyped("json", checkpoint)),
|
|
88
87
|
pending_sends,
|
|
89
88
|
};
|
|
90
|
-
const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
|
|
89
|
+
const pendingWrites = await Promise.all(Object.values(this.writes[key] || {}).map(async ([taskId, channel, value]) => {
|
|
91
90
|
return [
|
|
92
91
|
taskId,
|
|
93
92
|
channel,
|
|
@@ -127,6 +126,7 @@ export class MemorySaver extends BaseCheckpointSaver {
|
|
|
127
126
|
? [config.configurable?.thread_id]
|
|
128
127
|
: Object.keys(this.storage);
|
|
129
128
|
const configCheckpointNamespace = config.configurable?.checkpoint_ns;
|
|
129
|
+
const configCheckpointId = config.configurable?.checkpoint_id;
|
|
130
130
|
for (const threadId of threadIds) {
|
|
131
131
|
for (const checkpointNamespace of Object.keys(this.storage[threadId] ?? {})) {
|
|
132
132
|
if (configCheckpointNamespace !== undefined &&
|
|
@@ -136,7 +136,11 @@ export class MemorySaver extends BaseCheckpointSaver {
|
|
|
136
136
|
const checkpoints = this.storage[threadId]?.[checkpointNamespace] ?? {};
|
|
137
137
|
const sortedCheckpoints = Object.entries(checkpoints).sort((a, b) => b[0].localeCompare(a[0]));
|
|
138
138
|
for (const [checkpointId, [checkpoint, metadataStr, parentCheckpointId],] of sortedCheckpoints) {
|
|
139
|
-
// Filter by checkpoint ID
|
|
139
|
+
// Filter by checkpoint ID from config
|
|
140
|
+
if (configCheckpointId && checkpointId !== configCheckpointId) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Filter by checkpoint ID from before config
|
|
140
144
|
if (before &&
|
|
141
145
|
before.configurable?.checkpoint_id &&
|
|
142
146
|
checkpointId >= before.configurable.checkpoint_id) {
|
|
@@ -152,10 +156,10 @@ export class MemorySaver extends BaseCheckpointSaver {
|
|
|
152
156
|
if (limit !== undefined) {
|
|
153
157
|
if (limit <= 0)
|
|
154
158
|
break;
|
|
155
|
-
// eslint-disable-next-line no-param-reassign
|
|
156
159
|
limit -= 1;
|
|
157
160
|
}
|
|
158
|
-
const
|
|
161
|
+
const key = _generateKey(threadId, checkpointNamespace, checkpointId);
|
|
162
|
+
const writes = Object.values(this.writes[key] || {});
|
|
159
163
|
const pending_sends = await this._getPendingSends(threadId, checkpointNamespace, parentCheckpointId);
|
|
160
164
|
const pendingWrites = await Promise.all(writes.map(async ([taskId, channel, value]) => {
|
|
161
165
|
return [
|
|
@@ -233,14 +237,22 @@ export class MemorySaver extends BaseCheckpointSaver {
|
|
|
233
237
|
if (checkpointId === undefined) {
|
|
234
238
|
throw new Error(`Failed to put writes. The passed RunnableConfig is missing a required "checkpoint_id" field in its "configurable" property.`);
|
|
235
239
|
}
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
240
|
+
const outerKey = _generateKey(threadId, checkpointNamespace, checkpointId);
|
|
241
|
+
const outerWrites_ = this.writes[outerKey];
|
|
242
|
+
if (this.writes[outerKey] === undefined) {
|
|
243
|
+
this.writes[outerKey] = {};
|
|
239
244
|
}
|
|
240
|
-
|
|
245
|
+
writes.forEach(([channel, value], idx) => {
|
|
241
246
|
const [, serializedValue] = this.serde.dumpsTyped(value);
|
|
242
|
-
|
|
247
|
+
const innerKey = [
|
|
248
|
+
taskId,
|
|
249
|
+
WRITES_IDX_MAP[channel] || idx,
|
|
250
|
+
];
|
|
251
|
+
const innerKeyStr = `${innerKey[0]},${innerKey[1]}`;
|
|
252
|
+
if (innerKey[1] >= 0 && outerWrites_ && innerKeyStr in outerWrites_) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this.writes[outerKey][innerKeyStr] = [taskId, channel, serializedValue];
|
|
243
256
|
});
|
|
244
|
-
this.writes[key].push(...pendingWrites);
|
|
245
257
|
}
|
|
246
258
|
}
|
package/dist/serde/types.cjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SCHEDULED = exports.ERROR = exports.TASKS = void 0;
|
|
3
|
+
exports.RESUME = exports.INTERRUPT = exports.SCHEDULED = exports.ERROR = exports.TASKS = void 0;
|
|
4
4
|
exports.TASKS = "__pregel_tasks";
|
|
5
5
|
exports.ERROR = "__error__";
|
|
6
6
|
exports.SCHEDULED = "__scheduled__";
|
|
7
|
+
exports.INTERRUPT = "__interrupt__";
|
|
8
|
+
exports.RESUME = "__resume__";
|
package/dist/serde/types.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export declare const TASKS = "__pregel_tasks";
|
|
2
2
|
export declare const ERROR = "__error__";
|
|
3
3
|
export declare const SCHEDULED = "__scheduled__";
|
|
4
|
+
export declare const INTERRUPT = "__interrupt__";
|
|
5
|
+
export declare const RESUME = "__resume__";
|
|
4
6
|
export interface ChannelProtocol<ValueType = unknown, UpdateType = unknown, CheckpointType = unknown> {
|
|
5
7
|
ValueType: ValueType;
|
|
6
8
|
UpdateType: UpdateType;
|
package/dist/serde/types.js
CHANGED
package/dist/store/base.cjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.BaseStore = exports.InvalidNamespaceError = void 0;
|
|
3
|
+
exports.BaseStore = exports.tokenizePath = exports.getTextAtPath = exports.InvalidNamespaceError = void 0;
|
|
5
4
|
/**
|
|
6
5
|
* Error thrown when an invalid namespace is provided.
|
|
7
6
|
*/
|
|
@@ -38,69 +37,160 @@ function validateNamespace(namespace) {
|
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
/**
|
|
41
|
-
*
|
|
40
|
+
* Utility function to get text at a specific JSON path
|
|
41
|
+
*/
|
|
42
|
+
function getTextAtPath(obj, path) {
|
|
43
|
+
const parts = path.split(".");
|
|
44
|
+
let current = obj;
|
|
45
|
+
for (const part of parts) {
|
|
46
|
+
if (part.includes("[")) {
|
|
47
|
+
const [arrayName, indexStr] = part.split("[");
|
|
48
|
+
const index = indexStr.replace("]", "");
|
|
49
|
+
if (!current[arrayName])
|
|
50
|
+
return [];
|
|
51
|
+
if (index === "*") {
|
|
52
|
+
const results = [];
|
|
53
|
+
for (const item of current[arrayName]) {
|
|
54
|
+
if (typeof item === "string")
|
|
55
|
+
results.push(item);
|
|
56
|
+
}
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
const idx = parseInt(index, 10);
|
|
60
|
+
if (Number.isNaN(idx))
|
|
61
|
+
return [];
|
|
62
|
+
current = current[arrayName][idx];
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
current = current[part];
|
|
66
|
+
}
|
|
67
|
+
if (current === undefined)
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
return typeof current === "string" ? [current] : [];
|
|
71
|
+
}
|
|
72
|
+
exports.getTextAtPath = getTextAtPath;
|
|
73
|
+
/**
|
|
74
|
+
* Tokenizes a JSON path into parts
|
|
75
|
+
*/
|
|
76
|
+
function tokenizePath(path) {
|
|
77
|
+
return path.split(".");
|
|
78
|
+
}
|
|
79
|
+
exports.tokenizePath = tokenizePath;
|
|
80
|
+
/**
|
|
81
|
+
* Abstract base class for persistent key-value stores.
|
|
82
|
+
*
|
|
83
|
+
* Stores enable persistence and memory that can be shared across threads,
|
|
84
|
+
* scoped to user IDs, assistant IDs, or other arbitrary namespaces.
|
|
85
|
+
*
|
|
86
|
+
* Features:
|
|
87
|
+
* - Hierarchical namespaces for organization
|
|
88
|
+
* - Key-value storage with metadata
|
|
89
|
+
* - Vector similarity search (if configured)
|
|
90
|
+
* - Filtering and pagination
|
|
42
91
|
*/
|
|
43
92
|
class BaseStore {
|
|
44
|
-
// convenience methods
|
|
45
93
|
/**
|
|
46
|
-
* Retrieve a single item.
|
|
47
|
-
*
|
|
48
|
-
* @param
|
|
49
|
-
* @
|
|
94
|
+
* Retrieve a single item by its namespace and key.
|
|
95
|
+
*
|
|
96
|
+
* @param namespace Hierarchical path for the item
|
|
97
|
+
* @param key Unique identifier within the namespace
|
|
98
|
+
* @returns Promise resolving to the item or null if not found
|
|
50
99
|
*/
|
|
51
100
|
async get(namespace, key) {
|
|
52
|
-
|
|
53
|
-
return batchResult[0];
|
|
101
|
+
return (await this.batch([{ namespace, key }]))[0];
|
|
54
102
|
}
|
|
55
103
|
/**
|
|
56
104
|
* Search for items within a namespace prefix.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* @param
|
|
60
|
-
* @param options
|
|
61
|
-
* @
|
|
62
|
-
*
|
|
105
|
+
* Supports both metadata filtering and vector similarity search.
|
|
106
|
+
*
|
|
107
|
+
* @param namespacePrefix Hierarchical path prefix to search within
|
|
108
|
+
* @param options Search options for filtering and pagination
|
|
109
|
+
* @returns Promise resolving to list of matching items with relevance scores
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* // Search with filters
|
|
113
|
+
* await store.search(["documents"], {
|
|
114
|
+
* filter: { type: "report", status: "active" },
|
|
115
|
+
* limit: 5,
|
|
116
|
+
* offset: 10
|
|
117
|
+
* });
|
|
118
|
+
*
|
|
119
|
+
* // Vector similarity search
|
|
120
|
+
* await store.search(["users", "content"], {
|
|
121
|
+
* query: "technical documentation about APIs",
|
|
122
|
+
* limit: 20
|
|
123
|
+
* });
|
|
63
124
|
*/
|
|
64
|
-
async search(namespacePrefix, options) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
125
|
+
async search(namespacePrefix, options = {}) {
|
|
126
|
+
const { filter, limit = 10, offset = 0, query } = options;
|
|
127
|
+
return (await this.batch([
|
|
128
|
+
{
|
|
129
|
+
namespacePrefix,
|
|
130
|
+
filter,
|
|
131
|
+
limit,
|
|
132
|
+
offset,
|
|
133
|
+
query,
|
|
134
|
+
},
|
|
135
|
+
]))[0];
|
|
74
136
|
}
|
|
75
137
|
/**
|
|
76
138
|
* Store or update an item.
|
|
77
|
-
*
|
|
78
|
-
* @param
|
|
79
|
-
* @param
|
|
139
|
+
*
|
|
140
|
+
* @param namespace Hierarchical path for the item
|
|
141
|
+
* @param key Unique identifier within the namespace
|
|
142
|
+
* @param value Object containing the item's data
|
|
143
|
+
* @param index Optional indexing configuration
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* // Simple storage
|
|
147
|
+
* await store.put(["docs"], "report", { title: "Annual Report" });
|
|
148
|
+
*
|
|
149
|
+
* // With specific field indexing
|
|
150
|
+
* await store.put(
|
|
151
|
+
* ["docs"],
|
|
152
|
+
* "report",
|
|
153
|
+
* {
|
|
154
|
+
* title: "Q4 Report",
|
|
155
|
+
* chapters: [{ content: "..." }, { content: "..." }]
|
|
156
|
+
* },
|
|
157
|
+
* ["title", "chapters[*].content"]
|
|
158
|
+
* );
|
|
80
159
|
*/
|
|
81
|
-
async put(namespace, key, value) {
|
|
160
|
+
async put(namespace, key, value, index) {
|
|
82
161
|
validateNamespace(namespace);
|
|
83
|
-
await this.batch([{ namespace, key, value }]);
|
|
162
|
+
await this.batch([{ namespace, key, value, index }]);
|
|
84
163
|
}
|
|
85
164
|
/**
|
|
86
|
-
* Delete an item.
|
|
87
|
-
*
|
|
88
|
-
* @param
|
|
165
|
+
* Delete an item from the store.
|
|
166
|
+
*
|
|
167
|
+
* @param namespace Hierarchical path for the item
|
|
168
|
+
* @param key Unique identifier within the namespace
|
|
89
169
|
*/
|
|
90
170
|
async delete(namespace, key) {
|
|
91
171
|
await this.batch([{ namespace, key, value: null }]);
|
|
92
172
|
}
|
|
93
173
|
/**
|
|
94
174
|
* List and filter namespaces in the store.
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* @param options
|
|
98
|
-
* @
|
|
99
|
-
*
|
|
100
|
-
* @
|
|
101
|
-
*
|
|
175
|
+
* Used to explore data organization and navigate the namespace hierarchy.
|
|
176
|
+
*
|
|
177
|
+
* @param options Options for listing namespaces
|
|
178
|
+
* @returns Promise resolving to list of namespace paths
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* // List all namespaces under "documents"
|
|
182
|
+
* await store.listNamespaces({
|
|
183
|
+
* prefix: ["documents"],
|
|
184
|
+
* maxDepth: 2
|
|
185
|
+
* });
|
|
186
|
+
*
|
|
187
|
+
* // List namespaces ending with "v1"
|
|
188
|
+
* await store.listNamespaces({
|
|
189
|
+
* suffix: ["v1"],
|
|
190
|
+
* limit: 50
|
|
191
|
+
* });
|
|
102
192
|
*/
|
|
103
|
-
async listNamespaces(options) {
|
|
193
|
+
async listNamespaces(options = {}) {
|
|
104
194
|
const { prefix, suffix, maxDepth, limit = 100, offset = 0 } = options;
|
|
105
195
|
const matchConditions = [];
|
|
106
196
|
if (prefix) {
|
|
@@ -109,26 +199,22 @@ class BaseStore {
|
|
|
109
199
|
if (suffix) {
|
|
110
200
|
matchConditions.push({ matchType: "suffix", path: suffix });
|
|
111
201
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
202
|
+
return (await this.batch([
|
|
203
|
+
{
|
|
204
|
+
matchConditions: matchConditions.length ? matchConditions : undefined,
|
|
205
|
+
maxDepth,
|
|
206
|
+
limit,
|
|
207
|
+
offset,
|
|
208
|
+
},
|
|
209
|
+
]))[0];
|
|
120
210
|
}
|
|
121
211
|
/**
|
|
122
|
-
*
|
|
212
|
+
* Start the store. Override if initialization is needed.
|
|
123
213
|
*/
|
|
124
|
-
|
|
125
|
-
// no-op if not implemented.
|
|
126
|
-
}
|
|
214
|
+
start() { }
|
|
127
215
|
/**
|
|
128
|
-
*
|
|
216
|
+
* Stop the store. Override if cleanup is needed.
|
|
129
217
|
*/
|
|
130
|
-
|
|
131
|
-
// no-op if not implemented.
|
|
132
|
-
}
|
|
218
|
+
stop() { }
|
|
133
219
|
}
|
|
134
220
|
exports.BaseStore = BaseStore;
|