@mastra/agent-browser 0.0.0 → 0.1.0

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
@@ -5,12 +5,9 @@ import { z } from 'zod';
5
5
 
6
6
  // src/agent-browser.ts
7
7
  var AgentBrowserThreadManager = class extends ThreadManager {
8
- sharedManager = null;
9
8
  browserConfig;
10
9
  resolveCdpUrl;
11
10
  onBrowserCreated;
12
- /** Map of thread ID to dedicated browser manager (for 'thread' scope) */
13
- threadBrowsers = /* @__PURE__ */ new Map();
14
11
  constructor(config) {
15
12
  super(config);
16
13
  this.browserConfig = config.browserConfig;
@@ -18,25 +15,11 @@ var AgentBrowserThreadManager = class extends ThreadManager {
18
15
  this.onBrowserCreated = config.onBrowserCreated;
19
16
  }
20
17
  /**
21
- * Set the shared browser manager (called after browser launch).
18
+ * Get the page for a specific thread, creating session if needed.
22
19
  */
23
- setSharedManager(manager) {
24
- this.sharedManager = manager;
25
- }
26
- /**
27
- * Clear the shared browser manager (called when browser disconnects).
28
- */
29
- clearSharedManager() {
30
- this.sharedManager = null;
31
- }
32
- /**
33
- * Get the shared browser manager.
34
- */
35
- getSharedManager() {
36
- if (!this.sharedManager) {
37
- throw new Error("Browser not launched");
38
- }
39
- return this.sharedManager;
20
+ async getPageForThread(threadId) {
21
+ const manager = await this.getManagerForThread(threadId);
22
+ return manager.getPage();
40
23
  }
41
24
  /**
42
25
  * Create a new session for a thread.
@@ -67,7 +50,7 @@ var AgentBrowserThreadManager = class extends ThreadManager {
67
50
  throw error;
68
51
  }
69
52
  session.manager = manager;
70
- this.threadBrowsers.set(threadId, manager);
53
+ this.threadManagers.set(threadId, manager);
71
54
  try {
72
55
  if (savedState && savedState.tabs.length > 0) {
73
56
  this.logger?.debug?.(`Restoring browser state for thread ${threadId}: ${savedState.tabs.length} tabs`);
@@ -75,7 +58,7 @@ var AgentBrowserThreadManager = class extends ThreadManager {
75
58
  }
76
59
  this.onBrowserCreated?.(manager, threadId);
77
60
  } catch (error) {
78
- this.threadBrowsers.delete(threadId);
61
+ this.threadManagers.delete(threadId);
79
62
  session.manager = void 0;
80
63
  try {
81
64
  await manager.close();
@@ -115,13 +98,6 @@ var AgentBrowserThreadManager = class extends ThreadManager {
115
98
  this.logger?.warn?.(`Failed to restore browser state: ${error}`);
116
99
  }
117
100
  }
118
- /**
119
- * Switch to an existing session.
120
- * For 'thread' scope, no switching needed - each thread has its own manager.
121
- * For 'shared' scope, nothing to switch.
122
- */
123
- async switchToSession(_session) {
124
- }
125
101
  /**
126
102
  * Get the browser manager for a specific session.
127
103
  */
@@ -137,61 +113,15 @@ var AgentBrowserThreadManager = class extends ThreadManager {
137
113
  async doDestroySession(session) {
138
114
  if (this.scope === "thread" && session.manager) {
139
115
  await session.manager.close();
140
- this.threadBrowsers.delete(session.threadId);
141
116
  }
142
117
  }
143
118
  /**
144
119
  * Destroy all sessions (called during browser close).
120
+ * doDestroySession handles closing individual browser managers.
145
121
  */
146
122
  async destroyAllSessions() {
147
- for (const [threadId, manager] of this.threadBrowsers) {
148
- try {
149
- await manager.close();
150
- } catch {
151
- this.logger?.debug?.(`Failed to close browser for thread: ${threadId}`);
152
- }
153
- }
154
- this.threadBrowsers.clear();
155
123
  await super.destroyAllSessions();
156
124
  }
157
- /**
158
- * Check if any thread browsers are still running.
159
- */
160
- hasActiveThreadBrowsers() {
161
- return this.threadBrowsers.size > 0;
162
- }
163
- /**
164
- * Get the browser manager for an existing thread session without creating a new one.
165
- * Returns null if no session exists for the thread.
166
- */
167
- getExistingManagerForThread(threadId) {
168
- if (this.scope === "thread") {
169
- return this.threadBrowsers.get(threadId) ?? null;
170
- }
171
- return this.sharedManager;
172
- }
173
- /**
174
- * Clear all session tracking without closing browsers.
175
- * Used when browsers have been externally closed and we just need to reset state.
176
- */
177
- clearAllSessions() {
178
- this.threadBrowsers.clear();
179
- this.sessions.clear();
180
- }
181
- /**
182
- * Clear a specific thread's session without closing the browser.
183
- * Used when a thread's browser has been externally closed.
184
- * Preserves the browser state for potential restoration.
185
- * @param threadId - The thread ID to clear
186
- */
187
- clearSession(threadId) {
188
- const session = this.sessions.get(threadId);
189
- if (session?.browserState) {
190
- this.savedBrowserStates.set(threadId, session.browserState);
191
- }
192
- this.threadBrowsers.delete(threadId);
193
- this.sessions.delete(threadId);
194
- }
195
125
  };
196
126
  var gotoInputSchema = z.object({
197
127
  url: z.string().describe("The URL to navigate to"),
@@ -560,30 +490,18 @@ function createAgentBrowserTools(browser) {
560
490
  }
561
491
 
562
492
  // src/agent-browser.ts
563
- var AgentBrowser = class _AgentBrowser extends MastraBrowser {
493
+ var AgentBrowser = class extends MastraBrowser {
564
494
  id;
565
495
  name = "AgentBrowser";
566
496
  provider = "vercel-labs/agent-browser";
567
- /** Primary browser manager (for 'none' mode, also used as fallback) */
568
- browserManager = null;
569
497
  defaultTimeout = 3e4;
570
- /** Active screencast streams per thread (for triggering reconnects on tab changes) */
571
- activeScreencastStreams = /* @__PURE__ */ new Map();
572
- /** Default key for shared scope */
573
- static SHARED_STREAM_KEY = "__shared__";
574
498
  constructor(config = {}) {
575
499
  super(config);
576
500
  this.id = `agent-browser-${Date.now()}`;
577
501
  if (config.timeout) {
578
502
  this.defaultTimeout = config.timeout;
579
503
  }
580
- let effectiveScope = config.scope ?? "thread";
581
- if (config.cdpUrl && effectiveScope === "thread") {
582
- this.logger.warn?.(
583
- 'Browser scope "thread" is not supported when connecting via cdpUrl. Falling back to "shared" (shared browser connection).'
584
- );
585
- effectiveScope = "shared";
586
- }
504
+ const effectiveScope = config.cdpUrl ? config.scope ?? "shared" : config.scope ?? "thread";
587
505
  this.threadManager = new AgentBrowserThreadManager({
588
506
  scope: effectiveScope,
589
507
  browserConfig: config,
@@ -600,13 +518,13 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
600
518
  });
601
519
  }
602
520
  // ---------------------------------------------------------------------------
603
- // Thread Isolation (delegated to ThreadManager)
521
+ // Thread Scope (delegated to ThreadManager)
604
522
  // ---------------------------------------------------------------------------
605
523
  /**
606
524
  * Ensure browser is ready and thread session exists.
607
525
  * Creates a new page/context for the current thread if needed.
608
526
  *
609
- * For 'browser' isolation, we need to create the thread session BEFORE
527
+ * For 'thread' scope, we need to create the thread session BEFORE
610
528
  * calling super.ensureReady() because the base class's ensureReady() will
611
529
  * call checkBrowserAlive(), which needs at least one thread browser to exist.
612
530
  */
@@ -624,7 +542,7 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
624
542
  }
625
543
  /**
626
544
  * Get the browser manager for the current thread.
627
- * Delegates to ThreadManager for isolation handling.
545
+ * Delegates to ThreadManager for scope handling.
628
546
  */
629
547
  async getManagerForThread(threadId) {
630
548
  const effectiveThreadId = threadId ?? this.getCurrentThread();
@@ -637,33 +555,17 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
637
555
  }
638
556
  return this.threadManager.getManagerForThread(effectiveThreadId);
639
557
  }
640
- /**
641
- * Get the page for a specific thread.
642
- * For thread-isolated modes, ensures we're on the correct context/page.
643
- */
644
- async getPageForThread(threadId) {
645
- const manager = await this.getManagerForThread(threadId);
646
- return manager.getPage();
647
- }
648
- /**
649
- * Close a specific thread's browser session.
650
- * Delegates to ThreadManager and notifies registered callbacks.
651
- */
652
- async closeThreadSession(threadId) {
653
- await this.threadManager.destroySession(threadId);
654
- this.notifyBrowserClosed(threadId);
655
- }
656
558
  // ---------------------------------------------------------------------------
657
559
  // Lifecycle
658
560
  // ---------------------------------------------------------------------------
659
561
  async doLaunch() {
660
562
  const scope = this.threadManager.getScope();
661
563
  if (scope === "thread") {
662
- this.browserManager = new BrowserManager();
663
- this.threadManager.setSharedManager(this.browserManager);
564
+ this.sharedManager = new BrowserManager();
565
+ this.threadManager.setSharedManager(this.sharedManager);
664
566
  return;
665
567
  }
666
- this.browserManager = new BrowserManager();
568
+ this.sharedManager = new BrowserManager();
667
569
  const localConfig = this.config;
668
570
  const launchOptions = {
669
571
  headless: localConfig.headless ?? true,
@@ -672,15 +574,15 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
672
574
  if (localConfig.cdpUrl) {
673
575
  launchOptions.cdpUrl = await this.resolveCdpUrl(localConfig.cdpUrl);
674
576
  }
675
- await this.browserManager.launch(launchOptions);
676
- this.threadManager.setSharedManager(this.browserManager);
677
- this.setupCloseListenerForNoneIsolation(this.browserManager);
577
+ await this.sharedManager.launch(launchOptions);
578
+ this.threadManager.setSharedManager(this.sharedManager);
579
+ this.setupCloseListenerForSharedScope(this.sharedManager);
678
580
  }
679
581
  /**
680
- * Set up close event listeners for 'none' isolation shared browser.
582
+ * Set up close event listeners for 'shared' scope browser.
681
583
  * This handles the case where the shared browser is closed externally.
682
584
  */
683
- setupCloseListenerForNoneIsolation(manager) {
585
+ setupCloseListenerForSharedScope(manager) {
684
586
  try {
685
587
  let disconnectHandled = false;
686
588
  const handleDisconnect = () => {
@@ -708,10 +610,10 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
708
610
  await this.threadManager.destroyAllSessions();
709
611
  this.setCurrentThread(void 0);
710
612
  const scope = this.threadManager.getScope();
711
- if (scope === "shared" && this.browserManager) {
712
- await this.browserManager.close();
613
+ if (scope === "shared" && this.sharedManager) {
614
+ await this.sharedManager.close();
713
615
  }
714
- this.browserManager = null;
616
+ this.sharedManager = null;
715
617
  }
716
618
  /**
717
619
  * Check if the browser is still alive by verifying the page is connected.
@@ -720,13 +622,13 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
720
622
  async checkBrowserAlive() {
721
623
  const scope = this.threadManager.getScope();
722
624
  if (scope === "thread") {
723
- return this.threadManager.hasActiveThreadBrowsers();
625
+ return this.threadManager.hasActiveThreadManagers();
724
626
  }
725
- if (!this.browserManager) {
627
+ if (!this.sharedManager) {
726
628
  return false;
727
629
  }
728
630
  try {
729
- const page = this.browserManager.getPage();
631
+ const page = this.sharedManager.getPage();
730
632
  const url = page.url();
731
633
  if (url && url !== "about:blank") {
732
634
  const state = await this.getBrowserState();
@@ -765,28 +667,21 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
765
667
  async getPage(explicitThreadId) {
766
668
  const scope = this.getScope();
767
669
  const threadId = explicitThreadId ?? this.getCurrentThread();
768
- if (scope === "thread" || scope !== "shared" && threadId !== DEFAULT_THREAD_ID) {
769
- return this.getPageForThread(threadId);
670
+ if (scope === "thread") {
671
+ return this.threadManager.getPageForThread(threadId);
770
672
  }
771
- if (!this.browserManager) throw new Error("Browser not launched");
772
- return this.browserManager.getPage();
673
+ if (!this.sharedManager) throw new Error("Browser not launched");
674
+ return this.sharedManager.getPage();
773
675
  }
774
676
  /**
775
- * Handle browser disconnection by clearing internal state.
776
- * For 'thread' scope, only notifies the specific thread's callbacks.
777
- * For 'shared' scope, notifies all callbacks.
677
+ * Get the active page for a thread (implements abstract method from base class).
678
+ * Returns null if no page is available, unlike getPage which throws.
778
679
  */
779
- handleBrowserDisconnected() {
780
- const scope = this.threadManager.getScope();
781
- const threadId = this.getCurrentThread();
782
- if (scope === "thread" && threadId !== DEFAULT_THREAD_ID) {
783
- this.threadManager.clearSession(threadId);
784
- this.logger.debug?.(`Cleared browser session for thread: ${threadId}`);
785
- this.notifyBrowserClosed(threadId);
786
- } else {
787
- this.browserManager = null;
788
- this.threadManager.clearSharedManager();
789
- super.handleBrowserDisconnected();
680
+ async getActivePage(threadId) {
681
+ try {
682
+ return await this.getPage(threadId);
683
+ } catch {
684
+ return null;
790
685
  }
791
686
  }
792
687
  /**
@@ -817,15 +712,6 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
817
712
  } catch {
818
713
  }
819
714
  }
820
- /**
821
- * Handle browser disconnection for a specific thread.
822
- * Called when a thread's browser is closed externally.
823
- */
824
- handleThreadBrowserDisconnected(threadId) {
825
- this.threadManager.clearSession(threadId);
826
- this.logger.debug?.(`Cleared browser session for thread: ${threadId}`);
827
- this.notifyBrowserClosed(threadId);
828
- }
829
715
  /**
830
716
  * Create an error response from an exception.
831
717
  * Extends base class to add agent-browser specific error handling.
@@ -949,6 +835,16 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
949
835
  return null;
950
836
  }
951
837
  }
838
+ /**
839
+ * Get browser state for a thread (implements abstract method from base class).
840
+ * Sync version that uses existing manager lookup without creating sessions.
841
+ */
842
+ getBrowserStateForThread(threadId) {
843
+ const effectiveThreadId = threadId ?? this.getCurrentThread() ?? DEFAULT_THREAD_ID;
844
+ const manager = this.threadManager.getExistingManagerForThread(effectiveThreadId);
845
+ if (!manager) return null;
846
+ return this.getBrowserStateForManager(manager);
847
+ }
952
848
  /**
953
849
  * Get browser state from a specific manager instance.
954
850
  */
@@ -988,29 +884,6 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
988
884
  return 0;
989
885
  }
990
886
  }
991
- /**
992
- * Update the browser state in the thread session.
993
- * Called on navigation, tab open/close to keep state fresh.
994
- */
995
- updateSessionBrowserState(threadId) {
996
- try {
997
- const effectiveThreadId = threadId ?? this.getCurrentThread() ?? DEFAULT_THREAD_ID;
998
- const scope = this.threadManager.getScope();
999
- let manager = null;
1000
- if (scope === "thread") {
1001
- manager = this.threadManager.getExistingManagerForThread(effectiveThreadId);
1002
- } else {
1003
- manager = this.browserManager;
1004
- }
1005
- if (manager) {
1006
- const state = this.getBrowserStateForManager(manager);
1007
- if (state) {
1008
- this.threadManager.updateBrowserState(effectiveThreadId, state);
1009
- }
1010
- }
1011
- } catch {
1012
- }
1013
- }
1014
887
  // ---------------------------------------------------------------------------
1015
888
  // 1. browser_goto - Navigate to URL
1016
889
  // ---------------------------------------------------------------------------
@@ -1433,10 +1306,10 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
1433
1306
  );
1434
1307
  }
1435
1308
  await browser.switchTo(input.index);
1436
- await this.reconnectScreencast("tab switch");
1309
+ await this.reconnectScreencastForThread(threadId, "tab switch");
1437
1310
  const page = browser.getPage();
1438
1311
  const pageUrl = page.url();
1439
- const streamKey = this.getStreamKey(this.getCurrentThread());
1312
+ const streamKey = this.getStreamKey(threadId);
1440
1313
  const stream = this.activeScreencastStreams.get(streamKey);
1441
1314
  if (pageUrl && stream?.isActive()) {
1442
1315
  stream.emitUrl(pageUrl);
@@ -1459,7 +1332,7 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
1459
1332
  );
1460
1333
  }
1461
1334
  await browser.closeTab(input.index);
1462
- await this.reconnectScreencast("tab close");
1335
+ await this.reconnectScreencastForThread(threadId, "tab close");
1463
1336
  this.updateSessionBrowserState(threadId);
1464
1337
  const tabsList = await browser.listTabs?.() ?? [];
1465
1338
  return {
@@ -1555,47 +1428,15 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
1555
1428
  // ---------------------------------------------------------------------------
1556
1429
  // Screencast (for Studio live view)
1557
1430
  // ---------------------------------------------------------------------------
1558
- /**
1559
- * Get the stream key for a thread (or shared key for shared scope).
1560
- */
1561
- getStreamKey(threadId) {
1562
- return threadId || _AgentBrowser.SHARED_STREAM_KEY;
1563
- }
1564
- /**
1565
- * Trigger a screencast reconnect after tab changes.
1566
- * Called internally when tabs are switched or closed.
1567
- */
1568
- async reconnectScreencast(_reason) {
1569
- const threadId = this.getCurrentThread();
1570
- const streamKey = this.getStreamKey(threadId);
1571
- const stream = this.activeScreencastStreams.get(streamKey);
1572
- if (stream?.isActive()) {
1573
- await new Promise((resolve) => setTimeout(resolve, 150));
1574
- if (stream?.isActive()) {
1575
- try {
1576
- await stream.reconnect();
1577
- const manager = this.threadManager.getExistingManagerForThread(threadId) ?? this.browserManager;
1578
- const activePage = manager?.getPage();
1579
- if (activePage) {
1580
- const url = activePage.url();
1581
- if (url) {
1582
- stream.emitUrl(url);
1583
- }
1584
- }
1585
- } catch (err) {
1586
- console.error("[AgentBrowser] Failed to reconnect screencast:", err);
1587
- }
1588
- }
1589
- }
1590
- }
1591
1431
  async startScreencast(_options) {
1592
- const threadId = _options?.threadId;
1432
+ const requestedThreadId = _options?.threadId;
1433
+ const effectiveThreadId = this.getScope() === "thread" ? requestedThreadId ?? this.getCurrentThread() ?? DEFAULT_THREAD_ID : requestedThreadId;
1593
1434
  let browserManager;
1594
- if (this.getScope() === "thread" && threadId) {
1595
- browserManager = await this.getManagerForThread(threadId);
1435
+ if (this.getScope() === "thread") {
1436
+ browserManager = await this.getManagerForThread(effectiveThreadId);
1596
1437
  } else {
1597
- if (!this.browserManager) throw new Error("Browser not launched");
1598
- browserManager = this.browserManager;
1438
+ if (!this.sharedManager) throw new Error("Browser not launched");
1439
+ browserManager = this.sharedManager;
1599
1440
  }
1600
1441
  const provider = {
1601
1442
  getCdpSession: async () => {
@@ -1609,7 +1450,7 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
1609
1450
  isBrowserRunning: () => browserManager.isLaunched()
1610
1451
  };
1611
1452
  const stream = new ScreencastStreamImpl(provider, _options);
1612
- const streamKey = this.getStreamKey(threadId);
1453
+ const streamKey = this.getStreamKey(effectiveThreadId);
1613
1454
  this.activeScreencastStreams.set(streamKey, stream);
1614
1455
  const context = browserManager.getContext();
1615
1456
  if (context) {
@@ -1628,7 +1469,7 @@ var AgentBrowser = class _AgentBrowser extends MastraBrowser {
1628
1469
  const onFrameNavigated = (frame) => {
1629
1470
  if (!frame.parentFrame()) {
1630
1471
  stream.emitUrl(frame.url());
1631
- this.updateSessionBrowserState(threadId);
1472
+ this.updateSessionBrowserState(effectiveThreadId);
1632
1473
  }
1633
1474
  };
1634
1475
  page.on("framenavigated", onFrameNavigated);