@effect-agent/platform-cloudflare 0.1.0-beta.13 → 0.1.0-beta.131

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 (106) hide show
  1. package/dist/Alarm.d.mts +244 -0
  2. package/dist/Alarm.mjs +947 -0
  3. package/dist/Alarm.mjs.map +1 -0
  4. package/dist/BrowserCredentials.d.mts +91 -0
  5. package/dist/BrowserCredentials.mjs +120 -0
  6. package/dist/BrowserCredentials.mjs.map +1 -0
  7. package/dist/BrowserRestCapture.d.mts +34 -0
  8. package/dist/BrowserRestCapture.mjs +238 -0
  9. package/dist/BrowserRestCapture.mjs.map +1 -0
  10. package/dist/BrowserRestCrawl.d.mts +16 -0
  11. package/dist/BrowserRestCrawl.mjs +244 -0
  12. package/dist/BrowserRestCrawl.mjs.map +1 -0
  13. package/dist/BrowserSession-DP6b-cpL.d.mts +125 -0
  14. package/dist/BrowserSession-DWoRC_iM.mjs +505 -0
  15. package/dist/BrowserSession-DWoRC_iM.mjs.map +1 -0
  16. package/dist/BrowserSession.d.mts +3 -0
  17. package/dist/BrowserSession.mjs +4 -0
  18. package/dist/CloudflareAiGateway.d.mts +64 -0
  19. package/dist/CloudflareAiGateway.mjs +70 -0
  20. package/dist/CloudflareAiGateway.mjs.map +1 -0
  21. package/dist/CloudflareBindings.d.mts +115 -0
  22. package/dist/CloudflareBindings.mjs +113 -0
  23. package/dist/CloudflareBindings.mjs.map +1 -0
  24. package/dist/CloudflareBrowser-DV6kya1H.mjs +495 -0
  25. package/dist/CloudflareBrowser-DV6kya1H.mjs.map +1 -0
  26. package/dist/CloudflareBrowser-Doe1M7R5.d.mts +80 -0
  27. package/dist/CloudflareBrowser.d.mts +2 -0
  28. package/dist/CloudflareBrowser.mjs +2 -0
  29. package/dist/CloudflareCodeMode.d.mts +44 -0
  30. package/dist/CloudflareCodeMode.mjs +616 -0
  31. package/dist/CloudflareCodeMode.mjs.map +1 -0
  32. package/dist/CloudflareConfig-DKXGo8L3.d.mts +106 -0
  33. package/dist/CloudflareConfig.d.mts +2 -0
  34. package/dist/CloudflareConfig.mjs +128 -0
  35. package/dist/CloudflareConfig.mjs.map +1 -0
  36. package/dist/CloudflareMemory.d.mts +142 -0
  37. package/dist/CloudflareMemory.mjs +202 -0
  38. package/dist/CloudflareMemory.mjs.map +1 -0
  39. package/dist/CloudflareScheduling.d.mts +52 -0
  40. package/dist/CloudflareScheduling.mjs +396 -0
  41. package/dist/CloudflareScheduling.mjs.map +1 -0
  42. package/dist/CloudflareSubscriptions.d.mts +87 -0
  43. package/dist/CloudflareSubscriptions.mjs +530 -0
  44. package/dist/CloudflareSubscriptions.mjs.map +1 -0
  45. package/dist/CloudflareThreadClient.d.mts +1752 -0
  46. package/dist/CloudflareThreadClient.mjs +382 -0
  47. package/dist/CloudflareThreadClient.mjs.map +1 -0
  48. package/dist/InteractiveBrowser-BnqY_Y-F.d.mts +228 -0
  49. package/dist/InteractiveBrowser-CxFT-Nwm.mjs +1923 -0
  50. package/dist/InteractiveBrowser-CxFT-Nwm.mjs.map +1 -0
  51. package/dist/InteractiveBrowser.d.mts +2 -0
  52. package/dist/InteractiveBrowser.mjs +2 -0
  53. package/dist/ThreadObject-CVhZC-7l.mjs +849 -0
  54. package/dist/ThreadObject-CVhZC-7l.mjs.map +1 -0
  55. package/dist/ThreadObject-D8GzJxJB.d.mts +839 -0
  56. package/dist/ThreadObject.d.mts +2 -0
  57. package/dist/ThreadObject.mjs +3 -0
  58. package/dist/WakeScheduler.d.mts +23 -0
  59. package/dist/WakeScheduler.mjs +57 -0
  60. package/dist/WakeScheduler.mjs.map +1 -0
  61. package/dist/boundary-BguazVkh.mjs +42 -0
  62. package/dist/boundary-BguazVkh.mjs.map +1 -0
  63. package/dist/index.d.mts +15 -1550
  64. package/dist/index.mjs +15 -1716
  65. package/dist/prepared-admission-DU0_T-ev.mjs +73 -0
  66. package/dist/prepared-admission-DU0_T-ev.mjs.map +1 -0
  67. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  68. package/package.json +1 -56
  69. package/src/Alarm.ts +1979 -0
  70. package/src/BrowserCredentials.ts +146 -0
  71. package/src/BrowserRestCapture.ts +477 -0
  72. package/src/BrowserRestCrawl.ts +540 -0
  73. package/src/BrowserSession.ts +624 -0
  74. package/src/CloudflareAiGateway.ts +170 -0
  75. package/src/CloudflareBindings.ts +199 -0
  76. package/src/CloudflareBrowser.ts +15 -0
  77. package/src/{code-mode-executor.ts → CloudflareCodeMode.ts} +308 -283
  78. package/src/{config.ts → CloudflareConfig.ts} +19 -12
  79. package/src/CloudflareMemory.ts +420 -0
  80. package/src/CloudflareScheduling.ts +747 -0
  81. package/src/CloudflareSubscriptions.ts +1041 -0
  82. package/src/CloudflareThreadClient.ts +809 -0
  83. package/src/InteractiveBrowser.ts +3246 -0
  84. package/src/ThreadObject.ts +1190 -0
  85. package/src/{wake-scheduler.ts → WakeScheduler.ts} +32 -26
  86. package/src/index.ts +14 -23
  87. package/src/internal/boundary.ts +55 -0
  88. package/src/internal/browser-binding.ts +322 -0
  89. package/src/internal/browser-credentials.ts +362 -0
  90. package/src/internal/browser-failure.ts +92 -0
  91. package/src/internal/browser-file-selection.ts +126 -0
  92. package/src/internal/browser-quick-action.ts +857 -0
  93. package/src/internal/browser-readonly-live-view.ts +111 -0
  94. package/src/internal/browser-session-lifecycle.ts +193 -0
  95. package/src/internal/layers.ts +773 -0
  96. package/src/internal/message-delivery.ts +131 -0
  97. package/src/internal/prepared-admission.ts +116 -0
  98. package/src/internal/progress-wait.ts +121 -0
  99. package/src/internal/transport.ts +44 -0
  100. package/dist/index.mjs.map +0 -1
  101. package/src/alarm.ts +0 -334
  102. package/src/bindings.ts +0 -145
  103. package/src/client.ts +0 -646
  104. package/src/conversation-object.ts +0 -768
  105. package/src/layers.ts +0 -376
  106. package/src/transport.ts +0 -38
@@ -0,0 +1,238 @@
1
+ import { i as BrowserQuickActionWorkersAi } from "./CloudflareBrowser-DV6kya1H.mjs";
2
+ import { Effect, Layer, Option, Redacted, Schema, Stream } from "effect";
3
+ import { PageCapture, PageCaptureInferencePolicyError, PageCaptureInferenceUse, PageCaptureNavigationError, PageCaptureOutputLimitError, PageCaptureProtocolError, PageCaptureRateLimitedError, PageCaptureResourceUse, PageCaptureResult, PageCaptureUnsupportedError, PageContentCaptured, PageLinksCaptured, PageMarkdownCaptured, PageScrapeCaptured, PageStructuredCaptured } from "effect-agent/page-capture";
4
+ import { SandboxImplementation } from "effect-agent/sandbox";
5
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
6
+ //#region src/BrowserRestCapture.ts
7
+ /** Node-safe REST PageCapture implementation; it never imports Worker runtime modules. */
8
+ const browserRestCaptureImplementation = SandboxImplementation.make({
9
+ isolation: "isolated",
10
+ identity: "cloudflare-browser-rest"
11
+ });
12
+ const API_ORIGIN = "https://api.cloudflare.com";
13
+ const MAX_DIAGNOSTIC_LENGTH = 8e3;
14
+ const boundedDiagnostic = (message) => message.slice(0, MAX_DIAGNOSTIC_LENGTH);
15
+ const RestSuccessEnvelope = Schema.Struct({
16
+ success: Schema.Literal(true),
17
+ result: Schema.Json,
18
+ errors: Schema.optionalKey(Schema.Array(Schema.Unknown)),
19
+ meta: Schema.optionalKey(Schema.Unknown)
20
+ });
21
+ const RestErrorEnvelope = Schema.Struct({
22
+ success: Schema.Literal(false),
23
+ errors: Schema.Array(Schema.Struct({
24
+ message: Schema.String,
25
+ code: Schema.optionalKey(Schema.Number)
26
+ })),
27
+ result: Schema.optionalKey(Schema.Json),
28
+ meta: Schema.optionalKey(Schema.Unknown)
29
+ });
30
+ const RestEnvelope = Schema.Union([RestSuccessEnvelope, RestErrorEnvelope]);
31
+ const decodeEnvelope = Schema.decodeUnknownOption(Schema.fromJsonString(RestEnvelope));
32
+ const protocolError = (message, cause) => PageCaptureProtocolError.make({
33
+ implementation: browserRestCaptureImplementation,
34
+ message: boundedDiagnostic(message),
35
+ ...cause === void 0 ? {} : { cause }
36
+ });
37
+ const navigationError = (message, cause) => PageCaptureNavigationError.make({
38
+ implementation: browserRestCaptureImplementation,
39
+ message: boundedDiagnostic(message),
40
+ ...cause === void 0 ? {} : { cause }
41
+ });
42
+ const privateResponseCause = (bodyText, apiToken) => {
43
+ if (bodyText.length === 0) return void 0;
44
+ const token = Redacted.value(apiToken);
45
+ const diagnostic = boundedDiagnostic(bodyText);
46
+ return new Error(token.length === 0 ? diagnostic : diagnostic.replaceAll(token, "[REDACTED]"));
47
+ };
48
+ const requestBody = (request) => {
49
+ const body = request.target._tag === "PageUrlTarget" ? { url: request.target.url } : { html: request.target.html };
50
+ if (request.navigation !== void 0) {
51
+ const goto = {};
52
+ if (request.navigation.waitUntil !== void 0) goto.waitUntil = request.navigation.waitUntil;
53
+ if (request.navigation.timeoutMillis !== void 0) goto.timeout = request.navigation.timeoutMillis;
54
+ if (Object.keys(goto).length > 0) body.gotoOptions = goto;
55
+ if (request.navigation.waitForSelector !== void 0) body.waitForSelector = {
56
+ selector: request.navigation.waitForSelector.selector,
57
+ ...request.navigation.waitForSelector.timeoutMillis === void 0 ? {} : { timeout: request.navigation.waitForSelector.timeoutMillis }
58
+ };
59
+ }
60
+ if (request.viewport !== void 0) body.viewport = request.viewport;
61
+ if (request.resourcePolicy?.rejectResourceTypes !== void 0) body.rejectResourceTypes = [...request.resourcePolicy.rejectResourceTypes];
62
+ if (request.resourcePolicy?.allowRequestPatterns !== void 0) body.allowRequestPattern = [...request.resourcePolicy.allowRequestPatterns];
63
+ return body;
64
+ };
65
+ const actionName = (action) => {
66
+ switch (action._tag) {
67
+ case "CapturePageContent": return "content";
68
+ case "CapturePageMarkdown": return "markdown";
69
+ case "CapturePageLinks": return "links";
70
+ case "CapturePageScrape": return "scrape";
71
+ case "CapturePageStructured": return "json";
72
+ }
73
+ };
74
+ const actionBody = (request) => {
75
+ const body = requestBody(request);
76
+ switch (request.action._tag) {
77
+ case "CapturePageContent":
78
+ case "CapturePageMarkdown": return body;
79
+ case "CapturePageLinks": return {
80
+ ...body,
81
+ ...request.action.visibleLinksOnly === void 0 ? {} : { visibleLinksOnly: request.action.visibleLinksOnly }
82
+ };
83
+ case "CapturePageScrape": return {
84
+ ...body,
85
+ elements: request.action.selectors.map((selector) => ({ selector }))
86
+ };
87
+ case "CapturePageStructured": return {
88
+ ...body,
89
+ response_format: {
90
+ type: "json_schema",
91
+ json_schema: request.action.responseFormat
92
+ },
93
+ ...request.action.prompt === void 0 ? {} : { prompt: request.action.prompt }
94
+ };
95
+ }
96
+ };
97
+ const retryAfterMillis = (headers) => {
98
+ const seconds = Number(headers["retry-after"]);
99
+ if (!Number.isSafeInteger(seconds) || seconds < 0) return void 0;
100
+ const millis = seconds * 1e3;
101
+ return Number.isSafeInteger(millis) ? millis : void 0;
102
+ };
103
+ const browserMillis = (headers) => {
104
+ const millis = Number(headers["x-browser-ms-used"]);
105
+ return Number.isSafeInteger(millis) && millis >= 0 ? millis : void 0;
106
+ };
107
+ /** Response framing is provider metadata, never content controlled by the rendered page. */
108
+ const isJsonResponse = (headers) => {
109
+ const contentType = headers["content-type"];
110
+ if (contentType === void 0) return false;
111
+ const mediaType = contentType.split(";", 1)[0]?.trim().toLowerCase();
112
+ return mediaType === "application/json" || mediaType?.endsWith("+json") === true;
113
+ };
114
+ const readBoundedResponse = Effect.fn("BrowserRestCapture.readResponse")(function* (response, request) {
115
+ const decoder = new TextDecoder("utf-8", {
116
+ fatal: true,
117
+ ignoreBOM: false
118
+ });
119
+ const chunks = yield* Stream.runFoldEffect(response.stream, () => ({
120
+ observed: 0,
121
+ text: ""
122
+ }), (state, chunk) => {
123
+ const observed = state.observed + chunk.byteLength;
124
+ if (observed > request.limits.maxOutputBytes) return Effect.fail(PageCaptureOutputLimitError.make({
125
+ implementation: browserRestCaptureImplementation,
126
+ limit: request.limits.maxOutputBytes,
127
+ observed
128
+ }));
129
+ return Effect.try({
130
+ try: () => ({
131
+ observed,
132
+ text: state.text + decoder.decode(chunk, { stream: true })
133
+ }),
134
+ catch: (cause) => protocolError("Decoding the Browser Run response failed", cause)
135
+ });
136
+ }).pipe(Effect.mapError((error) => Schema.is(PageCaptureOutputLimitError)(error) || Schema.is(PageCaptureProtocolError)(error) ? error : protocolError("Reading the Browser Run response failed", error)));
137
+ return yield* Effect.try({
138
+ try: () => chunks.text + decoder.decode(),
139
+ catch: (cause) => protocolError("Decoding the Browser Run response failed", cause)
140
+ });
141
+ });
142
+ const parseOutput = (action, bodyText, apiToken) => {
143
+ const envelope = decodeEnvelope(bodyText);
144
+ if (Option.isNone(envelope)) return protocolError("The Browser Run response did not carry a valid response envelope", privateResponseCause(bodyText, apiToken));
145
+ if (!envelope.value.success) return navigationError("Browser Run reported a navigation failure", privateResponseCause(bodyText, apiToken));
146
+ switch (action._tag) {
147
+ case "CapturePageContent":
148
+ case "CapturePageMarkdown":
149
+ if (typeof envelope.value.result !== "string") return protocolError("The Browser Run response envelope carried a non-text result");
150
+ return action._tag === "CapturePageContent" ? PageContentCaptured.make({ html: envelope.value.result }) : PageMarkdownCaptured.make({ markdown: envelope.value.result });
151
+ case "CapturePageLinks": {
152
+ const decoded = Schema.decodeUnknownOption(PageLinksCaptured)({
153
+ _tag: "PageLinksCaptured",
154
+ links: envelope.value.result
155
+ });
156
+ return Option.isSome(decoded) ? decoded.value : protocolError("Browser Run links did not return a bounded array of valid URLs");
157
+ }
158
+ case "CapturePageScrape": {
159
+ const decoded = Schema.decodeUnknownOption(PageScrapeCaptured)({
160
+ _tag: "PageScrapeCaptured",
161
+ groups: envelope.value.result
162
+ });
163
+ return Option.isSome(decoded) ? decoded.value : protocolError("Browser Run scrape did not return bounded grouped element records");
164
+ }
165
+ case "CapturePageStructured": return PageStructuredCaptured.make({ value: envelope.value.result });
166
+ }
167
+ };
168
+ const isQuotaMessage = (text) => /time limit|daily|quota/i.test(text);
169
+ const makeCapture = (client, options, workersAi) => Effect.fn("BrowserRestCapture.capture")(function* (request) {
170
+ const usesWorkersAi = request.action._tag === "CapturePageStructured";
171
+ if (usesWorkersAi) {
172
+ if (workersAi === void 0) return yield* PageCaptureUnsupportedError.make({
173
+ implementation: browserRestCaptureImplementation,
174
+ feature: "action",
175
+ message: "Structured capture invokes separately billed Workers AI and requires an explicit authorization and accounting policy"
176
+ });
177
+ yield* workersAi.authorizeAndAccount(request).pipe(Effect.mapError((cause) => PageCaptureInferencePolicyError.make({
178
+ implementation: browserRestCaptureImplementation,
179
+ provider: "cloudflare-workers-ai",
180
+ reason: cause.reason,
181
+ message: cause.reason === "authorization" ? "Workers AI extraction was not authorized" : "Workers AI extraction could not be accounted for",
182
+ cause
183
+ })));
184
+ }
185
+ const action = actionName(request.action);
186
+ const path = `${API_ORIGIN}/client/v4/accounts/${encodeURIComponent(options.accountId)}/browser-rendering/${action}`;
187
+ const requestWithBody = yield* HttpClientRequest.post(path).pipe(request.engine === "kitesurf" ? HttpClientRequest.setUrlParam("browser", "kitesurf") : (value) => value, HttpClientRequest.bearerToken(options.apiToken), HttpClientRequest.acceptJson, HttpClientRequest.bodyJson(actionBody(request)), Effect.mapError((cause) => protocolError("Encoding the Browser Run request failed", cause)));
188
+ const response = yield* client.execute(requestWithBody).pipe(Effect.mapError((cause) => protocolError("Calling the Browser Run REST API failed", cause)));
189
+ const bodyText = yield* readBoundedResponse(response, request);
190
+ if (response.status === 429) {
191
+ const reason = isQuotaMessage(bodyText) ? "quota" : "rate";
192
+ return yield* PageCaptureRateLimitedError.make({
193
+ implementation: browserRestCaptureImplementation,
194
+ reason,
195
+ ...retryAfterMillis(response.headers) === void 0 ? {} : { retryAfterMillis: retryAfterMillis(response.headers) },
196
+ ...privateResponseCause(bodyText, options.apiToken) === void 0 ? {} : { cause: privateResponseCause(bodyText, options.apiToken) },
197
+ message: reason === "quota" ? "Browser Run exceeded its browser quota" : "Browser Run was rate limited"
198
+ });
199
+ }
200
+ if (response.status < 200 || response.status >= 300) return yield* response.status >= 500 ? protocolError(`Browser Run answered HTTP ${String(response.status)}`, privateResponseCause(bodyText, options.apiToken)) : navigationError(`Browser Run answered HTTP ${String(response.status)}`, privateResponseCause(bodyText, options.apiToken));
201
+ if (!isJsonResponse(response.headers)) return yield* protocolError("The Browser Run success response was not a JSON response envelope", privateResponseCause(bodyText, options.apiToken));
202
+ const output = parseOutput(request.action, bodyText, options.apiToken);
203
+ if (output._tag === "PageCaptureNavigationError" || output._tag === "PageCaptureProtocolError") return yield* output;
204
+ return PageCaptureResult.make({
205
+ implementation: browserRestCaptureImplementation,
206
+ output,
207
+ resourceUse: PageCaptureResourceUse.make({
208
+ ...browserMillis(response.headers) === void 0 ? {} : { browserMillis: browserMillis(response.headers) },
209
+ ...usesWorkersAi ? { inference: PageCaptureInferenceUse.make({
210
+ provider: "cloudflare-workers-ai",
211
+ modelCalls: 1
212
+ }) } : {}
213
+ })
214
+ });
215
+ });
216
+ /** REST PageCapture layer for Node and other non-Worker composition roots. */
217
+ const browserRestCaptureLayer = (options) => Layer.effect(PageCapture, Effect.map(HttpClient.HttpClient, (client) => PageCapture.of({ capture: makeCapture(client, options) })));
218
+ /** REST structured extraction additionally requires explicit Workers AI authorization/accounting. */
219
+ const browserRestWorkersAiCaptureLayer = (options) => Layer.effect(PageCapture, Effect.gen(function* () {
220
+ const client = yield* HttpClient.HttpClient;
221
+ const workersAi = yield* BrowserQuickActionWorkersAi;
222
+ return PageCapture.of({ capture: makeCapture(client, options, workersAi) });
223
+ }));
224
+ /** Node-safe WebCapture handler assembly; the application supplies its HttpClient. */
225
+ const CloudflareBrowserRest = {
226
+ /**
227
+ * Supports capture, scrape, and extraction definitions. Only PageCapture is supplied;
228
+ * handler errors and other services remain visible. Extraction fails closed without
229
+ * workersAi. Existing host policies, response limits, and scoped cleanup are unchanged.
230
+ */
231
+ layer: (definition, options) => {
232
+ const capture = options.workersAi === void 0 ? browserRestCaptureLayer(options) : browserRestWorkersAiCaptureLayer(options).pipe(Layer.provide(BrowserQuickActionWorkersAi.layer(options.workersAi)));
233
+ return definition.handlers.pipe(Layer.provide(capture));
234
+ } };
235
+ //#endregion
236
+ export { CloudflareBrowserRest, browserRestCaptureImplementation, browserRestCaptureLayer, browserRestWorkersAiCaptureLayer };
237
+
238
+ //# sourceMappingURL=BrowserRestCapture.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserRestCapture.mjs","names":[],"sources":["../src/BrowserRestCapture.ts"],"sourcesContent":["import { Effect, Layer, Option, Redacted, Schema, Stream } from \"effect\";\nimport {\n PageCapture,\n PageCaptureInferencePolicyError,\n PageCaptureInferenceUse,\n PageCaptureNavigationError,\n PageCaptureOutputLimitError,\n PageCaptureProtocolError,\n PageCaptureRateLimitedError,\n PageCaptureResourceUse,\n PageCaptureResult,\n PageContentCaptured,\n PageLinksCaptured,\n PageMarkdownCaptured,\n PageScrapeCaptured,\n PageStructuredCaptured,\n PageCaptureUnsupportedError,\n type PageCaptureAction,\n type PageCaptureCapture,\n type PageCaptureError,\n type PageCaptureOutput,\n type PageCaptureRequest,\n} from \"effect-agent/page-capture\";\nimport { SandboxImplementation } from \"effect-agent/sandbox\";\nimport { HttpClient, HttpClientRequest, type HttpClientError } from \"effect/unstable/http\";\n\nimport {\n BrowserQuickActionWorkersAi,\n type BrowserQuickActionWorkersAiPolicy,\n} from \"./CloudflareBrowser.ts\";\n\n/** Node-safe REST PageCapture implementation; it never imports Worker runtime modules. */\nexport const browserRestCaptureImplementation = SandboxImplementation.make({\n isolation: \"isolated\",\n identity: \"cloudflare-browser-rest\",\n});\n\nconst API_ORIGIN = \"https://api.cloudflare.com\";\nconst MAX_DIAGNOSTIC_LENGTH = 8_000;\nconst boundedDiagnostic = (message: string): string => message.slice(0, MAX_DIAGNOSTIC_LENGTH);\n\n/** Explicit construction inputs; credentials are only projected into the fixed-origin Authorization header. */\nexport interface BrowserRestCaptureOptions {\n readonly accountId: string;\n readonly apiToken: Redacted.Redacted<string>;\n}\n\nconst RestSuccessEnvelope = Schema.Struct({\n success: Schema.Literal(true),\n result: Schema.Json,\n errors: Schema.optionalKey(Schema.Array(Schema.Unknown)),\n meta: Schema.optionalKey(Schema.Unknown),\n});\n\nconst RestErrorEnvelope = Schema.Struct({\n success: Schema.Literal(false),\n errors: Schema.Array(\n Schema.Struct({\n message: Schema.String,\n code: Schema.optionalKey(Schema.Number),\n }),\n ),\n result: Schema.optionalKey(Schema.Json),\n meta: Schema.optionalKey(Schema.Unknown),\n});\n\nconst RestEnvelope = Schema.Union([RestSuccessEnvelope, RestErrorEnvelope]);\nconst decodeEnvelope = Schema.decodeUnknownOption(Schema.fromJsonString(RestEnvelope));\n\nconst protocolError = (message: string, cause?: unknown): PageCaptureProtocolError =>\n PageCaptureProtocolError.make({\n implementation: browserRestCaptureImplementation,\n message: boundedDiagnostic(message),\n ...(cause === undefined ? {} : { cause }),\n });\n\nconst navigationError = (message: string, cause?: unknown): PageCaptureNavigationError =>\n PageCaptureNavigationError.make({\n implementation: browserRestCaptureImplementation,\n message: boundedDiagnostic(message),\n ...(cause === undefined ? {} : { cause }),\n });\n\nconst privateResponseCause = (\n bodyText: string,\n apiToken: Redacted.Redacted<string>,\n): Error | undefined => {\n if (bodyText.length === 0) return undefined;\n const token = Redacted.value(apiToken);\n const diagnostic = boundedDiagnostic(bodyText);\n\n return new Error(token.length === 0 ? diagnostic : diagnostic.replaceAll(token, \"[REDACTED]\"));\n};\n\nconst requestBody = (request: PageCaptureRequest): Record<string, unknown> => {\n const body: Record<string, unknown> =\n request.target._tag === \"PageUrlTarget\"\n ? { url: request.target.url }\n : { html: request.target.html };\n\n if (request.navigation !== undefined) {\n const goto: Record<string, unknown> = {};\n\n if (request.navigation.waitUntil !== undefined) goto.waitUntil = request.navigation.waitUntil;\n if (request.navigation.timeoutMillis !== undefined)\n goto.timeout = request.navigation.timeoutMillis;\n if (Object.keys(goto).length > 0) body.gotoOptions = goto;\n if (request.navigation.waitForSelector !== undefined) {\n body.waitForSelector = {\n selector: request.navigation.waitForSelector.selector,\n ...(request.navigation.waitForSelector.timeoutMillis === undefined\n ? {}\n : { timeout: request.navigation.waitForSelector.timeoutMillis }),\n };\n }\n }\n if (request.viewport !== undefined) body.viewport = request.viewport;\n if (request.resourcePolicy?.rejectResourceTypes !== undefined) {\n body.rejectResourceTypes = [...request.resourcePolicy.rejectResourceTypes];\n }\n if (request.resourcePolicy?.allowRequestPatterns !== undefined) {\n body.allowRequestPattern = [...request.resourcePolicy.allowRequestPatterns];\n }\n\n return body;\n};\n\nconst actionName = (\n action: PageCaptureAction,\n): \"content\" | \"markdown\" | \"links\" | \"scrape\" | \"json\" => {\n switch (action._tag) {\n case \"CapturePageContent\":\n return \"content\";\n case \"CapturePageMarkdown\":\n return \"markdown\";\n case \"CapturePageLinks\":\n return \"links\";\n case \"CapturePageScrape\":\n return \"scrape\";\n case \"CapturePageStructured\":\n return \"json\";\n }\n};\n\nconst actionBody = (request: PageCaptureRequest): Record<string, unknown> => {\n const body = requestBody(request);\n\n switch (request.action._tag) {\n case \"CapturePageContent\":\n case \"CapturePageMarkdown\":\n return body;\n case \"CapturePageLinks\":\n return {\n ...body,\n ...(request.action.visibleLinksOnly === undefined\n ? {}\n : { visibleLinksOnly: request.action.visibleLinksOnly }),\n };\n case \"CapturePageScrape\":\n return {\n ...body,\n elements: request.action.selectors.map((selector) => ({ selector })),\n };\n case \"CapturePageStructured\":\n return {\n ...body,\n response_format: { type: \"json_schema\", json_schema: request.action.responseFormat },\n ...(request.action.prompt === undefined ? {} : { prompt: request.action.prompt }),\n };\n }\n};\n\nconst retryAfterMillis = (\n headers: Readonly<Record<string, string | undefined>>,\n): number | undefined => {\n const seconds = Number(headers[\"retry-after\"]);\n\n if (!Number.isSafeInteger(seconds) || seconds < 0) return undefined;\n const millis = seconds * 1_000;\n\n return Number.isSafeInteger(millis) ? millis : undefined;\n};\n\nconst browserMillis = (\n headers: Readonly<Record<string, string | undefined>>,\n): number | undefined => {\n const millis = Number(headers[\"x-browser-ms-used\"]);\n\n return Number.isSafeInteger(millis) && millis >= 0 ? millis : undefined;\n};\n\n/** Response framing is provider metadata, never content controlled by the rendered page. */\nconst isJsonResponse = (headers: Readonly<Record<string, string | undefined>>): boolean => {\n const contentType = headers[\"content-type\"];\n\n if (contentType === undefined) return false;\n const mediaType = contentType.split(\";\", 1)[0]?.trim().toLowerCase();\n\n return mediaType === \"application/json\" || mediaType?.endsWith(\"+json\") === true;\n};\n\nconst readBoundedResponse = Effect.fn(\"BrowserRestCapture.readResponse\")(function* (\n response: { readonly stream: Stream.Stream<Uint8Array, HttpClientError.HttpClientError> },\n request: PageCaptureRequest,\n): Effect.fn.Return<string, PageCaptureError> {\n const decoder = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: false });\n\n const chunks = yield* Stream.runFoldEffect<\n Uint8Array,\n HttpClientError.HttpClientError,\n never,\n { readonly observed: number; readonly text: string },\n PageCaptureOutputLimitError | PageCaptureProtocolError,\n never\n >(\n response.stream,\n () => ({ observed: 0, text: \"\" }),\n (state, chunk) => {\n const observed = state.observed + chunk.byteLength;\n\n if (observed > request.limits.maxOutputBytes) {\n return Effect.fail(\n PageCaptureOutputLimitError.make({\n implementation: browserRestCaptureImplementation,\n limit: request.limits.maxOutputBytes,\n observed,\n }),\n );\n }\n\n return Effect.try({\n try: () => ({ observed, text: state.text + decoder.decode(chunk, { stream: true }) }),\n catch: (cause) => protocolError(\"Decoding the Browser Run response failed\", cause),\n });\n },\n ).pipe(\n Effect.mapError((error) =>\n Schema.is(PageCaptureOutputLimitError)(error) || Schema.is(PageCaptureProtocolError)(error)\n ? error\n : protocolError(\"Reading the Browser Run response failed\", error),\n ),\n );\n\n return yield* Effect.try({\n try: () => chunks.text + decoder.decode(),\n catch: (cause) => protocolError(\"Decoding the Browser Run response failed\", cause),\n });\n});\n\nconst parseOutput = (\n action: PageCaptureAction,\n bodyText: string,\n apiToken: Redacted.Redacted<string>,\n): PageCaptureOutput | PageCaptureNavigationError | PageCaptureProtocolError => {\n const envelope = decodeEnvelope(bodyText);\n\n if (Option.isNone(envelope)) {\n return protocolError(\n \"The Browser Run response did not carry a valid response envelope\",\n privateResponseCause(bodyText, apiToken),\n );\n }\n if (!envelope.value.success) {\n return navigationError(\n \"Browser Run reported a navigation failure\",\n privateResponseCause(bodyText, apiToken),\n );\n }\n switch (action._tag) {\n case \"CapturePageContent\":\n case \"CapturePageMarkdown\":\n if (typeof envelope.value.result !== \"string\") {\n return protocolError(\"The Browser Run response envelope carried a non-text result\");\n }\n\n return action._tag === \"CapturePageContent\"\n ? PageContentCaptured.make({ html: envelope.value.result })\n : PageMarkdownCaptured.make({ markdown: envelope.value.result });\n case \"CapturePageLinks\": {\n const decoded = Schema.decodeUnknownOption(PageLinksCaptured)({\n _tag: \"PageLinksCaptured\",\n links: envelope.value.result,\n });\n\n return Option.isSome(decoded)\n ? decoded.value\n : protocolError(\"Browser Run links did not return a bounded array of valid URLs\");\n }\n case \"CapturePageScrape\": {\n const decoded = Schema.decodeUnknownOption(PageScrapeCaptured)({\n _tag: \"PageScrapeCaptured\",\n groups: envelope.value.result,\n });\n\n return Option.isSome(decoded)\n ? decoded.value\n : protocolError(\"Browser Run scrape did not return bounded grouped element records\");\n }\n case \"CapturePageStructured\":\n return PageStructuredCaptured.make({ value: envelope.value.result });\n }\n};\n\nconst isQuotaMessage = (text: string): boolean => /time limit|daily|quota/i.test(text);\n\nconst makeCapture = (\n client: HttpClient.HttpClient,\n options: BrowserRestCaptureOptions,\n workersAi?: BrowserQuickActionWorkersAiPolicy,\n): PageCaptureCapture =>\n Effect.fn(\"BrowserRestCapture.capture\")(function* (\n request: PageCaptureRequest,\n ): Effect.fn.Return<PageCaptureResult, PageCaptureError> {\n const usesWorkersAi = request.action._tag === \"CapturePageStructured\";\n\n if (usesWorkersAi) {\n if (workersAi === undefined) {\n return yield* PageCaptureUnsupportedError.make({\n implementation: browserRestCaptureImplementation,\n feature: \"action\",\n message:\n \"Structured capture invokes separately billed Workers AI and requires an explicit authorization and accounting policy\",\n });\n }\n yield* workersAi.authorizeAndAccount(request).pipe(\n Effect.mapError((cause) =>\n PageCaptureInferencePolicyError.make({\n implementation: browserRestCaptureImplementation,\n provider: \"cloudflare-workers-ai\",\n reason: cause.reason,\n message:\n cause.reason === \"authorization\"\n ? \"Workers AI extraction was not authorized\"\n : \"Workers AI extraction could not be accounted for\",\n cause,\n }),\n ),\n );\n }\n const action = actionName(request.action);\n const path = `${API_ORIGIN}/client/v4/accounts/${encodeURIComponent(options.accountId)}/browser-rendering/${action}`;\n\n const requestWithBody = yield* HttpClientRequest.post(path).pipe(\n request.engine === \"kitesurf\"\n ? HttpClientRequest.setUrlParam(\"browser\", \"kitesurf\")\n : (value) => value,\n HttpClientRequest.bearerToken(options.apiToken),\n HttpClientRequest.acceptJson,\n HttpClientRequest.bodyJson(actionBody(request)),\n Effect.mapError((cause) => protocolError(\"Encoding the Browser Run request failed\", cause)),\n );\n\n const response = yield* client\n .execute(requestWithBody)\n .pipe(\n Effect.mapError((cause) => protocolError(\"Calling the Browser Run REST API failed\", cause)),\n );\n\n const bodyText = yield* readBoundedResponse(response, request);\n\n if (response.status === 429) {\n const reason = isQuotaMessage(bodyText) ? \"quota\" : \"rate\";\n\n return yield* PageCaptureRateLimitedError.make({\n implementation: browserRestCaptureImplementation,\n reason,\n ...(retryAfterMillis(response.headers) === undefined\n ? {}\n : { retryAfterMillis: retryAfterMillis(response.headers) }),\n ...(privateResponseCause(bodyText, options.apiToken) === undefined\n ? {}\n : { cause: privateResponseCause(bodyText, options.apiToken) }),\n message:\n reason === \"quota\"\n ? \"Browser Run exceeded its browser quota\"\n : \"Browser Run was rate limited\",\n });\n }\n if (response.status < 200 || response.status >= 300) {\n const error =\n response.status >= 500\n ? protocolError(\n `Browser Run answered HTTP ${String(response.status)}`,\n privateResponseCause(bodyText, options.apiToken),\n )\n : navigationError(\n `Browser Run answered HTTP ${String(response.status)}`,\n privateResponseCause(bodyText, options.apiToken),\n );\n\n return yield* error;\n }\n if (!isJsonResponse(response.headers)) {\n return yield* protocolError(\n \"The Browser Run success response was not a JSON response envelope\",\n privateResponseCause(bodyText, options.apiToken),\n );\n }\n const output = parseOutput(request.action, bodyText, options.apiToken);\n\n if (\n output._tag === \"PageCaptureNavigationError\" ||\n output._tag === \"PageCaptureProtocolError\"\n ) {\n return yield* output;\n }\n\n return PageCaptureResult.make({\n implementation: browserRestCaptureImplementation,\n output,\n resourceUse: PageCaptureResourceUse.make({\n ...(browserMillis(response.headers) === undefined\n ? {}\n : { browserMillis: browserMillis(response.headers) }),\n ...(usesWorkersAi\n ? {\n inference: PageCaptureInferenceUse.make({\n provider: \"cloudflare-workers-ai\",\n modelCalls: 1,\n }),\n }\n : {}),\n }),\n });\n });\n\n/** REST PageCapture layer for Node and other non-Worker composition roots. */\nexport const browserRestCaptureLayer = (\n options: BrowserRestCaptureOptions,\n): Layer.Layer<PageCapture, never, HttpClient.HttpClient> =>\n Layer.effect(\n PageCapture,\n Effect.map(HttpClient.HttpClient, (client) =>\n PageCapture.of({ capture: makeCapture(client, options) }),\n ),\n );\n\n/** REST structured extraction additionally requires explicit Workers AI authorization/accounting. */\nexport const browserRestWorkersAiCaptureLayer = (\n options: BrowserRestCaptureOptions,\n): Layer.Layer<PageCapture, never, HttpClient.HttpClient | BrowserQuickActionWorkersAi> =>\n Layer.effect(\n PageCapture,\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient;\n const workersAi = yield* BrowserQuickActionWorkersAi;\n\n return PageCapture.of({ capture: makeCapture(client, options, workersAi) });\n }),\n );\n\n/** Explicit REST credentials and optional authorization for separately billed extraction. */\nexport interface CloudflareBrowserRestOptions extends BrowserRestCaptureOptions {\n readonly workersAi?: BrowserQuickActionWorkersAiPolicy;\n}\n\n/** Node-safe WebCapture handler assembly; the application supplies its HttpClient. */\nexport const CloudflareBrowserRest = {\n /**\n * Supports capture, scrape, and extraction definitions. Only PageCapture is supplied;\n * handler errors and other services remain visible. Extraction fails closed without\n * workersAi. Existing host policies, response limits, and scoped cleanup are unchanged.\n */\n layer: <A, E, R>(\n definition: { readonly handlers: Layer.Layer<A, E, R> },\n options: CloudflareBrowserRestOptions,\n ): Layer.Layer<A, E, Exclude<R, PageCapture> | HttpClient.HttpClient> => {\n const capture =\n options.workersAi === undefined\n ? browserRestCaptureLayer(options)\n : browserRestWorkersAiCaptureLayer(options).pipe(\n Layer.provide(BrowserQuickActionWorkersAi.layer(options.workersAi)),\n );\n\n return definition.handlers.pipe(Layer.provide(capture));\n },\n};\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { Layer, Redacted } from "effect";
2
+ import { SandboxImplementation } from "effect-agent/sandbox";
3
+ import { HttpClient } from "effect/unstable/http";
4
+ import { PageCrawl } from "effect-agent/page-crawl";
5
+ //#region src/BrowserRestCrawl.d.ts
6
+ /** Node-safe REST crawl implementation. It never exposes or persists provider job identity. */
7
+ export declare const browserRestCrawlImplementation: SandboxImplementation;
8
+ /** Explicit credentials. The token is projected only into fixed-origin Authorization headers. */
9
+ export interface BrowserRestCrawlOptions {
10
+ readonly accountId: string;
11
+ readonly apiToken: Redacted.Redacted<string>;
12
+ }
13
+ /** Cloudflare REST PageCrawl Layer for Node and other non-Worker composition roots. */
14
+ export declare const browserRestCrawlLayer: (options: BrowserRestCrawlOptions) => Layer.Layer<PageCrawl, never, HttpClient.HttpClient>;
15
+ //#endregion
16
+ //# sourceMappingURL=BrowserRestCrawl.d.mts.map
@@ -0,0 +1,244 @@
1
+ import { Duration, Effect, Layer, Option, Redacted, Ref, Schedule, Schema, Scope, Stream } from "effect";
2
+ import { SandboxImplementation } from "effect-agent/sandbox";
3
+ import { HttpClient, HttpClientRequest } from "effect/unstable/http";
4
+ import { PageCrawl, PageCrawlLimitError, PageCrawlProtocolError, PageCrawlRateLimitedError, PageCrawlRecord, PageCrawlTerminalError } from "effect-agent/page-crawl";
5
+ //#region src/BrowserRestCrawl.ts
6
+ /** Node-safe REST crawl implementation. It never exposes or persists provider job identity. */
7
+ const browserRestCrawlImplementation = SandboxImplementation.make({
8
+ isolation: "isolated",
9
+ identity: "cloudflare-browser-rest-crawl"
10
+ });
11
+ const API_ORIGIN = "https://api.cloudflare.com";
12
+ const POLL_INTERVAL = Duration.seconds(1);
13
+ const CANCEL_TIMEOUT = Duration.seconds(30);
14
+ const MAX_CONTROL_RESPONSE_BYTES = 65536;
15
+ const MAX_RESULTS_RESPONSE_BYTES = 12582912;
16
+ const MAX_DIAGNOSTIC_LENGTH = 8e3;
17
+ const MAX_CURSOR_LENGTH = 1024;
18
+ const MAX_RECORDS_PER_RESPONSE = 1e4;
19
+ const BoundedJobId = Schema.NonEmptyString.check(Schema.isMaxLength(256));
20
+ const BoundedCursorString = Schema.NonEmptyString.check(Schema.isMaxLength(MAX_CURSOR_LENGTH));
21
+ const ProviderCursor = Schema.Union([BoundedCursorString, Schema.Natural]);
22
+ const ProviderJobStatus = Schema.Literals([
23
+ "running",
24
+ "completed",
25
+ "errored",
26
+ "cancelled_by_user",
27
+ "cancelled_due_to_timeout",
28
+ "cancelled_due_to_limits"
29
+ ]);
30
+ const ProviderResult = Schema.Struct({
31
+ id: BoundedJobId,
32
+ status: ProviderJobStatus,
33
+ browserSecondsUsed: Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))),
34
+ total: Schema.optionalKey(Schema.Natural),
35
+ finished: Schema.optionalKey(Schema.Natural),
36
+ skipped: Schema.optionalKey(Schema.Natural),
37
+ records: Schema.Array(PageCrawlRecord).check(Schema.isMaxLength(MAX_RECORDS_PER_RESPONSE)),
38
+ cursor: Schema.optionalKey(ProviderCursor)
39
+ });
40
+ const CreateEnvelope = Schema.Struct({
41
+ success: Schema.Literal(true),
42
+ result: BoundedJobId
43
+ });
44
+ const ResultEnvelope = Schema.Struct({
45
+ success: Schema.Literal(true),
46
+ result: ProviderResult
47
+ });
48
+ const DeleteEnvelope = Schema.Struct({
49
+ success: Schema.Literal(true),
50
+ result: Schema.Struct({
51
+ job_id: BoundedJobId,
52
+ message: Schema.String.check(Schema.isMaxLength(MAX_DIAGNOSTIC_LENGTH))
53
+ })
54
+ });
55
+ const boundedDiagnostic = (message) => message.slice(0, MAX_DIAGNOSTIC_LENGTH);
56
+ const privateCause = (value, apiToken) => {
57
+ const raw = boundedDiagnostic(String(value));
58
+ if (raw.length === 0) return void 0;
59
+ const token = Redacted.value(apiToken);
60
+ return new Error(token.length === 0 ? raw : raw.replaceAll(token, "[REDACTED]"));
61
+ };
62
+ const protocolError = (message, cause) => PageCrawlProtocolError.make({
63
+ implementation: browserRestCrawlImplementation,
64
+ message: boundedDiagnostic(message),
65
+ ...cause === void 0 ? {} : { cause }
66
+ });
67
+ const limitError = (request, limit, observed) => {
68
+ const maximum = limit === "pages" ? request.limits.maxPages : limit === "page-bytes" ? request.limits.maxPageBytes : limit === "total-bytes" ? request.limits.maxTotalBytes : request.limits.deadlineMillis;
69
+ return PageCrawlLimitError.make({
70
+ implementation: browserRestCrawlImplementation,
71
+ limit,
72
+ maximum,
73
+ observed,
74
+ message: `The crawl exceeded its ${limit} limit`
75
+ });
76
+ };
77
+ const retryAfterMillis = (headers) => {
78
+ const seconds = Number(headers["retry-after"]);
79
+ if (!Number.isSafeInteger(seconds) || seconds < 0) return void 0;
80
+ const millis = seconds * 1e3;
81
+ return Number.isSafeInteger(millis) ? millis : void 0;
82
+ };
83
+ const isJsonResponse = (headers) => {
84
+ const contentType = headers["content-type"];
85
+ if (contentType === void 0) return false;
86
+ const mediaType = contentType.split(";", 1)[0]?.trim().toLowerCase();
87
+ return mediaType === "application/json" || mediaType?.endsWith("+json") === true;
88
+ };
89
+ const isQuotaMessage = (bodyText) => /time limit|daily|quota/i.test(bodyText);
90
+ const endpoint = (options, jobId) => `${API_ORIGIN}/client/v4/accounts/${encodeURIComponent(options.accountId)}/browser-rendering/crawl${jobId === void 0 ? "" : `/${encodeURIComponent(jobId)}`}`;
91
+ const readBoundedResponse = Effect.fn("BrowserRestCrawl.readBoundedResponse")(function* (response, maximum) {
92
+ const decoder = new TextDecoder("utf-8", {
93
+ fatal: true,
94
+ ignoreBOM: false
95
+ });
96
+ const state = yield* Stream.runFoldEffect(response.stream, () => ({
97
+ observed: 0,
98
+ text: ""
99
+ }), (current, chunk) => {
100
+ const observed = current.observed + chunk.byteLength;
101
+ if (observed > maximum) return Effect.fail(protocolError(`The Browser Run crawl response exceeded ${String(maximum)} transport bytes`));
102
+ return Effect.try({
103
+ try: () => ({
104
+ observed,
105
+ text: current.text + decoder.decode(chunk, { stream: true })
106
+ }),
107
+ catch: (cause) => protocolError("Decoding the Browser Run crawl response failed", cause)
108
+ });
109
+ }).pipe(Effect.mapError((cause) => Schema.is(PageCrawlProtocolError)(cause) ? cause : protocolError("Reading the Browser Run crawl response failed", cause)));
110
+ return yield* Effect.try({
111
+ try: () => state.text + decoder.decode(),
112
+ catch: (cause) => protocolError("Decoding the Browser Run crawl response failed", cause)
113
+ });
114
+ });
115
+ const deadlineFailure = Effect.fn("BrowserRestCrawl.deadlineFailure")(function* (request, startedAt) {
116
+ const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
117
+ return yield* limitError(request, "deadline", Math.max(0, now - startedAt));
118
+ });
119
+ const withinDeadline = Effect.fn("BrowserRestCrawl.withinDeadline")(function* (effect, request, startedAt) {
120
+ const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
121
+ const elapsed = Math.max(0, now - startedAt);
122
+ const remaining = request.limits.deadlineMillis - elapsed;
123
+ if (remaining <= 0) return yield* limitError(request, "deadline", elapsed);
124
+ return yield* effect.pipe(Effect.timeoutOrElse({
125
+ duration: Duration.millis(remaining),
126
+ orElse: () => deadlineFailure(request, startedAt)
127
+ }));
128
+ });
129
+ const checkDeadline = Effect.fn("BrowserRestCrawl.checkDeadline")(function* (request, startedAt) {
130
+ const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
131
+ const elapsed = Math.max(0, now - startedAt);
132
+ if (elapsed >= request.limits.deadlineMillis) return yield* limitError(request, "deadline", elapsed);
133
+ });
134
+ const normalizedCursor = (cursor) => String(cursor);
135
+ const sameHost = (expectedHost, url) => new URL(url).host === expectedHost;
136
+ const makeCrawl = (client, options) => (input) => Stream.unwrap(Effect.gen(function* () {
137
+ const startedAt = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
138
+ const startHost = new URL(input.startUrl).host;
139
+ const executeJson = Effect.fn("BrowserRestCrawl.executeJson")(function* (request, schema, maximum) {
140
+ const authorized = HttpClientRequest.bearerToken(request, options.apiToken).pipe(HttpClientRequest.acceptJson);
141
+ const response = yield* client.execute(authorized).pipe(Effect.mapError((cause) => protocolError("Calling the Browser Run crawl REST API failed", privateCause(cause, options.apiToken))));
142
+ const bodyText = yield* readBoundedResponse(response, maximum);
143
+ if (response.status === 429) {
144
+ const reason = isQuotaMessage(bodyText) ? "quota" : "rate";
145
+ return yield* PageCrawlRateLimitedError.make({
146
+ implementation: browserRestCrawlImplementation,
147
+ reason,
148
+ ...retryAfterMillis(response.headers) === void 0 ? {} : { retryAfterMillis: retryAfterMillis(response.headers) },
149
+ message: reason === "quota" ? "Browser Run exceeded its browser quota" : "Browser Run crawl was rate limited",
150
+ ...privateCause(bodyText, options.apiToken) === void 0 ? {} : { cause: privateCause(bodyText, options.apiToken) }
151
+ });
152
+ }
153
+ if (response.status < 200 || response.status >= 300) return yield* protocolError(`Browser Run crawl answered HTTP ${String(response.status)}`, privateCause(bodyText, options.apiToken));
154
+ if (!isJsonResponse(response.headers)) return yield* protocolError("The Browser Run crawl success response was not JSON", privateCause(bodyText, options.apiToken));
155
+ return yield* Schema.decodeEffect(Schema.fromJsonString(schema))(bodyText).pipe(Effect.mapError((cause) => protocolError("Browser Run returned a malformed crawl response", privateCause(cause, options.apiToken))));
156
+ });
157
+ const createJob = Effect.gen(function* () {
158
+ const requestWithBody = yield* HttpClientRequest.post(endpoint(options)).pipe(HttpClientRequest.bodyJson({
159
+ url: input.startUrl,
160
+ crawlPurposes: [...input.purposes],
161
+ limit: input.limits.maxPages,
162
+ depth: input.limits.maxDepth,
163
+ formats: ["markdown"],
164
+ render: true,
165
+ options: {
166
+ includeExternalLinks: false,
167
+ includeSubdomains: false
168
+ }
169
+ }), Effect.mapError((cause) => protocolError("Encoding the Browser Run crawl request failed", cause)));
170
+ return (yield* withinDeadline(executeJson(requestWithBody, CreateEnvelope, MAX_CONTROL_RESPONSE_BYTES), input, startedAt)).result;
171
+ });
172
+ const job = yield* Effect.uninterruptibleMask((restore) => restore(createJob).pipe(Effect.flatMap((id) => Effect.gen(function* () {
173
+ const state = yield* Ref.make("running");
174
+ const scope = yield* Effect.scope;
175
+ const cancelIfRunning = Ref.modify(state, (current) => current === "running" ? [true, "terminal"] : [false, "terminal"]).pipe(Effect.flatMap((shouldCancel) => {
176
+ if (!shouldCancel) return Effect.void;
177
+ return executeJson(HttpClientRequest.delete(endpoint(options, id)), DeleteEnvelope, MAX_CONTROL_RESPONSE_BYTES).pipe(Effect.flatMap((deleted) => deleted.result.job_id === id ? Effect.void : protocolError("Browser Run cancelled a different crawl job"))).pipe(Effect.timeoutOrElse({
178
+ duration: CANCEL_TIMEOUT,
179
+ orElse: () => protocolError("Cancelling the Browser Run crawl exceeded 30 seconds")
180
+ }));
181
+ }));
182
+ yield* Scope.addFinalizer(scope, cancelIfRunning.pipe(Effect.catchCause(() => Effect.logWarning("Browser Run crawl cancellation failed"))));
183
+ return {
184
+ id,
185
+ state
186
+ };
187
+ }))));
188
+ const fetchResult = Effect.fn("BrowserRestCrawl.fetchResult")(function* (cursor, statusOnly) {
189
+ let request = HttpClientRequest.get(endpoint(options, job.id));
190
+ if (statusOnly) request = HttpClientRequest.setUrlParam(request, "limit", "1");
191
+ if (Option.isSome(cursor)) request = HttpClientRequest.setUrlParam(request, "cursor", cursor.value);
192
+ const envelope = yield* withinDeadline(executeJson(request, ResultEnvelope, MAX_RESULTS_RESPONSE_BYTES), input, startedAt);
193
+ if (envelope.result.id !== job.id) return yield* protocolError("Browser Run returned a different crawl job identity");
194
+ return envelope.result;
195
+ });
196
+ const terminal = yield* Effect.uninterruptibleMask((restore) => restore(withinDeadline(Effect.repeat(fetchResult(Option.none(), true), {
197
+ schedule: Schedule.spaced(POLL_INTERVAL),
198
+ until: (result) => result.status !== "running"
199
+ }), input, startedAt)).pipe(Effect.tap(() => Ref.set(job.state, "terminal"))));
200
+ if (terminal.status === "running") return yield* protocolError("Browser Run polling stopped before a terminal status");
201
+ if (terminal.status !== "completed") return yield* PageCrawlTerminalError.make({
202
+ implementation: browserRestCrawlImplementation,
203
+ status: terminal.status,
204
+ message: `Browser Run crawl ended with status ${terminal.status}`
205
+ });
206
+ return Stream.paginate({
207
+ cursor: Option.none(),
208
+ seen: []
209
+ }, (state) => Effect.gen(function* () {
210
+ const result = yield* fetchResult(state.cursor, false);
211
+ if (result.status !== "completed") return yield* protocolError("Browser Run changed crawl status during result pagination");
212
+ if (result.cursor === void 0) return [result.records, Option.none()];
213
+ const cursor = normalizedCursor(result.cursor);
214
+ if (state.seen.includes(cursor)) return yield* protocolError("Browser Run repeated a crawl result cursor");
215
+ if (state.seen.length >= input.limits.maxPages) return yield* protocolError("Browser Run returned too many crawl result cursors");
216
+ return [result.records, Option.some({
217
+ cursor: Option.some(cursor),
218
+ seen: [...state.seen, cursor]
219
+ })];
220
+ })).pipe(Stream.rechunk(1), Stream.mapAccumEffect(() => ({
221
+ pages: 0,
222
+ totalBytes: 0
223
+ }), (state, record) => Effect.gen(function* () {
224
+ yield* checkDeadline(input, startedAt);
225
+ const pages = state.pages + 1;
226
+ if (pages > input.limits.maxPages) return yield* limitError(input, "pages", pages);
227
+ if (!sameHost(startHost, record.url)) return yield* protocolError("Browser Run returned an off-host crawl record");
228
+ if (record.metadata !== void 0 && !sameHost(startHost, record.metadata.url)) return yield* protocolError("Browser Run returned off-host crawl metadata");
229
+ const pageBytes = record.markdown === void 0 ? 0 : new TextEncoder().encode(record.markdown).byteLength;
230
+ if (pageBytes > input.limits.maxPageBytes) return yield* limitError(input, "page-bytes", pageBytes);
231
+ const totalBytes = state.totalBytes + pageBytes;
232
+ if (totalBytes > input.limits.maxTotalBytes) return yield* limitError(input, "total-bytes", totalBytes);
233
+ return [{
234
+ pages,
235
+ totalBytes
236
+ }, [record]];
237
+ })));
238
+ }));
239
+ /** Cloudflare REST PageCrawl Layer for Node and other non-Worker composition roots. */
240
+ const browserRestCrawlLayer = (options) => Layer.effect(PageCrawl, Effect.map(HttpClient.HttpClient, (client) => PageCrawl.of({ crawl: makeCrawl(client, options) })));
241
+ //#endregion
242
+ export { browserRestCrawlImplementation, browserRestCrawlLayer };
243
+
244
+ //# sourceMappingURL=BrowserRestCrawl.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserRestCrawl.mjs","names":[],"sources":["../src/BrowserRestCrawl.ts"],"sourcesContent":["import {\n Duration,\n Effect,\n Layer,\n Option,\n Redacted,\n Ref,\n Schedule,\n Schema,\n Scope,\n Stream,\n} from \"effect\";\nimport {\n PageCrawl,\n PageCrawlLimitError,\n PageCrawlProtocolError,\n PageCrawlRateLimitedError,\n PageCrawlRecord,\n PageCrawlTerminalError,\n type PageCrawlCrawl,\n type PageCrawlError,\n type PageCrawlRequest,\n} from \"effect-agent/page-crawl\";\nimport { SandboxImplementation } from \"effect-agent/sandbox\";\nimport {\n HttpClient,\n HttpClientRequest,\n type HttpClientError,\n type HttpClientResponse,\n} from \"effect/unstable/http\";\n\n/** Node-safe REST crawl implementation. It never exposes or persists provider job identity. */\nexport const browserRestCrawlImplementation = SandboxImplementation.make({\n isolation: \"isolated\",\n identity: \"cloudflare-browser-rest-crawl\",\n});\n\nconst API_ORIGIN = \"https://api.cloudflare.com\";\nconst POLL_INTERVAL = Duration.seconds(1);\nconst CANCEL_TIMEOUT = Duration.seconds(30);\nconst MAX_CONTROL_RESPONSE_BYTES = 64 * 1024;\nconst MAX_RESULTS_RESPONSE_BYTES = 12 * 1024 * 1024;\nconst MAX_DIAGNOSTIC_LENGTH = 8_000;\nconst MAX_CURSOR_LENGTH = 1_024;\nconst MAX_RECORDS_PER_RESPONSE = 10_000;\nconst BoundedJobId = Schema.NonEmptyString.check(Schema.isMaxLength(256));\nconst BoundedCursorString = Schema.NonEmptyString.check(Schema.isMaxLength(MAX_CURSOR_LENGTH));\n// The generated API response Schema says string; the current product example returns a number.\nconst ProviderCursor = Schema.Union([BoundedCursorString, Schema.Natural]);\n\nconst ProviderJobStatus = Schema.Literals([\n \"running\",\n \"completed\",\n \"errored\",\n \"cancelled_by_user\",\n \"cancelled_due_to_timeout\",\n \"cancelled_due_to_limits\",\n]);\n\nconst ProviderResult = Schema.Struct({\n id: BoundedJobId,\n status: ProviderJobStatus,\n browserSecondsUsed: Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))),\n total: Schema.optionalKey(Schema.Natural),\n finished: Schema.optionalKey(Schema.Natural),\n skipped: Schema.optionalKey(Schema.Natural),\n records: Schema.Array(PageCrawlRecord).check(Schema.isMaxLength(MAX_RECORDS_PER_RESPONSE)),\n cursor: Schema.optionalKey(ProviderCursor),\n});\n\nconst CreateEnvelope = Schema.Struct({\n success: Schema.Literal(true),\n result: BoundedJobId,\n});\n\nconst ResultEnvelope = Schema.Struct({\n success: Schema.Literal(true),\n result: ProviderResult,\n});\n\nconst DeleteEnvelope = Schema.Struct({\n success: Schema.Literal(true),\n result: Schema.Struct({\n job_id: BoundedJobId,\n message: Schema.String.check(Schema.isMaxLength(MAX_DIAGNOSTIC_LENGTH)),\n }),\n});\n\n/** Explicit credentials. The token is projected only into fixed-origin Authorization headers. */\nexport interface BrowserRestCrawlOptions {\n readonly accountId: string;\n readonly apiToken: Redacted.Redacted<string>;\n}\n\nconst boundedDiagnostic = (message: string): string => message.slice(0, MAX_DIAGNOSTIC_LENGTH);\n\nconst privateCause = (value: unknown, apiToken: Redacted.Redacted<string>): Error | undefined => {\n const raw = boundedDiagnostic(String(value));\n\n if (raw.length === 0) return undefined;\n const token = Redacted.value(apiToken);\n\n return new Error(token.length === 0 ? raw : raw.replaceAll(token, \"[REDACTED]\"));\n};\n\nconst protocolError = (message: string, cause?: unknown): PageCrawlProtocolError =>\n PageCrawlProtocolError.make({\n implementation: browserRestCrawlImplementation,\n message: boundedDiagnostic(message),\n ...(cause === undefined ? {} : { cause }),\n });\n\nconst limitError = (\n request: PageCrawlRequest,\n limit: PageCrawlLimitError[\"limit\"],\n observed: number,\n): PageCrawlLimitError => {\n const maximum =\n limit === \"pages\"\n ? request.limits.maxPages\n : limit === \"page-bytes\"\n ? request.limits.maxPageBytes\n : limit === \"total-bytes\"\n ? request.limits.maxTotalBytes\n : request.limits.deadlineMillis;\n\n return PageCrawlLimitError.make({\n implementation: browserRestCrawlImplementation,\n limit,\n maximum,\n observed,\n message: `The crawl exceeded its ${limit} limit`,\n });\n};\n\nconst retryAfterMillis = (\n headers: Readonly<Record<string, string | undefined>>,\n): number | undefined => {\n const seconds = Number(headers[\"retry-after\"]);\n\n if (!Number.isSafeInteger(seconds) || seconds < 0) return undefined;\n const millis = seconds * 1_000;\n\n return Number.isSafeInteger(millis) ? millis : undefined;\n};\n\nconst isJsonResponse = (headers: Readonly<Record<string, string | undefined>>): boolean => {\n const contentType = headers[\"content-type\"];\n\n if (contentType === undefined) return false;\n const mediaType = contentType.split(\";\", 1)[0]?.trim().toLowerCase();\n\n return mediaType === \"application/json\" || mediaType?.endsWith(\"+json\") === true;\n};\n\nconst isQuotaMessage = (bodyText: string): boolean => /time limit|daily|quota/i.test(bodyText);\n\nconst endpoint = (options: BrowserRestCrawlOptions, jobId?: string): string =>\n `${API_ORIGIN}/client/v4/accounts/${encodeURIComponent(options.accountId)}/browser-rendering/crawl${\n jobId === undefined ? \"\" : `/${encodeURIComponent(jobId)}`\n }`;\n\nconst readBoundedResponse = Effect.fn(\"BrowserRestCrawl.readBoundedResponse\")(function* (\n response: HttpClientResponse.HttpClientResponse,\n maximum: number,\n): Effect.fn.Return<string, PageCrawlProtocolError> {\n const decoder = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: false });\n\n const state = yield* Stream.runFoldEffect<\n Uint8Array,\n HttpClientError.HttpClientError,\n never,\n { readonly observed: number; readonly text: string },\n PageCrawlProtocolError,\n never\n >(\n response.stream,\n () => ({ observed: 0, text: \"\" }),\n (current, chunk) => {\n const observed = current.observed + chunk.byteLength;\n\n if (observed > maximum) {\n return Effect.fail(\n protocolError(\n `The Browser Run crawl response exceeded ${String(maximum)} transport bytes`,\n ),\n );\n }\n\n return Effect.try({\n try: () => ({ observed, text: current.text + decoder.decode(chunk, { stream: true }) }),\n catch: (cause) => protocolError(\"Decoding the Browser Run crawl response failed\", cause),\n });\n },\n ).pipe(\n Effect.mapError((cause) =>\n Schema.is(PageCrawlProtocolError)(cause)\n ? cause\n : protocolError(\"Reading the Browser Run crawl response failed\", cause),\n ),\n );\n\n return yield* Effect.try({\n try: () => state.text + decoder.decode(),\n catch: (cause) => protocolError(\"Decoding the Browser Run crawl response failed\", cause),\n });\n});\n\nconst deadlineFailure = Effect.fn(\"BrowserRestCrawl.deadlineFailure\")(function* (\n request: PageCrawlRequest,\n startedAt: number,\n) {\n const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n\n return yield* limitError(request, \"deadline\", Math.max(0, now - startedAt));\n});\n\nconst withinDeadline = Effect.fn(\"BrowserRestCrawl.withinDeadline\")(function* <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n request: PageCrawlRequest,\n startedAt: number,\n): Effect.fn.Return<A, E | PageCrawlLimitError, R> {\n const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n const elapsed = Math.max(0, now - startedAt);\n const remaining = request.limits.deadlineMillis - elapsed;\n\n if (remaining <= 0) return yield* limitError(request, \"deadline\", elapsed);\n\n return yield* effect.pipe(\n Effect.timeoutOrElse({\n duration: Duration.millis(remaining),\n orElse: () => deadlineFailure(request, startedAt),\n }),\n );\n});\n\nconst checkDeadline = Effect.fn(\"BrowserRestCrawl.checkDeadline\")(function* (\n request: PageCrawlRequest,\n startedAt: number,\n) {\n const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n const elapsed = Math.max(0, now - startedAt);\n\n if (elapsed >= request.limits.deadlineMillis) {\n return yield* limitError(request, \"deadline\", elapsed);\n }\n});\n\nconst normalizedCursor = (cursor: typeof ProviderCursor.Type): string => String(cursor);\n\ninterface PaginationState {\n readonly cursor: Option.Option<string>;\n readonly seen: ReadonlyArray<string>;\n}\n\ninterface RecordLimitsState {\n readonly pages: number;\n readonly totalBytes: number;\n}\n\nconst sameHost = (expectedHost: string, url: string): boolean => new URL(url).host === expectedHost;\n\nconst makeCrawl =\n (client: HttpClient.HttpClient, options: BrowserRestCrawlOptions): PageCrawlCrawl =>\n (input) =>\n Stream.unwrap(\n Effect.gen(function* () {\n const startedAt = yield* Effect.clockWith((clock) => clock.currentTimeMillis);\n const startHost = new URL(input.startUrl).host;\n\n const executeJson = Effect.fn(\"BrowserRestCrawl.executeJson\")(function* <\n S extends Schema.Top,\n >(request: HttpClientRequest.HttpClientRequest, schema: S, maximum: number) {\n const authorized = HttpClientRequest.bearerToken(request, options.apiToken).pipe(\n HttpClientRequest.acceptJson,\n );\n\n const response = yield* client\n .execute(authorized)\n .pipe(\n Effect.mapError((cause) =>\n protocolError(\n \"Calling the Browser Run crawl REST API failed\",\n privateCause(cause, options.apiToken),\n ),\n ),\n );\n\n const bodyText = yield* readBoundedResponse(response, maximum);\n\n if (response.status === 429) {\n const reason = isQuotaMessage(bodyText) ? \"quota\" : \"rate\";\n\n return yield* PageCrawlRateLimitedError.make({\n implementation: browserRestCrawlImplementation,\n reason,\n ...(retryAfterMillis(response.headers) === undefined\n ? {}\n : { retryAfterMillis: retryAfterMillis(response.headers) }),\n message:\n reason === \"quota\"\n ? \"Browser Run exceeded its browser quota\"\n : \"Browser Run crawl was rate limited\",\n ...(privateCause(bodyText, options.apiToken) === undefined\n ? {}\n : { cause: privateCause(bodyText, options.apiToken) }),\n });\n }\n if (response.status < 200 || response.status >= 300) {\n return yield* protocolError(\n `Browser Run crawl answered HTTP ${String(response.status)}`,\n privateCause(bodyText, options.apiToken),\n );\n }\n if (!isJsonResponse(response.headers)) {\n return yield* protocolError(\n \"The Browser Run crawl success response was not JSON\",\n privateCause(bodyText, options.apiToken),\n );\n }\n\n return yield* Schema.decodeEffect(Schema.fromJsonString(schema))(bodyText).pipe(\n Effect.mapError((cause) =>\n protocolError(\n \"Browser Run returned a malformed crawl response\",\n privateCause(cause, options.apiToken),\n ),\n ),\n );\n });\n\n const createJob = Effect.gen(function* () {\n const requestWithBody = yield* HttpClientRequest.post(endpoint(options)).pipe(\n HttpClientRequest.bodyJson({\n url: input.startUrl,\n crawlPurposes: [...input.purposes],\n limit: input.limits.maxPages,\n depth: input.limits.maxDepth,\n formats: [\"markdown\"],\n render: true,\n options: {\n includeExternalLinks: false,\n includeSubdomains: false,\n },\n }),\n Effect.mapError((cause) =>\n protocolError(\"Encoding the Browser Run crawl request failed\", cause),\n ),\n );\n\n const created = yield* withinDeadline(\n executeJson(requestWithBody, CreateEnvelope, MAX_CONTROL_RESPONSE_BYTES),\n input,\n startedAt,\n );\n\n return created.result;\n });\n\n const job = yield* Effect.uninterruptibleMask((restore) =>\n restore(createJob).pipe(\n Effect.flatMap((id) =>\n Effect.gen(function* () {\n const state = yield* Ref.make<\"running\" | \"terminal\">(\"running\");\n const scope = yield* Effect.scope;\n\n const cancelIfRunning = Ref.modify(state, (current) =>\n current === \"running\"\n ? [true, \"terminal\" as const]\n : [false, \"terminal\" as const],\n ).pipe(\n Effect.flatMap((shouldCancel) => {\n if (!shouldCancel) return Effect.void;\n\n const cancellation = executeJson(\n HttpClientRequest.delete(endpoint(options, id)),\n DeleteEnvelope,\n MAX_CONTROL_RESPONSE_BYTES,\n ).pipe(\n Effect.flatMap((deleted) =>\n deleted.result.job_id === id\n ? Effect.void\n : protocolError(\"Browser Run cancelled a different crawl job\"),\n ),\n );\n\n return cancellation.pipe(\n Effect.timeoutOrElse({\n duration: CANCEL_TIMEOUT,\n orElse: () =>\n protocolError(\"Cancelling the Browser Run crawl exceeded 30 seconds\"),\n }),\n );\n }),\n );\n\n yield* Scope.addFinalizer(\n scope,\n cancelIfRunning.pipe(\n Effect.catchCause(() =>\n Effect.logWarning(\"Browser Run crawl cancellation failed\"),\n ),\n ),\n );\n\n return { id, state } as const;\n }),\n ),\n ),\n );\n\n const fetchResult = Effect.fn(\"BrowserRestCrawl.fetchResult\")(function* (\n cursor: Option.Option<string>,\n statusOnly: boolean,\n ) {\n let request = HttpClientRequest.get(endpoint(options, job.id));\n\n if (statusOnly) request = HttpClientRequest.setUrlParam(request, \"limit\", \"1\");\n if (Option.isSome(cursor)) {\n request = HttpClientRequest.setUrlParam(request, \"cursor\", cursor.value);\n }\n\n // `limit=1` still permits one complete record, so poll GETs use the bounded result cap.\n const envelope = yield* withinDeadline(\n executeJson(request, ResultEnvelope, MAX_RESULTS_RESPONSE_BYTES),\n input,\n startedAt,\n );\n\n if (envelope.result.id !== job.id) {\n return yield* protocolError(\"Browser Run returned a different crawl job identity\");\n }\n\n return envelope.result;\n });\n\n const terminal = yield* Effect.uninterruptibleMask((restore) =>\n restore(\n withinDeadline(\n Effect.repeat(fetchResult(Option.none(), true), {\n schedule: Schedule.spaced(POLL_INTERVAL),\n until: (result) => result.status !== \"running\",\n }),\n input,\n startedAt,\n ),\n ).pipe(Effect.tap(() => Ref.set(job.state, \"terminal\"))),\n );\n\n if (terminal.status === \"running\") {\n return yield* protocolError(\"Browser Run polling stopped before a terminal status\");\n }\n if (terminal.status !== \"completed\") {\n return yield* PageCrawlTerminalError.make({\n implementation: browserRestCrawlImplementation,\n status: terminal.status,\n message: `Browser Run crawl ended with status ${terminal.status}`,\n });\n }\n\n const pages = Stream.paginate<PaginationState, PageCrawlRecord, PageCrawlError>(\n { cursor: Option.none(), seen: [] },\n (state) =>\n Effect.gen(function* () {\n const result = yield* fetchResult(state.cursor, false);\n\n if (result.status !== \"completed\") {\n return yield* protocolError(\n \"Browser Run changed crawl status during result pagination\",\n );\n }\n if (result.cursor === undefined) {\n return [result.records, Option.none()] as const;\n }\n const cursor = normalizedCursor(result.cursor);\n\n if (state.seen.includes(cursor)) {\n return yield* protocolError(\"Browser Run repeated a crawl result cursor\");\n }\n if (state.seen.length >= input.limits.maxPages) {\n return yield* protocolError(\"Browser Run returned too many crawl result cursors\");\n }\n\n return [\n result.records,\n Option.some({ cursor: Option.some(cursor), seen: [...state.seen, cursor] }),\n ] as const;\n }),\n );\n\n return pages.pipe(\n Stream.rechunk(1),\n Stream.mapAccumEffect(\n (): RecordLimitsState => ({ pages: 0, totalBytes: 0 }),\n (state, record) =>\n Effect.gen(function* () {\n yield* checkDeadline(input, startedAt);\n const pages = state.pages + 1;\n\n if (pages > input.limits.maxPages) {\n return yield* limitError(input, \"pages\", pages);\n }\n if (!sameHost(startHost, record.url)) {\n return yield* protocolError(\"Browser Run returned an off-host crawl record\");\n }\n if (record.metadata !== undefined && !sameHost(startHost, record.metadata.url)) {\n return yield* protocolError(\"Browser Run returned off-host crawl metadata\");\n }\n\n const pageBytes =\n record.markdown === undefined\n ? 0\n : new TextEncoder().encode(record.markdown).byteLength;\n\n if (pageBytes > input.limits.maxPageBytes) {\n return yield* limitError(input, \"page-bytes\", pageBytes);\n }\n const totalBytes = state.totalBytes + pageBytes;\n\n if (totalBytes > input.limits.maxTotalBytes) {\n return yield* limitError(input, \"total-bytes\", totalBytes);\n }\n\n return [{ pages, totalBytes }, [record]] as const;\n }),\n ),\n );\n }),\n );\n\n/** Cloudflare REST PageCrawl Layer for Node and other non-Worker composition roots. */\nexport const browserRestCrawlLayer = (\n options: BrowserRestCrawlOptions,\n): Layer.Layer<PageCrawl, never, HttpClient.HttpClient> =>\n Layer.effect(\n PageCrawl,\n Effect.map(HttpClient.HttpClient, (client) =>\n PageCrawl.of({ crawl: makeCrawl(client, options) }),\n ),\n );\n"],"mappings":"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"}