@effect-agent/platform-cloudflare 0.0.1-beta.4 → 0.0.1-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,11 @@
1
- import { Clock, Context, Duration, Effect, Layer, ManagedRuntime, Option, PubSub, Random, Ref, Schema, Stream } from "effect";
1
+ import { Cause, Clock, Context, Duration, Effect, Exit, Fiber, Layer, ManagedRuntime, 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, WorkerEntrypoint } from "cloudflare:workers";
8
+ import { CodeExecutionHost, CodeExecutionProtocolError, CodeExecutionResourceUse, CodeExecutionResult, CodeExecutionTimeoutError, CodeExecutor, CodeExecutorStartError, CodeExecutorTerminatedError, CodeExecutorUnsupportedError, CodeHostCall, CodeHostCallLimitError, CodeOutputLimitError, CodeProgramFailedError, CodeSourceError, SandboxImplementation } from "@effect-agent/sandbox";
8
9
  //#region src/bindings.ts
9
10
  /**
10
11
  * Cloudflare platform bindings as Effect services (DEPLOY-010: "Cloudflare platform bindings
@@ -13,7 +14,7 @@ import { DurableObject } from "cloudflare:workers";
13
14
  * per incarnation and everything downstream consumes the services.
14
15
  */
15
16
  /** A Cloudflare platform binding was missing or carried the wrong shape (DEPLOY-003/010). */
16
- var CloudflareBindingError = class extends Schema.TaggedErrorClass()("CloudflareBindingError", {
17
+ var CloudflareBindingError = class extends Schema.TaggedError()("CloudflareBindingError", {
17
18
  binding: Schema.String,
18
19
  message: Schema.String
19
20
  }) {};
@@ -76,7 +77,7 @@ var ConversationObjectIdentity = class extends Context.Service()("@effect-agent/
76
77
  const PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0));
77
78
  const NonNegativeMillis = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
78
79
  /** The supplied Cloudflare durable runtime configuration failed validation (DEPLOY-003). */
79
- var CloudflarePlatformConfigError = class extends Schema.TaggedErrorClass()("CloudflarePlatformConfigError", {
80
+ var CloudflarePlatformConfigError = class extends Schema.TaggedError()("CloudflarePlatformConfigError", {
80
81
  message: Schema.String,
81
82
  cause: Schema.optionalKey(Schema.Defect())
82
83
  }) {};
@@ -88,7 +89,7 @@ var CloudflarePlatformConfigError = class extends Schema.TaggedErrorClass()("Clo
88
89
  * Conversation Object's submit entry point before `DurableAgentRuntime.submit` runs, and
89
90
  * nothing was admitted or written.
90
91
  */
91
- var AdmissionLimitExceeded = class extends Schema.TaggedErrorClass()("AdmissionLimitExceeded", {
92
+ var AdmissionLimitExceeded = class extends Schema.TaggedError()("AdmissionLimitExceeded", {
92
93
  limit: Schema.Literals([
93
94
  "queue-depth",
94
95
  "input-bytes",
@@ -146,6 +147,8 @@ var CloudflareDurableRuntimeConfigValue = class extends Schema.Class("@effect-ag
146
147
  abortPollInterval: PositiveMillis,
147
148
  /** Canonical observation poll cadence of the Durable Object store. */
148
149
  observationPollInterval: NonNegativeMillis,
150
+ /** Cooperative background-export budget; native Object delivery never awaits the flush. */
151
+ telemetryFlushTimeout: PositiveMillis,
149
152
  /** Per-value byte bound; must stay under the platform's 2 MB SQLite value limit. */
150
153
  maxStoredValueBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(2e6)),
151
154
  /** Opt-in full payload/digest-chain audit while opening the store. */
@@ -164,6 +167,7 @@ const CLOUDFLARE_RUNTIME_DEFAULTS = {
164
167
  leaseRenewalInterval: 1e4,
165
168
  abortPollInterval: 500,
166
169
  observationPollInterval: 25,
170
+ telemetryFlushTimeout: 2e3,
167
171
  maxStoredValueBytes: DEFAULT_MAX_STORED_VALUE_BYTES,
168
172
  verifyOnOpen: false,
169
173
  maxQueueDepthPerLane: 256,
@@ -185,7 +189,7 @@ const CLOUDFLARE_RUNTIME_DEFAULTS = {
185
189
  * alarms are harmless by design: a pass over an all-settled lane simply deletes the slot.
186
190
  */
187
191
  /** The Durable Object alarm API failed; surfaces on host entry points as a typed refusal. */
188
- var DurableAlarmError = class extends Schema.TaggedErrorClass()("DurableAlarmError", {
192
+ var DurableAlarmError = class extends Schema.TaggedError()("DurableAlarmError", {
189
193
  operation: Schema.String,
190
194
  message: Schema.String,
191
195
  cause: Schema.optionalKey(Schema.Defect())
@@ -351,7 +355,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
351
355
  */
352
356
  const WAKE_BUFFER_CAPACITY = 1024;
353
357
  /** A remote wake stub call failed; always swallowed and logged (hints are droppable). */
354
- var RemoteWakeDropped = class extends Schema.TaggedErrorClass()("RemoteWakeDropped", {
358
+ var RemoteWakeDropped = class extends Schema.TaggedError()("RemoteWakeDropped", {
355
359
  conversationId: Schema.String,
356
360
  message: Schema.String,
357
361
  cause: Schema.optionalKey(Schema.Defect())
@@ -411,6 +415,31 @@ const conversationPortTransportLayer = Layer.effect(ConversationPortTransport)(E
411
415
  }).pipe(Effect.withSpan("CloudflarePortTransport.call", { attributes: { conversationId } })) });
412
416
  }));
413
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
414
443
  //#region src/layers.ts
415
444
  /**
416
445
  * Owner-side endpoint body for the Conversation Object's `portCall` (plan §1.3): decode,
@@ -433,6 +462,7 @@ const configFromOptions = (options) => decodeConfigValue({
433
462
  leaseRenewalInterval: options.leaseRenewalInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.leaseRenewalInterval,
434
463
  abortPollInterval: options.abortPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.abortPollInterval,
435
464
  observationPollInterval: options.observationPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.observationPollInterval,
465
+ telemetryFlushTimeout: options.telemetryFlushTimeout ?? CLOUDFLARE_RUNTIME_DEFAULTS.telemetryFlushTimeout,
436
466
  maxStoredValueBytes: options.maxStoredValueBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxStoredValueBytes,
437
467
  verifyOnOpen: options.verifyOnOpen ?? CLOUDFLARE_RUNTIME_DEFAULTS.verifyOnOpen,
438
468
  limits: {
@@ -453,7 +483,10 @@ const conversationIdFromState = (ctx) => ctx.id.name === void 0 ? Effect.fail(Cl
453
483
  message: `The Durable Object name is not a valid ConversationId: ${error.message}`,
454
484
  cause: error
455
485
  })));
456
- const resolveBindings = (bindings) => bindings === void 0 ? Effect.succeed([]) : Effect.isEffect(bindings) ? bindings : Effect.succeed(bindings);
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);
457
490
  /**
458
491
  * The DC Layer assembly (deployment §12: a Layer-assembly library, not an app entrypoint;
459
492
  * plan §1.4). `layer(options)` decodes the configuration, derives this Object's Conversation
@@ -467,14 +500,14 @@ const resolveBindings = (bindings) => bindings === void 0 ? Effect.succeed([]) :
467
500
  * Storage compatibility is verified during construction: an incompatible database fails the
468
501
  * Layer typed (`DoStorageCompatibilityError`) before anything is mutated (DEPLOY-008).
469
502
  *
470
- * Requires only the two binding services (`DurableObjectContext`,
471
- * `ConversationObjectNamespace`) — platform values enter exclusively through Layers
472
- * (DEPLOY-010).
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).
473
506
  */
474
507
  var CloudflareDurableRuntime = class {
475
508
  static layer(options) {
476
509
  return Layer.unwrap(Effect.gen(function* () {
477
- const { ctx } = yield* DurableObjectContext;
510
+ const { ctx, env } = yield* DurableObjectContext;
478
511
  const config = yield* configFromOptions(options);
479
512
  const conversationId = yield* conversationIdFromState(ctx);
480
513
  const producerId = yield* decodeProducerId(`${config.producerPrefix}:${conversationId}`).pipe(Effect.mapError((error) => CloudflarePlatformConfigError.make({
@@ -515,7 +548,12 @@ var CloudflareDurableRuntime = class {
515
548
  }));
516
549
  const runtimeFailpointLayer = options.runtimeFailpoint === void 0 ? DurableRuntimeFailpoint.layer : Layer.succeed(DurableRuntimeFailpoint)({ hit: options.runtimeFailpoint(ctx) });
517
550
  const reconcilerLayer = options.toolReconciler ?? ToolReconciler.uncertain;
518
- const bindingResolverLayer = Layer.effect(AgentBindingResolver)(Effect.map(resolveBindings(options.bindings), AgentBindingResolver.fromBindings));
551
+ const bindingResolverLayer = Layer.effect(AgentBindingResolver)(Effect.map(resolveBindings(options.bindings, {
552
+ ctx,
553
+ env,
554
+ conversationId,
555
+ producerId
556
+ }), (bindings) => AgentBindingResolver.fromBindings(bindings)));
519
557
  const base = Layer.mergeAll(identityLayer, cloudflareConfigLayer, DurableAlarmService.layer);
520
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));
521
559
  return Layer.mergeAll(runtimeStack, ConversationMaintenance.layer.pipe(Layer.provide(runtimeStack)), portsEndpointLayer);
@@ -541,9 +579,9 @@ const boundHostDiagnostic = (value) => value.length > MAX_HOST_DIAGNOSTIC_LENGTH
541
579
  * The envelope itself could not be honored: the Object could not decode the request, or a
542
580
  * response could not be encoded/decoded. Never carries operation semantics.
543
581
  */
544
- var HostProtocolError = class extends Schema.TaggedErrorClass()("HostProtocolError", { message: BoundedDiagnostic }) {};
582
+ var HostProtocolError = class extends Schema.TaggedError()("HostProtocolError", { message: BoundedDiagnostic }) {};
545
583
  /** The Worker-side stub call itself failed (RPC rejection, overload, eviction mid-call). */
546
- var ConversationClientError = class extends Schema.TaggedErrorClass()("ConversationClientError", {
584
+ var ConversationClientError = class extends Schema.TaggedError()("ConversationClientError", {
547
585
  conversationId: Schema.String,
548
586
  message: Schema.String,
549
587
  cause: Schema.optionalKey(Schema.Defect())
@@ -770,6 +808,258 @@ var CloudflareConversationClient = class CloudflareConversationClient extends Co
770
808
  }));
771
809
  };
772
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
773
1063
  //#region src/conversation-object.ts
774
1064
  /** Port envelope tags whose owner-side execution durably mutates this Object's lane. */
775
1065
  const MUTATING_PORT_TAGS = /* @__PURE__ */ new Set([
@@ -1009,62 +1299,643 @@ const gateEndpoint = Effect.gen(function* () {
1009
1299
  yield* (yield* ConversationMaintenance).ensureAlarm;
1010
1300
  });
1011
1301
  /**
1012
- * Build the application's Conversation Object class (export it from the
1013
- * Worker entry). The explicit return type is what makes declaration emit
1014
- * possible: the class body carries a private runtime field, and TS4094
1015
- * rejects inferring an exported anonymous class type around it.
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.
1016
1304
  */
1017
- const makeConversationObjectClass = (options) => {
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) => {
1018
1329
  class ConversationObject extends DurableObject {
1019
1330
  #runtime;
1331
+ #ctx;
1332
+ #telemetryFlush;
1020
1333
  constructor(ctx, env) {
1021
1334
  super(ctx, env);
1022
- this.#runtime = ManagedRuntime.make(CloudflareDurableRuntime.layer(options).pipe(Layer.provideMerge(Layer.mergeAll(DurableObjectContext.layer(ctx, env), conversationNamespaceLayer(env, options.namespaceBinding)))));
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
+ } });
1023
1342
  ctx.blockConcurrencyWhile(() => this.#runtime.runPromise(gateEndpoint));
1024
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
+ }
1025
1349
  async submitEncoded(encoded) {
1026
- return this.#runtime.runPromise(submitEndpoint(encoded));
1350
+ return this.#runNative("submit", submitEndpoint(encoded));
1027
1351
  }
1028
1352
  async awaitSettlementEncoded(encoded) {
1029
- return this.#runtime.runPromise(awaitSettlementEndpoint(encoded));
1353
+ return this.#runNative("await_settlement", awaitSettlementEndpoint(encoded));
1030
1354
  }
1031
1355
  async observePage(encoded) {
1032
- return this.#runtime.runPromise(observePageEndpoint(encoded));
1356
+ return this.#runNative("observe", observePageEndpoint(encoded));
1033
1357
  }
1034
1358
  async abortEncoded(encoded) {
1035
- return this.#runtime.runPromise(abortEndpoint(encoded));
1359
+ return this.#runNative("abort", abortEndpoint(encoded));
1036
1360
  }
1037
1361
  async resolveApprovalEncoded(encoded) {
1038
- return this.#runtime.runPromise(resolveApprovalEndpoint(encoded));
1362
+ return this.#runNative("resolve_approval", resolveApprovalEndpoint(encoded));
1039
1363
  }
1040
1364
  async resolveUnknownEncoded(encoded) {
1041
- return this.#runtime.runPromise(resolveUnknownEndpoint(encoded));
1365
+ return this.#runNative("resolve_unknown", resolveUnknownEndpoint(encoded));
1042
1366
  }
1043
1367
  async explainEncoded(encoded) {
1044
- return this.#runtime.runPromise(explainEndpoint(encoded));
1368
+ return this.#runNative("explain", explainEndpoint(encoded));
1045
1369
  }
1046
1370
  async verifyEncoded(encoded) {
1047
- return this.#runtime.runPromise(verifyEndpoint(encoded));
1371
+ return this.#runNative("verify", verifyEndpoint(encoded));
1048
1372
  }
1049
1373
  async retryEncoded(encoded) {
1050
- return this.#runtime.runPromise(retryEndpoint(encoded));
1374
+ return this.#runNative("retry", retryEndpoint(encoded));
1051
1375
  }
1052
1376
  async obligationsEncoded(encoded) {
1053
- return this.#runtime.runPromise(obligationsEndpoint(encoded));
1377
+ return this.#runNative("obligations", obligationsEndpoint(encoded));
1054
1378
  }
1055
1379
  async portCall(encoded) {
1056
- return this.#runtime.runPromise(portCallEndpoint(encoded));
1380
+ return this.#runNative("port_call", portCallEndpoint(encoded));
1057
1381
  }
1058
1382
  async wake() {
1059
- await this.#runtime.runPromise(wakeEndpoint);
1383
+ await this.#runNative("wake", wakeEndpoint);
1060
1384
  }
1061
1385
  async alarm() {
1062
- await this.#runtime.runPromise(alarmEndpoint);
1386
+ await this.#runNative("alarm", alarmEndpoint);
1063
1387
  }
1064
1388
  }
1065
1389
  return ConversationObject;
1066
1390
  };
1067
1391
  //#endregion
1068
- 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 };
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 };
1069
1940
 
1070
1941
  //# sourceMappingURL=index.mjs.map