@effect-agent/platform-cloudflare 0.1.0-beta.22 → 0.1.0-beta.23
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 +77 -108
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -8
- package/src/code-mode-executor.ts +108 -163
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-agent/platform-cloudflare",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.23",
|
|
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.
|
|
12
|
-
"@effect-agent/engine": "0.1.0-beta.
|
|
13
|
-
"@effect-agent/sandbox": "0.1.0-beta.
|
|
14
|
-
"@effect-agent/session": "0.1.0-beta.
|
|
15
|
-
"@effect-agent/storage-cloudflare": "0.1.0-beta.
|
|
11
|
+
"@effect-agent/core": "0.1.0-beta.23",
|
|
12
|
+
"@effect-agent/engine": "0.1.0-beta.23",
|
|
13
|
+
"@effect-agent/sandbox": "0.1.0-beta.23",
|
|
14
|
+
"@effect-agent/session": "0.1.0-beta.23",
|
|
15
|
+
"@effect-agent/storage-cloudflare": "0.1.0-beta.23",
|
|
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"
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@cloudflare/vitest-pool-workers": "0.21.3",
|
|
45
45
|
"@cloudflare/workers-types": "5.20260813.1",
|
|
46
|
-
"@effect-agent/capabilities": "0.1.0-beta.
|
|
47
|
-
"@effect-agent/testing": "0.1.0-beta.
|
|
46
|
+
"@effect-agent/capabilities": "0.1.0-beta.22",
|
|
47
|
+
"@effect-agent/testing": "0.1.0-beta.22",
|
|
48
48
|
"@effect/vitest": "4.0.0-rc.110",
|
|
49
49
|
"effect-cf": "0.27.0",
|
|
50
50
|
"esbuild": "0.28.1",
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
type CodeExecutionRequest,
|
|
20
20
|
} from "@effect-agent/sandbox";
|
|
21
21
|
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
22
|
-
import {
|
|
22
|
+
import { Duration, Effect, Exit, Fiber, Layer, Option, Queue, Schema } from "effect";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* The Cloudflare Dynamic Worker `CodeExecutor` adapter (C4 of ADR-0017;
|
|
@@ -346,23 +346,21 @@ const utf8ByteLength = (value: string): number => {
|
|
|
346
346
|
return total;
|
|
347
347
|
};
|
|
348
348
|
|
|
349
|
-
type HostDispatchFailure =
|
|
350
|
-
| { readonly _tag: "host-call-limit" }
|
|
351
|
-
| { readonly _tag: "host-call-result-limit"; readonly observed: number }
|
|
352
|
-
| { readonly _tag: "host-call-protocol" }
|
|
353
|
-
| { readonly _tag: "wall-clock-timeout" }
|
|
354
|
-
| { readonly _tag: "host-call-defect"; readonly cause: Cause.Cause<never> };
|
|
355
|
-
|
|
356
349
|
interface QueuedHostCall {
|
|
357
350
|
readonly call: CodeHostCall;
|
|
358
351
|
readonly resolve: (value: unknown) => void;
|
|
359
352
|
readonly reject: (reason: unknown) => void;
|
|
360
353
|
}
|
|
361
354
|
|
|
362
|
-
|
|
363
|
-
readonly
|
|
364
|
-
readonly
|
|
365
|
-
|
|
355
|
+
type HostWork =
|
|
356
|
+
| { readonly _tag: "call"; readonly queued: QueuedHostCall }
|
|
357
|
+
| { readonly _tag: "limit" };
|
|
358
|
+
|
|
359
|
+
type HostDispatchError =
|
|
360
|
+
| CodeExecutionTimeoutError
|
|
361
|
+
| CodeOutputLimitError
|
|
362
|
+
| CodeExecutionProtocolError
|
|
363
|
+
| CodeHostCallLimitError;
|
|
366
364
|
|
|
367
365
|
/** Reserved global names the harness owns inside the dynamic worker. */
|
|
368
366
|
const reservedHarnessGlobals = new Set(["console"]);
|
|
@@ -426,131 +424,92 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
426
424
|
let issuedHostCalls = 0;
|
|
427
425
|
let passOpen = true;
|
|
428
426
|
const queuedHostCalls: Array<QueuedHostCall> = [];
|
|
429
|
-
let
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const hostDispatchFailureSignal = new Promise<HostDispatchFailure>((resolve) => {
|
|
434
|
-
signalHostDispatchFailure = resolve;
|
|
435
|
-
});
|
|
427
|
+
let passFailure: HostDispatchError | undefined;
|
|
428
|
+
const failPass = (error: HostDispatchError): void => {
|
|
429
|
+
if (passFailure === undefined) passFailure = error;
|
|
430
|
+
};
|
|
436
431
|
const rejectQueuedHostCalls = (reason: Error): void => {
|
|
437
432
|
for (const queued of queuedHostCalls.splice(0)) {
|
|
438
433
|
queued.reject(reason);
|
|
439
434
|
}
|
|
440
435
|
};
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
const failHostDispatch = (failure: HostDispatchFailure) => {
|
|
448
|
-
switch (failure._tag) {
|
|
449
|
-
case "host-call-limit":
|
|
450
|
-
return Effect.fail(
|
|
451
|
-
CodeHostCallLimitError.make({
|
|
452
|
-
implementation: dynamicWorkerImplementation,
|
|
453
|
-
limit: request.limits.maxHostCalls,
|
|
454
|
-
logs: [],
|
|
455
|
-
}),
|
|
456
|
-
);
|
|
457
|
-
case "host-call-result-limit":
|
|
458
|
-
return Effect.fail(
|
|
459
|
-
CodeOutputLimitError.make({
|
|
460
|
-
implementation: dynamicWorkerImplementation,
|
|
461
|
-
surface: "host-call-result",
|
|
462
|
-
limit: request.limits.maxHostCallResultBytes,
|
|
463
|
-
observed: failure.observed,
|
|
464
|
-
logs: [],
|
|
465
|
-
}),
|
|
466
|
-
);
|
|
467
|
-
case "host-call-protocol":
|
|
468
|
-
return Effect.fail(
|
|
469
|
-
CodeExecutionProtocolError.make({
|
|
470
|
-
implementation: dynamicWorkerImplementation,
|
|
471
|
-
message: "The execution host returned a value outside the CodeHostCallResult schema",
|
|
472
|
-
}),
|
|
473
|
-
);
|
|
474
|
-
case "wall-clock-timeout":
|
|
475
|
-
return Effect.fail(
|
|
476
|
-
CodeExecutionTimeoutError.make({
|
|
477
|
-
implementation: dynamicWorkerImplementation,
|
|
478
|
-
kind: "wall-clock",
|
|
479
|
-
maxWallTime: request.limits.maxWallTime,
|
|
480
|
-
logs: [],
|
|
481
|
-
}),
|
|
482
|
-
);
|
|
483
|
-
case "host-call-defect":
|
|
484
|
-
return Effect.failCause(failure.cause);
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
|
-
const propagateHostDispatchFailure = (failure: HostDispatchFailure) =>
|
|
436
|
+
const queue = yield* Queue.unbounded<HostWork>();
|
|
437
|
+
|
|
438
|
+
const deliverHostOutcome = (
|
|
439
|
+
queued: QueuedHostCall,
|
|
440
|
+
outcome: CodeHostCallResult,
|
|
441
|
+
): Effect.Effect<void, CodeExecutionProtocolError | CodeOutputLimitError> =>
|
|
488
442
|
Effect.gen(function* () {
|
|
489
|
-
|
|
490
|
-
|
|
443
|
+
const decoded = decodeHostCallResult(outcome);
|
|
444
|
+
if (Option.isNone(decoded)) {
|
|
445
|
+
const error = CodeExecutionProtocolError.make({
|
|
446
|
+
implementation: dynamicWorkerImplementation,
|
|
447
|
+
message: "The execution host returned a value outside the CodeHostCallResult schema",
|
|
448
|
+
});
|
|
449
|
+
failPass(error);
|
|
450
|
+
return yield* error;
|
|
451
|
+
}
|
|
452
|
+
const encoded = encodeHostResultPayload(decoded.value);
|
|
453
|
+
if (encoded === undefined || encoded.resultBytes > request.limits.maxHostCallResultBytes) {
|
|
454
|
+
const error = CodeOutputLimitError.make({
|
|
455
|
+
implementation: dynamicWorkerImplementation,
|
|
456
|
+
surface: "host-call-result",
|
|
457
|
+
limit: request.limits.maxHostCallResultBytes,
|
|
458
|
+
observed: encoded?.resultBytes ?? 0,
|
|
459
|
+
logs: [],
|
|
460
|
+
});
|
|
461
|
+
failPass(error);
|
|
462
|
+
return yield* error;
|
|
463
|
+
}
|
|
464
|
+
const normalizedPayload: unknown = JSON.parse(encoded.encodedPayload);
|
|
465
|
+
queued.resolve(
|
|
466
|
+
decoded.value._tag === "CodeHostCallSuccess"
|
|
467
|
+
? { _tag: "CodeHostCallSuccess", value: normalizedPayload }
|
|
468
|
+
: { _tag: "CodeHostCallFailure", error: normalizedPayload },
|
|
469
|
+
);
|
|
491
470
|
});
|
|
492
471
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
472
|
+
// Workers RPC into the loader isolate cannot settle on the fiber blocked
|
|
473
|
+
// in `entrypoint.run()`. A Scope-owned sibling fiber keeps that
|
|
474
|
+
// independence while inheriting the pass Context and dying with the Scope.
|
|
475
|
+
const serveHostCalls = Effect.gen(function* () {
|
|
476
|
+
while (true) {
|
|
477
|
+
const work = yield* Queue.take(queue);
|
|
478
|
+
if (work._tag === "limit") {
|
|
479
|
+
const error = CodeHostCallLimitError.make({
|
|
480
|
+
implementation: dynamicWorkerImplementation,
|
|
481
|
+
limit: request.limits.maxHostCalls,
|
|
482
|
+
logs: [],
|
|
483
|
+
});
|
|
484
|
+
failPass(error);
|
|
485
|
+
return yield* error;
|
|
486
|
+
}
|
|
487
|
+
const queued = work.queued;
|
|
488
|
+
yield* host.call(queued.call).pipe(
|
|
505
489
|
Effect.timeoutOrElse({
|
|
506
490
|
duration: remainingPassWallTime(),
|
|
507
|
-
orElse: () =>
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
Effect.map((outcome) => {
|
|
514
|
-
const decoded = decodeHostCallResult(outcome);
|
|
515
|
-
if (Option.isNone(decoded)) {
|
|
516
|
-
recordHostDispatchFailure({ _tag: "host-call-protocol" });
|
|
517
|
-
throw new Error("host-call protocol violation");
|
|
518
|
-
}
|
|
519
|
-
const encoded = encodeHostResultPayload(decoded.value);
|
|
520
|
-
if (
|
|
521
|
-
encoded === undefined ||
|
|
522
|
-
encoded.resultBytes > request.limits.maxHostCallResultBytes
|
|
523
|
-
) {
|
|
524
|
-
recordHostDispatchFailure({
|
|
525
|
-
_tag: "host-call-result-limit",
|
|
526
|
-
observed: encoded?.resultBytes ?? 0,
|
|
491
|
+
orElse: () => {
|
|
492
|
+
const error = CodeExecutionTimeoutError.make({
|
|
493
|
+
implementation: dynamicWorkerImplementation,
|
|
494
|
+
kind: "wall-clock",
|
|
495
|
+
maxWallTime: request.limits.maxWallTime,
|
|
496
|
+
logs: [],
|
|
527
497
|
});
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
return decoded.value._tag === "CodeHostCallSuccess"
|
|
532
|
-
? { _tag: "CodeHostCallSuccess", value: normalizedPayload }
|
|
533
|
-
: { _tag: "CodeHostCallFailure", error: normalizedPayload };
|
|
498
|
+
failPass(error);
|
|
499
|
+
return error;
|
|
500
|
+
},
|
|
534
501
|
}),
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
queued.
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
queued.reject(new Error("Code Mode host call failed"));
|
|
547
|
-
})
|
|
548
|
-
.finally(() => {
|
|
549
|
-
if (activeHostCall?.fiber === fiber) activeHostCall = undefined;
|
|
550
|
-
startNextHostCall();
|
|
551
|
-
});
|
|
552
|
-
activeHostCall = { fiber, settlement };
|
|
553
|
-
};
|
|
502
|
+
Effect.flatMap((outcome) => deliverHostOutcome(queued, outcome)),
|
|
503
|
+
Effect.tapError(() =>
|
|
504
|
+
Effect.sync(() => queued.reject(new Error("Code Mode host call failed"))),
|
|
505
|
+
),
|
|
506
|
+
Effect.onInterrupt(() =>
|
|
507
|
+
Effect.sync(() => queued.reject(new Error("Code Mode pass is closing"))),
|
|
508
|
+
),
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
const server = yield* serveHostCalls.pipe(Effect.forkScoped);
|
|
554
513
|
|
|
555
514
|
const dispatch = (hostCall: unknown): Promise<unknown> => {
|
|
556
515
|
if (!passOpen) {
|
|
@@ -558,7 +517,13 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
558
517
|
}
|
|
559
518
|
issuedHostCalls += 1;
|
|
560
519
|
if (issuedHostCalls > request.limits.maxHostCalls) {
|
|
561
|
-
|
|
520
|
+
const error = CodeHostCallLimitError.make({
|
|
521
|
+
implementation: dynamicWorkerImplementation,
|
|
522
|
+
limit: request.limits.maxHostCalls,
|
|
523
|
+
logs: [],
|
|
524
|
+
});
|
|
525
|
+
failPass(error);
|
|
526
|
+
Queue.offerUnsafe(queue, { _tag: "limit" });
|
|
562
527
|
return Promise.reject(new Error("host-call limit exceeded"));
|
|
563
528
|
}
|
|
564
529
|
const decoded = decodeHostCall(hostCall);
|
|
@@ -566,45 +531,23 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
566
531
|
return Promise.reject(new TypeError("host calls must match the CodeHostCall schema"));
|
|
567
532
|
}
|
|
568
533
|
return new Promise((resolve, reject) => {
|
|
569
|
-
|
|
570
|
-
|
|
534
|
+
const queued = { call: decoded.value, resolve, reject };
|
|
535
|
+
queuedHostCalls.push(queued);
|
|
536
|
+
Queue.offerUnsafe(queue, { _tag: "call", queued });
|
|
571
537
|
});
|
|
572
538
|
};
|
|
573
539
|
|
|
574
|
-
const
|
|
540
|
+
const closeAdmission = Effect.sync(() => {
|
|
575
541
|
passOpen = false;
|
|
576
542
|
passRegistry.delete(passId);
|
|
577
543
|
rejectQueuedHostCalls(new Error("Code Mode pass is closing"));
|
|
578
|
-
const active = activeHostCall;
|
|
579
|
-
if (active !== undefined) {
|
|
580
|
-
yield* Fiber.interrupt(active.fiber);
|
|
581
|
-
yield* Effect.promise(() => active.settlement);
|
|
582
|
-
if (activeHostCall === active) activeHostCall = undefined;
|
|
583
|
-
}
|
|
584
|
-
return hostDispatchFailure;
|
|
585
544
|
});
|
|
586
|
-
const stopHostDispatch = closeHostDispatch.pipe(
|
|
587
|
-
Effect.flatMap((failure) =>
|
|
588
|
-
failure !== undefined && propagatedHostDispatchFailure !== failure
|
|
589
|
-
? propagateHostDispatchFailure(failure)
|
|
590
|
-
: Effect.void,
|
|
591
|
-
),
|
|
592
|
-
);
|
|
593
|
-
const releaseHostDispatch = closeHostDispatch.pipe(
|
|
594
|
-
Effect.flatMap((failure) => {
|
|
595
|
-
if (failure?._tag !== "host-call-defect" || propagatedHostDispatchFailure === failure) {
|
|
596
|
-
return Effect.void;
|
|
597
|
-
}
|
|
598
|
-
propagatedHostDispatchFailure = failure;
|
|
599
|
-
return Effect.failCause(failure.cause);
|
|
600
|
-
}),
|
|
601
|
-
);
|
|
602
545
|
|
|
603
546
|
yield* Effect.acquireRelease(
|
|
604
547
|
Effect.sync(() => {
|
|
605
548
|
passRegistry.set(passId, { dispatch });
|
|
606
549
|
}),
|
|
607
|
-
() =>
|
|
550
|
+
() => closeAdmission.pipe(Effect.andThen(Fiber.interrupt(server))),
|
|
608
551
|
);
|
|
609
552
|
|
|
610
553
|
// No `allowExperimental`: the runtime only accepts it when the CALLING
|
|
@@ -683,7 +626,7 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
683
626
|
catch: (cause) => classifyWorkerFailure(cause, request.limits.maxWallTime),
|
|
684
627
|
});
|
|
685
628
|
|
|
686
|
-
const
|
|
629
|
+
const exit = yield* Effect.raceFirst(
|
|
687
630
|
rpc.pipe(
|
|
688
631
|
Effect.timeoutOrElse({
|
|
689
632
|
duration: remainingPassWallTime(),
|
|
@@ -696,16 +639,18 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
696
639
|
}),
|
|
697
640
|
}),
|
|
698
641
|
),
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
);
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
if (
|
|
707
|
-
return yield*
|
|
642
|
+
Fiber.join(server),
|
|
643
|
+
).pipe(Effect.exit);
|
|
644
|
+
yield* closeAdmission;
|
|
645
|
+
yield* Fiber.interrupt(server);
|
|
646
|
+
if (passFailure !== undefined) {
|
|
647
|
+
return yield* passFailure;
|
|
648
|
+
}
|
|
649
|
+
if (Exit.isFailure(exit)) {
|
|
650
|
+
return yield* Effect.failCause(exit.cause);
|
|
708
651
|
}
|
|
652
|
+
const raw = exit.value;
|
|
653
|
+
const finishedAt = performance.now();
|
|
709
654
|
|
|
710
655
|
const outcome = decodeHarnessOutcome(raw);
|
|
711
656
|
if (Option.isNone(outcome)) {
|