@effect-agent/platform-cloudflare 0.1.0-beta.38 → 0.1.0-beta.40
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/bindings-BrVEOkfe.d.mts +102 -0
- package/dist/browser-quick-action.d.mts +18 -1
- package/dist/browser-quick-action.mjs +14 -1
- package/dist/browser-quick-action.mjs.map +1 -1
- package/dist/browser-rest-capture.d.mts +17 -2
- package/dist/browser-rest-capture.mjs +12 -1
- package/dist/browser-rest-capture.mjs.map +1 -1
- package/dist/browser-rest-crawl.mjs +2 -2
- package/dist/browser-rest-crawl.mjs.map +1 -1
- package/dist/index.d.mts +245 -237
- package/dist/index.mjs +168 -164
- package/dist/index.mjs.map +1 -1
- package/dist/interactive-browser.d.mts +160 -2
- package/dist/interactive-browser.mjs +1166 -2
- package/dist/interactive-browser.mjs.map +1 -0
- package/dist/{scheduling-B-OFqoS9.mjs → prepared-admission-BKp_Upw2.mjs} +134 -433
- package/dist/prepared-admission-BKp_Upw2.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/scheduling.d.mts +47 -2
- package/dist/scheduling.mjs +339 -1
- package/dist/scheduling.mjs.map +1 -0
- package/dist/subscriptions.d.mts +52 -0
- package/dist/subscriptions.mjs +346 -0
- package/dist/subscriptions.mjs.map +1 -0
- package/package.json +20 -13
- package/src/alarm.ts +264 -265
- package/src/bindings.ts +34 -30
- package/src/browser-quick-action.ts +31 -0
- package/src/browser-rest-capture.ts +27 -0
- package/src/client.ts +111 -105
- package/src/code-mode-executor.ts +19 -0
- package/src/config.ts +7 -7
- package/src/index.ts +10 -11
- package/src/interactive-browser.ts +29 -1
- package/src/layers.ts +178 -257
- package/src/prepared-admission.ts +90 -0
- package/src/scheduling.ts +19 -55
- package/src/subscriptions.ts +661 -0
- package/src/{conversation-object.ts → thread-object.ts} +167 -136
- package/src/transport.ts +15 -15
- package/src/wake-scheduler.ts +17 -22
- package/dist/interactive-browser-BRo-Bfvb.d.mts +0 -144
- package/dist/interactive-browser-Cy5q-ldl.mjs +0 -1154
- package/dist/interactive-browser-Cy5q-ldl.mjs.map +0 -1
- package/dist/scheduling-B-OFqoS9.mjs.map +0 -1
- package/dist/scheduling-BJs_kHTx.d.mts +0 -142
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { Clock, Context, Crypto,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { Clock, Context, Crypto, Duration, Effect, Layer, Option, Predicate, Random, Ref, Schema, Semaphore, Stream } from "effect";
|
|
2
|
+
import { DEFAULT_MAX_STORED_VALUE_BYTES } from "@effect-agent/storage-cloudflare";
|
|
3
|
+
import { AbortCommand, AbortIntent, AdmissionConflict, AppendConflict, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, CanonicalRecordEnvelope, CanonicalSequence, DEFAULT_OWNERSHIP_LEASE_DURATION, DefinitionDigests, DeploymentId, DigestError, DurableAgentRuntime, DurableRuntimeFailpointError, FenceRejected, IdempotencyKey, JoinedToHost, LedgerError, OperationDenied, PersistedJson, PreparedInputAdmission, Principal, Receipt, ScheduleStorageError, ScheduledInputAdmission, ScheduledInputRetryable, Settlement, SettlementConflict, SubmissionLedger, ThreadNotMaterialized, ThreadStoreError, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent } from "@effect-agent/thread";
|
|
4
4
|
import { AgentId, AgentInputError } from "@effect-agent/core";
|
|
5
5
|
import { BrowserCrypto } from "@effect/platform-browser";
|
|
6
|
-
import {
|
|
7
|
-
import { DurableObject, DurableObjectAlarm, DurableObjectState, RpcTracing } from "effect-cf";
|
|
6
|
+
import { RpcTracing } from "effect-cf";
|
|
8
7
|
//#region src/bindings.ts
|
|
9
8
|
/**
|
|
10
9
|
* Cloudflare platform bindings as Effect services (DEPLOY-010: "Cloudflare platform bindings
|
|
11
10
|
* are supplied as Effect services/Layers"). Application code never reads `env` or touches a
|
|
12
|
-
* `DurableObjectState` directly — the
|
|
11
|
+
* `DurableObjectState` directly — the Thread Object class constructs these Layers once
|
|
13
12
|
* per incarnation and everything downstream consumes the services.
|
|
14
13
|
*/
|
|
15
14
|
/** A Cloudflare platform binding was missing or carried the wrong shape (DEPLOY-003/010). */
|
|
@@ -18,13 +17,16 @@ var CloudflareBindingError = class extends Schema.TaggedError()("CloudflareBindi
|
|
|
18
17
|
message: Schema.String
|
|
19
18
|
}) {};
|
|
20
19
|
/**
|
|
21
|
-
* The `DurableObjectNamespace` binding that addresses
|
|
22
|
-
* identity rule is `namespace.idFromName(
|
|
20
|
+
* The `DurableObjectNamespace` binding that addresses Thread Objects. The Object
|
|
21
|
+
* identity rule is `namespace.idFromName(threadId)` (plan §1.2): Thread IDs are
|
|
23
22
|
* globally unique, so the mapping is total and deterministic and no directory service exists.
|
|
24
23
|
*/
|
|
25
|
-
var
|
|
26
|
-
static layer(namespace) {
|
|
27
|
-
return Layer.succeed(
|
|
24
|
+
var ThreadObjectNamespace = class ThreadObjectNamespace extends Context.Service()("@effect-agent/platform-cloudflare/ThreadObjectNamespace") {
|
|
25
|
+
static layer(namespace, options = {}) {
|
|
26
|
+
return Layer.succeed(ThreadObjectNamespace)({
|
|
27
|
+
namespace,
|
|
28
|
+
...options.rpcTracing === void 0 ? {} : { rpcTracing: options.rpcTracing }
|
|
29
|
+
});
|
|
28
30
|
}
|
|
29
31
|
};
|
|
30
32
|
/**
|
|
@@ -33,7 +35,7 @@ var ConversationObjectNamespace = class ConversationObjectNamespace extends Cont
|
|
|
33
35
|
* narrowest-boundary check (structural probe for the namespace surface the transport uses);
|
|
34
36
|
* a missing or misshaped binding fails typed before any Layer is built.
|
|
35
37
|
*/
|
|
36
|
-
const
|
|
38
|
+
const threadNamespaceFromEnv = Effect.fn("threadNamespaceFromEnv")(function* (env, binding) {
|
|
37
39
|
if (!Predicate.isObjectKeyword(env)) return yield* CloudflareBindingError.make({
|
|
38
40
|
binding,
|
|
39
41
|
message: "The Worker environment is not an object; no bindings are available."
|
|
@@ -54,14 +56,14 @@ const conversationNamespaceFromEnv = Effect.fn("conversationNamespaceFromEnv")(f
|
|
|
54
56
|
if (candidate !== void 0) return candidate;
|
|
55
57
|
return yield* CloudflareBindingError.make({
|
|
56
58
|
binding,
|
|
57
|
-
message: `env.${binding} is not a DurableObjectNamespace binding; declare the
|
|
59
|
+
message: `env.${binding} is not a DurableObjectNamespace binding; declare the Thread Object class under this binding in the Worker configuration.`
|
|
58
60
|
});
|
|
59
61
|
});
|
|
60
62
|
/**
|
|
61
63
|
* Build the namespace from Worker `env` (fails typed). Enable `rpcTracing` only when the
|
|
62
64
|
* receiver also opts into the effect-cf native RPC trace-context contract.
|
|
63
65
|
*/
|
|
64
|
-
const
|
|
66
|
+
const threadNamespaceLayer = (env, binding, options = {}) => Layer.effect(ThreadObjectNamespace)(Effect.map(threadNamespaceFromEnv(env, binding), (namespace) => ({
|
|
65
67
|
namespace,
|
|
66
68
|
...options.rpcTracing === true ? { rpcTracing: binding } : {}
|
|
67
69
|
})));
|
|
@@ -79,11 +81,11 @@ var DurableObjectContext = class DurableObjectContext extends Context.Service()(
|
|
|
79
81
|
}
|
|
80
82
|
};
|
|
81
83
|
/**
|
|
82
|
-
* The
|
|
83
|
-
* write with (`{producerPrefix}:{
|
|
84
|
-
* from `ctx.id.name` — the Object identity rule guarantees the name IS the
|
|
84
|
+
* The Thread identity this Object serializes and the producer identity its Attempts
|
|
85
|
+
* write with (`{producerPrefix}:{threadId}`, plan §1.4). Derived once per incarnation
|
|
86
|
+
* from `ctx.id.name` — the Object identity rule guarantees the name IS the Thread ID.
|
|
85
87
|
*/
|
|
86
|
-
var
|
|
88
|
+
var ThreadObjectIdentity = class extends Context.Service()("@effect-agent/platform-cloudflare/ThreadObjectIdentity") {};
|
|
87
89
|
//#endregion
|
|
88
90
|
//#region src/config.ts
|
|
89
91
|
/**
|
|
@@ -103,7 +105,7 @@ var CloudflarePlatformConfigError = class extends Schema.TaggedError()("Cloudfla
|
|
|
103
105
|
* (deployment spec §8, DEPLOY-007: "Admission has explicit bounded quota and overload
|
|
104
106
|
* behavior... a typed rejection"). This is the DC analogue of `NodeDurableHost`'s
|
|
105
107
|
* `AdmissionClosed` host gate: the port surface stays untouched, the refusal happens in the
|
|
106
|
-
*
|
|
108
|
+
* Thread Object's submit entry point before `DurableAgentRuntime.submit` runs, and
|
|
107
109
|
* nothing was admitted or written.
|
|
108
110
|
*/
|
|
109
111
|
var AdmissionLimitExceeded = class extends Schema.TaggedError()("AdmissionLimitExceeded", {
|
|
@@ -124,11 +126,11 @@ const CLOUDFLARE_DATABASE_CAP_BYTES = 1e10;
|
|
|
124
126
|
/** Default database-size admission ceiling: a 1 GB safety margin under the platform cap. */
|
|
125
127
|
const DEFAULT_MAX_DATABASE_BYTES = 9e9;
|
|
126
128
|
/**
|
|
127
|
-
* Explicit bounded admission quotas checked by the
|
|
129
|
+
* Explicit bounded admission quotas checked by the Thread Object BEFORE admission
|
|
128
130
|
* (exit gate "resource limits are checked before admission").
|
|
129
131
|
*/
|
|
130
132
|
var CloudflareAdmissionLimitsValue = class extends Schema.Class("@effect-agent/platform-cloudflare/CloudflareAdmissionLimitsValue")({
|
|
131
|
-
/** Maximum nonterminal Submissions per
|
|
133
|
+
/** Maximum nonterminal Submissions per Thread lane before new admissions refuse. */
|
|
132
134
|
maxQueueDepthPerLane: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(1e5)),
|
|
133
135
|
/** Maximum encoded input bytes; never above the storage per-value bound. */
|
|
134
136
|
maxInputBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(2e6)),
|
|
@@ -137,13 +139,13 @@ var CloudflareAdmissionLimitsValue = class extends Schema.Class("@effect-agent/p
|
|
|
137
139
|
}) {};
|
|
138
140
|
/**
|
|
139
141
|
* Validated Cloudflare durable runtime configuration. The producer identity of one
|
|
140
|
-
*
|
|
142
|
+
* Thread Object is `{producerPrefix}:{threadId}` — stable across incarnations of
|
|
141
143
|
* the same deployment, distinct across deployments — and producer-epoch fencing (not the
|
|
142
144
|
* producer name) remains the correctness authority (DUR-006).
|
|
143
145
|
*/
|
|
144
146
|
var CloudflareDurableRuntimeConfigValue = class extends Schema.Class("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfigValue")({
|
|
145
147
|
deploymentId: DeploymentId,
|
|
146
|
-
/** Head of the minted producer identity `{producerPrefix}:{
|
|
148
|
+
/** Head of the minted producer identity `{producerPrefix}:{threadId}`. */
|
|
147
149
|
producerPrefix: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
|
|
148
150
|
/** Submission ownership lease duration (D5); fences work across Object incarnations. */
|
|
149
151
|
ownershipLeaseDuration: PositiveMillis,
|
|
@@ -172,7 +174,7 @@ var CloudflareDurableRuntimeConfigValue = class extends Schema.Class("@effect-ag
|
|
|
172
174
|
}) {};
|
|
173
175
|
/** Explicit configuration authority for the assembled Cloudflare durable runtime. */
|
|
174
176
|
var CloudflareDurableRuntimeConfig = class extends Context.Service()("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfig") {};
|
|
175
|
-
/** Documented production defaults applied by `
|
|
177
|
+
/** Documented production defaults applied by `ThreadObject.layer`. */
|
|
176
178
|
const CLOUDFLARE_RUNTIME_DEFAULTS = {
|
|
177
179
|
ownershipLeaseDuration: Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION),
|
|
178
180
|
alarmBackoffBase: 100,
|
|
@@ -300,21 +302,21 @@ var MaintenancePassReport = class extends Schema.Class("@effect-agent/platform-c
|
|
|
300
302
|
alarm: Schema.Literals(["rearmed", "cleared"])
|
|
301
303
|
}) {};
|
|
302
304
|
/** Test-only fault authority; production uses the inert layer. */
|
|
303
|
-
var
|
|
305
|
+
var ThreadMaintenanceFailpoint = class extends Context.Service()("@effect-agent/platform-cloudflare/ThreadMaintenanceFailpoint") {
|
|
304
306
|
static layer = Layer.succeed(this)({ hit: () => Effect.void });
|
|
305
307
|
};
|
|
306
308
|
const MaintenanceGeneration = Schema.BigIntFromString.check(Schema.isGreaterThanOrEqualToBigInt(0n));
|
|
307
309
|
/** Versioned, platform-private maintenance state stored through Durable Object KV. */
|
|
308
|
-
var
|
|
310
|
+
var ThreadMaintenanceState = class extends Schema.Class("@effect-agent/platform-cloudflare/ThreadMaintenanceState")({
|
|
309
311
|
schemaVersion: Schema.Literal(1),
|
|
310
312
|
dirty: MaintenanceGeneration,
|
|
311
313
|
processed: MaintenanceGeneration,
|
|
312
314
|
nonterminal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
|
|
313
315
|
}) {};
|
|
314
|
-
const MAINTENANCE_STATE_KEY = "effect-agent:
|
|
315
|
-
const decodeMaintenanceState = Schema.decodeUnknownSync(
|
|
316
|
-
const encodeMaintenanceState = Schema.encodeSync(
|
|
317
|
-
const initialMaintenanceState = () =>
|
|
316
|
+
const MAINTENANCE_STATE_KEY = "effect-agent:thread-maintenance:v1";
|
|
317
|
+
const decodeMaintenanceState = Schema.decodeUnknownSync(ThreadMaintenanceState);
|
|
318
|
+
const encodeMaintenanceState = Schema.encodeSync(ThreadMaintenanceState);
|
|
319
|
+
const initialMaintenanceState = () => ThreadMaintenanceState.make({
|
|
318
320
|
schemaVersion: 1,
|
|
319
321
|
dirty: 1n,
|
|
320
322
|
processed: 0n,
|
|
@@ -363,16 +365,15 @@ const stableExternalWait = (snapshot, reports) => {
|
|
|
363
365
|
* 4. Stable external waits acknowledge and clear. Autonomous retry, indeterminate, and lease
|
|
364
366
|
* recovery states leave their generation dirty and retain bounded backoff rearming.
|
|
365
367
|
*/
|
|
366
|
-
var
|
|
367
|
-
static layer = Layer.effect(
|
|
368
|
+
var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effect-agent/platform-cloudflare/ThreadMaintenance") {
|
|
369
|
+
static layer = (bindings) => Layer.effect(ThreadMaintenance)(Effect.gen(function* () {
|
|
368
370
|
const runtime = yield* DurableAgentRuntime;
|
|
369
|
-
const resolver = yield* AgentBindingResolver;
|
|
370
371
|
const ledger = yield* SubmissionLedger;
|
|
371
372
|
const alarm = yield* DurableAlarmService;
|
|
372
373
|
const config = yield* CloudflareDurableRuntimeConfig;
|
|
373
|
-
const identity = yield*
|
|
374
|
+
const identity = yield* ThreadObjectIdentity;
|
|
374
375
|
const { ctx } = yield* DurableObjectContext;
|
|
375
|
-
const failpoint = yield*
|
|
376
|
+
const failpoint = yield* ThreadMaintenanceFailpoint;
|
|
376
377
|
/**
|
|
377
378
|
* Consecutive no-progress passes — an in-memory CACHE, not state: a fresh incarnation
|
|
378
379
|
* restarts at zero and merely re-arms sooner than a long-lived one would have.
|
|
@@ -392,12 +393,12 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
392
393
|
try: transaction,
|
|
393
394
|
catch: alarmFailure(operation)
|
|
394
395
|
});
|
|
395
|
-
const beginMutation = Effect.fn("
|
|
396
|
+
const beginMutation = Effect.fn("ThreadMaintenance.beginMutation")(function* () {
|
|
396
397
|
yield* failpoint.hit("maintenance:dirty:before");
|
|
397
398
|
const now = yield* Clock.currentTimeMillis;
|
|
398
399
|
yield* runTransaction("advance maintenance generation", () => ctx.storage.transaction(async (transaction) => {
|
|
399
400
|
const { state } = await readMaintenanceState(transaction);
|
|
400
|
-
const next =
|
|
401
|
+
const next = ThreadMaintenanceState.make({
|
|
401
402
|
...state,
|
|
402
403
|
dirty: state.dirty + 1n
|
|
403
404
|
});
|
|
@@ -409,7 +410,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
409
410
|
});
|
|
410
411
|
const endMutation = generationGate.withPermit(Ref.update(activeMutations, (active) => Math.max(0, active - 1)));
|
|
411
412
|
const withMutation = (body) => Effect.acquireUseRelease(generationGate.withPermit(beginMutation()), () => failpoint.hit("maintenance:mutation:armed").pipe(Effect.andThen(body), Effect.tap(() => failpoint.hit("maintenance:mutation:finished"))), () => endMutation);
|
|
412
|
-
const ensureAlarm = Effect.fn("
|
|
413
|
+
const ensureAlarm = Effect.fn("ThreadMaintenance.ensureAlarm")(function* () {
|
|
413
414
|
yield* failpoint.hit("maintenance:ensure:before");
|
|
414
415
|
const now = yield* Clock.currentTimeMillis;
|
|
415
416
|
yield* runTransaction("ensure maintenance alarm", () => ctx.storage.transaction(async (transaction) => {
|
|
@@ -419,7 +420,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
419
420
|
}));
|
|
420
421
|
yield* failpoint.hit("maintenance:ensure:after");
|
|
421
422
|
});
|
|
422
|
-
const beginPass = Effect.fn("
|
|
423
|
+
const beginPass = Effect.fn("ThreadMaintenance.beginPass")(function* () {
|
|
423
424
|
yield* failpoint.hit("maintenance:begin:before");
|
|
424
425
|
const now = yield* Clock.currentTimeMillis;
|
|
425
426
|
const result = yield* runTransaction("begin maintenance pass", () => ctx.storage.transaction(async (transaction) => {
|
|
@@ -441,7 +442,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
441
442
|
yield* failpoint.hit("maintenance:begin:after");
|
|
442
443
|
return result;
|
|
443
444
|
});
|
|
444
|
-
const rearmDelay = Effect.fn("
|
|
445
|
+
const rearmDelay = Effect.fn("ThreadMaintenance.rearmDelay")(function* (progressed) {
|
|
445
446
|
const priorStalls = yield* Ref.getAndUpdate(stalls, (count) => progressed ? 0 : count + 1);
|
|
446
447
|
if (progressed) return config.alarmBackoffBase;
|
|
447
448
|
const exponent = Math.min(priorStalls, 30);
|
|
@@ -450,7 +451,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
450
451
|
const jittered = Math.ceil(backoff / 2 + backoff / 2 * jitter);
|
|
451
452
|
return Math.min(jittered, config.wakeScanInterval);
|
|
452
453
|
});
|
|
453
|
-
const pass = Effect.fn("
|
|
454
|
+
const pass = Effect.fn("ThreadMaintenance.pass")(function* () {
|
|
454
455
|
const annotate = (report) => Effect.annotateCurrentSpan({
|
|
455
456
|
phase: report.phase,
|
|
456
457
|
recovered: report.recovered,
|
|
@@ -473,7 +474,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
473
474
|
alarm: "cleared"
|
|
474
475
|
}));
|
|
475
476
|
const recovered = yield* runtime.runRecovery;
|
|
476
|
-
const settlements = yield* runtime.
|
|
477
|
+
const settlements = yield* runtime.processThreadResolved(identity.threadId, bindings);
|
|
477
478
|
const remaining = yield* Stream.runCollect(ledger.scanNonterminal);
|
|
478
479
|
const reports = new Map(recovered.map((report) => [report.submissionId, report]));
|
|
479
480
|
const head = remaining[0];
|
|
@@ -491,7 +492,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
491
492
|
return yield* runTransaction("finish maintenance pass", () => ctx.storage.transaction(async (transaction) => {
|
|
492
493
|
const { state } = await readMaintenanceState(transaction);
|
|
493
494
|
const processed = autonomous || started.activeAtStart > 0 || active > 0 ? state.processed : state.processed > started.generation ? state.processed : started.generation;
|
|
494
|
-
const next =
|
|
495
|
+
const next = ThreadMaintenanceState.make({
|
|
495
496
|
...state,
|
|
496
497
|
processed,
|
|
497
498
|
nonterminal: remaining.length
|
|
@@ -519,7 +520,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
519
520
|
alarm: alarmDisposition
|
|
520
521
|
}));
|
|
521
522
|
});
|
|
522
|
-
return
|
|
523
|
+
return ThreadMaintenance.of({
|
|
523
524
|
pass: alarm.withWakesDeferred(maintenancePassGate.withPermit(pass())),
|
|
524
525
|
ensureAlarm: ensureAlarm(),
|
|
525
526
|
withMutation
|
|
@@ -529,7 +530,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
529
530
|
//#endregion
|
|
530
531
|
//#region src/client.ts
|
|
531
532
|
/**
|
|
532
|
-
* The Worker↔
|
|
533
|
+
* The Worker↔Thread-Object host protocol (plan §1.4): Schema envelopes for the host
|
|
533
534
|
* entry points (`submitEncoded`, `awaitSettlementEncoded`, `observePage`, `abortEncoded`,
|
|
534
535
|
* `resolveApprovalEncoded`, `resolveUnknownEncoded`) plus the Worker-side client that speaks
|
|
535
536
|
* it. Mirrors the WP2 port protocol: requests and responses are closed Schema unions, typed
|
|
@@ -540,15 +541,15 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
540
541
|
const MAX_HOST_DIAGNOSTIC_LENGTH = 4096;
|
|
541
542
|
const BoundedDiagnostic = Schema.String.check(Schema.isMaxLength(MAX_HOST_DIAGNOSTIC_LENGTH));
|
|
542
543
|
/** Truncate a diagnostic string to the host protocol's bounded length. */
|
|
543
|
-
const boundHostDiagnostic = (value) => value.length > MAX_HOST_DIAGNOSTIC_LENGTH ? `${value.slice(0,
|
|
544
|
+
const boundHostDiagnostic = (value) => value.length > MAX_HOST_DIAGNOSTIC_LENGTH ? `${value.slice(0, 4093)}...` : value;
|
|
544
545
|
/**
|
|
545
546
|
* The envelope itself could not be honored: the Object could not decode the request, or a
|
|
546
547
|
* response could not be encoded/decoded. Never carries operation semantics.
|
|
547
548
|
*/
|
|
548
549
|
var HostProtocolError = class extends Schema.TaggedError()("HostProtocolError", { message: BoundedDiagnostic }) {};
|
|
549
550
|
/** The Worker-side stub call itself failed (RPC rejection, overload, eviction mid-call). */
|
|
550
|
-
var
|
|
551
|
-
|
|
551
|
+
var ThreadClientError = class extends Schema.TaggedError()("ThreadClientError", {
|
|
552
|
+
threadId: Schema.String,
|
|
552
553
|
message: Schema.String,
|
|
553
554
|
cause: Schema.optionalKey(Schema.Defect()),
|
|
554
555
|
/** Cloudflare's own classification for a failure safe to retry with a fresh stub. */
|
|
@@ -560,7 +561,7 @@ var ConversationClientError = class extends Schema.TaggedError()("ConversationCl
|
|
|
560
561
|
* One durable submission, input ALREADY encoded by the caller through the Agent Binding's
|
|
561
562
|
* input schema (the Worker bundles the same Agent definitions as the Object, so schema
|
|
562
563
|
* validation happens client-side; the resolved Binding re-validates at claim time). The
|
|
563
|
-
*
|
|
564
|
+
* Thread identity is deliberately absent — the addressed Object IS the lane.
|
|
564
565
|
*/
|
|
565
566
|
var SubmitRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/SubmitRequest")({
|
|
566
567
|
agentId: AgentId,
|
|
@@ -596,8 +597,8 @@ const HostFailure = Schema.Union([
|
|
|
596
597
|
UnknownResolutionConflict,
|
|
597
598
|
JoinedToHost,
|
|
598
599
|
LedgerError,
|
|
599
|
-
|
|
600
|
-
|
|
600
|
+
ThreadStoreError,
|
|
601
|
+
ThreadNotMaterialized,
|
|
601
602
|
AppendConflict,
|
|
602
603
|
FenceRejected,
|
|
603
604
|
DurableRuntimeFailpointError,
|
|
@@ -647,14 +648,14 @@ const decodeUnknownResolutionCommand = Schema.decodeUnknownEffect(UnknownResolut
|
|
|
647
648
|
const encodeUnknownResolutionCommand = Schema.encodeEffect(UnknownResolutionCommand);
|
|
648
649
|
const encodeHostResponse = Schema.encodeEffect(HostResponse);
|
|
649
650
|
const decodeHostResponse = Schema.decodeUnknownEffect(HostResponse);
|
|
650
|
-
/** Failure surface of `
|
|
651
|
+
/** Failure surface of `CloudflareThreadClient.submit`. */
|
|
651
652
|
const ClientSubmitHostFailure = Schema.Union([
|
|
652
653
|
AgentInputError,
|
|
653
654
|
DigestError,
|
|
654
655
|
AdmissionConflict,
|
|
655
656
|
LedgerError,
|
|
656
|
-
|
|
657
|
-
|
|
657
|
+
ThreadStoreError,
|
|
658
|
+
ThreadNotMaterialized,
|
|
658
659
|
AppendConflict,
|
|
659
660
|
FenceRejected,
|
|
660
661
|
DurableRuntimeFailpointError,
|
|
@@ -665,15 +666,17 @@ const ClientSubmitHostFailure = Schema.Union([
|
|
|
665
666
|
const ClientAwaitHostFailure = Schema.Union([
|
|
666
667
|
LedgerError,
|
|
667
668
|
SettlementConflict,
|
|
669
|
+
OperationDenied,
|
|
668
670
|
HostProtocolError
|
|
669
671
|
]);
|
|
670
672
|
const ClientObserveHostFailure = Schema.Union([
|
|
671
|
-
|
|
672
|
-
|
|
673
|
+
ThreadStoreError,
|
|
674
|
+
ThreadNotMaterialized,
|
|
673
675
|
OperationDenied,
|
|
674
676
|
HostProtocolError
|
|
675
677
|
]);
|
|
676
678
|
const ClientAbortHostFailure = Schema.Union([
|
|
679
|
+
OperationDenied,
|
|
677
680
|
LedgerError,
|
|
678
681
|
SettlementConflict,
|
|
679
682
|
JoinedToHost,
|
|
@@ -699,9 +702,9 @@ const ClientUnknownHostFailure = Schema.Union([
|
|
|
699
702
|
DurableAlarmError,
|
|
700
703
|
HostProtocolError
|
|
701
704
|
]);
|
|
702
|
-
const outOfContract = (
|
|
703
|
-
|
|
704
|
-
message: boundHostDiagnostic(`The
|
|
705
|
+
const outOfContract = (threadId, operation, observed) => ThreadClientError.make({
|
|
706
|
+
threadId,
|
|
707
|
+
message: boundHostDiagnostic(`The Thread Object answered ${operation} with the out-of-contract ${observed}.`)
|
|
705
708
|
});
|
|
706
709
|
const hostRpcMethods = {
|
|
707
710
|
submit: "submitEncoded",
|
|
@@ -713,52 +716,60 @@ const hostRpcMethods = {
|
|
|
713
716
|
resolveApproval: "resolveApprovalEncoded",
|
|
714
717
|
resolveUnknown: "resolveUnknownEncoded"
|
|
715
718
|
};
|
|
716
|
-
/** Worker-side client over the
|
|
717
|
-
var
|
|
718
|
-
|
|
719
|
-
|
|
719
|
+
/** Worker-side client over the Thread Object namespace (DEPLOY-010). */
|
|
720
|
+
var CloudflareThreadClient = class CloudflareThreadClient extends Context.Service()("@effect-agent/platform-cloudflare/CloudflareThreadClient") {
|
|
721
|
+
/**
|
|
722
|
+
* Assemble the client from a resolved namespace and the platform Crypto implementation.
|
|
723
|
+
* rpcTracing is the binding name and remains opt-in. Caller authentication, principals,
|
|
724
|
+
* idempotency keys, and definition digests are still explicit submission inputs.
|
|
725
|
+
*/
|
|
726
|
+
static layerFromBinding(options) {
|
|
727
|
+
return CloudflareThreadClient.layer.pipe(Layer.provide([ThreadObjectNamespace.layer(options.namespace, options), BrowserCrypto.layer]));
|
|
728
|
+
}
|
|
729
|
+
static layer = Layer.effect(CloudflareThreadClient)(Effect.gen(function* () {
|
|
730
|
+
const { namespace, rpcTracing } = yield* ThreadObjectNamespace;
|
|
720
731
|
const crypto = yield* Crypto.Crypto;
|
|
721
|
-
const call = Effect.fn(function* (
|
|
732
|
+
const call = Effect.fn(function* (threadId, operation, encoded) {
|
|
722
733
|
const traceArgs = rpcTracing === void 0 ? [] : yield* RpcTracing.withRpcTraceContext([]);
|
|
723
734
|
const raw = yield* Effect.tryPromise({
|
|
724
735
|
try: () => {
|
|
725
|
-
return namespace.get(namespace.idFromName(
|
|
736
|
+
return namespace.get(namespace.idFromName(threadId))[hostRpcMethods[operation]](encoded, ...traceArgs);
|
|
726
737
|
},
|
|
727
|
-
catch: (cause) =>
|
|
728
|
-
|
|
729
|
-
message: boundHostDiagnostic(`${operation} did not reach the
|
|
738
|
+
catch: (cause) => ThreadClientError.make({
|
|
739
|
+
threadId,
|
|
740
|
+
message: boundHostDiagnostic(`${operation} did not reach the Thread Object: ${safeCauseMessage(cause, "the RPC failed without a diagnostic")}`),
|
|
730
741
|
cause,
|
|
731
742
|
...cloudflareFailureSignals(cause)
|
|
732
743
|
})
|
|
733
744
|
});
|
|
734
745
|
return yield* decodeHostResponse(raw).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`The ${operation} answer could not be decoded: ${error.message}`) })));
|
|
735
|
-
}, (effect,
|
|
736
|
-
|
|
746
|
+
}, (effect, threadId, operation) => rpcTracing === void 0 ? Effect.withSpan(effect, "CloudflareThreadClient.call", { attributes: {
|
|
747
|
+
threadId,
|
|
737
748
|
operation
|
|
738
749
|
} }) : RpcTracing.withRpcClientSpan(effect, rpcTracing, hostRpcMethods[operation]));
|
|
739
|
-
const expect = (
|
|
750
|
+
const expect = (threadId, operation, resultSchema, failureSchema) => {
|
|
740
751
|
const isExpectedResult = Schema.is(resultSchema);
|
|
741
752
|
const isExpectedFailure = Schema.is(failureSchema);
|
|
742
753
|
return (response) => {
|
|
743
754
|
if (response._tag === "HostFailed") {
|
|
744
755
|
const failure = response.failure;
|
|
745
|
-
return isExpectedFailure(failure) ? Effect.fail(failure) : Effect.fail(outOfContract(
|
|
756
|
+
return isExpectedFailure(failure) ? Effect.fail(failure) : Effect.fail(outOfContract(threadId, operation, `failure ${failure._tag}`));
|
|
746
757
|
}
|
|
747
|
-
if (!isExpectedResult(response)) return Effect.fail(outOfContract(
|
|
758
|
+
if (!isExpectedResult(response)) return Effect.fail(outOfContract(threadId, operation, `result ${response._tag}`));
|
|
748
759
|
return Effect.succeed(response);
|
|
749
760
|
};
|
|
750
761
|
};
|
|
751
|
-
const readPage = (
|
|
762
|
+
const readPage = (threadId, options) => Effect.gen(function* () {
|
|
752
763
|
const request = ObservePageRequest.make({
|
|
753
764
|
...options?.afterSequence === void 0 ? {} : { afterSequence: options.afterSequence },
|
|
754
765
|
limit: options?.limit ?? 256
|
|
755
766
|
});
|
|
756
767
|
const encoded = yield* encodeObservePageRequest(request).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`observePage request encode failed: ${error.message}`) })));
|
|
757
|
-
const response = yield* call(
|
|
758
|
-
return (yield* expect(
|
|
768
|
+
const response = yield* call(threadId, "observePage", encoded);
|
|
769
|
+
return (yield* expect(threadId, "observePage", ObservedPage, ClientObserveHostFailure)(response)).records;
|
|
759
770
|
});
|
|
760
|
-
const cancelProgress = (
|
|
761
|
-
return
|
|
771
|
+
const cancelProgress = (threadId, waiterId) => encodeCancelProgressRequest(CancelProgressRequest.make({ waiterId })).pipe(Effect.mapError(() => void 0), Effect.flatMap((encoded) => call(threadId, "cancelProgress", encoded)), Effect.asVoid, Effect.ignore);
|
|
772
|
+
return CloudflareThreadClient.of({
|
|
762
773
|
submit: (agent, input, options) => Effect.gen(function* () {
|
|
763
774
|
const encodedInput = yield* Schema.encodeEffect(agent.definition.input)(input).pipe(Effect.mapError((cause) => AgentInputError.make({ message: `Unable to encode Agent input: ${cause.message}` })));
|
|
764
775
|
const inputPayload = yield* Schema.decodeUnknownEffect(PersistedJson)(encodedInput).pipe(Effect.mapError(() => AgentInputError.make({ message: "Agent input does not satisfy the canonical persistence bounds" })));
|
|
@@ -770,30 +781,30 @@ var CloudflareConversationClient = class CloudflareConversationClient extends Co
|
|
|
770
781
|
inputPayload
|
|
771
782
|
});
|
|
772
783
|
const encoded = yield* encodeSubmitRequest(request).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`submit request encode failed: ${error.message}`) })));
|
|
773
|
-
const response = yield* call(options.
|
|
774
|
-
return (yield* expect(options.
|
|
784
|
+
const response = yield* call(options.threadId, "submit", encoded);
|
|
785
|
+
return (yield* expect(options.threadId, "submit", SubmitSucceeded, ClientSubmitHostFailure)(response)).receipt;
|
|
775
786
|
}),
|
|
776
787
|
awaitSettlement: (receipt) => Effect.gen(function* () {
|
|
777
788
|
const encoded = yield* encodeReceipt(receipt).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`receipt encode failed: ${error.message}`) })));
|
|
778
|
-
const response = yield* call(receipt.
|
|
779
|
-
return (yield* expect(receipt.
|
|
789
|
+
const response = yield* call(receipt.threadId, "awaitSettlement", encoded);
|
|
790
|
+
return (yield* expect(receipt.threadId, "awaitSettlement", SettlementReached, ClientAwaitHostFailure)(response)).settlement;
|
|
780
791
|
}),
|
|
781
|
-
awaitProgress: (
|
|
792
|
+
awaitProgress: (threadId, afterSequence) => Effect.gen(function* () {
|
|
782
793
|
const waiterId = yield* crypto.randomUUIDv4.pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`awaitProgress cancellation identity generation failed: ${error.message}`) })));
|
|
783
794
|
const request = AwaitProgressRequest.make({
|
|
784
795
|
afterSequence,
|
|
785
796
|
waiterId
|
|
786
797
|
});
|
|
787
798
|
const encoded = yield* encodeAwaitProgressRequest(request).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`awaitProgress request encode failed: ${error.message}`) })));
|
|
788
|
-
const attempt = (retry) => call(
|
|
789
|
-
yield* attempt(0).pipe(Effect.onInterrupt(() => cancelProgress(
|
|
799
|
+
const attempt = (retry) => call(threadId, "awaitProgress", encoded).pipe(Effect.flatMap(expect(threadId, "awaitProgress", ProgressObserved, ClientObserveHostFailure)), Effect.asVoid, Effect.catchTag("ThreadClientError", (error) => error.retryable === true && error.overloaded !== true && retry < 5 ? Effect.sleep(Duration.millis(10 * 2 ** retry)).pipe(Effect.andThen(attempt(retry + 1))) : Effect.fail(error)));
|
|
800
|
+
yield* attempt(0).pipe(Effect.onInterrupt(() => cancelProgress(threadId, waiterId)));
|
|
790
801
|
}),
|
|
791
802
|
readPage,
|
|
792
|
-
readAll: (
|
|
803
|
+
readAll: (threadId) => Effect.gen(function* () {
|
|
793
804
|
const all = [];
|
|
794
805
|
let after;
|
|
795
806
|
for (;;) {
|
|
796
|
-
const page = yield* readPage(
|
|
807
|
+
const page = yield* readPage(threadId, {
|
|
797
808
|
afterSequence: after,
|
|
798
809
|
limit: 1024
|
|
799
810
|
});
|
|
@@ -803,387 +814,77 @@ var CloudflareConversationClient = class CloudflareConversationClient extends Co
|
|
|
803
814
|
after = last.sequence;
|
|
804
815
|
}
|
|
805
816
|
}),
|
|
806
|
-
abort: (
|
|
817
|
+
abort: (threadId, command) => Effect.gen(function* () {
|
|
807
818
|
const encoded = yield* encodeAbortCommand(command).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`abort command encode failed: ${error.message}`) })));
|
|
808
|
-
const response = yield* call(
|
|
809
|
-
return (yield* expect(
|
|
819
|
+
const response = yield* call(threadId, "abort", encoded);
|
|
820
|
+
return (yield* expect(threadId, "abort", AbortRecorded, ClientAbortHostFailure)(response)).intent;
|
|
810
821
|
}),
|
|
811
|
-
resolveApproval: (
|
|
822
|
+
resolveApproval: (threadId, command) => Effect.gen(function* () {
|
|
812
823
|
const encoded = yield* encodeApprovalDecisionCommand(command).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`approval command encode failed: ${error.message}`) })));
|
|
813
|
-
const response = yield* call(
|
|
814
|
-
return (yield* expect(
|
|
824
|
+
const response = yield* call(threadId, "resolveApproval", encoded);
|
|
825
|
+
return (yield* expect(threadId, "resolveApproval", ApprovalRecorded, ClientApprovalHostFailure)(response)).intent;
|
|
815
826
|
}),
|
|
816
|
-
resolveUnknown: (
|
|
827
|
+
resolveUnknown: (threadId, command) => Effect.gen(function* () {
|
|
817
828
|
const encoded = yield* encodeUnknownResolutionCommand(command).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`resolution command encode failed: ${error.message}`) })));
|
|
818
|
-
const response = yield* call(
|
|
819
|
-
return (yield* expect(
|
|
829
|
+
const response = yield* call(threadId, "resolveUnknown", encoded);
|
|
830
|
+
return (yield* expect(threadId, "resolveUnknown", UnknownResolutionRecorded, ClientUnknownHostFailure)(response)).intent;
|
|
820
831
|
})
|
|
821
832
|
});
|
|
822
833
|
}));
|
|
823
834
|
};
|
|
824
835
|
//#endregion
|
|
825
|
-
//#region src/
|
|
826
|
-
const SCHEDULE_ALARM_TAG = "effect-agent/ScheduleOwnerWake";
|
|
827
|
-
const SCHEDULE_ALARM_ID = "driver";
|
|
828
|
-
const ScheduleAlarmPayload = Schema.Struct({
|
|
829
|
-
schemaVersion: Schema.Literal(1),
|
|
830
|
-
generation: Schema.Int.check(Schema.isGreaterThan(0))
|
|
831
|
-
});
|
|
832
|
-
var ScheduleAlarmProtocolError = class extends Schema.TaggedError()("ScheduleAlarmProtocolError", { message: Schema.String }) {};
|
|
833
|
-
const boundedProtocolMessage = (message) => message.length <= 4096 ? message : `${message.slice(0, 4093)}...`;
|
|
834
|
-
var ScheduleOwnerProtocolError = class extends Schema.TaggedError()("ScheduleOwnerProtocolError", { message: Schema.String.check(Schema.isMaxLength(4096)) }) {};
|
|
835
|
-
const ScheduleMutationRequestFields = {
|
|
836
|
-
schemaVersion: Schema.Literal(1),
|
|
837
|
-
agentId: AgentId,
|
|
838
|
-
input: PersistedJson,
|
|
839
|
-
scope: ScheduleScope,
|
|
840
|
-
scheduleId: ScheduleId,
|
|
841
|
-
timing: ScheduleTimingRequest,
|
|
842
|
-
destination: ScheduleDestination,
|
|
843
|
-
deliveryPrincipal: ScheduleScope.fields.principal,
|
|
844
|
-
definitions: DefinitionDigests
|
|
845
|
-
};
|
|
846
|
-
const ScheduleCreateRequest = Schema.TaggedStruct("Create", ScheduleMutationRequestFields);
|
|
847
|
-
const ScheduleUpdateRequest = Schema.TaggedStruct("Update", {
|
|
848
|
-
...ScheduleMutationRequestFields,
|
|
849
|
-
expectedRevision: Schema.Int.check(Schema.isGreaterThan(0))
|
|
850
|
-
});
|
|
851
|
-
const ScheduleGetRequest = Schema.TaggedStruct("Get", {
|
|
852
|
-
schemaVersion: Schema.Literal(1),
|
|
853
|
-
scope: ScheduleScope,
|
|
854
|
-
scheduleId: ScheduleId
|
|
855
|
-
});
|
|
856
|
-
const ScheduleListRequest = Schema.TaggedStruct("List", {
|
|
857
|
-
schemaVersion: Schema.Literal(1),
|
|
858
|
-
scope: ScheduleScope,
|
|
859
|
-
after: Schema.optionalKey(ScheduleId),
|
|
860
|
-
limit: Schema.optionalKey(Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(100)))
|
|
861
|
-
});
|
|
862
|
-
const ScheduleControlRequest = Schema.TaggedStruct("Control", {
|
|
863
|
-
schemaVersion: Schema.Literal(1),
|
|
864
|
-
operation: Schema.Literals([
|
|
865
|
-
"pause",
|
|
866
|
-
"resume",
|
|
867
|
-
"cancel"
|
|
868
|
-
]),
|
|
869
|
-
scope: ScheduleScope,
|
|
870
|
-
scheduleId: ScheduleId,
|
|
871
|
-
expectedRevision: Schema.Int.check(Schema.isGreaterThan(0))
|
|
872
|
-
});
|
|
873
|
-
const ScheduleOwnerRequest = Schema.Union([
|
|
874
|
-
ScheduleCreateRequest,
|
|
875
|
-
ScheduleUpdateRequest,
|
|
876
|
-
ScheduleGetRequest,
|
|
877
|
-
ScheduleListRequest,
|
|
878
|
-
ScheduleControlRequest
|
|
879
|
-
]);
|
|
880
|
-
const ScheduleOwnerFailure = Schema.Union([
|
|
881
|
-
ScheduleValidationError,
|
|
882
|
-
ScheduleAuthorizationError,
|
|
883
|
-
ScheduleConflict,
|
|
884
|
-
ScheduleNotFound,
|
|
885
|
-
ScheduleCapacityError,
|
|
886
|
-
ScheduleStorageError,
|
|
887
|
-
ScheduleFailpointError,
|
|
888
|
-
ScheduleOwnerProtocolError
|
|
889
|
-
]);
|
|
890
|
-
const ScheduleOwnerResponse = Schema.Union([
|
|
891
|
-
Schema.TaggedStruct("Snapshot", { value: ScheduleSnapshot }),
|
|
892
|
-
Schema.TaggedStruct("Page", { value: ScheduleSnapshotPage }),
|
|
893
|
-
Schema.TaggedStruct("Failed", { failure: ScheduleOwnerFailure })
|
|
894
|
-
]);
|
|
895
|
-
const decodeScheduleOwnerRequest = Schema.decodeUnknownEffect(ScheduleOwnerRequest);
|
|
896
|
-
const encodeScheduleOwnerRequest = Schema.encodeEffect(ScheduleOwnerRequest);
|
|
897
|
-
const decodeScheduleOwnerResponse = Schema.decodeUnknownEffect(ScheduleOwnerResponse);
|
|
898
|
-
const encodeScheduleOwnerResponse = Schema.encodeEffect(ScheduleOwnerResponse);
|
|
899
|
-
const scheduleProtocolFailure = (message) => ({
|
|
900
|
-
_tag: "Failed",
|
|
901
|
-
failure: ScheduleOwnerProtocolError.make({ message: boundedProtocolMessage(message) })
|
|
902
|
-
});
|
|
903
|
-
var ScheduleOwnerNamespace = class extends Context.Service()("@effect-agent/platform-cloudflare/ScheduleOwnerNamespace") {};
|
|
836
|
+
//#region src/prepared-admission.ts
|
|
904
837
|
const passthroughAgent = (agentId) => ({ definition: {
|
|
905
838
|
id: agentId,
|
|
906
839
|
input: PersistedJson
|
|
907
840
|
} });
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
const call = Effect.fn("CloudflareSchedulingClient.call")(function* (owner, request) {
|
|
914
|
-
const encoded = yield* encodeScheduleOwnerRequest(request).pipe(Effect.mapError(() => ScheduleStorageError.make({
|
|
915
|
-
operation: "Schedule Owner protocol",
|
|
916
|
-
reason: "corrupt"
|
|
917
|
-
})));
|
|
918
|
-
const raw = yield* Effect.tryPromise({
|
|
919
|
-
try: () => namespace.get(namespace.idFromName(scheduleOwnerKey(owner))).schedule(encoded),
|
|
920
|
-
catch: () => ScheduleStorageError.make({
|
|
921
|
-
operation: "call Schedule Owner",
|
|
922
|
-
reason: "unavailable"
|
|
923
|
-
})
|
|
924
|
-
});
|
|
925
|
-
const response = yield* decodeScheduleOwnerResponse(raw).pipe(Effect.mapError(() => ScheduleStorageError.make({
|
|
926
|
-
operation: "Schedule Owner protocol",
|
|
927
|
-
reason: "corrupt"
|
|
928
|
-
})));
|
|
929
|
-
if (response._tag !== "Failed") return response;
|
|
930
|
-
return yield* response.failure._tag === "ScheduleOwnerProtocolError" ? ScheduleStorageError.make({
|
|
931
|
-
operation: "Schedule Owner protocol",
|
|
932
|
-
reason: "corrupt"
|
|
933
|
-
}) : response.failure;
|
|
934
|
-
});
|
|
935
|
-
const encodeInput = Effect.fn("CloudflareSchedulingClient.encodeInput")(function* (agent, input) {
|
|
936
|
-
const encoded = yield* Schema.encodeEffect(agent.definition.input)(input).pipe(Effect.mapError(() => ScheduleValidationError.make({ message: "Unable to encode Agent input" })));
|
|
937
|
-
return yield* Schema.decodeUnknownEffect(PersistedJson)(encoded).pipe(Effect.mapError(() => ScheduleValidationError.make({ message: "Agent input does not satisfy the canonical persistence bounds" })));
|
|
938
|
-
});
|
|
939
|
-
const create = (agent, input, options) => Effect.gen(function* () {
|
|
940
|
-
const payload = yield* encodeInput(agent, input);
|
|
941
|
-
const response = yield* call(options.scope.owner, {
|
|
942
|
-
_tag: "Create",
|
|
943
|
-
schemaVersion: 1,
|
|
944
|
-
agentId: agent.definition.id,
|
|
945
|
-
input: payload,
|
|
946
|
-
...options
|
|
947
|
-
});
|
|
948
|
-
return response._tag === "Snapshot" ? response.value : yield* ScheduleStorageError.make({
|
|
949
|
-
operation: "Schedule Owner protocol",
|
|
950
|
-
reason: "corrupt"
|
|
951
|
-
});
|
|
952
|
-
});
|
|
953
|
-
const update = (agent, input, options) => Effect.gen(function* () {
|
|
954
|
-
const payload = yield* encodeInput(agent, input);
|
|
955
|
-
const response = yield* call(options.scope.owner, {
|
|
956
|
-
_tag: "Update",
|
|
957
|
-
schemaVersion: 1,
|
|
958
|
-
agentId: agent.definition.id,
|
|
959
|
-
input: payload,
|
|
960
|
-
...options
|
|
961
|
-
});
|
|
962
|
-
return response._tag === "Snapshot" ? response.value : yield* ScheduleStorageError.make({
|
|
963
|
-
operation: "Schedule Owner protocol",
|
|
964
|
-
reason: "corrupt"
|
|
965
|
-
});
|
|
966
|
-
});
|
|
967
|
-
const get = (scope, scheduleId) => Effect.gen(function* () {
|
|
968
|
-
const response = yield* call(scope.owner, {
|
|
969
|
-
_tag: "Get",
|
|
970
|
-
schemaVersion: 1,
|
|
971
|
-
scope,
|
|
972
|
-
scheduleId
|
|
973
|
-
});
|
|
974
|
-
return response._tag === "Snapshot" ? response.value : yield* ScheduleStorageError.make({
|
|
975
|
-
operation: "Schedule Owner protocol",
|
|
976
|
-
reason: "corrupt"
|
|
977
|
-
});
|
|
978
|
-
});
|
|
979
|
-
const list = (scope, options = {}) => Effect.gen(function* () {
|
|
980
|
-
const response = yield* call(scope.owner, {
|
|
981
|
-
_tag: "List",
|
|
982
|
-
schemaVersion: 1,
|
|
983
|
-
scope,
|
|
984
|
-
...options.after === void 0 ? {} : { after: options.after },
|
|
985
|
-
...options.limit === void 0 ? {} : { limit: options.limit }
|
|
986
|
-
});
|
|
987
|
-
return response._tag === "Page" ? response.value : yield* ScheduleStorageError.make({
|
|
988
|
-
operation: "Schedule Owner protocol",
|
|
989
|
-
reason: "corrupt"
|
|
990
|
-
});
|
|
991
|
-
});
|
|
992
|
-
const control = (operation, scope, scheduleId, expectedRevision) => Effect.gen(function* () {
|
|
993
|
-
const response = yield* call(scope.owner, {
|
|
994
|
-
_tag: "Control",
|
|
995
|
-
schemaVersion: 1,
|
|
996
|
-
operation,
|
|
997
|
-
scope,
|
|
998
|
-
scheduleId,
|
|
999
|
-
expectedRevision
|
|
1000
|
-
});
|
|
1001
|
-
return response._tag === "Snapshot" ? response.value : yield* ScheduleStorageError.make({
|
|
1002
|
-
operation: "Schedule Owner protocol",
|
|
1003
|
-
reason: "corrupt"
|
|
1004
|
-
});
|
|
1005
|
-
});
|
|
1006
|
-
return Scheduling.of({
|
|
1007
|
-
create,
|
|
1008
|
-
update,
|
|
1009
|
-
get,
|
|
1010
|
-
list,
|
|
1011
|
-
pause: (scope, id, revision) => control("pause", scope, id, revision),
|
|
1012
|
-
resume: (scope, id, revision) => control("resume", scope, id, revision),
|
|
1013
|
-
cancel: (scope, id, revision) => control("cancel", scope, id, revision)
|
|
1014
|
-
});
|
|
1015
|
-
}));
|
|
1016
|
-
};
|
|
1017
|
-
var ScheduleOwnerIdentity = class extends Context.Service()("@effect-agent/platform-cloudflare/ScheduleOwnerIdentity") {};
|
|
1018
|
-
const decodeOwnerName = Effect.fn("decodeScheduleOwnerName")(function* (name) {
|
|
1019
|
-
if (name === null || name === void 0) return yield* ScheduleOwnerProtocolError.make({ message: "Schedule Owner objects require an idFromName identity" });
|
|
1020
|
-
const tuple = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Tuple([Schema.String, Schema.String])))(name).pipe(Effect.mapError(() => ScheduleOwnerProtocolError.make({ message: "Schedule Owner object name is malformed" })));
|
|
1021
|
-
return yield* Schema.decodeUnknownEffect(ScheduleOwner)({
|
|
1022
|
-
tenantId: tuple[0],
|
|
1023
|
-
ownerId: tuple[1]
|
|
1024
|
-
}).pipe(Effect.mapError(() => ScheduleOwnerProtocolError.make({ message: "Schedule Owner object identity is invalid" })));
|
|
1025
|
-
});
|
|
1026
|
-
const alarmStorageError = (operation) => (error) => ScheduleStorageError.make({
|
|
1027
|
-
operation,
|
|
1028
|
-
reason: error._tag === "StorageOperationError" ? "unavailable" : "corrupt"
|
|
1029
|
-
});
|
|
1030
|
-
const transactionLayer = Layer.effect(DoScheduleTransaction, Effect.gen(function* () {
|
|
1031
|
-
const alarms = yield* DurableObjectAlarm.DurableObjectAlarm;
|
|
1032
|
-
return DoScheduleTransaction.of({ run: (body) => Effect.gen(function* () {
|
|
1033
|
-
const nowMillis = yield* Clock.currentTimeMillis;
|
|
1034
|
-
return yield* alarms.transaction((transaction) => body((replacement) => replacement.deadlineAtMillis === null ? transaction.cancelAlarm({
|
|
1035
|
-
id: SCHEDULE_ALARM_ID,
|
|
1036
|
-
tag: SCHEDULE_ALARM_TAG
|
|
1037
|
-
}).pipe(Effect.mapError(alarmStorageError("cancel Schedule Owner alarm"))) : Effect.fromOption(DateTime.make(Math.max(replacement.deadlineAtMillis, nowMillis + 1))).pipe(Effect.mapError(() => ScheduleStorageError.make({
|
|
1038
|
-
operation: "validate Schedule Owner alarm deadline",
|
|
1039
|
-
reason: "corrupt"
|
|
1040
|
-
})), Effect.flatMap((runAt) => transaction.scheduleAlarm({
|
|
1041
|
-
id: SCHEDULE_ALARM_ID,
|
|
1042
|
-
tag: SCHEDULE_ALARM_TAG,
|
|
1043
|
-
runAt,
|
|
1044
|
-
payload: {
|
|
1045
|
-
schemaVersion: 1,
|
|
1046
|
-
generation: replacement.generation
|
|
1047
|
-
}
|
|
1048
|
-
}).pipe(Effect.mapError(alarmStorageError("schedule Schedule Owner alarm"))))))).pipe(Effect.catchTag("StorageOperationError", () => ScheduleStorageError.make({
|
|
1049
|
-
operation: "commit Schedule Owner transaction",
|
|
1050
|
-
reason: "unavailable"
|
|
1051
|
-
})));
|
|
1052
|
-
}) });
|
|
1053
|
-
}));
|
|
1054
|
-
const admissionLayer = Layer.effect(ScheduledInputAdmission, Effect.gen(function* () {
|
|
1055
|
-
const client = yield* CloudflareConversationClient;
|
|
1056
|
-
const submit = (envelope) => client.submit(passthroughAgent(envelope.agentId), envelope.input, {
|
|
1057
|
-
conversationId: envelope.conversationId,
|
|
841
|
+
/** Ordinary prepared admission through one freshly addressed Thread Object call. */
|
|
842
|
+
const cloudflarePreparedInputAdmissionLayer = Layer.effect(PreparedInputAdmission, Effect.gen(function* () {
|
|
843
|
+
const client = yield* CloudflareThreadClient;
|
|
844
|
+
return PreparedInputAdmission.of({ submit: (envelope) => client.submit(passthroughAgent(envelope.agentId), envelope.input, {
|
|
845
|
+
threadId: envelope.threadId,
|
|
1058
846
|
principal: envelope.deliveryPrincipal,
|
|
1059
847
|
idempotencyKey: envelope.admissionKey,
|
|
1060
848
|
definitions: envelope.definitions
|
|
1061
849
|
}).pipe(Effect.catchTags({
|
|
1062
850
|
AdmissionConflict: () => ScheduleStorageError.make({
|
|
1063
|
-
operation: "
|
|
851
|
+
operation: "prepared admission",
|
|
1064
852
|
reason: "corrupt"
|
|
1065
853
|
}),
|
|
1066
854
|
AdmissionLimitExceeded: () => ScheduledInputRetryable.make({ reason: "capacity" }),
|
|
1067
|
-
|
|
855
|
+
ThreadClientError: (error) => ScheduledInputRetryable.make({ reason: error.overloaded === true ? "capacity" : "transport" }),
|
|
1068
856
|
HostProtocolError: () => ScheduledInputRetryable.make({ reason: "ambiguous" }),
|
|
1069
857
|
LedgerError: () => ScheduledInputRetryable.make({ reason: "storage" }),
|
|
1070
|
-
|
|
858
|
+
ThreadStoreError: () => ScheduledInputRetryable.make({ reason: "storage" }),
|
|
1071
859
|
DurableAlarmError: () => ScheduledInputRetryable.make({ reason: "storage" }),
|
|
1072
860
|
AgentInputError: () => ScheduleStorageError.make({
|
|
1073
|
-
operation: "
|
|
861
|
+
operation: "prepared admission",
|
|
1074
862
|
reason: "corrupt"
|
|
1075
863
|
}),
|
|
1076
864
|
DigestError: () => ScheduleStorageError.make({
|
|
1077
|
-
operation: "
|
|
865
|
+
operation: "prepared admission",
|
|
1078
866
|
reason: "corrupt"
|
|
1079
867
|
}),
|
|
1080
|
-
|
|
868
|
+
ThreadNotMaterialized: () => ScheduledInputRetryable.make({ reason: "storage" }),
|
|
1081
869
|
AppendConflict: () => ScheduledInputRetryable.make({ reason: "ambiguous" }),
|
|
1082
870
|
FenceRejected: () => ScheduledInputRetryable.make({ reason: "ambiguous" }),
|
|
1083
871
|
DurableRuntimeFailpointError: () => ScheduledInputRetryable.make({ reason: "ambiguous" })
|
|
1084
|
-
}));
|
|
1085
|
-
return ScheduledInputAdmission.of({ submit });
|
|
872
|
+
})) });
|
|
1086
873
|
}));
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
const response = yield* Effect.gen(function* () {
|
|
1098
|
-
yield* ensureOwner(owner, request);
|
|
1099
|
-
switch (request._tag) {
|
|
1100
|
-
case "Create": return {
|
|
1101
|
-
_tag: "Snapshot",
|
|
1102
|
-
value: yield* scheduling.create(passthroughAgent(request.agentId), request.input, {
|
|
1103
|
-
scope: request.scope,
|
|
1104
|
-
scheduleId: request.scheduleId,
|
|
1105
|
-
timing: request.timing,
|
|
1106
|
-
destination: request.destination,
|
|
1107
|
-
deliveryPrincipal: request.deliveryPrincipal,
|
|
1108
|
-
definitions: request.definitions
|
|
1109
|
-
})
|
|
1110
|
-
};
|
|
1111
|
-
case "Update": return {
|
|
1112
|
-
_tag: "Snapshot",
|
|
1113
|
-
value: yield* scheduling.update(passthroughAgent(request.agentId), request.input, {
|
|
1114
|
-
scope: request.scope,
|
|
1115
|
-
scheduleId: request.scheduleId,
|
|
1116
|
-
timing: request.timing,
|
|
1117
|
-
destination: request.destination,
|
|
1118
|
-
deliveryPrincipal: request.deliveryPrincipal,
|
|
1119
|
-
definitions: request.definitions,
|
|
1120
|
-
expectedRevision: request.expectedRevision
|
|
1121
|
-
})
|
|
1122
|
-
};
|
|
1123
|
-
case "Get": return {
|
|
1124
|
-
_tag: "Snapshot",
|
|
1125
|
-
value: yield* scheduling.get(request.scope, request.scheduleId)
|
|
1126
|
-
};
|
|
1127
|
-
case "List": return {
|
|
1128
|
-
_tag: "Page",
|
|
1129
|
-
value: yield* scheduling.list(request.scope, {
|
|
1130
|
-
...request.after === void 0 ? {} : { after: request.after },
|
|
1131
|
-
...request.limit === void 0 ? {} : { limit: request.limit }
|
|
1132
|
-
})
|
|
1133
|
-
};
|
|
1134
|
-
case "Control": return {
|
|
1135
|
-
_tag: "Snapshot",
|
|
1136
|
-
value: request.operation === "pause" ? yield* scheduling.pause(request.scope, request.scheduleId, request.expectedRevision) : request.operation === "resume" ? yield* scheduling.resume(request.scope, request.scheduleId, request.expectedRevision) : yield* scheduling.cancel(request.scope, request.scheduleId, request.expectedRevision)
|
|
1137
|
-
};
|
|
1138
|
-
}
|
|
1139
|
-
}).pipe(Effect.map((value) => value), Effect.catch((failure) => Schema.is(ScheduleOwnerFailure)(failure) ? Effect.succeed({
|
|
1140
|
-
_tag: "Failed",
|
|
1141
|
-
failure
|
|
1142
|
-
}) : Effect.succeed(scheduleProtocolFailure("The Schedule operation failed outside its public contract"))));
|
|
1143
|
-
return yield* encodeScheduleOwnerResponse(response).pipe(Effect.orDie);
|
|
874
|
+
const preparedFromSchedule = (envelope) => ({
|
|
875
|
+
schemaVersion: 1,
|
|
876
|
+
threadId: envelope.threadId,
|
|
877
|
+
deliveryPrincipal: envelope.deliveryPrincipal,
|
|
878
|
+
agentId: envelope.agentId,
|
|
879
|
+
definitions: envelope.definitions,
|
|
880
|
+
input: envelope.input,
|
|
881
|
+
inputDigest: envelope.inputDigest,
|
|
882
|
+
admissionKey: envelope.admissionKey,
|
|
883
|
+
authorization: envelope.authorization
|
|
1144
884
|
});
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
yield* Schema.decodeUnknownEffect(ScheduleAlarmPayload)(event.payload).pipe(Effect.mapError(() => ScheduleAlarmProtocolError.make({ message: "Unsupported Schedule Owner alarm payload version" })));
|
|
1148
|
-
const scheduling = yield* ScheduleDriver;
|
|
1149
|
-
const alarmControl = yield* DoScheduleAlarmControl;
|
|
1150
|
-
const { owner } = yield* ScheduleOwnerIdentity;
|
|
1151
|
-
const nowMillis = yield* Clock.currentTimeMillis;
|
|
1152
|
-
yield* alarmControl.prearm(nowMillis + limits.recoveryPollMillis);
|
|
1153
|
-
if ((yield* scheduling.runDue(owner)).failed > 0) yield* alarmControl.prearm((yield* Clock.currentTimeMillis) + limits.recoveryPollMillis);
|
|
1154
|
-
else yield* alarmControl.reconcile;
|
|
1155
|
-
}), { mode: "ordered" }).pipe(Effect.asVoid);
|
|
1156
|
-
/**
|
|
1157
|
-
* The host Layer supplies authorization and routing and is cached for the object incarnation.
|
|
1158
|
-
* Cloudflare eviction does not guarantee its finalizers run. Do not acquire resources requiring
|
|
1159
|
-
* cleanup in this Layer; acquire them inside scoped `manage` / `prepare` operations instead.
|
|
1160
|
-
* Native services belong to effect-cf; the database and alarm runtime remain instance-owned.
|
|
1161
|
-
*/
|
|
1162
|
-
const makeScheduleOwnerObjectClass = (host, limits = defaultSchedulingLimits) => {
|
|
1163
|
-
const ownerLayer = Layer.effect(ScheduleOwnerIdentity, Effect.gen(function* () {
|
|
1164
|
-
const state = yield* DurableObjectState.DurableObjectState;
|
|
1165
|
-
return ScheduleOwnerIdentity.of({ owner: yield* decodeOwnerName(state.raw.id.name) });
|
|
1166
|
-
}));
|
|
1167
|
-
const sqlLayer = Layer.unwrap(Effect.map(DurableObjectState.DurableObjectState, (state) => SqliteClient.layer({ storage: state.raw.storage })));
|
|
1168
|
-
const application = Layer.merge(Scheduling.layer(limits), ScheduleDriver.layer(limits)).pipe(Layer.provideMerge(scheduleStoreLayer.pipe(Layer.provide(transactionLayer), Layer.provide(sqlLayer))), Layer.provide(admissionLayer.pipe(Layer.provide(CloudflareConversationClient.layer))), Layer.provide(ScheduleWakeNoop), Layer.provide(BrowserCrypto.layer), Layer.provideMerge(DurableObjectAlarm.DurableObjectAlarm.layer), Layer.provide(host), Layer.provideMerge(ownerLayer));
|
|
1169
|
-
const runtime = Layer.effectContext(Effect.gen(function* () {
|
|
1170
|
-
const state = yield* DurableObjectState.DurableObjectState;
|
|
1171
|
-
const scope = yield* Effect.scope;
|
|
1172
|
-
return yield* state.blockConcurrencyWhile(Layer.buildWithScope(application, scope));
|
|
1173
|
-
}));
|
|
1174
|
-
const Base = DurableObject.make(runtime, {
|
|
1175
|
-
initialize: Effect.void,
|
|
1176
|
-
rpc: { schedule: (encoded) => handleScheduleRequest(encoded) },
|
|
1177
|
-
alarms: scheduleAlarmHandler(limits)
|
|
1178
|
-
});
|
|
1179
|
-
class ScheduleOwnerObject extends Base {
|
|
1180
|
-
alarm(alarmInfo) {
|
|
1181
|
-
return super.alarm?.(alarmInfo);
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
return ScheduleOwnerObject;
|
|
1185
|
-
};
|
|
885
|
+
/** Compatibility adapter retaining Scheduling's public admission port. */
|
|
886
|
+
const cloudflareScheduledInputAdmissionLayer = Layer.effect(ScheduledInputAdmission, Effect.map(PreparedInputAdmission, (admission) => ScheduledInputAdmission.of({ submit: (envelope) => admission.submit(preparedFromSchedule(envelope)) })));
|
|
1186
887
|
//#endregion
|
|
1187
|
-
export {
|
|
888
|
+
export { CloudflarePlatformConfigError as $, decodeUnknownResolutionCommand as A, DurableAlarmError as B, decodeApprovalDecisionCommand as C, decodeObservePageRequest as D, decodeHostResponse as E, encodeHostResponse as F, safeCauseDiagnostic as G, MaintenancePassReport as H, encodeObservePageRequest as I, CLOUDFLARE_DATABASE_CAP_BYTES as J, safeCauseMessage as K, encodeReceipt as L, encodeApprovalDecisionCommand as M, encodeAwaitProgressRequest as N, decodeReceipt as O, encodeCancelProgressRequest as P, CloudflareDurableRuntimeConfigValue as Q, encodeSubmitRequest as R, decodeAbortCommand as S, decodeCancelProgressRequest as T, ThreadMaintenance as U, DurableAlarmService as V, ThreadMaintenanceFailpoint as W, CloudflareAdmissionLimitsValue as X, CLOUDFLARE_RUNTIME_DEFAULTS as Y, CloudflareDurableRuntimeConfig as Z, SubmitRequest as _, AwaitProgressRequest as a, threadNamespaceFromEnv as at, UnknownResolutionRecorded as b, HostFailed as c, HostResponse as d, DEFAULT_MAX_DATABASE_BYTES as et, ObservePageRequest as f, SettlementReached as g, ProgressObserved as h, ApprovalRecorded as i, ThreadObjectNamespace as it, encodeAbortCommand as j, decodeSubmitRequest as k, HostFailure as l, ProgressCancelled as m, cloudflareScheduledInputAdmissionLayer as n, DurableObjectContext as nt, CancelProgressRequest as o, threadNamespaceLayer as ot, ObservedPage as p, AdmissionLimitExceeded as q, AbortRecorded as r, ThreadObjectIdentity as rt, CloudflareThreadClient as s, cloudflarePreparedInputAdmissionLayer as t, CloudflareBindingError as tt, HostProtocolError as u, SubmitSucceeded as v, decodeAwaitProgressRequest as w, boundHostDiagnostic as x, ThreadClientError as y, encodeUnknownResolutionCommand as z };
|
|
1188
889
|
|
|
1189
|
-
//# sourceMappingURL=
|
|
890
|
+
//# sourceMappingURL=prepared-admission-BKp_Upw2.mjs.map
|