@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/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,17 @@ import {
|
|
|
29
29
|
UnknownResolutionIntent,
|
|
30
30
|
type DurableSubmitAgent,
|
|
31
31
|
type DurableSubmitOptions,
|
|
32
|
-
} from "@effect-agent/
|
|
32
|
+
} from "@effect-agent/thread";
|
|
33
33
|
import { Context, Crypto, Duration, Effect, Layer, Schema } from "effect";
|
|
34
34
|
import { RpcTracing } from "effect-cf";
|
|
35
35
|
|
|
36
36
|
import { DurableAlarmError } from "./alarm.ts";
|
|
37
|
-
import {
|
|
37
|
+
import { ThreadObjectNamespace, type ThreadObjectRpc } from "./bindings.ts";
|
|
38
38
|
import { cloudflareFailureSignals, safeCauseMessage } from "./boundary.ts";
|
|
39
39
|
import { AdmissionLimitExceeded } from "./config.ts";
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* The Worker↔
|
|
42
|
+
* The Worker↔Thread-Object host protocol (plan §1.4): Schema envelopes for the host
|
|
43
43
|
* entry points (`submitEncoded`, `awaitSettlementEncoded`, `observePage`, `abortEncoded`,
|
|
44
44
|
* `resolveApprovalEncoded`, `resolveUnknownEncoded`) plus the Worker-side client that speaks
|
|
45
45
|
* it. Mirrors the WP2 port protocol: requests and responses are closed Schema unions, typed
|
|
@@ -70,10 +70,10 @@ export class HostProtocolError extends Schema.TaggedError<HostProtocolError>()(
|
|
|
70
70
|
) {}
|
|
71
71
|
|
|
72
72
|
/** The Worker-side stub call itself failed (RPC rejection, overload, eviction mid-call). */
|
|
73
|
-
export class
|
|
74
|
-
"
|
|
73
|
+
export class ThreadClientError extends Schema.TaggedError<ThreadClientError>()(
|
|
74
|
+
"ThreadClientError",
|
|
75
75
|
{
|
|
76
|
-
|
|
76
|
+
threadId: Schema.String,
|
|
77
77
|
message: Schema.String,
|
|
78
78
|
cause: Schema.optionalKey(Schema.Defect()),
|
|
79
79
|
/** Cloudflare's own classification for a failure safe to retry with a fresh stub. */
|
|
@@ -91,7 +91,7 @@ export class ConversationClientError extends Schema.TaggedError<ConversationClie
|
|
|
91
91
|
* One durable submission, input ALREADY encoded by the caller through the Agent Binding's
|
|
92
92
|
* input schema (the Worker bundles the same Agent definitions as the Object, so schema
|
|
93
93
|
* validation happens client-side; the resolved Binding re-validates at claim time). The
|
|
94
|
-
*
|
|
94
|
+
* Thread identity is deliberately absent — the addressed Object IS the lane.
|
|
95
95
|
*/
|
|
96
96
|
export class SubmitRequest extends Schema.Class<SubmitRequest>(
|
|
97
97
|
"@effect-agent/platform-cloudflare/SubmitRequest",
|
|
@@ -145,8 +145,8 @@ export const HostFailure = Schema.Union([
|
|
|
145
145
|
UnknownResolutionConflict,
|
|
146
146
|
JoinedToHost,
|
|
147
147
|
LedgerError,
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
ThreadStoreError,
|
|
149
|
+
ThreadNotMaterialized,
|
|
150
150
|
AppendConflict,
|
|
151
151
|
FenceRejected,
|
|
152
152
|
DurableRuntimeFailpointError,
|
|
@@ -250,14 +250,14 @@ export const decodeHostResponse = Schema.decodeUnknownEffect(HostResponse);
|
|
|
250
250
|
// Worker-side client
|
|
251
251
|
// ---------------------------------------------------------------------------
|
|
252
252
|
|
|
253
|
-
/** Failure surface of `
|
|
253
|
+
/** Failure surface of `CloudflareThreadClient.submit`. */
|
|
254
254
|
const ClientSubmitHostFailure = Schema.Union([
|
|
255
255
|
AgentInputError,
|
|
256
256
|
DigestError,
|
|
257
257
|
AdmissionConflict,
|
|
258
258
|
LedgerError,
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
ThreadStoreError,
|
|
260
|
+
ThreadNotMaterialized,
|
|
261
261
|
AppendConflict,
|
|
262
262
|
FenceRejected,
|
|
263
263
|
DurableRuntimeFailpointError,
|
|
@@ -265,14 +265,20 @@ const ClientSubmitHostFailure = Schema.Union([
|
|
|
265
265
|
DurableAlarmError,
|
|
266
266
|
HostProtocolError,
|
|
267
267
|
]);
|
|
268
|
-
const ClientAwaitHostFailure = Schema.Union([
|
|
268
|
+
const ClientAwaitHostFailure = Schema.Union([
|
|
269
|
+
LedgerError,
|
|
270
|
+
SettlementConflict,
|
|
271
|
+
OperationDenied,
|
|
272
|
+
HostProtocolError,
|
|
273
|
+
]);
|
|
269
274
|
const ClientObserveHostFailure = Schema.Union([
|
|
270
|
-
|
|
271
|
-
|
|
275
|
+
ThreadStoreError,
|
|
276
|
+
ThreadNotMaterialized,
|
|
272
277
|
OperationDenied,
|
|
273
278
|
HostProtocolError,
|
|
274
279
|
]);
|
|
275
280
|
const ClientAbortHostFailure = Schema.Union([
|
|
281
|
+
OperationDenied,
|
|
276
282
|
LedgerError,
|
|
277
283
|
SettlementConflict,
|
|
278
284
|
JoinedToHost,
|
|
@@ -299,23 +305,19 @@ const ClientUnknownHostFailure = Schema.Union([
|
|
|
299
305
|
HostProtocolError,
|
|
300
306
|
]);
|
|
301
307
|
|
|
302
|
-
export type ClientSubmitFailure = typeof ClientSubmitHostFailure.Type |
|
|
303
|
-
export type ClientAwaitFailure = typeof ClientAwaitHostFailure.Type |
|
|
304
|
-
export type ClientObserveFailure = typeof ClientObserveHostFailure.Type |
|
|
308
|
+
export type ClientSubmitFailure = typeof ClientSubmitHostFailure.Type | ThreadClientError;
|
|
309
|
+
export type ClientAwaitFailure = typeof ClientAwaitHostFailure.Type | ThreadClientError;
|
|
310
|
+
export type ClientObserveFailure = typeof ClientObserveHostFailure.Type | ThreadClientError;
|
|
305
311
|
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,
|
|
312
|
+
export type ClientAbortFailure = typeof ClientAbortHostFailure.Type | ThreadClientError;
|
|
313
|
+
export type ClientApprovalFailure = typeof ClientApprovalHostFailure.Type | ThreadClientError;
|
|
314
|
+
export type ClientUnknownFailure = typeof ClientUnknownHostFailure.Type | ThreadClientError;
|
|
315
|
+
|
|
316
|
+
const outOfContract = (threadId: string, operation: string, observed: string): ThreadClientError =>
|
|
317
|
+
ThreadClientError.make({
|
|
318
|
+
threadId,
|
|
317
319
|
message: boundHostDiagnostic(
|
|
318
|
-
`The
|
|
320
|
+
`The Thread Object answered ${operation} with the out-of-contract ${observed}.`,
|
|
319
321
|
),
|
|
320
322
|
});
|
|
321
323
|
|
|
@@ -328,11 +330,11 @@ const hostRpcMethods = {
|
|
|
328
330
|
abort: "abortEncoded",
|
|
329
331
|
resolveApproval: "resolveApprovalEncoded",
|
|
330
332
|
resolveUnknown: "resolveUnknownEncoded",
|
|
331
|
-
} as const satisfies Record<string, keyof
|
|
333
|
+
} as const satisfies Record<string, keyof ThreadObjectRpc>;
|
|
332
334
|
|
|
333
|
-
/** Worker-side client over the
|
|
334
|
-
export class
|
|
335
|
-
|
|
335
|
+
/** Worker-side client over the Thread Object namespace (DEPLOY-010). */
|
|
336
|
+
export class CloudflareThreadClient extends Context.Service<
|
|
337
|
+
CloudflareThreadClient,
|
|
336
338
|
{
|
|
337
339
|
/** Encode the input client-side, then durably submit to the owning Object. */
|
|
338
340
|
readonly submit: <InputSchema extends Schema.Top>(
|
|
@@ -347,12 +349,12 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
347
349
|
* The result is deliberately void: canonical records remain authoritative and must be read.
|
|
348
350
|
*/
|
|
349
351
|
readonly awaitProgress: (
|
|
350
|
-
|
|
352
|
+
threadId: ThreadId,
|
|
351
353
|
afterSequence: CanonicalSequence,
|
|
352
354
|
) => Effect.Effect<void, ClientProgressFailure>;
|
|
353
355
|
/** One bounded page of canonical records. */
|
|
354
356
|
readonly readPage: (
|
|
355
|
-
|
|
357
|
+
threadId: ThreadId,
|
|
356
358
|
options?: {
|
|
357
359
|
readonly afterSequence?: CanonicalSequence | undefined;
|
|
358
360
|
readonly limit?: number | undefined;
|
|
@@ -364,55 +366,55 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
364
366
|
* `awaitSettlement`.
|
|
365
367
|
*/
|
|
366
368
|
readonly readAll: (
|
|
367
|
-
|
|
369
|
+
threadId: ThreadId,
|
|
368
370
|
) => Effect.Effect<ReadonlyArray<CanonicalRecordEnvelope>, ClientObserveFailure>;
|
|
369
371
|
/**
|
|
370
|
-
* Submission-addressed operations take the owning
|
|
372
|
+
* Submission-addressed operations take the owning Thread explicitly (from the
|
|
371
373
|
* Receipt): minted Submission identities stay OPAQUE outside the storage adapter that
|
|
372
374
|
* minted them (D-P6-5), so the client never parses one to find the lane.
|
|
373
375
|
*/
|
|
374
376
|
readonly abort: (
|
|
375
|
-
|
|
377
|
+
threadId: ThreadId,
|
|
376
378
|
command: AbortCommand,
|
|
377
379
|
) => Effect.Effect<AbortIntent, ClientAbortFailure>;
|
|
378
380
|
readonly resolveApproval: (
|
|
379
|
-
|
|
381
|
+
threadId: ThreadId,
|
|
380
382
|
command: ApprovalDecisionCommand,
|
|
381
383
|
) => Effect.Effect<ApprovalDecisionIntent, ClientApprovalFailure>;
|
|
382
384
|
readonly resolveUnknown: (
|
|
383
|
-
|
|
385
|
+
threadId: ThreadId,
|
|
384
386
|
command: UnknownResolutionCommand,
|
|
385
387
|
) => Effect.Effect<UnknownResolutionIntent, ClientUnknownFailure>;
|
|
386
388
|
}
|
|
387
|
-
>()("@effect-agent/platform-cloudflare/
|
|
389
|
+
>()("@effect-agent/platform-cloudflare/CloudflareThreadClient") {
|
|
388
390
|
static readonly layer: Layer.Layer<
|
|
389
|
-
|
|
391
|
+
CloudflareThreadClient,
|
|
390
392
|
never,
|
|
391
|
-
|
|
392
|
-
> = Layer.effect(
|
|
393
|
+
ThreadObjectNamespace | Crypto.Crypto
|
|
394
|
+
> = Layer.effect(CloudflareThreadClient)(
|
|
393
395
|
Effect.gen(function* () {
|
|
394
|
-
const { namespace, rpcTracing } = yield*
|
|
396
|
+
const { namespace, rpcTracing } = yield* ThreadObjectNamespace;
|
|
395
397
|
const crypto = yield* Crypto.Crypto;
|
|
396
398
|
|
|
397
399
|
const call = Effect.fn(
|
|
398
400
|
function* (
|
|
399
|
-
|
|
401
|
+
threadId: string,
|
|
400
402
|
operation: keyof typeof hostRpcMethods,
|
|
401
403
|
encoded: unknown,
|
|
402
|
-
): Effect.fn.Return<HostResponse,
|
|
404
|
+
): Effect.fn.Return<HostResponse, ThreadClientError | HostProtocolError> {
|
|
403
405
|
// Empty arguments preserve native arity. Passing `undefined` still adds an argument.
|
|
404
406
|
const traceArgs =
|
|
405
407
|
rpcTracing === undefined ? [] : yield* RpcTracing.withRpcTraceContext([]);
|
|
406
408
|
const raw = yield* Effect.tryPromise({
|
|
407
409
|
try: () => {
|
|
408
|
-
const stub = namespace.get(namespace.idFromName(
|
|
410
|
+
const stub = namespace.get(namespace.idFromName(threadId));
|
|
409
411
|
return stub[hostRpcMethods[operation]](encoded, ...traceArgs);
|
|
410
412
|
},
|
|
411
413
|
catch: (cause) =>
|
|
412
|
-
|
|
413
|
-
|
|
414
|
+
ThreadClientError.make({
|
|
415
|
+
threadId,
|
|
414
416
|
message: boundHostDiagnostic(
|
|
415
|
-
`${operation} did not reach the
|
|
417
|
+
`${operation} did not reach the Thread Object: ${safeCauseMessage(
|
|
416
418
|
cause,
|
|
417
419
|
"the RPC failed without a diagnostic",
|
|
418
420
|
)}`,
|
|
@@ -432,16 +434,16 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
432
434
|
),
|
|
433
435
|
);
|
|
434
436
|
},
|
|
435
|
-
(effect,
|
|
437
|
+
(effect, threadId, operation) =>
|
|
436
438
|
rpcTracing === undefined
|
|
437
|
-
? Effect.withSpan(effect, "
|
|
438
|
-
attributes: {
|
|
439
|
+
? Effect.withSpan(effect, "CloudflareThreadClient.call", {
|
|
440
|
+
attributes: { threadId, operation },
|
|
439
441
|
})
|
|
440
442
|
: RpcTracing.withRpcClientSpan(effect, rpcTracing, hostRpcMethods[operation]),
|
|
441
443
|
);
|
|
442
444
|
|
|
443
445
|
const expect = <ResultSchema extends Schema.Top, FailureSchema extends Schema.Top>(
|
|
444
|
-
|
|
446
|
+
threadId: string,
|
|
445
447
|
operation: string,
|
|
446
448
|
resultSchema: ResultSchema,
|
|
447
449
|
failureSchema: FailureSchema,
|
|
@@ -450,22 +452,22 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
450
452
|
const isExpectedFailure = Schema.is(failureSchema);
|
|
451
453
|
return (
|
|
452
454
|
response: HostResponse,
|
|
453
|
-
): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] |
|
|
455
|
+
): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | ThreadClientError> => {
|
|
454
456
|
if (response._tag === "HostFailed") {
|
|
455
457
|
const failure = response.failure;
|
|
456
458
|
return isExpectedFailure(failure)
|
|
457
459
|
? Effect.fail(failure)
|
|
458
|
-
: Effect.fail(outOfContract(
|
|
460
|
+
: Effect.fail(outOfContract(threadId, operation, `failure ${failure._tag}`));
|
|
459
461
|
}
|
|
460
462
|
if (!isExpectedResult(response)) {
|
|
461
|
-
return Effect.fail(outOfContract(
|
|
463
|
+
return Effect.fail(outOfContract(threadId, operation, `result ${response._tag}`));
|
|
462
464
|
}
|
|
463
465
|
return Effect.succeed(response);
|
|
464
466
|
};
|
|
465
467
|
};
|
|
466
468
|
|
|
467
469
|
const readPage = (
|
|
468
|
-
|
|
470
|
+
threadId: ThreadId,
|
|
469
471
|
options?: {
|
|
470
472
|
readonly afterSequence?: CanonicalSequence | undefined;
|
|
471
473
|
readonly limit?: number | undefined;
|
|
@@ -485,9 +487,9 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
485
487
|
}),
|
|
486
488
|
),
|
|
487
489
|
);
|
|
488
|
-
const response = yield* call(
|
|
490
|
+
const response = yield* call(threadId, "observePage", encoded);
|
|
489
491
|
const page = yield* expect(
|
|
490
|
-
|
|
492
|
+
threadId,
|
|
491
493
|
"observePage",
|
|
492
494
|
ObservedPage,
|
|
493
495
|
ClientObserveHostFailure,
|
|
@@ -495,18 +497,15 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
495
497
|
return page.records;
|
|
496
498
|
});
|
|
497
499
|
|
|
498
|
-
const cancelProgress = (
|
|
499
|
-
conversationId: ConversationId,
|
|
500
|
-
waiterId: string,
|
|
501
|
-
): Effect.Effect<void> =>
|
|
500
|
+
const cancelProgress = (threadId: ThreadId, waiterId: string): Effect.Effect<void> =>
|
|
502
501
|
encodeCancelProgressRequest(CancelProgressRequest.make({ waiterId })).pipe(
|
|
503
502
|
Effect.mapError(() => undefined),
|
|
504
|
-
Effect.flatMap((encoded) => call(
|
|
503
|
+
Effect.flatMap((encoded) => call(threadId, "cancelProgress", encoded)),
|
|
505
504
|
Effect.asVoid,
|
|
506
505
|
Effect.ignore,
|
|
507
506
|
);
|
|
508
507
|
|
|
509
|
-
return
|
|
508
|
+
return CloudflareThreadClient.of({
|
|
510
509
|
submit: <InputSchema extends Schema.Top>(
|
|
511
510
|
agent: DurableSubmitAgent<InputSchema>,
|
|
512
511
|
input: InputSchema["Type"],
|
|
@@ -543,9 +542,9 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
543
542
|
}),
|
|
544
543
|
),
|
|
545
544
|
);
|
|
546
|
-
const response = yield* call(options.
|
|
545
|
+
const response = yield* call(options.threadId, "submit", encoded);
|
|
547
546
|
const succeeded = yield* expect(
|
|
548
|
-
options.
|
|
547
|
+
options.threadId,
|
|
549
548
|
"submit",
|
|
550
549
|
SubmitSucceeded,
|
|
551
550
|
ClientSubmitHostFailure,
|
|
@@ -562,9 +561,9 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
562
561
|
}),
|
|
563
562
|
),
|
|
564
563
|
);
|
|
565
|
-
const response = yield* call(receipt.
|
|
564
|
+
const response = yield* call(receipt.threadId, "awaitSettlement", encoded);
|
|
566
565
|
const settled = yield* expect(
|
|
567
|
-
receipt.
|
|
566
|
+
receipt.threadId,
|
|
568
567
|
"awaitSettlement",
|
|
569
568
|
SettlementReached,
|
|
570
569
|
ClientAwaitHostFailure,
|
|
@@ -572,7 +571,7 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
572
571
|
return settled.settlement;
|
|
573
572
|
}),
|
|
574
573
|
|
|
575
|
-
awaitProgress: (
|
|
574
|
+
awaitProgress: (threadId, afterSequence) =>
|
|
576
575
|
Effect.gen(function* () {
|
|
577
576
|
const waiterId = yield* crypto.randomUUIDv4.pipe(
|
|
578
577
|
Effect.mapError((error) =>
|
|
@@ -595,17 +594,12 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
595
594
|
);
|
|
596
595
|
|
|
597
596
|
const attempt = (retry: number): Effect.Effect<void, ClientProgressFailure> =>
|
|
598
|
-
call(
|
|
597
|
+
call(threadId, "awaitProgress", encoded).pipe(
|
|
599
598
|
Effect.flatMap(
|
|
600
|
-
expect(
|
|
601
|
-
conversationId,
|
|
602
|
-
"awaitProgress",
|
|
603
|
-
ProgressObserved,
|
|
604
|
-
ClientObserveHostFailure,
|
|
605
|
-
),
|
|
599
|
+
expect(threadId, "awaitProgress", ProgressObserved, ClientObserveHostFailure),
|
|
606
600
|
),
|
|
607
601
|
Effect.asVoid,
|
|
608
|
-
Effect.catchTag("
|
|
602
|
+
Effect.catchTag("ThreadClientError", (error) =>
|
|
609
603
|
error.retryable === true && error.overloaded !== true && retry < 5
|
|
610
604
|
? Effect.sleep(Duration.millis(10 * 2 ** retry)).pipe(
|
|
611
605
|
Effect.andThen(attempt(retry + 1)),
|
|
@@ -614,19 +608,17 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
614
608
|
),
|
|
615
609
|
);
|
|
616
610
|
|
|
617
|
-
yield* attempt(0).pipe(
|
|
618
|
-
Effect.onInterrupt(() => cancelProgress(conversationId, waiterId)),
|
|
619
|
-
);
|
|
611
|
+
yield* attempt(0).pipe(Effect.onInterrupt(() => cancelProgress(threadId, waiterId)));
|
|
620
612
|
}),
|
|
621
613
|
|
|
622
614
|
readPage,
|
|
623
615
|
|
|
624
|
-
readAll: (
|
|
616
|
+
readAll: (threadId) =>
|
|
625
617
|
Effect.gen(function* () {
|
|
626
618
|
const all: Array<CanonicalRecordEnvelope> = [];
|
|
627
619
|
let after: CanonicalSequence | undefined;
|
|
628
620
|
for (;;) {
|
|
629
|
-
const page = yield* readPage(
|
|
621
|
+
const page = yield* readPage(threadId, { afterSequence: after, limit: 1_024 });
|
|
630
622
|
all.push(...page);
|
|
631
623
|
const last = page.at(-1);
|
|
632
624
|
if (page.length < 1_024 || last === undefined) return all;
|
|
@@ -634,7 +626,7 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
634
626
|
}
|
|
635
627
|
}),
|
|
636
628
|
|
|
637
|
-
abort: (
|
|
629
|
+
abort: (threadId, command) =>
|
|
638
630
|
Effect.gen(function* () {
|
|
639
631
|
const encoded = yield* encodeAbortCommand(command).pipe(
|
|
640
632
|
Effect.mapError((error) =>
|
|
@@ -643,9 +635,9 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
643
635
|
}),
|
|
644
636
|
),
|
|
645
637
|
);
|
|
646
|
-
const response = yield* call(
|
|
638
|
+
const response = yield* call(threadId, "abort", encoded);
|
|
647
639
|
const recorded = yield* expect(
|
|
648
|
-
|
|
640
|
+
threadId,
|
|
649
641
|
"abort",
|
|
650
642
|
AbortRecorded,
|
|
651
643
|
ClientAbortHostFailure,
|
|
@@ -653,7 +645,7 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
653
645
|
return recorded.intent;
|
|
654
646
|
}),
|
|
655
647
|
|
|
656
|
-
resolveApproval: (
|
|
648
|
+
resolveApproval: (threadId, command) =>
|
|
657
649
|
Effect.gen(function* () {
|
|
658
650
|
const encoded = yield* encodeApprovalDecisionCommand(command).pipe(
|
|
659
651
|
Effect.mapError((error) =>
|
|
@@ -662,9 +654,9 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
662
654
|
}),
|
|
663
655
|
),
|
|
664
656
|
);
|
|
665
|
-
const response = yield* call(
|
|
657
|
+
const response = yield* call(threadId, "resolveApproval", encoded);
|
|
666
658
|
const recorded = yield* expect(
|
|
667
|
-
|
|
659
|
+
threadId,
|
|
668
660
|
"resolveApproval",
|
|
669
661
|
ApprovalRecorded,
|
|
670
662
|
ClientApprovalHostFailure,
|
|
@@ -672,7 +664,7 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
672
664
|
return recorded.intent;
|
|
673
665
|
}),
|
|
674
666
|
|
|
675
|
-
resolveUnknown: (
|
|
667
|
+
resolveUnknown: (threadId, command) =>
|
|
676
668
|
Effect.gen(function* () {
|
|
677
669
|
const encoded = yield* encodeUnknownResolutionCommand(command).pipe(
|
|
678
670
|
Effect.mapError((error) =>
|
|
@@ -683,9 +675,9 @@ export class CloudflareConversationClient extends Context.Service<
|
|
|
683
675
|
}),
|
|
684
676
|
),
|
|
685
677
|
);
|
|
686
|
-
const response = yield* call(
|
|
678
|
+
const response = yield* call(threadId, "resolveUnknown", encoded);
|
|
687
679
|
const recorded = yield* expect(
|
|
688
|
-
|
|
680
|
+
threadId,
|
|
689
681
|
"resolveUnknown",
|
|
690
682
|
UnknownResolutionRecorded,
|
|
691
683
|
ClientUnknownHostFailure,
|
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,10 @@ 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 * from "./browser-quick-action.ts";
|
|
28
|
-
export * from "./browser-rest-capture.ts";
|
|
29
|
-
export * from "./interactive-browser.ts";
|
|
30
|
-
export * from "./browser-rest-crawl.ts";
|