@effect-agent/platform-cloudflare 0.1.0-beta.29 → 0.1.0-beta.31
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/browser-quick-action.d.mts +7 -3
- package/dist/browser-quick-action.mjs +138 -2
- package/dist/browser-quick-action.mjs.map +1 -1
- package/dist/browser-rest-capture.d.mts +19 -0
- package/dist/browser-rest-capture.mjs +226 -0
- package/dist/browser-rest-capture.mjs.map +1 -0
- package/dist/browser-rest-crawl.d.mts +16 -0
- package/dist/browser-rest-crawl.mjs +243 -0
- package/dist/browser-rest-crawl.mjs.map +1 -0
- package/dist/index.d.mts +40 -37
- package/dist/index.mjs +5 -2
- package/dist/index.mjs.map +1 -1
- package/dist/interactive-browser.d.mts +49 -0
- package/dist/interactive-browser.mjs +376 -0
- package/dist/interactive-browser.mjs.map +1 -0
- package/package.json +22 -9
- package/src/browser-quick-action.ts +218 -2
- package/src/browser-rest-capture.ts +420 -0
- package/src/browser-rest-crawl.ts +497 -0
- package/src/index.ts +3 -0
- package/src/interactive-browser.ts +748 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Context, Layer } from "effect";
|
|
2
|
+
import { InteractiveBrowser, SandboxImplementation } from "@effect-agent/sandbox";
|
|
3
|
+
//#region src/interactive-browser.d.ts
|
|
4
|
+
declare const browserRunInteractiveImplementation: SandboxImplementation;
|
|
5
|
+
/** One intercepted Puppeteer request, narrowed to the policy-relevant surface. */
|
|
6
|
+
interface BrowserRunInteractiveRequest {
|
|
7
|
+
readonly url: () => string;
|
|
8
|
+
readonly abort: () => Promise<void>;
|
|
9
|
+
readonly continue: () => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
type BrowserRunInteractiveRequestListener = (request: BrowserRunInteractiveRequest) => void;
|
|
12
|
+
/** Narrow page boundary used by deterministic tests; SDK values remain in this package. */
|
|
13
|
+
interface BrowserRunInteractivePage {
|
|
14
|
+
readonly close: () => Promise<void>;
|
|
15
|
+
readonly setBypassServiceWorker: (enabled: boolean) => Promise<void>;
|
|
16
|
+
readonly setRequestInterception: (enabled: boolean) => Promise<void>;
|
|
17
|
+
readonly onRequest: (listener: BrowserRunInteractiveRequestListener) => void;
|
|
18
|
+
readonly offRequest: (listener: BrowserRunInteractiveRequestListener) => void;
|
|
19
|
+
readonly goto: (url: string) => Promise<void>;
|
|
20
|
+
readonly url: () => unknown;
|
|
21
|
+
readonly readText: (selector: string | undefined, maximumBytes: number) => Promise<unknown>;
|
|
22
|
+
readonly fill: (selector: string, value: string) => Promise<void>;
|
|
23
|
+
readonly click: (selector: string) => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
interface BrowserRunInteractiveContext {
|
|
26
|
+
readonly newPage: () => Promise<BrowserRunInteractivePage>;
|
|
27
|
+
readonly close: () => Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
interface BrowserRunInteractiveBrowser {
|
|
30
|
+
readonly createContext: () => Promise<BrowserRunInteractiveContext>;
|
|
31
|
+
readonly close: () => Promise<void>;
|
|
32
|
+
readonly isConnected: () => boolean;
|
|
33
|
+
readonly onDisconnected: (listener: () => void) => void;
|
|
34
|
+
readonly offDisconnected: (listener: () => void) => void;
|
|
35
|
+
}
|
|
36
|
+
declare const BrowserRunInteractiveBinding_base: Context.ServiceClass<BrowserRunInteractiveBinding, "@effect-agent/platform-cloudflare/BrowserRunInteractiveBinding", {
|
|
37
|
+
readonly launch: (keepAliveMillis: number) => Promise<BrowserRunInteractiveBrowser>;
|
|
38
|
+
}>;
|
|
39
|
+
/** Host-supplied Browser Run binding projected into one fakeable launch operation. */
|
|
40
|
+
declare class BrowserRunInteractiveBinding extends BrowserRunInteractiveBinding_base {
|
|
41
|
+
static layer(options: {
|
|
42
|
+
readonly browser: BrowserRun;
|
|
43
|
+
}): Layer.Layer<BrowserRunInteractiveBinding>;
|
|
44
|
+
}
|
|
45
|
+
/** Worker-only Cloudflare Puppeteer adapter; the caller supplies the Browser Run binding Layer. */
|
|
46
|
+
declare const browserRunInteractiveLayer: () => Layer.Layer<InteractiveBrowser, never, BrowserRunInteractiveBinding>;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { BrowserRunInteractiveBinding, BrowserRunInteractiveBrowser, BrowserRunInteractiveContext, BrowserRunInteractivePage, BrowserRunInteractiveRequest, BrowserRunInteractiveRequestListener, browserRunInteractiveImplementation, browserRunInteractiveLayer };
|
|
49
|
+
//# sourceMappingURL=interactive-browser.d.mts.map
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { Context, Duration, Effect, Layer, Option, Ref, Schema, Semaphore } from "effect";
|
|
2
|
+
import { BrowserActionResult, BrowserNavigationResult, BrowserTextResult, InteractiveBrowser, InteractiveBrowserActionError, InteractiveBrowserBusyError, InteractiveBrowserCapacityError, InteractiveBrowserExpiredError, InteractiveBrowserLimitError, InteractiveBrowserPolicy, InteractiveBrowserPolicyDeniedError, InteractiveBrowserProtocolError, SandboxImplementation } from "@effect-agent/sandbox";
|
|
3
|
+
import puppeteer from "@cloudflare/puppeteer";
|
|
4
|
+
//#region src/interactive-browser.ts
|
|
5
|
+
const browserRunInteractiveImplementation = SandboxImplementation.make({
|
|
6
|
+
isolation: "isolated",
|
|
7
|
+
identity: "cloudflare-browser-run-interactive"
|
|
8
|
+
});
|
|
9
|
+
const MIN_KEEP_ALIVE_MILLIS = 1e4;
|
|
10
|
+
const MAX_KEEP_ALIVE_MILLIS = 6e5;
|
|
11
|
+
const BoundedRemoteText = Schema.String.check(Schema.isMaxLength(8 * 1024 * 1024));
|
|
12
|
+
const TextObservation = Schema.Union([
|
|
13
|
+
Schema.Struct({
|
|
14
|
+
_tag: Schema.Literal("Text"),
|
|
15
|
+
text: BoundedRemoteText
|
|
16
|
+
}),
|
|
17
|
+
Schema.Struct({ _tag: Schema.Literal("MissingElement") }),
|
|
18
|
+
Schema.Struct({
|
|
19
|
+
_tag: Schema.Literal("OverLimit"),
|
|
20
|
+
observed: Schema.Natural
|
|
21
|
+
})
|
|
22
|
+
]);
|
|
23
|
+
/** Host-supplied Browser Run binding projected into one fakeable launch operation. */
|
|
24
|
+
var BrowserRunInteractiveBinding = class BrowserRunInteractiveBinding extends Context.Service()("@effect-agent/platform-cloudflare/BrowserRunInteractiveBinding") {
|
|
25
|
+
static layer(options) {
|
|
26
|
+
return Layer.succeed(BrowserRunInteractiveBinding)({ launch: async (keepAliveMillis) => makeProductionBrowser(await puppeteer.launch(options.browser, { keep_alive: keepAliveMillis })) });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const makeProductionRequest = (request) => ({
|
|
30
|
+
url: () => request.url(),
|
|
31
|
+
abort: () => request.abort("blockedbyclient"),
|
|
32
|
+
continue: () => request.continue()
|
|
33
|
+
});
|
|
34
|
+
const makeProductionPage = (page) => {
|
|
35
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
36
|
+
return {
|
|
37
|
+
close: () => page.close(),
|
|
38
|
+
setBypassServiceWorker: (enabled) => page.setBypassServiceWorker(enabled),
|
|
39
|
+
setRequestInterception: (enabled) => page.setRequestInterception(enabled),
|
|
40
|
+
onRequest: (listener) => {
|
|
41
|
+
const sdkListener = (request) => listener(makeProductionRequest(request));
|
|
42
|
+
listeners.set(listener, sdkListener);
|
|
43
|
+
page.on("request", sdkListener);
|
|
44
|
+
},
|
|
45
|
+
offRequest: (listener) => {
|
|
46
|
+
const sdkListener = listeners.get(listener);
|
|
47
|
+
if (sdkListener !== void 0) {
|
|
48
|
+
page.off("request", sdkListener);
|
|
49
|
+
listeners.delete(listener);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
goto: async (url) => {
|
|
53
|
+
await page.goto(url, {
|
|
54
|
+
waitUntil: "networkidle0",
|
|
55
|
+
timeout: 0
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
url: () => page.url(),
|
|
59
|
+
readText: (selector, maximumBytes) => page.evaluate((requestedSelector, maximum) => {
|
|
60
|
+
const pageDocument = Reflect.get(globalThis, "document");
|
|
61
|
+
const element = requestedSelector === void 0 ? Reflect.get(pageDocument, "body") : Reflect.apply(Reflect.get(pageDocument, "querySelector"), pageDocument, [requestedSelector]);
|
|
62
|
+
if (element === null) return { _tag: "MissingElement" };
|
|
63
|
+
const innerText = Reflect.get(element, "innerText");
|
|
64
|
+
const textContent = Reflect.get(element, "textContent");
|
|
65
|
+
const text = typeof innerText === "string" ? innerText : typeof textContent === "string" ? textContent : "";
|
|
66
|
+
const observed = new TextEncoder().encode(text).byteLength;
|
|
67
|
+
return observed > maximum ? {
|
|
68
|
+
_tag: "OverLimit",
|
|
69
|
+
observed
|
|
70
|
+
} : {
|
|
71
|
+
_tag: "Text",
|
|
72
|
+
text
|
|
73
|
+
};
|
|
74
|
+
}, selector, maximumBytes),
|
|
75
|
+
fill: async (selector, value) => {
|
|
76
|
+
await page.$eval(selector, (element, nextValue) => {
|
|
77
|
+
if (!("value" in element)) throw new Error("The selector did not resolve to a fillable field");
|
|
78
|
+
const focus = Reflect.get(element, "focus");
|
|
79
|
+
if (typeof focus === "function") Reflect.apply(focus, element, []);
|
|
80
|
+
Reflect.set(element, "value", nextValue);
|
|
81
|
+
const dispatchEvent = Reflect.get(element, "dispatchEvent");
|
|
82
|
+
if (typeof dispatchEvent === "function") {
|
|
83
|
+
Reflect.apply(dispatchEvent, element, [new Event("input", { bubbles: true })]);
|
|
84
|
+
Reflect.apply(dispatchEvent, element, [new Event("change", { bubbles: true })]);
|
|
85
|
+
}
|
|
86
|
+
}, value);
|
|
87
|
+
},
|
|
88
|
+
click: (selector) => page.click(selector)
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
const makeProductionContext = (context) => ({
|
|
92
|
+
newPage: async () => makeProductionPage(await context.newPage()),
|
|
93
|
+
close: () => context.close()
|
|
94
|
+
});
|
|
95
|
+
const makeProductionBrowser = (browser) => ({
|
|
96
|
+
createContext: async () => makeProductionContext(await browser.createBrowserContext()),
|
|
97
|
+
close: () => browser.close(),
|
|
98
|
+
isConnected: () => browser.isConnected(),
|
|
99
|
+
onDisconnected: (listener) => {
|
|
100
|
+
browser.on("disconnected", listener);
|
|
101
|
+
},
|
|
102
|
+
offDisconnected: (listener) => {
|
|
103
|
+
browser.off("disconnected", listener);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
const protocolError = (message, cause) => InteractiveBrowserProtocolError.make({
|
|
107
|
+
implementation: browserRunInteractiveImplementation,
|
|
108
|
+
message,
|
|
109
|
+
...cause === void 0 ? {} : { cause }
|
|
110
|
+
});
|
|
111
|
+
const actionError = (operation, cause) => InteractiveBrowserActionError.make({
|
|
112
|
+
implementation: browserRunInteractiveImplementation,
|
|
113
|
+
operation,
|
|
114
|
+
message: `The interactive browser ${operation} operation failed`,
|
|
115
|
+
...cause === void 0 ? {} : { cause }
|
|
116
|
+
});
|
|
117
|
+
const policyError = (message) => InteractiveBrowserPolicyDeniedError.make({
|
|
118
|
+
implementation: browserRunInteractiveImplementation,
|
|
119
|
+
message
|
|
120
|
+
});
|
|
121
|
+
const expiredError = () => InteractiveBrowserExpiredError.make({
|
|
122
|
+
implementation: browserRunInteractiveImplementation,
|
|
123
|
+
message: "The remote browser is no longer usable"
|
|
124
|
+
});
|
|
125
|
+
const causeText = (cause) => {
|
|
126
|
+
if (cause instanceof Error) return cause.message.slice(0, 8e3);
|
|
127
|
+
return String(cause).slice(0, 8e3);
|
|
128
|
+
};
|
|
129
|
+
const isCapacityRefusal = (cause) => /(^|\D)429(\D|$)|browser time limit|capacity|too many concurrent/i.test(causeText(cause));
|
|
130
|
+
const isRemoteClosure = (cause) => /target closed|browser.*closed|session.*closed|connection.*closed|not connected|websocket.*closed/i.test(causeText(cause));
|
|
131
|
+
const snapshotPolicy = (input) => Schema.decodeUnknownEffect(InteractiveBrowserPolicy)(input).pipe(Effect.mapError(() => policyError("The interactive browser policy is malformed")), Effect.map((decoded) => Object.freeze({
|
|
132
|
+
allowedHosts: Object.freeze([...decoded.allowedHosts]),
|
|
133
|
+
maxActions: decoded.maxActions,
|
|
134
|
+
maxElapsedMillis: decoded.maxElapsedMillis,
|
|
135
|
+
maxReturnedBytes: decoded.maxReturnedBytes
|
|
136
|
+
})));
|
|
137
|
+
const hostAllowed = (policy, value) => {
|
|
138
|
+
try {
|
|
139
|
+
const url = new URL(value);
|
|
140
|
+
return url.protocol === "https:" && url.username === "" && url.password === "" && policy.allowedHosts.some((host) => host === url.host);
|
|
141
|
+
} catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const keepAliveMillis = (policy) => Math.max(MIN_KEEP_ALIVE_MILLIS, Math.min(MAX_KEEP_ALIVE_MILLIS, policy.maxElapsedMillis));
|
|
146
|
+
const closeLateAcquisition = async (signal, acquire) => {
|
|
147
|
+
const acquired = await acquire();
|
|
148
|
+
if (!signal.aborted) return acquired;
|
|
149
|
+
try {
|
|
150
|
+
await acquired.close();
|
|
151
|
+
} catch {}
|
|
152
|
+
throw new Error("The interrupted browser acquisition completed late");
|
|
153
|
+
};
|
|
154
|
+
const closeWithWarning = (close, warning) => Effect.tryPromise({
|
|
155
|
+
try: close,
|
|
156
|
+
catch: () => protocolError(warning)
|
|
157
|
+
}).pipe(Effect.catchCause(() => Effect.logWarning(warning)));
|
|
158
|
+
const deadlineError = Effect.fn("BrowserRunInteractive.deadlineError")(function* (policy, startedAt) {
|
|
159
|
+
const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
160
|
+
return yield* InteractiveBrowserLimitError.make({
|
|
161
|
+
implementation: browserRunInteractiveImplementation,
|
|
162
|
+
limit: "elapsed",
|
|
163
|
+
maximum: policy.maxElapsedMillis,
|
|
164
|
+
observed: Math.max(policy.maxElapsedMillis, now - startedAt),
|
|
165
|
+
message: "The browser elapsed-time limit was reached"
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
const withinDeadline = Effect.fn("BrowserRunInteractive.withinDeadline")(function* (effect, policy, startedAt, onTimeout) {
|
|
169
|
+
const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
170
|
+
const elapsed = Math.max(0, now - startedAt);
|
|
171
|
+
const remaining = policy.maxElapsedMillis - elapsed;
|
|
172
|
+
if (remaining <= 0) return yield* deadlineError(policy, startedAt);
|
|
173
|
+
return yield* effect.pipe(Effect.timeoutOrElse({
|
|
174
|
+
duration: Duration.millis(remaining),
|
|
175
|
+
orElse: () => Effect.sync(() => onTimeout?.()).pipe(Effect.andThen(deadlineError(policy, startedAt)))
|
|
176
|
+
}));
|
|
177
|
+
});
|
|
178
|
+
const stateFailure = (state) => {
|
|
179
|
+
if (state.violation.value !== void 0) return state.violation.value;
|
|
180
|
+
if (state.disconnected.value || state.uncertain.value) return expiredError();
|
|
181
|
+
};
|
|
182
|
+
const awaitPendingRequests = (state) => Effect.suspend(() => {
|
|
183
|
+
const pending = [...state.pendingRequests];
|
|
184
|
+
return pending.length === 0 ? Effect.void : Effect.promise(() => Promise.all(pending)).pipe(Effect.asVoid, Effect.andThen(awaitPendingRequests(state)));
|
|
185
|
+
});
|
|
186
|
+
const decodeNavigationResult = Effect.fn("BrowserRunInteractive.decodeNavigationResult")(function* (page, policy) {
|
|
187
|
+
const url = yield* Effect.try({
|
|
188
|
+
try: page.url,
|
|
189
|
+
catch: (cause) => protocolError("Reading the browser navigation URL failed", cause)
|
|
190
|
+
});
|
|
191
|
+
const result = yield* Schema.decodeUnknownEffect(BrowserNavigationResult)({ url }).pipe(Effect.mapError((cause) => protocolError("The browser returned a malformed navigation URL", cause)));
|
|
192
|
+
if (!hostAllowed(policy, result.url)) return yield* policyError("The browser returned an off-policy page URL");
|
|
193
|
+
return result;
|
|
194
|
+
});
|
|
195
|
+
const decodeActionResult = Effect.fn("BrowserRunInteractive.decodeActionResult")(function* (page, policy) {
|
|
196
|
+
const url = yield* Effect.try({
|
|
197
|
+
try: page.url,
|
|
198
|
+
catch: (cause) => protocolError("Reading the browser page URL failed", cause)
|
|
199
|
+
});
|
|
200
|
+
const result = yield* Schema.decodeUnknownEffect(BrowserActionResult)({ url }).pipe(Effect.mapError((cause) => protocolError("The browser returned a malformed page URL", cause)));
|
|
201
|
+
if (!hostAllowed(policy, result.url)) return yield* policyError("The browser returned an off-policy page URL");
|
|
202
|
+
return result;
|
|
203
|
+
});
|
|
204
|
+
const makeRequestListener = (policy, state) => (request) => {
|
|
205
|
+
let allowed = false;
|
|
206
|
+
try {
|
|
207
|
+
allowed = hostAllowed(policy, request.url());
|
|
208
|
+
} catch {
|
|
209
|
+
allowed = false;
|
|
210
|
+
}
|
|
211
|
+
if (!allowed) state.violation.value = policyError("The browser requested an off-policy URL");
|
|
212
|
+
let settlement;
|
|
213
|
+
try {
|
|
214
|
+
settlement = allowed ? request.continue() : request.abort();
|
|
215
|
+
} catch {
|
|
216
|
+
state.violation.value = protocolError("Resolving an intercepted browser request failed");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const observed = settlement.catch(() => {
|
|
220
|
+
state.violation.value = protocolError("Resolving an intercepted browser request failed");
|
|
221
|
+
}).finally(() => {
|
|
222
|
+
state.pendingRequests.delete(observed);
|
|
223
|
+
});
|
|
224
|
+
state.pendingRequests.add(observed);
|
|
225
|
+
};
|
|
226
|
+
const makeHandle = Effect.fn("BrowserRunInteractive.makeHandle")(function* (page, policy, startedAt, state) {
|
|
227
|
+
const permits = yield* Semaphore.make(1);
|
|
228
|
+
const actions = yield* Ref.make(0);
|
|
229
|
+
const remote = (operation, evaluate) => Effect.tryPromise({
|
|
230
|
+
try: evaluate,
|
|
231
|
+
catch: (cause) => {
|
|
232
|
+
if (state.disconnected.value || isRemoteClosure(cause)) {
|
|
233
|
+
state.disconnected.value = true;
|
|
234
|
+
return expiredError();
|
|
235
|
+
}
|
|
236
|
+
return actionError(operation, cause);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
const run = (effect, preflight = Effect.void) => permits.withPermitsIfAvailable(1)(Effect.gen(function* () {
|
|
240
|
+
const unavailable = stateFailure(state);
|
|
241
|
+
if (unavailable !== void 0) return yield* unavailable;
|
|
242
|
+
yield* preflight;
|
|
243
|
+
const admitted = yield* Ref.modify(actions, (count) => count >= policy.maxActions ? [{
|
|
244
|
+
allowed: false,
|
|
245
|
+
observed: count + 1
|
|
246
|
+
}, count] : [{
|
|
247
|
+
allowed: true,
|
|
248
|
+
observed: count + 1
|
|
249
|
+
}, count + 1]);
|
|
250
|
+
if (!admitted.allowed) return yield* InteractiveBrowserLimitError.make({
|
|
251
|
+
implementation: browserRunInteractiveImplementation,
|
|
252
|
+
limit: "actions",
|
|
253
|
+
maximum: policy.maxActions,
|
|
254
|
+
observed: admitted.observed,
|
|
255
|
+
message: "The browser action limit was reached"
|
|
256
|
+
});
|
|
257
|
+
const completed = effect.pipe(Effect.catch((error) => {
|
|
258
|
+
const failure = stateFailure(state);
|
|
259
|
+
return Effect.fail(failure ?? error);
|
|
260
|
+
}), Effect.flatMap((result) => awaitPendingRequests(state).pipe(Effect.flatMap(() => {
|
|
261
|
+
const failure = stateFailure(state);
|
|
262
|
+
return failure === void 0 ? Effect.succeed(result) : Effect.fail(failure);
|
|
263
|
+
}))));
|
|
264
|
+
return yield* withinDeadline(completed, policy, startedAt, () => {
|
|
265
|
+
state.uncertain.value = true;
|
|
266
|
+
}).pipe(Effect.onInterrupt(() => Effect.sync(() => {
|
|
267
|
+
state.uncertain.value = true;
|
|
268
|
+
})), Effect.catch((error) => {
|
|
269
|
+
if (Schema.is(InteractiveBrowserLimitError)(error) && error.limit === "elapsed") return Effect.fail(error);
|
|
270
|
+
const failure = stateFailure(state);
|
|
271
|
+
return Effect.fail(failure ?? error);
|
|
272
|
+
}));
|
|
273
|
+
})).pipe(Effect.flatMap((result) => Option.isSome(result) ? Effect.succeed(result.value) : Effect.fail(InteractiveBrowserBusyError.make({
|
|
274
|
+
implementation: browserRunInteractiveImplementation,
|
|
275
|
+
message: "The browser handle already has an operation in flight"
|
|
276
|
+
}))));
|
|
277
|
+
return {
|
|
278
|
+
navigate: (request) => run(Effect.gen(function* () {
|
|
279
|
+
yield* remote("navigate", () => page.goto(request.url));
|
|
280
|
+
return yield* decodeNavigationResult(page, policy);
|
|
281
|
+
}), Effect.suspend(() => hostAllowed(policy, request.url) ? Effect.void : Effect.fail(policyError("The navigation URL is outside the browser policy")))),
|
|
282
|
+
readText: (request) => run(Effect.gen(function* () {
|
|
283
|
+
const raw = yield* remote("read-text", () => page.readText(request.selector, policy.maxReturnedBytes));
|
|
284
|
+
const observation = yield* Schema.decodeUnknownEffect(TextObservation)(raw).pipe(Effect.mapError((cause) => protocolError("The browser returned a malformed text observation", cause)));
|
|
285
|
+
if (observation._tag === "MissingElement") return yield* actionError("read-text");
|
|
286
|
+
if (observation._tag === "OverLimit") return yield* InteractiveBrowserLimitError.make({
|
|
287
|
+
implementation: browserRunInteractiveImplementation,
|
|
288
|
+
limit: "returned-bytes",
|
|
289
|
+
maximum: policy.maxReturnedBytes,
|
|
290
|
+
observed: observation.observed,
|
|
291
|
+
message: "The browser returned-text limit was reached"
|
|
292
|
+
});
|
|
293
|
+
const observed = new TextEncoder().encode(observation.text).byteLength;
|
|
294
|
+
if (observed > policy.maxReturnedBytes) return yield* InteractiveBrowserLimitError.make({
|
|
295
|
+
implementation: browserRunInteractiveImplementation,
|
|
296
|
+
limit: "returned-bytes",
|
|
297
|
+
maximum: policy.maxReturnedBytes,
|
|
298
|
+
observed,
|
|
299
|
+
message: "The browser returned-text limit was reached"
|
|
300
|
+
});
|
|
301
|
+
return yield* Schema.decodeUnknownEffect(BrowserTextResult)({ text: observation.text }).pipe(Effect.mapError((cause) => protocolError("The browser returned malformed page text", cause)));
|
|
302
|
+
})),
|
|
303
|
+
fill: (request) => run(remote("fill", () => page.fill(request.selector, request.value)).pipe(Effect.andThen(decodeActionResult(page, policy)))),
|
|
304
|
+
click: (request) => run(remote("click", () => page.click(request.selector)).pipe(Effect.andThen(decodeActionResult(page, policy))))
|
|
305
|
+
};
|
|
306
|
+
});
|
|
307
|
+
/** Worker-only Cloudflare Puppeteer adapter; the caller supplies the Browser Run binding Layer. */
|
|
308
|
+
const browserRunInteractiveLayer = () => Layer.effect(InteractiveBrowser, Effect.gen(function* () {
|
|
309
|
+
const binding = yield* BrowserRunInteractiveBinding;
|
|
310
|
+
return InteractiveBrowser.of({ open: (policy) => Effect.gen(function* () {
|
|
311
|
+
const fixedPolicy = yield* snapshotPolicy(policy);
|
|
312
|
+
const startedAt = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
313
|
+
const state = {
|
|
314
|
+
disconnected: { value: false },
|
|
315
|
+
uncertain: { value: false },
|
|
316
|
+
violation: { value: void 0 },
|
|
317
|
+
pendingRequests: /* @__PURE__ */ new Set()
|
|
318
|
+
};
|
|
319
|
+
const browser = yield* Effect.acquireRelease(withinDeadline(Effect.tryPromise({
|
|
320
|
+
try: (signal) => closeLateAcquisition(signal, () => binding.launch(keepAliveMillis(fixedPolicy))),
|
|
321
|
+
catch: (cause) => isCapacityRefusal(cause) ? InteractiveBrowserCapacityError.make({
|
|
322
|
+
implementation: browserRunInteractiveImplementation,
|
|
323
|
+
message: "Browser Run has no capacity for a new browser session"
|
|
324
|
+
}) : protocolError("Launching the Browser Run session failed", cause)
|
|
325
|
+
}), fixedPolicy, startedAt), (acquired) => Effect.sync(() => {
|
|
326
|
+
state.disconnected.value = true;
|
|
327
|
+
}).pipe(Effect.andThen(closeWithWarning(acquired.close, "Closing the interactive browser failed"))), { interruptible: true });
|
|
328
|
+
const disconnected = () => {
|
|
329
|
+
state.disconnected.value = true;
|
|
330
|
+
};
|
|
331
|
+
yield* Effect.acquireRelease(Effect.try({
|
|
332
|
+
try: () => browser.onDisconnected(disconnected),
|
|
333
|
+
catch: (cause) => protocolError("Installing the browser disconnect listener failed", cause)
|
|
334
|
+
}), () => Effect.sync(() => {
|
|
335
|
+
browser.offDisconnected(disconnected);
|
|
336
|
+
}).pipe(Effect.catchCause(() => Effect.logWarning("Removing the browser disconnect listener failed"))));
|
|
337
|
+
if (!(yield* Effect.try({
|
|
338
|
+
try: browser.isConnected,
|
|
339
|
+
catch: (cause) => protocolError("Reading the Browser Run connection state failed", cause)
|
|
340
|
+
}))) {
|
|
341
|
+
state.disconnected.value = true;
|
|
342
|
+
return yield* expiredError();
|
|
343
|
+
}
|
|
344
|
+
const context = yield* Effect.acquireRelease(withinDeadline(Effect.tryPromise({
|
|
345
|
+
try: (signal) => closeLateAcquisition(signal, browser.createContext),
|
|
346
|
+
catch: (cause) => state.disconnected.value || isRemoteClosure(cause) ? expiredError() : protocolError("Creating the browser context failed", cause)
|
|
347
|
+
}), fixedPolicy, startedAt), (acquired) => closeWithWarning(acquired.close, "Closing the interactive browser context failed"), { interruptible: true });
|
|
348
|
+
const page = yield* Effect.acquireRelease(withinDeadline(Effect.tryPromise({
|
|
349
|
+
try: (signal) => closeLateAcquisition(signal, context.newPage),
|
|
350
|
+
catch: (cause) => state.disconnected.value || isRemoteClosure(cause) ? expiredError() : protocolError("Creating the browser page failed", cause)
|
|
351
|
+
}), fixedPolicy, startedAt), (acquired) => closeWithWarning(acquired.close, "Closing the interactive browser page failed"), { interruptible: true });
|
|
352
|
+
yield* withinDeadline(Effect.tryPromise({
|
|
353
|
+
try: () => page.setBypassServiceWorker(true),
|
|
354
|
+
catch: (cause) => protocolError("Bypassing browser service workers failed", cause)
|
|
355
|
+
}), fixedPolicy, startedAt);
|
|
356
|
+
const requestListener = makeRequestListener(fixedPolicy, state);
|
|
357
|
+
yield* Effect.acquireRelease(Effect.try({
|
|
358
|
+
try: () => page.onRequest(requestListener),
|
|
359
|
+
catch: (cause) => protocolError("Installing the browser request listener failed", cause)
|
|
360
|
+
}), () => Effect.sync(() => {
|
|
361
|
+
page.offRequest(requestListener);
|
|
362
|
+
}).pipe(Effect.catchCause(() => Effect.logWarning("Removing the browser request policy failed"))));
|
|
363
|
+
yield* withinDeadline(Effect.tryPromise({
|
|
364
|
+
try: () => page.setRequestInterception(true),
|
|
365
|
+
catch: (cause) => protocolError("Installing the browser request policy failed", cause)
|
|
366
|
+
}), fixedPolicy, startedAt);
|
|
367
|
+
yield* withinDeadline(awaitPendingRequests(state), fixedPolicy, startedAt);
|
|
368
|
+
const setupFailure = stateFailure(state);
|
|
369
|
+
if (setupFailure !== void 0) return yield* setupFailure;
|
|
370
|
+
return yield* makeHandle(page, fixedPolicy, startedAt, state);
|
|
371
|
+
}) });
|
|
372
|
+
}));
|
|
373
|
+
//#endregion
|
|
374
|
+
export { BrowserRunInteractiveBinding, browserRunInteractiveImplementation, browserRunInteractiveLayer };
|
|
375
|
+
|
|
376
|
+
//# sourceMappingURL=interactive-browser.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactive-browser.mjs","names":[],"sources":["../src/interactive-browser.ts"],"sourcesContent":["/// <reference types=\"@cloudflare/workers-types\" />\n\nimport puppeteer, {\n type Browser,\n type BrowserContext,\n type HTTPRequest,\n type Page,\n} from \"@cloudflare/puppeteer\";\nimport {\n BrowserActionResult,\n BrowserNavigationResult,\n BrowserTextResult,\n InteractiveBrowser,\n InteractiveBrowserActionError,\n InteractiveBrowserBusyError,\n InteractiveBrowserCapacityError,\n InteractiveBrowserExpiredError,\n InteractiveBrowserLimitError,\n InteractiveBrowserPolicy,\n InteractiveBrowserPolicyDeniedError,\n InteractiveBrowserProtocolError,\n SandboxImplementation,\n type BrowserHandle,\n type InteractiveBrowserError,\n} from \"@effect-agent/sandbox\";\nimport { Context, Duration, Effect, Layer, Option, Ref, Schema, Semaphore } from \"effect\";\n\nexport const browserRunInteractiveImplementation = SandboxImplementation.make({\n isolation: \"isolated\",\n identity: \"cloudflare-browser-run-interactive\",\n});\n\nconst MIN_KEEP_ALIVE_MILLIS = 10_000;\nconst MAX_KEEP_ALIVE_MILLIS = 600_000;\nconst MAX_TEXT_LENGTH = 8 * 1024 * 1024;\nconst BoundedRemoteText = Schema.String.check(Schema.isMaxLength(MAX_TEXT_LENGTH));\nconst TextObservation = Schema.Union([\n Schema.Struct({ _tag: Schema.Literal(\"Text\"), text: BoundedRemoteText }),\n Schema.Struct({ _tag: Schema.Literal(\"MissingElement\") }),\n Schema.Struct({ _tag: Schema.Literal(\"OverLimit\"), observed: Schema.Natural }),\n]);\n\ntype BrowserFailure = typeof InteractiveBrowserError.Type;\ntype BrowserOperation = InteractiveBrowserActionError[\"operation\"];\n\ninterface InteractiveBrowserPolicySnapshot {\n readonly allowedHosts: ReadonlyArray<string>;\n readonly maxActions: number;\n readonly maxElapsedMillis: number;\n readonly maxReturnedBytes: number;\n}\n\n/** One intercepted Puppeteer request, narrowed to the policy-relevant surface. */\nexport interface BrowserRunInteractiveRequest {\n readonly url: () => string;\n readonly abort: () => Promise<void>;\n readonly continue: () => Promise<void>;\n}\n\nexport type BrowserRunInteractiveRequestListener = (request: BrowserRunInteractiveRequest) => void;\n\n/** Narrow page boundary used by deterministic tests; SDK values remain in this package. */\nexport interface BrowserRunInteractivePage {\n readonly close: () => Promise<void>;\n readonly setBypassServiceWorker: (enabled: boolean) => Promise<void>;\n readonly setRequestInterception: (enabled: boolean) => Promise<void>;\n readonly onRequest: (listener: BrowserRunInteractiveRequestListener) => void;\n readonly offRequest: (listener: BrowserRunInteractiveRequestListener) => void;\n readonly goto: (url: string) => Promise<void>;\n readonly url: () => unknown;\n readonly readText: (selector: string | undefined, maximumBytes: number) => Promise<unknown>;\n readonly fill: (selector: string, value: string) => Promise<void>;\n readonly click: (selector: string) => Promise<void>;\n}\n\nexport interface BrowserRunInteractiveContext {\n readonly newPage: () => Promise<BrowserRunInteractivePage>;\n readonly close: () => Promise<void>;\n}\n\nexport interface BrowserRunInteractiveBrowser {\n readonly createContext: () => Promise<BrowserRunInteractiveContext>;\n readonly close: () => Promise<void>;\n readonly isConnected: () => boolean;\n readonly onDisconnected: (listener: () => void) => void;\n readonly offDisconnected: (listener: () => void) => void;\n}\n\n/** Host-supplied Browser Run binding projected into one fakeable launch operation. */\nexport class BrowserRunInteractiveBinding extends Context.Service<\n BrowserRunInteractiveBinding,\n {\n readonly launch: (keepAliveMillis: number) => Promise<BrowserRunInteractiveBrowser>;\n }\n>()(\"@effect-agent/platform-cloudflare/BrowserRunInteractiveBinding\") {\n static layer(options: {\n readonly browser: BrowserRun;\n }): Layer.Layer<BrowserRunInteractiveBinding> {\n return Layer.succeed(BrowserRunInteractiveBinding)({\n launch: async (keepAliveMillis) =>\n makeProductionBrowser(\n await puppeteer.launch(options.browser, {\n keep_alive: keepAliveMillis,\n }),\n ),\n });\n }\n}\n\nconst makeProductionRequest = (request: HTTPRequest): BrowserRunInteractiveRequest => ({\n url: () => request.url(),\n abort: () => request.abort(\"blockedbyclient\"),\n continue: () => request.continue(),\n});\n\nconst makeProductionPage = (page: Page): BrowserRunInteractivePage => {\n const listeners = new Map<BrowserRunInteractiveRequestListener, (request: HTTPRequest) => void>();\n return {\n close: () => page.close(),\n setBypassServiceWorker: (enabled) => page.setBypassServiceWorker(enabled),\n setRequestInterception: (enabled) => page.setRequestInterception(enabled),\n onRequest: (listener) => {\n const sdkListener = (request: HTTPRequest) => listener(makeProductionRequest(request));\n listeners.set(listener, sdkListener);\n page.on(\"request\", sdkListener);\n },\n offRequest: (listener) => {\n const sdkListener = listeners.get(listener);\n if (sdkListener !== undefined) {\n page.off(\"request\", sdkListener);\n listeners.delete(listener);\n }\n },\n goto: async (url) => {\n await page.goto(url, { waitUntil: \"networkidle0\", timeout: 0 });\n },\n url: () => page.url(),\n readText: (selector, maximumBytes) =>\n page.evaluate(\n (requestedSelector, maximum) => {\n const pageDocument = Reflect.get(globalThis, \"document\");\n const element =\n requestedSelector === undefined\n ? Reflect.get(pageDocument, \"body\")\n : Reflect.apply(Reflect.get(pageDocument, \"querySelector\"), pageDocument, [\n requestedSelector,\n ]);\n if (element === null) return { _tag: \"MissingElement\" };\n const innerText = Reflect.get(element, \"innerText\");\n const textContent = Reflect.get(element, \"textContent\");\n const text =\n typeof innerText === \"string\"\n ? innerText\n : typeof textContent === \"string\"\n ? textContent\n : \"\";\n const observed = new TextEncoder().encode(text).byteLength;\n return observed > maximum ? { _tag: \"OverLimit\", observed } : { _tag: \"Text\", text };\n },\n selector,\n maximumBytes,\n ),\n fill: async (selector, value) => {\n await page.$eval(\n selector,\n (element, nextValue) => {\n if (!(\"value\" in element)) {\n throw new Error(\"The selector did not resolve to a fillable field\");\n }\n const focus = Reflect.get(element, \"focus\");\n if (typeof focus === \"function\") Reflect.apply(focus, element, []);\n Reflect.set(element, \"value\", nextValue);\n const dispatchEvent = Reflect.get(element, \"dispatchEvent\");\n if (typeof dispatchEvent === \"function\") {\n Reflect.apply(dispatchEvent, element, [new Event(\"input\", { bubbles: true })]);\n Reflect.apply(dispatchEvent, element, [new Event(\"change\", { bubbles: true })]);\n }\n },\n value,\n );\n },\n click: (selector) => page.click(selector),\n };\n};\n\nconst makeProductionContext = (context: BrowserContext): BrowserRunInteractiveContext => ({\n newPage: async () => makeProductionPage(await context.newPage()),\n close: () => context.close(),\n});\n\nconst makeProductionBrowser = (browser: Browser): BrowserRunInteractiveBrowser => ({\n createContext: async () => makeProductionContext(await browser.createBrowserContext()),\n close: () => browser.close(),\n isConnected: () => browser.isConnected(),\n onDisconnected: (listener) => {\n browser.on(\"disconnected\", listener);\n },\n offDisconnected: (listener) => {\n browser.off(\"disconnected\", listener);\n },\n});\n\nconst protocolError = (message: string, cause?: unknown): InteractiveBrowserProtocolError =>\n InteractiveBrowserProtocolError.make({\n implementation: browserRunInteractiveImplementation,\n message,\n ...(cause === undefined ? {} : { cause }),\n });\n\nconst actionError = (operation: BrowserOperation, cause?: unknown): InteractiveBrowserActionError =>\n InteractiveBrowserActionError.make({\n implementation: browserRunInteractiveImplementation,\n operation,\n message: `The interactive browser ${operation} operation failed`,\n ...(cause === undefined ? {} : { cause }),\n });\n\nconst policyError = (message: string): InteractiveBrowserPolicyDeniedError =>\n InteractiveBrowserPolicyDeniedError.make({\n implementation: browserRunInteractiveImplementation,\n message,\n });\n\nconst expiredError = (): InteractiveBrowserExpiredError =>\n InteractiveBrowserExpiredError.make({\n implementation: browserRunInteractiveImplementation,\n message: \"The remote browser is no longer usable\",\n });\n\nconst causeText = (cause: unknown): string => {\n if (cause instanceof Error) return cause.message.slice(0, 8_000);\n return String(cause).slice(0, 8_000);\n};\n\nconst isCapacityRefusal = (cause: unknown): boolean =>\n /(^|\\D)429(\\D|$)|browser time limit|capacity|too many concurrent/i.test(causeText(cause));\n\nconst isRemoteClosure = (cause: unknown): boolean =>\n /target closed|browser.*closed|session.*closed|connection.*closed|not connected|websocket.*closed/i.test(\n causeText(cause),\n );\n\nconst snapshotPolicy = (\n input: InteractiveBrowserPolicy,\n): Effect.Effect<InteractiveBrowserPolicySnapshot, InteractiveBrowserPolicyDeniedError> =>\n Schema.decodeUnknownEffect(InteractiveBrowserPolicy)(input).pipe(\n Effect.mapError(() => policyError(\"The interactive browser policy is malformed\")),\n Effect.map((decoded) =>\n Object.freeze({\n allowedHosts: Object.freeze([...decoded.allowedHosts]),\n maxActions: decoded.maxActions,\n maxElapsedMillis: decoded.maxElapsedMillis,\n maxReturnedBytes: decoded.maxReturnedBytes,\n }),\n ),\n );\n\nconst hostAllowed = (policy: InteractiveBrowserPolicySnapshot, value: string): boolean => {\n try {\n const url = new URL(value);\n return (\n url.protocol === \"https:\" &&\n url.username === \"\" &&\n url.password === \"\" &&\n policy.allowedHosts.some((host) => host === url.host)\n );\n } catch {\n return false;\n }\n};\n\nconst keepAliveMillis = (policy: InteractiveBrowserPolicySnapshot): number =>\n Math.max(MIN_KEEP_ALIVE_MILLIS, Math.min(MAX_KEEP_ALIVE_MILLIS, policy.maxElapsedMillis));\n\ninterface CloseableRemote {\n readonly close: () => Promise<void>;\n}\n\nconst closeLateAcquisition = async <A extends CloseableRemote>(\n signal: AbortSignal,\n acquire: () => Promise<A>,\n): Promise<A> => {\n const acquired = await acquire();\n if (!signal.aborted) return acquired;\n\n // The SDK does not accept AbortSignal. If an acquisition settles after Effect\n // has interrupted it, ownership never reaches Scope, so close it here instead.\n try {\n await acquired.close();\n } catch {\n // No caller remains to observe a late cleanup failure, and provider details\n // must not escape through an unhandled rejection.\n }\n throw new Error(\"The interrupted browser acquisition completed late\");\n};\n\nconst closeWithWarning = (close: () => Promise<void>, warning: string): Effect.Effect<void> =>\n Effect.tryPromise({\n try: close,\n catch: () => protocolError(warning),\n }).pipe(Effect.catchCause(() => Effect.logWarning(warning)));\n\nconst deadlineError = Effect.fn(\"BrowserRunInteractive.deadlineError\")(function* (\n policy: InteractiveBrowserPolicySnapshot,\n startedAt: number,\n) {\n const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n return yield* InteractiveBrowserLimitError.make({\n implementation: browserRunInteractiveImplementation,\n limit: \"elapsed\",\n maximum: policy.maxElapsedMillis,\n observed: Math.max(policy.maxElapsedMillis, now - startedAt),\n message: \"The browser elapsed-time limit was reached\",\n });\n});\n\nconst withinDeadline = Effect.fn(\"BrowserRunInteractive.withinDeadline\")(function* <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n policy: InteractiveBrowserPolicySnapshot,\n startedAt: number,\n onTimeout?: () => void,\n): Effect.fn.Return<A, E | InteractiveBrowserLimitError, R> {\n const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n const elapsed = Math.max(0, now - startedAt);\n const remaining = policy.maxElapsedMillis - elapsed;\n if (remaining <= 0) return yield* deadlineError(policy, startedAt);\n return yield* effect.pipe(\n Effect.timeoutOrElse({\n duration: Duration.millis(remaining),\n orElse: () =>\n Effect.sync(() => onTimeout?.()).pipe(Effect.andThen(deadlineError(policy, startedAt))),\n }),\n );\n});\n\ninterface HandleState {\n readonly disconnected: { value: boolean };\n readonly uncertain: { value: boolean };\n readonly violation: { value: BrowserFailure | undefined };\n readonly pendingRequests: Set<Promise<void>>;\n}\n\nconst stateFailure = (state: HandleState): BrowserFailure | undefined => {\n if (state.violation.value !== undefined) return state.violation.value;\n if (state.disconnected.value || state.uncertain.value) return expiredError();\n return undefined;\n};\n\nconst awaitPendingRequests = (state: HandleState): Effect.Effect<void> =>\n Effect.suspend(() => {\n const pending = [...state.pendingRequests];\n return pending.length === 0\n ? Effect.void\n : Effect.promise(() => Promise.all(pending)).pipe(\n Effect.asVoid,\n Effect.andThen(awaitPendingRequests(state)),\n );\n });\n\nconst decodeNavigationResult = Effect.fn(\"BrowserRunInteractive.decodeNavigationResult\")(function* (\n page: BrowserRunInteractivePage,\n policy: InteractiveBrowserPolicySnapshot,\n) {\n const url = yield* Effect.try({\n try: page.url,\n catch: (cause) => protocolError(\"Reading the browser navigation URL failed\", cause),\n });\n const result = yield* Schema.decodeUnknownEffect(BrowserNavigationResult)({\n url,\n }).pipe(\n Effect.mapError((cause) =>\n protocolError(\"The browser returned a malformed navigation URL\", cause),\n ),\n );\n if (!hostAllowed(policy, result.url)) {\n return yield* policyError(\"The browser returned an off-policy page URL\");\n }\n return result;\n});\n\nconst decodeActionResult = Effect.fn(\"BrowserRunInteractive.decodeActionResult\")(function* (\n page: BrowserRunInteractivePage,\n policy: InteractiveBrowserPolicySnapshot,\n) {\n const url = yield* Effect.try({\n try: page.url,\n catch: (cause) => protocolError(\"Reading the browser page URL failed\", cause),\n });\n const result = yield* Schema.decodeUnknownEffect(BrowserActionResult)({ url }).pipe(\n Effect.mapError((cause) => protocolError(\"The browser returned a malformed page URL\", cause)),\n );\n if (!hostAllowed(policy, result.url)) {\n return yield* policyError(\"The browser returned an off-policy page URL\");\n }\n return result;\n});\n\nconst makeRequestListener =\n (\n policy: InteractiveBrowserPolicySnapshot,\n state: HandleState,\n ): BrowserRunInteractiveRequestListener =>\n (request) => {\n let allowed = false;\n try {\n allowed = hostAllowed(policy, request.url());\n } catch {\n allowed = false;\n }\n if (!allowed) {\n state.violation.value = policyError(\"The browser requested an off-policy URL\");\n }\n\n let settlement: Promise<void>;\n try {\n settlement = allowed ? request.continue() : request.abort();\n } catch {\n state.violation.value = protocolError(\"Resolving an intercepted browser request failed\");\n return;\n }\n const observed = settlement\n .catch(() => {\n state.violation.value = protocolError(\"Resolving an intercepted browser request failed\");\n })\n .finally(() => {\n state.pendingRequests.delete(observed);\n });\n state.pendingRequests.add(observed);\n };\n\nconst makeHandle = Effect.fn(\"BrowserRunInteractive.makeHandle\")(function* (\n page: BrowserRunInteractivePage,\n policy: InteractiveBrowserPolicySnapshot,\n startedAt: number,\n state: HandleState,\n): Effect.fn.Return<BrowserHandle> {\n const permits = yield* Semaphore.make(1);\n const actions = yield* Ref.make(0);\n\n const remote = <A>(operation: BrowserOperation, evaluate: () => Promise<A>) =>\n Effect.tryPromise({\n try: evaluate,\n catch: (cause) => {\n if (state.disconnected.value || isRemoteClosure(cause)) {\n state.disconnected.value = true;\n return expiredError();\n }\n return actionError(operation, cause);\n },\n });\n\n const run = <A>(\n effect: Effect.Effect<A, BrowserFailure>,\n preflight: Effect.Effect<void, BrowserFailure> = Effect.void,\n ) =>\n permits\n .withPermitsIfAvailable(1)(\n Effect.gen(function* () {\n const unavailable = stateFailure(state);\n if (unavailable !== undefined) return yield* unavailable;\n yield* preflight;\n\n const admitted = yield* Ref.modify(actions, (count) =>\n count >= policy.maxActions\n ? [{ allowed: false, observed: count + 1 }, count]\n : [{ allowed: true, observed: count + 1 }, count + 1],\n );\n if (!admitted.allowed) {\n return yield* InteractiveBrowserLimitError.make({\n implementation: browserRunInteractiveImplementation,\n limit: \"actions\",\n maximum: policy.maxActions,\n observed: admitted.observed,\n message: \"The browser action limit was reached\",\n });\n }\n\n const completed = effect.pipe(\n Effect.catch((error) => {\n const failure = stateFailure(state);\n return Effect.fail(failure ?? error);\n }),\n Effect.flatMap((result) =>\n awaitPendingRequests(state).pipe(\n Effect.flatMap(() => {\n const failure = stateFailure(state);\n return failure === undefined ? Effect.succeed(result) : Effect.fail(failure);\n }),\n ),\n ),\n );\n return yield* withinDeadline(completed, policy, startedAt, () => {\n state.uncertain.value = true;\n }).pipe(\n Effect.onInterrupt(() =>\n Effect.sync(() => {\n state.uncertain.value = true;\n }),\n ),\n Effect.catch((error) => {\n if (Schema.is(InteractiveBrowserLimitError)(error) && error.limit === \"elapsed\") {\n return Effect.fail(error);\n }\n const failure = stateFailure(state);\n return Effect.fail(failure ?? error);\n }),\n );\n }),\n )\n .pipe(\n Effect.flatMap((result) =>\n Option.isSome(result)\n ? Effect.succeed(result.value)\n : Effect.fail(\n InteractiveBrowserBusyError.make({\n implementation: browserRunInteractiveImplementation,\n message: \"The browser handle already has an operation in flight\",\n }),\n ),\n ),\n );\n\n return {\n navigate: (request) =>\n run(\n Effect.gen(function* () {\n yield* remote(\"navigate\", () => page.goto(request.url));\n return yield* decodeNavigationResult(page, policy);\n }),\n Effect.suspend(() =>\n hostAllowed(policy, request.url)\n ? Effect.void\n : Effect.fail(policyError(\"The navigation URL is outside the browser policy\")),\n ),\n ),\n readText: (request) =>\n run(\n Effect.gen(function* () {\n const raw = yield* remote(\"read-text\", () =>\n page.readText(request.selector, policy.maxReturnedBytes),\n );\n const observation = yield* Schema.decodeUnknownEffect(TextObservation)(raw).pipe(\n Effect.mapError((cause) =>\n protocolError(\"The browser returned a malformed text observation\", cause),\n ),\n );\n if (observation._tag === \"MissingElement\") {\n return yield* actionError(\"read-text\");\n }\n if (observation._tag === \"OverLimit\") {\n return yield* InteractiveBrowserLimitError.make({\n implementation: browserRunInteractiveImplementation,\n limit: \"returned-bytes\",\n maximum: policy.maxReturnedBytes,\n observed: observation.observed,\n message: \"The browser returned-text limit was reached\",\n });\n }\n const observed = new TextEncoder().encode(observation.text).byteLength;\n if (observed > policy.maxReturnedBytes) {\n return yield* InteractiveBrowserLimitError.make({\n implementation: browserRunInteractiveImplementation,\n limit: \"returned-bytes\",\n maximum: policy.maxReturnedBytes,\n observed,\n message: \"The browser returned-text limit was reached\",\n });\n }\n return yield* Schema.decodeUnknownEffect(BrowserTextResult)({\n text: observation.text,\n }).pipe(\n Effect.mapError((cause) =>\n protocolError(\"The browser returned malformed page text\", cause),\n ),\n );\n }),\n ),\n fill: (request) =>\n run(\n remote(\"fill\", () => page.fill(request.selector, request.value)).pipe(\n Effect.andThen(decodeActionResult(page, policy)),\n ),\n ),\n click: (request) =>\n run(\n remote(\"click\", () => page.click(request.selector)).pipe(\n Effect.andThen(decodeActionResult(page, policy)),\n ),\n ),\n };\n});\n\n/** Worker-only Cloudflare Puppeteer adapter; the caller supplies the Browser Run binding Layer. */\nexport const browserRunInteractiveLayer = (): Layer.Layer<\n InteractiveBrowser,\n never,\n BrowserRunInteractiveBinding\n> =>\n Layer.effect(\n InteractiveBrowser,\n Effect.gen(function* () {\n const binding = yield* BrowserRunInteractiveBinding;\n return InteractiveBrowser.of({\n open: (policy) =>\n Effect.gen(function* () {\n const fixedPolicy = yield* snapshotPolicy(policy);\n const startedAt = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n const state: HandleState = {\n disconnected: { value: false },\n uncertain: { value: false },\n violation: { value: undefined },\n pendingRequests: new Set(),\n };\n const browser = yield* Effect.acquireRelease(\n withinDeadline(\n Effect.tryPromise({\n try: (signal) =>\n closeLateAcquisition(signal, () =>\n binding.launch(keepAliveMillis(fixedPolicy)),\n ),\n catch: (cause) =>\n isCapacityRefusal(cause)\n ? InteractiveBrowserCapacityError.make({\n implementation: browserRunInteractiveImplementation,\n message: \"Browser Run has no capacity for a new browser session\",\n })\n : protocolError(\"Launching the Browser Run session failed\", cause),\n }),\n fixedPolicy,\n startedAt,\n ),\n (acquired) =>\n Effect.sync(() => {\n state.disconnected.value = true;\n }).pipe(\n Effect.andThen(\n closeWithWarning(acquired.close, \"Closing the interactive browser failed\"),\n ),\n ),\n { interruptible: true },\n );\n\n const disconnected = () => {\n state.disconnected.value = true;\n };\n yield* Effect.acquireRelease(\n Effect.try({\n try: () => browser.onDisconnected(disconnected),\n catch: (cause) =>\n protocolError(\"Installing the browser disconnect listener failed\", cause),\n }),\n () =>\n Effect.sync(() => {\n browser.offDisconnected(disconnected);\n }).pipe(\n Effect.catchCause(() =>\n Effect.logWarning(\"Removing the browser disconnect listener failed\"),\n ),\n ),\n );\n const connected = yield* Effect.try({\n try: browser.isConnected,\n catch: (cause) =>\n protocolError(\"Reading the Browser Run connection state failed\", cause),\n });\n if (!connected) {\n state.disconnected.value = true;\n return yield* expiredError();\n }\n\n const context = yield* Effect.acquireRelease(\n withinDeadline(\n Effect.tryPromise({\n try: (signal) => closeLateAcquisition(signal, browser.createContext),\n catch: (cause) =>\n state.disconnected.value || isRemoteClosure(cause)\n ? expiredError()\n : protocolError(\"Creating the browser context failed\", cause),\n }),\n fixedPolicy,\n startedAt,\n ),\n (acquired) =>\n closeWithWarning(acquired.close, \"Closing the interactive browser context failed\"),\n { interruptible: true },\n );\n const page = yield* Effect.acquireRelease(\n withinDeadline(\n Effect.tryPromise({\n try: (signal) => closeLateAcquisition(signal, context.newPage),\n catch: (cause) =>\n state.disconnected.value || isRemoteClosure(cause)\n ? expiredError()\n : protocolError(\"Creating the browser page failed\", cause),\n }),\n fixedPolicy,\n startedAt,\n ),\n (acquired) =>\n closeWithWarning(acquired.close, \"Closing the interactive browser page failed\"),\n { interruptible: true },\n );\n\n yield* withinDeadline(\n Effect.tryPromise({\n try: () => page.setBypassServiceWorker(true),\n catch: (cause) => protocolError(\"Bypassing browser service workers failed\", cause),\n }),\n fixedPolicy,\n startedAt,\n );\n\n const requestListener = makeRequestListener(fixedPolicy, state);\n yield* Effect.acquireRelease(\n Effect.try({\n try: () => page.onRequest(requestListener),\n catch: (cause) =>\n protocolError(\"Installing the browser request listener failed\", cause),\n }),\n () =>\n Effect.sync(() => {\n page.offRequest(requestListener);\n }).pipe(\n Effect.catchCause(() =>\n Effect.logWarning(\"Removing the browser request policy failed\"),\n ),\n ),\n );\n\n yield* withinDeadline(\n Effect.tryPromise({\n try: () => page.setRequestInterception(true),\n catch: (cause) =>\n protocolError(\"Installing the browser request policy failed\", cause),\n }),\n fixedPolicy,\n startedAt,\n );\n\n yield* withinDeadline(awaitPendingRequests(state), fixedPolicy, startedAt);\n const setupFailure = stateFailure(state);\n if (setupFailure !== undefined) return yield* setupFailure;\n\n return yield* makeHandle(page, fixedPolicy, startedAt, state);\n }),\n });\n }),\n );\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-agent/platform-cloudflare",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.31",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": {
|
|
6
6
|
"types": "./dist/index.d.mts",
|
|
@@ -9,14 +9,27 @@
|
|
|
9
9
|
"./browser-quick-action": {
|
|
10
10
|
"types": "./dist/browser-quick-action.d.mts",
|
|
11
11
|
"default": "./dist/browser-quick-action.mjs"
|
|
12
|
+
},
|
|
13
|
+
"./interactive-browser": {
|
|
14
|
+
"types": "./dist/interactive-browser.d.mts",
|
|
15
|
+
"default": "./dist/interactive-browser.mjs"
|
|
16
|
+
},
|
|
17
|
+
"./browser-rest-capture": {
|
|
18
|
+
"types": "./dist/browser-rest-capture.d.mts",
|
|
19
|
+
"default": "./dist/browser-rest-capture.mjs"
|
|
20
|
+
},
|
|
21
|
+
"./browser-rest-crawl": {
|
|
22
|
+
"types": "./dist/browser-rest-crawl.d.mts",
|
|
23
|
+
"default": "./dist/browser-rest-crawl.mjs"
|
|
12
24
|
}
|
|
13
25
|
},
|
|
14
26
|
"dependencies": {
|
|
15
|
-
"@
|
|
16
|
-
"@effect-agent/
|
|
17
|
-
"@effect-agent/
|
|
18
|
-
"@effect-agent/
|
|
19
|
-
"@effect-agent/
|
|
27
|
+
"@cloudflare/puppeteer": "1.1.0",
|
|
28
|
+
"@effect-agent/core": "0.1.0-beta.31",
|
|
29
|
+
"@effect-agent/engine": "0.1.0-beta.31",
|
|
30
|
+
"@effect-agent/sandbox": "0.1.0-beta.31",
|
|
31
|
+
"@effect-agent/session": "0.1.0-beta.31",
|
|
32
|
+
"@effect-agent/storage-cloudflare": "0.1.0-beta.31",
|
|
20
33
|
"@effect/platform-browser": "4.0.0-rc.110",
|
|
21
34
|
"@effect/sql-sqlite-do": "4.0.0-rc.110",
|
|
22
35
|
"effect": "4.0.0-rc.110"
|
|
@@ -24,7 +37,7 @@
|
|
|
24
37
|
"peerDependencies": {
|
|
25
38
|
"effect-cf": "^0.27.0"
|
|
26
39
|
},
|
|
27
|
-
"description": "
|
|
40
|
+
"description": "Cloudflare Layer assembly for Effect Agent: Durable Objects and Browser Run adapters.",
|
|
28
41
|
"license": "MIT",
|
|
29
42
|
"repository": {
|
|
30
43
|
"type": "git",
|
|
@@ -47,8 +60,8 @@
|
|
|
47
60
|
"devDependencies": {
|
|
48
61
|
"@cloudflare/vitest-pool-workers": "0.21.3",
|
|
49
62
|
"@cloudflare/workers-types": "5.20260813.1",
|
|
50
|
-
"@effect-agent/capabilities": "0.1.0-beta.
|
|
51
|
-
"@effect-agent/testing": "0.1.0-beta.
|
|
63
|
+
"@effect-agent/capabilities": "0.1.0-beta.30",
|
|
64
|
+
"@effect-agent/testing": "0.1.0-beta.30",
|
|
52
65
|
"@effect/vitest": "4.0.0-rc.110",
|
|
53
66
|
"effect-cf": "0.27.0",
|
|
54
67
|
"esbuild": "0.28.1",
|