@effect-agent/platform-cloudflare 0.1.0-beta.92 → 0.1.0-beta.93

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.
@@ -17,7 +17,7 @@ import {
17
17
  Ref,
18
18
  Schema,
19
19
  Semaphore,
20
- type Scope,
20
+ Scope,
21
21
  } from "effect";
22
22
  import {
23
23
  BrowserActionResult,
@@ -348,11 +348,12 @@ export interface BrowserRunInteractiveBrowser {
348
348
  readonly offDisconnected: (listener: () => void) => void;
349
349
  }
350
350
 
351
- /** Host-supplied Browser Run binding projected into one fakeable launch operation. */
351
+ /** Host-supplied Browser Run allocation and connection transport. */
352
352
  export class BrowserRunInteractiveBinding extends Context.Service<
353
353
  BrowserRunInteractiveBinding,
354
354
  {
355
- readonly launch: (keepAliveMillis: number) => Promise<BrowserRunInteractiveBrowser>;
355
+ readonly acquire: (keepAliveMillis: number) => Promise<unknown>;
356
+ readonly connect: (sessionId: string) => Promise<BrowserRunInteractiveBrowser>;
356
357
  /** Success proves whole-browser termination or exact-session absence. */
357
358
  readonly closeSession: (
358
359
  sessionId: Redacted.Redacted<string>,
@@ -375,13 +376,10 @@ export class BrowserRunInteractiveBinding extends Context.Service<
375
376
  options.viewport === undefined ? undefined : yield* decodeViewport(options.viewport);
376
377
 
377
378
  return {
378
- launch: async (keepAliveMillis: number) =>
379
- makeProductionBrowser(
380
- await puppeteer.launch(options.browser, {
381
- keep_alive: keepAliveMillis,
382
- ...(viewport === undefined ? {} : { defaultViewport: { ...viewport } }),
383
- }),
384
- ),
379
+ acquire: async (keepAliveMillis: number) =>
380
+ (await puppeteer.acquire(options.browser, { keep_alive: keepAliveMillis })).sessionId,
381
+ connect: async (sessionId: string) =>
382
+ makeProductionBrowser(await puppeteer.connect(options.browser, sessionId), viewport),
385
383
  closeSession: (sessionId: Redacted.Redacted<string>) =>
386
384
  lifecycle
387
385
  .close(sessionId)
@@ -414,11 +412,21 @@ export interface BrowserRunInteractiveSession {
414
412
  readonly close: Effect.Effect<void, InteractiveBrowserError>;
415
413
  }
416
414
 
415
+ /** Persist the private cleanup identity before connecting or creating a page. */
416
+ export interface BrowserRunInteractiveAcquisition {
417
+ readonly sessionId: Redacted.Redacted<string>;
418
+ /** Connect at most once, within the acquisition's original scope and elapsed deadline. */
419
+ readonly connect: Effect.Effect<BrowserRunInteractiveSession, InteractiveBrowserError>;
420
+ }
421
+
417
422
  /** Cloudflare host authority kept separate from the provider-neutral browser handle. */
418
423
  export class BrowserRunInteractiveHost extends Context.Service<
419
424
  BrowserRunInteractiveHost,
420
425
  {
421
426
  readonly cleanupSemantics?: "confirmed-terminal";
427
+ readonly acquire: (
428
+ policy: InteractiveBrowserPolicy,
429
+ ) => Effect.Effect<BrowserRunInteractiveAcquisition, InteractiveBrowserError, Scope.Scope>;
422
430
  readonly open: (
423
431
  policy: InteractiveBrowserPolicy,
424
432
  ) => Effect.Effect<BrowserRunInteractiveSession, InteractiveBrowserError, Scope.Scope>;
@@ -1097,13 +1105,25 @@ const makeProductionPage = (page: Page): BrowserRunInteractivePage => {
1097
1105
  };
1098
1106
  };
1099
1107
 
1100
- const makeProductionContext = (context: BrowserContext): BrowserRunInteractiveContext => ({
1101
- newPage: async () => makeProductionPage(await context.newPage()),
1108
+ const makeProductionContext = (
1109
+ context: BrowserContext,
1110
+ viewport?: BrowserRunViewport,
1111
+ ): BrowserRunInteractiveContext => ({
1112
+ newPage: async () => {
1113
+ const page = await context.newPage();
1114
+
1115
+ if (viewport !== undefined) await page.setViewport(viewport);
1116
+
1117
+ return makeProductionPage(page);
1118
+ },
1102
1119
  close: () => context.close(),
1103
1120
  });
1104
1121
 
1105
- const makeProductionBrowser = (browser: Browser): BrowserRunInteractiveBrowser => ({
1106
- createContext: async () => makeProductionContext(await browser.createBrowserContext()),
1122
+ const makeProductionBrowser = (
1123
+ browser: Browser,
1124
+ viewport?: BrowserRunViewport,
1125
+ ): BrowserRunInteractiveBrowser => ({
1126
+ createContext: async () => makeProductionContext(await browser.createBrowserContext(), viewport),
1107
1127
  close: () => browser.close(),
1108
1128
  sessionId: () => browser.sessionId(),
1109
1129
  isConnected: () => browser.isConnected(),
@@ -1941,9 +1961,10 @@ const makeHostService = (
1941
1961
  ]);
1942
1962
  });
1943
1963
 
1944
- const open = Effect.fn("BrowserRunInteractiveHost.open")(function* (
1964
+ const acquire = Effect.fn("BrowserRunInteractiveHost.acquire")(function* (
1945
1965
  policy: InteractiveBrowserPolicy,
1946
- ): Effect.fn.Return<BrowserRunInteractiveSession, InteractiveBrowserError, Scope.Scope> {
1966
+ ): Effect.fn.Return<BrowserRunInteractiveAcquisition, InteractiveBrowserError, Scope.Scope> {
1967
+ const scope = yield* Scope.Scope;
1947
1968
  const fixedPolicy = yield* snapshotPolicy(policy);
1948
1969
  const startedAt = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
1949
1970
  // Late SDK replies outlive the opening fiber; cleanup retains this pass's clock.
@@ -1970,14 +1991,19 @@ const makeHostService = (
1970
1991
  : entry.close.pipe(Effect.catchCause(() => Effect.logWarning(entry.warning))),
1971
1992
  );
1972
1993
 
1973
- const browser = yield* Effect.acquireRelease(
1994
+ const sessionIdValue = yield* Effect.acquireRelease(
1974
1995
  withinDeadline(
1975
1996
  Effect.tryPromise({
1976
1997
  try: (signal) =>
1977
1998
  closeLateAcquisition(
1978
1999
  signal,
1979
- () => binding.launch(keepAliveMillis(fixedPolicy)),
1980
- (acquired) => runCleanup(closeAcquired(acquired)),
2000
+ () => binding.acquire(keepAliveMillis(fixedPolicy)),
2001
+ (id) =>
2002
+ runCleanup(
2003
+ Schema.decodeUnknownEffect(BrowserRunSessionId)(id).pipe(
2004
+ Effect.flatMap((value) => terminate(Redacted.make(value), [])),
2005
+ ),
2006
+ ),
1981
2007
  ),
1982
2008
  catch: (cause) =>
1983
2009
  isCapacityRefusal(cause)
@@ -1985,313 +2011,351 @@ const makeHostService = (
1985
2011
  implementation: browserRunInteractiveImplementation,
1986
2012
  message: "Browser Run has no capacity for a new browser session",
1987
2013
  })
1988
- : protocolError("Launching the Browser Run session failed", cause),
1989
- }),
2014
+ : protocolError("Acquiring the Browser Run session failed", cause),
2015
+ }).pipe(
2016
+ Effect.flatMap(Schema.decodeUnknownEffect(BrowserRunSessionId)),
2017
+ Effect.catchTag("SchemaError", () =>
2018
+ protocolError("The Browser Run session identity was malformed"),
2019
+ ),
2020
+ ),
1990
2021
  fixedPolicy,
1991
2022
  startedAt,
1992
2023
  ),
1993
- (acquired) => {
1994
- state.disconnected.value = true;
2024
+ (id) =>
2025
+ Effect.suspend(() => {
2026
+ state.closed.value = true;
2027
+ state.disconnected.value = true;
1995
2028
 
1996
- return lifecycle.managedTeardownInstalled
1997
- ? Effect.void
1998
- : closeAcquired(acquired).pipe(
1999
- Effect.catch(() => Effect.logWarning("Whole-browser cleanup remains unconfirmed")),
2000
- );
2001
- },
2029
+ return lifecycle.managedTeardownInstalled
2030
+ ? Effect.void
2031
+ : terminate(Redacted.make(id), []).pipe(
2032
+ Effect.catch(() => Effect.logWarning("Whole-browser cleanup remains unconfirmed")),
2033
+ );
2034
+ }),
2002
2035
  { interruptible: true },
2003
2036
  );
2004
2037
 
2005
- closers.push(closeEntry(browser.close, "Closing the interactive browser failed"));
2038
+ const connect = yield* Effect.cached(
2039
+ Effect.gen(function* (): Effect.fn.Return<
2040
+ BrowserRunInteractiveSession,
2041
+ InteractiveBrowserError,
2042
+ Scope.Scope
2043
+ > {
2044
+ const browser = yield* Effect.acquireRelease(
2045
+ withinDeadline(
2046
+ Effect.tryPromise({
2047
+ try: (signal) =>
2048
+ closeLateAcquisition(
2049
+ signal,
2050
+ () => binding.connect(sessionIdValue),
2051
+ (acquired) => runCleanup(closeAcquired(acquired)),
2052
+ ),
2053
+ catch: (cause) =>
2054
+ protocolError("Connecting to the Browser Run session failed", cause),
2055
+ }),
2056
+ fixedPolicy,
2057
+ startedAt,
2058
+ ),
2059
+ (acquired) =>
2060
+ releaseBeforeManaged(
2061
+ closeEntry(acquired.close, "Closing the local browser connection failed"),
2062
+ ),
2063
+ { interruptible: true },
2064
+ );
2006
2065
 
2007
- const disconnected = () => {
2008
- state.disconnected.value = true;
2009
- };
2066
+ closers.push(closeEntry(browser.close, "Closing the interactive browser failed"));
2010
2067
 
2011
- yield* Effect.acquireRelease(
2012
- Effect.try({
2013
- try: () => browser.onDisconnected(disconnected),
2014
- catch: (cause) => protocolError("Installing the browser disconnect listener failed", cause),
2015
- }),
2016
- () =>
2017
- releaseBeforeManaged(
2068
+ const disconnected = () => {
2069
+ state.disconnected.value = true;
2070
+ };
2071
+
2072
+ yield* Effect.acquireRelease(
2073
+ Effect.try({
2074
+ try: () => browser.onDisconnected(disconnected),
2075
+ catch: (cause) =>
2076
+ protocolError("Installing the browser disconnect listener failed", cause),
2077
+ }),
2078
+ () =>
2079
+ releaseBeforeManaged(
2080
+ syncCloseEntry(
2081
+ () => browser.offDisconnected(disconnected),
2082
+ "Removing the browser disconnect listener failed",
2083
+ ),
2084
+ ),
2085
+ );
2086
+ closers.push(
2018
2087
  syncCloseEntry(
2019
2088
  () => browser.offDisconnected(disconnected),
2020
2089
  "Removing the browser disconnect listener failed",
2021
2090
  ),
2022
- ),
2023
- );
2024
- closers.push(
2025
- syncCloseEntry(
2026
- () => browser.offDisconnected(disconnected),
2027
- "Removing the browser disconnect listener failed",
2028
- ),
2029
- );
2030
-
2031
- const connected = yield* Effect.try({
2032
- try: browser.isConnected,
2033
- catch: (cause) => protocolError("Reading the Browser Run connection state failed", cause),
2034
- });
2035
-
2036
- if (!connected) {
2037
- state.disconnected.value = true;
2038
-
2039
- return yield* expiredError();
2040
- }
2041
-
2042
- const sessionIdValue = yield* Effect.try({
2043
- try: browser.sessionId,
2044
- catch: (cause) => protocolError("Reading the Browser Run session identity failed", cause),
2045
- }).pipe(
2046
- Effect.flatMap((value) =>
2047
- Schema.decodeUnknownEffect(BrowserRunSessionId)(value).pipe(
2048
- Effect.mapError(() => protocolError("The Browser Run session identity was malformed")),
2049
- ),
2050
- ),
2051
- );
2091
+ );
2052
2092
 
2053
- const context = yield* Effect.acquireRelease(
2054
- withinDeadline(
2055
- Effect.tryPromise({
2056
- try: (signal) =>
2057
- closeLateAcquisition(signal, browser.createContext, (acquired) => acquired.close()),
2058
- catch: (cause) =>
2059
- state.disconnected.value || isRemoteClosure(cause)
2060
- ? expiredError()
2061
- : protocolError("Creating the browser context failed", cause),
2062
- }),
2063
- fixedPolicy,
2064
- startedAt,
2065
- ),
2066
- (acquired) =>
2067
- releaseBeforeManaged(
2068
- closeEntry(acquired.close, "Closing the interactive browser context failed"),
2069
- ),
2070
- { interruptible: true },
2071
- );
2093
+ const connected = yield* Effect.try({
2094
+ try: browser.isConnected,
2095
+ catch: (cause) => protocolError("Reading the Browser Run connection state failed", cause),
2096
+ });
2072
2097
 
2073
- closers.push(closeEntry(context.close, "Closing the interactive browser context failed"));
2098
+ if (!connected) {
2099
+ state.disconnected.value = true;
2100
+
2101
+ return yield* expiredError();
2102
+ }
2103
+
2104
+ const context = yield* Effect.acquireRelease(
2105
+ withinDeadline(
2106
+ Effect.tryPromise({
2107
+ try: (signal) =>
2108
+ closeLateAcquisition(signal, browser.createContext, (acquired) => acquired.close()),
2109
+ catch: (cause) =>
2110
+ state.disconnected.value || isRemoteClosure(cause)
2111
+ ? expiredError()
2112
+ : protocolError("Creating the browser context failed", cause),
2113
+ }),
2114
+ fixedPolicy,
2115
+ startedAt,
2116
+ ),
2117
+ (acquired) =>
2118
+ releaseBeforeManaged(
2119
+ closeEntry(acquired.close, "Closing the interactive browser context failed"),
2120
+ ),
2121
+ { interruptible: true },
2122
+ );
2074
2123
 
2075
- const page = yield* Effect.acquireRelease(
2076
- withinDeadline(
2077
- Effect.tryPromise({
2078
- try: (signal) =>
2079
- closeLateAcquisition(signal, context.newPage, (acquired) => acquired.close()),
2080
- catch: (cause) =>
2081
- state.disconnected.value || isRemoteClosure(cause)
2082
- ? expiredError()
2083
- : protocolError("Creating the browser page failed", cause),
2084
- }),
2085
- fixedPolicy,
2086
- startedAt,
2087
- ),
2088
- (acquired) =>
2089
- releaseBeforeManaged(
2090
- closeEntry(acquired.close, "Closing the interactive browser page failed"),
2091
- ),
2092
- { interruptible: true },
2093
- );
2124
+ closers.push(closeEntry(context.close, "Closing the interactive browser context failed"));
2125
+
2126
+ const page = yield* Effect.acquireRelease(
2127
+ withinDeadline(
2128
+ Effect.tryPromise({
2129
+ try: (signal) =>
2130
+ closeLateAcquisition(signal, context.newPage, (acquired) => acquired.close()),
2131
+ catch: (cause) =>
2132
+ state.disconnected.value || isRemoteClosure(cause)
2133
+ ? expiredError()
2134
+ : protocolError("Creating the browser page failed", cause),
2135
+ }),
2136
+ fixedPolicy,
2137
+ startedAt,
2138
+ ),
2139
+ (acquired) =>
2140
+ releaseBeforeManaged(
2141
+ closeEntry(acquired.close, "Closing the interactive browser page failed"),
2142
+ ),
2143
+ { interruptible: true },
2144
+ );
2094
2145
 
2095
- closers.push(closeEntry(page.close, "Closing the interactive browser page failed"));
2146
+ closers.push(closeEntry(page.close, "Closing the interactive browser page failed"));
2096
2147
 
2097
- yield* withinDeadline(
2098
- Effect.tryPromise({
2099
- try: () => page.setBypassServiceWorker(true),
2100
- catch: (cause) => protocolError("Bypassing browser service workers failed", cause),
2101
- }),
2102
- fixedPolicy,
2103
- startedAt,
2104
- );
2148
+ yield* withinDeadline(
2149
+ Effect.tryPromise({
2150
+ try: () => page.setBypassServiceWorker(true),
2151
+ catch: (cause) => protocolError("Bypassing browser service workers failed", cause),
2152
+ }),
2153
+ fixedPolicy,
2154
+ startedAt,
2155
+ );
2105
2156
 
2106
- const requestListener = makeRequestListener(fixedPolicy, state);
2157
+ const requestListener = makeRequestListener(fixedPolicy, state);
2107
2158
 
2108
- yield* Effect.acquireRelease(
2109
- Effect.try({
2110
- try: () => page.onRequest(requestListener),
2111
- catch: (cause) => protocolError("Installing the browser request listener failed", cause),
2112
- }),
2113
- () =>
2114
- releaseBeforeManaged(
2159
+ yield* Effect.acquireRelease(
2160
+ Effect.try({
2161
+ try: () => page.onRequest(requestListener),
2162
+ catch: (cause) =>
2163
+ protocolError("Installing the browser request listener failed", cause),
2164
+ }),
2165
+ () =>
2166
+ releaseBeforeManaged(
2167
+ syncCloseEntry(
2168
+ () => page.offRequest(requestListener),
2169
+ "Removing the browser request policy failed",
2170
+ ),
2171
+ ),
2172
+ );
2173
+ closers.push(
2115
2174
  syncCloseEntry(
2116
2175
  () => page.offRequest(requestListener),
2117
2176
  "Removing the browser request policy failed",
2118
2177
  ),
2119
- ),
2120
- );
2121
- closers.push(
2122
- syncCloseEntry(
2123
- () => page.offRequest(requestListener),
2124
- "Removing the browser request policy failed",
2125
- ),
2126
- );
2178
+ );
2127
2179
 
2128
- yield* withinDeadline(
2129
- Effect.tryPromise({
2130
- try: () => page.setRequestInterception(true),
2131
- catch: (cause) => protocolError("Installing the browser request policy failed", cause),
2132
- }),
2133
- fixedPolicy,
2134
- startedAt,
2135
- );
2180
+ yield* withinDeadline(
2181
+ Effect.tryPromise({
2182
+ try: () => page.setRequestInterception(true),
2183
+ catch: (cause) => protocolError("Installing the browser request policy failed", cause),
2184
+ }),
2185
+ fixedPolicy,
2186
+ startedAt,
2187
+ );
2136
2188
 
2137
- yield* withinDeadline(awaitPendingRequests(state), fixedPolicy, startedAt);
2138
- const setupFailure = stateFailure(state);
2189
+ yield* withinDeadline(awaitPendingRequests(state), fixedPolicy, startedAt);
2190
+ const setupFailure = stateFailure(state);
2139
2191
 
2140
- if (setupFailure !== undefined) return yield* setupFailure;
2192
+ if (setupFailure !== undefined) return yield* setupFailure;
2141
2193
 
2142
- const teardown = yield* Effect.uninterruptible(
2143
- Effect.gen(function* () {
2144
- const cached = yield* Effect.cached(terminate(Redacted.make(sessionIdValue), closers));
2145
-
2146
- lifecycle.managedTeardownInstalled = true;
2147
- yield* Effect.addFinalizer(() =>
2148
- Effect.uninterruptible(
2149
- Effect.sync(() => {
2150
- state.closed.value = true;
2151
- state.disconnected.value = true;
2152
- }).pipe(
2153
- Effect.andThen(cached),
2154
- Effect.catch(() => Effect.logWarning("Whole-browser cleanup remains unconfirmed")),
2155
- ),
2156
- ),
2194
+ const teardown = yield* Effect.uninterruptible(
2195
+ Effect.gen(function* () {
2196
+ const cached = yield* Effect.cached(terminate(Redacted.make(sessionIdValue), closers));
2197
+
2198
+ lifecycle.managedTeardownInstalled = true;
2199
+ yield* Effect.addFinalizer(() =>
2200
+ Effect.uninterruptible(
2201
+ Effect.sync(() => {
2202
+ state.closed.value = true;
2203
+ state.disconnected.value = true;
2204
+ }).pipe(
2205
+ Effect.andThen(cached),
2206
+ Effect.catch(() =>
2207
+ Effect.logWarning("Whole-browser cleanup remains unconfirmed"),
2208
+ ),
2209
+ ),
2210
+ ),
2211
+ );
2212
+
2213
+ return cached;
2214
+ }),
2157
2215
  );
2158
2216
 
2159
- return cached;
2160
- }),
2161
- );
2217
+ const close: Effect.Effect<void, InteractiveBrowserError> = Effect.uninterruptible(
2218
+ Effect.sync(() => {
2219
+ state.closed.value = true;
2220
+ state.disconnected.value = true;
2221
+ }).pipe(Effect.andThen(teardown)),
2222
+ );
2162
2223
 
2163
- const close: Effect.Effect<void, InteractiveBrowserError> = Effect.uninterruptible(
2164
- Effect.sync(() => {
2165
- state.closed.value = true;
2166
- state.disconnected.value = true;
2167
- }).pipe(Effect.andThen(teardown)),
2168
- );
2224
+ const runtime = yield* makeHandle(page, fixedPolicy, startedAt, state, close);
2225
+ const currentPagePreflight = decodeActionResult(page, fixedPolicy).pipe(Effect.asVoid);
2169
2226
 
2170
- const runtime = yield* makeHandle(page, fixedPolicy, startedAt, state, close);
2171
- const currentPagePreflight = decodeActionResult(page, fixedPolicy).pipe(Effect.asVoid);
2227
+ const requestFitsSession = (requestedMillis: number): Effect.Effect<void, BrowserFailure> =>
2228
+ remainingMillis(fixedPolicy, startedAt).pipe(
2229
+ Effect.flatMap((remaining) =>
2230
+ remaining > 0 && requestedMillis <= remaining
2231
+ ? Effect.void
2232
+ : Effect.fail(
2233
+ policyError("The host browser request exceeds the remaining session time"),
2234
+ ),
2235
+ ),
2236
+ );
2172
2237
 
2173
- const requestFitsSession = (requestedMillis: number): Effect.Effect<void, BrowserFailure> =>
2174
- remainingMillis(fixedPolicy, startedAt).pipe(
2175
- Effect.flatMap((remaining) =>
2176
- remaining > 0 && requestedMillis <= remaining
2177
- ? Effect.void
2178
- : Effect.fail(
2179
- policyError("The host browser request exceeds the remaining session time"),
2238
+ return {
2239
+ handle: runtime.handle,
2240
+ sessionId: Redacted.make(sessionIdValue),
2241
+ resizeViewport: (viewport) =>
2242
+ decodeViewport(viewport).pipe(
2243
+ Effect.flatMap((decoded) =>
2244
+ runtime.run(
2245
+ Effect.tryPromise({
2246
+ try: () => page.setViewport(decoded),
2247
+ catch: (cause) => {
2248
+ if (state.disconnected.value || isRemoteClosure(cause)) {
2249
+ state.disconnected.value = true;
2250
+
2251
+ return expiredError();
2252
+ }
2253
+
2254
+ return protocolError("Resizing the browser viewport failed", cause);
2255
+ },
2256
+ }),
2257
+ currentPagePreflight,
2258
+ false,
2259
+ ),
2180
2260
  ),
2181
- ),
2182
- );
2183
-
2184
- return {
2185
- handle: runtime.handle,
2186
- sessionId: Redacted.make(sessionIdValue),
2187
- resizeViewport: (viewport) =>
2188
- decodeViewport(viewport).pipe(
2189
- Effect.flatMap((decoded) =>
2190
- runtime.run(
2191
- Effect.tryPromise({
2192
- try: () => page.setViewport(decoded),
2193
- catch: (cause) => {
2194
- if (state.disconnected.value || isRemoteClosure(cause)) {
2195
- state.disconnected.value = true;
2196
-
2197
- return expiredError();
2198
- }
2199
-
2200
- return protocolError("Resizing the browser viewport failed", cause);
2201
- },
2202
- }),
2203
- currentPagePreflight,
2204
- false,
2205
2261
  ),
2206
- ),
2207
- ),
2208
- getLiveView: (request) =>
2209
- Schema.decodeEffect(BrowserRunLiveViewRequest)(request).pipe(
2210
- Effect.mapError(() => policyError("The Live View request is malformed")),
2211
- Effect.flatMap((decoded) =>
2212
- runtime.run(
2213
- cdpCommand(
2214
- page,
2215
- state,
2216
- "Cloudflare.getLiveView",
2217
- { mode: decoded.mode, expiresInMs: decoded.expiresInMs },
2218
- LiveViewObservation,
2219
- "Cloudflare returned a malformed Live View response",
2220
- ).pipe(
2221
- Effect.flatMap((observation) =>
2222
- Schema.decodeEffect(BrowserRunLiveViewResult)({
2223
- devtoolsFrontendUrl: Redacted.make(observation.devtoolsFrontendUrl),
2224
- }).pipe(
2225
- Effect.mapError(() =>
2226
- protocolError("Cloudflare returned a malformed Live View response"),
2262
+ getLiveView: (request) =>
2263
+ Schema.decodeEffect(BrowserRunLiveViewRequest)(request).pipe(
2264
+ Effect.mapError(() => policyError("The Live View request is malformed")),
2265
+ Effect.flatMap((decoded) =>
2266
+ runtime.run(
2267
+ cdpCommand(
2268
+ page,
2269
+ state,
2270
+ "Cloudflare.getLiveView",
2271
+ { mode: decoded.mode, expiresInMs: decoded.expiresInMs },
2272
+ LiveViewObservation,
2273
+ "Cloudflare returned a malformed Live View response",
2274
+ ).pipe(
2275
+ Effect.flatMap((observation) =>
2276
+ Schema.decodeEffect(BrowserRunLiveViewResult)({
2277
+ devtoolsFrontendUrl: Redacted.make(observation.devtoolsFrontendUrl),
2278
+ }).pipe(
2279
+ Effect.mapError(() =>
2280
+ protocolError("Cloudflare returned a malformed Live View response"),
2281
+ ),
2282
+ ),
2227
2283
  ),
2228
2284
  ),
2285
+ currentPagePreflight.pipe(
2286
+ Effect.andThen(requestFitsSession(decoded.expiresInMs)),
2287
+ ),
2229
2288
  ),
2230
2289
  ),
2231
- currentPagePreflight.pipe(Effect.andThen(requestFitsSession(decoded.expiresInMs))),
2232
2290
  ),
2233
- ),
2234
- ),
2235
- handoff: (request) =>
2236
- Schema.decodeEffect(BrowserRunHandoffRequest)(request).pipe(
2237
- Effect.mapError(() => policyError("The browser handoff request is malformed")),
2238
- Effect.flatMap((decoded) =>
2239
- runtime.run(
2240
- cdpCommand(
2241
- page,
2242
- state,
2243
- "Cloudflare.handoff",
2244
- { instructions: decoded.instructions, timeout: decoded.timeout },
2245
- HandoffObservation,
2246
- "Cloudflare returned a malformed browser handoff response",
2247
- ).pipe(
2248
- Effect.flatMap((observation) =>
2249
- Schema.decodeEffect(BrowserRunHandoffResult)({
2250
- handoffId: Redacted.make(observation.handoffId),
2251
- }).pipe(
2252
- Effect.mapError(() =>
2253
- protocolError("Cloudflare returned a malformed browser handoff response"),
2291
+ handoff: (request) =>
2292
+ Schema.decodeEffect(BrowserRunHandoffRequest)(request).pipe(
2293
+ Effect.mapError(() => policyError("The browser handoff request is malformed")),
2294
+ Effect.flatMap((decoded) =>
2295
+ runtime.run(
2296
+ cdpCommand(
2297
+ page,
2298
+ state,
2299
+ "Cloudflare.handoff",
2300
+ { instructions: decoded.instructions, timeout: decoded.timeout },
2301
+ HandoffObservation,
2302
+ "Cloudflare returned a malformed browser handoff response",
2303
+ ).pipe(
2304
+ Effect.flatMap((observation) =>
2305
+ Schema.decodeEffect(BrowserRunHandoffResult)({
2306
+ handoffId: Redacted.make(observation.handoffId),
2307
+ }).pipe(
2308
+ Effect.mapError(() =>
2309
+ protocolError("Cloudflare returned a malformed browser handoff response"),
2310
+ ),
2311
+ ),
2254
2312
  ),
2255
2313
  ),
2314
+ currentPagePreflight.pipe(Effect.andThen(requestFitsSession(decoded.timeout))),
2256
2315
  ),
2257
2316
  ),
2258
- currentPagePreflight.pipe(Effect.andThen(requestFitsSession(decoded.timeout))),
2259
2317
  ),
2260
- ),
2261
- ),
2262
- getHandoffState: runtime.run(
2263
- cdpCommand(
2264
- page,
2265
- state,
2266
- "Cloudflare.getHandoffState",
2267
- {},
2268
- HandoffStateObservation,
2269
- "Cloudflare returned a malformed browser handoff state",
2270
- ).pipe(
2271
- Effect.flatMap((observation) =>
2272
- Schema.decodeEffect(BrowserRunHandoffState)({
2273
- active: observation.active,
2274
- ...(observation.handoffId === undefined
2275
- ? {}
2276
- : { handoffId: Redacted.make(observation.handoffId) }),
2277
- ...(observation.durationMs === undefined
2278
- ? {}
2279
- : { durationMs: observation.durationMs }),
2280
- }).pipe(
2281
- Effect.mapError(() =>
2282
- protocolError("Cloudflare returned a malformed browser handoff state"),
2318
+ getHandoffState: runtime.run(
2319
+ cdpCommand(
2320
+ page,
2321
+ state,
2322
+ "Cloudflare.getHandoffState",
2323
+ {},
2324
+ HandoffStateObservation,
2325
+ "Cloudflare returned a malformed browser handoff state",
2326
+ ).pipe(
2327
+ Effect.flatMap((observation) =>
2328
+ Schema.decodeEffect(BrowserRunHandoffState)({
2329
+ active: observation.active,
2330
+ ...(observation.handoffId === undefined
2331
+ ? {}
2332
+ : { handoffId: Redacted.make(observation.handoffId) }),
2333
+ ...(observation.durationMs === undefined
2334
+ ? {}
2335
+ : { durationMs: observation.durationMs }),
2336
+ }).pipe(
2337
+ Effect.mapError(() =>
2338
+ protocolError("Cloudflare returned a malformed browser handoff state"),
2339
+ ),
2340
+ ),
2283
2341
  ),
2284
2342
  ),
2343
+ currentPagePreflight,
2285
2344
  ),
2286
- ),
2287
- currentPagePreflight,
2288
- ),
2289
- close,
2345
+ close,
2346
+ };
2347
+ }).pipe(Effect.provideService(Scope.Scope, scope)),
2348
+ );
2349
+
2350
+ return {
2351
+ sessionId: Redacted.make(sessionIdValue),
2352
+ connect: Effect.suspend(() => (state.closed.value ? expiredError() : connect)),
2290
2353
  };
2291
2354
  });
2292
2355
 
2293
2356
  return BrowserRunInteractiveHost.of({
2294
- open,
2357
+ acquire,
2358
+ open: (policy) => acquire(policy).pipe(Effect.flatMap((acquired) => acquired.connect)),
2295
2359
  closeSession,
2296
2360
  cleanupSemantics: "confirmed-terminal",
2297
2361
  });