@effect-agent/platform-cloudflare 0.1.0-beta.13 → 0.1.0-beta.131
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Alarm.d.mts +244 -0
- package/dist/Alarm.mjs +947 -0
- package/dist/Alarm.mjs.map +1 -0
- package/dist/BrowserCredentials.d.mts +91 -0
- package/dist/BrowserCredentials.mjs +120 -0
- package/dist/BrowserCredentials.mjs.map +1 -0
- package/dist/BrowserRestCapture.d.mts +34 -0
- package/dist/BrowserRestCapture.mjs +238 -0
- package/dist/BrowserRestCapture.mjs.map +1 -0
- package/dist/BrowserRestCrawl.d.mts +16 -0
- package/dist/BrowserRestCrawl.mjs +244 -0
- package/dist/BrowserRestCrawl.mjs.map +1 -0
- package/dist/BrowserSession-DP6b-cpL.d.mts +125 -0
- package/dist/BrowserSession-DWoRC_iM.mjs +505 -0
- package/dist/BrowserSession-DWoRC_iM.mjs.map +1 -0
- package/dist/BrowserSession.d.mts +3 -0
- package/dist/BrowserSession.mjs +4 -0
- package/dist/CloudflareAiGateway.d.mts +64 -0
- package/dist/CloudflareAiGateway.mjs +70 -0
- package/dist/CloudflareAiGateway.mjs.map +1 -0
- package/dist/CloudflareBindings.d.mts +115 -0
- package/dist/CloudflareBindings.mjs +113 -0
- package/dist/CloudflareBindings.mjs.map +1 -0
- package/dist/CloudflareBrowser-DV6kya1H.mjs +495 -0
- package/dist/CloudflareBrowser-DV6kya1H.mjs.map +1 -0
- package/dist/CloudflareBrowser-Doe1M7R5.d.mts +80 -0
- package/dist/CloudflareBrowser.d.mts +2 -0
- package/dist/CloudflareBrowser.mjs +2 -0
- package/dist/CloudflareCodeMode.d.mts +44 -0
- package/dist/CloudflareCodeMode.mjs +616 -0
- package/dist/CloudflareCodeMode.mjs.map +1 -0
- package/dist/CloudflareConfig-DKXGo8L3.d.mts +106 -0
- package/dist/CloudflareConfig.d.mts +2 -0
- package/dist/CloudflareConfig.mjs +128 -0
- package/dist/CloudflareConfig.mjs.map +1 -0
- package/dist/CloudflareMemory.d.mts +142 -0
- package/dist/CloudflareMemory.mjs +202 -0
- package/dist/CloudflareMemory.mjs.map +1 -0
- package/dist/CloudflareScheduling.d.mts +52 -0
- package/dist/CloudflareScheduling.mjs +396 -0
- package/dist/CloudflareScheduling.mjs.map +1 -0
- package/dist/CloudflareSubscriptions.d.mts +87 -0
- package/dist/CloudflareSubscriptions.mjs +530 -0
- package/dist/CloudflareSubscriptions.mjs.map +1 -0
- package/dist/CloudflareThreadClient.d.mts +1752 -0
- package/dist/CloudflareThreadClient.mjs +382 -0
- package/dist/CloudflareThreadClient.mjs.map +1 -0
- package/dist/InteractiveBrowser-BnqY_Y-F.d.mts +228 -0
- package/dist/InteractiveBrowser-CxFT-Nwm.mjs +1923 -0
- package/dist/InteractiveBrowser-CxFT-Nwm.mjs.map +1 -0
- package/dist/InteractiveBrowser.d.mts +2 -0
- package/dist/InteractiveBrowser.mjs +2 -0
- package/dist/ThreadObject-CVhZC-7l.mjs +849 -0
- package/dist/ThreadObject-CVhZC-7l.mjs.map +1 -0
- package/dist/ThreadObject-D8GzJxJB.d.mts +839 -0
- package/dist/ThreadObject.d.mts +2 -0
- package/dist/ThreadObject.mjs +3 -0
- package/dist/WakeScheduler.d.mts +23 -0
- package/dist/WakeScheduler.mjs +57 -0
- package/dist/WakeScheduler.mjs.map +1 -0
- package/dist/boundary-BguazVkh.mjs +42 -0
- package/dist/boundary-BguazVkh.mjs.map +1 -0
- package/dist/index.d.mts +15 -1550
- package/dist/index.mjs +15 -1716
- package/dist/prepared-admission-DU0_T-ev.mjs +73 -0
- package/dist/prepared-admission-DU0_T-ev.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -56
- package/src/Alarm.ts +1979 -0
- package/src/BrowserCredentials.ts +146 -0
- package/src/BrowserRestCapture.ts +477 -0
- package/src/BrowserRestCrawl.ts +540 -0
- package/src/BrowserSession.ts +624 -0
- package/src/CloudflareAiGateway.ts +170 -0
- package/src/CloudflareBindings.ts +199 -0
- package/src/CloudflareBrowser.ts +15 -0
- package/src/{code-mode-executor.ts → CloudflareCodeMode.ts} +308 -283
- package/src/{config.ts → CloudflareConfig.ts} +19 -12
- package/src/CloudflareMemory.ts +420 -0
- package/src/CloudflareScheduling.ts +747 -0
- package/src/CloudflareSubscriptions.ts +1041 -0
- package/src/CloudflareThreadClient.ts +809 -0
- package/src/InteractiveBrowser.ts +3246 -0
- package/src/ThreadObject.ts +1190 -0
- package/src/{wake-scheduler.ts → WakeScheduler.ts} +32 -26
- package/src/index.ts +14 -23
- package/src/internal/boundary.ts +55 -0
- package/src/internal/browser-binding.ts +322 -0
- package/src/internal/browser-credentials.ts +362 -0
- package/src/internal/browser-failure.ts +92 -0
- package/src/internal/browser-file-selection.ts +126 -0
- package/src/internal/browser-quick-action.ts +857 -0
- package/src/internal/browser-readonly-live-view.ts +111 -0
- package/src/internal/browser-session-lifecycle.ts +193 -0
- package/src/internal/layers.ts +773 -0
- package/src/internal/message-delivery.ts +131 -0
- package/src/internal/prepared-admission.ts +116 -0
- package/src/internal/progress-wait.ts +121 -0
- package/src/internal/transport.ts +44 -0
- package/dist/index.mjs.map +0 -1
- package/src/alarm.ts +0 -334
- package/src/bindings.ts +0 -145
- package/src/client.ts +0 -646
- package/src/conversation-object.ts +0 -768
- package/src/layers.ts +0 -376
- package/src/transport.ts +0 -38
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { RpcTarget } from "cloudflare:workers";
|
|
2
|
+
import { Clock, Duration, Effect, Exit, Fiber, Layer, Option, Queue, Schema } from "effect";
|
|
1
3
|
import {
|
|
2
4
|
CodeExecutionHost,
|
|
3
5
|
CodeExecutionResourceUse,
|
|
@@ -14,60 +16,54 @@ import {
|
|
|
14
16
|
CodeOutputLimitError,
|
|
15
17
|
CodeProgramFailedError,
|
|
16
18
|
CodeSourceError,
|
|
17
|
-
SandboxImplementation,
|
|
18
19
|
type CodeExecutorExecute,
|
|
19
20
|
type CodeExecutionRequest,
|
|
20
|
-
} from "
|
|
21
|
-
import {
|
|
22
|
-
|
|
21
|
+
} from "effect-agent/code-executor";
|
|
22
|
+
import { SandboxImplementation } from "effect-agent/sandbox";
|
|
23
|
+
|
|
24
|
+
import { safeCauseDiagnostic, safeCauseMessage } from "./internal/boundary.ts";
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* The Cloudflare Dynamic Worker `CodeExecutor` adapter (C4 of ADR-0017;
|
|
26
28
|
* DEPLOY-011). Each pass loads one fresh Worker through the Worker Loader
|
|
27
29
|
* with `globalOutbound: null`, so generated code has no ambient network,
|
|
28
|
-
* bindings, or secrets; its only authority is the pass-scoped
|
|
29
|
-
* routes back
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* 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.
|
|
35
36
|
*/
|
|
36
37
|
export const dynamicWorkerImplementation = SandboxImplementation.make({
|
|
37
38
|
isolation: "isolated",
|
|
38
39
|
identity: "cloudflare-dynamic-worker",
|
|
39
40
|
});
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
readonly call: (passId: string, hostCall: unknown) => Promise<unknown>;
|
|
42
|
+
interface CodeModePassHost extends Rpc.RpcTargetBranded {
|
|
43
|
+
readonly call: (hostCall: unknown) => Promise<unknown>;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
interface
|
|
47
|
-
readonly
|
|
46
|
+
interface CodeModeHarnessEntrypoint extends Rpc.WorkerEntrypointBranded {
|
|
47
|
+
readonly run: (host: CodeModePassHost) => Promise<unknown>;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* `
|
|
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.
|
|
54
56
|
*/
|
|
55
|
-
|
|
57
|
+
class CodeModePassHostTarget extends RpcTarget implements CodeModePassHost {
|
|
58
|
+
readonly #dispatch: (hostCall: unknown) => Promise<unknown>;
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
export class CodeModeHostEntrypoint extends WorkerEntrypoint {
|
|
65
|
-
async call(passId: unknown, hostCall: unknown): Promise<unknown> {
|
|
66
|
-
const pass = passRegistry.get(String(passId));
|
|
67
|
-
if (pass === undefined) {
|
|
68
|
-
throw new Error("Unknown Code Mode pass");
|
|
69
|
-
}
|
|
70
|
-
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);
|
|
71
67
|
}
|
|
72
68
|
}
|
|
73
69
|
|
|
@@ -103,9 +99,8 @@ const safeJson = (value) => {
|
|
|
103
99
|
};
|
|
104
100
|
|
|
105
101
|
export default class CodeModeHarness extends WorkerEntrypoint {
|
|
106
|
-
async run() {
|
|
107
|
-
const config =
|
|
108
|
-
const host = this.env.CODE_MODE_HOST;
|
|
102
|
+
async run(host) {
|
|
103
|
+
const config = this.env.CODE_MODE_PASS;
|
|
109
104
|
const limits = config.limits;
|
|
110
105
|
const logs = [];
|
|
111
106
|
let logBytes = 0;
|
|
@@ -143,7 +138,7 @@ export default class CodeModeHarness extends WorkerEntrypoint {
|
|
|
143
138
|
};
|
|
144
139
|
throw new Error("code-mode host-call argument limit exceeded");
|
|
145
140
|
}
|
|
146
|
-
const outcome = await host.call(
|
|
141
|
+
const outcome = await host.call({
|
|
147
142
|
namespace,
|
|
148
143
|
method,
|
|
149
144
|
argument: JSON.parse(argText),
|
|
@@ -227,55 +222,50 @@ const BoundedLogs = Schema.Array(Schema.String.check(Schema.isMaxLength(16 * 102
|
|
|
227
222
|
Schema.isMaxLength(4_096),
|
|
228
223
|
);
|
|
229
224
|
|
|
230
|
-
const HarnessCompleted = Schema.
|
|
231
|
-
_tag: Schema.Literal("completed"),
|
|
225
|
+
const HarnessCompleted = Schema.TaggedStruct("completed", {
|
|
232
226
|
value: Schema.Json,
|
|
233
227
|
logs: BoundedLogs,
|
|
234
228
|
hostCalls: Schema.Natural,
|
|
235
229
|
logBytes: Schema.Natural,
|
|
236
230
|
resultBytes: Schema.Natural,
|
|
237
231
|
});
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
message: Schema.String,
|
|
241
|
-
});
|
|
242
|
-
const HarnessNotAFunction = Schema.Struct({
|
|
243
|
-
_tag: Schema.Literal("source-not-a-function"),
|
|
232
|
+
|
|
233
|
+
const HarnessNotAFunction = Schema.TaggedStruct("source-not-a-function", {
|
|
244
234
|
actual: Schema.String,
|
|
245
235
|
});
|
|
246
|
-
|
|
247
|
-
|
|
236
|
+
|
|
237
|
+
const HarnessProgramFailed = Schema.TaggedStruct("program-failed", {
|
|
248
238
|
reason: Schema.Literals(["threw", "rejected", "non-json-result"]),
|
|
249
239
|
thrown: Schema.Json,
|
|
250
240
|
message: Schema.String,
|
|
251
241
|
logs: BoundedLogs,
|
|
252
242
|
});
|
|
253
|
-
|
|
254
|
-
|
|
243
|
+
|
|
244
|
+
const HarnessLogLimit = Schema.TaggedStruct("log-limit", {
|
|
255
245
|
observed: Schema.Natural,
|
|
256
246
|
logs: BoundedLogs,
|
|
257
247
|
});
|
|
258
|
-
|
|
259
|
-
|
|
248
|
+
|
|
249
|
+
const HarnessArgumentLimit = Schema.TaggedStruct("argument-limit", {
|
|
260
250
|
observed: Schema.Natural,
|
|
261
251
|
logs: BoundedLogs,
|
|
262
252
|
});
|
|
263
|
-
|
|
264
|
-
|
|
253
|
+
|
|
254
|
+
const HarnessResultLimit = Schema.TaggedStruct("result-limit", {
|
|
265
255
|
observed: Schema.Natural,
|
|
266
256
|
logs: BoundedLogs,
|
|
267
257
|
});
|
|
268
|
-
|
|
269
|
-
|
|
258
|
+
|
|
259
|
+
const HarnessHostCallLimit = Schema.TaggedStruct("host-call-limit", {
|
|
270
260
|
logs: BoundedLogs,
|
|
271
261
|
});
|
|
272
|
-
|
|
273
|
-
|
|
262
|
+
|
|
263
|
+
const HarnessProtocol = Schema.TaggedStruct("protocol", {
|
|
274
264
|
message: Schema.String,
|
|
275
265
|
});
|
|
266
|
+
|
|
276
267
|
const HarnessOutcome = Schema.Union([
|
|
277
268
|
HarnessCompleted,
|
|
278
|
-
HarnessSourceInvalid,
|
|
279
269
|
HarnessNotAFunction,
|
|
280
270
|
HarnessProgramFailed,
|
|
281
271
|
HarnessLogLimit,
|
|
@@ -285,6 +275,25 @@ const HarnessOutcome = Schema.Union([
|
|
|
285
275
|
HarnessProtocol,
|
|
286
276
|
]);
|
|
287
277
|
|
|
278
|
+
const HarnessPassConfig = Schema.Struct({
|
|
279
|
+
namespaces: Schema.Array(
|
|
280
|
+
Schema.Struct({
|
|
281
|
+
name: Schema.NonEmptyString,
|
|
282
|
+
methods: Schema.Array(Schema.NonEmptyString).check(Schema.isMaxLength(64)),
|
|
283
|
+
}),
|
|
284
|
+
).check(Schema.isMaxLength(32)),
|
|
285
|
+
limits: Schema.Struct({
|
|
286
|
+
maxLogBytes: Schema.Natural,
|
|
287
|
+
maxResultBytes: Schema.Natural,
|
|
288
|
+
maxHostCalls: Schema.Natural,
|
|
289
|
+
maxHostCallArgumentBytes: Schema.Natural,
|
|
290
|
+
}),
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const encodeHarnessPassConfig = Schema.encodeSync(HarnessPassConfig);
|
|
294
|
+
const encodeJsonPayload = Schema.encodeSync(Schema.fromJsonString(Schema.Json));
|
|
295
|
+
const decodeJsonPayload = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Json));
|
|
296
|
+
|
|
288
297
|
const decodeHarnessOutcome = (value: unknown) => {
|
|
289
298
|
try {
|
|
290
299
|
return Schema.decodeUnknownOption(HarnessOutcome)(value);
|
|
@@ -309,6 +318,28 @@ const decodeHostCallResult = (value: unknown) => {
|
|
|
309
318
|
}
|
|
310
319
|
};
|
|
311
320
|
|
|
321
|
+
/** Dispose a Cloudflare RPC handle when the runtime supplies its untyped disposal hook. @internal */
|
|
322
|
+
export const disposeRpcHandle = (handle: unknown): Effect.Effect<void> =>
|
|
323
|
+
Effect.try({
|
|
324
|
+
try: () => {
|
|
325
|
+
if ((typeof handle !== "object" && typeof handle !== "function") || handle === null) return;
|
|
326
|
+
if (!(Symbol.dispose in handle)) return;
|
|
327
|
+
const dispose = Reflect.get(handle, Symbol.dispose);
|
|
328
|
+
|
|
329
|
+
if (typeof dispose === "function") {
|
|
330
|
+
Reflect.apply(dispose, handle, []);
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
catch: (cause) =>
|
|
334
|
+
safeCauseDiagnostic(cause, "The Cloudflare RPC disposal hook failed without a diagnostic"),
|
|
335
|
+
}).pipe(
|
|
336
|
+
Effect.catch((diagnostic) =>
|
|
337
|
+
Effect.logWarning(`Cloudflare RPC handle disposal failed: ${diagnostic}`).pipe(
|
|
338
|
+
Effect.ignoreCause,
|
|
339
|
+
),
|
|
340
|
+
),
|
|
341
|
+
);
|
|
342
|
+
|
|
312
343
|
/**
|
|
313
344
|
* Project a host outcome to the plain JSON envelope the harness reads. A
|
|
314
345
|
* `CodeExecutionHost` may return either real `CodeHostCallResult` instances
|
|
@@ -326,8 +357,8 @@ const encodeHostResultPayload = (
|
|
|
326
357
|
): EncodedHostResultPayload | undefined => {
|
|
327
358
|
try {
|
|
328
359
|
const payload = outcome._tag === "CodeHostCallSuccess" ? outcome.value : outcome.error;
|
|
329
|
-
const encodedPayload =
|
|
330
|
-
|
|
360
|
+
const encodedPayload = encodeJsonPayload(payload);
|
|
361
|
+
|
|
331
362
|
return {
|
|
332
363
|
encodedPayload,
|
|
333
364
|
resultBytes: utf8ByteLength(encodedPayload),
|
|
@@ -339,30 +370,31 @@ const encodeHostResultPayload = (
|
|
|
339
370
|
|
|
340
371
|
const utf8ByteLength = (value: string): number => {
|
|
341
372
|
let total = 0;
|
|
373
|
+
|
|
342
374
|
for (const character of value) {
|
|
343
375
|
const codePoint = character.codePointAt(0) ?? 0;
|
|
376
|
+
|
|
344
377
|
total += codePoint <= 0x7f ? 1 : codePoint <= 0x7ff ? 2 : codePoint <= 0xffff ? 3 : 4;
|
|
345
378
|
}
|
|
379
|
+
|
|
346
380
|
return total;
|
|
347
381
|
};
|
|
348
382
|
|
|
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
383
|
interface QueuedHostCall {
|
|
357
384
|
readonly call: CodeHostCall;
|
|
358
385
|
readonly resolve: (value: unknown) => void;
|
|
359
386
|
readonly reject: (reason: unknown) => void;
|
|
360
387
|
}
|
|
361
388
|
|
|
362
|
-
|
|
363
|
-
readonly
|
|
364
|
-
readonly
|
|
365
|
-
|
|
389
|
+
type HostWork =
|
|
390
|
+
| { readonly _tag: "call"; readonly queued: QueuedHostCall }
|
|
391
|
+
| { readonly _tag: "limit" };
|
|
392
|
+
|
|
393
|
+
type HostDispatchError =
|
|
394
|
+
| CodeExecutionTimeoutError
|
|
395
|
+
| CodeOutputLimitError
|
|
396
|
+
| CodeExecutionProtocolError
|
|
397
|
+
| CodeHostCallLimitError;
|
|
366
398
|
|
|
367
399
|
/** Reserved global names the harness owns inside the dynamic worker. */
|
|
368
400
|
const reservedHarnessGlobals = new Set(["console"]);
|
|
@@ -370,19 +402,14 @@ const reservedHarnessGlobals = new Set(["console"]);
|
|
|
370
402
|
export interface DynamicWorkerCodeExecutorOptions {
|
|
371
403
|
/** The `worker_loader` binding. */
|
|
372
404
|
readonly loader: WorkerLoader;
|
|
373
|
-
/**
|
|
374
|
-
* A SAME-INSTANCE stub of `CodeModeHostEntrypoint`. In production create it
|
|
375
|
-
* with `ctx.exports.CodeModeHostEntrypoint()`; a cross-instance stub would
|
|
376
|
-
* dispatch host calls into an isolate without this pass's registry entry.
|
|
377
|
-
*/
|
|
378
|
-
readonly hostStub: CodeModeHostStub;
|
|
379
405
|
/** Compatibility date for dynamic workers; defaults to `2025-05-01`. */
|
|
380
406
|
readonly compatibilityDate?: string | undefined;
|
|
381
407
|
}
|
|
382
408
|
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
409
|
+
const makeExecute = (
|
|
410
|
+
options: DynamicWorkerCodeExecutorOptions,
|
|
411
|
+
clock: Clock.Clock,
|
|
412
|
+
): CodeExecutorExecute =>
|
|
386
413
|
Effect.fn("DynamicWorkerCodeExecutor.execute")(function* (request: CodeExecutionRequest) {
|
|
387
414
|
if (request.network._tag !== "NetworkDisabled") {
|
|
388
415
|
return yield* CodeExecutorUnsupportedError.make({
|
|
@@ -393,6 +420,7 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
393
420
|
});
|
|
394
421
|
}
|
|
395
422
|
const sourceBytes = utf8ByteLength(request.source);
|
|
423
|
+
|
|
396
424
|
if (sourceBytes > request.limits.maxSourceBytes) {
|
|
397
425
|
return yield* CodeSourceError.make({
|
|
398
426
|
implementation: dynamicWorkerImplementation,
|
|
@@ -409,148 +437,140 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
409
437
|
});
|
|
410
438
|
}
|
|
411
439
|
}
|
|
412
|
-
|
|
413
440
|
const host = yield* CodeExecutionHost;
|
|
414
|
-
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
441
|
+
|
|
442
|
+
// This synchronous clock access is confined to callbacks that must compute a timeout
|
|
443
|
+
// immediately. The Clock service remains the authority, so tests and hosts can replace it.
|
|
444
|
+
const startedAt = clock.monotonicTimeNanosUnsafe();
|
|
445
|
+
const passDeadline = startedAt + Duration.toNanosUnsafe(request.limits.maxWallTime);
|
|
446
|
+
|
|
447
|
+
const remainingPassWallTime = (): Duration.Duration => {
|
|
448
|
+
const now = clock.monotonicTimeNanosUnsafe();
|
|
449
|
+
|
|
450
|
+
return Duration.nanos(passDeadline > now ? passDeadline - now : 0n);
|
|
451
|
+
};
|
|
452
|
+
|
|
426
453
|
let issuedHostCalls = 0;
|
|
427
454
|
let passOpen = true;
|
|
428
455
|
const queuedHostCalls: Array<QueuedHostCall> = [];
|
|
429
|
-
let
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
});
|
|
456
|
+
let passFailure: HostDispatchError | undefined;
|
|
457
|
+
|
|
458
|
+
const failPass = (error: HostDispatchError): void => {
|
|
459
|
+
if (passFailure === undefined) passFailure = error;
|
|
460
|
+
};
|
|
461
|
+
|
|
436
462
|
const rejectQueuedHostCalls = (reason: Error): void => {
|
|
437
463
|
for (const queued of queuedHostCalls.splice(0)) {
|
|
438
464
|
queued.reject(reason);
|
|
439
465
|
}
|
|
440
466
|
};
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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) =>
|
|
467
|
+
|
|
468
|
+
const queue = yield* Queue.unbounded<HostWork>();
|
|
469
|
+
|
|
470
|
+
const deliverHostOutcome = (
|
|
471
|
+
queued: QueuedHostCall,
|
|
472
|
+
outcome: CodeHostCallResult,
|
|
473
|
+
): Effect.Effect<void, CodeExecutionProtocolError | CodeOutputLimitError> =>
|
|
488
474
|
Effect.gen(function* () {
|
|
489
|
-
|
|
490
|
-
|
|
475
|
+
const decoded = decodeHostCallResult(outcome);
|
|
476
|
+
|
|
477
|
+
if (Option.isNone(decoded)) {
|
|
478
|
+
const error = CodeExecutionProtocolError.make({
|
|
479
|
+
implementation: dynamicWorkerImplementation,
|
|
480
|
+
message: "The execution host returned a value outside the CodeHostCallResult schema",
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
failPass(error);
|
|
484
|
+
|
|
485
|
+
return yield* error;
|
|
486
|
+
}
|
|
487
|
+
const encoded = encodeHostResultPayload(decoded.value);
|
|
488
|
+
|
|
489
|
+
if (encoded === undefined || encoded.resultBytes > request.limits.maxHostCallResultBytes) {
|
|
490
|
+
const error = CodeOutputLimitError.make({
|
|
491
|
+
implementation: dynamicWorkerImplementation,
|
|
492
|
+
surface: "host-call-result",
|
|
493
|
+
limit: request.limits.maxHostCallResultBytes,
|
|
494
|
+
observed: encoded?.resultBytes ?? 0,
|
|
495
|
+
logs: [],
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
failPass(error);
|
|
499
|
+
|
|
500
|
+
return yield* error;
|
|
501
|
+
}
|
|
502
|
+
const normalizedPayload = decodeJsonPayload(encoded.encodedPayload);
|
|
503
|
+
|
|
504
|
+
if (Option.isNone(normalizedPayload)) {
|
|
505
|
+
const error = CodeExecutionProtocolError.make({
|
|
506
|
+
implementation: dynamicWorkerImplementation,
|
|
507
|
+
message: "The execution host returned a result that could not cross the JSON boundary",
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
failPass(error);
|
|
511
|
+
|
|
512
|
+
return yield* error;
|
|
513
|
+
}
|
|
514
|
+
queued.resolve(
|
|
515
|
+
decoded.value._tag === "CodeHostCallSuccess"
|
|
516
|
+
? { _tag: "CodeHostCallSuccess", value: normalizedPayload.value }
|
|
517
|
+
: { _tag: "CodeHostCallFailure", error: normalizedPayload.value },
|
|
518
|
+
);
|
|
491
519
|
});
|
|
492
520
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
521
|
+
// Workers RPC into the loader isolate cannot settle on the fiber blocked
|
|
522
|
+
// in `entrypoint.run()`. A Scope-owned sibling fiber keeps that
|
|
523
|
+
// independence while inheriting the pass Context and dying with the Scope.
|
|
524
|
+
const serveHostCalls = Effect.gen(function* () {
|
|
525
|
+
while (true) {
|
|
526
|
+
const work = yield* Queue.take(queue);
|
|
527
|
+
|
|
528
|
+
if (work._tag === "limit") {
|
|
529
|
+
const error = CodeHostCallLimitError.make({
|
|
530
|
+
implementation: dynamicWorkerImplementation,
|
|
531
|
+
limit: request.limits.maxHostCalls,
|
|
532
|
+
logs: [],
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
failPass(error);
|
|
536
|
+
|
|
537
|
+
return yield* error;
|
|
538
|
+
}
|
|
539
|
+
const queued = work.queued;
|
|
540
|
+
|
|
541
|
+
yield* host.call(queued.call).pipe(
|
|
505
542
|
Effect.timeoutOrElse({
|
|
506
543
|
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,
|
|
544
|
+
orElse: () => {
|
|
545
|
+
const error = CodeExecutionTimeoutError.make({
|
|
546
|
+
implementation: dynamicWorkerImplementation,
|
|
547
|
+
kind: "wall-clock",
|
|
548
|
+
maxWallTime: request.limits.maxWallTime,
|
|
549
|
+
logs: [],
|
|
527
550
|
});
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
: { _tag: "CodeHostCallFailure", error: normalizedPayload };
|
|
551
|
+
|
|
552
|
+
failPass(error);
|
|
553
|
+
|
|
554
|
+
return error;
|
|
555
|
+
},
|
|
534
556
|
}),
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
queued.
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
activeHostCall = { fiber, settlement };
|
|
553
|
-
};
|
|
557
|
+
Effect.flatMap((outcome) => deliverHostOutcome(queued, outcome)),
|
|
558
|
+
Effect.tapError(() =>
|
|
559
|
+
Effect.sync(() => queued.reject(new Error("Code Mode host call failed"))),
|
|
560
|
+
),
|
|
561
|
+
Effect.onInterrupt(() =>
|
|
562
|
+
Effect.sync(() => queued.reject(new Error("Code Mode pass is closing"))),
|
|
563
|
+
),
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
const concurrency = request.limits.maxHostCallConcurrency ?? 4;
|
|
569
|
+
|
|
570
|
+
const server = yield* Effect.all(
|
|
571
|
+
Array.from({ length: concurrency }, () => serveHostCalls),
|
|
572
|
+
{ concurrency, discard: true },
|
|
573
|
+
).pipe(Effect.andThen(Effect.never), Effect.forkScoped);
|
|
554
574
|
|
|
555
575
|
const dispatch = (hostCall: unknown): Promise<unknown> => {
|
|
556
576
|
if (!passOpen) {
|
|
@@ -558,67 +578,51 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
558
578
|
}
|
|
559
579
|
issuedHostCalls += 1;
|
|
560
580
|
if (issuedHostCalls > request.limits.maxHostCalls) {
|
|
561
|
-
|
|
581
|
+
const error = CodeHostCallLimitError.make({
|
|
582
|
+
implementation: dynamicWorkerImplementation,
|
|
583
|
+
limit: request.limits.maxHostCalls,
|
|
584
|
+
logs: [],
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
failPass(error);
|
|
588
|
+
Queue.offerUnsafe(queue, { _tag: "limit" });
|
|
589
|
+
|
|
562
590
|
return Promise.reject(new Error("host-call limit exceeded"));
|
|
563
591
|
}
|
|
564
592
|
const decoded = decodeHostCall(hostCall);
|
|
593
|
+
|
|
565
594
|
if (Option.isNone(decoded)) {
|
|
566
595
|
return Promise.reject(new TypeError("host calls must match the CodeHostCall schema"));
|
|
567
596
|
}
|
|
597
|
+
|
|
568
598
|
return new Promise((resolve, reject) => {
|
|
569
|
-
|
|
570
|
-
|
|
599
|
+
const queued = { call: decoded.value, resolve, reject };
|
|
600
|
+
|
|
601
|
+
queuedHostCalls.push(queued);
|
|
602
|
+
Queue.offerUnsafe(queue, { _tag: "call", queued });
|
|
571
603
|
});
|
|
572
604
|
};
|
|
573
605
|
|
|
574
|
-
const
|
|
606
|
+
const closeAdmission = Effect.sync(() => {
|
|
575
607
|
passOpen = false;
|
|
576
|
-
passRegistry.delete(passId);
|
|
577
608
|
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
609
|
});
|
|
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
610
|
|
|
603
|
-
yield* Effect.
|
|
604
|
-
Effect.sync(() => {
|
|
605
|
-
passRegistry.set(passId, { dispatch });
|
|
606
|
-
}),
|
|
607
|
-
() => releaseHostDispatch,
|
|
608
|
-
);
|
|
611
|
+
yield* Effect.addFinalizer(() => closeAdmission.pipe(Effect.andThen(Fiber.interrupt(server))));
|
|
609
612
|
|
|
610
|
-
|
|
613
|
+
// No `allowExperimental`: the runtime only accepts it when the CALLING
|
|
614
|
+
// worker carries the `experimental` compatibility flag, which deployed
|
|
615
|
+
// consumers cannot set — the option would reject every pass in
|
|
616
|
+
// production. The harness needs no experimental runtime features.
|
|
617
|
+
const workerCode: WorkerLoaderWorkerCode = {
|
|
611
618
|
compatibilityDate: options.compatibilityDate ?? "2025-05-01",
|
|
612
|
-
allowExperimental: true,
|
|
613
619
|
mainModule: "harness.js",
|
|
614
620
|
modules: {
|
|
615
621
|
"harness.js": HARNESS_MODULE,
|
|
616
622
|
"program.js": `export default (\n${request.source}\n);`,
|
|
617
623
|
},
|
|
618
624
|
env: {
|
|
619
|
-
|
|
620
|
-
CODE_MODE_PASS: JSON.stringify({
|
|
621
|
-
passId,
|
|
625
|
+
CODE_MODE_PASS: encodeHarnessPassConfig({
|
|
622
626
|
namespaces: request.namespaces.map((namespace) => ({
|
|
623
627
|
name: namespace.name,
|
|
624
628
|
methods: namespace.methods,
|
|
@@ -644,9 +648,10 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
644
648
|
|
|
645
649
|
const worker = yield* Effect.acquireRelease(
|
|
646
650
|
Effect.try({
|
|
647
|
-
try: () => options.loader.load(workerCode
|
|
651
|
+
try: () => options.loader.load(workerCode),
|
|
648
652
|
catch: (cause) => {
|
|
649
|
-
const text = cause
|
|
653
|
+
const text = safeCauseMessage(cause, "The Worker Loader failed without a diagnostic");
|
|
654
|
+
|
|
650
655
|
// Blame the program's source ONLY on a genuine compile diagnostic;
|
|
651
656
|
// any other load rejection is an infrastructure start failure, not
|
|
652
657
|
// the model's fault (see classifyWorkerFailure for the same split).
|
|
@@ -657,6 +662,7 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
657
662
|
message: text.slice(0, 8_000),
|
|
658
663
|
});
|
|
659
664
|
}
|
|
665
|
+
|
|
660
666
|
return CodeExecutorStartError.make({
|
|
661
667
|
implementation: dynamicWorkerImplementation,
|
|
662
668
|
message: `The Worker Loader rejected the pass: ${text}`.slice(0, 8_000),
|
|
@@ -664,23 +670,23 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
664
670
|
});
|
|
665
671
|
},
|
|
666
672
|
}),
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
673
|
+
disposeRpcHandle,
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
const entrypoint = yield* Effect.acquireRelease(
|
|
677
|
+
Effect.try({
|
|
678
|
+
try: () => worker.getEntrypoint<CodeModeHarnessEntrypoint>(),
|
|
679
|
+
catch: (cause) => classifyWorkerFailure(cause, request.limits.maxWallTime),
|
|
680
|
+
}),
|
|
681
|
+
disposeRpcHandle,
|
|
671
682
|
);
|
|
672
683
|
|
|
673
684
|
const rpc = Effect.tryPromise({
|
|
674
|
-
try:
|
|
675
|
-
const entrypoint = worker.getEntrypoint() as unknown as {
|
|
676
|
-
run(): Promise<unknown>;
|
|
677
|
-
};
|
|
678
|
-
return await entrypoint.run();
|
|
679
|
-
},
|
|
685
|
+
try: () => entrypoint.run(new CodeModePassHostTarget(dispatch)),
|
|
680
686
|
catch: (cause) => classifyWorkerFailure(cause, request.limits.maxWallTime),
|
|
681
687
|
});
|
|
682
688
|
|
|
683
|
-
const
|
|
689
|
+
const exit = yield* Effect.raceFirst(
|
|
684
690
|
rpc.pipe(
|
|
685
691
|
Effect.timeoutOrElse({
|
|
686
692
|
duration: remainingPassWallTime(),
|
|
@@ -693,18 +699,22 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
693
699
|
}),
|
|
694
700
|
}),
|
|
695
701
|
),
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
),
|
|
699
|
-
);
|
|
700
|
-
yield* stopHostDispatch;
|
|
701
|
-
const finishedAt = performance.now();
|
|
702
|
+
Fiber.join(server),
|
|
703
|
+
).pipe(Effect.exit);
|
|
702
704
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
+
yield* closeAdmission;
|
|
706
|
+
yield* Fiber.interrupt(server);
|
|
707
|
+
if (passFailure !== undefined) {
|
|
708
|
+
return yield* passFailure;
|
|
709
|
+
}
|
|
710
|
+
if (Exit.isFailure(exit)) {
|
|
711
|
+
return yield* Effect.failCause(exit.cause);
|
|
705
712
|
}
|
|
713
|
+
const raw = exit.value;
|
|
714
|
+
const finishedAt = clock.monotonicTimeNanosUnsafe();
|
|
706
715
|
|
|
707
716
|
const outcome = decodeHarnessOutcome(raw);
|
|
717
|
+
|
|
708
718
|
if (Option.isNone(outcome)) {
|
|
709
719
|
return yield* CodeExecutionProtocolError.make({
|
|
710
720
|
implementation: dynamicWorkerImplementation,
|
|
@@ -718,20 +728,13 @@ const makeExecute = (options: DynamicWorkerCodeExecutorOptions): CodeExecutorExe
|
|
|
718
728
|
value: outcome.value.value,
|
|
719
729
|
logs: outcome.value.logs,
|
|
720
730
|
resourceUse: CodeExecutionResourceUse.make({
|
|
721
|
-
wallTime: Duration.
|
|
731
|
+
wallTime: Duration.nanos(finishedAt > startedAt ? finishedAt - startedAt : 0n),
|
|
722
732
|
hostCalls: outcome.value.hostCalls,
|
|
723
733
|
logBytes: outcome.value.logBytes,
|
|
724
734
|
resultBytes: outcome.value.resultBytes,
|
|
725
735
|
}),
|
|
726
736
|
});
|
|
727
737
|
}
|
|
728
|
-
case "source-invalid": {
|
|
729
|
-
return yield* CodeSourceError.make({
|
|
730
|
-
implementation: dynamicWorkerImplementation,
|
|
731
|
-
reason: "invalid",
|
|
732
|
-
message: outcome.value.message.slice(0, 8_000),
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
738
|
case "source-not-a-function": {
|
|
736
739
|
return yield* CodeSourceError.make({
|
|
737
740
|
implementation: dynamicWorkerImplementation,
|
|
@@ -804,13 +807,8 @@ const classifyWorkerFailure = (
|
|
|
804
807
|
| CodeExecutorTerminatedError
|
|
805
808
|
| CodeExecutorStartError
|
|
806
809
|
| CodeSourceError => {
|
|
807
|
-
const text = (
|
|
808
|
-
|
|
809
|
-
return cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause);
|
|
810
|
-
} catch {
|
|
811
|
-
return "[unserializable worker failure]";
|
|
812
|
-
}
|
|
813
|
-
})();
|
|
810
|
+
const text = safeCauseDiagnostic(cause, "[unserializable worker failure]");
|
|
811
|
+
|
|
814
812
|
// `WorkerLoader.load()` is lazy, so a module-compile error in the generated
|
|
815
813
|
// program surfaces here at first use. Blame the program's source ONLY on a
|
|
816
814
|
// genuine compile diagnostic (a `SyntaxError` or an explicit compile
|
|
@@ -840,6 +838,7 @@ const classifyWorkerFailure = (
|
|
|
840
838
|
cause,
|
|
841
839
|
});
|
|
842
840
|
}
|
|
841
|
+
|
|
843
842
|
return CodeExecutorTerminatedError.make({
|
|
844
843
|
implementation: dynamicWorkerImplementation,
|
|
845
844
|
message: text.slice(0, 8_000),
|
|
@@ -850,4 +849,30 @@ const classifyWorkerFailure = (
|
|
|
850
849
|
export const dynamicWorkerCodeExecutorLayer = (
|
|
851
850
|
options: DynamicWorkerCodeExecutorOptions,
|
|
852
851
|
): Layer.Layer<CodeExecutor> =>
|
|
853
|
-
Layer.
|
|
852
|
+
Layer.effect(
|
|
853
|
+
CodeExecutor,
|
|
854
|
+
Effect.gen(function* () {
|
|
855
|
+
const clock = yield* Clock.Clock;
|
|
856
|
+
|
|
857
|
+
return CodeExecutor.of({ execute: makeExecute(options, clock) });
|
|
858
|
+
}),
|
|
859
|
+
);
|
|
860
|
+
|
|
861
|
+
/** Assemble Code Mode handlers with the isolated Dynamic Worker executor. */
|
|
862
|
+
export const CloudflareCodeMode = {
|
|
863
|
+
/**
|
|
864
|
+
* Provide the selected tool handlers at construction, where Code Mode captures them.
|
|
865
|
+
* Their errors and remaining dependencies stay visible. The definition still owns the
|
|
866
|
+
* allowlist and limits; the runtime supplies the live Tool broker for each scoped pass.
|
|
867
|
+
*/
|
|
868
|
+
layer: <A, E, R, Handlers, HandlerError, HandlerRequirements>(
|
|
869
|
+
definition: { readonly handlers: Layer.Layer<A, E, R> },
|
|
870
|
+
options: DynamicWorkerCodeExecutorOptions & {
|
|
871
|
+
readonly handlers: Layer.Layer<Handlers, HandlerError, HandlerRequirements>;
|
|
872
|
+
},
|
|
873
|
+
) =>
|
|
874
|
+
definition.handlers.pipe(
|
|
875
|
+
Layer.provide(options.handlers),
|
|
876
|
+
Layer.provide(dynamicWorkerCodeExecutorLayer(options)),
|
|
877
|
+
),
|
|
878
|
+
};
|