@yielded/agent-platform-cloudflare 0.1.0-beta.167 → 0.1.0-beta.169
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-B0ODr1NE.mjs → Alarm-BMEAZo3p.mjs} +261 -159
- package/dist/Alarm-BMEAZo3p.mjs.map +1 -0
- package/dist/Alarm.d.mts +29 -36
- package/dist/Alarm.mjs +2 -2
- package/dist/BrowserCredentials-C5IWfi7f.d.mts +91 -0
- package/dist/BrowserCredentials.d.mts +2 -91
- package/dist/BrowserRestCapture.mjs +3 -3
- package/dist/BrowserRestCapture.mjs.map +1 -1
- package/dist/BrowserRestCrawl.mjs +6 -6
- package/dist/BrowserRestCrawl.mjs.map +1 -1
- package/dist/{BrowserSession-RfIzxGCH.mjs → BrowserSession-BfNmyayH.mjs} +5 -5
- package/dist/BrowserSession-BfNmyayH.mjs.map +1 -0
- package/dist/{BrowserSession-B969Xg-2.d.mts → BrowserSession-Bx0V1-6_.d.mts} +6 -3
- package/dist/BrowserSession.d.mts +1 -1
- package/dist/BrowserSession.mjs +2 -2
- package/dist/BrowserUse-Bc2JdLW2.mjs +1528 -0
- package/dist/BrowserUse-Bc2JdLW2.mjs.map +1 -0
- package/dist/BrowserUse.d.mts +83 -0
- package/dist/BrowserUse.mjs +2 -0
- package/dist/CloudflareBindings.d.mts +8 -1
- package/dist/CloudflareBindings.mjs +2 -2
- package/dist/CloudflareBindings.mjs.map +1 -1
- package/dist/{CloudflareBrowser-5an6m-Qb.mjs → CloudflareBrowser-Vt8qTdYW.mjs} +11 -13
- package/dist/CloudflareBrowser-Vt8qTdYW.mjs.map +1 -0
- package/dist/CloudflareBrowser.mjs +1 -1
- package/dist/CloudflareCodeMode.mjs +1 -1
- package/dist/CloudflareCodeMode.mjs.map +1 -1
- package/dist/{CloudflareConfig-qDrRLofL.d.mts → CloudflareConfig-C9MvPbX8.d.mts} +9 -11
- package/dist/CloudflareConfig.d.mts +1 -1
- package/dist/CloudflareConfig.mjs +9 -12
- package/dist/CloudflareConfig.mjs.map +1 -1
- package/dist/CloudflareMemory.d.mts +3 -25
- package/dist/CloudflareMemory.mjs +33 -31
- package/dist/CloudflareMemory.mjs.map +1 -1
- package/dist/CloudflareScheduling.mjs +8 -15
- package/dist/CloudflareScheduling.mjs.map +1 -1
- package/dist/CloudflareSubscriptions.mjs +9 -16
- package/dist/CloudflareSubscriptions.mjs.map +1 -1
- package/dist/CloudflareThreadClient-CLZpN9zU.mjs +588 -0
- package/dist/CloudflareThreadClient-CLZpN9zU.mjs.map +1 -0
- package/dist/CloudflareThreadClient-QsVzLGGZ.d.mts +2033 -0
- package/dist/CloudflareThreadClient.d.mts +2 -1796
- package/dist/CloudflareThreadClient.mjs +2 -382
- package/dist/{InteractiveBrowser-BbG3kGA9.mjs → InteractiveBrowser-LdKzQeLR.mjs} +18 -19
- package/dist/InteractiveBrowser-LdKzQeLR.mjs.map +1 -0
- package/dist/InteractiveBrowser.mjs +1 -1
- package/dist/{ThreadObject-BcfXbzSO.d.mts → ThreadObject-7s5XnlH-.d.mts} +103 -79
- package/dist/{ThreadObject-DHjrnlX4.mjs → ThreadObject-CgXZMlr4.mjs} +265 -66
- package/dist/ThreadObject-CgXZMlr4.mjs.map +1 -0
- package/dist/ThreadObject.d.mts +2 -2
- package/dist/ThreadObject.mjs +2 -3
- package/dist/WakeScheduler.d.mts +6 -5
- package/dist/WakeScheduler.mjs +15 -11
- package/dist/WakeScheduler.mjs.map +1 -1
- package/dist/index.d.mts +7 -6
- package/dist/index.mjs +7 -6
- package/dist/page-function-CmUCcyNP.mjs +11 -0
- package/dist/page-function-CmUCcyNP.mjs.map +1 -0
- package/dist/{prepared-admission-DMX5p9dC.mjs → prepared-admission-B61A7y6O.mjs} +3 -3
- package/dist/{prepared-admission-DMX5p9dC.mjs.map → prepared-admission-B61A7y6O.mjs.map} +1 -1
- package/package.json +1 -1
- package/src/Alarm.ts +512 -279
- package/src/BrowserRestCapture.ts +2 -2
- package/src/BrowserRestCrawl.ts +10 -14
- package/src/BrowserSession.ts +10 -2
- package/src/BrowserUse.ts +1591 -0
- package/src/CloudflareBindings.ts +13 -4
- package/src/CloudflareCodeMode.ts +1 -1
- package/src/CloudflareConfig.ts +9 -12
- package/src/CloudflareMemory.ts +21 -28
- package/src/CloudflareScheduling.ts +7 -15
- package/src/CloudflareSubscriptions.ts +11 -20
- package/src/CloudflareThreadClient.ts +289 -9
- package/src/InteractiveBrowser.ts +325 -323
- package/src/ThreadObject.ts +172 -52
- package/src/WakeScheduler.ts +29 -18
- package/src/index.ts +1 -0
- package/src/internal/browser-dom.ts +886 -0
- package/src/internal/browser-file-selection.ts +8 -1
- package/src/internal/browser-jev-dom.ts +362 -0
- package/src/internal/browser-quick-action.ts +7 -8
- package/src/internal/browser-session-lifecycle.ts +2 -5
- package/src/internal/crypto.ts +41 -0
- package/src/internal/due-queue.ts +5 -69
- package/src/internal/layers.ts +100 -17
- package/src/internal/live-text-protocol.ts +51 -0
- package/src/internal/live-text.ts +256 -0
- package/src/internal/page-function.ts +12 -0
- package/src/internal/prepared-admission.ts +1 -1
- package/src/internal/progress-wait.ts +19 -24
- package/src/internal/transport.ts +1 -1
- package/dist/Alarm-B0ODr1NE.mjs.map +0 -1
- package/dist/BrowserSession-RfIzxGCH.mjs.map +0 -1
- package/dist/CloudflareBrowser-5an6m-Qb.mjs.map +0 -1
- package/dist/CloudflareThreadClient.mjs.map +0 -1
- package/dist/InteractiveBrowser-BbG3kGA9.mjs.map +0 -1
- package/dist/ThreadObject-DHjrnlX4.mjs.map +0 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { BrowserCrypto } from "@effect/platform-browser";
|
|
2
1
|
import { AgentInputError } from "@yielded/agent/agent-error";
|
|
3
2
|
import { DigestError } from "@yielded/agent/digest";
|
|
4
3
|
import {
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
CanonicalSequence,
|
|
16
15
|
DefinitionDigests,
|
|
17
16
|
PersistedJson,
|
|
17
|
+
SubmissionSettledRecord,
|
|
18
18
|
WorkerAdmission,
|
|
19
19
|
} from "@yielded/agent/records";
|
|
20
20
|
import {
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
IdempotencyKey,
|
|
31
31
|
JoinedToHost,
|
|
32
32
|
LedgerError,
|
|
33
|
+
OwnershipLost,
|
|
33
34
|
Principal,
|
|
34
35
|
Settlement,
|
|
35
36
|
SettlementConflict,
|
|
@@ -42,10 +43,9 @@ import {
|
|
|
42
43
|
AppendConflict,
|
|
43
44
|
ThreadNotMaterialized,
|
|
44
45
|
ThreadStoreError,
|
|
45
|
-
FenceRejected,
|
|
46
46
|
} from "@yielded/agent/thread-store";
|
|
47
|
-
import { Context, Crypto, Duration, Effect, Layer, Schema } from "effect";
|
|
48
|
-
import { RpcTracing } from "effect-cf";
|
|
47
|
+
import { Context, Crypto, Duration, Effect, Layer, Predicate, Schema, Stream } from "effect";
|
|
48
|
+
import { RpcTargets, RpcTracing } from "effect-cf";
|
|
49
49
|
|
|
50
50
|
import { DurableAlarmError } from "./Alarm.ts";
|
|
51
51
|
import {
|
|
@@ -55,6 +55,16 @@ import {
|
|
|
55
55
|
} from "./CloudflareBindings.ts";
|
|
56
56
|
import { AdmissionLimitExceeded } from "./CloudflareConfig.ts";
|
|
57
57
|
import { cloudflareFailureSignals, safeCauseMessage } from "./internal/boundary.ts";
|
|
58
|
+
import { cloudflareCryptoLayer } from "./internal/crypto.ts";
|
|
59
|
+
import {
|
|
60
|
+
Frame as LiveTextFrame,
|
|
61
|
+
isBoundedEvent,
|
|
62
|
+
MAX_DRAFT_PARTS,
|
|
63
|
+
MAX_FRAME_BYTES,
|
|
64
|
+
Request as WatchTextRequest,
|
|
65
|
+
} from "./internal/live-text-protocol.ts";
|
|
66
|
+
|
|
67
|
+
export { Frame as LiveTextFrame } from "./internal/live-text-protocol.ts";
|
|
58
68
|
|
|
59
69
|
/**
|
|
60
70
|
* The Worker↔Thread-Object host protocol (plan §1.4): Schema envelopes for the host
|
|
@@ -169,10 +179,10 @@ export const HostFailure = Schema.Union([
|
|
|
169
179
|
UnknownResolutionConflict,
|
|
170
180
|
JoinedToHost,
|
|
171
181
|
LedgerError,
|
|
182
|
+
OwnershipLost,
|
|
172
183
|
ThreadStoreError,
|
|
173
184
|
ThreadNotMaterialized,
|
|
174
185
|
AppendConflict,
|
|
175
|
-
FenceRejected,
|
|
176
186
|
DurableRuntimeFailpointError,
|
|
177
187
|
AdmissionLimitExceeded,
|
|
178
188
|
DurableAlarmError,
|
|
@@ -198,6 +208,13 @@ export class SettlementReached extends Schema.TaggedClass<SettlementReached>(
|
|
|
198
208
|
settlement: Settlement,
|
|
199
209
|
}) {}
|
|
200
210
|
|
|
211
|
+
/** The exact canonical record of an operationally finalized Submission. */
|
|
212
|
+
export class SettlementRecordReached extends Schema.TaggedClass<SettlementRecordReached>(
|
|
213
|
+
"@effect-agent/platform-cloudflare/SettlementRecordReached",
|
|
214
|
+
)("SettlementRecordReached", {
|
|
215
|
+
record: SubmissionSettledRecord,
|
|
216
|
+
}) {}
|
|
217
|
+
|
|
201
218
|
export class ObservedPage extends Schema.TaggedClass<ObservedPage>(
|
|
202
219
|
"@effect-agent/platform-cloudflare/ObservedPage",
|
|
203
220
|
)("ObservedPage", {
|
|
@@ -242,6 +259,7 @@ export class HostFailed extends Schema.TaggedClass<HostFailed>(
|
|
|
242
259
|
export const HostResponse = Schema.Union([
|
|
243
260
|
SubmitSucceeded,
|
|
244
261
|
SettlementReached,
|
|
262
|
+
SettlementRecordReached,
|
|
245
263
|
SubmissionStatusResponse,
|
|
246
264
|
ObservedPage,
|
|
247
265
|
ProgressObserved,
|
|
@@ -288,10 +306,10 @@ const ClientSubmitHostFailure = Schema.Union([
|
|
|
288
306
|
AdmissionConflict,
|
|
289
307
|
AdmissionPolicyError,
|
|
290
308
|
LedgerError,
|
|
309
|
+
OwnershipLost,
|
|
291
310
|
ThreadStoreError,
|
|
292
311
|
ThreadNotMaterialized,
|
|
293
312
|
AppendConflict,
|
|
294
|
-
FenceRejected,
|
|
295
313
|
DurableRuntimeFailpointError,
|
|
296
314
|
AdmissionLimitExceeded,
|
|
297
315
|
DurableAlarmError,
|
|
@@ -300,6 +318,7 @@ const ClientSubmitHostFailure = Schema.Union([
|
|
|
300
318
|
|
|
301
319
|
const ClientAwaitHostFailure = Schema.Union([
|
|
302
320
|
LedgerError,
|
|
321
|
+
OwnershipLost,
|
|
303
322
|
SettlementConflict,
|
|
304
323
|
OperationDenied,
|
|
305
324
|
HostProtocolError,
|
|
@@ -315,6 +334,7 @@ const ClientObserveHostFailure = Schema.Union([
|
|
|
315
334
|
const ClientAbortHostFailure = Schema.Union([
|
|
316
335
|
OperationDenied,
|
|
317
336
|
LedgerError,
|
|
337
|
+
OwnershipLost,
|
|
318
338
|
SettlementConflict,
|
|
319
339
|
JoinedToHost,
|
|
320
340
|
DurableRuntimeFailpointError,
|
|
@@ -324,6 +344,7 @@ const ClientAbortHostFailure = Schema.Union([
|
|
|
324
344
|
|
|
325
345
|
const ClientApprovalHostFailure = Schema.Union([
|
|
326
346
|
LedgerError,
|
|
347
|
+
OwnershipLost,
|
|
327
348
|
SettlementConflict,
|
|
328
349
|
ApprovalConflict,
|
|
329
350
|
OperationDenied,
|
|
@@ -333,6 +354,7 @@ const ClientApprovalHostFailure = Schema.Union([
|
|
|
333
354
|
|
|
334
355
|
const ClientUnknownHostFailure = Schema.Union([
|
|
335
356
|
LedgerError,
|
|
357
|
+
OwnershipLost,
|
|
336
358
|
SettlementConflict,
|
|
337
359
|
UnknownResolutionConflict,
|
|
338
360
|
JoinedToHost,
|
|
@@ -350,6 +372,127 @@ export type ClientAbortFailure = typeof ClientAbortHostFailure.Type | ThreadClie
|
|
|
350
372
|
export type ClientApprovalFailure = typeof ClientApprovalHostFailure.Type | ThreadClientError;
|
|
351
373
|
export type ClientUnknownFailure = typeof ClientUnknownHostFailure.Type | ThreadClientError;
|
|
352
374
|
|
|
375
|
+
const liveTextProtocolFailure = () =>
|
|
376
|
+
HostProtocolError.make({
|
|
377
|
+
message: "Provisional text is incomplete or invalid; discard drafts and read canonical records",
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const decodeLiveText = (
|
|
381
|
+
threadId: ThreadId,
|
|
382
|
+
source: ReadableStream<unknown>,
|
|
383
|
+
): Stream.Stream<LiveTextFrame, HostProtocolError | ThreadClientError> =>
|
|
384
|
+
Stream.suspend(() => {
|
|
385
|
+
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
|
|
386
|
+
const decode = Schema.decodeUnknownSync(Schema.fromJsonString(LiveTextFrame));
|
|
387
|
+
let pending = "";
|
|
388
|
+
let pendingBytes = 0;
|
|
389
|
+
let expected = 0;
|
|
390
|
+
let streamId: string | undefined;
|
|
391
|
+
const models = new Map<string, { attemptId: string; parts: Set<string> }>();
|
|
392
|
+
|
|
393
|
+
const accept = (frame: LiveTextFrame): boolean => {
|
|
394
|
+
if (frame._tag === "Reset") {
|
|
395
|
+
if (frame.streamId === streamId || frame.threadId !== threadId)
|
|
396
|
+
throw liveTextProtocolFailure();
|
|
397
|
+
streamId = frame.streamId;
|
|
398
|
+
expected = 1;
|
|
399
|
+
models.clear();
|
|
400
|
+
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
if (
|
|
404
|
+
streamId === undefined ||
|
|
405
|
+
frame.streamId !== streamId ||
|
|
406
|
+
frame.sequence !== expected++ ||
|
|
407
|
+
frame.event.threadId !== threadId ||
|
|
408
|
+
!isBoundedEvent(frame.event)
|
|
409
|
+
)
|
|
410
|
+
throw liveTextProtocolFailure();
|
|
411
|
+
const event = frame.event;
|
|
412
|
+
|
|
413
|
+
if (event._tag === "AttemptEnded") {
|
|
414
|
+
for (const [key, model] of models)
|
|
415
|
+
if (model.attemptId === event.attemptId) models.delete(key);
|
|
416
|
+
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
const key = JSON.stringify([event.attemptId, event.runId, event.turnId, event.generation]);
|
|
420
|
+
|
|
421
|
+
if (event._tag === "Discard") {
|
|
422
|
+
models.delete(key);
|
|
423
|
+
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
let model = models.get(key);
|
|
427
|
+
|
|
428
|
+
if (event.part.type === "text-start") {
|
|
429
|
+
if (model?.parts.has(event.part.id)) throw liveTextProtocolFailure();
|
|
430
|
+
if (
|
|
431
|
+
[...models.values()].reduce((size, value) => size + value.parts.size, 0) >=
|
|
432
|
+
MAX_DRAFT_PARTS
|
|
433
|
+
)
|
|
434
|
+
throw liveTextProtocolFailure();
|
|
435
|
+
if (model === undefined) {
|
|
436
|
+
model = { attemptId: event.attemptId, parts: new Set() };
|
|
437
|
+
models.set(key, model);
|
|
438
|
+
}
|
|
439
|
+
model.parts.add(event.part.id);
|
|
440
|
+
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
if (!model?.parts.has(event.part.id)) throw liveTextProtocolFailure();
|
|
444
|
+
if (event.part.type === "text-end") {
|
|
445
|
+
model.parts.delete(event.part.id);
|
|
446
|
+
if (model.parts.size === 0) models.delete(key);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return true;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
return Stream.fromReadableStream({
|
|
453
|
+
evaluate: () => source,
|
|
454
|
+
// The subscription Scope owns the single bounded cancellation after this reader unlocks.
|
|
455
|
+
releaseLockOnEnd: true,
|
|
456
|
+
onError: (cause) =>
|
|
457
|
+
ThreadClientError.make({
|
|
458
|
+
threadId,
|
|
459
|
+
message: "Provisional text disconnected; discard drafts and read canonical records",
|
|
460
|
+
cause,
|
|
461
|
+
}),
|
|
462
|
+
}).pipe(
|
|
463
|
+
Stream.mapEffect((chunk) =>
|
|
464
|
+
Effect.try({
|
|
465
|
+
try: () => {
|
|
466
|
+
if (!(chunk instanceof Uint8Array)) throw liveTextProtocolFailure();
|
|
467
|
+
const frames: LiveTextFrame[] = [];
|
|
468
|
+
|
|
469
|
+
for (let offset = 0; offset < chunk.byteLength;) {
|
|
470
|
+
const newline = chunk.indexOf(10, offset);
|
|
471
|
+
const end = newline < 0 ? chunk.byteLength : newline;
|
|
472
|
+
|
|
473
|
+
pendingBytes += end - offset;
|
|
474
|
+
if (pendingBytes > MAX_FRAME_BYTES) throw liveTextProtocolFailure();
|
|
475
|
+
pending += decoder.decode(chunk.subarray(offset, end), { stream: newline < 0 });
|
|
476
|
+
offset = end + 1;
|
|
477
|
+
if (newline < 0) break;
|
|
478
|
+
const frame = decode(pending);
|
|
479
|
+
|
|
480
|
+
pending = "";
|
|
481
|
+
pendingBytes = 0;
|
|
482
|
+
if (accept(frame)) frames.push(frame);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return frames;
|
|
486
|
+
},
|
|
487
|
+
catch: liveTextProtocolFailure,
|
|
488
|
+
}),
|
|
489
|
+
),
|
|
490
|
+
Stream.flatMap(Stream.fromArray),
|
|
491
|
+
// This connection is open-ended. Clean EOF is also loss of provisional state.
|
|
492
|
+
Stream.concat(Stream.fail(liveTextProtocolFailure())),
|
|
493
|
+
);
|
|
494
|
+
});
|
|
495
|
+
|
|
353
496
|
const outOfContract = (threadId: string, operation: string, observed: string): ThreadClientError =>
|
|
354
497
|
ThreadClientError.make({
|
|
355
498
|
threadId,
|
|
@@ -361,6 +504,7 @@ const outOfContract = (threadId: string, operation: string, observed: string): T
|
|
|
361
504
|
const hostRpcMethods = {
|
|
362
505
|
submit: "submitEncoded",
|
|
363
506
|
awaitSettlement: "awaitSettlementEncoded",
|
|
507
|
+
awaitSettlementRecord: "awaitSettlementRecordEncoded",
|
|
364
508
|
submissionStatus: "submissionStatusEncoded",
|
|
365
509
|
awaitProgress: "awaitProgressEncoded",
|
|
366
510
|
cancelProgress: "cancelProgressEncoded",
|
|
@@ -385,6 +529,14 @@ export class CloudflareThreadClient extends Context.Service<
|
|
|
385
529
|
receipt: Receipt,
|
|
386
530
|
) => Effect.Effect<SubmissionStatus, ClientAwaitFailure>;
|
|
387
531
|
readonly awaitSettlement: (receipt: Receipt) => Effect.Effect<Settlement, ClientAwaitFailure>;
|
|
532
|
+
/**
|
|
533
|
+
* Wait for finalization and return this Receipt's canonical terminal record in one RPC.
|
|
534
|
+
* Requires both settlement and observation authority. Decode an ordinary completed
|
|
535
|
+
* record's result through the Agent output Schema; joined completion may have no result.
|
|
536
|
+
*/
|
|
537
|
+
readonly awaitSettlementRecord: (
|
|
538
|
+
receipt: Receipt,
|
|
539
|
+
) => Effect.Effect<SubmissionSettledRecord, ClientAwaitFailure>;
|
|
388
540
|
/**
|
|
389
541
|
* Wait without polling until progress after `afterSequence` is already durable or hinted.
|
|
390
542
|
* The result is deliberately void: canonical records remain authoritative and must be read.
|
|
@@ -394,6 +546,14 @@ export class CloudflareThreadClient extends Context.Service<
|
|
|
394
546
|
threadId: ThreadId,
|
|
395
547
|
afterSequence: CanonicalSequence,
|
|
396
548
|
) => Effect.Effect<void, ClientProgressFailure>;
|
|
549
|
+
/**
|
|
550
|
+
* Current provisional text followed by native deltas. Reset starts a replacement snapshot,
|
|
551
|
+
* including when a slow consumer exceeds the fixed backlog. Reconcile by
|
|
552
|
+
* Run/Turn identity to canonical records; discard drafts on Discard, AttemptEnded,
|
|
553
|
+
* Reset or stream failure. Snapshots exist only in the Object incarnation's memory.
|
|
554
|
+
* Scope the stream: interruption cancels its remote subscription.
|
|
555
|
+
*/
|
|
556
|
+
readonly watchText: (threadId: ThreadId) => Stream.Stream<LiveTextFrame, ClientObserveFailure>;
|
|
397
557
|
/** One bounded page of canonical records. */
|
|
398
558
|
readonly readPage: (
|
|
399
559
|
threadId: ThreadId,
|
|
@@ -439,7 +599,10 @@ export class CloudflareThreadClient extends Context.Service<
|
|
|
439
599
|
readonly rpcTracing?: string;
|
|
440
600
|
}): Layer.Layer<CloudflareThreadClient> {
|
|
441
601
|
return CloudflareThreadClient.layer.pipe(
|
|
442
|
-
Layer.provide([
|
|
602
|
+
Layer.provide([
|
|
603
|
+
ThreadObjectNamespace.layer(options.namespace, options),
|
|
604
|
+
cloudflareCryptoLayer,
|
|
605
|
+
]),
|
|
443
606
|
);
|
|
444
607
|
}
|
|
445
608
|
|
|
@@ -453,7 +616,7 @@ export class CloudflareThreadClient extends Context.Service<
|
|
|
453
616
|
const { rpcTracing } = namespace;
|
|
454
617
|
const crypto = yield* Crypto.Crypto;
|
|
455
618
|
|
|
456
|
-
const call = Effect.
|
|
619
|
+
const call = Effect.fnUntraced(
|
|
457
620
|
function* (
|
|
458
621
|
threadId: ThreadId,
|
|
459
622
|
operation: keyof typeof hostRpcMethods,
|
|
@@ -676,9 +839,31 @@ export class CloudflareThreadClient extends Context.Service<
|
|
|
676
839
|
return settled.settlement;
|
|
677
840
|
}),
|
|
678
841
|
|
|
842
|
+
awaitSettlementRecord: (receipt) =>
|
|
843
|
+
Effect.gen(function* () {
|
|
844
|
+
const encoded = yield* encodeReceipt(receipt).pipe(
|
|
845
|
+
Effect.mapError((error) =>
|
|
846
|
+
HostProtocolError.make({
|
|
847
|
+
message: boundHostDiagnostic(`receipt encode failed: ${error.message}`),
|
|
848
|
+
}),
|
|
849
|
+
),
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
const response = yield* call(receipt.threadId, "awaitSettlementRecord", encoded);
|
|
853
|
+
|
|
854
|
+
const settled = yield* expect(
|
|
855
|
+
receipt.threadId,
|
|
856
|
+
"awaitSettlementRecord",
|
|
857
|
+
SettlementRecordReached,
|
|
858
|
+
ClientAwaitHostFailure,
|
|
859
|
+
)(response);
|
|
860
|
+
|
|
861
|
+
return settled.record;
|
|
862
|
+
}),
|
|
863
|
+
|
|
679
864
|
awaitProgress: (threadId, afterSequence) =>
|
|
680
865
|
Effect.gen(function* () {
|
|
681
|
-
const waiterId = yield* crypto.randomUUIDv4.pipe(
|
|
866
|
+
const waiterId = yield* crypto.randomUUIDv4().pipe(
|
|
682
867
|
Effect.mapError((error) =>
|
|
683
868
|
HostProtocolError.make({
|
|
684
869
|
message: boundHostDiagnostic(
|
|
@@ -718,6 +903,101 @@ export class CloudflareThreadClient extends Context.Service<
|
|
|
718
903
|
yield* attempt(0).pipe(Effect.onInterrupt(() => cancelProgress(threadId, waiterId)));
|
|
719
904
|
}),
|
|
720
905
|
|
|
906
|
+
watchText: (threadId) =>
|
|
907
|
+
Stream.unwrap(
|
|
908
|
+
Effect.gen(function* () {
|
|
909
|
+
const traceArgs =
|
|
910
|
+
rpcTracing === undefined ? [] : yield* RpcTracing.withRpcTraceContext([]);
|
|
911
|
+
|
|
912
|
+
const openingFailure = (cause: unknown) =>
|
|
913
|
+
ThreadClientError.make({
|
|
914
|
+
threadId,
|
|
915
|
+
message: "Provisional text observation could not reach the Thread Object",
|
|
916
|
+
cause,
|
|
917
|
+
...cloudflareFailureSignals(cause),
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
// Retain the original RPC promise: resolving through callThreadObject would
|
|
921
|
+
// erase its optional native disposer before an interrupted opening can use it.
|
|
922
|
+
const target = yield* RpcTargets.get(namespace, threadId, () =>
|
|
923
|
+
namespace.get(threadId),
|
|
924
|
+
).pipe(Effect.mapError(openingFailure));
|
|
925
|
+
|
|
926
|
+
let request: Promise<unknown> | undefined;
|
|
927
|
+
let received: unknown;
|
|
928
|
+
let closed = false;
|
|
929
|
+
|
|
930
|
+
const cancel = () => {
|
|
931
|
+
const value = received;
|
|
932
|
+
|
|
933
|
+
received = undefined;
|
|
934
|
+
|
|
935
|
+
return value instanceof ReadableStream && !value.locked
|
|
936
|
+
? value.cancel().catch(() => undefined)
|
|
937
|
+
: Promise.resolve();
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
// Install ownership before invoking foreign code, not after its promise settles.
|
|
941
|
+
yield* Effect.addFinalizer(() =>
|
|
942
|
+
Effect.try({
|
|
943
|
+
try: () => {
|
|
944
|
+
closed = true;
|
|
945
|
+
if (Predicate.hasProperty(request, Symbol.dispose)) {
|
|
946
|
+
const dispose = request[Symbol.dispose];
|
|
947
|
+
|
|
948
|
+
if (typeof dispose === "function") dispose.call(request);
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
catch: () => undefined,
|
|
952
|
+
}).pipe(
|
|
953
|
+
Effect.ignore,
|
|
954
|
+
Effect.andThen(
|
|
955
|
+
Effect.tryPromise({ try: cancel, catch: () => undefined }).pipe(
|
|
956
|
+
Effect.interruptible,
|
|
957
|
+
Effect.timeout(PROGRESS_CANCELLATION_TIMEOUT),
|
|
958
|
+
Effect.ignore,
|
|
959
|
+
),
|
|
960
|
+
),
|
|
961
|
+
),
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
const raw = yield* Effect.tryPromise({
|
|
965
|
+
try: () => {
|
|
966
|
+
request = target.watchTextEncoded(
|
|
967
|
+
Schema.encodeSync(WatchTextRequest)({ schemaVersion: 1 }),
|
|
968
|
+
...traceArgs,
|
|
969
|
+
);
|
|
970
|
+
|
|
971
|
+
return request.then((value) => {
|
|
972
|
+
received = value;
|
|
973
|
+
|
|
974
|
+
// A custom endpoint may return a plain, non-cancellable promise. Keep
|
|
975
|
+
// late cleanup on that native promise chain; it starts no Effect fiber.
|
|
976
|
+
return closed ? cancel().then(() => value) : value;
|
|
977
|
+
});
|
|
978
|
+
},
|
|
979
|
+
catch: openingFailure,
|
|
980
|
+
}).pipe(
|
|
981
|
+
Effect.interruptible,
|
|
982
|
+
Effect.tapCause(() => RpcTargets.invalidate(target)),
|
|
983
|
+
);
|
|
984
|
+
|
|
985
|
+
if (raw instanceof ReadableStream) return decodeLiveText(threadId, raw);
|
|
986
|
+
|
|
987
|
+
const response = yield* decodeHostResponse(raw).pipe(
|
|
988
|
+
Effect.mapError(liveTextProtocolFailure),
|
|
989
|
+
);
|
|
990
|
+
|
|
991
|
+
if (
|
|
992
|
+
response._tag === "HostFailed" &&
|
|
993
|
+
Schema.is(ClientObserveHostFailure)(response.failure)
|
|
994
|
+
)
|
|
995
|
+
return yield* response.failure;
|
|
996
|
+
|
|
997
|
+
return yield* liveTextProtocolFailure();
|
|
998
|
+
}),
|
|
999
|
+
),
|
|
1000
|
+
|
|
721
1001
|
readPage,
|
|
722
1002
|
|
|
723
1003
|
readAll: (threadId) =>
|