@effect-agent/platform-cloudflare 0.1.0-beta.25 → 0.1.0-beta.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-agent/platform-cloudflare",
3
- "version": "0.1.0-beta.25",
3
+ "version": "0.1.0-beta.26",
4
4
  "exports": {
5
5
  ".": {
6
6
  "types": "./dist/index.d.mts",
@@ -8,11 +8,11 @@
8
8
  }
9
9
  },
10
10
  "dependencies": {
11
- "@effect-agent/core": "0.1.0-beta.25",
12
- "@effect-agent/engine": "0.1.0-beta.25",
13
- "@effect-agent/sandbox": "0.1.0-beta.25",
14
- "@effect-agent/session": "0.1.0-beta.25",
15
- "@effect-agent/storage-cloudflare": "0.1.0-beta.25",
11
+ "@effect-agent/core": "0.1.0-beta.26",
12
+ "@effect-agent/engine": "0.1.0-beta.26",
13
+ "@effect-agent/sandbox": "0.1.0-beta.26",
14
+ "@effect-agent/session": "0.1.0-beta.26",
15
+ "@effect-agent/storage-cloudflare": "0.1.0-beta.26",
16
16
  "@effect/platform-browser": "4.0.0-rc.110",
17
17
  "@effect/sql-sqlite-do": "4.0.0-rc.110",
18
18
  "effect": "4.0.0-rc.110"
@@ -48,7 +48,7 @@
48
48
  "@effect/vitest": "4.0.0-rc.110",
49
49
  "effect-cf": "0.27.0",
50
50
  "esbuild": "0.28.1",
51
- "miniflare": "4.20260730.0",
51
+ "miniflare": "5.20260811.1-alpha",
52
52
  "typescript": "7.0.2",
53
53
  "vite-plus": "0.2.6",
54
54
  "vitest": "4.1.10"
@@ -18,9 +18,8 @@ import {
18
18
  type CodeExecutorExecute,
19
19
  type CodeExecutionRequest,
20
20
  } from "@effect-agent/sandbox";
21
- import { BrowserCrypto } from "@effect/platform-browser";
22
- import { WorkerEntrypoint } from "cloudflare:workers";
23
- import { Clock, Crypto, Duration, Effect, Exit, Fiber, Layer, Option, Queue, Schema } from "effect";
21
+ import { RpcTarget } from "cloudflare:workers";
22
+ import { Clock, Duration, Effect, Exit, Fiber, Layer, Option, Queue, Schema } from "effect";
24
23
 
25
24
  import { safeCauseDiagnostic, safeCauseMessage } from "./boundary.ts";
26
25
 
@@ -28,56 +27,43 @@ import { safeCauseDiagnostic, safeCauseMessage } from "./boundary.ts";
28
27
  * The Cloudflare Dynamic Worker `CodeExecutor` adapter (C4 of ADR-0017;
29
28
  * DEPLOY-011). Each pass loads one fresh Worker through the Worker Loader
30
29
  * with `globalOutbound: null`, so generated code has no ambient network,
31
- * bindings, or secrets; its only authority is the pass-scoped host stub that
32
- * routes back to `CodeModeHostEntrypoint` and, from there, into the pass's
33
- * `CodeExecutionHost` service. Platform CPU limits stop synchronous runaway
34
- * programs; the executor-owned wall-clock deadline interrupts asynchronously
35
- * suspended passes. Deployment class `E` only: the adapter records no
36
- * persistent state and a later pass may run in a completely different
37
- * isolate.
30
+ * bindings, or secrets; its only authority is the pass-scoped RPC target that
31
+ * routes back into the owning event's `CodeExecutionHost` service. Platform
32
+ * CPU limits stop synchronous runaway programs; the executor-owned wall-clock
33
+ * deadline interrupts asynchronously suspended passes. Deployment class `E`
34
+ * only: the adapter records no persistent state and a later pass may run in a
35
+ * completely different isolate.
38
36
  */
39
37
  export const dynamicWorkerImplementation = SandboxImplementation.make({
40
38
  isolation: "isolated",
41
39
  identity: "cloudflare-dynamic-worker",
42
40
  });
43
41
 
44
- /** The narrow host stub the dynamic worker receives (Workers RPC). */
45
- export interface CodeModeHostStub {
46
- readonly call: (passId: string, hostCall: unknown) => Promise<unknown>;
42
+ interface CodeModePassHost extends Rpc.RpcTargetBranded {
43
+ readonly call: (hostCall: unknown) => Promise<unknown>;
47
44
  }
48
45
 
49
46
  interface CodeModeHarnessEntrypoint extends Rpc.WorkerEntrypointBranded {
50
- readonly run: () => Promise<unknown>;
51
- }
52
-
53
- interface RegisteredPass {
54
- readonly dispatch: (hostCall: unknown) => Promise<unknown>;
47
+ readonly run: (host: CodeModePassHost) => Promise<unknown>;
55
48
  }
56
49
 
57
50
  /**
58
- * Live passes by identity. Entries are Scope-managed: registered when a pass
59
- * opens and removed by its finalizer, so a stale harness (or a forged
60
- * `passId`) cannot reach any host authority.
51
+ * One object-capability endpoint for one execution pass. Workers RPC invokes
52
+ * the target in the request context where it was created, so the native
53
+ * Promise returned by `dispatch` and the Effect fiber that settles it share
54
+ * one I/O owner. Passing the target as `run()`'s argument also scopes the
55
+ * remote stub to that RPC call; no request state lives at module scope.
61
56
  */
62
- const passRegistry = new Map<string, RegisteredPass>();
57
+ class CodeModePassHostTarget extends RpcTarget implements CodeModePassHost {
58
+ readonly #dispatch: (hostCall: unknown) => Promise<unknown>;
63
59
 
64
- /**
65
- * The host-side RPC target for dynamic workers. The application exposes it
66
- * from its Worker entry (`export { CodeModeHostEntrypoint }`) and hands the
67
- * adapter a same-instance stub — `ctx.exports.CodeModeHostEntrypoint()` in
68
- * production (a self service binding may reach a different instance and must
69
- * not be used there); tests bind it through Miniflare's `kCurrentWorker`.
70
- */
71
- export class CodeModeHostEntrypoint extends WorkerEntrypoint {
72
- async call(passId: unknown, hostCall: unknown): Promise<unknown> {
73
- if (typeof passId !== "string") {
74
- throw new TypeError("Code Mode pass identities must be strings");
75
- }
76
- const pass = passRegistry.get(passId);
77
- if (pass === undefined) {
78
- throw new Error("Unknown Code Mode pass");
79
- }
80
- return pass.dispatch(hostCall);
60
+ constructor(dispatch: (hostCall: unknown) => Promise<unknown>) {
61
+ super();
62
+ this.#dispatch = dispatch;
63
+ }
64
+
65
+ call(hostCall: unknown): Promise<unknown> {
66
+ return this.#dispatch(hostCall);
81
67
  }
82
68
  }
83
69
 
@@ -113,9 +99,8 @@ const safeJson = (value) => {
113
99
  };
114
100
 
115
101
  export default class CodeModeHarness extends WorkerEntrypoint {
116
- async run() {
102
+ async run(host) {
117
103
  const config = this.env.CODE_MODE_PASS;
118
- const host = this.env.CODE_MODE_HOST;
119
104
  const limits = config.limits;
120
105
  const logs = [];
121
106
  let logBytes = 0;
@@ -153,7 +138,7 @@ export default class CodeModeHarness extends WorkerEntrypoint {
153
138
  };
154
139
  throw new Error("code-mode host-call argument limit exceeded");
155
140
  }
156
- const outcome = await host.call(config.passId, {
141
+ const outcome = await host.call({
157
142
  namespace,
158
143
  method,
159
144
  argument: JSON.parse(argText),
@@ -287,7 +272,6 @@ const HarnessOutcome = Schema.Union([
287
272
  ]);
288
273
 
289
274
  const HarnessPassConfig = Schema.Struct({
290
- passId: Schema.NonEmptyString,
291
275
  namespaces: Schema.Array(
292
276
  Schema.Struct({
293
277
  name: Schema.NonEmptyString,
@@ -409,19 +393,12 @@ const reservedHarnessGlobals = new Set(["console"]);
409
393
  export interface DynamicWorkerCodeExecutorOptions {
410
394
  /** The `worker_loader` binding. */
411
395
  readonly loader: WorkerLoader;
412
- /**
413
- * A SAME-INSTANCE stub of `CodeModeHostEntrypoint`. In production create it
414
- * with `ctx.exports.CodeModeHostEntrypoint()`; a cross-instance stub would
415
- * dispatch host calls into an isolate without this pass's registry entry.
416
- */
417
- readonly hostStub: CodeModeHostStub;
418
396
  /** Compatibility date for dynamic workers; defaults to `2025-05-01`. */
419
397
  readonly compatibilityDate?: string | undefined;
420
398
  }
421
399
 
422
400
  const makeExecute = (
423
401
  options: DynamicWorkerCodeExecutorOptions,
424
- crypto: Crypto.Crypto,
425
402
  clock: Clock.Clock,
426
403
  ): CodeExecutorExecute =>
427
404
  Effect.fn("DynamicWorkerCodeExecutor.execute")(function* (request: CodeExecutionRequest) {
@@ -450,25 +427,7 @@ const makeExecute = (
450
427
  });
451
428
  }
452
429
  }
453
-
454
430
  const host = yield* CodeExecutionHost;
455
- // The pass id is the only credential a loaded program presents to reach
456
- // its host authority, so it must be unguessable: a program cannot forge
457
- // another pass's id even if two passes were ever concurrent (the broker
458
- // keeps them sequential, but the id must not rely on that).
459
- const passId = yield* crypto.randomUUIDv4.pipe(
460
- Effect.mapError((cause) =>
461
- CodeExecutorStartError.make({
462
- implementation: dynamicWorkerImplementation,
463
- message: `Could not mint the Code Mode pass identity: ${safeCauseMessage(
464
- cause,
465
- "the crypto service failed without a diagnostic",
466
- )}`.slice(0, 8_000),
467
- cause,
468
- }),
469
- ),
470
- Effect.map((uuid) => `code-mode-pass-${uuid}`),
471
- );
472
431
 
473
432
  // This synchronous clock access is confined to callbacks that must compute a timeout
474
433
  // immediately. The Clock service remains the authority, so tests and hosts can replace it.
@@ -604,16 +563,9 @@ const makeExecute = (
604
563
 
605
564
  const closeAdmission = Effect.sync(() => {
606
565
  passOpen = false;
607
- passRegistry.delete(passId);
608
566
  rejectQueuedHostCalls(new Error("Code Mode pass is closing"));
609
567
  });
610
-
611
- yield* Effect.acquireRelease(
612
- Effect.sync(() => {
613
- passRegistry.set(passId, { dispatch });
614
- }),
615
- () => closeAdmission.pipe(Effect.andThen(Fiber.interrupt(server))),
616
- );
568
+ yield* Effect.addFinalizer(() => closeAdmission.pipe(Effect.andThen(Fiber.interrupt(server))));
617
569
 
618
570
  // No `allowExperimental`: the runtime only accepts it when the CALLING
619
571
  // worker carries the `experimental` compatibility flag, which deployed
@@ -627,9 +579,7 @@ const makeExecute = (
627
579
  "program.js": `export default (\n${request.source}\n);`,
628
580
  },
629
581
  env: {
630
- CODE_MODE_HOST: options.hostStub,
631
582
  CODE_MODE_PASS: encodeHarnessPassConfig({
632
- passId,
633
583
  namespaces: request.namespaces.map((namespace) => ({
634
584
  name: namespace.name,
635
585
  methods: namespace.methods,
@@ -687,7 +637,7 @@ const makeExecute = (
687
637
  );
688
638
 
689
639
  const rpc = Effect.tryPromise({
690
- try: () => entrypoint.run(),
640
+ try: () => entrypoint.run(new CodeModePassHostTarget(dispatch)),
691
641
  catch: (cause) => classifyWorkerFailure(cause, request.limits.maxWallTime),
692
642
  });
693
643
 
@@ -860,8 +810,7 @@ export const dynamicWorkerCodeExecutorLayer = (
860
810
  Layer.effect(
861
811
  CodeExecutor,
862
812
  Effect.gen(function* () {
863
- const crypto = yield* Crypto.Crypto;
864
813
  const clock = yield* Clock.Clock;
865
- return CodeExecutor.of({ execute: makeExecute(options, crypto, clock) });
814
+ return CodeExecutor.of({ execute: makeExecute(options, clock) });
866
815
  }),
867
- ).pipe(Layer.provide(BrowserCrypto.layer));
816
+ );