@effect-agent/platform-cloudflare 0.0.1-beta.5 → 0.1.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,10 +1,12 @@
1
- import { Clock, Context, Duration, Effect, Layer, ManagedRuntime, Option, PubSub, Random, Ref, Schema, Stream } from "effect";
1
+ import { Clock, Context, Duration, Effect, Fiber, Layer, Option, PubSub, Random, Ref, Schema, Stream } from "effect";
2
2
  import { AbortCommand, AbortIntent, AdmissionConflict, AgentBindingResolver, AppendConflict, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, CanonicalRecordEnvelope, CanonicalSequence, ConversationNotMaterialized, ConversationRead, ConversationStore, ConversationStoreError, DEFAULT_OWNERSHIP_LEASE_DURATION, DefinitionDigests, DeploymentId, DigestError, DurableAgentRuntime, DurableRuntimeConfig, DurableRuntimeFailpoint, DurableRuntimeFailpointError, FenceRejected, IdempotencyKey, IntegrityReport, JoinedToHost, LedgerError, ObligationReport, ObligationThresholds, OperationAuthorizationRequest, OperationAuthorizer, OperationDenied, OwnershipLost, PersistedJson, Principal, ProducerId, Receipt, RecoveryExplanation, RecoveryReport, RetryCommand, RetryRefused, RunJournalError, Settlement, SettlementConflict, SubmissionLedger, SubmissionLookupByKey, ToolReconciler, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, WakeScheduler } from "@effect-agent/session";
3
3
  import { ConversationPortTransport, DEFAULT_MAX_STORED_VALUE_BYTES, conversationStoreLayer, handleEncodedPortRequest, portTransportFailure, routedConversationStoreLayer, routedSubmissionLedgerLayer, storageConfigLayer, storageFailpointLayer, submissionLedgerLayer } from "@effect-agent/storage-cloudflare";
4
4
  import { AgentId, AgentInputError, ConversationId, SubmissionId } from "@effect-agent/core";
5
5
  import { BrowserCrypto } from "@effect/platform-browser";
6
6
  import { SqliteClient } from "@effect/sql-sqlite-do";
7
- import { DurableObject } from "cloudflare:workers";
7
+ import { DurableObject, DurableObjectState, WorkerEnvironment } from "effect-cf";
8
+ import { CodeExecutionHost, CodeExecutionProtocolError, CodeExecutionResourceUse, CodeExecutionResult, CodeExecutionTimeoutError, CodeExecutor, CodeExecutorStartError, CodeExecutorTerminatedError, CodeExecutorUnsupportedError, CodeHostCall, CodeHostCallLimitError, CodeOutputLimitError, CodeProgramFailedError, CodeSourceError, SandboxImplementation } from "@effect-agent/sandbox";
9
+ import { WorkerEntrypoint } from "cloudflare:workers";
8
10
  //#region src/bindings.ts
9
11
  /**
10
12
  * Cloudflare platform bindings as Effect services (DEPLOY-010: "Cloudflare platform bindings
@@ -1017,62 +1019,618 @@ const gateEndpoint = Effect.gen(function* () {
1017
1019
  yield* (yield* ConversationMaintenance).ensureAlarm;
1018
1020
  });
1019
1021
  /**
1020
- * Build the application's Conversation Object class (export it from the
1021
- * Worker entry). The explicit return type is what makes declaration emit
1022
- * possible: the class body carries a private runtime field, and TS4094
1023
- * rejects inferring an exported anonymous class type around it.
1022
+ * Adapter from effect-cf's native Durable Object services to Effect Agent's existing platform
1023
+ * ports. effect-cf owns the cached ManagedRuntime and supplies these values once per Object
1024
+ * incarnation; the durable runtime continues to depend only on the narrow services below.
1024
1025
  */
1025
- const makeConversationObjectClass = (options) => {
1026
- class ConversationObject extends DurableObject {
1027
- #runtime;
1028
- constructor(ctx, env) {
1029
- super(ctx, env);
1030
- this.#runtime = ManagedRuntime.make(CloudflareDurableRuntime.layer(options).pipe(Layer.provideMerge(Layer.mergeAll(DurableObjectContext.layer(ctx, env), conversationNamespaceLayer(env, options.namespaceBinding)))));
1031
- ctx.blockConcurrencyWhile(() => this.#runtime.runPromise(gateEndpoint));
1032
- }
1033
- async submitEncoded(encoded) {
1034
- return this.#runtime.runPromise(submitEndpoint(encoded));
1035
- }
1036
- async awaitSettlementEncoded(encoded) {
1037
- return this.#runtime.runPromise(awaitSettlementEndpoint(encoded));
1038
- }
1039
- async observePage(encoded) {
1040
- return this.#runtime.runPromise(observePageEndpoint(encoded));
1041
- }
1042
- async abortEncoded(encoded) {
1043
- return this.#runtime.runPromise(abortEndpoint(encoded));
1044
- }
1045
- async resolveApprovalEncoded(encoded) {
1046
- return this.#runtime.runPromise(resolveApprovalEndpoint(encoded));
1047
- }
1048
- async resolveUnknownEncoded(encoded) {
1049
- return this.#runtime.runPromise(resolveUnknownEndpoint(encoded));
1050
- }
1051
- async explainEncoded(encoded) {
1052
- return this.#runtime.runPromise(explainEndpoint(encoded));
1053
- }
1054
- async verifyEncoded(encoded) {
1055
- return this.#runtime.runPromise(verifyEndpoint(encoded));
1056
- }
1057
- async retryEncoded(encoded) {
1058
- return this.#runtime.runPromise(retryEndpoint(encoded));
1059
- }
1060
- async obligationsEncoded(encoded) {
1061
- return this.#runtime.runPromise(obligationsEndpoint(encoded));
1026
+ const effectCfPlatformLayer = (namespaceBinding) => {
1027
+ const context = Layer.effect(DurableObjectContext)(Effect.gen(function* () {
1028
+ const state = yield* DurableObjectState.DurableObjectState;
1029
+ const env = yield* WorkerEnvironment;
1030
+ return DurableObjectContext.of({
1031
+ ctx: state.raw,
1032
+ env
1033
+ });
1034
+ }));
1035
+ const namespace = Layer.effect(ConversationObjectNamespace)(Effect.gen(function* () {
1036
+ const binding = yield* conversationNamespaceFromEnv(yield* WorkerEnvironment, namespaceBinding);
1037
+ return ConversationObjectNamespace.of({ namespace: binding });
1038
+ }));
1039
+ return Layer.merge(context, namespace);
1040
+ };
1041
+ /**
1042
+ * Build the application's Conversation Object class (export it from the Worker entry).
1043
+ * effect-cf owns the cached ManagedRuntime, native RPC methods, event scopes, and post-handler
1044
+ * OTLP flush scheduling for RPC and alarm events. The optional outer Layer is built per native
1045
+ * event, so a host can install Tracer/Logger/Metric services and `OtlpExporter.Flusher` without
1046
+ * Effect Agent owning exporter lifecycle machinery.
1047
+ */
1048
+ const makeConversationObjectClass = (options, observability) => {
1049
+ const application = CloudflareDurableRuntime.layer(options).pipe(Layer.provideMerge(effectCfPlatformLayer(options.namespaceBinding)));
1050
+ const runtime = Layer.effectContext(Effect.gen(function* () {
1051
+ const state = yield* DurableObjectState.DurableObjectState;
1052
+ const scope = yield* Effect.scope;
1053
+ return yield* state.blockConcurrencyWhile(Effect.gen(function* () {
1054
+ const services = yield* Layer.buildWithScope(application, scope);
1055
+ yield* gateEndpoint.pipe(Effect.provide(services));
1056
+ return services;
1057
+ }));
1058
+ }));
1059
+ const rpc = {
1060
+ submitEncoded: (encoded) => submitEndpoint(encoded),
1061
+ awaitSettlementEncoded: (encoded) => awaitSettlementEndpoint(encoded),
1062
+ observePage: (encoded) => observePageEndpoint(encoded),
1063
+ abortEncoded: (encoded) => abortEndpoint(encoded),
1064
+ resolveApprovalEncoded: (encoded) => resolveApprovalEndpoint(encoded),
1065
+ resolveUnknownEncoded: (encoded) => resolveUnknownEndpoint(encoded),
1066
+ explainEncoded: (encoded) => explainEndpoint(encoded),
1067
+ verifyEncoded: (encoded) => verifyEndpoint(encoded),
1068
+ retryEncoded: (encoded) => retryEndpoint(encoded),
1069
+ obligationsEncoded: (encoded) => obligationsEndpoint(encoded),
1070
+ portCall: (encoded) => portCallEndpoint(encoded),
1071
+ wake: () => wakeEndpoint
1072
+ };
1073
+ const EffectCfConversationObject = DurableObject.make(runtime, {
1074
+ ...observability === void 0 ? {} : { eventLayer: observability },
1075
+ initialize: Effect.void,
1076
+ rpc,
1077
+ alarm: () => alarmEndpoint
1078
+ });
1079
+ class ConversationObject extends EffectCfConversationObject {
1080
+ alarm(alarmInfo) {
1081
+ return super.alarm?.(alarmInfo);
1062
1082
  }
1063
- async portCall(encoded) {
1064
- return this.#runtime.runPromise(portCallEndpoint(encoded));
1083
+ }
1084
+ return ConversationObject;
1085
+ };
1086
+ //#endregion
1087
+ //#region src/code-mode-executor.ts
1088
+ /**
1089
+ * The Cloudflare Dynamic Worker `CodeExecutor` adapter (C4 of ADR-0017;
1090
+ * DEPLOY-011). Each pass loads one fresh Worker through the Worker Loader
1091
+ * with `globalOutbound: null`, so generated code has no ambient network,
1092
+ * bindings, or secrets; its only authority is the pass-scoped host stub that
1093
+ * routes back to `CodeModeHostEntrypoint` and, from there, into the pass's
1094
+ * `CodeExecutionHost` service. Platform CPU limits stop synchronous runaway
1095
+ * programs; the executor-owned wall-clock deadline interrupts asynchronously
1096
+ * suspended passes. Deployment class `E` only: the adapter records no
1097
+ * persistent state and a later pass may run in a completely different
1098
+ * isolate.
1099
+ */
1100
+ const dynamicWorkerImplementation = SandboxImplementation.make({
1101
+ isolation: "isolated",
1102
+ identity: "cloudflare-dynamic-worker"
1103
+ });
1104
+ /**
1105
+ * Live passes by identity. Entries are Scope-managed: registered when a pass
1106
+ * opens and removed by its finalizer, so a stale harness (or a forged
1107
+ * `passId`) cannot reach any host authority.
1108
+ */
1109
+ const passRegistry = /* @__PURE__ */ new Map();
1110
+ /**
1111
+ * The host-side RPC target for dynamic workers. The application exposes it
1112
+ * from its Worker entry (`export { CodeModeHostEntrypoint }`) and hands the
1113
+ * adapter a same-instance stub — `ctx.exports.CodeModeHostEntrypoint()` in
1114
+ * production (a self service binding may reach a different instance and must
1115
+ * not be used there); tests bind it through Miniflare's `kCurrentWorker`.
1116
+ */
1117
+ var CodeModeHostEntrypoint = class extends WorkerEntrypoint {
1118
+ async call(passId, hostCall) {
1119
+ const pass = passRegistry.get(String(passId));
1120
+ if (pass === void 0) throw new Error("Unknown Code Mode pass");
1121
+ return pass.dispatch(hostCall);
1122
+ }
1123
+ };
1124
+ /**
1125
+ * The fixed harness loaded as the dynamic worker's main module. The generated
1126
+ * source becomes `program.mjs` (`export default (<expression>);`) — a module,
1127
+ * never `eval`. The harness installs namespace globals and a bounded console,
1128
+ * imports the program, invokes it exactly once, and returns one envelope the
1129
+ * host validates through Effect Schema.
1130
+ */
1131
+ const HARNESS_MODULE = String.raw`
1132
+ import { WorkerEntrypoint } from "cloudflare:workers";
1133
+ import programDefault from "./program.js";
1134
+
1135
+ const encoder = new TextEncoder();
1136
+ const utf8 = (text) => encoder.encode(text).byteLength;
1137
+ const safeText = (value) => {
1138
+ try {
1139
+ if (value instanceof Error) return (value.name + ": " + value.message).slice(0, 4000);
1140
+ if (typeof value === "string") return value.slice(0, 4000);
1141
+ const encoded = JSON.stringify(value);
1142
+ return (encoded === undefined ? String(value) : encoded).slice(0, 4000);
1143
+ } catch {
1144
+ return "[unserializable value]";
1145
+ }
1146
+ };
1147
+ const safeJson = (value) => {
1148
+ try {
1149
+ const encoded = JSON.stringify(value);
1150
+ if (encoded !== undefined && encoded.length <= 4000) return JSON.parse(encoded);
1151
+ } catch {}
1152
+ return safeText(value);
1153
+ };
1154
+
1155
+ export default class CodeModeHarness extends WorkerEntrypoint {
1156
+ async run() {
1157
+ const config = JSON.parse(this.env.CODE_MODE_PASS);
1158
+ const host = this.env.CODE_MODE_HOST;
1159
+ const limits = config.limits;
1160
+ const logs = [];
1161
+ let logBytes = 0;
1162
+ let fatal;
1163
+ const boundedLogs = () => logs.slice(0, 4096);
1164
+ const write = (...values) => {
1165
+ const joined = values.map(safeText).join(" ");
1166
+ const line = joined.length > 16000 ? joined.slice(0, 15999) + "…" : joined;
1167
+ const bytes = utf8(line);
1168
+ if (logs.length >= 4096 || logBytes + bytes > limits.maxLogBytes) {
1169
+ fatal = fatal ?? { _tag: "log-limit", observed: logBytes + bytes, logs: boundedLogs() };
1170
+ throw new Error("code-mode log limit exceeded");
1171
+ }
1172
+ logs.push(line);
1173
+ logBytes += bytes;
1174
+ };
1175
+ globalThis.console = { log: write, info: write, warn: write, error: write, debug: write };
1176
+
1177
+ let hostCalls = 0;
1178
+ const makeMethod = (namespace, method) => async (argument) => {
1179
+ hostCalls += 1;
1180
+ if (hostCalls > limits.maxHostCalls) {
1181
+ fatal = fatal ?? { _tag: "host-call-limit", logs: boundedLogs() };
1182
+ throw new Error("code-mode host-call limit exceeded");
1183
+ }
1184
+ let argText;
1185
+ try {
1186
+ argText = JSON.stringify(argument);
1187
+ } catch {}
1188
+ if (argText === undefined || utf8(argText) > limits.maxHostCallArgumentBytes) {
1189
+ fatal = fatal ?? {
1190
+ _tag: "argument-limit",
1191
+ observed: argText === undefined ? 0 : utf8(argText),
1192
+ logs: boundedLogs(),
1193
+ };
1194
+ throw new Error("code-mode host-call argument limit exceeded");
1195
+ }
1196
+ const outcome = await host.call(config.passId, {
1197
+ namespace,
1198
+ method,
1199
+ argument: JSON.parse(argText),
1200
+ });
1201
+ if (outcome !== null && typeof outcome === "object" && outcome._tag === "CodeHostCallSuccess") {
1202
+ return outcome.value;
1203
+ }
1204
+ if (outcome !== null && typeof outcome === "object" && outcome._tag === "CodeHostCallFailure") {
1205
+ throw outcome.error;
1206
+ }
1207
+ fatal = fatal ?? { _tag: "protocol", message: "host returned an unrecognized outcome" };
1208
+ throw new Error("code-mode host protocol violation");
1209
+ };
1210
+ for (const namespace of config.namespaces) {
1211
+ const methods = {};
1212
+ for (const method of namespace.methods) {
1213
+ methods[method] = makeMethod(namespace.name, method);
1214
+ }
1215
+ globalThis[namespace.name] = methods;
1216
+ }
1217
+
1218
+ // program.js is imported statically at the top of this module, so a
1219
+ // syntactically invalid program fails the whole harness at load (mapped
1220
+ // to a source error by the host). Using a static import keeps this module
1221
+ // free of dynamic-import expressions, which single-script Miniflare hosts
1222
+ // reject. The isolation boundary does NOT depend on the ordering of this
1223
+ // import versus the console/namespace shims installed below: the loaded
1224
+ // Worker has globalOutbound: null and no bindings, secrets, or env from
1225
+ // the Worker Loader config BEFORE any module in the graph evaluates, so
1226
+ // module-level program code has no ambient authority regardless. The
1227
+ // shims below are usability wrappers (bounded console, namespace globals),
1228
+ // and the accepted program is a single async-function expression whose
1229
+ // body runs only when invoked here — after the shims exist.
1230
+ const program = programDefault;
1231
+ if (typeof program !== "function") {
1232
+ return { _tag: "source-not-a-function", actual: typeof program };
1233
+ }
1234
+ try {
1235
+ const value = await program();
1236
+ if (fatal !== undefined) return fatal;
1237
+ let text;
1238
+ try {
1239
+ text = JSON.stringify(value);
1240
+ } catch {}
1241
+ if (text === undefined) {
1242
+ return {
1243
+ _tag: "program-failed",
1244
+ reason: "non-json-result",
1245
+ thrown: null,
1246
+ message: "The program must return a JSON value",
1247
+ logs: boundedLogs(),
1248
+ };
1249
+ }
1250
+ const resultBytes = utf8(text);
1251
+ if (resultBytes > limits.maxResultBytes) {
1252
+ return { _tag: "result-limit", observed: resultBytes, logs: boundedLogs() };
1253
+ }
1254
+ return {
1255
+ _tag: "completed",
1256
+ value: JSON.parse(text),
1257
+ logs: boundedLogs(),
1258
+ hostCalls,
1259
+ logBytes,
1260
+ resultBytes,
1261
+ };
1262
+ } catch (cause) {
1263
+ if (fatal !== undefined) return fatal;
1264
+ return {
1265
+ _tag: "program-failed",
1266
+ reason: cause instanceof Error ? "threw" : "rejected",
1267
+ thrown: safeJson(cause),
1268
+ message: safeText(cause),
1269
+ logs: boundedLogs(),
1270
+ };
1271
+ }
1272
+ }
1273
+ }
1274
+ `;
1275
+ const BoundedLogs = Schema.Array(Schema.String.check(Schema.isMaxLength(16 * 1024))).check(Schema.isMaxLength(4096));
1276
+ const HarnessCompleted = Schema.Struct({
1277
+ _tag: Schema.Literal("completed"),
1278
+ value: Schema.Json,
1279
+ logs: BoundedLogs,
1280
+ hostCalls: Schema.Natural,
1281
+ logBytes: Schema.Natural,
1282
+ resultBytes: Schema.Natural
1283
+ });
1284
+ const HarnessSourceInvalid = Schema.Struct({
1285
+ _tag: Schema.Literal("source-invalid"),
1286
+ message: Schema.String
1287
+ });
1288
+ const HarnessNotAFunction = Schema.Struct({
1289
+ _tag: Schema.Literal("source-not-a-function"),
1290
+ actual: Schema.String
1291
+ });
1292
+ const HarnessProgramFailed = Schema.Struct({
1293
+ _tag: Schema.Literal("program-failed"),
1294
+ reason: Schema.Literals([
1295
+ "threw",
1296
+ "rejected",
1297
+ "non-json-result"
1298
+ ]),
1299
+ thrown: Schema.Json,
1300
+ message: Schema.String,
1301
+ logs: BoundedLogs
1302
+ });
1303
+ const HarnessLogLimit = Schema.Struct({
1304
+ _tag: Schema.Literal("log-limit"),
1305
+ observed: Schema.Natural,
1306
+ logs: BoundedLogs
1307
+ });
1308
+ const HarnessArgumentLimit = Schema.Struct({
1309
+ _tag: Schema.Literal("argument-limit"),
1310
+ observed: Schema.Natural,
1311
+ logs: BoundedLogs
1312
+ });
1313
+ const HarnessResultLimit = Schema.Struct({
1314
+ _tag: Schema.Literal("result-limit"),
1315
+ observed: Schema.Natural,
1316
+ logs: BoundedLogs
1317
+ });
1318
+ const HarnessHostCallLimit = Schema.Struct({
1319
+ _tag: Schema.Literal("host-call-limit"),
1320
+ logs: BoundedLogs
1321
+ });
1322
+ const HarnessProtocol = Schema.Struct({
1323
+ _tag: Schema.Literal("protocol"),
1324
+ message: Schema.String
1325
+ });
1326
+ const HarnessOutcome = Schema.Union([
1327
+ HarnessCompleted,
1328
+ HarnessSourceInvalid,
1329
+ HarnessNotAFunction,
1330
+ HarnessProgramFailed,
1331
+ HarnessLogLimit,
1332
+ HarnessArgumentLimit,
1333
+ HarnessResultLimit,
1334
+ HarnessHostCallLimit,
1335
+ HarnessProtocol
1336
+ ]);
1337
+ const decodeHarnessOutcome = (value) => {
1338
+ try {
1339
+ return Schema.decodeUnknownOption(HarnessOutcome)(value);
1340
+ } catch {
1341
+ return Option.none();
1342
+ }
1343
+ };
1344
+ const decodeHostCall = (value) => {
1345
+ try {
1346
+ return Schema.decodeUnknownOption(CodeHostCall)(value);
1347
+ } catch {
1348
+ return Option.none();
1349
+ }
1350
+ };
1351
+ /**
1352
+ * Project a host outcome to the plain JSON envelope the harness reads. A
1353
+ * `CodeExecutionHost` may return either real `CodeHostCallResult` instances
1354
+ * (the substitute and conformance kit) or plain-object equivalents (the Code
1355
+ * Mode capability's broker route), so this reads the shared fields rather than
1356
+ * `Schema.encodeSync`, which would reject a plain object.
1357
+ */
1358
+ const hostResultEnvelope = (outcome) => outcome._tag === "CodeHostCallSuccess" ? {
1359
+ _tag: "CodeHostCallSuccess",
1360
+ value: outcome.value
1361
+ } : {
1362
+ _tag: "CodeHostCallFailure",
1363
+ error: outcome.error
1364
+ };
1365
+ const utf8ByteLength = (value) => {
1366
+ let total = 0;
1367
+ for (const character of value) {
1368
+ const codePoint = character.codePointAt(0) ?? 0;
1369
+ total += codePoint <= 127 ? 1 : codePoint <= 2047 ? 2 : codePoint <= 65535 ? 3 : 4;
1370
+ }
1371
+ return total;
1372
+ };
1373
+ const encodedJsonByteLength = (value) => {
1374
+ try {
1375
+ const encoded = JSON.stringify(value);
1376
+ return encoded === void 0 ? void 0 : utf8ByteLength(encoded);
1377
+ } catch {
1378
+ return;
1379
+ }
1380
+ };
1381
+ /** Reserved global names the harness owns inside the dynamic worker. */
1382
+ const reservedHarnessGlobals = /* @__PURE__ */ new Set(["console"]);
1383
+ const passCounterState = { next: 0 };
1384
+ const makeExecute = (options) => Effect.fn("DynamicWorkerCodeExecutor.execute")(function* (request) {
1385
+ if (request.network._tag !== "NetworkDisabled") return yield* CodeExecutorUnsupportedError.make({
1386
+ implementation: dynamicWorkerImplementation,
1387
+ feature: "network",
1388
+ message: "The Dynamic Worker executor denies all egress with globalOutbound: null; an allowlist is not supported in the first slice"
1389
+ });
1390
+ const sourceBytes = utf8ByteLength(request.source);
1391
+ if (sourceBytes > request.limits.maxSourceBytes) return yield* CodeSourceError.make({
1392
+ implementation: dynamicWorkerImplementation,
1393
+ reason: "oversized",
1394
+ message: `Source is ${sourceBytes} bytes; the request allows ${request.limits.maxSourceBytes}`
1395
+ });
1396
+ for (const namespace of request.namespaces) if (reservedHarnessGlobals.has(namespace.name)) return yield* CodeExecutorUnsupportedError.make({
1397
+ implementation: dynamicWorkerImplementation,
1398
+ feature: "namespaces",
1399
+ message: `Namespace ${namespace.name} collides with a harness binding`
1400
+ });
1401
+ const host = yield* CodeExecutionHost;
1402
+ passCounterState.next += 1;
1403
+ const passId = `code-mode-pass-${passCounterState.next}-${crypto.randomUUID()}`;
1404
+ const pending = [];
1405
+ let wake;
1406
+ let issuedHostCalls = 0;
1407
+ const dispatch = (hostCall) => new Promise((resolve, reject) => {
1408
+ issuedHostCalls += 1;
1409
+ if (issuedHostCalls > request.limits.maxHostCalls + 1) {
1410
+ reject(/* @__PURE__ */ new Error("host-call limit exceeded"));
1411
+ return;
1065
1412
  }
1066
- async wake() {
1067
- await this.#runtime.runPromise(wakeEndpoint);
1413
+ pending.push({
1414
+ hostCall,
1415
+ resolve,
1416
+ reject
1417
+ });
1418
+ wake?.();
1419
+ });
1420
+ yield* Effect.acquireRelease(Effect.sync(() => {
1421
+ passRegistry.set(passId, { dispatch });
1422
+ }), () => Effect.sync(() => {
1423
+ passRegistry.delete(passId);
1424
+ }));
1425
+ const nextPending = Effect.suspend(() => {
1426
+ const item = pending.shift();
1427
+ if (item !== void 0) return Effect.succeed(item);
1428
+ return Effect.callback((resume) => {
1429
+ wake = () => {
1430
+ wake = void 0;
1431
+ const next = pending.shift();
1432
+ if (next !== void 0) resume(Effect.succeed(next));
1433
+ };
1434
+ });
1435
+ });
1436
+ const serveHostCalls = Effect.gen(function* () {
1437
+ let served = 0;
1438
+ while (true) {
1439
+ const item = yield* nextPending;
1440
+ served += 1;
1441
+ if (served > request.limits.maxHostCalls) return yield* CodeHostCallLimitError.make({
1442
+ implementation: dynamicWorkerImplementation,
1443
+ limit: request.limits.maxHostCalls,
1444
+ logs: []
1445
+ });
1446
+ const decoded = decodeHostCall(item.hostCall);
1447
+ if (Option.isNone(decoded)) {
1448
+ item.reject(/* @__PURE__ */ new TypeError("host calls must match the CodeHostCall schema"));
1449
+ continue;
1450
+ }
1451
+ const outcome = yield* host.call(decoded.value);
1452
+ if (outcome._tag === "CodeHostCallSuccess") {
1453
+ const bytes = encodedJsonByteLength(outcome.value);
1454
+ if (bytes === void 0 || bytes > request.limits.maxHostCallResultBytes) return yield* CodeOutputLimitError.make({
1455
+ implementation: dynamicWorkerImplementation,
1456
+ surface: "host-call-result",
1457
+ limit: request.limits.maxHostCallResultBytes,
1458
+ observed: bytes ?? 0,
1459
+ logs: []
1460
+ });
1461
+ }
1462
+ item.resolve(hostResultEnvelope(outcome));
1068
1463
  }
1069
- async alarm() {
1070
- await this.#runtime.runPromise(alarmEndpoint);
1464
+ });
1465
+ const workerCode = {
1466
+ compatibilityDate: options.compatibilityDate ?? "2025-05-01",
1467
+ allowExperimental: true,
1468
+ mainModule: "harness.js",
1469
+ modules: {
1470
+ "harness.js": HARNESS_MODULE,
1471
+ "program.js": `export default (\n${request.source}\n);`
1472
+ },
1473
+ env: {
1474
+ CODE_MODE_HOST: options.hostStub,
1475
+ CODE_MODE_PASS: JSON.stringify({
1476
+ passId,
1477
+ namespaces: request.namespaces.map((namespace) => ({
1478
+ name: namespace.name,
1479
+ methods: namespace.methods
1480
+ })),
1481
+ limits: {
1482
+ maxLogBytes: request.limits.maxLogBytes,
1483
+ maxResultBytes: request.limits.maxResultBytes,
1484
+ maxHostCalls: request.limits.maxHostCalls,
1485
+ maxHostCallArgumentBytes: request.limits.maxHostCallArgumentBytes
1486
+ }
1487
+ })
1488
+ },
1489
+ globalOutbound: null,
1490
+ ...request.limits.cpuMillis === void 0 ? {} : { limits: {
1491
+ cpuMs: request.limits.cpuMillis,
1492
+ subRequests: request.limits.maxHostCalls + 8
1493
+ } }
1494
+ };
1495
+ const startedAt = yield* Clock.currentTimeMillis;
1496
+ const worker = yield* Effect.acquireRelease(Effect.try({
1497
+ try: () => options.loader.load(workerCode),
1498
+ catch: (cause) => {
1499
+ const text = cause instanceof Error ? cause.message : String(cause);
1500
+ if (/syntaxerror|failed to (compile|parse)/i.test(text)) return CodeSourceError.make({
1501
+ implementation: dynamicWorkerImplementation,
1502
+ reason: "invalid",
1503
+ message: text.slice(0, 8e3)
1504
+ });
1505
+ return CodeExecutorStartError.make({
1506
+ implementation: dynamicWorkerImplementation,
1507
+ message: `The Worker Loader rejected the pass: ${text}`.slice(0, 8e3),
1508
+ cause
1509
+ });
1071
1510
  }
1511
+ }), (stub) => Effect.sync(() => {
1512
+ stub[Symbol.dispose]?.();
1513
+ }));
1514
+ const server = yield* serveHostCalls.pipe(Effect.forkScoped);
1515
+ const rpc = Effect.tryPromise({
1516
+ try: async () => {
1517
+ return await worker.getEntrypoint().run();
1518
+ },
1519
+ catch: (cause) => classifyWorkerFailure(cause, request.limits.maxWallTime)
1520
+ });
1521
+ const raw = yield* Effect.raceFirst(rpc, Fiber.join(server)).pipe(Effect.timeoutOrElse({
1522
+ duration: request.limits.maxWallTime,
1523
+ orElse: () => CodeExecutionTimeoutError.make({
1524
+ implementation: dynamicWorkerImplementation,
1525
+ kind: "wall-clock",
1526
+ maxWallTime: request.limits.maxWallTime,
1527
+ logs: []
1528
+ })
1529
+ }), Effect.ensuring(Fiber.interrupt(server)));
1530
+ const finishedAt = yield* Clock.currentTimeMillis;
1531
+ const outcome = decodeHarnessOutcome(raw);
1532
+ if (Option.isNone(outcome)) return yield* CodeExecutionProtocolError.make({
1533
+ implementation: dynamicWorkerImplementation,
1534
+ message: "The dynamic worker returned a value outside the harness envelope schema"
1535
+ });
1536
+ switch (outcome.value._tag) {
1537
+ case "completed": return CodeExecutionResult.make({
1538
+ implementation: dynamicWorkerImplementation,
1539
+ value: outcome.value.value,
1540
+ logs: outcome.value.logs,
1541
+ resourceUse: CodeExecutionResourceUse.make({
1542
+ wallTime: Duration.millis(Math.max(0, finishedAt - startedAt)),
1543
+ hostCalls: outcome.value.hostCalls,
1544
+ logBytes: outcome.value.logBytes,
1545
+ resultBytes: outcome.value.resultBytes
1546
+ })
1547
+ });
1548
+ case "source-invalid": return yield* CodeSourceError.make({
1549
+ implementation: dynamicWorkerImplementation,
1550
+ reason: "invalid",
1551
+ message: outcome.value.message.slice(0, 8e3)
1552
+ });
1553
+ case "source-not-a-function": return yield* CodeSourceError.make({
1554
+ implementation: dynamicWorkerImplementation,
1555
+ reason: "not-a-function",
1556
+ message: `The source expression evaluated to ${outcome.value.actual}; it must evaluate to one async function`
1557
+ });
1558
+ case "program-failed": return yield* CodeProgramFailedError.make({
1559
+ implementation: dynamicWorkerImplementation,
1560
+ reason: outcome.value.reason,
1561
+ thrown: outcome.value.thrown,
1562
+ message: outcome.value.message.slice(0, 8e3),
1563
+ logs: outcome.value.logs
1564
+ });
1565
+ case "log-limit": return yield* CodeOutputLimitError.make({
1566
+ implementation: dynamicWorkerImplementation,
1567
+ surface: "logs",
1568
+ limit: request.limits.maxLogBytes,
1569
+ observed: outcome.value.observed,
1570
+ logs: outcome.value.logs
1571
+ });
1572
+ case "argument-limit": return yield* CodeOutputLimitError.make({
1573
+ implementation: dynamicWorkerImplementation,
1574
+ surface: "host-call-argument",
1575
+ limit: request.limits.maxHostCallArgumentBytes,
1576
+ observed: outcome.value.observed,
1577
+ logs: outcome.value.logs
1578
+ });
1579
+ case "result-limit": return yield* CodeOutputLimitError.make({
1580
+ implementation: dynamicWorkerImplementation,
1581
+ surface: "result",
1582
+ limit: request.limits.maxResultBytes,
1583
+ observed: outcome.value.observed,
1584
+ logs: outcome.value.logs
1585
+ });
1586
+ case "host-call-limit": return yield* CodeHostCallLimitError.make({
1587
+ implementation: dynamicWorkerImplementation,
1588
+ limit: request.limits.maxHostCalls,
1589
+ logs: outcome.value.logs
1590
+ });
1591
+ case "protocol": return yield* CodeExecutionProtocolError.make({
1592
+ implementation: dynamicWorkerImplementation,
1593
+ message: outcome.value.message.slice(0, 8e3)
1594
+ });
1072
1595
  }
1073
- return ConversationObject;
1596
+ });
1597
+ /**
1598
+ * Expected worker-level failures map into the typed union with bounded
1599
+ * diagnostics; anything unrecognized stays a start/termination error rather
1600
+ * than a fabricated program result.
1601
+ */
1602
+ const classifyWorkerFailure = (cause, maxWallTime) => {
1603
+ const text = (() => {
1604
+ try {
1605
+ return cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause);
1606
+ } catch {
1607
+ return "[unserializable worker failure]";
1608
+ }
1609
+ })();
1610
+ if (/syntaxerror|failed to (compile|parse)/i.test(text)) return CodeSourceError.make({
1611
+ implementation: dynamicWorkerImplementation,
1612
+ reason: "invalid",
1613
+ message: text.slice(0, 8e3)
1614
+ });
1615
+ if (/cpu/i.test(text)) return CodeExecutionTimeoutError.make({
1616
+ implementation: dynamicWorkerImplementation,
1617
+ kind: "cpu",
1618
+ maxWallTime,
1619
+ logs: []
1620
+ });
1621
+ if (/failed to start worker/i.test(text)) return CodeExecutorStartError.make({
1622
+ implementation: dynamicWorkerImplementation,
1623
+ message: text.slice(0, 8e3),
1624
+ cause
1625
+ });
1626
+ return CodeExecutorTerminatedError.make({
1627
+ implementation: dynamicWorkerImplementation,
1628
+ message: text.slice(0, 8e3)
1629
+ });
1074
1630
  };
1631
+ /** Layer building the Dynamic Worker `CodeExecutor` from resolved bindings. */
1632
+ const dynamicWorkerCodeExecutorLayer = (options) => Layer.succeed(CodeExecutor)(CodeExecutor.of({ execute: makeExecute(options) }));
1075
1633
  //#endregion
1076
- export { AbortRecorded, AdminExplainRequest, AdminFailed, AdminFailure, AdminResponse, AdminVerifyRequest, AdmissionLimitExceeded, ApprovalRecorded, CLOUDFLARE_DATABASE_CAP_BYTES, CLOUDFLARE_RUNTIME_DEFAULTS, CloudflareAdmissionLimitsValue, CloudflareBindingError, CloudflareConversationClient, CloudflareDurableRuntime, CloudflareDurableRuntimeConfig, CloudflareDurableRuntimeConfigValue, CloudflarePlatformConfigError, 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, encodeAbortCommand, encodeAdminResponse, encodeApprovalDecisionCommand, encodeHostResponse, encodeObservePageRequest, encodeReceipt, encodeSubmitRequest, encodeUnknownResolutionCommand, makeConversationObjectClass };
1634
+ export { AbortRecorded, AdminExplainRequest, AdminFailed, AdminFailure, AdminResponse, AdminVerifyRequest, AdmissionLimitExceeded, ApprovalRecorded, CLOUDFLARE_DATABASE_CAP_BYTES, CLOUDFLARE_RUNTIME_DEFAULTS, CloudflareAdmissionLimitsValue, CloudflareBindingError, CloudflareConversationClient, CloudflareDurableRuntime, CloudflareDurableRuntimeConfig, CloudflareDurableRuntimeConfigValue, CloudflarePlatformConfigError, 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 };
1077
1635
 
1078
1636
  //# sourceMappingURL=index.mjs.map