@ferricstore/ferricstore 0.11.11 → 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.
Files changed (87) hide show
  1. package/README.md +19 -1
  2. package/dist/durability-DlDCsdlo.d.cts +16 -0
  3. package/dist/durability-DplL0SbW.d.ts +16 -0
  4. package/dist/index.cjs +1 -1
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +5 -162
  7. package/dist/index.d.ts +5 -162
  8. package/dist/index.js +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal-lEEDZpPH.d.cts +4 -0
  11. package/dist/internal-lEEDZpPH.d.ts +4 -0
  12. package/dist/langgraph.cjs +1548 -0
  13. package/dist/langgraph.cjs.map +1 -0
  14. package/dist/langgraph.d.cts +158 -0
  15. package/dist/langgraph.d.ts +158 -0
  16. package/dist/langgraph.js +1522 -0
  17. package/dist/langgraph.js.map +1 -0
  18. package/dist/openai-agents.cjs +717 -0
  19. package/dist/openai-agents.cjs.map +1 -0
  20. package/dist/openai-agents.d.cts +50 -0
  21. package/dist/openai-agents.d.ts +50 -0
  22. package/dist/openai-agents.js +692 -0
  23. package/dist/openai-agents.js.map +1 -0
  24. package/dist/outcomes-BbFDp3AH.d.ts +160 -0
  25. package/dist/outcomes-DmBwnq0Y.d.cts +160 -0
  26. package/docs/agent-api/assets/hierarchy.js +1 -0
  27. package/docs/agent-api/assets/highlight.css +92 -0
  28. package/docs/agent-api/assets/icons.js +18 -0
  29. package/docs/agent-api/assets/icons.svg +1 -0
  30. package/docs/agent-api/assets/main.js +60 -0
  31. package/docs/agent-api/assets/navigation.js +1 -0
  32. package/docs/agent-api/assets/search.js +1 -0
  33. package/docs/agent-api/assets/style.css +1648 -0
  34. package/docs/agent-api/classes/langgraph.FerricStoreSaver.html +297 -0
  35. package/docs/agent-api/classes/langgraph.FerricStoreStore.html +298 -0
  36. package/docs/agent-api/classes/langgraph.LangGraphFlow.html +190 -0
  37. package/docs/agent-api/classes/langgraph.LangGraphFlowContext.html +158 -0
  38. package/docs/agent-api/classes/langgraph.LangGraphFlowRun.html +133 -0
  39. package/docs/agent-api/classes/openai-agents.FerricStoreSession.html +241 -0
  40. package/docs/agent-api/hierarchy.html +44 -0
  41. package/docs/agent-api/index.html +142 -0
  42. package/docs/agent-api/interfaces/langgraph.FerricFlowHandlerContext.html +94 -0
  43. package/docs/agent-api/interfaces/langgraph.FerricStoreCommandClient.html +77 -0
  44. package/docs/agent-api/interfaces/langgraph.FerricStoreLockOptions.html +81 -0
  45. package/docs/agent-api/interfaces/langgraph.FerricStoreSaverOptions.html +106 -0
  46. package/docs/agent-api/interfaces/langgraph.FerricStoreStoreOptions.html +98 -0
  47. package/docs/agent-api/interfaces/langgraph.InvokableLangGraph.html +83 -0
  48. package/docs/agent-api/interfaces/langgraph.LangGraphFlowOptions.html +116 -0
  49. package/docs/agent-api/interfaces/openai-agents.FerricStoreSessionOptions.html +114 -0
  50. package/docs/agent-api/interfaces/openai-agents.Session.html +208 -0
  51. package/docs/agent-api/interfaces/openai-agents.SessionHistoryRewriteAwareSession.html +230 -0
  52. package/docs/agent-api/interfaces/openai-agents.SessionHistoryTransactionAwareSession.html +237 -0
  53. package/docs/agent-api/modules/langgraph.html +44 -0
  54. package/docs/agent-api/modules/openai-agents.html +44 -0
  55. package/docs/agent-api/modules.html +35 -0
  56. package/docs/agent-api/types/langgraph.LangGraphChannelVersions.html +33 -0
  57. package/docs/agent-api/types/langgraph.LangGraphInvocationConfig.html +33 -0
  58. package/docs/agent-api/types/langgraph.LangGraphOutcomeMapper.html +50 -0
  59. package/docs/agent-api/types/langgraph.LangGraphPendingWrite.html +33 -0
  60. package/docs/agent-api/types/openai-agents.AgentInputItem.html +35 -0
  61. package/docs/agent-frameworks.md +176 -0
  62. package/docs/api/assets/highlight.css +12 -12
  63. package/docs/api/classes/ClaimHydrationError.html +2 -2
  64. package/docs/api/classes/ConnectionClosedError.html +2 -2
  65. package/docs/api/classes/FerricStoreError.html +2 -2
  66. package/docs/api/classes/FlowAlreadyExistsError.html +2 -2
  67. package/docs/api/classes/FlowBatchError.html +2 -2
  68. package/docs/api/classes/FlowNotFoundError.html +2 -2
  69. package/docs/api/classes/FlowQueryError.html +2 -2
  70. package/docs/api/classes/FlowWrongStateError.html +2 -2
  71. package/docs/api/classes/HTTPTransportError.html +2 -2
  72. package/docs/api/classes/InvalidCommandError.html +2 -2
  73. package/docs/api/classes/LeaseRenewalError.html +2 -2
  74. package/docs/api/classes/LockHeldError.html +2 -2
  75. package/docs/api/classes/LockNotOwnedError.html +2 -2
  76. package/docs/api/classes/OverloadedError.html +2 -2
  77. package/docs/api/classes/QueueCompletionError.html +2 -2
  78. package/docs/api/classes/RequestTimeoutError.html +2 -2
  79. package/docs/api/classes/RerouteError.html +2 -2
  80. package/docs/api/classes/StaleLeaseError.html +2 -2
  81. package/docs/api/classes/StalePolicyGenerationError.html +2 -2
  82. package/docs/api/index.html +37 -24
  83. package/docs/api/media/agent-frameworks.md +176 -0
  84. package/docs/api/media/langgraph.ts +23 -0
  85. package/docs/api/media/openai-agents-session.ts +13 -0
  86. package/docs/api/variables/FERRICSTORE_SDK_VERSION.html +1 -1
  87. package/package.json +43 -4
@@ -0,0 +1,692 @@
1
+ // src/openai-agents.ts
2
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
3
+
4
+ // src/agent-persistence/durability.ts
5
+ import { randomUUID } from "crypto";
6
+ import { setTimeout as delay } from "timers/promises";
7
+
8
+ // src/errors.ts
9
+ var FerricStoreError = class extends Error {
10
+ code = "ferricstore_error";
11
+ raw;
12
+ retryable;
13
+ safeToRetry;
14
+ retryAfterMs;
15
+ constructor(message, options = {}) {
16
+ super(message, { cause: options.cause });
17
+ this.name = new.target.name;
18
+ this.raw = options.raw;
19
+ this.retryable = options.retryable ?? structuredBooleanField(options.raw, "retryable");
20
+ this.safeToRetry = options.safeToRetry ?? structuredBooleanField(options.raw, "safe_to_retry");
21
+ this.retryAfterMs = options.retryAfterMs ?? structuredIntegerField(options.raw, "retry_after_ms");
22
+ }
23
+ };
24
+ var LockHeldError = class extends FerricStoreError {
25
+ code = "lock_held";
26
+ };
27
+ function structuredIntegerField(raw, name) {
28
+ const value = structuredField(raw, name);
29
+ if (typeof value === "number") {
30
+ return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
31
+ }
32
+ if (typeof value === "bigint") {
33
+ return value >= 0n && value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : void 0;
34
+ }
35
+ const text = binaryText(value);
36
+ return text == null ? void 0 : nonNegativeSafeIntegerText(text);
37
+ }
38
+ function structuredBooleanField(raw, name) {
39
+ const value = structuredField(raw, name);
40
+ if (typeof value === "boolean") return value;
41
+ const text = binaryText(value)?.toLowerCase();
42
+ if (text === "true" || text === "1") return true;
43
+ if (text === "false" || text === "0") return false;
44
+ return void 0;
45
+ }
46
+ function nonNegativeSafeIntegerText(value) {
47
+ if (!/^[0-9]+$/u.test(value)) return void 0;
48
+ const parsed = Number.parseInt(value, 10);
49
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
50
+ }
51
+ function structuredField(raw, name) {
52
+ if (raw instanceof Map) {
53
+ if (raw.has(name)) return raw.get(name);
54
+ for (const [key, value] of raw.entries()) {
55
+ if (binaryText(key) === name) return value;
56
+ }
57
+ return void 0;
58
+ }
59
+ if (typeof raw === "object" && raw != null && Object.hasOwn(raw, name)) {
60
+ return raw[name];
61
+ }
62
+ return void 0;
63
+ }
64
+ function binaryText(value) {
65
+ if (typeof value === "string") return value;
66
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array) return Buffer.from(value).toString("utf8");
67
+ return void 0;
68
+ }
69
+
70
+ // src/agent-persistence/durability.ts
71
+ var DEFAULT_LOCK_OPTIONS = {
72
+ lockRetryMs: 10,
73
+ lockTtlMs: 3e5,
74
+ lockWaitMs: 3e4
75
+ };
76
+ function normalizeKeyPrefix(value, defaultValue) {
77
+ const prefix = value.length === 0 ? defaultValue : value;
78
+ if (prefix.includes("\0")) throw new TypeError("keyPrefix must not contain NUL bytes");
79
+ const normalized = prefix.replace(/:+$/u, "");
80
+ if (normalized.length === 0) throw new TypeError("keyPrefix must contain a character other than ':'");
81
+ return normalized;
82
+ }
83
+ function positiveInteger(value, fallback, name) {
84
+ const normalized = value ?? fallback;
85
+ if (!Number.isSafeInteger(normalized) || normalized <= 0) {
86
+ throw new TypeError(`${name} must be a positive safe integer`);
87
+ }
88
+ return normalized;
89
+ }
90
+ function nonNegativeInteger(value, fallback, name) {
91
+ const normalized = value ?? fallback;
92
+ if (!Number.isSafeInteger(normalized) || normalized < 0) {
93
+ throw new TypeError(`${name} must be a non-negative safe integer`);
94
+ }
95
+ return normalized;
96
+ }
97
+ function textResponse(value, name) {
98
+ if (typeof value === "string") return value;
99
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array) return Buffer.from(value).toString("utf8");
100
+ throw new TypeError(`FerricStore returned an invalid ${name}`);
101
+ }
102
+ function integerResponse(value, name) {
103
+ const parsed = typeof value === "number" ? value : Number(textResponse(value, name));
104
+ if (!Number.isSafeInteger(parsed)) throw new TypeError(`FerricStore returned an invalid ${name}`);
105
+ return parsed;
106
+ }
107
+ async function readAtomicValue(client, key, name) {
108
+ const value = await client.command("GET", key);
109
+ if (value == null) return void 0;
110
+ if (typeof value === "string") return Buffer.from(value, "utf8");
111
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array) return Buffer.from(value);
112
+ throw new TypeError(`FerricStore returned a non-binary ${name}`);
113
+ }
114
+ async function compareAndSetAtomicValue(client, key, expected, value) {
115
+ if (expected == null) {
116
+ const response2 = await client.command("SET", key, value, "NX");
117
+ if (response2 == null || response2 === false) return false;
118
+ if (response2 === true) return true;
119
+ return textResponse(response2, "SET NX response").toUpperCase() === "OK";
120
+ }
121
+ const response = await client.command("CAS", key, expected, value);
122
+ if (response == null || response === false) return false;
123
+ if (response === true) return true;
124
+ return integerResponse(response, "CAS response") === 1;
125
+ }
126
+ async function withMutationLocks(client, keys, operation, options = {}) {
127
+ const orderedKeys = [...new Set(keys)].sort();
128
+ if (orderedKeys.length === 0) {
129
+ const signal = new AbortController().signal;
130
+ return await operation({
131
+ signal,
132
+ assertOwned: () => void 0,
133
+ publish: async (...args) => await additiveCommand(client, args),
134
+ compareAndSet: async (key, expected, value) => await compareAndSetAtomicValue(client, key, expected, value)
135
+ });
136
+ }
137
+ const normalized = {
138
+ lockRetryMs: positiveInteger(options.lockRetryMs, DEFAULT_LOCK_OPTIONS.lockRetryMs, "lockRetryMs"),
139
+ lockTtlMs: positiveInteger(options.lockTtlMs, DEFAULT_LOCK_OPTIONS.lockTtlMs, "lockTtlMs"),
140
+ lockWaitMs: nonNegativeInteger(options.lockWaitMs, DEFAULT_LOCK_OPTIONS.lockWaitMs, "lockWaitMs")
141
+ };
142
+ if (normalized.lockRetryMs >= normalized.lockTtlMs) {
143
+ throw new TypeError("lockRetryMs must be less than lockTtlMs");
144
+ }
145
+ const owner = randomUUID();
146
+ const acquired = [];
147
+ const deadline = performance.now() + normalized.lockWaitMs;
148
+ let primaryError;
149
+ let heartbeatError;
150
+ let releaseError;
151
+ let result;
152
+ let operationCompleted = false;
153
+ let conditionalCommitCompleted = false;
154
+ const heartbeatAbort = new AbortController();
155
+ const ownershipAbort = new AbortController();
156
+ const lastExtended = /* @__PURE__ */ new Map();
157
+ const loseOwnership = (error) => {
158
+ const normalizedError = errorObject(error);
159
+ heartbeatError ??= normalizedError;
160
+ if (!ownershipAbort.signal.aborted) ownershipAbort.abort(normalizedError);
161
+ return normalizedError;
162
+ };
163
+ const assertOwned = () => {
164
+ if (heartbeatError != null) throw errorObject(heartbeatError);
165
+ if (ownershipAbort.signal.aborted) throw errorObject(ownershipAbort.signal.reason);
166
+ };
167
+ const renewOwned = async () => {
168
+ assertOwned();
169
+ for (const key of acquired) {
170
+ try {
171
+ const response = await client.command("EXTEND", key, owner, normalized.lockTtlMs);
172
+ if (integerResponse(response, "EXTEND response") !== 1) {
173
+ throw new Error(`lost FerricStore lock ${JSON.stringify(key)} while mutating data`);
174
+ }
175
+ lastExtended.set(key, performance.now());
176
+ } catch (error) {
177
+ throw loseOwnership(new Error(
178
+ `could not validate FerricStore lock ${JSON.stringify(key)} before mutating data`,
179
+ { cause: error }
180
+ ));
181
+ }
182
+ }
183
+ assertOwned();
184
+ };
185
+ const lease = {
186
+ signal: ownershipAbort.signal,
187
+ assertOwned,
188
+ publish: async (...args) => {
189
+ await renewOwned();
190
+ const response = await additiveCommand(client, args);
191
+ assertOwned();
192
+ return response;
193
+ },
194
+ compareAndSet: async (key, expected, value) => {
195
+ await renewOwned();
196
+ const committed = await compareAndSetAtomicValue(client, key, expected, value);
197
+ if (committed) conditionalCommitCompleted = true;
198
+ else assertOwned();
199
+ return committed;
200
+ }
201
+ };
202
+ try {
203
+ for (const key of orderedKeys) {
204
+ while (!await tryAcquireLock(client, key, owner, normalized.lockTtlMs)) {
205
+ if (performance.now() >= deadline) {
206
+ throw new Error(`timed out acquiring FerricStore lock ${JSON.stringify(key)}`);
207
+ }
208
+ await extendAcquiredLocks(client, acquired, owner, normalized.lockTtlMs);
209
+ await delay(normalized.lockRetryMs);
210
+ }
211
+ acquired.push(key);
212
+ }
213
+ await extendAcquiredLocks(client, acquired, owner, normalized.lockTtlMs);
214
+ for (const key of acquired) lastExtended.set(key, performance.now());
215
+ const heartbeat = renewLocks(
216
+ client,
217
+ acquired,
218
+ owner,
219
+ normalized.lockTtlMs,
220
+ lastExtended,
221
+ heartbeatAbort.signal,
222
+ (error) => {
223
+ loseOwnership(error);
224
+ }
225
+ );
226
+ try {
227
+ result = await operation(lease);
228
+ operationCompleted = true;
229
+ } catch (error) {
230
+ primaryError = error;
231
+ } finally {
232
+ heartbeatAbort.abort();
233
+ await heartbeat;
234
+ }
235
+ } catch (error) {
236
+ primaryError ??= error;
237
+ } finally {
238
+ heartbeatAbort.abort();
239
+ for (const key of acquired.reverse()) {
240
+ try {
241
+ await client.command("UNLOCK", key, owner);
242
+ } catch (error) {
243
+ releaseError ??= error;
244
+ }
245
+ }
246
+ }
247
+ if (primaryError != null) throw errorObject(primaryError);
248
+ if (heartbeatError != null && !conditionalCommitCompleted) throw errorObject(heartbeatError);
249
+ if (releaseError != null && !(heartbeatError != null && conditionalCommitCompleted)) {
250
+ throw errorObject(releaseError);
251
+ }
252
+ if (!operationCompleted) throw new Error("FerricStore mutation did not complete");
253
+ return result;
254
+ }
255
+ async function additiveCommand(client, args) {
256
+ const rawName = args[0];
257
+ const name = typeof rawName === "string" ? rawName.toUpperCase() : Buffer.isBuffer(rawName) || rawName instanceof Uint8Array ? Buffer.from(rawName).toString("utf8").toUpperCase() : "";
258
+ if (name !== "SADD" && name !== "ZADD") {
259
+ throw new TypeError("FerricStore mutation leases only publish add-only SADD or ZADD indexes");
260
+ }
261
+ if (name === "ZADD" && (args.length < 4 || args.length % 2 !== 0 || args.slice(2).some((value, index) => index % 2 === 0 && Number(value) !== 0))) {
262
+ throw new TypeError("FerricStore mutation leases only publish zero-score ZADD indexes");
263
+ }
264
+ return await client.command(...args);
265
+ }
266
+ async function extendAcquiredLocks(client, keys, owner, ttlMs) {
267
+ for (const key of keys) {
268
+ const response = await client.command("EXTEND", key, owner, ttlMs);
269
+ if (integerResponse(response, "EXTEND response") !== 1) {
270
+ throw new Error(`lost FerricStore lock ${JSON.stringify(key)} before mutating data`);
271
+ }
272
+ }
273
+ }
274
+ async function tryAcquireLock(client, key, owner, ttlMs) {
275
+ try {
276
+ const response = await client.command("LOCK", key, owner, ttlMs);
277
+ return response === true || response === "OK" || Buffer.isBuffer(response) && response.equals(Buffer.from("OK"));
278
+ } catch (error) {
279
+ if (error instanceof LockHeldError) return false;
280
+ throw error;
281
+ }
282
+ }
283
+ async function renewLocks(client, keys, owner, ttlMs, lastExtended, signal, onError) {
284
+ const intervalMs = Math.max(Math.floor(ttlMs / 3), 1);
285
+ const retryMs = Math.min(Math.max(Math.floor(intervalMs / 10), 1), 1e3);
286
+ let waitMs = intervalMs;
287
+ while (!signal.aborted) {
288
+ try {
289
+ await delay(waitMs, void 0, { signal });
290
+ } catch (error) {
291
+ if (signal.aborted) return;
292
+ onError(error);
293
+ return;
294
+ }
295
+ const now = performance.now();
296
+ let retry = false;
297
+ for (const key of keys) {
298
+ try {
299
+ const response = await client.command("EXTEND", key, owner, ttlMs);
300
+ if (integerResponse(response, "EXTEND response") !== 1) {
301
+ onError(new Error(`lost FerricStore lock ${JSON.stringify(key)} while mutating data`));
302
+ return;
303
+ }
304
+ lastExtended.set(key, now);
305
+ } catch (error) {
306
+ if (now - (lastExtended.get(key) ?? 0) >= ttlMs) {
307
+ onError(new Error(`lost FerricStore lock ${JSON.stringify(key)} while mutating data`, { cause: error }));
308
+ return;
309
+ }
310
+ retry = true;
311
+ }
312
+ }
313
+ waitMs = retry ? retryMs : intervalMs;
314
+ }
315
+ }
316
+ function errorObject(value) {
317
+ return value instanceof Error ? value : new Error("FerricStore mutation failed", { cause: value });
318
+ }
319
+
320
+ // src/agent-persistence/snapshot.ts
321
+ import { createHash } from "crypto";
322
+ function encodeSnapshot(value) {
323
+ return encodeSnapshotWith(value, defaultKeyComparator);
324
+ }
325
+ function decodeSnapshot(value, name) {
326
+ const bytes = typeof value === "string" ? Buffer.from(value, "utf8") : Buffer.from(asBytes(value, name));
327
+ let encoded;
328
+ try {
329
+ encoded = JSON.parse(bytes.toString("utf8"));
330
+ } catch (error) {
331
+ throw new Error(`FerricStore returned an invalid ${name}`, { cause: error });
332
+ }
333
+ return restore(encoded, name);
334
+ }
335
+ function snapshotDigest(value) {
336
+ return createHash("sha256").update(encodeSnapshot(value)).digest("hex");
337
+ }
338
+ function legacySnapshotDigest(value, locale) {
339
+ return createHash("sha256").update(encodeSnapshotWith(value, (left, right) => left.localeCompare(right, locale))).digest("hex");
340
+ }
341
+ function cloneSnapshot(value) {
342
+ return decodeSnapshot(encodeSnapshot(value), "snapshot");
343
+ }
344
+ function snapshotsEqual(left, right) {
345
+ return encodeSnapshot(left).equals(encodeSnapshot(right));
346
+ }
347
+ function encodeSnapshotWith(value, compareKeys) {
348
+ return Buffer.from(JSON.stringify(snapshot(value, /* @__PURE__ */ new WeakSet(), compareKeys)), "utf8");
349
+ }
350
+ function defaultKeyComparator(left, right) {
351
+ return left < right ? -1 : left > right ? 1 : 0;
352
+ }
353
+ function snapshot(value, ancestors, compareKeys) {
354
+ if (value === null) return ["null"];
355
+ if (value === void 0) return ["undefined"];
356
+ if (typeof value === "string") return ["string", value];
357
+ if (typeof value === "boolean") return ["boolean", value];
358
+ if (typeof value === "number") {
359
+ if (Object.is(value, -0)) return ["number", "-0"];
360
+ if (Number.isNaN(value)) return ["number", "NaN"];
361
+ if (value === Infinity) return ["number", "+Infinity"];
362
+ if (value === -Infinity) return ["number", "-Infinity"];
363
+ return ["number", value];
364
+ }
365
+ if (typeof value !== "object") throw new TypeError("session history contains unsupported data");
366
+ if (value instanceof Uint8Array) {
367
+ const keys = Reflect.ownKeys(value);
368
+ if (keys.length !== value.length || keys.some((key) => typeof key !== "string" || !isArrayIndex(key, value.length))) {
369
+ throw new TypeError("session history binary data contains custom properties");
370
+ }
371
+ return ["binary", Buffer.from(value).toString("base64")];
372
+ }
373
+ if (ancestors.has(value)) throw new TypeError("session history contains cyclic data");
374
+ ancestors.add(value);
375
+ try {
376
+ if (Array.isArray(value)) {
377
+ const keys2 = Reflect.ownKeys(value);
378
+ if (keys2.length !== value.length + 1 || keys2.some((key) => typeof key !== "string" || key !== "length" && !isArrayIndex(key, value.length))) {
379
+ throw new TypeError("session history contains a sparse or customized array");
380
+ }
381
+ const items = [];
382
+ for (let index = 0; index < value.length; index += 1) {
383
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
384
+ if (descriptor == null || !descriptor.enumerable || !("value" in descriptor)) {
385
+ throw new TypeError("session history contains an unsupported array item");
386
+ }
387
+ items.push(snapshot(descriptor.value, ancestors, compareKeys));
388
+ }
389
+ return ["array", items];
390
+ }
391
+ const prototype = Object.getPrototypeOf(value);
392
+ if (prototype !== Object.prototype && prototype !== null) {
393
+ throw new TypeError("session history contains an unsupported object");
394
+ }
395
+ const keys = Reflect.ownKeys(value);
396
+ if (keys.some((key) => typeof key !== "string")) {
397
+ throw new TypeError("session history contains a symbol property");
398
+ }
399
+ const entries = [];
400
+ for (const key of keys.sort(compareKeys)) {
401
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
402
+ if (descriptor == null || !descriptor.enumerable || !("value" in descriptor)) {
403
+ throw new TypeError("session history contains an unsupported property");
404
+ }
405
+ entries.push([key, snapshot(descriptor.value, ancestors, compareKeys)]);
406
+ }
407
+ return ["object", entries];
408
+ } finally {
409
+ ancestors.delete(value);
410
+ }
411
+ }
412
+ function restore(value, name) {
413
+ if (!Array.isArray(value) || typeof value[0] !== "string") throw new TypeError(`invalid ${name}`);
414
+ switch (value[0]) {
415
+ case "null":
416
+ return null;
417
+ case "undefined":
418
+ return void 0;
419
+ case "string":
420
+ return requireValueType(value[1], "string", name);
421
+ case "boolean":
422
+ return requireValueType(value[1], "boolean", name);
423
+ case "binary":
424
+ return Buffer.from(requireValueType(value[1], "string", name), "base64");
425
+ case "number":
426
+ return restoreNumber(value[1], name);
427
+ case "array": {
428
+ if (!Array.isArray(value[1])) throw new TypeError(`invalid ${name}`);
429
+ return value[1].map((item) => restore(item, name));
430
+ }
431
+ case "object": {
432
+ if (!Array.isArray(value[1])) throw new TypeError(`invalid ${name}`);
433
+ const result = {};
434
+ for (const entry of value[1]) {
435
+ if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string") {
436
+ throw new TypeError(`invalid ${name}`);
437
+ }
438
+ Object.defineProperty(result, entry[0], {
439
+ configurable: true,
440
+ enumerable: true,
441
+ value: restore(entry[1], name),
442
+ writable: true
443
+ });
444
+ }
445
+ return result;
446
+ }
447
+ default:
448
+ throw new TypeError(`invalid ${name}`);
449
+ }
450
+ }
451
+ function restoreNumber(value, name) {
452
+ if (typeof value === "number") return value;
453
+ if (value === "NaN") return Number.NaN;
454
+ if (value === "+Infinity") return Infinity;
455
+ if (value === "-Infinity") return -Infinity;
456
+ if (value === "-0") return -0;
457
+ throw new TypeError(`invalid ${name}`);
458
+ }
459
+ function requireValueType(value, type, name) {
460
+ if (typeof value !== type) throw new TypeError(`invalid ${name}`);
461
+ return value;
462
+ }
463
+ function asBytes(value, name) {
464
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array) return value;
465
+ throw new TypeError(`FerricStore returned a non-binary ${name}`);
466
+ }
467
+ function isArrayIndex(key, length) {
468
+ if (!/^(?:0|[1-9]\d*)$/u.test(key)) return false;
469
+ const index = Number(key);
470
+ return Number.isSafeInteger(index) && index >= 0 && index < length;
471
+ }
472
+
473
+ // src/openai-agents.ts
474
+ var SESSION_FORMAT_VERSION = 1;
475
+ var SESSION_STATE_FIELD = "state";
476
+ var RECEIPT_DIGEST_VERSION = "v2:";
477
+ var FerricStoreSession = class {
478
+ client;
479
+ sessionId;
480
+ keyPrefix;
481
+ initialItems;
482
+ lockOptions;
483
+ legacyReceiptLocales;
484
+ sessionKey;
485
+ stateKey;
486
+ lockKey;
487
+ constructor(client, options = {}) {
488
+ this.client = client;
489
+ this.sessionId = options.sessionId ?? randomUUID2();
490
+ if (typeof this.sessionId !== "string" || this.sessionId.trim().length === 0) {
491
+ throw new TypeError("sessionId must be a non-empty string");
492
+ }
493
+ this.keyPrefix = normalizeKeyPrefix(options.keyPrefix ?? "openai:agents:session", "openai:agents:session");
494
+ this.initialItems = snapshotItems(options.initialItems ?? [], "initialItems");
495
+ this.lockOptions = {
496
+ lockRetryMs: options.lockRetryMs,
497
+ lockTtlMs: options.lockTtlMs,
498
+ lockWaitMs: options.lockWaitMs
499
+ };
500
+ if (options.legacyReceiptLocales != null && !Array.isArray(options.legacyReceiptLocales)) {
501
+ throw new TypeError("legacyReceiptLocales must be an array");
502
+ }
503
+ try {
504
+ this.legacyReceiptLocales = Intl.getCanonicalLocales(options.legacyReceiptLocales ?? []);
505
+ } catch (error) {
506
+ throw new TypeError("legacyReceiptLocales contains an invalid locale", { cause: error });
507
+ }
508
+ const digest = createHash2("sha256").update(this.sessionId, "utf8").digest("hex");
509
+ this.sessionKey = `${this.keyPrefix}:{oais:${digest}}:session`;
510
+ this.stateKey = `${this.sessionKey}:atomic-state`;
511
+ this.lockKey = `${this.keyPrefix}:{oais:${digest}}:mutation-lock`;
512
+ }
513
+ async getSessionId() {
514
+ await this.mutate(async (state) => state);
515
+ return this.sessionId;
516
+ }
517
+ async getItems(limit) {
518
+ if (limit != null && limit <= 0) return [];
519
+ if (limit != null && !Number.isSafeInteger(limit)) {
520
+ throw new TypeError("limit must be a safe integer");
521
+ }
522
+ const state = await this.readState();
523
+ const items = limit == null ? state.items : state.items.slice(Math.max(state.items.length - limit, 0));
524
+ return cloneSnapshot(items);
525
+ }
526
+ async addItems(items) {
527
+ if (items.length === 0) return;
528
+ const additions = snapshotItems(items, "items");
529
+ await this.mutate(async (state) => ({
530
+ ...state,
531
+ items: [...state.items, ...additions]
532
+ }));
533
+ }
534
+ async replaceHistoryWithCompaction(items) {
535
+ const replacement = snapshotItems(items, "items");
536
+ await this.mutate(async (state) => ({ ...state, items: replacement }));
537
+ }
538
+ async popItem() {
539
+ let popped;
540
+ await this.mutate(async (state) => {
541
+ popped = state.items.at(-1);
542
+ return popped == null ? state : { ...state, items: state.items.slice(0, -1) };
543
+ });
544
+ return popped == null ? void 0 : cloneSnapshot(popped);
545
+ }
546
+ async clearSession() {
547
+ await this.mutate(async (state) => ({ ...state, items: [], operations: {} }));
548
+ }
549
+ async applyHistoryMutations(args) {
550
+ if (args == null || !Array.isArray(args.mutations)) {
551
+ throw new TypeError("session history mutations are invalid");
552
+ }
553
+ if (args.mutations.length === 0) return;
554
+ const mutations = cloneSnapshot(args.mutations);
555
+ await this.mutate(async (state) => {
556
+ let items = cloneSnapshot(state.items);
557
+ for (const mutation of mutations) {
558
+ if (mutation.type !== "replace_function_call") {
559
+ throw new TypeError("unsupported session history mutation");
560
+ }
561
+ const replacement = snapshotItem(mutation.replacement, "mutation replacement");
562
+ let keptReplacement = false;
563
+ const next = [];
564
+ for (const item of items) {
565
+ if (item.type === "function_call" && item.callId === mutation.callId) {
566
+ if (!keptReplacement) {
567
+ next.push(replacement);
568
+ keptReplacement = true;
569
+ }
570
+ } else {
571
+ next.push(item);
572
+ }
573
+ }
574
+ items = next;
575
+ }
576
+ return { ...state, items };
577
+ });
578
+ }
579
+ async applyHistoryTransaction(args) {
580
+ const { operationId, transaction } = snapshotTransactionArgs(args);
581
+ const digest = `${RECEIPT_DIGEST_VERSION}${snapshotDigest(transaction)}`;
582
+ const legacyDigests = /* @__PURE__ */ new Set([
583
+ snapshotDigest(transaction),
584
+ legacySnapshotDigest(transaction),
585
+ ...this.legacyReceiptLocales.map((locale) => legacySnapshotDigest(transaction, locale))
586
+ ]);
587
+ await this.mutate(async (state) => {
588
+ const existing = Object.getOwnPropertyDescriptor(state.operations, operationId)?.value;
589
+ if (existing != null) {
590
+ if (typeof existing !== "string") throw new Error("corrupt session history operation receipt");
591
+ if (existing === digest) return state;
592
+ if (!legacyDigests.has(existing)) {
593
+ throw new Error("session history operation was already applied with a different transaction");
594
+ }
595
+ return { ...state, operations: { ...state.operations, [operationId]: digest } };
596
+ }
597
+ let items;
598
+ if (transaction.type === "append_items") {
599
+ items = [...state.items, ...transaction.items];
600
+ } else {
601
+ const suffixStart = state.items.length - transaction.expectedSuffix.length;
602
+ const actualSuffix = suffixStart < 0 ? [] : state.items.slice(suffixStart);
603
+ if (suffixStart < 0 || !snapshotsEqual(actualSuffix, transaction.expectedSuffix)) {
604
+ throw new Error("session history suffix no longer matches the transaction precondition");
605
+ }
606
+ items = [...state.items.slice(0, suffixStart), ...transaction.replacement];
607
+ }
608
+ return {
609
+ ...state,
610
+ items,
611
+ operations: { ...state.operations, [operationId]: digest }
612
+ };
613
+ });
614
+ }
615
+ async mutate(operation) {
616
+ await withMutationLocks(this.client, [this.lockKey], async (lease) => {
617
+ for (let attempt = 0; attempt < 8; attempt += 1) {
618
+ lease.assertOwned();
619
+ const snapshot2 = await this.readMutationState();
620
+ const next = await operation(snapshot2.state);
621
+ if (await lease.compareAndSet(this.stateKey, snapshot2.expected, encodeSnapshot(next))) return;
622
+ }
623
+ throw new Error("concurrent FerricStore OpenAI Agents session mutation did not converge");
624
+ }, this.lockOptions);
625
+ }
626
+ async readState() {
627
+ return (await this.readMutationState()).state;
628
+ }
629
+ async readMutationState() {
630
+ const expected = await readAtomicValue(this.client, this.stateKey, "OpenAI Agents atomic session state");
631
+ const value = expected ?? await this.client.command("HGET", this.sessionKey, SESSION_STATE_FIELD);
632
+ if (value == null) return { expected, state: this.emptyState() };
633
+ const state = decodeSnapshot(value, "OpenAI Agents session state");
634
+ if (state == null || typeof state !== "object" || state.formatVersion !== SESSION_FORMAT_VERSION || state.sessionId !== this.sessionId || !Array.isArray(state.items) || state.operations == null || typeof state.operations !== "object" || Array.isArray(state.operations) || Object.values(state.operations).some((digest) => typeof digest !== "string")) {
635
+ throw new Error("unsupported or corrupt FerricStore OpenAI Agents session state");
636
+ }
637
+ return { expected, state };
638
+ }
639
+ emptyState() {
640
+ return {
641
+ formatVersion: SESSION_FORMAT_VERSION,
642
+ items: cloneSnapshot(this.initialItems),
643
+ operations: {},
644
+ sessionId: this.sessionId
645
+ };
646
+ }
647
+ };
648
+ function snapshotTransactionArgs(args) {
649
+ if (args == null || typeof args !== "object") throw new TypeError("session history transaction is invalid");
650
+ if (typeof args.operationId !== "string" || args.operationId.trim().length === 0) {
651
+ throw new TypeError("session history transaction operationId must be a non-empty string");
652
+ }
653
+ const transaction = cloneSnapshot(args.transaction);
654
+ if (transaction == null || typeof transaction !== "object") {
655
+ throw new TypeError("session history transaction must be an object");
656
+ }
657
+ if (transaction.type === "append_items") {
658
+ if (!Array.isArray(transaction.items)) throw new TypeError("session history append items are invalid");
659
+ return {
660
+ operationId: args.operationId,
661
+ transaction: { type: "append_items", items: snapshotItems(transaction.items, "transaction items") }
662
+ };
663
+ }
664
+ if (transaction.type === "replace_suffix") {
665
+ if (!Array.isArray(transaction.expectedSuffix) || !Array.isArray(transaction.replacement)) {
666
+ throw new TypeError("session history suffix transaction is invalid");
667
+ }
668
+ return {
669
+ operationId: args.operationId,
670
+ transaction: {
671
+ type: "replace_suffix",
672
+ expectedSuffix: snapshotItems(transaction.expectedSuffix, "transaction expectedSuffix"),
673
+ replacement: snapshotItems(transaction.replacement, "transaction replacement")
674
+ }
675
+ };
676
+ }
677
+ throw new TypeError("unsupported session history transaction type");
678
+ }
679
+ function snapshotItems(items, name) {
680
+ if (!Array.isArray(items)) throw new TypeError(`${name} must be an array`);
681
+ return items.map((item) => snapshotItem(item, name));
682
+ }
683
+ function snapshotItem(item, name) {
684
+ if (item == null || typeof item !== "object" || Array.isArray(item)) {
685
+ throw new TypeError(`${name} contains an invalid agent item`);
686
+ }
687
+ return cloneSnapshot(item);
688
+ }
689
+ export {
690
+ FerricStoreSession
691
+ };
692
+ //# sourceMappingURL=openai-agents.js.map