@effect-agent/platform-cloudflare 0.1.0-beta.121 → 0.1.0-beta.123

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.
Files changed (35) hide show
  1. package/dist/BrowserCredentials.d.mts +91 -0
  2. package/dist/BrowserCredentials.mjs +120 -0
  3. package/dist/BrowserCredentials.mjs.map +1 -0
  4. package/dist/BrowserSession-DuOUcC6G.mjs +442 -0
  5. package/dist/BrowserSession-DuOUcC6G.mjs.map +1 -0
  6. package/dist/BrowserSession-DxCN-5F9.d.mts +109 -0
  7. package/dist/BrowserSession.d.mts +3 -0
  8. package/dist/BrowserSession.mjs +4 -0
  9. package/dist/CloudflareThreadClient.d.mts +50 -50
  10. package/dist/{InteractiveBrowser-Cm4xgiGy.d.mts → InteractiveBrowser-BnqY_Y-F.d.mts} +2 -2
  11. package/dist/{InteractiveBrowser-DhOkdUx9.mjs → InteractiveBrowser-DeOlcu-2.mjs} +190 -50
  12. package/dist/InteractiveBrowser-DeOlcu-2.mjs.map +1 -0
  13. package/dist/InteractiveBrowser.d.mts +1 -1
  14. package/dist/InteractiveBrowser.mjs +1 -1
  15. package/dist/{ThreadObject-mt0uxvZZ.d.mts → ThreadObject-BTo1jqab.d.mts} +33 -33
  16. package/dist/ThreadObject.d.mts +1 -1
  17. package/dist/index.d.mts +4 -2
  18. package/dist/index.mjs +3 -1
  19. package/package.json +1 -1
  20. package/src/BrowserCredentials.ts +146 -0
  21. package/src/BrowserSession.ts +589 -0
  22. package/src/InteractiveBrowser.ts +1 -1
  23. package/src/index.ts +2 -0
  24. package/src/internal/browser-binding.ts +245 -57
  25. package/src/internal/browser-credentials.ts +362 -0
  26. package/dist/InteractiveBrowser-DhOkdUx9.mjs.map +0 -1
  27. package/dist/ProtectedBrowser.d.mts +0 -128
  28. package/dist/ProtectedBrowser.mjs +0 -1201
  29. package/dist/ProtectedBrowser.mjs.map +0 -1
  30. package/src/ProtectedBrowser.ts +0 -9
  31. package/src/protected-browser/binding.ts +0 -366
  32. package/src/protected-browser/host.ts +0 -382
  33. package/src/protected-browser/inspect-frame.ts +0 -140
  34. package/src/protected-browser/native.ts +0 -483
  35. package/src/protected-browser/policy.ts +0 -872
@@ -1,366 +0,0 @@
1
- /// <reference types="@cloudflare/workers-types" />
2
-
3
- import { Cause, Context, Crypto, Effect, Layer, Redacted, Schema, type Scope } from "effect";
4
- import { type InteractiveBrowserPolicy } from "effect-agent/interactive-browser";
5
- import { ProtectedBrowserError } from "effect-agent/protected-browser";
6
- import {
7
- type Browser,
8
- type Page,
9
- type CDPSession,
10
- } from "puppeteer-core/lib/esm/puppeteer/puppeteer-core-browser.js";
11
-
12
- import { BrowserRunBinding } from "../internal/browser-binding.ts";
13
- import {
14
- browserFailure,
15
- BrowserRunFailure,
16
- reportBrowserCause,
17
- reportedBrowserError,
18
- } from "../internal/browser-failure.ts";
19
- import { BrowserRunSessionLifecycle } from "../internal/browser-session-lifecycle.ts";
20
- import { makeProtectedNativeTransport, ProtectedNativeSession } from "./native.ts";
21
- import {
22
- BrowserRunProtectedTransport,
23
- ProtectedTransportError,
24
- type ProtectedBrowserTransport,
25
- } from "./policy.ts";
26
-
27
- /** Host-private exact page identity; never include it in model-visible results. */
28
- export const ProtectedProviderIdentity = Schema.Struct({
29
- sessionId: Schema.Redacted(Schema.String.check(Schema.isUUID())),
30
- contextId: Schema.Redacted(Schema.NonEmptyString.check(Schema.isMaxLength(256))),
31
- targetId: Schema.Redacted(Schema.NonEmptyString.check(Schema.isMaxLength(256))),
32
- });
33
-
34
- export type ProtectedProviderIdentity = typeof ProtectedProviderIdentity.Type;
35
-
36
- export interface ProtectedProviderSession {
37
- readonly identity: ProtectedProviderIdentity;
38
- readonly driver: ProtectedBrowserTransport;
39
- readonly command: (
40
- method: "Cloudflare.getLiveView" | "Cloudflare.handoff" | "Cloudflare.getHandoffState",
41
- parameters: Readonly<Record<string, string | number>>,
42
- ) => Effect.Effect<unknown, ProtectedBrowserError>;
43
- readonly detach: Effect.Effect<void, ProtectedBrowserError>;
44
- }
45
-
46
- export class BrowserRunProtectedBinding extends Context.Service<
47
- BrowserRunProtectedBinding,
48
- {
49
- readonly open: (
50
- policy: InteractiveBrowserPolicy,
51
- identity?: ProtectedProviderIdentity,
52
- ) => Effect.Effect<ProtectedProviderSession, ProtectedBrowserError, Scope.Scope>;
53
- }
54
- >()("@effect-agent/platform-cloudflare/BrowserRunProtectedBinding") {}
55
-
56
- /**
57
- * Acquires through BROWSER with recording=false explicitly on the wire. The trusted provider's
58
- * documented opt-in semantics are the recording guarantee; no recording-status API exists.
59
- * Account operators remain trusted, and must not attach external observers to private passes.
60
- */
61
- const protectedBindingLayer = (options: { readonly browser: Pick<BrowserRun, "fetch"> }) =>
62
- Layer.effect(BrowserRunProtectedBinding)(
63
- Effect.gen(function* () {
64
- const lifecycle = yield* BrowserRunSessionLifecycle;
65
- const binding = yield* BrowserRunBinding;
66
- const crypto = yield* Crypto.Crypto;
67
-
68
- const open = Effect.fn("BrowserRunProtectedTransport.open")(function* (
69
- policy: InteractiveBrowserPolicy,
70
- identity?: ProtectedProviderIdentity,
71
- ): Effect.fn.Return<ProtectedProviderSession, ProtectedBrowserError, Scope.Scope> {
72
- const failure = () =>
73
- new ProtectedBrowserError({
74
- reason: "provider",
75
- dispatch: "not-dispatched",
76
- milestone: "none",
77
- observation: "closed",
78
- cleanup: "unconfirmed",
79
- });
80
-
81
- let sessionId: Redacted.Redacted<string> | undefined = identity?.sessionId;
82
- let detached = false;
83
- let control: CDPSession | undefined;
84
- let providerIdentity: ProtectedProviderIdentity | undefined;
85
- let browser: Browser | undefined;
86
- let driver: ProtectedBrowserTransport | undefined;
87
- let invalid = false;
88
- // SDK acquisition may finish after interruption. Its late-reply callback must await cleanup,
89
- // retaining this pass's clock and private reporting scope.
90
- const runCleanup = Effect.runPromiseWith(yield* Effect.context<never>());
91
-
92
- const terminate = Effect.gen(function* () {
93
- invalid = true;
94
- driver?.invalidate();
95
- if (sessionId === undefined) return "unconfirmed" as const;
96
-
97
- // A failed resume has not acquired the host's retained provider. Release only
98
- // this connection; successful attachments and new allocations own termination.
99
- const cleanup = yield* identity !== undefined && driver === undefined
100
- ? Effect.succeed("unconfirmed" as const)
101
- : lifecycle.close(sessionId).pipe(
102
- Effect.as("confirmed" as const),
103
- Effect.catchCause((cause) =>
104
- reportBrowserCause("protected.close", cause).pipe(
105
- Effect.as("unconfirmed" as const),
106
- ),
107
- ),
108
- Effect.interruptible,
109
- Effect.timeoutOrElse({
110
- duration: "10 seconds",
111
- orElse: () =>
112
- reportBrowserCause(
113
- "protected.close",
114
- Cause.fail(
115
- new BrowserRunFailure({ operation: "protected.close", reason: "timeout" }),
116
- ),
117
- ).pipe(Effect.as("unconfirmed" as const)),
118
- }),
119
- );
120
-
121
- // Local disconnect is not remote-closure evidence. Do it even when confirmation fails.
122
- const connected = browser;
123
-
124
- if (connected !== undefined)
125
- yield* Effect.promise(() => connected.disconnect()).pipe(
126
- Effect.catchCause((cause) => reportBrowserCause("protected.disconnect", cause)),
127
- Effect.interruptible,
128
- Effect.timeoutOrElse({
129
- duration: "1 second",
130
- orElse: () =>
131
- reportBrowserCause(
132
- "protected.disconnect",
133
- Cause.fail(
134
- new BrowserRunFailure({
135
- operation: "protected.disconnect",
136
- reason: "timeout",
137
- }),
138
- ),
139
- ),
140
- }),
141
- );
142
-
143
- return cleanup;
144
- });
145
-
146
- const close = yield* Effect.cached(terminate);
147
-
148
- yield* Effect.addFinalizer(() => (detached ? Effect.void : close));
149
- let stage = identity === undefined ? "protected.acquire" : "protected.connect";
150
-
151
- const acquired = yield* Effect.tryPromise({
152
- try: async (signal) => {
153
- try {
154
- sessionId =
155
- identity === undefined
156
- ? Redacted.make(
157
- await binding.acquire(
158
- Math.min(600_000, Math.max(10_000, policy.maxElapsedMillis)),
159
- "protected.acquire",
160
- ),
161
- )
162
- : identity.sessionId;
163
- if (signal.aborted || invalid) {
164
- await runCleanup(terminate);
165
- throw new ProtectedTransportError({ reason: "stale-reference" });
166
- }
167
- // Resume only a host-persisted exact page. Never open a replacement page or context.
168
- stage = "protected.connect";
169
- browser = await binding.connect(Redacted.value(sessionId), stage, signal);
170
- if (signal.aborted || invalid) {
171
- await runCleanup(terminate);
172
- throw new ProtectedTransportError({ reason: "stale-reference" });
173
- }
174
- let page: Page;
175
-
176
- if (identity === undefined) {
177
- stage = "protected.context";
178
- const context = await browser.createBrowserContext();
179
-
180
- stage = "protected.page";
181
- page = await context.newPage();
182
- } else {
183
- stage = "protected.resume";
184
-
185
- const context = browser
186
- .browserContexts()
187
- .find((candidate) => candidate.id === Redacted.value(identity.contextId));
188
-
189
- if (context === undefined)
190
- throw new ProtectedTransportError({ reason: "stale-reference" });
191
- const pages = await context.pages();
192
- let found: Page | undefined;
193
-
194
- for (const candidate of pages) {
195
- const client = await candidate.createCDPSession();
196
- const info = await client.send("Target.getTargetInfo");
197
-
198
- await client.detach();
199
- if (info.targetInfo.targetId === Redacted.value(identity.targetId))
200
- found = candidate;
201
- }
202
- if (found === undefined || pages.length !== 1)
203
- throw new ProtectedTransportError({ reason: "stale-reference" });
204
- page = found;
205
- }
206
- stage = "protected.control";
207
- control = await page.createCDPSession();
208
- const info = await control.send("Target.getTargetInfo");
209
-
210
- stage = "protected.identity";
211
- providerIdentity = Schema.decodeSync(ProtectedProviderIdentity)({
212
- sessionId,
213
- contextId: Redacted.make(page.browserContext().id ?? ""),
214
- targetId: Redacted.make(info.targetInfo.targetId),
215
- });
216
-
217
- stage = "protected.interception";
218
- await page.setBypassServiceWorker(true);
219
- await page.setRequestInterception(true);
220
- page.on("request", (request) => {
221
- let allowed = policy.network._tag === "Unrestricted";
222
-
223
- try {
224
- const url = new URL(request.url());
225
-
226
- allowed ||=
227
- policy.network._tag === "ExactHosts" &&
228
- url.protocol === "https:" &&
229
- !url.username &&
230
- !url.password &&
231
- policy.network.allowedHosts.includes(url.host);
232
- } catch {
233
- /* Refuse malformed destinations. */
234
- }
235
- void (
236
- allowed && !invalid ? request.continue() : request.abort("blockedbyclient")
237
- ).catch(async (cause) => {
238
- invalid = true;
239
- driver?.invalidate();
240
- await runCleanup(reportBrowserCause("protected.interception", Cause.fail(cause)));
241
- });
242
- });
243
- if (signal.aborted || invalid) {
244
- await runCleanup(terminate);
245
- throw new ProtectedTransportError({ reason: "stale-reference" });
246
- }
247
-
248
- return ProtectedNativeSession.of({
249
- browser,
250
- page,
251
- close,
252
- release: Effect.suspend(() => (detached ? Effect.void : close.pipe(Effect.asVoid))),
253
- });
254
- } catch (cause) {
255
- if (signal.aborted && !(cause instanceof ProtectedTransportError))
256
- await runCleanup(reportBrowserCause(stage, Cause.fail(cause)));
257
- throw cause;
258
- }
259
- },
260
- catch: (cause) => browserFailure(stage, cause),
261
- }).pipe(
262
- Effect.timeoutOrElse({
263
- duration: Math.min(policy.maxElapsedMillis, 30_000),
264
- orElse: () =>
265
- Effect.fail(new BrowserRunFailure({ operation: stage, reason: "timeout" })),
266
- }),
267
- Effect.catch((error) =>
268
- reportBrowserCause(stage, Cause.fail(error)).pipe(
269
- Effect.andThen(close),
270
- Effect.flatMap((cleanup) =>
271
- Effect.fail(
272
- reportedBrowserError(
273
- new ProtectedBrowserError({
274
- ...failure(),
275
- reason: error.reason === "timeout" ? "timeout" : "provider",
276
- cleanup:
277
- identity !== undefined && driver === undefined ? "not-requested" : cleanup,
278
- }),
279
- ),
280
- ),
281
- ),
282
- ),
283
- ),
284
- );
285
-
286
- driver = yield* makeProtectedNativeTransport(policy).pipe(
287
- Effect.provideService(ProtectedNativeSession, acquired),
288
- Effect.provideService(Crypto.Crypto, crypto),
289
- );
290
-
291
- const exact = providerIdentity;
292
-
293
- if (exact === undefined) return yield* failure();
294
-
295
- return {
296
- identity: exact,
297
- driver,
298
- command: (method, parameters) =>
299
- Effect.tryPromise({
300
- try: async () => {
301
- if (invalid || detached || control === undefined) throw undefined;
302
-
303
- // Cloudflare extends CDP with host-only methods absent from upstream ProtocolMapping.
304
- const send = Reflect.get(control, "send");
305
-
306
- return await Reflect.apply(send, control, [method, parameters]);
307
- },
308
- catch: (cause) => browserFailure("protected.command", cause),
309
- }).pipe(
310
- Effect.timeoutOrElse({
311
- duration: "10 seconds",
312
- orElse: () =>
313
- Effect.fail(
314
- new BrowserRunFailure({ operation: "protected.command", reason: "timeout" }),
315
- ),
316
- }),
317
- Effect.catch((error) =>
318
- reportBrowserCause("protected.command", Cause.fail(error)).pipe(
319
- Effect.andThen(Effect.fail(reportedBrowserError(failure()))),
320
- ),
321
- ),
322
- ),
323
- detach: Effect.tryPromise({
324
- try: async () => {
325
- if (invalid || detached || browser === undefined) throw undefined;
326
- // Stop this attachment before another controller can attach. The browser remains live.
327
- await browser.disconnect();
328
- detached = true;
329
- driver?.invalidate();
330
- },
331
- catch: (cause) => browserFailure("protected.detach", cause),
332
- }).pipe(
333
- Effect.timeoutOrElse({
334
- duration: "10 seconds",
335
- orElse: () =>
336
- Effect.fail(
337
- new BrowserRunFailure({ operation: "protected.detach", reason: "timeout" }),
338
- ),
339
- }),
340
- Effect.catch((error) =>
341
- reportBrowserCause("protected.detach", Cause.fail(error)).pipe(
342
- Effect.andThen(Effect.fail(reportedBrowserError(failure()))),
343
- ),
344
- ),
345
- ),
346
- };
347
- }, Effect.withTracerEnabled(false));
348
-
349
- return { open };
350
- }),
351
- ).pipe(Layer.provide(BrowserRunBinding.layer(options.browser)));
352
-
353
- /** One binding implementation serves scoped tools and host-managed protected handoffs. */
354
- export const browserRunProtectedBindingLayer = (options: {
355
- readonly browser: Pick<BrowserRun, "fetch">;
356
- }) =>
357
- Layer.effect(BrowserRunProtectedTransport)(
358
- Effect.gen(function* () {
359
- const binding = yield* BrowserRunProtectedBinding;
360
-
361
- return {
362
- open: (policy: InteractiveBrowserPolicy) =>
363
- binding.open(policy).pipe(Effect.map((session) => session.driver)),
364
- };
365
- }),
366
- ).pipe(Layer.provideMerge(protectedBindingLayer(options)));