@effect-agent/platform-cloudflare 0.1.0-beta.37 → 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 +191 -206
- package/dist/index.mjs +157 -163
- package/dist/index.mjs.map +1 -1
- package/dist/interactive-browser.d.mts +23 -3
- package/dist/interactive-browser.mjs +110 -35
- package/dist/interactive-browser.mjs.map +1 -1
- 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/browser-session-lifecycle.ts +142 -0
- package/src/client.ts +90 -98
- package/src/config.ts +7 -7
- package/src/index.ts +9 -11
- package/src/interactive-browser.ts +81 -70
- 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/scheduling-B-OFqoS9.mjs.map +0 -1
- package/dist/scheduling-BJs_kHTx.d.mts +0 -142
package/src/layers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ThreadId } from "@effect-agent/core";
|
|
2
2
|
import type {
|
|
3
3
|
RunContextPreparation,
|
|
4
4
|
RunCostEstimator,
|
|
@@ -7,27 +7,13 @@ import type {
|
|
|
7
7
|
import {
|
|
8
8
|
CurrentToolFailureObserver,
|
|
9
9
|
RunContextPreparationPassthrough,
|
|
10
|
+
RunToolAuthorization,
|
|
10
11
|
toolFailureObserverLayer,
|
|
11
12
|
} from "@effect-agent/engine";
|
|
12
13
|
import {
|
|
13
|
-
|
|
14
|
-
DurableAgentRuntime,
|
|
15
|
-
DurableRuntimeConfig,
|
|
16
|
-
DurableRuntimeFailpoint,
|
|
17
|
-
ProducerId,
|
|
18
|
-
operationAuthorizerLayer,
|
|
19
|
-
ToolReconciler,
|
|
20
|
-
type ConversationStore,
|
|
21
|
-
type DurableRuntimeFailpointHandler,
|
|
22
|
-
type OperationAuthorizerService,
|
|
23
|
-
type ResolvedBinding,
|
|
24
|
-
type SubmissionLedger,
|
|
25
|
-
type WakeScheduler,
|
|
26
|
-
} from "@effect-agent/session";
|
|
27
|
-
import {
|
|
28
|
-
conversationStoreLayer,
|
|
14
|
+
threadStoreLayer,
|
|
29
15
|
executePortRequest,
|
|
30
|
-
|
|
16
|
+
routedThreadStoreLayer,
|
|
31
17
|
routedSubmissionLedgerLayer,
|
|
32
18
|
storageConfigLayer,
|
|
33
19
|
storageFailpointLayer,
|
|
@@ -35,24 +21,42 @@ import {
|
|
|
35
21
|
type DoStorageFailpointHandler,
|
|
36
22
|
type DoStorageInitializationError,
|
|
37
23
|
type DoStorageOptions,
|
|
24
|
+
type DoStorageFailpoint,
|
|
38
25
|
type PortRequest,
|
|
39
26
|
type PortResponse,
|
|
40
27
|
} from "@effect-agent/storage-cloudflare";
|
|
28
|
+
import {
|
|
29
|
+
compileRegistrations,
|
|
30
|
+
DurableAgentRuntime,
|
|
31
|
+
DurableRuntimeConfig,
|
|
32
|
+
DurableRuntimeFailpoint,
|
|
33
|
+
ProducerId,
|
|
34
|
+
operationAuthorizerLayer,
|
|
35
|
+
ToolReconciler,
|
|
36
|
+
type AgentRegistration,
|
|
37
|
+
type ThreadStore,
|
|
38
|
+
type DigestError,
|
|
39
|
+
type DurableRuntimeFailpointHandler,
|
|
40
|
+
type OperationAuthorizerService,
|
|
41
|
+
type ResolvedBinding,
|
|
42
|
+
type SubmissionLedger,
|
|
43
|
+
type WakeScheduler,
|
|
44
|
+
} from "@effect-agent/thread";
|
|
41
45
|
import { BrowserCrypto } from "@effect/platform-browser";
|
|
42
46
|
import { SqliteClient } from "@effect/sql-sqlite-do";
|
|
43
47
|
import type { Crypto } from "effect";
|
|
44
48
|
import { Context, Duration, Effect, Layer, Schema } from "effect";
|
|
45
49
|
|
|
46
50
|
import {
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
ThreadMaintenance,
|
|
52
|
+
ThreadMaintenanceFailpoint,
|
|
49
53
|
DurableAlarmService,
|
|
50
|
-
type
|
|
54
|
+
type ThreadMaintenanceFailpointHandler,
|
|
51
55
|
} from "./alarm.ts";
|
|
52
56
|
import {
|
|
53
|
-
|
|
57
|
+
ThreadObjectIdentity,
|
|
54
58
|
DurableObjectContext,
|
|
55
|
-
type
|
|
59
|
+
type ThreadObjectNamespace,
|
|
56
60
|
} from "./bindings.ts";
|
|
57
61
|
import {
|
|
58
62
|
CLOUDFLARE_RUNTIME_DEFAULTS,
|
|
@@ -61,18 +65,18 @@ import {
|
|
|
61
65
|
CloudflarePlatformConfigError,
|
|
62
66
|
} from "./config.ts";
|
|
63
67
|
import { ProgressWaitRegistry } from "./progress-wait.ts";
|
|
64
|
-
import {
|
|
68
|
+
import { threadPortTransportLayer } from "./transport.ts";
|
|
65
69
|
import { cloudflareWakeSchedulerLayer } from "./wake-scheduler.ts";
|
|
66
70
|
|
|
67
71
|
/**
|
|
68
|
-
* Raw (unvalidated) construction options for `
|
|
72
|
+
* Raw (unvalidated) construction options for `ThreadObject.make`, mirroring
|
|
69
73
|
* `NodeDurableRuntimeOptions`. Optional fields default to the documented production values
|
|
70
74
|
* (`CLOUDFLARE_RUNTIME_DEFAULTS`); everything is schema-decoded into
|
|
71
75
|
* `CloudflareDurableRuntimeConfigValue` before any resource opens (deployment §5 gate 1).
|
|
72
76
|
*/
|
|
73
77
|
export interface CloudflareDurableRuntimeOptions {
|
|
74
78
|
readonly deploymentId: string;
|
|
75
|
-
/** Head of the minted producer identity `{producerPrefix}:{
|
|
79
|
+
/** Head of the minted producer identity `{producerPrefix}:{threadId}`. */
|
|
76
80
|
readonly producerPrefix: string;
|
|
77
81
|
/** Milliseconds; default 30s (D5). */
|
|
78
82
|
readonly ownershipLeaseDuration?: number | undefined;
|
|
@@ -114,9 +118,9 @@ export interface CloudflareDurableRuntimeOptions {
|
|
|
114
118
|
readonly runtimeFailpoint?:
|
|
115
119
|
| ((ctx: DurableObjectState) => DurableRuntimeFailpointHandler)
|
|
116
120
|
| undefined;
|
|
117
|
-
/**
|
|
121
|
+
/** Thread-maintenance generation/alarm fault injection; default none. */
|
|
118
122
|
readonly maintenanceFailpoint?:
|
|
119
|
-
| ((ctx: DurableObjectState) =>
|
|
123
|
+
| ((ctx: DurableObjectState) => ThreadMaintenanceFailpointHandler)
|
|
120
124
|
| undefined;
|
|
121
125
|
/** Host-supplied fail-closed authorization policy; defaults to service possession. */
|
|
122
126
|
readonly operationAuthorizer?: OperationAuthorizerService | undefined;
|
|
@@ -126,65 +130,36 @@ export interface CloudflareDurableRuntimeOptions {
|
|
|
126
130
|
* `ToolReconciler.uncertain`.
|
|
127
131
|
*/
|
|
128
132
|
readonly toolReconciler?: Layer.Layer<ToolReconciler> | undefined;
|
|
129
|
-
/**
|
|
130
|
-
* Registered worker Bindings resolved at durable claim time:
|
|
131
|
-
* build each with `DurableWorkerBinding.make(binding, digests)`. The callback receives the live
|
|
132
|
-
* Object context and derived identities and is evaluated once per incarnation during Layer
|
|
133
|
-
* construction. Defaults to the empty registration (every resolved claim fails closed).
|
|
134
|
-
*/
|
|
135
|
-
readonly bindings?: CloudflareBindingSource | undefined;
|
|
136
|
-
/**
|
|
137
|
-
* Generic Run context acquired with this Durable Object incarnation. It may prepare model
|
|
138
|
-
* context and/or authorize exact model-declared application Tool Calls at action time. The
|
|
139
|
-
* Layer may depend
|
|
140
|
-
* only on `Crypto.Crypto`, which this platform supplies with `BrowserCrypto`; hosts must close
|
|
141
|
-
* every application-specific service before passing it here. Default absent.
|
|
142
|
-
*/
|
|
143
|
-
readonly runContext?: CloudflareRunContextSource | undefined;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** Per-incarnation host values available to Effect-native runtime extension factories. */
|
|
147
|
-
export interface CloudflareRuntimeSourceContext {
|
|
148
|
-
readonly ctx: DurableObjectState;
|
|
149
|
-
readonly env: unknown;
|
|
150
|
-
readonly conversationId: ConversationId;
|
|
151
|
-
readonly producerId: ProducerId;
|
|
152
133
|
}
|
|
153
134
|
|
|
154
|
-
/**
|
|
155
|
-
export
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
export type CloudflareRunContextSource =
|
|
167
|
-
| CloudflareRunContextLayer
|
|
168
|
-
| ((context: CloudflareRuntimeSourceContext) => CloudflareRunContextLayer);
|
|
135
|
+
/** Services supplied before the application graph is built, including its dependencies. */
|
|
136
|
+
export type CloudflareBootstrapServices =
|
|
137
|
+
| CloudflareDurableRuntimeConfig
|
|
138
|
+
| ThreadObjectIdentity
|
|
139
|
+
| DurableRuntimeConfig
|
|
140
|
+
| Crypto.Crypto
|
|
141
|
+
| DoStorageFailpoint
|
|
142
|
+
| DurableRuntimeFailpoint
|
|
143
|
+
| ThreadMaintenanceFailpoint
|
|
144
|
+
| RunContextPreparation
|
|
145
|
+
| RunToolAuthorization
|
|
146
|
+
| ToolReconciler;
|
|
169
147
|
|
|
170
148
|
/** Every construction failure of the assembled Cloudflare durable runtime stack. */
|
|
171
149
|
export type CloudflareDurableRuntimeInitializationError =
|
|
172
150
|
| CloudflarePlatformConfigError
|
|
151
|
+
| DigestError
|
|
173
152
|
| DoStorageInitializationError;
|
|
174
153
|
|
|
175
|
-
/** The services `
|
|
154
|
+
/** The services `ThreadObject.layer` provides. */
|
|
176
155
|
export type CloudflareDurableRuntimeServices =
|
|
177
156
|
| DurableAgentRuntime
|
|
178
157
|
| SubmissionLedger
|
|
179
|
-
|
|
|
158
|
+
| ThreadStore
|
|
180
159
|
| WakeScheduler
|
|
181
|
-
| DurableRuntimeConfig
|
|
182
|
-
| AgentBindingResolver
|
|
183
|
-
| CloudflareDurableRuntimeConfig
|
|
184
|
-
| ConversationObjectIdentity
|
|
185
160
|
| DurableAlarmService
|
|
186
|
-
|
|
|
187
|
-
|
|
|
161
|
+
| ThreadMaintenance
|
|
162
|
+
| ThreadObjectPorts
|
|
188
163
|
| ProgressWaitRegistry;
|
|
189
164
|
|
|
190
165
|
/**
|
|
@@ -193,15 +168,15 @@ export type CloudflareDurableRuntimeServices =
|
|
|
193
168
|
* request cannot bounce between Objects — and returns the typed response for the endpoint to
|
|
194
169
|
* encode.
|
|
195
170
|
*/
|
|
196
|
-
export class
|
|
197
|
-
|
|
171
|
+
export class ThreadObjectPorts extends Context.Service<
|
|
172
|
+
ThreadObjectPorts,
|
|
198
173
|
{
|
|
199
174
|
readonly handle: (request: PortRequest) => Effect.Effect<PortResponse>;
|
|
200
175
|
}
|
|
201
|
-
>()("@effect-agent/platform-cloudflare/
|
|
176
|
+
>()("@effect-agent/platform-cloudflare/ThreadObjectPorts") {}
|
|
202
177
|
|
|
203
178
|
const decodeConfigValue = Schema.decodeUnknownEffect(CloudflareDurableRuntimeConfigValue);
|
|
204
|
-
const
|
|
179
|
+
const decodeThreadId = Schema.decodeUnknownEffect(ThreadId);
|
|
205
180
|
const decodeProducerId = Schema.decodeUnknownEffect(ProducerId);
|
|
206
181
|
|
|
207
182
|
const configFromOptions = (
|
|
@@ -244,203 +219,149 @@ const configFromOptions = (
|
|
|
244
219
|
);
|
|
245
220
|
|
|
246
221
|
/**
|
|
247
|
-
* The
|
|
248
|
-
* Objects are addressed exclusively by `idFromName(
|
|
249
|
-
*
|
|
222
|
+
* The Thread this Object owns, from the Object identity rule (plan §1.2): Thread
|
|
223
|
+
* Objects are addressed exclusively by `idFromName(threadId)`, so `ctx.id.name` IS the
|
|
224
|
+
* Thread ID. An unnamed Object (from `newUniqueId`) is a deployment error, not a lane.
|
|
250
225
|
*/
|
|
251
|
-
const
|
|
226
|
+
const threadIdFromState = (
|
|
252
227
|
ctx: DurableObjectState,
|
|
253
|
-
): Effect.Effect<
|
|
228
|
+
): Effect.Effect<ThreadId, CloudflarePlatformConfigError> =>
|
|
254
229
|
ctx.id.name === undefined
|
|
255
230
|
? Effect.fail(
|
|
256
231
|
CloudflarePlatformConfigError.make({
|
|
257
232
|
message:
|
|
258
|
-
"This Durable Object was not created via idFromName(
|
|
259
|
-
"Objects must be addressed by their
|
|
233
|
+
"This Durable Object was not created via idFromName(threadId); Thread " +
|
|
234
|
+
"Objects must be addressed by their Thread identity (plan §1.2).",
|
|
260
235
|
}),
|
|
261
236
|
)
|
|
262
|
-
:
|
|
237
|
+
: decodeThreadId(ctx.id.name).pipe(
|
|
263
238
|
Effect.mapError((error) =>
|
|
264
239
|
CloudflarePlatformConfigError.make({
|
|
265
|
-
message: `The Durable Object name is not a valid
|
|
240
|
+
message: `The Durable Object name is not a valid ThreadId: ${error.message}`,
|
|
266
241
|
cause: error,
|
|
267
242
|
}),
|
|
268
243
|
),
|
|
269
244
|
);
|
|
270
245
|
|
|
271
|
-
const resolveBindings = (
|
|
272
|
-
source: CloudflareDurableRuntimeOptions["bindings"],
|
|
273
|
-
context: CloudflareBindingSourceContext,
|
|
274
|
-
): Effect.Effect<ReadonlyArray<ResolvedBinding>> =>
|
|
275
|
-
source === undefined ? Effect.succeed([]) : Effect.suspend(() => source(context));
|
|
276
|
-
|
|
277
|
-
const resolveRunContext = (
|
|
278
|
-
source: CloudflareRunContextSource,
|
|
279
|
-
context: CloudflareRuntimeSourceContext,
|
|
280
|
-
): CloudflareRunContextLayer => (typeof source === "function" ? source(context) : source);
|
|
281
|
-
|
|
282
246
|
/**
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* `@effect/sql-sqlite-do` for BOTH the Conversation Log and the Submission Ledger (so claims
|
|
287
|
-
* fence the same producer epochs — ADR-0011 D7 transposed), wraps the local port facets with
|
|
288
|
-
* the WP2 cross-Object routing decorators over the Durable Object RPC transport, wires the
|
|
289
|
-
* alarm-backed wake scheduler and the maintenance pass, defaults reconciliation to the
|
|
290
|
-
* fail-closed `ToolReconciler.uncertain`, and provides a ready `DurableAgentRuntime` on top.
|
|
291
|
-
*
|
|
292
|
-
* Storage compatibility is verified during construction: an incompatible database fails the
|
|
293
|
-
* Layer typed (`DoStorageCompatibilityError`) before anything is mutated (DEPLOY-008).
|
|
294
|
-
*
|
|
295
|
-
* Requires only the two binding services (`DurableObjectContext`,
|
|
296
|
-
* `ConversationObjectNamespace`) — platform values enter exclusively through Layers
|
|
297
|
-
* (DEPLOY-010).
|
|
247
|
+
* Validate deployment settings and derive services before building application dependencies.
|
|
248
|
+
* The native class factory builds this Layer inside its constructor gate. Custom Effect hosts
|
|
249
|
+
* can provide it around the complete application Layer with the native context already supplied.
|
|
298
250
|
*/
|
|
299
|
-
export
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const producerId = yield* decodeProducerId(
|
|
313
|
-
`${config.producerPrefix}:${conversationId}`,
|
|
314
|
-
).pipe(
|
|
315
|
-
Effect.mapError((error) =>
|
|
316
|
-
CloudflarePlatformConfigError.make({
|
|
317
|
-
message: `The minted producer identity is invalid: ${error.message}`,
|
|
318
|
-
cause: error,
|
|
319
|
-
}),
|
|
320
|
-
),
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
const identityLayer = Layer.succeed(ConversationObjectIdentity)({
|
|
324
|
-
conversationId,
|
|
325
|
-
producerId,
|
|
326
|
-
});
|
|
327
|
-
const cloudflareConfigLayer = Layer.succeed(CloudflareDurableRuntimeConfig)(config);
|
|
328
|
-
|
|
329
|
-
const storageOptions: DoStorageOptions = {
|
|
330
|
-
storage: ctx.storage,
|
|
331
|
-
observationPollInterval: config.observationPollInterval,
|
|
332
|
-
ownershipLeaseDuration: config.ownershipLeaseDuration,
|
|
333
|
-
maxStoredValueBytes: config.maxStoredValueBytes,
|
|
334
|
-
verifyOnOpen: config.verifyOnOpen,
|
|
335
|
-
failpoint: options.storageFailpoint?.(ctx),
|
|
336
|
-
};
|
|
337
|
-
const infrastructure = Layer.mergeAll(
|
|
338
|
-
storageConfigLayer(storageOptions),
|
|
339
|
-
storageFailpointLayer(storageOptions),
|
|
340
|
-
SqliteClient.layer({ storage: ctx.storage }),
|
|
341
|
-
BrowserCrypto.layer,
|
|
342
|
-
);
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* ONE local-facet instance (a shared Layer value) serves both the routed decorators
|
|
346
|
-
* and the owner-side `portCall` executor — the executor must never see the routed
|
|
347
|
-
* ports (plan §1.3: re-routing could bounce a request between Objects).
|
|
348
|
-
*/
|
|
349
|
-
const localPorts = Layer.mergeAll(conversationStoreLayer, submissionLedgerLayer).pipe(
|
|
350
|
-
Layer.provide(infrastructure),
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
const portsEndpointLayer = Layer.effect(ConversationObjectPorts)(
|
|
354
|
-
Effect.gen(function* () {
|
|
355
|
-
const local = yield* Effect.context<SubmissionLedger | ConversationStore>();
|
|
356
|
-
return ConversationObjectPorts.of({
|
|
357
|
-
handle: (request) => executePortRequest(request).pipe(Effect.provide(local)),
|
|
358
|
-
});
|
|
359
|
-
}),
|
|
360
|
-
).pipe(Layer.provide(localPorts));
|
|
361
|
-
|
|
362
|
-
const routedPorts = Layer.mergeAll(
|
|
363
|
-
routedSubmissionLedgerLayer({ localConversationId: conversationId }),
|
|
364
|
-
routedConversationStoreLayer({ localConversationId: conversationId }),
|
|
365
|
-
).pipe(Layer.provide(localPorts), Layer.provide(conversationPortTransportLayer));
|
|
366
|
-
|
|
367
|
-
const runtimeConfigLayer = Layer.succeed(DurableRuntimeConfig)(
|
|
368
|
-
DurableRuntimeConfig.make({
|
|
369
|
-
deploymentId: config.deploymentId,
|
|
370
|
-
producerId,
|
|
371
|
-
settlementPollInterval: Duration.millis(config.settlementPollInterval),
|
|
372
|
-
leaseRenewalInterval: Duration.millis(config.leaseRenewalInterval),
|
|
373
|
-
abortPollInterval: Duration.millis(config.abortPollInterval),
|
|
374
|
-
...(options.estimateCostMicrousd === undefined
|
|
375
|
-
? {}
|
|
376
|
-
: { estimateCostMicrousd: options.estimateCostMicrousd }),
|
|
251
|
+
export const layerConfig = (
|
|
252
|
+
options: CloudflareDurableRuntimeOptions,
|
|
253
|
+
): Layer.Layer<CloudflareBootstrapServices, CloudflarePlatformConfigError, DurableObjectContext> =>
|
|
254
|
+
Layer.unwrap(
|
|
255
|
+
Effect.gen(function* () {
|
|
256
|
+
const { ctx } = yield* DurableObjectContext;
|
|
257
|
+
const config = yield* configFromOptions(options);
|
|
258
|
+
const threadId = yield* threadIdFromState(ctx);
|
|
259
|
+
const producerId = yield* decodeProducerId(`${config.producerPrefix}:${threadId}`).pipe(
|
|
260
|
+
Effect.mapError((error) =>
|
|
261
|
+
CloudflarePlatformConfigError.make({
|
|
262
|
+
message: `The minted producer identity is invalid: ${error.message}`,
|
|
263
|
+
cause: error,
|
|
377
264
|
}),
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
options.
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
265
|
+
),
|
|
266
|
+
);
|
|
267
|
+
return Layer.mergeAll(
|
|
268
|
+
Layer.succeed(CloudflareDurableRuntimeConfig, config),
|
|
269
|
+
Layer.succeed(ThreadObjectIdentity, { threadId, producerId }),
|
|
270
|
+
DurableRuntimeConfig.layer({
|
|
271
|
+
deploymentId: config.deploymentId,
|
|
272
|
+
producerId,
|
|
273
|
+
settlementPollInterval: Duration.millis(config.settlementPollInterval),
|
|
274
|
+
leaseRenewalInterval: Duration.millis(config.leaseRenewalInterval),
|
|
275
|
+
abortPollInterval: Duration.millis(config.abortPollInterval),
|
|
276
|
+
...(options.estimateCostMicrousd === undefined
|
|
277
|
+
? {}
|
|
278
|
+
: { estimateCostMicrousd: options.estimateCostMicrousd }),
|
|
279
|
+
}),
|
|
280
|
+
BrowserCrypto.layer,
|
|
281
|
+
storageFailpointLayer({ storage: ctx.storage, failpoint: options.storageFailpoint?.(ctx) }),
|
|
282
|
+
options.runtimeFailpoint === undefined
|
|
283
|
+
? DurableRuntimeFailpoint.layer
|
|
284
|
+
: Layer.succeed(DurableRuntimeFailpoint, { hit: options.runtimeFailpoint(ctx) }),
|
|
285
|
+
options.maintenanceFailpoint === undefined
|
|
286
|
+
? ThreadMaintenanceFailpoint.layer
|
|
287
|
+
: Layer.succeed(ThreadMaintenanceFailpoint, {
|
|
288
|
+
hit: options.maintenanceFailpoint(ctx),
|
|
289
|
+
}),
|
|
290
|
+
options.toolReconciler ?? ToolReconciler.uncertain,
|
|
291
|
+
options.operationAuthorizer === undefined
|
|
292
|
+
? Layer.empty
|
|
293
|
+
: operationAuthorizerLayer(options.operationAuthorizer),
|
|
294
|
+
options.toolFailureObserver === undefined
|
|
295
|
+
? Layer.succeed(CurrentToolFailureObserver, undefined)
|
|
296
|
+
: toolFailureObserverLayer(options.toolFailureObserver),
|
|
297
|
+
RunContextPreparationPassthrough,
|
|
298
|
+
RunToolAuthorization.allowAll,
|
|
299
|
+
);
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
411
302
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
303
|
+
/**
|
|
304
|
+
* Register typed Agents and version declarations. Hashing and dependency capture happen in
|
|
305
|
+
* this Layer's Scope, after application Layers have been provided. Every Agent's instruction,
|
|
306
|
+
* Tool, Schema, and model requirements remain visible until satisfied by Layer composition.
|
|
307
|
+
* Use Layer.unwrap for registration values that need effectful application setup.
|
|
308
|
+
*/
|
|
309
|
+
export const layer = <const Entries extends ReadonlyArray<AgentRegistration>>(
|
|
310
|
+
registrations: Entries,
|
|
311
|
+
) => Layer.unwrap(Effect.map(compileRegistrations(registrations), layerFromBindings));
|
|
419
312
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
313
|
+
/** Internal assembly shared by registration compilation and low-level adapter fixtures. */
|
|
314
|
+
export const layerFromBindings = (
|
|
315
|
+
bindings: ReadonlyArray<ResolvedBinding>,
|
|
316
|
+
): Layer.Layer<
|
|
317
|
+
CloudflareDurableRuntimeServices,
|
|
318
|
+
DoStorageInitializationError,
|
|
319
|
+
DurableObjectContext | ThreadObjectNamespace | CloudflareBootstrapServices
|
|
320
|
+
> =>
|
|
321
|
+
Layer.unwrap(
|
|
322
|
+
Effect.gen(function* () {
|
|
323
|
+
const { ctx } = yield* DurableObjectContext;
|
|
324
|
+
const config = yield* CloudflareDurableRuntimeConfig;
|
|
325
|
+
const { threadId } = yield* ThreadObjectIdentity;
|
|
326
|
+
const storageOptions: DoStorageOptions = {
|
|
327
|
+
storage: ctx.storage,
|
|
328
|
+
observationPollInterval: config.observationPollInterval,
|
|
329
|
+
ownershipLeaseDuration: config.ownershipLeaseDuration,
|
|
330
|
+
maxStoredValueBytes: config.maxStoredValueBytes,
|
|
331
|
+
verifyOnOpen: config.verifyOnOpen,
|
|
332
|
+
};
|
|
333
|
+
const infrastructure = Layer.mergeAll(
|
|
334
|
+
storageConfigLayer(storageOptions),
|
|
335
|
+
SqliteClient.layer({ storage: ctx.storage }),
|
|
336
|
+
);
|
|
437
337
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
338
|
+
// The same local ports serve routed decorators and owner-side RPC execution.
|
|
339
|
+
// The RPC executor must never receive routed ports and bounce requests between Objects.
|
|
340
|
+
const localPorts = Layer.mergeAll(threadStoreLayer, submissionLedgerLayer).pipe(
|
|
341
|
+
Layer.provide(infrastructure),
|
|
342
|
+
);
|
|
343
|
+
const portsEndpointLayer = Layer.effect(ThreadObjectPorts)(
|
|
344
|
+
Effect.gen(function* () {
|
|
345
|
+
const local = yield* Effect.context<SubmissionLedger | ThreadStore>();
|
|
346
|
+
return ThreadObjectPorts.of({
|
|
347
|
+
handle: (request) => executePortRequest(request).pipe(Effect.provide(local)),
|
|
348
|
+
});
|
|
349
|
+
}),
|
|
350
|
+
).pipe(Layer.provide(localPorts));
|
|
351
|
+
const routedPorts = Layer.mergeAll(
|
|
352
|
+
routedSubmissionLedgerLayer({ localThreadId: threadId }),
|
|
353
|
+
routedThreadStoreLayer({ localThreadId: threadId }),
|
|
354
|
+
).pipe(Layer.provide(localPorts), Layer.provide(threadPortTransportLayer));
|
|
355
|
+
const base = Layer.mergeAll(DurableAlarmService.layer, ProgressWaitRegistry.layer);
|
|
356
|
+
const runtimeStack = DurableAgentRuntime.layerWithServices.pipe(
|
|
357
|
+
Layer.provideMerge(routedPorts),
|
|
358
|
+
Layer.provideMerge(cloudflareWakeSchedulerLayer),
|
|
359
|
+
Layer.provideMerge(base),
|
|
360
|
+
);
|
|
361
|
+
return Layer.mergeAll(
|
|
362
|
+
runtimeStack,
|
|
363
|
+
ThreadMaintenance.layer(bindings).pipe(Layer.provide(runtimeStack)),
|
|
364
|
+
portsEndpointLayer,
|
|
365
|
+
);
|
|
366
|
+
}),
|
|
367
|
+
);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { AgentId } from "@effect-agent/core";
|
|
2
|
+
import {
|
|
3
|
+
type DurableSubmitAgent,
|
|
4
|
+
PersistedJson,
|
|
5
|
+
type PreparedInput,
|
|
6
|
+
PreparedInputAdmission,
|
|
7
|
+
type ScheduledEnvelope,
|
|
8
|
+
ScheduledInputAdmission,
|
|
9
|
+
ScheduledInputRetryable,
|
|
10
|
+
ScheduleStorageError,
|
|
11
|
+
} from "@effect-agent/thread";
|
|
12
|
+
import { Effect, Layer } from "effect";
|
|
13
|
+
|
|
14
|
+
import { CloudflareThreadClient, type ThreadClientError } from "./client.ts";
|
|
15
|
+
|
|
16
|
+
const passthroughAgent = (agentId: AgentId): DurableSubmitAgent<typeof PersistedJson> => ({
|
|
17
|
+
definition: { id: agentId, input: PersistedJson },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/** Ordinary prepared admission through one freshly addressed Thread Object call. */
|
|
21
|
+
export const cloudflarePreparedInputAdmissionLayer: Layer.Layer<
|
|
22
|
+
PreparedInputAdmission,
|
|
23
|
+
never,
|
|
24
|
+
CloudflareThreadClient
|
|
25
|
+
> = Layer.effect(
|
|
26
|
+
PreparedInputAdmission,
|
|
27
|
+
Effect.gen(function* () {
|
|
28
|
+
const client = yield* CloudflareThreadClient;
|
|
29
|
+
return PreparedInputAdmission.of({
|
|
30
|
+
submit: (envelope) =>
|
|
31
|
+
client
|
|
32
|
+
.submit(passthroughAgent(envelope.agentId), envelope.input, {
|
|
33
|
+
threadId: envelope.threadId,
|
|
34
|
+
principal: envelope.deliveryPrincipal,
|
|
35
|
+
idempotencyKey: envelope.admissionKey,
|
|
36
|
+
definitions: envelope.definitions,
|
|
37
|
+
})
|
|
38
|
+
.pipe(
|
|
39
|
+
Effect.catchTags({
|
|
40
|
+
AdmissionConflict: () =>
|
|
41
|
+
ScheduleStorageError.make({ operation: "prepared admission", reason: "corrupt" }),
|
|
42
|
+
AdmissionLimitExceeded: () => ScheduledInputRetryable.make({ reason: "capacity" }),
|
|
43
|
+
ThreadClientError: (error: ThreadClientError) =>
|
|
44
|
+
ScheduledInputRetryable.make({
|
|
45
|
+
reason: error.overloaded === true ? "capacity" : "transport",
|
|
46
|
+
}),
|
|
47
|
+
HostProtocolError: () => ScheduledInputRetryable.make({ reason: "ambiguous" }),
|
|
48
|
+
LedgerError: () => ScheduledInputRetryable.make({ reason: "storage" }),
|
|
49
|
+
ThreadStoreError: () => ScheduledInputRetryable.make({ reason: "storage" }),
|
|
50
|
+
DurableAlarmError: () => ScheduledInputRetryable.make({ reason: "storage" }),
|
|
51
|
+
AgentInputError: () =>
|
|
52
|
+
ScheduleStorageError.make({ operation: "prepared admission", reason: "corrupt" }),
|
|
53
|
+
DigestError: () =>
|
|
54
|
+
ScheduleStorageError.make({ operation: "prepared admission", reason: "corrupt" }),
|
|
55
|
+
ThreadNotMaterialized: () => ScheduledInputRetryable.make({ reason: "storage" }),
|
|
56
|
+
AppendConflict: () => ScheduledInputRetryable.make({ reason: "ambiguous" }),
|
|
57
|
+
FenceRejected: () => ScheduledInputRetryable.make({ reason: "ambiguous" }),
|
|
58
|
+
DurableRuntimeFailpointError: () =>
|
|
59
|
+
ScheduledInputRetryable.make({ reason: "ambiguous" }),
|
|
60
|
+
}),
|
|
61
|
+
),
|
|
62
|
+
});
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const preparedFromSchedule = (envelope: ScheduledEnvelope): PreparedInput => ({
|
|
67
|
+
schemaVersion: 1,
|
|
68
|
+
threadId: envelope.threadId,
|
|
69
|
+
deliveryPrincipal: envelope.deliveryPrincipal,
|
|
70
|
+
agentId: envelope.agentId,
|
|
71
|
+
definitions: envelope.definitions,
|
|
72
|
+
input: envelope.input,
|
|
73
|
+
inputDigest: envelope.inputDigest,
|
|
74
|
+
admissionKey: envelope.admissionKey,
|
|
75
|
+
authorization: envelope.authorization,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/** Compatibility adapter retaining Scheduling's public admission port. */
|
|
79
|
+
export const cloudflareScheduledInputAdmissionLayer: Layer.Layer<
|
|
80
|
+
ScheduledInputAdmission,
|
|
81
|
+
never,
|
|
82
|
+
PreparedInputAdmission
|
|
83
|
+
> = Layer.effect(
|
|
84
|
+
ScheduledInputAdmission,
|
|
85
|
+
Effect.map(PreparedInputAdmission, (admission) =>
|
|
86
|
+
ScheduledInputAdmission.of({
|
|
87
|
+
submit: (envelope) => admission.submit(preparedFromSchedule(envelope)),
|
|
88
|
+
}),
|
|
89
|
+
),
|
|
90
|
+
);
|