@effect-agent/platform-cloudflare 0.1.0-beta.6 → 0.1.0-beta.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Alarm.d.mts +168 -0
- package/dist/Alarm.mjs +415 -0
- package/dist/Alarm.mjs.map +1 -0
- package/dist/BrowserRestCapture.d.mts +35 -0
- package/dist/BrowserRestCapture.mjs +238 -0
- package/dist/BrowserRestCapture.mjs.map +1 -0
- package/dist/BrowserRestCrawl.d.mts +17 -0
- package/dist/BrowserRestCrawl.mjs +244 -0
- package/dist/BrowserRestCrawl.mjs.map +1 -0
- package/dist/CloudflareBindings.d.mts +105 -0
- package/dist/CloudflareBindings.mjs +98 -0
- package/dist/CloudflareBindings.mjs.map +1 -0
- package/dist/CloudflareBrowser-Bj22nNUT.mjs +466 -0
- package/dist/CloudflareBrowser-Bj22nNUT.mjs.map +1 -0
- package/dist/CloudflareBrowser-DJq53niJ.d.mts +80 -0
- package/dist/CloudflareBrowser.d.mts +2 -0
- package/dist/CloudflareBrowser.mjs +2 -0
- package/dist/CloudflareCodeMode.d.mts +44 -0
- package/dist/CloudflareCodeMode.mjs +611 -0
- package/dist/CloudflareCodeMode.mjs.map +1 -0
- package/dist/CloudflareConfig-f3CqTel1.d.mts +101 -0
- package/dist/CloudflareConfig.d.mts +2 -0
- package/dist/CloudflareConfig.mjs +122 -0
- package/dist/CloudflareConfig.mjs.map +1 -0
- package/dist/CloudflareMemory.d.mts +140 -0
- package/dist/CloudflareMemory.mjs +168 -0
- package/dist/CloudflareMemory.mjs.map +1 -0
- package/dist/CloudflareScheduling.d.mts +52 -0
- package/dist/CloudflareScheduling.mjs +389 -0
- package/dist/CloudflareScheduling.mjs.map +1 -0
- package/dist/CloudflareSubscriptions.d.mts +87 -0
- package/dist/CloudflareSubscriptions.mjs +524 -0
- package/dist/CloudflareSubscriptions.mjs.map +1 -0
- package/dist/CloudflareThreadClient.d.mts +1259 -0
- package/dist/CloudflareThreadClient.mjs +386 -0
- package/dist/CloudflareThreadClient.mjs.map +1 -0
- package/dist/InteractiveBrowser.d.mts +144 -0
- package/dist/InteractiveBrowser.mjs +1090 -0
- package/dist/InteractiveBrowser.mjs.map +1 -0
- package/dist/ProtectedBrowser.d.mts +65 -0
- package/dist/ProtectedBrowser.mjs +859 -0
- package/dist/ProtectedBrowser.mjs.map +1 -0
- package/dist/ThreadObject-BRhbfy6r.d.mts +772 -0
- package/dist/ThreadObject-s5w3V_co.mjs +675 -0
- package/dist/ThreadObject-s5w3V_co.mjs.map +1 -0
- package/dist/ThreadObject.d.mts +2 -0
- package/dist/ThreadObject.mjs +3 -0
- package/dist/WakeScheduler.d.mts +23 -0
- package/dist/WakeScheduler.mjs +60 -0
- package/dist/WakeScheduler.mjs.map +1 -0
- package/dist/boundary-BguazVkh.mjs +42 -0
- package/dist/boundary-BguazVkh.mjs.map +1 -0
- package/dist/browser-session-lifecycle-CUbVfEgP.mjs +84 -0
- package/dist/browser-session-lifecycle-CUbVfEgP.mjs.map +1 -0
- package/dist/browser-session-lifecycle-DJpqLXy_.d.mts +21 -0
- package/dist/index.d.mts +12 -1543
- package/dist/index.mjs +12 -1636
- package/dist/prepared-admission-G7N4DDqS.mjs +73 -0
- package/dist/prepared-admission-G7N4DDqS.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -53
- package/src/Alarm.ts +862 -0
- package/src/BrowserRestCapture.ts +477 -0
- package/src/BrowserRestCrawl.ts +540 -0
- package/src/CloudflareBindings.ts +171 -0
- package/src/CloudflareBrowser.ts +15 -0
- package/src/{code-mode-executor.ts → CloudflareCodeMode.ts} +367 -204
- package/src/{config.ts → CloudflareConfig.ts} +9 -8
- package/src/CloudflareMemory.ts +359 -0
- package/src/CloudflareScheduling.ts +733 -0
- package/src/CloudflareSubscriptions.ts +1045 -0
- package/src/CloudflareThreadClient.ts +807 -0
- package/src/InteractiveBrowser.ts +2357 -0
- package/src/ProtectedBrowser.ts +2 -0
- package/src/{conversation-object.ts → ThreadObject.ts} +423 -239
- package/src/{wake-scheduler.ts → WakeScheduler.ts} +23 -23
- package/src/index.ts +11 -23
- package/src/internal/boundary.ts +55 -0
- package/src/internal/browser-quick-action.ts +818 -0
- package/src/internal/browser-session-lifecycle.ts +160 -0
- package/src/internal/layers.ts +615 -0
- package/src/internal/message-delivery.ts +90 -0
- package/src/internal/prepared-admission.ts +116 -0
- package/src/internal/progress-wait.ts +121 -0
- package/src/internal/transport.ts +42 -0
- package/src/protected-browser/binding.ts +187 -0
- package/src/protected-browser/inspect-frame.ts +125 -0
- package/src/protected-browser/native.ts +429 -0
- package/src/protected-browser/policy.ts +719 -0
- package/dist/index.mjs.map +0 -1
- package/src/alarm.ts +0 -334
- package/src/bindings.ts +0 -145
- package/src/client.ts +0 -646
- package/src/layers.ts +0 -376
- package/src/transport.ts +0 -38
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import { ThreadId } from "@effect-agent/core/Identifiers";
|
|
2
|
+
import {
|
|
3
|
+
type RunContextPreparation,
|
|
4
|
+
type RunCostEstimator,
|
|
5
|
+
type RunToolFailureObserver,
|
|
6
|
+
} from "@effect-agent/engine/RunOptions";
|
|
7
|
+
import {
|
|
8
|
+
CurrentToolFailureObserver,
|
|
9
|
+
RunContextPreparationPassthrough,
|
|
10
|
+
RunToolAuthorization,
|
|
11
|
+
toolFailureObserverLayer,
|
|
12
|
+
} from "@effect-agent/engine/RunOptions";
|
|
13
|
+
import { doMessageDeliveryStoreLayer } from "@effect-agent/storage-cloudflare/DoMessageDeliveryStore";
|
|
14
|
+
import {
|
|
15
|
+
type DoStorageFailpointHandler,
|
|
16
|
+
type DoStorageFailpoint,
|
|
17
|
+
} from "@effect-agent/storage-cloudflare/DoStorageFailpoint";
|
|
18
|
+
import { submissionLedgerLayer } from "@effect-agent/storage-cloudflare/DoSubmissionLedger";
|
|
19
|
+
import {
|
|
20
|
+
threadStoreLayer,
|
|
21
|
+
storageConfigLayer,
|
|
22
|
+
storageFailpointLayer,
|
|
23
|
+
type DoStorageInitializationError,
|
|
24
|
+
type DoStorageOptions,
|
|
25
|
+
} from "@effect-agent/storage-cloudflare/DoThreadStore";
|
|
26
|
+
import { type PortRequest, type PortResponse } from "@effect-agent/storage-cloudflare/PortProtocol";
|
|
27
|
+
import {
|
|
28
|
+
executePortRequest,
|
|
29
|
+
routedThreadStoreLayer,
|
|
30
|
+
routedSubmissionLedgerLayer,
|
|
31
|
+
} from "@effect-agent/storage-cloudflare/PortRouting";
|
|
32
|
+
import {
|
|
33
|
+
compileRegistrations,
|
|
34
|
+
type AgentRegistration,
|
|
35
|
+
type ResolvedBinding,
|
|
36
|
+
} from "@effect-agent/thread/AgentRegistration";
|
|
37
|
+
import { type DigestError } from "@effect-agent/thread/Digest";
|
|
38
|
+
import {
|
|
39
|
+
DurableAgentRuntime,
|
|
40
|
+
DurableRuntimeConfig,
|
|
41
|
+
} from "@effect-agent/thread/DurableAgentRuntime";
|
|
42
|
+
import {
|
|
43
|
+
DurableRuntimeFailpoint,
|
|
44
|
+
type DurableRuntimeFailpointHandler,
|
|
45
|
+
} from "@effect-agent/thread/DurableFailpoint";
|
|
46
|
+
import {
|
|
47
|
+
type MessageDeliveryStore,
|
|
48
|
+
MessageDeliveryDriver,
|
|
49
|
+
type MessageDeliveryError,
|
|
50
|
+
} from "@effect-agent/thread/MessageDelivery";
|
|
51
|
+
import {
|
|
52
|
+
operationAuthorizerLayer,
|
|
53
|
+
type OperationAuthorizerService,
|
|
54
|
+
} from "@effect-agent/thread/OperationAuthorizer";
|
|
55
|
+
import { ProducerId } from "@effect-agent/thread/Records";
|
|
56
|
+
import { LedgerError, SubmissionLedger } from "@effect-agent/thread/SubmissionLedger";
|
|
57
|
+
import { ThreadProjectionMaintenance } from "@effect-agent/thread/ThreadProjectionMaintenance";
|
|
58
|
+
import { ThreadStoreError, ThreadStore } from "@effect-agent/thread/ThreadStore";
|
|
59
|
+
import { ToolReconciler } from "@effect-agent/thread/ToolReconciler";
|
|
60
|
+
import { type WakeScheduler } from "@effect-agent/thread/WakeScheduler";
|
|
61
|
+
import { BrowserCrypto } from "@effect/platform-browser";
|
|
62
|
+
import { SqliteClient } from "@effect/sql-sqlite-do";
|
|
63
|
+
import type { Crypto } from "effect";
|
|
64
|
+
import { Cause, Context, Duration, Effect, Layer, Schema, Semaphore } from "effect";
|
|
65
|
+
import { SqlClient } from "effect/unstable/sql/SqlClient";
|
|
66
|
+
|
|
67
|
+
import {
|
|
68
|
+
ThreadMaintenance,
|
|
69
|
+
ThreadMutationGate,
|
|
70
|
+
ThreadPublication,
|
|
71
|
+
publishCommitted,
|
|
72
|
+
ThreadMaintenanceFailpoint,
|
|
73
|
+
DurableAlarmService,
|
|
74
|
+
type ThreadMaintenanceFailpointHandler,
|
|
75
|
+
} from "../Alarm.ts";
|
|
76
|
+
import {
|
|
77
|
+
ThreadObjectIdentity,
|
|
78
|
+
DurableObjectContext,
|
|
79
|
+
type ThreadObjectNamespace,
|
|
80
|
+
} from "../CloudflareBindings.ts";
|
|
81
|
+
import {
|
|
82
|
+
CLOUDFLARE_RUNTIME_DEFAULTS,
|
|
83
|
+
CloudflareDurableRuntimeConfig,
|
|
84
|
+
CloudflareDurableRuntimeConfigValue,
|
|
85
|
+
CloudflarePlatformConfigError,
|
|
86
|
+
} from "../CloudflareConfig.ts";
|
|
87
|
+
import { CloudflareThreadClient } from "../CloudflareThreadClient.ts";
|
|
88
|
+
import { cloudflareWakeSchedulerLayer } from "../WakeScheduler.ts";
|
|
89
|
+
import {
|
|
90
|
+
guardedMessageDeliveryStoreLayer,
|
|
91
|
+
threadMessageDeliveryLayer,
|
|
92
|
+
} from "./message-delivery.ts";
|
|
93
|
+
import { cloudflarePreparedInputAdmissionLayer } from "./prepared-admission.ts";
|
|
94
|
+
import { ProgressWaitRegistry } from "./progress-wait.ts";
|
|
95
|
+
import { threadPortTransportLayer } from "./transport.ts";
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Raw (unvalidated) construction options for `ThreadObject.make`, mirroring
|
|
99
|
+
* `NodeDurableAgentRuntimeOptions`. Optional fields default to the documented production values
|
|
100
|
+
* (`CLOUDFLARE_RUNTIME_DEFAULTS`); everything is schema-decoded into
|
|
101
|
+
* `CloudflareDurableRuntimeConfigValue` before any resource opens (deployment §5 gate 1).
|
|
102
|
+
*/
|
|
103
|
+
export interface CloudflareDurableRuntimeOptions {
|
|
104
|
+
readonly deploymentId: string;
|
|
105
|
+
/** Head of the minted producer identity `{producerPrefix}:{threadId}`. */
|
|
106
|
+
readonly producerPrefix: string;
|
|
107
|
+
/** Milliseconds; default 30s (D5). */
|
|
108
|
+
readonly ownershipLeaseDuration?: number | undefined;
|
|
109
|
+
/** Milliseconds; default 100. */
|
|
110
|
+
readonly alarmBackoffBase?: number | undefined;
|
|
111
|
+
/** Milliseconds; default 5000. */
|
|
112
|
+
readonly alarmBackoffCap?: number | undefined;
|
|
113
|
+
/** Milliseconds; default 1000. Bounds every alarm re-arm delay. */
|
|
114
|
+
readonly wakeScanInterval?: number | undefined;
|
|
115
|
+
/** Milliseconds; default 500. */
|
|
116
|
+
readonly settlementPollInterval?: number | undefined;
|
|
117
|
+
/** Milliseconds; default 10000. */
|
|
118
|
+
readonly leaseRenewalInterval?: number | undefined;
|
|
119
|
+
/** Milliseconds; default 500. */
|
|
120
|
+
readonly abortPollInterval?: number | undefined;
|
|
121
|
+
/** Deployment-owned pricing authority used by durable cost budgets and settlements. */
|
|
122
|
+
readonly estimateCostMicrousd?: RunCostEstimator | undefined;
|
|
123
|
+
/** Closed trusted Tool failure reporting. Omission masks ambient observers at construction. */
|
|
124
|
+
readonly toolFailureObserver?: RunToolFailureObserver | undefined;
|
|
125
|
+
/** Milliseconds; default 25. */
|
|
126
|
+
readonly observationPollInterval?: number | undefined;
|
|
127
|
+
/** Bytes; default just under the 2 MB platform value limit. */
|
|
128
|
+
readonly maxStoredValueBytes?: number | undefined;
|
|
129
|
+
/** Default false. */
|
|
130
|
+
readonly verifyOnOpen?: boolean | undefined;
|
|
131
|
+
/** Nonterminal Submissions per lane before admission refuses; default 256. */
|
|
132
|
+
readonly maxQueueDepthPerLane?: number | undefined;
|
|
133
|
+
/** Encoded input bytes per Submission; default = the stored-value bound. */
|
|
134
|
+
readonly maxInputBytes?: number | undefined;
|
|
135
|
+
/** `ctx.storage.sql.databaseSize` ceiling at admission; default 9 GB (10 GB platform cap). */
|
|
136
|
+
readonly maxDatabaseBytes?: number | undefined;
|
|
137
|
+
/**
|
|
138
|
+
* Durable Object storage fault injection (`ledger:*` / `append:*` locations). Handlers are
|
|
139
|
+
* constructed per incarnation WITH the live `DurableObjectState`, so eviction harnesses can
|
|
140
|
+
* map an armed hit to `ctx.abort()` — the platform's real failure mode. Default none.
|
|
141
|
+
*/
|
|
142
|
+
readonly storageFailpoint?: ((ctx: DurableObjectState) => DoStorageFailpointHandler) | undefined;
|
|
143
|
+
/** Coordinator fault injection (`submit:*` / `terminalize:*` locations); default none. */
|
|
144
|
+
readonly runtimeFailpoint?:
|
|
145
|
+
| ((ctx: DurableObjectState) => DurableRuntimeFailpointHandler)
|
|
146
|
+
| undefined;
|
|
147
|
+
/** Thread-maintenance generation/alarm fault injection; default none. */
|
|
148
|
+
readonly maintenanceFailpoint?:
|
|
149
|
+
| ((ctx: DurableObjectState) => ThreadMaintenanceFailpointHandler)
|
|
150
|
+
| undefined;
|
|
151
|
+
/** Host-supplied fail-closed authorization policy; defaults to service possession. */
|
|
152
|
+
readonly operationAuthorizer?: OperationAuthorizerService | undefined;
|
|
153
|
+
/**
|
|
154
|
+
* Reconciliation policy consulted for open ordinary Tool Calls before an Unknown Outcome
|
|
155
|
+
* is recorded (durability §10, DUR-009). Defaults to the fail-closed
|
|
156
|
+
* `ToolReconciler.uncertain`.
|
|
157
|
+
*/
|
|
158
|
+
readonly toolReconciler?: Layer.Layer<ToolReconciler> | undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Services supplied before the application graph is built, including its dependencies. */
|
|
162
|
+
export type CloudflareBootstrapServices =
|
|
163
|
+
| CloudflareDurableRuntimeConfig
|
|
164
|
+
| ThreadObjectIdentity
|
|
165
|
+
| DurableRuntimeConfig
|
|
166
|
+
| Crypto.Crypto
|
|
167
|
+
| DoStorageFailpoint
|
|
168
|
+
| DurableRuntimeFailpoint
|
|
169
|
+
| ThreadMaintenanceFailpoint
|
|
170
|
+
| RunContextPreparation
|
|
171
|
+
| RunToolAuthorization
|
|
172
|
+
| ToolReconciler;
|
|
173
|
+
|
|
174
|
+
/** Every construction failure of the assembled Cloudflare durable runtime stack. */
|
|
175
|
+
export type CloudflareDurableRuntimeInitializationError =
|
|
176
|
+
| CloudflarePlatformConfigError
|
|
177
|
+
| DigestError
|
|
178
|
+
| MessageDeliveryError
|
|
179
|
+
| DoStorageInitializationError;
|
|
180
|
+
|
|
181
|
+
/** The services `ThreadObject.layer` provides, including its single owner SQL client. */
|
|
182
|
+
export type CloudflareDurableRuntimeServices =
|
|
183
|
+
| DurableAgentRuntime
|
|
184
|
+
| SubmissionLedger
|
|
185
|
+
| ThreadStore
|
|
186
|
+
| MessageDeliveryStore
|
|
187
|
+
| WakeScheduler
|
|
188
|
+
| DurableAlarmService
|
|
189
|
+
| ThreadMaintenance
|
|
190
|
+
| ThreadMutationGate
|
|
191
|
+
| ThreadPublication
|
|
192
|
+
| ThreadProjectionMaintenance
|
|
193
|
+
| ThreadObjectPorts
|
|
194
|
+
| ProgressWaitRegistry
|
|
195
|
+
| SqlClient;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Owner-side execution port for a `portCall` request the wire endpoint has already decoded.
|
|
199
|
+
* It executes against THIS Object's LOCAL port facets — never the routed decorators, so a
|
|
200
|
+
* request cannot bounce between Objects — and returns the typed response for the endpoint to
|
|
201
|
+
* encode.
|
|
202
|
+
*/
|
|
203
|
+
export class ThreadObjectPorts extends Context.Service<
|
|
204
|
+
ThreadObjectPorts,
|
|
205
|
+
{
|
|
206
|
+
readonly handle: (request: PortRequest) => Effect.Effect<PortResponse>;
|
|
207
|
+
}
|
|
208
|
+
>()("@effect-agent/platform-cloudflare/ThreadObjectPorts") {}
|
|
209
|
+
|
|
210
|
+
const decodeConfigValue = Schema.decodeUnknownEffect(CloudflareDurableRuntimeConfigValue);
|
|
211
|
+
const decodeThreadId = Schema.decodeUnknownEffect(ThreadId);
|
|
212
|
+
const decodeProducerId = Schema.decodeUnknownEffect(ProducerId);
|
|
213
|
+
|
|
214
|
+
const configFromOptions = (
|
|
215
|
+
options: CloudflareDurableRuntimeOptions,
|
|
216
|
+
): Effect.Effect<CloudflareDurableRuntimeConfigValue, CloudflarePlatformConfigError> =>
|
|
217
|
+
decodeConfigValue({
|
|
218
|
+
deploymentId: options.deploymentId,
|
|
219
|
+
producerPrefix: options.producerPrefix,
|
|
220
|
+
ownershipLeaseDuration:
|
|
221
|
+
options.ownershipLeaseDuration ?? CLOUDFLARE_RUNTIME_DEFAULTS.ownershipLeaseDuration,
|
|
222
|
+
alarmBackoffBase: options.alarmBackoffBase ?? CLOUDFLARE_RUNTIME_DEFAULTS.alarmBackoffBase,
|
|
223
|
+
alarmBackoffCap: options.alarmBackoffCap ?? CLOUDFLARE_RUNTIME_DEFAULTS.alarmBackoffCap,
|
|
224
|
+
wakeScanInterval: options.wakeScanInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.wakeScanInterval,
|
|
225
|
+
settlementPollInterval:
|
|
226
|
+
options.settlementPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.settlementPollInterval,
|
|
227
|
+
leaseRenewalInterval:
|
|
228
|
+
options.leaseRenewalInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.leaseRenewalInterval,
|
|
229
|
+
abortPollInterval: options.abortPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.abortPollInterval,
|
|
230
|
+
observationPollInterval:
|
|
231
|
+
options.observationPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.observationPollInterval,
|
|
232
|
+
maxStoredValueBytes:
|
|
233
|
+
options.maxStoredValueBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxStoredValueBytes,
|
|
234
|
+
verifyOnOpen: options.verifyOnOpen ?? CLOUDFLARE_RUNTIME_DEFAULTS.verifyOnOpen,
|
|
235
|
+
limits: {
|
|
236
|
+
maxQueueDepthPerLane:
|
|
237
|
+
options.maxQueueDepthPerLane ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxQueueDepthPerLane,
|
|
238
|
+
maxInputBytes: Math.min(
|
|
239
|
+
options.maxInputBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxInputBytes,
|
|
240
|
+
options.maxStoredValueBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxStoredValueBytes,
|
|
241
|
+
),
|
|
242
|
+
maxDatabaseBytes: options.maxDatabaseBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxDatabaseBytes,
|
|
243
|
+
},
|
|
244
|
+
}).pipe(
|
|
245
|
+
Effect.mapError((error) =>
|
|
246
|
+
CloudflarePlatformConfigError.make({
|
|
247
|
+
message: `Invalid Cloudflare durable runtime configuration: ${error.message}`,
|
|
248
|
+
cause: error,
|
|
249
|
+
}),
|
|
250
|
+
),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* The Thread this Object owns, from the Object identity rule (plan §1.2): Thread
|
|
255
|
+
* Objects are addressed exclusively by `idFromName(threadId)`, so `ctx.id.name` IS the
|
|
256
|
+
* Thread ID. An unnamed Object (from `newUniqueId`) is a deployment error, not a lane.
|
|
257
|
+
*/
|
|
258
|
+
const threadIdFromState = (
|
|
259
|
+
ctx: DurableObjectState,
|
|
260
|
+
): Effect.Effect<ThreadId, CloudflarePlatformConfigError> =>
|
|
261
|
+
ctx.id.name === undefined
|
|
262
|
+
? Effect.fail(
|
|
263
|
+
CloudflarePlatformConfigError.make({
|
|
264
|
+
message:
|
|
265
|
+
"This Durable Object was not created via idFromName(threadId); Thread " +
|
|
266
|
+
"Objects must be addressed by their Thread identity (plan §1.2).",
|
|
267
|
+
}),
|
|
268
|
+
)
|
|
269
|
+
: decodeThreadId(ctx.id.name).pipe(
|
|
270
|
+
Effect.mapError((error) =>
|
|
271
|
+
CloudflarePlatformConfigError.make({
|
|
272
|
+
message: `The Durable Object name is not a valid ThreadId: ${error.message}`,
|
|
273
|
+
cause: error,
|
|
274
|
+
}),
|
|
275
|
+
),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Validate deployment settings and derive services before building application dependencies.
|
|
280
|
+
* The native class factory builds this Layer inside its constructor gate. Custom Effect hosts
|
|
281
|
+
* can provide it around the complete application Layer with the native context already supplied.
|
|
282
|
+
*/
|
|
283
|
+
export const layerConfig = (
|
|
284
|
+
options: CloudflareDurableRuntimeOptions,
|
|
285
|
+
): Layer.Layer<CloudflareBootstrapServices, CloudflarePlatformConfigError, DurableObjectContext> =>
|
|
286
|
+
Layer.unwrap(
|
|
287
|
+
Effect.gen(function* () {
|
|
288
|
+
const { ctx } = yield* DurableObjectContext;
|
|
289
|
+
const config = yield* configFromOptions(options);
|
|
290
|
+
const threadId = yield* threadIdFromState(ctx);
|
|
291
|
+
|
|
292
|
+
const producerId = yield* decodeProducerId(`${config.producerPrefix}:${threadId}`).pipe(
|
|
293
|
+
Effect.mapError((error) =>
|
|
294
|
+
CloudflarePlatformConfigError.make({
|
|
295
|
+
message: `The minted producer identity is invalid: ${error.message}`,
|
|
296
|
+
cause: error,
|
|
297
|
+
}),
|
|
298
|
+
),
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
return Layer.mergeAll(
|
|
302
|
+
Layer.succeed(CloudflareDurableRuntimeConfig, config),
|
|
303
|
+
Layer.succeed(ThreadObjectIdentity, { threadId, producerId }),
|
|
304
|
+
DurableRuntimeConfig.layer({
|
|
305
|
+
deploymentId: config.deploymentId,
|
|
306
|
+
producerId,
|
|
307
|
+
settlementPollInterval: Duration.millis(config.settlementPollInterval),
|
|
308
|
+
leaseRenewalInterval: Duration.millis(config.leaseRenewalInterval),
|
|
309
|
+
abortPollInterval: Duration.millis(config.abortPollInterval),
|
|
310
|
+
...(options.estimateCostMicrousd === undefined
|
|
311
|
+
? {}
|
|
312
|
+
: { estimateCostMicrousd: options.estimateCostMicrousd }),
|
|
313
|
+
}),
|
|
314
|
+
BrowserCrypto.layer,
|
|
315
|
+
storageFailpointLayer({ storage: ctx.storage, failpoint: options.storageFailpoint?.(ctx) }),
|
|
316
|
+
options.runtimeFailpoint === undefined
|
|
317
|
+
? DurableRuntimeFailpoint.layer
|
|
318
|
+
: Layer.succeed(DurableRuntimeFailpoint, { hit: options.runtimeFailpoint(ctx) }),
|
|
319
|
+
options.maintenanceFailpoint === undefined
|
|
320
|
+
? ThreadMaintenanceFailpoint.layer
|
|
321
|
+
: Layer.succeed(ThreadMaintenanceFailpoint, {
|
|
322
|
+
hit: options.maintenanceFailpoint(ctx),
|
|
323
|
+
}),
|
|
324
|
+
options.toolReconciler ?? ToolReconciler.uncertain,
|
|
325
|
+
options.operationAuthorizer === undefined
|
|
326
|
+
? Layer.empty
|
|
327
|
+
: operationAuthorizerLayer(options.operationAuthorizer),
|
|
328
|
+
options.toolFailureObserver === undefined
|
|
329
|
+
? Layer.succeed(CurrentToolFailureObserver, undefined)
|
|
330
|
+
: toolFailureObserverLayer(options.toolFailureObserver),
|
|
331
|
+
RunContextPreparationPassthrough,
|
|
332
|
+
RunToolAuthorization.allowAll,
|
|
333
|
+
);
|
|
334
|
+
}),
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
export interface ThreadPublicationOptions<E = never, R = never, P = never> {
|
|
338
|
+
/**
|
|
339
|
+
* Optional host outbox consumer, built once per incarnation with RAW LOCAL ThreadStore and
|
|
340
|
+
* SubmissionLedger services. Yield DurableObjectContext and ThreadObjectIdentity for native
|
|
341
|
+
* bindings and identity. Initialization is local-only, inside the constructor gate; setup
|
|
342
|
+
* errors and additional requirements remain in the returned Layer. Layer.effect owns Scope.
|
|
343
|
+
* Canonical appends and durable approval, abort and unknown-resolution intents invalidate
|
|
344
|
+
* publication after commit. Custom host facts must use ThreadMaintenance.withMutation.
|
|
345
|
+
*/
|
|
346
|
+
readonly publication?: Layer.Layer<ThreadPublication, E, R>;
|
|
347
|
+
/**
|
|
348
|
+
* Disposable index maintenance, built once with the raw local ThreadStore and owner
|
|
349
|
+
* SqlClient. Additional services P are exposed by the returned Layer, allowing Tool
|
|
350
|
+
* handlers and maintenance to share one index instance. Construction is local-only.
|
|
351
|
+
* Live committed batches run before append returns; bounded backfill uses the native
|
|
352
|
+
* alarm without delaying execution behind projection backlog.
|
|
353
|
+
*/
|
|
354
|
+
readonly projection?: Layer.Layer<ThreadProjectionMaintenance | P, E, R>;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Register typed Agents and version declarations. Hashing and dependency capture happen in
|
|
359
|
+
* this Layer's Scope, after application Layers have been provided. Every Agent's instruction,
|
|
360
|
+
* Tool, Schema, and model requirements remain visible until satisfied by Layer composition.
|
|
361
|
+
* Use Layer.unwrap for registration values that need effectful application setup.
|
|
362
|
+
*/
|
|
363
|
+
const registeredLayer = <
|
|
364
|
+
const Entries extends ReadonlyArray<AgentRegistration>,
|
|
365
|
+
E = never,
|
|
366
|
+
R = never,
|
|
367
|
+
>(
|
|
368
|
+
registrations: Entries,
|
|
369
|
+
options: ThreadPublicationOptions<E, R> = {},
|
|
370
|
+
) =>
|
|
371
|
+
Layer.unwrap(
|
|
372
|
+
Effect.map(compileRegistrations(registrations), (bindings) => boundLayer(bindings, options)),
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
/** Preserve additional index services only when a projection Layer is actually supplied. */
|
|
376
|
+
export function layer<
|
|
377
|
+
const Entries extends ReadonlyArray<AgentRegistration>,
|
|
378
|
+
E = never,
|
|
379
|
+
R = never,
|
|
380
|
+
P = never,
|
|
381
|
+
PE = never,
|
|
382
|
+
PR = never,
|
|
383
|
+
>(
|
|
384
|
+
registrations: Entries,
|
|
385
|
+
options: Omit<ThreadPublicationOptions<E, R>, "projection"> & {
|
|
386
|
+
readonly projection: Layer.Layer<ThreadProjectionMaintenance | P, PE, PR>;
|
|
387
|
+
},
|
|
388
|
+
): Layer.Layer<
|
|
389
|
+
Layer.Success<ReturnType<typeof registeredLayer<Entries, E | PE, R | PR>>> | P,
|
|
390
|
+
Layer.Error<ReturnType<typeof registeredLayer<Entries, E | PE, R | PR>>>,
|
|
391
|
+
Layer.Services<ReturnType<typeof registeredLayer<Entries, E | PE, R | PR>>>
|
|
392
|
+
>;
|
|
393
|
+
|
|
394
|
+
export function layer<const Entries extends ReadonlyArray<AgentRegistration>, E = never, R = never>(
|
|
395
|
+
registrations: Entries,
|
|
396
|
+
options?: ThreadPublicationOptions<E, R>,
|
|
397
|
+
): ReturnType<typeof registeredLayer<Entries, E, R>>;
|
|
398
|
+
|
|
399
|
+
export function layer<const Entries extends ReadonlyArray<AgentRegistration>, E = never, R = never>(
|
|
400
|
+
registrations: Entries,
|
|
401
|
+
options: ThreadPublicationOptions<E, R> = {},
|
|
402
|
+
) {
|
|
403
|
+
return registeredLayer(registrations, options);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function layerFromBindings<E = never, R = never, P = never, PE = never, PR = never>(
|
|
407
|
+
bindings: ReadonlyArray<ResolvedBinding>,
|
|
408
|
+
options: Omit<ThreadPublicationOptions<E, R>, "projection"> & {
|
|
409
|
+
readonly projection: Layer.Layer<ThreadProjectionMaintenance | P, PE, PR>;
|
|
410
|
+
},
|
|
411
|
+
): Layer.Layer<
|
|
412
|
+
CloudflareDurableRuntimeServices | P,
|
|
413
|
+
Layer.Error<ReturnType<typeof boundLayer<E | PE, R | PR>>>,
|
|
414
|
+
Layer.Services<ReturnType<typeof boundLayer<E | PE, R | PR>>>
|
|
415
|
+
>;
|
|
416
|
+
|
|
417
|
+
export function layerFromBindings<E = never, R = never>(
|
|
418
|
+
bindings: ReadonlyArray<ResolvedBinding>,
|
|
419
|
+
options?: ThreadPublicationOptions<E, R>,
|
|
420
|
+
): ReturnType<typeof boundLayer<E, R>>;
|
|
421
|
+
|
|
422
|
+
export function layerFromBindings(
|
|
423
|
+
bindings: ReadonlyArray<ResolvedBinding>,
|
|
424
|
+
): ReturnType<typeof boundLayer<never, never>>;
|
|
425
|
+
|
|
426
|
+
export function layerFromBindings<E = never, R = never>(
|
|
427
|
+
bindings: ReadonlyArray<ResolvedBinding>,
|
|
428
|
+
options: ThreadPublicationOptions<E, R> = {},
|
|
429
|
+
) {
|
|
430
|
+
return boundLayer(bindings, options);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Assemble the durable runtime from already-resolved Agent Bindings.
|
|
435
|
+
* Use `ThreadObject.layer` to compile typed Agent registrations instead.
|
|
436
|
+
* Supply host services through `ThreadObject.make` or `ThreadObject.layerConfig` and
|
|
437
|
+
* the Durable Object context and namespace Layers when composing a custom host.
|
|
438
|
+
*/
|
|
439
|
+
const boundLayer = <E = never, R = never>(
|
|
440
|
+
bindings: ReadonlyArray<ResolvedBinding>,
|
|
441
|
+
options: ThreadPublicationOptions<E, R> = {},
|
|
442
|
+
): Layer.Layer<
|
|
443
|
+
CloudflareDurableRuntimeServices,
|
|
444
|
+
DoStorageInitializationError | MessageDeliveryError | E,
|
|
445
|
+
| DurableObjectContext
|
|
446
|
+
| ThreadObjectNamespace
|
|
447
|
+
| CloudflareBootstrapServices
|
|
448
|
+
| Exclude<R, ThreadStore | SubmissionLedger | SqlClient>
|
|
449
|
+
> =>
|
|
450
|
+
Layer.unwrap(
|
|
451
|
+
Effect.gen(function* () {
|
|
452
|
+
const { ctx } = yield* DurableObjectContext;
|
|
453
|
+
const config = yield* CloudflareDurableRuntimeConfig;
|
|
454
|
+
const { threadId } = yield* ThreadObjectIdentity;
|
|
455
|
+
|
|
456
|
+
const storageOptions: DoStorageOptions = {
|
|
457
|
+
storage: ctx.storage,
|
|
458
|
+
observationPollInterval: config.observationPollInterval,
|
|
459
|
+
ownershipLeaseDuration: config.ownershipLeaseDuration,
|
|
460
|
+
maxStoredValueBytes: config.maxStoredValueBytes,
|
|
461
|
+
verifyOnOpen: config.verifyOnOpen,
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const infrastructure = Layer.mergeAll(
|
|
465
|
+
storageConfigLayer(storageOptions),
|
|
466
|
+
SqliteClient.layer({ storage: ctx.storage }),
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
// The same local ports serve routed decorators and owner-side RPC execution.
|
|
470
|
+
// The RPC executor must never receive routed ports and bounce requests between Objects.
|
|
471
|
+
const rawLocalPorts = Layer.mergeAll(threadStoreLayer, submissionLedgerLayer).pipe(
|
|
472
|
+
Layer.provide(infrastructure),
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
const messageStore = guardedMessageDeliveryStoreLayer.pipe(
|
|
476
|
+
Layer.provide(doMessageDeliveryStoreLayer().pipe(Layer.provide(infrastructure))),
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
const messageRecovery = threadMessageDeliveryLayer.pipe(
|
|
480
|
+
// A single wave stays within the alarm event budget even at the policy's five-minute
|
|
481
|
+
// attempt ceiling. More due rows retain their indexed deadline for the next alarm.
|
|
482
|
+
Layer.provide(MessageDeliveryDriver.layer({ batchSize: 4, concurrency: 4 })),
|
|
483
|
+
Layer.provide(cloudflarePreparedInputAdmissionLayer),
|
|
484
|
+
Layer.provide(CloudflareThreadClient.layer),
|
|
485
|
+
Layer.provide(messageStore),
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
const publication = (options.publication ?? ThreadPublication.layer).pipe(
|
|
489
|
+
Layer.provide(Layer.mergeAll(rawLocalPorts, Layer.effect(SqlClient)(SqlClient))),
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
const projection = (options.projection ?? ThreadProjectionMaintenance.layer).pipe(
|
|
493
|
+
Layer.provide(Layer.mergeAll(rawLocalPorts, Layer.effect(SqlClient)(SqlClient))),
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
const localPorts =
|
|
497
|
+
options.publication === undefined && options.projection === undefined
|
|
498
|
+
? rawLocalPorts
|
|
499
|
+
: Layer.effectContext(
|
|
500
|
+
Effect.gen(function* () {
|
|
501
|
+
const store = yield* ThreadStore;
|
|
502
|
+
const ledger = yield* SubmissionLedger;
|
|
503
|
+
const mutations = yield* ThreadMutationGate;
|
|
504
|
+
const index = yield* ThreadProjectionMaintenance;
|
|
505
|
+
const publish = yield* Effect.context<ThreadPublication>();
|
|
506
|
+
const afterCommit = publishCommitted.pipe(Effect.provide(publish));
|
|
507
|
+
// A later append cannot mistake its still-projecting predecessor for old backlog.
|
|
508
|
+
// Publication remains outside this local source/index critical section.
|
|
509
|
+
const sourceCommits = yield* Semaphore.make(1);
|
|
510
|
+
|
|
511
|
+
// Every runtime-owned producer prearms too: a crash between commit and invalidation
|
|
512
|
+
// leaves a NEW, uncertified generation. Source errors keep their native port types.
|
|
513
|
+
const observedStore = ThreadStore.of({
|
|
514
|
+
...store,
|
|
515
|
+
append: (request) =>
|
|
516
|
+
mutations
|
|
517
|
+
.withMutation(
|
|
518
|
+
sourceCommits
|
|
519
|
+
.withPermit(
|
|
520
|
+
store
|
|
521
|
+
.append(request)
|
|
522
|
+
.pipe(
|
|
523
|
+
Effect.tap((result) =>
|
|
524
|
+
index
|
|
525
|
+
.applyCommitted(request, result)
|
|
526
|
+
.pipe(
|
|
527
|
+
Effect.catchCause((cause) =>
|
|
528
|
+
Cause.hasInterrupts(cause)
|
|
529
|
+
? Effect.interrupt
|
|
530
|
+
: Effect.logError(
|
|
531
|
+
"Thread projection deferred after source commit",
|
|
532
|
+
cause,
|
|
533
|
+
),
|
|
534
|
+
),
|
|
535
|
+
),
|
|
536
|
+
),
|
|
537
|
+
),
|
|
538
|
+
)
|
|
539
|
+
.pipe(Effect.tap(() => afterCommit)),
|
|
540
|
+
)
|
|
541
|
+
.pipe(
|
|
542
|
+
Effect.catchTag("DurableAlarmError", (cause) =>
|
|
543
|
+
ThreadStoreError.make({
|
|
544
|
+
operation: "prearm publication append",
|
|
545
|
+
message: cause.message,
|
|
546
|
+
cause,
|
|
547
|
+
}),
|
|
548
|
+
),
|
|
549
|
+
),
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
const observeIntent = <A, Failure>(body: Effect.Effect<A, Failure>) =>
|
|
553
|
+
mutations.withMutation(body.pipe(Effect.tap(() => afterCommit))).pipe(
|
|
554
|
+
Effect.catchTag("DurableAlarmError", (cause) =>
|
|
555
|
+
LedgerError.make({
|
|
556
|
+
operation: "prearm publication intent",
|
|
557
|
+
message: "The publication generation could not be armed",
|
|
558
|
+
cause,
|
|
559
|
+
}),
|
|
560
|
+
),
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
return Context.make(ThreadStore, observedStore).pipe(
|
|
564
|
+
Context.add(SubmissionLedger, {
|
|
565
|
+
...ledger,
|
|
566
|
+
recordApprovalDecision: (request) =>
|
|
567
|
+
observeIntent(ledger.recordApprovalDecision(request)),
|
|
568
|
+
requestAbort: (request) => observeIntent(ledger.requestAbort(request)),
|
|
569
|
+
recordUnknownResolution: (request) =>
|
|
570
|
+
observeIntent(ledger.recordUnknownResolution(request)),
|
|
571
|
+
}),
|
|
572
|
+
);
|
|
573
|
+
}),
|
|
574
|
+
).pipe(Layer.provide(rawLocalPorts));
|
|
575
|
+
|
|
576
|
+
const portsEndpointLayer = Layer.effect(ThreadObjectPorts)(
|
|
577
|
+
Effect.gen(function* () {
|
|
578
|
+
const local = yield* Effect.context<SubmissionLedger | ThreadStore>();
|
|
579
|
+
|
|
580
|
+
return ThreadObjectPorts.of({
|
|
581
|
+
handle: (request) => executePortRequest(request).pipe(Effect.provide(local)),
|
|
582
|
+
});
|
|
583
|
+
}),
|
|
584
|
+
).pipe(Layer.provide(localPorts));
|
|
585
|
+
|
|
586
|
+
const routedPorts = Layer.mergeAll(
|
|
587
|
+
routedSubmissionLedgerLayer({ localThreadId: threadId }),
|
|
588
|
+
routedThreadStoreLayer({ localThreadId: threadId }),
|
|
589
|
+
).pipe(Layer.provide(localPorts), Layer.provide(threadPortTransportLayer));
|
|
590
|
+
|
|
591
|
+
const base = Layer.mergeAll(DurableAlarmService.layer, ProgressWaitRegistry.layer);
|
|
592
|
+
|
|
593
|
+
const runtimeStack = DurableAgentRuntime.layerWithBindings(bindings).pipe(
|
|
594
|
+
Layer.provide(
|
|
595
|
+
cloudflarePreparedInputAdmissionLayer.pipe(Layer.provide(CloudflareThreadClient.layer)),
|
|
596
|
+
),
|
|
597
|
+
Layer.provideMerge(messageStore),
|
|
598
|
+
Layer.provideMerge(routedPorts),
|
|
599
|
+
Layer.provideMerge(cloudflareWakeSchedulerLayer),
|
|
600
|
+
Layer.provideMerge(base),
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
return Layer.mergeAll(
|
|
604
|
+
runtimeStack,
|
|
605
|
+
ThreadMaintenance.layer.pipe(Layer.provide(runtimeStack), Layer.provide(messageRecovery)),
|
|
606
|
+
portsEndpointLayer,
|
|
607
|
+
messageStore,
|
|
608
|
+
).pipe(
|
|
609
|
+
Layer.provideMerge(publication),
|
|
610
|
+
Layer.provideMerge(projection),
|
|
611
|
+
Layer.provideMerge(ThreadMutationGate.layer),
|
|
612
|
+
Layer.provideMerge(infrastructure),
|
|
613
|
+
);
|
|
614
|
+
}),
|
|
615
|
+
);
|