@ferricstore/ferricstore 0.11.11 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/dist/durability-DlDCsdlo.d.cts +16 -0
- package/dist/durability-DplL0SbW.d.ts +16 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -162
- package/dist/index.d.ts +5 -162
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal-lEEDZpPH.d.cts +4 -0
- package/dist/internal-lEEDZpPH.d.ts +4 -0
- package/dist/langgraph.cjs +1278 -0
- package/dist/langgraph.cjs.map +1 -0
- package/dist/langgraph.d.cts +148 -0
- package/dist/langgraph.d.ts +148 -0
- package/dist/langgraph.js +1252 -0
- package/dist/langgraph.js.map +1 -0
- package/dist/openai-agents.cjs +580 -0
- package/dist/openai-agents.cjs.map +1 -0
- package/dist/openai-agents.d.cts +44 -0
- package/dist/openai-agents.d.ts +44 -0
- package/dist/openai-agents.js +555 -0
- package/dist/openai-agents.js.map +1 -0
- package/dist/outcomes-BbFDp3AH.d.ts +160 -0
- package/dist/outcomes-DmBwnq0Y.d.cts +160 -0
- package/docs/agent-frameworks.md +159 -0
- package/docs/api/assets/highlight.css +12 -12
- package/docs/api/classes/ClaimHydrationError.html +2 -2
- package/docs/api/classes/ConnectionClosedError.html +2 -2
- package/docs/api/classes/FerricStoreError.html +2 -2
- package/docs/api/classes/FlowAlreadyExistsError.html +2 -2
- package/docs/api/classes/FlowBatchError.html +2 -2
- package/docs/api/classes/FlowNotFoundError.html +2 -2
- package/docs/api/classes/FlowQueryError.html +2 -2
- package/docs/api/classes/FlowWrongStateError.html +2 -2
- package/docs/api/classes/HTTPTransportError.html +2 -2
- package/docs/api/classes/InvalidCommandError.html +2 -2
- package/docs/api/classes/LeaseRenewalError.html +2 -2
- package/docs/api/classes/LockHeldError.html +2 -2
- package/docs/api/classes/LockNotOwnedError.html +2 -2
- package/docs/api/classes/OverloadedError.html +2 -2
- package/docs/api/classes/QueueCompletionError.html +2 -2
- package/docs/api/classes/RequestTimeoutError.html +2 -2
- package/docs/api/classes/RerouteError.html +2 -2
- package/docs/api/classes/StaleLeaseError.html +2 -2
- package/docs/api/classes/StalePolicyGenerationError.html +2 -2
- package/docs/api/index.html +34 -24
- package/docs/api/media/agent-frameworks.md +159 -0
- package/docs/api/media/langgraph.ts +23 -0
- package/docs/api/media/openai-agents-session.ts +13 -0
- package/docs/api/variables/FERRICSTORE_SDK_VERSION.html +1 -1
- package/package.json +41 -2
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/openai-agents.ts
|
|
21
|
+
var openai_agents_exports = {};
|
|
22
|
+
__export(openai_agents_exports, {
|
|
23
|
+
FerricStoreSession: () => FerricStoreSession
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(openai_agents_exports);
|
|
26
|
+
var import_node_crypto3 = require("crypto");
|
|
27
|
+
|
|
28
|
+
// src/agent-persistence/durability.ts
|
|
29
|
+
var import_node_crypto = require("crypto");
|
|
30
|
+
var import_promises = require("timers/promises");
|
|
31
|
+
|
|
32
|
+
// src/errors.ts
|
|
33
|
+
var FerricStoreError = class extends Error {
|
|
34
|
+
code = "ferricstore_error";
|
|
35
|
+
raw;
|
|
36
|
+
retryable;
|
|
37
|
+
safeToRetry;
|
|
38
|
+
retryAfterMs;
|
|
39
|
+
constructor(message, options = {}) {
|
|
40
|
+
super(message, { cause: options.cause });
|
|
41
|
+
this.name = new.target.name;
|
|
42
|
+
this.raw = options.raw;
|
|
43
|
+
this.retryable = options.retryable ?? structuredBooleanField(options.raw, "retryable");
|
|
44
|
+
this.safeToRetry = options.safeToRetry ?? structuredBooleanField(options.raw, "safe_to_retry");
|
|
45
|
+
this.retryAfterMs = options.retryAfterMs ?? structuredIntegerField(options.raw, "retry_after_ms");
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var LockHeldError = class extends FerricStoreError {
|
|
49
|
+
code = "lock_held";
|
|
50
|
+
};
|
|
51
|
+
function structuredIntegerField(raw, name) {
|
|
52
|
+
const value = structuredField(raw, name);
|
|
53
|
+
if (typeof value === "number") {
|
|
54
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
55
|
+
}
|
|
56
|
+
if (typeof value === "bigint") {
|
|
57
|
+
return value >= 0n && value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : void 0;
|
|
58
|
+
}
|
|
59
|
+
const text = binaryText(value);
|
|
60
|
+
return text == null ? void 0 : nonNegativeSafeIntegerText(text);
|
|
61
|
+
}
|
|
62
|
+
function structuredBooleanField(raw, name) {
|
|
63
|
+
const value = structuredField(raw, name);
|
|
64
|
+
if (typeof value === "boolean") return value;
|
|
65
|
+
const text = binaryText(value)?.toLowerCase();
|
|
66
|
+
if (text === "true" || text === "1") return true;
|
|
67
|
+
if (text === "false" || text === "0") return false;
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
function nonNegativeSafeIntegerText(value) {
|
|
71
|
+
if (!/^[0-9]+$/u.test(value)) return void 0;
|
|
72
|
+
const parsed = Number.parseInt(value, 10);
|
|
73
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
|
|
74
|
+
}
|
|
75
|
+
function structuredField(raw, name) {
|
|
76
|
+
if (raw instanceof Map) {
|
|
77
|
+
if (raw.has(name)) return raw.get(name);
|
|
78
|
+
for (const [key, value] of raw.entries()) {
|
|
79
|
+
if (binaryText(key) === name) return value;
|
|
80
|
+
}
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
if (typeof raw === "object" && raw != null && Object.hasOwn(raw, name)) {
|
|
84
|
+
return raw[name];
|
|
85
|
+
}
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
function binaryText(value) {
|
|
89
|
+
if (typeof value === "string") return value;
|
|
90
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array) return Buffer.from(value).toString("utf8");
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/agent-persistence/durability.ts
|
|
95
|
+
var DEFAULT_LOCK_OPTIONS = {
|
|
96
|
+
lockRetryMs: 10,
|
|
97
|
+
lockTtlMs: 3e5,
|
|
98
|
+
lockWaitMs: 3e4
|
|
99
|
+
};
|
|
100
|
+
function normalizeKeyPrefix(value, defaultValue) {
|
|
101
|
+
const prefix = value.length === 0 ? defaultValue : value;
|
|
102
|
+
if (prefix.includes("\0")) throw new TypeError("keyPrefix must not contain NUL bytes");
|
|
103
|
+
const normalized = prefix.replace(/:+$/u, "");
|
|
104
|
+
if (normalized.length === 0) throw new TypeError("keyPrefix must contain a character other than ':'");
|
|
105
|
+
return normalized;
|
|
106
|
+
}
|
|
107
|
+
function positiveInteger(value, fallback, name) {
|
|
108
|
+
const normalized = value ?? fallback;
|
|
109
|
+
if (!Number.isSafeInteger(normalized) || normalized <= 0) {
|
|
110
|
+
throw new TypeError(`${name} must be a positive safe integer`);
|
|
111
|
+
}
|
|
112
|
+
return normalized;
|
|
113
|
+
}
|
|
114
|
+
function nonNegativeInteger(value, fallback, name) {
|
|
115
|
+
const normalized = value ?? fallback;
|
|
116
|
+
if (!Number.isSafeInteger(normalized) || normalized < 0) {
|
|
117
|
+
throw new TypeError(`${name} must be a non-negative safe integer`);
|
|
118
|
+
}
|
|
119
|
+
return normalized;
|
|
120
|
+
}
|
|
121
|
+
function textResponse(value, name) {
|
|
122
|
+
if (typeof value === "string") return value;
|
|
123
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array) return Buffer.from(value).toString("utf8");
|
|
124
|
+
throw new TypeError(`FerricStore returned an invalid ${name}`);
|
|
125
|
+
}
|
|
126
|
+
function integerResponse(value, name) {
|
|
127
|
+
const parsed = typeof value === "number" ? value : Number(textResponse(value, name));
|
|
128
|
+
if (!Number.isSafeInteger(parsed)) throw new TypeError(`FerricStore returned an invalid ${name}`);
|
|
129
|
+
return parsed;
|
|
130
|
+
}
|
|
131
|
+
async function withMutationLocks(client, keys, operation, options = {}) {
|
|
132
|
+
const orderedKeys = [...new Set(keys)].sort();
|
|
133
|
+
if (orderedKeys.length === 0) return await operation();
|
|
134
|
+
const normalized = {
|
|
135
|
+
lockRetryMs: positiveInteger(options.lockRetryMs, DEFAULT_LOCK_OPTIONS.lockRetryMs, "lockRetryMs"),
|
|
136
|
+
lockTtlMs: positiveInteger(options.lockTtlMs, DEFAULT_LOCK_OPTIONS.lockTtlMs, "lockTtlMs"),
|
|
137
|
+
lockWaitMs: nonNegativeInteger(options.lockWaitMs, DEFAULT_LOCK_OPTIONS.lockWaitMs, "lockWaitMs")
|
|
138
|
+
};
|
|
139
|
+
const owner = (0, import_node_crypto.randomUUID)();
|
|
140
|
+
const acquired = [];
|
|
141
|
+
const deadline = performance.now() + normalized.lockWaitMs;
|
|
142
|
+
let primaryError;
|
|
143
|
+
let heartbeatError;
|
|
144
|
+
let releaseError;
|
|
145
|
+
let result;
|
|
146
|
+
let operationCompleted = false;
|
|
147
|
+
const heartbeatAbort = new AbortController();
|
|
148
|
+
try {
|
|
149
|
+
for (const key of orderedKeys) {
|
|
150
|
+
while (!await tryAcquireLock(client, key, owner, normalized.lockTtlMs)) {
|
|
151
|
+
if (performance.now() >= deadline) {
|
|
152
|
+
throw new Error(`timed out acquiring FerricStore lock ${JSON.stringify(key)}`);
|
|
153
|
+
}
|
|
154
|
+
await (0, import_promises.setTimeout)(normalized.lockRetryMs);
|
|
155
|
+
}
|
|
156
|
+
acquired.push(key);
|
|
157
|
+
}
|
|
158
|
+
const heartbeat = renewLocks(
|
|
159
|
+
client,
|
|
160
|
+
acquired,
|
|
161
|
+
owner,
|
|
162
|
+
normalized.lockTtlMs,
|
|
163
|
+
heartbeatAbort.signal,
|
|
164
|
+
(error) => {
|
|
165
|
+
heartbeatError ??= error;
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
try {
|
|
169
|
+
result = await operation();
|
|
170
|
+
operationCompleted = true;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
primaryError = error;
|
|
173
|
+
} finally {
|
|
174
|
+
heartbeatAbort.abort();
|
|
175
|
+
await heartbeat;
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
primaryError ??= error;
|
|
179
|
+
} finally {
|
|
180
|
+
heartbeatAbort.abort();
|
|
181
|
+
for (const key of acquired.reverse()) {
|
|
182
|
+
try {
|
|
183
|
+
await client.command("UNLOCK", key, owner);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
releaseError ??= error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (primaryError != null) throw errorObject(primaryError);
|
|
190
|
+
if (heartbeatError != null) throw errorObject(heartbeatError);
|
|
191
|
+
if (releaseError != null) throw errorObject(releaseError);
|
|
192
|
+
if (!operationCompleted) throw new Error("FerricStore mutation did not complete");
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
async function tryAcquireLock(client, key, owner, ttlMs) {
|
|
196
|
+
try {
|
|
197
|
+
const response = await client.command("LOCK", key, owner, ttlMs);
|
|
198
|
+
return response === true || response === "OK" || Buffer.isBuffer(response) && response.equals(Buffer.from("OK"));
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (error instanceof LockHeldError) return false;
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function renewLocks(client, keys, owner, ttlMs, signal, onError) {
|
|
205
|
+
const intervalMs = Math.max(Math.floor(ttlMs / 3), 10);
|
|
206
|
+
const retryMs = Math.min(Math.max(Math.floor(intervalMs / 10), 10), 1e3);
|
|
207
|
+
const lastExtended = new Map(keys.map((key) => [key, performance.now()]));
|
|
208
|
+
let waitMs = intervalMs;
|
|
209
|
+
while (!signal.aborted) {
|
|
210
|
+
try {
|
|
211
|
+
await (0, import_promises.setTimeout)(waitMs, void 0, { signal });
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (signal.aborted) return;
|
|
214
|
+
onError(error);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const now = performance.now();
|
|
218
|
+
let retry = false;
|
|
219
|
+
for (const key of keys) {
|
|
220
|
+
try {
|
|
221
|
+
const response = await client.command("EXTEND", key, owner, ttlMs);
|
|
222
|
+
if (integerResponse(response, "EXTEND response") !== 1) {
|
|
223
|
+
onError(new Error(`lost FerricStore lock ${JSON.stringify(key)} while mutating data`));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
lastExtended.set(key, now);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (now - (lastExtended.get(key) ?? 0) >= ttlMs) {
|
|
229
|
+
onError(new Error(`lost FerricStore lock ${JSON.stringify(key)} while mutating data`, { cause: error }));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
retry = true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
waitMs = retry ? retryMs : intervalMs;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function errorObject(value) {
|
|
239
|
+
return value instanceof Error ? value : new Error("FerricStore mutation failed", { cause: value });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/agent-persistence/snapshot.ts
|
|
243
|
+
var import_node_crypto2 = require("crypto");
|
|
244
|
+
function encodeSnapshot(value) {
|
|
245
|
+
return Buffer.from(JSON.stringify(snapshot(value, /* @__PURE__ */ new WeakSet())), "utf8");
|
|
246
|
+
}
|
|
247
|
+
function decodeSnapshot(value, name) {
|
|
248
|
+
const bytes = typeof value === "string" ? Buffer.from(value, "utf8") : Buffer.from(asBytes(value, name));
|
|
249
|
+
let encoded;
|
|
250
|
+
try {
|
|
251
|
+
encoded = JSON.parse(bytes.toString("utf8"));
|
|
252
|
+
} catch (error) {
|
|
253
|
+
throw new Error(`FerricStore returned an invalid ${name}`, { cause: error });
|
|
254
|
+
}
|
|
255
|
+
return restore(encoded, name);
|
|
256
|
+
}
|
|
257
|
+
function snapshotDigest(value) {
|
|
258
|
+
return (0, import_node_crypto2.createHash)("sha256").update(encodeSnapshot(value)).digest("hex");
|
|
259
|
+
}
|
|
260
|
+
function cloneSnapshot(value) {
|
|
261
|
+
return decodeSnapshot(encodeSnapshot(value), "snapshot");
|
|
262
|
+
}
|
|
263
|
+
function snapshotsEqual(left, right) {
|
|
264
|
+
return encodeSnapshot(left).equals(encodeSnapshot(right));
|
|
265
|
+
}
|
|
266
|
+
function snapshot(value, ancestors) {
|
|
267
|
+
if (value === null) return ["null"];
|
|
268
|
+
if (value === void 0) return ["undefined"];
|
|
269
|
+
if (typeof value === "string") return ["string", value];
|
|
270
|
+
if (typeof value === "boolean") return ["boolean", value];
|
|
271
|
+
if (typeof value === "number") {
|
|
272
|
+
if (Object.is(value, -0)) return ["number", "-0"];
|
|
273
|
+
if (Number.isNaN(value)) return ["number", "NaN"];
|
|
274
|
+
if (value === Infinity) return ["number", "+Infinity"];
|
|
275
|
+
if (value === -Infinity) return ["number", "-Infinity"];
|
|
276
|
+
return ["number", value];
|
|
277
|
+
}
|
|
278
|
+
if (typeof value !== "object") throw new TypeError("session history contains unsupported data");
|
|
279
|
+
if (value instanceof Uint8Array) {
|
|
280
|
+
const keys = Reflect.ownKeys(value);
|
|
281
|
+
if (keys.length !== value.length || keys.some((key) => typeof key !== "string" || !isArrayIndex(key, value.length))) {
|
|
282
|
+
throw new TypeError("session history binary data contains custom properties");
|
|
283
|
+
}
|
|
284
|
+
return ["binary", Buffer.from(value).toString("base64")];
|
|
285
|
+
}
|
|
286
|
+
if (ancestors.has(value)) throw new TypeError("session history contains cyclic data");
|
|
287
|
+
ancestors.add(value);
|
|
288
|
+
try {
|
|
289
|
+
if (Array.isArray(value)) {
|
|
290
|
+
const keys2 = Reflect.ownKeys(value);
|
|
291
|
+
if (keys2.length !== value.length + 1 || keys2.some((key) => typeof key !== "string" || key !== "length" && !isArrayIndex(key, value.length))) {
|
|
292
|
+
throw new TypeError("session history contains a sparse or customized array");
|
|
293
|
+
}
|
|
294
|
+
const items = [];
|
|
295
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
296
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
297
|
+
if (descriptor == null || !descriptor.enumerable || !("value" in descriptor)) {
|
|
298
|
+
throw new TypeError("session history contains an unsupported array item");
|
|
299
|
+
}
|
|
300
|
+
items.push(snapshot(descriptor.value, ancestors));
|
|
301
|
+
}
|
|
302
|
+
return ["array", items];
|
|
303
|
+
}
|
|
304
|
+
const prototype = Object.getPrototypeOf(value);
|
|
305
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
306
|
+
throw new TypeError("session history contains an unsupported object");
|
|
307
|
+
}
|
|
308
|
+
const keys = Reflect.ownKeys(value);
|
|
309
|
+
if (keys.some((key) => typeof key !== "string")) {
|
|
310
|
+
throw new TypeError("session history contains a symbol property");
|
|
311
|
+
}
|
|
312
|
+
const entries = [];
|
|
313
|
+
for (const key of keys.sort((left, right) => left.localeCompare(right))) {
|
|
314
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
315
|
+
if (descriptor == null || !descriptor.enumerable || !("value" in descriptor)) {
|
|
316
|
+
throw new TypeError("session history contains an unsupported property");
|
|
317
|
+
}
|
|
318
|
+
entries.push([key, snapshot(descriptor.value, ancestors)]);
|
|
319
|
+
}
|
|
320
|
+
return ["object", entries];
|
|
321
|
+
} finally {
|
|
322
|
+
ancestors.delete(value);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function restore(value, name) {
|
|
326
|
+
if (!Array.isArray(value) || typeof value[0] !== "string") throw new TypeError(`invalid ${name}`);
|
|
327
|
+
switch (value[0]) {
|
|
328
|
+
case "null":
|
|
329
|
+
return null;
|
|
330
|
+
case "undefined":
|
|
331
|
+
return void 0;
|
|
332
|
+
case "string":
|
|
333
|
+
return requireValueType(value[1], "string", name);
|
|
334
|
+
case "boolean":
|
|
335
|
+
return requireValueType(value[1], "boolean", name);
|
|
336
|
+
case "binary":
|
|
337
|
+
return Buffer.from(requireValueType(value[1], "string", name), "base64");
|
|
338
|
+
case "number":
|
|
339
|
+
return restoreNumber(value[1], name);
|
|
340
|
+
case "array": {
|
|
341
|
+
if (!Array.isArray(value[1])) throw new TypeError(`invalid ${name}`);
|
|
342
|
+
return value[1].map((item) => restore(item, name));
|
|
343
|
+
}
|
|
344
|
+
case "object": {
|
|
345
|
+
if (!Array.isArray(value[1])) throw new TypeError(`invalid ${name}`);
|
|
346
|
+
const result = {};
|
|
347
|
+
for (const entry of value[1]) {
|
|
348
|
+
if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string") {
|
|
349
|
+
throw new TypeError(`invalid ${name}`);
|
|
350
|
+
}
|
|
351
|
+
Object.defineProperty(result, entry[0], {
|
|
352
|
+
configurable: true,
|
|
353
|
+
enumerable: true,
|
|
354
|
+
value: restore(entry[1], name),
|
|
355
|
+
writable: true
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return result;
|
|
359
|
+
}
|
|
360
|
+
default:
|
|
361
|
+
throw new TypeError(`invalid ${name}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function restoreNumber(value, name) {
|
|
365
|
+
if (typeof value === "number") return value;
|
|
366
|
+
if (value === "NaN") return Number.NaN;
|
|
367
|
+
if (value === "+Infinity") return Infinity;
|
|
368
|
+
if (value === "-Infinity") return -Infinity;
|
|
369
|
+
if (value === "-0") return -0;
|
|
370
|
+
throw new TypeError(`invalid ${name}`);
|
|
371
|
+
}
|
|
372
|
+
function requireValueType(value, type, name) {
|
|
373
|
+
if (typeof value !== type) throw new TypeError(`invalid ${name}`);
|
|
374
|
+
return value;
|
|
375
|
+
}
|
|
376
|
+
function asBytes(value, name) {
|
|
377
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array) return value;
|
|
378
|
+
throw new TypeError(`FerricStore returned a non-binary ${name}`);
|
|
379
|
+
}
|
|
380
|
+
function isArrayIndex(key, length) {
|
|
381
|
+
if (!/^(?:0|[1-9]\d*)$/u.test(key)) return false;
|
|
382
|
+
const index = Number(key);
|
|
383
|
+
return Number.isSafeInteger(index) && index >= 0 && index < length;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/openai-agents.ts
|
|
387
|
+
var SESSION_FORMAT_VERSION = 1;
|
|
388
|
+
var SESSION_STATE_FIELD = "state";
|
|
389
|
+
var FerricStoreSession = class {
|
|
390
|
+
client;
|
|
391
|
+
sessionId;
|
|
392
|
+
keyPrefix;
|
|
393
|
+
initialItems;
|
|
394
|
+
lockOptions;
|
|
395
|
+
sessionKey;
|
|
396
|
+
lockKey;
|
|
397
|
+
constructor(client, options = {}) {
|
|
398
|
+
this.client = client;
|
|
399
|
+
this.sessionId = options.sessionId ?? (0, import_node_crypto3.randomUUID)();
|
|
400
|
+
if (typeof this.sessionId !== "string" || this.sessionId.trim().length === 0) {
|
|
401
|
+
throw new TypeError("sessionId must be a non-empty string");
|
|
402
|
+
}
|
|
403
|
+
this.keyPrefix = normalizeKeyPrefix(options.keyPrefix ?? "openai:agents:session", "openai:agents:session");
|
|
404
|
+
this.initialItems = snapshotItems(options.initialItems ?? [], "initialItems");
|
|
405
|
+
this.lockOptions = {
|
|
406
|
+
lockRetryMs: options.lockRetryMs,
|
|
407
|
+
lockTtlMs: options.lockTtlMs,
|
|
408
|
+
lockWaitMs: options.lockWaitMs
|
|
409
|
+
};
|
|
410
|
+
const digest = (0, import_node_crypto3.createHash)("sha256").update(this.sessionId, "utf8").digest("hex");
|
|
411
|
+
this.sessionKey = `${this.keyPrefix}:{oais:${digest}}:session`;
|
|
412
|
+
this.lockKey = `${this.keyPrefix}:{oais:${digest}}:mutation-lock`;
|
|
413
|
+
}
|
|
414
|
+
async getSessionId() {
|
|
415
|
+
await this.mutate(async (state) => state);
|
|
416
|
+
return this.sessionId;
|
|
417
|
+
}
|
|
418
|
+
async getItems(limit) {
|
|
419
|
+
if (limit != null && limit <= 0) return [];
|
|
420
|
+
if (limit != null && !Number.isSafeInteger(limit)) {
|
|
421
|
+
throw new TypeError("limit must be a safe integer");
|
|
422
|
+
}
|
|
423
|
+
const state = await this.readState();
|
|
424
|
+
const items = limit == null ? state.items : state.items.slice(Math.max(state.items.length - limit, 0));
|
|
425
|
+
return cloneSnapshot(items);
|
|
426
|
+
}
|
|
427
|
+
async addItems(items) {
|
|
428
|
+
if (items.length === 0) return;
|
|
429
|
+
const additions = snapshotItems(items, "items");
|
|
430
|
+
await this.mutate(async (state) => ({
|
|
431
|
+
...state,
|
|
432
|
+
items: [...state.items, ...additions]
|
|
433
|
+
}));
|
|
434
|
+
}
|
|
435
|
+
async replaceHistoryWithCompaction(items) {
|
|
436
|
+
const replacement = snapshotItems(items, "items");
|
|
437
|
+
await this.mutate(async (state) => ({ ...state, items: replacement }));
|
|
438
|
+
}
|
|
439
|
+
async popItem() {
|
|
440
|
+
let popped;
|
|
441
|
+
await this.mutate(async (state) => {
|
|
442
|
+
popped = state.items.at(-1);
|
|
443
|
+
return popped == null ? state : { ...state, items: state.items.slice(0, -1) };
|
|
444
|
+
});
|
|
445
|
+
return popped == null ? void 0 : cloneSnapshot(popped);
|
|
446
|
+
}
|
|
447
|
+
async clearSession() {
|
|
448
|
+
await this.mutate(async (state) => ({ ...state, items: [], operations: {} }));
|
|
449
|
+
}
|
|
450
|
+
async applyHistoryMutations(args) {
|
|
451
|
+
if (args == null || !Array.isArray(args.mutations)) {
|
|
452
|
+
throw new TypeError("session history mutations are invalid");
|
|
453
|
+
}
|
|
454
|
+
if (args.mutations.length === 0) return;
|
|
455
|
+
const mutations = cloneSnapshot(args.mutations);
|
|
456
|
+
await this.mutate(async (state) => {
|
|
457
|
+
let items = cloneSnapshot(state.items);
|
|
458
|
+
for (const mutation of mutations) {
|
|
459
|
+
if (mutation.type !== "replace_function_call") {
|
|
460
|
+
throw new TypeError("unsupported session history mutation");
|
|
461
|
+
}
|
|
462
|
+
const replacement = snapshotItem(mutation.replacement, "mutation replacement");
|
|
463
|
+
let keptReplacement = false;
|
|
464
|
+
const next = [];
|
|
465
|
+
for (const item of items) {
|
|
466
|
+
if (item.type === "function_call" && item.callId === mutation.callId) {
|
|
467
|
+
if (!keptReplacement) {
|
|
468
|
+
next.push(replacement);
|
|
469
|
+
keptReplacement = true;
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
next.push(item);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
items = next;
|
|
476
|
+
}
|
|
477
|
+
return { ...state, items };
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
async applyHistoryTransaction(args) {
|
|
481
|
+
const { operationId, transaction } = snapshotTransactionArgs(args);
|
|
482
|
+
const digest = snapshotDigest(transaction);
|
|
483
|
+
await this.mutate(async (state) => {
|
|
484
|
+
const existing = Object.getOwnPropertyDescriptor(state.operations, operationId)?.value;
|
|
485
|
+
if (existing != null) {
|
|
486
|
+
if (typeof existing !== "string") throw new Error("corrupt session history operation receipt");
|
|
487
|
+
if (existing !== digest) {
|
|
488
|
+
throw new Error("session history operation was already applied with a different transaction");
|
|
489
|
+
}
|
|
490
|
+
return state;
|
|
491
|
+
}
|
|
492
|
+
let items;
|
|
493
|
+
if (transaction.type === "append_items") {
|
|
494
|
+
items = [...state.items, ...transaction.items];
|
|
495
|
+
} else {
|
|
496
|
+
const suffixStart = state.items.length - transaction.expectedSuffix.length;
|
|
497
|
+
const actualSuffix = suffixStart < 0 ? [] : state.items.slice(suffixStart);
|
|
498
|
+
if (suffixStart < 0 || !snapshotsEqual(actualSuffix, transaction.expectedSuffix)) {
|
|
499
|
+
throw new Error("session history suffix no longer matches the transaction precondition");
|
|
500
|
+
}
|
|
501
|
+
items = [...state.items.slice(0, suffixStart), ...transaction.replacement];
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
...state,
|
|
505
|
+
items,
|
|
506
|
+
operations: { ...state.operations, [operationId]: digest }
|
|
507
|
+
};
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
async mutate(operation) {
|
|
511
|
+
await withMutationLocks(this.client, [this.lockKey], async () => {
|
|
512
|
+
const current = await this.readState();
|
|
513
|
+
const next = await operation(current);
|
|
514
|
+
await this.client.command("HSET", this.sessionKey, SESSION_STATE_FIELD, encodeSnapshot(next));
|
|
515
|
+
}, this.lockOptions);
|
|
516
|
+
}
|
|
517
|
+
async readState() {
|
|
518
|
+
const value = await this.client.command("HGET", this.sessionKey, SESSION_STATE_FIELD);
|
|
519
|
+
if (value == null) return this.emptyState();
|
|
520
|
+
const state = decodeSnapshot(value, "OpenAI Agents session state");
|
|
521
|
+
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")) {
|
|
522
|
+
throw new Error("unsupported or corrupt FerricStore OpenAI Agents session state");
|
|
523
|
+
}
|
|
524
|
+
return state;
|
|
525
|
+
}
|
|
526
|
+
emptyState() {
|
|
527
|
+
return {
|
|
528
|
+
formatVersion: SESSION_FORMAT_VERSION,
|
|
529
|
+
items: cloneSnapshot(this.initialItems),
|
|
530
|
+
operations: {},
|
|
531
|
+
sessionId: this.sessionId
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
function snapshotTransactionArgs(args) {
|
|
536
|
+
if (args == null || typeof args !== "object") throw new TypeError("session history transaction is invalid");
|
|
537
|
+
if (typeof args.operationId !== "string" || args.operationId.trim().length === 0) {
|
|
538
|
+
throw new TypeError("session history transaction operationId must be a non-empty string");
|
|
539
|
+
}
|
|
540
|
+
const transaction = cloneSnapshot(args.transaction);
|
|
541
|
+
if (transaction == null || typeof transaction !== "object") {
|
|
542
|
+
throw new TypeError("session history transaction must be an object");
|
|
543
|
+
}
|
|
544
|
+
if (transaction.type === "append_items") {
|
|
545
|
+
if (!Array.isArray(transaction.items)) throw new TypeError("session history append items are invalid");
|
|
546
|
+
return {
|
|
547
|
+
operationId: args.operationId,
|
|
548
|
+
transaction: { type: "append_items", items: snapshotItems(transaction.items, "transaction items") }
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
if (transaction.type === "replace_suffix") {
|
|
552
|
+
if (!Array.isArray(transaction.expectedSuffix) || !Array.isArray(transaction.replacement)) {
|
|
553
|
+
throw new TypeError("session history suffix transaction is invalid");
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
operationId: args.operationId,
|
|
557
|
+
transaction: {
|
|
558
|
+
type: "replace_suffix",
|
|
559
|
+
expectedSuffix: snapshotItems(transaction.expectedSuffix, "transaction expectedSuffix"),
|
|
560
|
+
replacement: snapshotItems(transaction.replacement, "transaction replacement")
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
throw new TypeError("unsupported session history transaction type");
|
|
565
|
+
}
|
|
566
|
+
function snapshotItems(items, name) {
|
|
567
|
+
if (!Array.isArray(items)) throw new TypeError(`${name} must be an array`);
|
|
568
|
+
return items.map((item) => snapshotItem(item, name));
|
|
569
|
+
}
|
|
570
|
+
function snapshotItem(item, name) {
|
|
571
|
+
if (item == null || typeof item !== "object" || Array.isArray(item)) {
|
|
572
|
+
throw new TypeError(`${name} contains an invalid agent item`);
|
|
573
|
+
}
|
|
574
|
+
return cloneSnapshot(item);
|
|
575
|
+
}
|
|
576
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
577
|
+
0 && (module.exports = {
|
|
578
|
+
FerricStoreSession
|
|
579
|
+
});
|
|
580
|
+
//# sourceMappingURL=openai-agents.cjs.map
|