@ferricstore/ferricstore 0.12.0 → 0.12.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/README.md +3 -0
- package/dist/langgraph.cjs +395 -125
- package/dist/langgraph.cjs.map +1 -1
- package/dist/langgraph.d.cts +17 -7
- package/dist/langgraph.d.ts +17 -7
- package/dist/langgraph.js +396 -126
- package/dist/langgraph.js.map +1 -1
- package/dist/openai-agents.cjs +161 -24
- package/dist/openai-agents.cjs.map +1 -1
- package/dist/openai-agents.d.cts +10 -4
- package/dist/openai-agents.d.ts +10 -4
- package/dist/openai-agents.js +161 -24
- package/dist/openai-agents.js.map +1 -1
- package/docs/agent-api/assets/hierarchy.js +1 -0
- package/docs/agent-api/assets/highlight.css +92 -0
- package/docs/agent-api/assets/icons.js +18 -0
- package/docs/agent-api/assets/icons.svg +1 -0
- package/docs/agent-api/assets/main.js +60 -0
- package/docs/agent-api/assets/navigation.js +1 -0
- package/docs/agent-api/assets/search.js +1 -0
- package/docs/agent-api/assets/style.css +1648 -0
- package/docs/agent-api/classes/langgraph.FerricStoreSaver.html +297 -0
- package/docs/agent-api/classes/langgraph.FerricStoreStore.html +298 -0
- package/docs/agent-api/classes/langgraph.LangGraphFlow.html +190 -0
- package/docs/agent-api/classes/langgraph.LangGraphFlowContext.html +158 -0
- package/docs/agent-api/classes/langgraph.LangGraphFlowRun.html +133 -0
- package/docs/agent-api/classes/openai-agents.FerricStoreSession.html +241 -0
- package/docs/agent-api/hierarchy.html +44 -0
- package/docs/agent-api/index.html +142 -0
- package/docs/agent-api/interfaces/langgraph.FerricFlowHandlerContext.html +94 -0
- package/docs/agent-api/interfaces/langgraph.FerricStoreCommandClient.html +77 -0
- package/docs/agent-api/interfaces/langgraph.FerricStoreLockOptions.html +81 -0
- package/docs/agent-api/interfaces/langgraph.FerricStoreSaverOptions.html +106 -0
- package/docs/agent-api/interfaces/langgraph.FerricStoreStoreOptions.html +98 -0
- package/docs/agent-api/interfaces/langgraph.InvokableLangGraph.html +83 -0
- package/docs/agent-api/interfaces/langgraph.LangGraphFlowOptions.html +116 -0
- package/docs/agent-api/interfaces/openai-agents.FerricStoreSessionOptions.html +114 -0
- package/docs/agent-api/interfaces/openai-agents.Session.html +208 -0
- package/docs/agent-api/interfaces/openai-agents.SessionHistoryRewriteAwareSession.html +230 -0
- package/docs/agent-api/interfaces/openai-agents.SessionHistoryTransactionAwareSession.html +237 -0
- package/docs/agent-api/modules/langgraph.html +44 -0
- package/docs/agent-api/modules/openai-agents.html +44 -0
- package/docs/agent-api/modules.html +35 -0
- package/docs/agent-api/types/langgraph.LangGraphChannelVersions.html +33 -0
- package/docs/agent-api/types/langgraph.LangGraphInvocationConfig.html +33 -0
- package/docs/agent-api/types/langgraph.LangGraphOutcomeMapper.html +50 -0
- package/docs/agent-api/types/langgraph.LangGraphPendingWrite.html +33 -0
- package/docs/agent-api/types/openai-agents.AgentInputItem.html +35 -0
- package/docs/agent-frameworks.md +25 -8
- package/docs/api/index.html +4 -1
- package/docs/api/media/agent-frameworks.md +25 -8
- package/package.json +3 -3
package/dist/langgraph.cjs
CHANGED
|
@@ -139,14 +139,44 @@ function integerResponse(value, name) {
|
|
|
139
139
|
if (!Number.isSafeInteger(parsed)) throw new TypeError(`FerricStore returned an invalid ${name}`);
|
|
140
140
|
return parsed;
|
|
141
141
|
}
|
|
142
|
+
async function readAtomicValue(client, key, name) {
|
|
143
|
+
const value = await client.command("GET", key);
|
|
144
|
+
if (value == null) return void 0;
|
|
145
|
+
if (typeof value === "string") return Buffer.from(value, "utf8");
|
|
146
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array) return Buffer.from(value);
|
|
147
|
+
throw new TypeError(`FerricStore returned a non-binary ${name}`);
|
|
148
|
+
}
|
|
149
|
+
async function compareAndSetAtomicValue(client, key, expected, value) {
|
|
150
|
+
if (expected == null) {
|
|
151
|
+
const response2 = await client.command("SET", key, value, "NX");
|
|
152
|
+
if (response2 == null || response2 === false) return false;
|
|
153
|
+
if (response2 === true) return true;
|
|
154
|
+
return textResponse(response2, "SET NX response").toUpperCase() === "OK";
|
|
155
|
+
}
|
|
156
|
+
const response = await client.command("CAS", key, expected, value);
|
|
157
|
+
if (response == null || response === false) return false;
|
|
158
|
+
if (response === true) return true;
|
|
159
|
+
return integerResponse(response, "CAS response") === 1;
|
|
160
|
+
}
|
|
142
161
|
async function withMutationLocks(client, keys, operation, options = {}) {
|
|
143
162
|
const orderedKeys = [...new Set(keys)].sort();
|
|
144
|
-
if (orderedKeys.length === 0)
|
|
163
|
+
if (orderedKeys.length === 0) {
|
|
164
|
+
const signal = new AbortController().signal;
|
|
165
|
+
return await operation({
|
|
166
|
+
signal,
|
|
167
|
+
assertOwned: () => void 0,
|
|
168
|
+
publish: async (...args) => await additiveCommand(client, args),
|
|
169
|
+
compareAndSet: async (key, expected, value) => await compareAndSetAtomicValue(client, key, expected, value)
|
|
170
|
+
});
|
|
171
|
+
}
|
|
145
172
|
const normalized = {
|
|
146
173
|
lockRetryMs: positiveInteger(options.lockRetryMs, DEFAULT_LOCK_OPTIONS.lockRetryMs, "lockRetryMs"),
|
|
147
174
|
lockTtlMs: positiveInteger(options.lockTtlMs, DEFAULT_LOCK_OPTIONS.lockTtlMs, "lockTtlMs"),
|
|
148
175
|
lockWaitMs: nonNegativeInteger(options.lockWaitMs, DEFAULT_LOCK_OPTIONS.lockWaitMs, "lockWaitMs")
|
|
149
176
|
};
|
|
177
|
+
if (normalized.lockRetryMs >= normalized.lockTtlMs) {
|
|
178
|
+
throw new TypeError("lockRetryMs must be less than lockTtlMs");
|
|
179
|
+
}
|
|
150
180
|
const owner = (0, import_node_crypto.randomUUID)();
|
|
151
181
|
const acquired = [];
|
|
152
182
|
const deadline = performance.now() + normalized.lockWaitMs;
|
|
@@ -155,29 +185,81 @@ async function withMutationLocks(client, keys, operation, options = {}) {
|
|
|
155
185
|
let releaseError;
|
|
156
186
|
let result;
|
|
157
187
|
let operationCompleted = false;
|
|
188
|
+
let conditionalCommitCompleted = false;
|
|
158
189
|
const heartbeatAbort = new AbortController();
|
|
190
|
+
const ownershipAbort = new AbortController();
|
|
191
|
+
const lastExtended = /* @__PURE__ */ new Map();
|
|
192
|
+
const loseOwnership = (error) => {
|
|
193
|
+
const normalizedError = errorObject(error);
|
|
194
|
+
heartbeatError ??= normalizedError;
|
|
195
|
+
if (!ownershipAbort.signal.aborted) ownershipAbort.abort(normalizedError);
|
|
196
|
+
return normalizedError;
|
|
197
|
+
};
|
|
198
|
+
const assertOwned = () => {
|
|
199
|
+
if (heartbeatError != null) throw errorObject(heartbeatError);
|
|
200
|
+
if (ownershipAbort.signal.aborted) throw errorObject(ownershipAbort.signal.reason);
|
|
201
|
+
};
|
|
202
|
+
const renewOwned = async () => {
|
|
203
|
+
assertOwned();
|
|
204
|
+
for (const key of acquired) {
|
|
205
|
+
try {
|
|
206
|
+
const response = await client.command("EXTEND", key, owner, normalized.lockTtlMs);
|
|
207
|
+
if (integerResponse(response, "EXTEND response") !== 1) {
|
|
208
|
+
throw new Error(`lost FerricStore lock ${JSON.stringify(key)} while mutating data`);
|
|
209
|
+
}
|
|
210
|
+
lastExtended.set(key, performance.now());
|
|
211
|
+
} catch (error) {
|
|
212
|
+
throw loseOwnership(new Error(
|
|
213
|
+
`could not validate FerricStore lock ${JSON.stringify(key)} before mutating data`,
|
|
214
|
+
{ cause: error }
|
|
215
|
+
));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
assertOwned();
|
|
219
|
+
};
|
|
220
|
+
const lease = {
|
|
221
|
+
signal: ownershipAbort.signal,
|
|
222
|
+
assertOwned,
|
|
223
|
+
publish: async (...args) => {
|
|
224
|
+
await renewOwned();
|
|
225
|
+
const response = await additiveCommand(client, args);
|
|
226
|
+
assertOwned();
|
|
227
|
+
return response;
|
|
228
|
+
},
|
|
229
|
+
compareAndSet: async (key, expected, value) => {
|
|
230
|
+
await renewOwned();
|
|
231
|
+
const committed = await compareAndSetAtomicValue(client, key, expected, value);
|
|
232
|
+
if (committed) conditionalCommitCompleted = true;
|
|
233
|
+
else assertOwned();
|
|
234
|
+
return committed;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
159
237
|
try {
|
|
160
238
|
for (const key of orderedKeys) {
|
|
161
239
|
while (!await tryAcquireLock(client, key, owner, normalized.lockTtlMs)) {
|
|
162
240
|
if (performance.now() >= deadline) {
|
|
163
241
|
throw new Error(`timed out acquiring FerricStore lock ${JSON.stringify(key)}`);
|
|
164
242
|
}
|
|
243
|
+
await extendAcquiredLocks(client, acquired, owner, normalized.lockTtlMs);
|
|
165
244
|
await (0, import_promises.setTimeout)(normalized.lockRetryMs);
|
|
166
245
|
}
|
|
167
246
|
acquired.push(key);
|
|
168
247
|
}
|
|
248
|
+
await extendAcquiredLocks(client, acquired, owner, normalized.lockTtlMs);
|
|
249
|
+
for (const key of acquired) lastExtended.set(key, performance.now());
|
|
169
250
|
const heartbeat = renewLocks(
|
|
170
251
|
client,
|
|
171
252
|
acquired,
|
|
172
253
|
owner,
|
|
173
254
|
normalized.lockTtlMs,
|
|
255
|
+
lastExtended,
|
|
174
256
|
heartbeatAbort.signal,
|
|
175
257
|
(error) => {
|
|
176
|
-
|
|
258
|
+
loseOwnership(error);
|
|
177
259
|
}
|
|
178
260
|
);
|
|
179
261
|
try {
|
|
180
|
-
result = await operation();
|
|
262
|
+
result = await operation(lease);
|
|
181
263
|
operationCompleted = true;
|
|
182
264
|
} catch (error) {
|
|
183
265
|
primaryError = error;
|
|
@@ -198,11 +280,32 @@ async function withMutationLocks(client, keys, operation, options = {}) {
|
|
|
198
280
|
}
|
|
199
281
|
}
|
|
200
282
|
if (primaryError != null) throw errorObject(primaryError);
|
|
201
|
-
if (heartbeatError != null) throw errorObject(heartbeatError);
|
|
202
|
-
if (releaseError != null
|
|
283
|
+
if (heartbeatError != null && !conditionalCommitCompleted) throw errorObject(heartbeatError);
|
|
284
|
+
if (releaseError != null && !(heartbeatError != null && conditionalCommitCompleted)) {
|
|
285
|
+
throw errorObject(releaseError);
|
|
286
|
+
}
|
|
203
287
|
if (!operationCompleted) throw new Error("FerricStore mutation did not complete");
|
|
204
288
|
return result;
|
|
205
289
|
}
|
|
290
|
+
async function additiveCommand(client, args) {
|
|
291
|
+
const rawName = args[0];
|
|
292
|
+
const name = typeof rawName === "string" ? rawName.toUpperCase() : Buffer.isBuffer(rawName) || rawName instanceof Uint8Array ? Buffer.from(rawName).toString("utf8").toUpperCase() : "";
|
|
293
|
+
if (name !== "SADD" && name !== "ZADD") {
|
|
294
|
+
throw new TypeError("FerricStore mutation leases only publish add-only SADD or ZADD indexes");
|
|
295
|
+
}
|
|
296
|
+
if (name === "ZADD" && (args.length < 4 || args.length % 2 !== 0 || args.slice(2).some((value, index) => index % 2 === 0 && Number(value) !== 0))) {
|
|
297
|
+
throw new TypeError("FerricStore mutation leases only publish zero-score ZADD indexes");
|
|
298
|
+
}
|
|
299
|
+
return await client.command(...args);
|
|
300
|
+
}
|
|
301
|
+
async function extendAcquiredLocks(client, keys, owner, ttlMs) {
|
|
302
|
+
for (const key of keys) {
|
|
303
|
+
const response = await client.command("EXTEND", key, owner, ttlMs);
|
|
304
|
+
if (integerResponse(response, "EXTEND response") !== 1) {
|
|
305
|
+
throw new Error(`lost FerricStore lock ${JSON.stringify(key)} before mutating data`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
206
309
|
async function tryAcquireLock(client, key, owner, ttlMs) {
|
|
207
310
|
try {
|
|
208
311
|
const response = await client.command("LOCK", key, owner, ttlMs);
|
|
@@ -212,10 +315,9 @@ async function tryAcquireLock(client, key, owner, ttlMs) {
|
|
|
212
315
|
throw error;
|
|
213
316
|
}
|
|
214
317
|
}
|
|
215
|
-
async function renewLocks(client, keys, owner, ttlMs, signal, onError) {
|
|
216
|
-
const intervalMs = Math.max(Math.floor(ttlMs / 3),
|
|
217
|
-
const retryMs = Math.min(Math.max(Math.floor(intervalMs / 10),
|
|
218
|
-
const lastExtended = new Map(keys.map((key) => [key, performance.now()]));
|
|
318
|
+
async function renewLocks(client, keys, owner, ttlMs, lastExtended, signal, onError) {
|
|
319
|
+
const intervalMs = Math.max(Math.floor(ttlMs / 3), 1);
|
|
320
|
+
const retryMs = Math.min(Math.max(Math.floor(intervalMs / 10), 1), 1e3);
|
|
219
321
|
let waitMs = intervalMs;
|
|
220
322
|
while (!signal.aborted) {
|
|
221
323
|
try {
|
|
@@ -252,6 +354,8 @@ function errorObject(value) {
|
|
|
252
354
|
|
|
253
355
|
// src/langgraph/checkpoint.ts
|
|
254
356
|
var FORMAT_VERSION = 1;
|
|
357
|
+
var LEGACY_EPOCH_PREFIX = "legacy:";
|
|
358
|
+
var CURRENT_EPOCH_PREFIX = "v2:";
|
|
255
359
|
var CHECKPOINT_FIELD_PREFIX = "checkpoint:";
|
|
256
360
|
var WRITE_FIELD_PREFIX = "write:";
|
|
257
361
|
var WRITES_INDEX = {
|
|
@@ -279,22 +383,18 @@ var FerricStoreSaver = class extends import_langgraph.BaseCheckpointSaver {
|
|
|
279
383
|
async getTuple(config) {
|
|
280
384
|
const { checkpointNs, threadId } = identity(config);
|
|
281
385
|
const key = this.threadKey(threadId, checkpointNs);
|
|
386
|
+
const epoch = await this.readThreadEpoch(threadId);
|
|
282
387
|
let id = checkpointId(config);
|
|
283
388
|
let record;
|
|
284
389
|
if (id != null) {
|
|
285
|
-
record = await this.
|
|
390
|
+
record = await this.readRecordAtEpoch(key, id, epoch);
|
|
286
391
|
} else {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (values.length === 0) return void 0;
|
|
294
|
-
id = textResponse(values[0], "checkpoint ID");
|
|
295
|
-
record = await this.readRecord(key, id);
|
|
296
|
-
if (record != null) break;
|
|
297
|
-
offset += 1;
|
|
392
|
+
for (const candidate of await this.checkpointIdsAtEpoch(key, epoch)) {
|
|
393
|
+
record = await this.readRecordAtEpoch(key, candidate, epoch);
|
|
394
|
+
if (record != null) {
|
|
395
|
+
id = candidate;
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
298
398
|
}
|
|
299
399
|
}
|
|
300
400
|
if (id == null || record == null) return void 0;
|
|
@@ -314,14 +414,15 @@ var FerricStoreSaver = class extends import_langgraph.BaseCheckpointSaver {
|
|
|
314
414
|
const checkpointNs = namespaceSpecified ? requiredText(configurable?.checkpoint_ns, "checkpoint_ns", true) : void 0;
|
|
315
415
|
const matches = [];
|
|
316
416
|
if (threadId == null) {
|
|
317
|
-
await this.collectGlobal(matches, beforeId, expectedId, options.filter, limit);
|
|
417
|
+
await this.collectGlobal(matches, beforeId, expectedId, checkpointNs, options.filter, limit);
|
|
318
418
|
} else {
|
|
319
419
|
const keys = checkpointNs == null ? await this.threadKeys(threadId) : [this.threadKey(threadId, checkpointNs)];
|
|
320
420
|
for (const key of keys) {
|
|
321
|
-
const
|
|
421
|
+
const epoch = await this.readThreadEpoch(threadId);
|
|
422
|
+
const ids = expectedId == null ? await this.checkpointIdsAtEpoch(key, epoch) : [expectedId];
|
|
322
423
|
for (const id of ids) {
|
|
323
424
|
if (beforeId != null && id >= beforeId) continue;
|
|
324
|
-
const record = await this.
|
|
425
|
+
const record = await this.readRecordAtEpoch(key, id, epoch);
|
|
325
426
|
if (record == null || !metadataMatches(record.metadata, options.filter)) continue;
|
|
326
427
|
matches.push({ key, record });
|
|
327
428
|
}
|
|
@@ -348,13 +449,24 @@ var FerricStoreSaver = class extends import_langgraph.BaseCheckpointSaver {
|
|
|
348
449
|
threadId
|
|
349
450
|
};
|
|
350
451
|
const key = this.threadKey(threadId, checkpointNs);
|
|
351
|
-
await withMutationLocks(this.client, [this.threadLockKey(threadId)], async () => {
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
await
|
|
355
|
-
await
|
|
356
|
-
await
|
|
357
|
-
await
|
|
452
|
+
await withMutationLocks(this.client, [this.threadLockKey(threadId)], async (lease) => {
|
|
453
|
+
const epoch = await this.ensureThreadEpoch(threadId, lease);
|
|
454
|
+
const locator = checkpointLocator(checkpoint.id, key, threadId);
|
|
455
|
+
await lease.publish("SADD", this.threadCatalogKey(threadId), key);
|
|
456
|
+
await lease.publish("ZADD", this.threadLocatorCatalogKey(threadId), 0, locator);
|
|
457
|
+
await lease.publish("ZADD", this.catalogKey(), 0, locator);
|
|
458
|
+
await lease.publish("ZADD", this.checkpointIndexKey(key), 0, checkpoint.id);
|
|
459
|
+
await lease.publish("ZADD", this.atomicCheckpointIndexKey(key, requireEpoch(epoch)), 0, checkpoint.id);
|
|
460
|
+
const dataKey = this.checkpointRecordKey(key, requireEpoch(epoch), checkpoint.id);
|
|
461
|
+
const value = await this.serialize(record);
|
|
462
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
463
|
+
const expected = await readAtomicValue(this.client, dataKey, "LangGraph atomic checkpoint record");
|
|
464
|
+
if (await lease.compareAndSet(dataKey, expected, value)) {
|
|
465
|
+
await this.assertCurrentEpoch(threadId, epoch);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
throw new Error("concurrent FerricStore LangGraph checkpoint mutation did not converge");
|
|
358
470
|
}, this.lockOptions);
|
|
359
471
|
return checkpointConfig(threadId, checkpointNs, checkpoint.id);
|
|
360
472
|
}
|
|
@@ -376,35 +488,39 @@ var FerricStoreSaver = class extends import_langgraph.BaseCheckpointSaver {
|
|
|
376
488
|
return { index, value: await this.serialize(record) };
|
|
377
489
|
}));
|
|
378
490
|
const key = this.threadKey(threadId, checkpointNs);
|
|
379
|
-
await withMutationLocks(this.client, [this.threadLockKey(threadId)], async () => {
|
|
380
|
-
await this.
|
|
491
|
+
await withMutationLocks(this.client, [this.threadLockKey(threadId)], async (lease) => {
|
|
492
|
+
const epoch = await this.ensureThreadEpoch(threadId, lease);
|
|
493
|
+
const epochValue = requireEpoch(epoch);
|
|
494
|
+
await lease.publish("SADD", this.threadCatalogKey(threadId), key);
|
|
381
495
|
for (const snapshot of snapshots) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
496
|
+
const dataKey = this.pendingWriteKey(key, epochValue, id, taskId, snapshot.index);
|
|
497
|
+
const indexKey = this.atomicPendingWriteIndexKey(key, epochValue, id);
|
|
498
|
+
await lease.publish("ZADD", indexKey, 0, dataKey);
|
|
499
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
500
|
+
const expected = await readAtomicValue(this.client, dataKey, "LangGraph atomic pending write");
|
|
501
|
+
if (snapshot.index >= 0 && expected != null) break;
|
|
502
|
+
if (snapshot.index >= 0 && epoch.legacyFallback) {
|
|
503
|
+
const legacy = await this.client.command("HGET", key, writeField(id, taskId, snapshot.index));
|
|
504
|
+
if (legacy != null) break;
|
|
505
|
+
}
|
|
506
|
+
if (await lease.compareAndSet(dataKey, expected, snapshot.value)) break;
|
|
507
|
+
if (attempt === 7) {
|
|
508
|
+
throw new Error("concurrent FerricStore LangGraph pending-write mutation did not converge");
|
|
509
|
+
}
|
|
510
|
+
}
|
|
388
511
|
}
|
|
512
|
+
await this.assertCurrentEpoch(threadId, epoch);
|
|
389
513
|
}, this.lockOptions);
|
|
390
514
|
}
|
|
391
515
|
async deleteThread(threadId) {
|
|
392
516
|
const normalized = requiredText(threadId, "threadId");
|
|
393
|
-
await withMutationLocks(this.client, [this.threadLockKey(normalized)], async () => {
|
|
394
|
-
for (
|
|
395
|
-
await this.
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
while (true) {
|
|
399
|
-
const locators = arrayResponse(
|
|
400
|
-
await this.client.command("ZRANGE", locatorKey, 0, this.scanCount - 1),
|
|
401
|
-
"thread checkpoint locator response"
|
|
402
|
-
);
|
|
403
|
-
if (locators.length === 0) break;
|
|
404
|
-
await this.client.command("ZREM", this.catalogKey(), ...asArguments(locators));
|
|
405
|
-
await this.client.command("ZREM", locatorKey, ...asArguments(locators));
|
|
517
|
+
await withMutationLocks(this.client, [this.threadLockKey(normalized)], async (lease) => {
|
|
518
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
519
|
+
const epoch = await this.ensureThreadEpoch(normalized, lease);
|
|
520
|
+
const next = Buffer.from(`${CURRENT_EPOCH_PREFIX}${(0, import_node_crypto2.randomUUID)()}`, "utf8");
|
|
521
|
+
if (await lease.compareAndSet(this.threadEpochKey(normalized), epoch.raw, next)) return;
|
|
406
522
|
}
|
|
407
|
-
|
|
523
|
+
throw new Error("concurrent FerricStore LangGraph thread deletion did not converge");
|
|
408
524
|
}, this.lockOptions);
|
|
409
525
|
}
|
|
410
526
|
catalogKey() {
|
|
@@ -426,6 +542,47 @@ var FerricStoreSaver = class extends import_langgraph.BaseCheckpointSaver {
|
|
|
426
542
|
checkpointIndexKey(threadKey) {
|
|
427
543
|
return `${threadKey}:checkpoint-index`;
|
|
428
544
|
}
|
|
545
|
+
threadEpochKey(threadId) {
|
|
546
|
+
return `${this.threadCatalogKey(threadId)}:atomic-epoch`;
|
|
547
|
+
}
|
|
548
|
+
atomicCheckpointIndexKey(threadKey, epoch) {
|
|
549
|
+
return `${threadKey}:atomic:${sha256(epoch)}:checkpoint-index`;
|
|
550
|
+
}
|
|
551
|
+
atomicPendingWriteIndexKey(threadKey, epoch, checkpointId2) {
|
|
552
|
+
return `${threadKey}:atomic:${sha256(epoch)}:writes:${encodeComponent(checkpointId2)}`;
|
|
553
|
+
}
|
|
554
|
+
checkpointRecordKey(threadKey, epoch, checkpointId2) {
|
|
555
|
+
return `${threadKey}:atomic:${sha256(epoch)}:checkpoint:${encodeComponent(checkpointId2)}`;
|
|
556
|
+
}
|
|
557
|
+
pendingWriteKey(threadKey, epoch, checkpointId2, taskId, index) {
|
|
558
|
+
return `${threadKey}:atomic:${sha256(epoch)}:write:${encodeComponent(checkpointId2)}:${encodeComponent(taskId)}:${index}`;
|
|
559
|
+
}
|
|
560
|
+
async readThreadEpoch(threadId) {
|
|
561
|
+
const raw = await readAtomicValue(this.client, this.threadEpochKey(threadId), "LangGraph thread epoch");
|
|
562
|
+
if (raw == null) return { legacyFallback: true, raw, value: void 0 };
|
|
563
|
+
const value = raw.toString("utf8");
|
|
564
|
+
if (!value.startsWith(LEGACY_EPOCH_PREFIX) && !value.startsWith(CURRENT_EPOCH_PREFIX)) {
|
|
565
|
+
throw new Error("unsupported or corrupt FerricStore LangGraph thread epoch");
|
|
566
|
+
}
|
|
567
|
+
return { legacyFallback: value.startsWith(LEGACY_EPOCH_PREFIX), raw, value };
|
|
568
|
+
}
|
|
569
|
+
async ensureThreadEpoch(threadId, lease) {
|
|
570
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
571
|
+
const epoch = await this.readThreadEpoch(threadId);
|
|
572
|
+
if (epoch.value != null) return epoch;
|
|
573
|
+
const raw = Buffer.from(`${LEGACY_EPOCH_PREFIX}${(0, import_node_crypto2.randomUUID)()}`, "utf8");
|
|
574
|
+
if (await lease.compareAndSet(this.threadEpochKey(threadId), void 0, raw)) {
|
|
575
|
+
return { legacyFallback: true, raw, value: raw.toString("utf8") };
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
throw new Error("concurrent FerricStore LangGraph epoch initialization did not converge");
|
|
579
|
+
}
|
|
580
|
+
async assertCurrentEpoch(threadId, expected) {
|
|
581
|
+
const current = await this.readThreadEpoch(threadId);
|
|
582
|
+
if (expected.raw == null || current.raw?.equals(expected.raw) !== true) {
|
|
583
|
+
throw new Error("FerricStore LangGraph thread epoch changed while committing data");
|
|
584
|
+
}
|
|
585
|
+
}
|
|
429
586
|
async serialize(value) {
|
|
430
587
|
const [type, data] = await this.serde.dumpsTyped(value);
|
|
431
588
|
const typeBytes = Buffer.from(type, "utf8");
|
|
@@ -445,8 +602,16 @@ var FerricStoreSaver = class extends import_langgraph.BaseCheckpointSaver {
|
|
|
445
602
|
const type = bytes.subarray(2, typeLength + 2).toString("utf8");
|
|
446
603
|
return await this.serde.loadsTyped(type, bytes.subarray(typeLength + 2));
|
|
447
604
|
}
|
|
448
|
-
async
|
|
449
|
-
|
|
605
|
+
async readRecordAtEpoch(key, id, epoch) {
|
|
606
|
+
let value;
|
|
607
|
+
if (epoch.value != null) {
|
|
608
|
+
value = await this.client.command("GET", this.checkpointRecordKey(key, epoch.value, id));
|
|
609
|
+
if (value == null && epoch.legacyFallback) {
|
|
610
|
+
value = await this.client.command("HGET", key, checkpointField(id));
|
|
611
|
+
}
|
|
612
|
+
} else {
|
|
613
|
+
value = await this.client.command("HGET", key, checkpointField(id));
|
|
614
|
+
}
|
|
450
615
|
if (value == null) return void 0;
|
|
451
616
|
const record = await this.deserialize(value, "LangGraph checkpoint record");
|
|
452
617
|
if (record.formatVersion !== FORMAT_VERSION || record.checkpointId !== id) {
|
|
@@ -454,23 +619,45 @@ var FerricStoreSaver = class extends import_langgraph.BaseCheckpointSaver {
|
|
|
454
619
|
}
|
|
455
620
|
return record;
|
|
456
621
|
}
|
|
457
|
-
async
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
622
|
+
async pendingWritesAtEpoch(key, id, epoch) {
|
|
623
|
+
const records = /* @__PURE__ */ new Map();
|
|
624
|
+
if (epoch.value != null) {
|
|
625
|
+
const keys = arrayResponse(
|
|
626
|
+
await this.client.command("ZRANGE", this.atomicPendingWriteIndexKey(key, epoch.value, id), 0, -1),
|
|
627
|
+
"LangGraph atomic pending-write index response"
|
|
628
|
+
);
|
|
629
|
+
for (const rawKey of keys) {
|
|
630
|
+
const dataKey = textResponse(rawKey, "LangGraph pending-write key");
|
|
631
|
+
const value = await this.client.command("GET", dataKey);
|
|
632
|
+
if (value == null) continue;
|
|
633
|
+
const record = await this.deserialize(value, "LangGraph pending write");
|
|
634
|
+
records.set(pendingWriteIdentity(record), record);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (epoch.legacyFallback) {
|
|
638
|
+
const fields = await this.scanHash(key, `${WRITE_FIELD_PREFIX}${encodeComponent(id)}:*`);
|
|
639
|
+
for (const [, value] of fields) {
|
|
640
|
+
const record = await this.deserialize(value, "LangGraph pending write");
|
|
641
|
+
const identity2 = pendingWriteIdentity(record);
|
|
642
|
+
if (!records.has(identity2)) records.set(identity2, record);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
const ordered = [...records.values()];
|
|
646
|
+
for (const record of ordered) {
|
|
461
647
|
if (record.formatVersion !== FORMAT_VERSION) {
|
|
462
648
|
throw new Error("unsupported FerricStore LangGraph pending-write format");
|
|
463
649
|
}
|
|
464
650
|
}
|
|
465
|
-
|
|
466
|
-
return
|
|
651
|
+
ordered.sort((left, right) => left.taskId.localeCompare(right.taskId) || left.index - right.index);
|
|
652
|
+
return ordered.map((record) => [record.taskId, record.channel, record.value]);
|
|
467
653
|
}
|
|
468
654
|
async tupleFromRecord(key, record) {
|
|
655
|
+
const epoch = await this.readThreadEpoch(record.threadId);
|
|
469
656
|
return {
|
|
470
657
|
checkpoint: record.checkpoint,
|
|
471
658
|
config: checkpointConfig(record.threadId, record.checkpointNs, record.checkpointId),
|
|
472
659
|
metadata: record.metadata,
|
|
473
|
-
pendingWrites: await this.
|
|
660
|
+
pendingWrites: await this.pendingWritesAtEpoch(key, record.checkpointId, epoch),
|
|
474
661
|
...record.parentCheckpointId == null ? {} : { parentConfig: checkpointConfig(record.threadId, record.checkpointNs, record.parentCheckpointId) }
|
|
475
662
|
};
|
|
476
663
|
}
|
|
@@ -502,15 +689,27 @@ var FerricStoreSaver = class extends import_langgraph.BaseCheckpointSaver {
|
|
|
502
689
|
);
|
|
503
690
|
return values.map((value) => textResponse(value, "thread checkpoint key")).sort();
|
|
504
691
|
}
|
|
505
|
-
async
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
692
|
+
async checkpointIdsAtEpoch(key, epoch) {
|
|
693
|
+
const ids = /* @__PURE__ */ new Set();
|
|
694
|
+
if (epoch.value != null) {
|
|
695
|
+
const values = arrayResponse(
|
|
696
|
+
await this.client.command("ZREVRANGE", this.atomicCheckpointIndexKey(key, epoch.value), 0, -1),
|
|
697
|
+
"atomic checkpoint index response"
|
|
698
|
+
);
|
|
699
|
+
for (const value of values) ids.add(textResponse(value, "checkpoint ID"));
|
|
700
|
+
}
|
|
701
|
+
if (epoch.legacyFallback) {
|
|
702
|
+
const values = arrayResponse(
|
|
703
|
+
await this.client.command("ZREVRANGE", this.checkpointIndexKey(key), 0, -1),
|
|
704
|
+
"checkpoint index response"
|
|
705
|
+
);
|
|
706
|
+
for (const value of values) ids.add(textResponse(value, "checkpoint ID"));
|
|
707
|
+
}
|
|
708
|
+
return [...ids].sort((left, right) => right.localeCompare(left));
|
|
511
709
|
}
|
|
512
|
-
async collectGlobal(result, beforeId, expectedId, filter, limit) {
|
|
710
|
+
async collectGlobal(result, beforeId, expectedId, checkpointNs, filter, limit) {
|
|
513
711
|
let offset = 0;
|
|
712
|
+
const seen = /* @__PURE__ */ new Set();
|
|
514
713
|
while (limit == null || result.length < limit) {
|
|
515
714
|
const values = arrayResponse(
|
|
516
715
|
await this.client.command("ZREVRANGE", this.catalogKey(), offset, offset + this.scanCount - 1),
|
|
@@ -518,11 +717,24 @@ var FerricStoreSaver = class extends import_langgraph.BaseCheckpointSaver {
|
|
|
518
717
|
);
|
|
519
718
|
if (values.length === 0) break;
|
|
520
719
|
for (const value of values) {
|
|
521
|
-
const
|
|
720
|
+
const locator = decodeCheckpointLocator(value);
|
|
721
|
+
const { checkpointId: id, threadKey: key } = locator;
|
|
522
722
|
if (expectedId != null && id !== expectedId) continue;
|
|
523
723
|
if (beforeId != null && id >= beforeId) continue;
|
|
524
|
-
|
|
525
|
-
if (
|
|
724
|
+
let record;
|
|
725
|
+
if (locator.threadId != null) {
|
|
726
|
+
record = await this.readRecordAtEpoch(key, id, await this.readThreadEpoch(locator.threadId));
|
|
727
|
+
} else {
|
|
728
|
+
const legacy = await this.client.command("HGET", key, checkpointField(id));
|
|
729
|
+
if (legacy != null) {
|
|
730
|
+
const candidate = await this.deserialize(legacy, "LangGraph checkpoint record");
|
|
731
|
+
record = await this.readRecordAtEpoch(key, id, await this.readThreadEpoch(candidate.threadId));
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (record == null || checkpointNs != null && record.checkpointNs !== checkpointNs || !metadataMatches(record.metadata, filter)) continue;
|
|
735
|
+
const identity2 = lengthPrefixed([record.threadId, record.checkpointNs, record.checkpointId]).toString("base64url");
|
|
736
|
+
if (seen.has(identity2)) continue;
|
|
737
|
+
seen.add(identity2);
|
|
526
738
|
result.push({ key, record });
|
|
527
739
|
if (limit != null && result.length >= limit) break;
|
|
528
740
|
}
|
|
@@ -571,8 +783,18 @@ function writeField(id, taskId, index) {
|
|
|
571
783
|
function encodeComponent(value) {
|
|
572
784
|
return Buffer.from(value, "utf8").toString("base64url");
|
|
573
785
|
}
|
|
574
|
-
function checkpointLocator(id, threadKey) {
|
|
575
|
-
return Buffer.concat([orderedText(id), Buffer.from(threadKey, "utf8")]);
|
|
786
|
+
function checkpointLocator(id, threadKey, threadId) {
|
|
787
|
+
if (threadId == null) return Buffer.concat([orderedText(id), Buffer.from(threadKey, "utf8")]);
|
|
788
|
+
const threadIdBytes = Buffer.from(threadId, "utf8");
|
|
789
|
+
const length = Buffer.allocUnsafe(4);
|
|
790
|
+
length.writeUInt32BE(threadIdBytes.length);
|
|
791
|
+
return Buffer.concat([
|
|
792
|
+
orderedText(id),
|
|
793
|
+
Buffer.from([0, 1]),
|
|
794
|
+
length,
|
|
795
|
+
threadIdBytes,
|
|
796
|
+
Buffer.from(threadKey, "utf8")
|
|
797
|
+
]);
|
|
576
798
|
}
|
|
577
799
|
function decodeCheckpointLocator(value) {
|
|
578
800
|
if (!(typeof value === "string" || Buffer.isBuffer(value) || value instanceof Uint8Array)) {
|
|
@@ -580,9 +802,21 @@ function decodeCheckpointLocator(value) {
|
|
|
580
802
|
}
|
|
581
803
|
const bytes = typeof value === "string" ? Buffer.from(value, "utf8") : Buffer.from(value);
|
|
582
804
|
const decoded = decodeOrderedText(bytes, 0);
|
|
583
|
-
|
|
805
|
+
let offset = decoded.offset;
|
|
806
|
+
let threadId;
|
|
807
|
+
if (bytes[offset] === 0 && bytes[offset + 1] === 1) {
|
|
808
|
+
offset += 2;
|
|
809
|
+
if (bytes.length < offset + 4) throw new Error("truncated FerricStore checkpoint locator");
|
|
810
|
+
const length = bytes.readUInt32BE(offset);
|
|
811
|
+
offset += 4;
|
|
812
|
+
if (bytes.length < offset + length) throw new Error("truncated FerricStore checkpoint locator thread ID");
|
|
813
|
+
threadId = bytes.subarray(offset, offset + length).toString("utf8");
|
|
814
|
+
offset += length;
|
|
815
|
+
if (threadId.length === 0) throw new Error("FerricStore checkpoint locator has an empty thread ID");
|
|
816
|
+
}
|
|
817
|
+
const threadKey = bytes.subarray(offset).toString("utf8");
|
|
584
818
|
if (threadKey.length === 0) throw new Error("FerricStore checkpoint locator has an empty thread key");
|
|
585
|
-
return { checkpointId: decoded.value, threadKey };
|
|
819
|
+
return { checkpointId: decoded.value, ...threadId == null ? {} : { threadId }, threadKey };
|
|
586
820
|
}
|
|
587
821
|
function orderedText(value) {
|
|
588
822
|
const output = [];
|
|
@@ -625,12 +859,12 @@ function lengthPrefixed(values) {
|
|
|
625
859
|
}
|
|
626
860
|
return Buffer.concat(parts);
|
|
627
861
|
}
|
|
628
|
-
function
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
862
|
+
function requireEpoch(epoch) {
|
|
863
|
+
if (epoch.value == null) throw new Error("FerricStore LangGraph thread epoch is not initialized");
|
|
864
|
+
return epoch.value;
|
|
865
|
+
}
|
|
866
|
+
function pendingWriteIdentity(record) {
|
|
867
|
+
return lengthPrefixed([record.taskId, String(record.index)]).toString("base64url");
|
|
634
868
|
}
|
|
635
869
|
|
|
636
870
|
// src/langgraph/store.ts
|
|
@@ -638,6 +872,7 @@ var import_node_crypto3 = require("crypto");
|
|
|
638
872
|
var import_langgraph2 = require("@langchain/langgraph");
|
|
639
873
|
var FORMAT_VERSION2 = 1;
|
|
640
874
|
var ITEM_FIELD_PREFIX = "item:";
|
|
875
|
+
var DELETED_ITEM = Buffer.from("ferricstore:langgraph:item:deleted:v2", "utf8");
|
|
641
876
|
var FerricStoreStore = class extends import_langgraph2.BaseStore {
|
|
642
877
|
client;
|
|
643
878
|
keyPrefix;
|
|
@@ -660,7 +895,7 @@ var FerricStoreStore = class extends import_langgraph2.BaseStore {
|
|
|
660
895
|
validatePut(operation);
|
|
661
896
|
return this.itemLockKey(operation.namespace, operation.key);
|
|
662
897
|
});
|
|
663
|
-
return await withMutationLocks(this.client, lockKeys, async () => {
|
|
898
|
+
return await withMutationLocks(this.client, lockKeys, async (lease) => {
|
|
664
899
|
const results = [];
|
|
665
900
|
const puts = /* @__PURE__ */ new Map();
|
|
666
901
|
for (const operation of operations) {
|
|
@@ -677,7 +912,7 @@ var FerricStoreStore = class extends import_langgraph2.BaseStore {
|
|
|
677
912
|
throw new TypeError("unsupported LangGraph store operation");
|
|
678
913
|
}
|
|
679
914
|
}
|
|
680
|
-
for (const operation of puts.values()) await this.putOperation(operation);
|
|
915
|
+
for (const operation of puts.values()) await this.putOperation(operation, lease);
|
|
681
916
|
return results;
|
|
682
917
|
}, this.lockOptions);
|
|
683
918
|
}
|
|
@@ -688,47 +923,45 @@ var FerricStoreStore = class extends import_langgraph2.BaseStore {
|
|
|
688
923
|
return `${this.keyPrefix}:{lgs:${sha2562(namespaceIdentity(namespace))}}:namespace`;
|
|
689
924
|
}
|
|
690
925
|
itemLockKey(namespace, key) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
keyBytes
|
|
696
|
-
]))}}:mutation-lock`;
|
|
926
|
+
return `${this.keyPrefix}:{lgsi:${itemIdentityDigest(namespace, key)}}:mutation-lock`;
|
|
927
|
+
}
|
|
928
|
+
itemDataKey(namespace, key) {
|
|
929
|
+
return `${this.keyPrefix}:{lgsi:${itemIdentityDigest(namespace, key)}}:atomic-item`;
|
|
697
930
|
}
|
|
698
931
|
async getOperation(operation) {
|
|
699
932
|
validateNamespace(operation.namespace);
|
|
700
933
|
if (typeof operation.key !== "string") throw new TypeError("store key must be text");
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
this.namespaceKey(operation.namespace),
|
|
704
|
-
itemField(operation.key)
|
|
705
|
-
);
|
|
706
|
-
return value == null ? null : decodeItem(value);
|
|
934
|
+
const snapshot = await this.readItemSnapshot(operation.namespace, operation.key);
|
|
935
|
+
return snapshot.record == null ? null : itemFromRecord(snapshot.record);
|
|
707
936
|
}
|
|
708
|
-
async putOperation(operation) {
|
|
937
|
+
async putOperation(operation, lease) {
|
|
709
938
|
validatePut(operation);
|
|
710
|
-
const
|
|
711
|
-
const field = itemField(operation.key);
|
|
939
|
+
const dataKey = this.itemDataKey(operation.namespace, operation.key);
|
|
712
940
|
const locator = catalogMember(operation.namespace, operation.key);
|
|
713
941
|
if (operation.value == null) {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
942
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
943
|
+
const snapshot = await this.readItemSnapshot(operation.namespace, operation.key);
|
|
944
|
+
if (snapshot.expected?.equals(DELETED_ITEM) === true) return;
|
|
945
|
+
if (await lease.compareAndSet(dataKey, snapshot.expected, DELETED_ITEM)) return;
|
|
946
|
+
}
|
|
947
|
+
throw new Error("concurrent FerricStore LangGraph store deletion did not converge");
|
|
717
948
|
}
|
|
718
949
|
const storedValue = snapshotJsonValue(operation.value);
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
950
|
+
await lease.publish("ZADD", this.catalogKey(), 0, locator);
|
|
951
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
952
|
+
const snapshot = await this.readItemSnapshot(operation.namespace, operation.key);
|
|
953
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
954
|
+
const record = {
|
|
955
|
+
createdAt: snapshot.record?.createdAt ?? now,
|
|
956
|
+
formatVersion: FORMAT_VERSION2,
|
|
957
|
+
key: operation.key,
|
|
958
|
+
namespace: [...operation.namespace],
|
|
959
|
+
updatedAt: now,
|
|
960
|
+
value: storedValue
|
|
961
|
+
};
|
|
962
|
+
if (await lease.compareAndSet(dataKey, snapshot.expected, encodeItem(record))) return;
|
|
963
|
+
}
|
|
964
|
+
throw new Error("concurrent FerricStore LangGraph store mutation did not converge");
|
|
732
965
|
}
|
|
733
966
|
async searchOperation(operation) {
|
|
734
967
|
validateNamespacePrefix(operation.namespacePrefix);
|
|
@@ -766,8 +999,23 @@ var FerricStoreStore = class extends import_langgraph2.BaseStore {
|
|
|
766
999
|
return [...namespaces.values()].sort(compareNamespaces).slice(offset, offset + limit);
|
|
767
1000
|
}
|
|
768
1001
|
async readCatalogItem(namespace, key) {
|
|
769
|
-
const
|
|
770
|
-
return
|
|
1002
|
+
const snapshot = await this.readItemSnapshot(namespace, key);
|
|
1003
|
+
return snapshot.record == null ? null : itemFromRecord(snapshot.record);
|
|
1004
|
+
}
|
|
1005
|
+
async readItemSnapshot(namespace, key) {
|
|
1006
|
+
const expected = await readAtomicValue(
|
|
1007
|
+
this.client,
|
|
1008
|
+
this.itemDataKey(namespace, key),
|
|
1009
|
+
"LangGraph atomic store item"
|
|
1010
|
+
);
|
|
1011
|
+
if (expected != null) {
|
|
1012
|
+
return {
|
|
1013
|
+
expected,
|
|
1014
|
+
record: expected.equals(DELETED_ITEM) ? null : decodeItemRecord(expected)
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
const legacy = await this.client.command("HGET", this.namespaceKey(namespace), itemField(key));
|
|
1018
|
+
return { expected, record: legacy == null ? null : decodeItemRecord(legacy) };
|
|
771
1019
|
}
|
|
772
1020
|
async *catalogLocators() {
|
|
773
1021
|
let offset = 0;
|
|
@@ -867,8 +1115,7 @@ function snapshotJsonValue(value) {
|
|
|
867
1115
|
function encodeItem(record) {
|
|
868
1116
|
return Buffer.from(JSON.stringify(record), "utf8");
|
|
869
1117
|
}
|
|
870
|
-
function
|
|
871
|
-
const record = decodeItemRecord(value);
|
|
1118
|
+
function itemFromRecord(record) {
|
|
872
1119
|
return {
|
|
873
1120
|
createdAt: new Date(record.createdAt),
|
|
874
1121
|
key: record.key,
|
|
@@ -877,6 +1124,14 @@ function decodeItem(value) {
|
|
|
877
1124
|
value: record.value
|
|
878
1125
|
};
|
|
879
1126
|
}
|
|
1127
|
+
function itemIdentityDigest(namespace, key) {
|
|
1128
|
+
const keyBytes = Buffer.from(key, "utf8");
|
|
1129
|
+
return sha2562(Buffer.concat([
|
|
1130
|
+
namespaceIdentity(namespace),
|
|
1131
|
+
uint64(keyBytes.length),
|
|
1132
|
+
keyBytes
|
|
1133
|
+
]));
|
|
1134
|
+
}
|
|
880
1135
|
function decodeItemRecord(value) {
|
|
881
1136
|
if (!(typeof value === "string" || Buffer.isBuffer(value) || value instanceof Uint8Array)) {
|
|
882
1137
|
throw new TypeError("FerricStore returned a non-binary LangGraph store item");
|
|
@@ -1143,13 +1398,22 @@ var LangGraphFlow = class {
|
|
|
1143
1398
|
this.options = { ...options };
|
|
1144
1399
|
}
|
|
1145
1400
|
async config(flow, graphContext) {
|
|
1401
|
+
const invokeOptions = this.options.invokeOptions ?? {};
|
|
1146
1402
|
const additional = await this.options.config?.(flow) ?? {};
|
|
1403
|
+
const baseConfigurable = invokeOptions.configurable ?? {};
|
|
1404
|
+
if (typeof baseConfigurable !== "object" || Array.isArray(baseConfigurable)) {
|
|
1405
|
+
throw new TypeError("LangGraph invokeOptions configurable must be an object");
|
|
1406
|
+
}
|
|
1147
1407
|
const rawConfigurable = additional.configurable ?? {};
|
|
1148
|
-
if (
|
|
1408
|
+
if (typeof rawConfigurable !== "object" || Array.isArray(rawConfigurable)) {
|
|
1149
1409
|
throw new TypeError("LangGraph config configurable must be an object");
|
|
1150
1410
|
}
|
|
1411
|
+
const baseMetadata = invokeOptions.metadata ?? {};
|
|
1412
|
+
if (typeof baseMetadata !== "object" || Array.isArray(baseMetadata)) {
|
|
1413
|
+
throw new TypeError("LangGraph invokeOptions metadata must be an object");
|
|
1414
|
+
}
|
|
1151
1415
|
const rawMetadata = additional.metadata ?? {};
|
|
1152
|
-
if (
|
|
1416
|
+
if (typeof rawMetadata !== "object" || Array.isArray(rawMetadata)) {
|
|
1153
1417
|
throw new TypeError("LangGraph config metadata must be an object");
|
|
1154
1418
|
}
|
|
1155
1419
|
const threadId = requireText(
|
|
@@ -1162,13 +1426,19 @@ var LangGraphFlow = class {
|
|
|
1162
1426
|
"checkpointNs",
|
|
1163
1427
|
true
|
|
1164
1428
|
);
|
|
1165
|
-
const context = graphContext === void 0 ? this.options.context
|
|
1429
|
+
const context = graphContext === void 0 ? this.options.context != null ? await this.options.context(flow) : Object.hasOwn(additional, "context") ? additional.context : Object.hasOwn(invokeOptions, "context") ? invokeOptions.context : new LangGraphFlowContext(flow, threadId, checkpointNs) : graphContext;
|
|
1166
1430
|
return {
|
|
1167
|
-
...
|
|
1431
|
+
...invokeOptions,
|
|
1168
1432
|
...additional,
|
|
1169
|
-
configurable: {
|
|
1433
|
+
configurable: {
|
|
1434
|
+
...baseConfigurable,
|
|
1435
|
+
...rawConfigurable,
|
|
1436
|
+
checkpoint_ns: checkpointNs,
|
|
1437
|
+
thread_id: threadId
|
|
1438
|
+
},
|
|
1170
1439
|
context,
|
|
1171
1440
|
metadata: {
|
|
1441
|
+
...baseMetadata,
|
|
1172
1442
|
...rawMetadata,
|
|
1173
1443
|
ferricflow_id: flow.id,
|
|
1174
1444
|
ferricflow_state: flow.logicalState,
|
|
@@ -1184,7 +1454,7 @@ var LangGraphFlow = class {
|
|
|
1184
1454
|
if ((this.options.recoverExisting ?? true) && this.graph.getState != null) {
|
|
1185
1455
|
hasCheckpoint = snapshotHasCheckpoint(await this.graph.getState(config));
|
|
1186
1456
|
}
|
|
1187
|
-
input = hasCheckpoint ? null :
|
|
1457
|
+
input = hasCheckpoint ? null : this.options.input == null ? flow.payload : await this.options.input(flow);
|
|
1188
1458
|
}
|
|
1189
1459
|
const value = await this.graph.invoke(input, config);
|
|
1190
1460
|
const configurable = config.configurable;
|