@polpo-ai/core 0.15.76 → 0.15.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/run-delivery-follower.d.ts +40 -0
- package/dist/run-delivery-follower.d.ts.map +1 -0
- package/dist/run-delivery-follower.js +171 -0
- package/dist/run-delivery-follower.js.map +1 -0
- package/dist/run-delivery-follower.test.d.ts +2 -0
- package/dist/run-delivery-follower.test.d.ts.map +1 -0
- package/dist/run-delivery-follower.test.js +195 -0
- package/dist/run-delivery-follower.test.js.map +1 -0
- package/dist/run-delivery-owner.d.ts +34 -0
- package/dist/run-delivery-owner.d.ts.map +1 -0
- package/dist/run-delivery-owner.js +156 -0
- package/dist/run-delivery-owner.js.map +1 -0
- package/dist/run-delivery-owner.test.d.ts +2 -0
- package/dist/run-delivery-owner.test.d.ts.map +1 -0
- package/dist/run-delivery-owner.test.js +135 -0
- package/dist/run-delivery-owner.test.js.map +1 -0
- package/dist/run-delivery.d.ts +147 -0
- package/dist/run-delivery.d.ts.map +1 -0
- package/dist/run-delivery.js +412 -0
- package/dist/run-delivery.js.map +1 -0
- package/dist/run-delivery.test.d.ts +2 -0
- package/dist/run-delivery.test.d.ts.map +1 -0
- package/dist/run-delivery.test.js +212 -0
- package/dist/run-delivery.test.js.map +1 -0
- package/package.json +13 -1
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
export const RUN_STREAM_EVENT_SCHEMA_VERSION = 1;
|
|
2
|
+
export const DEFAULT_RUN_EVENT_PAGE_SIZE = 100;
|
|
3
|
+
export const MAX_RUN_EVENT_PAGE_SIZE = 1_000;
|
|
4
|
+
export const MAX_RUN_EVENT_BYTES = 256 * 1024;
|
|
5
|
+
export const MAX_RUN_EVENT_ID_LENGTH = 500;
|
|
6
|
+
export const MAX_RUN_ID_LENGTH = 200;
|
|
7
|
+
export const MAX_RUN_EVENT_TYPE_LENGTH = 200;
|
|
8
|
+
export class RunDeliveryValidationError extends Error {
|
|
9
|
+
name = "RunDeliveryValidationError";
|
|
10
|
+
}
|
|
11
|
+
export class RunEventCursorError extends RunDeliveryValidationError {
|
|
12
|
+
name = "RunEventCursorError";
|
|
13
|
+
}
|
|
14
|
+
export class RunEventCursorAheadError extends RunEventCursorError {
|
|
15
|
+
name = "RunEventCursorAheadError";
|
|
16
|
+
}
|
|
17
|
+
export class RunEventCursorExpiredError extends RunEventCursorError {
|
|
18
|
+
name = "RunEventCursorExpiredError";
|
|
19
|
+
}
|
|
20
|
+
export class RunEventConflictError extends Error {
|
|
21
|
+
name = "RunEventConflictError";
|
|
22
|
+
}
|
|
23
|
+
export class RunExecutionLeaseValidationError extends RunDeliveryValidationError {
|
|
24
|
+
name = "RunExecutionLeaseValidationError";
|
|
25
|
+
}
|
|
26
|
+
export function resolveRunDeliveryPolicy(input) {
|
|
27
|
+
if (input === undefined)
|
|
28
|
+
return { onDisconnect: "cancel" };
|
|
29
|
+
if (!isPlainRecord(input)) {
|
|
30
|
+
throw new RunDeliveryValidationError("Run delivery policy must be an object");
|
|
31
|
+
}
|
|
32
|
+
const keys = Object.keys(input);
|
|
33
|
+
if (keys.some((key) => key !== "onDisconnect")) {
|
|
34
|
+
throw new RunDeliveryValidationError("Run delivery policy contains unknown fields");
|
|
35
|
+
}
|
|
36
|
+
const onDisconnect = input.onDisconnect ?? "cancel";
|
|
37
|
+
if (onDisconnect !== "cancel" && onDisconnect !== "continue") {
|
|
38
|
+
throw new RunDeliveryValidationError('Run delivery onDisconnect must be "cancel" or "continue"');
|
|
39
|
+
}
|
|
40
|
+
return { onDisconnect };
|
|
41
|
+
}
|
|
42
|
+
/** Parse a run-scoped cursor. Empty means before the first event. */
|
|
43
|
+
export function parseRunEventCursor(cursor) {
|
|
44
|
+
if (cursor === undefined || cursor === "")
|
|
45
|
+
return 0;
|
|
46
|
+
if (!/^(0|[1-9]\d*)$/.test(cursor)) {
|
|
47
|
+
throw new RunEventCursorError("Run event cursor must be a canonical non-negative integer");
|
|
48
|
+
}
|
|
49
|
+
const parsed = Number(cursor);
|
|
50
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
51
|
+
throw new RunEventCursorError("Run event cursor exceeds the supported range");
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
export function formatRunEventCursor(sequence) {
|
|
56
|
+
if (!Number.isSafeInteger(sequence) || sequence < 0) {
|
|
57
|
+
throw new RunEventCursorError("Run event sequence must be a non-negative safe integer");
|
|
58
|
+
}
|
|
59
|
+
return String(sequence);
|
|
60
|
+
}
|
|
61
|
+
export function validateRunDeliveryRunId(runId) {
|
|
62
|
+
assertBoundedString(runId, "runId", MAX_RUN_ID_LENGTH);
|
|
63
|
+
return runId;
|
|
64
|
+
}
|
|
65
|
+
/** Reference store for conformance tests and ephemeral single-process runtimes. */
|
|
66
|
+
export class InMemoryRunEventStore {
|
|
67
|
+
eventsByRun = new Map();
|
|
68
|
+
eventsByProducerId = new Map();
|
|
69
|
+
now;
|
|
70
|
+
maxEventBytes;
|
|
71
|
+
constructor(options = {}) {
|
|
72
|
+
this.now = options.now ?? (() => new Date());
|
|
73
|
+
this.maxEventBytes = options.maxEventBytes ?? MAX_RUN_EVENT_BYTES;
|
|
74
|
+
assertPositiveInteger(this.maxEventBytes, "maxEventBytes");
|
|
75
|
+
}
|
|
76
|
+
async append(runId, input) {
|
|
77
|
+
const normalized = normalizeRunStreamEventInput(runId, input, { now: this.now });
|
|
78
|
+
const existingById = this.eventsByProducerId.get(runId);
|
|
79
|
+
if (normalized.id) {
|
|
80
|
+
const existing = existingById?.get(normalized.id);
|
|
81
|
+
if (existing) {
|
|
82
|
+
if (!runStreamEventMatchesInput(existing, normalized)) {
|
|
83
|
+
throw new RunEventConflictError(`Run event ${normalized.id} already exists with different content`);
|
|
84
|
+
}
|
|
85
|
+
return cloneRunEvent(existing);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const events = this.eventsByRun.get(runId) ?? [];
|
|
89
|
+
const sequence = events.length === 0 ? 1 : events[events.length - 1].sequence + 1;
|
|
90
|
+
if (!Number.isSafeInteger(sequence)) {
|
|
91
|
+
throw new RunDeliveryValidationError("Run event sequence exceeds the supported range");
|
|
92
|
+
}
|
|
93
|
+
const event = materializeRunStreamEvent(runId, sequence, normalized, {
|
|
94
|
+
maxEventBytes: this.maxEventBytes,
|
|
95
|
+
});
|
|
96
|
+
events.push(event);
|
|
97
|
+
this.eventsByRun.set(runId, events);
|
|
98
|
+
const byId = existingById ?? new Map();
|
|
99
|
+
byId.set(event.id, event);
|
|
100
|
+
this.eventsByProducerId.set(runId, byId);
|
|
101
|
+
return cloneRunEvent(event);
|
|
102
|
+
}
|
|
103
|
+
async listAfter(runId, cursor, limit = DEFAULT_RUN_EVENT_PAGE_SIZE) {
|
|
104
|
+
validateRunDeliveryRunId(runId);
|
|
105
|
+
const sequence = parseRunEventCursor(cursor);
|
|
106
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > MAX_RUN_EVENT_PAGE_SIZE) {
|
|
107
|
+
throw new RunDeliveryValidationError(`Run event page limit must be between 1 and ${MAX_RUN_EVENT_PAGE_SIZE}`);
|
|
108
|
+
}
|
|
109
|
+
const available = (this.eventsByRun.get(runId) ?? []).filter((event) => event.sequence > sequence);
|
|
110
|
+
const selected = available.slice(0, limit);
|
|
111
|
+
const lastSequence = selected.at(-1)?.sequence ?? sequence;
|
|
112
|
+
return {
|
|
113
|
+
events: selected.map(cloneRunEvent),
|
|
114
|
+
nextCursor: formatRunEventCursor(lastSequence),
|
|
115
|
+
hasMore: available.length > selected.length,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async bounds(runId) {
|
|
119
|
+
validateRunDeliveryRunId(runId);
|
|
120
|
+
const events = this.eventsByRun.get(runId) ?? [];
|
|
121
|
+
if (events.length === 0)
|
|
122
|
+
return null;
|
|
123
|
+
return {
|
|
124
|
+
firstCursor: formatRunEventCursor(events[0].sequence),
|
|
125
|
+
lastCursor: formatRunEventCursor(events[events.length - 1].sequence),
|
|
126
|
+
count: events.length,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Compare-and-set reference lease store used by local runtimes and tests. */
|
|
131
|
+
export class InMemoryRunExecutionLeaseStore {
|
|
132
|
+
leases = new Map();
|
|
133
|
+
now;
|
|
134
|
+
constructor(options = {}) {
|
|
135
|
+
this.now = options.now ?? (() => new Date());
|
|
136
|
+
}
|
|
137
|
+
async claim(runId, input) {
|
|
138
|
+
const now = validNow(this.now);
|
|
139
|
+
const lease = normalizeRunExecutionLease(runId, input, { now, requireFuture: true });
|
|
140
|
+
const current = this.leases.get(runId);
|
|
141
|
+
if (current && Date.parse(current.expiresAt) > now.getTime()) {
|
|
142
|
+
return sameLease(current, lease);
|
|
143
|
+
}
|
|
144
|
+
this.leases.set(runId, lease);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
async renew(runId, input) {
|
|
148
|
+
const now = validNow(this.now);
|
|
149
|
+
const lease = normalizeRunExecutionLease(runId, input, { now, requireFuture: true });
|
|
150
|
+
const current = this.leases.get(runId);
|
|
151
|
+
if (!current || Date.parse(current.expiresAt) <= now.getTime())
|
|
152
|
+
return false;
|
|
153
|
+
if (current.owner !== lease.owner || current.token !== lease.token)
|
|
154
|
+
return false;
|
|
155
|
+
const currentExpiry = Date.parse(current.expiresAt);
|
|
156
|
+
const nextExpiry = Date.parse(lease.expiresAt);
|
|
157
|
+
if (nextExpiry < currentExpiry) {
|
|
158
|
+
throw new RunExecutionLeaseValidationError("A run execution lease cannot be shortened");
|
|
159
|
+
}
|
|
160
|
+
this.leases.set(runId, lease);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
async release(runId, input) {
|
|
164
|
+
const lease = normalizeRunExecutionLease(runId, input, {
|
|
165
|
+
now: validNow(this.now),
|
|
166
|
+
requireFuture: false,
|
|
167
|
+
});
|
|
168
|
+
const current = this.leases.get(runId);
|
|
169
|
+
if (!current || current.owner !== lease.owner || current.token !== lease.token)
|
|
170
|
+
return false;
|
|
171
|
+
this.leases.delete(runId);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
async get(runId) {
|
|
175
|
+
assertLeaseRunId(runId);
|
|
176
|
+
const lease = this.leases.get(runId);
|
|
177
|
+
return lease ? { ...lease } : null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
export class InMemoryRunCancellationStore {
|
|
181
|
+
requests = new Map();
|
|
182
|
+
async request(runId, input) {
|
|
183
|
+
const request = normalizeRunCancellationRequest(runId, input);
|
|
184
|
+
const existing = this.requests.get(runId);
|
|
185
|
+
if (existing)
|
|
186
|
+
return { ...existing };
|
|
187
|
+
this.requests.set(runId, request);
|
|
188
|
+
return { ...request };
|
|
189
|
+
}
|
|
190
|
+
async get(runId) {
|
|
191
|
+
validateRunDeliveryRunId(runId);
|
|
192
|
+
const request = this.requests.get(runId);
|
|
193
|
+
return request ? { ...request } : null;
|
|
194
|
+
}
|
|
195
|
+
async clear(runId) {
|
|
196
|
+
validateRunDeliveryRunId(runId);
|
|
197
|
+
return this.requests.delete(runId);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
export function normalizeRunCancellationRequest(runId, input) {
|
|
201
|
+
validateRunDeliveryRunId(runId);
|
|
202
|
+
if (!isPlainRecord(input)) {
|
|
203
|
+
throw new RunDeliveryValidationError("Run cancellation request must be an object");
|
|
204
|
+
}
|
|
205
|
+
const keys = Object.keys(input);
|
|
206
|
+
if (keys.some((key) => key !== "requestedAt" && key !== "reason")) {
|
|
207
|
+
throw new RunDeliveryValidationError("Run cancellation request contains unknown fields");
|
|
208
|
+
}
|
|
209
|
+
const requestedAt = normalizeTimestamp(input.requestedAt, () => new Date());
|
|
210
|
+
if (input.reason !== undefined) {
|
|
211
|
+
assertBoundedString(input.reason, "cancellation.reason", 500);
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
requestedAt,
|
|
215
|
+
...(input.reason === undefined ? {} : { reason: input.reason }),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function assertBoundedString(value, field, max) {
|
|
219
|
+
if (typeof value !== "string" || value.length < 1 || value.length > max) {
|
|
220
|
+
throw new RunDeliveryValidationError(`${field} must be between 1 and ${max} characters`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function assertPositiveInteger(value, field) {
|
|
224
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
225
|
+
throw new RunDeliveryValidationError(`${field} must be a positive integer`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function validNow(now) {
|
|
229
|
+
const value = now();
|
|
230
|
+
if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
|
|
231
|
+
throw new RunExecutionLeaseValidationError("Run execution lease clock returned an invalid date");
|
|
232
|
+
}
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
235
|
+
function assertLeaseRunId(runId) {
|
|
236
|
+
try {
|
|
237
|
+
assertBoundedString(runId, "runId", MAX_RUN_ID_LENGTH);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
throw new RunExecutionLeaseValidationError(error.message);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
export function normalizeRunExecutionLease(runId, input, options = {}) {
|
|
244
|
+
assertLeaseRunId(runId);
|
|
245
|
+
const now = options.now ?? new Date();
|
|
246
|
+
if (!(now instanceof Date) || !Number.isFinite(now.getTime())) {
|
|
247
|
+
throw new RunExecutionLeaseValidationError("Run execution lease clock returned an invalid date");
|
|
248
|
+
}
|
|
249
|
+
const requireFuture = options.requireFuture ?? true;
|
|
250
|
+
if (!isPlainRecord(input)) {
|
|
251
|
+
throw new RunExecutionLeaseValidationError("Run execution lease must be an object");
|
|
252
|
+
}
|
|
253
|
+
const keys = Object.keys(input);
|
|
254
|
+
if (keys.some((key) => key !== "owner" && key !== "token" && key !== "expiresAt")) {
|
|
255
|
+
throw new RunExecutionLeaseValidationError("Run execution lease contains unknown fields");
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
assertBoundedString(input.owner, "lease.owner", 500);
|
|
259
|
+
assertBoundedString(input.token, "lease.token", 500);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
throw new RunExecutionLeaseValidationError(error.message);
|
|
263
|
+
}
|
|
264
|
+
if (typeof input.expiresAt !== "string" || input.expiresAt.length === 0) {
|
|
265
|
+
throw new RunExecutionLeaseValidationError("lease.expiresAt must be a valid timestamp");
|
|
266
|
+
}
|
|
267
|
+
const expiresAt = new Date(input.expiresAt);
|
|
268
|
+
if (!Number.isFinite(expiresAt.getTime())) {
|
|
269
|
+
throw new RunExecutionLeaseValidationError("lease.expiresAt must be a valid timestamp");
|
|
270
|
+
}
|
|
271
|
+
if (requireFuture && expiresAt.getTime() <= now.getTime()) {
|
|
272
|
+
throw new RunExecutionLeaseValidationError("lease.expiresAt must be in the future");
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
owner: input.owner,
|
|
276
|
+
token: input.token,
|
|
277
|
+
expiresAt: expiresAt.toISOString(),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function sameLease(left, right) {
|
|
281
|
+
return left.owner === right.owner
|
|
282
|
+
&& left.token === right.token
|
|
283
|
+
&& left.expiresAt === right.expiresAt;
|
|
284
|
+
}
|
|
285
|
+
export function normalizeRunStreamEventInput(runId, input, options = {}) {
|
|
286
|
+
validateRunDeliveryRunId(runId);
|
|
287
|
+
if (!isPlainRecord(input)) {
|
|
288
|
+
throw new RunDeliveryValidationError("Run event must be an object");
|
|
289
|
+
}
|
|
290
|
+
assertBoundedString(input.type, "event.type", MAX_RUN_EVENT_TYPE_LENGTH);
|
|
291
|
+
if (!isPlainRecord(input.data)) {
|
|
292
|
+
throw new RunDeliveryValidationError("Run event data must be a JSON object");
|
|
293
|
+
}
|
|
294
|
+
if (input.id !== undefined) {
|
|
295
|
+
assertBoundedString(input.id, "event.id", MAX_RUN_EVENT_ID_LENGTH);
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
...(input.id === undefined ? {} : { id: input.id }),
|
|
299
|
+
type: input.type,
|
|
300
|
+
data: cloneJsonRecord(input.data, "event.data"),
|
|
301
|
+
createdAt: normalizeTimestamp(input.createdAt, options.now ?? (() => new Date())),
|
|
302
|
+
createdAtExplicit: input.createdAt !== undefined,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
export function materializeRunStreamEvent(runId, sequence, input, options = {}) {
|
|
306
|
+
validateRunDeliveryRunId(runId);
|
|
307
|
+
if (!Number.isSafeInteger(sequence) || sequence < 1) {
|
|
308
|
+
throw new RunDeliveryValidationError("Run event sequence must be a positive safe integer");
|
|
309
|
+
}
|
|
310
|
+
const maxEventBytes = options.maxEventBytes ?? MAX_RUN_EVENT_BYTES;
|
|
311
|
+
assertPositiveInteger(maxEventBytes, "maxEventBytes");
|
|
312
|
+
const event = {
|
|
313
|
+
id: input.id ?? formatRunEventCursor(sequence),
|
|
314
|
+
runId,
|
|
315
|
+
sequence,
|
|
316
|
+
schemaVersion: RUN_STREAM_EVENT_SCHEMA_VERSION,
|
|
317
|
+
type: input.type,
|
|
318
|
+
data: cloneJsonRecord(input.data, "event.data"),
|
|
319
|
+
createdAt: input.createdAt,
|
|
320
|
+
};
|
|
321
|
+
const byteLength = new TextEncoder().encode(JSON.stringify(event)).byteLength;
|
|
322
|
+
if (byteLength > maxEventBytes) {
|
|
323
|
+
throw new RunDeliveryValidationError(`Run event exceeds maximum size of ${maxEventBytes} bytes`);
|
|
324
|
+
}
|
|
325
|
+
return event;
|
|
326
|
+
}
|
|
327
|
+
export function runStreamEventMatchesInput(event, input) {
|
|
328
|
+
return canonicalJson({
|
|
329
|
+
type: event.type,
|
|
330
|
+
data: event.data,
|
|
331
|
+
createdAt: event.createdAt,
|
|
332
|
+
}) === canonicalJson({
|
|
333
|
+
type: input.type,
|
|
334
|
+
data: input.data,
|
|
335
|
+
createdAt: input.createdAtExplicit ? input.createdAt : event.createdAt,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function normalizeTimestamp(value, now) {
|
|
339
|
+
const candidate = value ?? now().toISOString();
|
|
340
|
+
if (typeof candidate !== "string" || candidate.length === 0) {
|
|
341
|
+
throw new RunDeliveryValidationError("event.createdAt must be a valid timestamp");
|
|
342
|
+
}
|
|
343
|
+
const parsed = new Date(candidate);
|
|
344
|
+
if (!Number.isFinite(parsed.getTime())) {
|
|
345
|
+
throw new RunDeliveryValidationError("event.createdAt must be a valid timestamp");
|
|
346
|
+
}
|
|
347
|
+
return parsed.toISOString();
|
|
348
|
+
}
|
|
349
|
+
function isPlainRecord(value) {
|
|
350
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
351
|
+
return false;
|
|
352
|
+
const prototype = Object.getPrototypeOf(value);
|
|
353
|
+
return prototype === Object.prototype || prototype === null;
|
|
354
|
+
}
|
|
355
|
+
function cloneJsonRecord(value, path) {
|
|
356
|
+
return cloneJsonValue(value, path, new WeakSet());
|
|
357
|
+
}
|
|
358
|
+
function cloneJsonValue(value, path, ancestors) {
|
|
359
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
360
|
+
return value;
|
|
361
|
+
if (typeof value === "number") {
|
|
362
|
+
if (!Number.isFinite(value)) {
|
|
363
|
+
throw new RunDeliveryValidationError(`${path} contains a non-finite number`);
|
|
364
|
+
}
|
|
365
|
+
return value;
|
|
366
|
+
}
|
|
367
|
+
if (typeof value !== "object") {
|
|
368
|
+
throw new RunDeliveryValidationError(`${path} must be JSON-serializable`);
|
|
369
|
+
}
|
|
370
|
+
if (ancestors.has(value)) {
|
|
371
|
+
throw new RunDeliveryValidationError(`${path} contains a circular reference`);
|
|
372
|
+
}
|
|
373
|
+
ancestors.add(value);
|
|
374
|
+
try {
|
|
375
|
+
if (Array.isArray(value)) {
|
|
376
|
+
return value.map((item, index) => {
|
|
377
|
+
if (!(index in value)) {
|
|
378
|
+
throw new RunDeliveryValidationError(`${path} contains a sparse array`);
|
|
379
|
+
}
|
|
380
|
+
return cloneJsonValue(item, `${path}[${index}]`, ancestors);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
if (!isPlainRecord(value)) {
|
|
384
|
+
throw new RunDeliveryValidationError(`${path} contains a non-JSON object`);
|
|
385
|
+
}
|
|
386
|
+
const clone = {};
|
|
387
|
+
for (const [key, item] of Object.entries(value)) {
|
|
388
|
+
clone[key] = cloneJsonValue(item, `${path}.${key}`, ancestors);
|
|
389
|
+
}
|
|
390
|
+
return clone;
|
|
391
|
+
}
|
|
392
|
+
finally {
|
|
393
|
+
ancestors.delete(value);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function canonicalJson(value) {
|
|
397
|
+
if (value === null || typeof value !== "object")
|
|
398
|
+
return JSON.stringify(value);
|
|
399
|
+
if (Array.isArray(value))
|
|
400
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
401
|
+
return `{${Object.keys(value)
|
|
402
|
+
.sort()
|
|
403
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
404
|
+
.join(",")}}`;
|
|
405
|
+
}
|
|
406
|
+
function cloneRunEvent(event) {
|
|
407
|
+
return {
|
|
408
|
+
...event,
|
|
409
|
+
data: cloneJsonRecord(event.data, "event.data"),
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
//# sourceMappingURL=run-delivery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-delivery.js","sourceRoot":"","sources":["../src/run-delivery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-delivery.test.d.ts","sourceRoot":"","sources":["../src/run-delivery.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { InMemoryRunEventStore, InMemoryRunExecutionLeaseStore, RunDeliveryValidationError, RunEventConflictError, RunEventCursorError, RunExecutionLeaseValidationError, parseRunEventCursor, resolveRunDeliveryPolicy, } from "./run-delivery.js";
|
|
3
|
+
describe("run delivery policy", () => {
|
|
4
|
+
it("preserves attached cancellation when the policy is omitted", () => {
|
|
5
|
+
expect(resolveRunDeliveryPolicy()).toEqual({ onDisconnect: "cancel" });
|
|
6
|
+
expect(resolveRunDeliveryPolicy({})).toEqual({ onDisconnect: "cancel" });
|
|
7
|
+
});
|
|
8
|
+
it("accepts the explicit durable policy without retaining caller objects", () => {
|
|
9
|
+
const input = { onDisconnect: "continue" };
|
|
10
|
+
const resolved = resolveRunDeliveryPolicy(input);
|
|
11
|
+
expect(resolved).toEqual({ onDisconnect: "continue" });
|
|
12
|
+
expect(resolved).not.toBe(input);
|
|
13
|
+
});
|
|
14
|
+
it.each([
|
|
15
|
+
null,
|
|
16
|
+
"continue",
|
|
17
|
+
[],
|
|
18
|
+
{ onDisconnect: "later" },
|
|
19
|
+
{ onDisconnect: true },
|
|
20
|
+
{ onDisconnect: "continue", retention: "forever" },
|
|
21
|
+
])("rejects malformed or unbounded delivery policy %#", (value) => {
|
|
22
|
+
expect(() => resolveRunDeliveryPolicy(value)).toThrow(RunDeliveryValidationError);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe("InMemoryRunExecutionLeaseStore", () => {
|
|
26
|
+
const first = {
|
|
27
|
+
owner: "worker-a",
|
|
28
|
+
token: "token-a",
|
|
29
|
+
expiresAt: "2026-08-19T10:05:00.000Z",
|
|
30
|
+
};
|
|
31
|
+
it("allows one owner and makes an identical claim idempotent", async () => {
|
|
32
|
+
const store = new InMemoryRunExecutionLeaseStore({
|
|
33
|
+
now: () => new Date("2026-08-19T10:00:00.000Z"),
|
|
34
|
+
});
|
|
35
|
+
expect(await store.claim("run-a", first)).toBe(true);
|
|
36
|
+
expect(await store.claim("run-a", first)).toBe(true);
|
|
37
|
+
expect(await store.claim("run-a", {
|
|
38
|
+
owner: "worker-b",
|
|
39
|
+
token: "token-b",
|
|
40
|
+
expiresAt: "2026-08-19T10:05:00.000Z",
|
|
41
|
+
})).toBe(false);
|
|
42
|
+
expect(await store.get("run-a")).toEqual(first);
|
|
43
|
+
});
|
|
44
|
+
it("renews only the exact active owner token", async () => {
|
|
45
|
+
const store = new InMemoryRunExecutionLeaseStore({
|
|
46
|
+
now: () => new Date("2026-08-19T10:00:00.000Z"),
|
|
47
|
+
});
|
|
48
|
+
await store.claim("run-a", first);
|
|
49
|
+
expect(await store.renew("run-a", {
|
|
50
|
+
...first,
|
|
51
|
+
expiresAt: "2026-08-19T10:10:00.000Z",
|
|
52
|
+
})).toBe(true);
|
|
53
|
+
expect(await store.renew("run-a", {
|
|
54
|
+
owner: "worker-a",
|
|
55
|
+
token: "stale-token",
|
|
56
|
+
expiresAt: "2026-08-19T10:15:00.000Z",
|
|
57
|
+
})).toBe(false);
|
|
58
|
+
expect((await store.get("run-a"))?.expiresAt).toBe("2026-08-19T10:10:00.000Z");
|
|
59
|
+
});
|
|
60
|
+
it("allows takeover at expiry and prevents the stale owner from releasing it", async () => {
|
|
61
|
+
let now = new Date("2026-08-19T10:00:00.000Z");
|
|
62
|
+
const store = new InMemoryRunExecutionLeaseStore({ now: () => now });
|
|
63
|
+
await store.claim("run-a", first);
|
|
64
|
+
now = new Date(first.expiresAt);
|
|
65
|
+
const takeover = {
|
|
66
|
+
owner: "worker-b",
|
|
67
|
+
token: "token-b",
|
|
68
|
+
expiresAt: "2026-08-19T10:20:00.000Z",
|
|
69
|
+
};
|
|
70
|
+
expect(await store.claim("run-a", takeover)).toBe(true);
|
|
71
|
+
expect(await store.release("run-a", first)).toBe(false);
|
|
72
|
+
expect(await store.get("run-a")).toEqual(takeover);
|
|
73
|
+
expect(await store.release("run-a", takeover)).toBe(true);
|
|
74
|
+
expect(await store.get("run-a")).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
it.each([
|
|
77
|
+
["", first],
|
|
78
|
+
["run-a", { ...first, owner: "" }],
|
|
79
|
+
["run-a", { ...first, token: "" }],
|
|
80
|
+
["run-a", { ...first, expiresAt: "invalid" }],
|
|
81
|
+
["run-a", { ...first, expiresAt: "2026-08-19T10:00:00.000Z" }],
|
|
82
|
+
])("rejects malformed or already-expired leases %#", async (runId, lease) => {
|
|
83
|
+
const store = new InMemoryRunExecutionLeaseStore({
|
|
84
|
+
now: () => new Date("2026-08-19T10:00:00.000Z"),
|
|
85
|
+
});
|
|
86
|
+
await expect(store.claim(runId, lease)).rejects.toThrow(RunExecutionLeaseValidationError);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe("run event cursors", () => {
|
|
90
|
+
it.each([
|
|
91
|
+
[undefined, 0],
|
|
92
|
+
["", 0],
|
|
93
|
+
["0", 0],
|
|
94
|
+
["1", 1],
|
|
95
|
+
["9007199254740991", Number.MAX_SAFE_INTEGER],
|
|
96
|
+
])("parses cursor %j", (cursor, expected) => {
|
|
97
|
+
expect(parseRunEventCursor(cursor)).toBe(expected);
|
|
98
|
+
});
|
|
99
|
+
it.each(["-1", "+1", "01", "1.2", "1e3", "NaN", "Infinity", " 1 "])("rejects ambiguous cursor %j", (cursor) => {
|
|
100
|
+
expect(() => parseRunEventCursor(cursor)).toThrow(RunEventCursorError);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe("InMemoryRunEventStore", () => {
|
|
104
|
+
it("assigns ordered run-scoped sequences and paginates strictly after a cursor", async () => {
|
|
105
|
+
const store = new InMemoryRunEventStore({
|
|
106
|
+
now: () => new Date("2026-08-19T10:00:00.000Z"),
|
|
107
|
+
});
|
|
108
|
+
await store.append("run-a", { type: "run.started", data: {} });
|
|
109
|
+
await store.append("run-a", {
|
|
110
|
+
type: "output.text.delta",
|
|
111
|
+
data: { text: "hello" },
|
|
112
|
+
});
|
|
113
|
+
await store.append("run-a", {
|
|
114
|
+
type: "run.completed",
|
|
115
|
+
data: { finishReason: "stop" },
|
|
116
|
+
});
|
|
117
|
+
const first = await store.listAfter("run-a", undefined, 2);
|
|
118
|
+
expect(first.events.map((event) => [event.id, event.sequence, event.type])).toEqual([
|
|
119
|
+
["1", 1, "run.started"],
|
|
120
|
+
["2", 2, "output.text.delta"],
|
|
121
|
+
]);
|
|
122
|
+
expect(first.nextCursor).toBe("2");
|
|
123
|
+
expect(first.hasMore).toBe(true);
|
|
124
|
+
const second = await store.listAfter("run-a", first.nextCursor, 2);
|
|
125
|
+
expect(second.events.map((event) => event.id)).toEqual(["3"]);
|
|
126
|
+
expect(second.nextCursor).toBe("3");
|
|
127
|
+
expect(second.hasMore).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
it("keeps runs isolated even when they use the same producer event id", async () => {
|
|
130
|
+
const store = new InMemoryRunEventStore();
|
|
131
|
+
const left = await store.append("run-a", {
|
|
132
|
+
id: "provider-event",
|
|
133
|
+
type: "tool.completed",
|
|
134
|
+
data: { tool: "left" },
|
|
135
|
+
});
|
|
136
|
+
const right = await store.append("run-b", {
|
|
137
|
+
id: "provider-event",
|
|
138
|
+
type: "tool.completed",
|
|
139
|
+
data: { tool: "right" },
|
|
140
|
+
});
|
|
141
|
+
expect(left.sequence).toBe(1);
|
|
142
|
+
expect(right.sequence).toBe(1);
|
|
143
|
+
expect((await store.listAfter("run-a")).events[0]?.data).toEqual({ tool: "left" });
|
|
144
|
+
expect((await store.listAfter("run-b")).events[0]?.data).toEqual({ tool: "right" });
|
|
145
|
+
});
|
|
146
|
+
it("returns the existing event for an identical producer retry", async () => {
|
|
147
|
+
const store = new InMemoryRunEventStore();
|
|
148
|
+
const input = {
|
|
149
|
+
id: "stable-tool-result",
|
|
150
|
+
type: "tool.completed",
|
|
151
|
+
data: { toolCallId: "call-1", result: { ok: true } },
|
|
152
|
+
createdAt: "2026-08-19T10:00:00.000Z",
|
|
153
|
+
};
|
|
154
|
+
const first = await store.append("run-a", input);
|
|
155
|
+
const retried = await store.append("run-a", {
|
|
156
|
+
...input,
|
|
157
|
+
data: { toolCallId: "call-1", result: { ok: true } },
|
|
158
|
+
});
|
|
159
|
+
expect(retried).toEqual(first);
|
|
160
|
+
expect((await store.listAfter("run-a")).events).toHaveLength(1);
|
|
161
|
+
});
|
|
162
|
+
it("deduplicates a later producer retry when createdAt was server-generated", async () => {
|
|
163
|
+
let now = new Date("2026-08-19T10:00:00.000Z");
|
|
164
|
+
const store = new InMemoryRunEventStore({ now: () => now });
|
|
165
|
+
const input = {
|
|
166
|
+
id: "stable-tool-result",
|
|
167
|
+
type: "tool.completed",
|
|
168
|
+
data: { toolCallId: "call-1", result: { ok: true } },
|
|
169
|
+
};
|
|
170
|
+
const first = await store.append("run-a", input);
|
|
171
|
+
now = new Date("2026-08-19T10:01:00.000Z");
|
|
172
|
+
await expect(store.append("run-a", input)).resolves.toEqual(first);
|
|
173
|
+
expect((await store.listAfter("run-a")).events).toHaveLength(1);
|
|
174
|
+
});
|
|
175
|
+
it("fails closed when a producer reuses an event id with different content", async () => {
|
|
176
|
+
const store = new InMemoryRunEventStore();
|
|
177
|
+
await store.append("run-a", {
|
|
178
|
+
id: "stable-id",
|
|
179
|
+
type: "output.text.delta",
|
|
180
|
+
data: { text: "one" },
|
|
181
|
+
});
|
|
182
|
+
await expect(store.append("run-a", {
|
|
183
|
+
id: "stable-id",
|
|
184
|
+
type: "output.text.delta",
|
|
185
|
+
data: { text: "two" },
|
|
186
|
+
})).rejects.toBeInstanceOf(RunEventConflictError);
|
|
187
|
+
});
|
|
188
|
+
it("serializes concurrent append calls without duplicate sequences", async () => {
|
|
189
|
+
const store = new InMemoryRunEventStore();
|
|
190
|
+
const events = await Promise.all(Array.from({ length: 100 }, (_, index) => store.append("run-a", {
|
|
191
|
+
id: `event-${index}`,
|
|
192
|
+
type: "output.text.delta",
|
|
193
|
+
data: { text: String(index) },
|
|
194
|
+
})));
|
|
195
|
+
expect(events.map((event) => event.sequence).sort((a, b) => a - b)).toEqual(Array.from({ length: 100 }, (_, index) => index + 1));
|
|
196
|
+
expect((await store.bounds("run-a"))).toEqual({
|
|
197
|
+
firstCursor: "1",
|
|
198
|
+
lastCursor: "100",
|
|
199
|
+
count: 100,
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
it.each([
|
|
203
|
+
["", { type: "run.started", data: {} }],
|
|
204
|
+
["run-a", { type: "", data: {} }],
|
|
205
|
+
["run-a", { type: "run.started", data: { invalid: undefined } }],
|
|
206
|
+
["run-a", { type: "run.started", data: { invalid: Number.NaN } }],
|
|
207
|
+
])("rejects malformed event input %#", async (runId, event) => {
|
|
208
|
+
const store = new InMemoryRunEventStore();
|
|
209
|
+
await expect(store.append(runId, event)).rejects.toThrow(RunDeliveryValidationError);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
//# sourceMappingURL=run-delivery.test.js.map
|