@effect-agent/storage-memory 0.1.0-beta.42 → 0.1.0-beta.44
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.mjs.map +1 -1
- package/package.json +1 -48
- package/src/memory-ledger.ts +192 -0
- package/src/memory-schedule-store.ts +42 -0
- package/src/memory-storage.ts +48 -0
- package/src/memory-subscription-store.ts +149 -0
- package/src/semantic-memory-index.ts +37 -0
- package/src/testing.ts +1 -0
|
@@ -59,7 +59,9 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
59
59
|
(record, ownerLimit) =>
|
|
60
60
|
Effect.gen(function* () {
|
|
61
61
|
const encoded = yield* encodeRecord("insert", record);
|
|
62
|
+
|
|
62
63
|
yield* failpoint.hit("schedule:insert:before");
|
|
64
|
+
|
|
63
65
|
const decision = yield* Effect.uninterruptible(
|
|
64
66
|
Ref.modify(
|
|
65
67
|
state,
|
|
@@ -74,17 +76,20 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
74
76
|
] => {
|
|
75
77
|
const key = scheduleKeyString(record);
|
|
76
78
|
const existingText = current.records.get(key);
|
|
79
|
+
|
|
77
80
|
if (existingText !== undefined) {
|
|
78
81
|
const decoded = Result.try({
|
|
79
82
|
try: () => Schema.decodeSync(Schema.fromJsonString(ScheduleRecord))(existingText),
|
|
80
83
|
catch: () => storageError("insert", "corrupt"),
|
|
81
84
|
});
|
|
85
|
+
|
|
82
86
|
if (Result.isFailure(decoded)) {
|
|
83
87
|
return [Result.fail(decoded.failure), current];
|
|
84
88
|
}
|
|
85
89
|
if (decoded.success.creationFingerprint === record.creationFingerprint) {
|
|
86
90
|
return [Result.succeed(decoded.success), current];
|
|
87
91
|
}
|
|
92
|
+
|
|
88
93
|
return [
|
|
89
94
|
Result.fail(
|
|
90
95
|
ScheduleConflict.make({ reason: "creation", key: scheduleKeyOf(record) }),
|
|
@@ -94,11 +99,13 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
let ownerCount = 0;
|
|
102
|
+
|
|
97
103
|
for (const text of current.records.values()) {
|
|
98
104
|
const decoded = Result.try({
|
|
99
105
|
try: () => Schema.decodeSync(Schema.fromJsonString(ScheduleRecord))(text),
|
|
100
106
|
catch: () => storageError("insert", "corrupt"),
|
|
101
107
|
});
|
|
108
|
+
|
|
102
109
|
if (Result.isFailure(decoded)) {
|
|
103
110
|
return [Result.fail(decoded.failure), current];
|
|
104
111
|
}
|
|
@@ -112,14 +119,19 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
112
119
|
return [Result.fail(ScheduleCapacityError.make({ limit: ownerLimit })), current];
|
|
113
120
|
}
|
|
114
121
|
const records = new Map(current.records);
|
|
122
|
+
|
|
115
123
|
records.set(key, encoded);
|
|
116
124
|
const next = { records };
|
|
125
|
+
|
|
117
126
|
return [Result.succeed(record), next];
|
|
118
127
|
},
|
|
119
128
|
),
|
|
120
129
|
);
|
|
130
|
+
|
|
121
131
|
const inserted = yield* Effect.fromResult(decision);
|
|
132
|
+
|
|
122
133
|
yield* failpoint.hit("schedule:insert:after");
|
|
134
|
+
|
|
123
135
|
return yield* decodeRecord("insert", yield* encodeRecord("insert", inserted));
|
|
124
136
|
}),
|
|
125
137
|
);
|
|
@@ -128,6 +140,7 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
128
140
|
function* (key) {
|
|
129
141
|
const decodedKey = yield* decodeInput("get", ScheduleKey, key);
|
|
130
142
|
const text = (yield* Ref.get(state)).records.get(scheduleKeyString(decodedKey));
|
|
143
|
+
|
|
131
144
|
return text === undefined ? null : yield* decodeRecord("get", text);
|
|
132
145
|
},
|
|
133
146
|
);
|
|
@@ -136,8 +149,10 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
136
149
|
function* (request) {
|
|
137
150
|
const decodedRequest = yield* decodeInput("list", SchedulePageRequest, request);
|
|
138
151
|
const records: Array<ScheduleRecord> = [];
|
|
152
|
+
|
|
139
153
|
for (const text of (yield* Ref.get(state)).records.values()) {
|
|
140
154
|
const record = yield* decodeRecord("list", text);
|
|
155
|
+
|
|
141
156
|
if (
|
|
142
157
|
sameOwner(record, decodedRequest.owner) &&
|
|
143
158
|
(decodedRequest.after === undefined ||
|
|
@@ -149,6 +164,7 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
149
164
|
records.sort((left, right) => compareScheduleNames(left.scheduleId, right.scheduleId));
|
|
150
165
|
const hasNext = records.length > decodedRequest.limit;
|
|
151
166
|
const items = records.slice(0, decodedRequest.limit);
|
|
167
|
+
|
|
152
168
|
return {
|
|
153
169
|
items,
|
|
154
170
|
next: hasNext ? (items.at(-1)?.scheduleId ?? null) : null,
|
|
@@ -161,7 +177,9 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
161
177
|
Effect.gen(function* () {
|
|
162
178
|
const decodedKey = yield* decodeInput("change", ScheduleKey, key);
|
|
163
179
|
const decodedCommand = yield* decodeInput("change", ScheduleChange, command);
|
|
180
|
+
|
|
164
181
|
yield* failpoint.hit(`schedule:${decodedCommand._tag.toLowerCase()}:before`);
|
|
182
|
+
|
|
165
183
|
const decision = yield* Effect.uninterruptible(
|
|
166
184
|
Ref.modify(
|
|
167
185
|
state,
|
|
@@ -176,17 +194,21 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
176
194
|
] => {
|
|
177
195
|
const storageKey = scheduleKeyString(decodedKey);
|
|
178
196
|
const text = current.records.get(storageKey);
|
|
197
|
+
|
|
179
198
|
if (text === undefined) {
|
|
180
199
|
return [Result.fail(ScheduleNotFound.make({ key: decodedKey })), current];
|
|
181
200
|
}
|
|
201
|
+
|
|
182
202
|
const decoded = Result.try({
|
|
183
203
|
try: () => Schema.decodeSync(Schema.fromJsonString(ScheduleRecord))(text),
|
|
184
204
|
catch: () => storageError("change", "corrupt"),
|
|
185
205
|
});
|
|
206
|
+
|
|
186
207
|
if (Result.isFailure(decoded)) {
|
|
187
208
|
return [Result.fail(decoded.failure), current];
|
|
188
209
|
}
|
|
189
210
|
const applied = applyScheduleChange(decoded.success, decodedCommand);
|
|
211
|
+
|
|
190
212
|
if (Result.isFailure(applied)) {
|
|
191
213
|
return [Result.fail(applied.failure), current];
|
|
192
214
|
}
|
|
@@ -195,10 +217,12 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
195
217
|
}
|
|
196
218
|
if (!scheduleUsesCapacity(decoded.success) && scheduleUsesCapacity(applied.success)) {
|
|
197
219
|
let count = 0;
|
|
220
|
+
|
|
198
221
|
for (const text of current.records.values()) {
|
|
199
222
|
const candidate = Schema.decodeUnknownResult(
|
|
200
223
|
Schema.fromJsonString(ScheduleRecord),
|
|
201
224
|
)(text);
|
|
225
|
+
|
|
202
226
|
if (Result.isFailure(candidate))
|
|
203
227
|
return [Result.fail(storageError("change", "corrupt")), current];
|
|
204
228
|
if (
|
|
@@ -210,23 +234,30 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
210
234
|
if (count >= ownerLimit)
|
|
211
235
|
return [Result.fail(ScheduleCapacityError.make({ limit: ownerLimit })), current];
|
|
212
236
|
}
|
|
237
|
+
|
|
213
238
|
const encoded = Result.try({
|
|
214
239
|
try: () =>
|
|
215
240
|
Schema.encodeSync(Schema.fromJsonString(ScheduleRecord))(applied.success),
|
|
216
241
|
catch: () => storageError("change", "corrupt"),
|
|
217
242
|
});
|
|
243
|
+
|
|
218
244
|
if (Result.isFailure(encoded)) {
|
|
219
245
|
return [Result.fail(encoded.failure), current];
|
|
220
246
|
}
|
|
221
247
|
const records = new Map(current.records);
|
|
248
|
+
|
|
222
249
|
records.set(storageKey, encoded.success);
|
|
223
250
|
const next = { records };
|
|
251
|
+
|
|
224
252
|
return [Result.succeed(applied.success), next];
|
|
225
253
|
},
|
|
226
254
|
),
|
|
227
255
|
);
|
|
256
|
+
|
|
228
257
|
const changed = yield* Effect.fromResult(decision);
|
|
258
|
+
|
|
229
259
|
yield* failpoint.hit(`schedule:${decodedCommand._tag.toLowerCase()}:after`);
|
|
260
|
+
|
|
230
261
|
return yield* decodeRecord("change", yield* encodeRecord("change", changed));
|
|
231
262
|
}),
|
|
232
263
|
);
|
|
@@ -235,12 +266,16 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
235
266
|
function* (nowMillis, limit, owner, after) {
|
|
236
267
|
const decodedOwner =
|
|
237
268
|
owner === undefined ? undefined : yield* decodeInput("due", ScheduleOwner, owner);
|
|
269
|
+
|
|
238
270
|
const cursor =
|
|
239
271
|
after === undefined ? undefined : yield* decodeInput("due", ScheduleDueCursor, after);
|
|
272
|
+
|
|
240
273
|
const records: Array<ScheduleDueCursor> = [];
|
|
274
|
+
|
|
241
275
|
for (const text of (yield* Ref.get(state)).records.values()) {
|
|
242
276
|
const record = yield* decodeRecord("due", text);
|
|
243
277
|
const deadline = scheduleDeadline(record);
|
|
278
|
+
|
|
244
279
|
if (
|
|
245
280
|
deadline !== null &&
|
|
246
281
|
deadline <= nowMillis &&
|
|
@@ -254,8 +289,10 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
254
289
|
}
|
|
255
290
|
records.sort((left, right) => {
|
|
256
291
|
const byDeadline = left.deadlineAtMillis - right.deadlineAtMillis;
|
|
292
|
+
|
|
257
293
|
return byDeadline !== 0 ? byDeadline : compareScheduleKeys(left, right);
|
|
258
294
|
});
|
|
295
|
+
|
|
259
296
|
return records.slice(0, limit);
|
|
260
297
|
},
|
|
261
298
|
);
|
|
@@ -265,13 +302,18 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
265
302
|
)(function* (owner) {
|
|
266
303
|
const decodedOwner =
|
|
267
304
|
owner === undefined ? undefined : yield* decodeInput("nextDeadline", ScheduleOwner, owner);
|
|
305
|
+
|
|
268
306
|
let earliest: number | null = null;
|
|
307
|
+
|
|
269
308
|
for (const text of (yield* Ref.get(state)).records.values()) {
|
|
270
309
|
const record = yield* decodeRecord("nextDeadline", text);
|
|
310
|
+
|
|
271
311
|
if (decodedOwner !== undefined && !sameOwner(record, decodedOwner)) continue;
|
|
272
312
|
const deadline = scheduleDeadline(record);
|
|
313
|
+
|
|
273
314
|
if (deadline !== null && (earliest === null || deadline < earliest)) earliest = deadline;
|
|
274
315
|
}
|
|
316
|
+
|
|
275
317
|
return earliest;
|
|
276
318
|
});
|
|
277
319
|
|
package/src/memory-storage.ts
CHANGED
|
@@ -104,10 +104,12 @@ const offsetSequence = Effect.fn("MemoryThreadStore.offsetSequence")((
|
|
|
104
104
|
if (offset === undefined) return Effect.succeed(ZERO_CANONICAL_SEQUENCE);
|
|
105
105
|
const prefix = `memory:v1:${Encoding.encodeBase64(threadId)}:`;
|
|
106
106
|
const encodedSequence = offset.startsWith(prefix) ? offset.slice(prefix.length) : "";
|
|
107
|
+
|
|
107
108
|
if (!/^\d+$/.test(encodedSequence)) {
|
|
108
109
|
return Effect.fail(storeError("observe", "Malformed observation offset"));
|
|
109
110
|
}
|
|
110
111
|
const sequence = Number(encodedSequence);
|
|
112
|
+
|
|
111
113
|
return Number.isSafeInteger(sequence)
|
|
112
114
|
? Schema.decodeUnknownEffect(CanonicalSequence)(sequence).pipe(
|
|
113
115
|
Effect.mapError(() => storeError("observe", "Malformed observation offset")),
|
|
@@ -123,6 +125,7 @@ const findThread = Effect.fn("MemoryThreadStore.findThread")((
|
|
|
123
125
|
threadId: ThreadId,
|
|
124
126
|
): Effect.Effect<StoredThread, ThreadNotMaterialized> => {
|
|
125
127
|
const thread = state.threads.get(threadId);
|
|
128
|
+
|
|
126
129
|
return thread === undefined
|
|
127
130
|
? Effect.fail(ThreadNotMaterialized.make({ threadId }))
|
|
128
131
|
: Effect.succeed(thread);
|
|
@@ -140,6 +143,7 @@ const validateCheckpointVersion = Effect.fn("MemoryThreadStore.validateCheckpoin
|
|
|
140
143
|
const envelope = yield* Schema.decodeUnknownEffect(CheckpointVersionEnvelope)(value).pipe(
|
|
141
144
|
Effect.mapError(() => storeError("saveCheckpoint", "Invalid saveCheckpoint request")),
|
|
142
145
|
);
|
|
146
|
+
|
|
143
147
|
if (envelope.checkpoint.schemaVersion !== 1) {
|
|
144
148
|
return yield* CheckpointRejected.make({
|
|
145
149
|
threadId: envelope.checkpoint.threadId,
|
|
@@ -153,6 +157,7 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
153
157
|
const crypto = yield* Crypto.Crypto;
|
|
154
158
|
const state = yield* Ref.make<MemoryState>({ threads: new Map() });
|
|
155
159
|
const updates = yield* PubSub.sliding<void>(1);
|
|
160
|
+
|
|
156
161
|
yield* Effect.addFinalizer(() => PubSub.shutdown(updates));
|
|
157
162
|
|
|
158
163
|
const materialize: ThreadStore["Service"]["materialize"] = Effect.fn(
|
|
@@ -160,10 +165,12 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
160
165
|
)((unvalidated) =>
|
|
161
166
|
Effect.gen(function* () {
|
|
162
167
|
const request = yield* validate(ThreadMaterialization, "materialize", unvalidated);
|
|
168
|
+
|
|
163
169
|
const decision = yield* Ref.modify(
|
|
164
170
|
state,
|
|
165
171
|
(current): readonly [MaterializeDecision, MemoryState] => {
|
|
166
172
|
const existing = current.threads.get(request.threadId);
|
|
173
|
+
|
|
167
174
|
if (existing !== undefined) {
|
|
168
175
|
if (request.producerEpoch < existing.producerEpoch) {
|
|
169
176
|
return [
|
|
@@ -182,10 +189,12 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
182
189
|
return [{ _tag: "success" }, current];
|
|
183
190
|
}
|
|
184
191
|
const threads = new Map(current.threads);
|
|
192
|
+
|
|
185
193
|
threads.set(request.threadId, {
|
|
186
194
|
...existing,
|
|
187
195
|
producerEpoch: request.producerEpoch,
|
|
188
196
|
});
|
|
197
|
+
|
|
189
198
|
return [{ _tag: "success" }, { threads }];
|
|
190
199
|
}
|
|
191
200
|
if (current.threads.size >= MAX_THREADS) {
|
|
@@ -198,6 +207,7 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
198
207
|
];
|
|
199
208
|
}
|
|
200
209
|
const threads = new Map(current.threads);
|
|
210
|
+
|
|
201
211
|
threads.set(request.threadId, {
|
|
202
212
|
producerEpoch: request.producerEpoch,
|
|
203
213
|
tailSequence: ZERO_CANONICAL_SEQUENCE,
|
|
@@ -208,9 +218,11 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
208
218
|
tailDigests: new Map([[ZERO_CANONICAL_SEQUENCE, EMPTY_TAIL_DIGEST]]),
|
|
209
219
|
checkpoints: new Map(),
|
|
210
220
|
});
|
|
221
|
+
|
|
211
222
|
return [{ _tag: "success" }, { threads }];
|
|
212
223
|
},
|
|
213
224
|
);
|
|
225
|
+
|
|
214
226
|
if (decision._tag === "failure") return yield* decision.error;
|
|
215
227
|
}),
|
|
216
228
|
);
|
|
@@ -219,6 +231,7 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
219
231
|
(unvalidated) =>
|
|
220
232
|
Effect.gen(function* () {
|
|
221
233
|
const request = yield* validate(FencedAppendRequest, "append", unvalidated);
|
|
234
|
+
|
|
222
235
|
const digest = yield* digestCanonicalBatch(request.expectedTailDigest, request.batch).pipe(
|
|
223
236
|
Effect.provideService(Crypto.Crypto, crypto),
|
|
224
237
|
Effect.mapError((error) => storeError("append", error.message, error)),
|
|
@@ -227,6 +240,7 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
227
240
|
const decision = yield* Effect.uninterruptible(
|
|
228
241
|
Ref.modify(state, (current): readonly [AppendDecision, MemoryState] => {
|
|
229
242
|
const thread = current.threads.get(request.threadId);
|
|
243
|
+
|
|
230
244
|
if (thread === undefined) {
|
|
231
245
|
return [
|
|
232
246
|
{
|
|
@@ -252,6 +266,7 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
252
266
|
];
|
|
253
267
|
}
|
|
254
268
|
const previous = thread.batches.get(request.batch.batchId);
|
|
269
|
+
|
|
255
270
|
if (previous !== undefined) {
|
|
256
271
|
if (previous.digest !== digest) {
|
|
257
272
|
return [
|
|
@@ -266,6 +281,7 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
266
281
|
current,
|
|
267
282
|
];
|
|
268
283
|
}
|
|
284
|
+
|
|
269
285
|
return [
|
|
270
286
|
{
|
|
271
287
|
_tag: "success",
|
|
@@ -312,6 +328,7 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
312
328
|
}
|
|
313
329
|
|
|
314
330
|
const batchRecordIds = new Set<RecordId>();
|
|
331
|
+
|
|
315
332
|
for (const record of request.batch.records) {
|
|
316
333
|
if (thread.recordIds.has(record.recordId) || batchRecordIds.has(record.recordId)) {
|
|
317
334
|
return [
|
|
@@ -331,6 +348,7 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
331
348
|
|
|
332
349
|
const records = request.batch.records.map((record, index) => {
|
|
333
350
|
const sequence = decodeCanonicalSequence(thread.tailSequence + index + 1);
|
|
351
|
+
|
|
334
352
|
return CanonicalRecordEnvelope.make({
|
|
335
353
|
threadId: request.threadId,
|
|
336
354
|
batchId: request.batch.batchId,
|
|
@@ -339,20 +357,27 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
339
357
|
record,
|
|
340
358
|
});
|
|
341
359
|
});
|
|
360
|
+
|
|
342
361
|
const lastSequence = decodeCanonicalSequence(thread.tailSequence + records.length);
|
|
362
|
+
|
|
343
363
|
const result = AppendResult.make({
|
|
344
364
|
firstSequence: decodeCanonicalSequence(thread.tailSequence + 1),
|
|
345
365
|
lastSequence,
|
|
346
366
|
tailDigest: digest,
|
|
347
367
|
replayed: false,
|
|
348
368
|
});
|
|
369
|
+
|
|
349
370
|
const batches = new Map(thread.batches);
|
|
371
|
+
|
|
350
372
|
batches.set(request.batch.batchId, { digest, result });
|
|
351
373
|
const recordIds = new Set(thread.recordIds);
|
|
374
|
+
|
|
352
375
|
for (const recordId of batchRecordIds) recordIds.add(recordId);
|
|
353
376
|
const tailDigests = new Map(thread.tailDigests);
|
|
377
|
+
|
|
354
378
|
tailDigests.set(lastSequence, digest);
|
|
355
379
|
const threads = new Map(current.threads);
|
|
380
|
+
|
|
356
381
|
threads.set(request.threadId, {
|
|
357
382
|
...thread,
|
|
358
383
|
tailSequence: lastSequence,
|
|
@@ -362,6 +387,7 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
362
387
|
batches,
|
|
363
388
|
tailDigests,
|
|
364
389
|
});
|
|
390
|
+
|
|
365
391
|
return [{ _tag: "success", result, records }, { threads }];
|
|
366
392
|
}).pipe(
|
|
367
393
|
Effect.tap((decision) =>
|
|
@@ -371,7 +397,9 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
371
397
|
),
|
|
372
398
|
),
|
|
373
399
|
);
|
|
400
|
+
|
|
374
401
|
if (decision._tag === "failure") return yield* decision.error;
|
|
402
|
+
|
|
375
403
|
return decision.result;
|
|
376
404
|
}),
|
|
377
405
|
);
|
|
@@ -393,6 +421,7 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
393
421
|
Effect.gen(function* () {
|
|
394
422
|
const request = yield* validate(ThreadRead, "read", unvalidated);
|
|
395
423
|
const records = yield* readSnapshot(request.threadId, request.afterSequence, request.limit);
|
|
424
|
+
|
|
396
425
|
return Stream.fromIterable(records);
|
|
397
426
|
}),
|
|
398
427
|
);
|
|
@@ -402,16 +431,20 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
402
431
|
Effect.gen(function* () {
|
|
403
432
|
const request = yield* validate(ThreadObservation, "observe", unvalidated);
|
|
404
433
|
const afterSequence = yield* offsetSequence(request.threadId, request.afterOffset);
|
|
434
|
+
|
|
405
435
|
return Stream.unwrap(
|
|
406
436
|
Effect.gen(function* () {
|
|
407
437
|
const subscription = yield* PubSub.subscribe(updates);
|
|
438
|
+
|
|
408
439
|
const initial = yield* readSnapshot(
|
|
409
440
|
request.threadId,
|
|
410
441
|
afterSequence,
|
|
411
442
|
MAX_RECORDS_PER_THREAD,
|
|
412
443
|
);
|
|
444
|
+
|
|
413
445
|
const highWater =
|
|
414
446
|
initial.length === 0 ? afterSequence : (initial.at(-1)?.sequence ?? afterSequence);
|
|
447
|
+
|
|
415
448
|
const live = Stream.fromEffectRepeat(PubSub.take(subscription)).pipe(
|
|
416
449
|
Stream.mapAccumEffect(
|
|
417
450
|
() => highWater,
|
|
@@ -423,6 +456,7 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
423
456
|
),
|
|
424
457
|
),
|
|
425
458
|
);
|
|
459
|
+
|
|
426
460
|
return Stream.fromIterable(initial).pipe(Stream.concat(live));
|
|
427
461
|
}),
|
|
428
462
|
);
|
|
@@ -433,9 +467,11 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
433
467
|
(unvalidated) =>
|
|
434
468
|
Effect.gen(function* () {
|
|
435
469
|
const request = yield* validate(ThreadExportRequest, "export", unvalidated);
|
|
470
|
+
|
|
436
471
|
const thread = yield* Ref.get(state).pipe(
|
|
437
472
|
Effect.flatMap((current) => findThread(current, request.threadId)),
|
|
438
473
|
);
|
|
474
|
+
|
|
439
475
|
return ThreadExport.make({
|
|
440
476
|
format: "effect-agent/thread@1",
|
|
441
477
|
threadId: request.threadId,
|
|
@@ -451,9 +487,11 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
451
487
|
)((unvalidated) =>
|
|
452
488
|
Effect.gen(function* () {
|
|
453
489
|
const request = yield* validate(ThreadTailRequest, "inspectTail", unvalidated);
|
|
490
|
+
|
|
454
491
|
const thread = yield* Ref.get(state).pipe(
|
|
455
492
|
Effect.flatMap((current) => findThread(current, request.threadId)),
|
|
456
493
|
);
|
|
494
|
+
|
|
457
495
|
return ThreadTail.make({
|
|
458
496
|
threadId: request.threadId,
|
|
459
497
|
tailSequence: thread.tailSequence,
|
|
@@ -468,11 +506,13 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
468
506
|
Effect.gen(function* () {
|
|
469
507
|
yield* validateCheckpointVersion(unvalidated);
|
|
470
508
|
const request = yield* validate(SaveCheckpointRequest, "saveCheckpoint", unvalidated);
|
|
509
|
+
|
|
471
510
|
const decision = yield* Ref.modify(
|
|
472
511
|
state,
|
|
473
512
|
(current): readonly [CheckpointDecision, MemoryState] => {
|
|
474
513
|
const checkpoint = request.checkpoint;
|
|
475
514
|
const thread = current.threads.get(checkpoint.threadId);
|
|
515
|
+
|
|
476
516
|
if (thread === undefined) {
|
|
477
517
|
return [
|
|
478
518
|
{
|
|
@@ -524,12 +564,16 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
524
564
|
];
|
|
525
565
|
}
|
|
526
566
|
const checkpoints = new Map(thread.checkpoints);
|
|
567
|
+
|
|
527
568
|
checkpoints.set(checkpoint.throughSequence, checkpoint);
|
|
528
569
|
const threads = new Map(current.threads);
|
|
570
|
+
|
|
529
571
|
threads.set(checkpoint.threadId, { ...thread, checkpoints });
|
|
572
|
+
|
|
530
573
|
return [{ _tag: "success" }, { threads }];
|
|
531
574
|
},
|
|
532
575
|
);
|
|
576
|
+
|
|
533
577
|
if (decision._tag === "failure") return yield* decision.error;
|
|
534
578
|
}),
|
|
535
579
|
);
|
|
@@ -538,11 +582,14 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
538
582
|
(unvalidated) =>
|
|
539
583
|
Effect.gen(function* () {
|
|
540
584
|
const request = yield* validate(LoadCheckpointRequest, "loadCheckpoint", unvalidated);
|
|
585
|
+
|
|
541
586
|
const thread = yield* Ref.get(state).pipe(
|
|
542
587
|
Effect.flatMap((current) => findThread(current, request.threadId)),
|
|
543
588
|
);
|
|
589
|
+
|
|
544
590
|
const maximum = request.atOrBeforeSequence ?? thread.tailSequence;
|
|
545
591
|
let selected: ThreadCheckpoint | undefined;
|
|
592
|
+
|
|
546
593
|
for (const [sequence, checkpoint] of thread.checkpoints) {
|
|
547
594
|
if (
|
|
548
595
|
sequence <= maximum &&
|
|
@@ -560,6 +607,7 @@ const makeThreadStore = Effect.gen(function* () {
|
|
|
560
607
|
reason: "digest-mismatch",
|
|
561
608
|
});
|
|
562
609
|
}
|
|
610
|
+
|
|
563
611
|
return Option.fromNullishOr(selected);
|
|
564
612
|
}),
|
|
565
613
|
);
|