@browserbasehq/stagehand
Version:
Browserbase's SDK for building browser agents
5,851 lines • 236 kB
JavaScript
import { z } from "zod/v4";
import { appendFileSync, constants, createReadStream } from "node:fs";
import { ROOT_CONTEXT, SpanKind, SpanStatusCode, context, defaultTextMapGetter, defaultTextMapSetter, trace } from "@opentelemetry/api";
import { W3CTraceContextPropagator } from "@opentelemetry/core";
import { validateHeaderName, validateHeaderValue } from "node:http";
import Browserbase from "@browserbasehq/sdk";
import { access, mkdir, mkdtemp, readFile, rm, stat } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { spawn } from "node:child_process";
import { createServer } from "node:net";
import { tmpdir } from "node:os";
import path from "node:path";
//#region ../protocol/json-rpc/schemas.ts
const JSONRPCErrorCodes = {
parseError: -32700,
invalidRequest: -32600,
methodNotFound: -32601,
invalidParams: -32602,
internalError: -32603
};
const JSONRPCParamsSchema = z.union([z.array(z.json()), z.record(z.string(), z.json())]).meta({ id: "JSONRPCParams" });
const JSONRPCRequestIdSchema = z.int().nonnegative().meta({ id: "JSONRPCRequestId" });
const JSONRPCErrorResponseIdSchema = z.union([JSONRPCRequestIdSchema, z.null()]).meta({ id: "JSONRPCErrorResponseId" });
const JSONRPCErrorObjectSchema = z.strictObject({
code: z.int(),
message: z.string(),
data: z.json().optional()
}).meta({ id: "JSONRPCErrorObject" });
const JSONRPCRequestSchema = z.strictObject({
jsonrpc: z.literal("2.0"),
id: JSONRPCRequestIdSchema,
method: z.string(),
params: JSONRPCParamsSchema.optional(),
traceparent: z.string().optional(),
tracestate: z.string().optional()
}).meta({ id: "JSONRPCRequest" });
const JSONRPCNotificationSchema = z.strictObject({
jsonrpc: z.literal("2.0"),
method: z.string(),
params: JSONRPCParamsSchema.optional()
}).meta({ id: "JSONRPCNotification" });
const JSONRPCSuccessResponseSchema = z.strictObject({
jsonrpc: z.literal("2.0"),
result: z.json(),
id: JSONRPCRequestIdSchema
}).meta({ id: "JSONRPCSuccessResponse" });
const JSONRPCErrorResponseSchema = z.strictObject({
jsonrpc: z.literal("2.0"),
error: JSONRPCErrorObjectSchema,
id: JSONRPCErrorResponseIdSchema
}).meta({ id: "JSONRPCErrorResponse" });
const JSONRPCResponseSchema = z.union([JSONRPCSuccessResponseSchema, JSONRPCErrorResponseSchema]).meta({ id: "JSONRPCResponse" });
z.union([
JSONRPCRequestSchema,
JSONRPCNotificationSchema,
JSONRPCResponseSchema
]).meta({ id: "JSONRPCMessage" });
const JSONRPCWireInputSchema = z.unknown().transform((input, context) => {
if (typeof input !== "string") return input;
try {
return JSON.parse(input);
} catch {
context.addIssue({
code: "custom",
message: "Invalid JSON"
});
return z.NEVER;
}
}).meta({ id: "JSONRPCWireInput" });
const JSONRPCEnvelopeSchema = z.record(z.string(), z.unknown()).meta({ id: "JSONRPCEnvelope" });
//#endregion
//#region ../../node_modules/.pnpm/map-obj@6.0.0/node_modules/map-obj/index.js
const isObject$2 = (value) => typeof value === "object" && value !== null;
const isObjectCustom$1 = (value) => {
if (!isObject$2(value)) return false;
if (value instanceof RegExp || value instanceof Error || value instanceof Date || value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet || value instanceof Promise || value instanceof ArrayBuffer || value instanceof DataView || ArrayBuffer.isView(value) || globalThis.Blob && value instanceof globalThis.Blob) return false;
if (typeof value.$$typeof === "symbol" || typeof value.asymmetricMatch === "function") return false;
return true;
};
const mapObjectSkip$1 = Symbol("mapObjectSkip");
const getEnumerableKeys = (object, includeSymbols) => {
if (includeSymbols) {
const stringKeys = Object.keys(object);
const symbolKeys = Object.getOwnPropertySymbols(object).filter((symbol) => Object.getOwnPropertyDescriptor(object, symbol)?.enumerable);
return [...stringKeys, ...symbolKeys];
}
return Object.keys(object);
};
const _mapObject$1 = (object, mapper, options, isSeen = /* @__PURE__ */ new WeakMap()) => {
const { target = {}, ...processOptions } = {
deep: false,
includeSymbols: false,
...options
};
if (isSeen.has(object)) return isSeen.get(object);
isSeen.set(object, target);
const mapArray = (array) => array.map((element) => isObjectCustom$1(element) ? _mapObject$1(element, mapper, processOptions, isSeen) : element);
if (Array.isArray(object)) return mapArray(object);
for (const key of getEnumerableKeys(object, processOptions.includeSymbols)) {
const value = object[key];
const mapResult = mapper(key, value);
if (mapResult === mapObjectSkip$1) continue;
if (!Array.isArray(mapResult)) throw new TypeError(`Mapper must return an array or mapObjectSkip, got ${mapResult === null ? "null" : typeof mapResult}`);
if (mapResult.length < 2) throw new TypeError(`Mapper must return an array with at least 2 elements [key, value], got ${mapResult.length} elements`);
let [newKey, newValue, { shouldRecurse = true } = {}] = mapResult;
if (newKey === "__proto__") continue;
if (processOptions.deep && shouldRecurse && isObjectCustom$1(newValue)) newValue = Array.isArray(newValue) ? mapArray(newValue) : _mapObject$1(newValue, mapper, processOptions, isSeen);
try {
target[newKey] = newValue;
} catch (error) {
if (error.name === "TypeError" && error.message.includes("read only")) continue;
throw error;
}
}
return target;
};
function mapObject$1(object, mapper, options) {
if (!isObject$2(object)) throw new TypeError(`Expected an object, got \`${object}\` (${typeof object})`);
if (Array.isArray(object)) throw new TypeError("Expected an object, got an array");
const mapperWithRoot = (key, value) => mapper(key, value, object);
return _mapObject$1(object, mapperWithRoot, options);
}
//#endregion
//#region ../../node_modules/.pnpm/camelcase@9.0.0/node_modules/camelcase/index.js
const UPPERCASE = /[\p{Lu}]/u;
const LOWERCASE = /[\p{Ll}]/u;
const LEADING_CAPITAL = /^[\p{Lu}](?![\p{Lu}])/u;
const SEPARATORS = /[_.\- ]+/;
const IDENTIFIER = /([\p{Alpha}\p{N}_]|$)/u;
const LEADING_SEPARATORS = new RegExp("^" + SEPARATORS.source);
const SEPARATORS_AND_IDENTIFIER = new RegExp(SEPARATORS.source + IDENTIFIER.source, "gu");
const NUMBERS_AND_IDENTIFIER = new RegExp(String.raw`\d+` + IDENTIFIER.source, "gu");
const preserveCamelCase = (string, toLowerCase, toUpperCase, preserveConsecutiveUppercase) => {
let isLastCharLower = false;
let isLastCharUpper = false;
let isLastLastCharUpper = false;
let isLastLastCharPreserved = false;
for (let index = 0; index < string.length; index++) {
const character = string[index];
isLastLastCharPreserved = index > 2 ? string[index - 3] === "-" : true;
if (isLastCharLower && UPPERCASE.test(character)) {
string = string.slice(0, index) + "-" + string.slice(index);
isLastCharLower = false;
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = true;
index++;
} else if (isLastCharUpper && isLastLastCharUpper && LOWERCASE.test(character) && (!isLastLastCharPreserved || preserveConsecutiveUppercase)) {
string = string.slice(0, index - 1) + "-" + string.slice(index - 1);
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = false;
isLastCharLower = true;
} else {
isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character;
isLastLastCharUpper = isLastCharUpper;
isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character;
}
}
return string;
};
const preserveConsecutiveUppercase = (input, toLowerCase) => input.replace(LEADING_CAPITAL, (match) => toLowerCase(match));
const processWithCasePreservation = (input, toLowerCase, preserveConsecutiveUppercase) => {
let result = "";
let previousWasNumber = false;
let previousWasUppercase = false;
const characters = [...input];
for (let index = 0; index < characters.length; index++) {
const character = characters[index];
const isUpperCase = UPPERCASE.test(character);
const nextCharIsUpperCase = index + 1 < characters.length && UPPERCASE.test(characters[index + 1]);
if (previousWasNumber && /[\p{Alpha}]/u.test(character)) {
result += character;
previousWasNumber = false;
previousWasUppercase = isUpperCase;
} else if (preserveConsecutiveUppercase && isUpperCase && (previousWasUppercase || nextCharIsUpperCase)) {
result += character;
previousWasUppercase = true;
} else if (/\d/.test(character)) {
result += character;
previousWasNumber = true;
previousWasUppercase = false;
} else if (SEPARATORS.test(character)) {
result += character;
previousWasUppercase = false;
} else {
result += toLowerCase(character);
previousWasNumber = false;
previousWasUppercase = false;
}
}
return result;
};
/**
Core post-processing:
- Collapses separators and uppercases the following identifier character.
- Optionally uppercases the identifier following a numeric sequence.
Two-pass strategy prevents conflicts:
1. NUMBERS_AND_IDENTIFIER: handles digit-to-letter transitions
2. SEPARATORS_AND_IDENTIFIER: handles separator-to-identifier transitions
Example: "b2b_registration" with capitalizeAfterNumber: true
- Pass 1: "2b" matches, next char is "_" (separator), so don't capitalize → "b2b_registration"
- Pass 2: "_r" matches, replace with "R" → "b2bRegistration"
*/
const postProcess = (input, toUpperCase, { capitalizeAfterNumber }) => {
const transformNumericIdentifier = capitalizeAfterNumber ? (match, identifier, offset, string) => {
const nextCharacter = string.charAt(offset + match.length);
if (SEPARATORS.test(nextCharacter)) return match;
return identifier ? match.slice(0, -identifier.length) + toUpperCase(identifier) : match;
} : (match) => match;
return input.replaceAll(NUMBERS_AND_IDENTIFIER, transformNumericIdentifier).replaceAll(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier));
};
function camelCase(input, options) {
if (!(typeof input === "string" || Array.isArray(input))) throw new TypeError("Expected the input to be `string | string[]`");
options = {
pascalCase: false,
preserveConsecutiveUppercase: false,
capitalizeAfterNumber: true,
...options
};
if (Array.isArray(input)) input = input.map((element) => element.trim()).filter((element) => element.length > 0).join("-");
else input = input.trim();
if (input.length === 0) return "";
const leadingPrefix = input.match(/^[_$]*/)[0];
input = input.slice(leadingPrefix.length);
if (input.length === 0) return leadingPrefix;
const toLowerCase = options.locale === false ? (string) => string.toLowerCase() : (string) => string.toLocaleLowerCase(options.locale);
const toUpperCase = options.locale === false ? (string) => string.toUpperCase() : (string) => string.toLocaleUpperCase(options.locale);
if (input.length === 1) {
if (SEPARATORS.test(input)) return leadingPrefix;
return leadingPrefix + (options.pascalCase ? toUpperCase(input) : toLowerCase(input));
}
if (input !== toLowerCase(input)) input = preserveCamelCase(input, toLowerCase, toUpperCase, options.preserveConsecutiveUppercase);
input = input.replace(LEADING_SEPARATORS, "");
if (options.capitalizeAfterNumber) input = options.preserveConsecutiveUppercase ? preserveConsecutiveUppercase(input, toLowerCase) : toLowerCase(input);
else input = processWithCasePreservation(input, toLowerCase, options.preserveConsecutiveUppercase);
if (options.pascalCase && input.length > 0) input = toUpperCase(input[0]) + input.slice(1);
return leadingPrefix + postProcess(input, toUpperCase, options);
}
//#endregion
//#region ../../node_modules/.pnpm/quick-lru@7.3.0/node_modules/quick-lru/index.js
var QuickLRU = class extends Map {
#size = 0;
#cache = /* @__PURE__ */ new Map();
#oldCache = /* @__PURE__ */ new Map();
#maxSize;
#maxAge;
#onEviction;
constructor(options = {}) {
super();
if (!(options.maxSize && options.maxSize > 0)) throw new TypeError("`maxSize` must be a number greater than 0");
if (typeof options.maxAge === "number" && options.maxAge === 0) throw new TypeError("`maxAge` must be a number greater than 0");
this.#maxSize = options.maxSize;
this.#maxAge = options.maxAge || Number.POSITIVE_INFINITY;
this.#onEviction = options.onEviction;
}
get __oldCache() {
return this.#oldCache;
}
#emitEvictions(cache) {
if (typeof this.#onEviction !== "function") return;
for (const [key, item] of cache) this.#onEviction(key, item.value);
}
#deleteIfExpired(key, item) {
if (typeof item.expiry === "number" && item.expiry <= Date.now()) {
if (typeof this.#onEviction === "function") this.#onEviction(key, item.value);
return this.delete(key);
}
return false;
}
#getOrDeleteIfExpired(key, item) {
if (this.#deleteIfExpired(key, item) === false) return item.value;
}
#getItemValue(key, item) {
return item.expiry ? this.#getOrDeleteIfExpired(key, item) : item.value;
}
#peek(key, cache) {
const item = cache.get(key);
return this.#getItemValue(key, item);
}
#set(key, value) {
this.#cache.set(key, value);
this.#size++;
if (this.#size >= this.#maxSize) {
this.#size = 0;
this.#emitEvictions(this.#oldCache);
this.#oldCache = this.#cache;
this.#cache = /* @__PURE__ */ new Map();
}
}
#moveToRecent(key, item) {
this.#oldCache.delete(key);
this.#set(key, item);
}
*#entriesAscending() {
for (const item of this.#oldCache) {
const [key, value] = item;
if (!this.#cache.has(key)) {
if (this.#deleteIfExpired(key, value) === false) yield item;
}
}
for (const item of this.#cache) {
const [key, value] = item;
if (this.#deleteIfExpired(key, value) === false) yield item;
}
}
get(key) {
if (this.#cache.has(key)) {
const item = this.#cache.get(key);
return this.#getItemValue(key, item);
}
if (this.#oldCache.has(key)) {
const item = this.#oldCache.get(key);
if (this.#deleteIfExpired(key, item) === false) {
this.#moveToRecent(key, item);
return item.value;
}
}
}
set(key, value, { maxAge = this.#maxAge } = {}) {
const expiry = typeof maxAge === "number" && maxAge !== Number.POSITIVE_INFINITY ? Date.now() + maxAge : void 0;
if (this.#cache.has(key)) this.#cache.set(key, {
value,
expiry
});
else this.#set(key, {
value,
expiry
});
return this;
}
has(key) {
if (this.#cache.has(key)) return !this.#deleteIfExpired(key, this.#cache.get(key));
if (this.#oldCache.has(key)) return !this.#deleteIfExpired(key, this.#oldCache.get(key));
return false;
}
peek(key) {
if (this.#cache.has(key)) return this.#peek(key, this.#cache);
if (this.#oldCache.has(key)) return this.#peek(key, this.#oldCache);
}
expiresIn(key) {
const item = this.#cache.get(key) ?? this.#oldCache.get(key);
if (item) return item.expiry ? item.expiry - Date.now() : Number.POSITIVE_INFINITY;
}
delete(key) {
const deleted = this.#cache.delete(key);
if (deleted) this.#size--;
return this.#oldCache.delete(key) || deleted;
}
clear() {
this.#cache.clear();
this.#oldCache.clear();
this.#size = 0;
}
resize(newSize) {
if (!(newSize && newSize > 0)) throw new TypeError("`maxSize` must be a number greater than 0");
const items = [...this.#entriesAscending()];
const removeCount = items.length - newSize;
if (removeCount < 0) {
this.#cache = new Map(items);
this.#oldCache = /* @__PURE__ */ new Map();
this.#size = items.length;
} else {
if (removeCount > 0) this.#emitEvictions(items.slice(0, removeCount));
this.#oldCache = new Map(items.slice(removeCount));
this.#cache = /* @__PURE__ */ new Map();
this.#size = 0;
}
this.#maxSize = newSize;
}
evict(count = 1) {
const requested = Number(count);
if (!requested || requested <= 0) return;
const items = [...this.#entriesAscending()];
const evictCount = Math.trunc(Math.min(requested, Math.max(items.length - 1, 0)));
if (evictCount <= 0) return;
this.#emitEvictions(items.slice(0, evictCount));
this.#oldCache = new Map(items.slice(evictCount));
this.#cache = /* @__PURE__ */ new Map();
this.#size = 0;
}
*keys() {
for (const [key] of this) yield key;
}
*values() {
for (const [, value] of this) yield value;
}
*[Symbol.iterator]() {
for (const item of this.#cache) {
const [key, value] = item;
if (this.#deleteIfExpired(key, value) === false) yield [key, value.value];
}
for (const item of this.#oldCache) {
const [key, value] = item;
if (!this.#cache.has(key)) {
if (this.#deleteIfExpired(key, value) === false) yield [key, value.value];
}
}
}
*entriesDescending() {
let items = [...this.#cache];
for (let i = items.length - 1; i >= 0; --i) {
const [key, value] = items[i];
if (this.#deleteIfExpired(key, value) === false) yield [key, value.value];
}
items = [...this.#oldCache];
for (let i = items.length - 1; i >= 0; --i) {
const [key, value] = items[i];
if (!this.#cache.has(key)) {
if (this.#deleteIfExpired(key, value) === false) yield [key, value.value];
}
}
}
*entriesAscending() {
for (const [key, value] of this.#entriesAscending()) yield [key, value.value];
}
get size() {
if (!this.#size) return this.#oldCache.size;
let oldCacheSize = 0;
for (const key of this.#oldCache.keys()) if (!this.#cache.has(key)) oldCacheSize++;
return Math.min(this.#size + oldCacheSize, this.#maxSize);
}
get maxSize() {
return this.#maxSize;
}
get maxAge() {
return this.#maxAge;
}
entries() {
return this.entriesAscending();
}
forEach(callbackFunction, thisArgument = this) {
for (const [key, value] of this.entriesAscending()) callbackFunction.call(thisArgument, value, key, this);
}
get [Symbol.toStringTag]() {
return "QuickLRU";
}
toString() {
return `QuickLRU(${this.size}/${this.maxSize})`;
}
[Symbol.for("nodejs.util.inspect.custom")]() {
return this.toString();
}
};
//#endregion
//#region ../../node_modules/.pnpm/camelcase-keys@10.0.2/node_modules/camelcase-keys/index.js
const has = (array, key) => array.some((element) => {
if (typeof element === "string") return element === key;
element.lastIndex = 0;
return element.test(key);
});
const isNumericKey = (key) => key.trim() !== "" && !Number.isNaN(Number(key));
const cache = new QuickLRU({ maxSize: 1e5 });
const isBuiltIn = (value) => ArrayBuffer.isView(value) || value instanceof Date || value instanceof RegExp || value instanceof Error || value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet || value instanceof Promise;
const isObject$1 = (value) => typeof value === "object" && value !== null && !isBuiltIn(value);
const transform = (input, options = {}, isSeen = /* @__PURE__ */ new WeakMap(), parentPath) => {
if (!isObject$1(input)) return input;
if (isSeen.has(input)) return isSeen.get(input);
const { exclude, pascalCase = false, stopPaths, deep = false, preserveConsecutiveUppercase = false } = options;
const stopPathsSet = new Set(stopPaths);
if (Array.isArray(input)) {
const result = [];
isSeen.set(input, result);
for (const item of input) result.push(isObject$1(item) ? transform(item, options, isSeen, parentPath) : item);
return result;
}
const result = {};
isSeen.set(input, result);
const makeMapper = (currentParentPath) => (key, value) => {
if (deep && isObject$1(value)) {
const path = currentParentPath === void 0 ? key : `${currentParentPath}.${key}`;
if (!stopPathsSet.has(path)) value = Array.isArray(value) ? value.map((item) => isObject$1(item) ? transform(item, options, isSeen, path) : item) : transform(value, options, isSeen, path);
}
if (typeof key === "string" && !isNumericKey(key) && !(exclude && has(exclude, key))) {
const cacheKey = pascalCase ? `${key}_` : key;
if (cache.has(cacheKey)) key = cache.get(cacheKey);
else {
key = camelCase(key, {
pascalCase,
locale: false,
preserveConsecutiveUppercase
});
if (key.length < 100) cache.set(cacheKey, key);
}
}
return [key, value];
};
const mappedResult = mapObject$1(input, makeMapper(parentPath), { deep: false });
Object.assign(result, mappedResult);
const symbols = Object.getOwnPropertySymbols(input);
for (const symbol of symbols) result[symbol] = deep && isObject$1(input[symbol]) ? transform(input[symbol], options, isSeen, parentPath) : input[symbol];
return result;
};
function camelcaseKeys(input, options) {
const isSeen = /* @__PURE__ */ new WeakMap();
if (Array.isArray(input)) return input.map((item) => isObject$1(item) ? transform(item, options, isSeen, void 0) : item);
return transform(input, options, isSeen);
}
//#endregion
//#region ../../node_modules/.pnpm/map-obj@5.0.2/node_modules/map-obj/index.js
const isObject = (value) => typeof value === "object" && value !== null;
const isObjectCustom = (value) => isObject(value) && !(value instanceof RegExp) && !(value instanceof Error) && !(value instanceof Date) && !(globalThis.Blob && value instanceof globalThis.Blob);
const mapObjectSkip = Symbol("mapObjectSkip");
const _mapObject = (object, mapper, options, isSeen = /* @__PURE__ */ new WeakMap()) => {
options = {
deep: false,
target: {},
...options
};
if (isSeen.has(object)) return isSeen.get(object);
isSeen.set(object, options.target);
const { target } = options;
delete options.target;
const mapArray = (array) => array.map((element) => isObjectCustom(element) ? _mapObject(element, mapper, options, isSeen) : element);
if (Array.isArray(object)) return mapArray(object);
for (const [key, value] of Object.entries(object)) {
const mapResult = mapper(key, value, object);
if (mapResult === mapObjectSkip) continue;
let [newKey, newValue, { shouldRecurse = true } = {}] = mapResult;
if (newKey === "__proto__") continue;
if (options.deep && shouldRecurse && isObjectCustom(newValue)) newValue = Array.isArray(newValue) ? mapArray(newValue) : _mapObject(newValue, mapper, options, isSeen);
target[newKey] = newValue;
}
return target;
};
function mapObject(object, mapper, options) {
if (!isObject(object)) throw new TypeError(`Expected an object, got \`${object}\` (${typeof object})`);
if (Array.isArray(object)) throw new TypeError("Expected an object, got an array");
return _mapObject(object, mapper, options);
}
//#endregion
//#region ../../node_modules/.pnpm/change-case@5.4.4/node_modules/change-case/dist/index.js
const SPLIT_LOWER_UPPER_RE = /([\p{Ll}\d])(\p{Lu})/gu;
const SPLIT_UPPER_UPPER_RE = /(\p{Lu})([\p{Lu}][\p{Ll}])/gu;
const SPLIT_SEPARATE_NUMBER_RE = /(\d)\p{Ll}|(\p{L})\d/u;
const DEFAULT_STRIP_REGEXP = /[^\p{L}\d]+/giu;
const SPLIT_REPLACE_VALUE = "$1\0$2";
const DEFAULT_PREFIX_SUFFIX_CHARACTERS = "";
/**
* Split any cased input strings into an array of words.
*/
function split(value) {
let result = value.trim();
result = result.replace(SPLIT_LOWER_UPPER_RE, SPLIT_REPLACE_VALUE).replace(SPLIT_UPPER_UPPER_RE, SPLIT_REPLACE_VALUE);
result = result.replace(DEFAULT_STRIP_REGEXP, "\0");
let start = 0;
let end = result.length;
while (result.charAt(start) === "\0") start++;
if (start === end) return [];
while (result.charAt(end - 1) === "\0") end--;
return result.slice(start, end).split(/\0/g);
}
/**
* Split the input string into an array of words, separating numbers.
*/
function splitSeparateNumbers(value) {
const words = split(value);
for (let i = 0; i < words.length; i++) {
const word = words[i];
const match = SPLIT_SEPARATE_NUMBER_RE.exec(word);
if (match) {
const offset = match.index + (match[1] ?? match[2]).length;
words.splice(i, 1, word.slice(0, offset), word.slice(offset));
}
}
return words;
}
/**
* Convert a string to space separated lower case (`foo bar`).
*/
function noCase(input, options) {
const [prefix, words, suffix] = splitPrefixSuffix(input, options);
return prefix + words.map(lowerFactory(options?.locale)).join(options?.delimiter ?? " ") + suffix;
}
/**
* Convert a string to snake case (`foo_bar`).
*/
function snakeCase(input, options) {
return noCase(input, {
delimiter: "_",
...options
});
}
function lowerFactory(locale) {
return locale === false ? (input) => input.toLowerCase() : (input) => input.toLocaleLowerCase(locale);
}
function splitPrefixSuffix(input, options = {}) {
const splitFn = options.split ?? (options.separateNumbers ? splitSeparateNumbers : split);
const prefixCharacters = options.prefixCharacters ?? DEFAULT_PREFIX_SUFFIX_CHARACTERS;
const suffixCharacters = options.suffixCharacters ?? DEFAULT_PREFIX_SUFFIX_CHARACTERS;
let prefixIndex = 0;
let suffixIndex = input.length;
while (prefixIndex < input.length) {
const char = input.charAt(prefixIndex);
if (!prefixCharacters.includes(char)) break;
prefixIndex++;
}
while (suffixIndex > prefixIndex) {
const index = suffixIndex - 1;
const char = input.charAt(index);
if (!suffixCharacters.includes(char)) break;
suffixIndex = index;
}
return [
input.slice(0, prefixIndex),
splitFn(input.slice(prefixIndex, suffixIndex)),
input.slice(suffixIndex)
];
}
//#endregion
//#region ../../node_modules/.pnpm/snakecase-keys@9.0.2/node_modules/snakecase-keys/index.js
const PlainObjectConstructor = {}.constructor;
function snakecaseKeys(obj, options) {
if (Array.isArray(obj)) {
if (obj.some((item) => item.constructor !== PlainObjectConstructor)) throw new Error("obj must be array of plain objects");
options = {
deep: true,
exclude: [],
parsingOptions: {},
...options
};
const convertCase = options.snakeCase || ((key) => snakeCase(key, options.parsingOptions));
return obj.map((item) => {
return mapObject(item, (key, val) => {
return [
matches(options.exclude, key) ? key : convertCase(key),
val,
mapperOptions(key, val, options)
];
}, options);
});
} else if (obj.constructor !== PlainObjectConstructor) throw new Error("obj must be an plain object");
options = {
deep: true,
exclude: [],
parsingOptions: {},
...options
};
const convertCase = options.snakeCase || ((key) => snakeCase(key, options.parsingOptions));
return mapObject(obj, (key, val) => {
return [
matches(options.exclude, key) ? key : convertCase(key),
val,
mapperOptions(key, val, options)
];
}, options);
}
function matches(patterns, value) {
return patterns.some((pattern) => {
return typeof pattern === "string" ? pattern === value : pattern.test(value);
});
}
function mapperOptions(key, val, options) {
return options.shouldRecurse ? { shouldRecurse: options.shouldRecurse(key, val) } : void 0;
}
//#endregion
//#region ../protocol/json-rpc/wire-casing.ts
const defaultOpaqueKeys = /* @__PURE__ */ new Set([
"headers",
"variables",
"attributes",
"body",
"data",
"userMetadata"
]);
const apiAcronymWireKeys = {
base_url: "base_URL",
prefer_css_page_size: "prefer_CSS_page_size"
};
function wireSchema(schema, options = {}) {
return z.preprocess((value) => isCaseable(value) ? camelcaseKeys(preserveApiAcronyms(value, options.opaqueKeys), {
deep: true,
preserveConsecutiveUppercase: true,
stopPaths: findOpaquePaths(value, options)
}) : value, schema);
}
function encodeWireValue(value, options = {}) {
const opaqueKeys = resolveOpaqueKeys(options);
return z.json().parse(isCaseable(value) ? snakecaseKeys(value, {
deep: true,
shouldRecurse: (key) => !opaqueKeys.has(String(key))
}) : value);
}
function findOpaquePaths(value, options = {}) {
const paths = [];
const opaqueKeys = new Set(Object.keys(snakecaseKeys(Object.fromEntries([...resolveOpaqueKeys(options)].map((key) => [key, null])))));
function visit(entry, path) {
if (Array.isArray(entry)) {
entry.forEach((item) => visit(item, path));
return;
}
if (!isRecord(entry)) return;
for (const [key, child] of Object.entries(entry)) {
const childPath = path ? `${path}.${key}` : key;
if (opaqueKeys.has(key)) paths.push(childPath);
else visit(child, childPath);
}
}
visit(value, "");
return paths;
}
function resolveOpaqueKeys(options) {
const opaqueKeys = /* @__PURE__ */ new Set([...defaultOpaqueKeys, ...options.opaqueKeys ?? []]);
for (const key of options.transformKeys ?? []) opaqueKeys.delete(key);
return opaqueKeys;
}
function preserveApiAcronyms(value, additionalOpaqueKeys = []) {
const opaqueKeys = new Set(Object.keys(snakecaseKeys(Object.fromEntries([...defaultOpaqueKeys, ...additionalOpaqueKeys].map((key) => [key, null])))));
function visit(entry) {
if (Array.isArray(entry)) return entry.map(visit);
if (!isRecord(entry)) return entry;
return Object.fromEntries(Object.entries(entry).map(([key, child]) => [Object.hasOwn(apiAcronymWireKeys, key) ? apiAcronymWireKeys[key] : key, opaqueKeys.has(key) ? child : visit(child)]));
}
return visit(value);
}
function isCaseable(value) {
return isRecord(value) || Array.isArray(value) && value.every(isRecord);
}
function isRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
//#endregion
//#region ../protocol/package.json
var version$1 = "3.0.0";
//#endregion
//#region ../protocol/protocol-version.ts
const STAGEHAND_SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/u;
const StagehandProtocolVersionSchema = z.string().regex(STAGEHAND_SEMVER_PATTERN, "Expected a SemVer 2.0.0 version");
const STAGEHAND_PROTOCOL_VERSION = StagehandProtocolVersionSchema.parse(version$1);
/**
* Stable protocol releases are compatible when their majors match and the
* server minor is at least the client minor. Patch differences are compatible.
* Prerelease protocol versions must match exactly.
*/
function checkProtocolCompatibility(clientVersion, serverVersion) {
const client = parseProtocolVersion(clientVersion);
const server = parseProtocolVersion(serverVersion);
if (client === void 0 || server === void 0) return {
compatible: false,
reason: "protocol-invalid-version"
};
if (client.prerelease !== void 0 || server.prerelease !== void 0) return clientVersion === serverVersion ? { compatible: true } : {
compatible: false,
reason: "protocol-prerelease-mismatch"
};
if (client.major !== server.major) return {
compatible: false,
reason: "protocol-major-mismatch"
};
if (server.minor < client.minor) return {
compatible: false,
reason: "protocol-server-too-old"
};
return { compatible: true };
}
function parseProtocolVersion(version) {
const match = STAGEHAND_SEMVER_PATTERN.exec(version);
if (!match) return void 0;
return {
major: BigInt(match[1]),
minor: BigInt(match[2]),
...match[4] === void 0 ? {} : { prerelease: match[4] }
};
}
//#endregion
//#region ../protocol/schemas.ts
const ModelIdSchema = z.string().min(1).regex(/^\S+$/);
const OpenAIModelIdSchema = ModelIdSchema.meta({ id: "OpenAIModelId" });
const AnthropicModelIdSchema = ModelIdSchema.meta({ id: "AnthropicModelId" });
const GoogleModelIdSchema = ModelIdSchema.meta({ id: "GoogleModelId" });
const GroqModelIdSchema = ModelIdSchema.meta({ id: "GroqModelId" });
const CerebrasModelIdSchema = ModelIdSchema.meta({ id: "CerebrasModelId" });
const ModelProviderSchema = z.enum([
"openai",
"anthropic",
"google",
"groq",
"cerebras"
]).meta({ id: "ModelProvider" });
const OpenAIModelNameSchema = z.templateLiteral(["openai/", OpenAIModelIdSchema]).meta({ id: "OpenAIModelName" });
const AnthropicModelNameSchema = z.templateLiteral(["anthropic/", AnthropicModelIdSchema]).meta({ id: "AnthropicModelName" });
const GoogleModelNameSchema = z.templateLiteral(["google/", GoogleModelIdSchema]).meta({ id: "GoogleModelName" });
const GroqModelNameSchema = z.templateLiteral(["groq/", GroqModelIdSchema]).meta({ id: "GroqModelName" });
const CerebrasModelNameSchema = z.templateLiteral(["cerebras/", CerebrasModelIdSchema]).meta({ id: "CerebrasModelName" });
const ModelNameSchema = z.union([
OpenAIModelNameSchema,
AnthropicModelNameSchema,
GoogleModelNameSchema,
GroqModelNameSchema,
CerebrasModelNameSchema
]).meta({
id: "ModelName",
description: "A model name with a supported provider prefix"
});
const CookieSchema = z.strictObject({
name: z.string(),
value: z.string(),
domain: z.string(),
path: z.string(),
expires: z.number(),
httpOnly: z.boolean(),
secure: z.boolean(),
sameSite: z.enum([
"Strict",
"Lax",
"None"
])
}).meta({ id: "Cookie" });
const CookieParamSchema = z.strictObject({
name: z.string(),
value: z.string(),
url: z.string().optional(),
domain: z.string().optional(),
path: z.string().optional(),
expires: z.number().optional(),
httpOnly: z.boolean().optional(),
secure: z.boolean().optional(),
sameSite: z.enum([
"Strict",
"Lax",
"None"
]).optional()
}).superRefine((cookie, context) => {
let parsedUrl;
let invalidUrl = false;
if (!cookie.url && !(cookie.domain && cookie.path)) context.addIssue({
code: "custom",
path: ["url"],
message: `Cookie "${cookie.name}" must have a url or a domain/path pair`
});
if (cookie.url && cookie.domain) context.addIssue({
code: "custom",
path: ["url"],
message: `Cookie "${cookie.name}" should have either url or domain, not both`
});
if (cookie.url && cookie.path) context.addIssue({
code: "custom",
path: ["url"],
message: `Cookie "${cookie.name}" should have either url or path, not both`
});
if (cookie.expires !== void 0 && cookie.expires < 0 && cookie.expires !== -1) context.addIssue({
code: "custom",
path: ["expires"],
message: `Cookie "${cookie.name}" has an invalid expires value; use -1 for session cookies or a positive unix timestamp`
});
if (cookie.url === "about:blank") {
invalidUrl = true;
context.addIssue({
code: "custom",
path: ["url"],
message: `Blank page cannot have cookie "${cookie.name}"`
});
} else if (cookie.url?.startsWith("data:")) {
invalidUrl = true;
context.addIssue({
code: "custom",
path: ["url"],
message: `Data URL page cannot have cookie "${cookie.name}"`
});
} else if (cookie.url) try {
parsedUrl = new URL(cookie.url);
} catch {
invalidUrl = true;
context.addIssue({
code: "custom",
path: ["url"],
message: `Cookie "${cookie.name}" has an invalid url: "${cookie.url}"`
});
}
const effectivelySecure = cookie.url ? parsedUrl?.protocol === "https:" : cookie.secure === true;
if (cookie.sameSite === "None" && !invalidUrl && !effectivelySecure) context.addIssue({
code: "custom",
path: ["secure"],
message: `Cookie "${cookie.name}" has sameSite: "None" without secure: true. Browsers require secure: true when sameSite is "None".`
});
}).meta({ id: "CookieParam" });
const CookieRegexSchema = z.strictObject({
source: z.string(),
flags: z.string().regex(/^[dgimsuvy]*$/).optional()
}).superRefine(({ source, flags }, context) => {
try {
new RegExp(source, flags);
} catch {
context.addIssue({
code: "custom",
message: "Invalid cookie filter regular expression"
});
}
}).meta({ id: "CookieRegex" });
const CookieFilterSchema = z.union([z.string(), CookieRegexSchema]).meta({ id: "CookieFilter" });
const ClearCookieOptionsSchema = z.strictObject({
name: CookieFilterSchema.optional(),
domain: CookieFilterSchema.optional(),
path: CookieFilterSchema.optional()
}).meta({ id: "ClearCookieOptions" });
const DomainPolicySchema = z.strictObject({
allowedDomains: z.array(z.string()).optional(),
blockedDomains: z.array(z.string()).optional()
}).meta({ id: "DomainPolicy" });
const LLMRoleSchema = z.enum(["user", "assistant"]).meta({ id: "LLMRole" });
const LLMAnnotationsSchema = z.strictObject({
audience: z.array(LLMRoleSchema).optional(),
priority: z.number().min(0).max(1).optional(),
lastModified: z.string().optional()
}).meta({ id: "LLMAnnotations" });
const LLMTextContentSchema = z.strictObject({
type: z.literal("text"),
text: z.string(),
annotations: LLMAnnotationsSchema.optional()
}).meta({ id: "LLMTextContent" });
const LLMImageContentSchema = z.strictObject({
type: z.literal("image"),
data: z.base64().meta({ format: "byte" }),
mimeType: z.string(),
annotations: LLMAnnotationsSchema.optional()
}).meta({ id: "LLMImageContent" });
const LLMToolUseContentSchema = z.strictObject({
type: z.literal("tool_use"),
id: z.string(),
name: z.string(),
input: z.record(z.string(), z.json())
}).meta({ id: "LLMToolUseContent" });
const LLMToolResultContentBlockSchema = z.discriminatedUnion("type", [LLMTextContentSchema, LLMImageContentSchema]).meta({ id: "LLMToolResultContentBlock" });
const LLMToolResultContentSchema = z.strictObject({
type: z.literal("tool_result"),
toolUseId: z.string(),
content: z.array(LLMToolResultContentBlockSchema),
structuredContent: z.record(z.string(), z.json()).optional(),
isError: z.boolean().optional()
}).meta({ id: "LLMToolResultContent" });
const LLMMessageContentBlockSchema = z.discriminatedUnion("type", [
LLMTextContentSchema,
LLMImageContentSchema,
LLMToolUseContentSchema,
LLMToolResultContentSchema
]).meta({ id: "LLMMessageContentBlock" });
const LLMMessageSchema = z.strictObject({
role: LLMRoleSchema,
content: z.union([LLMMessageContentBlockSchema, z.array(LLMMessageContentBlockSchema)])
}).meta({ id: "LLMMessage" });
const LLMToolAnnotationsSchema = z.strictObject({
title: z.string().optional(),
readOnlyHint: z.boolean().optional(),
destructiveHint: z.boolean().optional(),
idempotentHint: z.boolean().optional(),
openWorldHint: z.boolean().optional()
}).meta({ id: "LLMToolAnnotations" });
const LLMToolExecutionSchema = z.strictObject({ taskSupport: z.enum([
"forbidden",
"optional",
"required"
]).optional() }).meta({ id: "LLMToolExecution" });
const LLMToolIconSchema = z.strictObject({
src: z.url(),
mimeType: z.string().optional(),
sizes: z.array(z.string()).optional(),
theme: z.enum(["light", "dark"]).optional()
}).meta({ id: "LLMToolIcon" });
const LLMToolJsonSchema = z.strictObject({
$schema: z.string().optional(),
type: z.literal("object"),
properties: z.record(z.string(), z.record(z.string(), z.json())).optional(),
required: z.array(z.string()).optional()
}).meta({ id: "LLMToolJson" });
z.strictObject({
type: z.literal("function"),
name: z.string(),
description: z.string(),
parameters: z.record(z.string(), z.unknown())
}).required().meta({ id: "LLMTool" });
const LLMClientToolSchema = z.strictObject({
name: z.string(),
title: z.string().optional(),
icons: z.array(LLMToolIconSchema).optional(),
description: z.string().optional(),
inputSchema: LLMToolJsonSchema,
execution: LLMToolExecutionSchema.optional(),
outputSchema: LLMToolJsonSchema.optional(),
annotations: LLMToolAnnotationsSchema.optional()
}).meta({ id: "LLMClientTool" });
const LLMToolChoiceSchema = z.strictObject({ mode: z.enum([
"auto",
"required",
"none"
]).optional() }).meta({ id: "LLMToolChoice" });
const LLMTextResponseFormatSchema = z.strictObject({ type: z.literal("text") }).meta({ id: "LLMTextResponseFormat" });
const LLMJsonSchemaResponseFormatSchema = z.strictObject({
type: z.literal("json_schema"),
name: z.string(),
description: z.string().optional(),
schema: z.json()
}).meta({ id: "LLMJsonSchemaResponseFormat" });
z.discriminatedUnion("type", [LLMTextResponseFormatSchema, LLMJsonSchemaResponseFormatSchema]).meta({ id: "LLMResponseFormat" });
const LLMGenerateBaseParamsSchema = z.strictObject({
messages: z.array(LLMMessageSchema),
systemPrompt: z.string().optional(),
temperature: z.number().optional(),
stopSequences: z.array(z.string()).optional()
}).meta({ id: "LLMGenerateBaseParams" });
const LLMMessageGenerateParamsSchema = LLMGenerateBaseParamsSchema.extend({
tools: z.array(LLMClientToolSchema).optional(),
toolChoice: LLMToolChoiceSchema.optional(),
responseFormat: LLMTextResponseFormatSchema.optional()
}).meta({ id: "LLMMessageGenerateParams" });
const LLMStructuredGenerateParamsSchema = LLMGenerateBaseParamsSchema.extend({ responseFormat: LLMJsonSchemaResponseFormatSchema }).meta({ id: "LLMStructuredGenerateParams" });
const LLMGenerateParamsSchema = z.union([LLMStructuredGenerateParamsSchema, LLMMessageGenerateParamsSchema]).meta({ id: "LLMGenerateParams" });
const LLMUsageSchema = z.strictObject({
inputTokens: z.number().int().nonnegative(),
outputTokens: z.number().int().nonnegative(),
totalTokens: z.number().int().nonnegative(),
reasoningTokens: z.number().int().nonnegative().optional(),
cachedInputTokens: z.number().int().nonnegative().optional()
}).meta({ id: "LLMUsage" });
const LLMGenerateBaseResultSchema = z.strictObject({
role: LLMRoleSchema,
content: z.union([LLMMessageContentBlockSchema, z.array(LLMMessageContentBlockSchema)]),
stopReason: z.string().optional(),
usage: LLMUsageSchema.optional()
}).meta({ id: "LLMGenerateBaseResult" });
const LLMMessageGenerateResultSchema = LLMGenerateBaseResultSchema.extend({ outputFormat: z.literal("text") }).meta({ id: "LLMMessageGenerateResult" });
const LLMStructuredGenerateResultSchema = LLMGenerateBaseResultSchema.extend({
outputFormat: z.literal("json_schema"),
structuredContent: z.json()
}).meta({ id: "LLMStructuredGenerateResult" });
const LLMGenerateResultSchema = z.discriminatedUnion("outputFormat", [LLMMessageGenerateResultSchema, LLMStructuredGenerateResultSchema]).meta({ id: "LLMGenerateResult" });
const VariablePrimitiveSchema = z.union([
z.string(),
z.number(),
z.boolean()
]).meta({ id: "VariablePrimitive" });
const VariableValueSchema = z.union([VariablePrimitiveSchema, z.strictObject({
value: VariablePrimitiveSchema,
description: z.string().optional()
}).meta({ id: "DescribedVariableValue" })]).meta({ id: "VariableValue" });
const VariablesSchema = z.record(z.string(), VariableValueSchema).meta({ id: "Variables" });
z.strictObject({
pageIdx: z.number().int().nonnegative().nullable().optional(),
url: z.string().nullable().optional(),
title: z.string().nullable().optional(),
active: z.boolean().nullable().optional(),
targetId: z.string().nullable().optional(),
tabId: z.number().int().nonnegative().nullable().optional(),
frameId: z.string().nullable().optional()
}).meta({ id: "PageLocator" });
const LocatorSchema = z.strictObject({
selector: z.string().min(1),
nth: z.number().int().nonnegative().optional()
}).meta({ id: "Locator" });
const StagehandMetricsSchema = z.strictObject({
actPromptTokens: z.number(),
actCompletionTokens: z.number(),
actReasoningTokens: z.number(),
actCachedInputTokens: z.number(),
actInferenceTimeMs: z.number(),
extractPromptTokens: z.number(),
extractCompletionTokens: z.number(),
extractReasoningTokens: z.number(),
extractCachedInputTokens: z.number(),
extractInferenceTimeMs: z.number(),
observePromptTokens: z.number(),
observeCompletionTokens: z.number(),
observeReasoningTokens: z.number(),
observeCachedInputTokens: z.number(),
observeInferenceTimeMs: z.number(),
totalPromptTokens: z.number(),
totalCompletionTokens: z.number(),
totalReasoningTokens: z.number(),
totalCachedInputTokens: z.number(),
totalInferenceTimeMs: z.number()
}).meta({ id: "StagehandMetrics" });
const MAX_CALLBACK_BATCH_TIMEOUT_MS = 2147473647;
const CallbackBatchOptionsSchema = z.strictObject({
pageId: z.string().min(1).optional(),
timeout: z.number().int().positive().max(MAX_CALLBACK_BATCH_TIMEOUT_MS).default(3e4)
}).meta({ id: "CallbackBatchOptions" });
const CallbackBatchParamsSchema = z.strictObject({
callbackSource: z.string().min(1),
input: z.json().optional(),
options: CallbackBatchOptionsSchema
}).meta({ id: "CallbackBatchParams" });
const CallbackBatchResultSchema = z.strictObject({ value: z.json().optional() }).meta({ id: "CallbackBatchResult" });
const CacheStatusSchema = z.enum([
"HIT",
"MISS",
"DISABLED"
]).meta({ id: "CacheStatus" });
/** Server-side caching configuration: a boolean toggle, or an object enabling
* caching with an optional hit-count threshold (how many identical results
* must be seen before the cache serves a hit; overrides the project's
* configured threshold). */
const CachingSchema = z.union([z.boolean(), z.strictObject({ threshold: z.number().int().positive().optional() })]).meta({ id: "Caching" });
const ApiKeyAuthSchema = z.strictObject({
type: z.literal("apiKey"),
apiKey: z.string().min(1)
}).meta({ id: "ApiKeyAuth" });
/** Detailed model configuration object */
const GoogleServiceAccountCredentialsSchema = z.strictObject({
type: z.literal("service_account").optional(),
projectId: z.string().optional(),
privateKeyId: z.string().optional(),
privateKey: z.string(),
clientEmail: z.string(),
clientId: z.string().optional(),
authUri: z.url().optional(),
tokenUri: z.url().optional(),
authProviderX509CertUrl: z.url().optional(),
clientX509CertUrl: z.url().optional(),
universeDomain: z.string().optional()
}).meta({ id: "GoogleServiceAccountCredentials" });
const GoogleServiceAccountAuthSchema = z.strictObject({
type: z.literal("googleServiceAccount").meta({ description: "Use inline Google Cloud service account credentials for provider authentication" }),
credentials: GoogleServiceAccountCredentialsSchema.meta({ description: "Google Cloud service account credentials" }),
scopes: z.union([z.string(), z.array(z.string())]).optional().meta({ description: "Google auth scopes for the desired API request" }),
projectId: z.string().optional().meta({ description: "Google Cloud project ID used by google-auth-library" }),
universeDomain: z.string().optional().meta({ description: "Google Cloud universe domain" })
}).meta({ id: "GoogleServiceAccountAuth" });
const AzureEntraIdAuthSchema = z.strictObject({
type: z.literal("azureEntraId").meta({ description: "Use a Microsoft Entra ID bearer token for authentication" }),
token: z.string().min(1).meta({ description: "Microsoft Entra ID bearer token for Azure OpenAI" })
}).meta({ id: "AzureEntraIdAuth" });
const VertexProviderOptionsSchema = z.strictObject({
project: z.string().meta({
description: "Google Cloud project ID for Vertex AI models",
example: "my-gcp-project"
}),
location: z.string().meta({
description: "Google Cloud location for Vertex AI models",
example: "us-central1"
}),
baseURL: z.url().optional().meta({ description: "Base URL for the Vertex AI provider" }),
headers: z.record(z.string(), z.string()).optional().meta({ description: "Custom headers sent with every request to the Vertex AI provider" })
}).meta({ id: "VertexProviderOptions" });
const AzureProviderOptionsSchema = z.strictObject({
resourceName: z.string().optional().meta({
description: "Azure OpenAI resource name",
example: "my-azure-openai-resource"
}),
baseURL: z.url().optional().meta({ description: "Base URL for the Azure OpenAI provider" }),
apiVersion: z.string().optional().meta({
description: "Azure OpenAI API version",
example: "2024-10-01-preview"
}),
useDeploymentBasedUrls: z.boolean().optional().meta({ description: "Whether to use deployment-based Azure OpenAI URLs" }),
headers: z.record(z.string(), z.string()).optional().meta({ description: "Custom headers sent with every request to the Azure OpenAI provider" })
}).meta({ id: "AzureProviderOptions" });
const VertexModelProviderOptionsSchema = z.strictObject({
type: z.literal("vertex"),
options: VertexProviderOptionsSchema.meta({ description: "Vertex AI provider-specific settings" })
}).meta({ id: "VertexModelProviderOptions" });
const AzureModelProviderOptionsSchema = z.strictObject({
type: z.literal("azure"),
options: AzureProviderOptionsSchema.meta({ description: "Azure OpenAI provider-specific settings" })
}).meta({ id: "AzureModelProviderOptions" });
const ThinkingEffortSchema = z.enum([
"none",
"low",
"medium",
"high",
"xhigh",
"max"
]).meta({ id: "ThinkingEffort" });
const ModelAuthSchema = z.discriminatedUnion("type", [
ApiKeyAuthSchema,
GoogleServiceAccountAuthSchema,
AzureEntraIdAuthSchema
]).meta({ id: "ModelAuth" });
const ModelProviderOptionsSchema = z.discriminatedUnion("type", [VertexModelProviderOptionsSchema, AzureModelProviderOptionsSchema]).meta({ id: "ModelProviderOptions" });
z.strictObject({
provider: ModelProviderSchema.optional(),
auth: ModelAuthSchema.optional(),
providerOptions: ModelProviderOptionsSchema.optional(),
baseURL: z.string().optional(),
organization: z.string().optional(),
thinkingBudget: z.number().optional(),
thinkingEffort: ThinkingEffortSchema.optional(),
headers: z.record(z.string(), z.string()).optional(),
reasoningEffort: z.string().optional()
}).meta({ id: "ClientOptionsBase" }).default({}).meta({ id: "ClientOptions" });
const ModelConfigSchema = z.strictObject({
apiKey: z.string().min(1).optional().meta({
description: "API key for the model provider",
example: "sk-some-openai-api-key"
}),
headers: z.record(z.string(), z.string()).optional().meta({ description: "Custom headers sent with every request to the model provider" })
}).meta({ id: "ModelConnection" }).extend({ modelName: ModelNameSchema.meta({
description: "A model name with a supported provider prefix",
example: "openai/gpt-5.4-mini"
}) }).meta({ id: "ModelConfig" });
/** Serializable reference to an LLM implemented by the connected Stagehand client. */
const ClientModelReferenceSchema = z.strictObject({ source: z.literal("client") }).meta({ id: "ClientModelReference" });
/** Browserbase viewport configuration. */
const BrowserbaseViewportSchema = z.strictObject({
width: z.number().optional(),
height: z.number().optional()
}).meta({ id: "BrowserbaseViewport" });
/** Browserbase fingerprint screen configuration. */
const BrowserbaseFingerprintScreenSchema = z.strictObject({
maxHeight: z.number().optional(),
maxWidth: z.number().optional(),
minHeight: z.number().optional(),
minWidth: z.number().optional()
}).meta({ id: "BrowserbaseFingerprintScreen" });
/** Browserbase fingerprint configuration for stealth mode. */
const BrowserbaseFingerprintSchema = z.strictObject({
browsers: z.array(z.enum([
"chrome",
"edge",
"firefox",
"safari"
])).optional(),
devices: z.array(z.enum(["desktop", "mobile"])).optional(),
httpVersion: z.enum(["1", "2"]).optional(),
locales: z.array(z.string()).optional(),
operatingSystems: z.array(z.enum([
"android",
"ios",
"linux",
"macos",
"windows"
])).optional(),
screen: BrowserbaseFingerprintScreenSchema.optional()
}).meta({ id: "BrowserbaseFingerprint" });
/** Browserbase context configuration for session persistence. */
const BrowserbaseContextSchema = z.strictObject({
id: z.string(),
persist: z.boolean().optional()
}).meta({ id: "BrowserbaseContext" });
/** Browserbase browser settings for session creation. */
const BrowserbaseBrowserSettingsSchema = z.strictObject({
advancedStealth: z.boolean().optional(),
blockAds: z.boolean().optional(),
captchaImageSelector: z.string().optional(),
captchaInputSelector: z.string().optional(),
context: BrowserbaseContextSchema.optional(),
extensionId: z.string().optional(),
fingerprint: BrowserbaseFingerprintSchema.optional(),
logSession: z.boolean().optional(),
os: z.enum([
"windows",
"mac",
"linux",
"mobile",
"tablet"
]).optional(),
recordSession: z.boolean().optional(),
solveCaptchas: z.boolean().optional(),
verified: z.boolean().optional(),
viewport: BrowserbaseViewportSchema.optional()
}).meta({ id: "BrowserbaseBrowserSettings" });
/** Browserbase managed proxy geolocation configuration. */
const BrowserbaseProxyGeolocationSchema = z.strictObject({
country: z.string(),
city: z.string().optional(),
state: z.string().optional()
}).meta({ id: "BrowserbaseProxyGeolocation" });
/** Browserbase managed proxy configuration. */
const BrowserbaseProxyConfigSchema = z.strictObject({
type: z.literal("browserbase"),
domainPattern: z.string().optional(),
geolocation: BrowserbaseProxyGeolocationSchema.optional()
}).meta({ id: "BrowserbaseProxyConfig" });
/** External proxy configuration. */
const ExternalProxyConfigSchema = z.strictObject({
type: z.literal("external"),
server: z.string(),
domainPattern: z.string().optional(),
username: z.string().optional(),
password: z.string().optional()
}).meta({ id: "ExternalProxyConfig" });
/** Browserbase session proxy configuration. */
const ProxyConfigSchema = z.discriminatedUnion("type", [BrowserbaseProxyConfigSchema, ExternalProxyConfigSchema]).meta({ id: "ProxyConfig" });
/** Browserbase region identifier for multi-region support. */
const BrowserbaseRegionSchema = z.enum([
"us-west-2",
"us-east-1",
"eu-central-1",
"ap-southeast-1"
]).meta({ id: "BrowserbaseRegion" });
z.strictObject({
browserSettings: BrowserbaseBrowserSettingsSchema.optional(),
extensionId: z.string().optional(),
keepAlive: z.boolean().optional(),
proxies: z.union([z.boolean(), z.array(ProxyConfigSchema)]).optional(),
region: BrowserbaseRegionSchema.optional(),
timeout: z.number().optional(),
userMetadata: z.record(z.string(), z.unknown()).optional()
}).meta({ id: "BrowserbaseSessionCreateParams" });
/** Browser session metadata used by provider-independent worker services. */
const BrowserSessionMetadataSchema = z.strictObject({
sessionId: z.string().min(1),
region: BrowserbaseRegionSchema.optional()
}).meta({ id: "BrowserSessionMetadata" });
z.strictObject({
args: z.array(z.string()).optional(),
executablePath: z.string().optional(),
port: z.number().optional(),
userDataDir: z.string().optional(),
preserveUserDataDir: z.boolean().optional(),
headless: z.boolean().optional(),
devtools: z.boolean().optional(),
chromiumSandbox: z.boolean().optional(),
ignoreDefaultArgs: z.union([z.boolean(), z.array(z.string())]).optional(),
proxy: z.strictObject({
server: z.string(),
bypass: z.string().optional(),
username: z.string().optional(),
password: z.string().optional()
}).optional(),
locale: z.string().optional(),
viewport: z.strictObject({
width: z.number(),
height: z.number()
}).optional(),
deviceScaleFactor: z.number().optional(),
hasTouch: z.boolean().optional(),
ignoreHTTPSErrors: z.boolean().optional(),
downloadsPath: z.string().optional(),
acceptDownloads: z.boolean().optional(),
keepAlive: z.boolean().optional()
}).meta({ id: "LocalBrowserLaunchOptions" });
/** Action object returned by observe and used by act */
const ActionSchema = z.strictObject({
selector: z.string().meta({
description: "CSS selector or XPath for the element",
example: "[data-testid='submit-button']"
}),
description: z.string().meta({
description: "Human-readable description of the action",
example: "Click the submit button"
}),
method: z.string().optional().meta({
description: "The method to execute (click, fill, etc.)",
example: "click"
}),
arguments: z.array(z.string()).optional().meta({
description: "Arguments to pass to the method",
example: ["Hello World"]
})
}).meta({
id: "Action",
description: "Action object returned by observe and used by act"
});
const StagehandResultUsageSchema = z.strictObject({
inputTokens: z.number().int().nonnegative().default(0).meta({ description: "Input tokens consumed by all LLM calls made for this operation" }),
outputTokens: z.number().int().nonnegative().default(0).meta({ description: "Output tokens consumed by all LLM calls made for this operation" }),
reasoningTokens: z.number().int().nonnegative().default(0).meta({ description: "Reasoning tokens consumed by all LLM calls made for this operation" }),
cachedInputTokens: z.number().int().nonnegative().default(0).meta({ description: "Cached input tokens used by all LLM calls made for this operation" }),
inferenceTimeMs: z.number().int().nonnegative().default(0).meta({ description: "Total time spent waiting for LLM inference during this operation" })
}).meta({
id: "StagehandResultUsage",
description: "Aggregate LLM usage for one Stagehand operation"
});
/** LLM tokens avoided by serving a request from the cache. */
const CacheTokenSavingsSchema = z.strictObject({
inputTokens: z.number().int().nonnegative().default(0),
outputTokens: z.number().int().nonnegative().default(0),
totalTokens: z.number().int().nonnegative().default(0)
}).meta({ id: "CacheTokenSavings" });
/**
* Cache observability for one act/observe/extract call. Always present, like
* `usage`: DISABLED says no lookup ran, and otherwise it is self-explaining —
* a miss carries why it missed, a hit carries how established the entry is.
*/
const CacheMetadataSchema = z.strictObject({
status: CacheStatusSchema.meta({ description: "Whether server-side caching served this result, computed it, or was not consulted" }),
count: z.number().int().nonnegative().optional().meta({ description: "Times this cache key has been seen, including this request; compare with threshold to see how close the key is to being served" }),
threshold: z.number().int().positive().optional().meta({ description: "Hit-count threshold in effect for this key" }),
missReason: z.string().optional().meta({ description: "Why the cache did not serve this request; misses only. Reported by the server: \"not_found\", \"threshold\", \"empty_array\", \"timeout\", \"error\", \"bypass\", \"screenshot\", \"not_enabled\", \"no_cache_key\". Reported locally: \"timeout\" (the cache read exceeded its budget), \"read_failed\" (another cache read failure), \"payload_too_large\" (the serialized cache request exceeds 25,000,000 UTF-8 bytes), and \"replay_failed\" (a cached value was found but could not be applied)" }),
tokensSaved: CacheTokenSavingsSchema.optional().meta({ description: "LLM tokens avoided by serving this request from cache; hits only" })
}).meta({ id: "CacheMetadata" });
const StagehandResultMetadataSchema = z.strictObject({
actionId: z.string().optional().meta({ description: "Action ID for tracking" }),
cache: CacheMetadataSchema.meta({ description: "Cache observability for this result; status is DISABLED when no cache lookup ran" }),
usage: StagehandResultUsageSchema.meta({ description: "Aggregate LLM usage for this operation; zeroed when the operation did not run inference" })
}).meta({ id: "StagehandResultMetadata" });
const ActOptionsSchema = z.strictObject({
model: ModelConfigSchema.optional().meta({ description: "Complete model configuration for this call; when omitted, the initialized Stagehand model is used, or Browserbase selects one automatically when no initialized model exists" }),
variables: VariablesSchema.optional().meta({
description: "Variables to substitute in the action instruction. Accepts flat primitives or { value, description? } objects.",
example: {
username: "john_doe",
password: {
value: "secret123",
description: "The login password"
}
}
}),
timeout: z.number().optional().meta({
description: "Timeout in ms for the action",
example: 3e4
}),
locator: LocatorSchema.optional().meta({ description: "Serializable element locator for the action target" }),
ignoreLocators: z.array(LocatorSchema).optional().meta({
description: "Locators for elements and subtrees that should be excluded from action planning",
example: [
{ selector: "nav" },
{ selector: ".cookie-banner" },
{ selector: "#sidebar-ads" }
]
}),
cache: CachingSchema.optional().meta({ description: "Override the instance-level cache setting for this request" })
}).meta({ id: "ActOptions" });
/** Inner act result data */
const ActResultDataSchema = z.strictObject({
success: z.boolean().meta({
description: "Whether the action completed successfully",
example: true
}),
message: z.string().meta({
description: "Human-readable result message",
example: "Successfully clicked the login button"
}),
actionDescription: z.string().meta({
description: "Description of the action that was performed",
example: "Clicked button with text 'Login'"
}),
actions: z.array(ActionSchema).meta({ description: "List of actions that were executed" })
}).meta({ id: "ActResultData" });
const ActResultSchema = z.strictObject({
data: ActResultDataSchema,
metadata: StagehandResultMetadataSchema
}).meta({ id: "ActResult" });
const ExtractOptionsSchema = z.strictObject({
model: ModelConfigSchema.optional().meta({ description: "Complete model configuration for this call; when omitted, the initialized Stagehand model is used, or Browserbase selects one automatically when no initialized model exists" }),
timeout: z.number().optional().meta({
description: "Timeout in ms for the extraction",
example: 3e4
}),
locator: LocatorSchema.optional().meta({ description: "Locator that scopes extraction to a specific element" }),
ignoreLocators: z.array(LocatorSchema).optional().meta({
description: "Locators for elements and subtrees that should be excluded from extraction",
example: [
{ selector: "nav" },
{ selector: ".cookie-banner" },
{ selector: "#sidebar-ads" }
]
}),
screenshot: z.boolean().optional().meta({
description: "When true, include a screenshot of the current viewport in the extraction LLM call. Defaults to false.",
example: false
}),
cache: CachingSchema.optional().meta({ description: "Override the instance-level cache setting for this request" })
}).meta({ id: "ExtractOptions" });
const ExtractResultSchema = z.strictObject({
data: z.json().meta({ description: "Extracted data matching the requested schema" }),
metadata: StagehandResultMetadataSchema
}).meta({ id: "ExtractResult" });
const ObserveOptionsSchema = z.strictObject({
model: ModelConfigSchema.optional().meta({ description: "Complete model configuration for this call; when omitted, the initialized Stagehand model is used, or Browserbase selects one automatically when no initialized model exists" }),
variables: VariablesSchema.optional().meta({
description: "Variables whose names are exposed to the model so observe() returns %variableName% placeholders in suggested action arguments instead of literal values. Accepts flat primitives or { value, description? } objects.",
example: {
username: {
value: "john@example.com",
description: "The login email"
},
rememberMe: true
}
}),
timeout: z.number().optional().meta({
description: "Timeout in ms for the observation",
example: 3e4
}),
locator: LocatorSchema.optional().meta({ description: "Locator that scopes observation to a specific element" }),
ignoreLocators: z.array(LocatorSchema).optional().meta({
description: "Locators for elements and subtrees that should be excluded from observation",
example: [
{ selector: "nav" },
{ selector: ".cookie-banner" },
{ selector: "#sidebar-ads" }
]
}),
cache: CachingSchema.optional().meta({ description: "Override the instance-level cache setting for this request" })
}).meta({ id: "ObserveOptions" });
const ObserveResultSchema = z.strictObject({
data: z.array(ActionSchema),
metadata: StagehandResultMetadataSchema
}).meta({ id: "ObserveResult" });
const EmptyParamsSchema = z.strictObject({}).meta({ id: "EmptyParams" });
const LoadStateSchema = z.enum([
"load",
"domcontentloaded",
"networkidle"
]).meta({ id: "LoadState" });
const pageNavigationUrlSchema = z.string().min(1);
const PageNavigationOptionsSchema = z.strictObject({
waitUntil: LoadStateSchema.optional(),
timeout: z.number().int().positive().optional()
}).meta({ id: "PageNavigationOptions" });
const PageVoidResultSchema = z.strictObject({ ok: z.literal(true) }).meta({ id: "PageVoidResult" });
const ContextVoidResultSchema = z.strictObject({ ok: z.literal(true) }).meta({ id: "ContextVoidResult" });
const PageScreenshotClipSchema = z.strictObject({
x: z.number(),
y: z.number(),
width: z.number().positive(),
height: z.number().positive()
}).meta({ id: "PageScreenshotClip" });
const SnapshotResultSchema = z.strictObject({
formattedTree: z.string(),
xpathMap: z.record(z.string(), z.string()),
urlMap: z.record(z.string(), z.string())
}).meta({ id: "SnapshotResult" });
const PageSnapshotOptionsSchema = z.strictObject({
timeout: z.number().nonnegative().optional().describe("Milliseconds for the whole snapshot call. Defaults to 20000. Zero disables the timeout."),
includeIframes: z.boolean().optional()
}).meta({ id: "PageSnapshotOptions" });
const PageRefSchema = z.strictObject({
pageId: z.string(),
url: z.string().optional(),
title: z.string().optional()
}).meta({ id: "PageRef" });
const NavigationHeaderSchema = z.strictObject({
name: z.string(),
value: z.string()
}).meta({ id: "NavigationHeader" });
const NavigationSecurityDetailsSchema = z.strictObject({
issuer: z.string(),
protocol: z.string(),
subjectName: z.string(),
validFrom: z.number(),
validTo: z.number()
}).meta({ id: "NavigationSecurityDetails" });
const NavigationServerAddrSchema = z.strictObject({
ipAddress: z.string(),
port: z.number().int().min(0).max(65535)
}).meta({ id: "NavigationServerAddr" });
const NavigationFinishedErrorSchema = z.strictObject({ message: z.string() }).meta({ id: "NavigationFinishedError" });
const NavigationResponseDescriptorSchema = z.strictObject({
responseId: z.string().min(1),
url: z.string(),
status: z.number().int().nonnegative(),
statusText: z.string(),
headers: z.record(z.string(), z.string()),
fromServiceWorker: z.boolean()
}).meta({ id: "NavigationResponseDescriptor" });
const PageNavigationResultSchema = z.strictObject({
page: PageRefSchema,
response: NavigationResponseDescriptorSchema.nullable()
}).meta({ id: "PageNavigationResult" });
const ResponseIdParamsSchema = z.strictObject({ responseId: z.string().min(1) }).meta({ id: "ResponseIdParams" });
const ResponseBodyResultSchema = z.strictObject({
body: z.string(),
base64Encoded: z.literal(true)
}).meta({ id: "ResponseBodyResult" });
const ResponseAllHeadersResultSchema = z.strictObject({ headers: z.record(z.string(), z.string()) }).meta({ id: "ResponseAllHeadersResult" });
const ResponseHeadersArrayResultSchema = z.strictObject({ headers: z.array(NavigationHeaderSchema) }).meta({ id: "ResponseHeadersArrayResult" });
const ResponseSecurityDetailsResultSchema = z.strictObject({ value: NavigationSecurityDetailsSchema.nullable() }).meta({ id: "ResponseSecurityDetailsResult" });
const ResponseServerAddrResultSchema = z.strictObject({ value: NavigationServerAddrSchema.nullable() }).meta({ id: "ResponseServerAddrResult" });
const ResponseFinishedResultSchema = z.strictObject({ error: NavigationFinishedErrorSchema.nullable() }).meta({ id: "ResponseFinishedResult" });
z.enum(["console"]).meta({ id: "PageEventName" });
const PageSubscriptionEventNameSchema = z.enum([
"console",
"toolsadded",
"toolsremoved"
]).meta({ id: "PageSubscriptionEventName" });
const PageCDPEventParamsSchema = z.record(z.string(), z.json()).meta({ id: "PageCDPEventParams" });
const PageCDPEventSchema = z.strictObject({
pageId: z.string().min(1),
method: z.enum(["Runtime.consoleAPICalled"]),
params: PageCDPEventParamsSchema,
sessionId: z.string().min(1),
targetId: z.string().min(1)
}).meta({ id: "PageCDPEvent" });
const PageCDPEventNotificationSchema = z.strictObject({
subscriptionId: z.string().min(1),
event: PageCDPEventSchema
}).meta({ id: "PageCDPEventNotification" });
const WebMCPAnnotationSchema = z.strictObject({
readOnly: z.boolean().optional(),
untrustedContent: z.boolean().optional(),
autosubmit: z.boolean().optional()
}).meta({ id: "WebMCPAnnotation" });
const WebMCPJsonValueSchema = z.json().meta({ id: "WebMCPJsonValue" });
const WebMCPToolDescriptorSchema = z.strictObject({
name: z.string().min(1),
description: z.string(),
inputSchema: z.record(z.string(), WebMCPJsonValueSchema).optional(),
annotations: WebMCPAnnotationSchema.optional(),
frameId: z.string().min(1),
backendNodeId: z.number().int().nonnegative().optional()
}).meta({ id: "WebMCPToolDescriptor" });
const WebMCPToolIdentitySchema = z.strictObject({
frameId: z.string().min(1),
name: z.string().min(1)
}).meta({ id: "WebMCPToolIdentity" });
const pageEventRoutingShape = {
subscriptionId: z.string().min(1),
pageId: z.string().min(1),
sessionId: z.string().min(1),
targetId: z.string().min(1)
};
const PageToolsAddedNotificationSchema = z.strictObject({
...pageEventRoutingShape,
event: z.literal("toolsadded"),
tools: z.array(WebMCPToolDescriptorSchema)
}).meta({ id: "PageToolsAddedNotification" });
const PageToolsRemovedNotificationSchema = z.strictObject({
...pageEventRoutingShape,
event: z.literal("toolsremoved"),
tools: z.array(WebMCPToolIdentitySchema)
}).meta({ id: "PageToolsRemovedNotification" });
const PageEventNotificationSchema = z.discriminatedUnion("event", [PageToolsAddedNotificationSchema, PageToolsRemovedNotificationSchema]).meta({ id: "PageEventNotification" });
const WebMCPToolsOptionsSchema$1 = z.strictObject({ timeout: z.number().nonnegative().default(1e3) }).meta({ id: "WebMCPToolsOptions" });
const WebMCPInvokeOptionsSchema$1 = z.strictObject({ input: z.record(z.string(), WebMCPJsonValueSchema).default({}) }).meta({ id: "WebMCPInvokeOptions" });
const WebMCPResultOptionsSchema$1 = z.strictObject({ timeout: z.number().nonnegative().optional() }).meta({ id: "WebMCPResultOptions" });
const WebMCPInvocationDescriptorSchema = z.strictObject({
invocationId: z.string().min(1),
toolName: z.string().min(1),
frameId: z.string().min(1),
input: z.record(z.string(), WebMCPJsonValueSchema)
}).meta({ id: "WebMCPInvocationDescriptor" });
const WebMCPInvocationStatusSchema = z.enum([
"Completed",
"Canceled",
"Error"
]).meta({ id: "WebMCPInvocationStatus" });
const WebMCPRemoteObjectSchema = z.record(z.string(), WebMCPJsonValueSchema).meta({ id: "WebMCPRemoteObject" });
const WebMCPToolResponseSchema = z.strictObject({
invocationId: z.string().min(1),
status: WebMCPInvocationStatusSchema,
output: WebMCPJsonValueSchema.optional(),
errorText: z.string().optional(),
exception: WebMCPRemoteObjectSchema.optional()
}).meta({ id: "WebMCPToolResponse" });
const LocatorDescriptorSchema = z.strictObject({
pageId: z.string(),
...LocatorSchema.shape
}).meta({ id: "LocatorDescriptor" });
/** One HTTP attempt; the caller owns retries and response interpretation. */
const BrowserbaseRequestParamsSchema = z.strictObject({
operation: z.enum([
"gateway.responses",
"cache.get",
"cache.set"
]),
headers: z.record(z.string(), z.string()).meta({ description: "Application headers. SDKs validate names and values, omit caller-supplied Host, Content-Length, hop-by-hop and forwarding headers, and supply x-bb-api-key and, for gateway.responses, x-bb-session-id." }),
body: z.string().meta({ description: "JSON request body sent as UTF-8 text" }),
remainingTimeoutMs: z.number().nonnegative().optional().meta({ description: "Remaining caller budget in milliseconds, including response-body buffering; omission adds no transport deadline" })
}).meta({ id: "BrowserbaseRequestParams" });
const BrowserbaseHttpResponseSchema = z.strictObject({
type: z.literal("response"),
status: z.int().min(200).max(599),
statusText: z.string(),
headers: z.record(z.string(), z.string()).meta({ description: "Lowercase names with one value per name after SDK HTTP normalization; repeated fields are collapsed. Header bytes use Latin-1 code points. content-encoding and content-length are omitted." }),
body: z.string().meta({ description: "Fully buffered response body decoded as UTF-8 text, with a leading BOM removed and invalid byte sequences replaced" })
}).meta({
id: "BrowserbaseHttpResponse",
description: "Buffered HTTP response with SDK-normalized headers and text. Redirect responses are returned without following them."
});
const BrowserbaseTransportErrorSchema = z.strictObject({
type: z.literal("error"),
reason: z.enum([
"network",
"cancelled",
"timeout",
"unavailable",
"disconnected"
]),
message: z.string()
}).meta({
id: "BrowserbaseTransportError",
description: "Transport failure without an HTTP response; disconnected is synthesized locally when RPC closes"
});
const BrowserbaseRequestResultSchema = z.discriminatedUnion("type", [BrowserbaseHttpResponseSchema, BrowserbaseTransportErrorSchema]).meta({ id: "BrowserbaseRequestResult" });
const BrowserbaseCancelParamsSchema = z.strictObject({
requestId: JSONRPCRequestIdSchema,
reason: z.enum(["cancelled", "timeout"])
}).meta({
id: "BrowserbaseCancelParams",
description: "Cancel a browserbase.request by its JSON-RPC ID on the same connection; ignore completed or unknown IDs"
});
const ImplementationInfoSchema = z.strictObject({
name: z.string().min(1),
version: z.string().min(1)
}).meta({ id: "ImplementationInfo" });
z.strictObject({
protocolVersion: StagehandProtocolVersionSchema,
serverInfo: ImplementationInfoSchema.extend({ name: z.literal("stagehand") })
}).meta({ id: "RuntimeDescriptor" });
const TelemetryConfigSchema = z.strictObject({ traces: z.strictObject({
endpoint: z.url().refine((value) => new URL(value).pathname.endsWith("/v1/traces"), { message: "OTLP trace endpoint must end with /v1/traces" }),
headers: z.record(z.string(), z.string()).default({})
}).meta({ id: "TelemetryTraces" }) }).meta({ id: "TelemetryConfig" });
const StagehandInitParamsSchema = z.strictObject({
protocolVersion: StagehandProtocolVersionSchema,
clientInfo: ImplementationInfoSchema,
browserCdpUrl: z.string().min(1).optional(),
browserbase: z.literal("client").optional(),
browser: BrowserSessionMetadataSchema.optional(),
model: z.union([ModelConfigSchema, ClientModelReferenceSchema]).optional().meta({ description: "Default model configuration; when omitted and a Browserbase Model Gateway session is available, Browserbase selects a model automatically for inference calls" }),
telemetry: TelemetryConfigSchema.optional(),
logLevel: z.enum([
"off",
"error",
"warn",
"info",
"debug"
]).default("info"),
systemPrompt: z.string().optional(),
selfHeal: z.boolean().optional(),
domSettleTimeoutMs: z.number().int().positive().optional(),
cache: CachingSchema.optional().meta({ description: "Server-side caching of act/observe/extract results for this instance: a boolean toggle, or an object with an optional hit-count threshold. Requires client Browserbase transport and a browser sessionId. Can be overridden per request via options.cache." })
}).refine((params) => params.browserbase === void 0 || params.browser !== void 0, {
message: "Client Browserbase transport requires browser metadata",
path: ["browserbase"]
}).meta({
id: "StagehandInitParams",
dependentRequired: { browserbase: ["browser"] }
});
const StagehandActParamsSchema = z.strictObject({
pageId: z.string().min(1),
instruction: z.union([z.string().min(1), ActionSchema]),
options: ActOptionsSchema.optional()
}).meta({ id: "StagehandActParams" });
const StagehandObserveParamsSchema = z.strictObject({
pageId: z.string().min(1),
instruction: z.string().optional(),
options: ObserveOptionsSchema.optional()
}).meta({ id: "StagehandObserveParams" });
const DefaultExtractDataSchema = z.strictObject({ extraction: z.string() }).meta({ id: "DefaultExtractData" });
const DEFAULT_EXTRACT_JSON_SCHEMA = z.json().parse(z.toJSONSchema(DefaultExtractDataSchema));
const StagehandExtractParamsSchema = z.strictObject({
pageId: z.string().min(1),
instruction: z.string().min(1),
schema: z.json().default(DEFAULT_EXTRACT_JSON_SCHEMA),
options: ExtractOptionsSchema.optional()
}).meta({ id: "StagehandExtractParams" });
const ContextNewPageParamsSchema = z.strictObject({ url: pageNavigationUrlSchema.optional() }).meta({ id: "ContextNewPageParams" });
const ContextSetActivePageParamsSchema = z.strictObject({ pageId: z.string() }).meta({ id: "ContextSetActivePageParams" });
const ContextAddInitScriptParamsSchema = z.strictObject({ source: z.string() }).meta({ id: "ContextAddInitScriptParams" });
const ContextSetExtraHTTPHeadersParamsSchema = z.strictObject({ headers: z.record(z.string(), z.string()) }).meta({ id: "ContextSetExtraHTTPHeadersParams" });
const ContextSetDomainPolicyParamsSchema = z.strictObject({ policy: DomainPolicySchema.nullable() }).meta({ id: "ContextSetDomainPolicyParams" });
const ContextCookiesParamsSchema = z.strictObject({ urls: z.union([z.string(), z.array(z.string())]).optional() }).meta({ id: "ContextCookiesParams" });
const ContextAddCookiesParamsSchema = z.strictObject({ cookies: z.array(CookieParamSchema) }).meta({ id: "ContextAddCookiesParams" });
const ContextClearCookiesParamsSchema = z.strictObject({ options: ClearCookieOptionsSchema.optional() }).meta({ id: "ContextClearCookiesParams" });
const ContextClipboardTargetSchema = z.strictObject({ pageId: z.string().optional() }).meta({ id: "ContextClipboardTarget" });
const ContextClipboardReadTextParamsSchema = ContextClipboardTargetSchema;
const ContextClipboardWriteTextParamsSchema = ContextClipboardTargetSchema.extend({ text: z.string() }).meta({ id: "ContextClipboardWriteTextParams" });
const ContextClipboardClearParamsSchema = ContextClipboardTargetSchema;
const ContextClipboardPasteParamsSchema = ContextClipboardTargetSchema.extend({ shortcut: z.enum([
"ControlOrMeta+V",
"Meta+V",
"Control+V"
]).optional() }).meta({ id: "ContextClipboardPasteParams" });
const ContextClipboardCopyParamsSchema = ContextClipboardTargetSchema;
const ContextClipboardCutParamsSchema = ContextClipboardTargetSchema;
const PageGotoParamsSchema = z.strictObject({
pageId: z.string(),
url: pageNavigationUrlSchema,
options: PageNavigationOptionsSchema.optional()
}).meta({ id: "PageGotoParams" });
const PageIdParamsSchema = z.strictObject({ pageId: z.string() }).meta({ id: "PageIdParams" });
const PageOnParamsSchema = PageIdParamsSchema.extend({
subscriptionId: z.string().min(1),
event: PageSubscriptionEventNameSchema
}).meta({ id: "PageOnParams" });
const PageOffParamsSchema = z.strictObject({ subscriptionId: z.string().min(1) }).meta({ id: "PageOffParams" });
const PageWebMCPToolsParamsSchema = z.strictObject({
...PageIdParamsSchema.shape,
options: WebMCPToolsOptionsSchema$1.optional()
}).meta({ id: "PageWebMCPToolsParams" });
const PageWebMCPToolsResultSchema = z.strictObject({ tools: z.array(WebMCPToolDescriptorSchema) }).meta({ id: "PageWebMCPToolsResult" });
const PageWebMCPInvokeToolParamsSchema = z.strictObject({
...PageIdParamsSchema.shape,
frameId: z.string().min(1),
toolName: z.string().min(1),
...WebMCPInvokeOptionsSchema$1.shape
}).meta({ id: "PageWebMCPInvokeToolParams" });
const PageWebMCPInvocationResultParamsSchema = z.strictObject({
...PageIdParamsSchema.shape,
invocationId: z.string().min(1),
options: WebMCPResultOptionsSchema$1.optional()
}).meta({ id: "PageWebMCPInvocationResultParams" });
const PageWebMCPCancelInvocationParamsSchema = z.strictObject({
...PageIdParamsSchema.shape,
invocationId: z.string().min(1)
}).meta({ id: "PageWebMCPCancelInvocationParams" });
const MouseButtonSchema = z.enum([
"left",
"right",
"middle"
]).meta({ id: "MouseButton" });
const PageReloadParamsSchema = PageIdParamsSchema.extend({ options: PageNavigationOptionsSchema.extend({ ignoreCache: z.boolean().optional() }).meta({ id: "PageReloadOptions" }).optional() }).meta({ id: "PageReloadParams" });
const PageGoBackParamsSchema = PageIdParamsSchema.extend({ options: PageNavigationOptionsSchema.optional() }).meta({ id: "PageGoBackParams" });
const PageGoForwardParamsSchema = PageIdParamsSchema.extend({ options: PageNavigationOptionsSchema.optional() }).meta({ id: "PageGoForwardParams" });
const PageClickParamsSchema = PageIdParamsSchema.extend({
x: z.number(),
y: z.number(),
options: z.strictObject({
button: MouseButtonSchema.optional(),
clickCount: z.number().int().positive().optional()
}).meta({ id: "PageClickOptions" }).optional()
}).meta({ id: "PageClickParams" });
const PageHoverParamsSchema = PageIdParamsSchema.extend({
x: z.number(),
y: z.number()
}).meta({ id: "PageHoverParams" });
const PageScrollParamsSchema = PageIdParamsSchema.extend({
x: z.number(),
y: z.number(),
deltaX: z.number(),
deltaY: z.number()
}).meta({ id: "PageScrollParams" });
const PageDragAndDropRoutePointSchema = z.strictObject({
x: z.number(),
y: z.number()
}).meta({ id: "PageDragAndDropRoutePoint" });
const PageDragAndDropParamsSchema = PageIdParamsSchema.extend({
fromX: z.number(),
fromY: z.number(),
toX: z.number(),
toY: z.number(),
options: z.strictObject({
button: MouseButtonSchema.optional(),
steps: z.number().int().positive().optional(),
delay: z.number().nonnegative().optional(),
route: z.array(PageDragAndDropRoutePointSchema).optional()
}).meta({ id: "PageDragAndDropOptions" }).optional()
}).meta({ id: "PageDragAndDropParams" });
const PageTypeParamsSchema = PageIdParamsSchema.extend({
text: z.string(),
options: z.strictObject({
delay: z.number().nonnegative().optional(),
withMistakes: z.boolean().optional()
}).meta({ id: "PageTypeOptions" }).optional()
}).meta({ id: "PageTypeParams" });
const PageKeyPressParamsSchema = PageIdParamsSchema.extend({
key: z.string().min(1),
options: z.strictObject({ delay: z.number().nonnegative().optional() }).meta({ id: "PageKeyPressOptions" }).optional()
}).meta({ id: "PageKeyPressParams" });
const PageEvaluateParamsSchema = PageIdParamsSchema.extend({ expression: z.string() }).meta({ id: "PageEvaluateParams" });
const PageAddInitScriptParamsSchema = PageIdParamsSchema.extend({ source: z.string() }).meta({ id: "PageAddInitScriptParams" });
const PageSetExtraHTTPHeadersParamsSchema = PageIdParamsSchema.extend({ headers: z.record(z.string(), z.string()) }).meta({ id: "PageSetExtraHTTPHeadersParams" });
const PageScreenshotOptionsSchema = z.strictObject({
animations: z.enum(["disabled", "allow"]).optional(),
caret: z.enum(["hide", "initial"]).optional(),
clip: PageScreenshotClipSchema.optional(),
fullPage: z.boolean().optional(),
mask: z.array(LocatorDescriptorSchema).optional(),
maskColor: z.string().optional(),
omitBackground: z.boolean().optional(),
quality: z.number().int().min(0).max(100).optional(),
scale: z.enum(["css", "device"]).optional(),
style: z.string().optional(),
timeout: z.number().nonnegative().optional(),
type: z.enum(["png", "jpeg"]).optional()
}).refine((options) => !(options.fullPage && options.clip), {
message: "fullPage and clip cannot be used together",
path: ["clip"]
}).refine((options) => options.type === "jpeg" || options.quality === void 0, {
message: "quality is only valid when type is \"jpeg\"",
path: ["quality"]
}).meta({ id: "PageScreenshotOptions" });
const PageScreenshotParamsSchema = PageIdParamsSchema.extend({ options: PageScreenshotOptionsSchema.optional() }).meta({ id: "PageScreenshotParams" });
const PagePDFMarginSchema = z.strictObject({
top: z.number().nonnegative().optional(),
bottom: z.number().nonnegative().optional(),
left: z.number().nonnegative().optional(),
right: z.number().nonnegative().optional()
}).meta({ id: "PagePDFMargin" });
const PagePDFOptionsSchema = z.strictObject({
landscape: z.boolean().optional(),
displayHeaderFooter: z.boolean().optional(),
printBackground: z.boolean().optional(),
scale: z.number().min(.1).max(2).optional(),
width: z.number().positive().optional(),
height: z.number().positive().optional(),
margin: PagePDFMarginSchema.optional(),
pageRanges: z.string().optional(),
headerTemplate: z.string().optional(),
footerTemplate: z.string().optional(),
preferCSSPageSize: z.boolean().optional(),
tagged: z.boolean().optional(),
outline: z.boolean().optional(),
timeout: z.number().nonnegative().max(2147473647).optional()
}).meta({ id: "PagePDFOptions" });
const PagePDFParamsSchema = PageIdParamsSchema.extend({ options: PagePDFOptionsSchema.optional() }).meta({ id: "PagePDFParams" });
const PageSnapshotParamsSchema = PageIdParamsSchema.extend({ options: PageSnapshotOptionsSchema.optional() }).meta({ id: "PageSnapshotParams" });
const PageSetViewportSizeParamsSchema = PageIdParamsSchema.extend({
width: z.number().int().positive(),
height: z.number().int().positive(),
options: z.strictObject({ deviceScaleFactor: z.number().positive().optional() }).meta({ id: "PageSetViewportSizeOptions" }).optional()
}).meta({ id: "PageSetViewportSizeParams" });
const PageWaitForLoadStateParamsSchema = PageIdParamsSchema.extend({
state: LoadStateSchema,
timeout: z.number().int().nonnegative().optional()
}).meta({ id: "PageWaitForLoadStateParams" });
const PageWaitForTimeoutParamsSchema = PageIdParamsSchema.extend({ ms: z.number().int().nonnegative() }).meta({ id: "PageWaitForTimeoutParams" });
const PageWaitForFunctionParamsSchema = PageIdParamsSchema.extend({
expression: z.string(),
options: z.strictObject({
polling: z.union([z.literal("raf"), z.number().positive().max(2147483647)]).optional(),
timeout: z.number().int().nonnegative().optional()
}).meta({ id: "PageWaitForFunctionOptions" }).optional()
}).meta({ id: "PageWaitForFunctionParams" });
const PageWaitForSelectorParamsSchema = PageIdParamsSchema.extend({
selector: z.string().min(1),
options: z.strictObject({
state: z.enum([
"attached",
"detached",
"visible",
"hidden"
]).optional(),
timeout: z.number().int().nonnegative().optional(),
pierceShadow: z.boolean().optional()
}).meta({ id: "PageWaitForSelectorOptions" }).optional()
}).meta({ id: "PageWaitForSelectorParams" });
const LocatorOptionsSchema = z.strictObject({ timeout: z.number().nonnegative().optional().describe("Milliseconds for the whole locator call. Zero disables the timeout.") }).meta({ id: "LocatorOptions" });
const LocatorParamsSchema = LocatorDescriptorSchema.extend({ options: LocatorOptionsSchema.optional() }).meta({ id: "LocatorParams" });
const LocatorClickParamsSchema = LocatorParamsSchema.extend({ options: LocatorOptionsSchema.extend({
button: MouseButtonSchema.optional(),
clickCount: z.number().int().positive().optional()
}).meta({ id: "LocatorClickOptions" }).optional() }).meta({ id: "LocatorClickParams" });
const LocatorFillParamsSchema = LocatorParamsSchema.extend({ value: z.string() }).meta({ id: "LocatorFillParams" });
const LocatorScrollToParamsSchema = LocatorParamsSchema.extend({ percent: z.union([z.number(), z.string()]) }).meta({ id: "LocatorScrollToParams" });
const RgbaColorSchema = z.strictObject({
r: z.number(),
g: z.number(),
b: z.number(),
a: z.number().optional()
}).meta({ id: "RgbaColor" });
const LocatorHighlightParamsSchema = LocatorParamsSchema.extend({ options: LocatorOptionsSchema.extend({
durationMs: z.number().int().nonnegative().optional(),
borderColor: RgbaColorSchema.optional(),
contentColor: RgbaColorSchema.optional()
}).meta({ id: "LocatorHighlightOptions" }).optional() }).meta({ id: "LocatorHighlightParams" });
const LocatorSendClickEventParamsSchema = LocatorParamsSchema.extend({ options: LocatorOptionsSchema.extend({
bubbles: z.boolean().optional(),
cancelable: z.boolean().optional(),
composed: z.boolean().optional(),
detail: z.number().optional()
}).meta({ id: "LocatorSendClickEventOptions" }).optional() }).meta({ id: "LocatorSendClickEventParams" });
const LocatorTypeParamsSchema = LocatorParamsSchema.extend({
text: z.string(),
options: LocatorOptionsSchema.extend({ delay: z.number().nonnegative().optional() }).meta({ id: "LocatorTypeOptions" }).optional()
}).meta({ id: "LocatorTypeParams" });
const LocatorSelectOptionParamsSchema = LocatorParamsSchema.extend({ values: z.union([z.string(), z.array(z.string())]) }).meta({ id: "LocatorSelectOptionParams" });
const MAX_INPUT_FILE_BYTES$1 = 50 * 1024 * 1024;
const MAX_INPUT_FILE_BASE64_LENGTH = Math.ceil(MAX_INPUT_FILE_BYTES$1 / 3) * 4;
function decodedBase64ByteLength(data) {
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
return data.length / 4 * 3 - padding;
}
const InputFilePayloadSchema = z.strictObject({
name: z.string().min(1),
mimeType: z.string().min(1).optional(),
data: z.base64().max(MAX_INPUT_FILE_BASE64_LENGTH).refine((value) => decodedBase64ByteLength(value) <= MAX_INPUT_FILE_BYTES$1, { message: "File data must decode to 50 MiB or less" }).meta({ format: "byte" }),
lastModified: z.number().int().nonnegative().optional()
}).meta({ id: "InputFilePayload" });
const LocatorSetInputFilesParamsSchema = LocatorParamsSchema.extend({ files: z.array(InputFilePayloadSchema) }).meta({ id: "LocatorSetInputFilesParams" });
const StagehandInitResultSchema = z.strictObject({
initialized: z.literal(true),
pages: z.array(PageRefSchema)
}).meta({ id: "StagehandInitResult" });
const StagehandCloseResultSchema = z.strictObject({ closed: z.literal(true) }).meta({ id: "StagehandCloseResult" });
const ContextPagesResultSchema = z.array(PageRefSchema).meta({ id: "ContextPagesResult" });
const ContextActivePageResultSchema = PageRefSchema.nullable().meta({ id: "ContextActivePageResult" });
const ContextGetDomainPolicyResultSchema = DomainPolicySchema.nullable().meta({ id: "ContextGetDomainPolicyResult" });
const ContextCookiesResultSchema = z.array(CookieSchema).meta({ id: "ContextCookiesResult" });
const ContextClipboardReadTextResultSchema = z.string().meta({ id: "ContextClipboardReadTextResult" });
const PageUrlResultSchema = z.string().meta({ id: "PageUrlResult" });
const PageTitleResultSchema = z.string().meta({ id: "PageTitleResult" });
const PageCloseResultSchema = z.strictObject({ closed: z.literal(true) }).meta({ id: "PageCloseResult" });
const PageEvaluateResultSchema = z.strictObject({ value: z.json() }).meta({ id: "PageEvaluateResult" });
const PageScreenshotResultSchema = z.strictObject({ data: z.base64().meta({ format: "byte" }) }).meta({ id: "PageScreenshotResult" });
const PagePDFResultSchema = z.strictObject({ data: z.base64().meta({ format: "byte" }) }).meta({ id: "PagePDFResult" });
const PageWaitForSelectorResultSchema = z.strictObject({ matched: z.boolean() }).meta({ id: "PageWaitForSelectorResult" });
const PageWaitForFunctionResultSchema = z.strictObject({ value: z.json() }).meta({ id: "PageWaitForFunctionResult" });
const LocatorClickResultSchema = z.strictObject({ clicked: z.literal(true) }).meta({ id: "LocatorClickResult" });
const LocatorFillResultSchema = z.strictObject({ filled: z.literal(true) }).meta({ id: "LocatorFillResult" });
const LocatorHoverResultSchema = z.strictObject({ hovered: z.literal(true) }).meta({ id: "LocatorHoverResult" });
const LocatorCountResultSchema = z.number().int().nonnegative().meta({ id: "LocatorCountResult" });
const LocatorIsCheckedResultSchema = z.boolean().meta({ id: "LocatorIsCheckedResult" });
const LocatorInputValueResultSchema = z.string().meta({ id: "LocatorInputValueResult" });
const LocatorIsVisibleResultSchema = z.boolean().meta({ id: "LocatorIsVisibleResult" });
const LocatorInnerTextResultSchema = z.string().meta({ id: "LocatorInnerTextResult" });
const LocatorInnerHtmlResultSchema = z.string().meta({ id: "LocatorInnerHtmlResult" });
const LocatorTextContentResultSchema = z.string().meta({ id: "LocatorTextContentResult" });
const LocatorScrollToResultSchema = z.strictObject({ scrolled: z.literal(true) }).meta({ id: "LocatorScrollToResult" });
const LocatorCentroidResultSchema = z.strictObject({
x: z.number(),
y: z.number()
}).meta({ id: "LocatorCentroidResult" });
const LocatorHighlightResultSchema = z.strictObject({ highlighted: z.literal(true) }).meta({ id: "LocatorHighlightResult" });
const LocatorSendClickEventResultSchema = z.strictObject({ clicked: z.literal(true) }).meta({ id: "LocatorSendClickEventResult" });
const LocatorTypeResultSchema = z.strictObject({ typed: z.literal(true) }).meta({ id: "LocatorTypeResult" });
const LocatorSelectOptionResultSchema = z.array(z.string()).meta({ id: "LocatorSelectOptionResult" });
const LocatorSetInputFilesResultSchema = z.strictObject({ set: z.literal(true) }).meta({ id: "LocatorSetInputFilesResult" });
const StagehandLogLevelSchema = z.enum([
"debug",
"info",
"warn",
"error"
]).meta({ id: "StagehandLogLevel" });
const StagehandLogDataSchema = z.record(z.string(), z.json()).meta({ id: "StagehandLogData" });
const StagehandLogSchema = z.strictObject({
level: StagehandLogLevelSchema,
message: z.string().min(1),
data: StagehandLogDataSchema
}).meta({ id: "StagehandLog" });
//#endregion
//#region ../protocol/schema-registry.ts
const STAGEHAND_SEND_TO_HOST_BINDING = "__stagehandSendToHost";
const StagehandSendToHostBindingSchema = z.literal(STAGEHAND_SEND_TO_HOST_BINDING).meta({ id: "StagehandSendToHostBinding" });
const StagehandMethods = {
stagehandInit: {
name: "stagehand.init",
params: StagehandInitParamsSchema,
result: StagehandInitResultSchema
},
stagehandClose: {
name: "stagehand.close",
params: EmptyParamsSchema,
result: StagehandCloseResultSchema
},
stagehandAct: {
name: "stagehand.act",
params: StagehandActParamsSchema,
result: ActResultSchema,
resultWire: { transformKeys: ["data"] }
},
stagehandObserve: {
name: "stagehand.observe",
params: StagehandObserveParamsSchema,
result: ObserveResultSchema,
resultWire: { transformKeys: ["data"] }
},
stagehandExtract: {
name: "stagehand.extract",
params: StagehandExtractParamsSchema,
result: ExtractResultSchema,
paramsWire: { opaqueKeys: ["schema"] },
resultWire: { opaqueKeys: ["data"] }
},
stagehandMetrics: {
name: "stagehand.metrics",
params: EmptyParamsSchema,
result: StagehandMetricsSchema
},
stagehandCallbackBatch: {
name: "stagehand.callback_batch",
params: CallbackBatchParamsSchema,
result: CallbackBatchResultSchema,
paramsWire: { opaqueKeys: ["input"] },
resultWire: { opaqueKeys: ["value"] }
},
browserbaseRequest: {
name: "browserbase.request",
params: BrowserbaseRequestParamsSchema,
result: BrowserbaseRequestResultSchema
},
llmGenerate: {
name: "llm.generate",
params: LLMGenerateParamsSchema,
result: LLMGenerateResultSchema,
paramsWire: { opaqueKeys: [
"inputSchema",
"outputSchema",
"input",
"structuredContent",
"schema"
] },
resultWire: { opaqueKeys: ["structuredContent"] }
},
contextPages: {
name: "context.pages",
params: EmptyParamsSchema,
result: ContextPagesResultSchema
},
contextNewPage: {
name: "context.new_page",
params: ContextNewPageParamsSchema,
result: PageRefSchema
},
contextActivePage: {
name: "context.active_page",
params: EmptyParamsSchema,
result: ContextActivePageResultSchema
},
contextSetActivePage: {
name: "context.set_active_page",
params: ContextSetActivePageParamsSchema,
result: ContextVoidResultSchema
},
contextAddInitScript: {
name: "context.add_init_script",
params: ContextAddInitScriptParamsSchema,
result: ContextVoidResultSchema
},
contextSetExtraHTTPHeaders: {
name: "context.set_extra_http_headers",
params: ContextSetExtraHTTPHeadersParamsSchema,
result: ContextVoidResultSchema,
paramsWire: { opaqueKeys: ["headers"] }
},
contextGetDomainPolicy: {
name: "context.get_domain_policy",
params: EmptyParamsSchema,
result: ContextGetDomainPolicyResultSchema
},
contextSetDomainPolicy: {
name: "context.set_domain_policy",
params: ContextSetDomainPolicyParamsSchema,
result: ContextVoidResultSchema
},
contextCookies: {
name: "context.cookies",
params: ContextCookiesParamsSchema,
result: ContextCookiesResultSchema
},
contextAddCookies: {
name: "context.add_cookies",
params: ContextAddCookiesParamsSchema,
result: ContextVoidResultSchema
},
contextClearCookies: {
name: "context.clear_cookies",
params: ContextClearCookiesParamsSchema,
result: ContextVoidResultSchema
},
contextClipboardReadText: {
name: "context.clipboard_read_text",
params: ContextClipboardReadTextParamsSchema,
result: ContextClipboardReadTextResultSchema
},
contextClipboardWriteText: {
name: "context.clipboard_write_text",
params: ContextClipboardWriteTextParamsSchema,
result: ContextVoidResultSchema
},
contextClipboardClear: {
name: "context.clipboard_clear",
params: ContextClipboardClearParamsSchema,
result: ContextVoidResultSchema
},
contextClipboardPaste: {
name: "context.clipboard_paste",
params: ContextClipboardPasteParamsSchema,
result: ContextVoidResultSchema
},
contextClipboardCopy: {
name: "context.clipboard_copy",
params: ContextClipboardCopyParamsSchema,
result: ContextVoidResultSchema
},
contextClipboardCut: {
name: "context.clipboard_cut",
params: ContextClipboardCutParamsSchema,
result: ContextVoidResultSchema
},
pageGoto: {
name: "page.goto",
params: PageGotoParamsSchema,
result: PageNavigationResultSchema,
resultWire: { opaqueKeys: ["headers"] }
},
pageUrl: {
name: "page.url",
params: PageIdParamsSchema,
result: PageUrlResultSchema
},
pageTitle: {
name: "page.title",
params: PageIdParamsSchema,
result: PageTitleResultSchema
},
pageClose: {
name: "page.close",
params: PageIdParamsSchema,
result: PageCloseResultSchema
},
pageReload: {
name: "page.reload",
params: PageReloadParamsSchema,
result: PageNavigationResultSchema,
resultWire: { opaqueKeys: ["headers"] }
},
pageGoBack: {
name: "page.go_back",
params: PageGoBackParamsSchema,
result: PageNavigationResultSchema,
resultWire: { opaqueKeys: ["headers"] }
},
pageGoForward: {
name: "page.go_forward",
params: PageGoForwardParamsSchema,
result: PageNavigationResultSchema,
resultWire: { opaqueKeys: ["headers"] }
},
pageClick: {
name: "page.click",
params: PageClickParamsSchema,
result: PageVoidResultSchema
},
pageHover: {
name: "page.hover",
params: PageHoverParamsSchema,
result: PageVoidResultSchema
},
pageScroll: {
name: "page.scroll",
params: PageScrollParamsSchema,
result: PageVoidResultSchema
},
pageDragAndDrop: {
name: "page.drag_and_drop",
params: PageDragAndDropParamsSchema,
result: PageVoidResultSchema
},
pageType: {
name: "page.type",
params: PageTypeParamsSchema,
result: PageVoidResultSchema
},
pageKeyPress: {
name: "page.key_press",
params: PageKeyPressParamsSchema,
result: PageVoidResultSchema
},
pageEvaluate: {
name: "page.evaluate",
params: PageEvaluateParamsSchema,
result: PageEvaluateResultSchema,
resultWire: { opaqueKeys: ["value"] }
},
pageAddInitScript: {
name: "page.add_init_script",
params: PageAddInitScriptParamsSchema,
result: PageVoidResultSchema
},
pageOn: {
name: "page.on",
params: PageOnParamsSchema,
result: PageVoidResultSchema
},
pageOff: {
name: "page.off",
params: PageOffParamsSchema,
result: PageVoidResultSchema
},
pageSetExtraHTTPHeaders: {
name: "page.set_extra_http_headers",
params: PageSetExtraHTTPHeadersParamsSchema,
result: PageVoidResultSchema,
paramsWire: { opaqueKeys: ["headers"] }
},
pageScreenshot: {
name: "page.screenshot",
params: PageScreenshotParamsSchema,
result: PageScreenshotResultSchema
},
pagePDF: {
name: "page.pdf",
params: PagePDFParamsSchema,
result: PagePDFResultSchema
},
pageSnapshot: {
name: "page.snapshot",
params: PageSnapshotParamsSchema,
result: SnapshotResultSchema,
resultWire: { opaqueKeys: ["xpathMap", "urlMap"] }
},
pageSetViewportSize: {
name: "page.set_viewport_size",
params: PageSetViewportSizeParamsSchema,
result: PageVoidResultSchema
},
pageWaitForLoadState: {
name: "page.wait_for_load_state",
params: PageWaitForLoadStateParamsSchema,
result: PageVoidResultSchema
},
pageWaitForTimeout: {
name: "page.wait_for_timeout",
params: PageWaitForTimeoutParamsSchema,
result: PageVoidResultSchema
},
pageWaitForSelector: {
name: "page.wait_for_selector",
params: PageWaitForSelectorParamsSchema,
result: PageWaitForSelectorResultSchema
},
pageWaitForFunction: {
name: "page.wait_for_function",
params: PageWaitForFunctionParamsSchema,
result: PageWaitForFunctionResultSchema,
resultWire: { opaqueKeys: ["value"] }
},
pageWebMCPTools: {
name: "page.webmcp_tools",
params: PageWebMCPToolsParamsSchema,
result: PageWebMCPToolsResultSchema,
resultWire: { opaqueKeys: ["inputSchema"] }
},
pageWebMCPInvokeTool: {
name: "page.webmcp_invoke_tool",
params: PageWebMCPInvokeToolParamsSchema,
result: WebMCPInvocationDescriptorSchema,
paramsWire: { opaqueKeys: ["input"] },
resultWire: { opaqueKeys: ["input"] }
},
pageWebMCPInvocationResult: {
name: "page.webmcp_invocation_result",
params: PageWebMCPInvocationResultParamsSchema,
result: WebMCPToolResponseSchema,
resultWire: { opaqueKeys: ["output", "exception"] }
},
pageWebMCPCancelInvocation: {
name: "page.webmcp_cancel_invocation",
params: PageWebMCPCancelInvocationParamsSchema,
result: PageVoidResultSchema
},
locatorClick: {
name: "locator.click",
params: LocatorClickParamsSchema,
result: LocatorClickResultSchema
},
locatorFill: {
name: "locator.fill",
params: LocatorFillParamsSchema,
result: LocatorFillResultSchema
},
locatorHover: {
name: "locator.hover",
params: LocatorParamsSchema,
result: LocatorHoverResultSchema
},
locatorCount: {
name: "locator.count",
params: LocatorParamsSchema,
result: LocatorCountResultSchema
},
locatorIsChecked: {
name: "locator.is_checked",
params: LocatorParamsSchema,
result: LocatorIsCheckedResultSchema
},
locatorInputValue: {
name: "locator.input_value",
params: LocatorParamsSchema,
result: LocatorInputValueResultSchema
},
locatorIsVisible: {
name: "locator.is_visible",
params: LocatorParamsSchema,
result: LocatorIsVisibleResultSchema
},
locatorInnerText: {
name: "locator.inner_text",
params: LocatorParamsSchema,
result: LocatorInnerTextResultSchema
},
locatorInnerHtml: {
name: "locator.inner_html",
params: LocatorParamsSchema,
result: LocatorInnerHtmlResultSchema
},
locatorTextContent: {
name: "locator.text_content",
params: LocatorParamsSchema,
result: LocatorTextContentResultSchema
},
locatorScrollTo: {
name: "locator.scroll_to",
params: LocatorScrollToParamsSchema,
result: LocatorScrollToResultSchema
},
locatorCentroid: {
name: "locator.centroid",
params: LocatorParamsSchema,
result: LocatorCentroidResultSchema
},
locatorHighlight: {
name: "locator.highlight",
params: LocatorHighlightParamsSchema,
result: LocatorHighlightResultSchema
},
locatorSendClickEvent: {
name: "locator.send_click_event",
params: LocatorSendClickEventParamsSchema,
result: LocatorSendClickEventResultSchema
},
locatorType: {
name: "locator.type",
params: LocatorTypeParamsSchema,
result: LocatorTypeResultSchema
},
locatorSelectOption: {
name: "locator.select_option",
params: LocatorSelectOptionParamsSchema,
result: LocatorSelectOptionResultSchema
},
locatorSetInputFiles: {
name: "locator.set_input_files",
params: LocatorSetInputFilesParamsSchema,
result: LocatorSetInputFilesResultSchema
},
responseBody: {
name: "response.body",
params: ResponseIdParamsSchema,
result: ResponseBodyResultSchema
},
responseAllHeaders: {
name: "response.all_headers",
params: ResponseIdParamsSchema,
result: ResponseAllHeadersResultSchema,
resultWire: { opaqueKeys: ["headers"] }
},
responseHeadersArray: {
name: "response.headers_array",
params: ResponseIdParamsSchema,
result: ResponseHeadersArrayResultSchema
},
responseSecurityDetails: {
name: "response.security_details",
params: ResponseIdParamsSchema,
result: ResponseSecurityDetailsResultSchema
},
responseServerAddr: {
name: "response.server_addr",
params: ResponseIdParamsSchema,
result: ResponseServerAddrResultSchema
},
responseFinished: {
name: "response.finished",
params: ResponseIdParamsSchema,
result: ResponseFinishedResultSchema
}
};
z.enum(Object.values(StagehandMethods).map((method) => method.name)).meta({ id: "StagehandMethod" });
const stagehandRpcRequestSchemas = Object.values(StagehandMethods).map((method) => JSONRPCRequestSchema.extend({
method: z.literal(method.name),
params: wireSchema(method.params, "paramsWire" in method ? method.paramsWire : void 0)
}));
z.union(stagehandRpcRequestSchemas).meta({ id: "StagehandRpcRuntimeRequest" });
const StagehandNotifications = {
browserbaseCancel: {
name: "browserbase.cancel",
params: BrowserbaseCancelParamsSchema,
paramsWire: void 0
},
log: {
name: "stagehand.log",
params: StagehandLogSchema,
paramsWire: void 0
},
pageCDPEvent: {
name: "page.cdp_event",
params: PageCDPEventNotificationSchema,
paramsWire: { opaqueKeys: ["params"] }
},
pageEvent: {
name: "page.event",
params: PageEventNotificationSchema,
paramsWire: { opaqueKeys: ["inputSchema"] }
}
};
const stagehandRpcNotificationSchemas = Object.values(StagehandNotifications).map((notification) => JSONRPCNotificationSchema.extend({
method: z.literal(notification.name),
params: wireSchema(notification.params, notification.paramsWire)
}));
const StagehandRpcNotificationSchema = z.union(stagehandRpcNotificationSchemas).meta({ id: "StagehandRpcRuntimeNotification" });
new Map(Object.values(StagehandMethods).map((method) => [method.name, method]));
//#endregion
//#region src/browserClipboard.ts
var BrowserClipboard = class {
constructor(rpcClient) {
this.rpcClient = rpcClient;
}
async readText(options) {
return await this.rpcClient.send(StagehandMethods.contextClipboardReadText, clipboardTarget(options));
}
async writeText(text, options) {
await this.rpcClient.send(StagehandMethods.contextClipboardWriteText, {
text,
...clipboardTarget(options)
});
}
async clear(options) {
await this.rpcClient.send(StagehandMethods.contextClipboardClear, clipboardTarget(options));
}
async paste(options) {
await this.rpcClient.send(StagehandMethods.contextClipboardPaste, {
...clipboardTarget(options),
...options?.shortcut === void 0 ? {} : { shortcut: options.shortcut }
});
}
async copy(options) {
await this.rpcClient.send(StagehandMethods.contextClipboardCopy, clipboardTarget(options));
}
async cut(options) {
await this.rpcClient.send(StagehandMethods.contextClipboardCut, clipboardTarget(options));
}
};
function clipboardTarget(options) {
return options?.page ? { pageId: options.page.pageId } : {};
}
//#endregion
//#region src/base64.ts
const BASE64_PATTERN = /^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$/;
function decodeBase64(value, source) {
if (value.length % 4 !== 0 || !BASE64_PATTERN.test(value)) throw new Error(`${source} returned invalid base64`);
let binary;
try {
binary = globalThis.atob(value);
} catch {
throw new Error(`${source} returned invalid base64`);
}
if (globalThis.btoa(binary) !== value) throw new Error(`${source} returned invalid base64`);
const bytes = new Uint8Array(binary.length);
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
return bytes;
}
//#endregion
//#region src/fileUpload.ts
const MAX_INPUT_FILE_BYTES = 50 * 1024 * 1024;
async function normalizeFileInput(files) {
const entries = Array.isArray(files) ? files : [files];
const payloads = [];
for (const entry of entries) payloads.push(await normalizeFile(entry));
return payloads;
}
async function normalizeFile(file) {
if (typeof file === "string") {
const [fs, path] = await Promise.all([import(
/* @vite-ignore */
"node:fs/promises"
), import(
/* @vite-ignore */
"node:path"
)]).catch(() => {
throw new TypeError("setInputFiles(): file paths are only supported in Node.js; use a file payload instead");
});
const absolutePath = path.resolve(file);
const handle = await fs.open(absolutePath, "r").catch(() => {
throw new TypeError("setInputFiles(): could not read file");
});
try {
const fileStat = await handle.stat().catch(() => {
throw new TypeError("setInputFiles(): could not read file");
});
if (!fileStat.isFile()) throw new TypeError("setInputFiles(): expected a file");
if (fileStat.size > MAX_INPUT_FILE_BYTES) throw new RangeError(`setInputFiles(): file is larger than the 50 MiB upload limit`);
const chunks = [];
let bytesRead = 0;
try {
for await (const chunk of handle.createReadStream({
autoClose: false,
end: MAX_INPUT_FILE_BYTES,
start: 0
})) {
const bytes = Buffer.from(chunk);
chunks.push(bytes);
bytesRead += bytes.byteLength;
}
} catch {
throw new TypeError("setInputFiles(): could not read file");
}
if (bytesRead > MAX_INPUT_FILE_BYTES) throw new RangeError(`setInputFiles(): file is larger than the 50 MiB upload limit`);
const lastModified = Math.trunc(fileStat.mtimeMs);
return {
name: path.basename(absolutePath),
data: Buffer.concat(chunks, bytesRead).toString("base64"),
...lastModified < 0 ? {} : { lastModified }
};
} finally {
await handle.close().catch(() => {});
}
}
if (!file.name) throw new TypeError("setInputFiles(): file payload name cannot be empty");
if (typeof file.buffer === "string") {
const nodeBuffer = globalThis.Buffer;
if (nodeBuffer ? nodeBuffer.byteLength(file.buffer, "utf8") > MAX_INPUT_FILE_BYTES : utf8ByteLengthExceeds(file.buffer, MAX_INPUT_FILE_BYTES)) throw new RangeError(`setInputFiles(): file is larger than the 50 MiB upload limit`);
}
const bytes = typeof file.buffer === "string" ? new TextEncoder().encode(file.buffer) : file.buffer instanceof ArrayBuffer ? new Uint8Array(file.buffer) : file.buffer;
if (bytes.byteLength > MAX_INPUT_FILE_BYTES) throw new RangeError(`setInputFiles(): file is larger than the 50 MiB upload limit`);
if (file.lastModified !== void 0 && (!Number.isInteger(file.lastModified) || file.lastModified < 0)) throw new RangeError("setInputFiles(): lastModified must be a non-negative integer");
return {
name: file.name,
...file.mimeType ? { mimeType: file.mimeType } : {},
data: encodeBase64(bytes),
...file.lastModified === void 0 ? {} : { lastModified: file.lastModified }
};
}
function utf8ByteLengthExceeds(value, limit) {
let byteLength = 0;
for (let index = 0; index < value.length; index++) {
const codeUnit = value.charCodeAt(index);
if (codeUnit <= 127) byteLength += 1;
else if (codeUnit <= 2047) byteLength += 2;
else if (codeUnit >= 55296 && codeUnit <= 56319 && index + 1 < value.length && value.charCodeAt(index + 1) >= 56320 && value.charCodeAt(index + 1) <= 57343) {
byteLength += 4;
index += 1;
} else byteLength += 3;
if (byteLength > limit) return true;
}
return false;
}
function encodeBase64(bytes) {
const nodeBuffer = globalThis.Buffer;
if (nodeBuffer) return nodeBuffer.from(bytes).toString("base64");
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary);
}
//#endregion
//#region src/locator.ts
var Locator = class Locator {
constructor(rpcClient, descriptor) {
this.rpcClient = rpcClient;
this.descriptor = descriptor;
}
async click(options) {
await this.rpcClient.send(StagehandMethods.locatorClick, {
...this.descriptor,
...options ? { options } : {}
});
}
async hover(options) {
await this.rpcClient.send(StagehandMethods.locatorHover, {
...this.descriptor,
...options ? { options } : {}
});
}
async fill(value, options) {
await this.rpcClient.send(StagehandMethods.locatorFill, {
...this.descriptor,
value,
...options ? { options } : {}
});
}
async count(options) {
return await this.rpcClient.send(StagehandMethods.locatorCount, {
...this.descriptor,
...options ? { options } : {}
});
}
async isChecked(options) {
return await this.rpcClient.send(StagehandMethods.locatorIsChecked, {
...this.descriptor,
...options ? { options } : {}
});
}
async inputValue(options) {
return await this.rpcClient.send(StagehandMethods.locatorInputValue, {
...this.descriptor,
...options ? { options } : {}
});
}
async isVisible(options) {
return await this.rpcClient.send(StagehandMethods.locatorIsVisible, {
...this.descriptor,
...options ? { options } : {}
});
}
async innerText(options) {
return await this.rpcClient.send(StagehandMethods.locatorInnerText, {
...this.descriptor,
...options ? { options } : {}
});
}
async innerHtml(options) {
return await this.rpcClient.send(StagehandMethods.locatorInnerHtml, {
...this.descriptor,
...options ? { options } : {}
});
}
async textContent(options) {
return await this.rpcClient.send(StagehandMethods.locatorTextContent, {
...this.descriptor,
...options ? { options } : {}
});
}
async scrollTo(percent, options) {
await this.rpcClient.send(StagehandMethods.locatorScrollTo, {
...this.descriptor,
percent,
...options ? { options } : {}
});
}
async centroid(options) {
return this.rpcClient.send(StagehandMethods.locatorCentroid, {
...this.descriptor,
...options ? { options } : {}
});
}
async highlight(options) {
await this.rpcClient.send(StagehandMethods.locatorHighlight, {
...this.descriptor,
...options ? { options } : {}
});
}
async sendClickEvent(options) {
await this.rpcClient.send(StagehandMethods.locatorSendClickEvent, {
...this.descriptor,
...options ? { options } : {}
});
}
async type(text, options) {
await this.rpcClient.send(StagehandMethods.locatorType, {
...this.descriptor,
text,
...options ? { options } : {}
});
}
async selectOption(values, options) {
return await this.rpcClient.send(StagehandMethods.locatorSelectOption, {
...this.descriptor,
values,
...options ? { options } : {}
});
}
async setInputFiles(files, options) {
await this.rpcClient.send(StagehandMethods.locatorSetInputFiles, {
...this.descriptor,
files: await normalizeFileInput(files),
...options ? { options } : {}
});
}
first() {
return this.nth(0);
}
nth(index) {
const parsedDescriptor = LocatorDescriptorSchema.safeParse({
...this.descriptor,
nth: index
});
if (!parsedDescriptor.success) throw parsedDescriptor.error;
return new Locator(this.rpcClient, parsedDescriptor.data);
}
};
//#endregion
//#region src/pageScripts.ts
function normalizeEvaluationExpression(expression, arg, caller = "page.evaluate") {
if (typeof expression === "string") return expression;
return `(${expression.toString()})(${serializeArgument(arg, caller)})`;
}
function normalizeFunctionWaitExpression(expression, arg, caller = "page.waitForFunction") {
if (typeof expression === "function") return `() => (${expression.toString()})(${parsedArgument(arg, caller)})`;
if (arg === void 0) return expression;
return `((value) => typeof value === "function" ? () => value(${parsedArgument(arg, caller)}) : value)((${expression}))`;
}
function parsedArgument(arg, caller) {
if (arg === void 0) return "undefined";
return `JSON.parse(${JSON.stringify(serializeArgument(arg, caller))})`;
}
async function normalizeInitScriptSource(script, arg, caller = "page.addInitScript") {
if (typeof script === "function") return `(${script.toString()})(${serializeArgument(arg, caller)})`;
if (arg !== void 0) throw new TypeError(`${caller}: 'arg' is only supported when passing a function.`);
if (typeof script === "string") return script;
if (!script || typeof script !== "object") throw new TypeError(`${caller}: provide a string, function, or an object with exactly one of path or content.`);
const hasContent = typeof script.content === "string";
if (hasContent === (typeof script.path === "string" && script.path.trim().length > 0)) throw new TypeError(`${caller}: provide an object with exactly one of path or content.`);
if (hasContent) return script.content;
const filePath = script.path;
const { readFile } = await import(
/* @vite-ignore */
"node:fs/promises"
);
return `${await readFile(filePath, "utf8")}\n//# sourceURL=${filePath.replace(/\n/g, "")}`;
}
function serializeArgument(arg, caller) {
if (arg === void 0) return "undefined";
if (!z.json().safeParse(arg).success) throw new TypeError(`${caller}: 'arg' must be JSON-serializable.`);
const serialized = JSON.stringify(arg);
if (serialized === void 0) throw new TypeError(`${caller}: 'arg' must be JSON-serializable.`);
return serialized;
}
//#endregion
//#region src/response.ts
var Response = class {
#rpcClient;
#descriptor;
constructor(rpcClient, descriptor) {
this.#rpcClient = rpcClient;
this.#descriptor = {
...descriptor,
headers: { ...descriptor.headers }
};
}
url() {
return this.#descriptor.url;
}
status() {
return this.#descriptor.status;
}
statusText() {
return this.#descriptor.statusText;
}
ok() {
return this.status() >= 200 && this.status() <= 299;
}
headers() {
return { ...this.#descriptor.headers };
}
async allHeaders() {
return { ...(await this.#rpcClient.send(StagehandMethods.responseAllHeaders, { responseId: this.#descriptor.responseId })).headers };
}
async headerValue(name) {
const values = await this.headerValues(name);
return values.length === 0 ? null : values.join(", ");
}
async headerValues(name) {
const normalizedName = name.toLowerCase();
return (await this.headersArray()).filter((header) => header.name.toLowerCase() === normalizedName).map((header) => header.value);
}
async headersArray() {
return (await this.#rpcClient.send(StagehandMethods.responseHeadersArray, { responseId: this.#descriptor.responseId })).headers.map((header) => ({ ...header }));
}
fromServiceWorker() {
return this.#descriptor.fromServiceWorker;
}
async securityDetails() {
const result = await this.#rpcClient.send(StagehandMethods.responseSecurityDetails, { responseId: this.#descriptor.responseId });
return result.value === null ? null : { ...result.value };
}
async serverAddr() {
const result = await this.#rpcClient.send(StagehandMethods.responseServerAddr, { responseId: this.#descriptor.responseId });
return result.value === null ? null : { ...result.value };
}
async body() {
return decodeBase64((await this.#rpcClient.send(StagehandMethods.responseBody, { responseId: this.#descriptor.responseId })).body, "response.body");
}
async text() {
return new TextDecoder().decode(await this.body());
}
async json() {
return JSON.parse(await this.text());
}
async finished() {
const result = await this.#rpcClient.send(StagehandMethods.responseFinished, { responseId: this.#descriptor.responseId });
return result.error === null ? null : new Error(result.error.message);
}
};
//#endregion
//#region src/webmcp.ts
var WebMCPTool = class {
#rpcClient;
#pageId;
constructor(rpcClient, pageId, descriptor) {
this.#rpcClient = rpcClient;
this.#pageId = pageId;
this.name = descriptor.name;
this.description = descriptor.description;
this.inputSchema = descriptor.inputSchema;
this.annotations = descriptor.annotations;
this.frameId = descriptor.frameId;
this.backendNodeId = descriptor.backendNodeId;
}
async invoke(options) {
const descriptor = await this.#rpcClient.send(StagehandMethods.pageWebMCPInvokeTool, {
pageId: this.#pageId,
frameId: this.frameId,
toolName: this.name,
input: options?.input ?? {}
});
return new WebMCPInvocation(this.#rpcClient, this.#pageId, descriptor);
}
};
var WebMCPInvocation = class {
#rpcClient;
#pageId;
#terminalResult;
constructor(rpcClient, pageId, descriptor) {
this.#rpcClient = rpcClient;
this.#pageId = pageId;
this.invocationId = descriptor.invocationId;
this.toolName = descriptor.toolName;
this.frameId = descriptor.frameId;
this.input = descriptor.input;
}
async result(options) {
if (this.#terminalResult !== void 0) return this.#terminalResult;
const result = await this.#rpcClient.send(StagehandMethods.pageWebMCPInvocationResult, {
pageId: this.#pageId,
invocationId: this.invocationId,
...options ? { options } : {}
});
this.#terminalResult ??= result;
return this.#terminalResult;
}
async cancel() {
await this.#rpcClient.send(StagehandMethods.pageWebMCPCancelInvocation, {
pageId: this.#pageId,
invocationId: this.invocationId
});
}
};
//#endregion
//#region src/page.ts
var CDPSubscription = class {
constructor(rpcClient, subscriptionId, removeLocalListener, onDisposed) {
this.rpcClient = rpcClient;
this.subscriptionId = subscriptionId;
this.removeLocalListener = removeLocalListener;
this.onDisposed = onDisposed;
}
unsubscribe() {
this.unsubscribePromise ??= this.unsubscribeOnce().catch((error) => {
this.unsubscribePromise = void 0;
throw error;
});
return this.unsubscribePromise;
}
async unsubscribeOnce() {
this.removeLocalListener();
this.onDisposed();
await this.rpcClient.send(StagehandMethods.pageOff, { subscriptionId: this.subscriptionId });
}
};
var Page = class {
constructor(rpcClient, ref) {
this.rpcClient = rpcClient;
this.eventSubscriptions = /* @__PURE__ */ new Set();
this.currentRef = ref;
}
get pageId() {
return this.currentRef.pageId;
}
get ref() {
return this.currentRef;
}
async goto(url, options) {
const result = await this.rpcClient.send(StagehandMethods.pageGoto, {
pageId: this.pageId,
url,
...options ? { options } : {}
});
this.currentRef = result.page;
return result.response === null ? null : new Response(this.rpcClient, result.response);
}
async reload(options) {
const result = await this.rpcClient.send(StagehandMethods.pageReload, {
pageId: this.pageId,
...options ? { options } : {}
});
this.currentRef = result.page;
return result.response === null ? null : new Response(this.rpcClient, result.response);
}
async goBack(options) {
const result = await this.rpcClient.send(StagehandMethods.pageGoBack, {
pageId: this.pageId,
...options ? { options } : {}
});
this.currentRef = result.page;
return result.response === null ? null : new Response(this.rpcClient, result.response);
}
async goForward(options) {
const result = await this.rpcClient.send(StagehandMethods.pageGoForward, {
pageId: this.pageId,
...options ? { options } : {}
});
this.currentRef = result.page;
return result.response === null ? null : new Response(this.rpcClient, result.response);
}
async click(x, y, options) {
await this.rpcClient.send(StagehandMethods.pageClick, {
pageId: this.pageId,
x,
y,
...options ? { options } : {}
});
}
async hover(x, y) {
await this.rpcClient.send(StagehandMethods.pageHover, {
pageId: this.pageId,
x,
y
});
}
async scroll(x, y, deltaX, deltaY) {
await this.rpcClient.send(StagehandMethods.pageScroll, {
pageId: this.pageId,
x,
y,
deltaX,
deltaY
});
}
async dragAndDrop(fromX, fromY, toX, toY, options) {
await this.rpcClient.send(StagehandMethods.pageDragAndDrop, {
pageId: this.pageId,
fromX,
fromY,
toX,
toY,
...options ? { options } : {}
});
}
async type(text, options) {
await this.rpcClient.send(StagehandMethods.pageType, {
pageId: this.pageId,
text,
...options ? { options } : {}
});
}
async keyPress(key, options) {
await this.rpcClient.send(StagehandMethods.pageKeyPress, {
pageId: this.pageId,
key,
...options ? { options } : {}
});
}
async evaluate(expression, arg) {
return (await this.rpcClient.send(StagehandMethods.pageEvaluate, {
pageId: this.pageId,
expression: normalizeEvaluationExpression(expression, arg)
})).value;
}
async addInitScript(script, arg) {
const source = await normalizeInitScriptSource(script, arg);
await this.rpcClient.send(StagehandMethods.pageAddInitScript, {
pageId: this.pageId,
source
});
}
async on(event, listener) {
if (event !== "console") throw new Error("page.on only supports \"console\" events");
return this.subscribe(event, (notification) => {
if (notification.method === StagehandNotifications.pageCDPEvent.name) return listener(notification.params.event);
});
}
async onToolsAdded(listener) {
return this.subscribe("toolsadded", (notification) => {
if (notification.method === StagehandNotifications.pageEvent.name && notification.params.event === "toolsadded") return listener(notification.params.tools.map((tool) => new WebMCPTool(this.rpcClient, this.pageId, tool)));
});
}
async onToolsRemoved(listener) {
return this.subscribe("toolsremoved", (notification) => {
if (notification.method === StagehandNotifications.pageEvent.name && notification.params.event === "toolsremoved") return listener(notification.params.tools);
});
}
async subscribe(event, deliver) {
const subscriptionId = crypto.randomUUID();
const removeNotificationListener = this.rpcClient.onNotification((notification) => {
if (notification.method !== StagehandNotifications.pageCDPEvent.name && notification.method !== StagehandNotifications.pageEvent.name || notification.params.subscriptionId !== subscriptionId) return;
try {
const result = deliver(notification);
if (result && typeof result === "object" && "then" in result) Promise.resolve(result).catch(reportPageEventListenerError);
} catch (error) {
reportPageEventListenerError(error);
}
});
let subscription;
subscription = new CDPSubscription(this.rpcClient, subscriptionId, removeNotificationListener, () => this.eventSubscriptions.delete(subscription));
this.eventSubscriptions.add(subscription);
try {
await this.rpcClient.send(StagehandMethods.pageOn, {
pageId: this.pageId,
subscriptionId,
event
});
return subscription;
} catch (error) {
removeNotificationListener();
try {
await this.rpcClient.send(StagehandMethods.pageOff, { subscriptionId });
this.eventSubscriptions.delete(subscription);
} catch (cleanupError) {
process.emitWarning(cleanupError instanceof Error ? cleanupError.message : String(cleanupError), { code: "STAGEHAND_PAGE_SUBSCRIPTION_CLEANUP_ERROR" });
}
throw error;
}
}
async setExtraHTTPHeaders(headers) {
await this.rpcClient.send(StagehandMethods.pageSetExtraHTTPHeaders, {
pageId: this.pageId,
headers
});
}
async setViewportSize(width, height, options) {
await this.rpcClient.send(StagehandMethods.pageSetViewportSize, {
pageId: this.pageId,
width,
height,
...options ? { options } : {}
});
}
async waitForLoadState(state, timeout) {
await this.rpcClient.send(StagehandMethods.pageWaitForLoadState, {
pageId: this.pageId,
state,
...timeout === void 0 ? {} : { timeout }
});
}
async waitForTimeout(ms) {
await this.rpcClient.send(StagehandMethods.pageWaitForTimeout, {
pageId: this.pageId,
ms
});
}
async waitForSelector(selector, options) {
return (await this.rpcClient.send(StagehandMethods.pageWaitForSelector, {
pageId: this.pageId,
selector,
...options ? { options } : {}
})).matched;
}
/** Poll in the page's main world until the expression returns a truthy JSON value. */
async waitForFunction(expression, arg, options) {
return (await this.rpcClient.send(StagehandMethods.pageWaitForFunction, {
pageId: this.pageId,
expression: normalizeFunctionWaitExpression(expression, arg),
...options ? { options } : {}
})).value;
}
async screenshot(options) {
const { path, mask, ...screenshotOptions } = options ?? {};
return await decodeCaptureResult((await this.rpcClient.send(StagehandMethods.pageScreenshot, {
pageId: this.pageId,
options: {
...screenshotOptions,
...mask ? { mask: mask.map((locator) => locator.descriptor) } : {}
}
})).data, "screenshot", path);
}
async pdf(options) {
const { path, ...pdfOptions } = options ?? {};
return await decodeCaptureResult((await this.rpcClient.send(StagehandMethods.pagePDF, {
pageId: this.pageId,
options: pdfOptions
})).data, "pdf", path);
}
async snapshot(options) {
return await this.rpcClient.send(StagehandMethods.pageSnapshot, {
pageId: this.pageId,
...options ? { options } : {}
});
}
async tools(options) {
return (await this.rpcClient.send(StagehandMethods.pageWebMCPTools, {
pageId: this.pageId,
...options ? { options } : {}
})).tools.map((descriptor) => new WebMCPTool(this.rpcClient, this.pageId, descriptor));
}
async url() {
return await this.rpcClient.send(StagehandMethods.pageUrl, { pageId: this.pageId });
}
async title() {
return await this.rpcClient.send(StagehandMethods.pageTitle, { pageId: this.pageId });
}
async close() {
await Promise.allSettled([...this.eventSubscriptions].map((subscription) => subscription.unsubscribe()));
await this.rpcClient.send(StagehandMethods.pageClose, { pageId: this.pageId });
}
locator(selector) {
return new Locator(this.rpcClient, {
pageId: this.pageId,
selector
});
}
};
async function decodeCaptureResult(data, method, path) {
const bytes = decodeBase64(data, `page.${method}`);
if (path !== void 0) {
const { writeFile } = await import(
/* @vite-ignore */
"node:fs/promises"
).catch(() => {
throw new TypeError(`page.${method}(): path is only supported in Node.js; omit path to receive ${method === "pdf" ? "PDF" : "screenshot"} bytes`);
});
await writeFile(path, bytes);
}
return bytes;
}
function reportPageEventListenerError(error) {
process.emitWarning(error instanceof Error ? error.message : String(error), { code: "STAGEHAND_PAGE_EVENT_LISTENER_ERROR" });
}
//#endregion
//#region src/browserContext.ts
var BrowserContext = class {
constructor(rpcClient, closeBrowser) {
this.rpcClient = rpcClient;
this.closeBrowser = closeBrowser;
}
get clipboard() {
return this.clipboardRef ??= new BrowserClipboard(this.rpcClient);
}
async pages() {
return (await this.rpcClient.send(StagehandMethods.contextPages, {})).map((pageRef) => new Page(this.rpcClient, pageRef));
}
async newPage(url) {
const params = url === void 0 ? {} : { url };
const pageRef = await this.rpcClient.send(StagehandMethods.contextNewPage, params);
return new Page(this.rpcClient, pageRef);
}
async activePage() {
const pageRef = await this.rpcClient.send(StagehandMethods.contextActivePage, {});
return pageRef ? new Page(this.rpcClient, pageRef) : void 0;
}
async setActivePage(page) {
await this.rpcClient.send(StagehandMethods.contextSetActivePage, { pageId: page.pageId });
}
/** Close the underlying browser. */
async close() {
if (!this.closeBrowser) throw new Error("Browser context is not attached to a browser");
await this.closeBrowser();
}
async addInitScript(script, arg) {
const source = await normalizeInitScriptSource(script, arg, "context.addInitScript");
await this.rpcClient.send(StagehandMethods.contextAddInitScript, { source });
}
async setExtraHTTPHeaders(headers) {
await this.rpcClient.send(StagehandMethods.contextSetExtraHTTPHeaders, { headers });
}
async getDomainPolicy() {
return await this.rpcClient.send(StagehandMethods.contextGetDomainPolicy, {});
}
async setDomainPolicy(policy) {
await this.rpcClient.send(StagehandMethods.contextSetDomainPolicy, { policy });
}
async cookies(urls) {
const params = urls === void 0 ? {} : { urls };
return await this.rpcClient.send(StagehandMethods.contextCookies, params);
}
async addCookies(cookies) {
await this.rpcClient.send(StagehandMethods.contextAddCookies, { cookies });
}
async clearCookies(options) {
const params = options === void 0 ? {} : { options: serializeClearCookieOptions(options) };
await this.rpcClient.send(StagehandMethods.contextClearCookies, params);
}
};
function serializeClearCookieOptions(options) {
return {
...options.name === void 0 ? {} : { name: serializeCookieFilter(options.name) },
...options.domain === void 0 ? {} : { domain: serializeCookieFilter(options.domain) },
...options.path === void 0 ? {} : { path: serializeCookieFilter(options.path) }
};
}
function serializeCookieFilter(filter) {
return typeof filter === "string" ? filter : {
source: filter.source,
flags: filter.flags
};
}
//#endregion
//#region src/runtimeCompatibility.ts
const STAGEHAND_RUNTIME_NAME = "stagehand";
const ReportedRuntimeDescriptorSchema = z.strictObject({
protocolVersion: z.string().min(1),
serverInfo: ImplementationInfoSchema
});
const DEFAULT_RUNTIME_REQUIREMENT = Object.freeze({ protocolVersion: STAGEHAND_PROTOCOL_VERSION });
function negotiateRuntimeCompatibility(required, raw) {
if (raw == null) return {
kind: "unknown",
reason: "missing-marker",
detail: "Runtime marker is absent"
};
try {
const result = ReportedRuntimeDescriptorSchema.safeParse(raw);
if (!result.success) return {
kind: "unknown",
reason: "unreadable-marker",
detail: z.prettifyError(result.error)
};
const reported = descriptor(result.data);
if (reported.serverInfo.name !== "stagehand") return incompatible("runtime-name-mismatch", `Runtime name mismatch: expected "${STAGEHAND_RUNTIME_NAME}", server reported "${reported.serverInfo.name}"`, required, reported);
const compatibility = checkProtocolCompatibility(required.protocolVersion, reported.protocolVersion);
if (!compatibility.compatible) return incompatible(compatibility.reason, compatibilityDetail(compatibility.reason, required.protocolVersion, reported.protocolVersion), required, reported);
return {
kind: "compatible",
protocolVersion: reported.protocolVersion,
serverInfo: reported.serverInfo
};
} catch {
return {
kind: "unknown",
reason: "unreadable-marker",
detail: "Runtime marker could not be read"
};
}
}
function compatibilityDetail(reason, clientVersion, serverVersion) {
switch (reason) {
case "protocol-invalid-version": return `Invalid protocol version: client ${clientVersion}, server ${serverVersion}`;
case "protocol-major-mismatch": return `Protocol major mismatch: client ${clientVersion}, server ${serverVersion}`;
case "protocol-server-too-old": return `Server protocol ${serverVersion} is older than client requirement ${clientVersion}`;
case "protocol-prerelease-mismatch": return `Protocol prereleases must match exactly: client ${clientVersion}, server ${serverVersion}`;
}
}
function descriptor(value) {
return {
protocolVersion: value.protocolVersion,
serverInfo: { ...value.serverInfo }
};
}
function incompatible(reason, detail, required, reported) {
return {
kind: "incompatible",
reason,
detail,
required: { ...required },
reported
};
}
//#endregion
//#region src/abort.ts
function abortReason(signal) {
return signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("Operation aborted");
}
function throwIfAborted(signal) {
if (signal?.aborted) throw abortReason(signal);
}
async function abortable(promise, signal) {
if (!signal) return await promise;
throwIfAborted(signal);
return await new Promise((resolve, reject) => {
const onAbort = () => reject(abortReason(signal));
signal.addEventListener("abort", onAbort, { once: true });
promise.then((value) => {
signal.removeEventListener("abort", onAbort);
resolve(value);
}, (error) => {
signal.removeEventListener("abort", onAbort);
reject(error);
});
});
}
async function abortableDelay(ms, signal) {
if (!signal) {
await new Promise((resolve) => setTimeout(resolve, ms));
return;
}
throwIfAborted(signal);
await new Promise((resolve, reject) => {
const onAbort = () => {
clearTimeout(timeoutId);
reject(abortReason(signal));
};
const timeoutId = setTimeout(() => {
signal.removeEventListener("abort", onAbort);
resolve();
}, ms);
signal.addEventListener("abort", onAbort, { once: true });
});
}
//#endregion
//#region src/cdpClient.ts
function callbackBatchExpression(input) {
return `(() => { const __name = (fn, name) => { try { Object.defineProperty(fn, "name", { value: name, configurable: true }); } catch {} return fn; }; void globalThis.__stagehandReceiveFromHost(${JSON.stringify(JSON.stringify(input.message))}, { callback: (${input.callbackSource}) }); return true; })()`;
}
function callbackSourceFromMessage(message) {
if (!("method" in message) || message.method !== StagehandMethods.stagehandCallbackBatch.name) return;
const params = message.params;
if (typeof params !== "object" || params === null || Array.isArray(params)) throw new TypeError("Stagehand callback batch request is missing callback_source");
const source = params.callback_source;
if (typeof source !== "string" || !source.trim()) throw new TypeError("Stagehand callback batch request is missing callback_source");
return source;
}
function stagehandMessageExpression(message) {
const callbackSource = callbackSourceFromMessage(message);
return callbackSource ? callbackBatchExpression({
message,
callbackSource
}) : `void globalThis.__stagehandReceiveFromHost(${JSON.stringify(JSON.stringify(message))}); true`;
}
const RUNTIME_INCOMPATIBLE_REMEDIATION = "Upgrade the Stagehand SDK and the Stagehand extension together so their protocol majors match, or start the session with the extension bundled in this SDK.";
/**
* Raised as soon as the connected Stagehand extension publishes a runtime marker this SDK cannot
* talk to. Initialization does not keep polling: the extension will not change its protocol
* version while the session is open.
*/
var StagehandRuntimeIncompatibleError = class extends Error {
constructor(compatibility) {
super("Incompatible Stagehand runtime: " + compatibility.detail + "; client protocol " + compatibility.required.protocolVersion + ", reported protocol " + String(compatibility.reported.protocolVersion) + ", server " + compatibility.reported.serverInfo.name + "/" + compatibility.reported.serverInfo.version + ". Upgrade the Stagehand SDK and the Stagehand extension together so their protocol majors match, or start the session with the extension bundled in this SDK.");
this.compatibility = compatibility;
this.remediation = RUNTIME_INCOMPATIBLE_REMEDIATION;
this.name = "StagehandRuntimeIncompatibleError";
this.reason = compatibility.reason;
this.detail = compatibility.detail;
this.clientProtocolVersion = compatibility.required.protocolVersion;
this.reportedProtocolVersion = compatibility.reported.protocolVersion;
this.serverInfo = { ...compatibility.reported.serverInfo };
}
};
const CDPErrorSchema = z.looseObject({
code: z.int(),
message: z.string(),
data: z.unknown().optional()
});
const CDPCommandErrorCauseSchema = CDPErrorSchema.extend({ method: z.string() });
const CDPResponseEnvelopeSchema = z.looseObject({
id: z.int(),
result: z.unknown().optional(),
error: CDPErrorSchema.optional(),
sessionId: z.string().optional()
});
const CDPEventEnvelopeSchema = z.looseObject({
method: z.string(),
params: z.unknown().optional(),
sessionId: z.string().optional()
});
const RuntimeBindingCalledSchema = z.looseObject({
name: StagehandSendToHostBindingSchema,
payload: z.string(),
executionContextId: z.int()
});
const RuntimeReadinessEnvelopeSchema = z.looseObject({
marker: z.unknown(),
hasReceiver: z.boolean()
});
const InstalledExtensionSchema = z.looseObject({
id: z.string().min(1),
name: z.string(),
version: z.string(),
path: z.string(),
enabled: z.boolean()
});
const InstalledExtensionsResultSchema = z.looseObject({ extensions: z.array(InstalledExtensionSchema) });
const STAGEHAND_EXTENSION_NAME = "Stagehand Runtime";
var CDPConnectionClosedError = class extends Error {
constructor(options) {
const detail = options?.code !== void 0 ? ` (close code ${options.code}${options.reason ? `: ${options.reason}` : ""})` : "";
super(`CDP connection closed${detail}`, options);
this.name = "CDPConnectionClosedError";
}
};
var CDPClient = class CDPClient {
constructor(socket, webSocketDebuggerUrl) {
this.socket = socket;
this.nextId = 1;
this.pending = /* @__PURE__ */ new Map();
this.closed = false;
this.heartbeatMs = cdpHeartbeatMs(process.env.STAGEHAND_CDP_HEARTBEAT_MS);
this.connectedAt = Date.now();
this.lastMsgAt = this.connectedAt;
this.lastSentAt = this.connectedAt;
this.lastMethod = "";
this.webSocketDebuggerUrl = webSocketDebuggerUrl;
this.socket.addEventListener("message", (event) => {
if (this.closed) return;
this.lastMsgAt = Date.now();
this.handleMessage(event.data).catch((error) => {
this.finishDrop(asError$1(error), "error");
});
});
this.socket.addEventListener("close", (event) => {
const { code, reason: closeReason } = event;
this.finishDrop(new CDPConnectionClosedError({
code,
reason: closeReason
}), "close", code);
});
this.socket.addEventListener("error", (event) => {
const socketError = asError$1(event.error ?? event);
this.finishDrop(new CDPConnectionClosedError({ cause: socketError }), "error");
});
this.startHeartbeat();
}
/** Browser-level traffic only; no replay, transport replacement or reconnection. */
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.closed || this.socket.readyState !== WebSocket.OPEN || this.heartbeatAbort) return;
const controller = new AbortController();
this.heartbeatAbort = controller;
const timeout = setTimeout(() => controller.abort(/* @__PURE__ */ new Error("CDP heartbeat timed out")), Math.min(this.heartbeatMs, 1e4));
timeout.unref?.();
this.sendCommand("Browser.getVersion", {}, void 0, controller.signal).catch(() => void 0).finally(() => {
clearTimeout(timeout);
if (this.heartbeatAbort === controller) this.heartbeatAbort = void 0;
});
}, this.heartbeatMs);
this.heartbeatTimer.unref?.();
}
stopHeartbeat() {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = void 0;
this.heartbeatAbort?.abort(/* @__PURE__ */ new Error("CDP heartbeat stopped"));
this.heartbeatAbort = void 0;
}
finishDrop(reason, kind, code) {
if (this.closed) return;
this.closed = true;
this.logDrop(reason, kind, code);
this.stopHeartbeat();
this.rejectPending(reason);
if (kind === "error") {
try {
this.socket.close();
} catch {}
this.onerror?.(reason);
} else this.onclose?.(reason);
}
logDrop(reason, kind, code) {
if (process.env.STAGEHAND_CDP_LOG !== "1") return;
const now = Date.now();
const line = `CDP_DROP ${JSON.stringify({
ts: new Date(now).toISOString(),
kind,
code: code ?? null,
idle_ms: now - this.lastMsgAt,
since_send_ms: now - this.lastSentAt,
age_ms: now - this.connectedAt,
pending: this.pending.size,
last_method: /^[A-Za-z][A-Za-z0-9_.]*$/u.test(this.lastMethod) ? this.lastMethod : "",
reason: cdpDiagnosticReason(reason)
})}\n`;
try {
process.stderr.write(line);
} catch {}
const file = process.env.STAGEHAND_CDP_LOG_FILE;
if (file) try {
appendFileSync(file, line);
} catch {}
}
static async connect(options) {
const signal = options.signal;
const webSocketDebuggerUrl = await resolveBrowserWebSocketUrl(options.cdpUrl, {
signal,
headers: options.headers
});
throwIfAborted(signal);
let headers = options.headers;
if (headers && Object.keys(headers).length > 0 && !canForwardCDPHeaders(options.cdpUrl, webSocketDebuggerUrl)) {
headers = void 0;
if (process.env.STAGEHAND_CDP_LOG === "1") try {
process.stderr.write("CDP headers were withheld from the advertised WebSocket endpoint because its host or port differs, or forwarding would downgrade transport security. If the handshake is rejected, check the gateway's advertised CDP endpoint.\n");
} catch {}
}
const socket = await openCDPWebSocket(webSocketDebuggerUrl, signal, void 0, headers);
const client = new CDPClient(socket, webSocketDebuggerUrl);
try {
let extensionId;
if (options.localExtensionDir) extensionId = await resolveExtension(client, {
loadIfNotFound: true,
extensionDir: options.localExtensionDir,
signal
});
else if (options.extensionDir) extensionId = await loadUnpackedExtension(client, options.extensionDir, signal);
else if (options.extensionId) extensionId = options.extensionId;
else if (options.preloadedExtension) extensionId = await resolveExtension(client, {
loadIfNotFound: false,
signal
});
else throw new Error("Exactly one of localExtensionDir, extensionDir, extensionId, or preloadedExtension is required");
const serviceWorker = await waitForServiceWorker(client, {
extensionId,
urlIncludes: options.serviceWorkerUrlIncludes,
signal
});
const attached = await client.sendCommand("Target.attachToTarget", {
targetId: serviceWorker.targetId,
flatten: true
}, void 0, signal);
client.sessionId = attached.sessionId;
client.attachedServiceWorker = {
targetId: serviceWorker.targetId,
title: serviceWorker.title,
url: serviceWorker.url,
extensionId
};
await client.sendCommand("Runtime.enable", {}, attached.sessionId, signal).catch(() => {
throwIfAborted(signal);
});
await client.sendCommand("Runtime.addBinding", { name: STAGEHAND_SEND_TO_HOST_BINDING }, attached.sessionId, signal);
await waitForRuntimeReady(client, attached.sessionId, {
runtimeRequirement: options.runtimeRequirement,
allowFallbackInstall: options.allowFallbackInstall,
signal
});
return client;
} catch (error) {
client.close();
throw error;
}
}
get serviceWorker() {
if (!this.attachedServiceWorker) throw new Error("Stagehand service worker is not attached");
return this.attachedServiceWorker;
}
async send(message, signal) {
if (this.closed) throw new Error("CDP client is closed");
if (!this.sessionId) throw new Error("Stagehand service worker is not attached");
const expression = stagehandMessageExpression(message);
const evaluated = await this.sendCommand("Runtime.evaluate", {
expression,
awaitPromise: false,
returnByValue: true
}, this.sessionId, signal);
if (evaluated.exceptionDetails) throw new Error(evaluated.exceptionDetails.exception?.description ?? evaluated.exceptionDetails.text ?? "Stagehand service worker rejected an RPC message");
}
async sendCommand(method, params = {}, sessionId, signal) {
throwIfAborted(signal);
if (this.closed) throw new CDPConnectionClosedError();
if (this.socket.readyState !== WebSocket.OPEN) throw new Error("CDP connection is not open");
const id = this.nextId++;
const message = sessionId ? {
id,
method,
params,
sessionId
} : {
id,
method,
params
};
return await new Promise((resolve, reject) => {
const onAbort = () => {
this.pending.delete(id);
reject(signal ? abortReason(signal) : /* @__PURE__ */ new Error("CDP command aborted"));
};
signal?.addEventListener("abort", onAbort, { once: true });
this.pending.set(id, {
method,
resolve: (value) => resolve(value),
reject,
...signal ? { removeAbortListener: () => signal.removeEventListener("abort", onAbort) } : {}
});
if (signal?.aborted) {
onAbort();
return;
}
try {
this.socket.send(JSON.stringify(message));
this.lastSentAt = Date.now();
if (method !== "Browser.getVersion") this.lastMethod = method;
} catch (error) {
this.pending.delete(id);
signal?.removeEventListener("abort", onAbort);
reject(asError$1(error));
}
});
}
close() {
if (this.closed) return;
this.closed = true;
this.stopHeartbeat();
const onclose = this.onclose;
this.onmessage = void 0;
this.onclose = void 0;
this.onerror = void 0;
const reason = /* @__PURE__ */ new Error("CDP client closed");
this.rejectPending(reason);
try {
this.socket.close();
} finally {
onclose?.(reason);
}
}
async handleMessage(data) {
const text = await messageDataToString(data);
const rawMessage = JSON.parse(text);
const response = CDPResponseEnvelopeSchema.safeParse(rawMessage);
if (response.success) {
this.handleResponse(response.data);
return;
}
const event = CDPEventEnvelopeSchema.parse(rawMessage);
if (event.method !== "Runtime.bindingCalled" || event.sessionId !== this.sessionId) return;
const binding = RuntimeBindingCalledSchema.safeParse(event.params);
if (!binding.success) return;
Promise.resolve(this.onmessage?.(binding.data.payload)).catch((error) => {
this.onerror?.(asError$1(error));
});
}
handleResponse(message) {
const pending = this.pending.get(message.id);
if (!pending) return;
this.pending.delete(message.id);
pending.removeAbortListener?.();
if (message.error) {
pending.reject(new Error(message.error.message, { cause: CDPCommandErrorCauseSchema.parse({
...message.error,
method: pending.method
}) }));
return;
}
pending.resolve(message.result);
}
rejectPending(error) {
for (const pending of this.pending.values()) {
pending.reject(error);
pending.removeAbortListener?.();
}
this.pending.clear();
}
};
async function openCDPWebSocket(url, signal, createSocket = (socketUrl, socketHeaders) => {
return socketHeaders ? new WebSocket(socketUrl, { headers: socketHeaders }) : new WebSocket(socketUrl);
}, headers) {
throwIfAborted(signal);
const socket = headers === void 0 ? createSocket(url) : createSocket(url, headers);
await new Promise((resolve, reject) => {
const cleanup = () => {
signal?.removeEventListener("abort", onAbort);
socket.removeEventListener("open", onOpen);
socket.removeEventListener("error", onError);
};
const onAbort = () => {
cleanup();
socket.close();
reject(signal ? abortReason(signal) : /* @__PURE__ */ new Error("CDP WebSocket opening aborted"));
};
const onOpen = () => {
cleanup();
resolve();
};
const onError = () => {
cleanup();
reject(/* @__PURE__ */ new Error("Failed to open CDP WebSocket"));
};
signal?.addEventListener("abort", onAbort, { once: true });
socket.addEventListener("open", onOpen, { once: true });
socket.addEventListener("error", onError, { once: true });
if (signal?.aborted) onAbort();
});
return socket;
}
async function waitForRuntimeReady(cdp, sessionId, options) {
const pollIntervalMs = options.pollIntervalMs ?? 100;
const delayFn = options.delayFn ?? delay;
while (true) {
throwIfAborted(options.signal);
const evaluated = await evaluateRuntimeReadiness(cdp, sessionId, options.signal);
if (!evaluated.exceptionDetails) {
const readiness = parseRuntimeReadiness(evaluated.result?.value);
const compatibility = negotiateRuntimeCompatibility(options.runtimeRequirement ?? DEFAULT_RUNTIME_REQUIREMENT, readiness.marker);
if (compatibility.kind === "incompatible" && options.allowFallbackInstall !== true) throw new StagehandRuntimeIncompatibleError(compatibility);
if (compatibility.kind === "compatible" && readiness.hasReceiver) return;
}
await abortable(delayFn(pollIntervalMs), options.signal);
}
}
/** @internal Discover the runtime, optionally loading a bundled extension if needed. */
async function resolveExtension(cdp, options) {
let id;
try {
id = await getInstalledStagehandExtensionId(cdp, options);
} catch (error) {
if (!options.loadIfNotFound || !isExtensionCommandUnavailable(error, "Extensions.getExtensions")) throw error;
}
throwIfAborted(options.signal);
if (id !== void 0) return id;
if (options.loadIfNotFound) {
if (!options.extensionDir) throw new Error("extensionDir is required to load the Stagehand extension");
return await loadUnpackedExtension(cdp, options.extensionDir, options.signal);
}
throw new Error("Stagehand extension is not installed in the connected browser. The extension must be included when the Browserbase session is created.");
}
async function getInstalledStagehandExtensionId(cdp, options) {
throwIfAborted(options.signal);
const response = await cdp.sendCommand("Extensions.getExtensions", {}, void 0, options.signal);
const installed = InstalledExtensionsResultSchema.parse(response).extensions.filter((extension) => extension.name === STAGEHAND_EXTENSION_NAME);
const enabled = installed.filter((extension) => extension.enabled);
if (enabled.length === 1) return enabled[0].id;
if (enabled.length > 1) {
const ids = enabled.map((extension) => extension.id).sort().join(", ");
throw new Error(`Multiple enabled Stagehand extensions are installed: ${ids}`);
}
if (installed.length > 0) throw new Error("Stagehand extension is installed in the connected browser but is disabled.");
}
async function evaluateRuntimeReadiness(cdp, sessionId, signal) {
return await cdp.sendCommand("Runtime.evaluate", {
expression: `(() => ({
marker: globalThis.__stagehand_runtime ?? null,
hasReceiver: typeof globalThis.__stagehandReceiveFromHost === "function",
}))()`,
returnByValue: true
}, sessionId, signal);
}
function parseRuntimeReadiness(value) {
const parsed = RuntimeReadinessEnvelopeSchema.safeParse(value);
return parsed.success ? parsed.data : {
marker: null,
hasReceiver: false
};
}
async function waitForServiceWorker(cdp, options) {
const startedAt = Date.now();
const activationDelayMs = options.activationDelayMs ?? 1e3;
const pollIntervalMs = options.pollIntervalMs ?? 100;
const delayFn = options.delayFn ?? delay;
const workerUrlIncludes = options.urlIncludes ?? "service-worker.js";
let activationTargetId;
try {
while (true) {
throwIfAborted(options.signal);
const serviceWorker = (await cdp.sendCommand("Target.getTargets", {}, void 0, options.signal)).targetInfos.find((target) => target.type === "service_worker" && target.url.startsWith("chrome-extension://") && target.url.startsWith(`chrome-extension://${options.extensionId}/`) && target.url.includes(workerUrlIncludes));
if (serviceWorker) return serviceWorker;
if (!activationTargetId && Date.now() - startedAt >= activationDelayMs) activationTargetId = (await cdp.sendCommand("Target.createTarget", { url: `chrome-extension://${options.extensionId}/wake-service-worker.html` }, void 0, options.signal).catch(() => {
throwIfAborted(options.signal);
}))?.targetId;
await abortable(delayFn(pollIntervalMs), options.signal);
}
} finally {
if (activationTargetId) cdp.sendCommand("Target.closeTarget", { targetId: activationTargetId }).catch(() => void 0);
}
}
async function loadUnpackedExtension(cdp, extensionDir, signal) {
let loaded;
try {
loaded = await cdp.sendCommand("Extensions.loadUnpacked", { path: extensionDir }, void 0, signal);
} catch (error) {
if (isExtensionCommandUnavailable(error, "Extensions.loadUnpacked")) throw new Error("This Chrome build does not support Extensions.loadUnpacked. Preload the Stagehand extension and connect using a Chrome build that supports Extensions.getExtensions.", { cause: error });
throw error;
}
if (!loaded.id) throw new Error("Extensions.loadUnpacked did not return an extension id");
return loaded.id;
}
/** @internal Require the same host and effective port, without downgrading TLS. */
function canForwardCDPHeaders(source, destination) {
const ports = {
"http:": "80",
"ws:": "80",
"https:": "443",
"wss:": "443"
};
try {
const first = new URL(source);
const second = new URL(destination);
return Boolean(ports[first.protocol] && ports[second.protocol] && first.hostname && first.hostname === second.hostname && (first.port || ports[first.protocol]) === (second.port || ports[second.protocol]) && !(["https:", "wss:"].includes(first.protocol) && ["http:", "ws:"].includes(second.protocol)));
} catch {
return false;
}
}
var CDPDiscoveryError = class extends Error {};
async function fetchCDPVersion(url, options) {
const fetchFn = options.fetchFn ?? fetch;
const authenticated = options.headers !== void 0 && Object.keys(options.headers).length > 0;
for (let redirects = 0;; redirects++) {
const response = await abortable(fetchFn(url, {
signal: options.signal,
...options.headers === void 0 ? {} : { headers: options.headers },
...authenticated ? { redirect: "manual" } : {}
}), options.signal);
if (!response.ok) await response.body?.cancel();
if (response.status === 401 || response.status === 403) throw new CDPDiscoveryError(`CDP discovery was rejected with HTTP ${response.status}. Check the gateway credentials and access permissions.`);
if (!authenticated || ![
301,
302,
303,
307,
308
].includes(response.status)) return response;
const location = response.headers?.get("location");
if (!location) return response;
let destination;
try {
destination = new URL(location, url);
} catch {
throw new CDPDiscoveryError("Invalid CDP HTTP redirect.");
}
if (!canForwardCDPHeaders(url, destination.href) || !["http:", "https:"].includes(destination.protocol) || destination.username || destination.password) throw new CDPDiscoveryError("Refusing CDP HTTP redirect with headers to a different host or port, an insecure transport, or a URL containing credentials.");
if (redirects >= 20) throw new CDPDiscoveryError("Too many CDP HTTP redirects.");
url = destination.href;
}
}
async function resolveBrowserWebSocketUrl(cdpUrl, options) {
throwIfAborted(options.signal);
if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) return cdpUrl;
const pollIntervalMs = options.pollIntervalMs ?? 250;
const delayFn = options.delayFn ?? delay;
const baseUrl = cdpUrl.replace(/\/$/, "");
while (true) {
throwIfAborted(options.signal);
try {
const response = await fetchCDPVersion(`${baseUrl}/json/version`, options);
if (response.ok) {
const version = await abortable(response.json(), options.signal);
if (version.webSocketDebuggerUrl) return version.webSocketDebuggerUrl;
}
} catch (error) {
if (error instanceof CDPDiscoveryError) throw error;
throwIfAborted(options.signal);
}
await abortable(delayFn(pollIntervalMs), options.signal);
}
}
async function messageDataToString(data) {
if (typeof data === "string") return data;
if (data instanceof ArrayBuffer) return Buffer.from(data).toString("utf8");
if (data instanceof Blob) return data.text();
const view = data;
return Buffer.from(view.buffer, view.byteOffset, view.byteLength).toString("utf8");
}
function delay(ms) {
return abortableDelay(ms);
}
function isExtensionCommandUnavailable(error, method) {
if (!(error instanceof Error)) return false;
const cause = CDPCommandErrorCauseSchema.safeParse(error.cause);
if (!cause.success) return false;
return cause.data.method === method && (cause.data.code === -32601 || /method not found|wasn't found/i.test(cause.data.message));
}
function asError$1(error) {
return error instanceof Error ? error : new Error(String(error));
}
function cdpHeartbeatMs(raw) {
const value = Number(raw);
return raw?.trim() && Number.isInteger(value) && value >= 1e3 && value <= 2147483647 ? value : 2e4;
}
/** Keep transport causes useful without serializing errors, payloads or arbitrary objects. */
function cdpDiagnosticReason(reason) {
const messages = [];
const seen = /* @__PURE__ */ new Set();
let current = reason;
for (let depth = 0; depth < 4; depth++) try {
if (typeof current === "string") {
messages.push(sanitizeCdpDiagnostic(current));
break;
}
if (!(current instanceof Error) || seen.has(current)) break;
seen.add(current);
if (typeof current.message === "string" && current.message) messages.push(sanitizeCdpDiagnostic(current.message));
current = current.cause;
} catch {
break;
}
return (messages.join(": ") || "CDP connection closed").slice(0, 160);
}
/** The diagnostic contains no payloads or connection URLs, including close text URLs. */
function sanitizeCdpDiagnostic(message) {
return message.replace(/(?:https?|wss?):\/\/[^\s"']+/giu, "[url]").replace(/\b(?:sk-[A-Za-z0-9_-]+|bb_(?:live|test)_[A-Za-z0-9_-]+|AIza[A-Za-z0-9_-]{20,})/gu, "[redacted]").replace(/\bBearer\s+[^\s]+/giu, "Bearer [redacted]");
}
//#endregion
//#region src/rpcErrors.ts
var RPCResponseTimeoutError = class extends Error {
constructor(method, timeoutMs) {
super(`RPC response timed out after ${timeoutMs}ms: ${method}`, { cause: {
method,
timeoutMs
} });
this.name = "RPCResponseTimeoutError";
this.method = method;
this.timeoutMs = timeoutMs;
}
};
//#endregion
//#region src/rpcClient.ts
const TRACER = trace.getTracer("@browserbasehq/stagehand");
const W3C_TRACE_CONTEXT_PROPAGATOR = new W3CTraceContextPropagator();
const MAX_PENDING_NOTIFICATIONS = 100;
const RPC_RESPONSE_GRACE_MS = 1e4;
const DEFAULT_OPERATION_TIMEOUT_MS = /* @__PURE__ */ new Map([
[StagehandMethods.pageGoto.name, 15e3],
[StagehandMethods.pageReload.name, 15e3],
[StagehandMethods.pageGoBack.name, 15e3],
[StagehandMethods.pageGoForward.name, 15e3],
[StagehandMethods.pageWaitForLoadState.name, 15e3],
[StagehandMethods.pageWaitForSelector.name, 3e4],
[StagehandMethods.pagePDF.name, 3e4],
[StagehandMethods.pageSnapshot.name, 2e4],
[StagehandMethods.pageWebMCPTools.name, 1e3]
]);
const UNBOUNDED_BY_DEFAULT_METHODS = /* @__PURE__ */ new Set([
StagehandMethods.pageWaitForFunction.name,
StagehandMethods.stagehandInit.name,
StagehandMethods.stagehandClose.name,
StagehandMethods.stagehandAct.name,
StagehandMethods.stagehandExtract.name,
StagehandMethods.stagehandObserve.name,
StagehandMethods.contextNewPage.name,
StagehandMethods.contextAddInitScript.name,
StagehandMethods.contextSetExtraHTTPHeaders.name,
StagehandMethods.contextGetDomainPolicy.name,
StagehandMethods.contextSetDomainPolicy.name,
StagehandMethods.contextCookies.name,
StagehandMethods.contextAddCookies.name,
StagehandMethods.contextClearCookies.name,
StagehandMethods.contextClipboardReadText.name,
StagehandMethods.contextClipboardWriteText.name,
StagehandMethods.contextClipboardClear.name,
StagehandMethods.contextClipboardPaste.name,
StagehandMethods.contextClipboardCopy.name,
StagehandMethods.contextClipboardCut.name,
StagehandMethods.pageClose.name,
StagehandMethods.pageEvaluate.name,
StagehandMethods.pageScreenshot.name,
StagehandMethods.pageWebMCPInvocationResult.name
]);
const RPCClientOptionsBaseSchema = z.object({
cdpUrl: z.string().min(1),
serviceWorkerUrlIncludes: z.string().min(1).optional(),
signal: z.custom((value) => value instanceof AbortSignal)
}).strict();
z.union([
RPCClientOptionsBaseSchema.extend({
extensionDir: z.string().min(1),
extensionId: z.never().optional(),
preloadedExtension: z.never().optional()
}).strict(),
RPCClientOptionsBaseSchema.extend({
extensionId: z.string().min(1),
extensionDir: z.never().optional(),
preloadedExtension: z.never().optional()
}).strict(),
RPCClientOptionsBaseSchema.extend({
preloadedExtension: z.literal(true),
extensionDir: z.never().optional(),
extensionId: z.never().optional()
}).strict()
]);
var RPCClient = class {
constructor(cdp) {
this.nextRequestId = 1;
this.pending = /* @__PURE__ */ new Map();
this.requestHandlers = /* @__PURE__ */ new Map();
this.notificationListeners = /* @__PURE__ */ new Set();
this.pendingNotifications = [];
this.closed = false;
this.closeController = new AbortController();
this.cdp = cdp;
this.serviceWorker = cdp.serviceWorker;
this.browserWebSocketDebuggerUrl = cdp.webSocketDebuggerUrl;
this.cdp.onmessage = (message) => this.receive(message);
this.cdp.onclose = (reason) => this.close(reason);
this.cdp.onerror = (error) => this.close(error);
}
async send(method, params, options = {}) {
if (this.closed) throw new Error("RPC client is closed");
if (method.name === StagehandMethods.stagehandInit.name && !options.signal) throw new Error("stagehand.init requires an initialization lifecycle signal");
const parentContext = context.active();
const span = TRACER.startSpan(method.name, {
kind: SpanKind.CLIENT,
attributes: {
"rpc.system.name": "jsonrpc",
"rpc.method": method.name
}
}, parentContext);
const requestContext = trace.setSpan(parentContext, span);
const requestId = this.nextRequestId++;
try {
return await context.with(requestContext, async () => {
const parsedParams = method.params.parse(params);
const request = JSONRPCRequestSchema.parse({
jsonrpc: "2.0",
id: requestId,
method: method.name,
params: encodeWireValue(parsedParams, method.paramsWire),
...getTraceContextFields(requestContext)
});
span.setAttribute("jsonrpc.request.id", String(request.id));
const responseTimeoutMs = options.responseTimeoutMs ?? rpcResponseTimeoutMs(method.name, parsedParams);
const timeoutController = responseTimeoutMs === void 0 ? void 0 : new AbortController();
const signal = options.signal && timeoutController ? AbortSignal.any([options.signal, timeoutController.signal]) : options.signal ?? timeoutController?.signal;
let timeoutId;
if (timeoutController && responseTimeoutMs !== void 0) {
const deadline = performance.now() + responseTimeoutMs;
const tick = () => {
const remaining = deadline - performance.now();
if (remaining > 0) timeoutId = setTimeout(tick, Math.min(2147483647, Math.ceil(remaining)));
else timeoutController.abort(new RPCResponseTimeoutError(method.name, responseTimeoutMs));
};
tick();
}
try {
const response = this.waitForResponse(request.id, method, signal);
const [, result] = await Promise.all([this.cdp.send(request, signal).catch((error) => {
this.rejectPending(request.id, asError(error));
}), response]);
return result;
} finally {
if (timeoutId !== void 0) clearTimeout(timeoutId);
}
});
} catch (error) {
markSpanError(span, error);
throw error;
} finally {
span.end();
}
}
async sendStagehandInit(params, signal) {
return await this.send(StagehandMethods.stagehandInit, params, { signal });
}
onRequest(method, handler) {
if (this.closed) throw new Error("RPC client is closed");
const registeredHandler = {
method,
handle: async (params, context) => await handler(params, context)
};
this.requestHandlers.set(method.name, registeredHandler);
return () => {
if (this.requestHandlers.get(method.name) === registeredHandler) this.requestHandlers.delete(method.name);
};
}
onNotification(listener) {
this.notificationListeners.add(listener);
const pending = this.pendingNotifications;
this.pendingNotifications = [];
for (const notification of pending) listener(notification);
return () => this.notificationListeners.delete(listener);
}
close(reason = /* @__PURE__ */ new Error("RPC client closed"), { closeTransport = true } = {}) {
if (this.closed) return;
this.closed = true;
this.closeController.abort(reason);
this.requestHandlers.clear();
this.notificationListeners.clear();
this.pendingNotifications = [];
for (const [id] of this.pending) this.rejectPending(id, reason);
this.cdp.onmessage = void 0;
this.cdp.onclose = void 0;
this.cdp.onerror = void 0;
if (closeTransport) this.cdp.close();
}
waitForResponse(id, method, signal) {
return new Promise((resolve, reject) => {
const onAbort = () => this.rejectPending(id, abortReason(signal));
this.pending.set(id, {
method,
resolve,
reject,
...signal ? { removeAbortListener: () => signal.removeEventListener("abort", onAbort) } : {}
});
signal?.addEventListener("abort", onAbort, { once: true });
if (signal?.aborted) onAbort();
});
}
async receive(raw) {
if (this.closed) return;
const wireInput = JSONRPCWireInputSchema.safeParse(raw);
if (!wireInput.success) {
await this.sendError(null, JSONRPCErrorCodes.parseError, "Parse error");
return;
}
const envelope = JSONRPCEnvelopeSchema.safeParse(wireInput.data);
if (!envelope.success) {
await this.sendError(null, JSONRPCErrorCodes.invalidRequest, "Invalid request");
return;
}
const message = envelope.data;
if ("result" in message || "error" in message) {
const response = JSONRPCResponseSchema.safeParse(message);
if (!response.success) {
this.close(/* @__PURE__ */ new Error("Invalid JSON-RPC response"));
return;
}
this.receiveResponse(response.data);
return;
}
if ("method" in message && !("id" in message)) {
const notification = StagehandRpcNotificationSchema.safeParse(message);
if (!notification.success) return;
this.handleNotification(notification.data);
return;
}
const request = JSONRPCRequestSchema.safeParse(message);
if (request.success) {
await this.handleRequest(request.data);
return;
}
const requestId = JSONRPCRequestIdSchema.safeParse(message.id);
await this.sendError(requestId.success ? requestId.data : null, JSONRPCErrorCodes.invalidRequest, "Invalid request");
}
async handleRequest(request) {
const registeredHandler = this.requestHandlers.get(request.method);
if (!registeredHandler) {
await this.sendError(request.id, JSONRPCErrorCodes.methodNotFound, "Method not found");
return;
}
const params = wireSchema(registeredHandler.method.params, registeredHandler.method.paramsWire).safeParse(request.params);
if (!params.success) {
await this.sendError(request.id, JSONRPCErrorCodes.invalidParams, "Invalid params");
return;
}
const parentContext = W3C_TRACE_CONTEXT_PROPAGATOR.extract(ROOT_CONTEXT, request, defaultTextMapGetter);
const span = TRACER.startSpan(request.method, {
kind: SpanKind.SERVER,
attributes: {
"rpc.system.name": "jsonrpc",
"rpc.method": request.method,
"jsonrpc.request.id": String(request.id)
}
}, parentContext);
const requestContext = trace.setSpan(parentContext, span);
try {
const result = await context.with(requestContext, () => registeredHandler.handle(params.data, {
requestId: request.id,
signal: this.closeController.signal
}));
if (this.closed) return;
const parsedResult = registeredHandler.method.result.safeParse(result);
if (!parsedResult.success) {
span.setAttribute("rpc.response.status_code", String(JSONRPCErrorCodes.internalError));
span.setAttribute("error.type", String(JSONRPCErrorCodes.internalError));
span.setStatus({ code: SpanStatusCode.ERROR });
await this.sendError(request.id, JSONRPCErrorCodes.internalError, "Internal error");
return;
}
await this.cdp.send(JSONRPCSuccessResponseSchema.parse({
jsonrpc: "2.0",
id: request.id,
result: encodeWireValue(parsedResult.data, registeredHandler.method.resultWire)
}));
} catch (error) {
markSpanError(span, error);
if (this.closed) return;
await this.sendError(request.id, JSONRPCErrorCodes.internalError, error instanceof Error ? error.message : String(error), { name: error instanceof Error ? error.name : "Error" });
} finally {
span.end();
}
}
receiveResponse(response) {
if (response.id === null) return;
const pending = this.pending.get(response.id);
if (!pending) return;
this.pending.delete(response.id);
pending.removeAbortListener?.();
if ("error" in response) {
const error = new Error(response.error.message, { cause: response.error });
if (recordProperty(response.error.data, "name") === "TimeoutError") error.name = "TimeoutError";
pending.reject(error);
return;
}
try {
pending.resolve(wireSchema(pending.method.result, pending.method.resultWire).parse(response.result));
} catch (error) {
pending.reject(asError(error));
}
}
rejectPending(id, error) {
const pending = this.pending.get(id);
if (!pending) return;
this.pending.delete(id);
pending.removeAbortListener?.();
pending.reject(error);
}
async sendError(id, code, message, data) {
await this.cdp.send(JSONRPCErrorResponseSchema.parse({
jsonrpc: "2.0",
id,
error: {
code,
message,
...data === void 0 ? {} : { data }
}
}));
}
handleNotification(notification) {
if (this.notificationListeners.size === 0) {
if (this.pendingNotifications.length === MAX_PENDING_NOTIFICATIONS) this.pendingNotifications.shift();
this.pendingNotifications.push(notification);
return;
}
for (const listener of this.notificationListeners) listener(notification);
}
};
function getTraceContextFields(requestContext) {
const fields = {};
W3C_TRACE_CONTEXT_PROPAGATOR.inject(requestContext, fields, defaultTextMapSetter);
return fields;
}
function markSpanError(span, error) {
const rpcError = error instanceof Error ? JSONRPCErrorObjectSchema.safeParse(error.cause) : null;
if (rpcError?.success) {
span.setAttribute("rpc.response.status_code", String(rpcError.data.code));
span.setAttribute("error.type", String(rpcError.data.code));
} else if (error instanceof Error) span.setAttribute("error.type", error.name);
span.setStatus({ code: SpanStatusCode.ERROR });
}
function asError(error) {
return error instanceof Error ? error : new Error(String(error));
}
function rpcResponseTimeoutMs(method, params) {
if (method.startsWith("locator.")) {
const timeout = numericProperty(recordProperty(params, "options"), "timeout") ?? 2e4;
return timeout === 0 ? void 0 : timeout + RPC_RESPONSE_GRACE_MS;
}
let operationTimeoutMs;
switch (method) {
case StagehandMethods.stagehandAct.name:
case StagehandMethods.stagehandExtract.name:
case StagehandMethods.stagehandObserve.name:
case StagehandMethods.stagehandCallbackBatch.name:
case StagehandMethods.pageGoto.name:
case StagehandMethods.pageReload.name:
case StagehandMethods.pageGoBack.name:
case StagehandMethods.pageGoForward.name:
case StagehandMethods.pageScreenshot.name:
case StagehandMethods.pagePDF.name:
case StagehandMethods.pageSnapshot.name:
case StagehandMethods.pageWaitForSelector.name:
case StagehandMethods.pageWaitForFunction.name:
case StagehandMethods.pageWebMCPTools.name:
case StagehandMethods.pageWebMCPInvocationResult.name:
operationTimeoutMs = numericProperty(recordProperty(params, "options"), "timeout");
break;
case StagehandMethods.pageWaitForLoadState.name:
operationTimeoutMs = numericProperty(params, "timeout");
break;
case StagehandMethods.pageWaitForTimeout.name:
operationTimeoutMs = numericProperty(params, "ms");
break;
}
if (operationTimeoutMs !== void 0) {
if ((method === StagehandMethods.pagePDF.name || method === StagehandMethods.pageSnapshot.name || method === StagehandMethods.pageWaitForSelector.name || method === StagehandMethods.pageWaitForFunction.name) && operationTimeoutMs === 0) return void 0;
return RPC_RESPONSE_GRACE_MS + Math.max(0, operationTimeoutMs);
}
const defaultOperationTimeoutMs = DEFAULT_OPERATION_TIMEOUT_MS.get(method);
if (defaultOperationTimeoutMs !== void 0) return RPC_RESPONSE_GRACE_MS + defaultOperationTimeoutMs;
if (UNBOUNDED_BY_DEFAULT_METHODS.has(method)) return;
return RPC_RESPONSE_GRACE_MS;
}
function recordProperty(value, property) {
return typeof value === "object" && value !== null ? value[property] : void 0;
}
function numericProperty(value, property) {
const candidate = recordProperty(value, property);
return typeof candidate === "number" && Number.isFinite(candidate) ? candidate : void 0;
}
//#endregion
//#region src/browserbaseConnection.ts
const REGION_API_URLS = {
"us-west-2": "https://api.stagehand.browserbase.com",
"us-east-1": "https://api.use1.stagehand.browserbase.com",
"eu-central-1": "https://api.euc1.stagehand.browserbase.com",
"ap-southeast-1": "https://api.apse1.stagehand.browserbase.com"
};
/** Resolves Gateway/cache settings from validated options and claimed-browser metadata. */
function resolveBrowserbaseConnection(createConfig, workerInitMetadata) {
const { apiKey, browser } = {
apiKey: createConfig.apiKey,
...workerInitMetadata
};
if (!apiKey || !browser?.sessionId) return void 0;
const baseUrl = createConfig.apiUrl ?? REGION_API_URLS[browser.region ?? "us-west-2"];
return Object.freeze({
apiKey,
apiUrl: `${baseUrl.replace(/\/+$/, "")}/v1`,
sessionId: browser.sessionId
});
}
//#endregion
//#region src/browserbaseTransport.ts
const OPERATION_PATHS = {
"gateway.responses": "/llm/responses",
"cache.get": "/cache/get",
"cache.set": "/cache/set"
};
const OMITTED_REQUEST_HEADERS = [
"host",
"content-length",
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"proxy-connection",
"te",
"trailer",
"transfer-encoding",
"upgrade",
"forwarded",
"x-bb-api-key",
"x-bb-session-id"
];
function requestHeaders(input) {
for (const [name, value] of Object.entries(input)) {
validateHeaderName(name);
validateHeaderValue(name, value);
}
const headers = new Headers(input);
const omitted = /* @__PURE__ */ new Set([...OMITTED_REQUEST_HEADERS, ...(headers.get("connection") ?? "").split(",").map((name) => name.trim().toLowerCase())]);
const namesToRemove = [...headers.keys()].filter((name) => omitted.has(name) || name.startsWith("x-forwarded-"));
for (const name of namesToRemove) headers.delete(name);
return headers;
}
/** Executes one Browserbase HTTP attempt; the worker interprets the response. */
async function executeBrowserbaseRequest(connection, input, callerSignal) {
const params = BrowserbaseRequestParamsSchema.parse(input);
if (!connection) return {
type: "error",
reason: "unavailable",
message: "Browserbase connection is unavailable"
};
const deadline = params.remainingTimeoutMs === void 0 ? void 0 : performance.now() + params.remainingTimeoutMs;
let request;
try {
const headers = requestHeaders(params.headers);
if (!headers.has("content-type")) headers.set("content-type", "application/json");
headers.set("x-bb-api-key", connection.apiKey);
if (params.operation === "gateway.responses") {
headers.set("x-bb-session-id", connection.sessionId);
if (!headers.has("authorization")) headers.set("authorization", `Bearer ${connection.apiKey}`);
}
for (const [name, value] of headers) validateHeaderValue(name, value);
request = new Request(`${connection.apiUrl}${OPERATION_PATHS[params.operation]}`, {
method: "POST",
headers,
body: params.body,
redirect: "manual"
});
} catch {
throw new TypeError("Invalid Browserbase HTTP request");
}
const timeoutController = new AbortController();
const signal = callerSignal ? AbortSignal.any([callerSignal, timeoutController.signal]) : timeoutController.signal;
let timer;
if (deadline !== void 0) {
const tick = () => {
const remaining = deadline - performance.now();
if (remaining > 0) timer = setTimeout(tick, Math.min(2147483647, Math.ceil(remaining)));
else timeoutController.abort(new DOMException("Browserbase request timed out", "TimeoutError"));
};
tick();
}
try {
signal.throwIfAborted();
const response = await fetch(request, { signal });
const body = await response.text();
const responseHeaders = new Headers(response.headers);
responseHeaders.delete("content-encoding");
responseHeaders.delete("content-length");
return {
type: "response",
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(responseHeaders),
body
};
} catch {
if (signal.aborted) {
const timedOut = signal.reason instanceof Error && signal.reason.name === "TimeoutError";
return {
type: "error",
reason: timedOut ? "timeout" : "cancelled",
message: timedOut ? "Browserbase request timed out" : "Browserbase request cancelled"
};
}
return {
type: "error",
reason: "network",
message: "Browserbase HTTP request failed"
};
} finally {
if (timer !== void 0) clearTimeout(timer);
}
}
//#endregion
//#region src/browserbaseRpc.ts
/** Installs Browserbase request handling for one RPC connection. */
function registerBrowserbaseTransport(client, connection) {
const pending = /* @__PURE__ */ new Map();
const removeRequest = client.onRequest(StagehandMethods.browserbaseRequest, async (params, context) => {
if (pending.has(context.requestId)) throw new Error("Browserbase request ID is already in use");
const controller = new AbortController();
pending.set(context.requestId, controller);
try {
return await executeBrowserbaseRequest(connection, params, AbortSignal.any([context.signal, controller.signal]));
} finally {
pending.delete(context.requestId);
}
});
const removeNotification = client.onNotification((notification) => {
if (notification.method !== StagehandNotifications.browserbaseCancel.name) return;
const { requestId, reason } = notification.params;
pending.get(requestId)?.abort(new DOMException(reason === "timeout" ? "Browserbase request timed out" : "Browserbase request cancelled", reason === "timeout" ? "TimeoutError" : "AbortError"));
});
return () => {
removeRequest();
removeNotification();
for (const controller of pending.values()) controller.abort();
pending.clear();
};
}
//#endregion
//#region src/clientLocatorOptions.ts
function serializeClientLocator(locator, pageId, method) {
if (locator.descriptor.pageId !== pageId) throw new TypeError(`${method}(): locator must belong to the target page`);
const { selector, nth } = locator.descriptor;
return {
selector,
...nth === void 0 ? {} : { nth }
};
}
function serializeClientLocatorOptions(method, pageId, options) {
const { locator, ignoreLocators, ...rest } = options;
return {
...rest,
...locator ? { locator: serializeClientLocator(locator, pageId, method) } : {},
...ignoreLocators ? { ignoreLocators: ignoreLocators.map((ignoredLocator) => serializeClientLocator(ignoredLocator, pageId, method)) } : {}
};
}
//#endregion
//#region src/browser/index.ts
const stagehandBrowserBrand = Symbol("StagehandBrowser");
const browserHandleInternals = /* @__PURE__ */ new WeakMap();
var StagehandBrowserHandle = class {
constructor(provider, origin, internals, sessionId) {
this.provider = provider;
this.origin = origin;
this.sessionId = sessionId;
this[stagehandBrowserBrand] = true;
browserHandleInternals.set(this, internals);
}
get closed() {
return browserHandleInternals.get(this)?.terminalPromise !== void 0;
}
get context() {
const context = requireBrowserHandleInternals(this).context;
if (!context) throw new Error("Browser context is unavailable. Attach the browser with await Stagehand.create({ browser }).");
return context;
}
close() {
const internals = requireBrowserHandleInternals(this);
internals.terminalPromise ??= Promise.resolve().then(internals.close);
return internals.terminalPromise;
}
};
/** @internal */
function createStagehandBrowserHandle(options) {
return new StagehandBrowserHandle(options.provider, options.origin, {
claimed: false,
attachment: options.attachment,
close: options.close,
invalidate: options.invalidate ?? options.close
}, options.sessionId);
}
/** @internal */
function claimStagehandBrowserHandle(browser) {
const internals = requireBrowserHandleInternals(browser);
if (internals.terminalPromise) throw new Error("Cannot attach Stagehand to a closed browser");
if (internals.claimed) throw new Error("This browser is already attached to a Stagehand instance");
internals.claimed = true;
return internals.attachment;
}
/** @internal */
function invalidateStagehandBrowserHandle(browser) {
const internals = requireBrowserHandleInternals(browser);
internals.terminalPromise ??= Promise.resolve().then(internals.invalidate);
return internals.terminalPromise;
}
/** @internal */
function releaseStagehandBrowserHandle(browser) {
const internals = requireBrowserHandleInternals(browser);
internals.claimed = false;
}
/** @internal */
function attachStagehandBrowserContext(browser, context) {
const internals = requireBrowserHandleInternals(browser);
if (!internals.claimed) throw new Error("Cannot attach a browser context before Stagehand claims the browser");
if (internals.context) throw new Error("This browser already has a Stagehand context");
internals.context = context;
}
/** @internal */
function detachStagehandBrowserContext(browser) {
const internals = requireBrowserHandleInternals(browser);
internals.context = void 0;
}
function isStagehandBrowser(value) {
return typeof value === "object" && value !== null && browserHandleInternals.has(value);
}
function requireBrowserHandleInternals(browser) {
const internals = browserHandleInternals.get(browser);
if (!internals) throw new TypeError("browser must be created by localBrowser or browserbase");
return internals;
}
//#endregion
//#region src/clientSchemas.ts
/**
* TypeScript SDK-owned schemas. They extend protocol schemas with SDK-only values such as local/CDP
* connection options, JavaScript callbacks, and Page instances. Those values are consumed by the
* SDK and never cross the RPC boundary. Other language SDKs should follow the same pattern around
* the shared wire params.
*/
const LocalBrowserLaunchOptionsSchema = z.strictObject({
args: z.array(z.string()).optional(),
executablePath: z.string().optional(),
port: z.number().optional(),
userDataDir: z.string().optional(),
preserveUserDataDir: z.boolean().optional(),
headless: z.boolean().optional(),
devtools: z.boolean().optional(),
chromiumSandbox: z.boolean().optional(),
ignoreDefaultArgs: z.union([z.boolean(), z.array(z.string())]).optional(),
proxy: z.strictObject({
server: z.string(),
bypass: z.string().optional(),
username: z.string().optional(),
password: z.string().optional()
}).optional(),
locale: z.string().optional(),
viewport: z.strictObject({
width: z.number(),
height: z.number()
}).optional(),
deviceScaleFactor: z.number().optional(),
hasTouch: z.boolean().optional(),
ignoreHTTPSErrors: z.boolean().optional(),
downloadsPath: z.string().optional(),
acceptDownloads: z.boolean().optional(),
keepAlive: z.boolean().optional()
}).meta({ id: "LocalBrowserLaunchOptions" });
const CDPHeadersSchema = z.preprocess((value) => {
try {
if (typeof value !== "object" || value === null || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return null;
const entries = Object.entries(value);
if (!entries.every(([name, content]) => name.length > 0 && !/[^!#$%&'*+\-.^_`|~0-9A-Za-z]/.test(name) && typeof content === "string" && !/[^\t\x20-\x7e]/.test(content) && content === content.replace(/^[ \t]+|[ \t]+$/g, ""))) return null;
return Object.fromEntries(entries);
} catch {
return null;
}
}, z.record(z.string(), z.string(), { error: "headers must map valid HTTP header names to ASCII string values without leading or trailing spaces or tabs" }));
const LocalBrowserConnectOptionsSchema = z.strictObject({
cdpUrl: z.string().min(1),
/** Headers for client-side CDP discovery and WebSocket connections. */
headers: CDPHeadersSchema.optional(),
extensionId: z.string().optional(),
/**
* Browser-level CDP WebSocket URL dialed by the extension inside Chrome.
* Defaults to the client's URL. Set this when the client connects through
* a gateway the extension cannot or should not use.
*/
extensionCdpUrl: z.url({
protocol: /^wss?$/,
hostname: /.+/
}).refine((value) => {
try {
return new URL(value).port !== "0";
} catch {
return false;
}
}, { message: "Port must be between 1 and 65535" }).optional()
}).meta({ id: "LocalBrowserConnectOptions" });
const DEFAULT_BROWSERBASE_URL = "https://api.browserbase.com";
const BrowserbaseClientOptionsSchema = z.strictObject({
timeout: z.int().nonnegative().optional(),
maxRetries: z.int().nonnegative().optional(),
defaultHeaders: z.record(z.string(), z.string().nullable().optional()).optional(),
defaultQuery: z.record(z.string(), z.string().optional()).optional(),
fetch: z.custom((value) => typeof value === "function", { message: "fetch must be a function" }).optional()
});
/**
* Browserbase owns the session option surface. Keep this object loose so newly added SDK options
* pass through without requiring a Stagehand protocol or schema update.
*/
const BrowserbaseLaunchOptionsSchema = z.looseObject({
apiKey: z.string().min(1),
baseUrl: z.url().default(DEFAULT_BROWSERBASE_URL),
clientOptions: BrowserbaseClientOptionsSchema.optional(),
apiUrl: z.never().optional(),
type: z.never().optional()
}).meta({ id: "BrowserbaseLaunchOptions" });
const BrowserbaseConnectOptionsSchema = z.strictObject({
apiKey: z.string().min(1),
baseUrl: z.url().default(DEFAULT_BROWSERBASE_URL),
clientOptions: BrowserbaseClientOptionsSchema.optional(),
sessionId: z.string().min(1),
extensionId: z.string().optional()
}).meta({ id: "BrowserbaseConnectOptions" });
const BrowserbaseCredentialsShape = {
apiKey: z.string().min(1),
baseUrl: z.url().default(DEFAULT_BROWSERBASE_URL)
};
const BrowserbaseSearchOptionsSchema = z.strictObject({
...BrowserbaseCredentialsShape,
query: z.string().min(1).max(200),
numResults: z.int().min(1).max(25).optional()
}).meta({ id: "BrowserbaseSearchOptions" });
const BrowserbaseFetchOptionsSchema = z.strictObject({
...BrowserbaseCredentialsShape,
url: z.url(),
allowInsecureSsl: z.boolean().optional(),
allowRedirects: z.boolean().optional(),
format: z.enum([
"raw",
"json",
"markdown"
]).optional(),
proxies: z.boolean().optional(),
schema: z.record(z.string(), z.unknown()).optional()
}).refine(({ format, schema }) => schema === void 0 || format === "json", {
message: "schema is only valid when format is \"json\"",
path: ["schema"]
}).refine(({ format, schema }) => format !== "json" || schema !== void 0, {
message: "schema is required when format is \"json\"",
path: ["schema"]
}).meta({ id: "BrowserbaseFetchOptions" });
const BrowserbaseSearchResultSchema = z.object({
query: z.string(),
requestId: z.string(),
results: z.array(z.object({
id: z.string(),
title: z.string(),
url: z.string(),
author: z.string().nullish(),
favicon: z.string().nullish(),
image: z.string().nullish(),
publishedDate: z.string().nullish()
}))
}).meta({ id: "BrowserbaseSearchResult" });
const BrowserbaseFetchResultSchema = z.object({
id: z.string(),
content: z.union([z.string(), z.record(z.string(), z.unknown())]),
contentType: z.string(),
encoding: z.string(),
headers: z.record(z.string(), z.string()),
statusCode: z.number().int()
}).meta({ id: "BrowserbaseFetchResult" });
/** Data returned by the Browserbase SDK after creating a session. */
const BrowserbaseSessionCreateResultSchema = z.object({
id: z.string(),
connectUrl: z.string()
}).meta({ id: "BrowserbaseSessionCreateResult" });
/** Data returned by the Browserbase SDK when retrieving a session. */
const BrowserbaseSessionRetrieveResultSchema = z.object({
id: z.string(),
connectUrl: z.string().optional(),
region: BrowserbaseRegionSchema.optional()
}).meta({ id: "BrowserbaseSessionRetrieveResult" });
/** Normalized connection data shared by Browserbase launch and connect flows. */
const BrowserbaseSessionConnectionSchema = z.strictObject({
sessionId: z.string().trim().min(1),
cdpUrl: z.string().trim().min(1),
region: BrowserbaseRegionSchema.optional()
}).meta({ id: "BrowserbaseSessionConnection" });
const WebMCPToolsOptionsSchema = WebMCPToolsOptionsSchema$1.partial();
const WebMCPInvokeOptionsSchema = WebMCPInvokeOptionsSchema$1.partial();
const WebMCPResultOptionsSchema = WebMCPResultOptionsSchema$1;
/** An LLM callback implemented locally by the SDK consumer. It never crosses the wire. */
const ClientLLMSchema = z.strictObject({ generate: z.function({
input: [LLMGenerateParamsSchema],
output: z.promise(LLMGenerateResultSchema)
}) }).meta({ id: "ClientLLM" });
const StagehandClientLogLevelSchema = z.union([StagehandLogLevelSchema, z.literal("off")]).meta({ id: "StagehandClientLogLevel" });
const StagehandClientLogFormatSchema = z.enum(["pretty", "json"]).meta({ id: "StagehandClientLogFormat" });
const StagehandClientOnLogSchema = z.custom((value) => typeof value === "function", "onLog must be a function").meta({ id: "StagehandClientOnLog" });
const StagehandClientLoggingConfigSchema = z.strictObject({
level: StagehandClientLogLevelSchema.default("info"),
format: StagehandClientLogFormatSchema.default("pretty"),
onLog: StagehandClientOnLogSchema.optional()
}).meta({ id: "StagehandClientLoggingConfig" });
const StagehandClientActOptionsSchema = ActOptionsSchema.extend({
locator: z.instanceof(Locator).optional(),
ignoreLocators: z.array(z.instanceof(Locator)).optional(),
page: z.instanceof(Page).optional()
}).meta({ id: "StagehandClientActOptions" });
const StagehandClientObserveOptionsSchema = ObserveOptionsSchema.extend({
locator: z.instanceof(Locator).optional(),
ignoreLocators: z.array(z.instanceof(Locator)).optional(),
page: z.instanceof(Page).optional()
}).meta({ id: "StagehandClientObserveOptions" });
const StagehandClientExtractOptionsSchema = ExtractOptionsSchema.extend({
locator: z.instanceof(Locator).optional(),
ignoreLocators: z.array(z.instanceof(Locator)).optional(),
page: z.instanceof(Page).optional()
}).meta({ id: "StagehandClientExtractOptions" });
const StagehandClientCreateConfigSchema = z.strictObject(StagehandInitParamsSchema.shape).omit({
browserbase: true,
protocolVersion: true,
clientInfo: true,
browserCdpUrl: true,
logLevel: true,
browser: true
}).extend({
apiKey: z.string().min(1).optional().meta({ description: "Browserbase API key. A Browserbase browser uses the key supplied to its launch/connect factory; this option does not override that key." }),
apiUrl: z.url({ protocol: /^https?$/ }).refine((value) => !value.includes("?") && !value.includes("#"), "Stagehand apiUrl must not include a query string or fragment").refine((value) => !new URL(value).pathname.replace(/\/+$/, "").endsWith("/v1"), { message: "Stagehand apiUrl must be a service origin without /v1" }).optional().meta({ description: "Stagehand API base URL override for managed services such as Model Gateway and server-side caching" }),
model: z.union([ModelConfigSchema, ClientLLMSchema]).optional(),
logging: StagehandClientLoggingConfigSchema.default({
level: "info",
format: "pretty"
})
}).strict().meta({ id: "StagehandClientCreateConfig" });
const StagehandBrowserSchema = z.custom(isStagehandBrowser, "browser must be created by localBrowser or browserbase").meta({ id: "StagehandBrowser" });
const StagehandCreateOptionsSchema = StagehandClientCreateConfigSchema.extend({ browser: StagehandBrowserSchema }).meta({ id: "StagehandCreateOptions" });
//#endregion
//#region src/sdkIdentity.ts
const STAGEHAND_SDK_IDENTITY = {
name: "stagehand-sdk-ts",
language: "typescript",
version: "4.3.0"
};
const STAGEHAND_SDK_CLIENT_INFO = {
name: STAGEHAND_SDK_IDENTITY.name,
version: STAGEHAND_SDK_IDENTITY.version
};
const STAGEHAND_SESSION_METADATA = {
stagehand: "true",
stagehand_sdk_language: STAGEHAND_SDK_IDENTITY.language,
stagehand_sdk_version: STAGEHAND_SDK_IDENTITY.version
};
//#endregion
//#region src/extensionAssets.ts
const packageRoot = new URL("../", import.meta.url);
function nonEmpty(value) {
const trimmed = value?.trim();
return trimmed ? trimmed : void 0;
}
const STAGEHAND_EXTENSION_ARCHIVE_PATH = nonEmpty(process.env.STAGEHAND_EXTENSION_ARCHIVE_PATH) ?? fileURLToPath(new URL("dist/assets/stagehand-extension.zip", packageRoot));
const STAGEHAND_EXTENSION_DIRECTORY_PATH = nonEmpty(process.env.STAGEHAND_EXTENSION_DIRECTORY_PATH) ?? fileURLToPath(new URL("dist/extension/", packageRoot));
/**
* Returns the ZIP archive of the extension bundled with this SDK.
* Save or upload the bytes directly, or extract it to a directory for Chrome's
* Load unpacked option. No browser session is required.
*/
async function getExtensionArchive() {
return readFile(STAGEHAND_EXTENSION_ARCHIVE_PATH);
}
//#endregion
//#region src/browserbaseExtension.ts
function createBrowserbaseExtensionClient(apiKey, createSdk = (key) => new Browserbase({ apiKey: key })) {
const browserbase = createSdk(apiKey);
return {
async uploadExtension(archivePath) {
return { id: (await browserbase.extensions.create({ file: createReadStream(archivePath) }, { maxRetries: 0 })).id };
},
async deleteExtension(extensionId) {
await browserbase.extensions.delete(extensionId, { headers: { "Content-Type": null } });
}
};
}
/**
* Upload attempts and the pause between them. Many Stagehand sessions
* launching at once each upload the same archive; Browserbase rejects part
* of such a burst, and one rejection used to kill the session for good.
*/
const UPLOAD_ATTEMPTS = 4;
const UPLOAD_BACKOFF_MS = [
500,
1500,
4e3
];
async function provisionBrowserbaseExtension(client, archivePath = STAGEHAND_EXTENSION_ARCHIVE_PATH, options = {}) {
const attempts = options.attempts ?? UPLOAD_ATTEMPTS;
if (!Number.isInteger(attempts) || attempts < 1 || attempts > UPLOAD_ATTEMPTS) throw new Error(`attempts must be an integer between 1 and ${UPLOAD_ATTEMPTS}`);
const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
let uploaded;
let lastError;
let attemptsMade = 0;
for (let attempt = 0; attempt < attempts && uploaded === void 0; attempt += 1) {
attemptsMade += 1;
try {
uploaded = await client.uploadExtension(archivePath);
} catch (error) {
lastError = error;
if (!error || typeof error !== "object" || !("status" in error) || error.status !== 429) break;
if (attempt + 1 < attempts) await sleep(UPLOAD_BACKOFF_MS[Math.min(attempt, UPLOAD_BACKOFF_MS.length - 1)]);
}
}
if (uploaded === void 0) throw new Error(`Failed to upload the Stagehand extension to Browserbase after ${attemptsMade} attempt(s).`, { cause: lastError });
const extensionId = uploaded.id.trim();
if (extensionId.length === 0) throw new Error("Browserbase extension upload returned an empty extension ID");
let cleaned = false;
return {
extensionId,
async cleanup() {
if (cleaned) return;
await client.deleteExtension(extensionId);
cleaned = true;
}
};
}
//#endregion
//#region src/browser/browserbaseSession.ts
var BrowserbaseSessionError = class extends Error {
constructor(message) {
super(message);
this.name = "BrowserbaseSessionError";
}
};
function createBrowserbaseSessionClient(apiKey, baseUrl, clientOptions = {}, dependencies = {}) {
const browserbase = dependencies.browserbase ?? createBrowserbaseApiClient(apiKey, baseUrl, clientOptions);
const provisionExtension = dependencies.provisionExtension ?? provisionBrowserbaseExtension;
return {
async createSession(params) {
const extension = (params.extensionId ?? params.browserSettings?.extensionId) === void 0 ? await provisionExtension(browserbase) : void 0;
let session;
try {
session = await browserbase.createSession({
...params,
...extension === void 0 ? {} : { extensionId: extension.extensionId },
userMetadata: {
...params.userMetadata,
...STAGEHAND_SESSION_METADATA
}
});
} catch {
await extension?.cleanup().catch(() => void 0);
throw new BrowserbaseSessionError("Failed to create a Browserbase session");
}
const sessionId = session.id.trim();
const cdpUrl = session.connectUrl.trim();
if (sessionId.length === 0 || cdpUrl.length === 0) {
await cleanupInvalidSession(browserbase, sessionId, extension);
throw new Error(sessionId.length === 0 ? "Browserbase session creation returned an empty session ID" : "Browserbase session creation returned an empty connection URL");
}
let sessionReleased = false;
let extensionCleaned = extension === void 0;
return {
...BrowserbaseSessionConnectionSchema.parse({
sessionId,
cdpUrl
}),
async close() {
let releaseError;
if (!sessionReleased) try {
await browserbase.releaseSession(sessionId);
sessionReleased = true;
} catch (error) {
releaseError = error;
}
let extensionCleanupError;
if (!extensionCleaned && extension) try {
await extension.cleanup();
extensionCleaned = true;
} catch (error) {
extensionCleanupError = error;
}
if (releaseError) throw releaseError;
if (extensionCleanupError) throw extensionCleanupError;
}
};
},
async connectSession(sessionId) {
const normalizedSessionId = sessionId.trim();
if (normalizedSessionId.length === 0) throw new BrowserbaseSessionError("A Browserbase session ID is required");
let session;
try {
session = await browserbase.retrieveSession(normalizedSessionId);
} catch {
throw new BrowserbaseSessionError("Failed to retrieve the Browserbase session");
}
const cdpUrl = session.connectUrl?.trim();
if (!cdpUrl) throw new BrowserbaseSessionError("Browserbase session is not available for connection");
let sessionReleased = false;
const connection = BrowserbaseSessionConnectionSchema.parse({
sessionId: session.id.trim() || normalizedSessionId,
cdpUrl,
...session.region === void 0 ? {} : { region: session.region }
});
return {
...connection,
async close() {
if (sessionReleased) return;
await browserbase.releaseSession(connection.sessionId);
sessionReleased = true;
}
};
}
};
}
function createBrowserbaseApiClient(apiKey, baseUrl, clientOptions = {}, createSdk = (key, baseURL, options) => new Browserbase({
...options,
apiKey: key,
baseURL
})) {
const sdk = createSdk(apiKey, baseUrl, clientOptions);
return {
...createBrowserbaseExtensionClient(apiKey, () => sdk),
async createSession(params) {
const session = await sdk.sessions.create(params);
return BrowserbaseSessionCreateResultSchema.parse(session);
},
async retrieveSession(sessionId) {
const session = await sdk.sessions.retrieve(sessionId);
return BrowserbaseSessionRetrieveResultSchema.parse(session);
},
async releaseSession(sessionId) {
await sdk.sessions.update(sessionId, { status: "REQUEST_RELEASE" });
}
};
}
async function cleanupInvalidSession(browserbase, sessionId, extension) {
if (sessionId.length > 0) await browserbase.releaseSession(sessionId).catch(() => void 0);
await extension?.cleanup().catch(() => void 0);
}
//#endregion
//#region src/browser/browserbaseServices.ts
function createBrowserbaseServicesClient(apiKey, baseUrl, createSdk = (key, baseURL) => new Browserbase({
apiKey: key,
baseURL
})) {
const sdk = createSdk(apiKey, baseUrl);
return {
async search(params) {
return BrowserbaseSearchResultSchema.parse(await sdk.search.web(params));
},
async fetch(params) {
return BrowserbaseFetchResultSchema.parse(await sdk.fetchAPI.create(params));
}
};
}
//#endregion
//#region src/browser/localBrowser.ts
const CHROME_POLL_INTERVAL_MS = 100;
const CHROME_REQUEST_TIMEOUT_MS = 100;
const CHROME_TERMINATION_TIMEOUT_MS = 3e3;
const DEFAULT_CHROME_FLAGS = [
"--disable-features=Translate,OptimizationHints,MediaRouter,DialMediaRouteProvider,CalculateNativeWinOcclusion,InterestFeedContentSuggestions,CertificateTransparencyComponentUpdater,AutofillServerCommunication,PrivacySandboxSettings4,RenderDocument",
"--disable-component-extensions-with-background-pages",
"--disable-background-networking",
"--disable-component-update",
"--disable-client-side-phishing-detection",
"--disable-sync",
"--metrics-recording-only",
"--disable-default-apps",
"--mute-audio",
"--no-default-browser-check",
"--no-first-run",
"--disable-backgrounding-occluded-windows",
"--disable-renderer-backgrounding",
"--disable-background-timer-throttling",
"--disable-ipc-flooding-protection",
"--password-store=basic",
"--use-mock-keychain",
"--force-fieldtrials=*BackgroundTracing/default/",
"--disable-hang-monitor",
"--disable-prompt-on-repost",
"--disable-domain-reliability",
"--propagate-iph-for-testing",
"--enable-unsafe-extension-debugging",
"--remote-allow-origins=*",
"--enable-features=WebMCP,WebMCPTesting,DevToolsWebMCPSupport"
];
const defaultDependencies = {
platform: process.platform,
env: process.env,
getuid: process.getuid,
isExecutableFile,
mkdir,
mkdtemp,
rm,
spawnChrome: (executablePath, args, options) => spawn(executablePath, args, {
detached: options.detached,
env: options.env,
stdio: "ignore"
}),
fetch: globalThis.fetch,
findAvailablePort,
assertPortAvailable,
signalProcess: (pid, signal) => process.kill(pid, signal),
runTaskkill
};
async function launchLocalBrowser(options, signal) {
return await launchChrome(options, signal, defaultDependencies);
}
async function launchChrome(options, signal, dependencies) {
validateLocalBrowserOptions(options);
throwIfAborted(signal);
const chromePath = await findChromePath(options.executablePath, dependencies);
const port = await resolveChromePort(options.port, dependencies);
const { userDataDir, removeProfile } = await resolveChromeProfile(options, dependencies);
let child;
let close;
try {
throwIfAborted(signal);
const args = localBrowserChromeFlags(options, port, userDataDir, shouldDisableSandbox(options, dependencies));
child = dependencies.spawnChrome(chromePath, args, {
detached: dependencies.platform !== "win32",
env: dependencies.env
});
const monitor = monitorChromeProcess(child);
close = memoizedChromeClose(child, monitor, userDataDir, removeProfile, dependencies);
await abortable(monitor.spawned, signal);
const cdpUrl = `http://127.0.0.1:${port}`;
await waitForChrome(cdpUrl, monitor, signal, dependencies.fetch);
return {
cdpUrl,
close
};
} catch (error) {
if (close) try {
await close();
} catch (cleanupError) {
throw new AggregateError([error, cleanupError], "Chrome launch failed and browser cleanup also failed", { cause: error });
}
else if (removeProfile) try {
await dependencies.rm(userDataDir, {
force: true,
recursive: true
});
} catch (cleanupError) {
throw new AggregateError([error, cleanupError], "Chrome launch failed and profile cleanup also failed", { cause: error });
}
throw error;
}
}
async function resolveChromeProfile(options, dependencies) {
if (options.userDataDir !== void 0 && options.userDataDir !== "") {
await dependencies.mkdir(options.userDataDir, {
recursive: true,
mode: 448
});
return {
userDataDir: options.userDataDir,
removeProfile: false
};
}
return {
userDataDir: await dependencies.mkdtemp(path.join(tmpdir(), "stagehand-chrome-")),
removeProfile: options.preserveUserDataDir !== true
};
}
function localBrowserChromeFlags(options, port, userDataDir, disableSandbox) {
const ignoredDefaultArgs = options.ignoreDefaultArgs;
const ignoredFlags = new Set(Array.isArray(ignoredDefaultArgs) ? ignoredDefaultArgs : []);
const includeDefaults = ignoredDefaultArgs !== true;
const viewport = options.viewport ?? {
width: 1280,
height: 800
};
const windowSizeFlag = `--window-size=${viewport.width},${viewport.height}`;
return [
...includeDefaults ? selectedChromeFlags(DEFAULT_CHROME_FLAGS, ignoredFlags) : [],
...options.viewport !== void 0 || includeDefaults && !ignoredFlags.has(windowSizeFlag) ? [windowSizeFlag] : [],
`--remote-debugging-port=${port}`,
`--user-data-dir=${userDataDir}`,
...options.headless === true ? ["--headless"] : [],
...options.devtools ? ["--auto-open-devtools-for-tabs"] : [],
...disableSandbox ? ["--no-sandbox"] : [],
...options.proxy ? [`--proxy-server=${options.proxy.server}`] : [],
...options.proxy?.bypass ? [`--proxy-bypass-list=${options.proxy.bypass}`] : [],
...options.locale ? [`--lang=${options.locale}`] : [],
...options.deviceScaleFactor === void 0 ? [] : [`--force-device-scale-factor=${options.deviceScaleFactor}`],
...options.hasTouch === true ? ["--touch-events=enabled"] : [],
...options.ignoreHTTPSErrors === true ? ["--ignore-certificate-errors"] : [],
...options.args ?? [],
"about:blank"
];
}
function selectedChromeFlags(flags, ignoredFlags) {
return flags.filter((flag) => !ignoredFlags.has(flag));
}
function validateLocalBrowserOptions(options) {
if (options.port !== void 0 && (!Number.isInteger(options.port) || options.port < 1 || options.port > 65535)) throw new Error("Chrome port must be an integer between 1 and 65535");
if (options.viewport !== void 0 && (!Number.isInteger(options.viewport.width) || !Number.isInteger(options.viewport.height) || options.viewport.width <= 0 || options.viewport.height <= 0)) throw new Error("Chrome viewport dimensions must be positive integers");
if (options.deviceScaleFactor !== void 0 && (!Number.isFinite(options.deviceScaleFactor) || options.deviceScaleFactor <= 0)) throw new Error("Chrome device scale factor must be positive and finite");
if (options.proxy !== void 0) {
if (options.proxy.server.length === 0) throw new Error("Chrome proxy server is required");
if (options.proxy.username !== void 0 || options.proxy.password !== void 0) throw new Error("Authenticated local browser proxies are not supported yet");
}
}
async function findChromePath(explicitPath, dependencies) {
if (explicitPath !== void 0) {
if (await dependencies.isExecutableFile(explicitPath)) return explicitPath;
throw new Error(`Chrome executable ${JSON.stringify(explicitPath)} does not exist`);
}
const configuredPath = dependencies.env.CHROME_PATH;
if (configuredPath && await dependencies.isExecutableFile(configuredPath)) return configuredPath;
const candidates = chromeCandidates(dependencies.platform, dependencies.env);
for (const candidate of candidates) if (await dependencies.isExecutableFile(candidate)) return candidate;
throw new Error("Chrome installation not found; set CHROME_PATH");
}
function chromeCandidates(platform, env) {
if (platform === "darwin") return [
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
"/Applications/Chromium.app/Contents/MacOS/Chromium"
];
if (platform === "win32") {
const roots = [
env.LOCALAPPDATA,
env.PROGRAMFILES,
env["PROGRAMFILES(X86)"]
].filter((root) => Boolean(root));
const suffixes = [[
"Google",
"Chrome SxS",
"Application",
"chrome.exe"
], [
"Google",
"Chrome",
"Application",
"chrome.exe"
]];
return roots.flatMap((root) => suffixes.map((suffix) => path.win32.join(root, ...suffix)));
}
if (platform === "linux") {
const directories = (env.PATH ?? "").split(path.delimiter).filter(Boolean);
return [
"google-chrome-stable",
"google-chrome",
"chromium-browser",
"chromium"
].flatMap((name) => directories.map((directory) => path.join(directory, name)));
}
throw new Error(`Chrome launching is not supported on ${platform}`);
}
async function isExecutableFile(filePath) {
try {
if (!(await stat(filePath)).isFile()) return false;
if (process.platform !== "win32") await access(filePath, constants.X_OK);
return true;
} catch {
return false;
}
}
async function resolveChromePort(requestedPort, dependencies) {
if (requestedPort === void 0) return await dependencies.findAvailablePort();
await dependencies.assertPortAvailable(requestedPort);
return requestedPort;
}
async function findAvailablePort() {
return await inspectPort(0);
}
async function assertPortAvailable(port) {
try {
await inspectPort(port);
} catch (error) {
throw new Error(`Chrome debugging port ${port} is already in use`, { cause: error });
}
}
async function inspectPort(port) {
const server = createServer();
server.unref();
try {
return await new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(port, "127.0.0.1", () => {
const address = server.address();
if (typeof address === "object" && address !== null) resolve(address.port);
else reject(/* @__PURE__ */ new Error("Failed to resolve Chrome debugging port"));
});
});
} finally {
await new Promise((resolve, reject) => {
if (!server.listening) {
resolve();
return;
}
server.close((error) => error ? reject(error) : resolve());
});
}
}
function shouldDisableSandbox(options, dependencies) {
return Boolean(dependencies.env.CI) || options.chromiumSandbox === false || dependencies.platform === "linux" && dependencies.getuid?.() === 0;
}
function monitorChromeProcess(child) {
let currentExit;
let resolveSpawned;
let rejectSpawned;
let resolveExited;
const spawned = new Promise((resolve, reject) => {
resolveSpawned = resolve;
rejectSpawned = reject;
});
const exited = new Promise((resolve) => {
resolveExited = resolve;
});
child.once("spawn", resolveSpawned);
child.once("error", (error) => {
rejectSpawned(error);
if (!currentExit) {
currentExit = {
code: null,
signal: null,
error
};
resolveExited(currentExit);
}
});
child.once("exit", (code, signal) => {
if (!currentExit) {
currentExit = {
code,
signal
};
resolveExited(currentExit);
}
});
if (child.exitCode !== null || child.signalCode !== null) {
currentExit = {
code: child.exitCode,
signal: child.signalCode
};
resolveSpawned();
resolveExited(currentExit);
}
return {
spawned,
exited,
currentExit: () => currentExit
};
}
async function waitForChrome(cdpUrl, monitor, signal, fetchImplementation) {
while (true) {
throwIfAborted(signal);
const exited = monitor.currentExit();
if (exited) throw chromeExitedBeforeReadyError(exited);
const outcome = await abortable(Promise.race([chromeDebuggingReady(cdpUrl, signal, fetchImplementation).then((ready) => ({
kind: "ready",
ready
})), monitor.exited.then((exit) => ({
kind: "exit",
exit
}))]), signal);
if (outcome.kind === "exit") throw chromeExitedBeforeReadyError(outcome.exit);
if (outcome.ready) {
const exitAfterReadiness = monitor.currentExit();
if (exitAfterReadiness) throw chromeExitedBeforeReadyError(exitAfterReadiness);
return;
}
await abortable(Promise.race([abortableDelay(CHROME_POLL_INTERVAL_MS, signal), monitor.exited.then((exit) => {
throw chromeExitedBeforeReadyError(exit);
})]), signal);
}
}
async function chromeDebuggingReady(cdpUrl, signal, fetchImplementation) {
const requestController = new AbortController();
const timeoutId = setTimeout(() => requestController.abort(), CHROME_REQUEST_TIMEOUT_MS);
const requestSignal = signal ? AbortSignal.any([signal, requestController.signal]) : requestController.signal;
try {
const response = await fetchImplementation(`${cdpUrl.replace(/\/$/, "")}/json/version`, { signal: requestSignal });
if (!response.ok) return false;
const version = await response.json();
return typeof version === "object" && version !== null && "webSocketDebuggerUrl" in version && typeof version.webSocketDebuggerUrl === "string" && version.webSocketDebuggerUrl.trim().length > 0;
} catch {
throwIfAborted(signal);
return false;
} finally {
clearTimeout(timeoutId);
}
}
function chromeExitedBeforeReadyError(exit) {
if (exit.error) return new Error(`Chrome exited before its debugging port was ready: ${exit.error.message}`, { cause: exit.error });
const detail = exit.signal ? `signal ${exit.signal}` : `code ${exit.code ?? "unknown"}`;
return /* @__PURE__ */ new Error(`Chrome exited before its debugging port was ready with ${detail}`);
}
function memoizedChromeClose(child, monitor, userDataDir, removeProfile, dependencies) {
let closePromise;
return () => {
closePromise ??= closeChrome(child, monitor, userDataDir, removeProfile, dependencies);
return closePromise;
};
}
async function closeChrome(child, monitor, userDataDir, removeProfile, dependencies) {
let processError;
try {
await closeChromeProcess(child, monitor, dependencies);
} catch (error) {
processError = error;
}
let profileError;
if (removeProfile) try {
await dependencies.rm(userDataDir, {
force: true,
recursive: true
});
} catch (error) {
profileError = error;
}
if (processError && profileError) throw new AggregateError([processError, profileError], "Chrome termination and profile cleanup both failed");
if (processError) throw processError;
if (profileError) throw profileError;
}
async function closeChromeProcess(child, monitor, dependencies) {
if (monitor.currentExit()) return;
const pid = child.pid;
if (pid === void 0) return;
try {
await terminateChromeProcess(pid, false, dependencies);
} catch (error) {
if (!isFinishedProcessError(error)) throw error;
}
if (await waitForExit(monitor, CHROME_TERMINATION_TIMEOUT_MS)) return;
try {
await terminateChromeProcess(pid, true, dependencies);
} catch (error) {
if (!isFinishedProcessError(error)) throw error;
}
await monitor.exited;
}
async function terminateChromeProcess(pid, force, dependencies) {
if (dependencies.platform === "win32") {
await dependencies.runTaskkill(pid, force);
return;
}
dependencies.signalProcess(-pid, force ? "SIGKILL" : "SIGTERM");
}
async function waitForExit(monitor, timeoutMs) {
if (monitor.currentExit()) return true;
return await new Promise((resolve) => {
const timeoutId = setTimeout(() => resolve(false), timeoutMs);
monitor.exited.then(() => {
clearTimeout(timeoutId);
resolve(true);
});
});
}
function isFinishedProcessError(error) {
return typeof error === "object" && error !== null && "code" in error && (error.code === "ESRCH" || error.code === 128);
}
async function runTaskkill(pid, force) {
const taskkill = spawn("taskkill", [
"/PID",
String(pid),
"/T",
...force ? ["/F"] : []
], { stdio: "ignore" });
await new Promise((resolve, reject) => {
taskkill.once("error", reject);
taskkill.once("exit", (code) => {
if (code === 0) resolve();
else reject(Object.assign(/* @__PURE__ */ new Error(`taskkill exited with code ${code ?? "unknown"}`), { code }));
});
});
}
//#endregion
//#region src/timeouts.ts
const STAGEHAND_INIT_TIMEOUT_MS = 6e4;
async function withStagehandInitDeadline(operation) {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort(/* @__PURE__ */ new Error(`Stagehand initialization timed out after ${STAGEHAND_INIT_TIMEOUT_MS}ms`));
}, STAGEHAND_INIT_TIMEOUT_MS);
try {
return await abortable(operation(controller.signal), controller.signal);
} finally {
clearTimeout(timeoutId);
}
}
//#endregion
//#region src/browser/factories.ts
function createBrowserFactories(dependencies = {}) {
const launchLocal = dependencies.launchLocalBrowser ?? launchLocalBrowser;
const createBrowserbase = dependencies.createBrowserbaseSessionClient ?? createBrowserbaseSessionClient;
const createBrowserbaseServices = dependencies.createBrowserbaseServicesClient ?? createBrowserbaseServicesClient;
const connectCdp = dependencies.connectCdp ?? ((options) => CDPClient.connect(options));
return {
localBrowser: {
async launch(input = {}) {
const options = LocalBrowserLaunchOptionsSchema.parse(input);
if (options.acceptDownloads === true && options.downloadsPath === void 0) throw new Error("downloadsPath is required when acceptDownloads is true");
return await withStagehandInitDeadline(async (signal) => {
const launchPromise = launchLocal(options, signal);
let launched;
try {
launched = await abortable(launchPromise, signal);
} catch (error) {
if (signal.aborted && options.keepAlive !== true) launchPromise.then((lateBrowser) => lateBrowser.close()).catch(() => void 0);
throw error;
}
return await connectBrowser({
provider: "local",
origin: "launched",
source: {
cdpUrl: launched.cdpUrl,
keepAlive: options.keepAlive ?? false,
close: launched.close
},
connectCdp,
extension: { extensionDir: STAGEHAND_EXTENSION_DIRECTORY_PATH },
signal,
afterConnect: options.acceptDownloads === void 0 && options.downloadsPath === void 0 ? void 0 : async (cdpClient, commandSignal) => {
await cdpClient.sendCommand("Browser.setDownloadBehavior", {
behavior: options.acceptDownloads === false ? "deny" : "allow",
...options.downloadsPath === void 0 ? {} : { downloadPath: options.downloadsPath }
}, void 0, commandSignal);
},
workerInitMetadata: {}
});
});
},
async connect(options) {
const parsed = LocalBrowserConnectOptionsSchema.parse(options);
const source = {
cdpUrl: parsed.cdpUrl,
keepAlive: true
};
return await withStagehandInitDeadline(async (signal) => connectBrowser({
provider: "local",
origin: "connected",
source,
connectCdp,
extension: { localExtensionDir: STAGEHAND_EXTENSION_DIRECTORY_PATH },
signal,
headers: parsed.headers,
workerInitMetadata: { browserCdpUrl: parsed.extensionCdpUrl }
}));
}
},
browserbase: {
async launch(input) {
const { apiKey, baseUrl, clientOptions, ...sessionOptions } = BrowserbaseLaunchOptionsSchema.parse(input);
return await withStagehandInitDeadline(async (signal) => {
const sessionPromise = createBrowserbase(apiKey, baseUrl, clientOptions).createSession(sessionOptions);
let session;
try {
session = await abortable(sessionPromise, signal);
} catch (error) {
if (signal.aborted && sessionOptions.keepAlive !== true) sessionPromise.then((lateSession) => lateSession.close?.()).catch(() => void 0);
throw error;
}
return await connectBrowser({
provider: "browserbase",
origin: "launched",
source: {
cdpUrl: session.cdpUrl,
keepAlive: sessionOptions.keepAlive ?? false,
close: session.close
},
connectCdp,
extension: { preloadedExtension: true },
signal,
workerInitMetadata: {
apiKey,
browser: {
sessionId: session.sessionId,
...sessionOptions.region === void 0 ? {} : { region: sessionOptions.region }
}
}
});
});
},
async connect(input) {
const options = BrowserbaseConnectOptionsSchema.parse(input);
const client = createBrowserbase(options.apiKey, options.baseUrl, options.clientOptions);
if (!client.connectSession) throw new Error("Browserbase session connection is not supported by this client");
return await withStagehandInitDeadline(async (signal) => {
const session = await abortable(client.connectSession(options.sessionId), signal);
return await connectBrowser({
provider: "browserbase",
origin: "connected",
source: {
cdpUrl: session.cdpUrl,
keepAlive: true,
close: session.close
},
connectCdp,
extension: { preloadedExtension: true },
signal,
workerInitMetadata: {
apiKey: options.apiKey,
browser: {
sessionId: session.sessionId,
...session.region === void 0 ? {} : { region: session.region }
}
}
});
});
},
async search(input) {
const { apiKey, baseUrl, ...params } = BrowserbaseSearchOptionsSchema.parse(input);
return await createBrowserbaseServices(apiKey, baseUrl).search(params);
},
async fetch(input) {
const { apiKey, baseUrl, ...params } = BrowserbaseFetchOptionsSchema.parse(input);
return await createBrowserbaseServices(apiKey, baseUrl).fetch(params);
}
}
};
}
const factories = createBrowserFactories();
const localBrowser = factories.localBrowser;
const browserbase = factories.browserbase;
/** @internal */
function claimStagehandBrowser(browser) {
return claimStagehandBrowserHandle(browser);
}
/** @internal */
function releaseStagehandBrowser(browser) {
releaseStagehandBrowserHandle(browser);
}
/** @internal */
function invalidateStagehandBrowser(browser) {
return invalidateStagehandBrowserHandle(browser);
}
async function connectBrowser(options) {
const ownsSource = options.origin === "launched" && !options.source.keepAlive;
let cdpClient;
try {
const connectionPromise = options.connectCdp({
cdpUrl: options.source.cdpUrl,
...options.headers === void 0 ? {} : { headers: options.headers },
...options.extension,
serviceWorkerUrlIncludes: "service-worker.js",
signal: options.signal
});
try {
cdpClient = await abortable(connectionPromise, options.signal);
} catch (error) {
if (options.signal.aborted) connectionPromise.then((lateClient) => lateClient.close()).catch(() => void 0);
throw error;
}
await options.afterConnect?.(cdpClient, options.signal);
const connectedClient = cdpClient;
const invalidate = async () => {
connectedClient.close();
if (ownsSource) await closeSource(options.source);
};
const close = async () => {
try {
if (options.provider === "local" && options.origin === "connected") try {
await connectedClient.sendCommand("Browser.close");
} catch (error) {
if (!(error instanceof CDPConnectionClosedError)) throw error;
}
else await closeSource(options.source);
} finally {
connectedClient.close();
}
};
return createStagehandBrowserHandle({
provider: options.provider,
origin: options.origin,
sessionId: options.workerInitMetadata.browser?.sessionId,
attachment: {
cdpClient: connectedClient,
workerInitMetadata: options.workerInitMetadata
},
close,
invalidate
});
} catch (error) {
cdpClient?.close();
if (ownsSource || options.provider === "browserbase" && options.origin === "launched") try {
await closeSource(options.source);
} catch (cleanupError) {
throw new AggregateError([error, cleanupError], "Browser connection failed and browser cleanup also failed", { cause: error });
}
throw error;
}
}
async function closeSource(source) {
await source.close?.();
}
//#endregion
//#region src/batch.ts
/** Slack the client grants the executor to report its own `timeout` before giving up locally. */
const CALLBACK_BATCH_CLIENT_GRACE_MS = 15e3;
/**
* The batch round trip exceeded its client-side deadline. The executor may
* still be running the callback; the browser session should be treated as
* unresponsive rather than retried blindly.
*/
var StagehandBatchTimeoutError = class extends Error {
constructor(details, options) {
super(`stagehand.experimentalBatch() received no response within ${details.clientTimeout}ms (callback timeout ${details.timeout}ms)`, options);
this.name = "StagehandBatchTimeoutError";
this.timeout = details.timeout;
this.clientTimeout = details.clientTimeout;
}
};
//#endregion
//#region src/stagehand.ts
const isZodSchema = (value) => typeof value === "object" && value !== null && "parse" in value && typeof value.parse === "function" && "safeParse" in value && typeof value.safeParse === "function";
const MAX_TIMER_DELAY_MS = 2147483647;
const nativeFunctionSourcePattern = /^\s*function(?:\s+[^()]*)?\([^)]*\)\s*\{\s*\[native code\]\s*\}\s*$/;
var Stagehand = class Stagehand {
constructor(browserHandle, createConfig) {
this.browserHandle = browserHandle;
this.createConfig = createConfig;
this.isInitialized = false;
this.initRequestStarted = false;
}
static async create(input) {
const { browser, ...createConfig } = StagehandCreateOptionsSchema.parse(input);
const claimedBrowser = claimStagehandBrowser(browser);
const stagehand = new Stagehand(browser, createConfig);
let lifecycleSignal;
try {
await withStagehandInitDeadline((signal) => {
lifecycleSignal = signal;
return stagehand.initialize(claimedBrowser, signal);
});
return stagehand;
} catch (error) {
if (lifecycleSignal?.aborted || stagehand.initRequestStarted && !isDefinitiveRPCErrorResponse(error)) try {
await invalidateStagehandBrowser(browser);
} catch (cleanupError) {
throw new AggregateError([error, cleanupError], "Stagehand initialization failed ambiguously and browser invalidation also failed", { cause: error });
}
else releaseStagehandBrowser(browser);
throw error;
}
}
get browser() {
return this.browserHandle;
}
get initialized() {
return this.isInitialized;
}
async metrics() {
return this.connectedRpcClient.send(StagehandMethods.stagehandMetrics, {});
}
async experimentalBatch(callback, input, options = {}) {
if (typeof callback !== "function") throw new TypeError("stagehand.experimentalBatch() requires a callback function");
if (options === null || typeof options !== "object" || Array.isArray(options)) throw new TypeError("stagehand.experimentalBatch() options must be an object");
const parsedInput = input === void 0 ? void 0 : z.json().parse(input);
const timeout = options.timeout ?? 3e4;
if (!Number.isInteger(timeout) || timeout <= 0) throw new RangeError("stagehand.experimentalBatch() timeout must be a positive integer");
if (timeout > 2147473647) throw new RangeError(`stagehand.experimentalBatch() timeout must not exceed ${MAX_CALLBACK_BATCH_TIMEOUT_MS} milliseconds`);
const callbackSource = Function.prototype.toString.call(callback);
if (nativeFunctionSourcePattern.test(callbackSource)) throw new TypeError("stagehand.experimentalBatch() callback must be serializable JavaScript");
const clientTimeout = Math.min(timeout + CALLBACK_BATCH_CLIENT_GRACE_MS, MAX_TIMER_DELAY_MS);
let result;
try {
result = await this.connectedRpcClient.send(StagehandMethods.stagehandCallbackBatch, {
callbackSource,
...parsedInput === void 0 ? {} : { input: parsedInput },
options: {
...options.page ? { pageId: options.page.pageId } : {},
timeout
}
}, { responseTimeoutMs: clientTimeout });
} catch (error) {
if (error instanceof RPCResponseTimeoutError) throw new StagehandBatchTimeoutError({
timeout,
clientTimeout
}, { cause: error });
throw error;
}
return result.value;
}
async initialize(browser, signal) {
const createConfig = this.createConfig;
const browserbaseConnection = resolveBrowserbaseConnection(createConfig, browser.workerInitMetadata);
const rpcClient = new RPCClient(browser.cdpClient);
this.rpcClient = rpcClient;
try {
this.removeBrowserbaseTransport = registerBrowserbaseTransport(rpcClient, browserbaseConnection);
this.removeNotificationListener = rpcClient.onNotification((notification) => handleStagehandNotification(notification, createConfig.logging));
if (createConfig.model && "generate" in createConfig.model) {
const { generate } = createConfig.model;
this.removeClientLLMHandler = rpcClient.onRequest(StagehandMethods.llmGenerate, (params) => generate(params));
}
this.initRequestStarted = true;
await rpcClient.sendStagehandInit(stagehandCreateParamsForWorker(createConfig, browser, browserbaseConnection !== void 0), signal);
attachStagehandBrowserContext(this.browserHandle, new BrowserContext(rpcClient, () => this.browserHandle.close()));
} catch (error) {
this.removeBrowserbaseTransport?.();
this.removeBrowserbaseTransport = void 0;
this.removeClientLLMHandler?.();
this.removeClientLLMHandler = void 0;
this.removeNotificationListener?.();
this.removeNotificationListener = void 0;
rpcClient.close(new Error("Stagehand initialization failed", { cause: error }), { closeTransport: false });
this.rpcClient = void 0;
throw error;
}
this.isInitialized = true;
this.closePromise = void 0;
}
async act(instruction, options) {
const { page, ...clientOptions } = StagehandClientActOptionsSchema.parse(options ?? {});
const targetPage = page ?? await this.browser.context.activePage();
if (!targetPage) throw new Error("Stagehand has no active page.");
const protocolOptions = serializeClientLocatorOptions("act", targetPage.pageId, clientOptions);
return await this.connectedRpcClient.send(StagehandMethods.stagehandAct, {
pageId: targetPage.pageId,
instruction,
...options === void 0 ? {} : { options: protocolOptions }
});
}
async observe(instruction, options) {
const { page, ...clientOptions } = StagehandClientObserveOptionsSchema.parse(options ?? {});
const targetPage = page ?? await this.browser.context.activePage();
if (!targetPage) throw new Error("Stagehand has no active page.");
const protocolOptions = serializeClientLocatorOptions("observe", targetPage.pageId, clientOptions);
return await this.connectedRpcClient.send(StagehandMethods.stagehandObserve, {
pageId: targetPage.pageId,
...instruction === void 0 ? {} : { instruction },
...options === void 0 ? {} : { options: protocolOptions }
});
}
async extract(instruction, schema, options) {
const hasCustomSchema = isZodSchema(schema);
const resolvedSchema = hasCustomSchema ? schema : DefaultExtractDataSchema;
const resolvedOptions = hasCustomSchema ? options : schema;
const { page, ...clientOptions } = StagehandClientExtractOptionsSchema.parse(resolvedOptions ?? {});
const targetPage = page ?? await this.browser.context.activePage();
if (!targetPage) throw new Error("Stagehand has no active page.");
const protocolOptions = serializeClientLocatorOptions("extract", targetPage.pageId, clientOptions);
const response = await this.connectedRpcClient.send(StagehandMethods.stagehandExtract, {
pageId: targetPage.pageId,
instruction,
...hasCustomSchema ? { schema: z.json().parse(z.toJSONSchema(resolvedSchema)) } : {},
...resolvedOptions === void 0 ? {} : { options: protocolOptions }
});
return {
...response,
data: resolvedSchema.parse(response.data)
};
}
close() {
this.closePromise ??= (async () => {
let shouldReleaseBrowserClaim = !this.isInitialized;
try {
if (this.isInitialized) try {
await this.rpcClient?.send(StagehandMethods.stagehandClose, {});
shouldReleaseBrowserClaim = true;
} catch (error) {
if (!(error instanceof CDPConnectionClosedError)) throw error;
shouldReleaseBrowserClaim = true;
}
} finally {
this.removeBrowserbaseTransport?.();
this.removeBrowserbaseTransport = void 0;
this.removeClientLLMHandler?.();
this.removeClientLLMHandler = void 0;
this.removeNotificationListener?.();
this.removeNotificationListener = void 0;
this.rpcClient?.close(/* @__PURE__ */ new Error("Stagehand closed"), { closeTransport: false });
this.rpcClient = void 0;
detachStagehandBrowserContext(this.browserHandle);
if (shouldReleaseBrowserClaim) releaseStagehandBrowser(this.browserHandle);
this.isInitialized = false;
}
})();
return this.closePromise;
}
get connectedRpcClient() {
if (!this.isInitialized || !this.rpcClient) throw new Error("Stagehand is unavailable. Create a new instance with await Stagehand.create().");
return this.rpcClient;
}
};
function isDefinitiveRPCErrorResponse(error) {
return error instanceof Error && JSONRPCErrorObjectSchema.safeParse(error.cause).success;
}
function stagehandCreateParamsForWorker(createConfig, browser, hasBrowserbaseConnection) {
const { logging, model, apiKey: _apiKey, apiUrl: _apiUrl, ...protocolParams } = createConfig;
const protocolModel = model && "generate" in model ? { source: "client" } : model;
return StagehandInitParamsSchema.parse({
protocolVersion: STAGEHAND_PROTOCOL_VERSION,
clientInfo: STAGEHAND_SDK_CLIENT_INFO,
browserCdpUrl: browser.workerInitMetadata.browserCdpUrl ?? browser.cdpClient.webSocketDebuggerUrl,
logLevel: logging.level,
...protocolParams,
...browser.workerInitMetadata.browser === void 0 ? {} : { browser: browser.workerInitMetadata.browser },
...hasBrowserbaseConnection ? { browserbase: "client" } : {},
...protocolModel === void 0 ? {} : { model: protocolModel }
});
}
const LOG_LEVEL_PRIORITY = {
debug: 10,
info: 20,
warn: 30,
error: 40,
off: Number.POSITIVE_INFINITY
};
function handleStagehandNotification(notification, logging) {
if (notification.method !== StagehandNotifications.log.name) return;
const log = notification.params;
if (LOG_LEVEL_PRIORITY[log.level] < LOG_LEVEL_PRIORITY[logging.level]) return;
process.stderr.write(renderStagehandLog(log, logging.format) + "\n");
if (!logging.onLog) return;
try {
const result = logging.onLog(log);
if (result && typeof result === "object" && "then" in result) Promise.resolve(result).catch(reportOnLogError);
} catch (error) {
reportOnLogError(error);
}
}
function renderStagehandLog(log, format) {
if (format === "json") return JSON.stringify(log);
const data = Object.keys(log.data).length === 0 ? "" : ` ${JSON.stringify(log.data)}`;
return `[stagehand] ${log.level.toUpperCase()} ${log.message}${data}`;
}
function reportOnLogError(error) {
const message = error instanceof Error ? error.message : String(error);
process.stderr.write(`[stagehand] ERROR onLog callback failed: ${message}\n`);
}
//#endregion
export { BrowserClipboard, BrowserContext, BrowserbaseConnectOptionsSchema, BrowserbaseFetchOptionsSchema, BrowserbaseFetchResultSchema, BrowserbaseLaunchOptionsSchema, BrowserbaseSearchOptionsSchema, BrowserbaseSearchResultSchema, CALLBACK_BATCH_CLIENT_GRACE_MS, CDPConnectionClosedError, CDPSubscription, ClientLLMSchema, LocalBrowserConnectOptionsSchema, LocalBrowserLaunchOptionsSchema, Locator, Page, RPCResponseTimeoutError, Response, Stagehand, StagehandBatchTimeoutError, StagehandBrowserSchema, StagehandClientCreateConfigSchema, StagehandClientLogFormatSchema, StagehandClientLogLevelSchema, StagehandClientLoggingConfigSchema, StagehandCreateOptionsSchema, StagehandRuntimeIncompatibleError, WebMCPInvocation, WebMCPInvokeOptionsSchema, WebMCPResultOptionsSchema, WebMCPTool, WebMCPToolsOptionsSchema, browserbase, getExtensionArchive, localBrowser };
//# sourceMappingURL=index.mjs.map