browser-pilot 0.0.15 → 0.0.16

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.
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,17 +17,31 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/providers/index.ts
21
31
  var providers_exports = {};
22
32
  __export(providers_exports, {
23
33
  BrowserBaseProvider: () => BrowserBaseProvider,
34
+ BrowserEndpointResolutionError: () => BrowserEndpointResolutionError,
24
35
  BrowserlessProvider: () => BrowserlessProvider,
25
36
  GenericProvider: () => GenericProvider,
37
+ buildLocalBrowserScanTargets: () => buildLocalBrowserScanTargets,
26
38
  createProvider: () => createProvider,
39
+ discoverLocalBrowsers: () => discoverLocalBrowsers,
27
40
  discoverTargets: () => discoverTargets,
28
- getBrowserWebSocketUrl: () => getBrowserWebSocketUrl
41
+ getBrowserWebSocketUrl: () => getBrowserWebSocketUrl,
42
+ parseDevToolsActivePortFile: () => parseDevToolsActivePortFile,
43
+ resolveBrowserEndpoint: () => resolveBrowserEndpoint,
44
+ resolveChromeUserDataDirs: () => resolveChromeUserDataDirs
29
45
  });
30
46
  module.exports = __toCommonJS(providers_exports);
31
47
 
@@ -217,6 +233,619 @@ async function getBrowserWebSocketUrl(host = "localhost:9222") {
217
233
  return info.webSocketDebuggerUrl;
218
234
  }
219
235
 
236
+ // src/utils/json.ts
237
+ function isRecord(value) {
238
+ return typeof value === "object" && value !== null;
239
+ }
240
+
241
+ // src/cdp/protocol.ts
242
+ var CDPError = class extends Error {
243
+ code;
244
+ data;
245
+ constructor(error) {
246
+ super(error.message);
247
+ this.name = "CDPError";
248
+ this.code = error.code;
249
+ this.data = error.data;
250
+ }
251
+ };
252
+
253
+ // src/cdp/transport.ts
254
+ function createTransport(wsUrl, options = {}) {
255
+ const { timeout = 3e4 } = options;
256
+ return new Promise((resolve, reject) => {
257
+ const timeoutId = setTimeout(() => {
258
+ reject(new Error(`WebSocket connection timeout after ${timeout}ms`));
259
+ }, timeout);
260
+ const ws = new WebSocket(wsUrl);
261
+ const messageHandlers = [];
262
+ const closeHandlers = [];
263
+ const errorHandlers = [];
264
+ ws.addEventListener("open", () => {
265
+ clearTimeout(timeoutId);
266
+ const transport = {
267
+ send(message) {
268
+ if (ws.readyState === WebSocket.OPEN) {
269
+ ws.send(message);
270
+ } else {
271
+ throw new Error(
272
+ `Cannot send message, WebSocket is ${getReadyStateString(ws.readyState)}`
273
+ );
274
+ }
275
+ },
276
+ async close() {
277
+ return new Promise((resolveClose) => {
278
+ if (ws.readyState === WebSocket.CLOSED) {
279
+ resolveClose();
280
+ return;
281
+ }
282
+ let settled = false;
283
+ let fallbackTimer;
284
+ const finish = () => {
285
+ if (settled) return;
286
+ settled = true;
287
+ if (fallbackTimer) clearTimeout(fallbackTimer);
288
+ ws.removeEventListener("close", onClose);
289
+ resolveClose();
290
+ };
291
+ const onClose = () => {
292
+ finish();
293
+ };
294
+ ws.addEventListener("close", onClose);
295
+ try {
296
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
297
+ ws.close();
298
+ }
299
+ } catch {
300
+ finish();
301
+ return;
302
+ }
303
+ fallbackTimer = setTimeout(finish, 200);
304
+ });
305
+ },
306
+ onMessage(handler) {
307
+ messageHandlers.push(handler);
308
+ },
309
+ onClose(handler) {
310
+ closeHandlers.push(handler);
311
+ },
312
+ onError(handler) {
313
+ errorHandlers.push(handler);
314
+ }
315
+ };
316
+ resolve(transport);
317
+ });
318
+ ws.addEventListener("message", (event) => {
319
+ const data = typeof event.data === "string" ? event.data : String(event.data);
320
+ for (const handler of messageHandlers) {
321
+ handler(data);
322
+ }
323
+ });
324
+ ws.addEventListener("close", () => {
325
+ for (const handler of closeHandlers) {
326
+ handler();
327
+ }
328
+ });
329
+ ws.addEventListener("error", (_event) => {
330
+ clearTimeout(timeoutId);
331
+ const error = new Error("WebSocket connection error");
332
+ for (const handler of errorHandlers) {
333
+ handler(error);
334
+ }
335
+ reject(error);
336
+ });
337
+ });
338
+ }
339
+ function getReadyStateString(state) {
340
+ switch (state) {
341
+ case WebSocket.CONNECTING:
342
+ return "CONNECTING";
343
+ case WebSocket.OPEN:
344
+ return "OPEN";
345
+ case WebSocket.CLOSING:
346
+ return "CLOSING";
347
+ case WebSocket.CLOSED:
348
+ return "CLOSED";
349
+ default:
350
+ return "UNKNOWN";
351
+ }
352
+ }
353
+
354
+ // src/cdp/client.ts
355
+ async function createCDPClient(wsUrl, options = {}) {
356
+ const { timeout = 3e4 } = options;
357
+ const transport = await createTransport(wsUrl, { timeout });
358
+ return buildCDPClient(transport, options);
359
+ }
360
+ function buildCDPClient(transport, options = {}) {
361
+ const { debug = false, timeout = 3e4 } = options;
362
+ let messageId = 0;
363
+ let currentSessionId;
364
+ let connected = true;
365
+ const pending = /* @__PURE__ */ new Map();
366
+ const eventHandlers = /* @__PURE__ */ new Map();
367
+ const anyEventHandlers = /* @__PURE__ */ new Set();
368
+ transport.onMessage((raw) => {
369
+ let msg;
370
+ try {
371
+ const parsed = JSON.parse(raw);
372
+ if (!isRecord(parsed)) {
373
+ if (debug) console.error("[CDP] Ignoring non-object message:", raw);
374
+ return;
375
+ }
376
+ if ("id" in parsed && typeof parsed["id"] === "number") {
377
+ msg = parsed;
378
+ } else if ("method" in parsed && typeof parsed["method"] === "string") {
379
+ msg = parsed;
380
+ } else {
381
+ if (debug) console.error("[CDP] Ignoring invalid message shape:", raw);
382
+ return;
383
+ }
384
+ } catch {
385
+ if (debug) console.error("[CDP] Failed to parse message:", raw);
386
+ return;
387
+ }
388
+ if (debug) {
389
+ console.log("[CDP] <--", JSON.stringify(msg, null, 2).slice(0, 500));
390
+ }
391
+ if ("id" in msg && typeof msg.id === "number") {
392
+ const response = msg;
393
+ const request = pending.get(response.id);
394
+ if (request) {
395
+ pending.delete(response.id);
396
+ clearTimeout(request.timer);
397
+ if (response.error) {
398
+ const error = typeof response.error === "string" ? { code: -32e3, message: response.error } : response.error;
399
+ request.reject(new CDPError(error));
400
+ } else {
401
+ request.resolve(response.result);
402
+ }
403
+ }
404
+ return;
405
+ }
406
+ if ("method" in msg) {
407
+ const event = msg;
408
+ const params = event.params ?? {};
409
+ for (const handler of anyEventHandlers) {
410
+ try {
411
+ handler(event.method, params);
412
+ } catch (e) {
413
+ if (debug) console.error("[CDP] Error in any-event handler:", e);
414
+ }
415
+ }
416
+ const handlers = eventHandlers.get(event.method);
417
+ if (handlers) {
418
+ for (const handler of handlers) {
419
+ try {
420
+ handler(params);
421
+ } catch (e) {
422
+ if (debug) console.error(`[CDP] Error in handler for ${event.method}:`, e);
423
+ }
424
+ }
425
+ }
426
+ }
427
+ });
428
+ transport.onClose(() => {
429
+ connected = false;
430
+ for (const [id, request] of pending) {
431
+ clearTimeout(request.timer);
432
+ request.reject(new Error("WebSocket connection closed"));
433
+ pending.delete(id);
434
+ }
435
+ });
436
+ transport.onError((error) => {
437
+ if (debug) console.error("[CDP] Transport error:", error);
438
+ });
439
+ const client = {
440
+ async send(method, params, sessionId) {
441
+ if (!connected) {
442
+ throw new Error("CDP client is not connected");
443
+ }
444
+ const id = ++messageId;
445
+ const effectiveSessionId = sessionId === null ? void 0 : sessionId ?? currentSessionId;
446
+ const request = { id, method };
447
+ if (params !== void 0) {
448
+ request.params = params;
449
+ }
450
+ if (effectiveSessionId !== void 0) {
451
+ request.sessionId = effectiveSessionId;
452
+ }
453
+ const message = JSON.stringify(request);
454
+ if (debug) {
455
+ console.log("[CDP] -->", message.slice(0, 500));
456
+ }
457
+ return new Promise((resolve, reject) => {
458
+ const timer = setTimeout(() => {
459
+ pending.delete(id);
460
+ reject(new Error(`CDP command ${method} timed out after ${timeout}ms`));
461
+ }, timeout);
462
+ pending.set(id, {
463
+ resolve,
464
+ reject,
465
+ method,
466
+ timer
467
+ });
468
+ try {
469
+ transport.send(message);
470
+ } catch (e) {
471
+ pending.delete(id);
472
+ clearTimeout(timer);
473
+ reject(e);
474
+ }
475
+ });
476
+ },
477
+ on(event, handler) {
478
+ let handlers = eventHandlers.get(event);
479
+ if (!handlers) {
480
+ handlers = /* @__PURE__ */ new Set();
481
+ eventHandlers.set(event, handlers);
482
+ }
483
+ handlers.add(handler);
484
+ },
485
+ off(event, handler) {
486
+ const handlers = eventHandlers.get(event);
487
+ if (handlers) {
488
+ handlers.delete(handler);
489
+ if (handlers.size === 0) {
490
+ eventHandlers.delete(event);
491
+ }
492
+ }
493
+ },
494
+ onAny(handler) {
495
+ anyEventHandlers.add(handler);
496
+ },
497
+ offAny(handler) {
498
+ anyEventHandlers.delete(handler);
499
+ },
500
+ async close() {
501
+ connected = false;
502
+ await transport.close();
503
+ },
504
+ async attachToTarget(targetId) {
505
+ const result = await this.send("Target.attachToTarget", {
506
+ targetId,
507
+ flatten: true
508
+ });
509
+ currentSessionId = result.sessionId;
510
+ return result.sessionId;
511
+ },
512
+ get sessionId() {
513
+ return currentSessionId;
514
+ },
515
+ setSessionId(sessionId) {
516
+ currentSessionId = sessionId;
517
+ },
518
+ get isConnected() {
519
+ return connected;
520
+ }
521
+ };
522
+ return client;
523
+ }
524
+
525
+ // src/providers/local-discovery.ts
526
+ var CHANNEL_ORDER = ["stable", "beta", "dev", "canary"];
527
+ var DEFAULT_PROBE_TIMEOUT_MS = 1e3;
528
+ var DevToolsActivePortParseError = class extends Error {
529
+ constructor(message, reason) {
530
+ super(message);
531
+ this.reason = reason;
532
+ this.name = "DevToolsActivePortParseError";
533
+ }
534
+ };
535
+ function getRuntimeEnv() {
536
+ if (typeof process === "undefined") {
537
+ return {};
538
+ }
539
+ return process.env;
540
+ }
541
+ function getRuntimePlatform() {
542
+ if (typeof process === "undefined") {
543
+ return void 0;
544
+ }
545
+ return process.platform;
546
+ }
547
+ function normalizePlatform(platform) {
548
+ if (platform === "darwin" || platform === "linux" || platform === "win32") {
549
+ return platform;
550
+ }
551
+ throw new Error(`Unsupported platform: ${platform ?? "unknown"}`);
552
+ }
553
+ function trimTrailingSeparator(path) {
554
+ return path.replace(/[\\/]+$/, "");
555
+ }
556
+ function joinPath(platform, ...parts) {
557
+ const separator = platform === "win32" ? "\\" : "/";
558
+ const cleaned = parts.map((part, index) => {
559
+ if (index === 0) return trimTrailingSeparator(part);
560
+ return part.replace(/^[\\/]+/, "").replace(/[\\/]+$/, "");
561
+ }).filter((part) => part.length > 0);
562
+ return cleaned.join(separator);
563
+ }
564
+ function resolveHomeDir(platform, env, explicitHomeDir) {
565
+ if (explicitHomeDir) {
566
+ return explicitHomeDir;
567
+ }
568
+ if (platform === "win32") {
569
+ return env["USERPROFILE"] ?? env["HOME"] ?? "";
570
+ }
571
+ return env["HOME"] ?? env["USERPROFILE"] ?? "";
572
+ }
573
+ function toFileFailure(target, error) {
574
+ const errno = error?.code;
575
+ if (errno === "ENOENT") {
576
+ return {
577
+ ...target,
578
+ reason: "missing-file",
579
+ message: `DevToolsActivePort not found at ${target.portFile}`
580
+ };
581
+ }
582
+ return {
583
+ ...target,
584
+ reason: "unreadable-file",
585
+ message: error instanceof Error ? error.message : `Could not read DevToolsActivePort at ${target.portFile}`
586
+ };
587
+ }
588
+ function toProbeFailure(target, wsUrl, error) {
589
+ const message = error instanceof Error ? error.message : String(error);
590
+ const lowerMessage = message.toLowerCase();
591
+ let reason = "connection-error";
592
+ if (lowerMessage.includes("refused") || lowerMessage.includes("econnrefused")) {
593
+ reason = "connection-refused";
594
+ } else if (lowerMessage.includes("timeout") || lowerMessage.includes("timed out")) {
595
+ reason = "connection-timeout";
596
+ } else if (lowerMessage.includes("closed")) {
597
+ reason = "unexpected-close";
598
+ } else if (lowerMessage.includes("browser.getversion") || lowerMessage.includes("cdp") || lowerMessage.includes("protocol")) {
599
+ reason = "cdp-error";
600
+ }
601
+ return {
602
+ ...target,
603
+ wsUrl,
604
+ reason,
605
+ message
606
+ };
607
+ }
608
+ async function readTextFile(path) {
609
+ const fs = await import("fs/promises");
610
+ return fs.readFile(path, "utf-8");
611
+ }
612
+ async function probeBrowserWebSocket(wsUrl, timeoutMs) {
613
+ let client;
614
+ try {
615
+ client = await createCDPClient(wsUrl, { timeout: timeoutMs });
616
+ const version = await client.send("Browser.getVersion", void 0, null);
617
+ return { browserVersion: version.product };
618
+ } finally {
619
+ await client?.close().catch(() => {
620
+ });
621
+ }
622
+ }
623
+ var defaultDependencies = {
624
+ readTextFile,
625
+ probeBrowserWebSocket,
626
+ getLegacyBrowserWebSocketUrl: getBrowserWebSocketUrl
627
+ };
628
+ function resolveChromeUserDataDirs(options = {}) {
629
+ const env = options.env ?? getRuntimeEnv();
630
+ const platform = normalizePlatform(options.platform ?? getRuntimePlatform());
631
+ const homeDir = resolveHomeDir(platform, env, options.homeDir);
632
+ if (!homeDir) {
633
+ throw new Error("Could not determine home directory for local Chrome discovery");
634
+ }
635
+ switch (platform) {
636
+ case "darwin": {
637
+ const base = joinPath(platform, homeDir, "Library", "Application Support", "Google");
638
+ return {
639
+ stable: joinPath(platform, base, "Chrome"),
640
+ beta: joinPath(platform, base, "Chrome Beta"),
641
+ dev: joinPath(platform, base, "Chrome Dev"),
642
+ canary: joinPath(platform, base, "Chrome Canary")
643
+ };
644
+ }
645
+ case "linux": {
646
+ const configHome = env["CHROME_CONFIG_HOME"] ?? env["XDG_CONFIG_HOME"] ?? joinPath(platform, homeDir, ".config");
647
+ return {
648
+ stable: joinPath(platform, configHome, "google-chrome"),
649
+ beta: joinPath(platform, configHome, "google-chrome-beta"),
650
+ dev: joinPath(platform, configHome, "google-chrome-dev"),
651
+ canary: joinPath(platform, configHome, "google-chrome-canary")
652
+ };
653
+ }
654
+ case "win32": {
655
+ const localAppData = env["LOCALAPPDATA"] ?? joinPath(platform, homeDir, "AppData", "Local");
656
+ const base = joinPath(platform, localAppData, "Google");
657
+ return {
658
+ stable: joinPath(platform, base, "Chrome", "User Data"),
659
+ beta: joinPath(platform, base, "Chrome Beta", "User Data"),
660
+ dev: joinPath(platform, base, "Chrome Dev", "User Data"),
661
+ canary: joinPath(platform, base, "Chrome SxS", "User Data")
662
+ };
663
+ }
664
+ }
665
+ throw new Error(`Unsupported platform for local Chrome discovery: ${platform}`);
666
+ }
667
+ function buildLocalBrowserScanTargets(options = {}) {
668
+ const env = options.env ?? getRuntimeEnv();
669
+ const platform = normalizePlatform(options.platform ?? getRuntimePlatform());
670
+ if (options.userDataDir) {
671
+ return [
672
+ {
673
+ channel: options.channel ?? "custom",
674
+ userDataDir: options.userDataDir,
675
+ portFile: joinPath(platform, options.userDataDir, "DevToolsActivePort")
676
+ }
677
+ ];
678
+ }
679
+ const dirs = resolveChromeUserDataDirs({
680
+ platform,
681
+ env,
682
+ homeDir: options.homeDir
683
+ });
684
+ const channels = options.channel ? [options.channel] : CHANNEL_ORDER;
685
+ return channels.map((channel) => ({
686
+ channel,
687
+ userDataDir: dirs[channel],
688
+ portFile: joinPath(platform, dirs[channel], "DevToolsActivePort")
689
+ }));
690
+ }
691
+ function parseDevToolsActivePortFile(content) {
692
+ const lines = content.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0);
693
+ if (lines.length !== 2) {
694
+ throw new DevToolsActivePortParseError(
695
+ `Expected exactly 2 non-empty lines in DevToolsActivePort, got ${lines.length}`,
696
+ "malformed-file"
697
+ );
698
+ }
699
+ const portText = lines[0];
700
+ const browserPath = lines[1];
701
+ const port = Number.parseInt(portText, 10);
702
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
703
+ throw new DevToolsActivePortParseError(
704
+ `Invalid DevToolsActivePort port: ${portText}`,
705
+ "invalid-port"
706
+ );
707
+ }
708
+ if (!browserPath.startsWith("/devtools/browser/") || browserPath.includes("..") || /[?#\s\\]/u.test(browserPath)) {
709
+ throw new DevToolsActivePortParseError(
710
+ `Invalid DevToolsActivePort browser path: ${browserPath}`,
711
+ "invalid-path"
712
+ );
713
+ }
714
+ return {
715
+ port,
716
+ browserPath,
717
+ wsUrl: `ws://127.0.0.1:${port}${browserPath}`
718
+ };
719
+ }
720
+ async function inspectScanTarget(target, options, deps) {
721
+ let content;
722
+ try {
723
+ content = await deps.readTextFile(target.portFile);
724
+ } catch (error) {
725
+ return { kind: "failure", failure: toFileFailure(target, error) };
726
+ }
727
+ let parsed;
728
+ try {
729
+ parsed = parseDevToolsActivePortFile(content);
730
+ } catch (error) {
731
+ if (error instanceof DevToolsActivePortParseError) {
732
+ return {
733
+ kind: "failure",
734
+ failure: {
735
+ ...target,
736
+ reason: error.reason,
737
+ message: error.message
738
+ }
739
+ };
740
+ }
741
+ throw error;
742
+ }
743
+ try {
744
+ const probe = await deps.probeBrowserWebSocket(
745
+ parsed.wsUrl,
746
+ options.probeTimeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS
747
+ );
748
+ return {
749
+ kind: "candidate",
750
+ candidate: {
751
+ ...target,
752
+ port: parsed.port,
753
+ browserPath: parsed.browserPath,
754
+ wsUrl: parsed.wsUrl,
755
+ browserVersion: probe.browserVersion
756
+ }
757
+ };
758
+ } catch (error) {
759
+ return {
760
+ kind: "failure",
761
+ failure: toProbeFailure(target, parsed.wsUrl, error)
762
+ };
763
+ }
764
+ }
765
+ async function discoverLocalBrowsers(options = {}, deps = defaultDependencies) {
766
+ const scanTargets = buildLocalBrowserScanTargets(options);
767
+ const outcomes = await Promise.all(
768
+ scanTargets.map((target) => inspectScanTarget(target, options, deps))
769
+ );
770
+ const candidates = [];
771
+ const failures = [];
772
+ for (const outcome of outcomes) {
773
+ if (outcome.kind === "candidate") {
774
+ candidates.push(outcome.candidate);
775
+ } else {
776
+ failures.push(outcome.failure);
777
+ }
778
+ }
779
+ return { candidates, failures };
780
+ }
781
+ var BrowserEndpointResolutionError = class extends Error {
782
+ constructor(code, message, details = {}) {
783
+ super(message);
784
+ this.code = code;
785
+ this.details = details;
786
+ }
787
+ name = "BrowserEndpointResolutionError";
788
+ };
789
+ async function resolveBrowserEndpoint(options = {}, deps = defaultDependencies) {
790
+ if (options.explicitWsUrl) {
791
+ return {
792
+ wsUrl: options.explicitWsUrl,
793
+ source: "explicit-ws"
794
+ };
795
+ }
796
+ let localDiscovery;
797
+ if (options.allowLocalDiscovery ?? true) {
798
+ localDiscovery = await discoverLocalBrowsers(options, deps);
799
+ if (localDiscovery.candidates.length === 1) {
800
+ const candidate = localDiscovery.candidates[0];
801
+ return {
802
+ wsUrl: candidate.wsUrl,
803
+ source: "devtools-active-port",
804
+ channel: candidate.channel,
805
+ userDataDir: candidate.userDataDir
806
+ };
807
+ }
808
+ if (localDiscovery.candidates.length > 1) {
809
+ throw new BrowserEndpointResolutionError(
810
+ "multiple-local-browsers",
811
+ "Multiple local Chrome profiles are available for auto-discovery",
812
+ {
813
+ candidates: localDiscovery.candidates,
814
+ failures: localDiscovery.failures
815
+ }
816
+ );
817
+ }
818
+ }
819
+ if (options.allowLegacyHostFallback ?? true) {
820
+ const legacyHost = options.legacyHost ?? "localhost:9222";
821
+ try {
822
+ return {
823
+ wsUrl: await deps.getLegacyBrowserWebSocketUrl(legacyHost),
824
+ source: "json-version"
825
+ };
826
+ } catch (error) {
827
+ throw new BrowserEndpointResolutionError(
828
+ "browser-not-found",
829
+ "Could not resolve a browser endpoint",
830
+ {
831
+ candidates: localDiscovery?.candidates,
832
+ failures: localDiscovery?.failures,
833
+ legacyError: error instanceof Error ? error : new Error(String(error)),
834
+ legacyHost
835
+ }
836
+ );
837
+ }
838
+ }
839
+ throw new BrowserEndpointResolutionError(
840
+ "browser-not-found",
841
+ "Could not resolve a browser endpoint",
842
+ {
843
+ candidates: localDiscovery?.candidates,
844
+ failures: localDiscovery?.failures
845
+ }
846
+ );
847
+ }
848
+
220
849
  // src/providers/index.ts
221
850
  function createProvider(options) {
222
851
  switch (options.provider) {
@@ -252,9 +881,15 @@ function createProvider(options) {
252
881
  // Annotate the CommonJS export names for ESM import in node:
253
882
  0 && (module.exports = {
254
883
  BrowserBaseProvider,
884
+ BrowserEndpointResolutionError,
255
885
  BrowserlessProvider,
256
886
  GenericProvider,
887
+ buildLocalBrowserScanTargets,
257
888
  createProvider,
889
+ discoverLocalBrowsers,
258
890
  discoverTargets,
259
- getBrowserWebSocketUrl
891
+ getBrowserWebSocketUrl,
892
+ parseDevToolsActivePortFile,
893
+ resolveBrowserEndpoint,
894
+ resolveChromeUserDataDirs
260
895
  });
@@ -1,5 +1,5 @@
1
- import { P as Provider, C as CreateSessionOptions, a as ProviderSession, b as ConnectOptions } from './types--wXNHUwt.cjs';
2
- export { c as ProxyConfig } from './types--wXNHUwt.cjs';
1
+ import { P as Provider, C as CreateSessionOptions, a as ProviderSession, b as ConnectOptions } from './types-DeVSWhXj.cjs';
2
+ export { B as BrowserEndpointResolutionError, d as ChromeChannel, e as ChromeUserDataDirOptions, D as DiscoverLocalBrowsersOptions, i as ProxyConfig, R as ResolvedBrowserEndpoint, g as ResolvedBrowserSource, c as buildLocalBrowserScanTargets, f as discoverLocalBrowsers, p as parseDevToolsActivePortFile, r as resolveBrowserEndpoint, h as resolveChromeUserDataDirs } from './types-DeVSWhXj.cjs';
3
3
 
4
4
  /**
5
5
  * BrowserBase provider implementation
@@ -1,5 +1,5 @@
1
- import { P as Provider, C as CreateSessionOptions, a as ProviderSession, b as ConnectOptions } from './types--wXNHUwt.js';
2
- export { c as ProxyConfig } from './types--wXNHUwt.js';
1
+ import { P as Provider, C as CreateSessionOptions, a as ProviderSession, b as ConnectOptions } from './types-DeVSWhXj.js';
2
+ export { B as BrowserEndpointResolutionError, d as ChromeChannel, e as ChromeUserDataDirOptions, D as DiscoverLocalBrowsersOptions, i as ProxyConfig, R as ResolvedBrowserEndpoint, g as ResolvedBrowserSource, c as buildLocalBrowserScanTargets, f as discoverLocalBrowsers, p as parseDevToolsActivePortFile, r as resolveBrowserEndpoint, h as resolveChromeUserDataDirs } from './types-DeVSWhXj.js';
3
3
 
4
4
  /**
5
5
  * BrowserBase provider implementation