Skip to content

Extensions

Browser tools

Give an agent rendered page text, extract records, collect a site’s Markdown, or let an operator watch an interactive browser pass. Your application supplies Cloudflare bindings or credentials, authorizes actions, and chooses the data its Tools return.

Start with a stateless capture for one page. Choose a crawl only when the task needs a bounded set of same-host pages. Use an interactive pass only when navigation or page actions are essential.

Need Choose Where it runs What the application provides
Render one URL as Markdown, scrape selectors, or take a PNG Quick Actions A Cloudflare Worker A Browser Run binding
Render Markdown, links, selector groups, or structured data from Node REST capture Any host with Effect HttpClient Cloudflare account ID and API token
Crawl a site into bounded rendered Markdown records REST crawl Any host with Effect HttpClient Account ID, API token, and a Scope
Navigate, read, click, fill, scroll, or capture one active page Interactive Browser A Cloudflare Worker Browser binding, lifecycle token, and Puppeteer
Let an operator inspect or take over an active pass Interactive Browser host controls A trusted Cloudflare Worker host A Browser Run API token, kept private
Keep one page through approval, credentials, and human takeover Browser Sessions A trusted Cloudflare Worker host Durable owner, current authority, browser binding, lifecycle token
Inspect and act through agent tools Native browser tools An existing Browser Session Model layers and current host authority

Browser output is untrusted input. Validate model-selected URLs against your host policy. Resolve vault credentials in the host; keep provider handles, Live View URLs, and handoff identities out of model Tools and agent journals.

In your application, install the browser adapters:

bun add @yielded/agent-platform-cloudflare@beta effect

Keep framework packages at the same release. The REST examples need no Puppeteer dependency.

Quick Actions are best for a single render operation: Markdown, selector scrape, screenshot, and the other Browser Run one-shot actions. A Worker binding authenticates the request without putting an API token in the Worker. Configure the binding and use a compatibility date of 2026-03-24 or newer. For local wrangler dev, Browser Run Quick Actions need remote mode.

{
"compatibility_date": "2026-03-24",
"browser": {
"binding": "BROWSER",
"remote": true,
},
}

The remote setting is for local development. Deployments use the binding normally. Cloudflare documents the binding, compatibility date, and remote-mode requirement in its Quick Actions guide.

For a WebCapture Tool, use CloudflareBrowser.layer(ReadPage, { browser: env.BROWSER }) as shown below. For direct port access, provide BrowserQuickActionBrowserBinding.layer({ browser: env.BROWSER }) to the adapter. Use browserQuickActionCaptureLayer for PageCapture and browserQuickActionScreenshotLayer for PageScreenshot. The capture adapter supports rendered Markdown, links, selector scrape, and structured extraction. Structured extraction may invoke Workers AI: authorize that separately and account for its provider cost before using it.

Quick Actions have no local implementation. Surface rate or quota failures and keep calls bounded.

The REST adapters run in Node or a Worker and need an account ID, a redacted API token with Browser Rendering - Edit permission, and FetchHttpClient.layer. They are useful when the browser work belongs in a Node service, job, or test harness rather than inside a Worker binding.

browserRestCaptureLayer implements PageCapture. It can capture rendered Markdown, links, selector scrape, and extraction requests. browserRestCrawlLayer implements PageCrawl: it starts the provider job, polls bounded pages, and cancels a known-running job when the consuming Scope exits. The REST crawl adapter deliberately exposes only a credential-free HTTPS starting URL and returns Markdown records from that start host.

Cloudflare’s Markdown endpoint accepts either a URL or HTML. PageCaptureRequest likewise accepts a PageUrlTarget or PageHtmlTarget; authorize a URL target in your host before requesting it. Cloudflare’s /crawl documentation explains how declared purposes interact with a target site’s Content Signals policy. The framework requires an explicit purposes array. Declare ai-input when feeding crawled content to a model; use search when building a search index.

An interactive pass owns one browser, context, and page for one Scope. It is for workflows that need to inspect an active page, follow a known flow, or perform host-approved UI actions. It is not a general browsing session and cannot become an agent Tool.

The adapter includes its Puppeteer client. Provide CloudflareInteractiveBrowser.layer({ browser: env.BROWSER, accountId, apiToken }) with FetchHttpClient.layer for browser actions. CloudflareInteractiveBrowser.hostLayer opts into trusted host controls for Live View and handoff. Both variants assemble the browser binding and confirmed-session cleanup; the API token must be redacted. The lower-level binding, lifecycle, and adapter Layers remain available for custom composition.

The policy is immutable when the pass opens:

  • ExactHosts permits only a fixed set of HTTPS host authorities for page requests. It is a URL allowlist, not a public-network boundary.
  • PublicWeb requires the adapter to enforce public-address containment at connection time. An adapter that cannot enforce it fails before opening a browser. Cloudflare rejects this policy with InteractiveBrowserUnsupportedError before acquisition.
  • Unrestricted explicitly opts out of host and private-network containment while retaining the action, elapsed-time, and result-byte limits.

Choose ExactHosts for a known site. Let a trusted host, never model output, choose Unrestricted. One policy also fixes maximum actions, elapsed time, and bytes returned by each operation.

BrowserUse separates the agent’s tools from the browser’s lifetime and authority. The Cloudflare native adapter implements observation and input over an existing scoped BrowserSession.run attachment. The host chooses the engine, owns credentials and cleanup, and authorizes every operation; it does not implement DOM interaction or recovery. The authorization callback receives current page/frame URLs, the observed input target, and the destination URL for tab selection. Its Effect dependencies are captured when the controller is built. The initial attachment read has no cached URL; authorize that attachment in the host before exposing its tools.

import * as
import NativeBrowser
NativeBrowser
from "@yielded/agent-platform-cloudflare/browser-use";
import type {
(alias) interface BrowserSession
import BrowserSession

One local attachment to an application-owned browser. Scope release disconnects; it never transfers ownership or closes a healthy remote session. The owner serializes attachments, fences old attempts, authorizes human access, and calls BrowserSessions.close on expiry/stop. Native callbacks are trusted host code: never retain SDK handles or start unawaited work.

BrowserSession
} from "@yielded/agent-platform-cloudflare/browser-session";
import {
import BrowserUse
BrowserUse
} from "@yielded/agent";
import {
import Effect
Effect
,
import Layer
Layer
} from "effect";
declare const
const session: BrowserSession
session
:
(alias) interface BrowserSession
import BrowserSession

One local attachment to an application-owned browser. Scope release disconnects; it never transfers ownership or closes a healthy remote session. The owner serializes attachments, fences old attempts, authorizes human access, and calls BrowserSessions.close on expiry/stop. Native callbacks are trusted host code: never retain SDK handles or start unawaited work.

BrowserSession
;
declare const
const authorize: (command: NativeBrowser.Command, context: NativeBrowser.AuthorizationContext) => Effect.Effect<void, BrowserUse.BrowserUseError, never>
authorize
:
import NativeBrowser
NativeBrowser
.
interface Options<R = never>
Options
["authorize"];
const
const browser: {
toolkit: Toolkit<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act: Tool<...>;
}>;
layer: () => Layer.Layer<...>;
}
browser
=
import BrowserUse
BrowserUse
.
function make(options: {
grounding?: "direct";
mode: "batched";
}): {
toolkit: Toolkit<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act: Tool<...>;
}>;
layer: () => Layer.Layer<...>;
} (+5 overloads)

Define browser tools and their matching handlers together. Include toolkit in your Agent and provide layer() once per page/run. All modes require BrowserActions; decision grounding also requires a native DecisionModel. The library never chooses a provider or opens a browser.

make
({
mode: "batched"
mode
: "batched" });
const
const attach: Effect.Effect<Layer.Layer<HandlersFor<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act: Tool<...>;
}> | HandlersFor<...>, never, never>, BrowserUse.BrowserUseError, Scope>
attach
=
import Effect
Effect
.
const gen: <Effect.Effect<{
actions: {
readonly observe: Effect.Effect<{
readonly controls: readonly {
readonly options: readonly string[];
readonly value: string;
readonly ref: string;
readonly kind: string;
readonly name: string;
readonly optionDetails?: readonly {
readonly value: string;
readonly label: string;
readonly disabled: boolean;
readonly selected: boolean;
readonly index?: number | undefined;
}[] | undefined;
readonly disabled?: boolean | undefined;
readonly optionCount?: number | undefined;
readonly frame?: string | undefined;
readonly editable?: boolean | undefined;
readonly checked?: boolean | undefined;
readonly pointerEvents?: string | undefined;
readonly attributes?: {
...;
} | undefined;
}[];
... 5 more ...;
readonly dialogs?: readonly {
...;
}[] | undefined;
}, BrowserUse.BrowserUseError>;
readonly latestObservation?: Effect.Effect<{
readonly controls: readonly {
readonly options: readonly string[];
readonly value: string;
readonly ref: string;
readonly kind: string;
readonly name: string;
readonly optionDetails?: readonly {
readonly value: string;
readonly label: string;
readonly disabled: boolean;
readonly selected: boolean;
readonly index?: number | undefined;
}[] | undefined;
readonly disabled?: boolean | undefined;
readonly optionCount?: number | undefined;
readonly frame?: string | undefined;
readonly editable?: boolean | undefined;
readonly checked?: boolean | undefined;
readonly pointerEvents?: string | undefined;
readonly attributes?: {
...;
} | undefined;
}[];
... 5 more ...;
readonly dialogs?: readonly {
...;
}[] | undefined;
} | null>;
readonly act: (actions: ReadonlyArray<BrowserUse.Action>) => Effect.Effect<{
...;
}, BrowserUse.BrowserUseError>;
};
control: {
...;
};
layer: Layer.Layer<...>;
}, BrowserUse.BrowserUseError, Scope>, Layer.Layer<...>>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const controller: {
actions: {
readonly observe: Effect.Effect<{
readonly controls: readonly {
readonly options: readonly string[];
readonly value: string;
readonly ref: string;
readonly kind: string;
readonly name: string;
readonly optionDetails?: readonly {
readonly value: string;
readonly label: string;
readonly disabled: boolean;
readonly selected: boolean;
readonly index?: number | undefined;
}[] | undefined;
readonly disabled?: boolean | undefined;
readonly optionCount?: number | undefined;
readonly frame?: string | undefined;
readonly editable?: boolean | undefined;
readonly checked?: boolean | undefined;
readonly pointerEvents?: string | undefined;
readonly attributes?: {
...;
} | undefined;
}[];
... 5 more ...;
readonly dialogs?: readonly {
...;
}[] | undefined;
}, BrowserUse.BrowserUseError>;
readonly latestObservation?: Effect.Effect<{
readonly controls: readonly {
readonly options: readonly string[];
readonly value: string;
readonly ref: string;
readonly kind: string;
readonly name: string;
readonly optionDetails?: readonly {
readonly value: string;
readonly label: string;
readonly disabled: boolean;
readonly selected: boolean;
readonly index?: number | undefined;
}[] | undefined;
readonly disabled?: boolean | undefined;
readonly optionCount?: number | undefined;
readonly frame?: string | undefined;
readonly editable?: boolean | undefined;
readonly checked?: boolean | undefined;
readonly pointerEvents?: string | undefined;
readonly attributes?: {
...;
} | undefined;
}[];
... 5 more ...;
readonly dialogs?: readonly {
...;
}[] | undefined;
} | null>;
readonly act: (actions: ReadonlyArray<BrowserUse.Action>) => Effect.Effect<{
...;
}, BrowserUse.BrowserUseError>;
};
control: {
...;
};
layer: Layer.Layer<...>;
}
controller
= yield*
import NativeBrowser
NativeBrowser
.
const make: <never>(session: Pick<BrowserSession, "run">, options: NativeBrowser.Options<never>) => Effect.Effect<{
actions: {
readonly observe: Effect.Effect<{
readonly controls: readonly {
readonly options: readonly string[];
readonly value: string;
readonly ref: string;
readonly kind: string;
readonly name: string;
readonly optionDetails?: readonly {
readonly value: string;
readonly label: string;
readonly disabled: boolean;
readonly selected: boolean;
readonly index?: number | undefined;
}[] | undefined;
readonly disabled?: boolean | undefined;
readonly optionCount?: number | undefined;
readonly frame?: string | undefined;
readonly editable?: boolean | undefined;
readonly checked?: boolean | undefined;
readonly pointerEvents?: string | undefined;
readonly attributes?: {
...;
} | undefined;
}[];
... 5 more ...;
readonly dialogs?: readonly {
...;
}[] | undefined;
}, BrowserUse.BrowserUseError>;
readonly latestObservation?: Effect.Effect<{
readonly controls: readonly {
readonly options: readonly string[];
readonly value: string;
readonly ref: string;
readonly kind: string;
readonly name: string;
readonly optionDetails?: readonly {
readonly value: string;
readonly label: string;
readonly disabled: boolean;
readonly selected: boolean;
readonly index?: number | undefined;
}[] | undefined;
readonly disabled?: boolean | undefined;
readonly optionCount?: number | undefined;
readonly frame?: string | undefined;
readonly editable?: boolean | undefined;
readonly checked?: boolean | undefined;
readonly pointerEvents?: string | undefined;
readonly attributes?: {
...;
} | undefined;
}[];
... 5 more ...;
readonly dialogs?: readonly {
...;
}[] | undefined;
} | null>;
readonly act: (actions: ReadonlyArray<BrowserUse.Action>) => Effect.Effect<{
...;
}, BrowserUse.BrowserUseError>;
};
control: {
...;
};
layer: Layer.Layer<...>;
}, BrowserUse.BrowserUseError, Scope>

One application-neutral controller over an existing scoped native attachment. No allocation, model, provider choice, input retry, or arbitrary page JS is exposed. Reads re-authorize at most five times when page/frame URLs change during authorization. References expire on inspection; outstanding native work retains the session's fencing.

make
(
const session: BrowserSession
session
, {
Options<never>.authorize: (command: NativeBrowser.Command, context: NativeBrowser.AuthorizationContext) => Effect.Effect<void, BrowserUse.BrowserUseError, never>

Rechecked under the native session lock. The host owns origins, identity and consent.

authorize
,
Options<never>.maxActions: number
maxActions
: 100,
Options<never>.maxReturnedBytes: number
maxReturnedBytes
: 128 * 1024,
});
// Build and consume these handlers within this attachment's Scope.
return
import Layer
Layer
.
const merge: <BrowserUse.BrowserActions, never, HandlersFor<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act: Tool<...>;
}>, BrowserUse.BrowserControl, never, HandlersFor<...>>(self: Layer.Layer<...>, that: Layer.Layer<...>) => Layer.Layer<...> (+3 overloads)

Merges this layer with another layer concurrently, producing a new layer with combined input, error, and output types.

When to use

Use to combine an existing Layer with another Layer or an array of layers while preserving pipeline style.

Details

This is a binary version of mergeAll that merges exactly two layers or one layer with an array of layers. The layers are built concurrently and their outputs are combined.

Example (Merging two layers)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
const dbLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result"))
})
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((_msg: string) => Effect.void)
})
const mergedLayer = Layer.merge(dbLayer, loggerLayer)
const program = Database.use((database) => database.query("SELECT 1"))
Effect.runSync(Effect.provide(program, mergedLayer)) // => "result"

@see ― mergeAll for merging several layers at once

@category ― zipping

@since ― 2.0.0

merge
(
const browser: {
toolkit: Toolkit<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act: Tool<...>;
}>;
layer: () => Layer.Layer<...>;
}
browser
.
layer: () => Layer.Layer<HandlersFor<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act: Tool<...>;
}>, never, BrowserUse.BrowserActions>
layer
(),
import BrowserUse
BrowserUse
.
const browserLayer: Layer.Layer<HandlersFor<{
readonly press: Tool<"press", {
readonly parameters: Struct<{
readonly ref: String;
readonly key: Literals<readonly ["Enter", "Escape", "Tab", "ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Home", "End", "Space", "Backspace", "Delete"]>;
}>;
readonly success: Struct<{
readonly completed: Natural;
readonly error: NullOr<String>;
readonly observation: NullOr<Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>>;
readonly dispatch: optionalKey<...>;
readonly pendingInput: optionalKey<...>;
readonly settledInput: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
... 6 more ...;
readonly screenshot: Tool<...>;
}>, never, BrowserUse.BrowserControl>
browserLayer
).
Pipeable.pipe<Layer.Layer<HandlersFor<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act: Tool<...>;
}> | HandlersFor<...>, never, BrowserUse.BrowserActions | BrowserUse.BrowserControl>, Layer.Layer<...>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <never, never, BrowserUse.BrowserActions | BrowserUse.BrowserControl>(that: Layer.Layer<BrowserUse.BrowserActions | BrowserUse.BrowserControl, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, BrowserUse.BrowserActions | BrowserUse.BrowserControl>> (+3 overloads)

Feeds the output services of the dependency layer into the requirements of this layer, returning a layer that only provides the services from this layer.

When to use

Use when you need to hide an implementation dependency layer from callers.

Details

In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is built first and is used to satisfy the requirements of serviceLayer.

Example (Providing layer dependencies)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
// Create dependency layers
const databaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`))
})
const logs: Array<string> = []
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`)))
})
// UserService depends on Database and Logger
const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() {
const database = yield* Database
const logger = yield* Logger
return {
getUser: Effect.fn("UserService.getUser")(function*(id: string) {
yield* logger.log(`Looking up user ${id}`)
const result = yield* database.query(
`SELECT * FROM users WHERE id = ${id}`
)
return { id, name: result }
})
}
}))
// Provide dependencies to UserService layer
const userServiceWithDependencies = userServiceLayer.pipe(
Layer.provide(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now UserService layer has no dependencies
const program = Effect.gen(function*() {
const userService = yield* UserService
return yield* userService.getUser("123")
}).pipe(
Effect.provide(userServiceWithDependencies)
)
Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" }
logs // => ["[LOG] Looking up user 123"]

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
const controller: {
actions: {
readonly observe: Effect.Effect<{
readonly controls: readonly {
readonly options: readonly string[];
readonly value: string;
readonly ref: string;
readonly kind: string;
readonly name: string;
readonly optionDetails?: readonly {
readonly value: string;
readonly label: string;
readonly disabled: boolean;
readonly selected: boolean;
readonly index?: number | undefined;
}[] | undefined;
readonly disabled?: boolean | undefined;
readonly optionCount?: number | undefined;
readonly frame?: string | undefined;
readonly editable?: boolean | undefined;
readonly checked?: boolean | undefined;
readonly pointerEvents?: string | undefined;
readonly attributes?: {
...;
} | undefined;
}[];
... 5 more ...;
readonly dialogs?: readonly {
...;
}[] | undefined;
}, BrowserUse.BrowserUseError>;
readonly latestObservation?: Effect.Effect<{
readonly controls: readonly {
readonly options: readonly string[];
readonly value: string;
readonly ref: string;
readonly kind: string;
readonly name: string;
readonly optionDetails?: readonly {
readonly value: string;
readonly label: string;
readonly disabled: boolean;
readonly selected: boolean;
readonly index?: number | undefined;
}[] | undefined;
readonly disabled?: boolean | undefined;
readonly optionCount?: number | undefined;
readonly frame?: string | undefined;
readonly editable?: boolean | undefined;
readonly checked?: boolean | undefined;
readonly pointerEvents?: string | undefined;
readonly attributes?: {
...;
} | undefined;
}[];
... 5 more ...;
readonly dialogs?: readonly {
...;
}[] | undefined;
} | null>;
readonly act: (actions: ReadonlyArray<BrowserUse.Action>) => Effect.Effect<{
...;
}, BrowserUse.BrowserUseError>;
};
control: {
...;
};
layer: Layer.Layer<...>;
}
controller
.
layer: Layer.Layer<BrowserUse.BrowserActions | BrowserUse.BrowserControl, never, never>
layer
),
);
});

Include both browser.toolkit and BrowserUse.browserTools in the Agent’s Toolkit. They provide observe/act, scoped inspect, navigation, native key presses, selection, scrolling, screenshots, condition waits, observed tab/popup selection, and native dialog responses. Inspection accepts CSS and bounded attributes; it never accepts page JavaScript. Screenshots return PNG bytes for the host; composing visual model input remains host-owned. Condition waits accept a case-sensitive text qualifier for every state, normalizing whitespace as observations do. For example, { selector: "body", state: "hidden", text: "Loading", timeoutMillis: 5000 } waits until the visible body no longer contains “Loading”. Set settleAfterAction: true on the native controller for bounded DOM settling, or "input" for brief frame and autocomplete settling. maxWaitMillis: 5000 caps condition waits at five seconds and returns the current observation on timeout; inspect that observation to determine whether the condition was met. The standalone journey host shows the complete composition.

By default, an observation contains live values, options, disabled/checked state, frame and tab references, and visible controls outside the viewport, including open shadow roots. It marks truncation explicitly. Inspection expires previous control references; narrow by selector or an observed frame when necessary. Hidden content is excluded from bounded inspection and text waits. Selector inspection starts at matching roots so unrelated visible content cannot consume its scan budget. Open-shadow discovery remains bounded and marks truncation explicitly. Default inspection reads the main frame; other frames are listed with inspected: false for explicit lookup. After input, observation follows the target frame, falling back to the main frame if it detached. Use optionFilter to find select options by label or value; current selections remain visible.

viewportOnly: true prioritizes controls in view, retaining offscreen popup controls only when none of that popup’s controls are in view. Duplicate names gain nearby captions. observationMode: "jev" reads enabled document controls whose centers are in the viewport, using accessible names and at most 6,000 characters of visible text. This mode follows the Jev reader’s roles and naming; its refs must remain in view through input preparation. Both modes retain the same native authorization and input guards.

Native fill supports writable inputs, textareas, and contenteditable controls. It verifies native selection of existing content before replacing it with native text input; unsupported selection returns not-dispatched. Closed shadow roots and transformed iframe coordinate spaces are unsupported. Switching tabs expires the previous page’s references; tab selection stays inside the attachment’s browser context. Frame and tab URL summaries omit data: document payloads. Host authorization always receives the complete native URL.

Before input, the adapter checks node identity, the observed name including external labels, current state and visibility, including iframe parents. Pointer input requires an unobstructed hit; keyboard input verifies native focus. Observed pointerEvents and tabindex distinguish keyboard-only controls; semantic click selection excludes pointerEvents: "none". Native input always revalidates these hints. Keyboard-only overlays check their containing element for obstruction. Visible native and ARIA modal dialogs block background input. Preparation has a two-second native deadline; stale or missing references return promptly without retiring a healthy browser. Truly pending native work retains the attachment’s termination/fencing. Native callbacks supplied by custom hosts must enforce the requested deadline as well.

completed counts acknowledged inputs. dispatch distinguishes not-dispatched, acknowledged, and unknown, independently of the next observation. An acknowledged input is not proof that the site saved the requested state. pendingInput identifies input suspended by a dialog. Respond to each newly observed dialog; the same pendingInput persists until the original input returns a settledInput receipt. Outstanding work belongs to the controller’s Scope and retains its native deadline. Observation waits briefly for a loading document to finish parsing; use an explicit condition wait for application readiness. A failed read reports that it dispatched no new input, without changing earlier input receipts or the session’s outstanding-work fencing. Read-only navigation races retry with fresh host authorization. Input is never automatically retried, and a failed observation never authorizes replay. Use a specific condition wait or fresh inspection to reconcile state. Password/file inputs remain host-owned; use the existing credential and file-selection contracts on the session’s original page; selecting a tab does not retarget those host helpers. A blocking JavaScript dialog can be inspected and answered after an input; a dialog that prevents navigation from settling remains subject to the native timeout.

Choose the engine before starting the workflow. Kitesurf’s beta implementation currently has gaps in cross-origin classic script loading, replacing nonempty number inputs, contenteditable input, native HTML dialogs and JavaScript dialogs. Choose Chromium when the workflow requires those capabilities. A new engine starts with separate browser state; never automatically switch engines or replay acknowledged or uncertain input. See Kitesurf’s lifecycle limits before choosing persistence or operator controls.

For Code Mode, put the same tools in its construction-time allowlist:

import {
import BrowserUse
BrowserUse
,
import CodeMode
CodeMode
} from "@yielded/agent";
const
const browser: {
toolkit: Toolkit<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act: Tool<...>;
}>;
layer: () => Layer<...>;
}
browser
=
import BrowserUse
BrowserUse
.
function make(options: {
grounding?: "direct";
mode: "batched";
}): {
toolkit: Toolkit<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act: Tool<...>;
}>;
layer: () => Layer<...>;
} (+5 overloads)

Define browser tools and their matching handlers together. Include toolkit in your Agent and provide layer() once per page/run. All modes require BrowserActions; decision grounding also requires a native DecisionModel. The library never chooses a provider or opens a browser.

make
({
mode: "batched"
mode
: "batched" });
const
const codeMode: CodeMode.CodeModeDefinition<"run_browser", {
browser: {
press: Tool<"press", {
readonly parameters: Struct<{
readonly ref: String;
readonly key: Literals<readonly ["Enter", "Escape", "Tab", "ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Home", "End", "Space", "Backspace", "Delete"]>;
}>;
readonly success: Struct<{
readonly completed: Natural;
readonly error: NullOr<String>;
readonly observation: NullOr<Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>>;
readonly dispatch: optionalKey<...>;
readonly pendingInput: optionalKey<...>;
readonly settledInput: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
... 8 more ...;
act: Tool<...>;
};
}, never>
codeMode
=
import CodeMode
CodeMode
.
make<"run_browser", {
browser: {
press: Tool<"press", {
readonly parameters: Struct<{
readonly ref: String;
readonly key: Literals<readonly ["Enter", "Escape", "Tab", "ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Home", "End", "Space", "Backspace", "Delete"]>;
}>;
readonly success: Struct<{
readonly completed: Natural;
readonly error: NullOr<String>;
readonly observation: NullOr<Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
... 8 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>>;
readonly dispatch: optionalKey<...>;
readonly pendingInput: optionalKey<...>;
readonly settledInput: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
... 8 more ...;
act: Tool<...>;
};
}, never>(name: "run_browser", options: CodeMode.CodeModeOptions<...>): CodeMode.CodeModeDefinition<...>
export make
make
("run_browser", {
CodeModeOptions<Namespaces extends CodeModeNamespaces, RedactionRequirements = never>.description: string

Model-visible description; the builder appends the sandbox contract.

description
: "Inspect current browser state and interact through its guarded tools.",
CodeModeOptions<{ browser: { press: Tool<"press", { readonly parameters: Struct<{ readonly ref: String; readonly key: Literals<readonly ["Enter", "Escape", "Tab", "ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Home", "End", "Space", "Backspace", "Delete"]>; }>; readonly success: Struct<...>; readonly failure: typeof BrowserUseError; readonly failureMode: "return"; }, never>; ... 8 more ...; act: Tool<...>; }; }, never>.tools: {
browser: {
press: Tool<"press", {
readonly parameters: Struct<{
readonly ref: String;
readonly key: Literals<readonly ["Enter", "Escape", "Tab", "ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Home", "End", "Space", "Backspace", "Delete"]>;
}>;
readonly success: Struct<{
readonly completed: Natural;
readonly error: NullOr<String>;
readonly observation: NullOr<Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>>;
readonly dispatch: optionalKey<...>;
readonly pendingInput: optionalKey<...>;
readonly settledInput: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
... 8 more ...;
act: Tool<...>;
};
}

Explicit allowlist: namespace name → method name → native Effect AI Tool. Reads and mutations are allowed; calls requiring additional approval fail closed.

tools
: {
browser: {
press: Tool<"press", {
readonly parameters: Struct<{
readonly ref: String;
readonly key: Literals<readonly ["Enter", "Escape", "Tab", "ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Home", "End", "Space", "Backspace", "Delete"]>;
}>;
readonly success: Struct<{
readonly completed: Natural;
readonly error: NullOr<String>;
readonly observation: NullOr<Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>>;
readonly dispatch: optionalKey<...>;
readonly pendingInput: optionalKey<...>;
readonly settledInput: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
... 8 more ...;
act: Tool<...>;
}
browser
: { ...
const browser: {
toolkit: Toolkit<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act: Tool<...>;
}>;
layer: () => Layer<...>;
}
browser
.
toolkit: Toolkit<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act: Tool<...>;
}>
toolkit
.
Toolkit<{ readonly observe: Tool<"observe", { readonly parameters: EmptyParams; readonly success: Struct<{ readonly text: String; readonly controls: $Array<Struct<{ readonly ref: String; readonly kind: String; ... 10 more ...; readonly attributes: optionalKey<...>; }>>; ... 4 more ...; readonly dialogs: optionalKey<...>; }>; readonly failure: typeof BrowserUseError; readonly failureMode: "return"; }, never>; readonly act: Tool<...>; }>.tools: {
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act: Tool<...>;
}

A record containing all tools in this toolkit.

tools
, ...
import BrowserUse
BrowserUse
.
const browserTools: Toolkit<{
readonly press: Tool<"press", {
readonly parameters: Struct<{
readonly ref: String;
readonly key: Literals<readonly ["Enter", "Escape", "Tab", "ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Home", "End", "Space", "Backspace", "Delete"]>;
}>;
readonly success: Struct<{
readonly completed: Natural;
readonly error: NullOr<String>;
readonly observation: NullOr<Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>>;
readonly dispatch: optionalKey<...>;
readonly pendingInput: optionalKey<...>;
readonly settledInput: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
... 6 more ...;
readonly screenshot: Tool<...>;
}>

Add these tools to the same broker allowlist as act/observe for Code Mode.

browserTools
.
Toolkit<{ readonly press: Tool<"press", { readonly parameters: Struct<{ readonly ref: String; readonly key: Literals<readonly ["Enter", "Escape", "Tab", "ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Home", "End", "Space", "Backspace", "Delete"]>; }>; readonly success: Struct<...>; readonly failure: typeof BrowserUseError; readonly failureMode: "return"; }, never>; ... 6 more ...; readonly screenshot: Tool<...>; }>.tools: {
readonly press: Tool<"press", {
readonly parameters: Struct<{
readonly ref: String;
readonly key: Literals<readonly ["Enter", "Escape", "Tab", "ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Home", "End", "Space", "Backspace", "Delete"]>;
}>;
readonly success: Struct<{
readonly completed: Natural;
readonly error: NullOr<String>;
readonly observation: NullOr<Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>>;
readonly dispatch: optionalKey<...>;
readonly pendingInput: optionalKey<...>;
readonly settledInput: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
... 6 more ...;
readonly screenshot: Tool<...>;
}

A record containing all tools in this toolkit.

tools
} },
CodeModeOptions<Namespaces extends CodeModeNamespaces, RedactionRequirements = never>.maxEgressBytes?: number | undefined

Aggregate model-visible egress budget in UTF-8 bytes across the final result, captured logs, and any thrown value (CAP-016). Default 65536.

maxEgressBytes
: 128 * 1024,
});

Provide those same handlers and an existing Code Mode executor. Generated programs can inspect a missing control, wait for readiness, and act on its new reference through the broker. They get the same authorization, ordering, budgets and receipts; they cannot bypass them through CDP. Never replay an uncertain program.

BrowserUse.make pairs a browser toolkit with its handler Layer. Choose grounding and batching once; the host supplies the page adapter and provider.

import {
import BrowserUse
BrowserUse
} from "@yielded/agent";
import {
import TypeSafeClient
TypeSafeClient
,
import TypeSafeDecisionModel
TypeSafeDecisionModel
} from "@effect/ai-typesafe";
import {
import Config
Config
,
import Layer
Layer
} from "effect";
import {
import FetchHttpClient
FetchHttpClient
} from "effect/http";
const
const browser: {
toolkit: Toolkit<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act_ref: Tool<...>;
readonly act: Tool<...>;
}>;
layer: (options?: BrowserUse.LayerOptions) => Layer.Layer<...>;
}
browser
=
import BrowserUse
BrowserUse
.
function make(options: {
grounding: "decision";
mode?: "single";
}): {
toolkit: Toolkit<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act_ref: Tool<...>;
readonly act: Tool<...>;
}>;
layer: (options?: BrowserUse.LayerOptions) => Layer.Layer<...>;
} (+5 overloads)

Define browser tools and their matching handlers together. Include toolkit in your Agent and provide layer() once per page/run. All modes require BrowserActions; decision grounding also requires a native DecisionModel. The library never chooses a provider or opens a browser.

make
({
grounding: "decision"
grounding
: "decision" });
// Include browser.toolkit in your Agent; merge your own completion tool if needed.
const
const JevLive: Layer.Layer<DecisionModel, Config.ConfigError, never>
JevLive
=
import TypeSafeDecisionModel
TypeSafeDecisionModel
.
const layer: (options: {
readonly model: TypeSafeDecisionModel.Model | (string & {});
}) => Layer.Layer<DecisionModel, never, TypeSafeClient.TypeSafeClient>

Provides DecisionModel using an existing TypeSafeClient.

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
({
model: TypeSafeDecisionModel.Model | (string & {})
model
: "jev-latest" }).
Pipeable.pipe<Layer.Layer<DecisionModel, never, TypeSafeClient.TypeSafeClient>, Layer.Layer<DecisionModel, Config.ConfigError, HttpClient>, Layer.Layer<DecisionModel, Config.ConfigError, never>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<DecisionModel, never, TypeSafeClient.TypeSafeClient>) => Layer.Layer<DecisionModel, Config.ConfigError, HttpClient>, bc: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <HttpClient, Config.ConfigError, TypeSafeClient.TypeSafeClient>(that: Layer.Layer<TypeSafeClient.TypeSafeClient, Config.ConfigError, HttpClient>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, Config.ConfigError | E2, HttpClient | Exclude<RIn2, TypeSafeClient.TypeSafeClient>> (+3 overloads)

Feeds the output services of the dependency layer into the requirements of this layer, returning a layer that only provides the services from this layer.

When to use

Use when you need to hide an implementation dependency layer from callers.

Details

In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is built first and is used to satisfy the requirements of serviceLayer.

Example (Providing layer dependencies)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
// Create dependency layers
const databaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`))
})
const logs: Array<string> = []
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`)))
})
// UserService depends on Database and Logger
const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() {
const database = yield* Database
const logger = yield* Logger
return {
getUser: Effect.fn("UserService.getUser")(function*(id: string) {
yield* logger.log(`Looking up user ${id}`)
const result = yield* database.query(
`SELECT * FROM users WHERE id = ${id}`
)
return { id, name: result }
})
}
}))
// Provide dependencies to UserService layer
const userServiceWithDependencies = userServiceLayer.pipe(
Layer.provide(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now UserService layer has no dependencies
const program = Effect.gen(function*() {
const userService = yield* UserService
return yield* userService.getUser("123")
}).pipe(
Effect.provide(userServiceWithDependencies)
)
Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" }
logs // => ["[LOG] Looking up user 123"]

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
import TypeSafeClient
TypeSafeClient
.
const layerConfig: (options?: {
readonly apiKey?: Config.Config<Redacted<string> | undefined> | undefined;
readonly apiUrl?: Config.Config<string> | undefined;
readonly transformClient?: ((client: HttpClient) => HttpClient) | undefined;
}) => Layer.Layer<TypeSafeClient.TypeSafeClient, Config.ConfigError, HttpClient>

Provides a client from configuration, defaulting to TYPESAFE_API_KEY.

@stability ― unstable

@category ― layers

@since ― 4.0.0

layerConfig
({
apiKey?: Config.Config<Redacted<string> | undefined> | undefined
apiKey
:
import Config
Config
.
function Redacted(name?: string): Config.Config<import("effect/Redacted").Redacted<string>>

Creates a config for a redacted string value. The parsed result is wrapped in a Redacted container that hides the value from logs and toString.

When to use

Use to read secret string settings that should not be exposed in logs or string output.

Details

Shortcut for Config.schema(Schema.Redacted(Schema.String), name).

Example (Reading a secret)

import { Config, ConfigProvider, Effect } from "effect"
const program = Config.Redacted("API_KEY").pipe(Effect.map(String))
const provider = ConfigProvider.fromEnv({
env: {
API_KEY: "sk-1234567890abcdef"
}
})
Effect.runSync(
program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider))
) // => "<redacted>"

@see ― String for non-secret string settings

@category ― constructors

@since ― 2.0.0

Redacted
("TYPESAFE_API_KEY") })),
import Layer
Layer
.
const provide: <never, never, HttpClient>(that: Layer.Layer<HttpClient, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, HttpClient>> (+3 overloads)

Feeds the output services of the dependency layer into the requirements of this layer, returning a layer that only provides the services from this layer.

When to use

Use when you need to hide an implementation dependency layer from callers.

Details

In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is built first and is used to satisfy the requirements of serviceLayer.

Example (Providing layer dependencies)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
// Create dependency layers
const databaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`))
})
const logs: Array<string> = []
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`)))
})
// UserService depends on Database and Logger
const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() {
const database = yield* Database
const logger = yield* Logger
return {
getUser: Effect.fn("UserService.getUser")(function*(id: string) {
yield* logger.log(`Looking up user ${id}`)
const result = yield* database.query(
`SELECT * FROM users WHERE id = ${id}`
)
return { id, name: result }
})
}
}))
// Provide dependencies to UserService layer
const userServiceWithDependencies = userServiceLayer.pipe(
Layer.provide(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now UserService layer has no dependencies
const program = Effect.gen(function*() {
const userService = yield* UserService
return yield* userService.getUser("123")
}).pipe(
Effect.provide(userServiceWithDependencies)
)
Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" }
logs // => ["[LOG] Looking up user 123"]

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
import FetchHttpClient
FetchHttpClient
.
const layer: Layer.Layer<HttpClient, never, never>

Layer that provides an HttpClient implementation backed by the configured Fetch function.

When to use

Use when an Effect program should execute HttpClient requests through the platform fetch implementation, especially in browser, edge, or Node.js runtimes with globalThis.fetch.

Details

The layer uses the current Fetch reference and optional RequestInit service for each request. Request-specific method, headers, body, and abort signal are supplied by the client and override matching RequestInit fields.

Gotchas

Fetch behavior comes from the runtime's implementation, so CORS, cookies, redirects, abort handling, and streaming support can vary by platform. Stream request bodies are sent as Web streams with duplex: "half", and any content-length header is removed before calling fetch.

@see ― Fetch for supplying the fetch implementation used by this layer

@see ― RequestInit for default RequestInit options applied before request-specific fields

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
),
);
const
const handlers: Layer.Layer<HandlersFor<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act_ref: Tool<...>;
readonly act: Tool<...>;
}>, Config.ConfigError, BrowserUse.BrowserActions>
handlers
=
const browser: {
toolkit: Toolkit<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act_ref: Tool<...>;
readonly act: Tool<...>;
}>;
layer: (options?: BrowserUse.LayerOptions) => Layer.Layer<...>;
}
browser
.
layer: (options?: BrowserUse.LayerOptions) => Layer.Layer<HandlersFor<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act_ref: Tool<...>;
readonly act: Tool<...>;
}>, never, BrowserUse.BrowserActions | DecisionModel>
layer
().
Pipeable.pipe<Layer.Layer<HandlersFor<{
readonly observe: Tool<"observe", {
readonly parameters: EmptyParams;
readonly success: Struct<{
readonly text: String;
readonly controls: $Array<Struct<{
readonly ref: String;
readonly kind: String;
readonly name: String;
readonly value: String;
readonly options: $Array<String>;
readonly optionDetails: optionalKey<$Array<Struct<{
readonly value: String;
readonly label: String;
readonly disabled: Boolean;
readonly selected: Boolean;
readonly index: optionalKey<Natural>;
}>>>;
... 6 more ...;
readonly attributes: optionalKey<...>;
}>>;
... 4 more ...;
readonly dialogs: optionalKey<...>;
}>;
readonly failure: typeof BrowserUse.BrowserUseError;
readonly failureMode: "return";
}, never>;
readonly act_ref: Tool<...>;
readonly act: Tool<...>;
}>, never, BrowserUse.BrowserActions | DecisionModel>, Layer.Layer<...>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<...>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <never, Config.ConfigError, DecisionModel>(that: Layer.Layer<DecisionModel, Config.ConfigError, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, Config.ConfigError | E2, Exclude<RIn2, DecisionModel>> (+3 overloads)

Feeds the output services of the dependency layer into the requirements of this layer, returning a layer that only provides the services from this layer.

When to use

Use when you need to hide an implementation dependency layer from callers.

Details

In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is built first and is used to satisfy the requirements of serviceLayer.

Example (Providing layer dependencies)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
// Create dependency layers
const databaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`))
})
const logs: Array<string> = []
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`)))
})
// UserService depends on Database and Logger
const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() {
const database = yield* Database
const logger = yield* Logger
return {
getUser: Effect.fn("UserService.getUser")(function*(id: string) {
yield* logger.log(`Looking up user ${id}`)
const result = yield* database.query(
`SELECT * FROM users WHERE id = ${id}`
)
return { id, name: result }
})
}
}))
// Provide dependencies to UserService layer
const userServiceWithDependencies = userServiceLayer.pipe(
Layer.provide(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now UserService layer has no dependencies
const program = Effect.gen(function*() {
const userService = yield* UserService
return yield* userService.getUser("123")
}).pipe(
Effect.provide(userServiceWithDependencies)
)
Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" }
logs // => ["[LOG] Looking up user 123"]

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
const JevLive: Layer.Layer<DecisionModel, Config.ConfigError, never>
JevLive
));
// handlers still requires BrowserUse.BrowserActions, supplied by your page adapter.

BrowserUse.make() defaults to direct, single actions: the planner supplies an observed ref, as in { action: { kind: "click", ref: "save" } }. With grounding: "decision", it describes { action: { kind: "click", target: "Save this task" } } and the supplied native DecisionModel selects the control. Add mode: "batched" to either configuration to accept actions arrays of up to eight items. The planner remains your ordinary Language Model. Grounding introduces no fallback planner. For a known sequence, { grounding: "decision", mode: "plan" } resolves each next step from the previous result without another planner turn. It stops on missing or ambiguous controls, failed observation, or uncertain input. Inspect or wait before submitting a new plan, and omit completed steps. Grounded toolkits also expose act_ref for direct actions on already resolved current refs. After selection fails, the cached observation cannot be classified again; inspect or wait for fresh evidence, or use that direct recovery path. Both paths retain the same native guards.

BrowserActions owns observation and dispatch. Its adapter assigns unique refs, limits the exposed page data, revalidates targets before input, and enforces navigation and action authority. It returns acknowledged action counts even when later observation fails; no handler replays completed actions. Browser lifetime, credentials, approvals, and outcome verification stay with the host. See the browser speed lab for a complete adapter using Cloudflare Browser Sessions, tracing, and an independent verifier.

Build one grounded handler Layer per page/run. It serializes observation and selection, keeps the latest observation, and discards stale evidence after a failed operation. browser.layer({ initialObservation }) avoids rereading an already prepared page. Selection permits 1–254 compatible controls per action, includes an abstention choice, defaults to an experimental minimum probability of 0.6, and times out after 15 seconds. Calibrate browser.layer({ minimumProbability }) against your own task cohort. These are selection limits, not correctness guarantees. Direct selectTargets returns choices and token usage for custom tools; it requires only DecisionModel. Grounded handlers emit BrowserUse.selectTargets spans with reference/confidence choices and token usage in the browser.selection attribute; use standard Effect tracing to observe them.

Wikipedia routing and Kitesurf connection setup remain example-owned. The lab’s Browser Sessions adapter does not use InteractiveBrowser’s separate guarded-action implementation.

This complete composition captures rendered Markdown through the Node-safe REST adapter. The application owns the Cloudflare credentials and provides the HttpClient; the result stays in the typed Effect channel.

import {
const browserRestCaptureLayer: (options: BrowserRestCaptureOptions) => Layer<PageCapture, never, HttpClient>

REST PageCapture layer for Node and other non-Worker composition roots.

browserRestCaptureLayer
} from "@yielded/agent-platform-cloudflare/browser-rest-capture";
import {
class CapturePageMarkdown

Return the rendered page converted to Markdown.

CapturePageMarkdown
,
class PageCapture

Stateless page-capture port. One capture is one pass: the adapter owns no session, replay, approval, or Thread semantics, identifies its isolation posture honestly (CAP-010), enforces or rejects every requested limit and policy, and returns exactly one bounded output. Capture results are untrusted input to whatever reads them.

PageCapture
,
class PageCaptureLimits

Limits an adapter must either enforce or reject. Bytes are UTF-8 encoded.

PageCaptureLimits
,
class PageCaptureRequest

Schema-first page capture request: one target, one action, one output.

PageCaptureRequest
,
class PageUrlTarget

Capture a URL after a full render, the common case.

PageUrlTarget
,
} from "@yielded/agent/page-capture";
import {
import Config
Config
,
import Effect
Effect
} from "effect";
import {
import FetchHttpClient
FetchHttpClient
} from "effect/http";
const
const captureExample: Effect.Effect<PageCaptureResult, PageCaptureRateLimitedError | PageCaptureNavigationError | PageCaptureInferencePolicyError | PageCaptureUnsupportedError | PageCaptureOutputLimitError | PageCaptureProtocolError | Config.ConfigError, never>
captureExample
=
import Effect
Effect
.
const gen: <Effect.Effect<string, Config.ConfigError, never> | Effect.Effect<Redacted<string>, Config.ConfigError, never> | Effect.Effect<PageCaptureResult, PageCaptureRateLimitedError | PageCaptureNavigationError | PageCaptureInferencePolicyError | PageCaptureUnsupportedError | PageCaptureOutputLimitError | PageCaptureProtocolError, HttpClient>, PageCaptureResult>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const accountId: string
accountId
= yield*
import Config
Config
.
function NonEmptyString(name?: string): Config.Config<string>

Creates a config for a non-empty string value. Fails if the value is an empty string.

When to use

Use to read a string config value that must contain at least one character.

Details

Shortcut for Config.schema(Schema.NonEmptyString, name).

@see ― String for allowing empty strings

@category ― constructors

@since ― 3.7.0

NonEmptyString
("CLOUDFLARE_ACCOUNT_ID");
const
const apiToken: Redacted<string>
apiToken
= yield*
import Config
Config
.
function Redacted(name?: string): Config.Config<import("effect/Redacted").Redacted<string>>

Creates a config for a redacted string value. The parsed result is wrapped in a Redacted container that hides the value from logs and toString.

When to use

Use to read secret string settings that should not be exposed in logs or string output.

Details

Shortcut for Config.schema(Schema.Redacted(Schema.String), name).

Example (Reading a secret)

import { Config, ConfigProvider, Effect } from "effect"
const program = Config.Redacted("API_KEY").pipe(Effect.map(String))
const provider = ConfigProvider.fromEnv({
env: {
API_KEY: "sk-1234567890abcdef"
}
})
Effect.runSync(
program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider))
) // => "<redacted>"

@see ― String for non-secret string settings

@category ― constructors

@since ― 2.0.0

Redacted
("CLOUDFLARE_API_TOKEN");
return yield*
import Effect
Effect
.
const gen: <Effect.Effect<PageCaptureResult, PageCaptureRateLimitedError | PageCaptureNavigationError | PageCaptureInferencePolicyError | PageCaptureUnsupportedError | PageCaptureOutputLimitError | PageCaptureProtocolError, never> | Effect.Effect<{
readonly capture: (request: PageCaptureRequest) => Effect.Effect<PageCaptureResult, PageCaptureError>;
}, never, PageCapture>, PageCaptureResult>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const capture: {
readonly capture: (request: PageCaptureRequest) => Effect.Effect<PageCaptureResult, PageCaptureError>;
}
capture
= yield*
class PageCapture

Stateless page-capture port. One capture is one pass: the adapter owns no session, replay, approval, or Thread semantics, identifies its isolation posture honestly (CAP-010), enforces or rejects every requested limit and policy, and returns exactly one bounded output. Capture results are untrusted input to whatever reads them.

PageCapture
;
return yield*
const capture: {
readonly capture: (request: PageCaptureRequest) => Effect.Effect<PageCaptureResult, PageCaptureError>;
}
capture
.
capture: (request: PageCaptureRequest) => Effect.Effect<PageCaptureResult, PageCaptureError>
capture
(
class PageCaptureRequest

Schema-first page capture request: one target, one action, one output.

PageCaptureRequest
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<PageCaptureRequest, { readonly engine: "chromium" | "kitesurf"; readonly action: { readonly _tag: "CapturePageMarkdown"; } | { ...; } | { ...; } | { ...; } | { ...; }; ... 4 more ...; readonly resourcePolicy?: { ...; } | undefined; }, readonly [...], { ...; }>, Struct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly engine: "chromium" | "kitesurf";
readonly action: CapturePageMarkdown | CapturePageContent | CapturePageLinks | CapturePageScrape | CapturePageStructured;
readonly target: PageUrlTarget | PageHtmlTarget;
readonly limits: PageCaptureLimits;
readonly navigation?: PageNavigationOptions | undefined;
readonly viewport?: PageViewport | undefined;
readonly resourcePolicy?: PageResourcePolicy | undefined;
}, options?: MakeOptions): PageCaptureRequest

Constructs a value from the make input representation synchronously.

When to use

Use when constructor input is trusted or when validation failure should abort with a thrown Error.

Details

Applies constructor defaults and type-side validation according to MakeOptions.

Gotchas

Throws an Error with the schema issue in its cause when validation fails. Schema validation failures use the generic message "Schema validation failed"; format the cause explicitly with SchemaIssue.makeFormatterDefault() when human-readable details are needed. Causes that contain defects, interruptions, or other non-schema reasons throw with the underlying Cause attached instead.

@see ― BottomWithoutNew.makeOption — construct synchronously and discard validation details

@see ― BottomWithoutNew.makeEffect — construct through Effect when validation failure should stay in the error channel

make
({
target: PageUrlTarget | PageHtmlTarget
target
:
class PageUrlTarget

Capture a URL after a full render, the common case.

PageUrlTarget
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<PageUrlTarget, { readonly _tag: "PageUrlTarget"; readonly url: string; }, readonly [TaggedStruct<"PageUrlTarget", { readonly url: NonEmptyString; }>], { ...; }>, TaggedStruct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly url: string;
readonly _tag?: "PageUrlTarget" | undefined;
}, options?: MakeOptions): PageUrlTarget

Constructs a value from the make input representation synchronously.

When to use

Use when constructor input is trusted or when validation failure should abort with a thrown Error.

Details

Applies constructor defaults and type-side validation according to MakeOptions.

Gotchas

Throws an Error with the schema issue in its cause when validation fails. Schema validation failures use the generic message "Schema validation failed"; format the cause explicitly with SchemaIssue.makeFormatterDefault() when human-readable details are needed. Causes that contain defects, interruptions, or other non-schema reasons throw with the underlying Cause attached instead.

@see ― BottomWithoutNew.makeOption — construct synchronously and discard validation details

@see ― BottomWithoutNew.makeEffect — construct through Effect when validation failure should stay in the error channel

make
({
url: string
url
: "https://example.com/" }),
action: CapturePageMarkdown | CapturePageContent | CapturePageLinks | CapturePageScrape | CapturePageStructured
action
:
class CapturePageMarkdown

Return the rendered page converted to Markdown.

CapturePageMarkdown
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<CapturePageMarkdown, { readonly _tag: "CapturePageMarkdown"; }, readonly [TaggedStruct<"CapturePageMarkdown", {}>], { ...; }>, TaggedStruct<...>, never, never>, ... 8 more ..., "required">.make(input: void | {
readonly _tag?: "CapturePageMarkdown" | undefined;
}, options?: MakeOptions): CapturePageMarkdown

Constructs a value from the make input representation synchronously.

When to use

Use when constructor input is trusted or when validation failure should abort with a thrown Error.

Details

Applies constructor defaults and type-side validation according to MakeOptions.

Gotchas

Throws an Error with the schema issue in its cause when validation fails. Schema validation failures use the generic message "Schema validation failed"; format the cause explicitly with SchemaIssue.makeFormatterDefault() when human-readable details are needed. Causes that contain defects, interruptions, or other non-schema reasons throw with the underlying Cause attached instead.

@see ― BottomWithoutNew.makeOption — construct synchronously and discard validation details

@see ― BottomWithoutNew.makeEffect — construct through Effect when validation failure should stay in the error channel

make
({}),
engine: "chromium" | "kitesurf"
engine
: "kitesurf",
limits: PageCaptureLimits
limits
:
class PageCaptureLimits

Limits an adapter must either enforce or reject. Bytes are UTF-8 encoded.

PageCaptureLimits
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<PageCaptureLimits, { readonly maxOutputBytes: number; }, readonly [Struct<{ readonly maxOutputBytes: Int; }>], { ...; }>, Struct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly maxOutputBytes: number;
}, options?: MakeOptions): PageCaptureLimits

Constructs a value from the make input representation synchronously.

When to use

Use when constructor input is trusted or when validation failure should abort with a thrown Error.

Details

Applies constructor defaults and type-side validation according to MakeOptions.

Gotchas

Throws an Error with the schema issue in its cause when validation fails. Schema validation failures use the generic message "Schema validation failed"; format the cause explicitly with SchemaIssue.makeFormatterDefault() when human-readable details are needed. Causes that contain defects, interruptions, or other non-schema reasons throw with the underlying Cause attached instead.

@see ― BottomWithoutNew.makeOption — construct synchronously and discard validation details

@see ― BottomWithoutNew.makeEffect — construct through Effect when validation failure should stay in the error channel

make
({
maxOutputBytes: number
maxOutputBytes
: 16 * 1_024 }),
}),
);
}).
Pipeable.pipe<Effect.Effect<PageCaptureResult, PageCaptureRateLimitedError | PageCaptureNavigationError | PageCaptureInferencePolicyError | PageCaptureUnsupportedError | PageCaptureOutputLimitError | PageCaptureProtocolError, PageCapture>, Effect.Effect<PageCaptureResult, PageCaptureRateLimitedError | ... 4 more ... | PageCaptureProtocolError, HttpClient>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const provide: <PageCapture, never, HttpClient>(layer: Layer<PageCapture, never, HttpClient>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, HttpClient | Exclude<R, PageCapture>> (+5 overloads)

Provides dependencies to an effect using layers or a context. Use options.local to build the layer every time; by default, layers are shared between provide calls.

Example (Providing dependencies with a layer)

import { Context, Effect, Layer } from "effect"
interface Database {
readonly query: (sql: string) => Effect.Effect<string>
}
const Database = Context.Service<Database>("Database")
const DatabaseLayer = Layer.succeed(Database)({
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))
})
const program = Effect.gen(function*() {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
const provided = Effect.provide(program, DatabaseLayer)
await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"

@category ― providing services

@since ― 2.0.0

provide
(
function browserRestCaptureLayer(options: BrowserRestCaptureOptions): Layer<PageCapture, never, HttpClient>

REST PageCapture layer for Node and other non-Worker composition roots.

browserRestCaptureLayer
({
BrowserRestCaptureOptions.accountId: string
accountId
,
BrowserRestCaptureOptions.apiToken: Redacted<string>
apiToken
})));
}).
Pipeable.pipe<Effect.Effect<PageCaptureResult, PageCaptureRateLimitedError | PageCaptureNavigationError | PageCaptureInferencePolicyError | PageCaptureUnsupportedError | PageCaptureOutputLimitError | PageCaptureProtocolError | Config.ConfigError, HttpClient>, Effect.Effect<PageCaptureResult, PageCaptureRateLimitedError | ... 5 more ... | Config.ConfigError, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const provide: <HttpClient, never, never>(layer: Layer<HttpClient, never, never>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, HttpClient>> (+5 overloads)

Provides dependencies to an effect using layers or a context. Use options.local to build the layer every time; by default, layers are shared between provide calls.

Example (Providing dependencies with a layer)

import { Context, Effect, Layer } from "effect"
interface Database {
readonly query: (sql: string) => Effect.Effect<string>
}
const Database = Context.Service<Database>("Database")
const DatabaseLayer = Layer.succeed(Database)({
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))
})
const program = Effect.gen(function*() {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
const provided = Effect.provide(program, DatabaseLayer)
await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"

@category ― providing services

@since ― 2.0.0

provide
(
import FetchHttpClient
FetchHttpClient
.
const layer: Layer<HttpClient, never, never>

Layer that provides an HttpClient implementation backed by the configured Fetch function.

When to use

Use when an Effect program should execute HttpClient requests through the platform fetch implementation, especially in browser, edge, or Node.js runtimes with globalThis.fetch.

Details

The layer uses the current Fetch reference and optional RequestInit service for each request. Request-specific method, headers, body, and abort signal are supplied by the client and override matching RequestInit fields.

Gotchas

Fetch behavior comes from the runtime's implementation, so CORS, cookies, redirects, abort handling, and streaming support can vary by platform. Stream request bodies are sent as Web streams with duplex: "half", and any content-length header is removed before calling fetch.

@see ― Fetch for supplying the fetch implementation used by this layer

@see ― RequestInit for default RequestInit options applied before request-specific fields

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
));

PageCaptureRequest fixes the URL, operation, browser engine, and output limit before the request starts. It can also carry a fixed resource policy, navigation options, and viewport. Capture results have a discriminated output type; inspect it before using Markdown, links, scrape groups, or structured data.

Use WebCapture from @yielded/agent to wrap capture in a native Effect AI Tool. Fix the allowed hosts, actions, and output size in the definition. In a Worker, the Cloudflare package assembles the capture adapter, binding, and handlers in one Layer:

import {
import WebCapture
WebCapture
} from "@yielded/agent";
import {
const CloudflareBrowser: {
layer: <A, E, R>(definition: {
readonly handlers: Layer<A, E, R>;
}, options: CloudflareBrowserOptions) => Layer<A, E, Exclude<R, PageCapture>>;
}

Compose WebCapture handlers with the Cloudflare Quick Action adapter.

CloudflareBrowser
,
type
(alias) interface CloudflareBrowserOptions
import CloudflareBrowserOptions

Host-owned Quick Action binding and optional, separately billed extraction authority.

CloudflareBrowserOptions
,
} from "@yielded/agent-platform-cloudflare/cloudflare-browser";
import {
import Toolkit
Toolkit
} from "effect/ai";
declare const
const env: {
BROWSER: CloudflareBrowserOptions["browser"];
}
env
: {
type BROWSER: BrowserRun
BROWSER
:
(alias) interface CloudflareBrowserOptions
import CloudflareBrowserOptions

Host-owned Quick Action binding and optional, separately billed extraction authority.

CloudflareBrowserOptions
["browser"] };
const
const ReadPage: WebCapture.WebCaptureDefinition<"read_page">
ReadPage
=
import WebCapture
WebCapture
.
make<"read_page">(name: "read_page", options: WebCapture.WebCaptureOptions): WebCapture.WebCaptureDefinition<"read_page">
export make
make
("read_page", {
WebCaptureSharedOptions.description: string

Model-visible description; the builder appends the policy contract.

description
: "Read example.com as rendered Markdown.",
WebCaptureSharedOptions.urls: readonly string[]

Allowed target hosts: bare host names (docs.example.com) or one-level wildcards (*.example.com, matching the apex and every subdomain). The list must be non-empty — capture is deny-by-default (security spec §9).

urls
: ["example.com"],
WebCaptureOptions.actions?: readonly ("markdown" | "content" | "links")[] | undefined

Model-selectable actions; defaults to every first-slice action.

actions
: ["markdown"],
WebCaptureSharedOptions.maxResponseBytes?: number | undefined

Response byte budget measured on the UTF-8 encoded platform response. Default 131072 (128 KiB); construction fails closed outside [1024, 1048576].

maxResponseBytes
: 16 * 1024,
});
export const
const BrowserTools: Toolkit.Toolkit<{
readonly read_page: WebCapture.WebCaptureTool<"read_page">;
}>
BrowserTools
=
import Toolkit
Toolkit
.
const make: <[WebCapture.WebCaptureTool<"read_page">]>(tools_0: WebCapture.WebCaptureTool<"read_page">) => Toolkit.Toolkit<{
readonly read_page: WebCapture.WebCaptureTool<"read_page">;
}>

Creates a new toolkit from the specified tools.

Details

This is the primary constructor for creating toolkits. It accepts multiple tools and organizes them into a toolkit that can be provided to AI language models.

Example (Creating a toolkit)

import { Effect, Schema } from "effect"
import { Tool, Toolkit } from "effect/ai"
const GetCurrentTime = Tool.make("GetCurrentTime", {
description: "Get the current timestamp",
success: Schema.Number
})
const GetWeather = Tool.make("get_weather", {
description: "Get weather information",
parameters: Schema.Struct({ location: Schema.String }),
success: Schema.Struct({
temperature: Schema.Number,
condition: Schema.String
})
})
const toolkit = Toolkit.make(GetCurrentTime, GetWeather)
const ready = toolkit.pipe(Effect.provide(toolkit.toLayer({
GetCurrentTime: () => Effect.succeed(0),
get_weather: () => Effect.succeed({ temperature: 20, condition: "clear" })
})))
Object.keys((await Effect.runPromise(ready)).tools) // => ["GetCurrentTime", "get_weather"]

@stability ― unstable

@category ― constructors

@since ― 4.0.0

make
(
const ReadPage: WebCapture.WebCaptureDefinition<"read_page">
ReadPage
.
WebCaptureDefinition<"read_page">.tool: WebCapture.WebCaptureTool<"read_page">

The ordinary Effect AI Tool to include in the model-facing Toolkit.

tool
);
export const
const ReadPageLive: Layer<Handler<"read_page">, never, never>
ReadPageLive
=
const CloudflareBrowser: {
layer: <A, E, R>(definition: {
readonly handlers: Layer<A, E, R>;
}, options: CloudflareBrowserOptions) => Layer<A, E, Exclude<R, PageCapture>>;
}

Compose WebCapture handlers with the Cloudflare Quick Action adapter.

CloudflareBrowser
.
layer: <Handler<"read_page">, never, PageCapture>(definition: {
readonly handlers: Layer<Handler<"read_page">, never, PageCapture>;
}, options: CloudflareBrowserOptions) => Layer<Handler<"read_page">, never, never>

Supply a WebCapture definition and the resolved Worker browser binding. Supports capture, scrape, and extraction definitions without importing capabilities. Only PageCapture is provided; other handler requirements and errors stay visible. Extraction fails closed unless workersAi explicitly authorizes and accounts for it. Capture limits, typed failures, tracing, and scoped response cleanup are unchanged.

layer
(
const ReadPage: WebCapture.WebCaptureDefinition<"read_page">
ReadPage
, {
BrowserQuickActionCaptureOptions.browser: BrowserRun

The resolved Wrangler browser binding (DEPLOY-014: supplied, never ambient).

browser
:
const env: {
BROWSER: CloudflareBrowserOptions["browser"];
}
env
.
type BROWSER: BrowserRun
BROWSER
,
});

Use BrowserTools as the agent’s toolkit and provide ReadPageLive when running it. CloudflareBrowser.layer also accepts WebCapture.makeScrape and WebCapture.makeExtract definitions. Extraction requires an explicit workersAi option with an authorizeAndAccount Effect, using the same policy as BrowserQuickActionWorkersAi.layer. Without it, extraction fails before making a browser request. The constructor supplies only PageCapture; any schema decoding services remain required. It preserves the definition’s host policy, output bounds, typed failures, and response cleanup.

For REST capture, use the Node-safe REST subpath and supply an HTTP client:

import {
import WebCapture
WebCapture
} from "@yielded/agent";
import {
const CloudflareBrowserRest: {
layer: <A, E, R>(definition: {
readonly handlers: Layer.Layer<A, E, R>;
}, options: CloudflareBrowserRestOptions) => Layer.Layer<A, E, Exclude<R, PageCapture> | HttpClient>;
}

Node-safe WebCapture handler assembly; the application supplies its HttpClient.

CloudflareBrowserRest
,
type
(alias) interface CloudflareBrowserRestOptions
import CloudflareBrowserRestOptions

Explicit REST credentials and optional authorization for separately billed extraction.

CloudflareBrowserRestOptions
,
} from "@yielded/agent-platform-cloudflare/browser-rest-capture";
import {
import Layer
Layer
} from "effect";
import {
import Toolkit
Toolkit
} from "effect/ai";
import {
import FetchHttpClient
FetchHttpClient
} from "effect/http";
const
const readPage: WebCapture.WebCaptureDefinition<"read_page">
readPage
=
import WebCapture
WebCapture
.
make<"read_page">(name: "read_page", options: WebCapture.WebCaptureOptions): WebCapture.WebCaptureDefinition<"read_page">
export make
make
("read_page", {
WebCaptureSharedOptions.description: string

Model-visible description; the builder appends the policy contract.

description
: "Read example.com as rendered Markdown.",
WebCaptureSharedOptions.urls: readonly string[]

Allowed target hosts: bare host names (docs.example.com) or one-level wildcards (*.example.com, matching the apex and every subdomain). The list must be non-empty — capture is deny-by-default (security spec §9).

urls
: ["example.com"],
WebCaptureOptions.actions?: readonly ("markdown" | "content" | "links")[] | undefined

Model-selectable actions; defaults to every first-slice action.

actions
: ["markdown"],
WebCaptureSharedOptions.maxResponseBytes?: number | undefined

Response byte budget measured on the UTF-8 encoded platform response. Default 131072 (128 KiB); construction fails closed outside [1024, 1048576].

maxResponseBytes
: 16 * 1024,
});
export const
const BrowserTools: Toolkit.Toolkit<{
readonly read_page: WebCapture.WebCaptureTool<"read_page">;
}>
BrowserTools
=
import Toolkit
Toolkit
.
const make: <[WebCapture.WebCaptureTool<"read_page">]>(tools_0: WebCapture.WebCaptureTool<"read_page">) => Toolkit.Toolkit<{
readonly read_page: WebCapture.WebCaptureTool<"read_page">;
}>

Creates a new toolkit from the specified tools.

Details

This is the primary constructor for creating toolkits. It accepts multiple tools and organizes them into a toolkit that can be provided to AI language models.

Example (Creating a toolkit)

import { Effect, Schema } from "effect"
import { Tool, Toolkit } from "effect/ai"
const GetCurrentTime = Tool.make("GetCurrentTime", {
description: "Get the current timestamp",
success: Schema.Number
})
const GetWeather = Tool.make("get_weather", {
description: "Get weather information",
parameters: Schema.Struct({ location: Schema.String }),
success: Schema.Struct({
temperature: Schema.Number,
condition: Schema.String
})
})
const toolkit = Toolkit.make(GetCurrentTime, GetWeather)
const ready = toolkit.pipe(Effect.provide(toolkit.toLayer({
GetCurrentTime: () => Effect.succeed(0),
get_weather: () => Effect.succeed({ temperature: 20, condition: "clear" })
})))
Object.keys((await Effect.runPromise(ready)).tools) // => ["GetCurrentTime", "get_weather"]

@stability ― unstable

@category ― constructors

@since ― 4.0.0

make
(
const readPage: WebCapture.WebCaptureDefinition<"read_page">
readPage
.
WebCaptureDefinition<"read_page">.tool: WebCapture.WebCaptureTool<"read_page">

The ordinary Effect AI Tool to include in the model-facing Toolkit.

tool
);
export const
const browserToolsLive: (credentials: CloudflareBrowserRestOptions) => Layer.Layer<Handler<"read_page">, never, never>
browserToolsLive
= (
credentials: CloudflareBrowserRestOptions
credentials
:
(alias) interface CloudflareBrowserRestOptions
import CloudflareBrowserRestOptions

Explicit REST credentials and optional authorization for separately billed extraction.

CloudflareBrowserRestOptions
) =>
const CloudflareBrowserRest: {
layer: <A, E, R>(definition: {
readonly handlers: Layer.Layer<A, E, R>;
}, options: CloudflareBrowserRestOptions) => Layer.Layer<A, E, Exclude<R, PageCapture> | HttpClient>;
}

Node-safe WebCapture handler assembly; the application supplies its HttpClient.

CloudflareBrowserRest
.
layer: <Handler<"read_page">, never, PageCapture>(definition: {
readonly handlers: Layer.Layer<Handler<"read_page">, never, PageCapture>;
}, options: CloudflareBrowserRestOptions) => Layer.Layer<Handler<"read_page">, never, HttpClient>

Supports capture, scrape, and extraction definitions. Only PageCapture is supplied; handler errors and other services remain visible. Extraction fails closed without workersAi. Existing host policies, response limits, and scoped cleanup are unchanged.

layer
(
const readPage: WebCapture.WebCaptureDefinition<"read_page">
readPage
,
credentials: CloudflareBrowserRestOptions
credentials
).
Pipeable.pipe<Layer.Layer<Handler<"read_page">, never, HttpClient>, Layer.Layer<Handler<"read_page">, never, never>>(this: Layer.Layer<Handler<"read_page">, never, HttpClient>, ab: (_: Layer.Layer<Handler<"read_page">, never, HttpClient>) => Layer.Layer<Handler<"read_page">, never, never>): Layer.Layer<Handler<"read_page">, never, never> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <never, never, HttpClient>(that: Layer.Layer<HttpClient, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, HttpClient>> (+3 overloads)

Feeds the output services of the dependency layer into the requirements of this layer, returning a layer that only provides the services from this layer.

When to use

Use when you need to hide an implementation dependency layer from callers.

Details

In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is built first and is used to satisfy the requirements of serviceLayer.

Example (Providing layer dependencies)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
// Create dependency layers
const databaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`))
})
const logs: Array<string> = []
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`)))
})
// UserService depends on Database and Logger
const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() {
const database = yield* Database
const logger = yield* Logger
return {
getUser: Effect.fn("UserService.getUser")(function*(id: string) {
yield* logger.log(`Looking up user ${id}`)
const result = yield* database.query(
`SELECT * FROM users WHERE id = ${id}`
)
return { id, name: result }
})
}
}))
// Provide dependencies to UserService layer
const userServiceWithDependencies = userServiceLayer.pipe(
Layer.provide(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now UserService layer has no dependencies
const program = Effect.gen(function*() {
const userService = yield* UserService
return yield* userService.getUser("123")
}).pipe(
Effect.provide(userServiceWithDependencies)
)
Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" }
logs // => ["[LOG] Looking up user 123"]

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
import FetchHttpClient
FetchHttpClient
.
const layer: Layer.Layer<HttpClient, never, never>

Layer that provides an HttpClient implementation backed by the configured Fetch function.

When to use

Use when an Effect program should execute HttpClient requests through the platform fetch implementation, especially in browser, edge, or Node.js runtimes with globalThis.fetch.

Details

The layer uses the current Fetch reference and optional RequestInit service for each request. Request-specific method, headers, body, and abort signal are supplied by the client and override matching RequestInit fields.

Gotchas

Fetch behavior comes from the runtime's implementation, so CORS, cookies, redirects, abort handling, and streaming support can vary by platform. Stream request bodies are sent as Web streams with duplex: "half", and any content-length header is removed before calling fetch.

@see ― Fetch for supplying the fetch implementation used by this layer

@see ― RequestInit for default RequestInit options applied before request-specific fields

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
));

Use BrowserTools as the agent’s toolkit and provide browserToolsLive(credentials) when running it. Use WebCapture.makeScrape for grouped selector results or WebCapture.makeExtract for Schema-validated extraction. Extraction also needs the adapter’s explicit Workers AI authorization and accounting policy. Capture Tools have uncertain external outcomes because page rendering can execute JavaScript. Code Mode can expose them through its authorized Tool allowlist; their resource policies still apply.

CloudflareBrowserRest.layer accepts the same optional workersAi policy as the Worker constructor. It preserves schema decoding requirements and leaves HttpClient injectable. For a custom capture adapter, provide its Layer directly to readPage.handlers.

PageCrawl.crawl returns a Stream. Consume it within Effect.scoped so interrupting the enclosing work cancels the provider job when the adapter has a job identity to clean up.

import {
const browserRestCrawlLayer: (options: BrowserRestCrawlOptions) => Layer.Layer<PageCrawl, never, HttpClient>

Cloudflare REST PageCrawl Layer for Node and other non-Worker composition roots.

browserRestCrawlLayer
} from "@yielded/agent-platform-cloudflare/browser-rest-crawl";
import {
class PageCrawl

Scoped exact-host crawl stream. Provider job identity and polling stay adapter-private.

PageCrawl
,
class PageCrawlLimits

Immutable caller limits for one scoped crawl.

PageCrawlLimits
,
class PageCrawlRequest

A rendered Markdown crawl rooted at one exact HTTPS host.

PageCrawlRequest
} from "@yielded/agent/page-crawl";
import {
import Config
Config
,
import Effect
Effect
,
import Layer
Layer
,
import Stream
Stream
} from "effect";
import {
import FetchHttpClient
FetchHttpClient
} from "effect/http";
const
const BrowserCrawlLive: Layer.Layer<PageCrawl, Config.ConfigError, never>
BrowserCrawlLive
=
import Layer
Layer
.
const unwrap: <PageCrawl, never, HttpClient, Config.ConfigError, never>(self: Effect.Effect<Layer.Layer<PageCrawl, never, HttpClient>, Config.ConfigError, never>) => Layer.Layer<PageCrawl, Config.ConfigError, HttpClient>

Unwraps a Layer from an Effect, flattening the nested structure.

When to use

Use when you have an Effect that produces a Layer and you want to use that layer directly.

Details

The resulting Layer will have the combined error and dependency types from both the outer Effect and the inner Layer.

Example (Unwrapping an effectful layer)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
const layerEffect = Effect.succeed(
Layer.succeed(Database, { query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) })
)
const unwrappedLayer = Layer.unwrap(layerEffect)
const program = Database.use((database) => database.query("SELECT 1"))
Effect.runSync(Effect.provide(program, unwrappedLayer)) // => "result"

@category ― converting

@since ― 4.0.0

unwrap
(
import Effect
Effect
.
const gen: <Effect.Effect<string, Config.ConfigError, never> | Effect.Effect<Redacted<string>, Config.ConfigError, never>, Layer.Layer<PageCrawl, never, HttpClient>>(f: () => Generator<Effect.Effect<string, Config.ConfigError, never> | Effect.Effect<Redacted<string>, Config.ConfigError, never>, Layer.Layer<PageCrawl, never, HttpClient>, never>) => Effect.Effect<Layer.Layer<PageCrawl, never, HttpClient>, Config.ConfigError, never> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const accountId: string
accountId
= yield*
import Config
Config
.
function NonEmptyString(name?: string): Config.Config<string>

Creates a config for a non-empty string value. Fails if the value is an empty string.

When to use

Use to read a string config value that must contain at least one character.

Details

Shortcut for Config.schema(Schema.NonEmptyString, name).

@see ― String for allowing empty strings

@category ― constructors

@since ― 3.7.0

NonEmptyString
("CLOUDFLARE_ACCOUNT_ID");
const
const apiToken: Redacted<string>
apiToken
= yield*
import Config
Config
.
function Redacted(name?: string): Config.Config<import("effect/Redacted").Redacted<string>>

Creates a config for a redacted string value. The parsed result is wrapped in a Redacted container that hides the value from logs and toString.

When to use

Use to read secret string settings that should not be exposed in logs or string output.

Details

Shortcut for Config.schema(Schema.Redacted(Schema.String), name).

Example (Reading a secret)

import { Config, ConfigProvider, Effect } from "effect"
const program = Config.Redacted("API_KEY").pipe(Effect.map(String))
const provider = ConfigProvider.fromEnv({
env: {
API_KEY: "sk-1234567890abcdef"
}
})
Effect.runSync(
program.pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider))
) // => "<redacted>"

@see ― String for non-secret string settings

@category ― constructors

@since ― 2.0.0

Redacted
("CLOUDFLARE_API_TOKEN");
return
function browserRestCrawlLayer(options: BrowserRestCrawlOptions): Layer.Layer<PageCrawl, never, HttpClient>

Cloudflare REST PageCrawl Layer for Node and other non-Worker composition roots.

browserRestCrawlLayer
({
BrowserRestCrawlOptions.accountId: string
accountId
,
BrowserRestCrawlOptions.apiToken: Redacted<string>
apiToken
});
}),
).
Pipeable.pipe<Layer.Layer<PageCrawl, Config.ConfigError, HttpClient>, Layer.Layer<PageCrawl, Config.ConfigError, never>>(this: Layer.Layer<PageCrawl, Config.ConfigError, HttpClient>, ab: (_: Layer.Layer<PageCrawl, Config.ConfigError, HttpClient>) => Layer.Layer<PageCrawl, Config.ConfigError, never>): Layer.Layer<PageCrawl, Config.ConfigError, never> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <never, never, HttpClient>(that: Layer.Layer<HttpClient, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, HttpClient>> (+3 overloads)

Feeds the output services of the dependency layer into the requirements of this layer, returning a layer that only provides the services from this layer.

When to use

Use when you need to hide an implementation dependency layer from callers.

Details

In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is built first and is used to satisfy the requirements of serviceLayer.

Example (Providing layer dependencies)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
// Create dependency layers
const databaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`))
})
const logs: Array<string> = []
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`)))
})
// UserService depends on Database and Logger
const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() {
const database = yield* Database
const logger = yield* Logger
return {
getUser: Effect.fn("UserService.getUser")(function*(id: string) {
yield* logger.log(`Looking up user ${id}`)
const result = yield* database.query(
`SELECT * FROM users WHERE id = ${id}`
)
return { id, name: result }
})
}
}))
// Provide dependencies to UserService layer
const userServiceWithDependencies = userServiceLayer.pipe(
Layer.provide(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now UserService layer has no dependencies
const program = Effect.gen(function*() {
const userService = yield* UserService
return yield* userService.getUser("123")
}).pipe(
Effect.provide(userServiceWithDependencies)
)
Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" }
logs // => ["[LOG] Looking up user 123"]

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
import FetchHttpClient
FetchHttpClient
.
const layer: Layer.Layer<HttpClient, never, never>

Layer that provides an HttpClient implementation backed by the configured Fetch function.

When to use

Use when an Effect program should execute HttpClient requests through the platform fetch implementation, especially in browser, edge, or Node.js runtimes with globalThis.fetch.

Details

The layer uses the current Fetch reference and optional RequestInit service for each request. Request-specific method, headers, body, and abort signal are supplied by the client and override matching RequestInit fields.

Gotchas

Fetch behavior comes from the runtime's implementation, so CORS, cookies, redirects, abort handling, and streaming support can vary by platform. Stream request bodies are sent as Web streams with duplex: "half", and any content-length header is removed before calling fetch.

@see ― Fetch for supplying the fetch implementation used by this layer

@see ― RequestInit for default RequestInit options applied before request-specific fields

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
));
const
const crawlDocumentation: Effect.Effect<PageCrawlRecord[], PageCrawlRateLimitedError | PageCrawlProtocolError | PageCrawlLimitError | PageCrawlTerminalError | Config.ConfigError, never>
crawlDocumentation
=
import Effect
Effect
.
const gen: <Effect.Effect<{
readonly crawl: (request: PageCrawlRequest) => Stream.Stream<PageCrawlRecord, PageCrawlError, Scope>;
}, never, PageCrawl> | Effect.Effect<PageCrawlRecord[], PageCrawlRateLimitedError | PageCrawlProtocolError | PageCrawlLimitError | PageCrawlTerminalError, Scope>, PageCrawlRecord[]>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const crawl: {
readonly crawl: (request: PageCrawlRequest) => Stream.Stream<PageCrawlRecord, PageCrawlError, Scope>;
}
crawl
= yield*
class PageCrawl

Scoped exact-host crawl stream. Provider job identity and polling stay adapter-private.

PageCrawl
;
return yield*
const crawl: {
readonly crawl: (request: PageCrawlRequest) => Stream.Stream<PageCrawlRecord, PageCrawlError, Scope>;
}
crawl
.
crawl: (request: PageCrawlRequest) => Stream.Stream<PageCrawlRecord, PageCrawlError, Scope>
crawl
(
class PageCrawlRequest

A rendered Markdown crawl rooted at one exact HTTPS host.

PageCrawlRequest
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<PageCrawlRequest, { readonly startUrl: string; readonly purposes: readonly ("search" | "ai-input" | "ai-train")[]; readonly limits: { ...; }; }, readonly [...], { ...; }>, Struct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly startUrl: string;
readonly purposes: readonly ("search" | "ai-input" | "ai-train")[];
readonly limits: PageCrawlLimits;
}, options?: MakeOptions): PageCrawlRequest

Constructs a value from the make input representation synchronously.

When to use

Use when constructor input is trusted or when validation failure should abort with a thrown Error.

Details

Applies constructor defaults and type-side validation according to MakeOptions.

Gotchas

Throws an Error with the schema issue in its cause when validation fails. Schema validation failures use the generic message "Schema validation failed"; format the cause explicitly with SchemaIssue.makeFormatterDefault() when human-readable details are needed. Causes that contain defects, interruptions, or other non-schema reasons throw with the underlying Cause attached instead.

@see ― BottomWithoutNew.makeOption — construct synchronously and discard validation details

@see ― BottomWithoutNew.makeEffect — construct through Effect when validation failure should stay in the error channel

make
({
startUrl: string
startUrl
: "https://example.com/docs/",
purposes: readonly ("search" | "ai-input" | "ai-train")[]
purposes
: ["search"],
limits: PageCrawlLimits
limits
:
class PageCrawlLimits

Immutable caller limits for one scoped crawl.

PageCrawlLimits
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<PageCrawlLimits, { readonly maxPages: number; readonly maxDepth: number; readonly maxPageBytes: number; readonly maxTotalBytes: number; readonly deadlineMillis: number; }, readonly [...], { ...; }>, Struct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly maxPages: number;
readonly maxDepth: number;
readonly maxPageBytes: number;
readonly maxTotalBytes: number;
readonly deadlineMillis: number;
}, options?: MakeOptions): PageCrawlLimits

Constructs a value from the make input representation synchronously.

When to use

Use when constructor input is trusted or when validation failure should abort with a thrown Error.

Details

Applies constructor defaults and type-side validation according to MakeOptions.

Gotchas

Throws an Error with the schema issue in its cause when validation fails. Schema validation failures use the generic message "Schema validation failed"; format the cause explicitly with SchemaIssue.makeFormatterDefault() when human-readable details are needed. Causes that contain defects, interruptions, or other non-schema reasons throw with the underlying Cause attached instead.

@see ― BottomWithoutNew.makeOption — construct synchronously and discard validation details

@see ― BottomWithoutNew.makeEffect — construct through Effect when validation failure should stay in the error channel

make
({
maxPages: number
maxPages
: 10,
maxDepth: number
maxDepth
: 2,
maxPageBytes: number
maxPageBytes
: 64 * 1_024,
maxTotalBytes: number
maxTotalBytes
: 512 * 1_024,
deadlineMillis: number
deadlineMillis
: 120_000,
}),
}),
)
.
Pipeable.pipe<Stream.Stream<PageCrawlRecord, PageCrawlRateLimitedError | PageCrawlProtocolError | PageCrawlLimitError | PageCrawlTerminalError, Scope>, Effect.Effect<PageCrawlRecord[], PageCrawlRateLimitedError | PageCrawlProtocolError | PageCrawlLimitError | PageCrawlTerminalError, Scope>>(this: Stream.Stream<...>, ab: (_: Stream.Stream<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Stream
Stream
.
const runCollect: <A, E, R>(self: Stream.Stream<A, E, R>) => Effect.Effect<Array<A>, E, R>

Runs the stream and collects all elements into an array.

Example (Collecting stream values)

import { Effect, Stream } from "effect"
const stream = Stream.make(1, 2, 3, 4, 5)
const program = Effect.gen(function*() {
const collected = yield* Stream.runCollect(stream)
collected // => [ 1, 2, 3, 4, 5 ]
})
await Effect.runPromise(program)

@category ― destructors

@since ― 2.0.0

runCollect
);
}).
Pipeable.pipe<Effect.Effect<PageCrawlRecord[], PageCrawlRateLimitedError | PageCrawlProtocolError | PageCrawlLimitError | PageCrawlTerminalError, PageCrawl | Scope>, Effect.Effect<PageCrawlRecord[], PageCrawlRateLimitedError | PageCrawlProtocolError | PageCrawlLimitError | PageCrawlTerminalError, PageCrawl>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const scoped: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Scope>>

Runs an effect with a scope that closes when the effect completes.

When to use

Use to acquire scoped resources for the duration of a single workflow.

Details

Finalizers for resources acquired inside the workflow run as soon as the workflow completes, whether by success, failure, or interruption.

Example (Running a scoped acquisition)

import { Effect } from "effect"
const output: Array<unknown> = []
const resource = Effect.acquireRelease(
Effect.sync(() => { output.push("Acquiring resource") }).pipe(Effect.as("resource")),
() => Effect.sync(() => { output.push("Releasing resource") })
)
const program = Effect.scoped(
Effect.gen(function*() {
const res = yield* resource
yield* Effect.sync(() => { output.push(`Using ${res}`) })
return res
})
)
Effect.runSync(program)
output // => ["Acquiring resource", "Using resource", "Releasing resource"]

@category ― resource management

@since ― 2.0.0

scoped
,
import Effect
Effect
.
const provide: <PageCrawl, Config.ConfigError, never>(layer: Layer.Layer<PageCrawl, Config.ConfigError, never>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, Config.ConfigError | E, Exclude<R, PageCrawl>> (+5 overloads)

Provides dependencies to an effect using layers or a context. Use options.local to build the layer every time; by default, layers are shared between provide calls.

Example (Providing dependencies with a layer)

import { Context, Effect, Layer } from "effect"
interface Database {
readonly query: (sql: string) => Effect.Effect<string>
}
const Database = Context.Service<Database>("Database")
const DatabaseLayer = Layer.succeed(Database)({
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))
})
const program = Effect.gen(function*() {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
const provided = Effect.provide(program, DatabaseLayer)
await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"

@category ― providing services

@since ― 2.0.0

provide
(
const BrowserCrawlLive: Layer.Layer<PageCrawl, Config.ConfigError, never>
BrowserCrawlLive
));

The Layer loads the real account ID and redacted token once from application configuration. The operation keeps the crawl and its cleanup in one Scope.

Each record includes a URL, provider status, optional bounded Markdown, and optional origin metadata. A non-completed status may have no Markdown. Treat a rate limit, protocol failure, caller limit, or provider terminal status as a typed crawl failure. Do not turn it into an empty successful crawl.

The framework caps requests at 100 pages, depth 10, 8 MiB per page, 64 MiB total, and a 10-minute deadline. Keep limits lower for an agent request and declare the narrowest purposes array. The provider’s crawl job identity and pagination are private to the adapter.

PageScreenshot is the stateless counterpart to an interactive screenshot. It returns exactly one bounded image/png byte array, which the caller owns. Use the Quick Action screenshot layer in a Worker; the REST capture adapter implements PageCapture, not PageScreenshot. Set the full-page choice and byte limit in PageScreenshotRequest; do not persist image bytes in framework thread records by default.

For a single known URL, use a stateless screenshot instead of opening an interactive session. Choose an interactive screenshot only when it must reflect the page after navigation, filling, clicking, or scrolling in that same pass.

Open the browser inside Effect.scoped, then use the handle only inside that Scope. The handle supports navigation, text reads, fill, click, screenshot, scroll, and early explicit close. Click and fill require exactly one matching element. Action failures are typed; malformed selectors and an undispatched provider action can be identified without treating them as a successful no-op.

import {
const CloudflareInteractiveBrowser: {
layer: (options: CloudflareInteractiveBrowserOptions) => Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, HttpClient>;
hostLayer: (options: CloudflareInteractiveBrowserOptions) => Layer.Layer<BrowserRunInteractiveHost, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, HttpClient>;
}

Worker-only assembly; importing ordinary capture never loads Puppeteer.

CloudflareInteractiveBrowser
} from "@yielded/agent-platform-cloudflare/interactive-browser";
import {
class BrowserNavigateRequest
BrowserNavigateRequest
,
class BrowserReadTextRequest
BrowserReadTextRequest
,
class InteractiveBrowser
InteractiveBrowser
,
class InteractiveBrowserPolicy

Caller-owned finite per-pass budgets. Elapsed time includes pauses; provider idle limits are separate.

InteractiveBrowserPolicy
,
} from "@yielded/agent/interactive-browser";
import {
import Effect
Effect
,
import Layer
Layer
,
import Redacted
Redacted
} from "effect";
import {
import FetchHttpClient
FetchHttpClient
} from "effect/http";
import {
class WorkerEnvironment

Context service for reading worker bindings from the current env object.

WorkerEnvironment
} from "effect-cf";
// In an application, Wrangler generates these binding types.
declare
namespace global
global
{
namespace
namespace Cloudflare
Cloudflare
{
interface
interface Cloudflare.Env
Env
{
readonly
Cloudflare.Env.BROWSER: BrowserRun
BROWSER
:
class BrowserRun

Browser Run API binding for automating headless browsers.

BrowserRun
;
readonly
Cloudflare.Env.CLOUDFLARE_ACCOUNT_ID: string
CLOUDFLARE_ACCOUNT_ID
: string;
readonly
Cloudflare.Env.BROWSER_RENDERING_API_TOKEN: string
BROWSER_RENDERING_API_TOKEN
: string;
}
}
}
const
const InteractiveLive: Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, WorkerEnvironment>
InteractiveLive
=
import Layer
Layer
.
const unwrap: <InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, never, never, WorkerEnvironment>(self: Effect.Effect<Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, never>, never, WorkerEnvironment>) => Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, WorkerEnvironment>

Unwraps a Layer from an Effect, flattening the nested structure.

When to use

Use when you have an Effect that produces a Layer and you want to use that layer directly.

Details

The resulting Layer will have the combined error and dependency types from both the outer Effect and the inner Layer.

Example (Unwrapping an effectful layer)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
const layerEffect = Effect.succeed(
Layer.succeed(Database, { query: Effect.fn("Database.query")((sql: string) => Effect.succeed("result")) })
)
const unwrappedLayer = Layer.unwrap(layerEffect)
const program = Database.use((database) => database.query("SELECT 1"))
Effect.runSync(Effect.provide(program, unwrappedLayer)) // => "result"

@category ― converting

@since ― 4.0.0

unwrap
(
import Effect
Effect
.
const gen: <Effect.Effect<Cloudflare.Env, never, WorkerEnvironment>, Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, never>>(f: () => Generator<Effect.Effect<Cloudflare.Env, never, WorkerEnvironment>, Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, never>, never>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const env: Cloudflare.Env
env
= yield*
class WorkerEnvironment

Context service for reading worker bindings from the current env object.

WorkerEnvironment
;
return
const CloudflareInteractiveBrowser: {
layer: (options: CloudflareInteractiveBrowserOptions) => Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, HttpClient>;
hostLayer: (options: CloudflareInteractiveBrowserOptions) => Layer.Layer<BrowserRunInteractiveHost, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, HttpClient>;
}

Worker-only assembly; importing ordinary capture never loads Puppeteer.

CloudflareInteractiveBrowser
.
layer: (options: CloudflareInteractiveBrowserOptions) => Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, HttpClient>

Assemble binding, confirmed-session cleanup, and the generic browser service. HttpClient and construction errors remain visible. Opening a pass still requires an explicit policy and Scope; no browser is launched during Layer construction.

layer
({
CloudflareInteractiveBrowserOptions.browser: Pick<BrowserRun, "fetch">
browser
:
const env: Cloudflare.Env
env
.
Cloudflare.Env.BROWSER: BrowserRun
BROWSER
,
BrowserRunLifecycleOptions.accountId: string
accountId
:
const env: Cloudflare.Env
env
.
Cloudflare.Env.CLOUDFLARE_ACCOUNT_ID: string
CLOUDFLARE_ACCOUNT_ID
,
BrowserRunLifecycleOptions.apiToken: Redacted.Redacted<string>
apiToken
:
import Redacted
Redacted
.
const make: <string>(value: string, options?: {
readonly label?: string | undefined;
}) => Redacted.Redacted<string>

Creates a Redacted wrapper for a sensitive value.

When to use

Use to wrap a sensitive value so normal string, JSON, and inspection output is redacted.

Details

The wrapper redacts string, JSON, and inspection output to reduce accidental disclosure. The original value remains retrievable with Redacted.value until the wrapper is wiped or becomes unreachable.

Example (Creating a redacted value)

import { Redacted } from "effect"
const API_KEY = Redacted.make("1234567890")
String(API_KEY) // => "<redacted>"

@category ― constructors

@since ― 3.3.0

make
(
const env: Cloudflare.Env
env
.
Cloudflare.Env.BROWSER_RENDERING_API_TOKEN: string
BROWSER_RENDERING_API_TOKEN
),
}).
Pipeable.pipe<Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, HttpClient>, Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, never>>(this: Layer.Layer<...>, ab: (_: Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, HttpClient>) => Layer.Layer<...>): Layer.Layer<...> (+21 overloads)
pipe
(
import Layer
Layer
.
const provide: <never, never, HttpClient>(that: Layer.Layer<HttpClient, never, never>) => <RIn2, E2, ROut2>(self: Layer.Layer<ROut2, E2, RIn2>) => Layer.Layer<ROut2, E2, Exclude<RIn2, HttpClient>> (+3 overloads)

Feeds the output services of the dependency layer into the requirements of this layer, returning a layer that only provides the services from this layer.

When to use

Use when you need to hide an implementation dependency layer from callers.

Details

In serviceLayer.pipe(Layer.provide(dependencyLayer)), the dependency layer is built first and is used to satisfy the requirements of serviceLayer.

Example (Providing layer dependencies)

import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, {
readonly query: (sql: string) => Effect.Effect<string>
}>()("Database") {}
class UserService extends Context.Service<UserService, {
readonly getUser: (id: string) => Effect.Effect<{
id: string
name: string
}>
}>()("UserService") {}
class Logger extends Context.Service<Logger, {
readonly log: (msg: string) => Effect.Effect<void>
}>()("Logger") {}
// Create dependency layers
const databaseLayer = Layer.succeed(Database, {
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`DB: ${sql}`))
})
const logs: Array<string> = []
const loggerLayer = Layer.succeed(Logger, {
log: Effect.fn("Logger.log")((msg: string) => Effect.sync(() => logs.push(`[LOG] ${msg}`)))
})
// UserService depends on Database and Logger
const userServiceLayer = Layer.effect(UserService, Effect.gen(function*() {
const database = yield* Database
const logger = yield* Logger
return {
getUser: Effect.fn("UserService.getUser")(function*(id: string) {
yield* logger.log(`Looking up user ${id}`)
const result = yield* database.query(
`SELECT * FROM users WHERE id = ${id}`
)
return { id, name: result }
})
}
}))
// Provide dependencies to UserService layer
const userServiceWithDependencies = userServiceLayer.pipe(
Layer.provide(Layer.mergeAll(databaseLayer, loggerLayer))
)
// Now UserService layer has no dependencies
const program = Effect.gen(function*() {
const userService = yield* UserService
return yield* userService.getUser("123")
}).pipe(
Effect.provide(userServiceWithDependencies)
)
Effect.runSync(program) // => { id: "123", name: "DB: SELECT * FROM users WHERE id = 123" }
logs // => ["[LOG] Looking up user 123"]

@see ― provideMerge for retaining the dependency services

@category ― providing services

@since ― 2.0.0

provide
(
import FetchHttpClient
FetchHttpClient
.
const layer: Layer.Layer<HttpClient, never, never>

Layer that provides an HttpClient implementation backed by the configured Fetch function.

When to use

Use when an Effect program should execute HttpClient requests through the platform fetch implementation, especially in browser, edge, or Node.js runtimes with globalThis.fetch.

Details

The layer uses the current Fetch reference and optional RequestInit service for each request. Request-specific method, headers, body, and abort signal are supplied by the client and override matching RequestInit fields.

Gotchas

Fetch behavior comes from the runtime's implementation, so CORS, cookies, redirects, abort handling, and streaming support can vary by platform. Stream request bodies are sent as Web streams with duplex: "half", and any content-length header is removed before calling fetch.

@see ― Fetch for supplying the fetch implementation used by this layer

@see ― RequestInit for default RequestInit options applied before request-specific fields

@stability ― unstable

@category ― layers

@since ― 4.0.0

layer
));
}),
);
export const
const readExampleDomain: Effect.Effect<BrowserTextResult, InteractiveBrowserPolicyDeniedError | InteractiveBrowserBusyError | InteractiveBrowserCapacityError | InteractiveBrowserExpiredError | InteractiveBrowserActionError | InteractiveBrowserProtocolError | InteractiveBrowserLimitError | InteractiveBrowserUnsupportedError, InteractiveBrowser>
readExampleDomain
=
import Effect
Effect
.
const gen: <Effect.Effect<BrowserHandle, InteractiveBrowserPolicyDeniedError | InteractiveBrowserBusyError | InteractiveBrowserCapacityError | InteractiveBrowserExpiredError | InteractiveBrowserActionError | InteractiveBrowserProtocolError | InteractiveBrowserLimitError | InteractiveBrowserUnsupportedError, Scope> | Effect.Effect<...> | Effect.Effect<...> | Effect.Effect<...>, BrowserTextResult>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const browser: {
readonly open: (policy: InteractiveBrowserPolicy) => Effect.Effect<BrowserHandle, InteractiveBrowserError, Scope>;
}
browser
= yield*
class InteractiveBrowser
InteractiveBrowser
;
const
const handle: BrowserHandle
handle
= yield*
const browser: {
readonly open: (policy: InteractiveBrowserPolicy) => Effect.Effect<BrowserHandle, InteractiveBrowserError, Scope>;
}
browser
.
open: (policy: InteractiveBrowserPolicy) => Effect.Effect<BrowserHandle, InteractiveBrowserError, Scope>
open
(
class InteractiveBrowserPolicy

Caller-owned finite per-pass budgets. Elapsed time includes pauses; provider idle limits are separate.

InteractiveBrowserPolicy
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<InteractiveBrowserPolicy, { readonly network: { readonly _tag: "ExactHosts"; readonly allowedHosts: readonly string[]; } | { ...; } | { ...; }; readonly maxActions: number; readonly maxElapsedMillis: number; readonly maxReturnedBytes: number; }, readonly [...], { ...; }>, Struct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly network: {
readonly allowedHosts: readonly string[];
readonly _tag?: "ExactHosts" | undefined;
} | {
readonly _tag?: "PublicWeb" | undefined;
} | {
readonly _tag?: "Unrestricted" | undefined;
};
readonly maxActions: number;
readonly maxElapsedMillis: number;
readonly maxReturnedBytes: number;
}, options?: MakeOptions): InteractiveBrowserPolicy

Constructs a value from the make input representation synchronously.

When to use

Use when constructor input is trusted or when validation failure should abort with a thrown Error.

Details

Applies constructor defaults and type-side validation according to MakeOptions.

Gotchas

Throws an Error with the schema issue in its cause when validation fails. Schema validation failures use the generic message "Schema validation failed"; format the cause explicitly with SchemaIssue.makeFormatterDefault() when human-readable details are needed. Causes that contain defects, interruptions, or other non-schema reasons throw with the underlying Cause attached instead.

@see ― BottomWithoutNew.makeOption — construct synchronously and discard validation details

@see ― BottomWithoutNew.makeEffect — construct through Effect when validation failure should stay in the error channel

make
({
network: {
readonly allowedHosts: readonly string[];
readonly _tag?: "ExactHosts" | undefined;
} | {
readonly _tag?: "PublicWeb" | undefined;
} | {
readonly _tag?: "Unrestricted" | undefined;
}
network
: {
_tag?: "ExactHosts" | undefined
_tag
: "ExactHosts",
allowedHosts: readonly string[]
allowedHosts
: ["example.com"] },
maxActions: number
maxActions
: 3,
maxElapsedMillis: number
maxElapsedMillis
: 30_000,
maxReturnedBytes: number
maxReturnedBytes
: 16 * 1_024,
}),
);
yield*
const handle: BrowserHandle
handle
.
BrowserHandle.navigate: (request: BrowserNavigateRequest) => Effect.Effect<BrowserNavigationResult, InteractiveBrowserError>
navigate
(
class BrowserNavigateRequest
BrowserNavigateRequest
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<BrowserNavigateRequest, { readonly url: string; }, readonly [Struct<{ readonly url: NonEmptyString; }>], { ...; }>, Struct<...>, never, never>, ... 8 more ..., "required">.make(input: {
readonly url: string;
}, options?: MakeOptions): BrowserNavigateRequest

Constructs a value from the make input representation synchronously.

When to use

Use when constructor input is trusted or when validation failure should abort with a thrown Error.

Details

Applies constructor defaults and type-side validation according to MakeOptions.

Gotchas

Throws an Error with the schema issue in its cause when validation fails. Schema validation failures use the generic message "Schema validation failed"; format the cause explicitly with SchemaIssue.makeFormatterDefault() when human-readable details are needed. Causes that contain defects, interruptions, or other non-schema reasons throw with the underlying Cause attached instead.

@see ― BottomWithoutNew.makeOption — construct synchronously and discard validation details

@see ― BottomWithoutNew.makeEffect — construct through Effect when validation failure should stay in the error channel

make
({
url: string
url
: "https://example.com/" }));
return yield*
const handle: BrowserHandle
handle
.
BrowserHandle.readText: (request: BrowserReadTextRequest) => Effect.Effect<BrowserTextResult, InteractiveBrowserError>
readText
(
class BrowserReadTextRequest
BrowserReadTextRequest
.
BottomWithoutNew<unknown, unknown, unknown, unknown, Declaration, decodeTo<declareConstructor<BrowserReadTextRequest, { readonly selector?: string | undefined; }, readonly [Struct<{ readonly selector: optionalKey<NonEmptyString>; }>], { ...; }>, Struct<...>, never, never>, ... 8 more ..., "required">.make(input: void | {
readonly selector?: string | undefined;
}, options?: MakeOptions): BrowserReadTextRequest

Constructs a value from the make input representation synchronously.

When to use

Use when constructor input is trusted or when validation failure should abort with a thrown Error.

Details

Applies constructor defaults and type-side validation according to MakeOptions.

Gotchas

Throws an Error with the schema issue in its cause when validation fails. Schema validation failures use the generic message "Schema validation failed"; format the cause explicitly with SchemaIssue.makeFormatterDefault() when human-readable details are needed. Causes that contain defects, interruptions, or other non-schema reasons throw with the underlying Cause attached instead.

@see ― BottomWithoutNew.makeOption — construct synchronously and discard validation details

@see ― BottomWithoutNew.makeEffect — construct through Effect when validation failure should stay in the error channel

make
({}));
}).
Pipeable.pipe<Effect.Effect<BrowserTextResult, InteractiveBrowserPolicyDeniedError | InteractiveBrowserBusyError | InteractiveBrowserCapacityError | InteractiveBrowserExpiredError | InteractiveBrowserActionError | InteractiveBrowserProtocolError | InteractiveBrowserLimitError | InteractiveBrowserUnsupportedError, InteractiveBrowser | Scope>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const scoped: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Scope>>

Runs an effect with a scope that closes when the effect completes.

When to use

Use to acquire scoped resources for the duration of a single workflow.

Details

Finalizers for resources acquired inside the workflow run as soon as the workflow completes, whether by success, failure, or interruption.

Example (Running a scoped acquisition)

import { Effect } from "effect"
const output: Array<unknown> = []
const resource = Effect.acquireRelease(
Effect.sync(() => { output.push("Acquiring resource") }).pipe(Effect.as("resource")),
() => Effect.sync(() => { output.push("Releasing resource") })
)
const program = Effect.scoped(
Effect.gen(function*() {
const res = yield* resource
yield* Effect.sync(() => { output.push(`Using ${res}`) })
return res
})
)
Effect.runSync(program)
output // => ["Acquiring resource", "Using resource", "Releasing resource"]

@category ― resource management

@since ― 2.0.0

scoped
);
export const
const program: Effect.Effect<BrowserTextResult, InteractiveBrowserPolicyDeniedError | InteractiveBrowserBusyError | InteractiveBrowserCapacityError | InteractiveBrowserExpiredError | InteractiveBrowserActionError | InteractiveBrowserProtocolError | InteractiveBrowserLimitError | InteractiveBrowserUnsupportedError | BrowserRunCleanupError, WorkerEnvironment>
program
=
const readExampleDomain: Effect.Effect<BrowserTextResult, InteractiveBrowserPolicyDeniedError | InteractiveBrowserBusyError | InteractiveBrowserCapacityError | InteractiveBrowserExpiredError | InteractiveBrowserActionError | InteractiveBrowserProtocolError | InteractiveBrowserLimitError | InteractiveBrowserUnsupportedError, InteractiveBrowser>
readExampleDomain
.
Pipeable.pipe<Effect.Effect<BrowserTextResult, InteractiveBrowserPolicyDeniedError | InteractiveBrowserBusyError | InteractiveBrowserCapacityError | InteractiveBrowserExpiredError | InteractiveBrowserActionError | InteractiveBrowserProtocolError | InteractiveBrowserLimitError | InteractiveBrowserUnsupportedError, InteractiveBrowser>, Effect.Effect<...>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const provide: <InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, WorkerEnvironment>(layer: Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, WorkerEnvironment>, options?: {
readonly local?: boolean | undefined;
} | undefined) => <A, E, R>(self: Effect.Effect<...>) => Effect.Effect<...> (+5 overloads)

Provides dependencies to an effect using layers or a context. Use options.local to build the layer every time; by default, layers are shared between provide calls.

Example (Providing dependencies with a layer)

import { Context, Effect, Layer } from "effect"
interface Database {
readonly query: (sql: string) => Effect.Effect<string>
}
const Database = Context.Service<Database>("Database")
const DatabaseLayer = Layer.succeed(Database)({
query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))
})
const program = Effect.gen(function*() {
const db = yield* Database
return yield* db.query("SELECT * FROM users")
})
const provided = Effect.provide(program, DatabaseLayer)
await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"

@category ― providing services

@since ― 2.0.0

provide
(
const InteractiveLive: Layer.Layer<InteractiveBrowser, InteractiveBrowserPolicyDeniedError | BrowserRunCleanupError, WorkerEnvironment>
InteractiveLive
));

readExampleDomain requires only InteractiveBrowser. InteractiveLive yields WorkerEnvironment to construct the Cloudflare adapter, so the composed program retains WorkerEnvironment in R. Run it inside an effect-cf Worker, which supplies that service. Tests can provide a different InteractiveBrowser Layer to the same operation.

The adapter installs BrowserRunSessionLifecycle even for ordinary actions because every session needs exact-session cleanup. The browser closes on Scope exit even after an interruption. Running handle.close ends the pass early and invalidates that handle.

BrowserRunInteractiveHost extends the regular pass with a short-lived redacted Live View URL, handoff start, handoff state, and host-controlled close. Keep these operations in trusted Worker code. Your application can expose these controls through an authenticated operator UI. Never expose them to the model or store them in canonical threads.

The host layer requires BrowserRunSessionLifecycle.layer({ accountId, apiToken }) and FetchHttpClient.layer in addition to the browser binding. The lifecycle token permits exact-session cleanup for every interactive pass; browser actions themselves use the Worker binding. Give a Live View a short expiry and a handoff a finite timeout. Your application owns authentication, operator authorization, and what happens after a handoff.

A live handle remains ephemeral. A trusted host can persist session.checkpoint, then call session.detach to retain the provider page when its Scope closes. With exclusive ownership, host.resume(checkpoint, { pendingInput }) attaches only the recorded session, context, and page; it preserves the original deadline and action budget, never creates a replacement page, and never replays navigation or input. Store this private checkpoint separately from model-visible records.

Write a durable input receipt before dispatch. Include any unfinished receipt in pendingInput, even when it was written after the checkpoint. Reconnection cannot prove old input stopped: restart-unknown input permits reads and screenshots but blocks mutations, Live View, and handoff. session.drainInput can clear a local running-input fence after the SDK settles; it cannot clear restart or transport uncertainty. Human abandonment of a receipt does not stop SDK input. Only confirmed exact-session closure ends an unprovable input fence.

Failures carry content-free execution evidence. dispatch: "completed" means SDK input completed before observation failed; it does not prove website acceptance. Recoverable failures leave reads usable. Keep durable unknown receipts independent of handle health and never replay uncertain input. BrowserRunPageObservation decodes the existing JSON text observation. Its document and node IDs can be passed as expectedTarget to click or fill; a replacement node is refused before dispatch. Include its control state snapshot to also refuse changed checked, disabled, input-type, label, or validity state on the same node. An optional scopeSelector must still resolve to one root containing that node. Guarded click and fill validate and dispatch on the node in one page task; guarded click uses native DOM click semantics rather than pointer coordinates. Human handoff receipts remain host-owned; getHandoffState queries the reattached provider page.

BrowserSessions keeps one native Cloudflare page under host ownership while scoped attachments come and go. The same page can survive an approval wait, a correction, or human takeover.

flowchart LR
  accTitle: Browser ownership and authorized access
  accDescr: The host owner retains one Cloudflare browser. An Attempt attaches to the same browser through authorized attachment, and a Human accesses it through authorized Live View.
  owner["Host owner"] -->|retains| browser["Cloudflare browser"]
  attempt["Attempt"] -->|authorized attachment| browser
  human["Human"] -->|authorized Live View| browser

Import BrowserSessions from @yielded/agent-platform-cloudflare/browser-session. Provide BrowserSessions.layer({ browser: env.BROWSER, accountId, apiToken }) and FetchHttpClient.layer. The binding runs native browser commands; the private API token permits exact-session cleanup.

import {
class BrowserSessions

Native Cloudflare sessions; no framework browser workflow, checkpoint transfer, or observation policy.

BrowserSessions
,
type
class BrowserSessionReference

Private host record, not a model capability. Persist together with the owner's controller state.

BrowserSessionReference
,
} from "@yielded/agent-platform-cloudflare/browser-session";
import {
import Effect
Effect
} from "effect";
declare const
const retain: (reference: BrowserSessionReference) => Effect.Effect<void>
retain
: (
reference: BrowserSessionReference
reference
:
class BrowserSessionReference

Private host record, not a model capability. Persist together with the owner's controller state.

BrowserSessionReference
) =>
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never>

The Effect interface defines a value that lazily describes a workflow or job. The workflow requires some context R, and may fail with an error of type E, or succeed with a value of type A.

When to use

Use when you need to represent a lazy, composable workflow that can require services, fail with a typed error, or succeed with a typed value.

Details

Effect values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability.

To run an Effect value, you need a Runtime, which is a type that is capable of executing Effect values.

@category ― models

@since ― 2.0.0

Effect
<void>;
declare const
const authorize: Effect.Effect<void, never, never>
authorize
:
import Effect
Effect
.
interface Effect<out A, out E = never, out R = never>

The Effect interface defines a value that lazily describes a workflow or job. The workflow requires some context R, and may fail with an error of type E, or succeed with a value of type A.

When to use

Use when you need to represent a lazy, composable workflow that can require services, fail with a typed error, or succeed with a typed value.

Details

Effect values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability.

To run an Effect value, you need a Runtime, which is a type that is capable of executing Effect values.

@category ― models

@since ― 2.0.0

Effect
<void>;
const
const startBrowser: Effect.Effect<string, BrowserSessionError, BrowserSessions>
startBrowser
=
import Effect
Effect
.
const gen: <Effect.Effect<BrowserSession, BrowserSessionError, Scope> | Effect.Effect<{
readonly create: <E, R>(options: BrowserSessionOptions, retain: (reference: BrowserSessionReference) => Effect.Effect<void, E, R>) => Effect.Effect<BrowserSessionReference, E | BrowserSessionError, R>;
readonly createAttached: <E, R>(options: BrowserSessionOptions, retain: (reference: BrowserSessionReference) => Effect.Effect<void, E, R>) => Effect.Effect<BrowserSession, E | BrowserSessionError, R | Scope>;
readonly attach: (reference: BrowserSessionReference) => Effect.Effect<BrowserSession, BrowserSessionError, Scope>;
readonly keepAlive: (sessionId: Redacted<string>) => Effect.Effect<void, BrowserSessionError>;
readonly close: (sessionId: Redacted<string>) => Effect.Effect<void, BrowserSessionError>;
}, never, BrowserSessions> | Effect.Effect<...>, string>(f: () => Generator<...>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const sessions: {
readonly create: <E, R>(options: BrowserSessionOptions, retain: (reference: BrowserSessionReference) => Effect.Effect<void, E, R>) => Effect.Effect<BrowserSessionReference, E | BrowserSessionError, R>;
readonly createAttached: <E, R>(options: BrowserSessionOptions, retain: (reference: BrowserSessionReference) => Effect.Effect<void, E, R>) => Effect.Effect<BrowserSession, E | BrowserSessionError, R | Scope>;
readonly attach: (reference: BrowserSessionReference) => Effect.Effect<BrowserSession, BrowserSessionError, Scope>;
readonly keepAlive: (sessionId: Redacted<string>) => Effect.Effect<void, BrowserSessionError>;
readonly close: (sessionId: Redacted<string>) => Effect.Effect<void, BrowserSessionError>;
}
sessions
= yield*
class BrowserSessions

Native Cloudflare sessions; no framework browser workflow, checkpoint transfer, or observation policy.

BrowserSessions
;
const
const session: BrowserSession
session
= yield*
const sessions: {
readonly create: <E, R>(options: BrowserSessionOptions, retain: (reference: BrowserSessionReference) => Effect.Effect<void, E, R>) => Effect.Effect<BrowserSessionReference, E | BrowserSessionError, R>;
readonly createAttached: <E, R>(options: BrowserSessionOptions, retain: (reference: BrowserSessionReference) => Effect.Effect<void, E, R>) => Effect.Effect<BrowserSession, E | BrowserSessionError, R | Scope>;
readonly attach: (reference: BrowserSessionReference) => Effect.Effect<BrowserSession, BrowserSessionError, Scope>;
readonly keepAlive: (sessionId: Redacted<string>) => Effect.Effect<void, BrowserSessionError>;
readonly close: (sessionId: Redacted<string>) => Effect.Effect<void, BrowserSessionError>;
}
sessions
.
createAttached: <never, never>(options: BrowserSessionOptions, retain: (reference: BrowserSessionReference) => Effect.Effect<void, never, never>) => Effect.Effect<BrowserSession, BrowserSessionError, Scope>

Allocate and retain as create does, keeping the initial attachment in the caller's Scope. No session is exposed before retain succeeds. Failed acquisition releases its attachment immediately; after retention the application owns remote cleanup. The 30-second acquisition timeout ends before use; each command retains its own timeout and the fixed session expiry.

createAttached
({
maxElapsedMillis: number
maxElapsedMillis
: 3_600_000 },
const retain: (reference: BrowserSessionReference) => Effect.Effect<void>
retain
);
return yield*
const session: BrowserSession
session
.
BrowserSession.run: <string, never, never>(authorize: Effect.Effect<void, never, never>, action: (page: Page) => Promise<string>, options?: {
readonly timeoutMillis: number;
}) => Effect.Effect<string, BrowserSessionError, never>

Check current authority under the lock before native dispatch. A settled SDK rejection leaves the session available for inspection, but may have changed the website; never retry blindly. Timeout/interruption fences outstanding SDK work and terminates the exact browser. An optional timeoutMillis narrows the command deadline, for example for bounded preparation.

run
(
const authorize: Effect.Effect<void, never, never>
authorize
, async (
page: Page
page
) => {
await
page: Page
page
.
Page.goto(url: string, options?: GoToOptions): Promise<HTTPResponse | null>

{@inheritDoc Frame.goto}

goto
("https://example.com/");
return await
page: Page
page
.
Page.title(): Promise<string>

The page's title

@remarks ― Shortcut for Frame.titlepage.mainFrame().title().

title
();
});
}).
Pipeable.pipe<Effect.Effect<string, BrowserSessionError, BrowserSessions | Scope>, Effect.Effect<string, BrowserSessionError, BrowserSessions>>(this: Effect.Effect<string, BrowserSessionError, BrowserSessions | Scope>, ab: (_: Effect.Effect<string, BrowserSessionError, BrowserSessions | Scope>) => Effect.Effect<string, BrowserSessionError, BrowserSessions>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect
Effect
.
const scoped: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Scope>>

Runs an effect with a scope that closes when the effect completes.

When to use

Use to acquire scoped resources for the duration of a single workflow.

Details

Finalizers for resources acquired inside the workflow run as soon as the workflow completes, whether by success, failure, or interruption.

Example (Running a scoped acquisition)

import { Effect } from "effect"
const output: Array<unknown> = []
const resource = Effect.acquireRelease(
Effect.sync(() => { output.push("Acquiring resource") }).pipe(Effect.as("resource")),
() => Effect.sync(() => { output.push("Releasing resource") })
)
const program = Effect.scoped(
Effect.gen(function*() {
const res = yield* resource
yield* Effect.sync(() => { output.push(`Using ${res}`) })
return res
})
)
Effect.runSync(program)
output // => ["Acquiring resource", "Using resource", "Releasing resource"]

@category ― resource management

@since ― 2.0.0

scoped
);

retain commits the private reference to the application’s existing durable owner. Creation attempts exact-session cleanup if that commit fails. Keep references outside Tool results and agent journals. The reference identifies the exact provider session, context, and page, with a fixed expiry. Attachment never creates a replacement page.

Use createAttached when the creating operation will also use the page: it preserves the initial attachment until the caller’s Scope exits, avoiding a disconnect and reconnect before the first command. Its 30-second acquisition timeout ends when the attachment is returned; commands retain their own timeout and the fixed session expiry. Use create when only the retained reference is needed, then attach(reference) inside a later operation’s Scope. Both creation methods retain the reference before returning and release failed acquisitions immediately.

The owner retains the reference and remains responsible for cleanup after an attachment’s Scope exits. Attachments disconnect locally; they do not transfer ownership or close the remote browser. At task completion, cancellation, or expiry, the owner calls sessions.close(reference.sessionId) and reconciles unconfirmed cleanup. An Attempt may supply current authority and borrow an attachment through attemptLayer; its end does not require a browser checkpoint or handoff.

Every session.run(authorize, action) checks the supplied Effect before invoking native Puppeteer code. Recheck the current controller and grants there. The application owns network restrictions, bounded Tool results, and durable receipts for external actions. Native callbacks are trusted host code: await every SDK operation and never accept model-provided JavaScript. A settled native SDK rejection leaves the session available for inspection, with uncertain dispatch evidence. Inspect and reconcile the page before deciding what to do next; never automatically replay that operation. Unfinished commands interrupted by timeout or cancellation, uncertain credential writes, and uncertain handoffs fence and terminate the session. Confirmed cleanup does not undo website effects.

For spectators, use session.getReadOnlyLiveView(authorize, { mode: "tab", expiresInMs: 60_000 }). It uses the REST credential to mint a connection that blocks input, navigation, and JavaScript, and fails unless Cloudflare confirms the read-only guardrail for the retained target. UI input suppression does not secure an interactive URL. Expiry limits when a connection can start; established connections last until the browser closes. Read-only viewers can still see page data.

For human control, fence agent dispatch in the owner, then use the attachment’s handoff, getLiveView, and getHandoffState methods with current operator authorization. Keep Live View URLs private to the authorized recipient. Before returning to agent control, verify the recorded handoff completed and inspect the current page under the new controller’s authority.

Cloudflare may expire an idle session before the application’s deadline. The owner’s existing alarm can call sessions.keepAlive(reference.sessionId) while the session remains authorized; this neither extends the reference’s expiry nor restores an expired browser. See session options for bounds and defaults.

Use session.fillCredential(request) on that same page. Import its schemas and BrowserCredentialAccess from @yielded/agent-platform-cloudflare/browser-credentials. Each call requires current invocation authority: the host authorizes the actual top-page, frame, and form-recipient origins and resolves redacted credential material from its vault. Bind the invocation’s caller and credential identifier to one vault item; repeated authorization checks consult that item’s current grants.

A FillCredentialRequest contains an opaque credential identifier, kind, an optional iframe selector path, and explicit { selector, role } fields. All selected fields must belong to one native form. Use separate calls for separate forms or processor frames. The helper fills supported native controls; it does not infer fields or submit the form. Authorize filling itself because the site’s input/change handlers may send data immediately. Submission remains an ordinary, separately authorized browser action.

Credential material stays out of fill arguments, results, logs, and traces. The browser is allowed to display it: subsequent native observations, screenshots, and page content follow the host’s ordinary disclosure policy. There is no protected observation mode or promise to scrub page echoes.

CredentialFillResult.filled counts acknowledged assignments. It proves neither authentication nor payment acceptance. Inspect the site’s result separately. An error’s dispatch, filled count, and cleanup retain partial-write and termination evidence; an uncertain fill must not be retried automatically. Confirmed cleanup does not undo website effects.

The runnable Worker proof uses a host-bound dummy login and guarded interactive input, then independently verifies a test checkout. It does not exercise BrowserCredentialAccess or card filling.

The /protected-browser APIs and transfer checkpoints have been removed. Move browser ownership to the host and use /browser-session plus /browser-credentials. Existing protected checkpoints are not new session references: close or reconcile their provider sessions through the owning application, then create a fresh session. Preserve existing operation and cleanup evidence.

Browser APIs use finite requests and typed expected failures:

  • PageCapture fixes one output-byte limit. Navigation, rate, protocol, unsupported-operation, and output-limit failures remain typed.
  • PageCrawl fixes the start host, purposes, page/depth/byte/deadline limits, and cancellation lifecycle. Its stream ends only after the provider reports a terminal result or a typed failure.
  • PageScreenshot accepts only PNG and enforces a caller-selected byte limit.
  • An interactive policy fixes network mode, at most 1,000 actions, a caller-selected positive safe integer elapsed allowance in milliseconds, and at most 8 MiB from one result. Handles expire at policy limits or explicit close.

Quick Action failures retain bounded response text and Browser Run API status, selected request identifiers, and body truncation metadata in their host-only cause. That status describes the Browser Run API response, not necessarily the destination page. Applications can explicitly redact and retain these causes for operator diagnostics; they are not automatically exposed to models or logged.

Recognized Browser Run navigation timeouts report the provider’s elapsed limit in the public PageCaptureNavigationError message. An API HTTP 422 is not the destination’s status and does not establish that the destination blocked the request. Unknown provider text stays private.

For JavaScript-rendered pages, choose a content-specific waitForSelector with a finite timeout alongside the navigation timeout. domcontentloaded alone can capture a navigation shell, and a heading alone may precede the content being researched. Inspect the returned evidence before treating the pass as useful; missing amenities are not evidence of their absence. See Cloudflare’s Markdown endpoint and independent timeout controls.

Quick Action response readers are canceled and unlocked on local interruption, including an outer Effect timeout. The native quickAction() binding exposes neither an abort signal nor a session handle: interrupting an unresolved RPC stops local waiting but does not confirm remote browser termination. Provider navigation/readiness limits remain important. The adapter does not retry that RPC. Tests with a scripted binding establish local waiting and reader cleanup only; hosted provider lifecycle behavior requires separate live evidence.

These caps do not authorize the destination, protect every network path, or make provider actions replay-safe. Keep an application allowlist for stateless capture; choose the interactive network policy that matches the actual isolation guarantee; and treat all rendered data as untrusted.

isBrowserRunUndispatchedActionError identifies selector failures before dispatch. Callers can correct those selectors. Other action failures invalidate the handle; never retry a mutation whose outcome is unknown. Interruption cannot reliably cancel an action already sent to Puppeteer.

readText().text contains JSON with page text, selector counts, and at most 64 controls. Control diagnostics omit field values and HTML. Results, including PNG screenshots, obey the pass byte limit. Logs omit URLs, selectors, labels, field values, credentials, and provider errors.

selectFile(BrowserSelectFileRequest.make({ selector, target: "input", fileName, mediaType, bytes })) selects up to 8 MiB of host-owned bytes without a browser filesystem path. Use target: "chooser" for a button that creates or opens a file input. The result confirms selection only; inspect the website’s receipt separately to establish upload or submission. Change handlers may send bytes immediately, so authorize the destination before selection and never replay an unknown outcome.

Set the initial viewport on BrowserRunInteractiveBinding.layer or use the host session’s resizeViewport. Width and height accept integers in 1..2048; deviceScaleFactor accepts 1..2, defaults to 1, and must satisfy max(width, height) * deviceScaleFactor <= 2048. Mobile, touch, and orientation options are unsupported. Resizing consumes no agent action but remains subject to the pass deadline and lock. Authorize viewport changes in your host.

For BrowserRunInteractiveHost, call host.acquire(policy), persist the returned private sessionId, then run acquisition.connect. The acquisition owns the browser in its original Scope even if connection or page setup fails; connection is attempted at most once. host.open(policy) combines these steps for callers that do not need a persistence boundary. Acquisition failures without an identity remain indeterminate unless the provider conclusively refused allocation.

Install an Effect ErrorReporter in the invocation runtime to capture recovered browser and cleanup failures. Adapters report only source-authored stages, failure categories, and HTTP statuses, including work that settles after interruption. Public errors retain dispatch and cleanup evidence without provider text or session capabilities. In-process public projections carry ErrorReporter.ignore; custom recovery/reporting hooks must honor it to avoid duplicate captures. Do not serialize that marker as a cross-process diagnostic receipt.

Session closure waits up to ten seconds to confirm whole-browser termination or exact-session absence. A pending close or transport/authentication failure is not proof of cleanup. BrowserRunCleanupError reports a sanitized reason. Correct authorization or configuration failures before retrying. The interactive browser API comments describe action timing and lifecycle details.

The repository includes an opt-in temporary deployment proof. It runs one real buyer with BrowserUse and the Cloudflare interactive browser against a test-only store. A host-bound password authenticates the designated buyer. The terminal submission Tool places the approved order once and reads its receipt after an ambiguous confirmation; the runner independently checks the exact purchase and submission count. Alchemy owns deployment and teardown. Its README documents credentials, revision checks, recovery, CI policy, and the limits of this controlled checkout. It does not establish payment-provider compatibility.

  • Tools & layers explains how browser services become bounded Effect AI Tools.
  • Cloudflare covers Durable Object agent hosts.
  • Operations covers host authorization and isolation.