@browserbasehq/orca 3.0.0-test.1 → 3.0.1-zod4

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-test.1";
7446
+ var STAGEHAND_VERSION = "3.0.1-zod4";
6299
7447
 
6300
7448
  // lib/v3/types/public/sdkErrors.ts
6301
7449
  var StagehandError = class extends Error {
@@ -7030,7 +8178,18 @@ function isTestEnvironment() {
7030
8178
  var _StagehandLogger = class _StagehandLogger {
7031
8179
  constructor(options = {}, externalLogger) {
7032
8180
  this.isTest = isTestEnvironment();
7033
- this.usePino = this.isTest ? false : options.usePino !== false;
8181
+ this.externalLogger = externalLogger;
8182
+ const externalProvided = typeof externalLogger === "function";
8183
+ const explicitUsePino = options.usePino;
8184
+ if (this.isTest) {
8185
+ this.usePino = false;
8186
+ } else if (explicitUsePino === true) {
8187
+ this.usePino = true;
8188
+ } else if (explicitUsePino === false) {
8189
+ this.usePino = false;
8190
+ } else {
8191
+ this.usePino = !externalProvided;
8192
+ }
7034
8193
  if (this.usePino) {
7035
8194
  if (!_StagehandLogger.sharedPinoLogger) {
7036
8195
  _StagehandLogger.sharedPinoLogger = createLogger(options);
@@ -7038,7 +8197,6 @@ var _StagehandLogger = class _StagehandLogger {
7038
8197
  this.logger = _StagehandLogger.sharedPinoLogger;
7039
8198
  }
7040
8199
  this.verbose = 1;
7041
- this.externalLogger = externalLogger;
7042
8200
  }
7043
8201
  /**
7044
8202
  * Set the verbosity level
@@ -7307,7 +8465,7 @@ var ActCache = class {
7307
8465
  }
7308
8466
  }
7309
8467
  });
7310
- return yield this.replayCachedActions(entry, page, timeout);
8468
+ return yield this.replayCachedActions(context, entry, page, timeout);
7311
8469
  });
7312
8470
  }
7313
8471
  store(context, result) {
@@ -7357,7 +8515,7 @@ var ActCache = class {
7357
8515
  });
7358
8516
  return (0, import_crypto.createHash)("sha256").update(payload).digest("hex");
7359
8517
  }
7360
- replayCachedActions(entry, page, timeout) {
8518
+ replayCachedActions(context, entry, page, timeout) {
7361
8519
  return __async(this, null, function* () {
7362
8520
  const handler = this.getActHandler();
7363
8521
  if (!handler) {
@@ -7393,6 +8551,13 @@ var ActCache = class {
7393
8551
  });
7394
8552
  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"}.`;
7395
8553
  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;
8554
+ if (success && actions.length > 0 && this.haveActionsChanged(entry.actions, actions)) {
8555
+ yield this.refreshCacheEntry(context, __spreadProps(__spreadValues({}, entry), {
8556
+ actions,
8557
+ message,
8558
+ actionDescription
8559
+ }));
8560
+ }
7396
8561
  return {
7397
8562
  success,
7398
8563
  message,
@@ -7403,6 +8568,67 @@ var ActCache = class {
7403
8568
  return yield this.runWithTimeout(execute, timeout);
7404
8569
  });
7405
8570
  }
8571
+ haveActionsChanged(original, updated) {
8572
+ var _a, _b, _c, _d;
8573
+ if (original.length !== updated.length) {
8574
+ return true;
8575
+ }
8576
+ for (let i = 0; i < original.length; i += 1) {
8577
+ const orig = original[i];
8578
+ const next = updated[i];
8579
+ if (!next) {
8580
+ return true;
8581
+ }
8582
+ if (orig.selector !== next.selector) {
8583
+ return true;
8584
+ }
8585
+ if (orig.description !== next.description) {
8586
+ return true;
8587
+ }
8588
+ if (((_a = orig.method) != null ? _a : "") !== ((_b = next.method) != null ? _b : "")) {
8589
+ return true;
8590
+ }
8591
+ const origArgs = (_c = orig.arguments) != null ? _c : [];
8592
+ const nextArgs = (_d = next.arguments) != null ? _d : [];
8593
+ if (origArgs.length !== nextArgs.length) {
8594
+ return true;
8595
+ }
8596
+ for (let j = 0; j < origArgs.length; j += 1) {
8597
+ if (origArgs[j] !== nextArgs[j]) {
8598
+ return true;
8599
+ }
8600
+ }
8601
+ }
8602
+ return false;
8603
+ }
8604
+ refreshCacheEntry(context, entry) {
8605
+ return __async(this, null, function* () {
8606
+ const { error, path: path6 } = yield this.storage.writeJson(
8607
+ `${context.cacheKey}.json`,
8608
+ entry
8609
+ );
8610
+ if (error && path6) {
8611
+ this.logger({
8612
+ category: "cache",
8613
+ message: "failed to update act cache entry after self-heal",
8614
+ level: 0,
8615
+ auxiliary: {
8616
+ error: { value: String(error), type: "string" }
8617
+ }
8618
+ });
8619
+ return;
8620
+ }
8621
+ this.logger({
8622
+ category: "cache",
8623
+ message: "act cache entry updated after self-heal",
8624
+ level: 2,
8625
+ auxiliary: {
8626
+ instruction: { value: context.instruction, type: "string" },
8627
+ url: { value: context.pageUrl, type: "string" }
8628
+ }
8629
+ });
8630
+ });
8631
+ }
7406
8632
  runWithTimeout(run, timeout) {
7407
8633
  return __async(this, null, function* () {
7408
8634
  if (!timeout) {
@@ -10045,25 +11271,37 @@ function evaluateZodSchema(schemaStr, logger) {
10045
11271
  message: `Failed to evaluate schema: ${(_a = e == null ? void 0 : e.message) != null ? _a : String(e)}`,
10046
11272
  level: 0
10047
11273
  });
10048
- throw new Error(
10049
- "Invalid schema: Ensure you're passing a valid Zod schema expression, e.g. z.object({ title: z.string() })"
10050
- );
11274
+ return import_zod14.z.any();
10051
11275
  }
10052
11276
  }
10053
11277
  var createExtractTool = (v3, executionModel, logger) => (0, import_ai10.tool)({
10054
- description: "Extract structured data. Optionally provide an instruction and Zod schema.",
11278
+ description: `Extract structured data from the current page based on a provided schema.
11279
+
11280
+ USAGE GUIDELINES:
11281
+ - Keep schemas MINIMAL - only include fields essential for the task
11282
+ - IMPORANT: only use this if explicitly asked for structured output. In most scenarios, you should use the aria tree tool over this.
11283
+ - If you need to extract a link, make sure the type defintion follows the format of z.string().url()
11284
+ EXAMPLES:
11285
+ 1. Extract a single value:
11286
+ instruction: "extract the product price"
11287
+ schema: "z.object({ price: z.number()})"
11288
+
11289
+ 2. Extract multiple fields:
11290
+ instruction: "extract product name and price"
11291
+ schema: "z.object({ name: z.string(), price: z.number() })"
11292
+
11293
+ 3. Extract arrays:
11294
+ instruction: "extract all product names and prices"
11295
+ schema: "z.object({ products: z.array(z.object({ name: z.string(), price: z.number() })) })"`,
10055
11296
  inputSchema: import_zod14.z.object({
10056
- instruction: import_zod14.z.string().optional(),
10057
- schema: import_zod14.z.string().optional().describe("Zod schema as code, e.g. z.object({ title: z.string() })"),
10058
- selector: import_zod14.z.string().optional()
11297
+ instruction: import_zod14.z.string(),
11298
+ schema: import_zod14.z.string().optional().describe("Zod schema as code, e.g. z.object({ title: z.string() })")
10059
11299
  }),
10060
- execute: (_0) => __async(null, [_0], function* ({ instruction, schema, selector }) {
11300
+ execute: (_0) => __async(null, [_0], function* ({ instruction, schema }) {
10061
11301
  var _a;
10062
11302
  try {
10063
11303
  const parsedSchema = schema ? evaluateZodSchema(schema, logger) : void 0;
10064
- const result = yield v3.extract(instruction, parsedSchema, __spreadProps(__spreadValues({}, executionModel ? { model: executionModel } : {}), {
10065
- selector
10066
- }));
11304
+ const result = yield v3.extract(instruction, parsedSchema, __spreadValues({}, executionModel ? { model: executionModel } : {}));
10067
11305
  return { success: true, result };
10068
11306
  } catch (error) {
10069
11307
  return { success: false, error: (_a = error == null ? void 0 : error.message) != null ? _a : String(error) };
@@ -10224,12 +11462,12 @@ function mapToolResultToActions({
10224
11462
  case "fillForm":
10225
11463
  return mapFillFormToolResult(toolResult, args, reasoning);
10226
11464
  default:
10227
- return [createStandardAction(toolCallName, args, reasoning)];
11465
+ return [createStandardAction(toolCallName, toolResult, args, reasoning)];
10228
11466
  }
10229
11467
  }
10230
11468
  function mapActToolResult(toolResult, args, reasoning) {
10231
11469
  if (!toolResult || typeof toolResult !== "object") {
10232
- return [createStandardAction("act", args, reasoning)];
11470
+ return [createStandardAction("act", toolResult, args, reasoning)];
10233
11471
  }
10234
11472
  const result = toolResult;
10235
11473
  const output = result.output || result;
@@ -10245,7 +11483,7 @@ function mapActToolResult(toolResult, args, reasoning) {
10245
11483
  }
10246
11484
  function mapFillFormToolResult(toolResult, args, reasoning) {
10247
11485
  if (!toolResult || typeof toolResult !== "object") {
10248
- return [createStandardAction("fillForm", args, reasoning)];
11486
+ return [createStandardAction("fillForm", toolResult, args, reasoning)];
10249
11487
  }
10250
11488
  const result = toolResult;
10251
11489
  const output = result.output || result;
@@ -10266,12 +11504,17 @@ function mapFillFormToolResult(toolResult, args, reasoning) {
10266
11504
  }
10267
11505
  return actions;
10268
11506
  }
10269
- function createStandardAction(toolCallName, args, reasoning) {
10270
- return __spreadValues({
11507
+ function createStandardAction(toolCallName, toolResult, args, reasoning) {
11508
+ const action = __spreadValues({
10271
11509
  type: toolCallName,
10272
11510
  reasoning,
10273
11511
  taskCompleted: toolCallName === "close" ? args == null ? void 0 : args.taskComplete : false
10274
11512
  }, args);
11513
+ if (toolCallName !== "ariaTree" && toolResult) {
11514
+ const { output } = toolResult;
11515
+ Object.assign(action, output);
11516
+ }
11517
+ return action;
10275
11518
  }
10276
11519
 
10277
11520
  // lib/v3/handlers/v3AgentHandler.ts
@@ -10416,7 +11659,7 @@ var V3AgentHandler = class {
10416
11659
  buildSystemPrompt(executionInstruction, systemInstructions) {
10417
11660
  if (systemInstructions) {
10418
11661
  return `${systemInstructions}
10419
- Your current goal: ${executionInstruction}`;
11662
+ Your current goal: ${executionInstruction} when the task is complete, use the "close" tool with taskComplete: true`;
10420
11663
  }
10421
11664
  return `You are a web automation assistant using browser automation tools to accomplish the user's goal.
10422
11665
 
@@ -37344,6 +38587,111 @@ var LOG_LEVEL_NAMES = {
37344
38587
  2: "debug"
37345
38588
  };
37346
38589
 
38590
+ // lib/v3/types/public/page.ts
38591
+ init_consoleMessage();
38592
+ init_response();
38593
+
38594
+ // examples/external_clients/aisdk.ts
38595
+ var import_ai14 = require("ai");
38596
+ var AISdkClient2 = class extends LLMClient {
38597
+ constructor({ model }) {
38598
+ super(model.modelId);
38599
+ this.type = "aisdk";
38600
+ this.model = model;
38601
+ }
38602
+ createChatCompletion(_0) {
38603
+ return __async(this, arguments, function* ({
38604
+ options
38605
+ }) {
38606
+ var _a, _b, _c, _d, _e, _f;
38607
+ const formattedMessages = options.messages.map(
38608
+ (message) => {
38609
+ if (Array.isArray(message.content)) {
38610
+ if (message.role === "system") {
38611
+ const systemMessage = {
38612
+ role: "system",
38613
+ content: message.content.map((c) => "text" in c ? c.text : "").join("\n")
38614
+ };
38615
+ return systemMessage;
38616
+ }
38617
+ const contentParts = message.content.map((content) => {
38618
+ if ("image_url" in content) {
38619
+ const imageContent = {
38620
+ type: "image",
38621
+ image: content.image_url.url
38622
+ };
38623
+ return imageContent;
38624
+ } else {
38625
+ const textContent = {
38626
+ type: "text",
38627
+ text: content.text
38628
+ };
38629
+ return textContent;
38630
+ }
38631
+ });
38632
+ if (message.role === "user") {
38633
+ const userMessage = {
38634
+ role: "user",
38635
+ content: contentParts
38636
+ };
38637
+ return userMessage;
38638
+ } else {
38639
+ const textOnlyParts = contentParts.map((part) => ({
38640
+ type: "text",
38641
+ text: part.type === "image" ? "[Image]" : part.text
38642
+ }));
38643
+ const assistantMessage = {
38644
+ role: "assistant",
38645
+ content: textOnlyParts
38646
+ };
38647
+ return assistantMessage;
38648
+ }
38649
+ }
38650
+ return {
38651
+ role: message.role,
38652
+ content: message.content
38653
+ };
38654
+ }
38655
+ );
38656
+ if (options.response_model) {
38657
+ const response2 = yield (0, import_ai14.generateObject)({
38658
+ model: this.model,
38659
+ messages: formattedMessages,
38660
+ schema: options.response_model.schema
38661
+ });
38662
+ return {
38663
+ data: response2.object,
38664
+ usage: {
38665
+ prompt_tokens: (_a = response2.usage.inputTokens) != null ? _a : 0,
38666
+ completion_tokens: (_b = response2.usage.outputTokens) != null ? _b : 0,
38667
+ total_tokens: (_c = response2.usage.totalTokens) != null ? _c : 0
38668
+ }
38669
+ };
38670
+ }
38671
+ const tools = {};
38672
+ for (const rawTool of options.tools) {
38673
+ tools[rawTool.name] = {
38674
+ description: rawTool.description,
38675
+ inputSchema: rawTool.parameters
38676
+ };
38677
+ }
38678
+ const response = yield (0, import_ai14.generateText)({
38679
+ model: this.model,
38680
+ messages: formattedMessages,
38681
+ tools
38682
+ });
38683
+ return {
38684
+ data: response.text,
38685
+ usage: {
38686
+ prompt_tokens: (_d = response.usage.inputTokens) != null ? _d : 0,
38687
+ completion_tokens: (_e = response.usage.outputTokens) != null ? _e : 0,
38688
+ total_tokens: (_f = response.usage.totalTokens) != null ? _f : 0
38689
+ }
38690
+ };
38691
+ });
38692
+ }
38693
+ };
38694
+
37347
38695
  // lib/v3/understudy/context.ts
37348
38696
  init_logger();
37349
38697
 
@@ -38351,6 +39699,85 @@ var StagehandAPIClient = class {
38351
39699
  return response;
38352
39700
  });
38353
39701
  }
39702
+ getReplayMetrics() {
39703
+ return __async(this, null, function* () {
39704
+ if (!this.sessionId) {
39705
+ throw new StagehandAPIError("sessionId is required to fetch metrics.");
39706
+ }
39707
+ const response = yield this.request(`/sessions/${this.sessionId}/replay`, {
39708
+ method: "GET"
39709
+ });
39710
+ if (response.status !== 200) {
39711
+ const errorText = yield response.text();
39712
+ this.logger({
39713
+ category: "api",
39714
+ message: `Failed to fetch metrics. Status ${response.status}: ${errorText}`,
39715
+ level: 0
39716
+ });
39717
+ throw new StagehandHttpError(
39718
+ `Failed to fetch metrics with status ${response.status}: ${errorText}`
39719
+ );
39720
+ }
39721
+ const data = yield response.json();
39722
+ if (!data.success) {
39723
+ throw new StagehandAPIError(
39724
+ `Failed to fetch metrics: ${data.error || "Unknown error"}`
39725
+ );
39726
+ }
39727
+ const apiData = data.data || {};
39728
+ const metrics = {
39729
+ actPromptTokens: 0,
39730
+ actCompletionTokens: 0,
39731
+ actInferenceTimeMs: 0,
39732
+ extractPromptTokens: 0,
39733
+ extractCompletionTokens: 0,
39734
+ extractInferenceTimeMs: 0,
39735
+ observePromptTokens: 0,
39736
+ observeCompletionTokens: 0,
39737
+ observeInferenceTimeMs: 0,
39738
+ agentPromptTokens: 0,
39739
+ agentCompletionTokens: 0,
39740
+ agentInferenceTimeMs: 0,
39741
+ totalPromptTokens: 0,
39742
+ totalCompletionTokens: 0,
39743
+ totalInferenceTimeMs: 0
39744
+ };
39745
+ const pages = apiData.pages || [];
39746
+ for (const page of pages) {
39747
+ const actions = page.actions || [];
39748
+ for (const action of actions) {
39749
+ const method = (action.method || "").toLowerCase();
39750
+ const tokenUsage = action.tokenUsage;
39751
+ if (tokenUsage) {
39752
+ const inputTokens = tokenUsage.inputTokens || 0;
39753
+ const outputTokens = tokenUsage.outputTokens || 0;
39754
+ const timeMs = tokenUsage.timeMs || 0;
39755
+ if (method === "act") {
39756
+ metrics.actPromptTokens += inputTokens;
39757
+ metrics.actCompletionTokens += outputTokens;
39758
+ metrics.actInferenceTimeMs += timeMs;
39759
+ } else if (method === "extract") {
39760
+ metrics.extractPromptTokens += inputTokens;
39761
+ metrics.extractCompletionTokens += outputTokens;
39762
+ metrics.extractInferenceTimeMs += timeMs;
39763
+ } else if (method === "observe") {
39764
+ metrics.observePromptTokens += inputTokens;
39765
+ metrics.observeCompletionTokens += outputTokens;
39766
+ metrics.observeInferenceTimeMs += timeMs;
39767
+ } else if (method === "agent") {
39768
+ metrics.agentPromptTokens += inputTokens;
39769
+ metrics.agentCompletionTokens += outputTokens;
39770
+ metrics.agentInferenceTimeMs += timeMs;
39771
+ }
39772
+ metrics.totalPromptTokens += inputTokens;
39773
+ metrics.totalCompletionTokens += outputTokens;
39774
+ metrics.totalInferenceTimeMs += timeMs;
39775
+ }
39776
+ }
39777
+ }
39778
+ return metrics;
39779
+ });
39780
+ }
38354
39781
  execute(_0) {
38355
39782
  return __async(this, arguments, function* ({
38356
39783
  method,
@@ -38531,15 +39958,15 @@ var _V3 = class _V3 {
38531
39958
  this.externalLogger = opts.logger;
38532
39959
  this.verbose = (_a = opts.verbose) != null ? _a : 1;
38533
39960
  this.instanceId = (_d = (_c = (_b = globalThis.crypto) == null ? void 0 : _b.randomUUID) == null ? void 0 : _c.call(_b)) != null ? _d : `${Date.now()}-${Math.floor(Math.random() * 1e9)}`;
38534
- this.stagehandLogger = new StagehandLogger(
38535
- {
38536
- usePino: !opts.disablePino,
38537
- pretty: true,
38538
- level: "info"
38539
- // Most permissive - filtering happens at instance level
38540
- },
38541
- opts.logger
38542
- );
39961
+ const loggerOptions = {
39962
+ pretty: true,
39963
+ level: "info"
39964
+ // Most permissive - filtering happens at instance level
39965
+ };
39966
+ if (opts.disablePino !== void 0) {
39967
+ loggerOptions.usePino = !opts.disablePino;
39968
+ }
39969
+ this.stagehandLogger = new StagehandLogger(loggerOptions, opts.logger);
38543
39970
  this.stagehandLogger.setVerbosity(this.verbose);
38544
39971
  try {
38545
39972
  if (this.externalLogger) {
@@ -38557,11 +39984,12 @@ var _V3 = class _V3 {
38557
39984
  this.logInferenceToFile = (_f = opts.logInferenceToFile) != null ? _f : false;
38558
39985
  this.llmProvider = new LLMProvider(this.logger);
38559
39986
  this.domSettleTimeoutMs = opts.domSettleTimeout;
38560
- this.disableAPI = (_g = opts.disableAPI) != null ? _g : true;
39987
+ this.disableAPI = (_g = opts.disableAPI) != null ? _g : false;
38561
39988
  const baseClientOptions = clientOptions ? __spreadValues({}, clientOptions) : {};
38562
39989
  if (opts.llmClient) {
38563
39990
  this.llmClient = opts.llmClient;
38564
39991
  this.modelClientOptions = baseClientOptions;
39992
+ this.disableAPI = true;
38565
39993
  } else {
38566
39994
  let apiKey = baseClientOptions.apiKey;
38567
39995
  if (!apiKey) {
@@ -38612,11 +40040,24 @@ var _V3 = class _V3 {
38612
40040
  this.opts = opts;
38613
40041
  _V3._instances.add(this);
38614
40042
  }
40043
+ get browserbaseSessionID() {
40044
+ return this.browserbaseSessionId;
40045
+ }
38615
40046
  /**
38616
40047
  * Async property for metrics so callers can `await v3.metrics`.
38617
- * Returning a Promise future-proofs async aggregation/storage.
40048
+ * When using API mode, fetches metrics from the API. Otherwise returns local metrics.
38618
40049
  */
38619
40050
  get metrics() {
40051
+ if (this.apiClient) {
40052
+ return this.apiClient.getReplayMetrics().catch((error) => {
40053
+ this.logger({
40054
+ category: "metrics",
40055
+ message: `Failed to fetch metrics from API: ${error}`,
40056
+ level: 0
40057
+ });
40058
+ return this.stagehandMetrics;
40059
+ });
40060
+ }
38620
40061
  return Promise.resolve(this.stagehandMetrics);
38621
40062
  }
38622
40063
  resolveLlmClient(model) {
@@ -38891,8 +40332,8 @@ var _V3 = class _V3 {
38891
40332
  let createdTemp = false;
38892
40333
  if (!userDataDir) {
38893
40334
  const base = import_path5.default.join(import_os2.default.tmpdir(), "stagehand-v3");
38894
- import_fs5.default.mkdirSync(base, { recursive: true });
38895
- userDataDir = import_fs5.default.mkdtempSync(import_path5.default.join(base, "profile-"));
40335
+ import_fs6.default.mkdirSync(base, { recursive: true });
40336
+ userDataDir = import_fs6.default.mkdtempSync(import_path5.default.join(base, "profile-"));
38896
40337
  createdTemp = true;
38897
40338
  }
38898
40339
  const defaults2 = [
@@ -39321,7 +40762,7 @@ var _V3 = class _V3 {
39321
40762
  }
39322
40763
  try {
39323
40764
  if (this.state.createdTempProfile && !this.state.preserveUserDataDir && this.state.userDataDir) {
39324
- import_fs5.default.rmSync(this.state.userDataDir, { recursive: true, force: true });
40765
+ import_fs6.default.rmSync(this.state.userDataDir, { recursive: true, force: true });
39325
40766
  }
39326
40767
  } catch (e) {
39327
40768
  }
@@ -39461,23 +40902,29 @@ var _V3 = class _V3 {
39461
40902
  * Mirrors the v2 Stagehand.agent() tool mode (no CUA provider here).
39462
40903
  */
39463
40904
  agent(options) {
39464
- var _a, _b, _c;
40905
+ var _a, _b;
39465
40906
  this.logger({
39466
40907
  category: "agent",
39467
- message: "Creating v3 agent instance with options:",
40908
+ message: `Creating v3 agent instance with options: ${JSON.stringify(options)}`,
39468
40909
  level: 1,
39469
- auxiliary: {
40910
+ auxiliary: __spreadValues({
39470
40911
  cua: { value: (options == null ? void 0 : options.cua) ? "true" : "false", type: "boolean" },
39471
- model: typeof (options == null ? void 0 : options.model) === "string" ? { value: options.model, type: "string" } : { value: options.model.modelName, type: "string" },
40912
+ 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" },
39472
40913
  systemPrompt: { value: (_a = options == null ? void 0 : options.systemPrompt) != null ? _a : "", type: "string" },
39473
- tools: { value: JSON.stringify((_b = options == null ? void 0 : options.tools) != null ? _b : {}), type: "object" },
40914
+ tools: { value: JSON.stringify((_b = options == null ? void 0 : options.tools) != null ? _b : {}), type: "object" }
40915
+ }, (options == null ? void 0 : options.integrations) && {
39474
40916
  integrations: {
39475
- value: JSON.stringify((_c = options == null ? void 0 : options.integrations) != null ? _c : []),
40917
+ value: JSON.stringify(options.integrations),
39476
40918
  type: "object"
39477
40919
  }
39478
- }
40920
+ })
39479
40921
  });
39480
40922
  if (options == null ? void 0 : options.cua) {
40923
+ if (((options == null ? void 0 : options.integrations) || (options == null ? void 0 : options.tools)) && !this.experimental) {
40924
+ throw new Error(
40925
+ "MCP integrations and custom tools are experimental. Enable experimental: true in V3 options."
40926
+ );
40927
+ }
39481
40928
  const modelToUse = (options == null ? void 0 : options.model) || __spreadValues({
39482
40929
  modelName: this.modelName
39483
40930
  }, this.modelClientOptions);
@@ -39575,9 +41022,9 @@ Do not ask follow up questions, the user will trust your judgement.`
39575
41022
  execute: (instructionOrOptions) => __async(this, null, function* () {
39576
41023
  return withInstanceLogContext(this.instanceId, () => __async(this, null, function* () {
39577
41024
  var _a2, _b2;
39578
- if ((options == null ? void 0 : options.integrations) && !this.experimental) {
41025
+ if (((options == null ? void 0 : options.integrations) || (options == null ? void 0 : options.tools)) && !this.experimental) {
39579
41026
  throw new Error(
39580
- "MCP integrations are experimental. Enable experimental: true in V3 options."
41027
+ "MCP integrations and custom tools are experimental. Enable experimental: true in V3 options."
39581
41028
  );
39582
41029
  }
39583
41030
  const tools = (options == null ? void 0 : options.integrations) ? yield resolveTools(options.integrations, options.tools) : (_a2 = options == null ? void 0 : options.tools) != null ? _a2 : {};
@@ -39882,12 +41329,14 @@ I'm providing ${screenshots.length} screenshots showing the progression of the t
39882
41329
  };
39883
41330
  // Annotate the CommonJS export names for ESM import in node:
39884
41331
  0 && (module.exports = {
41332
+ AISdkClient,
39885
41333
  AVAILABLE_CUA_MODELS,
39886
41334
  AgentProvider,
39887
41335
  AgentScreenshotProviderError,
39888
41336
  AnnotatedScreenshotText,
39889
41337
  BrowserbaseSessionNotFoundError,
39890
41338
  CaptchaTimeoutError,
41339
+ ConsoleMessage,
39891
41340
  ContentFrameNotFoundError,
39892
41341
  CreateChatCompletionResponseError,
39893
41342
  ExperimentalApiConflictError,
@@ -39900,6 +41349,7 @@ I'm providing ${screenshots.length} screenshots showing the progression of the t
39900
41349
  MCPConnectionError,
39901
41350
  MissingEnvironmentVariableError,
39902
41351
  MissingLLMConfigurationError,
41352
+ Response,
39903
41353
  Stagehand,
39904
41354
  StagehandAPIError,
39905
41355
  StagehandAPIUnauthorizedError,