@effect-agent/platform-cloudflare 0.1.0-beta.38 → 0.1.0-beta.39
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-C00ZfjsX.d.mts +100 -0
- package/dist/index.d.mts +196 -211
- package/dist/index.mjs +157 -163
- package/dist/index.mjs.map +1 -1
- package/dist/interactive-browser.d.mts +144 -2
- package/dist/interactive-browser.mjs +1153 -1
- package/dist/interactive-browser.mjs.map +1 -0
- package/dist/{scheduling-B-OFqoS9.mjs → prepared-admission-D5hQ-ofV.mjs} +121 -432
- package/dist/prepared-admission-D5hQ-ofV.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 +18 -11
- package/src/alarm.ts +264 -265
- package/src/bindings.ts +30 -30
- package/src/client.ts +90 -98
- package/src/config.ts +7 -7
- package/src/index.ts +9 -11
- 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,13 @@
|
|
|
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
|
-
import {
|
|
6
|
-
import { SqliteClient } from "@effect/sql-sqlite-do";
|
|
7
|
-
import { DurableObject, DurableObjectAlarm, DurableObjectState, RpcTracing } from "effect-cf";
|
|
5
|
+
import { RpcTracing } from "effect-cf";
|
|
8
6
|
//#region src/bindings.ts
|
|
9
7
|
/**
|
|
10
8
|
* Cloudflare platform bindings as Effect services (DEPLOY-010: "Cloudflare platform bindings
|
|
11
9
|
* are supplied as Effect services/Layers"). Application code never reads `env` or touches a
|
|
12
|
-
* `DurableObjectState` directly — the
|
|
10
|
+
* `DurableObjectState` directly — the Thread Object class constructs these Layers once
|
|
13
11
|
* per incarnation and everything downstream consumes the services.
|
|
14
12
|
*/
|
|
15
13
|
/** A Cloudflare platform binding was missing or carried the wrong shape (DEPLOY-003/010). */
|
|
@@ -18,13 +16,13 @@ var CloudflareBindingError = class extends Schema.TaggedError()("CloudflareBindi
|
|
|
18
16
|
message: Schema.String
|
|
19
17
|
}) {};
|
|
20
18
|
/**
|
|
21
|
-
* The `DurableObjectNamespace` binding that addresses
|
|
22
|
-
* identity rule is `namespace.idFromName(
|
|
19
|
+
* The `DurableObjectNamespace` binding that addresses Thread Objects. The Object
|
|
20
|
+
* identity rule is `namespace.idFromName(threadId)` (plan §1.2): Thread IDs are
|
|
23
21
|
* globally unique, so the mapping is total and deterministic and no directory service exists.
|
|
24
22
|
*/
|
|
25
|
-
var
|
|
23
|
+
var ThreadObjectNamespace = class ThreadObjectNamespace extends Context.Service()("@effect-agent/platform-cloudflare/ThreadObjectNamespace") {
|
|
26
24
|
static layer(namespace) {
|
|
27
|
-
return Layer.succeed(
|
|
25
|
+
return Layer.succeed(ThreadObjectNamespace)({ namespace });
|
|
28
26
|
}
|
|
29
27
|
};
|
|
30
28
|
/**
|
|
@@ -33,7 +31,7 @@ var ConversationObjectNamespace = class ConversationObjectNamespace extends Cont
|
|
|
33
31
|
* narrowest-boundary check (structural probe for the namespace surface the transport uses);
|
|
34
32
|
* a missing or misshaped binding fails typed before any Layer is built.
|
|
35
33
|
*/
|
|
36
|
-
const
|
|
34
|
+
const threadNamespaceFromEnv = Effect.fn("threadNamespaceFromEnv")(function* (env, binding) {
|
|
37
35
|
if (!Predicate.isObjectKeyword(env)) return yield* CloudflareBindingError.make({
|
|
38
36
|
binding,
|
|
39
37
|
message: "The Worker environment is not an object; no bindings are available."
|
|
@@ -54,14 +52,14 @@ const conversationNamespaceFromEnv = Effect.fn("conversationNamespaceFromEnv")(f
|
|
|
54
52
|
if (candidate !== void 0) return candidate;
|
|
55
53
|
return yield* CloudflareBindingError.make({
|
|
56
54
|
binding,
|
|
57
|
-
message: `env.${binding} is not a DurableObjectNamespace binding; declare the
|
|
55
|
+
message: `env.${binding} is not a DurableObjectNamespace binding; declare the Thread Object class under this binding in the Worker configuration.`
|
|
58
56
|
});
|
|
59
57
|
});
|
|
60
58
|
/**
|
|
61
59
|
* Build the namespace from Worker `env` (fails typed). Enable `rpcTracing` only when the
|
|
62
60
|
* receiver also opts into the effect-cf native RPC trace-context contract.
|
|
63
61
|
*/
|
|
64
|
-
const
|
|
62
|
+
const threadNamespaceLayer = (env, binding, options = {}) => Layer.effect(ThreadObjectNamespace)(Effect.map(threadNamespaceFromEnv(env, binding), (namespace) => ({
|
|
65
63
|
namespace,
|
|
66
64
|
...options.rpcTracing === true ? { rpcTracing: binding } : {}
|
|
67
65
|
})));
|
|
@@ -79,11 +77,11 @@ var DurableObjectContext = class DurableObjectContext extends Context.Service()(
|
|
|
79
77
|
}
|
|
80
78
|
};
|
|
81
79
|
/**
|
|
82
|
-
* The
|
|
83
|
-
* write with (`{producerPrefix}:{
|
|
84
|
-
* from `ctx.id.name` — the Object identity rule guarantees the name IS the
|
|
80
|
+
* The Thread identity this Object serializes and the producer identity its Attempts
|
|
81
|
+
* write with (`{producerPrefix}:{threadId}`, plan §1.4). Derived once per incarnation
|
|
82
|
+
* from `ctx.id.name` — the Object identity rule guarantees the name IS the Thread ID.
|
|
85
83
|
*/
|
|
86
|
-
var
|
|
84
|
+
var ThreadObjectIdentity = class extends Context.Service()("@effect-agent/platform-cloudflare/ThreadObjectIdentity") {};
|
|
87
85
|
//#endregion
|
|
88
86
|
//#region src/config.ts
|
|
89
87
|
/**
|
|
@@ -103,7 +101,7 @@ var CloudflarePlatformConfigError = class extends Schema.TaggedError()("Cloudfla
|
|
|
103
101
|
* (deployment spec §8, DEPLOY-007: "Admission has explicit bounded quota and overload
|
|
104
102
|
* behavior... a typed rejection"). This is the DC analogue of `NodeDurableHost`'s
|
|
105
103
|
* `AdmissionClosed` host gate: the port surface stays untouched, the refusal happens in the
|
|
106
|
-
*
|
|
104
|
+
* Thread Object's submit entry point before `DurableAgentRuntime.submit` runs, and
|
|
107
105
|
* nothing was admitted or written.
|
|
108
106
|
*/
|
|
109
107
|
var AdmissionLimitExceeded = class extends Schema.TaggedError()("AdmissionLimitExceeded", {
|
|
@@ -124,11 +122,11 @@ const CLOUDFLARE_DATABASE_CAP_BYTES = 1e10;
|
|
|
124
122
|
/** Default database-size admission ceiling: a 1 GB safety margin under the platform cap. */
|
|
125
123
|
const DEFAULT_MAX_DATABASE_BYTES = 9e9;
|
|
126
124
|
/**
|
|
127
|
-
* Explicit bounded admission quotas checked by the
|
|
125
|
+
* Explicit bounded admission quotas checked by the Thread Object BEFORE admission
|
|
128
126
|
* (exit gate "resource limits are checked before admission").
|
|
129
127
|
*/
|
|
130
128
|
var CloudflareAdmissionLimitsValue = class extends Schema.Class("@effect-agent/platform-cloudflare/CloudflareAdmissionLimitsValue")({
|
|
131
|
-
/** Maximum nonterminal Submissions per
|
|
129
|
+
/** Maximum nonterminal Submissions per Thread lane before new admissions refuse. */
|
|
132
130
|
maxQueueDepthPerLane: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(1e5)),
|
|
133
131
|
/** Maximum encoded input bytes; never above the storage per-value bound. */
|
|
134
132
|
maxInputBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(2e6)),
|
|
@@ -137,13 +135,13 @@ var CloudflareAdmissionLimitsValue = class extends Schema.Class("@effect-agent/p
|
|
|
137
135
|
}) {};
|
|
138
136
|
/**
|
|
139
137
|
* Validated Cloudflare durable runtime configuration. The producer identity of one
|
|
140
|
-
*
|
|
138
|
+
* Thread Object is `{producerPrefix}:{threadId}` — stable across incarnations of
|
|
141
139
|
* the same deployment, distinct across deployments — and producer-epoch fencing (not the
|
|
142
140
|
* producer name) remains the correctness authority (DUR-006).
|
|
143
141
|
*/
|
|
144
142
|
var CloudflareDurableRuntimeConfigValue = class extends Schema.Class("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfigValue")({
|
|
145
143
|
deploymentId: DeploymentId,
|
|
146
|
-
/** Head of the minted producer identity `{producerPrefix}:{
|
|
144
|
+
/** Head of the minted producer identity `{producerPrefix}:{threadId}`. */
|
|
147
145
|
producerPrefix: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
|
|
148
146
|
/** Submission ownership lease duration (D5); fences work across Object incarnations. */
|
|
149
147
|
ownershipLeaseDuration: PositiveMillis,
|
|
@@ -172,7 +170,7 @@ var CloudflareDurableRuntimeConfigValue = class extends Schema.Class("@effect-ag
|
|
|
172
170
|
}) {};
|
|
173
171
|
/** Explicit configuration authority for the assembled Cloudflare durable runtime. */
|
|
174
172
|
var CloudflareDurableRuntimeConfig = class extends Context.Service()("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfig") {};
|
|
175
|
-
/** Documented production defaults applied by `
|
|
173
|
+
/** Documented production defaults applied by `ThreadObject.layer`. */
|
|
176
174
|
const CLOUDFLARE_RUNTIME_DEFAULTS = {
|
|
177
175
|
ownershipLeaseDuration: Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION),
|
|
178
176
|
alarmBackoffBase: 100,
|
|
@@ -300,21 +298,21 @@ var MaintenancePassReport = class extends Schema.Class("@effect-agent/platform-c
|
|
|
300
298
|
alarm: Schema.Literals(["rearmed", "cleared"])
|
|
301
299
|
}) {};
|
|
302
300
|
/** Test-only fault authority; production uses the inert layer. */
|
|
303
|
-
var
|
|
301
|
+
var ThreadMaintenanceFailpoint = class extends Context.Service()("@effect-agent/platform-cloudflare/ThreadMaintenanceFailpoint") {
|
|
304
302
|
static layer = Layer.succeed(this)({ hit: () => Effect.void });
|
|
305
303
|
};
|
|
306
304
|
const MaintenanceGeneration = Schema.BigIntFromString.check(Schema.isGreaterThanOrEqualToBigInt(0n));
|
|
307
305
|
/** Versioned, platform-private maintenance state stored through Durable Object KV. */
|
|
308
|
-
var
|
|
306
|
+
var ThreadMaintenanceState = class extends Schema.Class("@effect-agent/platform-cloudflare/ThreadMaintenanceState")({
|
|
309
307
|
schemaVersion: Schema.Literal(1),
|
|
310
308
|
dirty: MaintenanceGeneration,
|
|
311
309
|
processed: MaintenanceGeneration,
|
|
312
310
|
nonterminal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))
|
|
313
311
|
}) {};
|
|
314
|
-
const MAINTENANCE_STATE_KEY = "effect-agent:
|
|
315
|
-
const decodeMaintenanceState = Schema.decodeUnknownSync(
|
|
316
|
-
const encodeMaintenanceState = Schema.encodeSync(
|
|
317
|
-
const initialMaintenanceState = () =>
|
|
312
|
+
const MAINTENANCE_STATE_KEY = "effect-agent:thread-maintenance:v1";
|
|
313
|
+
const decodeMaintenanceState = Schema.decodeUnknownSync(ThreadMaintenanceState);
|
|
314
|
+
const encodeMaintenanceState = Schema.encodeSync(ThreadMaintenanceState);
|
|
315
|
+
const initialMaintenanceState = () => ThreadMaintenanceState.make({
|
|
318
316
|
schemaVersion: 1,
|
|
319
317
|
dirty: 1n,
|
|
320
318
|
processed: 0n,
|
|
@@ -363,16 +361,15 @@ const stableExternalWait = (snapshot, reports) => {
|
|
|
363
361
|
* 4. Stable external waits acknowledge and clear. Autonomous retry, indeterminate, and lease
|
|
364
362
|
* recovery states leave their generation dirty and retain bounded backoff rearming.
|
|
365
363
|
*/
|
|
366
|
-
var
|
|
367
|
-
static layer = Layer.effect(
|
|
364
|
+
var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effect-agent/platform-cloudflare/ThreadMaintenance") {
|
|
365
|
+
static layer = (bindings) => Layer.effect(ThreadMaintenance)(Effect.gen(function* () {
|
|
368
366
|
const runtime = yield* DurableAgentRuntime;
|
|
369
|
-
const resolver = yield* AgentBindingResolver;
|
|
370
367
|
const ledger = yield* SubmissionLedger;
|
|
371
368
|
const alarm = yield* DurableAlarmService;
|
|
372
369
|
const config = yield* CloudflareDurableRuntimeConfig;
|
|
373
|
-
const identity = yield*
|
|
370
|
+
const identity = yield* ThreadObjectIdentity;
|
|
374
371
|
const { ctx } = yield* DurableObjectContext;
|
|
375
|
-
const failpoint = yield*
|
|
372
|
+
const failpoint = yield* ThreadMaintenanceFailpoint;
|
|
376
373
|
/**
|
|
377
374
|
* Consecutive no-progress passes — an in-memory CACHE, not state: a fresh incarnation
|
|
378
375
|
* restarts at zero and merely re-arms sooner than a long-lived one would have.
|
|
@@ -392,12 +389,12 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
392
389
|
try: transaction,
|
|
393
390
|
catch: alarmFailure(operation)
|
|
394
391
|
});
|
|
395
|
-
const beginMutation = Effect.fn("
|
|
392
|
+
const beginMutation = Effect.fn("ThreadMaintenance.beginMutation")(function* () {
|
|
396
393
|
yield* failpoint.hit("maintenance:dirty:before");
|
|
397
394
|
const now = yield* Clock.currentTimeMillis;
|
|
398
395
|
yield* runTransaction("advance maintenance generation", () => ctx.storage.transaction(async (transaction) => {
|
|
399
396
|
const { state } = await readMaintenanceState(transaction);
|
|
400
|
-
const next =
|
|
397
|
+
const next = ThreadMaintenanceState.make({
|
|
401
398
|
...state,
|
|
402
399
|
dirty: state.dirty + 1n
|
|
403
400
|
});
|
|
@@ -409,7 +406,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
409
406
|
});
|
|
410
407
|
const endMutation = generationGate.withPermit(Ref.update(activeMutations, (active) => Math.max(0, active - 1)));
|
|
411
408
|
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("
|
|
409
|
+
const ensureAlarm = Effect.fn("ThreadMaintenance.ensureAlarm")(function* () {
|
|
413
410
|
yield* failpoint.hit("maintenance:ensure:before");
|
|
414
411
|
const now = yield* Clock.currentTimeMillis;
|
|
415
412
|
yield* runTransaction("ensure maintenance alarm", () => ctx.storage.transaction(async (transaction) => {
|
|
@@ -419,7 +416,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
419
416
|
}));
|
|
420
417
|
yield* failpoint.hit("maintenance:ensure:after");
|
|
421
418
|
});
|
|
422
|
-
const beginPass = Effect.fn("
|
|
419
|
+
const beginPass = Effect.fn("ThreadMaintenance.beginPass")(function* () {
|
|
423
420
|
yield* failpoint.hit("maintenance:begin:before");
|
|
424
421
|
const now = yield* Clock.currentTimeMillis;
|
|
425
422
|
const result = yield* runTransaction("begin maintenance pass", () => ctx.storage.transaction(async (transaction) => {
|
|
@@ -441,7 +438,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
441
438
|
yield* failpoint.hit("maintenance:begin:after");
|
|
442
439
|
return result;
|
|
443
440
|
});
|
|
444
|
-
const rearmDelay = Effect.fn("
|
|
441
|
+
const rearmDelay = Effect.fn("ThreadMaintenance.rearmDelay")(function* (progressed) {
|
|
445
442
|
const priorStalls = yield* Ref.getAndUpdate(stalls, (count) => progressed ? 0 : count + 1);
|
|
446
443
|
if (progressed) return config.alarmBackoffBase;
|
|
447
444
|
const exponent = Math.min(priorStalls, 30);
|
|
@@ -450,7 +447,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
450
447
|
const jittered = Math.ceil(backoff / 2 + backoff / 2 * jitter);
|
|
451
448
|
return Math.min(jittered, config.wakeScanInterval);
|
|
452
449
|
});
|
|
453
|
-
const pass = Effect.fn("
|
|
450
|
+
const pass = Effect.fn("ThreadMaintenance.pass")(function* () {
|
|
454
451
|
const annotate = (report) => Effect.annotateCurrentSpan({
|
|
455
452
|
phase: report.phase,
|
|
456
453
|
recovered: report.recovered,
|
|
@@ -473,7 +470,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
473
470
|
alarm: "cleared"
|
|
474
471
|
}));
|
|
475
472
|
const recovered = yield* runtime.runRecovery;
|
|
476
|
-
const settlements = yield* runtime.
|
|
473
|
+
const settlements = yield* runtime.processThreadResolved(identity.threadId, bindings);
|
|
477
474
|
const remaining = yield* Stream.runCollect(ledger.scanNonterminal);
|
|
478
475
|
const reports = new Map(recovered.map((report) => [report.submissionId, report]));
|
|
479
476
|
const head = remaining[0];
|
|
@@ -491,7 +488,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
491
488
|
return yield* runTransaction("finish maintenance pass", () => ctx.storage.transaction(async (transaction) => {
|
|
492
489
|
const { state } = await readMaintenanceState(transaction);
|
|
493
490
|
const processed = autonomous || started.activeAtStart > 0 || active > 0 ? state.processed : state.processed > started.generation ? state.processed : started.generation;
|
|
494
|
-
const next =
|
|
491
|
+
const next = ThreadMaintenanceState.make({
|
|
495
492
|
...state,
|
|
496
493
|
processed,
|
|
497
494
|
nonterminal: remaining.length
|
|
@@ -519,7 +516,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
519
516
|
alarm: alarmDisposition
|
|
520
517
|
}));
|
|
521
518
|
});
|
|
522
|
-
return
|
|
519
|
+
return ThreadMaintenance.of({
|
|
523
520
|
pass: alarm.withWakesDeferred(maintenancePassGate.withPermit(pass())),
|
|
524
521
|
ensureAlarm: ensureAlarm(),
|
|
525
522
|
withMutation
|
|
@@ -529,7 +526,7 @@ var ConversationMaintenance = class ConversationMaintenance extends Context.Serv
|
|
|
529
526
|
//#endregion
|
|
530
527
|
//#region src/client.ts
|
|
531
528
|
/**
|
|
532
|
-
* The Worker↔
|
|
529
|
+
* The Worker↔Thread-Object host protocol (plan §1.4): Schema envelopes for the host
|
|
533
530
|
* entry points (`submitEncoded`, `awaitSettlementEncoded`, `observePage`, `abortEncoded`,
|
|
534
531
|
* `resolveApprovalEncoded`, `resolveUnknownEncoded`) plus the Worker-side client that speaks
|
|
535
532
|
* it. Mirrors the WP2 port protocol: requests and responses are closed Schema unions, typed
|
|
@@ -547,8 +544,8 @@ const boundHostDiagnostic = (value) => value.length > MAX_HOST_DIAGNOSTIC_LENGTH
|
|
|
547
544
|
*/
|
|
548
545
|
var HostProtocolError = class extends Schema.TaggedError()("HostProtocolError", { message: BoundedDiagnostic }) {};
|
|
549
546
|
/** The Worker-side stub call itself failed (RPC rejection, overload, eviction mid-call). */
|
|
550
|
-
var
|
|
551
|
-
|
|
547
|
+
var ThreadClientError = class extends Schema.TaggedError()("ThreadClientError", {
|
|
548
|
+
threadId: Schema.String,
|
|
552
549
|
message: Schema.String,
|
|
553
550
|
cause: Schema.optionalKey(Schema.Defect()),
|
|
554
551
|
/** Cloudflare's own classification for a failure safe to retry with a fresh stub. */
|
|
@@ -560,7 +557,7 @@ var ConversationClientError = class extends Schema.TaggedError()("ConversationCl
|
|
|
560
557
|
* One durable submission, input ALREADY encoded by the caller through the Agent Binding's
|
|
561
558
|
* input schema (the Worker bundles the same Agent definitions as the Object, so schema
|
|
562
559
|
* validation happens client-side; the resolved Binding re-validates at claim time). The
|
|
563
|
-
*
|
|
560
|
+
* Thread identity is deliberately absent — the addressed Object IS the lane.
|
|
564
561
|
*/
|
|
565
562
|
var SubmitRequest = class extends Schema.Class("@effect-agent/platform-cloudflare/SubmitRequest")({
|
|
566
563
|
agentId: AgentId,
|
|
@@ -596,8 +593,8 @@ const HostFailure = Schema.Union([
|
|
|
596
593
|
UnknownResolutionConflict,
|
|
597
594
|
JoinedToHost,
|
|
598
595
|
LedgerError,
|
|
599
|
-
|
|
600
|
-
|
|
596
|
+
ThreadStoreError,
|
|
597
|
+
ThreadNotMaterialized,
|
|
601
598
|
AppendConflict,
|
|
602
599
|
FenceRejected,
|
|
603
600
|
DurableRuntimeFailpointError,
|
|
@@ -647,14 +644,14 @@ const decodeUnknownResolutionCommand = Schema.decodeUnknownEffect(UnknownResolut
|
|
|
647
644
|
const encodeUnknownResolutionCommand = Schema.encodeEffect(UnknownResolutionCommand);
|
|
648
645
|
const encodeHostResponse = Schema.encodeEffect(HostResponse);
|
|
649
646
|
const decodeHostResponse = Schema.decodeUnknownEffect(HostResponse);
|
|
650
|
-
/** Failure surface of `
|
|
647
|
+
/** Failure surface of `CloudflareThreadClient.submit`. */
|
|
651
648
|
const ClientSubmitHostFailure = Schema.Union([
|
|
652
649
|
AgentInputError,
|
|
653
650
|
DigestError,
|
|
654
651
|
AdmissionConflict,
|
|
655
652
|
LedgerError,
|
|
656
|
-
|
|
657
|
-
|
|
653
|
+
ThreadStoreError,
|
|
654
|
+
ThreadNotMaterialized,
|
|
658
655
|
AppendConflict,
|
|
659
656
|
FenceRejected,
|
|
660
657
|
DurableRuntimeFailpointError,
|
|
@@ -665,15 +662,17 @@ const ClientSubmitHostFailure = Schema.Union([
|
|
|
665
662
|
const ClientAwaitHostFailure = Schema.Union([
|
|
666
663
|
LedgerError,
|
|
667
664
|
SettlementConflict,
|
|
665
|
+
OperationDenied,
|
|
668
666
|
HostProtocolError
|
|
669
667
|
]);
|
|
670
668
|
const ClientObserveHostFailure = Schema.Union([
|
|
671
|
-
|
|
672
|
-
|
|
669
|
+
ThreadStoreError,
|
|
670
|
+
ThreadNotMaterialized,
|
|
673
671
|
OperationDenied,
|
|
674
672
|
HostProtocolError
|
|
675
673
|
]);
|
|
676
674
|
const ClientAbortHostFailure = Schema.Union([
|
|
675
|
+
OperationDenied,
|
|
677
676
|
LedgerError,
|
|
678
677
|
SettlementConflict,
|
|
679
678
|
JoinedToHost,
|
|
@@ -699,9 +698,9 @@ const ClientUnknownHostFailure = Schema.Union([
|
|
|
699
698
|
DurableAlarmError,
|
|
700
699
|
HostProtocolError
|
|
701
700
|
]);
|
|
702
|
-
const outOfContract = (
|
|
703
|
-
|
|
704
|
-
message: boundHostDiagnostic(`The
|
|
701
|
+
const outOfContract = (threadId, operation, observed) => ThreadClientError.make({
|
|
702
|
+
threadId,
|
|
703
|
+
message: boundHostDiagnostic(`The Thread Object answered ${operation} with the out-of-contract ${observed}.`)
|
|
705
704
|
});
|
|
706
705
|
const hostRpcMethods = {
|
|
707
706
|
submit: "submitEncoded",
|
|
@@ -713,52 +712,52 @@ const hostRpcMethods = {
|
|
|
713
712
|
resolveApproval: "resolveApprovalEncoded",
|
|
714
713
|
resolveUnknown: "resolveUnknownEncoded"
|
|
715
714
|
};
|
|
716
|
-
/** Worker-side client over the
|
|
717
|
-
var
|
|
718
|
-
static layer = Layer.effect(
|
|
719
|
-
const { namespace, rpcTracing } = yield*
|
|
715
|
+
/** Worker-side client over the Thread Object namespace (DEPLOY-010). */
|
|
716
|
+
var CloudflareThreadClient = class CloudflareThreadClient extends Context.Service()("@effect-agent/platform-cloudflare/CloudflareThreadClient") {
|
|
717
|
+
static layer = Layer.effect(CloudflareThreadClient)(Effect.gen(function* () {
|
|
718
|
+
const { namespace, rpcTracing } = yield* ThreadObjectNamespace;
|
|
720
719
|
const crypto = yield* Crypto.Crypto;
|
|
721
|
-
const call = Effect.fn(function* (
|
|
720
|
+
const call = Effect.fn(function* (threadId, operation, encoded) {
|
|
722
721
|
const traceArgs = rpcTracing === void 0 ? [] : yield* RpcTracing.withRpcTraceContext([]);
|
|
723
722
|
const raw = yield* Effect.tryPromise({
|
|
724
723
|
try: () => {
|
|
725
|
-
return namespace.get(namespace.idFromName(
|
|
724
|
+
return namespace.get(namespace.idFromName(threadId))[hostRpcMethods[operation]](encoded, ...traceArgs);
|
|
726
725
|
},
|
|
727
|
-
catch: (cause) =>
|
|
728
|
-
|
|
729
|
-
message: boundHostDiagnostic(`${operation} did not reach the
|
|
726
|
+
catch: (cause) => ThreadClientError.make({
|
|
727
|
+
threadId,
|
|
728
|
+
message: boundHostDiagnostic(`${operation} did not reach the Thread Object: ${safeCauseMessage(cause, "the RPC failed without a diagnostic")}`),
|
|
730
729
|
cause,
|
|
731
730
|
...cloudflareFailureSignals(cause)
|
|
732
731
|
})
|
|
733
732
|
});
|
|
734
733
|
return yield* decodeHostResponse(raw).pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`The ${operation} answer could not be decoded: ${error.message}`) })));
|
|
735
|
-
}, (effect,
|
|
736
|
-
|
|
734
|
+
}, (effect, threadId, operation) => rpcTracing === void 0 ? Effect.withSpan(effect, "CloudflareThreadClient.call", { attributes: {
|
|
735
|
+
threadId,
|
|
737
736
|
operation
|
|
738
737
|
} }) : RpcTracing.withRpcClientSpan(effect, rpcTracing, hostRpcMethods[operation]));
|
|
739
|
-
const expect = (
|
|
738
|
+
const expect = (threadId, operation, resultSchema, failureSchema) => {
|
|
740
739
|
const isExpectedResult = Schema.is(resultSchema);
|
|
741
740
|
const isExpectedFailure = Schema.is(failureSchema);
|
|
742
741
|
return (response) => {
|
|
743
742
|
if (response._tag === "HostFailed") {
|
|
744
743
|
const failure = response.failure;
|
|
745
|
-
return isExpectedFailure(failure) ? Effect.fail(failure) : Effect.fail(outOfContract(
|
|
744
|
+
return isExpectedFailure(failure) ? Effect.fail(failure) : Effect.fail(outOfContract(threadId, operation, `failure ${failure._tag}`));
|
|
746
745
|
}
|
|
747
|
-
if (!isExpectedResult(response)) return Effect.fail(outOfContract(
|
|
746
|
+
if (!isExpectedResult(response)) return Effect.fail(outOfContract(threadId, operation, `result ${response._tag}`));
|
|
748
747
|
return Effect.succeed(response);
|
|
749
748
|
};
|
|
750
749
|
};
|
|
751
|
-
const readPage = (
|
|
750
|
+
const readPage = (threadId, options) => Effect.gen(function* () {
|
|
752
751
|
const request = ObservePageRequest.make({
|
|
753
752
|
...options?.afterSequence === void 0 ? {} : { afterSequence: options.afterSequence },
|
|
754
753
|
limit: options?.limit ?? 256
|
|
755
754
|
});
|
|
756
755
|
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(
|
|
756
|
+
const response = yield* call(threadId, "observePage", encoded);
|
|
757
|
+
return (yield* expect(threadId, "observePage", ObservedPage, ClientObserveHostFailure)(response)).records;
|
|
759
758
|
});
|
|
760
|
-
const cancelProgress = (
|
|
761
|
-
return
|
|
759
|
+
const cancelProgress = (threadId, waiterId) => encodeCancelProgressRequest(CancelProgressRequest.make({ waiterId })).pipe(Effect.mapError(() => void 0), Effect.flatMap((encoded) => call(threadId, "cancelProgress", encoded)), Effect.asVoid, Effect.ignore);
|
|
760
|
+
return CloudflareThreadClient.of({
|
|
762
761
|
submit: (agent, input, options) => Effect.gen(function* () {
|
|
763
762
|
const encodedInput = yield* Schema.encodeEffect(agent.definition.input)(input).pipe(Effect.mapError((cause) => AgentInputError.make({ message: `Unable to encode Agent input: ${cause.message}` })));
|
|
764
763
|
const inputPayload = yield* Schema.decodeUnknownEffect(PersistedJson)(encodedInput).pipe(Effect.mapError(() => AgentInputError.make({ message: "Agent input does not satisfy the canonical persistence bounds" })));
|
|
@@ -770,30 +769,30 @@ var CloudflareConversationClient = class CloudflareConversationClient extends Co
|
|
|
770
769
|
inputPayload
|
|
771
770
|
});
|
|
772
771
|
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.
|
|
772
|
+
const response = yield* call(options.threadId, "submit", encoded);
|
|
773
|
+
return (yield* expect(options.threadId, "submit", SubmitSucceeded, ClientSubmitHostFailure)(response)).receipt;
|
|
775
774
|
}),
|
|
776
775
|
awaitSettlement: (receipt) => Effect.gen(function* () {
|
|
777
776
|
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.
|
|
777
|
+
const response = yield* call(receipt.threadId, "awaitSettlement", encoded);
|
|
778
|
+
return (yield* expect(receipt.threadId, "awaitSettlement", SettlementReached, ClientAwaitHostFailure)(response)).settlement;
|
|
780
779
|
}),
|
|
781
|
-
awaitProgress: (
|
|
780
|
+
awaitProgress: (threadId, afterSequence) => Effect.gen(function* () {
|
|
782
781
|
const waiterId = yield* crypto.randomUUIDv4.pipe(Effect.mapError((error) => HostProtocolError.make({ message: boundHostDiagnostic(`awaitProgress cancellation identity generation failed: ${error.message}`) })));
|
|
783
782
|
const request = AwaitProgressRequest.make({
|
|
784
783
|
afterSequence,
|
|
785
784
|
waiterId
|
|
786
785
|
});
|
|
787
786
|
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(
|
|
787
|
+
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)));
|
|
788
|
+
yield* attempt(0).pipe(Effect.onInterrupt(() => cancelProgress(threadId, waiterId)));
|
|
790
789
|
}),
|
|
791
790
|
readPage,
|
|
792
|
-
readAll: (
|
|
791
|
+
readAll: (threadId) => Effect.gen(function* () {
|
|
793
792
|
const all = [];
|
|
794
793
|
let after;
|
|
795
794
|
for (;;) {
|
|
796
|
-
const page = yield* readPage(
|
|
795
|
+
const page = yield* readPage(threadId, {
|
|
797
796
|
afterSequence: after,
|
|
798
797
|
limit: 1024
|
|
799
798
|
});
|
|
@@ -803,387 +802,77 @@ var CloudflareConversationClient = class CloudflareConversationClient extends Co
|
|
|
803
802
|
after = last.sequence;
|
|
804
803
|
}
|
|
805
804
|
}),
|
|
806
|
-
abort: (
|
|
805
|
+
abort: (threadId, command) => Effect.gen(function* () {
|
|
807
806
|
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(
|
|
807
|
+
const response = yield* call(threadId, "abort", encoded);
|
|
808
|
+
return (yield* expect(threadId, "abort", AbortRecorded, ClientAbortHostFailure)(response)).intent;
|
|
810
809
|
}),
|
|
811
|
-
resolveApproval: (
|
|
810
|
+
resolveApproval: (threadId, command) => Effect.gen(function* () {
|
|
812
811
|
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(
|
|
812
|
+
const response = yield* call(threadId, "resolveApproval", encoded);
|
|
813
|
+
return (yield* expect(threadId, "resolveApproval", ApprovalRecorded, ClientApprovalHostFailure)(response)).intent;
|
|
815
814
|
}),
|
|
816
|
-
resolveUnknown: (
|
|
815
|
+
resolveUnknown: (threadId, command) => Effect.gen(function* () {
|
|
817
816
|
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(
|
|
817
|
+
const response = yield* call(threadId, "resolveUnknown", encoded);
|
|
818
|
+
return (yield* expect(threadId, "resolveUnknown", UnknownResolutionRecorded, ClientUnknownHostFailure)(response)).intent;
|
|
820
819
|
})
|
|
821
820
|
});
|
|
822
821
|
}));
|
|
823
822
|
};
|
|
824
823
|
//#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") {};
|
|
824
|
+
//#region src/prepared-admission.ts
|
|
904
825
|
const passthroughAgent = (agentId) => ({ definition: {
|
|
905
826
|
id: agentId,
|
|
906
827
|
input: PersistedJson
|
|
907
828
|
} });
|
|
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,
|
|
829
|
+
/** Ordinary prepared admission through one freshly addressed Thread Object call. */
|
|
830
|
+
const cloudflarePreparedInputAdmissionLayer = Layer.effect(PreparedInputAdmission, Effect.gen(function* () {
|
|
831
|
+
const client = yield* CloudflareThreadClient;
|
|
832
|
+
return PreparedInputAdmission.of({ submit: (envelope) => client.submit(passthroughAgent(envelope.agentId), envelope.input, {
|
|
833
|
+
threadId: envelope.threadId,
|
|
1058
834
|
principal: envelope.deliveryPrincipal,
|
|
1059
835
|
idempotencyKey: envelope.admissionKey,
|
|
1060
836
|
definitions: envelope.definitions
|
|
1061
837
|
}).pipe(Effect.catchTags({
|
|
1062
838
|
AdmissionConflict: () => ScheduleStorageError.make({
|
|
1063
|
-
operation: "
|
|
839
|
+
operation: "prepared admission",
|
|
1064
840
|
reason: "corrupt"
|
|
1065
841
|
}),
|
|
1066
842
|
AdmissionLimitExceeded: () => ScheduledInputRetryable.make({ reason: "capacity" }),
|
|
1067
|
-
|
|
843
|
+
ThreadClientError: (error) => ScheduledInputRetryable.make({ reason: error.overloaded === true ? "capacity" : "transport" }),
|
|
1068
844
|
HostProtocolError: () => ScheduledInputRetryable.make({ reason: "ambiguous" }),
|
|
1069
845
|
LedgerError: () => ScheduledInputRetryable.make({ reason: "storage" }),
|
|
1070
|
-
|
|
846
|
+
ThreadStoreError: () => ScheduledInputRetryable.make({ reason: "storage" }),
|
|
1071
847
|
DurableAlarmError: () => ScheduledInputRetryable.make({ reason: "storage" }),
|
|
1072
848
|
AgentInputError: () => ScheduleStorageError.make({
|
|
1073
|
-
operation: "
|
|
849
|
+
operation: "prepared admission",
|
|
1074
850
|
reason: "corrupt"
|
|
1075
851
|
}),
|
|
1076
852
|
DigestError: () => ScheduleStorageError.make({
|
|
1077
|
-
operation: "
|
|
853
|
+
operation: "prepared admission",
|
|
1078
854
|
reason: "corrupt"
|
|
1079
855
|
}),
|
|
1080
|
-
|
|
856
|
+
ThreadNotMaterialized: () => ScheduledInputRetryable.make({ reason: "storage" }),
|
|
1081
857
|
AppendConflict: () => ScheduledInputRetryable.make({ reason: "ambiguous" }),
|
|
1082
858
|
FenceRejected: () => ScheduledInputRetryable.make({ reason: "ambiguous" }),
|
|
1083
859
|
DurableRuntimeFailpointError: () => ScheduledInputRetryable.make({ reason: "ambiguous" })
|
|
1084
|
-
}));
|
|
1085
|
-
return ScheduledInputAdmission.of({ submit });
|
|
860
|
+
})) });
|
|
1086
861
|
}));
|
|
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);
|
|
862
|
+
const preparedFromSchedule = (envelope) => ({
|
|
863
|
+
schemaVersion: 1,
|
|
864
|
+
threadId: envelope.threadId,
|
|
865
|
+
deliveryPrincipal: envelope.deliveryPrincipal,
|
|
866
|
+
agentId: envelope.agentId,
|
|
867
|
+
definitions: envelope.definitions,
|
|
868
|
+
input: envelope.input,
|
|
869
|
+
inputDigest: envelope.inputDigest,
|
|
870
|
+
admissionKey: envelope.admissionKey,
|
|
871
|
+
authorization: envelope.authorization
|
|
1144
872
|
});
|
|
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
|
-
};
|
|
873
|
+
/** Compatibility adapter retaining Scheduling's public admission port. */
|
|
874
|
+
const cloudflareScheduledInputAdmissionLayer = Layer.effect(ScheduledInputAdmission, Effect.map(PreparedInputAdmission, (admission) => ScheduledInputAdmission.of({ submit: (envelope) => admission.submit(preparedFromSchedule(envelope)) })));
|
|
1186
875
|
//#endregion
|
|
1187
|
-
export {
|
|
876
|
+
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
877
|
|
|
1189
|
-
//# sourceMappingURL=
|
|
878
|
+
//# sourceMappingURL=prepared-admission-D5hQ-ofV.mjs.map
|