@yielded/agent-storage-memory 0.1.0-beta.166 → 0.1.0-beta.168
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/MemoryMessageDeliveryStore.d.mts +7 -3
- package/dist/MemoryMessageDeliveryStore.mjs +427 -64
- package/dist/MemoryMessageDeliveryStore.mjs.map +1 -1
- package/dist/MemoryScheduleStore.mjs +8 -8
- package/dist/MemoryScheduleStore.mjs.map +1 -1
- package/dist/MemorySemanticIndex.mjs +7 -9
- package/dist/MemorySemanticIndex.mjs.map +1 -1
- package/dist/MemorySubmissionLedger.d.mts +7 -4
- package/dist/MemorySubmissionLedger.mjs +761 -377
- package/dist/MemorySubmissionLedger.mjs.map +1 -1
- package/dist/MemorySubscriptionStore.mjs +23 -23
- package/dist/MemorySubscriptionStore.mjs.map +1 -1
- package/dist/MemoryThreadStore.d.mts +8 -4
- package/dist/MemoryThreadStore.mjs +1325 -277
- package/dist/MemoryThreadStore.mjs.map +1 -1
- package/dist/MemoryThreadStoreKernel-DYNDjlhO.d.mts +85 -0
- package/dist/WorkSelection-CmqTuws3.mjs +31 -0
- package/dist/WorkSelection-CmqTuws3.mjs.map +1 -0
- package/package.json +1 -1
- package/src/MemoryMessageDeliveryStore.ts +766 -230
- package/src/MemoryScheduleStore.ts +190 -200
- package/src/MemorySemanticIndex.ts +94 -95
- package/src/MemorySubmissionLedger.ts +1955 -1245
- package/src/MemorySubscriptionStore.ts +1122 -1117
- package/src/MemoryThreadStore.ts +2609 -628
- package/src/internal/MemoryThreadStoreKernel.ts +156 -0
- package/src/internal/WorkSelection.ts +29 -0
|
@@ -57,214 +57,204 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
57
57
|
const state = yield* Ref.make<MemoryScheduleState>({ records: new Map() });
|
|
58
58
|
const failpoint = yield* ScheduleFailpoint;
|
|
59
59
|
|
|
60
|
-
const insert: ScheduleStore["Service"]["insert"] = Effect.
|
|
61
|
-
(record, ownerLimit)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
if (Result.isFailure(decoded)) {
|
|
89
|
-
return [Result.fail(decoded.failure), current];
|
|
90
|
-
}
|
|
91
|
-
if (decoded.success.creationFingerprint === record.creationFingerprint) {
|
|
92
|
-
return [Result.succeed(decoded.success), current];
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return [
|
|
96
|
-
Result.fail(
|
|
97
|
-
ScheduleConflict.make({ reason: "creation", key: scheduleKeyOf(record) }),
|
|
98
|
-
),
|
|
99
|
-
current,
|
|
100
|
-
];
|
|
101
|
-
}
|
|
60
|
+
const insert: ScheduleStore["Service"]["insert"] = Effect.fnUntraced(
|
|
61
|
+
function* (record, ownerLimit) {
|
|
62
|
+
const encoded = yield* encodeRecord("insert", record);
|
|
63
|
+
|
|
64
|
+
yield* failpoint.hit("schedule:insert:before");
|
|
65
|
+
|
|
66
|
+
const decision = yield* Effect.uninterruptible(
|
|
67
|
+
Ref.modify(
|
|
68
|
+
state,
|
|
69
|
+
(
|
|
70
|
+
current,
|
|
71
|
+
): readonly [
|
|
72
|
+
Result.Result<
|
|
73
|
+
ScheduleRecord,
|
|
74
|
+
ScheduleConflict | ScheduleCapacityError | ScheduleStorageError
|
|
75
|
+
>,
|
|
76
|
+
MemoryScheduleState,
|
|
77
|
+
] => {
|
|
78
|
+
const key = scheduleKeyString(record);
|
|
79
|
+
const existingText = current.records.get(key);
|
|
80
|
+
|
|
81
|
+
if (existingText !== undefined) {
|
|
82
|
+
const decoded = Result.try({
|
|
83
|
+
try: () => Schema.decodeSync(Schema.fromJsonString(ScheduleRecord))(existingText),
|
|
84
|
+
catch: () => storageError("insert", "corrupt"),
|
|
85
|
+
});
|
|
102
86
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
for (const text of current.records.values()) {
|
|
106
|
-
const decoded = Result.try({
|
|
107
|
-
try: () => Schema.decodeSync(Schema.fromJsonString(ScheduleRecord))(text),
|
|
108
|
-
catch: () => storageError("insert", "corrupt"),
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
if (Result.isFailure(decoded)) {
|
|
112
|
-
return [Result.fail(decoded.failure), current];
|
|
113
|
-
}
|
|
114
|
-
if (
|
|
115
|
-
sameOwner(decoded.success, record.owner) &&
|
|
116
|
-
scheduleUsesCapacity(decoded.success)
|
|
117
|
-
)
|
|
118
|
-
ownerCount += 1;
|
|
87
|
+
if (Result.isFailure(decoded)) {
|
|
88
|
+
return [Result.fail(decoded.failure), current];
|
|
119
89
|
}
|
|
120
|
-
if (
|
|
121
|
-
return [Result.
|
|
90
|
+
if (decoded.success.creationFingerprint === record.creationFingerprint) {
|
|
91
|
+
return [Result.succeed(decoded.success), current];
|
|
122
92
|
}
|
|
123
|
-
const records = new Map(current.records);
|
|
124
|
-
|
|
125
|
-
records.set(key, encoded);
|
|
126
|
-
const next = { records };
|
|
127
|
-
|
|
128
|
-
return [Result.succeed(record), next];
|
|
129
|
-
},
|
|
130
|
-
),
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
const inserted = yield* Effect.fromResult(decision);
|
|
134
|
-
|
|
135
|
-
yield* failpoint.hit("schedule:insert:after");
|
|
136
|
-
|
|
137
|
-
return yield* decodeRecord("insert", yield* encodeRecord("insert", inserted));
|
|
138
|
-
}),
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
const get: ScheduleStore["Service"]["get"] = Effect.fn("MemoryScheduleStore.get")(
|
|
142
|
-
function* (key) {
|
|
143
|
-
const decodedKey = yield* decodeInput("get", ScheduleKey, key);
|
|
144
|
-
const text = (yield* Ref.get(state)).records.get(scheduleKeyString(decodedKey));
|
|
145
93
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
94
|
+
return [
|
|
95
|
+
Result.fail(
|
|
96
|
+
ScheduleConflict.make({ reason: "creation", key: scheduleKeyOf(record) }),
|
|
97
|
+
),
|
|
98
|
+
current,
|
|
99
|
+
];
|
|
100
|
+
}
|
|
149
101
|
|
|
150
|
-
|
|
151
|
-
function* (request) {
|
|
152
|
-
const decodedRequest = yield* decodeInput("list", SchedulePageRequest, request);
|
|
153
|
-
const records: Array<ScheduleRecord> = [];
|
|
154
|
-
|
|
155
|
-
for (const text of (yield* Ref.get(state)).records.values()) {
|
|
156
|
-
const record = yield* decodeRecord("list", text);
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
sameOwner(record, decodedRequest.owner) &&
|
|
160
|
-
(decodedRequest.after === undefined ||
|
|
161
|
-
compareScheduleNames(record.scheduleId, decodedRequest.after) > 0)
|
|
162
|
-
) {
|
|
163
|
-
records.push(record);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
records.sort((left, right) => compareScheduleNames(left.scheduleId, right.scheduleId));
|
|
167
|
-
const hasNext = records.length > decodedRequest.limit;
|
|
168
|
-
const items = records.slice(0, decodedRequest.limit);
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
items,
|
|
172
|
-
next: hasNext ? (items.at(-1)?.scheduleId ?? null) : null,
|
|
173
|
-
};
|
|
174
|
-
},
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
const change: ScheduleStore["Service"]["change"] = Effect.fn("MemoryScheduleStore.change")(
|
|
178
|
-
(key, command, ownerLimit = defaultSchedulingLimits.maxSchedulesPerOwner) =>
|
|
179
|
-
Effect.gen(function* () {
|
|
180
|
-
const decodedKey = yield* decodeInput("change", ScheduleKey, key);
|
|
181
|
-
const decodedCommand = yield* decodeInput("change", ScheduleChange, command);
|
|
182
|
-
|
|
183
|
-
yield* failpoint.hit(`schedule:${decodedCommand._tag.toLowerCase()}:before`);
|
|
184
|
-
|
|
185
|
-
const decision = yield* Effect.uninterruptible(
|
|
186
|
-
Ref.modify(
|
|
187
|
-
state,
|
|
188
|
-
(
|
|
189
|
-
current,
|
|
190
|
-
): readonly [
|
|
191
|
-
Result.Result<
|
|
192
|
-
ScheduleRecord,
|
|
193
|
-
ScheduleConflict | ScheduleStorageError | ScheduleNotFound | ScheduleCapacityError
|
|
194
|
-
>,
|
|
195
|
-
MemoryScheduleState,
|
|
196
|
-
] => {
|
|
197
|
-
const storageKey = scheduleKeyString(decodedKey);
|
|
198
|
-
const text = current.records.get(storageKey);
|
|
199
|
-
|
|
200
|
-
if (text === undefined) {
|
|
201
|
-
return [Result.fail(ScheduleNotFound.make({ key: decodedKey })), current];
|
|
202
|
-
}
|
|
102
|
+
let ownerCount = 0;
|
|
203
103
|
|
|
104
|
+
for (const text of current.records.values()) {
|
|
204
105
|
const decoded = Result.try({
|
|
205
106
|
try: () => Schema.decodeSync(Schema.fromJsonString(ScheduleRecord))(text),
|
|
206
|
-
catch: () => storageError("
|
|
107
|
+
catch: () => storageError("insert", "corrupt"),
|
|
207
108
|
});
|
|
208
109
|
|
|
209
110
|
if (Result.isFailure(decoded)) {
|
|
210
111
|
return [Result.fail(decoded.failure), current];
|
|
211
112
|
}
|
|
212
|
-
|
|
113
|
+
if (sameOwner(decoded.success, record.owner) && scheduleUsesCapacity(decoded.success))
|
|
114
|
+
ownerCount += 1;
|
|
115
|
+
}
|
|
116
|
+
if (ownerCount >= ownerLimit) {
|
|
117
|
+
return [Result.fail(ScheduleCapacityError.make({ limit: ownerLimit })), current];
|
|
118
|
+
}
|
|
119
|
+
const records = new Map(current.records);
|
|
213
120
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
if (applied.success === decoded.success) {
|
|
218
|
-
return [Result.succeed(decoded.success), current];
|
|
219
|
-
}
|
|
220
|
-
if (!scheduleUsesCapacity(decoded.success) && scheduleUsesCapacity(applied.success)) {
|
|
221
|
-
let count = 0;
|
|
222
|
-
|
|
223
|
-
for (const text of current.records.values()) {
|
|
224
|
-
const candidate = Schema.decodeResult(Schema.fromJsonString(ScheduleRecord))(
|
|
225
|
-
text,
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
if (Result.isFailure(candidate))
|
|
229
|
-
return [Result.fail(storageError("change", "corrupt")), current];
|
|
230
|
-
if (
|
|
231
|
-
sameOwner(candidate.success, decodedKey.owner) &&
|
|
232
|
-
scheduleUsesCapacity(candidate.success)
|
|
233
|
-
)
|
|
234
|
-
count += 1;
|
|
235
|
-
}
|
|
236
|
-
if (count >= ownerLimit)
|
|
237
|
-
return [Result.fail(ScheduleCapacityError.make({ limit: ownerLimit })), current];
|
|
238
|
-
}
|
|
121
|
+
records.set(key, encoded);
|
|
122
|
+
const next = { records };
|
|
239
123
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
});
|
|
124
|
+
return [Result.succeed(record), next];
|
|
125
|
+
},
|
|
126
|
+
),
|
|
127
|
+
);
|
|
245
128
|
|
|
246
|
-
|
|
247
|
-
return [Result.fail(encoded.failure), current];
|
|
248
|
-
}
|
|
249
|
-
const records = new Map(current.records);
|
|
129
|
+
const inserted = yield* Effect.fromResult(decision);
|
|
250
130
|
|
|
251
|
-
|
|
252
|
-
const next = { records };
|
|
131
|
+
yield* failpoint.hit("schedule:insert:after");
|
|
253
132
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
);
|
|
133
|
+
return yield* decodeRecord("insert", yield* encodeRecord("insert", inserted));
|
|
134
|
+
},
|
|
135
|
+
);
|
|
258
136
|
|
|
259
|
-
|
|
137
|
+
const get: ScheduleStore["Service"]["get"] = Effect.fnUntraced(function* (key) {
|
|
138
|
+
const decodedKey = yield* decodeInput("get", ScheduleKey, key);
|
|
139
|
+
const text = (yield* Ref.get(state)).records.get(scheduleKeyString(decodedKey));
|
|
260
140
|
|
|
261
|
-
|
|
141
|
+
return text === undefined ? null : yield* decodeRecord("get", text);
|
|
142
|
+
});
|
|
262
143
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
144
|
+
const list: ScheduleStore["Service"]["list"] = Effect.fnUntraced(function* (request) {
|
|
145
|
+
const decodedRequest = yield* decodeInput("list", SchedulePageRequest, request);
|
|
146
|
+
const records: Array<ScheduleRecord> = [];
|
|
147
|
+
|
|
148
|
+
for (const text of (yield* Ref.get(state)).records.values()) {
|
|
149
|
+
const record = yield* decodeRecord("list", text);
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
sameOwner(record, decodedRequest.owner) &&
|
|
153
|
+
(decodedRequest.after === undefined ||
|
|
154
|
+
compareScheduleNames(record.scheduleId, decodedRequest.after) > 0)
|
|
155
|
+
) {
|
|
156
|
+
records.push(record);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
records.sort((left, right) => compareScheduleNames(left.scheduleId, right.scheduleId));
|
|
160
|
+
const hasNext = records.length > decodedRequest.limit;
|
|
161
|
+
const items = records.slice(0, decodedRequest.limit);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
items,
|
|
165
|
+
next: hasNext ? (items.at(-1)?.scheduleId ?? null) : null,
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const change: ScheduleStore["Service"]["change"] = Effect.fnUntraced(function* (
|
|
170
|
+
key,
|
|
171
|
+
command,
|
|
172
|
+
ownerLimit = defaultSchedulingLimits.maxSchedulesPerOwner,
|
|
173
|
+
) {
|
|
174
|
+
const decodedKey = yield* decodeInput("change", ScheduleKey, key);
|
|
175
|
+
const decodedCommand = yield* decodeInput("change", ScheduleChange, command);
|
|
176
|
+
|
|
177
|
+
yield* failpoint.hit(`schedule:${decodedCommand._tag.toLowerCase()}:before`);
|
|
178
|
+
|
|
179
|
+
const decision = yield* Effect.uninterruptible(
|
|
180
|
+
Ref.modify(
|
|
181
|
+
state,
|
|
182
|
+
(
|
|
183
|
+
current,
|
|
184
|
+
): readonly [
|
|
185
|
+
Result.Result<
|
|
186
|
+
ScheduleRecord,
|
|
187
|
+
ScheduleConflict | ScheduleStorageError | ScheduleNotFound | ScheduleCapacityError
|
|
188
|
+
>,
|
|
189
|
+
MemoryScheduleState,
|
|
190
|
+
] => {
|
|
191
|
+
const storageKey = scheduleKeyString(decodedKey);
|
|
192
|
+
const text = current.records.get(storageKey);
|
|
193
|
+
|
|
194
|
+
if (text === undefined) {
|
|
195
|
+
return [Result.fail(ScheduleNotFound.make({ key: decodedKey })), current];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const decoded = Result.try({
|
|
199
|
+
try: () => Schema.decodeSync(Schema.fromJsonString(ScheduleRecord))(text),
|
|
200
|
+
catch: () => storageError("change", "corrupt"),
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (Result.isFailure(decoded)) {
|
|
204
|
+
return [Result.fail(decoded.failure), current];
|
|
205
|
+
}
|
|
206
|
+
const applied = applyScheduleChange(decoded.success, decodedCommand);
|
|
207
|
+
|
|
208
|
+
if (Result.isFailure(applied)) {
|
|
209
|
+
return [Result.fail(applied.failure), current];
|
|
210
|
+
}
|
|
211
|
+
if (applied.success === decoded.success) {
|
|
212
|
+
return [Result.succeed(decoded.success), current];
|
|
213
|
+
}
|
|
214
|
+
if (!scheduleUsesCapacity(decoded.success) && scheduleUsesCapacity(applied.success)) {
|
|
215
|
+
let count = 0;
|
|
216
|
+
|
|
217
|
+
for (const text of current.records.values()) {
|
|
218
|
+
const candidate = Schema.decodeResult(Schema.fromJsonString(ScheduleRecord))(text);
|
|
219
|
+
|
|
220
|
+
if (Result.isFailure(candidate))
|
|
221
|
+
return [Result.fail(storageError("change", "corrupt")), current];
|
|
222
|
+
if (
|
|
223
|
+
sameOwner(candidate.success, decodedKey.owner) &&
|
|
224
|
+
scheduleUsesCapacity(candidate.success)
|
|
225
|
+
)
|
|
226
|
+
count += 1;
|
|
227
|
+
}
|
|
228
|
+
if (count >= ownerLimit)
|
|
229
|
+
return [Result.fail(ScheduleCapacityError.make({ limit: ownerLimit })), current];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const encoded = Result.try({
|
|
233
|
+
try: () => Schema.encodeSync(Schema.fromJsonString(ScheduleRecord))(applied.success),
|
|
234
|
+
catch: () => storageError("change", "corrupt"),
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (Result.isFailure(encoded)) {
|
|
238
|
+
return [Result.fail(encoded.failure), current];
|
|
239
|
+
}
|
|
240
|
+
const records = new Map(current.records);
|
|
241
|
+
|
|
242
|
+
records.set(storageKey, encoded.success);
|
|
243
|
+
const next = { records };
|
|
244
|
+
|
|
245
|
+
return [Result.succeed(applied.success), next];
|
|
246
|
+
},
|
|
247
|
+
),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const changed = yield* Effect.fromResult(decision);
|
|
251
|
+
|
|
252
|
+
yield* failpoint.hit(`schedule:${decodedCommand._tag.toLowerCase()}:after`);
|
|
253
|
+
|
|
254
|
+
return yield* decodeRecord("change", yield* encodeRecord("change", changed));
|
|
255
|
+
});
|
|
266
256
|
|
|
267
|
-
const due: ScheduleStore["Service"]["due"] = Effect.
|
|
257
|
+
const due: ScheduleStore["Service"]["due"] = Effect.fnUntraced(
|
|
268
258
|
function* (nowMillis, limit, owner, after) {
|
|
269
259
|
const decodedOwner =
|
|
270
260
|
owner === undefined ? undefined : yield* decodeInput("due", ScheduleOwner, owner);
|
|
@@ -299,25 +289,25 @@ const makeScheduleStore = Effect.gen(function* () {
|
|
|
299
289
|
},
|
|
300
290
|
);
|
|
301
291
|
|
|
302
|
-
const nextDeadline: ScheduleStore["Service"]["nextDeadline"] = Effect.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
owner === undefined ? undefined : yield* decodeInput("nextDeadline", ScheduleOwner, owner);
|
|
292
|
+
const nextDeadline: ScheduleStore["Service"]["nextDeadline"] = Effect.fnUntraced(
|
|
293
|
+
function* (owner) {
|
|
294
|
+
const decodedOwner =
|
|
295
|
+
owner === undefined ? undefined : yield* decodeInput("nextDeadline", ScheduleOwner, owner);
|
|
307
296
|
|
|
308
|
-
|
|
297
|
+
let earliest: number | null = null;
|
|
309
298
|
|
|
310
|
-
|
|
311
|
-
|
|
299
|
+
for (const text of (yield* Ref.get(state)).records.values()) {
|
|
300
|
+
const record = yield* decodeRecord("nextDeadline", text);
|
|
312
301
|
|
|
313
|
-
|
|
314
|
-
|
|
302
|
+
if (decodedOwner !== undefined && !sameOwner(record, decodedOwner)) continue;
|
|
303
|
+
const deadline = scheduleDeadline(record);
|
|
315
304
|
|
|
316
|
-
|
|
317
|
-
|
|
305
|
+
if (deadline !== null && (earliest === null || deadline < earliest)) earliest = deadline;
|
|
306
|
+
}
|
|
318
307
|
|
|
319
|
-
|
|
320
|
-
|
|
308
|
+
return earliest;
|
|
309
|
+
},
|
|
310
|
+
);
|
|
321
311
|
|
|
322
312
|
return ScheduleStore.of({ insert, get, list, change, due, nextDeadline });
|
|
323
313
|
});
|
|
@@ -60,16 +60,15 @@ const keyString = (key: MemoryKey): string => JSON.stringify([key.namespace.addr
|
|
|
60
60
|
const sourceIdentityBytes = (source: MemoryIndexSource): number =>
|
|
61
61
|
Hex.encode(JSON.stringify(source)).length / 2;
|
|
62
62
|
|
|
63
|
-
const decodeBoundary =
|
|
63
|
+
const decodeBoundary = <A, I>(
|
|
64
64
|
schema: Schema.Codec<A, I, never>,
|
|
65
65
|
value: unknown,
|
|
66
66
|
operation: string,
|
|
67
|
-
): Effect.
|
|
68
|
-
|
|
67
|
+
): Effect.Effect<A, MemoryIndexError> =>
|
|
68
|
+
Schema.decodeUnknownEffect(schema)(value).pipe(
|
|
69
69
|
Effect.flatMap((decoded) => Schema.encodeEffect(schema)(decoded).pipe(Effect.as(decoded))),
|
|
70
70
|
Effect.mapError(() => error(operation, "invalid-input")),
|
|
71
71
|
);
|
|
72
|
-
});
|
|
73
72
|
|
|
74
73
|
const freezeSource = (source: MemoryIndexSource): MemoryIndexSource =>
|
|
75
74
|
Object.freeze(
|
|
@@ -107,7 +106,7 @@ const squaredNorm = (vector: ReadonlyArray<number>): number | null => {
|
|
|
107
106
|
const validVector = (vector: ReadonlyArray<number>, profile: SemanticMemoryProfile): boolean =>
|
|
108
107
|
vector.length === profile.dimensions && squaredNorm(vector) !== null;
|
|
109
108
|
|
|
110
|
-
const validateChunks = Effect.
|
|
109
|
+
const validateChunks = Effect.fnUntraced(function* (
|
|
111
110
|
chunks: ReadonlyArray<SemanticMemoryChunk>,
|
|
112
111
|
profile: SemanticMemoryProfile,
|
|
113
112
|
operation: string,
|
|
@@ -151,7 +150,7 @@ const cosine = (left: ReadonlyArray<number>, right: ReadonlyArray<number>): numb
|
|
|
151
150
|
const compareText = (left: string, right: string): number =>
|
|
152
151
|
left < right ? -1 : left > right ? 1 : 0;
|
|
153
152
|
|
|
154
|
-
const makeIndex = Effect.
|
|
153
|
+
const makeIndex = Effect.fnUntraced(function* (
|
|
155
154
|
rawProfile: SemanticMemoryProfile,
|
|
156
155
|
rawCapacity: InMemorySemanticIndexCapacity,
|
|
157
156
|
) {
|
|
@@ -181,110 +180,110 @@ const makeIndex = Effect.fn("InMemorySemanticIndex.make")(function* (
|
|
|
181
180
|
Ref.set(data, { closed: true, entries: new Map(), sourceBytes: 0 }),
|
|
182
181
|
);
|
|
183
182
|
|
|
184
|
-
const ensureOpen = Effect.
|
|
183
|
+
const ensureOpen = Effect.fnUntraced(function* (operation: string) {
|
|
185
184
|
if ((yield* Ref.get(data)).closed) return yield* error(operation, "unavailable");
|
|
186
185
|
});
|
|
187
186
|
|
|
188
|
-
const replace: SemanticMemoryIndex["Service"]["replace"] = Effect.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const nextSourceBytes = current.sourceBytes - (existing?.sourceBytes ?? 0) + sourceBytes;
|
|
221
|
-
|
|
222
|
-
if (nextSourceBytes > maxSourceBytes) return [error(operation, "budget"), current];
|
|
223
|
-
let count = chunks.length;
|
|
224
|
-
|
|
225
|
-
for (const [entryId, entry] of current.entries) {
|
|
226
|
-
if (entryId !== id && entry._tag === "Indexed") count += entry.chunks.length;
|
|
227
|
-
}
|
|
228
|
-
if (count > capacity.maxChunks) return [error(operation, "budget"), current];
|
|
229
|
-
const entries = new Map(current.entries);
|
|
230
|
-
|
|
231
|
-
entries.set(id, { _tag: "Indexed", source, sourceBytes, chunks, indexedAt });
|
|
232
|
-
|
|
233
|
-
return [undefined, { ...current, entries, sourceBytes: nextSourceBytes }];
|
|
234
|
-
},
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
if (failure !== undefined) return yield* failure;
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
const withdraw: SemanticMemoryIndex["Service"]["withdraw"] = Effect.fn(
|
|
241
|
-
"InMemorySemanticIndex.withdraw",
|
|
242
|
-
)(function* (rawSource) {
|
|
243
|
-
const operation = "withdraw semantic memory source";
|
|
187
|
+
const replace: SemanticMemoryIndex["Service"]["replace"] = Effect.fnUntraced(
|
|
188
|
+
function* (rawRequest) {
|
|
189
|
+
const operation = "replace semantic memory source";
|
|
190
|
+
|
|
191
|
+
yield* ensureOpen(operation);
|
|
192
|
+
const request = yield* decodeBoundary(MemoryIndexReplacement.Wire, rawRequest, operation);
|
|
193
|
+
const source = freezeSource(request.source);
|
|
194
|
+
const sourceBytes = sourceIdentityBytes(source);
|
|
195
|
+
const chunks = Object.freeze(request.chunks.map(freezeChunk));
|
|
196
|
+
|
|
197
|
+
if (source.source.id !== source.key.id) return yield* error(operation, "invalid-input");
|
|
198
|
+
if (!sameProfile(request.profile, profile)) return yield* error(operation, "incompatible");
|
|
199
|
+
yield* validateChunks(chunks, profile, operation);
|
|
200
|
+
const indexedAt = yield* Clock.currentTimeMillis;
|
|
201
|
+
|
|
202
|
+
const failure = yield* Ref.modify(
|
|
203
|
+
data,
|
|
204
|
+
(current): readonly [MemoryIndexError | undefined, IndexData] => {
|
|
205
|
+
if (current.closed) return [error(operation, "unavailable"), current];
|
|
206
|
+
const id = keyString(source.key);
|
|
207
|
+
const existing = current.entries.get(id);
|
|
208
|
+
|
|
209
|
+
if (
|
|
210
|
+
existing !== undefined &&
|
|
211
|
+
(existing._tag === "Withdrawn" || sourceIsFenced(source, existing))
|
|
212
|
+
) {
|
|
213
|
+
return [error(operation, "fenced"), current];
|
|
214
|
+
}
|
|
215
|
+
if (existing === undefined && current.entries.size >= capacity.maxSources) {
|
|
216
|
+
return [error(operation, "budget"), current];
|
|
217
|
+
}
|
|
218
|
+
const nextSourceBytes = current.sourceBytes - (existing?.sourceBytes ?? 0) + sourceBytes;
|
|
244
219
|
|
|
245
|
-
|
|
220
|
+
if (nextSourceBytes > maxSourceBytes) return [error(operation, "budget"), current];
|
|
221
|
+
let count = chunks.length;
|
|
246
222
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
223
|
+
for (const [entryId, entry] of current.entries) {
|
|
224
|
+
if (entryId !== id && entry._tag === "Indexed") count += entry.chunks.length;
|
|
225
|
+
}
|
|
226
|
+
if (count > capacity.maxChunks) return [error(operation, "budget"), current];
|
|
227
|
+
const entries = new Map(current.entries);
|
|
250
228
|
|
|
251
|
-
|
|
229
|
+
entries.set(id, { _tag: "Indexed", source, sourceBytes, chunks, indexedAt });
|
|
252
230
|
|
|
253
|
-
|
|
231
|
+
return [undefined, { ...current, entries, sourceBytes: nextSourceBytes }];
|
|
232
|
+
},
|
|
233
|
+
);
|
|
254
234
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (current.closed) return [error(operation, "unavailable"), current];
|
|
259
|
-
const id = keyString(source.key);
|
|
260
|
-
const existing = current.entries.get(id);
|
|
235
|
+
if (failure !== undefined) return yield* failure;
|
|
236
|
+
},
|
|
237
|
+
);
|
|
261
238
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
239
|
+
const withdraw: SemanticMemoryIndex["Service"]["withdraw"] = Effect.fnUntraced(
|
|
240
|
+
function* (rawSource) {
|
|
241
|
+
const operation = "withdraw semantic memory source";
|
|
242
|
+
|
|
243
|
+
yield* ensureOpen(operation);
|
|
244
|
+
|
|
245
|
+
const source = freezeSource(
|
|
246
|
+
yield* decodeBoundary(MemoryIndexSource.Wire, rawSource, operation),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const sourceBytes = sourceIdentityBytes(source);
|
|
250
|
+
|
|
251
|
+
if (source.source.id !== source.key.id) return yield* error(operation, "invalid-input");
|
|
252
|
+
|
|
253
|
+
const failure = yield* Ref.modify(
|
|
254
|
+
data,
|
|
255
|
+
(current): readonly [MemoryIndexError | undefined, IndexData] => {
|
|
256
|
+
if (current.closed) return [error(operation, "unavailable"), current];
|
|
257
|
+
const id = keyString(source.key);
|
|
258
|
+
const existing = current.entries.get(id);
|
|
259
|
+
|
|
260
|
+
if (existing !== undefined) {
|
|
261
|
+
if (existing._tag === "Withdrawn") {
|
|
262
|
+
return [
|
|
263
|
+
sameSource(source, existing.source) ? undefined : error(operation, "fenced"),
|
|
264
|
+
current,
|
|
265
|
+
];
|
|
266
|
+
}
|
|
267
|
+
if (sourceIsFenced(source, existing)) return [error(operation, "fenced"), current];
|
|
268
|
+
} else if (current.entries.size >= capacity.maxSources) {
|
|
269
|
+
return [error(operation, "budget"), current];
|
|
268
270
|
}
|
|
269
|
-
|
|
270
|
-
} else if (current.entries.size >= capacity.maxSources) {
|
|
271
|
-
return [error(operation, "budget"), current];
|
|
272
|
-
}
|
|
273
|
-
const nextSourceBytes = current.sourceBytes - (existing?.sourceBytes ?? 0) + sourceBytes;
|
|
271
|
+
const nextSourceBytes = current.sourceBytes - (existing?.sourceBytes ?? 0) + sourceBytes;
|
|
274
272
|
|
|
275
|
-
|
|
276
|
-
|
|
273
|
+
if (nextSourceBytes > maxSourceBytes) return [error(operation, "budget"), current];
|
|
274
|
+
const entries = new Map(current.entries);
|
|
277
275
|
|
|
278
|
-
|
|
276
|
+
entries.set(id, { _tag: "Withdrawn", source, sourceBytes });
|
|
279
277
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
return [undefined, { ...current, entries, sourceBytes: nextSourceBytes }];
|
|
279
|
+
},
|
|
280
|
+
);
|
|
283
281
|
|
|
284
|
-
|
|
285
|
-
|
|
282
|
+
if (failure !== undefined) return yield* failure;
|
|
283
|
+
},
|
|
284
|
+
);
|
|
286
285
|
|
|
287
|
-
const search = Effect.
|
|
286
|
+
const search = Effect.fnUntraced(function* (rawQuery: MemoryIndexQuery) {
|
|
288
287
|
const operation = "search semantic memory index";
|
|
289
288
|
|
|
290
289
|
yield* ensureOpen(operation);
|