@browserbasehq/orca 3.0.0-preview.8 → 3.0.1

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.js CHANGED
@@ -603,6 +603,19 @@ function captureHybridSnapshot(page, options) {
603
603
  const perFrameMaps = /* @__PURE__ */ new Map();
604
604
  const requestedFocus = (_b = options == null ? void 0 : options.focusSelector) == null ? void 0 : _b.trim();
605
605
  if (requestedFocus) {
606
+ const logScopeFallback = () => {
607
+ var _a2;
608
+ v3Logger({
609
+ message: `Unable to narrow scope with selector. Falling back to using full DOM`,
610
+ level: 1,
611
+ auxiliary: {
612
+ arguments: {
613
+ value: `selector: ${(_a2 = options == null ? void 0 : options.focusSelector) == null ? void 0 : _a2.trim()}`,
614
+ type: "string"
615
+ }
616
+ }
617
+ });
618
+ };
606
619
  try {
607
620
  let targetFrameId;
608
621
  let tailSelector;
@@ -641,7 +654,7 @@ function captureHybridSnapshot(page, options) {
641
654
  /*attemptOwnerLookup=*/
642
655
  sameSessionAsParent
643
656
  );
644
- const { outline, urlMap } = yield a11yForFrame(
657
+ const { outline, urlMap, scopeApplied } = yield a11yForFrame(
645
658
  owningSess,
646
659
  targetFrameId,
647
660
  {
@@ -663,7 +676,7 @@ function captureHybridSnapshot(page, options) {
663
676
  }
664
677
  }
665
678
  const combinedUrlMap2 = __spreadValues({}, urlMap);
666
- return {
679
+ const snapshot = {
667
680
  combinedTree: outline,
668
681
  combinedXpathMap: combinedXpathMap2,
669
682
  combinedUrlMap: combinedUrlMap2,
@@ -676,7 +689,12 @@ function captureHybridSnapshot(page, options) {
676
689
  }
677
690
  ]
678
691
  };
692
+ if (scopeApplied) {
693
+ return snapshot;
694
+ }
695
+ logScopeFallback();
679
696
  } catch (e) {
697
+ logScopeFallback();
680
698
  }
681
699
  }
682
700
  function buildSessionDomIndex(session, pierce2) {
@@ -1144,6 +1162,7 @@ function a11yForFrame(session, frameId, opts) {
1144
1162
  const enc = opts.encode(be);
1145
1163
  urlMap[enc] = url;
1146
1164
  }
1165
+ let scopeApplied = false;
1147
1166
  const nodesForOutline = yield (() => __async(null, null, function* () {
1148
1167
  var _a2, _b2, _c;
1149
1168
  const sel = (_a2 = opts.focusSelector) == null ? void 0 : _a2.trim();
@@ -1160,6 +1179,7 @@ function a11yForFrame(session, frameId, opts) {
1160
1179
  if (typeof be !== "number") return nodes;
1161
1180
  const target = nodes.find((n) => n.backendDOMNodeId === be);
1162
1181
  if (!target) return nodes;
1182
+ scopeApplied = true;
1163
1183
  const keep = /* @__PURE__ */ new Set([target.nodeId]);
1164
1184
  const queue = [target];
1165
1185
  while (queue.length) {
@@ -1181,7 +1201,7 @@ function a11yForFrame(session, frameId, opts) {
1181
1201
  const decorated = decorateRoles(nodesForOutline, opts);
1182
1202
  const { tree } = yield buildHierarchicalTree(decorated, opts);
1183
1203
  const simplified = tree.map((n) => formatTreeLine(n)).join("\n");
1184
- return { outline: simplified.trimEnd(), urlMap };
1204
+ return { outline: simplified.trimEnd(), urlMap, scopeApplied };
1185
1205
  });
1186
1206
  }
1187
1207
  function resolveObjectIdForXPath(session, xpath, frameId) {
@@ -1532,6 +1552,7 @@ var IFRAME_STEP_RE;
1532
1552
  var init_snapshot = __esm({
1533
1553
  "lib/v3/understudy/a11y/snapshot.ts"() {
1534
1554
  init_executionContextRegistry();
1555
+ init_logger();
1535
1556
  IFRAME_STEP_RE = /^iframe(?:\[\d+])?$/i;
1536
1557
  }
1537
1558
  });
@@ -3908,11 +3929,11 @@ var require_src = __commonJS({
3908
3929
  var require_is_docker = __commonJS({
3909
3930
  "../../node_modules/.pnpm/is-docker@2.2.1/node_modules/is-docker/index.js"(exports2, module2) {
3910
3931
  "use strict";
3911
- var fs7 = require("fs");
3932
+ var fs8 = require("fs");
3912
3933
  var isDocker;
3913
3934
  function hasDockerEnv() {
3914
3935
  try {
3915
- fs7.statSync("/.dockerenv");
3936
+ fs8.statSync("/.dockerenv");
3916
3937
  return true;
3917
3938
  } catch (_) {
3918
3939
  return false;
@@ -3920,7 +3941,7 @@ var require_is_docker = __commonJS({
3920
3941
  }
3921
3942
  function hasDockerCGroup() {
3922
3943
  try {
3923
- return fs7.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
3944
+ return fs8.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
3924
3945
  } catch (_) {
3925
3946
  return false;
3926
3947
  }
@@ -3939,7 +3960,7 @@ var require_is_wsl = __commonJS({
3939
3960
  "../../node_modules/.pnpm/is-wsl@2.2.0/node_modules/is-wsl/index.js"(exports2, module2) {
3940
3961
  "use strict";
3941
3962
  var os3 = require("os");
3942
- var fs7 = require("fs");
3963
+ var fs8 = require("fs");
3943
3964
  var isDocker = require_is_docker();
3944
3965
  var isWsl3 = () => {
3945
3966
  if (process.platform !== "linux") {
@@ -3952,7 +3973,7 @@ var require_is_wsl = __commonJS({
3952
3973
  return true;
3953
3974
  }
3954
3975
  try {
3955
- return fs7.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isDocker() : false;
3976
+ return fs8.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isDocker() : false;
3956
3977
  } catch (_) {
3957
3978
  return false;
3958
3979
  }
@@ -3965,6 +3986,342 @@ var require_is_wsl = __commonJS({
3965
3986
  }
3966
3987
  });
3967
3988
 
3989
+ // lib/v3/understudy/consoleMessage.ts
3990
+ function formatRemoteObject(obj) {
3991
+ var _a;
3992
+ if (!obj) return "";
3993
+ if ("value" in obj) {
3994
+ const value = obj.value;
3995
+ if (value === void 0) return "";
3996
+ if (typeof value === "string") return value;
3997
+ try {
3998
+ return JSON.stringify(value);
3999
+ } catch (e) {
4000
+ return String(value);
4001
+ }
4002
+ }
4003
+ if (obj.unserializableValue) return obj.unserializableValue;
4004
+ if (obj.description) return obj.description;
4005
+ return (_a = obj.type) != null ? _a : "";
4006
+ }
4007
+ var ConsoleMessage;
4008
+ var init_consoleMessage = __esm({
4009
+ "lib/v3/understudy/consoleMessage.ts"() {
4010
+ ConsoleMessage = class {
4011
+ constructor(event, pageRef) {
4012
+ this.event = event;
4013
+ this.pageRef = pageRef;
4014
+ }
4015
+ type() {
4016
+ return this.event.type;
4017
+ }
4018
+ text() {
4019
+ const args = this.args();
4020
+ if (!args.length) return "";
4021
+ return args.map((arg) => formatRemoteObject(arg)).filter((chunk) => chunk.length > 0).join(" ");
4022
+ }
4023
+ args() {
4024
+ return this.event.args ? [...this.event.args] : [];
4025
+ }
4026
+ location() {
4027
+ var _a, _b;
4028
+ const frame = (_b = (_a = this.event.stackTrace) == null ? void 0 : _a.callFrames) == null ? void 0 : _b[0];
4029
+ return {
4030
+ url: frame == null ? void 0 : frame.url,
4031
+ lineNumber: frame == null ? void 0 : frame.lineNumber,
4032
+ columnNumber: frame == null ? void 0 : frame.columnNumber
4033
+ };
4034
+ }
4035
+ page() {
4036
+ return this.pageRef;
4037
+ }
4038
+ timestamp() {
4039
+ return this.event.timestamp;
4040
+ }
4041
+ raw() {
4042
+ return this.event;
4043
+ }
4044
+ toString() {
4045
+ return this.text();
4046
+ }
4047
+ };
4048
+ }
4049
+ });
4050
+
4051
+ // lib/v3/understudy/response.ts
4052
+ function createDeferred() {
4053
+ let resolve3;
4054
+ let reject;
4055
+ const promise = new Promise((res, rej) => {
4056
+ resolve3 = res;
4057
+ reject = rej;
4058
+ });
4059
+ return { promise, resolve: resolve3, reject };
4060
+ }
4061
+ function normaliseHeaderName(name) {
4062
+ return name.toLowerCase();
4063
+ }
4064
+ function splitHeaderValues(value) {
4065
+ return value.split(/\r?\n/).map((part) => part.trim()).filter(Boolean);
4066
+ }
4067
+ function parseHeadersText(headersText) {
4068
+ if (!headersText) return [];
4069
+ const lines = headersText.split(/\r?\n/);
4070
+ const entries = [];
4071
+ for (const line of lines) {
4072
+ if (!line || line.startsWith("HTTP/")) continue;
4073
+ const index = line.indexOf(":");
4074
+ if (index === -1) continue;
4075
+ const name = line.slice(0, index).trim();
4076
+ const value = line.slice(index + 1).trim();
4077
+ entries.push({ name, value });
4078
+ }
4079
+ return entries;
4080
+ }
4081
+ var Response;
4082
+ var init_response = __esm({
4083
+ "lib/v3/understudy/response.ts"() {
4084
+ Response = class {
4085
+ /**
4086
+ * Build a response wrapper from the CDP notification associated with a
4087
+ * navigation. The constructor captures the owning page/session so follow-up
4088
+ * methods (body/text/json) can query CDP on-demand. The `response` payload is
4089
+ * the raw `Protocol.Network.Response` object emitted by Chrome.
4090
+ */
4091
+ constructor(params) {
4092
+ this.headersArrayCache = null;
4093
+ this.allHeadersCache = null;
4094
+ this.headerValuesMap = /* @__PURE__ */ new Map();
4095
+ this.finishedDeferred = createDeferred();
4096
+ this.finishedSettled = false;
4097
+ this.extraInfoHeaders = null;
4098
+ var _a;
4099
+ this.page = params.page;
4100
+ this.session = params.session;
4101
+ this.requestId = params.requestId;
4102
+ this.frameId = params.frameId;
4103
+ this.loaderId = params.loaderId;
4104
+ this.response = params.response;
4105
+ this.fromServiceWorkerFlag = params.fromServiceWorker;
4106
+ if (params.response.remoteIPAddress && params.response.remotePort !== void 0) {
4107
+ this.serverAddress = {
4108
+ ipAddress: params.response.remoteIPAddress,
4109
+ port: params.response.remotePort
4110
+ };
4111
+ } else {
4112
+ this.serverAddress = null;
4113
+ }
4114
+ this.headersObject = {};
4115
+ for (const [name, value] of Object.entries((_a = this.response.headers) != null ? _a : {})) {
4116
+ const lower = normaliseHeaderName(name);
4117
+ if (value === void 0) continue;
4118
+ const values = splitHeaderValues(String(value));
4119
+ this.headerValuesMap.set(lower, values);
4120
+ this.headersObject[lower] = values.join(", ");
4121
+ }
4122
+ }
4123
+ /** URL associated with the navigation request. */
4124
+ url() {
4125
+ return this.response.url;
4126
+ }
4127
+ /** HTTP status code reported by Chrome. */
4128
+ status() {
4129
+ return this.response.status;
4130
+ }
4131
+ /** Human-readable status text that accompanied the response. */
4132
+ statusText() {
4133
+ return this.response.statusText;
4134
+ }
4135
+ /** Convenience predicate that checks for 2xx statuses. */
4136
+ ok() {
4137
+ const status = this.status();
4138
+ return status >= 200 && status <= 299;
4139
+ }
4140
+ /** Returns the Stagehand frame object that initiated the navigation. */
4141
+ frame() {
4142
+ if (!this.frameId) return null;
4143
+ try {
4144
+ return this.page.frameForId(this.frameId);
4145
+ } catch (e) {
4146
+ return null;
4147
+ }
4148
+ }
4149
+ /** Indicates whether the response was serviced by a Service Worker. */
4150
+ fromServiceWorker() {
4151
+ return this.fromServiceWorkerFlag;
4152
+ }
4153
+ /**
4154
+ * Returns TLS security metadata when provided by the browser. In practice
4155
+ * this includes certificate issuer, protocol, and validity interval.
4156
+ */
4157
+ securityDetails() {
4158
+ return __async(this, null, function* () {
4159
+ var _a;
4160
+ return (_a = this.response.securityDetails) != null ? _a : null;
4161
+ });
4162
+ }
4163
+ /** Returns the resolved server address for the navigation when available. */
4164
+ serverAddr() {
4165
+ return __async(this, null, function* () {
4166
+ var _a;
4167
+ return (_a = this.serverAddress) != null ? _a : null;
4168
+ });
4169
+ }
4170
+ /**
4171
+ * Returns the response headers normalised to lowercase keys. Matches the
4172
+ * behaviour of Playwright's `headers()` by eliding duplicate header entries.
4173
+ */
4174
+ headers() {
4175
+ return __spreadValues({}, this.headersObject);
4176
+ }
4177
+ /**
4178
+ * Returns all headers including those only surfaced through
4179
+ * `responseReceivedExtraInfo` such as `set-cookie`. Values are reported as the
4180
+ * browser sends them (no further splitting or concatenation).
4181
+ */
4182
+ allHeaders() {
4183
+ return __async(this, null, function* () {
4184
+ var _a, _b;
4185
+ if (this.allHeadersCache) return __spreadValues({}, this.allHeadersCache);
4186
+ const source = (_b = (_a = this.extraInfoHeaders) != null ? _a : this.response.headers) != null ? _b : {};
4187
+ const map = {};
4188
+ for (const [name, value] of Object.entries(source)) {
4189
+ map[name] = String(value);
4190
+ }
4191
+ this.allHeadersCache = map;
4192
+ return __spreadValues({}, map);
4193
+ });
4194
+ }
4195
+ /** Returns a concatenated header string for the supplied header name. */
4196
+ headerValue(name) {
4197
+ return __async(this, null, function* () {
4198
+ const values = yield this.headerValues(name);
4199
+ if (!values.length) return null;
4200
+ return values.join(", ");
4201
+ });
4202
+ }
4203
+ /** Returns all values for a header (case-insensitive lookup). */
4204
+ headerValues(name) {
4205
+ return __async(this, null, function* () {
4206
+ var _a;
4207
+ const lower = normaliseHeaderName(name);
4208
+ if (this.extraInfoHeaders) {
4209
+ const raw = (_a = this.extraInfoHeaders[name]) != null ? _a : this.extraInfoHeaders[lower];
4210
+ if (raw !== void 0) {
4211
+ return splitHeaderValues(String(raw));
4212
+ }
4213
+ }
4214
+ const values = this.headerValuesMap.get(lower);
4215
+ return values ? [...values] : [];
4216
+ });
4217
+ }
4218
+ /**
4219
+ * Returns header entries preserving their original wire casing and ordering.
4220
+ * Falls back to the CDP object when the raw header text is unavailable.
4221
+ */
4222
+ headersArray() {
4223
+ return __async(this, null, function* () {
4224
+ var _a, _b;
4225
+ if (this.headersArrayCache) return [...this.headersArrayCache];
4226
+ const entriesFromText = parseHeadersText(this.extraInfoHeadersText);
4227
+ if (entriesFromText.length > 0) {
4228
+ this.headersArrayCache = entriesFromText;
4229
+ return [...entriesFromText];
4230
+ }
4231
+ const entries = [];
4232
+ const source = (_b = (_a = this.extraInfoHeaders) != null ? _a : this.response.headers) != null ? _b : {};
4233
+ for (const [name, value] of Object.entries(source)) {
4234
+ const values = splitHeaderValues(String(value));
4235
+ for (const val of values) {
4236
+ entries.push({ name, value: val });
4237
+ }
4238
+ }
4239
+ this.headersArrayCache = entries;
4240
+ return [...entries];
4241
+ });
4242
+ }
4243
+ /**
4244
+ * Requests the raw response body from Chrome DevTools Protocol. The method is
4245
+ * intentionally lazy because not every caller needs the payload, and CDP only
4246
+ * allows retrieving it once the response completes.
4247
+ */
4248
+ body() {
4249
+ return __async(this, null, function* () {
4250
+ const result = yield this.session.send(
4251
+ "Network.getResponseBody",
4252
+ { requestId: this.requestId }
4253
+ ).catch((error) => {
4254
+ throw new Error(`Failed to retrieve response body: ${String(error)}`);
4255
+ });
4256
+ if (result.base64Encoded) {
4257
+ return Buffer.from(result.body, "base64");
4258
+ }
4259
+ return Buffer.from(result.body, "utf-8");
4260
+ });
4261
+ }
4262
+ /** Decodes the response body as UTF-8 text. */
4263
+ text() {
4264
+ return __async(this, null, function* () {
4265
+ const buffer = yield this.body();
4266
+ return buffer.toString("utf-8");
4267
+ });
4268
+ }
4269
+ /** Parses the response body as JSON and throws if parsing fails. */
4270
+ json() {
4271
+ return __async(this, null, function* () {
4272
+ const text = yield this.text();
4273
+ try {
4274
+ return JSON.parse(text);
4275
+ } catch (error) {
4276
+ throw new Error(`Failed to parse JSON response: ${String(error)}`);
4277
+ }
4278
+ });
4279
+ }
4280
+ /**
4281
+ * Resolves once the underlying network request completes or fails. Mirrors
4282
+ * Playwright's behaviour by resolving to `null` on success and to an `Error`
4283
+ * instance when Chrome reports `Network.loadingFailed`.
4284
+ */
4285
+ finished() {
4286
+ return __async(this, null, function* () {
4287
+ return this.finishedDeferred.promise;
4288
+ });
4289
+ }
4290
+ /**
4291
+ * Internal helper invoked by the navigation tracker when CDP reports extra
4292
+ * header information. This keeps the cached header views in sync with the
4293
+ * richer metadata.
4294
+ */
4295
+ applyExtraInfo(event) {
4296
+ var _a;
4297
+ this.extraInfoHeaders = event.headers;
4298
+ this.extraInfoHeadersText = event.headersText;
4299
+ this.allHeadersCache = null;
4300
+ this.headersArrayCache = null;
4301
+ this.headersObject = {};
4302
+ this.headerValuesMap.clear();
4303
+ const source = (_a = event.headers) != null ? _a : {};
4304
+ for (const [name, value] of Object.entries(source)) {
4305
+ const lower = normaliseHeaderName(name);
4306
+ const segments = splitHeaderValues(String(value));
4307
+ this.headerValuesMap.set(lower, segments);
4308
+ this.headersObject[lower] = segments.join(", ");
4309
+ }
4310
+ }
4311
+ /** Marks the response as finished and resolves the `finished()` promise. */
4312
+ markFinished(error) {
4313
+ if (this.finishedSettled) return;
4314
+ this.finishedSettled = true;
4315
+ if (error) {
4316
+ this.finishedDeferred.resolve(error);
4317
+ } else {
4318
+ this.finishedDeferred.resolve(null);
4319
+ }
4320
+ }
4321
+ };
4322
+ }
4323
+ });
4324
+
3968
4325
  // lib/v3/understudy/frame.ts
3969
4326
  var Frame;
3970
4327
  var init_frame = __esm({
@@ -4084,12 +4441,32 @@ var init_frame = __esm({
4084
4441
  /** Page.captureScreenshot (frame-scoped session) */
4085
4442
  screenshot(options) {
4086
4443
  return __async(this, null, function* () {
4444
+ var _a;
4087
4445
  yield this.session.send("Page.enable");
4446
+ const format = (_a = options == null ? void 0 : options.type) != null ? _a : "png";
4088
4447
  const params = {
4089
- format: "png",
4448
+ format,
4449
+ fromSurface: true,
4090
4450
  captureBeyondViewport: options == null ? void 0 : options.fullPage
4091
4451
  };
4092
- if (options == null ? void 0 : options.clip) params.clip = __spreadProps(__spreadValues({}, options.clip), { scale: 1 });
4452
+ const clampScale = (value) => Math.min(2, Math.max(0.1, value));
4453
+ const normalizedScale = typeof (options == null ? void 0 : options.scale) === "number" ? clampScale(options.scale) : void 0;
4454
+ if (options == null ? void 0 : options.clip) {
4455
+ const clip = {
4456
+ x: options.clip.x,
4457
+ y: options.clip.y,
4458
+ width: options.clip.width,
4459
+ height: options.clip.height,
4460
+ scale: normalizedScale != null ? normalizedScale : 1
4461
+ };
4462
+ params.clip = clip;
4463
+ } else if (normalizedScale !== void 0 && normalizedScale !== 1) {
4464
+ params.scale = normalizedScale;
4465
+ }
4466
+ if (format === "jpeg" && typeof (options == null ? void 0 : options.quality) === "number") {
4467
+ const q = Math.round(options.quality);
4468
+ params.quality = Math.min(100, Math.max(0, q));
4469
+ }
4093
4470
  const { data } = yield this.session.send(
4094
4471
  "Page.captureScreenshot",
4095
4472
  params
@@ -4925,14 +5302,529 @@ var init_lifecycleWatcher = __esm({
4925
5302
  }
4926
5303
  });
4927
5304
 
5305
+ // lib/v3/understudy/navigationResponseTracker.ts
5306
+ var NavigationResponseTracker;
5307
+ var init_navigationResponseTracker = __esm({
5308
+ "lib/v3/understudy/navigationResponseTracker.ts"() {
5309
+ init_response();
5310
+ NavigationResponseTracker = class {
5311
+ /**
5312
+ * Create a tracker bound to a specific navigation command. The tracker begins
5313
+ * listening for network events immediately so it should be constructed before
5314
+ * the navigation request is dispatched.
5315
+ */
5316
+ constructor(params) {
5317
+ this.selectedRequestId = null;
5318
+ this.selectedResponse = null;
5319
+ this.acceptNextWithoutLoader = false;
5320
+ this.responseResolved = false;
5321
+ this.pendingResponsesByLoader = /* @__PURE__ */ new Map();
5322
+ this.pendingExtraInfo = /* @__PURE__ */ new Map();
5323
+ this.listeners = [];
5324
+ this.page = params.page;
5325
+ this.session = params.session;
5326
+ this.navigationCommandId = params.navigationCommandId;
5327
+ this.responsePromise = new Promise((resolve3) => {
5328
+ this.resolveResponse = (value) => {
5329
+ if (this.responseResolved) return;
5330
+ this.responseResolved = true;
5331
+ resolve3(value);
5332
+ };
5333
+ });
5334
+ this.installListeners();
5335
+ }
5336
+ /** Stop listening for CDP events and release any pending bookkeeping. */
5337
+ dispose() {
5338
+ for (const { event, handler } of this.listeners) {
5339
+ this.session.off(event, handler);
5340
+ }
5341
+ this.listeners.length = 0;
5342
+ this.pendingResponsesByLoader.clear();
5343
+ this.pendingExtraInfo.clear();
5344
+ }
5345
+ /**
5346
+ * Hint the tracker with the loader id returned by `Page.navigate`. Chrome only
5347
+ * emits this once the browser begins navigating, so we store early responses
5348
+ * and match them once the loader id is known.
5349
+ */
5350
+ setExpectedLoaderId(loaderId) {
5351
+ if (!loaderId) return;
5352
+ this.expectedLoaderId = loaderId;
5353
+ const pending = this.pendingResponsesByLoader.get(loaderId);
5354
+ if (pending) {
5355
+ this.pendingResponsesByLoader.delete(loaderId);
5356
+ this.selectResponse(pending);
5357
+ }
5358
+ }
5359
+ /**
5360
+ * Some navigation APIs (reload/history traversal) do not provide a loader id
5361
+ * up front. This flag instructs the tracker to accept the next qualifying
5362
+ * document response even if no loader id has been announced yet.
5363
+ */
5364
+ expectNavigationWithoutKnownLoader() {
5365
+ this.acceptNextWithoutLoader = true;
5366
+ }
5367
+ /**
5368
+ * Returns a promise that resolves with the matched response (or `null` when
5369
+ * no document response was observed).
5370
+ */
5371
+ navigationCompleted() {
5372
+ return __async(this, null, function* () {
5373
+ if (!this.responseResolved) {
5374
+ queueMicrotask(() => {
5375
+ if (!this.responseResolved) this.resolveResponse(null);
5376
+ });
5377
+ }
5378
+ return this.responsePromise;
5379
+ });
5380
+ }
5381
+ /** Expose the raw response promise (mainly for tests). */
5382
+ response() {
5383
+ return __async(this, null, function* () {
5384
+ return this.responsePromise;
5385
+ });
5386
+ }
5387
+ /** Register all CDP listeners relevant to navigation tracking. */
5388
+ installListeners() {
5389
+ this.addListener("Network.responseReceived", (event) => {
5390
+ this.onResponseReceived(event);
5391
+ });
5392
+ this.addListener("Network.responseReceivedExtraInfo", (event) => {
5393
+ this.onResponseReceivedExtraInfo(
5394
+ event
5395
+ );
5396
+ });
5397
+ this.addListener("Network.loadingFinished", (event) => {
5398
+ this.onLoadingFinished(event);
5399
+ });
5400
+ this.addListener("Network.loadingFailed", (event) => {
5401
+ this.onLoadingFailed(event);
5402
+ });
5403
+ }
5404
+ /** Attach a CDP listener and track it for later disposal. */
5405
+ addListener(event, handler) {
5406
+ this.session.on(event, handler);
5407
+ this.listeners.push({ event, handler });
5408
+ }
5409
+ /** Handle the initial response payload for document navigations. */
5410
+ onResponseReceived(event) {
5411
+ var _a;
5412
+ if (!this.page.isCurrentNavigationCommand(this.navigationCommandId)) return;
5413
+ if (!event || !event.response) return;
5414
+ if (event.type !== "Document") return;
5415
+ if (event.frameId !== this.page.mainFrameId()) return;
5416
+ const loaderId = (_a = event.loaderId) != null ? _a : "";
5417
+ if (this.acceptNextWithoutLoader) {
5418
+ this.acceptNextWithoutLoader = false;
5419
+ this.selectResponse(event);
5420
+ return;
5421
+ }
5422
+ if (this.expectedLoaderId) {
5423
+ if (loaderId && loaderId !== this.expectedLoaderId) {
5424
+ this.pendingResponsesByLoader.set(loaderId, event);
5425
+ return;
5426
+ }
5427
+ this.selectResponse(event);
5428
+ return;
5429
+ }
5430
+ if (loaderId) {
5431
+ this.pendingResponsesByLoader.set(loaderId, event);
5432
+ return;
5433
+ }
5434
+ this.selectResponse(event);
5435
+ }
5436
+ /** Merge auxiliary header information once Chrome exposes it. */
5437
+ onResponseReceivedExtraInfo(event) {
5438
+ var _a;
5439
+ if (!event || !event.requestId) return;
5440
+ if (this.selectedRequestId && event.requestId === this.selectedRequestId) {
5441
+ (_a = this.selectedResponse) == null ? void 0 : _a.applyExtraInfo(event);
5442
+ return;
5443
+ }
5444
+ this.pendingExtraInfo.set(event.requestId, event);
5445
+ }
5446
+ /** Resolve the response's finished promise when the request completes. */
5447
+ onLoadingFinished(event) {
5448
+ var _a;
5449
+ if (!event || !event.requestId) return;
5450
+ if (event.requestId !== this.selectedRequestId) return;
5451
+ (_a = this.selectedResponse) == null ? void 0 : _a.markFinished(null);
5452
+ }
5453
+ /** Resolve the response's finished promise with an error on failure. */
5454
+ onLoadingFailed(event) {
5455
+ var _a;
5456
+ if (!event || !event.requestId) return;
5457
+ if (event.requestId !== this.selectedRequestId) return;
5458
+ const errorText = event.errorText || "Navigation request failed";
5459
+ (_a = this.selectedResponse) == null ? void 0 : _a.markFinished(new Error(errorText));
5460
+ }
5461
+ /**
5462
+ * Create the `Response` wrapper for the chosen document response and
5463
+ * resolve awaiting consumers. Subsequent events flesh out the header/body
5464
+ * helpers and mark the request as finished.
5465
+ */
5466
+ selectResponse(event) {
5467
+ var _a, _b, _c, _d, _e, _f;
5468
+ if (event.loaderId) {
5469
+ this.pendingResponsesByLoader.delete(event.loaderId);
5470
+ }
5471
+ if (this.responseResolved) return;
5472
+ if (this.selectedResponse) return;
5473
+ const protocol = (_c = (_b = (_a = event.response) == null ? void 0 : _a.protocol) == null ? void 0 : _b.toLowerCase()) != null ? _c : "";
5474
+ const url = (_e = (_d = event.response) == null ? void 0 : _d.url) != null ? _e : "";
5475
+ const isDataUrl = protocol === "data" || url.startsWith("data:");
5476
+ const isAboutUrl = protocol === "about" || url.startsWith("about:");
5477
+ if (isDataUrl || isAboutUrl) {
5478
+ this.pendingExtraInfo.delete(event.requestId);
5479
+ this.selectedRequestId = null;
5480
+ this.selectedResponse = null;
5481
+ this.resolveResponse(null);
5482
+ return;
5483
+ }
5484
+ const response = new Response({
5485
+ page: this.page,
5486
+ session: this.session,
5487
+ requestId: event.requestId,
5488
+ frameId: event.frameId,
5489
+ loaderId: event.loaderId,
5490
+ response: event.response,
5491
+ fromServiceWorker: Boolean((_f = event.response) == null ? void 0 : _f.fromServiceWorker)
5492
+ });
5493
+ this.selectedRequestId = event.requestId;
5494
+ this.selectedResponse = response;
5495
+ const extraInfo = this.pendingExtraInfo.get(event.requestId);
5496
+ if (extraInfo) {
5497
+ response.applyExtraInfo(extraInfo);
5498
+ this.pendingExtraInfo.delete(event.requestId);
5499
+ }
5500
+ this.resolveResponse(response);
5501
+ }
5502
+ };
5503
+ }
5504
+ });
5505
+
5506
+ // lib/v3/understudy/screenshotUtils.ts
5507
+ function collectFramesForScreenshot(page) {
5508
+ const seen = /* @__PURE__ */ new Map();
5509
+ const main = page.mainFrame();
5510
+ seen.set(main.frameId, main);
5511
+ for (const frame of page.frames()) {
5512
+ seen.set(frame.frameId, frame);
5513
+ }
5514
+ return Array.from(seen.values());
5515
+ }
5516
+ function normalizeScreenshotClip(clip) {
5517
+ const x = Number(clip.x);
5518
+ const y = Number(clip.y);
5519
+ const width = Number(clip.width);
5520
+ const height = Number(clip.height);
5521
+ for (const [key, value] of Object.entries({ x, y, width, height })) {
5522
+ if (!Number.isFinite(value)) {
5523
+ throw new Error(`screenshot: clip.${key} must be a finite number`);
5524
+ }
5525
+ }
5526
+ if (width <= 0 || height <= 0) {
5527
+ throw new Error("screenshot: clip width/height must be positive");
5528
+ }
5529
+ return { x, y, width, height };
5530
+ }
5531
+ function computeScreenshotScale(page, mode) {
5532
+ return __async(this, null, function* () {
5533
+ if (mode !== "css") return void 0;
5534
+ try {
5535
+ const frame = page.mainFrame();
5536
+ const dpr = yield frame.evaluate(() => {
5537
+ const ratio = Number(window.devicePixelRatio || 1);
5538
+ return Number.isFinite(ratio) && ratio > 0 ? ratio : 1;
5539
+ }).catch(() => 1);
5540
+ const safeRatio = Number.isFinite(dpr) && dpr > 0 ? dpr : 1;
5541
+ return Math.min(2, Math.max(0.1, 1 / safeRatio));
5542
+ } catch (e) {
5543
+ return 1;
5544
+ }
5545
+ });
5546
+ }
5547
+ function setTransparentBackground(session) {
5548
+ return __async(this, null, function* () {
5549
+ yield session.send("Emulation.setDefaultBackgroundColorOverride", {
5550
+ color: { r: 0, g: 0, b: 0, a: 0 }
5551
+ }).catch(() => {
5552
+ });
5553
+ return () => __async(null, null, function* () {
5554
+ yield session.send("Emulation.setDefaultBackgroundColorOverride", {}).catch(() => {
5555
+ });
5556
+ });
5557
+ });
5558
+ }
5559
+ function applyStyleToFrames(frames, css, label) {
5560
+ return __async(this, null, function* () {
5561
+ const trimmed = css.trim();
5562
+ if (!trimmed) return () => __async(null, null, function* () {
5563
+ });
5564
+ const token = `__v3_style_${label}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
5565
+ yield Promise.all(
5566
+ frames.map(
5567
+ (frame) => frame.evaluate(
5568
+ ({ css: css2, token: token2 }) => {
5569
+ try {
5570
+ const doc = document;
5571
+ if (!doc) return;
5572
+ const style = doc.createElement("style");
5573
+ style.setAttribute("data-stagehand-style", token2);
5574
+ style.textContent = css2;
5575
+ const parent = doc.head || doc.documentElement || doc.body;
5576
+ parent == null ? void 0 : parent.appendChild(style);
5577
+ } catch (e) {
5578
+ }
5579
+ },
5580
+ { css: trimmed, token }
5581
+ ).catch(() => {
5582
+ })
5583
+ )
5584
+ );
5585
+ return () => __async(null, null, function* () {
5586
+ yield Promise.all(
5587
+ frames.map(
5588
+ (frame) => frame.evaluate((token2) => {
5589
+ try {
5590
+ const doc = document;
5591
+ if (!doc) return;
5592
+ const nodes = doc.querySelectorAll(
5593
+ `[data-stagehand-style="${token2}"]`
5594
+ );
5595
+ nodes.forEach((node) => node.remove());
5596
+ } catch (e) {
5597
+ }
5598
+ }, token).catch(() => {
5599
+ })
5600
+ )
5601
+ );
5602
+ });
5603
+ });
5604
+ }
5605
+ function disableAnimations(frames) {
5606
+ return __async(this, null, function* () {
5607
+ const css = `
5608
+ *,
5609
+ *::before,
5610
+ *::after {
5611
+ animation-delay: 0s !important;
5612
+ animation-duration: 0s !important;
5613
+ animation-iteration-count: 1 !important;
5614
+ animation-play-state: paused !important;
5615
+ transition-property: none !important;
5616
+ transition-duration: 0s !important;
5617
+ transition-delay: 0s !important;
5618
+ }`;
5619
+ const cleanup = yield applyStyleToFrames(frames, css, "animations");
5620
+ yield Promise.all(
5621
+ frames.map(
5622
+ (frame) => frame.evaluate(() => {
5623
+ var _a, _b, _c, _d, _e;
5624
+ try {
5625
+ const animations = typeof document.getAnimations === "function" ? document.getAnimations() : [];
5626
+ for (const animation of animations) {
5627
+ try {
5628
+ const details = (_b = (_a = animation.effect) == null ? void 0 : _a.getComputedTiming) == null ? void 0 : _b.call(_a);
5629
+ if (details && details.iterations !== Infinity) {
5630
+ (_c = animation.finish) == null ? void 0 : _c.call(animation);
5631
+ } else {
5632
+ (_d = animation.cancel) == null ? void 0 : _d.call(animation);
5633
+ }
5634
+ } catch (e) {
5635
+ (_e = animation.cancel) == null ? void 0 : _e.call(animation);
5636
+ }
5637
+ }
5638
+ } catch (e) {
5639
+ }
5640
+ }).catch(() => {
5641
+ })
5642
+ )
5643
+ );
5644
+ return cleanup;
5645
+ });
5646
+ }
5647
+ function hideCaret(frames) {
5648
+ return __async(this, null, function* () {
5649
+ const css = `
5650
+ input,
5651
+ textarea,
5652
+ [contenteditable],
5653
+ [contenteditable=""],
5654
+ [contenteditable="true"],
5655
+ [contenteditable="plaintext-only"],
5656
+ *:focus {
5657
+ caret-color: transparent !important;
5658
+ }`;
5659
+ return applyStyleToFrames(frames, css, "caret");
5660
+ });
5661
+ }
5662
+ function applyMaskOverlays(locators, color) {
5663
+ return __async(this, null, function* () {
5664
+ var _a;
5665
+ const rectsByFrame = /* @__PURE__ */ new Map();
5666
+ for (const locator of locators) {
5667
+ try {
5668
+ const info = yield resolveMaskRect(locator);
5669
+ if (!info) continue;
5670
+ const list = (_a = rectsByFrame.get(info.frame)) != null ? _a : [];
5671
+ list.push(info.rect);
5672
+ rectsByFrame.set(info.frame, list);
5673
+ } catch (e) {
5674
+ }
5675
+ }
5676
+ if (rectsByFrame.size === 0) {
5677
+ return () => __async(null, null, function* () {
5678
+ });
5679
+ }
5680
+ const token = `__v3_mask_${Date.now()}_${Math.random().toString(36).slice(2)}`;
5681
+ yield Promise.all(
5682
+ Array.from(rectsByFrame.entries()).map(
5683
+ ([frame, rects]) => frame.evaluate(
5684
+ ({ rects: rects2, color: color2, token: token2 }) => {
5685
+ try {
5686
+ const doc = document;
5687
+ if (!doc) return;
5688
+ const root = doc.documentElement || doc.body;
5689
+ if (!root) return;
5690
+ for (const rect of rects2) {
5691
+ const el = doc.createElement("div");
5692
+ el.setAttribute("data-stagehand-mask", token2);
5693
+ el.style.position = "absolute";
5694
+ el.style.left = `${rect.x}px`;
5695
+ el.style.top = `${rect.y}px`;
5696
+ el.style.width = `${rect.width}px`;
5697
+ el.style.height = `${rect.height}px`;
5698
+ el.style.backgroundColor = color2;
5699
+ el.style.pointerEvents = "none";
5700
+ el.style.zIndex = "2147483647";
5701
+ el.style.opacity = "1";
5702
+ el.style.mixBlendMode = "normal";
5703
+ root.appendChild(el);
5704
+ }
5705
+ } catch (e) {
5706
+ }
5707
+ },
5708
+ { rects, color, token }
5709
+ ).catch(() => {
5710
+ })
5711
+ )
5712
+ );
5713
+ return () => __async(null, null, function* () {
5714
+ yield Promise.all(
5715
+ Array.from(rectsByFrame.keys()).map(
5716
+ (frame) => frame.evaluate((token2) => {
5717
+ try {
5718
+ const doc = document;
5719
+ if (!doc) return;
5720
+ const nodes = doc.querySelectorAll(
5721
+ `[data-stagehand-mask="${token2}"]`
5722
+ );
5723
+ nodes.forEach((node) => node.remove());
5724
+ } catch (e) {
5725
+ }
5726
+ }, token).catch(() => {
5727
+ })
5728
+ )
5729
+ );
5730
+ });
5731
+ });
5732
+ }
5733
+ function resolveMaskRect(locator) {
5734
+ return __async(this, null, function* () {
5735
+ const frame = locator.getFrame();
5736
+ const session = frame.session;
5737
+ let objectId = null;
5738
+ try {
5739
+ const resolved = yield locator.resolveNode();
5740
+ objectId = resolved.objectId;
5741
+ const result = yield session.send(
5742
+ "Runtime.callFunctionOn",
5743
+ {
5744
+ objectId,
5745
+ functionDeclaration: `function() {
5746
+ if (!this || typeof this.getBoundingClientRect !== 'function') return null;
5747
+ const rect = this.getBoundingClientRect();
5748
+ if (!rect) return null;
5749
+ const style = window.getComputedStyle(this);
5750
+ if (!style) return null;
5751
+ if (style.visibility === 'hidden' || style.display === 'none') return null;
5752
+ if (rect.width <= 0 || rect.height <= 0) return null;
5753
+ return {
5754
+ x: rect.left + window.scrollX,
5755
+ y: rect.top + window.scrollY,
5756
+ width: rect.width,
5757
+ height: rect.height,
5758
+ };
5759
+ }`,
5760
+ returnByValue: true
5761
+ }
5762
+ );
5763
+ if (result.exceptionDetails) {
5764
+ return null;
5765
+ }
5766
+ const rect = result.result.value;
5767
+ if (!rect) return null;
5768
+ const { x, y, width, height } = rect;
5769
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
5770
+ return null;
5771
+ }
5772
+ return { frame, rect: { x, y, width, height } };
5773
+ } catch (e) {
5774
+ return null;
5775
+ } finally {
5776
+ if (objectId) {
5777
+ yield session.send("Runtime.releaseObject", { objectId }).catch(() => {
5778
+ });
5779
+ }
5780
+ }
5781
+ });
5782
+ }
5783
+ function runScreenshotCleanups(cleanups) {
5784
+ return __async(this, null, function* () {
5785
+ for (let i = cleanups.length - 1; i >= 0; i -= 1) {
5786
+ const fn = cleanups[i];
5787
+ if (!fn) continue;
5788
+ try {
5789
+ const result = fn();
5790
+ if (result && typeof result.then === "function") {
5791
+ yield result;
5792
+ }
5793
+ } catch (e) {
5794
+ }
5795
+ }
5796
+ });
5797
+ }
5798
+ function withScreenshotTimeout(timeoutMs, task) {
5799
+ return __async(this, null, function* () {
5800
+ if (!timeoutMs || timeoutMs <= 0) return task();
5801
+ let timer = null;
5802
+ const timeoutPromise = new Promise((_, reject) => {
5803
+ timer = setTimeout(() => {
5804
+ reject(new Error(`screenshot: timeout of ${timeoutMs}ms exceeded`));
5805
+ }, timeoutMs);
5806
+ });
5807
+ try {
5808
+ return yield Promise.race([task(), timeoutPromise]);
5809
+ } finally {
5810
+ if (timer) clearTimeout(timer);
5811
+ }
5812
+ });
5813
+ }
5814
+ var init_screenshotUtils = __esm({
5815
+ "lib/v3/understudy/screenshotUtils.ts"() {
5816
+ }
5817
+ });
5818
+
4928
5819
  // lib/v3/understudy/page.ts
4929
5820
  var page_exports = {};
4930
5821
  __export(page_exports, {
4931
5822
  Page: () => Page
4932
5823
  });
4933
- var LIFECYCLE_NAME, Page;
5824
+ var import_fs5, LIFECYCLE_NAME, Page;
4934
5825
  var init_page = __esm({
4935
5826
  "lib/v3/understudy/page.ts"() {
5827
+ import_fs5 = require("fs");
4936
5828
  init_logger();
4937
5829
  init_frame();
4938
5830
  init_frameLocator();
@@ -4941,6 +5833,9 @@ var init_page = __esm({
4941
5833
  init_frameRegistry();
4942
5834
  init_networkManager();
4943
5835
  init_lifecycleWatcher();
5836
+ init_navigationResponseTracker();
5837
+ init_consoleMessage();
5838
+ init_screenshotUtils();
4944
5839
  LIFECYCLE_NAME = {
4945
5840
  load: "load",
4946
5841
  domcontentloaded: "DOMContentLoaded",
@@ -4964,6 +5859,8 @@ var init_page = __esm({
4964
5859
  this.latestNavigationCommandId = 0;
4965
5860
  /** Optional API client for routing page operations to the API */
4966
5861
  this.apiClient = null;
5862
+ this.consoleListeners = /* @__PURE__ */ new Set();
5863
+ this.consoleHandlers = /* @__PURE__ */ new Map();
4967
5864
  // --- Optional visual cursor overlay management ---
4968
5865
  this.cursorEnabled = false;
4969
5866
  // Track pressed modifier keys
@@ -5165,6 +6062,9 @@ var init_page = __esm({
5165
6062
  var _a;
5166
6063
  if (childSession.id) this.sessions.set(childSession.id, childSession);
5167
6064
  this.networkManager.trackSession(childSession);
6065
+ if (this.consoleListeners.size > 0) {
6066
+ this.installConsoleTap(childSession);
6067
+ }
5168
6068
  this.registry.adoptChildSession(
5169
6069
  (_a = childSession.id) != null ? _a : "child",
5170
6070
  childMainFrameId
@@ -5218,6 +6118,7 @@ var init_page = __esm({
5218
6118
  this.registry.onFrameDetached(fid, "remove");
5219
6119
  this.frameCache.delete(fid);
5220
6120
  }
6121
+ this.teardownConsoleTap(sessionId);
5221
6122
  this.sessions.delete(sessionId);
5222
6123
  this.networkManager.untrackSession(sessionId);
5223
6124
  }
@@ -5248,10 +6149,65 @@ var init_page = __esm({
5248
6149
  unregisterSessionForNetwork(sessionId) {
5249
6150
  this.networkManager.untrackSession(sessionId);
5250
6151
  }
6152
+ on(event, listener) {
6153
+ if (event !== "console") {
6154
+ throw new Error(`Unsupported event: ${event}`);
6155
+ }
6156
+ const firstListener = this.consoleListeners.size === 0;
6157
+ this.consoleListeners.add(listener);
6158
+ if (firstListener) {
6159
+ this.ensureConsoleTaps();
6160
+ }
6161
+ return this;
6162
+ }
6163
+ once(event, listener) {
6164
+ if (event !== "console") {
6165
+ throw new Error(`Unsupported event: ${event}`);
6166
+ }
6167
+ const wrapper = (message) => {
6168
+ this.off("console", wrapper);
6169
+ listener(message);
6170
+ };
6171
+ return this.on("console", wrapper);
6172
+ }
6173
+ off(event, listener) {
6174
+ if (event !== "console") {
6175
+ throw new Error(`Unsupported event: ${event}`);
6176
+ }
6177
+ this.consoleListeners.delete(listener);
6178
+ if (this.consoleListeners.size === 0) {
6179
+ this.removeAllConsoleTaps();
6180
+ }
6181
+ return this;
6182
+ }
5251
6183
  // ---------------- MAIN APIs ----------------
5252
6184
  targetId() {
5253
6185
  return this._targetId;
5254
6186
  }
6187
+ /**
6188
+ * Send a CDP command through the main session.
6189
+ * Allows external consumers to execute arbitrary Chrome DevTools Protocol commands.
6190
+ *
6191
+ * @param method - The CDP method name (e.g., "Page.enable", "Runtime.evaluate")
6192
+ * @param params - Optional parameters for the CDP command
6193
+ * @returns Promise resolving to the typed CDP response
6194
+ *
6195
+ * @example
6196
+ * // Enable the Runtime domain
6197
+ * await page.sendCDP("Runtime.enable");
6198
+ *
6199
+ * @example
6200
+ * // Evaluate JavaScript with typed response
6201
+ * const result = await page.sendCDP<Protocol.Runtime.EvaluateResponse>(
6202
+ * "Runtime.evaluate",
6203
+ * { expression: "1 + 1" }
6204
+ * );
6205
+ */
6206
+ sendCDP(method, params) {
6207
+ return __async(this, null, function* () {
6208
+ return this.mainSession.send(method, params);
6209
+ });
6210
+ }
5255
6211
  /** Seed the cached URL before navigation events converge. */
5256
6212
  seedCurrentUrl(url) {
5257
6213
  if (!url) return;
@@ -5290,6 +6246,8 @@ var init_page = __esm({
5290
6246
  yield new Promise((r) => setTimeout(r, 25));
5291
6247
  }
5292
6248
  this.networkManager.dispose();
6249
+ this.removeAllConsoleTaps();
6250
+ this.consoleListeners.clear();
5293
6251
  });
5294
6252
  }
5295
6253
  getFullFrameTree() {
@@ -5312,6 +6270,71 @@ var init_page = __esm({
5312
6270
  listAllFrameIds() {
5313
6271
  return this.registry.listAllFrames();
5314
6272
  }
6273
+ ensureConsoleTaps() {
6274
+ if (this.consoleListeners.size === 0) return;
6275
+ this.installConsoleTap(this.mainSession);
6276
+ for (const session of this.sessions.values()) {
6277
+ this.installConsoleTap(session);
6278
+ }
6279
+ }
6280
+ installConsoleTap(session) {
6281
+ const key = this.sessionKey(session);
6282
+ if (this.consoleHandlers.has(key)) return;
6283
+ void session.send("Runtime.enable").catch(() => {
6284
+ });
6285
+ const handler = (evt) => {
6286
+ this.emitConsole(evt);
6287
+ };
6288
+ session.on(
6289
+ "Runtime.consoleAPICalled",
6290
+ handler
6291
+ );
6292
+ this.consoleHandlers.set(key, handler);
6293
+ }
6294
+ sessionKey(session) {
6295
+ var _a;
6296
+ return (_a = session.id) != null ? _a : "__root__";
6297
+ }
6298
+ resolveSessionByKey(key) {
6299
+ if (this.mainSession.id) {
6300
+ if (this.mainSession.id === key) return this.mainSession;
6301
+ } else if (key === "__root__") {
6302
+ return this.mainSession;
6303
+ }
6304
+ return this.sessions.get(key);
6305
+ }
6306
+ teardownConsoleTap(key) {
6307
+ const handler = this.consoleHandlers.get(key);
6308
+ if (!handler) return;
6309
+ const session = this.resolveSessionByKey(key);
6310
+ session == null ? void 0 : session.off("Runtime.consoleAPICalled", handler);
6311
+ this.consoleHandlers.delete(key);
6312
+ }
6313
+ removeAllConsoleTaps() {
6314
+ for (const key of [...this.consoleHandlers.keys()]) {
6315
+ this.teardownConsoleTap(key);
6316
+ }
6317
+ }
6318
+ emitConsole(evt) {
6319
+ if (this.consoleListeners.size === 0) return;
6320
+ const message = new ConsoleMessage(evt, this);
6321
+ const listeners = [...this.consoleListeners];
6322
+ for (const listener of listeners) {
6323
+ try {
6324
+ listener(message);
6325
+ } catch (error) {
6326
+ v3Logger({
6327
+ category: "page",
6328
+ message: "Console listener threw",
6329
+ level: 2,
6330
+ auxiliary: {
6331
+ error: { value: String(error), type: "string" },
6332
+ type: { value: evt.type, type: "string" }
6333
+ }
6334
+ });
6335
+ }
6336
+ }
6337
+ }
5315
6338
  // -------- Convenience APIs delegated to the current main frame --------
5316
6339
  /**
5317
6340
  * Navigate the page; optionally wait for a lifecycle state.
@@ -5323,6 +6346,11 @@ var init_page = __esm({
5323
6346
  const waitUntil = (_a = options == null ? void 0 : options.waitUntil) != null ? _a : "domcontentloaded";
5324
6347
  const timeout = (_b = options == null ? void 0 : options.timeoutMs) != null ? _b : 15e3;
5325
6348
  const navigationCommandId = this.beginNavigationCommand();
6349
+ const tracker = new NavigationResponseTracker({
6350
+ page: this,
6351
+ session: this.mainSession,
6352
+ navigationCommandId
6353
+ });
5326
6354
  const watcher = new LifecycleWatcher({
5327
6355
  page: this,
5328
6356
  mainSession: this.mainSession,
@@ -5339,17 +6367,22 @@ var init_page = __esm({
5339
6367
  this.mainFrameId()
5340
6368
  );
5341
6369
  this._currentUrl = url;
5342
- return;
6370
+ return null;
5343
6371
  }
5344
6372
  const response = yield this.mainSession.send(
5345
6373
  "Page.navigate",
5346
6374
  { url }
5347
6375
  );
5348
6376
  this._currentUrl = url;
5349
- if (response == null ? void 0 : response.loaderId) watcher.setExpectedLoaderId(response.loaderId);
6377
+ if (response == null ? void 0 : response.loaderId) {
6378
+ watcher.setExpectedLoaderId(response.loaderId);
6379
+ tracker.setExpectedLoaderId(response.loaderId);
6380
+ }
5350
6381
  yield watcher.wait();
6382
+ return yield tracker.navigationCompleted();
5351
6383
  } finally {
5352
6384
  watcher.dispose();
6385
+ tracker.dispose();
5353
6386
  }
5354
6387
  });
5355
6388
  }
@@ -5362,6 +6395,12 @@ var init_page = __esm({
5362
6395
  const waitUntil = options == null ? void 0 : options.waitUntil;
5363
6396
  const timeout = (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 15e3;
5364
6397
  const navigationCommandId = this.beginNavigationCommand();
6398
+ const tracker = new NavigationResponseTracker({
6399
+ page: this,
6400
+ session: this.mainSession,
6401
+ navigationCommandId
6402
+ });
6403
+ tracker.expectNavigationWithoutKnownLoader();
5365
6404
  const watcher = waitUntil ? new LifecycleWatcher({
5366
6405
  page: this,
5367
6406
  mainSession: this.mainSession,
@@ -5377,8 +6416,10 @@ var init_page = __esm({
5377
6416
  if (watcher) {
5378
6417
  yield watcher.wait();
5379
6418
  }
6419
+ return yield tracker.navigationCompleted();
5380
6420
  } finally {
5381
6421
  watcher == null ? void 0 : watcher.dispose();
6422
+ tracker.dispose();
5382
6423
  }
5383
6424
  });
5384
6425
  }
@@ -5392,10 +6433,16 @@ var init_page = __esm({
5392
6433
  "Page.getNavigationHistory"
5393
6434
  );
5394
6435
  const prev = entries[currentIndex - 1];
5395
- if (!prev) return;
6436
+ if (!prev) return null;
5396
6437
  const waitUntil = options == null ? void 0 : options.waitUntil;
5397
6438
  const timeout = (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 15e3;
5398
6439
  const navigationCommandId = this.beginNavigationCommand();
6440
+ const tracker = new NavigationResponseTracker({
6441
+ page: this,
6442
+ session: this.mainSession,
6443
+ navigationCommandId
6444
+ });
6445
+ tracker.expectNavigationWithoutKnownLoader();
5399
6446
  const watcher = waitUntil ? new LifecycleWatcher({
5400
6447
  page: this,
5401
6448
  mainSession: this.mainSession,
@@ -5412,8 +6459,10 @@ var init_page = __esm({
5412
6459
  if (watcher) {
5413
6460
  yield watcher.wait();
5414
6461
  }
6462
+ return yield tracker.navigationCompleted();
5415
6463
  } finally {
5416
6464
  watcher == null ? void 0 : watcher.dispose();
6465
+ tracker.dispose();
5417
6466
  }
5418
6467
  });
5419
6468
  }
@@ -5427,10 +6476,16 @@ var init_page = __esm({
5427
6476
  "Page.getNavigationHistory"
5428
6477
  );
5429
6478
  const next = entries[currentIndex + 1];
5430
- if (!next) return;
6479
+ if (!next) return null;
5431
6480
  const waitUntil = options == null ? void 0 : options.waitUntil;
5432
6481
  const timeout = (_a = options == null ? void 0 : options.timeoutMs) != null ? _a : 15e3;
5433
6482
  const navigationCommandId = this.beginNavigationCommand();
6483
+ const tracker = new NavigationResponseTracker({
6484
+ page: this,
6485
+ session: this.mainSession,
6486
+ navigationCommandId
6487
+ });
6488
+ tracker.expectNavigationWithoutKnownLoader();
5434
6489
  const watcher = waitUntil ? new LifecycleWatcher({
5435
6490
  page: this,
5436
6491
  mainSession: this.mainSession,
@@ -5447,8 +6502,10 @@ var init_page = __esm({
5447
6502
  if (watcher) {
5448
6503
  yield watcher.wait();
5449
6504
  }
6505
+ return yield tracker.navigationCompleted();
5450
6506
  } finally {
5451
6507
  watcher == null ? void 0 : watcher.dispose();
6508
+ tracker.dispose();
5452
6509
  }
5453
6510
  });
5454
6511
  }
@@ -5500,11 +6557,99 @@ var init_page = __esm({
5500
6557
  });
5501
6558
  }
5502
6559
  /**
5503
- * Capture a screenshot (delegated to the current main frame).
6560
+ * Capture a screenshot with Playwright-style options.
6561
+ *
6562
+ * @param options Optional screenshot configuration.
6563
+ * @param options.animations Control CSS/Web animations during capture. Use
6564
+ * "disabled" to fast-forward finite animations and pause infinite ones.
6565
+ * @param options.caret Either hide the text caret (default) or leave it
6566
+ * visible via "initial".
6567
+ * @param options.clip Restrict capture to a specific rectangle (in CSS
6568
+ * pixels). Cannot be combined with `fullPage`.
6569
+ * @param options.fullPage Capture the full scrollable page instead of the
6570
+ * current viewport.
6571
+ * @param options.mask Array of locators that should be covered with an
6572
+ * overlay while the screenshot is taken.
6573
+ * @param options.maskColor CSS color used for the mask overlay (default
6574
+ * `#FF00FF`).
6575
+ * @param options.omitBackground Make the default page background transparent
6576
+ * (PNG only).
6577
+ * @param options.path File path to write the screenshot to. The file extension
6578
+ * determines the image type when `type` is not explicitly provided.
6579
+ * @param options.quality JPEG quality (0–100). Only applies when
6580
+ * `type === "jpeg"`.
6581
+ * @param options.scale Render scale: use "css" for one pixel per CSS pixel,
6582
+ * otherwise the default "device" leverages the current device pixel ratio.
6583
+ * @param options.style Additional CSS text injected into every frame before
6584
+ * capture (removed afterwards).
6585
+ * @param options.timeout Maximum capture duration in milliseconds before a
6586
+ * timeout error is thrown.
6587
+ * @param options.type Image format (`"png"` by default).
5504
6588
  */
5505
6589
  screenshot(options) {
5506
6590
  return __async(this, null, function* () {
5507
- return this.mainFrameWrapper.screenshot(options);
6591
+ var _a, _b, _c, _d, _e;
6592
+ const opts = options != null ? options : {};
6593
+ const type = (_a = opts.type) != null ? _a : "png";
6594
+ if (type !== "png" && type !== "jpeg") {
6595
+ throw new Error(`screenshot: unsupported image type "${type}"`);
6596
+ }
6597
+ if (opts.fullPage && opts.clip) {
6598
+ throw new Error("screenshot: clip and fullPage cannot be used together");
6599
+ }
6600
+ if (type === "png" && typeof opts.quality === "number") {
6601
+ throw new Error(
6602
+ 'screenshot: quality option is only valid for type="jpeg"'
6603
+ );
6604
+ }
6605
+ const caretMode = (_b = opts.caret) != null ? _b : "hide";
6606
+ const animationsMode = (_c = opts.animations) != null ? _c : "allow";
6607
+ const scaleMode = (_d = opts.scale) != null ? _d : "device";
6608
+ const frames = collectFramesForScreenshot(this);
6609
+ const clip = opts.clip ? normalizeScreenshotClip(opts.clip) : void 0;
6610
+ const captureScale = yield computeScreenshotScale(this, scaleMode);
6611
+ const maskLocators = ((_e = opts.mask) != null ? _e : []).filter(
6612
+ (locator) => Boolean(locator)
6613
+ );
6614
+ const cleanupTasks = [];
6615
+ const exec = () => __async(this, null, function* () {
6616
+ var _a2;
6617
+ try {
6618
+ if (opts.omitBackground) {
6619
+ cleanupTasks.push(yield setTransparentBackground(this.mainSession));
6620
+ }
6621
+ if (animationsMode === "disabled") {
6622
+ cleanupTasks.push(yield disableAnimations(frames));
6623
+ }
6624
+ if (caretMode === "hide") {
6625
+ cleanupTasks.push(yield hideCaret(frames));
6626
+ }
6627
+ if (opts.style && opts.style.trim()) {
6628
+ cleanupTasks.push(
6629
+ yield applyStyleToFrames(frames, opts.style, "custom")
6630
+ );
6631
+ }
6632
+ if (maskLocators.length > 0) {
6633
+ cleanupTasks.push(
6634
+ yield applyMaskOverlays(maskLocators, (_a2 = opts.maskColor) != null ? _a2 : "#FF00FF")
6635
+ );
6636
+ }
6637
+ const buffer = yield this.mainFrameWrapper.screenshot({
6638
+ fullPage: opts.fullPage,
6639
+ clip,
6640
+ type,
6641
+ quality: type === "jpeg" ? opts.quality : void 0,
6642
+ scale: captureScale
6643
+ });
6644
+ if (opts.path) {
6645
+ yield import_fs5.promises.writeFile(opts.path, buffer);
6646
+ }
6647
+ return buffer;
6648
+ } finally {
6649
+ yield runScreenshotCleanups(cleanupTasks);
6650
+ }
6651
+ });
6652
+ return withScreenshotTimeout(opts.timeout, exec);
5508
6653
  });
5509
6654
  }
5510
6655
  /**
@@ -6222,12 +7367,14 @@ var init_page = __esm({
6222
7367
  // lib/v3/index.ts
6223
7368
  var v3_exports = {};
6224
7369
  __export(v3_exports, {
7370
+ AISdkClient: () => AISdkClient2,
6225
7371
  AVAILABLE_CUA_MODELS: () => AVAILABLE_CUA_MODELS,
6226
7372
  AgentProvider: () => AgentProvider,
6227
7373
  AgentScreenshotProviderError: () => AgentScreenshotProviderError,
6228
7374
  AnnotatedScreenshotText: () => AnnotatedScreenshotText,
6229
7375
  BrowserbaseSessionNotFoundError: () => BrowserbaseSessionNotFoundError,
6230
7376
  CaptchaTimeoutError: () => CaptchaTimeoutError,
7377
+ ConsoleMessage: () => ConsoleMessage,
6231
7378
  ContentFrameNotFoundError: () => ContentFrameNotFoundError,
6232
7379
  CreateChatCompletionResponseError: () => CreateChatCompletionResponseError,
6233
7380
  ExperimentalApiConflictError: () => ExperimentalApiConflictError,
@@ -6240,6 +7387,7 @@ __export(v3_exports, {
6240
7387
  MCPConnectionError: () => MCPConnectionError,
6241
7388
  MissingEnvironmentVariableError: () => MissingEnvironmentVariableError,
6242
7389
  MissingLLMConfigurationError: () => MissingLLMConfigurationError,
7390
+ Response: () => Response,
6243
7391
  Stagehand: () => V3,
6244
7392
  StagehandAPIError: () => StagehandAPIError,
6245
7393
  StagehandAPIUnauthorizedError: () => StagehandAPIUnauthorizedError,
@@ -6289,13 +7437,13 @@ module.exports = __toCommonJS(v3_exports);
6289
7437
 
6290
7438
  // lib/v3/v3.ts
6291
7439
  var import_dotenv = __toESM(require("dotenv"));
6292
- var import_fs5 = __toESM(require("fs"));
7440
+ var import_fs6 = __toESM(require("fs"));
6293
7441
  var import_os2 = __toESM(require("os"));
6294
7442
  var import_path5 = __toESM(require("path"));
6295
7443
  var import_process2 = __toESM(require("process"));
6296
7444
 
6297
7445
  // lib/version.ts
6298
- var STAGEHAND_VERSION = "3.0.0-preview.8";
7446
+ var STAGEHAND_VERSION = "3.0.1";
6299
7447
 
6300
7448
  // lib/v3/types/public/sdkErrors.ts
6301
7449
  var StagehandError = class extends Error {
@@ -7251,7 +8399,7 @@ var ActCache = class {
7251
8399
  }
7252
8400
  }
7253
8401
  });
7254
- return yield this.replayCachedActions(entry, page, timeout);
8402
+ return yield this.replayCachedActions(context, entry, page, timeout);
7255
8403
  });
7256
8404
  }
7257
8405
  store(context, result) {
@@ -7301,7 +8449,7 @@ var ActCache = class {
7301
8449
  });
7302
8450
  return (0, import_crypto.createHash)("sha256").update(payload).digest("hex");
7303
8451
  }
7304
- replayCachedActions(entry, page, timeout) {
8452
+ replayCachedActions(context, entry, page, timeout) {
7305
8453
  return __async(this, null, function* () {
7306
8454
  const handler = this.getActHandler();
7307
8455
  if (!handler) {
@@ -7337,6 +8485,13 @@ var ActCache = class {
7337
8485
  });
7338
8486
  const message = actionResults.map((r) => r.message).filter((m) => m && m.trim().length > 0).join(" \u2192 ") || entry.message || `Replayed ${entry.actions.length} cached action${entry.actions.length === 1 ? "" : "s"}.`;
7339
8487
  const actionDescription = entry.actionDescription || ((_b = actionResults[actionResults.length - 1]) == null ? void 0 : _b.actionDescription) || ((_c = entry.actions[entry.actions.length - 1]) == null ? void 0 : _c.description) || entry.instruction;
8488
+ if (success && actions.length > 0 && this.haveActionsChanged(entry.actions, actions)) {
8489
+ yield this.refreshCacheEntry(context, __spreadProps(__spreadValues({}, entry), {
8490
+ actions,
8491
+ message,
8492
+ actionDescription
8493
+ }));
8494
+ }
7340
8495
  return {
7341
8496
  success,
7342
8497
  message,
@@ -7347,6 +8502,67 @@ var ActCache = class {
7347
8502
  return yield this.runWithTimeout(execute, timeout);
7348
8503
  });
7349
8504
  }
8505
+ haveActionsChanged(original, updated) {
8506
+ var _a, _b, _c, _d;
8507
+ if (original.length !== updated.length) {
8508
+ return true;
8509
+ }
8510
+ for (let i = 0; i < original.length; i += 1) {
8511
+ const orig = original[i];
8512
+ const next = updated[i];
8513
+ if (!next) {
8514
+ return true;
8515
+ }
8516
+ if (orig.selector !== next.selector) {
8517
+ return true;
8518
+ }
8519
+ if (orig.description !== next.description) {
8520
+ return true;
8521
+ }
8522
+ if (((_a = orig.method) != null ? _a : "") !== ((_b = next.method) != null ? _b : "")) {
8523
+ return true;
8524
+ }
8525
+ const origArgs = (_c = orig.arguments) != null ? _c : [];
8526
+ const nextArgs = (_d = next.arguments) != null ? _d : [];
8527
+ if (origArgs.length !== nextArgs.length) {
8528
+ return true;
8529
+ }
8530
+ for (let j = 0; j < origArgs.length; j += 1) {
8531
+ if (origArgs[j] !== nextArgs[j]) {
8532
+ return true;
8533
+ }
8534
+ }
8535
+ }
8536
+ return false;
8537
+ }
8538
+ refreshCacheEntry(context, entry) {
8539
+ return __async(this, null, function* () {
8540
+ const { error, path: path6 } = yield this.storage.writeJson(
8541
+ `${context.cacheKey}.json`,
8542
+ entry
8543
+ );
8544
+ if (error && path6) {
8545
+ this.logger({
8546
+ category: "cache",
8547
+ message: "failed to update act cache entry after self-heal",
8548
+ level: 0,
8549
+ auxiliary: {
8550
+ error: { value: String(error), type: "string" }
8551
+ }
8552
+ });
8553
+ return;
8554
+ }
8555
+ this.logger({
8556
+ category: "cache",
8557
+ message: "act cache entry updated after self-heal",
8558
+ level: 2,
8559
+ auxiliary: {
8560
+ instruction: { value: context.instruction, type: "string" },
8561
+ url: { value: context.pageUrl, type: "string" }
8562
+ }
8563
+ });
8564
+ });
8565
+ }
7350
8566
  runWithTimeout(run, timeout) {
7351
8567
  return __async(this, null, function* () {
7352
8568
  if (!timeout) {
@@ -9989,25 +11205,37 @@ function evaluateZodSchema(schemaStr, logger) {
9989
11205
  message: `Failed to evaluate schema: ${(_a = e == null ? void 0 : e.message) != null ? _a : String(e)}`,
9990
11206
  level: 0
9991
11207
  });
9992
- throw new Error(
9993
- "Invalid schema: Ensure you're passing a valid Zod schema expression, e.g. z.object({ title: z.string() })"
9994
- );
11208
+ return import_v314.z.any();
9995
11209
  }
9996
11210
  }
9997
11211
  var createExtractTool = (v3, executionModel, logger) => (0, import_ai10.tool)({
9998
- description: "Extract structured data. Optionally provide an instruction and Zod schema.",
11212
+ description: `Extract structured data from the current page based on a provided schema.
11213
+
11214
+ USAGE GUIDELINES:
11215
+ - Keep schemas MINIMAL - only include fields essential for the task
11216
+ - IMPORANT: only use this if explicitly asked for structured output. In most scenarios, you should use the aria tree tool over this.
11217
+ - If you need to extract a link, make sure the type defintion follows the format of z.string().url()
11218
+ EXAMPLES:
11219
+ 1. Extract a single value:
11220
+ instruction: "extract the product price"
11221
+ schema: "z.object({ price: z.number()})"
11222
+
11223
+ 2. Extract multiple fields:
11224
+ instruction: "extract product name and price"
11225
+ schema: "z.object({ name: z.string(), price: z.number() })"
11226
+
11227
+ 3. Extract arrays:
11228
+ instruction: "extract all product names and prices"
11229
+ schema: "z.object({ products: z.array(z.object({ name: z.string(), price: z.number() })) })"`,
9999
11230
  inputSchema: import_v314.z.object({
10000
- instruction: import_v314.z.string().optional(),
10001
- schema: import_v314.z.string().optional().describe("Zod schema as code, e.g. z.object({ title: z.string() })"),
10002
- selector: import_v314.z.string().optional()
11231
+ instruction: import_v314.z.string(),
11232
+ schema: import_v314.z.string().optional().describe("Zod schema as code, e.g. z.object({ title: z.string() })")
10003
11233
  }),
10004
- execute: (_0) => __async(null, [_0], function* ({ instruction, schema, selector }) {
11234
+ execute: (_0) => __async(null, [_0], function* ({ instruction, schema }) {
10005
11235
  var _a;
10006
11236
  try {
10007
11237
  const parsedSchema = schema ? evaluateZodSchema(schema, logger) : void 0;
10008
- const result = yield v3.extract(instruction, parsedSchema, __spreadProps(__spreadValues({}, executionModel ? { model: executionModel } : {}), {
10009
- selector
10010
- }));
11238
+ const result = yield v3.extract(instruction, parsedSchema, __spreadValues({}, executionModel ? { model: executionModel } : {}));
10011
11239
  return { success: true, result };
10012
11240
  } catch (error) {
10013
11241
  return { success: false, error: (_a = error == null ? void 0 : error.message) != null ? _a : String(error) };
@@ -10168,12 +11396,12 @@ function mapToolResultToActions({
10168
11396
  case "fillForm":
10169
11397
  return mapFillFormToolResult(toolResult, args, reasoning);
10170
11398
  default:
10171
- return [createStandardAction(toolCallName, args, reasoning)];
11399
+ return [createStandardAction(toolCallName, toolResult, args, reasoning)];
10172
11400
  }
10173
11401
  }
10174
11402
  function mapActToolResult(toolResult, args, reasoning) {
10175
11403
  if (!toolResult || typeof toolResult !== "object") {
10176
- return [createStandardAction("act", args, reasoning)];
11404
+ return [createStandardAction("act", toolResult, args, reasoning)];
10177
11405
  }
10178
11406
  const result = toolResult;
10179
11407
  const output = result.output || result;
@@ -10189,7 +11417,7 @@ function mapActToolResult(toolResult, args, reasoning) {
10189
11417
  }
10190
11418
  function mapFillFormToolResult(toolResult, args, reasoning) {
10191
11419
  if (!toolResult || typeof toolResult !== "object") {
10192
- return [createStandardAction("fillForm", args, reasoning)];
11420
+ return [createStandardAction("fillForm", toolResult, args, reasoning)];
10193
11421
  }
10194
11422
  const result = toolResult;
10195
11423
  const output = result.output || result;
@@ -10210,12 +11438,17 @@ function mapFillFormToolResult(toolResult, args, reasoning) {
10210
11438
  }
10211
11439
  return actions;
10212
11440
  }
10213
- function createStandardAction(toolCallName, args, reasoning) {
10214
- return __spreadValues({
11441
+ function createStandardAction(toolCallName, toolResult, args, reasoning) {
11442
+ const action = __spreadValues({
10215
11443
  type: toolCallName,
10216
11444
  reasoning,
10217
11445
  taskCompleted: toolCallName === "close" ? args == null ? void 0 : args.taskComplete : false
10218
11446
  }, args);
11447
+ if (toolCallName !== "ariaTree" && toolResult) {
11448
+ const { output } = toolResult;
11449
+ Object.assign(action, output);
11450
+ }
11451
+ return action;
10219
11452
  }
10220
11453
 
10221
11454
  // lib/v3/handlers/v3AgentHandler.ts
@@ -10360,7 +11593,7 @@ var V3AgentHandler = class {
10360
11593
  buildSystemPrompt(executionInstruction, systemInstructions) {
10361
11594
  if (systemInstructions) {
10362
11595
  return `${systemInstructions}
10363
- Your current goal: ${executionInstruction}`;
11596
+ Your current goal: ${executionInstruction} when the task is complete, use the "close" tool with taskComplete: true`;
10364
11597
  }
10365
11598
  return `You are a web automation assistant using browser automation tools to accomplish the user's goal.
10366
11599
 
@@ -37288,6 +38521,111 @@ var LOG_LEVEL_NAMES = {
37288
38521
  2: "debug"
37289
38522
  };
37290
38523
 
38524
+ // lib/v3/types/public/page.ts
38525
+ init_consoleMessage();
38526
+ init_response();
38527
+
38528
+ // examples/external_clients/aisdk.ts
38529
+ var import_ai14 = require("ai");
38530
+ var AISdkClient2 = class extends LLMClient {
38531
+ constructor({ model }) {
38532
+ super(model.modelId);
38533
+ this.type = "aisdk";
38534
+ this.model = model;
38535
+ }
38536
+ createChatCompletion(_0) {
38537
+ return __async(this, arguments, function* ({
38538
+ options
38539
+ }) {
38540
+ var _a, _b, _c, _d, _e, _f;
38541
+ const formattedMessages = options.messages.map(
38542
+ (message) => {
38543
+ if (Array.isArray(message.content)) {
38544
+ if (message.role === "system") {
38545
+ const systemMessage = {
38546
+ role: "system",
38547
+ content: message.content.map((c) => "text" in c ? c.text : "").join("\n")
38548
+ };
38549
+ return systemMessage;
38550
+ }
38551
+ const contentParts = message.content.map((content) => {
38552
+ if ("image_url" in content) {
38553
+ const imageContent = {
38554
+ type: "image",
38555
+ image: content.image_url.url
38556
+ };
38557
+ return imageContent;
38558
+ } else {
38559
+ const textContent = {
38560
+ type: "text",
38561
+ text: content.text
38562
+ };
38563
+ return textContent;
38564
+ }
38565
+ });
38566
+ if (message.role === "user") {
38567
+ const userMessage = {
38568
+ role: "user",
38569
+ content: contentParts
38570
+ };
38571
+ return userMessage;
38572
+ } else {
38573
+ const textOnlyParts = contentParts.map((part) => ({
38574
+ type: "text",
38575
+ text: part.type === "image" ? "[Image]" : part.text
38576
+ }));
38577
+ const assistantMessage = {
38578
+ role: "assistant",
38579
+ content: textOnlyParts
38580
+ };
38581
+ return assistantMessage;
38582
+ }
38583
+ }
38584
+ return {
38585
+ role: message.role,
38586
+ content: message.content
38587
+ };
38588
+ }
38589
+ );
38590
+ if (options.response_model) {
38591
+ const response2 = yield (0, import_ai14.generateObject)({
38592
+ model: this.model,
38593
+ messages: formattedMessages,
38594
+ schema: options.response_model.schema
38595
+ });
38596
+ return {
38597
+ data: response2.object,
38598
+ usage: {
38599
+ prompt_tokens: (_a = response2.usage.inputTokens) != null ? _a : 0,
38600
+ completion_tokens: (_b = response2.usage.outputTokens) != null ? _b : 0,
38601
+ total_tokens: (_c = response2.usage.totalTokens) != null ? _c : 0
38602
+ }
38603
+ };
38604
+ }
38605
+ const tools = {};
38606
+ for (const rawTool of options.tools) {
38607
+ tools[rawTool.name] = {
38608
+ description: rawTool.description,
38609
+ inputSchema: rawTool.parameters
38610
+ };
38611
+ }
38612
+ const response = yield (0, import_ai14.generateText)({
38613
+ model: this.model,
38614
+ messages: formattedMessages,
38615
+ tools
38616
+ });
38617
+ return {
38618
+ data: response.text,
38619
+ usage: {
38620
+ prompt_tokens: (_d = response.usage.inputTokens) != null ? _d : 0,
38621
+ completion_tokens: (_e = response.usage.outputTokens) != null ? _e : 0,
38622
+ total_tokens: (_f = response.usage.totalTokens) != null ? _f : 0
38623
+ }
38624
+ };
38625
+ });
38626
+ }
38627
+ };
38628
+
37291
38629
  // lib/v3/understudy/context.ts
37292
38630
  init_logger();
37293
38631
 
@@ -38295,6 +39633,85 @@ var StagehandAPIClient = class {
38295
39633
  return response;
38296
39634
  });
38297
39635
  }
39636
+ getReplayMetrics() {
39637
+ return __async(this, null, function* () {
39638
+ if (!this.sessionId) {
39639
+ throw new StagehandAPIError("sessionId is required to fetch metrics.");
39640
+ }
39641
+ const response = yield this.request(`/sessions/${this.sessionId}/replay`, {
39642
+ method: "GET"
39643
+ });
39644
+ if (response.status !== 200) {
39645
+ const errorText = yield response.text();
39646
+ this.logger({
39647
+ category: "api",
39648
+ message: `Failed to fetch metrics. Status ${response.status}: ${errorText}`,
39649
+ level: 0
39650
+ });
39651
+ throw new StagehandHttpError(
39652
+ `Failed to fetch metrics with status ${response.status}: ${errorText}`
39653
+ );
39654
+ }
39655
+ const data = yield response.json();
39656
+ if (!data.success) {
39657
+ throw new StagehandAPIError(
39658
+ `Failed to fetch metrics: ${data.error || "Unknown error"}`
39659
+ );
39660
+ }
39661
+ const apiData = data.data || {};
39662
+ const metrics = {
39663
+ actPromptTokens: 0,
39664
+ actCompletionTokens: 0,
39665
+ actInferenceTimeMs: 0,
39666
+ extractPromptTokens: 0,
39667
+ extractCompletionTokens: 0,
39668
+ extractInferenceTimeMs: 0,
39669
+ observePromptTokens: 0,
39670
+ observeCompletionTokens: 0,
39671
+ observeInferenceTimeMs: 0,
39672
+ agentPromptTokens: 0,
39673
+ agentCompletionTokens: 0,
39674
+ agentInferenceTimeMs: 0,
39675
+ totalPromptTokens: 0,
39676
+ totalCompletionTokens: 0,
39677
+ totalInferenceTimeMs: 0
39678
+ };
39679
+ const pages = apiData.pages || [];
39680
+ for (const page of pages) {
39681
+ const actions = page.actions || [];
39682
+ for (const action of actions) {
39683
+ const method = (action.method || "").toLowerCase();
39684
+ const tokenUsage = action.tokenUsage;
39685
+ if (tokenUsage) {
39686
+ const inputTokens = tokenUsage.inputTokens || 0;
39687
+ const outputTokens = tokenUsage.outputTokens || 0;
39688
+ const timeMs = tokenUsage.timeMs || 0;
39689
+ if (method === "act") {
39690
+ metrics.actPromptTokens += inputTokens;
39691
+ metrics.actCompletionTokens += outputTokens;
39692
+ metrics.actInferenceTimeMs += timeMs;
39693
+ } else if (method === "extract") {
39694
+ metrics.extractPromptTokens += inputTokens;
39695
+ metrics.extractCompletionTokens += outputTokens;
39696
+ metrics.extractInferenceTimeMs += timeMs;
39697
+ } else if (method === "observe") {
39698
+ metrics.observePromptTokens += inputTokens;
39699
+ metrics.observeCompletionTokens += outputTokens;
39700
+ metrics.observeInferenceTimeMs += timeMs;
39701
+ } else if (method === "agent") {
39702
+ metrics.agentPromptTokens += inputTokens;
39703
+ metrics.agentCompletionTokens += outputTokens;
39704
+ metrics.agentInferenceTimeMs += timeMs;
39705
+ }
39706
+ metrics.totalPromptTokens += inputTokens;
39707
+ metrics.totalCompletionTokens += outputTokens;
39708
+ metrics.totalInferenceTimeMs += timeMs;
39709
+ }
39710
+ }
39711
+ }
39712
+ return metrics;
39713
+ });
39714
+ }
38298
39715
  execute(_0) {
38299
39716
  return __async(this, arguments, function* ({
38300
39717
  method,
@@ -38501,11 +39918,12 @@ var _V3 = class _V3 {
38501
39918
  this.logInferenceToFile = (_f = opts.logInferenceToFile) != null ? _f : false;
38502
39919
  this.llmProvider = new LLMProvider(this.logger);
38503
39920
  this.domSettleTimeoutMs = opts.domSettleTimeout;
38504
- this.disableAPI = (_g = opts.disableAPI) != null ? _g : true;
39921
+ this.disableAPI = (_g = opts.disableAPI) != null ? _g : false;
38505
39922
  const baseClientOptions = clientOptions ? __spreadValues({}, clientOptions) : {};
38506
39923
  if (opts.llmClient) {
38507
39924
  this.llmClient = opts.llmClient;
38508
39925
  this.modelClientOptions = baseClientOptions;
39926
+ this.disableAPI = true;
38509
39927
  } else {
38510
39928
  let apiKey = baseClientOptions.apiKey;
38511
39929
  if (!apiKey) {
@@ -38556,11 +39974,24 @@ var _V3 = class _V3 {
38556
39974
  this.opts = opts;
38557
39975
  _V3._instances.add(this);
38558
39976
  }
39977
+ get browserbaseSessionID() {
39978
+ return this.browserbaseSessionId;
39979
+ }
38559
39980
  /**
38560
39981
  * Async property for metrics so callers can `await v3.metrics`.
38561
- * Returning a Promise future-proofs async aggregation/storage.
39982
+ * When using API mode, fetches metrics from the API. Otherwise returns local metrics.
38562
39983
  */
38563
39984
  get metrics() {
39985
+ if (this.apiClient) {
39986
+ return this.apiClient.getReplayMetrics().catch((error) => {
39987
+ this.logger({
39988
+ category: "metrics",
39989
+ message: `Failed to fetch metrics from API: ${error}`,
39990
+ level: 0
39991
+ });
39992
+ return this.stagehandMetrics;
39993
+ });
39994
+ }
38564
39995
  return Promise.resolve(this.stagehandMetrics);
38565
39996
  }
38566
39997
  resolveLlmClient(model) {
@@ -38835,8 +40266,8 @@ var _V3 = class _V3 {
38835
40266
  let createdTemp = false;
38836
40267
  if (!userDataDir) {
38837
40268
  const base = import_path5.default.join(import_os2.default.tmpdir(), "stagehand-v3");
38838
- import_fs5.default.mkdirSync(base, { recursive: true });
38839
- userDataDir = import_fs5.default.mkdtempSync(import_path5.default.join(base, "profile-"));
40269
+ import_fs6.default.mkdirSync(base, { recursive: true });
40270
+ userDataDir = import_fs6.default.mkdtempSync(import_path5.default.join(base, "profile-"));
38840
40271
  createdTemp = true;
38841
40272
  }
38842
40273
  const defaults2 = [
@@ -39265,7 +40696,7 @@ var _V3 = class _V3 {
39265
40696
  }
39266
40697
  try {
39267
40698
  if (this.state.createdTempProfile && !this.state.preserveUserDataDir && this.state.userDataDir) {
39268
- import_fs5.default.rmSync(this.state.userDataDir, { recursive: true, force: true });
40699
+ import_fs6.default.rmSync(this.state.userDataDir, { recursive: true, force: true });
39269
40700
  }
39270
40701
  } catch (e) {
39271
40702
  }
@@ -39405,23 +40836,29 @@ var _V3 = class _V3 {
39405
40836
  * Mirrors the v2 Stagehand.agent() tool mode (no CUA provider here).
39406
40837
  */
39407
40838
  agent(options) {
39408
- var _a, _b, _c;
40839
+ var _a, _b;
39409
40840
  this.logger({
39410
40841
  category: "agent",
39411
- message: "Creating v3 agent instance with options:",
40842
+ message: `Creating v3 agent instance with options: ${JSON.stringify(options)}`,
39412
40843
  level: 1,
39413
- auxiliary: {
40844
+ auxiliary: __spreadValues({
39414
40845
  cua: { value: (options == null ? void 0 : options.cua) ? "true" : "false", type: "boolean" },
39415
- model: typeof (options == null ? void 0 : options.model) === "string" ? { value: options.model, type: "string" } : { value: options.model.modelName, type: "string" },
40846
+ model: (options == null ? void 0 : options.model) ? typeof (options == null ? void 0 : options.model) === "string" ? { value: options.model, type: "string" } : { value: options.model.modelName, type: "string" } : { value: this.llmClient.modelName, type: "string" },
39416
40847
  systemPrompt: { value: (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : "", type: "string" },
39417
- tools: { value: JSON.stringify((_b = options == null ? void 0 : options.tools) != null ? _b : {}), type: "object" },
40848
+ tools: { value: JSON.stringify((_b = options == null ? void 0 : options.tools) != null ? _b : {}), type: "object" }
40849
+ }, (options == null ? void 0 : options.integrations) && {
39418
40850
  integrations: {
39419
- value: JSON.stringify((_c = options == null ? void 0 : options.integrations) != null ? _c : []),
40851
+ value: JSON.stringify(options.integrations),
39420
40852
  type: "object"
39421
40853
  }
39422
- }
40854
+ })
39423
40855
  });
39424
40856
  if (options == null ? void 0 : options.cua) {
40857
+ if (((options == null ? void 0 : options.integrations) || (options == null ? void 0 : options.tools)) && !this.experimental) {
40858
+ throw new Error(
40859
+ "MCP integrations and custom tools are experimental. Enable experimental: true in V3 options."
40860
+ );
40861
+ }
39425
40862
  const modelToUse = (options == null ? void 0 : options.model) || __spreadValues({
39426
40863
  modelName: this.modelName
39427
40864
  }, this.modelClientOptions);
@@ -39519,9 +40956,9 @@ Do not ask follow up questions, the user will trust your judgement.`
39519
40956
  execute: (instructionOrOptions) => __async(this, null, function* () {
39520
40957
  return withInstanceLogContext(this.instanceId, () => __async(this, null, function* () {
39521
40958
  var _a2, _b2;
39522
- if ((options == null ? void 0 : options.integrations) && !this.experimental) {
40959
+ if (((options == null ? void 0 : options.integrations) || (options == null ? void 0 : options.tools)) && !this.experimental) {
39523
40960
  throw new Error(
39524
- "MCP integrations are experimental. Enable experimental: true in V3 options."
40961
+ "MCP integrations and custom tools are experimental. Enable experimental: true in V3 options."
39525
40962
  );
39526
40963
  }
39527
40964
  const tools = (options == null ? void 0 : options.integrations) ? yield resolveTools(options.integrations, options.tools) : (_a2 = options == null ? void 0 : options.tools) != null ? _a2 : {};
@@ -39826,12 +41263,14 @@ I'm providing ${screenshots.length} screenshots showing the progression of the t
39826
41263
  };
39827
41264
  // Annotate the CommonJS export names for ESM import in node:
39828
41265
  0 && (module.exports = {
41266
+ AISdkClient,
39829
41267
  AVAILABLE_CUA_MODELS,
39830
41268
  AgentProvider,
39831
41269
  AgentScreenshotProviderError,
39832
41270
  AnnotatedScreenshotText,
39833
41271
  BrowserbaseSessionNotFoundError,
39834
41272
  CaptchaTimeoutError,
41273
+ ConsoleMessage,
39835
41274
  ContentFrameNotFoundError,
39836
41275
  CreateChatCompletionResponseError,
39837
41276
  ExperimentalApiConflictError,
@@ -39844,6 +41283,7 @@ I'm providing ${screenshots.length} screenshots showing the progression of the t
39844
41283
  MCPConnectionError,
39845
41284
  MissingEnvironmentVariableError,
39846
41285
  MissingLLMConfigurationError,
41286
+ Response,
39847
41287
  Stagehand,
39848
41288
  StagehandAPIError,
39849
41289
  StagehandAPIUnauthorizedError,