@effect-agent/platform-cloudflare 0.1.0-beta.9 → 0.1.0-beta.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Alarm.d.mts +188 -0
- package/dist/Alarm.mjs +451 -0
- package/dist/Alarm.mjs.map +1 -0
- package/dist/BrowserRestCapture.d.mts +35 -0
- package/dist/BrowserRestCapture.mjs +238 -0
- package/dist/BrowserRestCapture.mjs.map +1 -0
- package/dist/BrowserRestCrawl.d.mts +17 -0
- package/dist/BrowserRestCrawl.mjs +244 -0
- package/dist/BrowserRestCrawl.mjs.map +1 -0
- package/dist/CloudflareAiGateway.d.mts +64 -0
- package/dist/CloudflareAiGateway.mjs +70 -0
- package/dist/CloudflareAiGateway.mjs.map +1 -0
- package/dist/CloudflareBindings.d.mts +115 -0
- package/dist/CloudflareBindings.mjs +113 -0
- package/dist/CloudflareBindings.mjs.map +1 -0
- package/dist/CloudflareBrowser-DV6kya1H.mjs +495 -0
- package/dist/CloudflareBrowser-DV6kya1H.mjs.map +1 -0
- package/dist/CloudflareBrowser-Doe1M7R5.d.mts +80 -0
- package/dist/CloudflareBrowser.d.mts +2 -0
- package/dist/CloudflareBrowser.mjs +2 -0
- package/dist/CloudflareCodeMode.d.mts +44 -0
- package/dist/CloudflareCodeMode.mjs +616 -0
- package/dist/CloudflareCodeMode.mjs.map +1 -0
- package/dist/CloudflareConfig-f3CqTel1.d.mts +101 -0
- package/dist/CloudflareConfig.d.mts +2 -0
- package/dist/CloudflareConfig.mjs +122 -0
- package/dist/CloudflareConfig.mjs.map +1 -0
- package/dist/CloudflareMemory.d.mts +142 -0
- package/dist/CloudflareMemory.mjs +202 -0
- package/dist/CloudflareMemory.mjs.map +1 -0
- package/dist/CloudflareScheduling.d.mts +52 -0
- package/dist/CloudflareScheduling.mjs +396 -0
- package/dist/CloudflareScheduling.mjs.map +1 -0
- package/dist/CloudflareSubscriptions.d.mts +87 -0
- package/dist/CloudflareSubscriptions.mjs +530 -0
- package/dist/CloudflareSubscriptions.mjs.map +1 -0
- package/dist/CloudflareThreadClient.d.mts +1496 -0
- package/dist/CloudflareThreadClient.mjs +382 -0
- package/dist/CloudflareThreadClient.mjs.map +1 -0
- package/dist/InteractiveBrowser-DUE_m12-.d.mts +161 -0
- package/dist/InteractiveBrowser-DupDtfw2.mjs +1169 -0
- package/dist/InteractiveBrowser-DupDtfw2.mjs.map +1 -0
- package/dist/InteractiveBrowser.d.mts +2 -0
- package/dist/InteractiveBrowser.mjs +2 -0
- package/dist/ProtectedBrowser.d.mts +123 -0
- package/dist/ProtectedBrowser.mjs +1135 -0
- package/dist/ProtectedBrowser.mjs.map +1 -0
- package/dist/ThreadObject-CIbmXZCY.d.mts +812 -0
- package/dist/ThreadObject-DvRG0dDz.mjs +835 -0
- package/dist/ThreadObject-DvRG0dDz.mjs.map +1 -0
- package/dist/ThreadObject.d.mts +2 -0
- package/dist/ThreadObject.mjs +3 -0
- package/dist/WakeScheduler.d.mts +23 -0
- package/dist/WakeScheduler.mjs +57 -0
- package/dist/WakeScheduler.mjs.map +1 -0
- package/dist/boundary-BguazVkh.mjs +42 -0
- package/dist/boundary-BguazVkh.mjs.map +1 -0
- package/dist/index.d.mts +13 -1548
- package/dist/index.mjs +13 -1636
- package/dist/prepared-admission-DU0_T-ev.mjs +73 -0
- package/dist/prepared-admission-DU0_T-ev.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -53
- package/src/Alarm.ts +973 -0
- package/src/BrowserRestCapture.ts +477 -0
- package/src/BrowserRestCrawl.ts +540 -0
- package/src/CloudflareAiGateway.ts +170 -0
- package/src/CloudflareBindings.ts +199 -0
- package/src/CloudflareBrowser.ts +15 -0
- package/src/{code-mode-executor.ts → CloudflareCodeMode.ts} +372 -204
- package/src/{config.ts → CloudflareConfig.ts} +9 -8
- package/src/CloudflareMemory.ts +420 -0
- package/src/CloudflareScheduling.ts +747 -0
- package/src/CloudflareSubscriptions.ts +1041 -0
- package/src/CloudflareThreadClient.ts +809 -0
- package/src/InteractiveBrowser.ts +2354 -0
- package/src/ProtectedBrowser.ts +9 -0
- package/src/ThreadObject.ts +1179 -0
- package/src/{wake-scheduler.ts → WakeScheduler.ts} +32 -26
- package/src/index.ts +12 -23
- package/src/internal/boundary.ts +55 -0
- package/src/internal/browser-quick-action.ts +857 -0
- package/src/internal/browser-session-lifecycle.ts +160 -0
- package/src/internal/layers.ts +751 -0
- package/src/internal/message-delivery.ts +152 -0
- package/src/internal/prepared-admission.ts +116 -0
- package/src/internal/progress-wait.ts +121 -0
- package/src/internal/transport.ts +44 -0
- package/src/protected-browser/binding.ts +313 -0
- package/src/protected-browser/host.ts +344 -0
- package/src/protected-browser/inspect-frame.ts +125 -0
- package/src/protected-browser/native.ts +445 -0
- package/src/protected-browser/policy.ts +821 -0
- package/dist/index.mjs.map +0 -1
- package/src/alarm.ts +0 -334
- package/src/bindings.ts +0 -145
- package/src/client.ts +0 -646
- package/src/conversation-object.ts +0 -768
- package/src/layers.ts +0 -376
- package/src/transport.ts +0 -38
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
import { BrowserCrypto } from "@effect/platform-browser";
|
|
2
|
+
import { Context, Crypto, Duration, Effect, Layer, Schema } from "effect";
|
|
3
|
+
import { AgentInputError } from "effect-agent/agent-error";
|
|
4
|
+
import { DigestError } from "effect-agent/digest";
|
|
5
|
+
import {
|
|
6
|
+
Receipt,
|
|
7
|
+
type DurableSubmitAgent,
|
|
8
|
+
type DurableSubmitOptions,
|
|
9
|
+
} from "effect-agent/durable-agent-runtime";
|
|
10
|
+
import { DurableRuntimeFailpointError } from "effect-agent/durable-failpoint";
|
|
11
|
+
import { AgentId, type ThreadId } from "effect-agent/identifiers";
|
|
12
|
+
import { InputMessage } from "effect-agent/messaging";
|
|
13
|
+
import { OperationDenied } from "effect-agent/operation-authorizer";
|
|
14
|
+
import {
|
|
15
|
+
CanonicalRecordEnvelope,
|
|
16
|
+
CanonicalSequence,
|
|
17
|
+
DefinitionDigests,
|
|
18
|
+
PersistedJson,
|
|
19
|
+
WorkerAdmission,
|
|
20
|
+
} from "effect-agent/records";
|
|
21
|
+
import {
|
|
22
|
+
AdmissionFence,
|
|
23
|
+
AdmissionGroup,
|
|
24
|
+
AbortCommand,
|
|
25
|
+
AbortIntent,
|
|
26
|
+
AdmissionConflict,
|
|
27
|
+
AdmissionPolicyError,
|
|
28
|
+
ApprovalConflict,
|
|
29
|
+
ApprovalDecisionCommand,
|
|
30
|
+
ApprovalDecisionIntent,
|
|
31
|
+
IdempotencyKey,
|
|
32
|
+
JoinedToHost,
|
|
33
|
+
LedgerError,
|
|
34
|
+
Principal,
|
|
35
|
+
Settlement,
|
|
36
|
+
SettlementConflict,
|
|
37
|
+
UnknownResolutionCommand,
|
|
38
|
+
UnknownResolutionConflict,
|
|
39
|
+
UnknownResolutionIntent,
|
|
40
|
+
} from "effect-agent/submission-ledger";
|
|
41
|
+
import { SubmissionStatus } from "effect-agent/submission-status";
|
|
42
|
+
import {
|
|
43
|
+
AppendConflict,
|
|
44
|
+
ThreadNotMaterialized,
|
|
45
|
+
ThreadStoreError,
|
|
46
|
+
FenceRejected,
|
|
47
|
+
} from "effect-agent/thread-store";
|
|
48
|
+
import { RpcTracing } from "effect-cf";
|
|
49
|
+
|
|
50
|
+
import { DurableAlarmError } from "./Alarm.ts";
|
|
51
|
+
import {
|
|
52
|
+
callThreadObject,
|
|
53
|
+
ThreadObjectNamespace,
|
|
54
|
+
type ThreadObjectRpc,
|
|
55
|
+
} from "./CloudflareBindings.ts";
|
|
56
|
+
import { AdmissionLimitExceeded } from "./CloudflareConfig.ts";
|
|
57
|
+
import { cloudflareFailureSignals, safeCauseMessage } from "./internal/boundary.ts";
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The Worker↔Thread-Object host protocol (plan §1.4): Schema envelopes for the host
|
|
61
|
+
* entry points (`submitEncoded`, `awaitSettlementEncoded`, `observePage`, `abortEncoded`,
|
|
62
|
+
* `resolveApprovalEncoded`, `resolveUnknownEncoded`) plus the Worker-side client that speaks
|
|
63
|
+
* it. Mirrors the WP2 port protocol: requests and responses are closed Schema unions, typed
|
|
64
|
+
* failures travel as their own tagged classes and RE-DECODE to identical tags on the caller
|
|
65
|
+
* (error-tag fidelity), and protocol anomalies are answered typed instead of thrown.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/** Ceiling for host protocol diagnostic strings. */
|
|
69
|
+
const MAX_HOST_DIAGNOSTIC_LENGTH = 4_096;
|
|
70
|
+
const PROGRESS_CANCELLATION_TIMEOUT = "1 second";
|
|
71
|
+
|
|
72
|
+
const BoundedDiagnostic = Schema.String.check(Schema.isMaxLength(MAX_HOST_DIAGNOSTIC_LENGTH));
|
|
73
|
+
|
|
74
|
+
/** Truncate a diagnostic string to the host protocol's bounded length. */
|
|
75
|
+
export const boundHostDiagnostic = (value: string): string =>
|
|
76
|
+
value.length > MAX_HOST_DIAGNOSTIC_LENGTH
|
|
77
|
+
? `${value.slice(0, MAX_HOST_DIAGNOSTIC_LENGTH - 3)}...`
|
|
78
|
+
: value;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The envelope itself could not be honored: the Object could not decode the request, or a
|
|
82
|
+
* response could not be encoded/decoded. Never carries operation semantics.
|
|
83
|
+
*/
|
|
84
|
+
export class HostProtocolError extends Schema.TaggedError<HostProtocolError>()(
|
|
85
|
+
"HostProtocolError",
|
|
86
|
+
{
|
|
87
|
+
message: BoundedDiagnostic,
|
|
88
|
+
},
|
|
89
|
+
) {}
|
|
90
|
+
|
|
91
|
+
/** The Worker-side stub call itself failed (RPC rejection, overload, eviction mid-call). */
|
|
92
|
+
export class ThreadClientError extends Schema.TaggedError<ThreadClientError>()(
|
|
93
|
+
"ThreadClientError",
|
|
94
|
+
{
|
|
95
|
+
threadId: Schema.String,
|
|
96
|
+
message: Schema.String,
|
|
97
|
+
cause: Schema.optionalKey(Schema.Defect()),
|
|
98
|
+
/** Cloudflare's own classification for a failure safe to retry with a fresh stub. */
|
|
99
|
+
retryable: Schema.optionalKey(Schema.Boolean),
|
|
100
|
+
/** Cloudflare overloads are surfaced immediately instead of adding retry pressure. */
|
|
101
|
+
overloaded: Schema.optionalKey(Schema.Boolean),
|
|
102
|
+
},
|
|
103
|
+
) {}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Requests
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* One durable submission, input ALREADY encoded by the caller through the Agent Binding's
|
|
111
|
+
* input schema (the Worker bundles the same Agent definitions as the Object, so schema
|
|
112
|
+
* validation happens client-side; the resolved Binding re-validates at claim time). The
|
|
113
|
+
* Thread identity is deliberately absent — the addressed Object IS the lane.
|
|
114
|
+
*/
|
|
115
|
+
export class SubmitRequest extends Schema.Class<SubmitRequest>(
|
|
116
|
+
"@effect-agent/platform-cloudflare/SubmitRequest",
|
|
117
|
+
)({
|
|
118
|
+
agentId: AgentId,
|
|
119
|
+
principal: Principal,
|
|
120
|
+
idempotencyKey: IdempotencyKey,
|
|
121
|
+
admissionGroup: Schema.optionalKey(AdmissionGroup),
|
|
122
|
+
admissionFence: Schema.optionalKey(AdmissionFence),
|
|
123
|
+
workerAdmission: Schema.optionalKey(WorkerAdmission),
|
|
124
|
+
messageAdmission: Schema.optionalKey(InputMessage),
|
|
125
|
+
definitions: DefinitionDigests,
|
|
126
|
+
inputPayload: PersistedJson,
|
|
127
|
+
}) {}
|
|
128
|
+
|
|
129
|
+
/** One bounded page of canonical records after an optional sequence. */
|
|
130
|
+
export class ObservePageRequest extends Schema.Class<ObservePageRequest>(
|
|
131
|
+
"@effect-agent/platform-cloudflare/ObservePageRequest",
|
|
132
|
+
)({
|
|
133
|
+
afterSequence: Schema.optionalKey(CanonicalSequence),
|
|
134
|
+
limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(1_024)),
|
|
135
|
+
}) {}
|
|
136
|
+
|
|
137
|
+
/** One event-driven wait for canonical progress strictly after this sequence. */
|
|
138
|
+
export class AwaitProgressRequest extends Schema.Class<AwaitProgressRequest>(
|
|
139
|
+
"@effect-agent/platform-cloudflare/AwaitProgressRequest",
|
|
140
|
+
)({
|
|
141
|
+
afterSequence: CanonicalSequence,
|
|
142
|
+
waiterId: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256)),
|
|
143
|
+
}) {}
|
|
144
|
+
|
|
145
|
+
/** Best-effort cancellation of one in-flight progress RPC. */
|
|
146
|
+
export class CancelProgressRequest extends Schema.Class<CancelProgressRequest>(
|
|
147
|
+
"@effect-agent/platform-cloudflare/CancelProgressRequest",
|
|
148
|
+
)({
|
|
149
|
+
waiterId: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256)),
|
|
150
|
+
}) {}
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Responses
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Every typed failure a host entry point can produce, plus the protocol's own errors. Same
|
|
158
|
+
* closed-union discipline as the WP2 `PortFailure`: members re-decode to the SAME tagged
|
|
159
|
+
* classes on the Worker side; `cause` chains travel as Schema defects without instance
|
|
160
|
+
* fidelity (plan §2.8).
|
|
161
|
+
*/
|
|
162
|
+
export const HostFailure = Schema.Union([
|
|
163
|
+
AgentInputError,
|
|
164
|
+
DigestError,
|
|
165
|
+
AdmissionConflict,
|
|
166
|
+
AdmissionPolicyError,
|
|
167
|
+
SettlementConflict,
|
|
168
|
+
ApprovalConflict,
|
|
169
|
+
UnknownResolutionConflict,
|
|
170
|
+
JoinedToHost,
|
|
171
|
+
LedgerError,
|
|
172
|
+
ThreadStoreError,
|
|
173
|
+
ThreadNotMaterialized,
|
|
174
|
+
AppendConflict,
|
|
175
|
+
FenceRejected,
|
|
176
|
+
DurableRuntimeFailpointError,
|
|
177
|
+
AdmissionLimitExceeded,
|
|
178
|
+
DurableAlarmError,
|
|
179
|
+
OperationDenied,
|
|
180
|
+
HostProtocolError,
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
export type HostFailure = typeof HostFailure.Type;
|
|
184
|
+
|
|
185
|
+
export class SubmitSucceeded extends Schema.TaggedClass<SubmitSucceeded>(
|
|
186
|
+
"@effect-agent/platform-cloudflare/SubmitSucceeded",
|
|
187
|
+
)("SubmitSucceeded", {
|
|
188
|
+
receipt: Receipt,
|
|
189
|
+
}) {}
|
|
190
|
+
|
|
191
|
+
export class SubmissionStatusResponse extends Schema.TaggedClass<SubmissionStatusResponse>(
|
|
192
|
+
"@effect-agent/platform-cloudflare/SubmissionStatusResponse",
|
|
193
|
+
)("SubmissionStatusResponse", { status: SubmissionStatus }) {}
|
|
194
|
+
|
|
195
|
+
export class SettlementReached extends Schema.TaggedClass<SettlementReached>(
|
|
196
|
+
"@effect-agent/platform-cloudflare/SettlementReached",
|
|
197
|
+
)("SettlementReached", {
|
|
198
|
+
settlement: Settlement,
|
|
199
|
+
}) {}
|
|
200
|
+
|
|
201
|
+
export class ObservedPage extends Schema.TaggedClass<ObservedPage>(
|
|
202
|
+
"@effect-agent/platform-cloudflare/ObservedPage",
|
|
203
|
+
)("ObservedPage", {
|
|
204
|
+
records: Schema.Array(CanonicalRecordEnvelope).check(Schema.isMaxLength(1_024)),
|
|
205
|
+
}) {}
|
|
206
|
+
|
|
207
|
+
/** A record was already committed or an incarnation-local hint says the caller should re-read. */
|
|
208
|
+
export class ProgressObserved extends Schema.TaggedClass<ProgressObserved>(
|
|
209
|
+
"@effect-agent/platform-cloudflare/ProgressObserved",
|
|
210
|
+
)("ProgressObserved", {}) {}
|
|
211
|
+
|
|
212
|
+
export class ProgressCancelled extends Schema.TaggedClass<ProgressCancelled>(
|
|
213
|
+
"@effect-agent/platform-cloudflare/ProgressCancelled",
|
|
214
|
+
)("ProgressCancelled", {}) {}
|
|
215
|
+
|
|
216
|
+
export class AbortRecorded extends Schema.TaggedClass<AbortRecorded>(
|
|
217
|
+
"@effect-agent/platform-cloudflare/AbortRecorded",
|
|
218
|
+
)("AbortRecorded", {
|
|
219
|
+
intent: AbortIntent,
|
|
220
|
+
}) {}
|
|
221
|
+
|
|
222
|
+
export class ApprovalRecorded extends Schema.TaggedClass<ApprovalRecorded>(
|
|
223
|
+
"@effect-agent/platform-cloudflare/ApprovalRecorded",
|
|
224
|
+
)("ApprovalRecorded", {
|
|
225
|
+
intent: ApprovalDecisionIntent,
|
|
226
|
+
}) {}
|
|
227
|
+
|
|
228
|
+
export class UnknownResolutionRecorded extends Schema.TaggedClass<UnknownResolutionRecorded>(
|
|
229
|
+
"@effect-agent/platform-cloudflare/UnknownResolutionRecorded",
|
|
230
|
+
)("UnknownResolutionRecorded", {
|
|
231
|
+
intent: UnknownResolutionIntent,
|
|
232
|
+
}) {}
|
|
233
|
+
|
|
234
|
+
/** The entry point failed TYPED on the Object; the failure re-decodes verbatim. */
|
|
235
|
+
export class HostFailed extends Schema.TaggedClass<HostFailed>(
|
|
236
|
+
"@effect-agent/platform-cloudflare/HostFailed",
|
|
237
|
+
)("HostFailed", {
|
|
238
|
+
failure: HostFailure,
|
|
239
|
+
}) {}
|
|
240
|
+
|
|
241
|
+
/** The uniform answer of one host entry point. Callers narrow by the tag their call implies. */
|
|
242
|
+
export const HostResponse = Schema.Union([
|
|
243
|
+
SubmitSucceeded,
|
|
244
|
+
SettlementReached,
|
|
245
|
+
SubmissionStatusResponse,
|
|
246
|
+
ObservedPage,
|
|
247
|
+
ProgressObserved,
|
|
248
|
+
ProgressCancelled,
|
|
249
|
+
AbortRecorded,
|
|
250
|
+
ApprovalRecorded,
|
|
251
|
+
UnknownResolutionRecorded,
|
|
252
|
+
HostFailed,
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
export type HostResponse = typeof HostResponse.Type;
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Codecs (shared by the Object endpoints and the Worker client)
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
export const decodeSubmitRequest = Schema.decodeUnknownEffect(SubmitRequest);
|
|
262
|
+
export const encodeSubmitRequest = Schema.encodeEffect(SubmitRequest);
|
|
263
|
+
export const decodeReceipt = Schema.decodeUnknownEffect(Receipt);
|
|
264
|
+
export const encodeReceipt = Schema.encodeEffect(Receipt);
|
|
265
|
+
export const decodeObservePageRequest = Schema.decodeUnknownEffect(ObservePageRequest);
|
|
266
|
+
export const encodeObservePageRequest = Schema.encodeEffect(ObservePageRequest);
|
|
267
|
+
export const decodeAwaitProgressRequest = Schema.decodeUnknownEffect(AwaitProgressRequest);
|
|
268
|
+
export const encodeAwaitProgressRequest = Schema.encodeEffect(AwaitProgressRequest);
|
|
269
|
+
export const decodeCancelProgressRequest = Schema.decodeUnknownEffect(CancelProgressRequest);
|
|
270
|
+
export const encodeCancelProgressRequest = Schema.encodeEffect(CancelProgressRequest);
|
|
271
|
+
export const decodeAbortCommand = Schema.decodeUnknownEffect(AbortCommand);
|
|
272
|
+
export const encodeAbortCommand = Schema.encodeEffect(AbortCommand);
|
|
273
|
+
export const decodeApprovalDecisionCommand = Schema.decodeUnknownEffect(ApprovalDecisionCommand);
|
|
274
|
+
export const encodeApprovalDecisionCommand = Schema.encodeEffect(ApprovalDecisionCommand);
|
|
275
|
+
export const decodeUnknownResolutionCommand = Schema.decodeUnknownEffect(UnknownResolutionCommand);
|
|
276
|
+
export const encodeUnknownResolutionCommand = Schema.encodeEffect(UnknownResolutionCommand);
|
|
277
|
+
export const encodeHostResponse = Schema.encodeEffect(HostResponse);
|
|
278
|
+
export const decodeHostResponse = Schema.decodeUnknownEffect(HostResponse);
|
|
279
|
+
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// Worker-side client
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
/** Failure surface of `CloudflareThreadClient.submit`. */
|
|
285
|
+
const ClientSubmitHostFailure = Schema.Union([
|
|
286
|
+
AgentInputError,
|
|
287
|
+
DigestError,
|
|
288
|
+
AdmissionConflict,
|
|
289
|
+
AdmissionPolicyError,
|
|
290
|
+
LedgerError,
|
|
291
|
+
ThreadStoreError,
|
|
292
|
+
ThreadNotMaterialized,
|
|
293
|
+
AppendConflict,
|
|
294
|
+
FenceRejected,
|
|
295
|
+
DurableRuntimeFailpointError,
|
|
296
|
+
AdmissionLimitExceeded,
|
|
297
|
+
DurableAlarmError,
|
|
298
|
+
HostProtocolError,
|
|
299
|
+
]);
|
|
300
|
+
|
|
301
|
+
const ClientAwaitHostFailure = Schema.Union([
|
|
302
|
+
LedgerError,
|
|
303
|
+
SettlementConflict,
|
|
304
|
+
OperationDenied,
|
|
305
|
+
HostProtocolError,
|
|
306
|
+
]);
|
|
307
|
+
|
|
308
|
+
const ClientObserveHostFailure = Schema.Union([
|
|
309
|
+
ThreadStoreError,
|
|
310
|
+
ThreadNotMaterialized,
|
|
311
|
+
OperationDenied,
|
|
312
|
+
HostProtocolError,
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
const ClientAbortHostFailure = Schema.Union([
|
|
316
|
+
OperationDenied,
|
|
317
|
+
LedgerError,
|
|
318
|
+
SettlementConflict,
|
|
319
|
+
JoinedToHost,
|
|
320
|
+
DurableRuntimeFailpointError,
|
|
321
|
+
DurableAlarmError,
|
|
322
|
+
HostProtocolError,
|
|
323
|
+
]);
|
|
324
|
+
|
|
325
|
+
const ClientApprovalHostFailure = Schema.Union([
|
|
326
|
+
LedgerError,
|
|
327
|
+
SettlementConflict,
|
|
328
|
+
ApprovalConflict,
|
|
329
|
+
OperationDenied,
|
|
330
|
+
DurableAlarmError,
|
|
331
|
+
HostProtocolError,
|
|
332
|
+
]);
|
|
333
|
+
|
|
334
|
+
const ClientUnknownHostFailure = Schema.Union([
|
|
335
|
+
LedgerError,
|
|
336
|
+
SettlementConflict,
|
|
337
|
+
UnknownResolutionConflict,
|
|
338
|
+
JoinedToHost,
|
|
339
|
+
DurableRuntimeFailpointError,
|
|
340
|
+
OperationDenied,
|
|
341
|
+
DurableAlarmError,
|
|
342
|
+
HostProtocolError,
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
export type ClientSubmitFailure = typeof ClientSubmitHostFailure.Type | ThreadClientError;
|
|
346
|
+
export type ClientAwaitFailure = typeof ClientAwaitHostFailure.Type | ThreadClientError;
|
|
347
|
+
export type ClientObserveFailure = typeof ClientObserveHostFailure.Type | ThreadClientError;
|
|
348
|
+
export type ClientProgressFailure = ClientObserveFailure;
|
|
349
|
+
export type ClientAbortFailure = typeof ClientAbortHostFailure.Type | ThreadClientError;
|
|
350
|
+
export type ClientApprovalFailure = typeof ClientApprovalHostFailure.Type | ThreadClientError;
|
|
351
|
+
export type ClientUnknownFailure = typeof ClientUnknownHostFailure.Type | ThreadClientError;
|
|
352
|
+
|
|
353
|
+
const outOfContract = (threadId: string, operation: string, observed: string): ThreadClientError =>
|
|
354
|
+
ThreadClientError.make({
|
|
355
|
+
threadId,
|
|
356
|
+
message: boundHostDiagnostic(
|
|
357
|
+
`The Thread Object answered ${operation} with the out-of-contract ${observed}.`,
|
|
358
|
+
),
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const hostRpcMethods = {
|
|
362
|
+
submit: "submitEncoded",
|
|
363
|
+
awaitSettlement: "awaitSettlementEncoded",
|
|
364
|
+
submissionStatus: "submissionStatusEncoded",
|
|
365
|
+
awaitProgress: "awaitProgressEncoded",
|
|
366
|
+
cancelProgress: "cancelProgressEncoded",
|
|
367
|
+
observePage: "observePage",
|
|
368
|
+
abort: "abortEncoded",
|
|
369
|
+
resolveApproval: "resolveApprovalEncoded",
|
|
370
|
+
resolveUnknown: "resolveUnknownEncoded",
|
|
371
|
+
} as const satisfies Record<string, keyof ThreadObjectRpc>;
|
|
372
|
+
|
|
373
|
+
/** Worker-side client over the Thread Object namespace (DEPLOY-010). */
|
|
374
|
+
export class CloudflareThreadClient extends Context.Service<
|
|
375
|
+
CloudflareThreadClient,
|
|
376
|
+
{
|
|
377
|
+
/** Encode the input client-side, then durably submit to the owning Object. */
|
|
378
|
+
readonly submit: <InputSchema extends Schema.Top>(
|
|
379
|
+
agent: DurableSubmitAgent<InputSchema>,
|
|
380
|
+
input: InputSchema["Type"],
|
|
381
|
+
options: DurableSubmitOptions,
|
|
382
|
+
) => Effect.Effect<Receipt, ClientSubmitFailure, InputSchema["EncodingServices"]>;
|
|
383
|
+
/** Wake-hinted, poll-guaranteed settlement wait executed inside the owning Object. */
|
|
384
|
+
readonly submissionStatus: (
|
|
385
|
+
receipt: Receipt,
|
|
386
|
+
) => Effect.Effect<SubmissionStatus, ClientAwaitFailure>;
|
|
387
|
+
readonly awaitSettlement: (receipt: Receipt) => Effect.Effect<Settlement, ClientAwaitFailure>;
|
|
388
|
+
/**
|
|
389
|
+
* Wait without polling until progress after `afterSequence` is already durable or hinted.
|
|
390
|
+
* The result is deliberately void: canonical records remain authoritative and must be read.
|
|
391
|
+
* Interruption waits at most one second for best-effort remote cancellation.
|
|
392
|
+
*/
|
|
393
|
+
readonly awaitProgress: (
|
|
394
|
+
threadId: ThreadId,
|
|
395
|
+
afterSequence: CanonicalSequence,
|
|
396
|
+
) => Effect.Effect<void, ClientProgressFailure>;
|
|
397
|
+
/** One bounded page of canonical records. */
|
|
398
|
+
readonly readPage: (
|
|
399
|
+
threadId: ThreadId,
|
|
400
|
+
options?: {
|
|
401
|
+
readonly afterSequence?: CanonicalSequence | undefined;
|
|
402
|
+
readonly limit?: number | undefined;
|
|
403
|
+
},
|
|
404
|
+
) => Effect.Effect<ReadonlyArray<CanonicalRecordEnvelope>, ClientObserveFailure>;
|
|
405
|
+
/**
|
|
406
|
+
* Every canonical record up to the CURRENT committed tail, via repeated pages. A
|
|
407
|
+
* snapshot read, not a live observation — callers wanting liveness re-read after
|
|
408
|
+
* `awaitSettlement`.
|
|
409
|
+
*/
|
|
410
|
+
readonly readAll: (
|
|
411
|
+
threadId: ThreadId,
|
|
412
|
+
) => Effect.Effect<ReadonlyArray<CanonicalRecordEnvelope>, ClientObserveFailure>;
|
|
413
|
+
/**
|
|
414
|
+
* Submission-addressed operations take the owning Thread explicitly (from the
|
|
415
|
+
* Receipt): minted Submission identities stay OPAQUE outside the storage adapter that
|
|
416
|
+
* minted them (D-P6-5), so the client never parses one to find the lane.
|
|
417
|
+
*/
|
|
418
|
+
readonly abort: (
|
|
419
|
+
threadId: ThreadId,
|
|
420
|
+
command: AbortCommand,
|
|
421
|
+
) => Effect.Effect<AbortIntent, ClientAbortFailure>;
|
|
422
|
+
readonly resolveApproval: (
|
|
423
|
+
threadId: ThreadId,
|
|
424
|
+
command: ApprovalDecisionCommand,
|
|
425
|
+
) => Effect.Effect<ApprovalDecisionIntent, ClientApprovalFailure>;
|
|
426
|
+
readonly resolveUnknown: (
|
|
427
|
+
threadId: ThreadId,
|
|
428
|
+
command: UnknownResolutionCommand,
|
|
429
|
+
) => Effect.Effect<UnknownResolutionIntent, ClientUnknownFailure>;
|
|
430
|
+
}
|
|
431
|
+
>()("@effect-agent/platform-cloudflare/CloudflareThreadClient") {
|
|
432
|
+
/**
|
|
433
|
+
* Assemble the client from a resolved namespace and the platform Crypto implementation.
|
|
434
|
+
* rpcTracing is the binding name and remains opt-in. Caller authentication, principals,
|
|
435
|
+
* idempotency keys, and definition digests are still explicit submission inputs.
|
|
436
|
+
*/
|
|
437
|
+
static layerFromBinding(options: {
|
|
438
|
+
readonly namespace: DurableObjectNamespace<ThreadObjectRpc>;
|
|
439
|
+
readonly rpcTracing?: string;
|
|
440
|
+
}): Layer.Layer<CloudflareThreadClient> {
|
|
441
|
+
return CloudflareThreadClient.layer.pipe(
|
|
442
|
+
Layer.provide([ThreadObjectNamespace.layer(options.namespace, options), BrowserCrypto.layer]),
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
static readonly layer: Layer.Layer<
|
|
447
|
+
CloudflareThreadClient,
|
|
448
|
+
never,
|
|
449
|
+
ThreadObjectNamespace | Crypto.Crypto
|
|
450
|
+
> = Layer.effect(CloudflareThreadClient)(
|
|
451
|
+
Effect.gen(function* () {
|
|
452
|
+
const namespace = yield* ThreadObjectNamespace;
|
|
453
|
+
const { rpcTracing } = namespace;
|
|
454
|
+
const crypto = yield* Crypto.Crypto;
|
|
455
|
+
|
|
456
|
+
const call = Effect.fn(
|
|
457
|
+
function* (
|
|
458
|
+
threadId: ThreadId,
|
|
459
|
+
operation: keyof typeof hostRpcMethods,
|
|
460
|
+
encoded: unknown,
|
|
461
|
+
): Effect.fn.Return<HostResponse, ThreadClientError | HostProtocolError> {
|
|
462
|
+
// Empty arguments preserve native arity. Passing `undefined` still adds an argument.
|
|
463
|
+
const traceArgs =
|
|
464
|
+
rpcTracing === undefined ? [] : yield* RpcTracing.withRpcTraceContext([]);
|
|
465
|
+
|
|
466
|
+
const raw = yield* callThreadObject(
|
|
467
|
+
threadId,
|
|
468
|
+
(stub) => stub[hostRpcMethods[operation]](encoded, ...traceArgs),
|
|
469
|
+
(cause) =>
|
|
470
|
+
ThreadClientError.make({
|
|
471
|
+
threadId,
|
|
472
|
+
message: boundHostDiagnostic(
|
|
473
|
+
`${operation} did not reach the Thread Object: ${safeCauseMessage(
|
|
474
|
+
cause,
|
|
475
|
+
"the RPC failed without a diagnostic",
|
|
476
|
+
)}`,
|
|
477
|
+
),
|
|
478
|
+
cause,
|
|
479
|
+
...cloudflareFailureSignals(cause),
|
|
480
|
+
}),
|
|
481
|
+
).pipe(Effect.provideService(ThreadObjectNamespace, namespace));
|
|
482
|
+
|
|
483
|
+
return yield* decodeHostResponse(raw).pipe(
|
|
484
|
+
Effect.mapError((error): HostProtocolError =>
|
|
485
|
+
HostProtocolError.make({
|
|
486
|
+
message: boundHostDiagnostic(
|
|
487
|
+
`The ${operation} answer could not be decoded: ${error.message}`,
|
|
488
|
+
),
|
|
489
|
+
}),
|
|
490
|
+
),
|
|
491
|
+
);
|
|
492
|
+
},
|
|
493
|
+
(effect, threadId, operation) =>
|
|
494
|
+
rpcTracing === undefined
|
|
495
|
+
? Effect.withSpan(effect, "CloudflareThreadClient.call", {
|
|
496
|
+
attributes: { threadId, operation },
|
|
497
|
+
})
|
|
498
|
+
: RpcTracing.withRpcClientSpan(effect, rpcTracing, hostRpcMethods[operation]),
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
const expect = <ResultSchema extends Schema.Top, FailureSchema extends Schema.Top>(
|
|
502
|
+
threadId: string,
|
|
503
|
+
operation: string,
|
|
504
|
+
resultSchema: ResultSchema,
|
|
505
|
+
failureSchema: FailureSchema,
|
|
506
|
+
) => {
|
|
507
|
+
const isExpectedResult = Schema.is(resultSchema);
|
|
508
|
+
const isExpectedFailure = Schema.is(failureSchema);
|
|
509
|
+
|
|
510
|
+
return (
|
|
511
|
+
response: HostResponse,
|
|
512
|
+
): Effect.Effect<ResultSchema["Type"], FailureSchema["Type"] | ThreadClientError> => {
|
|
513
|
+
if (response._tag === "HostFailed") {
|
|
514
|
+
const failure = response.failure;
|
|
515
|
+
|
|
516
|
+
return isExpectedFailure(failure)
|
|
517
|
+
? Effect.fail(failure)
|
|
518
|
+
: Effect.fail(outOfContract(threadId, operation, `failure ${failure._tag}`));
|
|
519
|
+
}
|
|
520
|
+
if (!isExpectedResult(response)) {
|
|
521
|
+
return Effect.fail(outOfContract(threadId, operation, `result ${response._tag}`));
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return Effect.succeed(response);
|
|
525
|
+
};
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const readPage = (
|
|
529
|
+
threadId: ThreadId,
|
|
530
|
+
options?: {
|
|
531
|
+
readonly afterSequence?: CanonicalSequence | undefined;
|
|
532
|
+
readonly limit?: number | undefined;
|
|
533
|
+
},
|
|
534
|
+
) =>
|
|
535
|
+
Effect.gen(function* () {
|
|
536
|
+
const request = ObservePageRequest.make({
|
|
537
|
+
...(options?.afterSequence === undefined
|
|
538
|
+
? {}
|
|
539
|
+
: { afterSequence: options.afterSequence }),
|
|
540
|
+
limit: options?.limit ?? 256,
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const encoded = yield* encodeObservePageRequest(request).pipe(
|
|
544
|
+
Effect.mapError((error) =>
|
|
545
|
+
HostProtocolError.make({
|
|
546
|
+
message: boundHostDiagnostic(`observePage request encode failed: ${error.message}`),
|
|
547
|
+
}),
|
|
548
|
+
),
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
const response = yield* call(threadId, "observePage", encoded);
|
|
552
|
+
|
|
553
|
+
const page = yield* expect(
|
|
554
|
+
threadId,
|
|
555
|
+
"observePage",
|
|
556
|
+
ObservedPage,
|
|
557
|
+
ClientObserveHostFailure,
|
|
558
|
+
)(response);
|
|
559
|
+
|
|
560
|
+
return page.records;
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const cancelProgress = (threadId: ThreadId, waiterId: string): Effect.Effect<void> =>
|
|
564
|
+
encodeCancelProgressRequest(CancelProgressRequest.make({ waiterId })).pipe(
|
|
565
|
+
Effect.mapError(() => undefined),
|
|
566
|
+
Effect.flatMap((encoded) => call(threadId, "cancelProgress", encoded)),
|
|
567
|
+
// Finalizers are uninterruptible; only the foreign RPC branch must remain
|
|
568
|
+
// interruptible so a lost cancellation reply cannot prevent local shutdown.
|
|
569
|
+
Effect.interruptible,
|
|
570
|
+
Effect.timeout(PROGRESS_CANCELLATION_TIMEOUT),
|
|
571
|
+
Effect.asVoid,
|
|
572
|
+
Effect.ignore,
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
return CloudflareThreadClient.of({
|
|
576
|
+
submit: <InputSchema extends Schema.Top>(
|
|
577
|
+
agent: DurableSubmitAgent<InputSchema>,
|
|
578
|
+
input: InputSchema["Type"],
|
|
579
|
+
options: DurableSubmitOptions,
|
|
580
|
+
) =>
|
|
581
|
+
Effect.gen(function* () {
|
|
582
|
+
// Client-side half of `DurableAgentRuntime.submit`'s input boundary: encode
|
|
583
|
+
// through the Agent's input schema and prove the canonical persistence bounds.
|
|
584
|
+
const encodedInput = yield* Schema.encodeEffect(agent.definition.input)(input).pipe(
|
|
585
|
+
Effect.mapError((cause) =>
|
|
586
|
+
AgentInputError.make({ message: `Unable to encode Agent input: ${cause.message}` }),
|
|
587
|
+
),
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
const inputPayload = yield* Schema.decodeUnknownEffect(PersistedJson)(
|
|
591
|
+
encodedInput,
|
|
592
|
+
).pipe(
|
|
593
|
+
Effect.mapError(() =>
|
|
594
|
+
AgentInputError.make({
|
|
595
|
+
message: "Agent input does not satisfy the canonical persistence bounds",
|
|
596
|
+
}),
|
|
597
|
+
),
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
const request = SubmitRequest.make({
|
|
601
|
+
agentId: agent.definition.id,
|
|
602
|
+
principal: options.principal,
|
|
603
|
+
idempotencyKey: options.idempotencyKey,
|
|
604
|
+
...(options.admissionGroup === undefined
|
|
605
|
+
? {}
|
|
606
|
+
: { admissionGroup: options.admissionGroup }),
|
|
607
|
+
...(options.admissionFence === undefined
|
|
608
|
+
? {}
|
|
609
|
+
: { admissionFence: options.admissionFence }),
|
|
610
|
+
...(options.workerAdmission === undefined
|
|
611
|
+
? {}
|
|
612
|
+
: { workerAdmission: options.workerAdmission }),
|
|
613
|
+
...(options.messageAdmission === undefined
|
|
614
|
+
? {}
|
|
615
|
+
: { messageAdmission: options.messageAdmission }),
|
|
616
|
+
definitions: options.definitions,
|
|
617
|
+
inputPayload,
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
const encoded = yield* encodeSubmitRequest(request).pipe(
|
|
621
|
+
Effect.mapError((error) =>
|
|
622
|
+
HostProtocolError.make({
|
|
623
|
+
message: boundHostDiagnostic(`submit request encode failed: ${error.message}`),
|
|
624
|
+
}),
|
|
625
|
+
),
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
const response = yield* call(options.threadId, "submit", encoded);
|
|
629
|
+
|
|
630
|
+
const succeeded = yield* expect(
|
|
631
|
+
options.threadId,
|
|
632
|
+
"submit",
|
|
633
|
+
SubmitSucceeded,
|
|
634
|
+
ClientSubmitHostFailure,
|
|
635
|
+
)(response);
|
|
636
|
+
|
|
637
|
+
return succeeded.receipt;
|
|
638
|
+
}),
|
|
639
|
+
|
|
640
|
+
submissionStatus: (receipt) =>
|
|
641
|
+
Effect.gen(function* () {
|
|
642
|
+
const encoded = yield* encodeReceipt(receipt).pipe(
|
|
643
|
+
Effect.mapError(() => HostProtocolError.make({ message: "Invalid receipt" })),
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
const response = yield* call(receipt.threadId, "submissionStatus", encoded);
|
|
647
|
+
|
|
648
|
+
const result = yield* expect(
|
|
649
|
+
receipt.threadId,
|
|
650
|
+
"submissionStatus",
|
|
651
|
+
SubmissionStatusResponse,
|
|
652
|
+
ClientAwaitHostFailure,
|
|
653
|
+
)(response);
|
|
654
|
+
|
|
655
|
+
return result.status;
|
|
656
|
+
}),
|
|
657
|
+
awaitSettlement: (receipt) =>
|
|
658
|
+
Effect.gen(function* () {
|
|
659
|
+
const encoded = yield* encodeReceipt(receipt).pipe(
|
|
660
|
+
Effect.mapError((error) =>
|
|
661
|
+
HostProtocolError.make({
|
|
662
|
+
message: boundHostDiagnostic(`receipt encode failed: ${error.message}`),
|
|
663
|
+
}),
|
|
664
|
+
),
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
const response = yield* call(receipt.threadId, "awaitSettlement", encoded);
|
|
668
|
+
|
|
669
|
+
const settled = yield* expect(
|
|
670
|
+
receipt.threadId,
|
|
671
|
+
"awaitSettlement",
|
|
672
|
+
SettlementReached,
|
|
673
|
+
ClientAwaitHostFailure,
|
|
674
|
+
)(response);
|
|
675
|
+
|
|
676
|
+
return settled.settlement;
|
|
677
|
+
}),
|
|
678
|
+
|
|
679
|
+
awaitProgress: (threadId, afterSequence) =>
|
|
680
|
+
Effect.gen(function* () {
|
|
681
|
+
const waiterId = yield* crypto.randomUUIDv4.pipe(
|
|
682
|
+
Effect.mapError((error) =>
|
|
683
|
+
HostProtocolError.make({
|
|
684
|
+
message: boundHostDiagnostic(
|
|
685
|
+
`awaitProgress cancellation identity generation failed: ${error.message}`,
|
|
686
|
+
),
|
|
687
|
+
}),
|
|
688
|
+
),
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
const request = AwaitProgressRequest.make({ afterSequence, waiterId });
|
|
692
|
+
|
|
693
|
+
const encoded = yield* encodeAwaitProgressRequest(request).pipe(
|
|
694
|
+
Effect.mapError((error) =>
|
|
695
|
+
HostProtocolError.make({
|
|
696
|
+
message: boundHostDiagnostic(
|
|
697
|
+
`awaitProgress request encode failed: ${error.message}`,
|
|
698
|
+
),
|
|
699
|
+
}),
|
|
700
|
+
),
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
const attempt = (retry: number): Effect.Effect<void, ClientProgressFailure> =>
|
|
704
|
+
call(threadId, "awaitProgress", encoded).pipe(
|
|
705
|
+
Effect.flatMap(
|
|
706
|
+
expect(threadId, "awaitProgress", ProgressObserved, ClientObserveHostFailure),
|
|
707
|
+
),
|
|
708
|
+
Effect.asVoid,
|
|
709
|
+
Effect.catchTag("ThreadClientError", (error) =>
|
|
710
|
+
error.retryable === true && error.overloaded !== true && retry < 5
|
|
711
|
+
? Effect.sleep(Duration.millis(10 * 2 ** retry)).pipe(
|
|
712
|
+
Effect.andThen(attempt(retry + 1)),
|
|
713
|
+
)
|
|
714
|
+
: Effect.fail(error),
|
|
715
|
+
),
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
yield* attempt(0).pipe(Effect.onInterrupt(() => cancelProgress(threadId, waiterId)));
|
|
719
|
+
}),
|
|
720
|
+
|
|
721
|
+
readPage,
|
|
722
|
+
|
|
723
|
+
readAll: (threadId) =>
|
|
724
|
+
Effect.gen(function* () {
|
|
725
|
+
const all: Array<CanonicalRecordEnvelope> = [];
|
|
726
|
+
let after: CanonicalSequence | undefined;
|
|
727
|
+
|
|
728
|
+
for (;;) {
|
|
729
|
+
const page = yield* readPage(threadId, { afterSequence: after, limit: 1_024 });
|
|
730
|
+
|
|
731
|
+
all.push(...page);
|
|
732
|
+
const last = page.at(-1);
|
|
733
|
+
|
|
734
|
+
if (page.length < 1_024 || last === undefined) return all;
|
|
735
|
+
after = last.sequence;
|
|
736
|
+
}
|
|
737
|
+
}),
|
|
738
|
+
|
|
739
|
+
abort: (threadId, command) =>
|
|
740
|
+
Effect.gen(function* () {
|
|
741
|
+
const encoded = yield* encodeAbortCommand(command).pipe(
|
|
742
|
+
Effect.mapError((error) =>
|
|
743
|
+
HostProtocolError.make({
|
|
744
|
+
message: boundHostDiagnostic(`abort command encode failed: ${error.message}`),
|
|
745
|
+
}),
|
|
746
|
+
),
|
|
747
|
+
);
|
|
748
|
+
|
|
749
|
+
const response = yield* call(threadId, "abort", encoded);
|
|
750
|
+
|
|
751
|
+
const recorded = yield* expect(
|
|
752
|
+
threadId,
|
|
753
|
+
"abort",
|
|
754
|
+
AbortRecorded,
|
|
755
|
+
ClientAbortHostFailure,
|
|
756
|
+
)(response);
|
|
757
|
+
|
|
758
|
+
return recorded.intent;
|
|
759
|
+
}),
|
|
760
|
+
|
|
761
|
+
resolveApproval: (threadId, command) =>
|
|
762
|
+
Effect.gen(function* () {
|
|
763
|
+
const encoded = yield* encodeApprovalDecisionCommand(command).pipe(
|
|
764
|
+
Effect.mapError((error) =>
|
|
765
|
+
HostProtocolError.make({
|
|
766
|
+
message: boundHostDiagnostic(`approval command encode failed: ${error.message}`),
|
|
767
|
+
}),
|
|
768
|
+
),
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
const response = yield* call(threadId, "resolveApproval", encoded);
|
|
772
|
+
|
|
773
|
+
const recorded = yield* expect(
|
|
774
|
+
threadId,
|
|
775
|
+
"resolveApproval",
|
|
776
|
+
ApprovalRecorded,
|
|
777
|
+
ClientApprovalHostFailure,
|
|
778
|
+
)(response);
|
|
779
|
+
|
|
780
|
+
return recorded.intent;
|
|
781
|
+
}),
|
|
782
|
+
|
|
783
|
+
resolveUnknown: (threadId, command) =>
|
|
784
|
+
Effect.gen(function* () {
|
|
785
|
+
const encoded = yield* encodeUnknownResolutionCommand(command).pipe(
|
|
786
|
+
Effect.mapError((error) =>
|
|
787
|
+
HostProtocolError.make({
|
|
788
|
+
message: boundHostDiagnostic(
|
|
789
|
+
`resolution command encode failed: ${error.message}`,
|
|
790
|
+
),
|
|
791
|
+
}),
|
|
792
|
+
),
|
|
793
|
+
);
|
|
794
|
+
|
|
795
|
+
const response = yield* call(threadId, "resolveUnknown", encoded);
|
|
796
|
+
|
|
797
|
+
const recorded = yield* expect(
|
|
798
|
+
threadId,
|
|
799
|
+
"resolveUnknown",
|
|
800
|
+
UnknownResolutionRecorded,
|
|
801
|
+
ClientUnknownHostFailure,
|
|
802
|
+
)(response);
|
|
803
|
+
|
|
804
|
+
return recorded.intent;
|
|
805
|
+
}),
|
|
806
|
+
});
|
|
807
|
+
}),
|
|
808
|
+
);
|
|
809
|
+
}
|