@effect-agent/platform-cloudflare 0.1.0-beta.12 → 0.1.0-beta.13

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.d.mts CHANGED
@@ -594,14 +594,6 @@ declare const encodeHostResponse: (input: AbortRecorded | ApprovalRecorded | Hos
594
594
  readonly createdAt: string;
595
595
  readonly deploymentId: string;
596
596
  readonly payload: {
597
- readonly _tag: "SubagentStarted";
598
- readonly runId: string;
599
- readonly toolCallId: string;
600
- readonly childConversationId: string;
601
- readonly childSubmissionId: string;
602
- readonly childReceiptId: string;
603
- readonly childRunId: string;
604
- } | {
605
597
  readonly _tag: "ToolCallPrepared";
606
598
  readonly runId: string;
607
599
  readonly turnId: string;
@@ -637,6 +629,14 @@ declare const encodeHostResponse: (input: AbortRecorded | ApprovalRecorded | Hos
637
629
  readonly submissionId: string;
638
630
  readonly author: string;
639
631
  readonly reason: string;
632
+ } | {
633
+ readonly _tag: "SubagentStarted";
634
+ readonly runId: string;
635
+ readonly toolCallId: string;
636
+ readonly childConversationId: string;
637
+ readonly childSubmissionId: string;
638
+ readonly childReceiptId: string;
639
+ readonly childRunId: string;
640
640
  } | {
641
641
  readonly _tag: "ConversationCreated";
642
642
  readonly agentId: string;
@@ -717,6 +717,8 @@ declare const encodeHostResponse: (input: AbortRecorded | ApprovalRecorded | Hos
717
717
  readonly runId?: string | undefined;
718
718
  readonly result?: Schema.Json | undefined;
719
719
  readonly finishReason?: "budget-exhausted" | undefined;
720
+ readonly exhausted?: "tokens" | "tool-calls" | "turns" | undefined;
721
+ readonly policyLimit?: "cost" | "duration" | "repeated-failures" | "tokens" | "tool-calls" | "turns" | "usage" | undefined;
720
722
  } | {
721
723
  readonly _tag: "SubagentRequested";
722
724
  readonly runId: string;
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
- import { Clock, Context, Duration, Effect, Fiber, Layer, Option, PubSub, Random, Ref, Schema, Stream } from "effect";
1
+ import { Cause, Clock, Context, Duration, Effect, Exit, Fiber, Layer, Option, PubSub, Random, Ref, Schema, Stream } from "effect";
2
2
  import { AbortCommand, AbortIntent, AdmissionConflict, AgentBindingResolver, AppendConflict, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, CanonicalRecordEnvelope, CanonicalSequence, ConversationNotMaterialized, ConversationRead, ConversationStore, ConversationStoreError, DEFAULT_OWNERSHIP_LEASE_DURATION, DefinitionDigests, DeploymentId, DigestError, DurableAgentRuntime, DurableRuntimeConfig, DurableRuntimeFailpoint, DurableRuntimeFailpointError, FenceRejected, IdempotencyKey, IntegrityReport, JoinedToHost, LedgerError, ObligationReport, ObligationThresholds, OperationAuthorizationRequest, OperationAuthorizer, OperationDenied, OwnershipLost, PersistedJson, Principal, ProducerId, Receipt, RecoveryExplanation, RecoveryReport, RetryCommand, RetryRefused, RunJournalError, Settlement, SettlementConflict, SubmissionLedger, SubmissionLookupByKey, ToolReconciler, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, WakeScheduler } from "@effect-agent/session";
3
3
  import { ConversationPortTransport, DEFAULT_MAX_STORED_VALUE_BYTES, conversationStoreLayer, handleEncodedPortRequest, portTransportFailure, routedConversationStoreLayer, routedSubmissionLedgerLayer, storageConfigLayer, storageFailpointLayer, submissionLedgerLayer } from "@effect-agent/storage-cloudflare";
4
4
  import { AgentId, AgentInputError, ConversationId, SubmissionId } from "@effect-agent/core";
5
5
  import { BrowserCrypto } from "@effect/platform-browser";
6
6
  import { SqliteClient } from "@effect/sql-sqlite-do";
7
7
  import { DurableObject, DurableObjectState, WorkerEnvironment } from "effect-cf";
8
- import { CodeExecutionHost, CodeExecutionProtocolError, CodeExecutionResourceUse, CodeExecutionResult, CodeExecutionTimeoutError, CodeExecutor, CodeExecutorStartError, CodeExecutorTerminatedError, CodeExecutorUnsupportedError, CodeHostCall, CodeHostCallLimitError, CodeOutputLimitError, CodeProgramFailedError, CodeSourceError, SandboxImplementation } from "@effect-agent/sandbox";
8
+ import { CodeExecutionHost, CodeExecutionProtocolError, CodeExecutionResourceUse, CodeExecutionResult, CodeExecutionTimeoutError, CodeExecutor, CodeExecutorStartError, CodeExecutorTerminatedError, CodeExecutorUnsupportedError, CodeHostCall, CodeHostCallLimitError, CodeHostCallResult, CodeOutputLimitError, CodeProgramFailedError, CodeSourceError, SandboxImplementation } from "@effect-agent/sandbox";
9
9
  import { WorkerEntrypoint } from "cloudflare:workers";
10
10
  //#region src/bindings.ts
11
11
  /**
@@ -1348,19 +1348,25 @@ const decodeHostCall = (value) => {
1348
1348
  return Option.none();
1349
1349
  }
1350
1350
  };
1351
- /**
1352
- * Project a host outcome to the plain JSON envelope the harness reads. A
1353
- * `CodeExecutionHost` may return either real `CodeHostCallResult` instances
1354
- * (the substitute and conformance kit) or plain-object equivalents (the Code
1355
- * Mode capability's broker route), so this reads the shared fields rather than
1356
- * `Schema.encodeSync`, which would reject a plain object.
1357
- */
1358
- const hostResultEnvelope = (outcome) => outcome._tag === "CodeHostCallSuccess" ? {
1359
- _tag: "CodeHostCallSuccess",
1360
- value: outcome.value
1361
- } : {
1362
- _tag: "CodeHostCallFailure",
1363
- error: outcome.error
1351
+ const decodeHostCallResult = (value) => {
1352
+ try {
1353
+ return Schema.decodeUnknownOption(CodeHostCallResult)(value);
1354
+ } catch {
1355
+ return Option.none();
1356
+ }
1357
+ };
1358
+ const encodeHostResultPayload = (outcome) => {
1359
+ try {
1360
+ const payload = outcome._tag === "CodeHostCallSuccess" ? outcome.value : outcome.error;
1361
+ const encodedPayload = JSON.stringify(payload);
1362
+ if (encodedPayload === void 0) return void 0;
1363
+ return {
1364
+ encodedPayload,
1365
+ resultBytes: utf8ByteLength(encodedPayload)
1366
+ };
1367
+ } catch {
1368
+ return;
1369
+ }
1364
1370
  };
1365
1371
  const utf8ByteLength = (value) => {
1366
1372
  let total = 0;
@@ -1370,14 +1376,6 @@ const utf8ByteLength = (value) => {
1370
1376
  }
1371
1377
  return total;
1372
1378
  };
1373
- const encodedJsonByteLength = (value) => {
1374
- try {
1375
- const encoded = JSON.stringify(value);
1376
- return encoded === void 0 ? void 0 : utf8ByteLength(encoded);
1377
- } catch {
1378
- return;
1379
- }
1380
- };
1381
1379
  /** Reserved global names the harness owns inside the dynamic worker. */
1382
1380
  const reservedHarnessGlobals = /* @__PURE__ */ new Set(["console"]);
1383
1381
  const passCounterState = { next: 0 };
@@ -1401,67 +1399,149 @@ const makeExecute = (options) => Effect.fn("DynamicWorkerCodeExecutor.execute")(
1401
1399
  const host = yield* CodeExecutionHost;
1402
1400
  passCounterState.next += 1;
1403
1401
  const passId = `code-mode-pass-${passCounterState.next}-${crypto.randomUUID()}`;
1404
- const pending = [];
1405
- let wake;
1402
+ const startedAt = performance.now();
1403
+ const passDeadline = startedAt + Duration.toMillis(request.limits.maxWallTime);
1404
+ const remainingPassWallTime = () => Duration.millis(Math.max(0, passDeadline - performance.now()));
1406
1405
  let issuedHostCalls = 0;
1407
- const dispatch = (hostCall) => new Promise((resolve, reject) => {
1408
- issuedHostCalls += 1;
1409
- if (issuedHostCalls > request.limits.maxHostCalls + 1) {
1410
- reject(/* @__PURE__ */ new Error("host-call limit exceeded"));
1411
- return;
1412
- }
1413
- pending.push({
1414
- hostCall,
1415
- resolve,
1416
- reject
1417
- });
1418
- wake?.();
1419
- });
1420
- yield* Effect.acquireRelease(Effect.sync(() => {
1421
- passRegistry.set(passId, { dispatch });
1422
- }), () => Effect.sync(() => {
1423
- passRegistry.delete(passId);
1424
- }));
1425
- const nextPending = Effect.suspend(() => {
1426
- const item = pending.shift();
1427
- if (item !== void 0) return Effect.succeed(item);
1428
- return Effect.callback((resume) => {
1429
- wake = () => {
1430
- wake = void 0;
1431
- const next = pending.shift();
1432
- if (next !== void 0) resume(Effect.succeed(next));
1433
- };
1434
- });
1406
+ let passOpen = true;
1407
+ const queuedHostCalls = [];
1408
+ let activeHostCall;
1409
+ let hostDispatchFailure;
1410
+ let propagatedHostDispatchFailure;
1411
+ let signalHostDispatchFailure = (_failure) => void 0;
1412
+ const hostDispatchFailureSignal = new Promise((resolve) => {
1413
+ signalHostDispatchFailure = resolve;
1435
1414
  });
1436
- const serveHostCalls = Effect.gen(function* () {
1437
- let served = 0;
1438
- while (true) {
1439
- const item = yield* nextPending;
1440
- served += 1;
1441
- if (served > request.limits.maxHostCalls) return yield* CodeHostCallLimitError.make({
1415
+ const rejectQueuedHostCalls = (reason) => {
1416
+ for (const queued of queuedHostCalls.splice(0)) queued.reject(reason);
1417
+ };
1418
+ const recordHostDispatchFailure = (failure) => {
1419
+ if (hostDispatchFailure !== void 0) return;
1420
+ hostDispatchFailure = failure;
1421
+ signalHostDispatchFailure(failure);
1422
+ rejectQueuedHostCalls(/* @__PURE__ */ new Error("Code Mode pass failed"));
1423
+ };
1424
+ const failHostDispatch = (failure) => {
1425
+ switch (failure._tag) {
1426
+ case "host-call-limit": return Effect.fail(CodeHostCallLimitError.make({
1442
1427
  implementation: dynamicWorkerImplementation,
1443
1428
  limit: request.limits.maxHostCalls,
1444
1429
  logs: []
1445
- });
1446
- const decoded = decodeHostCall(item.hostCall);
1430
+ }));
1431
+ case "host-call-result-limit": return Effect.fail(CodeOutputLimitError.make({
1432
+ implementation: dynamicWorkerImplementation,
1433
+ surface: "host-call-result",
1434
+ limit: request.limits.maxHostCallResultBytes,
1435
+ observed: failure.observed,
1436
+ logs: []
1437
+ }));
1438
+ case "host-call-protocol": return Effect.fail(CodeExecutionProtocolError.make({
1439
+ implementation: dynamicWorkerImplementation,
1440
+ message: "The execution host returned a value outside the CodeHostCallResult schema"
1441
+ }));
1442
+ case "wall-clock-timeout": return Effect.fail(CodeExecutionTimeoutError.make({
1443
+ implementation: dynamicWorkerImplementation,
1444
+ kind: "wall-clock",
1445
+ maxWallTime: request.limits.maxWallTime,
1446
+ logs: []
1447
+ }));
1448
+ case "host-call-defect": return Effect.failCause(failure.cause);
1449
+ }
1450
+ };
1451
+ const propagateHostDispatchFailure = (failure) => Effect.gen(function* () {
1452
+ propagatedHostDispatchFailure = failure;
1453
+ return yield* failHostDispatch(failure);
1454
+ });
1455
+ const startNextHostCall = () => {
1456
+ if (!passOpen || hostDispatchFailure !== void 0 || activeHostCall !== void 0) return;
1457
+ const queued = queuedHostCalls.shift();
1458
+ if (queued === void 0) return;
1459
+ const fiber = Effect.runFork(Effect.yieldNow.pipe(Effect.andThen(host.call(queued.call)), Effect.timeoutOrElse({
1460
+ duration: remainingPassWallTime(),
1461
+ orElse: () => Effect.sync(() => {
1462
+ recordHostDispatchFailure({ _tag: "wall-clock-timeout" });
1463
+ throw new Error("code-mode host call exceeded the pass wall-clock limit");
1464
+ })
1465
+ }), Effect.map((outcome) => {
1466
+ const decoded = decodeHostCallResult(outcome);
1447
1467
  if (Option.isNone(decoded)) {
1448
- item.reject(/* @__PURE__ */ new TypeError("host calls must match the CodeHostCall schema"));
1449
- continue;
1468
+ recordHostDispatchFailure({ _tag: "host-call-protocol" });
1469
+ throw new Error("host-call protocol violation");
1450
1470
  }
1451
- const outcome = yield* host.call(decoded.value);
1452
- if (outcome._tag === "CodeHostCallSuccess") {
1453
- const bytes = encodedJsonByteLength(outcome.value);
1454
- if (bytes === void 0 || bytes > request.limits.maxHostCallResultBytes) return yield* CodeOutputLimitError.make({
1455
- implementation: dynamicWorkerImplementation,
1456
- surface: "host-call-result",
1457
- limit: request.limits.maxHostCallResultBytes,
1458
- observed: bytes ?? 0,
1459
- logs: []
1471
+ const encoded = encodeHostResultPayload(decoded.value);
1472
+ if (encoded === void 0 || encoded.resultBytes > request.limits.maxHostCallResultBytes) {
1473
+ recordHostDispatchFailure({
1474
+ _tag: "host-call-result-limit",
1475
+ observed: encoded?.resultBytes ?? 0
1460
1476
  });
1477
+ throw new Error("host-call result limit exceeded");
1461
1478
  }
1462
- item.resolve(hostResultEnvelope(outcome));
1479
+ const normalizedPayload = JSON.parse(encoded.encodedPayload);
1480
+ return decoded.value._tag === "CodeHostCallSuccess" ? {
1481
+ _tag: "CodeHostCallSuccess",
1482
+ value: normalizedPayload
1483
+ } : {
1484
+ _tag: "CodeHostCallFailure",
1485
+ error: normalizedPayload
1486
+ };
1487
+ })));
1488
+ activeHostCall = {
1489
+ fiber,
1490
+ settlement: Effect.runPromise(Fiber.await(fiber)).then((exit) => {
1491
+ if (Exit.isSuccess(exit)) {
1492
+ queued.resolve(exit.value);
1493
+ return;
1494
+ }
1495
+ if (!Cause.hasInterruptsOnly(exit.cause)) recordHostDispatchFailure({
1496
+ _tag: "host-call-defect",
1497
+ cause: exit.cause
1498
+ });
1499
+ queued.reject(/* @__PURE__ */ new Error("Code Mode host call failed"));
1500
+ }).finally(() => {
1501
+ if (activeHostCall?.fiber === fiber) activeHostCall = void 0;
1502
+ startNextHostCall();
1503
+ })
1504
+ };
1505
+ };
1506
+ const dispatch = (hostCall) => {
1507
+ if (!passOpen) return Promise.reject(/* @__PURE__ */ new Error("Code Mode pass is closing"));
1508
+ issuedHostCalls += 1;
1509
+ if (issuedHostCalls > request.limits.maxHostCalls) {
1510
+ recordHostDispatchFailure({ _tag: "host-call-limit" });
1511
+ return Promise.reject(/* @__PURE__ */ new Error("host-call limit exceeded"));
1512
+ }
1513
+ const decoded = decodeHostCall(hostCall);
1514
+ if (Option.isNone(decoded)) return Promise.reject(/* @__PURE__ */ new TypeError("host calls must match the CodeHostCall schema"));
1515
+ return new Promise((resolve, reject) => {
1516
+ queuedHostCalls.push({
1517
+ call: decoded.value,
1518
+ resolve,
1519
+ reject
1520
+ });
1521
+ startNextHostCall();
1522
+ });
1523
+ };
1524
+ const closeHostDispatch = Effect.gen(function* () {
1525
+ passOpen = false;
1526
+ passRegistry.delete(passId);
1527
+ rejectQueuedHostCalls(/* @__PURE__ */ new Error("Code Mode pass is closing"));
1528
+ const active = activeHostCall;
1529
+ if (active !== void 0) {
1530
+ yield* Fiber.interrupt(active.fiber);
1531
+ yield* Effect.promise(() => active.settlement);
1532
+ if (activeHostCall === active) activeHostCall = void 0;
1463
1533
  }
1534
+ return hostDispatchFailure;
1464
1535
  });
1536
+ const stopHostDispatch = closeHostDispatch.pipe(Effect.flatMap((failure) => failure !== void 0 && propagatedHostDispatchFailure !== failure ? propagateHostDispatchFailure(failure) : Effect.void));
1537
+ const releaseHostDispatch = closeHostDispatch.pipe(Effect.flatMap((failure) => {
1538
+ if (failure?._tag !== "host-call-defect" || propagatedHostDispatchFailure === failure) return Effect.void;
1539
+ propagatedHostDispatchFailure = failure;
1540
+ return Effect.failCause(failure.cause);
1541
+ }));
1542
+ yield* Effect.acquireRelease(Effect.sync(() => {
1543
+ passRegistry.set(passId, { dispatch });
1544
+ }), () => releaseHostDispatch);
1465
1545
  const workerCode = {
1466
1546
  compatibilityDate: options.compatibilityDate ?? "2025-05-01",
1467
1547
  allowExperimental: true,
@@ -1492,7 +1572,6 @@ const makeExecute = (options) => Effect.fn("DynamicWorkerCodeExecutor.execute")(
1492
1572
  subRequests: request.limits.maxHostCalls + 8
1493
1573
  } }
1494
1574
  };
1495
- const startedAt = yield* Clock.currentTimeMillis;
1496
1575
  const worker = yield* Effect.acquireRelease(Effect.try({
1497
1576
  try: () => options.loader.load(workerCode),
1498
1577
  catch: (cause) => {
@@ -1511,23 +1590,24 @@ const makeExecute = (options) => Effect.fn("DynamicWorkerCodeExecutor.execute")(
1511
1590
  }), (stub) => Effect.sync(() => {
1512
1591
  stub[Symbol.dispose]?.();
1513
1592
  }));
1514
- const server = yield* serveHostCalls.pipe(Effect.forkScoped);
1515
1593
  const rpc = Effect.tryPromise({
1516
1594
  try: async () => {
1517
1595
  return await worker.getEntrypoint().run();
1518
1596
  },
1519
1597
  catch: (cause) => classifyWorkerFailure(cause, request.limits.maxWallTime)
1520
1598
  });
1521
- const raw = yield* Effect.raceFirst(rpc, Fiber.join(server)).pipe(Effect.timeoutOrElse({
1522
- duration: request.limits.maxWallTime,
1599
+ const raw = yield* Effect.raceFirst(rpc.pipe(Effect.timeoutOrElse({
1600
+ duration: remainingPassWallTime(),
1523
1601
  orElse: () => CodeExecutionTimeoutError.make({
1524
1602
  implementation: dynamicWorkerImplementation,
1525
1603
  kind: "wall-clock",
1526
1604
  maxWallTime: request.limits.maxWallTime,
1527
1605
  logs: []
1528
1606
  })
1529
- }), Effect.ensuring(Fiber.interrupt(server)));
1530
- const finishedAt = yield* Clock.currentTimeMillis;
1607
+ })), Effect.promise(() => hostDispatchFailureSignal).pipe(Effect.flatMap(propagateHostDispatchFailure)));
1608
+ yield* stopHostDispatch;
1609
+ const finishedAt = performance.now();
1610
+ if (hostDispatchFailure !== void 0) return yield* propagateHostDispatchFailure(hostDispatchFailure);
1531
1611
  const outcome = decodeHarnessOutcome(raw);
1532
1612
  if (Option.isNone(outcome)) return yield* CodeExecutionProtocolError.make({
1533
1613
  implementation: dynamicWorkerImplementation,