@effect-agent/platform-cloudflare 0.1.0-beta.38 → 0.1.0-beta.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bindings-BrVEOkfe.d.mts +102 -0
- package/dist/browser-quick-action.d.mts +18 -1
- package/dist/browser-quick-action.mjs +14 -1
- package/dist/browser-quick-action.mjs.map +1 -1
- package/dist/browser-rest-capture.d.mts +17 -2
- package/dist/browser-rest-capture.mjs +12 -1
- package/dist/browser-rest-capture.mjs.map +1 -1
- package/dist/browser-rest-crawl.mjs +2 -2
- package/dist/browser-rest-crawl.mjs.map +1 -1
- package/dist/index.d.mts +245 -237
- package/dist/index.mjs +168 -164
- package/dist/index.mjs.map +1 -1
- package/dist/interactive-browser.d.mts +160 -2
- package/dist/interactive-browser.mjs +1166 -2
- package/dist/interactive-browser.mjs.map +1 -0
- package/dist/{scheduling-B-OFqoS9.mjs → prepared-admission-BKp_Upw2.mjs} +134 -433
- package/dist/prepared-admission-BKp_Upw2.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/scheduling.d.mts +47 -2
- package/dist/scheduling.mjs +339 -1
- package/dist/scheduling.mjs.map +1 -0
- package/dist/subscriptions.d.mts +52 -0
- package/dist/subscriptions.mjs +346 -0
- package/dist/subscriptions.mjs.map +1 -0
- package/package.json +20 -13
- package/src/alarm.ts +264 -265
- package/src/bindings.ts +34 -30
- package/src/browser-quick-action.ts +31 -0
- package/src/browser-rest-capture.ts +27 -0
- package/src/client.ts +111 -105
- package/src/code-mode-executor.ts +19 -0
- package/src/config.ts +7 -7
- package/src/index.ts +10 -11
- package/src/interactive-browser.ts +29 -1
- package/src/layers.ts +178 -257
- package/src/prepared-admission.ts +90 -0
- package/src/scheduling.ts +19 -55
- package/src/subscriptions.ts +661 -0
- package/src/{conversation-object.ts → thread-object.ts} +167 -136
- package/src/transport.ts +15 -15
- package/src/wake-scheduler.ts +17 -22
- package/dist/interactive-browser-BRo-Bfvb.d.mts +0 -144
- package/dist/interactive-browser-Cy5q-ldl.mjs +0 -1154
- package/dist/interactive-browser-Cy5q-ldl.mjs.map +0 -1
- package/dist/scheduling-B-OFqoS9.mjs.map +0 -1
- package/dist/scheduling-BJs_kHTx.d.mts +0 -142
package/src/client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentId, AgentInputError, type
|
|
1
|
+
import { AgentId, AgentInputError, type ThreadId } from "@effect-agent/core";
|
|
2
2
|
import {
|
|
3
3
|
AbortCommand,
|
|
4
4
|
AbortIntent,
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
ApprovalDecisionIntent,
|
|
10
10
|
CanonicalRecordEnvelope,
|
|
11
11
|
CanonicalSequence,
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
ThreadNotMaterialized,
|
|
13
|
+
ThreadStoreError,
|
|
14
14
|
DefinitionDigests,
|
|
15
15
|
DigestError,
|
|
16
16
|
DurableRuntimeFailpointError,
|
|
@@ -29,17 +29,18 @@ import {
|
|
|
29
29
|
UnknownResolutionIntent,
|
|
30
30
|
type DurableSubmitAgent,
|
|
31
31
|
type DurableSubmitOptions,
|
|
32
|
-
} from "@effect-agent/
|
|
32
|
+
} from "@effect-agent/thread";
|
|
33
|
+
import { BrowserCrypto } from "@effect/platform-browser";
|
|
33
34
|
import { Context, Crypto, Duration, Effect, Layer, Schema } from "effect";
|
|
34
35
|
import { RpcTracing } from "effect-cf";
|
|
35
36
|
|
|
36
37
|
import { DurableAlarmError } from "./alarm.ts";
|
|
37
|
-
import {
|
|
38
|
+
import { ThreadObjectNamespace, type ThreadObjectRpc } from "./bindings.ts";
|
|
38
39
|
import { cloudflareFailureSignals, safeCauseMessage } from "./boundary.ts";
|
|
39
40
|
import { AdmissionLimitExceeded } from "./config.ts";
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
|
-
* The Worker↔
|
|
43
|
+
* The Worker↔Thread-Object host protocol (plan §1.4): Schema envelopes for the host
|
|
43
44
|
* entry points (`submitEncoded`, `awaitSettlementEncoded`, `observePage`, `abortEncoded`,
|
|
44
45
|
* `resolveApprovalEncoded`, `resolveUnknownEncoded`) plus the Worker-side client that speaks
|
|
45
46
|
* it. Mirrors the WP2 port protocol: requests and responses are closed Schema unions, typed
|
|
@@ -70,10 +71,10 @@ export class HostProtocolError extends Schema.TaggedError<HostProtocolError>()(
|
|
|
70
71
|
) {}
|
|
71
72
|
|
|
72
73
|
/** The Worker-side stub call itself failed (RPC rejection, overload, eviction mid-call). */
|
|
73
|
-
export class
|
|
74
|
-
"
|
|
74
|
+
export class ThreadClientError extends Schema.TaggedError<ThreadClientError>()(
|
|
75
|
+
"ThreadClientError",
|
|
75
76
|
{
|
|
76
|
-
|
|
77
|
+
threadId: Schema.String,
|
|
77
78
|
message: Schema.String,
|
|
78
79
|
cause: Schema.optionalKey(Schema.Defect()),
|
|
79
80
|
/** Cloudflare's own classification for a failure safe to retry with a fresh stub. */
|
|
@@ -91,7 +92,7 @@ export class ConversationClientError extends Schema.TaggedError<ConversationClie
|
|
|
91
92
|
* One durable submission, input ALREADY encoded by the caller through the Agent Binding's
|
|
92
93
|
* input schema (the Worker bundles the same Agent definitions as the Object, so schema
|
|
93
94
|
* validation happens client-side; the resolved Binding re-validates at claim time). The
|
|
94
|
-
*
|
|
95
|
+
* Thread identity is deliberately absent — the addressed Object IS the lane.
|
|
95
96
|
*/
|
|
96
97
|
export class SubmitRequest extends Schema.Class<SubmitRequest>(
|
|
97
98
|
"@effect-agent/platform-cloudflare/SubmitRequest",
|
|
@@ -145,8 +146,8 @@ export const HostFailure = Schema.Union([
|
|
|
145
146
|
UnknownResolutionConflict,
|
|
146
147
|
JoinedToHost,
|
|
147
148
|
LedgerError,
|
|
148
|
-
|
|
149
|
-
|
|
149
|
+
ThreadStoreError,
|
|
150
|
+
ThreadNotMaterialized,
|
|
150
151
|
AppendConflict,
|
|
151
152
|
FenceRejected,
|
|
152
153
|
DurableRuntimeFailpointError,
|
|
@@ -250,14 +251,14 @@ export const decodeHostResponse = Schema.decodeUnknownEffect(HostResponse);
|
|
|
250
251
|
// Worker-side client
|
|
251
252
|
// ---------------------------------------------------------------------------
|
|
252
253
|
|
|
253
|
-
/** Failure surface of `
|
|
254
|
+
/** Failure surface of `CloudflareThreadClient.submit`. */
|
|
254
255
|
const ClientSubmitHostFailure = Schema.Union([
|
|
255
256
|
AgentInputError,
|
|
256
257
|
DigestError,
|
|
257
258
|
AdmissionConflict,
|
|
258
259
|
LedgerError,
|
|
259
|
-
|
|
260
|
-
|
|
260
|
+
ThreadStoreError,
|
|
261
|
+
ThreadNotMaterialized,
|
|
261
262
|
AppendConflict,
|
|
262
263
|
FenceRejected,
|
|
263
264
|
DurableRuntimeFailpointError,
|
|
@@ -265,14 +266,20 @@ const ClientSubmitHostFailure = Schema.Union([
|
|
|
265
266
|
DurableAlarmError,
|
|
266
267
|
HostProtocolError,
|
|
267
268
|
]);
|
|
268
|
-
const ClientAwaitHostFailure = Schema.Union([
|
|
269
|
+
const ClientAwaitHostFailure = Schema.Union([
|
|
270
|
+
LedgerError,
|
|
271
|
+
SettlementConflict,
|
|
272
|
+
OperationDenied,
|
|
273
|
+
HostProtocolError,
|
|
274
|
+
]);
|
|
269
275
|
const ClientObserveHostFailure = Schema.Union([
|
|
270
|
-
|
|
271
|
-
|
|
276
|
+
ThreadStoreError,
|
|
277
|
+
ThreadNotMaterialized,
|
|
272
278
|
OperationDenied,
|
|
273
279
|
HostProtocolError,
|
|
274
280
|
]);
|
|
275
281
|
const ClientAbortHostFailure = Schema.Union([
|
|
282
|
+
OperationDenied,
|
|
276
283
|
LedgerError,
|
|
277
284
|
SettlementConflict,
|
|
278
285
|
JoinedToHost,
|
|
@@ -299,23 +306,19 @@ const ClientUnknownHostFailure = Schema.Union([
|
|
|
299
306
|
HostProtocolError,
|
|
300
307
|
]);
|
|
301
308
|
|
|
302
|
-
export type ClientSubmitFailure = typeof ClientSubmitHostFailure.Type |
|
|
303
|
-
export type ClientAwaitFailure = typeof ClientAwaitHostFailure.Type |
|
|
304
|
-
export type ClientObserveFailure = typeof ClientObserveHostFailure.Type |
|
|
309
|
+
export type ClientSubmitFailure = typeof ClientSubmitHostFailure.Type | ThreadClientError;
|
|
310
|
+
export type ClientAwaitFailure = typeof ClientAwaitHostFailure.Type | ThreadClientError;
|
|
311
|
+
export type ClientObserveFailure = typeof ClientObserveHostFailure.Type | ThreadClientError;
|
|
305
312
|
export type ClientProgressFailure = ClientObserveFailure;
|
|
306
|
-
export type ClientAbortFailure = typeof ClientAbortHostFailure.Type |
|
|
307
|
-
export type ClientApprovalFailure = typeof ClientApprovalHostFailure.Type |
|
|
308
|
-
export type ClientUnknownFailure = typeof ClientUnknownHostFailure.Type |
|
|
309
|
-
|
|
310
|
-
const outOfContract = (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
observed: string,
|
|
314
|
-
): ConversationClientError =>
|
|
315
|
-
ConversationClientError.make({
|
|
316
|
-
conversationId,
|
|
313
|
+
export type ClientAbortFailure = typeof ClientAbortHostFailure.Type | ThreadClientError;
|
|
314
|
+
export type ClientApprovalFailure = typeof ClientApprovalHostFailure.Type | ThreadClientError;
|
|
315
|
+
export type ClientUnknownFailure = typeof ClientUnknownHostFailure.Type | ThreadClientError;
|
|
316
|
+
|
|
317
|
+
const outOfContract = (threadId: string, operation: string, observed: string): ThreadClientError =>
|
|
318
|
+
ThreadClientError.make({
|
|
319
|
+
threadId,
|
|
317
320
|
message: boundHostDiagnostic(
|
|
318
|
-
`The
|
|
321
|
+
`The Thread Object answered ${operation} with the out-of-contract ${observed}.`,
|
|
319
322
|
),
|
|
320
323
|
});
|
|
321
324
|
|
|
@@ -328,11 +331,11 @@ const hostRpcMethods = {
|
|
|
328
331
|
abort: "abortEncoded",
|
|
329
332
|
resolveApproval: "resolveApprovalEncoded",
|
|
330
333
|
resolveUnknown: "resolveUnknownEncoded",
|
|
331
|
-
} as const satisfies Record<string, keyof
|
|
334
|
+
} as const satisfies Record<string, keyof ThreadObjectRpc>;
|
|
332
335
|
|
|
333
|
-
/** Worker-side client over the
|
|
334
|
-
export class
|
|
335
|
-
|
|
336
|
+
/** Worker-side client over the Thread Object namespace (DEPLOY-010). */
|
|
337
|
+
export class CloudflareThreadClient extends Context.Service<
|
|
338
|
+
CloudflareThreadClient,
|
|
336
339
|
{
|
|
337
340
|
/** Encode the input client-side, then durably submit to the owning Object. */
|
|
338
341
|
readonly submit: <InputSchema extends Schema.Top>(
|
|
@@ -347,12 +350,12 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
347
350
|
* The result is deliberately void: canonical records remain authoritative and must be read.
|
|
348
351
|
*/
|
|
349
352
|
readonly awaitProgress: (
|
|
350
|
-
|
|
353
|
+
threadId: ThreadId,
|
|
351
354
|
afterSequence: CanonicalSequence,
|
|
352
355
|
) => Effect.Effect<void, ClientProgressFailure>;
|
|
353
356
|
/** One bounded page of canonical records. */
|
|
354
357
|
readonly readPage: (
|
|
355
|
-
|
|
358
|
+
threadId: ThreadId,
|
|
356
359
|
options?: {
|
|
357
360
|
readonly afterSequence?: CanonicalSequence | undefined;
|
|
358
361
|
readonly limit?: number | undefined;
|
|
@@ -364,55 +367,69 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
364
367
|
* `awaitSettlement`.
|
|
365
368
|
*/
|
|
366
369
|
readonly readAll: (
|
|
367
|
-
|
|
370
|
+
threadId: ThreadId,
|
|
368
371
|
) => Effect.Effect<ReadonlyArray<CanonicalRecordEnvelope>, ClientObserveFailure>;
|
|
369
372
|
/**
|
|
370
|
-
* Submission-addressed operations take the owning
|
|
373
|
+
* Submission-addressed operations take the owning Thread explicitly (from the
|
|
371
374
|
* Receipt): minted Submission identities stay OPAQUE outside the storage adapter that
|
|
372
375
|
* minted them (D-P6-5), so the client never parses one to find the lane.
|
|
373
376
|
*/
|
|
374
377
|
readonly abort: (
|
|
375
|
-
|
|
378
|
+
threadId: ThreadId,
|
|
376
379
|
command: AbortCommand,
|
|
377
380
|
) => Effect.Effect<AbortIntent, ClientAbortFailure>;
|
|
378
381
|
readonly resolveApproval: (
|
|
379
|
-
|
|
382
|
+
threadId: ThreadId,
|
|
380
383
|
command: ApprovalDecisionCommand,
|
|
381
384
|
) => Effect.Effect<ApprovalDecisionIntent, ClientApprovalFailure>;
|
|
382
385
|
readonly resolveUnknown: (
|
|
383
|
-
|
|
386
|
+
threadId: ThreadId,
|
|
384
387
|
command: UnknownResolutionCommand,
|
|
385
388
|
) => Effect.Effect<UnknownResolutionIntent, ClientUnknownFailure>;
|
|
386
389
|
}
|
|
387
|
-
>()("@effect-agent/platform-cloudflare/
|
|
390
|
+
>()("@effect-agent/platform-cloudflare/CloudflareThreadClient") {
|
|
391
|
+
/**
|
|
392
|
+
* Assemble the client from a resolved namespace and the platform Crypto implementation.
|
|
393
|
+
* rpcTracing is the binding name and remains opt-in. Caller authentication, principals,
|
|
394
|
+
* idempotency keys, and definition digests are still explicit submission inputs.
|
|
395
|
+
*/
|
|
396
|
+
static layerFromBinding(options: {
|
|
397
|
+
readonly namespace: DurableObjectNamespace<ThreadObjectRpc>;
|
|
398
|
+
readonly rpcTracing?: string;
|
|
399
|
+
}): Layer.Layer<CloudflareThreadClient> {
|
|
400
|
+
return CloudflareThreadClient.layer.pipe(
|
|
401
|
+
Layer.provide([ThreadObjectNamespace.layer(options.namespace, options), BrowserCrypto.layer]),
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
388
405
|
static readonly layer: Layer.Layer<
|
|
389
|
-
|
|
406
|
+
CloudflareThreadClient,
|
|
390
407
|
never,
|
|
391
|
-
|
|
392
|
-
> = Layer.effect(
|
|
408
|
+
ThreadObjectNamespace | Crypto.Crypto
|
|
409
|
+
> = Layer.effect(CloudflareThreadClient)(
|
|
393
410
|
Effect.gen(function* () {
|
|
394
|
-
const { namespace, rpcTracing } = yield*
|
|
411
|
+
const { namespace, rpcTracing } = yield* ThreadObjectNamespace;
|
|
395
412
|
const crypto = yield* Crypto.Crypto;
|
|
396
413
|
|
|
397
414
|
const call = Effect.fn(
|
|
398
415
|
function* (
|
|
399
|
-
|
|
416
|
+
threadId: string,
|
|
400
417
|
operation: keyof typeof hostRpcMethods,
|
|
401
418
|
encoded: unknown,
|
|
402
|
-
): Effect.fn.Return<HostResponse,
|
|
419
|
+
): Effect.fn.Return<HostResponse, ThreadClientError | HostProtocolError> {
|
|
403
420
|
// Empty arguments preserve native arity. Passing `undefined` still adds an argument.
|
|
404
421
|
const traceArgs =
|
|
405
422
|
rpcTracing === undefined ? [] : yield* RpcTracing.withRpcTraceContext([]);
|
|
406
423
|
const raw = yield* Effect.tryPromise({
|
|
407
424
|
try: () => {
|
|
408
|
-
const stub = namespace.get(namespace.idFromName(
|
|
425
|
+
const stub = namespace.get(namespace.idFromName(threadId));
|
|
409
426
|
return stub[hostRpcMethods[operation]](encoded, ...traceArgs);
|
|
410
427
|
},
|
|
411
428
|
catch: (cause) =>
|
|
412
|
-
|
|
413
|
-
|
|
429
|
+
ThreadClientError.make({
|
|
430
|
+
threadId,
|
|
414
431
|
message: boundHostDiagnostic(
|
|
415
|
-
`${operation} did not reach the
|
|
432
|
+
`${operation} did not reach the Thread Object: ${safeCauseMessage(
|
|
416
433
|
cause,
|
|
417
434
|
"the RPC failed without a diagnostic",
|
|
418
435
|
)}`,
|
|
@@ -422,26 +439,25 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
422
439
|
}),
|
|
423
440
|
});
|
|
424
441
|
return yield* decodeHostResponse(raw).pipe(
|
|
425
|
-
Effect.mapError(
|
|
426
|
-
(
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}),
|
|
442
|
+
Effect.mapError((error): HostProtocolError =>
|
|
443
|
+
HostProtocolError.make({
|
|
444
|
+
message: boundHostDiagnostic(
|
|
445
|
+
`The ${operation} answer could not be decoded: ${error.message}`,
|
|
446
|
+
),
|
|
447
|
+
}),
|
|
432
448
|
),
|
|
433
449
|
);
|
|
434
450
|
},
|
|
435
|
-
(effect,
|
|
451
|
+
(effect, threadId, operation) =>
|
|
436
452
|
rpcTracing === undefined
|
|
437
|
-
? Effect.withSpan(effect, "
|
|
438
|
-
attributes: {
|
|
453
|
+
? Effect.withSpan(effect, "CloudflareThreadClient.call", {
|
|
454
|
+
attributes: { threadId, operation },
|
|
439
455
|
})
|
|
440
456
|
: RpcTracing.withRpcClientSpan(effect, rpcTracing, hostRpcMethods[operation]),
|
|
441
457
|
);
|
|
442
458
|
|
|
443
459
|
const expect = <ResultSchema extends Schema.Top, FailureSchema extends Schema.Top>(
|
|
444
|
-
|
|
460
|
+
threadId: string,
|
|
445
461
|
operation: string,
|
|
446
462
|
resultSchema: ResultSchema,
|
|
447
463
|
failureSchema: FailureSchema,
|
|
@@ -450,22 +466,22 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
450
466
|
const isExpectedFailure = Schema.is(failureSchema);
|
|
451
467
|
return (
|
|
452
468
|
response: HostResponse,
|
|
453
|
-
): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] |
|
|
469
|
+
): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | ThreadClientError> => {
|
|
454
470
|
if (response._tag === "HostFailed") {
|
|
455
471
|
const failure = response.failure;
|
|
456
472
|
return isExpectedFailure(failure)
|
|
457
473
|
? Effect.fail(failure)
|
|
458
|
-
: Effect.fail(outOfContract(
|
|
474
|
+
: Effect.fail(outOfContract(threadId, operation, `failure ${failure._tag}`));
|
|
459
475
|
}
|
|
460
476
|
if (!isExpectedResult(response)) {
|
|
461
|
-
return Effect.fail(outOfContract(
|
|
477
|
+
return Effect.fail(outOfContract(threadId, operation, `result ${response._tag}`));
|
|
462
478
|
}
|
|
463
479
|
return Effect.succeed(response);
|
|
464
480
|
};
|
|
465
481
|
};
|
|
466
482
|
|
|
467
483
|
const readPage = (
|
|
468
|
-
|
|
484
|
+
threadId: ThreadId,
|
|
469
485
|
options?: {
|
|
470
486
|
readonly afterSequence?: CanonicalSequence | undefined;
|
|
471
487
|
readonly limit?: number | undefined;
|
|
@@ -485,9 +501,9 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
485
501
|
}),
|
|
486
502
|
),
|
|
487
503
|
);
|
|
488
|
-
const response = yield* call(
|
|
504
|
+
const response = yield* call(threadId, "observePage", encoded);
|
|
489
505
|
const page = yield* expect(
|
|
490
|
-
|
|
506
|
+
threadId,
|
|
491
507
|
"observePage",
|
|
492
508
|
ObservedPage,
|
|
493
509
|
ClientObserveHostFailure,
|
|
@@ -495,18 +511,15 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
495
511
|
return page.records;
|
|
496
512
|
});
|
|
497
513
|
|
|
498
|
-
const cancelProgress = (
|
|
499
|
-
conversationId: ConversationId,
|
|
500
|
-
waiterId: string,
|
|
501
|
-
): Effect.Effect<void> =>
|
|
514
|
+
const cancelProgress = (threadId: ThreadId, waiterId: string): Effect.Effect<void> =>
|
|
502
515
|
encodeCancelProgressRequest(CancelProgressRequest.make({ waiterId })).pipe(
|
|
503
516
|
Effect.mapError(() => undefined),
|
|
504
|
-
Effect.flatMap((encoded) => call(
|
|
517
|
+
Effect.flatMap((encoded) => call(threadId, "cancelProgress", encoded)),
|
|
505
518
|
Effect.asVoid,
|
|
506
519
|
Effect.ignore,
|
|
507
520
|
);
|
|
508
521
|
|
|
509
|
-
return
|
|
522
|
+
return CloudflareThreadClient.of({
|
|
510
523
|
submit: <InputSchema extends Schema.Top>(
|
|
511
524
|
agent: DurableSubmitAgent<InputSchema>,
|
|
512
525
|
input: InputSchema["Type"],
|
|
@@ -543,9 +556,9 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
543
556
|
}),
|
|
544
557
|
),
|
|
545
558
|
);
|
|
546
|
-
const response = yield* call(options.
|
|
559
|
+
const response = yield* call(options.threadId, "submit", encoded);
|
|
547
560
|
const succeeded = yield* expect(
|
|
548
|
-
options.
|
|
561
|
+
options.threadId,
|
|
549
562
|
"submit",
|
|
550
563
|
SubmitSucceeded,
|
|
551
564
|
ClientSubmitHostFailure,
|
|
@@ -562,9 +575,9 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
562
575
|
}),
|
|
563
576
|
),
|
|
564
577
|
);
|
|
565
|
-
const response = yield* call(receipt.
|
|
578
|
+
const response = yield* call(receipt.threadId, "awaitSettlement", encoded);
|
|
566
579
|
const settled = yield* expect(
|
|
567
|
-
receipt.
|
|
580
|
+
receipt.threadId,
|
|
568
581
|
"awaitSettlement",
|
|
569
582
|
SettlementReached,
|
|
570
583
|
ClientAwaitHostFailure,
|
|
@@ -572,7 +585,7 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
572
585
|
return settled.settlement;
|
|
573
586
|
}),
|
|
574
587
|
|
|
575
|
-
awaitProgress: (
|
|
588
|
+
awaitProgress: (threadId, afterSequence) =>
|
|
576
589
|
Effect.gen(function* () {
|
|
577
590
|
const waiterId = yield* crypto.randomUUIDv4.pipe(
|
|
578
591
|
Effect.mapError((error) =>
|
|
@@ -595,17 +608,12 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
595
608
|
);
|
|
596
609
|
|
|
597
610
|
const attempt = (retry: number): Effect.Effect<void, ClientProgressFailure> =>
|
|
598
|
-
call(
|
|
611
|
+
call(threadId, "awaitProgress", encoded).pipe(
|
|
599
612
|
Effect.flatMap(
|
|
600
|
-
expect(
|
|
601
|
-
conversationId,
|
|
602
|
-
"awaitProgress",
|
|
603
|
-
ProgressObserved,
|
|
604
|
-
ClientObserveHostFailure,
|
|
605
|
-
),
|
|
613
|
+
expect(threadId, "awaitProgress", ProgressObserved, ClientObserveHostFailure),
|
|
606
614
|
),
|
|
607
615
|
Effect.asVoid,
|
|
608
|
-
Effect.catchTag("
|
|
616
|
+
Effect.catchTag("ThreadClientError", (error) =>
|
|
609
617
|
error.retryable === true && error.overloaded !== true && retry < 5
|
|
610
618
|
? Effect.sleep(Duration.millis(10 * 2 ** retry)).pipe(
|
|
611
619
|
Effect.andThen(attempt(retry + 1)),
|
|
@@ -614,19 +622,17 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
614
622
|
),
|
|
615
623
|
);
|
|
616
624
|
|
|
617
|
-
yield* attempt(0).pipe(
|
|
618
|
-
Effect.onInterrupt(() => cancelProgress(conversationId, waiterId)),
|
|
619
|
-
);
|
|
625
|
+
yield* attempt(0).pipe(Effect.onInterrupt(() => cancelProgress(threadId, waiterId)));
|
|
620
626
|
}),
|
|
621
627
|
|
|
622
628
|
readPage,
|
|
623
629
|
|
|
624
|
-
readAll: (
|
|
630
|
+
readAll: (threadId) =>
|
|
625
631
|
Effect.gen(function* () {
|
|
626
632
|
const all: Array<CanonicalRecordEnvelope> = [];
|
|
627
633
|
let after: CanonicalSequence | undefined;
|
|
628
634
|
for (;;) {
|
|
629
|
-
const page = yield* readPage(
|
|
635
|
+
const page = yield* readPage(threadId, { afterSequence: after, limit: 1_024 });
|
|
630
636
|
all.push(...page);
|
|
631
637
|
const last = page.at(-1);
|
|
632
638
|
if (page.length < 1_024 || last === undefined) return all;
|
|
@@ -634,7 +640,7 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
634
640
|
}
|
|
635
641
|
}),
|
|
636
642
|
|
|
637
|
-
abort: (
|
|
643
|
+
abort: (threadId, command) =>
|
|
638
644
|
Effect.gen(function* () {
|
|
639
645
|
const encoded = yield* encodeAbortCommand(command).pipe(
|
|
640
646
|
Effect.mapError((error) =>
|
|
@@ -643,9 +649,9 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
643
649
|
}),
|
|
644
650
|
),
|
|
645
651
|
);
|
|
646
|
-
const response = yield* call(
|
|
652
|
+
const response = yield* call(threadId, "abort", encoded);
|
|
647
653
|
const recorded = yield* expect(
|
|
648
|
-
|
|
654
|
+
threadId,
|
|
649
655
|
"abort",
|
|
650
656
|
AbortRecorded,
|
|
651
657
|
ClientAbortHostFailure,
|
|
@@ -653,7 +659,7 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
653
659
|
return recorded.intent;
|
|
654
660
|
}),
|
|
655
661
|
|
|
656
|
-
resolveApproval: (
|
|
662
|
+
resolveApproval: (threadId, command) =>
|
|
657
663
|
Effect.gen(function* () {
|
|
658
664
|
const encoded = yield* encodeApprovalDecisionCommand(command).pipe(
|
|
659
665
|
Effect.mapError((error) =>
|
|
@@ -662,9 +668,9 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
662
668
|
}),
|
|
663
669
|
),
|
|
664
670
|
);
|
|
665
|
-
const response = yield* call(
|
|
671
|
+
const response = yield* call(threadId, "resolveApproval", encoded);
|
|
666
672
|
const recorded = yield* expect(
|
|
667
|
-
|
|
673
|
+
threadId,
|
|
668
674
|
"resolveApproval",
|
|
669
675
|
ApprovalRecorded,
|
|
670
676
|
ClientApprovalHostFailure,
|
|
@@ -672,7 +678,7 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
672
678
|
return recorded.intent;
|
|
673
679
|
}),
|
|
674
680
|
|
|
675
|
-
resolveUnknown: (
|
|
681
|
+
resolveUnknown: (threadId, command) =>
|
|
676
682
|
Effect.gen(function* () {
|
|
677
683
|
const encoded = yield* encodeUnknownResolutionCommand(command).pipe(
|
|
678
684
|
Effect.mapError((error) =>
|
|
@@ -683,9 +689,9 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
683
689
|
}),
|
|
684
690
|
),
|
|
685
691
|
);
|
|
686
|
-
const response = yield* call(
|
|
692
|
+
const response = yield* call(threadId, "resolveUnknown", encoded);
|
|
687
693
|
const recorded = yield* expect(
|
|
688
|
-
|
|
694
|
+
threadId,
|
|
689
695
|
"resolveUnknown",
|
|
690
696
|
UnknownResolutionRecorded,
|
|
691
697
|
ClientUnknownHostFailure,
|
|
@@ -814,3 +814,22 @@ export const dynamicWorkerCodeExecutorLayer = (
|
|
|
814
814
|
return CodeExecutor.of({ execute: makeExecute(options, clock) });
|
|
815
815
|
}),
|
|
816
816
|
);
|
|
817
|
+
|
|
818
|
+
/** Assemble Code Mode handlers with the isolated Dynamic Worker executor. */
|
|
819
|
+
export const CloudflareCodeMode = {
|
|
820
|
+
/**
|
|
821
|
+
* Provide the selected tool handlers at construction, where Code Mode captures them.
|
|
822
|
+
* Their errors and remaining dependencies stay visible. The definition still owns the
|
|
823
|
+
* allowlist and limits; the runtime supplies the live Tool broker for each scoped pass.
|
|
824
|
+
*/
|
|
825
|
+
layer: <A, E, R, Handlers, HandlerError, HandlerRequirements>(
|
|
826
|
+
definition: { readonly handlers: Layer.Layer<A, E, R> },
|
|
827
|
+
options: DynamicWorkerCodeExecutorOptions & {
|
|
828
|
+
readonly handlers: Layer.Layer<Handlers, HandlerError, HandlerRequirements>;
|
|
829
|
+
},
|
|
830
|
+
) =>
|
|
831
|
+
definition.handlers.pipe(
|
|
832
|
+
Layer.provide(options.handlers),
|
|
833
|
+
Layer.provide(dynamicWorkerCodeExecutorLayer(options)),
|
|
834
|
+
),
|
|
835
|
+
};
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DEFAULT_OWNERSHIP_LEASE_DURATION, DeploymentId } from "@effect-agent/session";
|
|
2
1
|
import { DEFAULT_MAX_STORED_VALUE_BYTES } from "@effect-agent/storage-cloudflare";
|
|
2
|
+
import { DEFAULT_OWNERSHIP_LEASE_DURATION, DeploymentId } from "@effect-agent/thread";
|
|
3
3
|
import { Context, Duration, Schema } from "effect";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -25,7 +25,7 @@ export class CloudflarePlatformConfigError extends Schema.TaggedError<Cloudflare
|
|
|
25
25
|
* (deployment spec §8, DEPLOY-007: "Admission has explicit bounded quota and overload
|
|
26
26
|
* behavior... a typed rejection"). This is the DC analogue of `NodeDurableHost`'s
|
|
27
27
|
* `AdmissionClosed` host gate: the port surface stays untouched, the refusal happens in the
|
|
28
|
-
*
|
|
28
|
+
* Thread Object's submit entry point before `DurableAgentRuntime.submit` runs, and
|
|
29
29
|
* nothing was admitted or written.
|
|
30
30
|
*/
|
|
31
31
|
export class AdmissionLimitExceeded extends Schema.TaggedError<AdmissionLimitExceeded>()(
|
|
@@ -52,13 +52,13 @@ export const CLOUDFLARE_DATABASE_CAP_BYTES = 10_000_000_000;
|
|
|
52
52
|
export const DEFAULT_MAX_DATABASE_BYTES = 9_000_000_000;
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Explicit bounded admission quotas checked by the
|
|
55
|
+
* Explicit bounded admission quotas checked by the Thread Object BEFORE admission
|
|
56
56
|
* (exit gate "resource limits are checked before admission").
|
|
57
57
|
*/
|
|
58
58
|
export class CloudflareAdmissionLimitsValue extends Schema.Class<CloudflareAdmissionLimitsValue>(
|
|
59
59
|
"@effect-agent/platform-cloudflare/CloudflareAdmissionLimitsValue",
|
|
60
60
|
)({
|
|
61
|
-
/** Maximum nonterminal Submissions per
|
|
61
|
+
/** Maximum nonterminal Submissions per Thread lane before new admissions refuse. */
|
|
62
62
|
maxQueueDepthPerLane: Schema.Int.check(
|
|
63
63
|
Schema.isGreaterThan(0),
|
|
64
64
|
Schema.isLessThanOrEqualTo(100_000),
|
|
@@ -74,7 +74,7 @@ export class CloudflareAdmissionLimitsValue extends Schema.Class<CloudflareAdmis
|
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* Validated Cloudflare durable runtime configuration. The producer identity of one
|
|
77
|
-
*
|
|
77
|
+
* Thread Object is `{producerPrefix}:{threadId}` — stable across incarnations of
|
|
78
78
|
* the same deployment, distinct across deployments — and producer-epoch fencing (not the
|
|
79
79
|
* producer name) remains the correctness authority (DUR-006).
|
|
80
80
|
*/
|
|
@@ -82,7 +82,7 @@ export class CloudflareDurableRuntimeConfigValue extends Schema.Class<Cloudflare
|
|
|
82
82
|
"@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfigValue",
|
|
83
83
|
)({
|
|
84
84
|
deploymentId: DeploymentId,
|
|
85
|
-
/** Head of the minted producer identity `{producerPrefix}:{
|
|
85
|
+
/** Head of the minted producer identity `{producerPrefix}:{threadId}`. */
|
|
86
86
|
producerPrefix: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
|
|
87
87
|
/** Submission ownership lease duration (D5); fences work across Object incarnations. */
|
|
88
88
|
ownershipLeaseDuration: PositiveMillis,
|
|
@@ -119,7 +119,7 @@ export class CloudflareDurableRuntimeConfig extends Context.Service<
|
|
|
119
119
|
CloudflareDurableRuntimeConfigValue
|
|
120
120
|
>()("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfig") {}
|
|
121
121
|
|
|
122
|
-
/** Documented production defaults applied by `
|
|
122
|
+
/** Documented production defaults applied by `ThreadObject.layer`. */
|
|
123
123
|
export const CLOUDFLARE_RUNTIME_DEFAULTS = {
|
|
124
124
|
ownershipLeaseDuration: Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION),
|
|
125
125
|
alarmBackoffBase: 100,
|
package/src/index.ts
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* `@effect-agent/platform-cloudflare` — the Cloudflare Layer-assembly library for deployment
|
|
3
3
|
* class DC (deployment spec §12: a Layer-assembly library, not an application entrypoint).
|
|
4
4
|
*
|
|
5
|
-
* One SQLite-backed Durable Object per
|
|
6
|
-
* `
|
|
7
|
-
* `
|
|
8
|
-
* adapters and the WP2 cross-Object routing, `DurableAlarmService`/`
|
|
5
|
+
* One SQLite-backed Durable Object per Thread is the serialized owner (durability §6):
|
|
6
|
+
* `ThreadObject.make` builds the class applications export from their Worker,
|
|
7
|
+
* `ThreadObject.layer` assembles the coordinator over the storage-cloudflare
|
|
8
|
+
* adapters and the WP2 cross-Object routing, `DurableAlarmService`/`ThreadMaintenance`
|
|
9
9
|
* multiplex every cadence into the Object's single alarm slot (dirty or autonomously
|
|
10
10
|
* actionable work retains a committed alarm; stable external waits quiesce until their next
|
|
11
11
|
* durably pre-armed mutation), and
|
|
12
|
-
* `
|
|
12
|
+
* `CloudflareThreadClient` is the Worker-side ingress. Platform bindings enter ONLY
|
|
13
13
|
* through the `bindings.ts` Layers (DEPLOY-010). This is the only workspace package allowed
|
|
14
14
|
* to import the `cloudflare:workers` runtime module.
|
|
15
15
|
*/
|
|
@@ -19,12 +19,11 @@ export * from "./alarm.ts";
|
|
|
19
19
|
export * from "./wake-scheduler.ts";
|
|
20
20
|
export * from "./progress-wait.ts";
|
|
21
21
|
export * from "./transport.ts";
|
|
22
|
-
export
|
|
23
|
-
export * from "./
|
|
22
|
+
export { ThreadObjectPorts } from "./layers.ts";
|
|
23
|
+
export * as ThreadObject from "./thread-object.ts";
|
|
24
24
|
export * from "./client.ts";
|
|
25
|
+
export * from "./prepared-admission.ts";
|
|
25
26
|
export * from "./scheduling.ts";
|
|
27
|
+
export * from "./subscriptions.ts";
|
|
26
28
|
export * from "./code-mode-executor.ts";
|
|
27
|
-
export
|
|
28
|
-
export * from "./browser-rest-capture.ts";
|
|
29
|
-
export * from "./interactive-browser.ts";
|
|
30
|
-
export * from "./browser-rest-crawl.ts";
|
|
29
|
+
export { CloudflareBrowser, type CloudflareBrowserOptions } from "./browser-quick-action.ts";
|
|
@@ -44,7 +44,10 @@ import {
|
|
|
44
44
|
type Scope,
|
|
45
45
|
} from "effect";
|
|
46
46
|
|
|
47
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
BrowserRunSessionLifecycle,
|
|
49
|
+
type BrowserRunLifecycleOptions,
|
|
50
|
+
} from "./browser-session-lifecycle.ts";
|
|
48
51
|
export { BrowserRunCleanupError, BrowserRunSessionLifecycle } from "./browser-session-lifecycle.ts";
|
|
49
52
|
|
|
50
53
|
export const browserRunInteractiveImplementation = SandboxImplementation.make({
|
|
@@ -2145,3 +2148,28 @@ export const browserRunInteractiveLayer = (): Layer.Layer<
|
|
|
2145
2148
|
});
|
|
2146
2149
|
}),
|
|
2147
2150
|
);
|
|
2151
|
+
|
|
2152
|
+
/** Resolved Worker binding, cleanup credentials, and optional initial viewport. */
|
|
2153
|
+
export interface CloudflareInteractiveBrowserOptions extends BrowserRunLifecycleOptions {
|
|
2154
|
+
readonly browser: BrowserRun;
|
|
2155
|
+
readonly viewport?: BrowserRunViewport;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
const interactiveBindingLayer = (options: CloudflareInteractiveBrowserOptions) =>
|
|
2159
|
+
BrowserRunInteractiveBinding.layer(options).pipe(
|
|
2160
|
+
Layer.provide(BrowserRunSessionLifecycle.layer(options)),
|
|
2161
|
+
);
|
|
2162
|
+
|
|
2163
|
+
/** Worker-only assembly; importing ordinary capture never loads Puppeteer. */
|
|
2164
|
+
export const CloudflareInteractiveBrowser = {
|
|
2165
|
+
/**
|
|
2166
|
+
* Assemble binding, confirmed-session cleanup, and the generic browser service.
|
|
2167
|
+
* HttpClient and construction errors remain visible. Opening a pass still requires
|
|
2168
|
+
* an explicit policy and Scope; no browser is launched during Layer construction.
|
|
2169
|
+
*/
|
|
2170
|
+
layer: (options: CloudflareInteractiveBrowserOptions) =>
|
|
2171
|
+
browserRunInteractiveLayer().pipe(Layer.provide(interactiveBindingLayer(options))),
|
|
2172
|
+
/** Opt into private host controls instead of exposing them through the generic service. */
|
|
2173
|
+
hostLayer: (options: CloudflareInteractiveBrowserOptions) =>
|
|
2174
|
+
browserRunInteractiveHostLayer().pipe(Layer.provide(interactiveBindingLayer(options))),
|
|
2175
|
+
};
|