@effect-agent/platform-cloudflare 0.0.1-beta.6 → 0.1.0-beta.100
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 +207 -0
- package/dist/Alarm.mjs +578 -0
- package/dist/Alarm.mjs.map +1 -0
- package/dist/BrowserRestCapture.d.mts +34 -0
- package/dist/BrowserRestCapture.mjs +238 -0
- package/dist/BrowserRestCapture.mjs.map +1 -0
- package/dist/BrowserRestCrawl.d.mts +16 -0
- package/dist/BrowserRestCrawl.mjs +244 -0
- package/dist/BrowserRestCrawl.mjs.map +1 -0
- package/dist/CloudflareAiGateway.d.mts +64 -0
- package/dist/CloudflareAiGateway.mjs +70 -0
- package/dist/CloudflareAiGateway.mjs.map +1 -0
- package/dist/CloudflareBindings.d.mts +115 -0
- package/dist/CloudflareBindings.mjs +113 -0
- package/dist/CloudflareBindings.mjs.map +1 -0
- package/dist/CloudflareBrowser-DV6kya1H.mjs +495 -0
- package/dist/CloudflareBrowser-DV6kya1H.mjs.map +1 -0
- package/dist/CloudflareBrowser-Doe1M7R5.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 +616 -0
- package/dist/CloudflareCodeMode.mjs.map +1 -0
- package/dist/CloudflareConfig-DKXGo8L3.d.mts +106 -0
- package/dist/CloudflareConfig.d.mts +2 -0
- package/dist/CloudflareConfig.mjs +128 -0
- package/dist/CloudflareConfig.mjs.map +1 -0
- package/dist/CloudflareMemory.d.mts +142 -0
- package/dist/CloudflareMemory.mjs +202 -0
- package/dist/CloudflareMemory.mjs.map +1 -0
- package/dist/CloudflareScheduling.d.mts +52 -0
- package/dist/CloudflareScheduling.mjs +396 -0
- package/dist/CloudflareScheduling.mjs.map +1 -0
- package/dist/CloudflareSubscriptions.d.mts +87 -0
- package/dist/CloudflareSubscriptions.mjs +530 -0
- package/dist/CloudflareSubscriptions.mjs.map +1 -0
- package/dist/CloudflareThreadClient.d.mts +1600 -0
- package/dist/CloudflareThreadClient.mjs +382 -0
- package/dist/CloudflareThreadClient.mjs.map +1 -0
- package/dist/InteractiveBrowser-BCS9g0tn.mjs +1180 -0
- package/dist/InteractiveBrowser-BCS9g0tn.mjs.map +1 -0
- package/dist/InteractiveBrowser-BR16UPbF.d.mts +169 -0
- package/dist/InteractiveBrowser.d.mts +2 -0
- package/dist/InteractiveBrowser.mjs +2 -0
- package/dist/ProtectedBrowser.d.mts +123 -0
- package/dist/ProtectedBrowser.mjs +1135 -0
- package/dist/ProtectedBrowser.mjs.map +1 -0
- package/dist/ThreadObject-CMQiW2uX.d.mts +827 -0
- package/dist/ThreadObject-DXavwaU-.mjs +853 -0
- package/dist/ThreadObject-DXavwaU-.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 +57 -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/index.d.mts +13 -1580
- package/dist/index.mjs +13 -1941
- package/dist/prepared-admission-DU0_T-ev.mjs +73 -0
- package/dist/prepared-admission-DU0_T-ev.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -52
- package/src/Alarm.ts +1270 -0
- package/src/BrowserRestCapture.ts +477 -0
- package/src/BrowserRestCrawl.ts +540 -0
- package/src/CloudflareAiGateway.ts +170 -0
- package/src/CloudflareBindings.ts +199 -0
- package/src/CloudflareBrowser.ts +15 -0
- package/src/{code-mode-executor.ts → CloudflareCodeMode.ts} +372 -204
- package/src/{config.ts → CloudflareConfig.ts} +19 -15
- package/src/CloudflareMemory.ts +420 -0
- package/src/CloudflareScheduling.ts +747 -0
- package/src/CloudflareSubscriptions.ts +1041 -0
- package/src/CloudflareThreadClient.ts +809 -0
- package/src/InteractiveBrowser.ts +2418 -0
- package/src/ProtectedBrowser.ts +9 -0
- package/src/ThreadObject.ts +1179 -0
- package/src/{wake-scheduler.ts → WakeScheduler.ts} +32 -26
- package/src/index.ts +12 -24
- package/src/internal/boundary.ts +55 -0
- package/src/internal/browser-quick-action.ts +857 -0
- package/src/internal/browser-session-lifecycle.ts +160 -0
- package/src/internal/layers.ts +756 -0
- package/src/internal/message-delivery.ts +181 -0
- package/src/internal/prepared-admission.ts +116 -0
- package/src/internal/progress-wait.ts +121 -0
- package/src/internal/transport.ts +44 -0
- package/src/protected-browser/binding.ts +313 -0
- package/src/protected-browser/host.ts +344 -0
- package/src/protected-browser/inspect-frame.ts +125 -0
- package/src/protected-browser/native.ts +445 -0
- package/src/protected-browser/policy.ts +821 -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/conversation-object.ts +0 -847
- package/src/layers.ts +0 -385
- package/src/telemetry-internal.ts +0 -432
- package/src/telemetry.ts +0 -37
- package/src/transport.ts +0 -38
package/dist/index.mjs
CHANGED
|
@@ -1,1941 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* per incarnation and everything downstream consumes the services.
|
|
15
|
-
*/
|
|
16
|
-
/** A Cloudflare platform binding was missing or carried the wrong shape (DEPLOY-003/010). */
|
|
17
|
-
var CloudflareBindingError = class extends Schema.TaggedError()("CloudflareBindingError", {
|
|
18
|
-
binding: Schema.String,
|
|
19
|
-
message: Schema.String
|
|
20
|
-
}) {};
|
|
21
|
-
/**
|
|
22
|
-
* The `DurableObjectNamespace` binding that addresses Conversation Objects. The Object
|
|
23
|
-
* identity rule is `namespace.idFromName(conversationId)` (plan §1.2): Conversation IDs are
|
|
24
|
-
* globally unique, so the mapping is total and deterministic and no directory service exists.
|
|
25
|
-
*/
|
|
26
|
-
var ConversationObjectNamespace = class ConversationObjectNamespace extends Context.Service()("@effect-agent/platform-cloudflare/ConversationObjectNamespace") {
|
|
27
|
-
static layer(namespace) {
|
|
28
|
-
return Layer.succeed(ConversationObjectNamespace)({ namespace });
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* Narrow one `env` member to a `DurableObjectNamespace`. `env` is an untyped platform value,
|
|
33
|
-
* and a namespace binding is a host object no Schema can decode, so this is the documented
|
|
34
|
-
* narrowest-boundary check (structural probe for the namespace surface the transport uses);
|
|
35
|
-
* a missing or misshaped binding fails typed before any Layer is built.
|
|
36
|
-
*/
|
|
37
|
-
const conversationNamespaceFromEnv = (env, binding) => Effect.suspend(() => {
|
|
38
|
-
if (typeof env !== "object" || env === null) return Effect.fail(CloudflareBindingError.make({
|
|
39
|
-
binding,
|
|
40
|
-
message: "The Worker environment is not an object; no bindings are available."
|
|
41
|
-
}));
|
|
42
|
-
const candidate = env[binding];
|
|
43
|
-
if (typeof candidate === "object" && candidate !== null && "idFromName" in candidate && typeof candidate.idFromName === "function" && "get" in candidate && typeof candidate.get === "function") return Effect.succeed(candidate);
|
|
44
|
-
return Effect.fail(CloudflareBindingError.make({
|
|
45
|
-
binding,
|
|
46
|
-
message: `env.${binding} is not a DurableObjectNamespace binding; declare the Conversation Object class under this binding in the Worker configuration.`
|
|
47
|
-
}));
|
|
48
|
-
});
|
|
49
|
-
/** `ConversationObjectNamespace` built from the untyped Worker `env` (fails typed). */
|
|
50
|
-
const conversationNamespaceLayer = (env, binding) => Layer.effect(ConversationObjectNamespace)(Effect.map(conversationNamespaceFromEnv(env, binding), (namespace) => ({ namespace })));
|
|
51
|
-
/**
|
|
52
|
-
* The live Durable Object execution context of THIS incarnation. Only Layer construction and
|
|
53
|
-
* the alarm service consume it; important state never lives on it (`ctx.storage` is truth,
|
|
54
|
-
* everything in memory is a cache — deployment spec §11).
|
|
55
|
-
*/
|
|
56
|
-
var DurableObjectContext = class DurableObjectContext extends Context.Service()("@effect-agent/platform-cloudflare/DurableObjectContext") {
|
|
57
|
-
static layer(ctx, env) {
|
|
58
|
-
return Layer.succeed(DurableObjectContext)({
|
|
59
|
-
ctx,
|
|
60
|
-
env
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
/**
|
|
65
|
-
* The Conversation identity this Object serializes and the producer identity its Attempts
|
|
66
|
-
* write with (`{producerPrefix}:{conversationId}`, plan §1.4). Derived once per incarnation
|
|
67
|
-
* from `ctx.id.name` — the Object identity rule guarantees the name IS the Conversation ID.
|
|
68
|
-
*/
|
|
69
|
-
var ConversationObjectIdentity = class extends Context.Service()("@effect-agent/platform-cloudflare/ConversationObjectIdentity") {};
|
|
70
|
-
//#endregion
|
|
71
|
-
//#region src/config.ts
|
|
72
|
-
/**
|
|
73
|
-
* Schema-validated configuration for the Cloudflare durable runtime (deployment spec §4:
|
|
74
|
-
* decoded once during Layer construction, exposed as a typed service; DEPLOY-003). Every
|
|
75
|
-
* cadence is in milliseconds; every bound is finite and checked before any resource opens.
|
|
76
|
-
*/
|
|
77
|
-
const PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0));
|
|
78
|
-
const NonNegativeMillis = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
79
|
-
/** The supplied Cloudflare durable runtime configuration failed validation (DEPLOY-003). */
|
|
80
|
-
var CloudflarePlatformConfigError = class extends Schema.TaggedError()("CloudflarePlatformConfigError", {
|
|
81
|
-
message: Schema.String,
|
|
82
|
-
cause: Schema.optionalKey(Schema.Defect())
|
|
83
|
-
}) {};
|
|
84
|
-
/**
|
|
85
|
-
* An admission was refused by a host resource limit BEFORE any ledger row existed
|
|
86
|
-
* (deployment spec §8, DEPLOY-007: "Admission has explicit bounded quota and overload
|
|
87
|
-
* behavior... a typed rejection"). This is the DC analogue of `NodeDurableHost`'s
|
|
88
|
-
* `AdmissionClosed` host gate: the port surface stays untouched, the refusal happens in the
|
|
89
|
-
* Conversation Object's submit entry point before `DurableAgentRuntime.submit` runs, and
|
|
90
|
-
* nothing was admitted or written.
|
|
91
|
-
*/
|
|
92
|
-
var AdmissionLimitExceeded = class extends Schema.TaggedError()("AdmissionLimitExceeded", {
|
|
93
|
-
limit: Schema.Literals([
|
|
94
|
-
"queue-depth",
|
|
95
|
-
"input-bytes",
|
|
96
|
-
"database-bytes"
|
|
97
|
-
]),
|
|
98
|
-
actual: Schema.Int,
|
|
99
|
-
maximum: Schema.Int
|
|
100
|
-
}) {
|
|
101
|
-
get message() {
|
|
102
|
-
return `Admission refused before any ledger row existed: ${this.limit} ${this.actual} exceeds the configured maximum ${this.maximum}. Accepted work is unaffected; retry after the lane drains or raise the limit (DEPLOY-007).`;
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
/** The platform's hard per-Object database cap (10 GB, developers.cloudflare.com limits). */
|
|
106
|
-
const CLOUDFLARE_DATABASE_CAP_BYTES = 1e10;
|
|
107
|
-
/** Default database-size admission ceiling: a 1 GB safety margin under the platform cap. */
|
|
108
|
-
const DEFAULT_MAX_DATABASE_BYTES = 9e9;
|
|
109
|
-
/**
|
|
110
|
-
* Explicit bounded admission quotas checked by the Conversation Object BEFORE admission
|
|
111
|
-
* (exit gate "resource limits are checked before admission").
|
|
112
|
-
*/
|
|
113
|
-
var CloudflareAdmissionLimitsValue = class extends Schema.Class("@effect-agent/platform-cloudflare/CloudflareAdmissionLimitsValue")({
|
|
114
|
-
/** Maximum nonterminal Submissions per Conversation lane before new admissions refuse. */
|
|
115
|
-
maxQueueDepthPerLane: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(1e5)),
|
|
116
|
-
/** Maximum encoded input bytes; never above the storage per-value bound. */
|
|
117
|
-
maxInputBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(2e6)),
|
|
118
|
-
/** Maximum `ctx.storage.sql.databaseSize` at admission; stays under the 10 GB platform cap. */
|
|
119
|
-
maxDatabaseBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(CLOUDFLARE_DATABASE_CAP_BYTES))
|
|
120
|
-
}) {};
|
|
121
|
-
/**
|
|
122
|
-
* Validated Cloudflare durable runtime configuration. The producer identity of one
|
|
123
|
-
* Conversation Object is `{producerPrefix}:{conversationId}` — stable across incarnations of
|
|
124
|
-
* the same deployment, distinct across deployments — and producer-epoch fencing (not the
|
|
125
|
-
* producer name) remains the correctness authority (DUR-006).
|
|
126
|
-
*/
|
|
127
|
-
var CloudflareDurableRuntimeConfigValue = class extends Schema.Class("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfigValue")({
|
|
128
|
-
deploymentId: DeploymentId,
|
|
129
|
-
/** Head of the minted producer identity `{producerPrefix}:{conversationId}`. */
|
|
130
|
-
producerPrefix: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
|
|
131
|
-
/** Submission ownership lease duration (D5); fences work across Object incarnations. */
|
|
132
|
-
ownershipLeaseDuration: PositiveMillis,
|
|
133
|
-
/** Base delay of the alarm re-arm backoff when a pass makes no progress. */
|
|
134
|
-
alarmBackoffBase: PositiveMillis,
|
|
135
|
-
/** Ceiling of the alarm re-arm backoff. */
|
|
136
|
-
alarmBackoffCap: PositiveMillis,
|
|
137
|
-
/**
|
|
138
|
-
* The maintenance-pass scan cadence and the ceiling of every re-arm delay: nonterminal
|
|
139
|
-
* work is revisited at least this often (wake/scan pairing, persistence §14).
|
|
140
|
-
*/
|
|
141
|
-
wakeScanInterval: PositiveMillis,
|
|
142
|
-
/** `awaitSettlement` ledger re-check cadence when no wake arrives. */
|
|
143
|
-
settlementPollInterval: PositiveMillis,
|
|
144
|
-
/** Worker ownership-lease renewal cadence during an active Attempt. */
|
|
145
|
-
leaseRenewalInterval: PositiveMillis,
|
|
146
|
-
/** Active-Run abort-intent poll cadence. */
|
|
147
|
-
abortPollInterval: PositiveMillis,
|
|
148
|
-
/** Canonical observation poll cadence of the Durable Object store. */
|
|
149
|
-
observationPollInterval: NonNegativeMillis,
|
|
150
|
-
/** Cooperative background-export budget; native Object delivery never awaits the flush. */
|
|
151
|
-
telemetryFlushTimeout: PositiveMillis,
|
|
152
|
-
/** Per-value byte bound; must stay under the platform's 2 MB SQLite value limit. */
|
|
153
|
-
maxStoredValueBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(2e6)),
|
|
154
|
-
/** Opt-in full payload/digest-chain audit while opening the store. */
|
|
155
|
-
verifyOnOpen: Schema.Boolean,
|
|
156
|
-
limits: CloudflareAdmissionLimitsValue
|
|
157
|
-
}) {};
|
|
158
|
-
/** Explicit configuration authority for the assembled Cloudflare durable runtime. */
|
|
159
|
-
var CloudflareDurableRuntimeConfig = class extends Context.Service()("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfig") {};
|
|
160
|
-
/** Documented production defaults applied by `CloudflareDurableRuntime.layer`. */
|
|
161
|
-
const CLOUDFLARE_RUNTIME_DEFAULTS = {
|
|
162
|
-
ownershipLeaseDuration: Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION),
|
|
163
|
-
alarmBackoffBase: 100,
|
|
164
|
-
alarmBackoffCap: 5e3,
|
|
165
|
-
wakeScanInterval: 1e3,
|
|
166
|
-
settlementPollInterval: 500,
|
|
167
|
-
leaseRenewalInterval: 1e4,
|
|
168
|
-
abortPollInterval: 500,
|
|
169
|
-
observationPollInterval: 25,
|
|
170
|
-
telemetryFlushTimeout: 2e3,
|
|
171
|
-
maxStoredValueBytes: DEFAULT_MAX_STORED_VALUE_BYTES,
|
|
172
|
-
verifyOnOpen: false,
|
|
173
|
-
maxQueueDepthPerLane: 256,
|
|
174
|
-
maxInputBytes: DEFAULT_MAX_STORED_VALUE_BYTES,
|
|
175
|
-
maxDatabaseBytes: DEFAULT_MAX_DATABASE_BYTES
|
|
176
|
-
};
|
|
177
|
-
//#endregion
|
|
178
|
-
//#region src/alarm.ts
|
|
179
|
-
/**
|
|
180
|
-
* The single multiplexed Durable Object alarm (decision D-P6-2). A Durable Object has ONE
|
|
181
|
-
* alarm slot; every cadence the Node host ran on fibers (wake scan, lease expiry, settlement
|
|
182
|
-
* and abort re-checks, retry backoff) multiplexes into one idempotent maintenance pass, and
|
|
183
|
-
* the slot always holds the EARLIEST deadline any caller asked for.
|
|
184
|
-
*
|
|
185
|
-
* The alarm invariant (plan §1.4): committed nonterminal work implies a committed alarm.
|
|
186
|
-
* It is established by pre-arming — every mutating entry point and every pass arms the alarm
|
|
187
|
-
* BEFORE its durable mutations — so an eviction at any failpoint leaves a persisted alarm
|
|
188
|
-
* that workerd re-delivers to a fresh incarnation WITHOUT any incoming request. Spurious
|
|
189
|
-
* alarms are harmless by design: a pass over an all-settled lane simply deletes the slot.
|
|
190
|
-
*/
|
|
191
|
-
/** The Durable Object alarm API failed; surfaces on host entry points as a typed refusal. */
|
|
192
|
-
var DurableAlarmError = class extends Schema.TaggedError()("DurableAlarmError", {
|
|
193
|
-
operation: Schema.String,
|
|
194
|
-
message: Schema.String,
|
|
195
|
-
cause: Schema.optionalKey(Schema.Defect())
|
|
196
|
-
}) {};
|
|
197
|
-
const alarmFailure = (operation) => (cause) => DurableAlarmError.make({
|
|
198
|
-
operation,
|
|
199
|
-
message: cause instanceof Error ? cause.message : String(cause),
|
|
200
|
-
cause
|
|
201
|
-
});
|
|
202
|
-
/** `ctx.storage` alarm slot as an Effect service; storage is truth, never a memory field. */
|
|
203
|
-
var DurableAlarmService = class DurableAlarmService extends Context.Service()("@effect-agent/platform-cloudflare/DurableAlarmService") {
|
|
204
|
-
static layer = Layer.effect(DurableAlarmService)(Effect.gen(function* () {
|
|
205
|
-
const { ctx } = yield* DurableObjectContext;
|
|
206
|
-
/**
|
|
207
|
-
* In-memory pass bookkeeping — a pure CACHE, never state: a fresh incarnation has no
|
|
208
|
-
* running pass, and a deferred wake lost to eviction was only ever a promptness hint
|
|
209
|
-
* on top of the already-committed pre-armed alarm.
|
|
210
|
-
*/
|
|
211
|
-
const runningPasses = yield* Ref.make(0);
|
|
212
|
-
const wakeDeferred = yield* Ref.make(false);
|
|
213
|
-
const scheduled = Effect.tryPromise({
|
|
214
|
-
try: () => ctx.storage.getAlarm(),
|
|
215
|
-
catch: alarmFailure("get alarm")
|
|
216
|
-
}).pipe(Effect.map((deadline) => deadline === null ? Option.none() : Option.some(deadline)));
|
|
217
|
-
const scheduleAt = (epochMillis) => Effect.tryPromise({
|
|
218
|
-
try: () => ctx.storage.setAlarm(epochMillis),
|
|
219
|
-
catch: alarmFailure("set alarm")
|
|
220
|
-
});
|
|
221
|
-
const ensureScheduledBy = (epochMillis) => scheduled.pipe(Effect.flatMap((existing) => Option.isSome(existing) && existing.value <= epochMillis ? Effect.void : scheduleAt(epochMillis)));
|
|
222
|
-
const armNow = Clock.currentTimeMillis.pipe(Effect.flatMap((now) => ensureScheduledBy(now)));
|
|
223
|
-
const scheduleNow = Ref.get(runningPasses).pipe(Effect.flatMap((passes) => passes > 0 ? Ref.set(wakeDeferred, true) : armNow));
|
|
224
|
-
/**
|
|
225
|
-
* Flush after the LAST concurrent pass: the re-arm lands at the very end of the alarm
|
|
226
|
-
* handler, where a superseding cancellation only re-delivers to the already-idempotent
|
|
227
|
-
* pass. Runs inside `Effect.ensuring`, so flush failures are logged, never raised.
|
|
228
|
-
*/
|
|
229
|
-
const flushDeferredWake = Ref.get(runningPasses).pipe(Effect.flatMap((passes) => passes > 0 ? Effect.void : Ref.getAndSet(wakeDeferred, false).pipe(Effect.flatMap((wanted) => wanted ? armNow : Effect.void))), Effect.catch((error) => Effect.logWarning("DurableAlarmService: deferred wake flush failed", error)));
|
|
230
|
-
const withWakesDeferred = (body) => Ref.update(runningPasses, (passes) => passes + 1).pipe(Effect.andThen(body), Effect.ensuring(Ref.update(runningPasses, (passes) => passes - 1).pipe(Effect.andThen(flushDeferredWake))));
|
|
231
|
-
const cancel = Effect.tryPromise({
|
|
232
|
-
try: () => ctx.storage.deleteAlarm(),
|
|
233
|
-
catch: alarmFailure("delete alarm")
|
|
234
|
-
});
|
|
235
|
-
return DurableAlarmService.of({
|
|
236
|
-
scheduled,
|
|
237
|
-
scheduleAt,
|
|
238
|
-
ensureScheduledBy,
|
|
239
|
-
scheduleNow,
|
|
240
|
-
withWakesDeferred,
|
|
241
|
-
cancel
|
|
242
|
-
});
|
|
243
|
-
}));
|
|
244
|
-
};
|
|
245
|
-
/** What one maintenance pass did — auditable evidence mirroring `NodeDurableHost`'s report. */
|
|
246
|
-
var MaintenancePassReport = class extends Schema.Class("@effect-agent/platform-cloudflare/MaintenancePassReport")({
|
|
247
|
-
/** Recovery decisions executed (or deferred) BEFORE any new claim in this pass. */
|
|
248
|
-
recovered: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
249
|
-
/** Settlements the drain pass finalized. */
|
|
250
|
-
settled: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
251
|
-
/** Submissions still nonterminal after the pass (suspended/unknown lanes stay honest). */
|
|
252
|
-
nonterminal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
253
|
-
/** `rearmed` while nonterminal work remains, `cleared` once everything settled. */
|
|
254
|
-
alarm: Schema.Literals(["rearmed", "cleared"])
|
|
255
|
-
}) {};
|
|
256
|
-
/**
|
|
257
|
-
* The idempotent maintenance pass and the alarm-invariant helpers (plan §1.4, D-P6-1/2).
|
|
258
|
-
*
|
|
259
|
-
* `pass` = pre-arm → `runRecovery` → `processConversationResolved` → re-arm-or-clear:
|
|
260
|
-
*
|
|
261
|
-
* 1. **Pre-arm**: the alarm is re-armed at `now + wakeScanInterval` BEFORE any work, so an
|
|
262
|
-
* eviction (or thrown failure → workerd alarm retry) at any point of the pass leaves a
|
|
263
|
-
* committed alarm and the fresh incarnation converges without an incoming request.
|
|
264
|
-
* 2. **Reconcile before new work** (exit gate): every pass classifies and repairs every
|
|
265
|
-
* nonterminal Submission before the drain claims anything; the pure classifier and the
|
|
266
|
-
* repair executors are idempotent, so at-least-once alarm delivery re-runs them safely.
|
|
267
|
-
* 3. **Drain**: one bounded `processConversationResolved` pass over this Object's lane — the
|
|
268
|
-
* D-P6-1 shape; no infinite `runResolvedWorker` loop ever pins the Object.
|
|
269
|
-
* 4. **Re-arm policy**: nonterminal work re-arms at `now + min(backoff-with-jitter,
|
|
270
|
-
* wakeScanInterval)` — the scan interval bounds every wait (lease expiry of a dead
|
|
271
|
-
* incarnation included, since claims retry each pass) and the backoff (reset on progress,
|
|
272
|
-
* grown otherwise) keeps stuck lanes from busy-spinning; all settled clears the slot.
|
|
273
|
-
*
|
|
274
|
-
* Every step is a durability-protocol step that already tolerates re-execution, which is
|
|
275
|
-
* what makes double-fired alarms harmless (the alarm.test.ts gate).
|
|
276
|
-
*/
|
|
277
|
-
var ConversationMaintenance = class ConversationMaintenance extends Context.Service()("@effect-agent/platform-cloudflare/ConversationMaintenance") {
|
|
278
|
-
static layer = Layer.effect(ConversationMaintenance)(Effect.gen(function* () {
|
|
279
|
-
const runtime = yield* DurableAgentRuntime;
|
|
280
|
-
const resolver = yield* AgentBindingResolver;
|
|
281
|
-
const ledger = yield* SubmissionLedger;
|
|
282
|
-
const alarm = yield* DurableAlarmService;
|
|
283
|
-
const config = yield* CloudflareDurableRuntimeConfig;
|
|
284
|
-
const identity = yield* ConversationObjectIdentity;
|
|
285
|
-
/**
|
|
286
|
-
* Consecutive no-progress passes — an in-memory CACHE, not state: a fresh incarnation
|
|
287
|
-
* restarts at zero and merely re-arms sooner than a long-lived one would have.
|
|
288
|
-
*/
|
|
289
|
-
const stalls = yield* Ref.make(0);
|
|
290
|
-
const preArm = Clock.currentTimeMillis.pipe(Effect.flatMap((now) => alarm.ensureScheduledBy(now + config.wakeScanInterval)));
|
|
291
|
-
const countNonterminal = Stream.runCollect(ledger.scanNonterminal).pipe(Effect.map((snapshots) => snapshots.length));
|
|
292
|
-
const rearmDelay = Effect.fn("ConversationMaintenance.rearmDelay")(function* (progressed) {
|
|
293
|
-
const priorStalls = yield* Ref.getAndUpdate(stalls, (count) => progressed ? 0 : count + 1);
|
|
294
|
-
if (progressed) return config.alarmBackoffBase;
|
|
295
|
-
const exponent = Math.min(priorStalls, 30);
|
|
296
|
-
const backoff = Math.min(config.alarmBackoffCap, config.alarmBackoffBase * 2 ** exponent);
|
|
297
|
-
const jitter = yield* Random.next;
|
|
298
|
-
const jittered = Math.ceil(backoff / 2 + backoff / 2 * jitter);
|
|
299
|
-
return Math.min(jittered, config.wakeScanInterval);
|
|
300
|
-
});
|
|
301
|
-
const pass = Effect.fn("ConversationMaintenance.pass")(function* () {
|
|
302
|
-
yield* preArm;
|
|
303
|
-
const recovered = yield* runtime.runRecovery;
|
|
304
|
-
const settlements = yield* runtime.processConversationResolved(identity.conversationId).pipe(Effect.provideService(AgentBindingResolver, resolver));
|
|
305
|
-
const nonterminal = yield* countNonterminal;
|
|
306
|
-
if (nonterminal === 0) {
|
|
307
|
-
yield* alarm.cancel;
|
|
308
|
-
yield* Ref.set(stalls, 0);
|
|
309
|
-
const appeared = yield* countNonterminal;
|
|
310
|
-
if (appeared > 0) {
|
|
311
|
-
yield* preArm;
|
|
312
|
-
return MaintenancePassReport.make({
|
|
313
|
-
recovered: recovered.length,
|
|
314
|
-
settled: settlements.length,
|
|
315
|
-
nonterminal: appeared,
|
|
316
|
-
alarm: "rearmed"
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
return MaintenancePassReport.make({
|
|
320
|
-
recovered: recovered.length,
|
|
321
|
-
settled: settlements.length,
|
|
322
|
-
nonterminal,
|
|
323
|
-
alarm: "cleared"
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
const progressed = settlements.length > 0 || recovered.some((report) => report.disposition === "repaired");
|
|
327
|
-
const delay = yield* rearmDelay(progressed);
|
|
328
|
-
const now = yield* Clock.currentTimeMillis;
|
|
329
|
-
yield* alarm.ensureScheduledBy(now + delay);
|
|
330
|
-
return MaintenancePassReport.make({
|
|
331
|
-
recovered: recovered.length,
|
|
332
|
-
settled: settlements.length,
|
|
333
|
-
nonterminal,
|
|
334
|
-
alarm: "rearmed"
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
const ensureAlarm = Effect.fn("ConversationMaintenance.ensureAlarm")(function* () {
|
|
338
|
-
if ((yield* countNonterminal) === 0) return;
|
|
339
|
-
yield* preArm;
|
|
340
|
-
});
|
|
341
|
-
return ConversationMaintenance.of({
|
|
342
|
-
pass: alarm.withWakesDeferred(pass()),
|
|
343
|
-
ensureAlarm: ensureAlarm(),
|
|
344
|
-
preArm
|
|
345
|
-
});
|
|
346
|
-
}));
|
|
347
|
-
};
|
|
348
|
-
//#endregion
|
|
349
|
-
//#region src/wake-scheduler.ts
|
|
350
|
-
/**
|
|
351
|
-
* Bounded in-memory wake buffer for same-incarnation `awaitSettlement` subscribers. Wake
|
|
352
|
-
* hints are droppable by contract (consumers pair every subscription with ledger polls), so
|
|
353
|
-
* a full buffer slides out the oldest hint and an eviction simply loses the buffer — the
|
|
354
|
-
* poll interval and the persisted alarm keep liveness (persistence §14).
|
|
355
|
-
*/
|
|
356
|
-
const WAKE_BUFFER_CAPACITY = 1024;
|
|
357
|
-
/** A remote wake stub call failed; always swallowed and logged (hints are droppable). */
|
|
358
|
-
var RemoteWakeDropped = class extends Schema.TaggedError()("RemoteWakeDropped", {
|
|
359
|
-
conversationId: Schema.String,
|
|
360
|
-
message: Schema.String,
|
|
361
|
-
cause: Schema.optionalKey(Schema.Defect())
|
|
362
|
-
}) {};
|
|
363
|
-
/**
|
|
364
|
-
* The DC `WakeScheduler` (plan §1.4):
|
|
365
|
-
*
|
|
366
|
-
* - `notify(local)` → `setAlarm(now)` — durable, cheap, coalescing (the alarm slot keeps the
|
|
367
|
-
* earliest deadline) — plus a same-incarnation PubSub hint for `awaitSettlement` waiters.
|
|
368
|
-
* - `notify(remote)` → fire-and-forget `wake()` on the owning Object's stub with every error
|
|
369
|
-
* swallowed and logged: hints are droppable, and the target's own alarm/scan pairing (the
|
|
370
|
-
* maintenance pass re-polls unsettled children) guarantees liveness without this call.
|
|
371
|
-
* - `wakes` → the bounded sliding PubSub only. No important state lives in memory: the
|
|
372
|
-
* subscription accelerates waiters within one incarnation and the settlement poll interval
|
|
373
|
-
* is the correctness path.
|
|
374
|
-
*/
|
|
375
|
-
const cloudflareWakeSchedulerLayer = Layer.effect(WakeScheduler)(Effect.gen(function* () {
|
|
376
|
-
const alarm = yield* DurableAlarmService;
|
|
377
|
-
const identity = yield* ConversationObjectIdentity;
|
|
378
|
-
const { namespace } = yield* ConversationObjectNamespace;
|
|
379
|
-
const hints = yield* PubSub.sliding(WAKE_BUFFER_CAPACITY);
|
|
380
|
-
yield* Effect.addFinalizer(() => PubSub.shutdown(hints));
|
|
381
|
-
const notifyLocal = (conversationId) => PubSub.publish(hints, conversationId).pipe(Effect.andThen(alarm.scheduleNow), Effect.catch((error) => Effect.logWarning("CloudflareWakeScheduler: local alarm wake failed", error)), Effect.asVoid);
|
|
382
|
-
const notifyRemote = (conversationId) => Effect.tryPromise({
|
|
383
|
-
try: () => namespace.get(namespace.idFromName(conversationId)).wake(),
|
|
384
|
-
catch: (cause) => RemoteWakeDropped.make({
|
|
385
|
-
conversationId,
|
|
386
|
-
message: cause instanceof Error ? cause.message : String(cause),
|
|
387
|
-
cause
|
|
388
|
-
})
|
|
389
|
-
}).pipe(Effect.catch((error) => Effect.logWarning(`CloudflareWakeScheduler: remote wake of ${conversationId} dropped`, error)), Effect.asVoid);
|
|
390
|
-
return WakeScheduler.of({
|
|
391
|
-
notify: (conversationId) => conversationId === identity.conversationId ? notifyLocal(conversationId) : notifyRemote(conversationId),
|
|
392
|
-
wakes: Stream.fromPubSub(hints)
|
|
393
|
-
});
|
|
394
|
-
}));
|
|
395
|
-
//#endregion
|
|
396
|
-
//#region src/transport.ts
|
|
397
|
-
/**
|
|
398
|
-
* `ConversationPortTransport` over native Durable Object JS RPC (decision D-P6-3): one
|
|
399
|
-
* `portCall(envelope)` on the stub of the Object that owns the addressed Conversation
|
|
400
|
-
* (`namespace.idFromName(conversationId)` — the identity rule, plan §1.2). The envelopes are
|
|
401
|
-
* already Schema-encoded JSON, so the RPC boundary carries only structured-cloneable values;
|
|
402
|
-
* the protocol module stays transport-agnostic and fetch-with-JSON remains the documented
|
|
403
|
-
* fallback carrier.
|
|
404
|
-
*
|
|
405
|
-
* Every delivery problem — stub construction, RPC rejection, overload, deploy-in-progress —
|
|
406
|
-
* surfaces as `PortTransportError` (preserving the platform stub's own `retryable` signal
|
|
407
|
-
* when present) and NEVER as a fabricated answer: on `resolveAdmission` the routing layer
|
|
408
|
-
* turns exactly this error into `AdmissionIndeterminate` (SUB-031).
|
|
409
|
-
*/
|
|
410
|
-
const conversationPortTransportLayer = Layer.effect(ConversationPortTransport)(Effect.gen(function* () {
|
|
411
|
-
const { namespace } = yield* ConversationObjectNamespace;
|
|
412
|
-
return ConversationPortTransport.of({ call: (conversationId, request) => Effect.tryPromise({
|
|
413
|
-
try: () => namespace.get(namespace.idFromName(conversationId)).portCall(request),
|
|
414
|
-
catch: (cause) => portTransportFailure(conversationId, cause)
|
|
415
|
-
}).pipe(Effect.withSpan("CloudflarePortTransport.call", { attributes: { conversationId } })) });
|
|
416
|
-
}));
|
|
417
|
-
//#endregion
|
|
418
|
-
//#region src/telemetry.ts
|
|
419
|
-
/** A host exporter failed during one coalesced background export attempt. */
|
|
420
|
-
var CloudflareTelemetryExportError = class extends Schema.TaggedError()("CloudflareTelemetryExportError", {
|
|
421
|
-
/** The original exporter failure, retained for host-side diagnostics. */
|
|
422
|
-
cause: Schema.Defect() }) {};
|
|
423
|
-
/**
|
|
424
|
-
* Host-owned exporter lifecycle for one Cloudflare Conversation Object incarnation.
|
|
425
|
-
*
|
|
426
|
-
* The platform package creates measurements but deliberately does not select an exporter or
|
|
427
|
-
* vendor. Native entrypoint instrumentation consumes this service at the Worker composition edge;
|
|
428
|
-
* applications pass its provider as the second `makeConversationObjectClass` argument together
|
|
429
|
-
* with their Effect Logger, Tracer, and Metric layers. That provider may read `DurableObjectContext` or
|
|
430
|
-
* `ConversationObjectNamespace`, and its typed acquisition error remains visible in construction.
|
|
431
|
-
* `flush` must attempt to export every configured signal. After the native span closes, the
|
|
432
|
-
* Conversation Object reserves it into a shared post-settlement batch before `ctx.waitUntil` under
|
|
433
|
-
* a cooperative timeout. Only each pending/trailing/queued batch's first owner registers its
|
|
434
|
-
* shared Promise. Cycles remain capped at one first exporter attempt plus at most one trailing
|
|
435
|
-
* attempt, with one queued cycle while trailing runs; exporter completion never holds endpoint
|
|
436
|
-
* delivery open and exporter failure never replaces endpoint behavior.
|
|
437
|
-
*/
|
|
438
|
-
var CloudflareRuntimeTelemetry = class CloudflareRuntimeTelemetry extends Context.Service()("@effect-agent/platform-cloudflare/CloudflareRuntimeTelemetry") {
|
|
439
|
-
/** Content-free default selected by `makeConversationObjectClass` when no host Layer is passed. */
|
|
440
|
-
static layerNoop = Layer.succeed(CloudflareRuntimeTelemetry)({ flush: Effect.void });
|
|
441
|
-
};
|
|
442
|
-
//#endregion
|
|
443
|
-
//#region src/layers.ts
|
|
444
|
-
/**
|
|
445
|
-
* Owner-side endpoint body for the Conversation Object's `portCall` (plan §1.3): decode,
|
|
446
|
-
* execute against THIS Object's LOCAL port facets — never the routed decorators, so a
|
|
447
|
-
* request cannot bounce between Objects — and answer the encoded response envelope. Total by
|
|
448
|
-
* construction (protocol anomalies answer `PortFailed(PortProtocolError)`).
|
|
449
|
-
*/
|
|
450
|
-
var ConversationObjectPorts = class extends Context.Service()("@effect-agent/platform-cloudflare/ConversationObjectPorts") {};
|
|
451
|
-
const decodeConfigValue = Schema.decodeUnknownEffect(CloudflareDurableRuntimeConfigValue);
|
|
452
|
-
const decodeConversationId = Schema.decodeUnknownEffect(ConversationId);
|
|
453
|
-
const decodeProducerId = Schema.decodeUnknownEffect(ProducerId);
|
|
454
|
-
const configFromOptions = (options) => decodeConfigValue({
|
|
455
|
-
deploymentId: options.deploymentId,
|
|
456
|
-
producerPrefix: options.producerPrefix,
|
|
457
|
-
ownershipLeaseDuration: options.ownershipLeaseDuration ?? CLOUDFLARE_RUNTIME_DEFAULTS.ownershipLeaseDuration,
|
|
458
|
-
alarmBackoffBase: options.alarmBackoffBase ?? CLOUDFLARE_RUNTIME_DEFAULTS.alarmBackoffBase,
|
|
459
|
-
alarmBackoffCap: options.alarmBackoffCap ?? CLOUDFLARE_RUNTIME_DEFAULTS.alarmBackoffCap,
|
|
460
|
-
wakeScanInterval: options.wakeScanInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.wakeScanInterval,
|
|
461
|
-
settlementPollInterval: options.settlementPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.settlementPollInterval,
|
|
462
|
-
leaseRenewalInterval: options.leaseRenewalInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.leaseRenewalInterval,
|
|
463
|
-
abortPollInterval: options.abortPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.abortPollInterval,
|
|
464
|
-
observationPollInterval: options.observationPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.observationPollInterval,
|
|
465
|
-
telemetryFlushTimeout: options.telemetryFlushTimeout ?? CLOUDFLARE_RUNTIME_DEFAULTS.telemetryFlushTimeout,
|
|
466
|
-
maxStoredValueBytes: options.maxStoredValueBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxStoredValueBytes,
|
|
467
|
-
verifyOnOpen: options.verifyOnOpen ?? CLOUDFLARE_RUNTIME_DEFAULTS.verifyOnOpen,
|
|
468
|
-
limits: {
|
|
469
|
-
maxQueueDepthPerLane: options.maxQueueDepthPerLane ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxQueueDepthPerLane,
|
|
470
|
-
maxInputBytes: Math.min(options.maxInputBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxInputBytes, options.maxStoredValueBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxStoredValueBytes),
|
|
471
|
-
maxDatabaseBytes: options.maxDatabaseBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxDatabaseBytes
|
|
472
|
-
}
|
|
473
|
-
}).pipe(Effect.mapError((error) => CloudflarePlatformConfigError.make({
|
|
474
|
-
message: `Invalid Cloudflare durable runtime configuration: ${error.message}`,
|
|
475
|
-
cause: error
|
|
476
|
-
})));
|
|
477
|
-
/**
|
|
478
|
-
* The Conversation this Object owns, from the Object identity rule (plan §1.2): Conversation
|
|
479
|
-
* Objects are addressed exclusively by `idFromName(conversationId)`, so `ctx.id.name` IS the
|
|
480
|
-
* Conversation ID. An unnamed Object (from `newUniqueId`) is a deployment error, not a lane.
|
|
481
|
-
*/
|
|
482
|
-
const conversationIdFromState = (ctx) => ctx.id.name === void 0 ? Effect.fail(CloudflarePlatformConfigError.make({ message: "This Durable Object was not created via idFromName(conversationId); Conversation Objects must be addressed by their Conversation identity (plan §1.2)." })) : decodeConversationId(ctx.id.name).pipe(Effect.mapError((error) => CloudflarePlatformConfigError.make({
|
|
483
|
-
message: `The Durable Object name is not a valid ConversationId: ${error.message}`,
|
|
484
|
-
cause: error
|
|
485
|
-
})));
|
|
486
|
-
const resolveBindings = (source, context) => source === void 0 ? Effect.succeed([]) : Effect.isEffect(source) ? source : typeof source === "function" ? Effect.suspend(() => {
|
|
487
|
-
const bindings = source(context);
|
|
488
|
-
return Effect.isEffect(bindings) ? bindings : Effect.succeed(bindings);
|
|
489
|
-
}) : Effect.succeed(source);
|
|
490
|
-
/**
|
|
491
|
-
* The DC Layer assembly (deployment §12: a Layer-assembly library, not an app entrypoint;
|
|
492
|
-
* plan §1.4). `layer(options)` decodes the configuration, derives this Object's Conversation
|
|
493
|
-
* and producer identities, opens the Object's private SQLite database through
|
|
494
|
-
* `@effect/sql-sqlite-do` for BOTH the Conversation Log and the Submission Ledger (so claims
|
|
495
|
-
* fence the same producer epochs — ADR-0011 D7 transposed), wraps the local port facets with
|
|
496
|
-
* the WP2 cross-Object routing decorators over the Durable Object RPC transport, wires the
|
|
497
|
-
* alarm-backed wake scheduler and the maintenance pass, defaults reconciliation to the
|
|
498
|
-
* fail-closed `ToolReconciler.uncertain`, and provides a ready `DurableAgentRuntime` on top.
|
|
499
|
-
*
|
|
500
|
-
* Storage compatibility is verified during construction: an incompatible database fails the
|
|
501
|
-
* Layer typed (`DoStorageCompatibilityError`) before anything is mutated (DEPLOY-008).
|
|
502
|
-
*
|
|
503
|
-
* Requires the two binding services (`DurableObjectContext`, `ConversationObjectNamespace`).
|
|
504
|
-
* Host observability belongs to the Worker composition edge because native entrypoint lifecycle
|
|
505
|
-
* instrumentation, rather than this durable runtime assembly, consumes it (DEPLOY-010).
|
|
506
|
-
*/
|
|
507
|
-
var CloudflareDurableRuntime = class {
|
|
508
|
-
static layer(options) {
|
|
509
|
-
return Layer.unwrap(Effect.gen(function* () {
|
|
510
|
-
const { ctx, env } = yield* DurableObjectContext;
|
|
511
|
-
const config = yield* configFromOptions(options);
|
|
512
|
-
const conversationId = yield* conversationIdFromState(ctx);
|
|
513
|
-
const producerId = yield* decodeProducerId(`${config.producerPrefix}:${conversationId}`).pipe(Effect.mapError((error) => CloudflarePlatformConfigError.make({
|
|
514
|
-
message: `The minted producer identity is invalid: ${error.message}`,
|
|
515
|
-
cause: error
|
|
516
|
-
})));
|
|
517
|
-
const identityLayer = Layer.succeed(ConversationObjectIdentity)({
|
|
518
|
-
conversationId,
|
|
519
|
-
producerId
|
|
520
|
-
});
|
|
521
|
-
const cloudflareConfigLayer = Layer.succeed(CloudflareDurableRuntimeConfig)(config);
|
|
522
|
-
const storageOptions = {
|
|
523
|
-
storage: ctx.storage,
|
|
524
|
-
observationPollInterval: config.observationPollInterval,
|
|
525
|
-
ownershipLeaseDuration: config.ownershipLeaseDuration,
|
|
526
|
-
maxStoredValueBytes: config.maxStoredValueBytes,
|
|
527
|
-
verifyOnOpen: config.verifyOnOpen,
|
|
528
|
-
failpoint: options.storageFailpoint?.(ctx)
|
|
529
|
-
};
|
|
530
|
-
const infrastructure = Layer.mergeAll(storageConfigLayer(storageOptions), storageFailpointLayer(storageOptions), SqliteClient.layer({ storage: ctx.storage }), BrowserCrypto.layer);
|
|
531
|
-
/**
|
|
532
|
-
* ONE local-facet instance (a shared Layer value) serves both the routed decorators
|
|
533
|
-
* and the owner-side `portCall` executor — the executor must never see the routed
|
|
534
|
-
* ports (plan §1.3: re-routing could bounce a request between Objects).
|
|
535
|
-
*/
|
|
536
|
-
const localPorts = Layer.mergeAll(conversationStoreLayer, submissionLedgerLayer).pipe(Layer.provide(infrastructure));
|
|
537
|
-
const portsEndpointLayer = Layer.effect(ConversationObjectPorts)(Effect.gen(function* () {
|
|
538
|
-
const local = yield* Effect.context();
|
|
539
|
-
return ConversationObjectPorts.of({ handle: (encoded) => handleEncodedPortRequest(encoded).pipe(Effect.provide(local)) });
|
|
540
|
-
})).pipe(Layer.provide(localPorts));
|
|
541
|
-
const routedPorts = Layer.mergeAll(routedSubmissionLedgerLayer({ localConversationId: conversationId }), routedConversationStoreLayer({ localConversationId: conversationId })).pipe(Layer.provide(localPorts), Layer.provide(conversationPortTransportLayer));
|
|
542
|
-
const runtimeConfigLayer = Layer.succeed(DurableRuntimeConfig)(DurableRuntimeConfig.make({
|
|
543
|
-
deploymentId: config.deploymentId,
|
|
544
|
-
producerId,
|
|
545
|
-
settlementPollInterval: Duration.millis(config.settlementPollInterval),
|
|
546
|
-
leaseRenewalInterval: Duration.millis(config.leaseRenewalInterval),
|
|
547
|
-
abortPollInterval: Duration.millis(config.abortPollInterval)
|
|
548
|
-
}));
|
|
549
|
-
const runtimeFailpointLayer = options.runtimeFailpoint === void 0 ? DurableRuntimeFailpoint.layer : Layer.succeed(DurableRuntimeFailpoint)({ hit: options.runtimeFailpoint(ctx) });
|
|
550
|
-
const reconcilerLayer = options.toolReconciler ?? ToolReconciler.uncertain;
|
|
551
|
-
const bindingResolverLayer = Layer.effect(AgentBindingResolver)(Effect.map(resolveBindings(options.bindings, {
|
|
552
|
-
ctx,
|
|
553
|
-
env,
|
|
554
|
-
conversationId,
|
|
555
|
-
producerId
|
|
556
|
-
}), (bindings) => AgentBindingResolver.fromBindings(bindings)));
|
|
557
|
-
const base = Layer.mergeAll(identityLayer, cloudflareConfigLayer, DurableAlarmService.layer);
|
|
558
|
-
const runtimeStack = DurableAgentRuntime.layer.pipe(Layer.provideMerge(routedPorts), Layer.provideMerge(cloudflareWakeSchedulerLayer), Layer.provideMerge(runtimeConfigLayer), Layer.provideMerge(bindingResolverLayer), Layer.provide(Layer.mergeAll(runtimeFailpointLayer, reconcilerLayer, BrowserCrypto.layer)), Layer.provideMerge(base));
|
|
559
|
-
return Layer.mergeAll(runtimeStack, ConversationMaintenance.layer.pipe(Layer.provide(runtimeStack)), portsEndpointLayer);
|
|
560
|
-
}));
|
|
561
|
-
}
|
|
562
|
-
};
|
|
563
|
-
//#endregion
|
|
564
|
-
//#region src/client.ts
|
|
565
|
-
/**
|
|
566
|
-
* The Worker↔Conversation-Object host protocol (plan §1.4): Schema envelopes for the host
|
|
567
|
-
* entry points (`submitEncoded`, `awaitSettlementEncoded`, `observePage`, `abortEncoded`,
|
|
568
|
-
* `resolveApprovalEncoded`, `resolveUnknownEncoded`) plus the Worker-side client that speaks
|
|
569
|
-
* it. Mirrors the WP2 port protocol: requests and responses are closed Schema unions, typed
|
|
570
|
-
* failures travel as their own tagged classes and RE-DECODE to identical tags on the caller
|
|
571
|
-
* (error-tag fidelity), and protocol anomalies are answered typed instead of thrown.
|
|
572
|
-
*/
|
|
573
|
-
/** Ceiling for host protocol diagnostic strings. */
|
|
574
|
-
const MAX_HOST_DIAGNOSTIC_LENGTH = 4096;
|
|
575
|
-
const BoundedDiagnostic = Schema.String.check(Schema.isMaxLength(MAX_HOST_DIAGNOSTIC_LENGTH));
|
|
576
|
-
/** Truncate a diagnostic string to the host protocol's bounded length. */
|
|
577
|
-
const boundHostDiagnostic = (value) => value.length > MAX_HOST_DIAGNOSTIC_LENGTH ? `${value.slice(0, MAX_HOST_DIAGNOSTIC_LENGTH - 3)}...` : value;
|
|
578
|
-
/**
|
|
579
|
-
* The envelope itself could not be honored: the Object could not decode the request, or a
|
|
580
|
-
* response could not be encoded/decoded. Never carries operation semantics.
|
|
581
|
-
*/
|
|
582
|
-
var HostProtocolError = class extends Schema.TaggedError()("HostProtocolError", { message: BoundedDiagnostic }) {};
|
|
583
|
-
/** The Worker-side stub call itself failed (RPC rejection, overload, eviction mid-call). */
|
|
584
|
-
var ConversationClientError = class extends Schema.TaggedError()("ConversationClientError", {
|
|
585
|
-
conversationId: Schema.String,
|
|
586
|
-
message: Schema.String,
|
|
587
|
-
cause: Schema.optionalKey(Schema.Defect())
|
|
588
|
-
}) {};
|
|
589
|
-
/**
|
|
590
|
-
* One durable submission, input ALREADY encoded by the caller through the Agent Binding's
|
|
591
|
-
* input schema (the Worker bundles the same Agent definitions as the Object, so schema
|
|
592
|
-
* validation happens client-side; the resolved Binding re-validates at claim time). The
|
|
593
|
-
* Conversation identity is deliberately absent — the addressed Object IS the lane.
|
|
594
|
-
*/
|
|
595
|
-
var SubmitRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/SubmitRequest")({
|
|
596
|
-
agentId: AgentId,
|
|
597
|
-
principal: Principal,
|
|
598
|
-
idempotencyKey: IdempotencyKey,
|
|
599
|
-
definitions: DefinitionDigests,
|
|
600
|
-
inputPayload: PersistedJson
|
|
601
|
-
}) {};
|
|
602
|
-
/** One bounded page of canonical records after an optional sequence. */
|
|
603
|
-
var ObservePageRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/ObservePageRequest")({
|
|
604
|
-
afterSequence: Schema.optionalKey(CanonicalSequence),
|
|
605
|
-
limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(1024))
|
|
606
|
-
}) {};
|
|
607
|
-
/**
|
|
608
|
-
* Every typed failure a host entry point can produce, plus the protocol's own errors. Same
|
|
609
|
-
* closed-union discipline as the WP2 `PortFailure`: members re-decode to the SAME tagged
|
|
610
|
-
* classes on the Worker side; `cause` chains travel as Schema defects without instance
|
|
611
|
-
* fidelity (plan §2.8).
|
|
612
|
-
*/
|
|
613
|
-
const HostFailure = Schema.Union([
|
|
614
|
-
AgentInputError,
|
|
615
|
-
DigestError,
|
|
616
|
-
AdmissionConflict,
|
|
617
|
-
SettlementConflict,
|
|
618
|
-
ApprovalConflict,
|
|
619
|
-
UnknownResolutionConflict,
|
|
620
|
-
JoinedToHost,
|
|
621
|
-
LedgerError,
|
|
622
|
-
ConversationStoreError,
|
|
623
|
-
ConversationNotMaterialized,
|
|
624
|
-
AppendConflict,
|
|
625
|
-
FenceRejected,
|
|
626
|
-
DurableRuntimeFailpointError,
|
|
627
|
-
AdmissionLimitExceeded,
|
|
628
|
-
DurableAlarmError,
|
|
629
|
-
HostProtocolError
|
|
630
|
-
]);
|
|
631
|
-
var SubmitSucceeded = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/SubmitSucceeded")("SubmitSucceeded", { receipt: Receipt }) {};
|
|
632
|
-
var SettlementReached = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/SettlementReached")("SettlementReached", { settlement: Settlement }) {};
|
|
633
|
-
var ObservedPage = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ObservedPage")("ObservedPage", { records: Schema.Array(CanonicalRecordEnvelope).check(Schema.isMaxLength(1024)) }) {};
|
|
634
|
-
var AbortRecorded = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/AbortRecorded")("AbortRecorded", { intent: AbortIntent }) {};
|
|
635
|
-
var ApprovalRecorded = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ApprovalRecorded")("ApprovalRecorded", { intent: ApprovalDecisionIntent }) {};
|
|
636
|
-
var UnknownResolutionRecorded = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/UnknownResolutionRecorded")("UnknownResolutionRecorded", { intent: UnknownResolutionIntent }) {};
|
|
637
|
-
/** The entry point failed TYPED on the Object; the failure re-decodes verbatim. */
|
|
638
|
-
var HostFailed = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/HostFailed")("HostFailed", { failure: HostFailure }) {};
|
|
639
|
-
/** The uniform answer of one host entry point. Callers narrow by the tag their call implies. */
|
|
640
|
-
const HostResponse = Schema.Union([
|
|
641
|
-
SubmitSucceeded,
|
|
642
|
-
SettlementReached,
|
|
643
|
-
ObservedPage,
|
|
644
|
-
AbortRecorded,
|
|
645
|
-
ApprovalRecorded,
|
|
646
|
-
UnknownResolutionRecorded,
|
|
647
|
-
HostFailed
|
|
648
|
-
]);
|
|
649
|
-
const decodeSubmitRequest = Schema.decodeUnknownEffect(SubmitRequest);
|
|
650
|
-
const encodeSubmitRequest = Schema.encodeEffect(SubmitRequest);
|
|
651
|
-
const decodeReceipt = Schema.decodeUnknownEffect(Receipt);
|
|
652
|
-
const encodeReceipt = Schema.encodeEffect(Receipt);
|
|
653
|
-
const decodeObservePageRequest = Schema.decodeUnknownEffect(ObservePageRequest);
|
|
654
|
-
const encodeObservePageRequest = Schema.encodeEffect(ObservePageRequest);
|
|
655
|
-
const decodeAbortCommand = Schema.decodeUnknownEffect(AbortCommand);
|
|
656
|
-
const encodeAbortCommand = Schema.encodeEffect(AbortCommand);
|
|
657
|
-
const decodeApprovalDecisionCommand = Schema.decodeUnknownEffect(ApprovalDecisionCommand);
|
|
658
|
-
const encodeApprovalDecisionCommand = Schema.encodeEffect(ApprovalDecisionCommand);
|
|
659
|
-
const decodeUnknownResolutionCommand = Schema.decodeUnknownEffect(UnknownResolutionCommand);
|
|
660
|
-
const encodeUnknownResolutionCommand = Schema.encodeEffect(UnknownResolutionCommand);
|
|
661
|
-
const encodeHostResponse = Schema.encodeEffect(HostResponse);
|
|
662
|
-
const decodeHostResponse = Schema.decodeUnknownEffect(HostResponse);
|
|
663
|
-
const SUBMIT_FAILURE_TAGS = /* @__PURE__ */ new Set([
|
|
664
|
-
"AgentInputError",
|
|
665
|
-
"DigestError",
|
|
666
|
-
"AdmissionConflict",
|
|
667
|
-
"LedgerError",
|
|
668
|
-
"ConversationStoreError",
|
|
669
|
-
"ConversationNotMaterialized",
|
|
670
|
-
"AppendConflict",
|
|
671
|
-
"FenceRejected",
|
|
672
|
-
"DurableRuntimeFailpointError",
|
|
673
|
-
"AdmissionLimitExceeded",
|
|
674
|
-
"DurableAlarmError",
|
|
675
|
-
"HostProtocolError"
|
|
676
|
-
]);
|
|
677
|
-
const AWAIT_FAILURE_TAGS = /* @__PURE__ */ new Set([
|
|
678
|
-
"LedgerError",
|
|
679
|
-
"SettlementConflict",
|
|
680
|
-
"HostProtocolError"
|
|
681
|
-
]);
|
|
682
|
-
const OBSERVE_FAILURE_TAGS = /* @__PURE__ */ new Set([
|
|
683
|
-
"ConversationStoreError",
|
|
684
|
-
"ConversationNotMaterialized",
|
|
685
|
-
"HostProtocolError"
|
|
686
|
-
]);
|
|
687
|
-
const ABORT_FAILURE_TAGS = /* @__PURE__ */ new Set([
|
|
688
|
-
"LedgerError",
|
|
689
|
-
"SettlementConflict",
|
|
690
|
-
"JoinedToHost",
|
|
691
|
-
"DurableRuntimeFailpointError",
|
|
692
|
-
"DurableAlarmError",
|
|
693
|
-
"HostProtocolError"
|
|
694
|
-
]);
|
|
695
|
-
const APPROVAL_FAILURE_TAGS = /* @__PURE__ */ new Set([
|
|
696
|
-
"LedgerError",
|
|
697
|
-
"SettlementConflict",
|
|
698
|
-
"ApprovalConflict",
|
|
699
|
-
"DurableAlarmError",
|
|
700
|
-
"HostProtocolError"
|
|
701
|
-
]);
|
|
702
|
-
const UNKNOWN_FAILURE_TAGS = /* @__PURE__ */ new Set([
|
|
703
|
-
"LedgerError",
|
|
704
|
-
"SettlementConflict",
|
|
705
|
-
"UnknownResolutionConflict",
|
|
706
|
-
"JoinedToHost",
|
|
707
|
-
"DurableRuntimeFailpointError",
|
|
708
|
-
"DurableAlarmError",
|
|
709
|
-
"HostProtocolError"
|
|
710
|
-
]);
|
|
711
|
-
const outOfContract = (conversationId, operation, observed) => ConversationClientError.make({
|
|
712
|
-
conversationId,
|
|
713
|
-
message: boundHostDiagnostic(`The Conversation Object answered ${operation} with the out-of-contract ${observed}.`)
|
|
714
|
-
});
|
|
715
|
-
/**
|
|
716
|
-
* Narrow one decoded `HostFailed.failure` to the operation's declared failure family; an
|
|
717
|
-
* out-of-contract tag folds into `ConversationClientError` instead of being erased or
|
|
718
|
-
* re-thrown raw (the WP2 discipline). The predicate is the single documented narrowing over
|
|
719
|
-
* the closed `HostFailure` union.
|
|
720
|
-
*/
|
|
721
|
-
const narrowFailure = (tags) => (failure) => tags.has(failure._tag);
|
|
722
|
-
/** Worker-side client over the Conversation Object namespace (DEPLOY-010). */
|
|
723
|
-
var CloudflareConversationClient = class CloudflareConversationClient extends Context.Service()("@effect-agent/platform-cloudflare/CloudflareConversationClient") {
|
|
724
|
-
static layer = Layer.effect(CloudflareConversationClient)(Effect.gen(function* () {
|
|
725
|
-
const { namespace } = yield* ConversationObjectNamespace;
|
|
726
|
-
const call = (conversationId, operation, invoke) => Effect.tryPromise({
|
|
727
|
-
try: () => invoke(namespace.get(namespace.idFromName(conversationId))),
|
|
728
|
-
catch: (cause) => ConversationClientError.make({
|
|
729
|
-
conversationId,
|
|
730
|
-
message: boundHostDiagnostic(`${operation} did not reach the Conversation Object: ${cause instanceof Error ? cause.message : String(cause)}`),
|
|
731
|
-
cause
|
|
732
|
-
})
|
|
733
|
-
}).pipe(Effect.flatMap((raw) => decodeHostResponse(raw).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`The ${operation} answer could not be decoded: ${error.message}`) })))), Effect.withSpan("CloudflareConversationClient.call", { attributes: {
|
|
734
|
-
conversationId,
|
|
735
|
-
operation
|
|
736
|
-
} }));
|
|
737
|
-
const expect = (conversationId, operation, resultTag, tags) => {
|
|
738
|
-
const isExpected = narrowFailure(tags);
|
|
739
|
-
return (response) => {
|
|
740
|
-
if (response._tag === "HostFailed") {
|
|
741
|
-
const failure = response.failure;
|
|
742
|
-
return isExpected(failure) ? Effect.fail(failure) : Effect.fail(outOfContract(conversationId, operation, `failure ${failure._tag}`));
|
|
743
|
-
}
|
|
744
|
-
if (response._tag !== resultTag) return Effect.fail(outOfContract(conversationId, operation, `result ${response._tag}`));
|
|
745
|
-
return Effect.succeed(response);
|
|
746
|
-
};
|
|
747
|
-
};
|
|
748
|
-
const readPage = (conversationId, options) => Effect.gen(function* () {
|
|
749
|
-
const request = ObservePageRequest.make({
|
|
750
|
-
...options?.afterSequence === void 0 ? {} : { afterSequence: options.afterSequence },
|
|
751
|
-
limit: options?.limit ?? 256
|
|
752
|
-
});
|
|
753
|
-
const encoded = yield* encodeObservePageRequest(request).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`observePage request encode failed: ${error.message}`) })));
|
|
754
|
-
const response = yield* call(conversationId, "observePage", (stub) => stub.observePage(encoded));
|
|
755
|
-
return (yield* expect(conversationId, "observePage", "ObservedPage", OBSERVE_FAILURE_TAGS)(response)).records;
|
|
756
|
-
});
|
|
757
|
-
return CloudflareConversationClient.of({
|
|
758
|
-
submit: (agent, input, options) => Effect.gen(function* () {
|
|
759
|
-
const encodedInput = yield* Schema.encodeEffect(agent.definition.input)(input).pipe(Effect.mapError((cause) => AgentInputError.make({ message: `Unable to encode Agent input: ${cause.message}` })));
|
|
760
|
-
const inputPayload = yield* Schema.decodeUnknownEffect(PersistedJson)(encodedInput).pipe(Effect.mapError(() => AgentInputError.make({ message: "Agent input does not satisfy the canonical persistence bounds" })));
|
|
761
|
-
const request = SubmitRequest.make({
|
|
762
|
-
agentId: agent.definition.id,
|
|
763
|
-
principal: options.principal,
|
|
764
|
-
idempotencyKey: options.idempotencyKey,
|
|
765
|
-
definitions: options.definitions,
|
|
766
|
-
inputPayload
|
|
767
|
-
});
|
|
768
|
-
const encoded = yield* encodeSubmitRequest(request).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`submit request encode failed: ${error.message}`) })));
|
|
769
|
-
const response = yield* call(options.conversationId, "submit", (stub) => stub.submitEncoded(encoded));
|
|
770
|
-
return (yield* expect(options.conversationId, "submit", "SubmitSucceeded", SUBMIT_FAILURE_TAGS)(response)).receipt;
|
|
771
|
-
}),
|
|
772
|
-
awaitSettlement: (receipt) => Effect.gen(function* () {
|
|
773
|
-
const encoded = yield* encodeReceipt(receipt).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`receipt encode failed: ${error.message}`) })));
|
|
774
|
-
const response = yield* call(receipt.conversationId, "awaitSettlement", (stub) => stub.awaitSettlementEncoded(encoded));
|
|
775
|
-
return (yield* expect(receipt.conversationId, "awaitSettlement", "SettlementReached", AWAIT_FAILURE_TAGS)(response)).settlement;
|
|
776
|
-
}),
|
|
777
|
-
readPage,
|
|
778
|
-
readAll: (conversationId) => Effect.gen(function* () {
|
|
779
|
-
const all = [];
|
|
780
|
-
let after;
|
|
781
|
-
for (;;) {
|
|
782
|
-
const page = yield* readPage(conversationId, {
|
|
783
|
-
afterSequence: after,
|
|
784
|
-
limit: 1024
|
|
785
|
-
});
|
|
786
|
-
all.push(...page);
|
|
787
|
-
const last = page.at(-1);
|
|
788
|
-
if (page.length < 1024 || last === void 0) return all;
|
|
789
|
-
after = last.sequence;
|
|
790
|
-
}
|
|
791
|
-
}),
|
|
792
|
-
abort: (conversationId, command) => Effect.gen(function* () {
|
|
793
|
-
const encoded = yield* encodeAbortCommand(command).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`abort command encode failed: ${error.message}`) })));
|
|
794
|
-
const response = yield* call(conversationId, "abort", (stub) => stub.abortEncoded(encoded));
|
|
795
|
-
return (yield* expect(conversationId, "abort", "AbortRecorded", ABORT_FAILURE_TAGS)(response)).intent;
|
|
796
|
-
}),
|
|
797
|
-
resolveApproval: (conversationId, command) => Effect.gen(function* () {
|
|
798
|
-
const encoded = yield* encodeApprovalDecisionCommand(command).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`approval command encode failed: ${error.message}`) })));
|
|
799
|
-
const response = yield* call(conversationId, "resolveApproval", (stub) => stub.resolveApprovalEncoded(encoded));
|
|
800
|
-
return (yield* expect(conversationId, "resolveApproval", "ApprovalRecorded", APPROVAL_FAILURE_TAGS)(response)).intent;
|
|
801
|
-
}),
|
|
802
|
-
resolveUnknown: (conversationId, command) => Effect.gen(function* () {
|
|
803
|
-
const encoded = yield* encodeUnknownResolutionCommand(command).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`resolution command encode failed: ${error.message}`) })));
|
|
804
|
-
const response = yield* call(conversationId, "resolveUnknown", (stub) => stub.resolveUnknownEncoded(encoded));
|
|
805
|
-
return (yield* expect(conversationId, "resolveUnknown", "UnknownResolutionRecorded", UNKNOWN_FAILURE_TAGS)(response)).intent;
|
|
806
|
-
})
|
|
807
|
-
});
|
|
808
|
-
}));
|
|
809
|
-
};
|
|
810
|
-
//#endregion
|
|
811
|
-
//#region src/telemetry-internal.ts
|
|
812
|
-
/** Bounded typed control signal used only while a native entrypoint span is active. */
|
|
813
|
-
var CloudflareNativeSpanFailure = class extends Error {
|
|
814
|
-
name = "ConversationObjectDeliveryFailed";
|
|
815
|
-
constructor() {
|
|
816
|
-
super("Cloudflare Conversation Object delivery failed");
|
|
817
|
-
}
|
|
818
|
-
};
|
|
819
|
-
const stripCloudflareNativeSpanFailures = (cause) => {
|
|
820
|
-
if (!cause.reasons.some((reason) => Cause.isFailReason(reason) && reason.error instanceof CloudflareNativeSpanFailure)) return {
|
|
821
|
-
found: false,
|
|
822
|
-
residual: cause
|
|
823
|
-
};
|
|
824
|
-
const reasons = cause.reasons.filter((reason) => {
|
|
825
|
-
if (Cause.isFailReason(reason) && reason.error instanceof CloudflareNativeSpanFailure) return false;
|
|
826
|
-
return true;
|
|
827
|
-
});
|
|
828
|
-
return {
|
|
829
|
-
found: true,
|
|
830
|
-
residual: Cause.fromReasons(reasons)
|
|
831
|
-
};
|
|
832
|
-
};
|
|
833
|
-
/**
|
|
834
|
-
* @internal Replace an entrypoint Cause with a bounded typed marker inside a span, then remove
|
|
835
|
-
* only that marker and restore the exact invocation-local Cause plus any newly composed reasons.
|
|
836
|
-
*/
|
|
837
|
-
const withCloudflareNativeSpanFailure = (effect, trace) => Effect.suspend(() => {
|
|
838
|
-
let original;
|
|
839
|
-
return trace(effect.pipe(Effect.catchCause((cause) => {
|
|
840
|
-
original = cause;
|
|
841
|
-
return Effect.fail(new CloudflareNativeSpanFailure());
|
|
842
|
-
}))).pipe(Effect.catchCause((cause) => {
|
|
843
|
-
const { found, residual } = stripCloudflareNativeSpanFailures(cause);
|
|
844
|
-
if (!found) return Effect.failCause(residual);
|
|
845
|
-
const restored = original === void 0 ? residual : Cause.combine(original, residual);
|
|
846
|
-
return Effect.failCause(restored);
|
|
847
|
-
}));
|
|
848
|
-
});
|
|
849
|
-
const logCloudflareTelemetryCause = (cause) => {
|
|
850
|
-
const kinds = /* @__PURE__ */ new Set();
|
|
851
|
-
for (const reason of cause.reasons) if (Cause.isFailReason(reason)) kinds.add(Cause.isTimeoutError(reason.error) ? "timeout" : "exporter");
|
|
852
|
-
else if (Cause.isDieReason(reason)) kinds.add("defect");
|
|
853
|
-
else kinds.add("interrupted");
|
|
854
|
-
return Effect.forEach(kinds, (kind) => {
|
|
855
|
-
return (kind === "defect" ? Effect.logError("Cloudflare telemetry flush defect") : Effect.logWarning(kind === "exporter" ? "Cloudflare telemetry exporter failed" : kind === "timeout" ? "Cloudflare telemetry flush exceeded its time budget" : "Cloudflare telemetry flush interrupted")).pipe(Effect.annotateLogs({ "effect_agent.cloudflare.telemetry.failure_kind": kind }));
|
|
856
|
-
}, { discard: true });
|
|
857
|
-
};
|
|
858
|
-
/**
|
|
859
|
-
* @internal Cooperative background flush budget. Interruptible exporters stop at the configured
|
|
860
|
-
* timeout; uninterruptible exporters remain bounded by Cloudflare's waitUntil lifecycle instead
|
|
861
|
-
* of holding the native delivery open.
|
|
862
|
-
*/
|
|
863
|
-
const flushCloudflareRuntimeTelemetry = (timeoutMillis) => Effect.flatMap(CloudflareRuntimeTelemetry, ({ flush }) => flush).pipe(Effect.interruptible, Effect.timeout(Duration.millis(timeoutMillis)), Effect.onExit((exit) => Exit.isFailure(exit) ? logCloudflareTelemetryCause(exit.cause) : Effect.void), Effect.asVoid);
|
|
864
|
-
/** @internal Convert the shared exporter result into the always-fulfilled waitUntil bridge. */
|
|
865
|
-
const fulfillCloudflareTelemetryBackground = (result) => result.then(() => void 0, () => void 0);
|
|
866
|
-
const makeFlushBatch = () => {
|
|
867
|
-
let resolveResult = () => void 0;
|
|
868
|
-
let rejectResult = (_cause) => void 0;
|
|
869
|
-
const result = new Promise((resolve, reject) => {
|
|
870
|
-
resolveResult = resolve;
|
|
871
|
-
rejectResult = reject;
|
|
872
|
-
});
|
|
873
|
-
let resolveReady = () => void 0;
|
|
874
|
-
const ready = new Promise((resolve) => {
|
|
875
|
-
resolveReady = resolve;
|
|
876
|
-
});
|
|
877
|
-
return {
|
|
878
|
-
result,
|
|
879
|
-
background: fulfillCloudflareTelemetryBackground(result),
|
|
880
|
-
resolveResult,
|
|
881
|
-
rejectResult,
|
|
882
|
-
ready,
|
|
883
|
-
resolveReady,
|
|
884
|
-
phase: "pending",
|
|
885
|
-
reservations: 0,
|
|
886
|
-
unsettledDeliveries: 0,
|
|
887
|
-
dropDiagnosed: false
|
|
888
|
-
};
|
|
889
|
-
};
|
|
890
|
-
const makeFlushCycle = () => {
|
|
891
|
-
let resolveCycle = () => void 0;
|
|
892
|
-
let rejectCycle = (_cause) => void 0;
|
|
893
|
-
const promise = new Promise((resolve, reject) => {
|
|
894
|
-
resolveCycle = resolve;
|
|
895
|
-
rejectCycle = reject;
|
|
896
|
-
});
|
|
897
|
-
fulfillCloudflareTelemetryBackground(promise);
|
|
898
|
-
return {
|
|
899
|
-
promise,
|
|
900
|
-
resolve: resolveCycle,
|
|
901
|
-
reject: rejectCycle,
|
|
902
|
-
first: makeFlushBatch(),
|
|
903
|
-
phase: "pending",
|
|
904
|
-
trailing: void 0
|
|
905
|
-
};
|
|
906
|
-
};
|
|
907
|
-
/**
|
|
908
|
-
* @internal Coordinate exporter attempts for one Conversation Object incarnation. Each cycle is
|
|
909
|
-
* capped at a first attempt plus one trailing attempt. Each batch is reserved synchronously and
|
|
910
|
-
* waits for every delivery assigned to it before exporting. Only the first owner of the pending,
|
|
911
|
-
* trailing, or queued batch registers that batch's shared background Promise, so a stalled
|
|
912
|
-
* exporter cannot accumulate per-delivery waitUntil registrations. Each batch retains at most
|
|
913
|
-
* `MAX_CLOUDFLARE_TELEMETRY_BATCH_DELIVERIES` settlement Promises; excess deliveries are lossy-
|
|
914
|
-
* coalesced into the already-requested export without settlement ownership and diagnosed once for
|
|
915
|
-
* that batch. Failures remain rejected by the raw cycle after every requested attempt runs; each
|
|
916
|
-
* shared waitUntil bridge always fulfills.
|
|
917
|
-
*/
|
|
918
|
-
const makeCloudflareTelemetryFlushCoordinator = (flush, options = {}) => {
|
|
919
|
-
let active;
|
|
920
|
-
let queued;
|
|
921
|
-
const closeBatch = (batch) => {
|
|
922
|
-
if (batch.phase !== "pending") return;
|
|
923
|
-
batch.phase = "waiting";
|
|
924
|
-
if (batch.unsettledDeliveries === 0) batch.resolveReady();
|
|
925
|
-
};
|
|
926
|
-
const cancelBatch = (batch) => {
|
|
927
|
-
if (batch.phase === "settled" || batch.phase === "cancelled") return;
|
|
928
|
-
batch.phase = "cancelled";
|
|
929
|
-
batch.resolveReady();
|
|
930
|
-
batch.resolveResult();
|
|
931
|
-
};
|
|
932
|
-
const reserveDelivery = (batch, delivery) => {
|
|
933
|
-
if (batch.reservations >= 64) {
|
|
934
|
-
if (!batch.dropDiagnosed) {
|
|
935
|
-
batch.dropDiagnosed = true;
|
|
936
|
-
try {
|
|
937
|
-
options.onReservationDropped?.();
|
|
938
|
-
} catch {}
|
|
939
|
-
}
|
|
940
|
-
return {
|
|
941
|
-
owner: false,
|
|
942
|
-
dropped: true
|
|
943
|
-
};
|
|
944
|
-
}
|
|
945
|
-
const owner = batch.reservations === 0;
|
|
946
|
-
batch.reservations += 1;
|
|
947
|
-
batch.unsettledDeliveries += 1;
|
|
948
|
-
const settled = () => {
|
|
949
|
-
batch.unsettledDeliveries -= 1;
|
|
950
|
-
if (batch.phase === "waiting" && batch.unsettledDeliveries === 0) batch.resolveReady();
|
|
951
|
-
};
|
|
952
|
-
delivery.then(settled, settled);
|
|
953
|
-
return {
|
|
954
|
-
owner,
|
|
955
|
-
dropped: false
|
|
956
|
-
};
|
|
957
|
-
};
|
|
958
|
-
const runCycle = async (cycle) => {
|
|
959
|
-
const failures = [];
|
|
960
|
-
const runAttempt = async () => {
|
|
961
|
-
try {
|
|
962
|
-
await flush();
|
|
963
|
-
} catch (cause) {
|
|
964
|
-
failures.push(cause);
|
|
965
|
-
}
|
|
966
|
-
};
|
|
967
|
-
const runBatch = async (batch) => {
|
|
968
|
-
closeBatch(batch);
|
|
969
|
-
await batch.ready;
|
|
970
|
-
if (batch.phase === "cancelled") return;
|
|
971
|
-
batch.phase = "running";
|
|
972
|
-
const failureStart = failures.length;
|
|
973
|
-
await runAttempt();
|
|
974
|
-
batch.phase = "settled";
|
|
975
|
-
if (failures.length === failureStart) batch.resolveResult();
|
|
976
|
-
else batch.rejectResult(failures[failures.length - 1]);
|
|
977
|
-
};
|
|
978
|
-
try {
|
|
979
|
-
cycle.phase = "first";
|
|
980
|
-
await runBatch(cycle.first);
|
|
981
|
-
const trailing = cycle.trailing;
|
|
982
|
-
if (trailing !== void 0) {
|
|
983
|
-
cycle.phase = "trailing";
|
|
984
|
-
await runBatch(trailing);
|
|
985
|
-
}
|
|
986
|
-
cycle.phase = "settling";
|
|
987
|
-
options.onSettling?.();
|
|
988
|
-
} finally {
|
|
989
|
-
const next = queued;
|
|
990
|
-
queued = void 0;
|
|
991
|
-
active = next;
|
|
992
|
-
if (next !== void 0) startCycle(next);
|
|
993
|
-
}
|
|
994
|
-
if (failures.length === 0) return;
|
|
995
|
-
if (failures.length === 1) throw failures[0];
|
|
996
|
-
throw new AggregateError(failures, `${failures.length} Cloudflare telemetry flush attempts failed`);
|
|
997
|
-
};
|
|
998
|
-
const startCycle = (cycle) => {
|
|
999
|
-
Promise.resolve().then(() => runCycle(cycle)).then(cycle.resolve, cycle.reject);
|
|
1000
|
-
};
|
|
1001
|
-
const reserve = (delivery) => {
|
|
1002
|
-
let cycle;
|
|
1003
|
-
let batch;
|
|
1004
|
-
let shouldStart = false;
|
|
1005
|
-
if (active === void 0) {
|
|
1006
|
-
cycle = makeFlushCycle();
|
|
1007
|
-
active = cycle;
|
|
1008
|
-
batch = cycle.first;
|
|
1009
|
-
shouldStart = true;
|
|
1010
|
-
} else if (active.phase === "pending" && active.first.phase !== "cancelled") {
|
|
1011
|
-
cycle = active;
|
|
1012
|
-
batch = active.first;
|
|
1013
|
-
} else if (active.phase === "first") {
|
|
1014
|
-
cycle = active;
|
|
1015
|
-
batch = active.trailing ??= makeFlushBatch();
|
|
1016
|
-
} else {
|
|
1017
|
-
if (queued === void 0) queued = makeFlushCycle();
|
|
1018
|
-
cycle = queued;
|
|
1019
|
-
batch = queued.first;
|
|
1020
|
-
}
|
|
1021
|
-
const { dropped, owner } = reserveDelivery(batch, delivery);
|
|
1022
|
-
if (shouldStart) startCycle(cycle);
|
|
1023
|
-
return {
|
|
1024
|
-
background: batch.background,
|
|
1025
|
-
cycle: cycle.promise,
|
|
1026
|
-
owner,
|
|
1027
|
-
dropped,
|
|
1028
|
-
cancel: () => {
|
|
1029
|
-
if (!owner) return;
|
|
1030
|
-
cancelBatch(batch);
|
|
1031
|
-
if (cycle.trailing === batch && cycle.phase === "first") cycle.trailing = void 0;
|
|
1032
|
-
else if (queued === cycle && active !== cycle) {
|
|
1033
|
-
queued = void 0;
|
|
1034
|
-
cycle.resolve();
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
};
|
|
1038
|
-
};
|
|
1039
|
-
return { reserve };
|
|
1040
|
-
};
|
|
1041
|
-
/**
|
|
1042
|
-
* @internal Reserve telemetry before registration without allowing a synchronous platform failure
|
|
1043
|
-
* to replace the already-running native delivery Promise. Only a batch owner calls waitUntil; its
|
|
1044
|
-
* shared bridge is already always fulfilled, and registration failure cancels export for that
|
|
1045
|
-
* unowned batch.
|
|
1046
|
-
*/
|
|
1047
|
-
const registerCloudflareTelemetryAfterNativeSettlement = (waitUntil, delivery, reserve, diagnoseRegistrationFailure) => {
|
|
1048
|
-
const reservation = reserve(delivery);
|
|
1049
|
-
if (!reservation.owner) return delivery;
|
|
1050
|
-
try {
|
|
1051
|
-
waitUntil(reservation.background);
|
|
1052
|
-
} catch (cause) {
|
|
1053
|
-
reservation.cancel();
|
|
1054
|
-
try {
|
|
1055
|
-
diagnoseRegistrationFailure(cause);
|
|
1056
|
-
} catch {}
|
|
1057
|
-
}
|
|
1058
|
-
return delivery;
|
|
1059
|
-
};
|
|
1060
|
-
/** @internal Content-free diagnostic for a rejected Cloudflare waitUntil registration. */
|
|
1061
|
-
const logCloudflareWaitUntilRegistrationFailure = (_cause) => Effect.logError("Cloudflare telemetry waitUntil registration failed").pipe(Effect.annotateLogs({ "effect_agent.cloudflare.telemetry.failure_kind": "wait_until_registration" }));
|
|
1062
|
-
//#endregion
|
|
1063
|
-
//#region src/conversation-object.ts
|
|
1064
|
-
/** Port envelope tags whose owner-side execution durably mutates this Object's lane. */
|
|
1065
|
-
const MUTATING_PORT_TAGS = /* @__PURE__ */ new Set([
|
|
1066
|
-
"LedgerAdmit",
|
|
1067
|
-
"LedgerMarkReady",
|
|
1068
|
-
"LedgerRequestAbort",
|
|
1069
|
-
"LedgerRecordChildSettled",
|
|
1070
|
-
"StoreMaterialize",
|
|
1071
|
-
"StoreAppend"
|
|
1072
|
-
]);
|
|
1073
|
-
const isMutatingPortRequest = (encoded) => typeof encoded === "object" && encoded !== null && "_tag" in encoded && typeof encoded._tag === "string" && MUTATING_PORT_TAGS.has(encoded._tag);
|
|
1074
|
-
/** The literal encoded `PortFailed(PortProtocolError)` fallback (same shape as WP2's). */
|
|
1075
|
-
const encodedPortProtocolFailure = (message) => ({
|
|
1076
|
-
_tag: "PortFailed",
|
|
1077
|
-
failure: {
|
|
1078
|
-
_tag: "PortProtocolError",
|
|
1079
|
-
message: boundHostDiagnostic(message)
|
|
1080
|
-
}
|
|
1081
|
-
});
|
|
1082
|
-
const protocolFailure = (context) => (error) => HostProtocolError.make({ message: boundHostDiagnostic(`${context}: ${error.message}`) });
|
|
1083
|
-
/** Fold one endpoint's typed failures into the uniform `HostResponse` envelope. */
|
|
1084
|
-
const respond = (effect) => effect.pipe(Effect.map((result) => result), Effect.catch((failure) => Effect.succeed(HostFailed.make({ failure }))));
|
|
1085
|
-
/** Encode the response envelope; an unencodable response degrades to a protocol failure. */
|
|
1086
|
-
const encodeResponse = (response) => encodeHostResponse(response).pipe(Effect.catch((error) => Effect.succeed({
|
|
1087
|
-
_tag: "HostFailed",
|
|
1088
|
-
failure: {
|
|
1089
|
-
_tag: "HostProtocolError",
|
|
1090
|
-
message: boundHostDiagnostic(`The host response could not be encoded: ${error.message}`)
|
|
1091
|
-
}
|
|
1092
|
-
})));
|
|
1093
|
-
const utf8Bytes = (value) => new TextEncoder().encode(JSON.stringify(value)).length;
|
|
1094
|
-
/**
|
|
1095
|
-
* The admission-limits gate, BEFORE `runtime.submit` touches the ledger (exit gate
|
|
1096
|
-
* "resource limits are checked before admission"; DEPLOY-007). A replayed idempotency key is
|
|
1097
|
-
* exempt: its accepted-work obligation already exists, and returning the original Receipt
|
|
1098
|
-
* consumes no new quota. Refusals are typed `AdmissionLimitExceeded` and nothing is written.
|
|
1099
|
-
*/
|
|
1100
|
-
const gateAdmissionLimits = Effect.fn("ConversationObject.gateAdmissionLimits")(function* (request) {
|
|
1101
|
-
const identity = yield* ConversationObjectIdentity;
|
|
1102
|
-
const config = yield* CloudflareDurableRuntimeConfig;
|
|
1103
|
-
const ledger = yield* SubmissionLedger;
|
|
1104
|
-
const { ctx } = yield* DurableObjectContext;
|
|
1105
|
-
const existing = yield* ledger.lookup(SubmissionLookupByKey.make({
|
|
1106
|
-
conversationId: identity.conversationId,
|
|
1107
|
-
principal: request.principal,
|
|
1108
|
-
idempotencyKey: request.idempotencyKey
|
|
1109
|
-
}));
|
|
1110
|
-
if (Option.isSome(existing)) return;
|
|
1111
|
-
const inputBytes = utf8Bytes(request.inputPayload);
|
|
1112
|
-
if (inputBytes > config.limits.maxInputBytes) return yield* AdmissionLimitExceeded.make({
|
|
1113
|
-
limit: "input-bytes",
|
|
1114
|
-
actual: inputBytes,
|
|
1115
|
-
maximum: config.limits.maxInputBytes
|
|
1116
|
-
});
|
|
1117
|
-
const nonterminal = yield* Stream.runCollect(ledger.scanNonterminal);
|
|
1118
|
-
if (nonterminal.length >= config.limits.maxQueueDepthPerLane) return yield* AdmissionLimitExceeded.make({
|
|
1119
|
-
limit: "queue-depth",
|
|
1120
|
-
actual: nonterminal.length,
|
|
1121
|
-
maximum: config.limits.maxQueueDepthPerLane
|
|
1122
|
-
});
|
|
1123
|
-
const databaseBytes = yield* Effect.sync(() => ctx.storage.sql.databaseSize);
|
|
1124
|
-
if (databaseBytes > config.limits.maxDatabaseBytes) return yield* AdmissionLimitExceeded.make({
|
|
1125
|
-
limit: "database-bytes",
|
|
1126
|
-
actual: databaseBytes,
|
|
1127
|
-
maximum: config.limits.maxDatabaseBytes
|
|
1128
|
-
});
|
|
1129
|
-
});
|
|
1130
|
-
/**
|
|
1131
|
-
* The submit-capable projection of an Agent Binding on the OBJECT side: the input arrived
|
|
1132
|
-
* already encoded through the real input schema on the Worker side (`client.ts`), so the
|
|
1133
|
-
* Object admits the canonical `PersistedJson` payload as-is; the resolved Binding re-derives
|
|
1134
|
-
* everything else from the stored `(agentId, agentDigests)` at claim time (SUB-023).
|
|
1135
|
-
*/
|
|
1136
|
-
const passthroughSubmitAgent = (agentId) => ({ definition: {
|
|
1137
|
-
id: agentId,
|
|
1138
|
-
input: PersistedJson
|
|
1139
|
-
} });
|
|
1140
|
-
const submitEndpoint = (encoded) => decodeSubmitRequest(encoded).pipe(Effect.mapError(protocolFailure("The submit request could not be decoded")), Effect.flatMap((request) => Effect.gen(function* () {
|
|
1141
|
-
const identity = yield* ConversationObjectIdentity;
|
|
1142
|
-
const maintenance = yield* ConversationMaintenance;
|
|
1143
|
-
const runtime = yield* DurableAgentRuntime;
|
|
1144
|
-
yield* gateAdmissionLimits(request);
|
|
1145
|
-
yield* maintenance.preArm;
|
|
1146
|
-
const receipt = yield* runtime.submit(passthroughSubmitAgent(request.agentId), request.inputPayload, {
|
|
1147
|
-
conversationId: identity.conversationId,
|
|
1148
|
-
principal: request.principal,
|
|
1149
|
-
idempotencyKey: request.idempotencyKey,
|
|
1150
|
-
definitions: request.definitions
|
|
1151
|
-
});
|
|
1152
|
-
return SubmitSucceeded.make({ receipt });
|
|
1153
|
-
})), respond, Effect.flatMap(encodeResponse));
|
|
1154
|
-
const awaitSettlementEndpoint = (encoded) => decodeReceipt(encoded).pipe(Effect.mapError(protocolFailure("The receipt could not be decoded")), Effect.flatMap((receipt) => Effect.gen(function* () {
|
|
1155
|
-
const settlement = yield* (yield* DurableAgentRuntime).awaitSettlement(receipt);
|
|
1156
|
-
return SettlementReached.make({ settlement });
|
|
1157
|
-
})), respond, Effect.flatMap(encodeResponse));
|
|
1158
|
-
const observePageEndpoint = (encoded) => decodeObservePageRequest(encoded).pipe(Effect.mapError(protocolFailure("The observe request could not be decoded")), Effect.flatMap((request) => Effect.gen(function* () {
|
|
1159
|
-
const identity = yield* ConversationObjectIdentity;
|
|
1160
|
-
const store = yield* ConversationStore;
|
|
1161
|
-
yield* (yield* OperationAuthorizer).authorize(OperationAuthorizationRequest.make({
|
|
1162
|
-
operation: "observe",
|
|
1163
|
-
conversationId: identity.conversationId
|
|
1164
|
-
})).pipe(Effect.catchTag("OperationDenied", deniedToProtocolFailure));
|
|
1165
|
-
const records = yield* Stream.runCollect(store.read(ConversationRead.make({
|
|
1166
|
-
conversationId: identity.conversationId,
|
|
1167
|
-
...request.afterSequence === void 0 ? {} : { afterSequence: request.afterSequence },
|
|
1168
|
-
limit: request.limit
|
|
1169
|
-
})));
|
|
1170
|
-
return ObservedPage.make({ records: [...records] });
|
|
1171
|
-
})), respond, Effect.flatMap(encodeResponse));
|
|
1172
|
-
const abortEndpoint = (encoded) => decodeAbortCommand(encoded).pipe(Effect.mapError(protocolFailure("The abort command could not be decoded")), Effect.flatMap((command) => Effect.gen(function* () {
|
|
1173
|
-
const maintenance = yield* ConversationMaintenance;
|
|
1174
|
-
const runtime = yield* DurableAgentRuntime;
|
|
1175
|
-
yield* maintenance.preArm;
|
|
1176
|
-
const intent = yield* runtime.abort(command);
|
|
1177
|
-
return AbortRecorded.make({ intent });
|
|
1178
|
-
})), respond, Effect.flatMap(encodeResponse));
|
|
1179
|
-
/**
|
|
1180
|
-
* The pre-P7 host protocol's failure union does not carry `OperationDenied` (the Worker client
|
|
1181
|
-
* predates the authorizer). This assembly always runs the default possession authorizer — no
|
|
1182
|
-
* `CloudflareDurableRuntimeOptions` authorizer lever exists yet — so a denial here is
|
|
1183
|
-
* unreachable today; if one ever surfaces it degrades to the protocol failure instead of an
|
|
1184
|
-
* out-of-contract throw. The four P7 admin entry points below carry `OperationDenied` typed.
|
|
1185
|
-
*/
|
|
1186
|
-
const deniedToProtocolFailure = (denied) => Effect.fail(HostProtocolError.make({ message: boundHostDiagnostic(`The ${denied.operation} operation was denied: ${denied.reason}`) }));
|
|
1187
|
-
const resolveApprovalEndpoint = (encoded) => decodeApprovalDecisionCommand(encoded).pipe(Effect.mapError(protocolFailure("The approval command could not be decoded")), Effect.flatMap((command) => Effect.gen(function* () {
|
|
1188
|
-
const maintenance = yield* ConversationMaintenance;
|
|
1189
|
-
const runtime = yield* DurableAgentRuntime;
|
|
1190
|
-
yield* maintenance.preArm;
|
|
1191
|
-
const intent = yield* runtime.resolveApproval(command).pipe(Effect.catchTag("OperationDenied", deniedToProtocolFailure));
|
|
1192
|
-
return ApprovalRecorded.make({ intent });
|
|
1193
|
-
})), respond, Effect.flatMap(encodeResponse));
|
|
1194
|
-
const resolveUnknownEndpoint = (encoded) => decodeUnknownResolutionCommand(encoded).pipe(Effect.mapError(protocolFailure("The resolution command could not be decoded")), Effect.flatMap((command) => Effect.gen(function* () {
|
|
1195
|
-
const maintenance = yield* ConversationMaintenance;
|
|
1196
|
-
const runtime = yield* DurableAgentRuntime;
|
|
1197
|
-
yield* maintenance.preArm;
|
|
1198
|
-
const intent = yield* runtime.resolveUnknown(command).pipe(Effect.catchTag("OperationDenied", deniedToProtocolFailure));
|
|
1199
|
-
return UnknownResolutionRecorded.make({ intent });
|
|
1200
|
-
})), respond, Effect.flatMap(encodeResponse));
|
|
1201
|
-
/** Explain one Submission (`submissionId` present) or every nonterminal lane member. */
|
|
1202
|
-
var AdminExplainRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/AdminExplainRequest")({ submissionId: Schema.optionalKey(SubmissionId) }) {};
|
|
1203
|
-
/** Verify carries no parameters — the addressed Object IS the lane. */
|
|
1204
|
-
var AdminVerifyRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/AdminVerifyRequest")({}) {};
|
|
1205
|
-
/** Every typed failure of the four admin entry points, plus the protocol's own errors. */
|
|
1206
|
-
const AdminFailure = Schema.Union([
|
|
1207
|
-
OperationDenied,
|
|
1208
|
-
RetryRefused,
|
|
1209
|
-
LedgerError,
|
|
1210
|
-
RunJournalError,
|
|
1211
|
-
DigestError,
|
|
1212
|
-
OwnershipLost,
|
|
1213
|
-
SettlementConflict,
|
|
1214
|
-
ConversationStoreError,
|
|
1215
|
-
ConversationNotMaterialized,
|
|
1216
|
-
AppendConflict,
|
|
1217
|
-
FenceRejected,
|
|
1218
|
-
DurableRuntimeFailpointError,
|
|
1219
|
-
DurableAlarmError,
|
|
1220
|
-
HostProtocolError
|
|
1221
|
-
]);
|
|
1222
|
-
var ExplainedRecovery = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ExplainedRecovery")("ExplainedRecovery", { explanations: Schema.Array(RecoveryExplanation).check(Schema.isMaxLength(1024)) }) {};
|
|
1223
|
-
var VerifiedIntegrity = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/VerifiedIntegrity")("VerifiedIntegrity", { report: IntegrityReport }) {};
|
|
1224
|
-
var RetryExecuted = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/RetryExecuted")("RetryExecuted", { report: RecoveryReport }) {};
|
|
1225
|
-
var ObligationsScanned = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/ObligationsScanned")("ObligationsScanned", { report: ObligationReport }) {};
|
|
1226
|
-
/** The admin entry point failed TYPED on the Object; the failure re-decodes verbatim. */
|
|
1227
|
-
var AdminFailed = class extends Schema.TaggedClass("@effect-agent/platform-cloudflare/AdminFailed")("AdminFailed", { failure: AdminFailure }) {};
|
|
1228
|
-
/** The uniform answer of one admin entry point. Callers narrow by the tag their call implies. */
|
|
1229
|
-
const AdminResponse = Schema.Union([
|
|
1230
|
-
ExplainedRecovery,
|
|
1231
|
-
VerifiedIntegrity,
|
|
1232
|
-
RetryExecuted,
|
|
1233
|
-
ObligationsScanned,
|
|
1234
|
-
AdminFailed
|
|
1235
|
-
]);
|
|
1236
|
-
const decodeAdminExplainRequest = Schema.decodeUnknownEffect(AdminExplainRequest);
|
|
1237
|
-
const decodeAdminVerifyRequest = Schema.decodeUnknownEffect(AdminVerifyRequest);
|
|
1238
|
-
const decodeRetryCommand = Schema.decodeUnknownEffect(RetryCommand);
|
|
1239
|
-
const decodeObligationThresholds = Schema.decodeUnknownEffect(ObligationThresholds);
|
|
1240
|
-
const encodeAdminResponse = Schema.encodeEffect(AdminResponse);
|
|
1241
|
-
const decodeAdminResponse = Schema.decodeUnknownEffect(AdminResponse);
|
|
1242
|
-
/** Fold one admin endpoint's typed failures into the uniform `AdminResponse` envelope. */
|
|
1243
|
-
const respondAdmin = (effect) => effect.pipe(Effect.map((result) => result), Effect.catch((failure) => Effect.succeed(AdminFailed.make({ failure }))));
|
|
1244
|
-
/** Encode the admin response envelope; an unencodable response degrades to a protocol failure. */
|
|
1245
|
-
const encodeAdminResponseTotal = (response) => encodeAdminResponse(response).pipe(Effect.catch((error) => Effect.succeed({
|
|
1246
|
-
_tag: "AdminFailed",
|
|
1247
|
-
failure: {
|
|
1248
|
-
_tag: "HostProtocolError",
|
|
1249
|
-
message: boundHostDiagnostic(`The admin response could not be encoded: ${error.message}`)
|
|
1250
|
-
}
|
|
1251
|
-
})));
|
|
1252
|
-
const explainEndpoint = (encoded) => decodeAdminExplainRequest(encoded).pipe(Effect.mapError(protocolFailure("The explain request could not be decoded")), Effect.flatMap((request) => Effect.gen(function* () {
|
|
1253
|
-
const identity = yield* ConversationObjectIdentity;
|
|
1254
|
-
const runtime = yield* DurableAgentRuntime;
|
|
1255
|
-
const explanations = request.submissionId === void 0 ? yield* runtime.explainConversation(identity.conversationId) : [yield* runtime.explain(request.submissionId)];
|
|
1256
|
-
return ExplainedRecovery.make({ explanations });
|
|
1257
|
-
})), respondAdmin, Effect.flatMap(encodeAdminResponseTotal));
|
|
1258
|
-
const verifyEndpoint = (encoded) => decodeAdminVerifyRequest(encoded).pipe(Effect.mapError(protocolFailure("The verify request could not be decoded")), Effect.flatMap(() => Effect.gen(function* () {
|
|
1259
|
-
const identity = yield* ConversationObjectIdentity;
|
|
1260
|
-
const report = yield* (yield* DurableAgentRuntime).verify(identity.conversationId);
|
|
1261
|
-
return VerifiedIntegrity.make({ report });
|
|
1262
|
-
})), respondAdmin, Effect.flatMap(encodeAdminResponseTotal));
|
|
1263
|
-
const retryEndpoint = (encoded) => decodeRetryCommand(encoded).pipe(Effect.mapError(protocolFailure("The retry command could not be decoded")), Effect.flatMap((command) => Effect.gen(function* () {
|
|
1264
|
-
const maintenance = yield* ConversationMaintenance;
|
|
1265
|
-
const runtime = yield* DurableAgentRuntime;
|
|
1266
|
-
yield* maintenance.preArm;
|
|
1267
|
-
const report = yield* runtime.retry(command);
|
|
1268
|
-
return RetryExecuted.make({ report });
|
|
1269
|
-
})), respondAdmin, Effect.flatMap(encodeAdminResponseTotal));
|
|
1270
|
-
const obligationsEndpoint = (encoded) => decodeObligationThresholds(encoded).pipe(Effect.mapError(protocolFailure("The obligation thresholds could not be decoded")), Effect.flatMap((thresholds) => Effect.gen(function* () {
|
|
1271
|
-
const report = yield* (yield* DurableAgentRuntime).scanObligations(thresholds);
|
|
1272
|
-
return ObligationsScanned.make({ report });
|
|
1273
|
-
})), respondAdmin, Effect.flatMap(encodeAdminResponseTotal));
|
|
1274
|
-
/**
|
|
1275
|
-
* Owner-side `portCall`: pre-arm before a mutating envelope (a routed admission committed by
|
|
1276
|
-
* THIS Object must already carry the alarm that will finish it), execute on the LOCAL facets
|
|
1277
|
-
* (never the routed decorators), then arm an immediate alarm so the mutated lane is
|
|
1278
|
-
* processed promptly. Protocol anomalies answer `PortFailed(PortProtocolError)`.
|
|
1279
|
-
*/
|
|
1280
|
-
const portCallEndpoint = (encoded) => Effect.gen(function* () {
|
|
1281
|
-
const ports = yield* ConversationObjectPorts;
|
|
1282
|
-
const maintenance = yield* ConversationMaintenance;
|
|
1283
|
-
const alarm = yield* DurableAlarmService;
|
|
1284
|
-
const mutating = isMutatingPortRequest(encoded);
|
|
1285
|
-
if (mutating) {
|
|
1286
|
-
if ((yield* maintenance.preArm.pipe(Effect.exit))._tag === "Failure") return encodedPortProtocolFailure("The owner Object could not arm its maintenance alarm before the mutation.");
|
|
1287
|
-
}
|
|
1288
|
-
const response = yield* ports.handle(encoded);
|
|
1289
|
-
if (mutating) yield* alarm.scheduleNow.pipe(Effect.catch((error) => Effect.logWarning("ConversationObject.portCall: immediate re-arm failed", error)));
|
|
1290
|
-
return response;
|
|
1291
|
-
});
|
|
1292
|
-
const wakeEndpoint = Effect.gen(function* () {
|
|
1293
|
-
yield* (yield* DurableAlarmService).scheduleNow.pipe(Effect.catch((error) => Effect.logWarning("ConversationObject.wake dropped", error)));
|
|
1294
|
-
});
|
|
1295
|
-
const alarmEndpoint = Effect.gen(function* () {
|
|
1296
|
-
yield* (yield* ConversationMaintenance).pass;
|
|
1297
|
-
});
|
|
1298
|
-
const gateEndpoint = Effect.gen(function* () {
|
|
1299
|
-
yield* (yield* ConversationMaintenance).ensureAlarm;
|
|
1300
|
-
});
|
|
1301
|
-
/**
|
|
1302
|
-
* Owner-side delivery measurement. The exact endpoint Cause is hidden behind a bounded typed
|
|
1303
|
-
* marker while the span closes, then restored together with any newly composed real reasons.
|
|
1304
|
-
*/
|
|
1305
|
-
const observeNativeEntrypoint = (entrypoint, effect) => Effect.gen(function* () {
|
|
1306
|
-
const identity = yield* ConversationObjectIdentity;
|
|
1307
|
-
const config = yield* CloudflareDurableRuntimeConfig;
|
|
1308
|
-
return yield* withCloudflareNativeSpanFailure(effect, (masked) => masked.pipe(Effect.withSpan(`effect_agent.cloudflare.conversation_object.${entrypoint}`, {
|
|
1309
|
-
kind: "server",
|
|
1310
|
-
attributes: {
|
|
1311
|
-
"effect_agent.cloudflare.entrypoint": entrypoint,
|
|
1312
|
-
"effect_agent.deployment.id": config.deploymentId,
|
|
1313
|
-
conversationId: identity.conversationId,
|
|
1314
|
-
producerId: identity.producerId
|
|
1315
|
-
}
|
|
1316
|
-
})));
|
|
1317
|
-
});
|
|
1318
|
-
const flushNativeEntrypointTelemetry = Effect.flatMap(CloudflareDurableRuntimeConfig, (config) => flushCloudflareRuntimeTelemetry(config.telemetryFlushTimeout));
|
|
1319
|
-
/**
|
|
1320
|
-
* Build the application's Conversation Object class (export it from the Worker entry).
|
|
1321
|
-
* `telemetry` is the host composition boundary: it may require the Object context/namespace and
|
|
1322
|
-
* retain a typed acquisition error, while any additional output services remain available to the
|
|
1323
|
-
* cached runtime. `TelemetryError` stays the first explicit generic for source compatibility;
|
|
1324
|
-
* additional outputs infer from the Layer. Omitting it installs the content-free no-op service.
|
|
1325
|
-
* The explicit return type is what makes declaration emit possible: the class body carries a
|
|
1326
|
-
* private runtime field, and TS4094 rejects inferring an exported anonymous class type around it.
|
|
1327
|
-
*/
|
|
1328
|
-
const makeConversationObjectClass = (options, telemetry) => {
|
|
1329
|
-
class ConversationObject extends DurableObject {
|
|
1330
|
-
#runtime;
|
|
1331
|
-
#ctx;
|
|
1332
|
-
#telemetryFlush;
|
|
1333
|
-
constructor(ctx, env) {
|
|
1334
|
-
super(ctx, env);
|
|
1335
|
-
this.#ctx = ctx;
|
|
1336
|
-
const platform = Layer.mergeAll(DurableObjectContext.layer(ctx, env), conversationNamespaceLayer(env, options.namespaceBinding));
|
|
1337
|
-
const hostTelemetry = (telemetry ?? CloudflareRuntimeTelemetry.layerNoop).pipe(Layer.provideMerge(platform));
|
|
1338
|
-
this.#runtime = ManagedRuntime.make(CloudflareDurableRuntime.layer(options).pipe(Layer.provideMerge(hostTelemetry)));
|
|
1339
|
-
this.#telemetryFlush = makeCloudflareTelemetryFlushCoordinator(() => this.#runtime.runPromise(flushNativeEntrypointTelemetry), { onReservationDropped: () => {
|
|
1340
|
-
this.#runtime.runSyncExit(Effect.logWarning("Cloudflare telemetry delivery coalesced at the batch reservation limit").pipe(Effect.annotateLogs({ "effect_agent.cloudflare.telemetry.failure_kind": "reservation_limit" })));
|
|
1341
|
-
} });
|
|
1342
|
-
ctx.blockConcurrencyWhile(() => this.#runtime.runPromise(gateEndpoint));
|
|
1343
|
-
}
|
|
1344
|
-
#runNative(entrypoint, effect) {
|
|
1345
|
-
return registerCloudflareTelemetryAfterNativeSettlement((background) => this.#ctx.waitUntil(background), this.#runtime.runPromise(observeNativeEntrypoint(entrypoint, effect)), this.#telemetryFlush.reserve, (cause) => {
|
|
1346
|
-
this.#runtime.runSyncExit(logCloudflareWaitUntilRegistrationFailure(cause));
|
|
1347
|
-
});
|
|
1348
|
-
}
|
|
1349
|
-
async submitEncoded(encoded) {
|
|
1350
|
-
return this.#runNative("submit", submitEndpoint(encoded));
|
|
1351
|
-
}
|
|
1352
|
-
async awaitSettlementEncoded(encoded) {
|
|
1353
|
-
return this.#runNative("await_settlement", awaitSettlementEndpoint(encoded));
|
|
1354
|
-
}
|
|
1355
|
-
async observePage(encoded) {
|
|
1356
|
-
return this.#runNative("observe", observePageEndpoint(encoded));
|
|
1357
|
-
}
|
|
1358
|
-
async abortEncoded(encoded) {
|
|
1359
|
-
return this.#runNative("abort", abortEndpoint(encoded));
|
|
1360
|
-
}
|
|
1361
|
-
async resolveApprovalEncoded(encoded) {
|
|
1362
|
-
return this.#runNative("resolve_approval", resolveApprovalEndpoint(encoded));
|
|
1363
|
-
}
|
|
1364
|
-
async resolveUnknownEncoded(encoded) {
|
|
1365
|
-
return this.#runNative("resolve_unknown", resolveUnknownEndpoint(encoded));
|
|
1366
|
-
}
|
|
1367
|
-
async explainEncoded(encoded) {
|
|
1368
|
-
return this.#runNative("explain", explainEndpoint(encoded));
|
|
1369
|
-
}
|
|
1370
|
-
async verifyEncoded(encoded) {
|
|
1371
|
-
return this.#runNative("verify", verifyEndpoint(encoded));
|
|
1372
|
-
}
|
|
1373
|
-
async retryEncoded(encoded) {
|
|
1374
|
-
return this.#runNative("retry", retryEndpoint(encoded));
|
|
1375
|
-
}
|
|
1376
|
-
async obligationsEncoded(encoded) {
|
|
1377
|
-
return this.#runNative("obligations", obligationsEndpoint(encoded));
|
|
1378
|
-
}
|
|
1379
|
-
async portCall(encoded) {
|
|
1380
|
-
return this.#runNative("port_call", portCallEndpoint(encoded));
|
|
1381
|
-
}
|
|
1382
|
-
async wake() {
|
|
1383
|
-
await this.#runNative("wake", wakeEndpoint);
|
|
1384
|
-
}
|
|
1385
|
-
async alarm() {
|
|
1386
|
-
await this.#runNative("alarm", alarmEndpoint);
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
return ConversationObject;
|
|
1390
|
-
};
|
|
1391
|
-
//#endregion
|
|
1392
|
-
//#region src/code-mode-executor.ts
|
|
1393
|
-
/**
|
|
1394
|
-
* The Cloudflare Dynamic Worker `CodeExecutor` adapter (C4 of ADR-0017;
|
|
1395
|
-
* DEPLOY-011). Each pass loads one fresh Worker through the Worker Loader
|
|
1396
|
-
* with `globalOutbound: null`, so generated code has no ambient network,
|
|
1397
|
-
* bindings, or secrets; its only authority is the pass-scoped host stub that
|
|
1398
|
-
* routes back to `CodeModeHostEntrypoint` and, from there, into the pass's
|
|
1399
|
-
* `CodeExecutionHost` service. Platform CPU limits stop synchronous runaway
|
|
1400
|
-
* programs; the executor-owned wall-clock deadline interrupts asynchronously
|
|
1401
|
-
* suspended passes. Deployment class `E` only: the adapter records no
|
|
1402
|
-
* persistent state and a later pass may run in a completely different
|
|
1403
|
-
* isolate.
|
|
1404
|
-
*/
|
|
1405
|
-
const dynamicWorkerImplementation = SandboxImplementation.make({
|
|
1406
|
-
isolation: "isolated",
|
|
1407
|
-
identity: "cloudflare-dynamic-worker"
|
|
1408
|
-
});
|
|
1409
|
-
/**
|
|
1410
|
-
* Live passes by identity. Entries are Scope-managed: registered when a pass
|
|
1411
|
-
* opens and removed by its finalizer, so a stale harness (or a forged
|
|
1412
|
-
* `passId`) cannot reach any host authority.
|
|
1413
|
-
*/
|
|
1414
|
-
const passRegistry = /* @__PURE__ */ new Map();
|
|
1415
|
-
/**
|
|
1416
|
-
* The host-side RPC target for dynamic workers. The application exposes it
|
|
1417
|
-
* from its Worker entry (`export { CodeModeHostEntrypoint }`) and hands the
|
|
1418
|
-
* adapter a same-instance stub — `ctx.exports.CodeModeHostEntrypoint()` in
|
|
1419
|
-
* production (a self service binding may reach a different instance and must
|
|
1420
|
-
* not be used there); tests bind it through Miniflare's `kCurrentWorker`.
|
|
1421
|
-
*/
|
|
1422
|
-
var CodeModeHostEntrypoint = class extends WorkerEntrypoint {
|
|
1423
|
-
async call(passId, hostCall) {
|
|
1424
|
-
const pass = passRegistry.get(String(passId));
|
|
1425
|
-
if (pass === void 0) throw new Error("Unknown Code Mode pass");
|
|
1426
|
-
return pass.dispatch(hostCall);
|
|
1427
|
-
}
|
|
1428
|
-
};
|
|
1429
|
-
/**
|
|
1430
|
-
* The fixed harness loaded as the dynamic worker's main module. The generated
|
|
1431
|
-
* source becomes `program.mjs` (`export default (<expression>);`) — a module,
|
|
1432
|
-
* never `eval`. The harness installs namespace globals and a bounded console,
|
|
1433
|
-
* imports the program, invokes it exactly once, and returns one envelope the
|
|
1434
|
-
* host validates through Effect Schema.
|
|
1435
|
-
*/
|
|
1436
|
-
const HARNESS_MODULE = String.raw`
|
|
1437
|
-
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
1438
|
-
import programDefault from "./program.js";
|
|
1439
|
-
|
|
1440
|
-
const encoder = new TextEncoder();
|
|
1441
|
-
const utf8 = (text) => encoder.encode(text).byteLength;
|
|
1442
|
-
const safeText = (value) => {
|
|
1443
|
-
try {
|
|
1444
|
-
if (value instanceof Error) return (value.name + ": " + value.message).slice(0, 4000);
|
|
1445
|
-
if (typeof value === "string") return value.slice(0, 4000);
|
|
1446
|
-
const encoded = JSON.stringify(value);
|
|
1447
|
-
return (encoded === undefined ? String(value) : encoded).slice(0, 4000);
|
|
1448
|
-
} catch {
|
|
1449
|
-
return "[unserializable value]";
|
|
1450
|
-
}
|
|
1451
|
-
};
|
|
1452
|
-
const safeJson = (value) => {
|
|
1453
|
-
try {
|
|
1454
|
-
const encoded = JSON.stringify(value);
|
|
1455
|
-
if (encoded !== undefined && encoded.length <= 4000) return JSON.parse(encoded);
|
|
1456
|
-
} catch {}
|
|
1457
|
-
return safeText(value);
|
|
1458
|
-
};
|
|
1459
|
-
|
|
1460
|
-
export default class CodeModeHarness extends WorkerEntrypoint {
|
|
1461
|
-
async run() {
|
|
1462
|
-
const config = JSON.parse(this.env.CODE_MODE_PASS);
|
|
1463
|
-
const host = this.env.CODE_MODE_HOST;
|
|
1464
|
-
const limits = config.limits;
|
|
1465
|
-
const logs = [];
|
|
1466
|
-
let logBytes = 0;
|
|
1467
|
-
let fatal;
|
|
1468
|
-
const boundedLogs = () => logs.slice(0, 4096);
|
|
1469
|
-
const write = (...values) => {
|
|
1470
|
-
const joined = values.map(safeText).join(" ");
|
|
1471
|
-
const line = joined.length > 16000 ? joined.slice(0, 15999) + "…" : joined;
|
|
1472
|
-
const bytes = utf8(line);
|
|
1473
|
-
if (logs.length >= 4096 || logBytes + bytes > limits.maxLogBytes) {
|
|
1474
|
-
fatal = fatal ?? { _tag: "log-limit", observed: logBytes + bytes, logs: boundedLogs() };
|
|
1475
|
-
throw new Error("code-mode log limit exceeded");
|
|
1476
|
-
}
|
|
1477
|
-
logs.push(line);
|
|
1478
|
-
logBytes += bytes;
|
|
1479
|
-
};
|
|
1480
|
-
globalThis.console = { log: write, info: write, warn: write, error: write, debug: write };
|
|
1481
|
-
|
|
1482
|
-
let hostCalls = 0;
|
|
1483
|
-
const makeMethod = (namespace, method) => async (argument) => {
|
|
1484
|
-
hostCalls += 1;
|
|
1485
|
-
if (hostCalls > limits.maxHostCalls) {
|
|
1486
|
-
fatal = fatal ?? { _tag: "host-call-limit", logs: boundedLogs() };
|
|
1487
|
-
throw new Error("code-mode host-call limit exceeded");
|
|
1488
|
-
}
|
|
1489
|
-
let argText;
|
|
1490
|
-
try {
|
|
1491
|
-
argText = JSON.stringify(argument);
|
|
1492
|
-
} catch {}
|
|
1493
|
-
if (argText === undefined || utf8(argText) > limits.maxHostCallArgumentBytes) {
|
|
1494
|
-
fatal = fatal ?? {
|
|
1495
|
-
_tag: "argument-limit",
|
|
1496
|
-
observed: argText === undefined ? 0 : utf8(argText),
|
|
1497
|
-
logs: boundedLogs(),
|
|
1498
|
-
};
|
|
1499
|
-
throw new Error("code-mode host-call argument limit exceeded");
|
|
1500
|
-
}
|
|
1501
|
-
const outcome = await host.call(config.passId, {
|
|
1502
|
-
namespace,
|
|
1503
|
-
method,
|
|
1504
|
-
argument: JSON.parse(argText),
|
|
1505
|
-
});
|
|
1506
|
-
if (outcome !== null && typeof outcome === "object" && outcome._tag === "CodeHostCallSuccess") {
|
|
1507
|
-
return outcome.value;
|
|
1508
|
-
}
|
|
1509
|
-
if (outcome !== null && typeof outcome === "object" && outcome._tag === "CodeHostCallFailure") {
|
|
1510
|
-
throw outcome.error;
|
|
1511
|
-
}
|
|
1512
|
-
fatal = fatal ?? { _tag: "protocol", message: "host returned an unrecognized outcome" };
|
|
1513
|
-
throw new Error("code-mode host protocol violation");
|
|
1514
|
-
};
|
|
1515
|
-
for (const namespace of config.namespaces) {
|
|
1516
|
-
const methods = {};
|
|
1517
|
-
for (const method of namespace.methods) {
|
|
1518
|
-
methods[method] = makeMethod(namespace.name, method);
|
|
1519
|
-
}
|
|
1520
|
-
globalThis[namespace.name] = methods;
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
// program.js is imported statically at the top of this module, so a
|
|
1524
|
-
// syntactically invalid program fails the whole harness at load (mapped
|
|
1525
|
-
// to a source error by the host). Using a static import keeps this module
|
|
1526
|
-
// free of dynamic-import expressions, which single-script Miniflare hosts
|
|
1527
|
-
// reject. The isolation boundary does NOT depend on the ordering of this
|
|
1528
|
-
// import versus the console/namespace shims installed below: the loaded
|
|
1529
|
-
// Worker has globalOutbound: null and no bindings, secrets, or env from
|
|
1530
|
-
// the Worker Loader config BEFORE any module in the graph evaluates, so
|
|
1531
|
-
// module-level program code has no ambient authority regardless. The
|
|
1532
|
-
// shims below are usability wrappers (bounded console, namespace globals),
|
|
1533
|
-
// and the accepted program is a single async-function expression whose
|
|
1534
|
-
// body runs only when invoked here — after the shims exist.
|
|
1535
|
-
const program = programDefault;
|
|
1536
|
-
if (typeof program !== "function") {
|
|
1537
|
-
return { _tag: "source-not-a-function", actual: typeof program };
|
|
1538
|
-
}
|
|
1539
|
-
try {
|
|
1540
|
-
const value = await program();
|
|
1541
|
-
if (fatal !== undefined) return fatal;
|
|
1542
|
-
let text;
|
|
1543
|
-
try {
|
|
1544
|
-
text = JSON.stringify(value);
|
|
1545
|
-
} catch {}
|
|
1546
|
-
if (text === undefined) {
|
|
1547
|
-
return {
|
|
1548
|
-
_tag: "program-failed",
|
|
1549
|
-
reason: "non-json-result",
|
|
1550
|
-
thrown: null,
|
|
1551
|
-
message: "The program must return a JSON value",
|
|
1552
|
-
logs: boundedLogs(),
|
|
1553
|
-
};
|
|
1554
|
-
}
|
|
1555
|
-
const resultBytes = utf8(text);
|
|
1556
|
-
if (resultBytes > limits.maxResultBytes) {
|
|
1557
|
-
return { _tag: "result-limit", observed: resultBytes, logs: boundedLogs() };
|
|
1558
|
-
}
|
|
1559
|
-
return {
|
|
1560
|
-
_tag: "completed",
|
|
1561
|
-
value: JSON.parse(text),
|
|
1562
|
-
logs: boundedLogs(),
|
|
1563
|
-
hostCalls,
|
|
1564
|
-
logBytes,
|
|
1565
|
-
resultBytes,
|
|
1566
|
-
};
|
|
1567
|
-
} catch (cause) {
|
|
1568
|
-
if (fatal !== undefined) return fatal;
|
|
1569
|
-
return {
|
|
1570
|
-
_tag: "program-failed",
|
|
1571
|
-
reason: cause instanceof Error ? "threw" : "rejected",
|
|
1572
|
-
thrown: safeJson(cause),
|
|
1573
|
-
message: safeText(cause),
|
|
1574
|
-
logs: boundedLogs(),
|
|
1575
|
-
};
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
`;
|
|
1580
|
-
const BoundedLogs = Schema.Array(Schema.String.check(Schema.isMaxLength(16 * 1024))).check(Schema.isMaxLength(4096));
|
|
1581
|
-
const HarnessCompleted = Schema.Struct({
|
|
1582
|
-
_tag: Schema.Literal("completed"),
|
|
1583
|
-
value: Schema.Json,
|
|
1584
|
-
logs: BoundedLogs,
|
|
1585
|
-
hostCalls: Schema.Natural,
|
|
1586
|
-
logBytes: Schema.Natural,
|
|
1587
|
-
resultBytes: Schema.Natural
|
|
1588
|
-
});
|
|
1589
|
-
const HarnessSourceInvalid = Schema.Struct({
|
|
1590
|
-
_tag: Schema.Literal("source-invalid"),
|
|
1591
|
-
message: Schema.String
|
|
1592
|
-
});
|
|
1593
|
-
const HarnessNotAFunction = Schema.Struct({
|
|
1594
|
-
_tag: Schema.Literal("source-not-a-function"),
|
|
1595
|
-
actual: Schema.String
|
|
1596
|
-
});
|
|
1597
|
-
const HarnessProgramFailed = Schema.Struct({
|
|
1598
|
-
_tag: Schema.Literal("program-failed"),
|
|
1599
|
-
reason: Schema.Literals([
|
|
1600
|
-
"threw",
|
|
1601
|
-
"rejected",
|
|
1602
|
-
"non-json-result"
|
|
1603
|
-
]),
|
|
1604
|
-
thrown: Schema.Json,
|
|
1605
|
-
message: Schema.String,
|
|
1606
|
-
logs: BoundedLogs
|
|
1607
|
-
});
|
|
1608
|
-
const HarnessLogLimit = Schema.Struct({
|
|
1609
|
-
_tag: Schema.Literal("log-limit"),
|
|
1610
|
-
observed: Schema.Natural,
|
|
1611
|
-
logs: BoundedLogs
|
|
1612
|
-
});
|
|
1613
|
-
const HarnessArgumentLimit = Schema.Struct({
|
|
1614
|
-
_tag: Schema.Literal("argument-limit"),
|
|
1615
|
-
observed: Schema.Natural,
|
|
1616
|
-
logs: BoundedLogs
|
|
1617
|
-
});
|
|
1618
|
-
const HarnessResultLimit = Schema.Struct({
|
|
1619
|
-
_tag: Schema.Literal("result-limit"),
|
|
1620
|
-
observed: Schema.Natural,
|
|
1621
|
-
logs: BoundedLogs
|
|
1622
|
-
});
|
|
1623
|
-
const HarnessHostCallLimit = Schema.Struct({
|
|
1624
|
-
_tag: Schema.Literal("host-call-limit"),
|
|
1625
|
-
logs: BoundedLogs
|
|
1626
|
-
});
|
|
1627
|
-
const HarnessProtocol = Schema.Struct({
|
|
1628
|
-
_tag: Schema.Literal("protocol"),
|
|
1629
|
-
message: Schema.String
|
|
1630
|
-
});
|
|
1631
|
-
const HarnessOutcome = Schema.Union([
|
|
1632
|
-
HarnessCompleted,
|
|
1633
|
-
HarnessSourceInvalid,
|
|
1634
|
-
HarnessNotAFunction,
|
|
1635
|
-
HarnessProgramFailed,
|
|
1636
|
-
HarnessLogLimit,
|
|
1637
|
-
HarnessArgumentLimit,
|
|
1638
|
-
HarnessResultLimit,
|
|
1639
|
-
HarnessHostCallLimit,
|
|
1640
|
-
HarnessProtocol
|
|
1641
|
-
]);
|
|
1642
|
-
const decodeHarnessOutcome = (value) => {
|
|
1643
|
-
try {
|
|
1644
|
-
return Schema.decodeUnknownOption(HarnessOutcome)(value);
|
|
1645
|
-
} catch {
|
|
1646
|
-
return Option.none();
|
|
1647
|
-
}
|
|
1648
|
-
};
|
|
1649
|
-
const decodeHostCall = (value) => {
|
|
1650
|
-
try {
|
|
1651
|
-
return Schema.decodeUnknownOption(CodeHostCall)(value);
|
|
1652
|
-
} catch {
|
|
1653
|
-
return Option.none();
|
|
1654
|
-
}
|
|
1655
|
-
};
|
|
1656
|
-
/**
|
|
1657
|
-
* Project a host outcome to the plain JSON envelope the harness reads. A
|
|
1658
|
-
* `CodeExecutionHost` may return either real `CodeHostCallResult` instances
|
|
1659
|
-
* (the substitute and conformance kit) or plain-object equivalents (the Code
|
|
1660
|
-
* Mode capability's broker route), so this reads the shared fields rather than
|
|
1661
|
-
* `Schema.encodeSync`, which would reject a plain object.
|
|
1662
|
-
*/
|
|
1663
|
-
const hostResultEnvelope = (outcome) => outcome._tag === "CodeHostCallSuccess" ? {
|
|
1664
|
-
_tag: "CodeHostCallSuccess",
|
|
1665
|
-
value: outcome.value
|
|
1666
|
-
} : {
|
|
1667
|
-
_tag: "CodeHostCallFailure",
|
|
1668
|
-
error: outcome.error
|
|
1669
|
-
};
|
|
1670
|
-
const utf8ByteLength = (value) => {
|
|
1671
|
-
let total = 0;
|
|
1672
|
-
for (const character of value) {
|
|
1673
|
-
const codePoint = character.codePointAt(0) ?? 0;
|
|
1674
|
-
total += codePoint <= 127 ? 1 : codePoint <= 2047 ? 2 : codePoint <= 65535 ? 3 : 4;
|
|
1675
|
-
}
|
|
1676
|
-
return total;
|
|
1677
|
-
};
|
|
1678
|
-
const encodedJsonByteLength = (value) => {
|
|
1679
|
-
try {
|
|
1680
|
-
const encoded = JSON.stringify(value);
|
|
1681
|
-
return encoded === void 0 ? void 0 : utf8ByteLength(encoded);
|
|
1682
|
-
} catch {
|
|
1683
|
-
return;
|
|
1684
|
-
}
|
|
1685
|
-
};
|
|
1686
|
-
/** Reserved global names the harness owns inside the dynamic worker. */
|
|
1687
|
-
const reservedHarnessGlobals = /* @__PURE__ */ new Set(["console"]);
|
|
1688
|
-
const passCounterState = { next: 0 };
|
|
1689
|
-
const makeExecute = (options) => Effect.fn("DynamicWorkerCodeExecutor.execute")(function* (request) {
|
|
1690
|
-
if (request.network._tag !== "NetworkDisabled") return yield* CodeExecutorUnsupportedError.make({
|
|
1691
|
-
implementation: dynamicWorkerImplementation,
|
|
1692
|
-
feature: "network",
|
|
1693
|
-
message: "The Dynamic Worker executor denies all egress with globalOutbound: null; an allowlist is not supported in the first slice"
|
|
1694
|
-
});
|
|
1695
|
-
const sourceBytes = utf8ByteLength(request.source);
|
|
1696
|
-
if (sourceBytes > request.limits.maxSourceBytes) return yield* CodeSourceError.make({
|
|
1697
|
-
implementation: dynamicWorkerImplementation,
|
|
1698
|
-
reason: "oversized",
|
|
1699
|
-
message: `Source is ${sourceBytes} bytes; the request allows ${request.limits.maxSourceBytes}`
|
|
1700
|
-
});
|
|
1701
|
-
for (const namespace of request.namespaces) if (reservedHarnessGlobals.has(namespace.name)) return yield* CodeExecutorUnsupportedError.make({
|
|
1702
|
-
implementation: dynamicWorkerImplementation,
|
|
1703
|
-
feature: "namespaces",
|
|
1704
|
-
message: `Namespace ${namespace.name} collides with a harness binding`
|
|
1705
|
-
});
|
|
1706
|
-
const host = yield* CodeExecutionHost;
|
|
1707
|
-
passCounterState.next += 1;
|
|
1708
|
-
const passId = `code-mode-pass-${passCounterState.next}-${crypto.randomUUID()}`;
|
|
1709
|
-
const pending = [];
|
|
1710
|
-
let wake;
|
|
1711
|
-
let issuedHostCalls = 0;
|
|
1712
|
-
const dispatch = (hostCall) => new Promise((resolve, reject) => {
|
|
1713
|
-
issuedHostCalls += 1;
|
|
1714
|
-
if (issuedHostCalls > request.limits.maxHostCalls + 1) {
|
|
1715
|
-
reject(/* @__PURE__ */ new Error("host-call limit exceeded"));
|
|
1716
|
-
return;
|
|
1717
|
-
}
|
|
1718
|
-
pending.push({
|
|
1719
|
-
hostCall,
|
|
1720
|
-
resolve,
|
|
1721
|
-
reject
|
|
1722
|
-
});
|
|
1723
|
-
wake?.();
|
|
1724
|
-
});
|
|
1725
|
-
yield* Effect.acquireRelease(Effect.sync(() => {
|
|
1726
|
-
passRegistry.set(passId, { dispatch });
|
|
1727
|
-
}), () => Effect.sync(() => {
|
|
1728
|
-
passRegistry.delete(passId);
|
|
1729
|
-
}));
|
|
1730
|
-
const nextPending = Effect.suspend(() => {
|
|
1731
|
-
const item = pending.shift();
|
|
1732
|
-
if (item !== void 0) return Effect.succeed(item);
|
|
1733
|
-
return Effect.callback((resume) => {
|
|
1734
|
-
wake = () => {
|
|
1735
|
-
wake = void 0;
|
|
1736
|
-
const next = pending.shift();
|
|
1737
|
-
if (next !== void 0) resume(Effect.succeed(next));
|
|
1738
|
-
};
|
|
1739
|
-
});
|
|
1740
|
-
});
|
|
1741
|
-
const serveHostCalls = Effect.gen(function* () {
|
|
1742
|
-
let served = 0;
|
|
1743
|
-
while (true) {
|
|
1744
|
-
const item = yield* nextPending;
|
|
1745
|
-
served += 1;
|
|
1746
|
-
if (served > request.limits.maxHostCalls) return yield* CodeHostCallLimitError.make({
|
|
1747
|
-
implementation: dynamicWorkerImplementation,
|
|
1748
|
-
limit: request.limits.maxHostCalls,
|
|
1749
|
-
logs: []
|
|
1750
|
-
});
|
|
1751
|
-
const decoded = decodeHostCall(item.hostCall);
|
|
1752
|
-
if (Option.isNone(decoded)) {
|
|
1753
|
-
item.reject(/* @__PURE__ */ new TypeError("host calls must match the CodeHostCall schema"));
|
|
1754
|
-
continue;
|
|
1755
|
-
}
|
|
1756
|
-
const outcome = yield* host.call(decoded.value);
|
|
1757
|
-
if (outcome._tag === "CodeHostCallSuccess") {
|
|
1758
|
-
const bytes = encodedJsonByteLength(outcome.value);
|
|
1759
|
-
if (bytes === void 0 || bytes > request.limits.maxHostCallResultBytes) return yield* CodeOutputLimitError.make({
|
|
1760
|
-
implementation: dynamicWorkerImplementation,
|
|
1761
|
-
surface: "host-call-result",
|
|
1762
|
-
limit: request.limits.maxHostCallResultBytes,
|
|
1763
|
-
observed: bytes ?? 0,
|
|
1764
|
-
logs: []
|
|
1765
|
-
});
|
|
1766
|
-
}
|
|
1767
|
-
item.resolve(hostResultEnvelope(outcome));
|
|
1768
|
-
}
|
|
1769
|
-
});
|
|
1770
|
-
const workerCode = {
|
|
1771
|
-
compatibilityDate: options.compatibilityDate ?? "2025-05-01",
|
|
1772
|
-
allowExperimental: true,
|
|
1773
|
-
mainModule: "harness.js",
|
|
1774
|
-
modules: {
|
|
1775
|
-
"harness.js": HARNESS_MODULE,
|
|
1776
|
-
"program.js": `export default (\n${request.source}\n);`
|
|
1777
|
-
},
|
|
1778
|
-
env: {
|
|
1779
|
-
CODE_MODE_HOST: options.hostStub,
|
|
1780
|
-
CODE_MODE_PASS: JSON.stringify({
|
|
1781
|
-
passId,
|
|
1782
|
-
namespaces: request.namespaces.map((namespace) => ({
|
|
1783
|
-
name: namespace.name,
|
|
1784
|
-
methods: namespace.methods
|
|
1785
|
-
})),
|
|
1786
|
-
limits: {
|
|
1787
|
-
maxLogBytes: request.limits.maxLogBytes,
|
|
1788
|
-
maxResultBytes: request.limits.maxResultBytes,
|
|
1789
|
-
maxHostCalls: request.limits.maxHostCalls,
|
|
1790
|
-
maxHostCallArgumentBytes: request.limits.maxHostCallArgumentBytes
|
|
1791
|
-
}
|
|
1792
|
-
})
|
|
1793
|
-
},
|
|
1794
|
-
globalOutbound: null,
|
|
1795
|
-
...request.limits.cpuMillis === void 0 ? {} : { limits: {
|
|
1796
|
-
cpuMs: request.limits.cpuMillis,
|
|
1797
|
-
subRequests: request.limits.maxHostCalls + 8
|
|
1798
|
-
} }
|
|
1799
|
-
};
|
|
1800
|
-
const startedAt = yield* Clock.currentTimeMillis;
|
|
1801
|
-
const worker = yield* Effect.acquireRelease(Effect.try({
|
|
1802
|
-
try: () => options.loader.load(workerCode),
|
|
1803
|
-
catch: (cause) => {
|
|
1804
|
-
const text = cause instanceof Error ? cause.message : String(cause);
|
|
1805
|
-
if (/syntaxerror|failed to (compile|parse)/i.test(text)) return CodeSourceError.make({
|
|
1806
|
-
implementation: dynamicWorkerImplementation,
|
|
1807
|
-
reason: "invalid",
|
|
1808
|
-
message: text.slice(0, 8e3)
|
|
1809
|
-
});
|
|
1810
|
-
return CodeExecutorStartError.make({
|
|
1811
|
-
implementation: dynamicWorkerImplementation,
|
|
1812
|
-
message: `The Worker Loader rejected the pass: ${text}`.slice(0, 8e3),
|
|
1813
|
-
cause
|
|
1814
|
-
});
|
|
1815
|
-
}
|
|
1816
|
-
}), (stub) => Effect.sync(() => {
|
|
1817
|
-
stub[Symbol.dispose]?.();
|
|
1818
|
-
}));
|
|
1819
|
-
const server = yield* serveHostCalls.pipe(Effect.forkScoped);
|
|
1820
|
-
const rpc = Effect.tryPromise({
|
|
1821
|
-
try: async () => {
|
|
1822
|
-
return await worker.getEntrypoint().run();
|
|
1823
|
-
},
|
|
1824
|
-
catch: (cause) => classifyWorkerFailure(cause, request.limits.maxWallTime)
|
|
1825
|
-
});
|
|
1826
|
-
const raw = yield* Effect.raceFirst(rpc, Fiber.join(server)).pipe(Effect.timeoutOrElse({
|
|
1827
|
-
duration: request.limits.maxWallTime,
|
|
1828
|
-
orElse: () => CodeExecutionTimeoutError.make({
|
|
1829
|
-
implementation: dynamicWorkerImplementation,
|
|
1830
|
-
kind: "wall-clock",
|
|
1831
|
-
maxWallTime: request.limits.maxWallTime,
|
|
1832
|
-
logs: []
|
|
1833
|
-
})
|
|
1834
|
-
}), Effect.ensuring(Fiber.interrupt(server)));
|
|
1835
|
-
const finishedAt = yield* Clock.currentTimeMillis;
|
|
1836
|
-
const outcome = decodeHarnessOutcome(raw);
|
|
1837
|
-
if (Option.isNone(outcome)) return yield* CodeExecutionProtocolError.make({
|
|
1838
|
-
implementation: dynamicWorkerImplementation,
|
|
1839
|
-
message: "The dynamic worker returned a value outside the harness envelope schema"
|
|
1840
|
-
});
|
|
1841
|
-
switch (outcome.value._tag) {
|
|
1842
|
-
case "completed": return CodeExecutionResult.make({
|
|
1843
|
-
implementation: dynamicWorkerImplementation,
|
|
1844
|
-
value: outcome.value.value,
|
|
1845
|
-
logs: outcome.value.logs,
|
|
1846
|
-
resourceUse: CodeExecutionResourceUse.make({
|
|
1847
|
-
wallTime: Duration.millis(Math.max(0, finishedAt - startedAt)),
|
|
1848
|
-
hostCalls: outcome.value.hostCalls,
|
|
1849
|
-
logBytes: outcome.value.logBytes,
|
|
1850
|
-
resultBytes: outcome.value.resultBytes
|
|
1851
|
-
})
|
|
1852
|
-
});
|
|
1853
|
-
case "source-invalid": return yield* CodeSourceError.make({
|
|
1854
|
-
implementation: dynamicWorkerImplementation,
|
|
1855
|
-
reason: "invalid",
|
|
1856
|
-
message: outcome.value.message.slice(0, 8e3)
|
|
1857
|
-
});
|
|
1858
|
-
case "source-not-a-function": return yield* CodeSourceError.make({
|
|
1859
|
-
implementation: dynamicWorkerImplementation,
|
|
1860
|
-
reason: "not-a-function",
|
|
1861
|
-
message: `The source expression evaluated to ${outcome.value.actual}; it must evaluate to one async function`
|
|
1862
|
-
});
|
|
1863
|
-
case "program-failed": return yield* CodeProgramFailedError.make({
|
|
1864
|
-
implementation: dynamicWorkerImplementation,
|
|
1865
|
-
reason: outcome.value.reason,
|
|
1866
|
-
thrown: outcome.value.thrown,
|
|
1867
|
-
message: outcome.value.message.slice(0, 8e3),
|
|
1868
|
-
logs: outcome.value.logs
|
|
1869
|
-
});
|
|
1870
|
-
case "log-limit": return yield* CodeOutputLimitError.make({
|
|
1871
|
-
implementation: dynamicWorkerImplementation,
|
|
1872
|
-
surface: "logs",
|
|
1873
|
-
limit: request.limits.maxLogBytes,
|
|
1874
|
-
observed: outcome.value.observed,
|
|
1875
|
-
logs: outcome.value.logs
|
|
1876
|
-
});
|
|
1877
|
-
case "argument-limit": return yield* CodeOutputLimitError.make({
|
|
1878
|
-
implementation: dynamicWorkerImplementation,
|
|
1879
|
-
surface: "host-call-argument",
|
|
1880
|
-
limit: request.limits.maxHostCallArgumentBytes,
|
|
1881
|
-
observed: outcome.value.observed,
|
|
1882
|
-
logs: outcome.value.logs
|
|
1883
|
-
});
|
|
1884
|
-
case "result-limit": return yield* CodeOutputLimitError.make({
|
|
1885
|
-
implementation: dynamicWorkerImplementation,
|
|
1886
|
-
surface: "result",
|
|
1887
|
-
limit: request.limits.maxResultBytes,
|
|
1888
|
-
observed: outcome.value.observed,
|
|
1889
|
-
logs: outcome.value.logs
|
|
1890
|
-
});
|
|
1891
|
-
case "host-call-limit": return yield* CodeHostCallLimitError.make({
|
|
1892
|
-
implementation: dynamicWorkerImplementation,
|
|
1893
|
-
limit: request.limits.maxHostCalls,
|
|
1894
|
-
logs: outcome.value.logs
|
|
1895
|
-
});
|
|
1896
|
-
case "protocol": return yield* CodeExecutionProtocolError.make({
|
|
1897
|
-
implementation: dynamicWorkerImplementation,
|
|
1898
|
-
message: outcome.value.message.slice(0, 8e3)
|
|
1899
|
-
});
|
|
1900
|
-
}
|
|
1901
|
-
});
|
|
1902
|
-
/**
|
|
1903
|
-
* Expected worker-level failures map into the typed union with bounded
|
|
1904
|
-
* diagnostics; anything unrecognized stays a start/termination error rather
|
|
1905
|
-
* than a fabricated program result.
|
|
1906
|
-
*/
|
|
1907
|
-
const classifyWorkerFailure = (cause, maxWallTime) => {
|
|
1908
|
-
const text = (() => {
|
|
1909
|
-
try {
|
|
1910
|
-
return cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause);
|
|
1911
|
-
} catch {
|
|
1912
|
-
return "[unserializable worker failure]";
|
|
1913
|
-
}
|
|
1914
|
-
})();
|
|
1915
|
-
if (/syntaxerror|failed to (compile|parse)/i.test(text)) return CodeSourceError.make({
|
|
1916
|
-
implementation: dynamicWorkerImplementation,
|
|
1917
|
-
reason: "invalid",
|
|
1918
|
-
message: text.slice(0, 8e3)
|
|
1919
|
-
});
|
|
1920
|
-
if (/cpu/i.test(text)) return CodeExecutionTimeoutError.make({
|
|
1921
|
-
implementation: dynamicWorkerImplementation,
|
|
1922
|
-
kind: "cpu",
|
|
1923
|
-
maxWallTime,
|
|
1924
|
-
logs: []
|
|
1925
|
-
});
|
|
1926
|
-
if (/failed to start worker/i.test(text)) return CodeExecutorStartError.make({
|
|
1927
|
-
implementation: dynamicWorkerImplementation,
|
|
1928
|
-
message: text.slice(0, 8e3),
|
|
1929
|
-
cause
|
|
1930
|
-
});
|
|
1931
|
-
return CodeExecutorTerminatedError.make({
|
|
1932
|
-
implementation: dynamicWorkerImplementation,
|
|
1933
|
-
message: text.slice(0, 8e3)
|
|
1934
|
-
});
|
|
1935
|
-
};
|
|
1936
|
-
/** Layer building the Dynamic Worker `CodeExecutor` from resolved bindings. */
|
|
1937
|
-
const dynamicWorkerCodeExecutorLayer = (options) => Layer.succeed(CodeExecutor)(CodeExecutor.of({ execute: makeExecute(options) }));
|
|
1938
|
-
//#endregion
|
|
1939
|
-
export { AbortRecorded, AdminExplainRequest, AdminFailed, AdminFailure, AdminResponse, AdminVerifyRequest, AdmissionLimitExceeded, ApprovalRecorded, CLOUDFLARE_DATABASE_CAP_BYTES, CLOUDFLARE_RUNTIME_DEFAULTS, CloudflareAdmissionLimitsValue, CloudflareBindingError, CloudflareConversationClient, CloudflareDurableRuntime, CloudflareDurableRuntimeConfig, CloudflareDurableRuntimeConfigValue, CloudflarePlatformConfigError, CloudflareRuntimeTelemetry, CloudflareTelemetryExportError, CodeModeHostEntrypoint, ConversationClientError, ConversationMaintenance, ConversationObjectIdentity, ConversationObjectNamespace, ConversationObjectPorts, DEFAULT_MAX_DATABASE_BYTES, DurableAlarmError, DurableAlarmService, DurableObjectContext, ExplainedRecovery, HostFailed, HostFailure, HostProtocolError, HostResponse, MaintenancePassReport, ObligationsScanned, ObservePageRequest, ObservedPage, RetryExecuted, SettlementReached, SubmitRequest, SubmitSucceeded, UnknownResolutionRecorded, VerifiedIntegrity, boundHostDiagnostic, cloudflareWakeSchedulerLayer, conversationNamespaceFromEnv, conversationNamespaceLayer, conversationPortTransportLayer, decodeAbortCommand, decodeAdminExplainRequest, decodeAdminResponse, decodeAdminVerifyRequest, decodeApprovalDecisionCommand, decodeHostResponse, decodeObligationThresholds, decodeObservePageRequest, decodeReceipt, decodeRetryCommand, decodeSubmitRequest, decodeUnknownResolutionCommand, dynamicWorkerCodeExecutorLayer, dynamicWorkerImplementation, encodeAbortCommand, encodeAdminResponse, encodeApprovalDecisionCommand, encodeHostResponse, encodeObservePageRequest, encodeReceipt, encodeSubmitRequest, encodeUnknownResolutionCommand, makeConversationObjectClass };
|
|
1940
|
-
|
|
1941
|
-
//# sourceMappingURL=index.mjs.map
|
|
1
|
+
import { t as CloudflareBindings_exports } from "./CloudflareBindings.mjs";
|
|
2
|
+
import { t as CloudflareConfig_exports } from "./CloudflareConfig.mjs";
|
|
3
|
+
import { t as Alarm_exports } from "./Alarm.mjs";
|
|
4
|
+
import { t as CloudflareBrowser_exports } from "./CloudflareBrowser-DV6kya1H.mjs";
|
|
5
|
+
import { t as CloudflareCodeMode_exports } from "./CloudflareCodeMode.mjs";
|
|
6
|
+
import { t as CloudflareMemory_exports } from "./CloudflareMemory.mjs";
|
|
7
|
+
import { t as CloudflareThreadClient_exports } from "./CloudflareThreadClient.mjs";
|
|
8
|
+
import { t as CloudflareScheduling_exports } from "./CloudflareScheduling.mjs";
|
|
9
|
+
import { t as CloudflareSubscriptions_exports } from "./CloudflareSubscriptions.mjs";
|
|
10
|
+
import { t as WakeScheduler_exports } from "./WakeScheduler.mjs";
|
|
11
|
+
import { l as ThreadObject_exports } from "./ThreadObject-DXavwaU-.mjs";
|
|
12
|
+
import { t as CloudflareAiGateway_exports } from "./CloudflareAiGateway.mjs";
|
|
13
|
+
export { Alarm_exports as Alarm, CloudflareAiGateway_exports as CloudflareAiGateway, CloudflareBindings_exports as CloudflareBindings, CloudflareBrowser_exports as CloudflareBrowser, CloudflareCodeMode_exports as CloudflareCodeMode, CloudflareConfig_exports as CloudflareConfig, CloudflareMemory_exports as CloudflareMemory, CloudflareScheduling_exports as CloudflareScheduling, CloudflareSubscriptions_exports as CloudflareSubscriptions, CloudflareThreadClient_exports as CloudflareThreadClient, ThreadObject_exports as ThreadObject, WakeScheduler_exports as WakeScheduler };
|