agents
Version:
A home for your AI agents
668 lines (666 loc) • 24.7 kB
TypeScript
import {
d as LifecycleRouteContext,
s as LifecycleCapability
} from "./capability-runner-BiVUtjqC.js";
import {
n as InternalHostInfo,
r as InternalHostMessage,
s as InternalHostsOptions
} from "./internal-hosts-Kfp5A392.js";
import {
O as CdpConnection,
a as ListableBrowserSessionStore,
d as BrowserBinding,
m as BrowserSessionGuardrails,
s as BrowserLiveView,
u as LiveViewMode
} from "./session-store-B7Ca-u1t.js";
//#region src/browser/spec.d.ts
/**
* The element type of an array field or type. `$ref` is always
* domain-qualified (`"Network.Cookie"`), matching a type's `name`.
*/
interface CdpItems {
type?: string;
$ref?: string;
enum?: string[];
}
/**
* A command parameter, return value, event parameter, or type property.
* Either `type` (a JSON type such as `"string"` or `"array"`) or `$ref`
* (a domain-qualified type name matching a type's `name`) is set.
*/
interface CdpField {
name: string;
description?: string;
optional?: boolean;
experimental?: boolean;
deprecated?: boolean;
type?: string;
$ref?: string;
enum?: string[];
items?: CdpItems;
}
interface SearchableCdpSpec {
domains: Array<{
name: string;
description?: string;
experimental?: boolean;
deprecated?: boolean;
commands: Array<{
name: string;
method: string;
description?: string;
experimental?: boolean;
deprecated?: boolean;
parameters: CdpField[];
returns: CdpField[];
}>;
events: Array<{
name: string;
event: string;
description?: string;
experimental?: boolean;
deprecated?: boolean;
parameters: CdpField[];
}>;
types: Array<{
id: string;
name: string;
description?: string;
experimental?: boolean;
deprecated?: boolean /** JSON type of the value, e.g. `"string"`, `"object"`, `"array"`. */;
type?: string;
enum?: string[];
properties: CdpField[];
items?: CdpItems;
}>;
}>;
}
interface CdpSpecSource {
/** Browser Rendering binding (Fetcher) — used in production */
browser?: BrowserBinding;
/** CDP base URL override (e.g. http://localhost:9222) */
cdpUrl?: string;
/** Headers to send with CDP URL discovery requests */
cdpHeaders?: Record<string, string>;
/**
* An existing Browser Run session to read the protocol from. Without it,
* loading through the binding creates (and deletes) a throwaway session.
*/
sessionId?: string;
}
/** Load the (cached) searchable CDP spec for a browser source. */
declare function loadCdpSpec(source: CdpSpecSource): Promise<SearchableCdpSpec>;
//#endregion
//#region src/browser/session.d.ts
/**
* What a {@link BrowserSession} connects to. Usually a `Browser` installed on
* the host's Lifecycle.
*/
interface BrowserSource {
/** The browser's name, for log messages. */
readonly name: string;
/**
* Reattach to the browser (or replace it) and open a CDP connection for
* a scope. A source that ignores `scope` shares one active tab between
* every caller.
*/
connect(options?: BrowserConnectOptions): Promise<BrowserConnection>;
/**
* The browser's internal hostnames, when it has any. The docs list them,
* and {@link BrowserSession.functions} adds the hosts functions.
*/
readonly internalHosts?: BrowserInternalHosts;
}
interface BrowserSessionOptions {
/**
* Who is driving: sessions with the same scope share an active tab, and
* each scope keeps its own. Cookies and logins are shared by every scope.
* Defaults to the browser's default scope.
*/
scope?: string;
/**
* The largest `Page.captureScreenshot` result `send` returns, measured as
* JSON. Defaults to 1,000,000 bytes.
*/
maxResultBytes?: number;
/**
* Lock the scope on this browser: the session takes the scope's lock at
* its first call (or at {@link BrowserSession.lock}) and holds it until it
* closes. Other locking sessions in the scope wait for the lock, in order,
* rather than fail, so their calls never interleave. Sessions without it
* don't wait and aren't waited for. Locks are per isolate, which is per
* Durable Object.
*
* Use it when several runs in one scope can start together (parallel
* tool calls, say) and must not see each other's navigation.
*
* Only `close()` releases the lock: not aborting a signal, losing the
* browser, or dropping the session. Close a locking session in `finally`,
* even when connecting failed, or its scope stays locked until the
* isolate restarts.
*/
lock?: boolean;
}
/** {@link BrowserSession.lock}'s options. */
interface BrowserLockOptions {
/**
* Bounds this call's wait: when it aborts, this call rejects with its
* reason. The session keeps waiting while other calls still are; once
* none is, it leaves the queue at once, so it doesn't hold up the
* sessions behind it. A call without a signal, including the wait a
* session's first function call makes, never gives up. The signal does
* nothing once the lock is held: only `close()` releases it.
*/
signal?: AbortSignal;
}
/** `send`'s input. */
interface BrowserSendInput {
/** CDP method, e.g. `"Page.navigate"`. */
method: string;
/** CDP command parameters. */
params?: Record<string, unknown>;
/**
* `"active"` for the current tab, or a handle from `attachToTarget`. Omit
* for `Browser.*` and `Target.*` commands.
*/
sessionId?: string;
/** Per-command timeout in milliseconds. */
timeoutMs?: number;
}
/** `attachToTarget`'s input. */
interface BrowserAttachInput {
/** Target id from `Target.getTargets` or `Target.createTarget`. */
targetId: string;
timeoutMs?: number;
}
/** `getDebugLog`'s input. */
interface BrowserDebugLogInput {
/** Most entries to return, newest last. Defaults to 50; 400 are kept. */
limit?: number;
}
/** A tab a page opened on its own during a session. */
interface BrowserNewTab {
targetId: string;
url?: string;
title?: string;
}
/**
* What happened to the browser during one session, beyond the results of its
* calls. {@link BrowserSession.close} returns it.
*/
interface BrowserSessionReport {
/** The browser was replaced before this session; earlier page state is gone. */
restarted: boolean;
/** Tabs the page opened itself (popups, `target=_blank`). */
newTabs: BrowserNewTab[];
/**
* The tab this scope was working in was closed by someone else (another
* scope, or a person in Live View), so `"active"` will be a new blank tab.
*/
tabClosed: boolean;
/** Another scope is working in this scope's active tab too. */
tabShared: boolean;
/**
* The report as sentences for the model, one per thing that happened,
* empty when nothing did. Show them after the run's result.
*/
notices: string[];
}
/** `sessionId` value that addresses the tab the agent is working in. */
declare const ACTIVE_PAGE_SESSION = "active";
/**
* One run's connection to a persistent browser, for one scope.
*
* It connects on its first call and stays connected until {@link close}.
* Concurrent first calls share one connect. Every method takes one options
* object, validated before anything touches the browser, because the input
* usually comes from model-written code.
*
* Create one per run with `browser.session({ scope })`, and close it when
* the run ends. The browser itself stays open. Pass `lock: true` when
* runs in one scope may overlap; see {@link BrowserSessionOptions.lock}.
*
* @experimental The API surface may change before stabilizing.
*/
declare class BrowserSession {
#private;
constructor(browser: BrowserSource, options?: BrowserSessionOptions);
/**
* The `browser.*` functions bound to this session, by name: the ones
* `Browser.functionDocs()` describes. Each takes one input object and
* validates it, so model-written code can call them directly. Like the
* session's methods, every one rejects once the session is closed, and
* waits for the scope's lock when the session locks.
*/
get functions(): Readonly<Record<string, BrowserFunction>>;
/**
* Take this scope's lock now rather than at the first call, and hold it
* until {@link close}. For sessions opened with `lock: true`; a
* caller with its own time limit calls it first, so the wait doesn't count
* against the limit, or passes `signal` to bound the wait.
*
* Resolves once the session holds the lock, after the locking sessions
* ahead of it close; later and concurrent calls resolve with the same
* lock. Rejects when the session closes while waiting. Each call's
* `signal` bounds only that call's wait: it rejects with the signal's
* reason, and the session leaves the queue once no call is still
* waiting (see {@link BrowserLockOptions.signal}); a later call or first
* call waits again.
*/
lock(options?: BrowserLockOptions): Promise<void>;
/**
* How long this session waited for its lock, in milliseconds: `0` until
* it has waited, and for sessions that don't lock.
*/
get waitedForLockMs(): number;
/**
* Send a CDP command and resolve with its result, without the JSON-RPC
* envelope. `sessionId: "active"` runs it in the scope's tab.
*/
send(input: BrowserSendInput): Promise<unknown>;
/**
* Make an open tab the scope's active tab, and resolve with a `sessionId`
* handle for it that keeps working in later runs.
*/
attachToTarget(input: BrowserAttachInput): Promise<{
sessionId: string;
}>;
/** The Chrome DevTools Protocol description the browser serves. */
spec(): Promise<SearchableCdpSpec>;
/** Recent CDP traffic on this session's connection, newest last. */
getDebugLog(input?: BrowserDebugLogInput): Promise<unknown[]>;
/** Forget the CDP traffic recorded so far. */
clearDebugLog(): Promise<null>;
/**
* End the session: save the scope's active tab, work out what happened to
* the browser, and close the connection. The browser stays open.
*
* Resolves with the report, or `undefined` when the session never
* connected. Never rejects: failures to read the browser's state are
* logged and leave the report incomplete. Later calls resolve with
* `undefined`, and other methods reject.
*
* A locking session releases the lock once the active tab is saved, or
* stops waiting for it if it was still waiting.
*/
close(): Promise<BrowserSessionReport | undefined>;
}
//#endregion
//#region src/browser/browser-functions.d.ts
/**
* One function on the `browser` object, as the host runs it: it takes one
* input object (usually from model-written code, so it's validated) and
* resolves with a JSON-safe result.
*
* @experimental The API surface may change before stabilizing.
*/
type BrowserFunction = (input: unknown) => Promise<unknown>;
/**
* What the model needs to know to use the `browser` object, for a tool or
* module description. `Browser.functionDocs()` returns it.
*
* @experimental The API surface may change before stabilizing.
*/
interface BrowserFunctionDocs {
/**
* The `browser.*` functions in Markdown, one bullet per function, then the
* browser's internal sites when it has any.
*/
functions: string;
/**
* How to use the browser, one rule per entry, without bullets. Rules about
* how the code runs (timeouts, how screenshots reach the model) belong to
* whatever runs it, so add those yourself.
*/
rules: string[];
}
//#endregion
//#region src/browser/providers/browser-run.d.ts
/**
* Browser Run settings applied every time a browser is created — including
* when a lost browser is replaced — so options like guardrails survive a
* restart.
*/
interface BrowserRunOptions<S = Record<string, unknown>> {
/**
* Platform `keep_alive` in milliseconds: how long an idle browser lives.
* Defaults to {@link BROWSER_SESSION_KEEP_ALIVE_DEFAULT_MS} (10 minutes).
* Browser Run accepts up to {@link BROWSER_SESSION_KEEP_ALIVE_MAX_MS}
* (20 minutes).
*/
keepAliveMs?: number;
/** Opt into Browser Run session recording (rrweb capture). */
recording?: boolean;
/**
* Hostname guardrails, fixed at launch for every connection. They don't
* apply to `internalHosts`: `allowedDomains: []` leaves the browser able
* to open internal hostnames only.
*/
guardrails?: BrowserSessionGuardrails;
/**
* Hostnames only this browser can open, served by your agent or a Worker
* instead of the internet. Plain http on port 80; no HTTPS or WebSockets.
* Deployed Workers only: in local dev the browser starts without them.
*
* Any http page open in the browser can send requests to these hosts, so
* a host whose requests have side effects should check `Origin` (see
* `InternalHost.fetch`).
*
* @example
* ```ts
* browserRun(this.env.BROWSER, {
* internalHosts: {
* router: this.ctx.exports.InternalHostRouter,
* hosts: {
* "preview.internal": {
* description: "A dev server in your sandbox",
* target: z.object({ port: z.number().int() }),
* fetch: (request, { port }) => this.sandboxPort(port, request)
* }
* }
* }
* });
* ```
*/
internalHosts?: InternalHostsOptions<S>;
}
/** A {@link Browser} provider backed by Cloudflare Browser Run. */
interface BrowserRunProvider {
readonly kind: "browser-run";
readonly binding: BrowserBinding;
readonly options: BrowserRunOptions;
}
/**
* Run the browser on Cloudflare Browser Run.
*
* @example
* ```ts
* new Browser({ provider: browserRun(env.BROWSER, { recording: true }) });
* ```
*/
declare function browserRun<const S extends Record<string, unknown> = {}>(
binding: BrowserBinding,
options?: BrowserRunOptions<S>
): BrowserRunProvider;
//#endregion
//#region src/browser/scope.d.ts
/**
* Browser scopes: who is driving a shared `Browser`. Sessions with the same
* scope share an active tab (and, when they lock, a lock); each scope
* keeps its own. Cookies and logins are shared by every scope.
*/
/**
* The scope used when a caller doesn't pick one: every caller that leaves
* `scope` out shares this one active tab. See `BrowserConnectOptions`.
*/
declare const DEFAULT_BROWSER_SCOPE = "shared";
/**
* The scope for one conversation: `conversation:<id>`. pi's `browserTool`
* gives each conversation this scope by default, so app code that opens
* sessions with the same scope (a Workspace's `codemode:browser`, say) lands
* in the same tab as the tool. pi's conversation ids are numbers: pass
* `String(conversationId)`.
*
* Throws on a missing or empty id rather than returning
* `"conversation:undefined"`, which would quietly put every conversation
* without an id in one tab.
*
* @experimental The API surface may change before stabilizing.
*/
declare function conversationScope(id: string): string;
//#endregion
//#region src/browser/browser.d.ts
/**
* Scope records kept per browser. Writing a new one past the cap deletes the
* least recently used; a scope that comes back afterwards starts fresh.
*/
declare const MAX_BROWSER_SCOPES = 100;
/** Where a {@link Browser} gets its browser from. */
type BrowserProvider = BrowserRunProvider;
interface BrowserOptions {
/** Where the browser runs. Create one with `browserRun()`. */
provider: BrowserProvider;
/**
* Which browser this is. Two `Browser` objects on the same Durable Object
* need different names. Defaults to {@link DEFAULT_BROWSER_NAME}.
*/
name?: string;
/**
* Where the browser's record lives. Defaults to a
* {@link DurableBrowserSessionStore} over the host object's storage, which
* requires installing the `Browser` with `Lifecycle.use()`. Passing a store
* lets the `Browser` work without Lifecycle. It must implement `list`.
*/
store?: ListableBrowserSessionStore;
/** Default CDP command timeout for {@link Browser.connect}. */
timeoutMs?: number;
/**
* Minimum interval between activity-driven `updatedAt` refreshes.
* Defaults to {@link SESSION_TOUCH_INTERVAL_MS}, capped at half the
* keep-alive window.
*
* @internal For tests.
*/
touchIntervalMs?: number;
}
interface ResolvedBrowser {
name: string;
/** The provider's session id — host-side only, never model-visible. */
sessionId: string;
/**
* `true` when this resolution had to create a fresh browser to replace one
* that previously existed (was closed, or expired or died upstream). Page
* state from the prior browser is gone; surface this loudly to the model.
* `false` only on first-ever use of the name — nothing was lost.
*/
restarted: boolean;
createdAt: number;
updatedAt: number;
}
interface BrowserConnectOptions {
/**
* Who is driving. Connections with the same scope share an active tab, and
* each scope keeps its own, so two conversations on one browser don't
* drive each other's page. Cookies, logins, and the tabs themselves are
* shared by every scope: a scope splits "which tab is mine", not the
* browser. Defaults to {@link DEFAULT_BROWSER_SCOPE}.
*/
scope?: string;
}
interface BrowserConnection {
name: string;
sessionId: string;
/** The scope this connection works in; see {@link BrowserConnectOptions}. */
scope: string;
/**
* `true` when this scope worked in an earlier browser that has since been
* replaced, whichever scope's connection replaced it. Its tabs and page
* state are gone; surface this loudly to the model. `false` on a scope's
* first connection, even one that replaced a lost browser: the scope had
* nothing to lose. This is per scope, not a browser-wide signal.
*/
restarted: boolean;
/**
* The tab this scope last worked in, when it recorded one in this
* browser.
*/
activeTargetId?: string;
/** Closing this connection does NOT close the browser. */
cdp: CdpConnection;
/**
* Record the tab this scope is working in (or clear it with `undefined`).
* Never resurrects: returns `false` when the browser was closed or
* replaced since this connection resolved it.
*/
setActiveTarget(targetId: string | undefined): Promise<boolean>;
/** The tabs other scopes are working in, in this browser. */
targetsInOtherScopes(): Promise<Set<string>>;
/**
* The Chrome DevTools Protocol description this browser serves, read from
* the browser itself (cached).
*/
spec(): Promise<SearchableCdpSpec>;
}
/**
* A browser's internal hostnames (`browserRun({ internalHosts })`), for the
* host and the browser tool. Assignments are stored with the agent, so they
* survive the browser being replaced, eviction, and redeploys.
*/
interface BrowserInternalHosts {
/** Every hostname with its description, schema, and current target. */
list(): Promise<InternalHostInfo[]>;
/**
* Point an assignable host somewhere: `target` is checked against the
* host's `target` schema and stored. The browser's next request to the
* host uses it. Throws with what's wrong when the target doesn't match.
*/
assign(
host: string,
target: unknown
): Promise<{
url: string;
}>;
/** Forget a host's target; its requests answer 502 until assigned again. */
clear(host: string): Promise<boolean>;
/** The hostnames with their descriptions and schemas, without reading storage. */
describe(): InternalHostInfo[];
}
/**
* One named, persistent browser.
*
* Install it with `Lifecycle.use()` — on an Agent subclass or any Durable
* Object that composes `Lifecycle` — and it stores its record in the
* object's own storage. It schedules nothing: the provider reclaims idle
* browsers itself (Browser Run once `keep_alive` elapses), and the next use
* replaces the lost browser with `restarted: true`.
*
* Everything here is host-side. Models never see browser names, provider
* session ids, or Live View URLs through this surface.
*
* Store discipline: locks wrap storage operations only — liveness probes and
* provider create/close calls always happen outside any lock, with a commit
* re-check to detect concurrent swaps.
*
* @experimental The API surface may change before stabilizing.
*/
declare class Browser extends LifecycleCapability {
#private;
/** Which browser this is; see {@link BrowserOptions.name}. */
readonly name: string;
constructor(options: BrowserOptions);
/**
* Reattach to the browser when it is still alive, otherwise create a fresh
* one. See {@link ResolvedBrowser.restarted} for the mortality signal.
*/
resolve(): Promise<ResolvedBrowser>;
/**
* Resolve the browser and open a CDP connection to it for one scope (see
* {@link BrowserConnectOptions.scope}). Commands sent over the connection
* refresh the record's `updatedAt` (throttled to
* {@link SESSION_TOUCH_INTERVAL_MS}), so hosts can tell an actively used
* browser from one the platform has likely reclaimed.
*
* If the browser expires between the liveness probe and the WebSocket
* upgrade, the record is retired and the browser resolved once more, so
* the caller gets a fresh browser instead of an error.
*/
connect(options?: BrowserConnectOptions): Promise<BrowserConnection>;
/**
* Start a session on this browser for one run: connects on its first
* call, resolves `sessionId: "active"` to the scope's tab, and reports
* what happened to the browser when you close it. `browserTool` opens
* one per run.
*/
session(options?: BrowserSessionOptions): BrowserSession;
/**
* What the model needs to know to use this browser's `browser.*`
* functions ({@link BrowserSession.functions}): the functions in Markdown,
* including the internal sites and hosts functions this browser has, and
* the rules for using them. For a tool or module description; add rules
* about how your code runs (timeouts, screenshots) yourself.
*/
functionDocs(): BrowserFunctionDocs;
/**
* Close the browser: retire the record (so the next resolve reports
* `restarted: true`) and close its provider session. Returns `false` when
* there was nothing to close.
*
* The provider close is best-effort: the retired record is the durable
* outcome, and a browser whose close failed is unreachable through this
* store, so the provider reclaims it once idle (Browser Run: after
* `keepAliveMs`, at most 20 minutes).
*/
close(): Promise<boolean>;
/**
* Mint Live View URLs for the browser's open tabs, fresh from a target
* listing. `undefined` when the browser was never created, is closed, or
* is gone. The URLs are bearer credentials with a short connect window
* (~5 min) — hand them to a trusted human, never to the model, and never
* store them: re-mint instead.
*/
liveView(options?: {
mode?: LiveViewMode;
}): Promise<BrowserLiveView | undefined>;
/**
* The browser's internal hostnames, or `undefined` when its provider has
* none. See {@link BrowserInternalHosts}.
*/
get internalHosts(): BrowserInternalHosts | undefined;
/**
* Serve one browser request to an internal hostname. `InternalHostRouter`
* calls the agent's `fetchInternalHost()` with the message; `Agent` passes
* it here on its own. A plain Durable Object passes it on itself:
*
* ```ts
* fetchInternalHost(message: InternalHostMessage) {
* return this.browser.fetchInternalHost(message);
* }
* ```
*
* Answers 502 with the reason when this browser doesn't serve the host.
*/
fetchInternalHost(message: InternalHostMessage): Promise<Response>;
/**
* `Agent.fetchInternalHost()` routes here through Lifecycle, so `Agent`
* needn't import the browser.
*
* @internal Called by Lifecycle; not part of the host API.
*/
onRoute(context: LifecycleRouteContext): Promise<unknown>;
}
//#endregion
export {
loadCdpSpec as A,
BrowserSessionOptions as C,
CdpItems as D,
CdpField as E,
CdpSpecSource as O,
BrowserSession as S,
BrowserSource as T,
BrowserAttachInput as _,
BrowserOptions as a,
BrowserNewTab as b,
ResolvedBrowser as c,
BrowserRunOptions as d,
BrowserRunProvider as f,
ACTIVE_PAGE_SESSION as g,
BrowserFunctionDocs as h,
BrowserInternalHosts as i,
SearchableCdpSpec as k,
DEFAULT_BROWSER_SCOPE as l,
BrowserFunction as m,
BrowserConnectOptions as n,
BrowserProvider as o,
browserRun as p,
BrowserConnection as r,
MAX_BROWSER_SCOPES as s,
Browser as t,
conversationScope as u,
BrowserDebugLogInput as v,
BrowserSessionReport as w,
BrowserSendInput as x,
BrowserLockOptions as y
};
//# sourceMappingURL=browser-CI97khiJ.d.ts.map