@effect-agent/platform-cloudflare 0.1.0-beta.6 → 0.1.0-beta.60
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/Alarm.d.mts +168 -0
- package/dist/Alarm.mjs +415 -0
- package/dist/Alarm.mjs.map +1 -0
- package/dist/BrowserRestCapture.d.mts +35 -0
- package/dist/BrowserRestCapture.mjs +238 -0
- package/dist/BrowserRestCapture.mjs.map +1 -0
- package/dist/BrowserRestCrawl.d.mts +17 -0
- package/dist/BrowserRestCrawl.mjs +244 -0
- package/dist/BrowserRestCrawl.mjs.map +1 -0
- package/dist/CloudflareBindings.d.mts +105 -0
- package/dist/CloudflareBindings.mjs +98 -0
- package/dist/CloudflareBindings.mjs.map +1 -0
- package/dist/CloudflareBrowser-Bj22nNUT.mjs +466 -0
- package/dist/CloudflareBrowser-Bj22nNUT.mjs.map +1 -0
- package/dist/CloudflareBrowser-DJq53niJ.d.mts +80 -0
- package/dist/CloudflareBrowser.d.mts +2 -0
- package/dist/CloudflareBrowser.mjs +2 -0
- package/dist/CloudflareCodeMode.d.mts +44 -0
- package/dist/CloudflareCodeMode.mjs +611 -0
- package/dist/CloudflareCodeMode.mjs.map +1 -0
- package/dist/CloudflareConfig-f3CqTel1.d.mts +101 -0
- package/dist/CloudflareConfig.d.mts +2 -0
- package/dist/CloudflareConfig.mjs +122 -0
- package/dist/CloudflareConfig.mjs.map +1 -0
- package/dist/CloudflareMemory.d.mts +140 -0
- package/dist/CloudflareMemory.mjs +168 -0
- package/dist/CloudflareMemory.mjs.map +1 -0
- package/dist/CloudflareScheduling.d.mts +52 -0
- package/dist/CloudflareScheduling.mjs +389 -0
- package/dist/CloudflareScheduling.mjs.map +1 -0
- package/dist/CloudflareSubscriptions.d.mts +87 -0
- package/dist/CloudflareSubscriptions.mjs +524 -0
- package/dist/CloudflareSubscriptions.mjs.map +1 -0
- package/dist/CloudflareThreadClient.d.mts +1214 -0
- package/dist/CloudflareThreadClient.mjs +386 -0
- package/dist/CloudflareThreadClient.mjs.map +1 -0
- package/dist/InteractiveBrowser.d.mts +144 -0
- package/dist/InteractiveBrowser.mjs +1090 -0
- package/dist/InteractiveBrowser.mjs.map +1 -0
- package/dist/ProtectedBrowser.d.mts +65 -0
- package/dist/ProtectedBrowser.mjs +859 -0
- package/dist/ProtectedBrowser.mjs.map +1 -0
- package/dist/ThreadObject-CvnEnVQH.d.mts +772 -0
- package/dist/ThreadObject-s5w3V_co.mjs +675 -0
- package/dist/ThreadObject-s5w3V_co.mjs.map +1 -0
- package/dist/ThreadObject.d.mts +2 -0
- package/dist/ThreadObject.mjs +3 -0
- package/dist/WakeScheduler.d.mts +23 -0
- package/dist/WakeScheduler.mjs +60 -0
- package/dist/WakeScheduler.mjs.map +1 -0
- package/dist/boundary-BguazVkh.mjs +42 -0
- package/dist/boundary-BguazVkh.mjs.map +1 -0
- package/dist/browser-session-lifecycle-CUbVfEgP.mjs +84 -0
- package/dist/browser-session-lifecycle-CUbVfEgP.mjs.map +1 -0
- package/dist/browser-session-lifecycle-DJpqLXy_.d.mts +21 -0
- package/dist/index.d.mts +12 -1543
- package/dist/index.mjs +12 -1636
- package/dist/prepared-admission-G7N4DDqS.mjs +73 -0
- package/dist/prepared-admission-G7N4DDqS.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -53
- package/src/Alarm.ts +862 -0
- package/src/BrowserRestCapture.ts +477 -0
- package/src/BrowserRestCrawl.ts +540 -0
- package/src/CloudflareBindings.ts +171 -0
- package/src/CloudflareBrowser.ts +15 -0
- package/src/{code-mode-executor.ts → CloudflareCodeMode.ts} +367 -204
- package/src/{config.ts → CloudflareConfig.ts} +9 -8
- package/src/CloudflareMemory.ts +359 -0
- package/src/CloudflareScheduling.ts +733 -0
- package/src/CloudflareSubscriptions.ts +1045 -0
- package/src/CloudflareThreadClient.ts +807 -0
- package/src/InteractiveBrowser.ts +2357 -0
- package/src/ProtectedBrowser.ts +2 -0
- package/src/{conversation-object.ts → ThreadObject.ts} +423 -239
- package/src/{wake-scheduler.ts → WakeScheduler.ts} +23 -23
- package/src/index.ts +11 -23
- package/src/internal/boundary.ts +55 -0
- package/src/internal/browser-quick-action.ts +818 -0
- package/src/internal/browser-session-lifecycle.ts +160 -0
- package/src/internal/layers.ts +615 -0
- package/src/internal/message-delivery.ts +90 -0
- package/src/internal/prepared-admission.ts +116 -0
- package/src/internal/progress-wait.ts +121 -0
- package/src/internal/transport.ts +42 -0
- package/src/protected-browser/binding.ts +187 -0
- package/src/protected-browser/inspect-frame.ts +125 -0
- package/src/protected-browser/native.ts +429 -0
- package/src/protected-browser/policy.ts +719 -0
- package/dist/index.mjs.map +0 -1
- package/src/alarm.ts +0 -334
- package/src/bindings.ts +0 -145
- package/src/client.ts +0 -646
- package/src/layers.ts +0 -376
- package/src/transport.ts +0 -38
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { DurableObjectContext, ThreadObjectIdentity, ThreadObjectNamespace, threadNamespaceFromEnv } from "./CloudflareBindings.mjs";
|
|
3
|
+
import { AdmissionLimitExceeded, CLOUDFLARE_RUNTIME_DEFAULTS, CloudflareDurableRuntimeConfig, CloudflareDurableRuntimeConfigValue, CloudflarePlatformConfigError } from "./CloudflareConfig.mjs";
|
|
4
|
+
import { DurableAlarmError, DurableAlarmService, ThreadMaintenance, ThreadMaintenanceFailpoint, ThreadMessageDelivery, ThreadMutationGate, ThreadPublication, publishCommitted } from "./Alarm.mjs";
|
|
5
|
+
import { AbortRecorded, ApprovalRecorded, CloudflareThreadClient, HostFailed, HostProtocolError, ObservedPage, ProgressCancelled, ProgressObserved, SettlementReached, SubmissionStatusResponse, SubmitSucceeded, UnknownResolutionRecorded, boundHostDiagnostic, decodeAbortCommand, decodeApprovalDecisionCommand, decodeAwaitProgressRequest, decodeCancelProgressRequest, decodeObservePageRequest, decodeReceipt, decodeSubmitRequest, decodeUnknownResolutionCommand, encodeHostResponse } from "./CloudflareThreadClient.mjs";
|
|
6
|
+
import { t as cloudflarePreparedInputAdmissionLayer } from "./prepared-admission-G7N4DDqS.mjs";
|
|
7
|
+
import { cloudflareWakeSchedulerLayer } from "./WakeScheduler.mjs";
|
|
8
|
+
import { compileRegistrations } from "@effect-agent/thread/AgentRegistration";
|
|
9
|
+
import { DurableAgentRuntime, DurableRuntimeConfig, RecoveryReport } from "@effect-agent/thread/DurableAgentRuntime";
|
|
10
|
+
import { AdmissionPolicyError, LedgerError, OwnershipLost, SettlementConflict, SubmissionLedger, SubmissionLookupByKey } from "@effect-agent/thread/SubmissionLedger";
|
|
11
|
+
import { ThreadProjectionMaintenance } from "@effect-agent/thread/ThreadProjectionMaintenance";
|
|
12
|
+
import { Cause, Clock, Context, Deferred, Duration, Effect, Layer, Option, Ref, Schema, Semaphore, Stream } from "effect";
|
|
13
|
+
import { SubmissionId, ThreadId } from "@effect-agent/core/Identifiers";
|
|
14
|
+
import { PersistedJson, ProducerId } from "@effect-agent/thread/Records";
|
|
15
|
+
import { DurableObject, DurableObjectState, WorkerEnvironment } from "effect-cf";
|
|
16
|
+
import { BrowserCrypto } from "@effect/platform-browser";
|
|
17
|
+
import { SqliteClient } from "@effect/sql-sqlite-do";
|
|
18
|
+
import { DigestError } from "@effect-agent/thread/Digest";
|
|
19
|
+
import { DurableRuntimeFailpoint, DurableRuntimeFailpointError } from "@effect-agent/thread/DurableFailpoint";
|
|
20
|
+
import { OperationAuthorizationRequest, OperationAuthorizer, OperationDenied, operationAuthorizerLayer } from "@effect-agent/thread/OperationAuthorizer";
|
|
21
|
+
import { AppendConflict, FenceRejected, ThreadNotMaterialized, ThreadRead, ThreadStore, ThreadStoreError } from "@effect-agent/thread/ThreadStore";
|
|
22
|
+
import { decodePortRequest, encodePortResponse } from "@effect-agent/storage-cloudflare/PortProtocol";
|
|
23
|
+
import { IntegrityReport, ObligationReport, ObligationThresholds, RecoveryExplanation, RetryCommand, RetryRefused } from "@effect-agent/thread/Admin";
|
|
24
|
+
import { RunJournalError } from "@effect-agent/thread/RunJournal";
|
|
25
|
+
import { WakeScheduler } from "@effect-agent/thread/WakeScheduler";
|
|
26
|
+
import { CurrentToolFailureObserver, RunContextPreparationPassthrough, RunToolAuthorization, toolFailureObserverLayer } from "@effect-agent/engine/RunOptions";
|
|
27
|
+
import { doMessageDeliveryStoreLayer } from "@effect-agent/storage-cloudflare/DoMessageDeliveryStore";
|
|
28
|
+
import "@effect-agent/storage-cloudflare/DoStorageFailpoint";
|
|
29
|
+
import { submissionLedgerLayer } from "@effect-agent/storage-cloudflare/DoSubmissionLedger";
|
|
30
|
+
import { storageConfigLayer, storageFailpointLayer, threadStoreLayer } from "@effect-agent/storage-cloudflare/DoThreadStore";
|
|
31
|
+
import { ThreadPortTransport, executePortRequest, portTransportFailure, routedSubmissionLedgerLayer, routedThreadStoreLayer } from "@effect-agent/storage-cloudflare/PortRouting";
|
|
32
|
+
import { MessageDeliveryDriver, MessageDeliveryError, MessageDeliveryStore } from "@effect-agent/thread/MessageDelivery";
|
|
33
|
+
import { ToolReconciler } from "@effect-agent/thread/ToolReconciler";
|
|
34
|
+
import { SqlClient } from "effect/unstable/sql/SqlClient";
|
|
35
|
+
//#region src/internal/message-delivery.ts
|
|
36
|
+
/** Every externally requested write prearms the owning Object's maintenance generation. */
|
|
37
|
+
const guardedMessageDeliveryStoreLayer = Layer.effect(MessageDeliveryStore, Effect.gen(function* () {
|
|
38
|
+
const store = yield* MessageDeliveryStore;
|
|
39
|
+
const mutations = yield* ThreadMutationGate;
|
|
40
|
+
const { threadId } = yield* ThreadObjectIdentity;
|
|
41
|
+
const deadline = yield* Ref.make(void 0);
|
|
42
|
+
const cacheGate = yield* Semaphore.make(1);
|
|
43
|
+
const local = (owner, body) => owner === threadId ? body : Effect.fail(MessageDeliveryError.make({
|
|
44
|
+
reason: "validation",
|
|
45
|
+
operation: "message owner"
|
|
46
|
+
}));
|
|
47
|
+
const mutate = (body) => mutations.withMutation(cacheGate.withPermit(Ref.set(deadline, void 0).pipe(Effect.andThen(body)))).pipe(Effect.catchTag("DurableAlarmError", () => MessageDeliveryError.make({
|
|
48
|
+
reason: "storage",
|
|
49
|
+
operation: "prearm message delivery"
|
|
50
|
+
})));
|
|
51
|
+
const nextDeadline = cacheGate.withPermit(Effect.gen(function* () {
|
|
52
|
+
const cached = yield* Ref.get(deadline);
|
|
53
|
+
if (cached !== void 0) return cached;
|
|
54
|
+
const current = yield* store.nextDeadline(threadId);
|
|
55
|
+
yield* Ref.set(deadline, current);
|
|
56
|
+
return current;
|
|
57
|
+
}));
|
|
58
|
+
return MessageDeliveryStore.of({
|
|
59
|
+
insert: (record) => local(record.key.ownerThreadId, mutate(store.insert(record))),
|
|
60
|
+
get: (key) => local(key.ownerThreadId, store.get(key)),
|
|
61
|
+
list: (request) => local(request.ownerThreadId, store.list(request)),
|
|
62
|
+
change: (key, change) => local(key.ownerThreadId, mutate(store.change(key, change))),
|
|
63
|
+
due: (nowMillis, limit, owner = threadId) => local(owner, store.due(nowMillis, limit, threadId)),
|
|
64
|
+
nextDeadline: (owner = threadId) => local(owner, nextDeadline)
|
|
65
|
+
});
|
|
66
|
+
}));
|
|
67
|
+
/** Message progress shares the native alarm slot without blocking source runtime work. */
|
|
68
|
+
const threadMessageDeliveryLayer = Layer.effectContext(Effect.gen(function* () {
|
|
69
|
+
const driver = yield* MessageDeliveryDriver;
|
|
70
|
+
const store = yield* MessageDeliveryStore;
|
|
71
|
+
const { threadId } = yield* ThreadObjectIdentity;
|
|
72
|
+
const failure = (operation) => () => DurableAlarmError.make({
|
|
73
|
+
operation,
|
|
74
|
+
message: "Durable message recovery remains pending"
|
|
75
|
+
});
|
|
76
|
+
return Context.make(ThreadMessageDelivery, {
|
|
77
|
+
drain: Effect.gen(function* () {
|
|
78
|
+
const deadline = yield* store.nextDeadline(threadId);
|
|
79
|
+
if (deadline !== null && deadline <= (yield* Clock.currentTimeMillis)) yield* driver.runDue(threadId);
|
|
80
|
+
}).pipe(Effect.mapError(failure("drain message delivery"))),
|
|
81
|
+
pendingDeadline: store.nextDeadline(threadId).pipe(Effect.map(Option.fromNullishOr), Effect.mapError(failure("read message deadline")))
|
|
82
|
+
});
|
|
83
|
+
}));
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/internal/progress-wait.ts
|
|
86
|
+
/** Cancellation tombstones are bounded hints, never durable authority. */
|
|
87
|
+
const MAX_CANCELLATION_TOMBSTONES = 1024;
|
|
88
|
+
/**
|
|
89
|
+
* Per-incarnation cancellation registry for long-lived progress RPCs. The public runtime owns
|
|
90
|
+
* the actual wake registration; this host-only registry lets an interrupted Worker Effect ask
|
|
91
|
+
* the Object to interrupt its scoped wait before the Worker execution context itself ends.
|
|
92
|
+
*/
|
|
93
|
+
var ProgressWaitRegistry = class ProgressWaitRegistry extends Context.Service()("@effect-agent/platform-cloudflare/ProgressWaitRegistry") {
|
|
94
|
+
static layer = Layer.effect(ProgressWaitRegistry, Effect.gen(function* () {
|
|
95
|
+
const registrations = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
96
|
+
const remove = (waiterId, deferred) => Ref.update(registrations, (current) => {
|
|
97
|
+
const existing = current.get(waiterId);
|
|
98
|
+
if (existing === void 0 || existing === "cancelled" || !existing.has(deferred)) return current;
|
|
99
|
+
const next = new Map(current);
|
|
100
|
+
const active = new Set(existing);
|
|
101
|
+
active.delete(deferred);
|
|
102
|
+
if (active.size === 0) next.delete(waiterId);
|
|
103
|
+
else next.set(waiterId, active);
|
|
104
|
+
return next;
|
|
105
|
+
});
|
|
106
|
+
const subscribe = Effect.fn("ProgressWaitRegistry.subscribe")((waiterId) => Effect.gen(function* () {
|
|
107
|
+
const deferred = yield* Deferred.make();
|
|
108
|
+
yield* Effect.addFinalizer(() => remove(waiterId, deferred));
|
|
109
|
+
return {
|
|
110
|
+
cancelled: yield* Ref.modify(registrations, (current) => {
|
|
111
|
+
const existing = current.get(waiterId);
|
|
112
|
+
const next = new Map(current);
|
|
113
|
+
if (existing === "cancelled") return [true, current];
|
|
114
|
+
const active = new Set(existing ?? []);
|
|
115
|
+
active.add(deferred);
|
|
116
|
+
next.set(waiterId, active);
|
|
117
|
+
return [false, next];
|
|
118
|
+
}),
|
|
119
|
+
deferred
|
|
120
|
+
};
|
|
121
|
+
}).pipe(Effect.map(({ cancelled, deferred }) => cancelled ? Effect.void : Deferred.await(deferred))));
|
|
122
|
+
const cancel = Effect.fn("ProgressWaitRegistry.cancel")(function* (waiterId) {
|
|
123
|
+
const waiters = yield* Ref.modify(registrations, (current) => {
|
|
124
|
+
const existing = current.get(waiterId);
|
|
125
|
+
if (existing === "cancelled") return [[], current];
|
|
126
|
+
const next = new Map(current);
|
|
127
|
+
next.delete(waiterId);
|
|
128
|
+
next.set(waiterId, "cancelled");
|
|
129
|
+
let tombstones = 0;
|
|
130
|
+
for (const registration of next.values()) if (registration === "cancelled") tombstones += 1;
|
|
131
|
+
if (tombstones > MAX_CANCELLATION_TOMBSTONES) for (const [id, registration] of next) {
|
|
132
|
+
if (registration !== "cancelled") continue;
|
|
133
|
+
next.delete(id);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
return [existing === void 0 ? [] : [...existing], next];
|
|
137
|
+
});
|
|
138
|
+
yield* Effect.forEach(waiters, (waiter) => Deferred.succeed(waiter, void 0), { discard: true });
|
|
139
|
+
}, Effect.uninterruptible);
|
|
140
|
+
return ProgressWaitRegistry.of({
|
|
141
|
+
subscribe,
|
|
142
|
+
cancel
|
|
143
|
+
});
|
|
144
|
+
}));
|
|
145
|
+
};
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/internal/transport.ts
|
|
148
|
+
/**
|
|
149
|
+
* `ThreadPortTransport` over native Durable Object JS RPC (decision D-P6-3): one
|
|
150
|
+
* `portCall(envelope)` on the stub of the Object that owns the addressed Thread
|
|
151
|
+
* (`namespace.idFromName(threadId)` — the identity rule, plan §1.2). The envelopes are
|
|
152
|
+
* already Schema-encoded JSON, so the RPC boundary carries only structured-cloneable values;
|
|
153
|
+
* the protocol module stays transport-agnostic and fetch-with-JSON remains the documented
|
|
154
|
+
* fallback carrier.
|
|
155
|
+
*
|
|
156
|
+
* Every delivery problem — stub construction, RPC rejection, overload, deploy-in-progress —
|
|
157
|
+
* surfaces as `PortTransportError` (preserving the platform stub's own `retryable` signal
|
|
158
|
+
* when present) and NEVER as a fabricated answer: on `resolveAdmission` the routing layer
|
|
159
|
+
* turns exactly this error into `AdmissionIndeterminate` (SUB-031).
|
|
160
|
+
*/
|
|
161
|
+
const threadPortTransportLayer = Layer.effect(ThreadPortTransport)(Effect.gen(function* () {
|
|
162
|
+
const { namespace } = yield* ThreadObjectNamespace;
|
|
163
|
+
return ThreadPortTransport.of({ call: (threadId, request) => Effect.tryPromise({
|
|
164
|
+
try: () => namespace.get(namespace.idFromName(threadId)).portCall(request),
|
|
165
|
+
catch: (cause) => portTransportFailure(threadId, cause)
|
|
166
|
+
}).pipe(Effect.withSpan("CloudflarePortTransport.call", { attributes: { threadId } })) });
|
|
167
|
+
}));
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/internal/layers.ts
|
|
170
|
+
/**
|
|
171
|
+
* Owner-side execution port for a `portCall` request the wire endpoint has already decoded.
|
|
172
|
+
* It executes against THIS Object's LOCAL port facets — never the routed decorators, so a
|
|
173
|
+
* request cannot bounce between Objects — and returns the typed response for the endpoint to
|
|
174
|
+
* encode.
|
|
175
|
+
*/
|
|
176
|
+
var ThreadObjectPorts = class extends Context.Service()("@effect-agent/platform-cloudflare/ThreadObjectPorts") {};
|
|
177
|
+
const decodeConfigValue = Schema.decodeUnknownEffect(CloudflareDurableRuntimeConfigValue);
|
|
178
|
+
const decodeThreadId = Schema.decodeUnknownEffect(ThreadId);
|
|
179
|
+
const decodeProducerId = Schema.decodeUnknownEffect(ProducerId);
|
|
180
|
+
const configFromOptions = (options) => decodeConfigValue({
|
|
181
|
+
deploymentId: options.deploymentId,
|
|
182
|
+
producerPrefix: options.producerPrefix,
|
|
183
|
+
ownershipLeaseDuration: options.ownershipLeaseDuration ?? CLOUDFLARE_RUNTIME_DEFAULTS.ownershipLeaseDuration,
|
|
184
|
+
alarmBackoffBase: options.alarmBackoffBase ?? CLOUDFLARE_RUNTIME_DEFAULTS.alarmBackoffBase,
|
|
185
|
+
alarmBackoffCap: options.alarmBackoffCap ?? CLOUDFLARE_RUNTIME_DEFAULTS.alarmBackoffCap,
|
|
186
|
+
wakeScanInterval: options.wakeScanInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.wakeScanInterval,
|
|
187
|
+
settlementPollInterval: options.settlementPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.settlementPollInterval,
|
|
188
|
+
leaseRenewalInterval: options.leaseRenewalInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.leaseRenewalInterval,
|
|
189
|
+
abortPollInterval: options.abortPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.abortPollInterval,
|
|
190
|
+
observationPollInterval: options.observationPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.observationPollInterval,
|
|
191
|
+
maxStoredValueBytes: options.maxStoredValueBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxStoredValueBytes,
|
|
192
|
+
verifyOnOpen: options.verifyOnOpen ?? CLOUDFLARE_RUNTIME_DEFAULTS.verifyOnOpen,
|
|
193
|
+
limits: {
|
|
194
|
+
maxQueueDepthPerLane: options.maxQueueDepthPerLane ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxQueueDepthPerLane,
|
|
195
|
+
maxInputBytes: Math.min(options.maxInputBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxInputBytes, options.maxStoredValueBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxStoredValueBytes),
|
|
196
|
+
maxDatabaseBytes: options.maxDatabaseBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxDatabaseBytes
|
|
197
|
+
}
|
|
198
|
+
}).pipe(Effect.mapError((error) => CloudflarePlatformConfigError.make({
|
|
199
|
+
message: `Invalid Cloudflare durable runtime configuration: ${error.message}`,
|
|
200
|
+
cause: error
|
|
201
|
+
})));
|
|
202
|
+
/**
|
|
203
|
+
* The Thread this Object owns, from the Object identity rule (plan §1.2): Thread
|
|
204
|
+
* Objects are addressed exclusively by `idFromName(threadId)`, so `ctx.id.name` IS the
|
|
205
|
+
* Thread ID. An unnamed Object (from `newUniqueId`) is a deployment error, not a lane.
|
|
206
|
+
*/
|
|
207
|
+
const threadIdFromState = (ctx) => ctx.id.name === void 0 ? Effect.fail(CloudflarePlatformConfigError.make({ message: "This Durable Object was not created via idFromName(threadId); Thread Objects must be addressed by their Thread identity (plan §1.2)." })) : decodeThreadId(ctx.id.name).pipe(Effect.mapError((error) => CloudflarePlatformConfigError.make({
|
|
208
|
+
message: `The Durable Object name is not a valid ThreadId: ${error.message}`,
|
|
209
|
+
cause: error
|
|
210
|
+
})));
|
|
211
|
+
/**
|
|
212
|
+
* Validate deployment settings and derive services before building application dependencies.
|
|
213
|
+
* The native class factory builds this Layer inside its constructor gate. Custom Effect hosts
|
|
214
|
+
* can provide it around the complete application Layer with the native context already supplied.
|
|
215
|
+
*/
|
|
216
|
+
const layerConfig = (options) => Layer.unwrap(Effect.gen(function* () {
|
|
217
|
+
const { ctx } = yield* DurableObjectContext;
|
|
218
|
+
const config = yield* configFromOptions(options);
|
|
219
|
+
const threadId = yield* threadIdFromState(ctx);
|
|
220
|
+
const producerId = yield* decodeProducerId(`${config.producerPrefix}:${threadId}`).pipe(Effect.mapError((error) => CloudflarePlatformConfigError.make({
|
|
221
|
+
message: `The minted producer identity is invalid: ${error.message}`,
|
|
222
|
+
cause: error
|
|
223
|
+
})));
|
|
224
|
+
return Layer.mergeAll(Layer.succeed(CloudflareDurableRuntimeConfig, config), Layer.succeed(ThreadObjectIdentity, {
|
|
225
|
+
threadId,
|
|
226
|
+
producerId
|
|
227
|
+
}), DurableRuntimeConfig.layer({
|
|
228
|
+
deploymentId: config.deploymentId,
|
|
229
|
+
producerId,
|
|
230
|
+
settlementPollInterval: Duration.millis(config.settlementPollInterval),
|
|
231
|
+
leaseRenewalInterval: Duration.millis(config.leaseRenewalInterval),
|
|
232
|
+
abortPollInterval: Duration.millis(config.abortPollInterval),
|
|
233
|
+
...options.estimateCostMicrousd === void 0 ? {} : { estimateCostMicrousd: options.estimateCostMicrousd }
|
|
234
|
+
}), BrowserCrypto.layer, storageFailpointLayer({
|
|
235
|
+
storage: ctx.storage,
|
|
236
|
+
failpoint: options.storageFailpoint?.(ctx)
|
|
237
|
+
}), options.runtimeFailpoint === void 0 ? DurableRuntimeFailpoint.layer : Layer.succeed(DurableRuntimeFailpoint, { hit: options.runtimeFailpoint(ctx) }), options.maintenanceFailpoint === void 0 ? ThreadMaintenanceFailpoint.layer : Layer.succeed(ThreadMaintenanceFailpoint, { hit: options.maintenanceFailpoint(ctx) }), options.toolReconciler ?? ToolReconciler.uncertain, options.operationAuthorizer === void 0 ? Layer.empty : operationAuthorizerLayer(options.operationAuthorizer), options.toolFailureObserver === void 0 ? Layer.succeed(CurrentToolFailureObserver, void 0) : toolFailureObserverLayer(options.toolFailureObserver), RunContextPreparationPassthrough, RunToolAuthorization.allowAll);
|
|
238
|
+
}));
|
|
239
|
+
/**
|
|
240
|
+
* Register typed Agents and version declarations. Hashing and dependency capture happen in
|
|
241
|
+
* this Layer's Scope, after application Layers have been provided. Every Agent's instruction,
|
|
242
|
+
* Tool, Schema, and model requirements remain visible until satisfied by Layer composition.
|
|
243
|
+
* Use Layer.unwrap for registration values that need effectful application setup.
|
|
244
|
+
*/
|
|
245
|
+
const registeredLayer = (registrations, options = {}) => Layer.unwrap(Effect.map(compileRegistrations(registrations), (bindings) => boundLayer(bindings, options)));
|
|
246
|
+
function layer(registrations, options = {}) {
|
|
247
|
+
return registeredLayer(registrations, options);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Assemble the durable runtime from already-resolved Agent Bindings.
|
|
251
|
+
* Use `ThreadObject.layer` to compile typed Agent registrations instead.
|
|
252
|
+
* Supply host services through `ThreadObject.make` or `ThreadObject.layerConfig` and
|
|
253
|
+
* the Durable Object context and namespace Layers when composing a custom host.
|
|
254
|
+
*/
|
|
255
|
+
const boundLayer = (bindings, options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
256
|
+
const { ctx } = yield* DurableObjectContext;
|
|
257
|
+
const config = yield* CloudflareDurableRuntimeConfig;
|
|
258
|
+
const { threadId } = yield* ThreadObjectIdentity;
|
|
259
|
+
const storageOptions = {
|
|
260
|
+
storage: ctx.storage,
|
|
261
|
+
observationPollInterval: config.observationPollInterval,
|
|
262
|
+
ownershipLeaseDuration: config.ownershipLeaseDuration,
|
|
263
|
+
maxStoredValueBytes: config.maxStoredValueBytes,
|
|
264
|
+
verifyOnOpen: config.verifyOnOpen
|
|
265
|
+
};
|
|
266
|
+
const infrastructure = Layer.mergeAll(storageConfigLayer(storageOptions), SqliteClient.layer({ storage: ctx.storage }));
|
|
267
|
+
const rawLocalPorts = Layer.mergeAll(threadStoreLayer, submissionLedgerLayer).pipe(Layer.provide(infrastructure));
|
|
268
|
+
const messageStore = guardedMessageDeliveryStoreLayer.pipe(Layer.provide(doMessageDeliveryStoreLayer().pipe(Layer.provide(infrastructure))));
|
|
269
|
+
const messageRecovery = threadMessageDeliveryLayer.pipe(Layer.provide(MessageDeliveryDriver.layer({
|
|
270
|
+
batchSize: 4,
|
|
271
|
+
concurrency: 4
|
|
272
|
+
})), Layer.provide(cloudflarePreparedInputAdmissionLayer), Layer.provide(CloudflareThreadClient.layer), Layer.provide(messageStore));
|
|
273
|
+
const publication = (options.publication ?? ThreadPublication.layer).pipe(Layer.provide(Layer.mergeAll(rawLocalPorts, Layer.effect(SqlClient)(SqlClient))));
|
|
274
|
+
const projection = (options.projection ?? ThreadProjectionMaintenance.layer).pipe(Layer.provide(Layer.mergeAll(rawLocalPorts, Layer.effect(SqlClient)(SqlClient))));
|
|
275
|
+
const localPorts = options.publication === void 0 && options.projection === void 0 ? rawLocalPorts : Layer.effectContext(Effect.gen(function* () {
|
|
276
|
+
const store = yield* ThreadStore;
|
|
277
|
+
const ledger = yield* SubmissionLedger;
|
|
278
|
+
const mutations = yield* ThreadMutationGate;
|
|
279
|
+
const index = yield* ThreadProjectionMaintenance;
|
|
280
|
+
const publish = yield* Effect.context();
|
|
281
|
+
const afterCommit = publishCommitted.pipe(Effect.provide(publish));
|
|
282
|
+
const sourceCommits = yield* Semaphore.make(1);
|
|
283
|
+
const observedStore = ThreadStore.of({
|
|
284
|
+
...store,
|
|
285
|
+
append: (request) => mutations.withMutation(sourceCommits.withPermit(store.append(request).pipe(Effect.tap((result) => index.applyCommitted(request, result).pipe(Effect.catchCause((cause) => Cause.hasInterrupts(cause) ? Effect.interrupt : Effect.logError("Thread projection deferred after source commit", cause)))))).pipe(Effect.tap(() => afterCommit))).pipe(Effect.catchTag("DurableAlarmError", (cause) => ThreadStoreError.make({
|
|
286
|
+
operation: "prearm publication append",
|
|
287
|
+
message: cause.message,
|
|
288
|
+
cause
|
|
289
|
+
})))
|
|
290
|
+
});
|
|
291
|
+
const observeIntent = (body) => mutations.withMutation(body.pipe(Effect.tap(() => afterCommit))).pipe(Effect.catchTag("DurableAlarmError", (cause) => LedgerError.make({
|
|
292
|
+
operation: "prearm publication intent",
|
|
293
|
+
message: "The publication generation could not be armed",
|
|
294
|
+
cause
|
|
295
|
+
})));
|
|
296
|
+
return Context.make(ThreadStore, observedStore).pipe(Context.add(SubmissionLedger, {
|
|
297
|
+
...ledger,
|
|
298
|
+
recordApprovalDecision: (request) => observeIntent(ledger.recordApprovalDecision(request)),
|
|
299
|
+
requestAbort: (request) => observeIntent(ledger.requestAbort(request)),
|
|
300
|
+
recordUnknownResolution: (request) => observeIntent(ledger.recordUnknownResolution(request))
|
|
301
|
+
}));
|
|
302
|
+
})).pipe(Layer.provide(rawLocalPorts));
|
|
303
|
+
const portsEndpointLayer = Layer.effect(ThreadObjectPorts)(Effect.gen(function* () {
|
|
304
|
+
const local = yield* Effect.context();
|
|
305
|
+
return ThreadObjectPorts.of({ handle: (request) => executePortRequest(request).pipe(Effect.provide(local)) });
|
|
306
|
+
})).pipe(Layer.provide(localPorts));
|
|
307
|
+
const routedPorts = Layer.mergeAll(routedSubmissionLedgerLayer({ localThreadId: threadId }), routedThreadStoreLayer({ localThreadId: threadId })).pipe(Layer.provide(localPorts), Layer.provide(threadPortTransportLayer));
|
|
308
|
+
const base = Layer.mergeAll(DurableAlarmService.layer, ProgressWaitRegistry.layer);
|
|
309
|
+
const runtimeStack = DurableAgentRuntime.layerWithBindings(bindings).pipe(Layer.provide(cloudflarePreparedInputAdmissionLayer.pipe(Layer.provide(CloudflareThreadClient.layer))), Layer.provideMerge(messageStore), Layer.provideMerge(routedPorts), Layer.provideMerge(cloudflareWakeSchedulerLayer), Layer.provideMerge(base));
|
|
310
|
+
return Layer.mergeAll(runtimeStack, ThreadMaintenance.layer.pipe(Layer.provide(runtimeStack), Layer.provide(messageRecovery)), portsEndpointLayer, messageStore).pipe(Layer.provideMerge(publication), Layer.provideMerge(projection), Layer.provideMerge(ThreadMutationGate.layer), Layer.provideMerge(infrastructure));
|
|
311
|
+
}));
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/ThreadObject.ts
|
|
314
|
+
var ThreadObject_exports = /* @__PURE__ */ __exportAll({
|
|
315
|
+
AdminExplainRequest: () => AdminExplainRequest,
|
|
316
|
+
AdminFailed: () => AdminFailed,
|
|
317
|
+
AdminFailure: () => AdminFailure,
|
|
318
|
+
AdminResponse: () => AdminResponse,
|
|
319
|
+
AdminVerifyRequest: () => AdminVerifyRequest,
|
|
320
|
+
ExplainedRecovery: () => ExplainedRecovery,
|
|
321
|
+
ObligationsScanned: () => ObligationsScanned,
|
|
322
|
+
RetryExecuted: () => RetryExecuted,
|
|
323
|
+
VerifiedIntegrity: () => VerifiedIntegrity,
|
|
324
|
+
decodeAdminExplainRequest: () => decodeAdminExplainRequest,
|
|
325
|
+
decodeAdminResponse: () => decodeAdminResponse,
|
|
326
|
+
decodeAdminVerifyRequest: () => decodeAdminVerifyRequest,
|
|
327
|
+
decodeObligationThresholds: () => decodeObligationThresholds,
|
|
328
|
+
decodeRetryCommand: () => decodeRetryCommand,
|
|
329
|
+
encodeAdminResponse: () => encodeAdminResponse,
|
|
330
|
+
layer: () => layer,
|
|
331
|
+
layerConfig: () => layerConfig,
|
|
332
|
+
make: () => make
|
|
333
|
+
});
|
|
334
|
+
/** Classify only a decoded port request so new protocol members cannot bypass pre-arming. */
|
|
335
|
+
const isMutatingPortRequest = (request) => {
|
|
336
|
+
switch (request._tag) {
|
|
337
|
+
case "LedgerAdmit":
|
|
338
|
+
case "LedgerMarkReady":
|
|
339
|
+
case "LedgerRequestAbort":
|
|
340
|
+
case "LedgerRecordChildSettled":
|
|
341
|
+
case "StoreMaterialize":
|
|
342
|
+
case "StoreAppend": return true;
|
|
343
|
+
case "LedgerLookup":
|
|
344
|
+
case "LedgerResolveAdmission":
|
|
345
|
+
case "StoreReadPage":
|
|
346
|
+
case "StoreInspectTail":
|
|
347
|
+
case "StoreExport": return false;
|
|
348
|
+
}
|
|
349
|
+
return false;
|
|
350
|
+
};
|
|
351
|
+
/** The literal encoded `PortFailed(PortProtocolError)` fallback (same shape as WP2's). */
|
|
352
|
+
const encodedPortProtocolFailure = (message) => ({
|
|
353
|
+
_tag: "PortFailed",
|
|
354
|
+
failure: {
|
|
355
|
+
_tag: "PortProtocolError",
|
|
356
|
+
message: boundHostDiagnostic(message)
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
const protocolFailure = (context) => (error) => HostProtocolError.make({ message: boundHostDiagnostic(`${context}: ${error.message}`) });
|
|
360
|
+
/** Fold one endpoint's typed failures into the uniform `HostResponse` envelope. */
|
|
361
|
+
const respond = (effect) => effect.pipe(Effect.map((result) => result), Effect.catch((failure) => Effect.succeed(HostFailed.make({ failure }))));
|
|
362
|
+
/** Encode the response envelope; an unencodable response degrades to a protocol failure. */
|
|
363
|
+
const encodeResponse = (response) => encodeHostResponse(response).pipe(Effect.catch((error) => Effect.succeed({
|
|
364
|
+
_tag: "HostFailed",
|
|
365
|
+
failure: {
|
|
366
|
+
_tag: "HostProtocolError",
|
|
367
|
+
message: boundHostDiagnostic(`The host response could not be encoded: ${error.message}`)
|
|
368
|
+
}
|
|
369
|
+
})));
|
|
370
|
+
const utf8Bytes = (value) => new TextEncoder().encode(JSON.stringify(value)).length;
|
|
371
|
+
/**
|
|
372
|
+
* The admission-limits gate, BEFORE `runtime.submit` touches the ledger (exit gate
|
|
373
|
+
* "resource limits are checked before admission"; DEPLOY-007). A replayed idempotency key is
|
|
374
|
+
* exempt: its accepted-work obligation already exists, and returning the original Receipt
|
|
375
|
+
* consumes no new quota. Refusals are typed `AdmissionLimitExceeded` and nothing is written.
|
|
376
|
+
*/
|
|
377
|
+
const gateAdmissionLimits = Effect.fn("ThreadObject.gateAdmissionLimits")(function* (request) {
|
|
378
|
+
const identity = yield* ThreadObjectIdentity;
|
|
379
|
+
const config = yield* CloudflareDurableRuntimeConfig;
|
|
380
|
+
const ledger = yield* SubmissionLedger;
|
|
381
|
+
const { ctx } = yield* DurableObjectContext;
|
|
382
|
+
const existing = yield* ledger.lookup(SubmissionLookupByKey.make({
|
|
383
|
+
threadId: identity.threadId,
|
|
384
|
+
principal: request.principal,
|
|
385
|
+
idempotencyKey: request.idempotencyKey
|
|
386
|
+
}));
|
|
387
|
+
if (Option.isSome(existing)) return;
|
|
388
|
+
const inputBytes = utf8Bytes(request.inputPayload);
|
|
389
|
+
if (inputBytes > config.limits.maxInputBytes) return yield* AdmissionLimitExceeded.make({
|
|
390
|
+
limit: "input-bytes",
|
|
391
|
+
actual: inputBytes,
|
|
392
|
+
maximum: config.limits.maxInputBytes
|
|
393
|
+
});
|
|
394
|
+
const nonterminal = yield* Stream.runCollect(ledger.scanNonterminal);
|
|
395
|
+
if (nonterminal.length >= config.limits.maxQueueDepthPerLane) return yield* AdmissionLimitExceeded.make({
|
|
396
|
+
limit: "queue-depth",
|
|
397
|
+
actual: nonterminal.length,
|
|
398
|
+
maximum: config.limits.maxQueueDepthPerLane
|
|
399
|
+
});
|
|
400
|
+
const databaseBytes = yield* Effect.sync(() => ctx.storage.sql.databaseSize);
|
|
401
|
+
if (databaseBytes > config.limits.maxDatabaseBytes) return yield* AdmissionLimitExceeded.make({
|
|
402
|
+
limit: "database-bytes",
|
|
403
|
+
actual: databaseBytes,
|
|
404
|
+
maximum: config.limits.maxDatabaseBytes
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
/**
|
|
408
|
+
* The submit-capable projection of an Agent Binding on the OBJECT side: the input arrived
|
|
409
|
+
* already encoded through the real input schema on the Worker side (`client.ts`), so the
|
|
410
|
+
* Object admits the canonical `PersistedJson` payload as-is; the resolved Binding re-derives
|
|
411
|
+
* everything else from the stored `(agentId, agentDigests)` at claim time (SUB-023).
|
|
412
|
+
*/
|
|
413
|
+
const passthroughSubmitAgent = (agentId) => ({ definition: {
|
|
414
|
+
id: agentId,
|
|
415
|
+
input: PersistedJson
|
|
416
|
+
} });
|
|
417
|
+
const submitEndpoint = (encoded) => decodeSubmitRequest(encoded).pipe(Effect.mapError(protocolFailure("The submit request could not be decoded")), Effect.flatMap((request) => Effect.gen(function* () {
|
|
418
|
+
const identity = yield* ThreadObjectIdentity;
|
|
419
|
+
const maintenance = yield* ThreadMaintenance;
|
|
420
|
+
const runtime = yield* DurableAgentRuntime;
|
|
421
|
+
yield* gateAdmissionLimits(request);
|
|
422
|
+
const receipt = yield* maintenance.withMutation(runtime.submit(passthroughSubmitAgent(request.agentId), request.inputPayload, {
|
|
423
|
+
threadId: identity.threadId,
|
|
424
|
+
principal: request.principal,
|
|
425
|
+
idempotencyKey: request.idempotencyKey,
|
|
426
|
+
...request.admissionGroup === void 0 ? {} : { admissionGroup: request.admissionGroup },
|
|
427
|
+
...request.admissionFence === void 0 ? {} : { admissionFence: request.admissionFence },
|
|
428
|
+
...request.workerAdmission === void 0 ? {} : { workerAdmission: request.workerAdmission },
|
|
429
|
+
...request.messageAdmission === void 0 ? {} : { messageAdmission: request.messageAdmission },
|
|
430
|
+
definitions: request.definitions
|
|
431
|
+
}));
|
|
432
|
+
return SubmitSucceeded.make({ receipt });
|
|
433
|
+
})), respond, Effect.flatMap(encodeResponse));
|
|
434
|
+
const submissionStatusEndpoint = (encoded) => decodeReceipt(encoded).pipe(Effect.mapError(protocolFailure("The receipt could not be decoded")), Effect.flatMap((receipt) => Effect.gen(function* () {
|
|
435
|
+
const runtime = yield* DurableAgentRuntime;
|
|
436
|
+
return SubmissionStatusResponse.make({ status: yield* runtime.submissionStatus(receipt) });
|
|
437
|
+
})), respond, Effect.flatMap(encodeResponse));
|
|
438
|
+
const awaitSettlementEndpoint = (encoded) => decodeReceipt(encoded).pipe(Effect.mapError(protocolFailure("The receipt could not be decoded")), Effect.flatMap((receipt) => Effect.gen(function* () {
|
|
439
|
+
const settlement = yield* (yield* DurableAgentRuntime).awaitSettlement(receipt);
|
|
440
|
+
return SettlementReached.make({ settlement });
|
|
441
|
+
})), respond, Effect.flatMap(encodeResponse));
|
|
442
|
+
const awaitProgressEndpoint = (encoded) => decodeAwaitProgressRequest(encoded).pipe(Effect.mapError(protocolFailure("The progress request could not be decoded")), Effect.flatMap((request) => Effect.gen(function* () {
|
|
443
|
+
const identity = yield* ThreadObjectIdentity;
|
|
444
|
+
const runtime = yield* DurableAgentRuntime;
|
|
445
|
+
const registry = yield* ProgressWaitRegistry;
|
|
446
|
+
yield* Effect.scoped(Effect.gen(function* () {
|
|
447
|
+
const cancelled = yield* registry.subscribe(request.waiterId);
|
|
448
|
+
yield* Effect.raceFirst(runtime.awaitProgress(identity.threadId, request.afterSequence), cancelled);
|
|
449
|
+
}));
|
|
450
|
+
return ProgressObserved.make();
|
|
451
|
+
})), respond, Effect.flatMap(encodeResponse));
|
|
452
|
+
const cancelProgressEndpoint = (encoded) => decodeCancelProgressRequest(encoded).pipe(Effect.mapError(protocolFailure("The progress cancellation could not be decoded")), Effect.flatMap((request) => Effect.gen(function* () {
|
|
453
|
+
yield* (yield* ProgressWaitRegistry).cancel(request.waiterId);
|
|
454
|
+
return ProgressCancelled.make();
|
|
455
|
+
})), respond, Effect.flatMap(encodeResponse));
|
|
456
|
+
const observePageEndpoint = (encoded) => decodeObservePageRequest(encoded).pipe(Effect.mapError(protocolFailure("The observe request could not be decoded")), Effect.flatMap((request) => Effect.gen(function* () {
|
|
457
|
+
const identity = yield* ThreadObjectIdentity;
|
|
458
|
+
const store = yield* ThreadStore;
|
|
459
|
+
yield* (yield* OperationAuthorizer).authorize(OperationAuthorizationRequest.make({
|
|
460
|
+
operation: "observe",
|
|
461
|
+
threadId: identity.threadId
|
|
462
|
+
}));
|
|
463
|
+
const records = yield* Stream.runCollect(store.read(ThreadRead.make({
|
|
464
|
+
threadId: identity.threadId,
|
|
465
|
+
...request.afterSequence === void 0 ? {} : { afterSequence: request.afterSequence },
|
|
466
|
+
limit: request.limit
|
|
467
|
+
})));
|
|
468
|
+
return ObservedPage.make({ records: [...records] });
|
|
469
|
+
})), respond, Effect.flatMap(encodeResponse));
|
|
470
|
+
const abortEndpoint = (encoded) => decodeAbortCommand(encoded).pipe(Effect.mapError(protocolFailure("The abort command could not be decoded")), Effect.flatMap((command) => Effect.gen(function* () {
|
|
471
|
+
const maintenance = yield* ThreadMaintenance;
|
|
472
|
+
const runtime = yield* DurableAgentRuntime;
|
|
473
|
+
const intent = yield* maintenance.withMutation(runtime.abort(command));
|
|
474
|
+
return AbortRecorded.make({ intent });
|
|
475
|
+
})), respond, Effect.flatMap(encodeResponse));
|
|
476
|
+
const resolveApprovalEndpoint = (encoded) => decodeApprovalDecisionCommand(encoded).pipe(Effect.mapError(protocolFailure("The approval command could not be decoded")), Effect.flatMap((command) => Effect.gen(function* () {
|
|
477
|
+
const maintenance = yield* ThreadMaintenance;
|
|
478
|
+
const runtime = yield* DurableAgentRuntime;
|
|
479
|
+
const intent = yield* maintenance.withMutation(runtime.resolveApproval(command));
|
|
480
|
+
return ApprovalRecorded.make({ intent });
|
|
481
|
+
})), respond, Effect.flatMap(encodeResponse));
|
|
482
|
+
const resolveUnknownEndpoint = (encoded) => decodeUnknownResolutionCommand(encoded).pipe(Effect.mapError(protocolFailure("The resolution command could not be decoded")), Effect.flatMap((command) => Effect.gen(function* () {
|
|
483
|
+
const maintenance = yield* ThreadMaintenance;
|
|
484
|
+
const runtime = yield* DurableAgentRuntime;
|
|
485
|
+
const intent = yield* maintenance.withMutation(runtime.resolveUnknown(command));
|
|
486
|
+
return UnknownResolutionRecorded.make({ intent });
|
|
487
|
+
})), respond, Effect.flatMap(encodeResponse));
|
|
488
|
+
/** Explain one Submission (`submissionId` present) or every nonterminal lane member. */
|
|
489
|
+
var AdminExplainRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/AdminExplainRequest")({ submissionId: Schema.optionalKey(SubmissionId) }) {};
|
|
490
|
+
/** Verify carries no parameters — the addressed Object IS the lane. */
|
|
491
|
+
var AdminVerifyRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/AdminVerifyRequest")({}) {};
|
|
492
|
+
/** Every typed failure of the four admin entry points, plus the protocol's own errors. */
|
|
493
|
+
const AdminFailure = Schema.Union([
|
|
494
|
+
AdmissionPolicyError,
|
|
495
|
+
OperationDenied,
|
|
496
|
+
RetryRefused,
|
|
497
|
+
LedgerError,
|
|
498
|
+
RunJournalError,
|
|
499
|
+
DigestError,
|
|
500
|
+
OwnershipLost,
|
|
501
|
+
SettlementConflict,
|
|
502
|
+
ThreadStoreError,
|
|
503
|
+
ThreadNotMaterialized,
|
|
504
|
+
AppendConflict,
|
|
505
|
+
FenceRejected,
|
|
506
|
+
DurableRuntimeFailpointError,
|
|
507
|
+
DurableAlarmError,
|
|
508
|
+
HostProtocolError
|
|
509
|
+
]);
|
|
510
|
+
var ExplainedRecovery = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ExplainedRecovery")("ExplainedRecovery", { explanations: Schema.Array(RecoveryExplanation).check(Schema.isMaxLength(1024)) }) {};
|
|
511
|
+
var VerifiedIntegrity = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/VerifiedIntegrity")("VerifiedIntegrity", { report: IntegrityReport }) {};
|
|
512
|
+
var RetryExecuted = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/RetryExecuted")("RetryExecuted", { report: RecoveryReport }) {};
|
|
513
|
+
var ObligationsScanned = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ObligationsScanned")("ObligationsScanned", { report: ObligationReport }) {};
|
|
514
|
+
/** The admin entry point failed TYPED on the Object; the failure re-decodes verbatim. */
|
|
515
|
+
var AdminFailed = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/AdminFailed")("AdminFailed", { failure: AdminFailure }) {};
|
|
516
|
+
/** The uniform answer of one admin entry point. Callers narrow by the tag their call implies. */
|
|
517
|
+
const AdminResponse = Schema.Union([
|
|
518
|
+
ExplainedRecovery,
|
|
519
|
+
VerifiedIntegrity,
|
|
520
|
+
RetryExecuted,
|
|
521
|
+
ObligationsScanned,
|
|
522
|
+
AdminFailed
|
|
523
|
+
]);
|
|
524
|
+
const decodeAdminExplainRequest = Schema.decodeUnknownEffect(AdminExplainRequest);
|
|
525
|
+
const decodeAdminVerifyRequest = Schema.decodeUnknownEffect(AdminVerifyRequest);
|
|
526
|
+
const decodeRetryCommand = Schema.decodeUnknownEffect(RetryCommand);
|
|
527
|
+
const decodeObligationThresholds = Schema.decodeUnknownEffect(ObligationThresholds);
|
|
528
|
+
const encodeAdminResponse = Schema.encodeEffect(AdminResponse);
|
|
529
|
+
const decodeAdminResponse = Schema.decodeUnknownEffect(AdminResponse);
|
|
530
|
+
/** Fold one admin endpoint's typed failures into the uniform `AdminResponse` envelope. */
|
|
531
|
+
const respondAdmin = (effect) => effect.pipe(Effect.map((result) => result), Effect.catch((failure) => Effect.succeed(AdminFailed.make({ failure }))));
|
|
532
|
+
/** Encode the admin response envelope; an unencodable response degrades to a protocol failure. */
|
|
533
|
+
const encodeAdminResponseTotal = (response) => encodeAdminResponse(response).pipe(Effect.catch((error) => Effect.succeed({
|
|
534
|
+
_tag: "AdminFailed",
|
|
535
|
+
failure: {
|
|
536
|
+
_tag: "HostProtocolError",
|
|
537
|
+
message: boundHostDiagnostic(`The admin response could not be encoded: ${error.message}`)
|
|
538
|
+
}
|
|
539
|
+
})));
|
|
540
|
+
const explainEndpoint = (encoded) => decodeAdminExplainRequest(encoded).pipe(Effect.mapError(protocolFailure("The explain request could not be decoded")), Effect.flatMap((request) => Effect.gen(function* () {
|
|
541
|
+
const identity = yield* ThreadObjectIdentity;
|
|
542
|
+
const runtime = yield* DurableAgentRuntime;
|
|
543
|
+
const explanations = request.submissionId === void 0 ? yield* runtime.explainThread(identity.threadId) : [yield* runtime.explain(request.submissionId)];
|
|
544
|
+
return ExplainedRecovery.make({ explanations });
|
|
545
|
+
})), respondAdmin, Effect.flatMap(encodeAdminResponseTotal));
|
|
546
|
+
const verifyEndpoint = (encoded) => decodeAdminVerifyRequest(encoded).pipe(Effect.mapError(protocolFailure("The verify request could not be decoded")), Effect.flatMap(() => Effect.gen(function* () {
|
|
547
|
+
const identity = yield* ThreadObjectIdentity;
|
|
548
|
+
const report = yield* (yield* DurableAgentRuntime).verify(identity.threadId);
|
|
549
|
+
return VerifiedIntegrity.make({ report });
|
|
550
|
+
})), respondAdmin, Effect.flatMap(encodeAdminResponseTotal));
|
|
551
|
+
const retryEndpoint = (encoded) => decodeRetryCommand(encoded).pipe(Effect.mapError(protocolFailure("The retry command could not be decoded")), Effect.flatMap((command) => Effect.gen(function* () {
|
|
552
|
+
const maintenance = yield* ThreadMaintenance;
|
|
553
|
+
const runtime = yield* DurableAgentRuntime;
|
|
554
|
+
const report = yield* maintenance.withMutation(runtime.retry(command));
|
|
555
|
+
return RetryExecuted.make({ report });
|
|
556
|
+
})), respondAdmin, Effect.flatMap(encodeAdminResponseTotal));
|
|
557
|
+
const obligationsEndpoint = (encoded) => decodeObligationThresholds(encoded).pipe(Effect.mapError(protocolFailure("The obligation thresholds could not be decoded")), Effect.flatMap((thresholds) => Effect.gen(function* () {
|
|
558
|
+
const report = yield* (yield* DurableAgentRuntime).scanObligations(thresholds);
|
|
559
|
+
return ObligationsScanned.make({ report });
|
|
560
|
+
})), respondAdmin, Effect.flatMap(encodeAdminResponseTotal));
|
|
561
|
+
/**
|
|
562
|
+
* Owner-side `portCall`: wrap a mutating envelope in the same pre-armed generation protocol as
|
|
563
|
+
* public RPC (a routed mutation committed by THIS Object must already carry the alarm that will
|
|
564
|
+
* finish it), execute on the LOCAL facets (never the routed decorators), then arm an immediate
|
|
565
|
+
* alarm so the mutated lane is processed promptly. Protocol anomalies answer
|
|
566
|
+
* `PortFailed(PortProtocolError)`.
|
|
567
|
+
*/
|
|
568
|
+
const portCallEndpoint = (encoded) => Effect.gen(function* () {
|
|
569
|
+
const ports = yield* ThreadObjectPorts;
|
|
570
|
+
const maintenance = yield* ThreadMaintenance;
|
|
571
|
+
const alarm = yield* DurableAlarmService;
|
|
572
|
+
const decoded = yield* decodePortRequest(encoded).pipe(Effect.map((request) => ({
|
|
573
|
+
_tag: "success",
|
|
574
|
+
request
|
|
575
|
+
})), Effect.catch((error) => Effect.succeed({
|
|
576
|
+
_tag: "failure",
|
|
577
|
+
message: error.message
|
|
578
|
+
})));
|
|
579
|
+
if (decoded._tag === "failure") return encodedPortProtocolFailure(`The port request could not be decoded: ${decoded.message}`);
|
|
580
|
+
const mutating = isMutatingPortRequest(decoded.request);
|
|
581
|
+
const handled = yield* (mutating ? maintenance.withMutation(ports.handle(decoded.request)) : ports.handle(decoded.request)).pipe(Effect.exit);
|
|
582
|
+
if (handled._tag === "Failure") return encodedPortProtocolFailure("The owner Object could not arm its maintenance alarm before the mutation.");
|
|
583
|
+
const response = yield* encodePortResponse(handled.value).pipe(Effect.catch((error) => Effect.succeed(encodedPortProtocolFailure(`The port response could not be encoded: ${error.message}`))));
|
|
584
|
+
if (mutating) yield* alarm.scheduleNow.pipe(Effect.catch((error) => Effect.logWarning("ThreadObject.portCall: immediate re-arm failed", error)));
|
|
585
|
+
return response;
|
|
586
|
+
});
|
|
587
|
+
const wakeEndpoint = Effect.gen(function* () {
|
|
588
|
+
const identity = yield* ThreadObjectIdentity;
|
|
589
|
+
yield* (yield* WakeScheduler).notify(identity.threadId);
|
|
590
|
+
});
|
|
591
|
+
const alarmEndpoint = Effect.gen(function* () {
|
|
592
|
+
yield* (yield* ThreadMaintenance).pass;
|
|
593
|
+
});
|
|
594
|
+
const gateEndpoint = Effect.gen(function* () {
|
|
595
|
+
yield* (yield* ThreadMaintenance).ensureAlarm;
|
|
596
|
+
});
|
|
597
|
+
/**
|
|
598
|
+
* Adapter from effect-cf's native Durable Object services to Effect Agent's existing platform
|
|
599
|
+
* ports. effect-cf owns the cached ManagedRuntime and supplies these values once per Object
|
|
600
|
+
* incarnation; the durable runtime continues to depend only on the narrow services below.
|
|
601
|
+
*/
|
|
602
|
+
const effectCfPlatformLayer = (namespaceBinding, rpcTracing = false) => {
|
|
603
|
+
const context = Layer.effect(DurableObjectContext)(Effect.gen(function* () {
|
|
604
|
+
const state = yield* DurableObjectState.DurableObjectState;
|
|
605
|
+
const env = yield* WorkerEnvironment;
|
|
606
|
+
return DurableObjectContext.of({
|
|
607
|
+
ctx: state.raw,
|
|
608
|
+
env
|
|
609
|
+
});
|
|
610
|
+
}));
|
|
611
|
+
const namespace = Layer.effect(ThreadObjectNamespace)(Effect.gen(function* () {
|
|
612
|
+
const env = yield* WorkerEnvironment;
|
|
613
|
+
const binding = yield* threadNamespaceFromEnv(env, namespaceBinding);
|
|
614
|
+
return ThreadObjectNamespace.of({
|
|
615
|
+
namespace: binding,
|
|
616
|
+
...rpcTracing === true ? { rpcTracing: namespaceBinding } : {}
|
|
617
|
+
});
|
|
618
|
+
}));
|
|
619
|
+
return Layer.merge(context, namespace);
|
|
620
|
+
};
|
|
621
|
+
/**
|
|
622
|
+
* Export a composed application Layer as a native Durable Object class.
|
|
623
|
+
* Bootstrap services are provided to the whole graph before it acquires, so application Layers
|
|
624
|
+
* can yield effect-cf's WorkerEnvironment and DurableObjectState, derived identity, and Crypto.
|
|
625
|
+
* Application dependencies remain visible until Layer.provide satisfies them. effect-cf owns the
|
|
626
|
+
* cached ManagedRuntime, native RPC methods, event scopes, and telemetry flushing.
|
|
627
|
+
* Initialization is local and bounded inside the constructor gate. Cloudflare eviction does not
|
|
628
|
+
* guarantee finalizers; put resources requiring timely release in scoped operations or eventLayer.
|
|
629
|
+
*/
|
|
630
|
+
const make = (applicationLayer, options) => {
|
|
631
|
+
const application = applicationLayer.pipe(Layer.provideMerge(layerConfig(options)), Layer.provideMerge(effectCfPlatformLayer(options.namespaceBinding, options.rpcTracing)));
|
|
632
|
+
const runtime = Layer.effectContext(Effect.gen(function* () {
|
|
633
|
+
const state = yield* DurableObjectState.DurableObjectState;
|
|
634
|
+
const scope = yield* Effect.scope;
|
|
635
|
+
return yield* state.blockConcurrencyWhile(Effect.gen(function* () {
|
|
636
|
+
const services = yield* Layer.buildWithScope(application, scope);
|
|
637
|
+
yield* gateEndpoint.pipe(Effect.provide(services));
|
|
638
|
+
return services;
|
|
639
|
+
}));
|
|
640
|
+
}));
|
|
641
|
+
const rpc = {
|
|
642
|
+
submitEncoded: (encoded) => submitEndpoint(encoded),
|
|
643
|
+
submissionStatusEncoded: (encoded) => submissionStatusEndpoint(encoded),
|
|
644
|
+
awaitSettlementEncoded: (encoded) => awaitSettlementEndpoint(encoded),
|
|
645
|
+
awaitProgressEncoded: (encoded) => awaitProgressEndpoint(encoded),
|
|
646
|
+
cancelProgressEncoded: (encoded) => cancelProgressEndpoint(encoded),
|
|
647
|
+
observePage: (encoded) => observePageEndpoint(encoded),
|
|
648
|
+
abortEncoded: (encoded) => abortEndpoint(encoded),
|
|
649
|
+
resolveApprovalEncoded: (encoded) => resolveApprovalEndpoint(encoded),
|
|
650
|
+
resolveUnknownEncoded: (encoded) => resolveUnknownEndpoint(encoded),
|
|
651
|
+
explainEncoded: (encoded) => explainEndpoint(encoded),
|
|
652
|
+
verifyEncoded: (encoded) => verifyEndpoint(encoded),
|
|
653
|
+
retryEncoded: (encoded) => retryEndpoint(encoded),
|
|
654
|
+
obligationsEncoded: (encoded) => obligationsEndpoint(encoded),
|
|
655
|
+
portCall: (encoded) => portCallEndpoint(encoded),
|
|
656
|
+
wake: () => wakeEndpoint
|
|
657
|
+
};
|
|
658
|
+
const EffectCfThreadObject = DurableObject.make(runtime, {
|
|
659
|
+
...options.rpcTracing === true ? { rpcTracing: { service: options.namespaceBinding } } : {},
|
|
660
|
+
...options.eventLayer === void 0 ? {} : { eventLayer: options.eventLayer },
|
|
661
|
+
initialize: Effect.void,
|
|
662
|
+
rpc,
|
|
663
|
+
alarm: () => alarmEndpoint
|
|
664
|
+
});
|
|
665
|
+
class ThreadObject extends EffectCfThreadObject {
|
|
666
|
+
alarm(alarmInfo) {
|
|
667
|
+
return super.alarm?.(alarmInfo);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return ThreadObject;
|
|
671
|
+
};
|
|
672
|
+
//#endregion
|
|
673
|
+
export { make as _, AdminVerifyRequest as a, RetryExecuted as c, decodeAdminExplainRequest as d, decodeAdminResponse as f, encodeAdminResponse as g, decodeRetryCommand as h, AdminResponse as i, ThreadObject_exports as l, decodeObligationThresholds as m, AdminFailed as n, ExplainedRecovery as o, decodeAdminVerifyRequest as p, AdminFailure as r, ObligationsScanned as s, AdminExplainRequest as t, VerifiedIntegrity as u, layer as v, layerConfig as y };
|
|
674
|
+
|
|
675
|
+
//# sourceMappingURL=ThreadObject-s5w3V_co.mjs.map
|