@browserbasehq/stagehand 2.5.4 → 2.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -52,7 +52,7 @@ interface LLMTool {
52
52
  parameters: Record<string, unknown>;
53
53
  }
54
54
 
55
- declare const AvailableModelSchema: z.ZodEnum<["gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4o", "gpt-4o-mini", "gpt-4o-2024-08-06", "gpt-4.5-preview", "o1-preview", "claude-3-5-sonnet-latest", "claude-3-5-sonnet-20241022", "claude-3-5-sonnet-20240620", "claude-3-7-sonnet-latest", "claude-3-7-sonnet-20250219", "cerebras-llama-3.3-70b", "cerebras-llama-3.1-8b", "groq-llama-3.3-70b-versatile", "groq-llama-3.3-70b-specdec", "gemini-1.5-flash", "gemini-1.5-pro", "gemini-1.5-flash-8b", "gemini-2.0-flash-lite", "gemini-2.0-flash", "gemini-2.5-flash-preview-04-17", "gemini-2.5-pro-preview-03-25"]>;
55
+ declare const AvailableModelSchema: z.ZodEnum<["gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4o", "gpt-4o-mini", "gpt-4o-2024-08-06", "gpt-4.5-preview", "o1-preview", "claude-haiku-4-5", "claude-3-7-sonnet-latest", "claude-3-7-sonnet-20250219", "cerebras-llama-3.3-70b", "cerebras-llama-3.1-8b", "groq-llama-3.3-70b-versatile", "groq-llama-3.3-70b-specdec", "gemini-1.5-flash", "gemini-1.5-pro", "gemini-1.5-flash-8b", "gemini-2.0-flash-lite", "gemini-2.0-flash", "gemini-2.5-flash-preview-04-17", "gemini-2.5-pro-preview-03-25"]>;
56
56
  type AvailableModel = z.infer<typeof AvailableModelSchema> | string;
57
57
  type ModelProvider = "openai" | "anthropic" | "cerebras" | "groq" | "google" | "aisdk";
58
58
  type ClientOptions = ClientOptions$1 | ClientOptions$2;
@@ -796,6 +796,7 @@ declare class StagehandContext {
796
796
  private pageMap;
797
797
  private activeStagehandPage;
798
798
  private readonly frameIdMap;
799
+ private static readonly contextsWithInitScript;
799
800
  private constructor();
800
801
  private createStagehandPage;
801
802
  static init(context: BrowserContext$1, stagehand: Stagehand): Promise<StagehandContext>;
@@ -835,7 +836,6 @@ declare class StagehandPage {
835
836
  ordinalForFrameId(fid: string | undefined): number;
836
837
  encodeWithFrameId(fid: string | undefined, backendId: number): EncodedId;
837
838
  resetFrameOrdinals(): void;
838
- private ensureStagehandScript;
839
839
  /** Register the custom selector engine that pierces open/closed shadow roots. */
840
840
  private ensureStagehandSelectorEngine;
841
841
  /**
package/dist/index.js CHANGED
@@ -494,7 +494,7 @@ var StagehandFunctionName = /* @__PURE__ */ ((StagehandFunctionName2) => {
494
494
  })(StagehandFunctionName || {});
495
495
 
496
496
  // lib/version.ts
497
- var STAGEHAND_VERSION = "2.5.4";
497
+ var STAGEHAND_VERSION = "2.5.5";
498
498
 
499
499
  // types/stagehandErrors.ts
500
500
  var StagehandError = class extends Error {
@@ -2609,6 +2609,115 @@ var PUA_START = 57344;
2609
2609
  var PUA_END = 63743;
2610
2610
  var NBSP_CHARS = /* @__PURE__ */ new Set([160, 8239, 8199, 65279]);
2611
2611
  var WORLD_NAME = "stagehand-world";
2612
+ var DOM_DEPTH_ATTEMPTS = [-1, 256, 128, 64, 32, 16, 8, 4, 2, 1];
2613
+ var DESCRIBE_DEPTH_ATTEMPTS = [-1, 64, 32, 16, 8, 4, 2, 1];
2614
+ function isCborStackError(message) {
2615
+ return message.includes("CBOR: stack limit exceeded");
2616
+ }
2617
+ function shouldExpandNode(node) {
2618
+ var _a15, _b, _c;
2619
+ const declaredChildren = (_a15 = node.childNodeCount) != null ? _a15 : 0;
2620
+ const realizedChildren = (_c = (_b = node.children) == null ? void 0 : _b.length) != null ? _c : 0;
2621
+ return declaredChildren > realizedChildren;
2622
+ }
2623
+ function mergeDomNodes(target, source) {
2624
+ var _a15, _b, _c, _d;
2625
+ target.childNodeCount = (_a15 = source.childNodeCount) != null ? _a15 : target.childNodeCount;
2626
+ target.children = (_b = source.children) != null ? _b : target.children;
2627
+ target.shadowRoots = (_c = source.shadowRoots) != null ? _c : target.shadowRoots;
2628
+ target.contentDocument = (_d = source.contentDocument) != null ? _d : target.contentDocument;
2629
+ }
2630
+ function collectDomTraversalTargets(node) {
2631
+ const targets = [];
2632
+ if (node.children) targets.push(...node.children);
2633
+ if (node.shadowRoots) targets.push(...node.shadowRoots);
2634
+ if (node.contentDocument) targets.push(node.contentDocument);
2635
+ return targets;
2636
+ }
2637
+ function hydrateDomTree(session, root) {
2638
+ return __async(this, null, function* () {
2639
+ var _a15, _b;
2640
+ const stack = [root];
2641
+ const expandedNodeIds = /* @__PURE__ */ new Set();
2642
+ const expandedBackendIds = /* @__PURE__ */ new Set();
2643
+ let describeCalls = 0;
2644
+ while (stack.length) {
2645
+ const node = stack.pop();
2646
+ const nodeId = typeof node.nodeId === "number" && node.nodeId > 0 ? node.nodeId : void 0;
2647
+ const backendId = typeof node.backendNodeId === "number" && node.backendNodeId > 0 ? node.backendNodeId : void 0;
2648
+ const seenByNode = nodeId ? expandedNodeIds.has(nodeId) : false;
2649
+ const seenByBackend = !nodeId && backendId ? expandedBackendIds.has(backendId) : false;
2650
+ if (seenByNode || seenByBackend) continue;
2651
+ if (nodeId) expandedNodeIds.add(nodeId);
2652
+ else if (backendId) expandedBackendIds.add(backendId);
2653
+ const needsExpansion = shouldExpandNode(node);
2654
+ if (needsExpansion && (nodeId || backendId)) {
2655
+ const describeParamsBase = nodeId ? { nodeId } : { backendNodeId: backendId };
2656
+ let expanded = false;
2657
+ for (const depth of DESCRIBE_DEPTH_ATTEMPTS) {
2658
+ try {
2659
+ const described = yield session.send("DOM.describeNode", __spreadProps(__spreadValues({}, describeParamsBase), {
2660
+ depth,
2661
+ pierce: true
2662
+ }));
2663
+ mergeDomNodes(node, described.node);
2664
+ if (!nodeId && described.node.nodeId && described.node.nodeId > 0) {
2665
+ node.nodeId = described.node.nodeId;
2666
+ expandedNodeIds.add(described.node.nodeId);
2667
+ }
2668
+ describeCalls++;
2669
+ expanded = true;
2670
+ break;
2671
+ } catch (err) {
2672
+ const message = err instanceof Error ? err.message : String(err);
2673
+ if (isCborStackError(message)) {
2674
+ continue;
2675
+ }
2676
+ const identifier = (_a15 = nodeId != null ? nodeId : backendId) != null ? _a15 : "unknown";
2677
+ throw new StagehandDomProcessError(
2678
+ `Failed to expand DOM node ${identifier}: ${String(err)}`
2679
+ );
2680
+ }
2681
+ }
2682
+ if (!expanded) {
2683
+ const identifier = (_b = nodeId != null ? nodeId : backendId) != null ? _b : "unknown";
2684
+ throw new StagehandDomProcessError(
2685
+ `Unable to expand DOM node ${identifier} after describeNode depth retries`
2686
+ );
2687
+ }
2688
+ }
2689
+ for (const child of collectDomTraversalTargets(node)) {
2690
+ stack.push(child);
2691
+ }
2692
+ }
2693
+ return describeCalls;
2694
+ });
2695
+ }
2696
+ function getDomTreeWithFallback(session) {
2697
+ return __async(this, null, function* () {
2698
+ let lastCborMessage = "";
2699
+ for (const depth of DOM_DEPTH_ATTEMPTS) {
2700
+ try {
2701
+ const params = { depth, pierce: true };
2702
+ const { root } = yield session.send("DOM.getDocument", params);
2703
+ if (depth !== -1) {
2704
+ yield hydrateDomTree(session, root);
2705
+ }
2706
+ return root;
2707
+ } catch (err) {
2708
+ const message = err instanceof Error ? err.message : String(err);
2709
+ if (isCborStackError(message)) {
2710
+ lastCborMessage = message;
2711
+ continue;
2712
+ }
2713
+ throw err;
2714
+ }
2715
+ }
2716
+ throw new StagehandDomProcessError(
2717
+ lastCborMessage ? `CDP DOM.getDocument failed after adaptive depth retries: ${lastCborMessage}` : "CDP DOM.getDocument failed after adaptive depth retries."
2718
+ );
2719
+ });
2720
+ }
2612
2721
  function cleanText(input) {
2613
2722
  let out = "";
2614
2723
  let prevWasSpace = false;
@@ -2666,10 +2775,7 @@ function buildBackendIdMaps(experimental, sp, targetFrame) {
2666
2775
  session === (yield sp.getCDPClient()) ? void 0 : targetFrame
2667
2776
  );
2668
2777
  try {
2669
- const { root } = yield session.send("DOM.getDocument", {
2670
- depth: -1,
2671
- pierce: true
2672
- });
2778
+ const root = yield getDomTreeWithFallback(session);
2673
2779
  let startNode = root;
2674
2780
  let rootFid = targetFrame && (yield getCDPFrameId(sp, targetFrame));
2675
2781
  if (targetFrame && targetFrame !== sp.page.mainFrame() && session === (yield sp.getCDPClient())) {
@@ -3771,9 +3877,6 @@ var StagehandResponseParseError = class extends StagehandAPIError {
3771
3877
  }
3772
3878
  };
3773
3879
 
3774
- // lib/dom/build/scriptContent.ts
3775
- var scriptContent = '(() => {\n // lib/dom/elementCheckUtils.ts\n function isElementNode(node) {\n return node.nodeType === Node.ELEMENT_NODE;\n }\n function isTextNode(node) {\n return node.nodeType === Node.TEXT_NODE && Boolean(node.textContent?.trim());\n }\n\n // lib/dom/xpathUtils.ts\n function getParentElement(node) {\n return isElementNode(node) ? node.parentElement : node.parentNode;\n }\n function getCombinations(attributes, size) {\n const results = [];\n function helper(start, combo) {\n if (combo.length === size) {\n results.push([...combo]);\n return;\n }\n for (let i = start; i < attributes.length; i++) {\n combo.push(attributes[i]);\n helper(i + 1, combo);\n combo.pop();\n }\n }\n helper(0, []);\n return results;\n }\n function isXPathFirstResultElement(xpath, target) {\n try {\n const result = document.evaluate(\n xpath,\n document.documentElement,\n null,\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\n null\n );\n return result.snapshotItem(0) === target;\n } catch (error) {\n console.warn(`Invalid XPath expression: ${xpath}`, error);\n return false;\n }\n }\n function escapeXPathString(value) {\n if (value.includes("\'")) {\n if (value.includes(\'"\')) {\n return "concat(" + value.split(/(\'+)/).map((part) => {\n if (part === "\'") {\n return `"\'"`;\n } else if (part.startsWith("\'") && part.endsWith("\'")) {\n return `"${part}"`;\n } else {\n return `\'${part}\'`;\n }\n }).join(",") + ")";\n } else {\n return `"${value}"`;\n }\n } else {\n return `\'${value}\'`;\n }\n }\n async function generateXPathsForElement(element) {\n if (!element) return [];\n const [complexXPath, standardXPath, idBasedXPath] = await Promise.all([\n generateComplexXPath(element),\n generateStandardXPath(element),\n generatedIdBasedXPath(element)\n ]);\n return [standardXPath, ...idBasedXPath ? [idBasedXPath] : [], complexXPath];\n }\n async function generateComplexXPath(element) {\n const parts = [];\n let currentElement = element;\n while (currentElement && (isTextNode(currentElement) || isElementNode(currentElement))) {\n if (isElementNode(currentElement)) {\n const el = currentElement;\n let selector = el.tagName.toLowerCase();\n const attributePriority = [\n "data-qa",\n "data-component",\n "data-role",\n "role",\n "aria-role",\n "type",\n "name",\n "aria-label",\n "placeholder",\n "title",\n "alt"\n ];\n const attributes = attributePriority.map((attr) => {\n let value = el.getAttribute(attr);\n if (attr === "href-full" && value) {\n value = el.getAttribute("href");\n }\n return value ? { attr: attr === "href-full" ? "href" : attr, value } : null;\n }).filter((attr) => attr !== null);\n let uniqueSelector = "";\n for (let i = 1; i <= attributes.length; i++) {\n const combinations = getCombinations(attributes, i);\n for (const combo of combinations) {\n const conditions = combo.map((a) => `@${a.attr}=${escapeXPathString(a.value)}`).join(" and ");\n const xpath2 = `//${selector}[${conditions}]`;\n if (isXPathFirstResultElement(xpath2, el)) {\n uniqueSelector = xpath2;\n break;\n }\n }\n if (uniqueSelector) break;\n }\n if (uniqueSelector) {\n parts.unshift(uniqueSelector.replace("//", ""));\n break;\n } else {\n const parent = getParentElement(el);\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n (sibling) => sibling.tagName === el.tagName\n );\n const index = siblings.indexOf(el) + 1;\n selector += siblings.length > 1 ? `[${index}]` : "";\n }\n parts.unshift(selector);\n }\n }\n currentElement = getParentElement(currentElement);\n }\n const xpath = "//" + parts.join("/");\n return xpath;\n }\n async function generateStandardXPath(element) {\n const parts = [];\n while (element && (isTextNode(element) || isElementNode(element))) {\n let index = 0;\n let hasSameTypeSiblings = false;\n const siblings = element.parentElement ? Array.from(element.parentElement.childNodes) : [];\n for (let i = 0; i < siblings.length; i++) {\n const sibling = siblings[i];\n if (sibling.nodeType === element.nodeType && sibling.nodeName === element.nodeName) {\n index = index + 1;\n hasSameTypeSiblings = true;\n if (sibling.isSameNode(element)) {\n break;\n }\n }\n }\n if (element.nodeName !== "#text") {\n const tagName = element.nodeName.toLowerCase();\n const pathIndex = hasSameTypeSiblings ? `[${index}]` : "";\n parts.unshift(`${tagName}${pathIndex}`);\n }\n element = element.parentElement;\n }\n return parts.length ? `/${parts.join("/")}` : "";\n }\n async function generatedIdBasedXPath(element) {\n if (isElementNode(element) && element.id) {\n return `//*[@id=\'${element.id}\']`;\n }\n return null;\n }\n\n // types/stagehandErrors.ts\n var StagehandError = class extends Error {\n constructor(message) {\n super(message);\n this.name = this.constructor.name;\n }\n };\n var StagehandDomProcessError = class extends StagehandError {\n constructor(message) {\n super(`Error Processing Dom: ${message}`);\n }\n };\n\n // lib/dom/utils.ts\n function canElementScroll(elem) {\n if (typeof elem.scrollTo !== "function") {\n console.warn("canElementScroll: .scrollTo is not a function.");\n return false;\n }\n try {\n const originalTop = elem.scrollTop;\n elem.scrollTo({\n top: originalTop + 100,\n left: 0,\n behavior: "instant"\n });\n if (elem.scrollTop === originalTop) {\n throw new StagehandDomProcessError("scrollTop did not change");\n }\n elem.scrollTo({\n top: originalTop,\n left: 0,\n behavior: "instant"\n });\n return true;\n } catch (error) {\n console.warn("canElementScroll error:", error.message || error);\n return false;\n }\n }\n function getNodeFromXpath(xpath) {\n return document.evaluate(\n xpath,\n document.documentElement,\n null,\n XPathResult.FIRST_ORDERED_NODE_TYPE,\n null\n ).singleNodeValue;\n }\n function waitForElementScrollEnd(element, idleMs = 100) {\n return new Promise((resolve) => {\n let scrollEndTimer;\n const handleScroll = () => {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = window.setTimeout(() => {\n element.removeEventListener("scroll", handleScroll);\n resolve();\n }, idleMs);\n };\n element.addEventListener("scroll", handleScroll, { passive: true });\n handleScroll();\n });\n }\n\n // lib/dom/process.ts\n function getScrollableElements(topN) {\n const docEl = document.documentElement;\n const scrollableElements = [docEl];\n const allElements = document.querySelectorAll("*");\n for (const elem of allElements) {\n const style = window.getComputedStyle(elem);\n const overflowY = style.overflowY;\n const isPotentiallyScrollable = overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay";\n if (isPotentiallyScrollable) {\n const candidateScrollDiff = elem.scrollHeight - elem.clientHeight;\n if (candidateScrollDiff > 0 && canElementScroll(elem)) {\n scrollableElements.push(elem);\n }\n }\n }\n scrollableElements.sort((a, b) => b.scrollHeight - a.scrollHeight);\n if (topN !== void 0) {\n return scrollableElements.slice(0, topN);\n }\n return scrollableElements;\n }\n async function getScrollableElementXpaths(topN) {\n const scrollableElems = getScrollableElements(topN);\n const xpaths = [];\n for (const elem of scrollableElems) {\n const allXPaths = await generateXPathsForElement(elem);\n const firstXPath = allXPaths?.[0] || "";\n xpaths.push(firstXPath);\n }\n return xpaths;\n }\n (() => {\n const closedRoots = /* @__PURE__ */ new WeakMap();\n const nativeAttachShadow = Element.prototype.attachShadow;\n Element.prototype.attachShadow = function(init) {\n const root = nativeAttachShadow.call(this, init);\n if (init.mode === "closed") closedRoots.set(this, root);\n return root;\n };\n const backdoor = {\n getClosedRoot: (host) => closedRoots.get(host),\n queryClosed: (host, selector) => {\n const root = closedRoots.get(host);\n return root ? Array.from(root.querySelectorAll(selector)) : [];\n },\n xpathClosed: (host, xp) => {\n const root = closedRoots.get(host);\n if (!root) return [];\n const it = document.evaluate(\n xp,\n root,\n null,\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\n null\n );\n const out = [];\n for (let i = 0; i < it.snapshotLength; ++i) {\n const n = it.snapshotItem(i);\n if (n) out.push(n);\n }\n return out;\n }\n };\n if (!("__stagehand__" in window)) {\n Object.defineProperty(window, "__stagehand__", {\n value: backdoor,\n enumerable: false,\n writable: false,\n configurable: false\n });\n }\n })();\n window.getScrollableElementXpaths = getScrollableElementXpaths;\n window.getNodeFromXpath = getNodeFromXpath;\n window.waitForElementScrollEnd = waitForElementScrollEnd;\n})();\n';
3776
-
3777
3880
  // lib/StagehandPage.ts
3778
3881
  function getCurrentRootFrameId(session) {
3779
3882
  return __async(this, null, function* () {
@@ -3858,32 +3961,6 @@ var StagehandPage = class _StagehandPage {
3858
3961
  resetFrameOrdinals() {
3859
3962
  this.fidOrdinals = /* @__PURE__ */ new Map([[void 0, 0]]);
3860
3963
  }
3861
- ensureStagehandScript() {
3862
- return __async(this, null, function* () {
3863
- try {
3864
- const injected = yield this.rawPage.evaluate(
3865
- () => !!window.__stagehandInjected
3866
- );
3867
- if (injected) return;
3868
- const guardedScript = `if (!window.__stagehandInjected) { window.__stagehandInjected = true; ${scriptContent} }`;
3869
- yield this.rawPage.addInitScript({ content: guardedScript });
3870
- yield this.rawPage.evaluate(guardedScript);
3871
- } catch (err) {
3872
- if (!this.stagehand.isClosed) {
3873
- this.stagehand.log({
3874
- category: "dom",
3875
- message: "Failed to inject Stagehand helper script",
3876
- level: 1,
3877
- auxiliary: {
3878
- error: { value: err.message, type: "string" },
3879
- trace: { value: err.stack, type: "string" }
3880
- }
3881
- });
3882
- throw err;
3883
- }
3884
- }
3885
- });
3886
- }
3887
3964
  /** Register the custom selector engine that pierces open/closed shadow roots. */
3888
3965
  ensureStagehandSelectorEngine() {
3889
3966
  return __async(this, null, function* () {
@@ -4039,7 +4116,6 @@ var StagehandPage = class _StagehandPage {
4039
4116
  const value = target[prop];
4040
4117
  if (prop === "evaluate" || prop === "evaluateHandle" || prop === "$eval" || prop === "$$eval") {
4041
4118
  return (...args) => __async(this, null, function* () {
4042
- yield this.ensureStagehandScript();
4043
4119
  return value.apply(
4044
4120
  target,
4045
4121
  args
@@ -4646,8 +4722,17 @@ var StagehandPage = class _StagehandPage {
4646
4722
  }
4647
4723
  };
4648
4724
 
4725
+ // lib/dom/build/scriptContent.ts
4726
+ var scriptContent = '(() => {\n // lib/dom/process.ts\n (() => {\n const closedRoots = /* @__PURE__ */ new WeakMap();\n const nativeAttachShadow = Element.prototype.attachShadow;\n Element.prototype.attachShadow = function(init) {\n const root = nativeAttachShadow.call(this, init);\n if (init.mode === "closed") closedRoots.set(this, root);\n return root;\n };\n const backdoor = {\n getClosedRoot: (host) => closedRoots.get(host),\n queryClosed: (host, selector) => {\n const root = closedRoots.get(host);\n return root ? Array.from(root.querySelectorAll(selector)) : [];\n },\n xpathClosed: (host, xp) => {\n const root = closedRoots.get(host);\n if (!root) return [];\n const it = document.evaluate(\n xp,\n root,\n null,\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\n null\n );\n const out = [];\n for (let i = 0; i < it.snapshotLength; ++i) {\n const n = it.snapshotItem(i);\n if (n) out.push(n);\n }\n return out;\n }\n };\n if (!("__stagehand__" in window)) {\n Object.defineProperty(window, "__stagehand__", {\n value: backdoor,\n enumerable: false,\n writable: false,\n configurable: false\n });\n }\n })();\n})();\n';
4727
+
4649
4728
  // lib/StagehandContext.ts
4650
- var StagehandContext = class _StagehandContext {
4729
+ var stagehandInitScript = `
4730
+ if (!window.__stagehandInjected) {
4731
+ window.__stagehandInjected = true;
4732
+ ${scriptContent}
4733
+ }
4734
+ `;
4735
+ var _StagehandContext = class _StagehandContext {
4651
4736
  constructor(context, stagehand) {
4652
4737
  this.activeStagehandPage = null;
4653
4738
  this.frameIdMap = /* @__PURE__ */ new Map();
@@ -4706,6 +4791,10 @@ var StagehandContext = class _StagehandContext {
4706
4791
  }
4707
4792
  static init(context, stagehand) {
4708
4793
  return __async(this, null, function* () {
4794
+ if (!_StagehandContext.contextsWithInitScript.has(context)) {
4795
+ yield context.addInitScript({ content: stagehandInitScript });
4796
+ _StagehandContext.contextsWithInitScript.add(context);
4797
+ }
4709
4798
  const instance = new _StagehandContext(context, stagehand);
4710
4799
  context.on("page", (pwPage) => __async(null, null, function* () {
4711
4800
  yield instance.handleNewPlaywrightPage(pwPage);
@@ -4808,6 +4897,8 @@ var StagehandContext = class _StagehandContext {
4808
4897
  });
4809
4898
  }
4810
4899
  };
4900
+ _StagehandContext.contextsWithInitScript = /* @__PURE__ */ new WeakSet();
4901
+ var StagehandContext = _StagehandContext;
4811
4902
 
4812
4903
  // lib/api.ts
4813
4904
  var import_zod_to_json_schema = __toESM(require("zod-to-json-schema"));
@@ -20273,9 +20364,7 @@ var modelToProviderMap = {
20273
20364
  "gpt-4o-2024-08-06": "openai",
20274
20365
  "gpt-4.5-preview": "openai",
20275
20366
  "o1-preview": "openai",
20276
- "claude-3-5-sonnet-latest": "anthropic",
20277
- "claude-3-5-sonnet-20240620": "anthropic",
20278
- "claude-3-5-sonnet-20241022": "anthropic",
20367
+ "claude-haiku-4-5": "anthropic",
20279
20368
  "claude-3-7-sonnet-20250219": "anthropic",
20280
20369
  "claude-3-7-sonnet-latest": "anthropic",
20281
20370
  "cerebras-llama-3.3-70b": "cerebras",
@@ -24073,9 +24162,7 @@ var AvailableModelSchema = import_v315.z.enum([
24073
24162
  "gpt-4o-2024-08-06",
24074
24163
  "gpt-4.5-preview",
24075
24164
  "o1-preview",
24076
- "claude-3-5-sonnet-latest",
24077
- "claude-3-5-sonnet-20241022",
24078
- "claude-3-5-sonnet-20240620",
24165
+ "claude-haiku-4-5",
24079
24166
  "claude-3-7-sonnet-latest",
24080
24167
  "claude-3-7-sonnet-20250219",
24081
24168
  "cerebras-llama-3.3-70b",
@@ -8,6 +8,7 @@ export declare class StagehandContext {
8
8
  private pageMap;
9
9
  private activeStagehandPage;
10
10
  private readonly frameIdMap;
11
+ private static readonly contextsWithInitScript;
11
12
  private constructor();
12
13
  private createStagehandPage;
13
14
  static init(context: PlaywrightContext, stagehand: Stagehand): Promise<StagehandContext>;
@@ -30,7 +30,6 @@ export declare class StagehandPage {
30
30
  ordinalForFrameId(fid: string | undefined): number;
31
31
  encodeWithFrameId(fid: string | undefined, backendId: number): EncodedId;
32
32
  resetFrameOrdinals(): void;
33
- private ensureStagehandScript;
34
33
  /** Register the custom selector engine that pierces open/closed shadow roots. */
35
34
  private ensureStagehandSelectorEngine;
36
35
  /**
@@ -1 +1 @@
1
- export declare const scriptContent = "(() => {\n // lib/dom/elementCheckUtils.ts\n function isElementNode(node) {\n return node.nodeType === Node.ELEMENT_NODE;\n }\n function isTextNode(node) {\n return node.nodeType === Node.TEXT_NODE && Boolean(node.textContent?.trim());\n }\n\n // lib/dom/xpathUtils.ts\n function getParentElement(node) {\n return isElementNode(node) ? node.parentElement : node.parentNode;\n }\n function getCombinations(attributes, size) {\n const results = [];\n function helper(start, combo) {\n if (combo.length === size) {\n results.push([...combo]);\n return;\n }\n for (let i = start; i < attributes.length; i++) {\n combo.push(attributes[i]);\n helper(i + 1, combo);\n combo.pop();\n }\n }\n helper(0, []);\n return results;\n }\n function isXPathFirstResultElement(xpath, target) {\n try {\n const result = document.evaluate(\n xpath,\n document.documentElement,\n null,\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\n null\n );\n return result.snapshotItem(0) === target;\n } catch (error) {\n console.warn(`Invalid XPath expression: ${xpath}`, error);\n return false;\n }\n }\n function escapeXPathString(value) {\n if (value.includes(\"'\")) {\n if (value.includes('\"')) {\n return \"concat(\" + value.split(/('+)/).map((part) => {\n if (part === \"'\") {\n return `\"'\"`;\n } else if (part.startsWith(\"'\") && part.endsWith(\"'\")) {\n return `\"${part}\"`;\n } else {\n return `'${part}'`;\n }\n }).join(\",\") + \")\";\n } else {\n return `\"${value}\"`;\n }\n } else {\n return `'${value}'`;\n }\n }\n async function generateXPathsForElement(element) {\n if (!element) return [];\n const [complexXPath, standardXPath, idBasedXPath] = await Promise.all([\n generateComplexXPath(element),\n generateStandardXPath(element),\n generatedIdBasedXPath(element)\n ]);\n return [standardXPath, ...idBasedXPath ? [idBasedXPath] : [], complexXPath];\n }\n async function generateComplexXPath(element) {\n const parts = [];\n let currentElement = element;\n while (currentElement && (isTextNode(currentElement) || isElementNode(currentElement))) {\n if (isElementNode(currentElement)) {\n const el = currentElement;\n let selector = el.tagName.toLowerCase();\n const attributePriority = [\n \"data-qa\",\n \"data-component\",\n \"data-role\",\n \"role\",\n \"aria-role\",\n \"type\",\n \"name\",\n \"aria-label\",\n \"placeholder\",\n \"title\",\n \"alt\"\n ];\n const attributes = attributePriority.map((attr) => {\n let value = el.getAttribute(attr);\n if (attr === \"href-full\" && value) {\n value = el.getAttribute(\"href\");\n }\n return value ? { attr: attr === \"href-full\" ? \"href\" : attr, value } : null;\n }).filter((attr) => attr !== null);\n let uniqueSelector = \"\";\n for (let i = 1; i <= attributes.length; i++) {\n const combinations = getCombinations(attributes, i);\n for (const combo of combinations) {\n const conditions = combo.map((a) => `@${a.attr}=${escapeXPathString(a.value)}`).join(\" and \");\n const xpath2 = `//${selector}[${conditions}]`;\n if (isXPathFirstResultElement(xpath2, el)) {\n uniqueSelector = xpath2;\n break;\n }\n }\n if (uniqueSelector) break;\n }\n if (uniqueSelector) {\n parts.unshift(uniqueSelector.replace(\"//\", \"\"));\n break;\n } else {\n const parent = getParentElement(el);\n if (parent) {\n const siblings = Array.from(parent.children).filter(\n (sibling) => sibling.tagName === el.tagName\n );\n const index = siblings.indexOf(el) + 1;\n selector += siblings.length > 1 ? `[${index}]` : \"\";\n }\n parts.unshift(selector);\n }\n }\n currentElement = getParentElement(currentElement);\n }\n const xpath = \"//\" + parts.join(\"/\");\n return xpath;\n }\n async function generateStandardXPath(element) {\n const parts = [];\n while (element && (isTextNode(element) || isElementNode(element))) {\n let index = 0;\n let hasSameTypeSiblings = false;\n const siblings = element.parentElement ? Array.from(element.parentElement.childNodes) : [];\n for (let i = 0; i < siblings.length; i++) {\n const sibling = siblings[i];\n if (sibling.nodeType === element.nodeType && sibling.nodeName === element.nodeName) {\n index = index + 1;\n hasSameTypeSiblings = true;\n if (sibling.isSameNode(element)) {\n break;\n }\n }\n }\n if (element.nodeName !== \"#text\") {\n const tagName = element.nodeName.toLowerCase();\n const pathIndex = hasSameTypeSiblings ? `[${index}]` : \"\";\n parts.unshift(`${tagName}${pathIndex}`);\n }\n element = element.parentElement;\n }\n return parts.length ? `/${parts.join(\"/\")}` : \"\";\n }\n async function generatedIdBasedXPath(element) {\n if (isElementNode(element) && element.id) {\n return `//*[@id='${element.id}']`;\n }\n return null;\n }\n\n // types/stagehandErrors.ts\n var StagehandError = class extends Error {\n constructor(message) {\n super(message);\n this.name = this.constructor.name;\n }\n };\n var StagehandDomProcessError = class extends StagehandError {\n constructor(message) {\n super(`Error Processing Dom: ${message}`);\n }\n };\n\n // lib/dom/utils.ts\n function canElementScroll(elem) {\n if (typeof elem.scrollTo !== \"function\") {\n console.warn(\"canElementScroll: .scrollTo is not a function.\");\n return false;\n }\n try {\n const originalTop = elem.scrollTop;\n elem.scrollTo({\n top: originalTop + 100,\n left: 0,\n behavior: \"instant\"\n });\n if (elem.scrollTop === originalTop) {\n throw new StagehandDomProcessError(\"scrollTop did not change\");\n }\n elem.scrollTo({\n top: originalTop,\n left: 0,\n behavior: \"instant\"\n });\n return true;\n } catch (error) {\n console.warn(\"canElementScroll error:\", error.message || error);\n return false;\n }\n }\n function getNodeFromXpath(xpath) {\n return document.evaluate(\n xpath,\n document.documentElement,\n null,\n XPathResult.FIRST_ORDERED_NODE_TYPE,\n null\n ).singleNodeValue;\n }\n function waitForElementScrollEnd(element, idleMs = 100) {\n return new Promise((resolve) => {\n let scrollEndTimer;\n const handleScroll = () => {\n clearTimeout(scrollEndTimer);\n scrollEndTimer = window.setTimeout(() => {\n element.removeEventListener(\"scroll\", handleScroll);\n resolve();\n }, idleMs);\n };\n element.addEventListener(\"scroll\", handleScroll, { passive: true });\n handleScroll();\n });\n }\n\n // lib/dom/process.ts\n function getScrollableElements(topN) {\n const docEl = document.documentElement;\n const scrollableElements = [docEl];\n const allElements = document.querySelectorAll(\"*\");\n for (const elem of allElements) {\n const style = window.getComputedStyle(elem);\n const overflowY = style.overflowY;\n const isPotentiallyScrollable = overflowY === \"auto\" || overflowY === \"scroll\" || overflowY === \"overlay\";\n if (isPotentiallyScrollable) {\n const candidateScrollDiff = elem.scrollHeight - elem.clientHeight;\n if (candidateScrollDiff > 0 && canElementScroll(elem)) {\n scrollableElements.push(elem);\n }\n }\n }\n scrollableElements.sort((a, b) => b.scrollHeight - a.scrollHeight);\n if (topN !== void 0) {\n return scrollableElements.slice(0, topN);\n }\n return scrollableElements;\n }\n async function getScrollableElementXpaths(topN) {\n const scrollableElems = getScrollableElements(topN);\n const xpaths = [];\n for (const elem of scrollableElems) {\n const allXPaths = await generateXPathsForElement(elem);\n const firstXPath = allXPaths?.[0] || \"\";\n xpaths.push(firstXPath);\n }\n return xpaths;\n }\n (() => {\n const closedRoots = /* @__PURE__ */ new WeakMap();\n const nativeAttachShadow = Element.prototype.attachShadow;\n Element.prototype.attachShadow = function(init) {\n const root = nativeAttachShadow.call(this, init);\n if (init.mode === \"closed\") closedRoots.set(this, root);\n return root;\n };\n const backdoor = {\n getClosedRoot: (host) => closedRoots.get(host),\n queryClosed: (host, selector) => {\n const root = closedRoots.get(host);\n return root ? Array.from(root.querySelectorAll(selector)) : [];\n },\n xpathClosed: (host, xp) => {\n const root = closedRoots.get(host);\n if (!root) return [];\n const it = document.evaluate(\n xp,\n root,\n null,\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\n null\n );\n const out = [];\n for (let i = 0; i < it.snapshotLength; ++i) {\n const n = it.snapshotItem(i);\n if (n) out.push(n);\n }\n return out;\n }\n };\n if (!(\"__stagehand__\" in window)) {\n Object.defineProperty(window, \"__stagehand__\", {\n value: backdoor,\n enumerable: false,\n writable: false,\n configurable: false\n });\n }\n })();\n window.getScrollableElementXpaths = getScrollableElementXpaths;\n window.getNodeFromXpath = getNodeFromXpath;\n window.waitForElementScrollEnd = waitForElementScrollEnd;\n})();\n";
1
+ export declare const scriptContent = "(() => {\n // lib/dom/process.ts\n (() => {\n const closedRoots = /* @__PURE__ */ new WeakMap();\n const nativeAttachShadow = Element.prototype.attachShadow;\n Element.prototype.attachShadow = function(init) {\n const root = nativeAttachShadow.call(this, init);\n if (init.mode === \"closed\") closedRoots.set(this, root);\n return root;\n };\n const backdoor = {\n getClosedRoot: (host) => closedRoots.get(host),\n queryClosed: (host, selector) => {\n const root = closedRoots.get(host);\n return root ? Array.from(root.querySelectorAll(selector)) : [];\n },\n xpathClosed: (host, xp) => {\n const root = closedRoots.get(host);\n if (!root) return [];\n const it = document.evaluate(\n xp,\n root,\n null,\n XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,\n null\n );\n const out = [];\n for (let i = 0; i < it.snapshotLength; ++i) {\n const n = it.snapshotItem(i);\n if (n) out.push(n);\n }\n return out;\n }\n };\n if (!(\"__stagehand__\" in window)) {\n Object.defineProperty(window, \"__stagehand__\", {\n value: backdoor,\n enumerable: false,\n writable: false,\n configurable: false\n });\n }\n })();\n})();\n";
@@ -1,2 +1 @@
1
- export * from "./process";
2
- export * from "./utils";
1
+ import "./process";
@@ -1,17 +0,0 @@
1
- /**
2
- * Finds and returns a list of scrollable elements on the page,
3
- * ordered from the element with the largest scrollHeight to the smallest.
4
- *
5
- * @param topN Optional maximum number of scrollable elements to return.
6
- * If not provided, all found scrollable elements are returned.
7
- * @returns An array of HTMLElements sorted by descending scrollHeight.
8
- */
9
- export declare function getScrollableElements(topN?: number): HTMLElement[];
10
- /**
11
- * Calls getScrollableElements, then for each element calls generateXPaths,
12
- * and returns the first XPath for each.
13
- *
14
- * @param topN (optional) integer limit on how many scrollable elements to process
15
- * @returns string[] list of XPaths (1 for each scrollable element)
16
- */
17
- export declare function getScrollableElementXpaths(topN?: number): Promise<string[]>;
@@ -2,4 +2,4 @@
2
2
  * AUTO-GENERATED — DO NOT EDIT BY HAND
3
3
  * Run `pnpm run gen-version` to refresh.
4
4
  */
5
- export declare const STAGEHAND_VERSION: "2.5.4";
5
+ export declare const STAGEHAND_VERSION: "2.5.5";
@@ -51,6 +51,7 @@ export interface TreeResult {
51
51
  xpathMap: Record<EncodedId, string>;
52
52
  }
53
53
  export type DOMNode = {
54
+ nodeId?: number;
54
55
  backendNodeId?: number;
55
56
  nodeName?: string;
56
57
  children?: DOMNode[];
@@ -59,6 +60,7 @@ export type DOMNode = {
59
60
  nodeType: number;
60
61
  frameId?: string;
61
62
  isScrollable?: boolean;
63
+ childNodeCount?: number;
62
64
  };
63
65
  export type BackendIdMaps = {
64
66
  tagNameMap: Record<number, string>;
@@ -1,7 +1,7 @@
1
1
  import type { ClientOptions as AnthropicClientOptions } from "@anthropic-ai/sdk";
2
2
  import type { ClientOptions as OpenAIClientOptions } from "openai";
3
3
  import { z } from "zod/v3";
4
- export declare const AvailableModelSchema: z.ZodEnum<["gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4o", "gpt-4o-mini", "gpt-4o-2024-08-06", "gpt-4.5-preview", "o1-preview", "claude-3-5-sonnet-latest", "claude-3-5-sonnet-20241022", "claude-3-5-sonnet-20240620", "claude-3-7-sonnet-latest", "claude-3-7-sonnet-20250219", "cerebras-llama-3.3-70b", "cerebras-llama-3.1-8b", "groq-llama-3.3-70b-versatile", "groq-llama-3.3-70b-specdec", "gemini-1.5-flash", "gemini-1.5-pro", "gemini-1.5-flash-8b", "gemini-2.0-flash-lite", "gemini-2.0-flash", "gemini-2.5-flash-preview-04-17", "gemini-2.5-pro-preview-03-25"]>;
4
+ export declare const AvailableModelSchema: z.ZodEnum<["gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "o4-mini", "o3", "o3-mini", "o1", "o1-mini", "gpt-4o", "gpt-4o-mini", "gpt-4o-2024-08-06", "gpt-4.5-preview", "o1-preview", "claude-haiku-4-5", "claude-3-7-sonnet-latest", "claude-3-7-sonnet-20250219", "cerebras-llama-3.3-70b", "cerebras-llama-3.1-8b", "groq-llama-3.3-70b-versatile", "groq-llama-3.3-70b-specdec", "gemini-1.5-flash", "gemini-1.5-pro", "gemini-1.5-flash-8b", "gemini-2.0-flash-lite", "gemini-2.0-flash", "gemini-2.5-flash-preview-04-17", "gemini-2.5-pro-preview-03-25"]>;
5
5
  export type AvailableModel = z.infer<typeof AvailableModelSchema> | string;
6
6
  export type ModelProvider = "openai" | "anthropic" | "cerebras" | "groq" | "google" | "aisdk";
7
7
  export type ClientOptions = OpenAIClientOptions | AnthropicClientOptions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserbasehq/stagehand",
3
- "version": "2.5.4",
3
+ "version": "2.5.5",
4
4
  "description": "An AI web browsing framework focused on simplicity and extensibility.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -1,2 +0,0 @@
1
- export declare function isElementNode(node: Node): node is Element;
2
- export declare function isTextNode(node: Node): node is Text;
@@ -1,7 +0,0 @@
1
- /**
2
- * Tests if the element actually responds to .scrollTo(...)
3
- * and that scrollTop changes as expected.
4
- */
5
- export declare function canElementScroll(elem: HTMLElement): boolean;
6
- export declare function getNodeFromXpath(xpath: string): Node;
7
- export declare function waitForElementScrollEnd(element: HTMLElement, idleMs?: number): Promise<void>;
@@ -1,14 +0,0 @@
1
- /**
2
- * Escapes a string for use in an XPath expression.
3
- * Handles special characters, including single and double quotes.
4
- * @param value - The string to escape.
5
- * @returns The escaped string safe for XPath.
6
- */
7
- export declare function escapeXPathString(value: string): string;
8
- /**
9
- * Generates both a complicated XPath and a standard XPath for a given DOM element.
10
- * @param element - The target DOM element.
11
- * @param documentOverride - Optional document override.
12
- * @returns An object containing both XPaths.
13
- */
14
- export declare function generateXPathsForElement(element: ChildNode): Promise<string[]>;