@effect-agent/storage-memory 0.1.0-beta.114 → 0.1.0-beta.116
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.mjs +26 -3
- package/dist/MemoryMessageDeliveryStore.mjs.map +1 -1
- package/dist/MemorySubmissionLedger.mjs +95 -8
- package/dist/MemorySubmissionLedger.mjs.map +1 -1
- package/dist/MemoryThreadStore.mjs +4 -0
- package/dist/MemoryThreadStore.mjs.map +1 -1
- package/package.json +1 -1
- package/src/MemoryMessageDeliveryStore.ts +61 -15
- package/src/MemorySubmissionLedger.ts +171 -6
- package/src/MemoryThreadStore.ts +10 -0
|
@@ -24,7 +24,8 @@ const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLim
|
|
|
24
24
|
const maxStoredValueBytes = yield* validateMessageDelivery(Schema.Int.check(Schema.isGreaterThan(0)), options.maxStoredValueBytes ?? 16777216, "stored-value-limit");
|
|
25
25
|
const state = yield* Ref.make({
|
|
26
26
|
records: /* @__PURE__ */ new Map(),
|
|
27
|
-
pending: /* @__PURE__ */ new Map()
|
|
27
|
+
pending: /* @__PURE__ */ new Map(),
|
|
28
|
+
workers: /* @__PURE__ */ new Map()
|
|
28
29
|
});
|
|
29
30
|
const commit = (record, encoded) => Ref.update(state, (current) => {
|
|
30
31
|
const key = messageDeliveryKeyString(record.key);
|
|
@@ -34,9 +35,26 @@ const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLim
|
|
|
34
35
|
const pending = new Map(current.pending);
|
|
35
36
|
if (keys.size === 0) pending.delete(record.key.ownerThreadId);
|
|
36
37
|
else pending.set(record.key.ownerThreadId, keys);
|
|
38
|
+
const workers = new Map(current.workers);
|
|
39
|
+
const admission = record.envelope.workerAdmission;
|
|
40
|
+
if (admission !== void 0) {
|
|
41
|
+
const worker = admission.origin.worker;
|
|
42
|
+
const startKey = JSON.stringify([
|
|
43
|
+
record.key.ownerThreadId,
|
|
44
|
+
worker.delegationId,
|
|
45
|
+
worker.targetAgentId
|
|
46
|
+
]);
|
|
47
|
+
if (record.key.messageId === admission.origin.firstMessageId) workers.set(startKey, /* @__PURE__ */ new Set([...workers.get(startKey) ?? [], record.key.messageId]));
|
|
48
|
+
const pendingKey = JSON.stringify([record.key.ownerThreadId, worker.threadId]);
|
|
49
|
+
const inputs = new Set(workers.get(pendingKey));
|
|
50
|
+
if ((record.status === "pending" || record.status === "parked") && record.receipt === null) inputs.add(record.key.messageId);
|
|
51
|
+
else inputs.delete(record.key.messageId);
|
|
52
|
+
workers.set(pendingKey, inputs);
|
|
53
|
+
}
|
|
37
54
|
return {
|
|
38
55
|
records: new Map(current.records).set(key, encoded),
|
|
39
|
-
pending
|
|
56
|
+
pending,
|
|
57
|
+
workers
|
|
40
58
|
};
|
|
41
59
|
});
|
|
42
60
|
const lock = yield* Semaphore.make(1);
|
|
@@ -124,7 +142,12 @@ const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLim
|
|
|
124
142
|
list: Effect.fn("MemoryMessageDeliveryStore.list")(function* (request) {
|
|
125
143
|
const input = yield* validateMessageDelivery(MessageDeliveryPageRequest, request, "list");
|
|
126
144
|
const current = yield* Ref.get(state);
|
|
127
|
-
const
|
|
145
|
+
const workerKey = input.workerStarts !== void 0 ? JSON.stringify([
|
|
146
|
+
input.ownerThreadId,
|
|
147
|
+
input.workerStarts.delegationId,
|
|
148
|
+
input.workerStarts.targetAgentId
|
|
149
|
+
]) : input.pendingWorker !== void 0 ? JSON.stringify([input.ownerThreadId, input.pendingWorker]) : void 0;
|
|
150
|
+
const records = (workerKey !== void 0 || input.pendingOnly ? yield* Effect.forEach([...(workerKey === void 0 ? current.pending.get(input.ownerThreadId) : current.workers.get(workerKey)) ?? []].filter((messageId) => input.after === void 0 || messageId > input.after).sort().slice(0, input.limit + 1), (messageId) => decode(current.records.get(messageDeliveryKeyString({
|
|
128
151
|
ownerThreadId: input.ownerThreadId,
|
|
129
152
|
messageId
|
|
130
153
|
})) ?? "")) : yield* all()).filter((record) => record.key.ownerThreadId === input.ownerThreadId && (input.after === void 0 || record.key.messageId > input.after)).sort((left, right) => left.key.messageId < right.key.messageId ? -1 : left.key.messageId > right.key.messageId ? 1 : 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MemoryMessageDeliveryStore.mjs","names":[],"sources":["../src/MemoryMessageDeliveryStore.ts"],"sourcesContent":["import { Effect, Layer, Ref, Schema, Semaphore } from \"effect\";\nimport { ThreadId } from \"effect-agent/identifiers\";\nimport {\n applyMessageDeliveryChange,\n defaultMessageDeliveryStoreLimits,\n MessageDeliveryChange,\n MessageDeliveryError,\n MessageDeliveryFailpoint,\n MessageDeliveryKey,\n MessageDeliveryPageRequest,\n MessageDeliveryRecord,\n MessageDeliveryStore,\n MessageDeliveryStoreLimits,\n messageDeliveryDeadline,\n messageDeliveryKeyString,\n messageDeliveryUsesCapacity,\n isWorkerUpdateDelivery,\n messageDeliveryCapacity,\n sameMessageDeliveryIdentity,\n validateMessageDelivery,\n} from \"effect-agent/message-delivery\";\nimport { ScheduleInstant } from \"effect-agent/schedule\";\n\nconst codec = Schema.fromJsonString(MessageDeliveryRecord);\n\nconst decode = (text: string) =>\n Schema.decodeEffect(codec)(text).pipe(\n Effect.mapError(() => MessageDeliveryError.make({ reason: \"corrupt\", operation: \"decode\" })),\n );\n\nconst Scan = Schema.Struct({\n nowMillis: ScheduleInstant,\n limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(100)),\n ownerThreadId: Schema.optionalKey(ThreadId),\n});\n\nexport interface MemoryMessageDeliveryStoreOptions {\n /** UTF-8 bound on the complete stored record, including a processed Settlement. */\n readonly maxStoredValueBytes?: number;\n}\n\n/** Reference adapter. All retained rows are bounded; completed deduplication evidence is never evicted. */\nexport const memoryMessageDeliveryStoreLayer = (\n limits: MessageDeliveryStoreLimits = defaultMessageDeliveryStoreLimits,\n options: MemoryMessageDeliveryStoreOptions = {},\n): Layer.Layer<MessageDeliveryStore, MessageDeliveryError> =>\n Layer.effect(\n MessageDeliveryStore,\n Effect.gen(function* () {\n const config = yield* validateMessageDelivery(MessageDeliveryStoreLimits, limits, \"limits\");\n\n const maxStoredValueBytes = yield* validateMessageDelivery(\n Schema.Int.check(Schema.isGreaterThan(0)),\n options.maxStoredValueBytes ?? 16 * 1_024 * 1_024,\n \"stored-value-limit\",\n );\n\n const state = yield* Ref.make({\n records: new Map<string, string>(),\n pending: new Map<ThreadId, ReadonlySet<MessageDeliveryKey[\"messageId\"]>>(),\n });\n\n const commit = (record: MessageDeliveryRecord, encoded: string) =>\n Ref.update(state, (current) => {\n const key = messageDeliveryKeyString(record.key);\n const keys = new Set(current.pending.get(record.key.ownerThreadId));\n\n if (messageDeliveryUsesCapacity(record)) keys.add(record.key.messageId);\n else keys.delete(record.key.messageId);\n const pending = new Map(current.pending);\n\n if (keys.size === 0) pending.delete(record.key.ownerThreadId);\n else pending.set(record.key.ownerThreadId, keys);\n\n return { records: new Map(current.records).set(key, encoded), pending };\n });\n\n const lock = yield* Semaphore.make(1);\n const failpoint = yield* MessageDeliveryFailpoint;\n\n const encode = Effect.fn(\"MemoryMessageDeliveryStore.encode\")(function* (\n record: MessageDeliveryRecord,\n ) {\n const text = yield* Schema.encodeEffect(codec)(record).pipe(\n Effect.mapError(() =>\n MessageDeliveryError.make({ reason: \"corrupt\", operation: \"encode\" }),\n ),\n );\n\n if (new TextEncoder().encode(text).byteLength > maxStoredValueBytes) {\n return yield* MessageDeliveryError.make({\n reason: \"capacity\",\n operation: \"stored-value-bytes\",\n });\n }\n\n return text;\n });\n\n const all = Effect.fn(\"MemoryMessageDeliveryStore.all\")(function* () {\n return yield* Effect.forEach((yield* Ref.get(state)).records.values(), decode);\n });\n\n const insert: MessageDeliveryStore[\"Service\"][\"insert\"] = Effect.fn(\n \"MemoryMessageDeliveryStore.insert\",\n )(function* (record) {\n const encoded = yield* encode(record);\n const input = yield* decode(encoded);\n\n if (\n input.version !== 1 ||\n input.status !== \"pending\" ||\n input.leaseUntilMillis !== null ||\n input.retry.attempts !== 0 ||\n input.retry.generation !== 0 ||\n input.retry.automaticAttempts !== 0 ||\n input.retry.nextAttemptAtMillis !== input.createdAtMillis ||\n input.retry.lastAttemptAtMillis !== null ||\n input.retry.lastFailure !== null ||\n input.deadlineAtMillis !== input.initialDeadlineAtMillis\n ) {\n return yield* MessageDeliveryError.make({\n reason: \"validation\",\n operation: \"insert-state\",\n });\n }\n if (\n new TextEncoder().encode(JSON.stringify(input.envelope)).byteLength >\n config.maxEnvelopeBytes\n ) {\n return yield* MessageDeliveryError.make({\n reason: \"capacity\",\n operation: \"envelope-bytes\",\n });\n }\n yield* failpoint.hit(\"message-delivery:insert:before\");\n\n const inserted = yield* lock.withPermit(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const { records } = yield* Ref.get(state);\n const key = messageDeliveryKeyString(input.key);\n const existingText = records.get(key);\n\n if (existingText !== undefined) {\n const existing = yield* decode(existingText);\n\n if (!sameMessageDeliveryIdentity(existing, input))\n return yield* MessageDeliveryError.make({\n reason: \"conflict\",\n operation: \"insert\",\n });\n\n return existing;\n }\n\n const owned = (yield* all()).filter(\n (record) =>\n record.key.ownerThreadId === input.key.ownerThreadId &&\n isWorkerUpdateDelivery(record) === isWorkerUpdateDelivery(input),\n );\n\n const capacity = messageDeliveryCapacity(config, isWorkerUpdateDelivery(input));\n\n if (\n owned.length >= capacity.retained ||\n owned.filter(messageDeliveryUsesCapacity).length >= capacity.pending\n ) {\n return yield* MessageDeliveryError.make({\n reason: \"capacity\",\n operation: \"insert\",\n });\n }\n yield* commit(input, encoded);\n\n return input;\n }),\n ),\n );\n\n yield* failpoint.hit(\"message-delivery:insert:after\");\n\n return yield* decode(yield* encode(inserted));\n });\n\n const get: MessageDeliveryStore[\"Service\"][\"get\"] = Effect.fn(\n \"MemoryMessageDeliveryStore.get\",\n )(function* (key) {\n const input = yield* validateMessageDelivery(MessageDeliveryKey, key, \"get\");\n const text = (yield* Ref.get(state)).records.get(messageDeliveryKeyString(input));\n\n return text === undefined ? null : yield* decode(text);\n });\n\n const change: MessageDeliveryStore[\"Service\"][\"change\"] = Effect.fn(\n \"MemoryMessageDeliveryStore.change\",\n )(function* (key, change) {\n const input = yield* validateMessageDelivery(MessageDeliveryChange, change, \"change\");\n const decodedKey = yield* validateMessageDelivery(MessageDeliveryKey, key, \"change\");\n const point = `message-delivery:${input._tag.toLowerCase()}`;\n\n yield* failpoint.hit(`${point}:before`);\n\n const changed = yield* lock.withPermit(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const existing = yield* get(decodedKey);\n\n if (existing === null)\n return yield* MessageDeliveryError.make({\n reason: \"not-found\",\n operation: \"change\",\n });\n const next = yield* Effect.fromResult(applyMessageDeliveryChange(existing, input));\n const encoded = yield* encode(next);\n\n yield* commit(next, encoded);\n\n return next;\n }),\n ),\n );\n\n yield* failpoint.hit(`${point}:after`);\n\n return yield* decode(yield* encode(changed));\n });\n\n return MessageDeliveryStore.of({\n limits: config,\n maxStoredValueBytes,\n insert,\n get,\n change,\n list: Effect.fn(\"MemoryMessageDeliveryStore.list\")(function* (request) {\n const input = yield* validateMessageDelivery(MessageDeliveryPageRequest, request, \"list\");\n\n const current = yield* Ref.get(state);\n\n const retained = input.pendingOnly\n ? yield* Effect.forEach(\n [...(current.pending.get(input.ownerThreadId) ?? [])]\n .filter((messageId) => input.after === undefined || messageId > input.after)\n .sort()\n .slice(0, input.limit + 1),\n (messageId) =>\n decode(\n current.records.get(\n messageDeliveryKeyString({ ownerThreadId: input.ownerThreadId, messageId }),\n ) ?? \"\",\n ),\n )\n : yield* all();\n\n const records = retained\n .filter(\n (record) =>\n record.key.ownerThreadId === input.ownerThreadId &&\n (input.after === undefined || record.key.messageId > input.after),\n )\n .sort((left, right) =>\n left.key.messageId < right.key.messageId\n ? -1\n : left.key.messageId > right.key.messageId\n ? 1\n : 0,\n );\n\n const items = records.slice(0, input.limit);\n\n return {\n items,\n next: records.length > input.limit ? (items.at(-1)?.key.messageId ?? null) : null,\n };\n }),\n due: Effect.fn(\"MemoryMessageDeliveryStore.due\")(\n function* (nowMillis, limit, ownerThreadId) {\n const input = yield* validateMessageDelivery(\n Scan,\n { nowMillis, limit, ...(ownerThreadId === undefined ? {} : { ownerThreadId }) },\n \"due\",\n );\n\n return (yield* all())\n .flatMap((record) => {\n const deadline = messageDeliveryDeadline(record);\n\n return deadline !== null &&\n deadline <= input.nowMillis &&\n (input.ownerThreadId === undefined ||\n input.ownerThreadId === record.key.ownerThreadId)\n ? [{ key: record.key, deadline }]\n : [];\n })\n .sort(\n (left, right) =>\n left.deadline - right.deadline ||\n (messageDeliveryKeyString(left.key) < messageDeliveryKeyString(right.key)\n ? -1\n : 1),\n )\n .slice(0, input.limit)\n .map((record) => record.key);\n },\n ),\n nextDeadline: Effect.fn(\"MemoryMessageDeliveryStore.nextDeadline\")(\n function* (ownerThreadId) {\n if (ownerThreadId !== undefined)\n yield* validateMessageDelivery(ThreadId, ownerThreadId, \"nextDeadline\");\n let next: number | null = null;\n\n for (const record of yield* all()) {\n if (ownerThreadId !== undefined && ownerThreadId !== record.key.ownerThreadId)\n continue;\n const deadline = messageDeliveryDeadline(record);\n\n if (deadline !== null && (next === null || deadline < next)) next = deadline;\n }\n\n return next;\n },\n ),\n });\n }),\n );\n\nexport const MemoryMessageDeliveryStoreLive = memoryMessageDeliveryStoreLayer();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MemoryMessageDeliveryStore.mjs","names":[],"sources":["../src/MemoryMessageDeliveryStore.ts"],"sourcesContent":["import { Effect, Layer, Ref, Schema, Semaphore } from \"effect\";\nimport { ThreadId } from \"effect-agent/identifiers\";\nimport {\n applyMessageDeliveryChange,\n defaultMessageDeliveryStoreLimits,\n MessageDeliveryChange,\n MessageDeliveryError,\n MessageDeliveryFailpoint,\n MessageDeliveryKey,\n MessageDeliveryPageRequest,\n MessageDeliveryRecord,\n MessageDeliveryStore,\n MessageDeliveryStoreLimits,\n messageDeliveryDeadline,\n messageDeliveryKeyString,\n messageDeliveryUsesCapacity,\n isWorkerUpdateDelivery,\n messageDeliveryCapacity,\n sameMessageDeliveryIdentity,\n validateMessageDelivery,\n} from \"effect-agent/message-delivery\";\nimport { ScheduleInstant } from \"effect-agent/schedule\";\n\nconst codec = Schema.fromJsonString(MessageDeliveryRecord);\n\nconst decode = (text: string) =>\n Schema.decodeEffect(codec)(text).pipe(\n Effect.mapError(() => MessageDeliveryError.make({ reason: \"corrupt\", operation: \"decode\" })),\n );\n\nconst Scan = Schema.Struct({\n nowMillis: ScheduleInstant,\n limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(100)),\n ownerThreadId: Schema.optionalKey(ThreadId),\n});\n\nexport interface MemoryMessageDeliveryStoreOptions {\n /** UTF-8 bound on the complete stored record, including a processed Settlement. */\n readonly maxStoredValueBytes?: number;\n}\n\n/** Reference adapter. All retained rows are bounded; completed deduplication evidence is never evicted. */\nexport const memoryMessageDeliveryStoreLayer = (\n limits: MessageDeliveryStoreLimits = defaultMessageDeliveryStoreLimits,\n options: MemoryMessageDeliveryStoreOptions = {},\n): Layer.Layer<MessageDeliveryStore, MessageDeliveryError> =>\n Layer.effect(\n MessageDeliveryStore,\n Effect.gen(function* () {\n const config = yield* validateMessageDelivery(MessageDeliveryStoreLimits, limits, \"limits\");\n\n const maxStoredValueBytes = yield* validateMessageDelivery(\n Schema.Int.check(Schema.isGreaterThan(0)),\n options.maxStoredValueBytes ?? 16 * 1_024 * 1_024,\n \"stored-value-limit\",\n );\n\n const state = yield* Ref.make({\n records: new Map<string, string>(),\n pending: new Map<ThreadId, ReadonlySet<MessageDeliveryKey[\"messageId\"]>>(),\n workers: new Map<string, ReadonlySet<MessageDeliveryKey[\"messageId\"]>>(),\n });\n\n const commit = (record: MessageDeliveryRecord, encoded: string) =>\n Ref.update(state, (current) => {\n const key = messageDeliveryKeyString(record.key);\n const keys = new Set(current.pending.get(record.key.ownerThreadId));\n\n if (messageDeliveryUsesCapacity(record)) keys.add(record.key.messageId);\n else keys.delete(record.key.messageId);\n const pending = new Map(current.pending);\n\n if (keys.size === 0) pending.delete(record.key.ownerThreadId);\n else pending.set(record.key.ownerThreadId, keys);\n\n const workers = new Map(current.workers);\n const admission = record.envelope.workerAdmission;\n\n if (admission !== undefined) {\n const worker = admission.origin.worker;\n\n const startKey = JSON.stringify([\n record.key.ownerThreadId,\n worker.delegationId,\n worker.targetAgentId,\n ]);\n\n if (record.key.messageId === admission.origin.firstMessageId)\n workers.set(\n startKey,\n new Set([...(workers.get(startKey) ?? []), record.key.messageId]),\n );\n const pendingKey = JSON.stringify([record.key.ownerThreadId, worker.threadId]);\n const inputs = new Set(workers.get(pendingKey));\n\n if (\n (record.status === \"pending\" || record.status === \"parked\") &&\n record.receipt === null\n )\n inputs.add(record.key.messageId);\n else inputs.delete(record.key.messageId);\n workers.set(pendingKey, inputs);\n }\n\n return { records: new Map(current.records).set(key, encoded), pending, workers };\n });\n\n const lock = yield* Semaphore.make(1);\n const failpoint = yield* MessageDeliveryFailpoint;\n\n const encode = Effect.fn(\"MemoryMessageDeliveryStore.encode\")(function* (\n record: MessageDeliveryRecord,\n ) {\n const text = yield* Schema.encodeEffect(codec)(record).pipe(\n Effect.mapError(() =>\n MessageDeliveryError.make({ reason: \"corrupt\", operation: \"encode\" }),\n ),\n );\n\n if (new TextEncoder().encode(text).byteLength > maxStoredValueBytes) {\n return yield* MessageDeliveryError.make({\n reason: \"capacity\",\n operation: \"stored-value-bytes\",\n });\n }\n\n return text;\n });\n\n const all = Effect.fn(\"MemoryMessageDeliveryStore.all\")(function* () {\n return yield* Effect.forEach((yield* Ref.get(state)).records.values(), decode);\n });\n\n const insert: MessageDeliveryStore[\"Service\"][\"insert\"] = Effect.fn(\n \"MemoryMessageDeliveryStore.insert\",\n )(function* (record) {\n const encoded = yield* encode(record);\n const input = yield* decode(encoded);\n\n if (\n input.version !== 1 ||\n input.status !== \"pending\" ||\n input.leaseUntilMillis !== null ||\n input.retry.attempts !== 0 ||\n input.retry.generation !== 0 ||\n input.retry.automaticAttempts !== 0 ||\n input.retry.nextAttemptAtMillis !== input.createdAtMillis ||\n input.retry.lastAttemptAtMillis !== null ||\n input.retry.lastFailure !== null ||\n input.deadlineAtMillis !== input.initialDeadlineAtMillis\n ) {\n return yield* MessageDeliveryError.make({\n reason: \"validation\",\n operation: \"insert-state\",\n });\n }\n if (\n new TextEncoder().encode(JSON.stringify(input.envelope)).byteLength >\n config.maxEnvelopeBytes\n ) {\n return yield* MessageDeliveryError.make({\n reason: \"capacity\",\n operation: \"envelope-bytes\",\n });\n }\n yield* failpoint.hit(\"message-delivery:insert:before\");\n\n const inserted = yield* lock.withPermit(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const { records } = yield* Ref.get(state);\n const key = messageDeliveryKeyString(input.key);\n const existingText = records.get(key);\n\n if (existingText !== undefined) {\n const existing = yield* decode(existingText);\n\n if (!sameMessageDeliveryIdentity(existing, input))\n return yield* MessageDeliveryError.make({\n reason: \"conflict\",\n operation: \"insert\",\n });\n\n return existing;\n }\n\n const owned = (yield* all()).filter(\n (record) =>\n record.key.ownerThreadId === input.key.ownerThreadId &&\n isWorkerUpdateDelivery(record) === isWorkerUpdateDelivery(input),\n );\n\n const capacity = messageDeliveryCapacity(config, isWorkerUpdateDelivery(input));\n\n if (\n owned.length >= capacity.retained ||\n owned.filter(messageDeliveryUsesCapacity).length >= capacity.pending\n ) {\n return yield* MessageDeliveryError.make({\n reason: \"capacity\",\n operation: \"insert\",\n });\n }\n yield* commit(input, encoded);\n\n return input;\n }),\n ),\n );\n\n yield* failpoint.hit(\"message-delivery:insert:after\");\n\n return yield* decode(yield* encode(inserted));\n });\n\n const get: MessageDeliveryStore[\"Service\"][\"get\"] = Effect.fn(\n \"MemoryMessageDeliveryStore.get\",\n )(function* (key) {\n const input = yield* validateMessageDelivery(MessageDeliveryKey, key, \"get\");\n const text = (yield* Ref.get(state)).records.get(messageDeliveryKeyString(input));\n\n return text === undefined ? null : yield* decode(text);\n });\n\n const change: MessageDeliveryStore[\"Service\"][\"change\"] = Effect.fn(\n \"MemoryMessageDeliveryStore.change\",\n )(function* (key, change) {\n const input = yield* validateMessageDelivery(MessageDeliveryChange, change, \"change\");\n const decodedKey = yield* validateMessageDelivery(MessageDeliveryKey, key, \"change\");\n const point = `message-delivery:${input._tag.toLowerCase()}`;\n\n yield* failpoint.hit(`${point}:before`);\n\n const changed = yield* lock.withPermit(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const existing = yield* get(decodedKey);\n\n if (existing === null)\n return yield* MessageDeliveryError.make({\n reason: \"not-found\",\n operation: \"change\",\n });\n const next = yield* Effect.fromResult(applyMessageDeliveryChange(existing, input));\n const encoded = yield* encode(next);\n\n yield* commit(next, encoded);\n\n return next;\n }),\n ),\n );\n\n yield* failpoint.hit(`${point}:after`);\n\n return yield* decode(yield* encode(changed));\n });\n\n return MessageDeliveryStore.of({\n limits: config,\n maxStoredValueBytes,\n insert,\n get,\n change,\n list: Effect.fn(\"MemoryMessageDeliveryStore.list\")(function* (request) {\n const input = yield* validateMessageDelivery(MessageDeliveryPageRequest, request, \"list\");\n\n const current = yield* Ref.get(state);\n\n const workerKey =\n input.workerStarts !== undefined\n ? JSON.stringify([\n input.ownerThreadId,\n input.workerStarts.delegationId,\n input.workerStarts.targetAgentId,\n ])\n : input.pendingWorker !== undefined\n ? JSON.stringify([input.ownerThreadId, input.pendingWorker])\n : undefined;\n\n const retained =\n workerKey !== undefined || input.pendingOnly\n ? yield* Effect.forEach(\n [\n ...((workerKey === undefined\n ? current.pending.get(input.ownerThreadId)\n : current.workers.get(workerKey)) ?? []),\n ]\n .filter((messageId) => input.after === undefined || messageId > input.after)\n .sort()\n .slice(0, input.limit + 1),\n (messageId) =>\n decode(\n current.records.get(\n messageDeliveryKeyString({ ownerThreadId: input.ownerThreadId, messageId }),\n ) ?? \"\",\n ),\n )\n : yield* all();\n\n const records = retained\n .filter(\n (record) =>\n record.key.ownerThreadId === input.ownerThreadId &&\n (input.after === undefined || record.key.messageId > input.after),\n )\n .sort((left, right) =>\n left.key.messageId < right.key.messageId\n ? -1\n : left.key.messageId > right.key.messageId\n ? 1\n : 0,\n );\n\n const items = records.slice(0, input.limit);\n\n return {\n items,\n next: records.length > input.limit ? (items.at(-1)?.key.messageId ?? null) : null,\n };\n }),\n due: Effect.fn(\"MemoryMessageDeliveryStore.due\")(\n function* (nowMillis, limit, ownerThreadId) {\n const input = yield* validateMessageDelivery(\n Scan,\n { nowMillis, limit, ...(ownerThreadId === undefined ? {} : { ownerThreadId }) },\n \"due\",\n );\n\n return (yield* all())\n .flatMap((record) => {\n const deadline = messageDeliveryDeadline(record);\n\n return deadline !== null &&\n deadline <= input.nowMillis &&\n (input.ownerThreadId === undefined ||\n input.ownerThreadId === record.key.ownerThreadId)\n ? [{ key: record.key, deadline }]\n : [];\n })\n .sort(\n (left, right) =>\n left.deadline - right.deadline ||\n (messageDeliveryKeyString(left.key) < messageDeliveryKeyString(right.key)\n ? -1\n : 1),\n )\n .slice(0, input.limit)\n .map((record) => record.key);\n },\n ),\n nextDeadline: Effect.fn(\"MemoryMessageDeliveryStore.nextDeadline\")(\n function* (ownerThreadId) {\n if (ownerThreadId !== undefined)\n yield* validateMessageDelivery(ThreadId, ownerThreadId, \"nextDeadline\");\n let next: number | null = null;\n\n for (const record of yield* all()) {\n if (ownerThreadId !== undefined && ownerThreadId !== record.key.ownerThreadId)\n continue;\n const deadline = messageDeliveryDeadline(record);\n\n if (deadline !== null && (next === null || deadline < next)) next = deadline;\n }\n\n return next;\n },\n ),\n });\n }),\n );\n\nexport const MemoryMessageDeliveryStoreLive = memoryMessageDeliveryStoreLayer();\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { Cause, Clock, DateTime, Duration, Effect, Exit, Fiber, Layer, Option, R
|
|
|
3
3
|
import { AttemptId, ReceiptId, SubmissionId } from "effect-agent/identifiers";
|
|
4
4
|
import { InputMessage } from "effect-agent/messaging";
|
|
5
5
|
import { PersistedJson, ProducerEpoch, WorkerAdmission } from "effect-agent/records";
|
|
6
|
-
import { AbortCommand, AbortIntent, AbortIntentRequest, AdmissionAdmitted, AdmissionConflict, AdmissionIndeterminate, AdmissionNotAdmitted, AdmissionPolicyError, AdmissionRequest, AdmissionResult, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildSettledNotification, Claim, ClaimJoiningRequest, ClaimRequest, DEFAULT_OWNERSHIP_LEASE_DURATION, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, OwnershipLost, OwnershipRenewal, OwnershipSnapshot, OwnershipToken, QueueSequence, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ReservedChildBudget, ReservedSettlement, RevertJoiningRequest, Settlement, SettlementConflict, SettlementFinalization, SettlementReservation, SettlementReservationSnapshot, SubmissionAdmissionFence, SubmissionLedger, SubmissionLookup, SubmissionLookupByKey, SubmissionSnapshot, SubmissionWorkItem, SuspendRequest, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, settlementFailureFromRecord } from "effect-agent/submission-ledger";
|
|
6
|
+
import { AbortCommand, AbortIntent, AbortIntentRequest, AdmissionAdmitted, AdmissionConflict, AdmissionIndeterminate, AdmissionNotAdmitted, AdmissionPolicyError, AdmissionRequest, AdmissionResult, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildSettledNotification, Claim, ClaimJoiningRequest, ClaimRequest, DEFAULT_OWNERSHIP_LEASE_DURATION, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, OwnershipLost, OwnershipRenewal, OwnershipSnapshot, OwnershipToken, QueueSequence, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ReservedChildBudget, ReservedSettlement, RevertJoiningRequest, Settlement, SettlementConflict, SettlementFinalization, SettlementReservation, SettlementReservationSnapshot, SubmissionAdmissionFence, SubmissionLedger, SubmissionLookup, SubmissionLookupByKey, SubmissionSnapshot, SubmissionWorkItem, SuspendRequest, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, WorkerLedgerState, WorkerStopCommand, settlementFailureFromRecord, workerTerminalFromRecord } from "effect-agent/submission-ledger";
|
|
7
7
|
//#region src/MemorySubmissionLedger.ts
|
|
8
8
|
var MemorySubmissionLedger_exports = /* @__PURE__ */ __exportAll({
|
|
9
9
|
MemorySubmissionLedgerLive: () => MemorySubmissionLedgerLive,
|
|
@@ -112,10 +112,16 @@ const ownershipLost = (state, stored) => OwnershipLost.make({
|
|
|
112
112
|
});
|
|
113
113
|
/** A retained row token cannot outlive a newer owner of the same Thread. */
|
|
114
114
|
const ownsLane = (state, stored, ownershipToken) => stored.ownership !== void 0 && stored.ownership.ownershipToken === ownershipToken && stored.ownership.producerEpoch === laneEpoch(state, stored.row.threadId);
|
|
115
|
-
const withSubmission = (state, stored) =>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
const withSubmission = (state, stored) => {
|
|
116
|
+
const active = new Set(state.activeByThread.get(stored.row.threadId));
|
|
117
|
+
if (stored.row.state === "settled") active.delete(stored.row.submissionId);
|
|
118
|
+
else active.add(stored.row.submissionId);
|
|
119
|
+
return {
|
|
120
|
+
...state,
|
|
121
|
+
submissions: new Map(state.submissions).set(stored.row.submissionId, stored),
|
|
122
|
+
activeByThread: new Map(state.activeByThread).set(stored.row.threadId, active)
|
|
123
|
+
};
|
|
124
|
+
};
|
|
119
125
|
const withChildReservation = (state, reservation) => ({
|
|
120
126
|
...state,
|
|
121
127
|
childReservations: new Map(state.childReservations).set(reservation.reservationId, reservation)
|
|
@@ -168,7 +174,10 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
|
|
|
168
174
|
admissionIndex: /* @__PURE__ */ new Map(),
|
|
169
175
|
lanes: /* @__PURE__ */ new Map(),
|
|
170
176
|
childReservations: /* @__PURE__ */ new Map(),
|
|
171
|
-
mintCounter: 0
|
|
177
|
+
mintCounter: 0,
|
|
178
|
+
stoppedWorkers: /* @__PURE__ */ new Map(),
|
|
179
|
+
latestByThread: /* @__PURE__ */ new Map(),
|
|
180
|
+
activeByThread: /* @__PURE__ */ new Map()
|
|
172
181
|
});
|
|
173
182
|
const admissionFence = yield* SubmissionAdmissionFence;
|
|
174
183
|
const leaseMillis = Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION);
|
|
@@ -200,6 +209,10 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
|
|
|
200
209
|
replayed: true
|
|
201
210
|
})), current];
|
|
202
211
|
}
|
|
212
|
+
if (current.stoppedWorkers.has(request.threadId)) return [failure(AdmissionPolicyError.make({
|
|
213
|
+
reason: "refused",
|
|
214
|
+
code: "worker-stopped"
|
|
215
|
+
})), current];
|
|
203
216
|
const first = [...current.submissions.values()].find(({ row }) => row.threadId === request.threadId);
|
|
204
217
|
if (first !== void 0) {
|
|
205
218
|
const previous = first.row.workerAdmissionJson === void 0 ? void 0 : Schema.decodeSync(Schema.fromJsonString(WorkerAdmission))(first.row.workerAdmissionJson);
|
|
@@ -273,7 +286,9 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
|
|
|
273
286
|
submissions,
|
|
274
287
|
admissionIndex,
|
|
275
288
|
lanes,
|
|
276
|
-
mintCounter
|
|
289
|
+
mintCounter,
|
|
290
|
+
latestByThread: new Map(current.latestByThread).set(request.threadId, row.submissionId),
|
|
291
|
+
activeByThread: new Map(current.activeByThread).set(request.threadId, /* @__PURE__ */ new Set([...current.activeByThread.get(request.threadId) ?? [], row.submissionId]))
|
|
277
292
|
}];
|
|
278
293
|
});
|
|
279
294
|
if (decision._tag === "failure") return yield* decision.error;
|
|
@@ -506,7 +521,33 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
|
|
|
506
521
|
...settlementFailure === void 0 ? {} : { failure: settlementFailure },
|
|
507
522
|
settledAt: utc(reservation.finalizedAtMillis)
|
|
508
523
|
})), current];
|
|
509
|
-
const
|
|
524
|
+
const terminal = stored.row.workerAdmissionJson === void 0 ? void 0 : workerTerminalFromRecord(toSnapshot(stored.row), reservation.record);
|
|
525
|
+
const latestId = current.latestByThread.get(stored.row.threadId);
|
|
526
|
+
const latest = latestId === void 0 ? void 0 : current.submissions.get(latestId);
|
|
527
|
+
const pendingNewer = terminal === "completed" && latest !== void 0 && latest.row.queueSequence > stored.row.queueSequence && !(latest.joinedHostSubmissionId === stored.row.submissionId && latest.inputApplied !== void 0);
|
|
528
|
+
let sealed = current;
|
|
529
|
+
if (terminal !== void 0 && !pendingNewer && !current.stoppedWorkers.has(stored.row.threadId)) {
|
|
530
|
+
const submissions = new Map(current.submissions);
|
|
531
|
+
for (const id of current.activeByThread.get(stored.row.threadId) ?? []) {
|
|
532
|
+
const other = submissions.get(id);
|
|
533
|
+
if (other === void 0) continue;
|
|
534
|
+
if (id !== stored.row.submissionId && !(other.joinedHostSubmissionId === stored.row.submissionId && other.inputApplied !== void 0) && other.abortIntent === void 0) submissions.set(id, {
|
|
535
|
+
...other,
|
|
536
|
+
abortIntent: AbortIntent.make({
|
|
537
|
+
submissionId: id,
|
|
538
|
+
author: stored.row.principal,
|
|
539
|
+
reason: `Worker assignment ${terminal}`,
|
|
540
|
+
requestedAt: utc(nowMillis)
|
|
541
|
+
})
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
sealed = {
|
|
545
|
+
...current,
|
|
546
|
+
submissions,
|
|
547
|
+
stoppedWorkers: new Map(current.stoppedWorkers).set(stored.row.threadId, terminal)
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
const next = withSubmission(sealed, {
|
|
510
551
|
...stored,
|
|
511
552
|
row: {
|
|
512
553
|
...stored.row,
|
|
@@ -531,6 +572,49 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
|
|
|
531
572
|
if (decision._tag === "failure") return yield* decision.error;
|
|
532
573
|
return decision.value;
|
|
533
574
|
}));
|
|
575
|
+
const inspectWorker = Effect.fn("MemorySubmissionLedger.inspectWorker")(function* (threadId) {
|
|
576
|
+
const current = yield* Ref.get(state);
|
|
577
|
+
const latestId = current.latestByThread.get(threadId);
|
|
578
|
+
const latest = latestId === void 0 ? void 0 : current.submissions.get(latestId);
|
|
579
|
+
const active = [...current.activeByThread.get(threadId) ?? []].flatMap((id) => {
|
|
580
|
+
const row = current.submissions.get(id);
|
|
581
|
+
return row === void 0 ? [] : [row];
|
|
582
|
+
}).sort((a, b) => a.row.queueSequence - b.row.queueSequence)[0];
|
|
583
|
+
const terminal = current.stoppedWorkers.get(threadId);
|
|
584
|
+
return WorkerLedgerState.make({
|
|
585
|
+
latest: latest === void 0 ? null : toSnapshot(latest.row),
|
|
586
|
+
active: active === void 0 ? null : toSnapshot(active.row),
|
|
587
|
+
stopped: current.stoppedWorkers.has(threadId),
|
|
588
|
+
...terminal === void 0 ? {} : { terminal }
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
const stopWorker = Effect.fn("MemorySubmissionLedger.stopWorker")(function* (unvalidated) {
|
|
592
|
+
const request = yield* validate(WorkerStopCommand, "stopWorker", unvalidated);
|
|
593
|
+
const now = yield* Clock.currentTimeMillis;
|
|
594
|
+
return yield* Ref.modify(state, (current) => {
|
|
595
|
+
const submissions = new Map(current.submissions);
|
|
596
|
+
let owned = 0;
|
|
597
|
+
for (const id of current.activeByThread.get(request.threadId) ?? []) {
|
|
598
|
+
const stored = submissions.get(id);
|
|
599
|
+
if (stored === void 0) continue;
|
|
600
|
+
if (stored.ownership !== void 0) owned++;
|
|
601
|
+
if (stored.abortIntent === void 0) submissions.set(id, {
|
|
602
|
+
...stored,
|
|
603
|
+
abortIntent: AbortIntent.make({
|
|
604
|
+
submissionId: id,
|
|
605
|
+
author: request.author,
|
|
606
|
+
reason: "Worker owner stopped the worker",
|
|
607
|
+
requestedAt: utc(now)
|
|
608
|
+
})
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
return [owned, {
|
|
612
|
+
...current,
|
|
613
|
+
submissions,
|
|
614
|
+
stoppedWorkers: current.stoppedWorkers.has(request.threadId) ? current.stoppedWorkers : new Map(current.stoppedWorkers).set(request.threadId, void 0)
|
|
615
|
+
}];
|
|
616
|
+
});
|
|
617
|
+
});
|
|
534
618
|
const requestAbort = Effect.fn("MemorySubmissionLedger.requestAbort")((unvalidated) => Effect.gen(function* () {
|
|
535
619
|
const request = yield* validate(AbortCommand, "requestAbort", unvalidated);
|
|
536
620
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
@@ -569,6 +653,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
|
|
|
569
653
|
const claimJoining = Effect.fn("MemorySubmissionLedger.claimJoining")((unvalidated) => Effect.gen(function* () {
|
|
570
654
|
const request = yield* validate(ClaimJoiningRequest, "claimJoining", unvalidated);
|
|
571
655
|
const decision = yield* Ref.modify(state, (current) => {
|
|
656
|
+
if (current.stoppedWorkers.has(request.threadId)) return [success([]), current];
|
|
572
657
|
const host = current.submissions.get(request.hostSubmissionId);
|
|
573
658
|
if (host === void 0) return [failure(ledgerError("claimJoining", `Unknown Submission ${request.hostSubmissionId}`)), current];
|
|
574
659
|
if (host.row.threadId !== request.threadId) return [failure(ledgerError("claimJoining", `Host Submission ${request.hostSubmissionId} does not belong to Thread ${request.threadId}`)), current];
|
|
@@ -1038,6 +1123,8 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
|
|
|
1038
1123
|
reserveSettlement,
|
|
1039
1124
|
finalizeSettlement,
|
|
1040
1125
|
requestAbort,
|
|
1126
|
+
stopWorker,
|
|
1127
|
+
inspectWorker,
|
|
1041
1128
|
claimJoining,
|
|
1042
1129
|
markJoined,
|
|
1043
1130
|
revertJoining,
|