import * as z from "zod" import { defineAction } from "../../automation/actions" import { CLOUDFLARE_ACCESSIBILITY_NODE_SCHEMA, CLOUDFLARE_BROWSER_ACCESSIBILITY_INPUT_SCHEMA, CLOUDFLARE_BROWSER_CANCEL_CRAWL_INPUT_SCHEMA, CLOUDFLARE_BROWSER_CANCEL_CRAWL_RESULT_SCHEMA, CLOUDFLARE_BROWSER_CONTENT_INPUT_SCHEMA, CLOUDFLARE_BROWSER_CREATE_CRAWL_INPUT_SCHEMA, CLOUDFLARE_BROWSER_CRAWL_RESULT_SCHEMA, CLOUDFLARE_BROWSER_GET_CRAWL_INPUT_SCHEMA, CLOUDFLARE_BROWSER_JSON_INPUT_SCHEMA, CLOUDFLARE_BROWSER_LINKS_INPUT_SCHEMA, CLOUDFLARE_BROWSER_MARKDOWN_INPUT_SCHEMA, CLOUDFLARE_BROWSER_PDF_INPUT_SCHEMA, CLOUDFLARE_BROWSER_SCRAPE_INPUT_SCHEMA, CLOUDFLARE_BROWSER_SCRAPE_RESULT_SCHEMA, CLOUDFLARE_BROWSER_SCREENSHOT_INPUT_SCHEMA, CLOUDFLARE_BROWSER_SNAPSHOT_INPUT_SCHEMA, CLOUDFLARE_BROWSER_SNAPSHOT_RESULT_SCHEMA, getCloudflareApi, } from "@automate.ax/integration-contracts/cloudflare" import { CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE, cloudflareAccountRequirement, } from "./lib" const ACCOUNT = "cloudflare" const ACCESSIBILITY_RESULT_SCHEMA = z.object({ accessibilityTree: CLOUDFLARE_ACCESSIBILITY_NODE_SCHEMA.nullable(), }) const JSON_RESULT_SCHEMA = z.record(z.string(), z.json()) const CRAWL_CREATED_SCHEMA = z.object({ jobId: z.string().min(1) }) /** Renders a page and returns its serialized HTML. */ export const renderCloudflareContent = defineAction("Render Cloudflare content") .describe("Renders a URL or HTML document and returns its serialized HTML.") .account( ACCOUNT, cloudflareAccountRequirement(CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE), ) .input(CLOUDFLARE_BROWSER_CONTENT_INPUT_SCHEMA) .output(z.string()) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { accountId, cacheTtl, ...body } }) => getCloudflareApi(account).request(browserPath(accountId, "content"), { body, keyFormat: "browser-rendering", method: "POST", query: { cacheTtl }, responseSchema: z.string(), }), ) /** Renders a page and converts its content to Markdown. */ export const extractCloudflareMarkdown = defineAction( "Extract Cloudflare Markdown", ) .describe( "Renders a URL or HTML document and returns its content as Markdown.", ) .account( ACCOUNT, cloudflareAccountRequirement(CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE), ) .input(CLOUDFLARE_BROWSER_MARKDOWN_INPUT_SCHEMA) .output(z.string()) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { accountId, cacheTtl, ...body } }) => getCloudflareApi(account).request(browserPath(accountId, "markdown"), { body, keyFormat: "browser-rendering", method: "POST", query: { cacheTtl }, responseSchema: z.string(), }), ) /** Renders a page and returns its accessibility tree. */ export const getCloudflareAccessibilityTree = defineAction( "Get Cloudflare accessibility tree", ) .describe( "Renders a URL or HTML document and returns its accessibility tree.", ) .account( ACCOUNT, cloudflareAccountRequirement(CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE), ) .input(CLOUDFLARE_BROWSER_ACCESSIBILITY_INPUT_SCHEMA) .output(CLOUDFLARE_ACCESSIBILITY_NODE_SCHEMA.nullable()) .retry({ replaySafety: "unsafe" }) .handler( async ({ account, input: { accountId, cacheTtl, ...body } }) => ( await getCloudflareApi(account).request( browserPath(accountId, "accessibilityTree"), { body, keyFormat: "browser-rendering", method: "POST", query: { cacheTtl }, responseSchema: ACCESSIBILITY_RESULT_SCHEMA, }, ) ).accessibilityTree, ) /** Renders a page and extracts its links. */ export const extractCloudflareLinks = defineAction("Extract Cloudflare links") .describe("Renders a URL or HTML document and returns links from the page.") .account( ACCOUNT, cloudflareAccountRequirement(CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE), ) .input(CLOUDFLARE_BROWSER_LINKS_INPUT_SCHEMA) .output(z.string().array()) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { accountId, cacheTtl, ...body } }) => getCloudflareApi(account).request(browserPath(accountId, "links"), { body, keyFormat: "browser-rendering", method: "POST", query: { cacheTtl }, responseSchema: z.string().array(), }), ) /** Renders a page and extracts elements matching CSS selectors. */ export const scrapeCloudflareElements = defineAction( "Scrape Cloudflare elements", ) .describe("Renders a URL or HTML document and extracts selected elements.") .account( ACCOUNT, cloudflareAccountRequirement(CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE), ) .input(CLOUDFLARE_BROWSER_SCRAPE_INPUT_SCHEMA) .output(CLOUDFLARE_BROWSER_SCRAPE_RESULT_SCHEMA.array()) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { accountId, cacheTtl, ...body } }) => getCloudflareApi(account).request(browserPath(accountId, "scrape"), { body, keyFormat: "browser-rendering", method: "POST", query: { cacheTtl }, responseSchema: CLOUDFLARE_BROWSER_SCRAPE_RESULT_SCHEMA.array(), }), ) /** Renders a page and returns an image. */ export const captureCloudflareScreenshot = defineAction( "Capture Cloudflare screenshot", ) .describe("Renders a URL or HTML document and returns a screenshot image.") .account( ACCOUNT, cloudflareAccountRequirement(CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE), ) .input(CLOUDFLARE_BROWSER_SCREENSHOT_INPUT_SCHEMA) .output(z.instanceof(Blob)) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { accountId, cacheTtl, ...body } }) => getCloudflareApi(account).requestBlob( browserPath(accountId, "screenshot"), { accept: "image/*", body, keyFormat: "browser-rendering", method: "POST", query: { cacheTtl }, }, ), ) /** Renders a page and returns a PDF. */ export const renderCloudflarePdf = defineAction("Render Cloudflare PDF") .describe("Renders a URL or HTML document and returns a PDF.") .account( ACCOUNT, cloudflareAccountRequirement(CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE), ) .input(CLOUDFLARE_BROWSER_PDF_INPUT_SCHEMA) .output(z.instanceof(Blob)) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { accountId, cacheTtl, ...body } }) => getCloudflareApi(account).requestBlob(browserPath(accountId, "pdf"), { accept: "application/pdf", body, keyFormat: "browser-rendering", method: "POST", query: { cacheTtl }, }), ) /** Renders a page and returns several representations in one result. */ export const captureCloudflareSnapshot = defineAction( "Capture Cloudflare snapshot", ) .describe("Renders a page and returns multiple requested content formats.") .account( ACCOUNT, cloudflareAccountRequirement(CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE), ) .input(CLOUDFLARE_BROWSER_SNAPSHOT_INPUT_SCHEMA) .output(CLOUDFLARE_BROWSER_SNAPSHOT_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { accountId, cacheTtl, ...body } }) => getCloudflareApi(account).request(browserPath(accountId, "snapshot"), { body, keyFormat: "browser-rendering", method: "POST", query: { cacheTtl }, responseSchema: CLOUDFLARE_BROWSER_SNAPSHOT_RESULT_SCHEMA, }), ) /** Renders a page and extracts structured JSON with Cloudflare AI. */ export const extractCloudflareJson = defineAction("Extract Cloudflare JSON") .describe("Renders a page and extracts structured JSON using Cloudflare AI.") .account( ACCOUNT, cloudflareAccountRequirement(CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE), ) .input(CLOUDFLARE_BROWSER_JSON_INPUT_SCHEMA) .output(JSON_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { accountId, cacheTtl, ...body } }) => getCloudflareApi(account).request(browserPath(accountId, "json"), { body, keyFormat: "browser-rendering", method: "POST", query: { cacheTtl }, responseKeyFormat: "preserve", responseSchema: JSON_RESULT_SCHEMA, }), ) /** Starts an asynchronous Cloudflare crawl. */ export const createCloudflareCrawl = defineAction("Create Cloudflare crawl") .describe("Starts an asynchronous crawl from one URL.") .account( ACCOUNT, cloudflareAccountRequirement(CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE), ) .input(CLOUDFLARE_BROWSER_CREATE_CRAWL_INPUT_SCHEMA) .output(CRAWL_CREATED_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input: { accountId, cacheTtl, ...body } }) => ({ jobId: await getCloudflareApi(account).request( browserPath(accountId, "crawl"), { body, keyFormat: "browser-rendering", method: "POST", query: { cacheTtl }, responseSchema: z.string(), }, ), })) /** Retrieves one Cloudflare crawl and its paginated records. */ export const getCloudflareCrawl = defineAction("Get Cloudflare crawl") .describe("Retrieves one crawl's status and current page of records.") .account( ACCOUNT, cloudflareAccountRequirement(CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE), ) .input(CLOUDFLARE_BROWSER_GET_CRAWL_INPUT_SCHEMA) .output(CLOUDFLARE_BROWSER_CRAWL_RESULT_SCHEMA) .retry({ replaySafety: "safe" }) .handler(({ account, input: { accountId, jobId, ...query } }) => getCloudflareApi(account).request( `${browserPath(accountId, "crawl")}/${encodeURIComponent(jobId)}`, { keyFormat: "browser-rendering", query, responseSchema: CLOUDFLARE_BROWSER_CRAWL_RESULT_SCHEMA, }, ), ) /** Cancels one Cloudflare crawl. */ export const cancelCloudflareCrawl = defineAction("Cancel Cloudflare crawl") .describe("Cancels an active crawl and stops its queued URLs.") .account( ACCOUNT, cloudflareAccountRequirement(CLOUDFLARE_BROWSER_RENDERING_WRITE_SCOPE), ) .input(CLOUDFLARE_BROWSER_CANCEL_CRAWL_INPUT_SCHEMA) .output(CLOUDFLARE_BROWSER_CANCEL_CRAWL_RESULT_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(({ account, input: { accountId, jobId } }) => getCloudflareApi(account).request( `${browserPath(accountId, "crawl")}/${encodeURIComponent(jobId)}`, { method: "DELETE", responseSchema: CLOUDFLARE_BROWSER_CANCEL_CRAWL_RESULT_SCHEMA, }, ), ) /** * Builds one account-scoped Browser Run API path. * * @param accountId - Cloudflare account ID. * @param resource - Browser Run resource name. */ function browserPath(accountId: string, resource: string) { return `accounts/${encodeURIComponent(accountId)}/browser-rendering/${resource}` }