Skip to content

Commit f1f7e7c

Browse files
committed
Added anchorbrowser computer implementation
1 parent 26e0146 commit f1f7e7c

File tree

5 files changed

+155
-2
lines changed

5 files changed

+155
-2
lines changed

.env.example

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ OPENAI_ORG = "org-123"
66
BROWSERBASE_API_KEY="00000000-0000-0000-0000-000000000000"
77
BROWSERBASE_PROJECT_ID="bb_live_00000000-00000"
88

9-
SCRAPYBARA_API_KEY="scrapy-123"
9+
SCRAPYBARA_API_KEY="scrapy-123"
10+
ANCHOR_API_KEY="anchor-123"

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Other included sample [computer environments](#computer-environments):
2626
- [Docker](https://docker.com/) (containerized desktop)
2727
- [Browserbase](https://www.browserbase.com/) (remote browser, requires account)
2828
- [Scrapybara](https://scrapybara.com) (remote browser or computer, requires account)
29+
- [Anchor](https://anchorbrowser.io) (remote browser, requires account)
2930
- ...or implement your own `Computer`!
3031

3132
## Overview
@@ -93,7 +94,7 @@ This sample app provides a set of implemented `Computer` examples, but feel free
9394
| `Browserbase` | browserbase | `browser` | Remote browser environment | [Browserbase](https://www.browserbase.com/) API key in `.env` |
9495
| `ScrapybaraBrowser` | scrapybara-browser | `browser` | Remote browser environment | [Scrapybara](https://scrapybara.com/dashboard) API key in `.env` |
9596
| `ScrapybaraUbuntu` | scrapybara-ubuntu | `linux` | Remote Ubuntu desktop environment | [Scrapybara](https://scrapybara.com/dashboard) API key in `.env` |
96-
97+
| `Anchor` | anchor | `browser` | Remote browser environment | [Anchor](https://anchorbrowser.io) API key in `.env` |
9798
Using the CLI, you can run the sample app with different computer environments using the options listed above:
9899

99100
```shell

cli.py

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
ScrapybaraUbuntu,
77
LocalPlaywrightComputer,
88
DockerComputer,
9+
AnchorBrowser,
910
)
1011

1112

@@ -26,6 +27,7 @@ def main():
2627
"local-playwright",
2728
"docker",
2829
"browserbase",
30+
"anchorbrowser",
2931
"scrapybara-browser",
3032
"scrapybara-ubuntu",
3133
],
@@ -60,6 +62,7 @@ def main():
6062
"local-playwright": LocalPlaywrightComputer,
6163
"docker": DockerComputer,
6264
"browserbase": BrowserbaseBrowser,
65+
"anchorbrowser": AnchorBrowser,
6366
"scrapybara-browser": ScrapybaraBrowser,
6467
"scrapybara-ubuntu": ScrapybaraUbuntu,
6568
}

computers/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
from .local_playwright import LocalPlaywrightComputer
44
from .docker import DockerComputer
55
from .scrapybara import ScrapybaraBrowser, ScrapybaraUbuntu
6+
from .anchor import AnchorBrowser

computers/anchor.py

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import os
2+
from typing import Tuple
3+
4+
from dotenv import load_dotenv
5+
from playwright.sync_api import Browser
6+
from playwright.sync_api import Error as PlaywrightError
7+
from playwright.sync_api import Page
8+
import requests
9+
10+
from .base_playwright import BasePlaywrightComputer
11+
12+
13+
load_dotenv()
14+
15+
16+
class AnchorBrowser(BasePlaywrightComputer):
17+
"""
18+
Computer implementation for Anchor browser (https://anchorbrowser.io)
19+
Requires an API key in the .env file as ANCHOR_API_KEY
20+
21+
IMPORTANT: The `goto` and navigation tools are already implemented and recommended
22+
when using the Anchor computer to help the agent navigate more effectively.
23+
"""
24+
25+
def __init__(
26+
self,
27+
width: int = 1024,
28+
height: int = 900,
29+
proxy_active: bool = True,
30+
adblock_active: bool = True,
31+
popup_blocking_active: bool = True,
32+
captcha_active: bool = True,
33+
timeout: int = 15,
34+
idle_timeout: int = 2,
35+
debug: bool = False,
36+
):
37+
"""Initialize the Anchor browser session"""
38+
super().__init__()
39+
self.api_key = os.getenv("ANCHOR_API_KEY")
40+
if not self.api_key:
41+
raise ValueError("ANCHOR_API_KEY not found in .env file")
42+
43+
self.debug = debug
44+
self.base_url = "https://api.anchorbrowser.io/api"
45+
self.base_ws_url = "wss://connect.anchorbrowser.io"
46+
self.session_id = None
47+
self.dimensions = (width, height)
48+
self.proxy_config = {"active": proxy_active}
49+
self.adblock_config = {
50+
"active": adblock_active,
51+
"popup_blocking_active": popup_blocking_active,
52+
}
53+
self.captcha_config = {"active": captcha_active}
54+
self.timeout = timeout
55+
self.idle_timeout = idle_timeout
56+
self._browser = None
57+
self._page = None
58+
self._start_session()
59+
60+
if self.debug:
61+
print(f"Anchor browser initialized with viewport {width}x{height}")
62+
63+
def _get_browser_and_page(self) -> Tuple[Browser, Page]:
64+
"""
65+
Get browser and page objects.
66+
For Anchor, we don't have direct browser/page objects, but we simulate them
67+
for compatibility with the BasePlaywrightComputer interface.
68+
"""
69+
# Create a session with Anchor API
70+
width, height = self.dimensions
71+
response = requests.post(
72+
f"{self.base_url}/sessions",
73+
headers={"anchor-api-key": f"{self.api_key}"},
74+
json={
75+
"width": width,
76+
"height": height,
77+
"useProxy": self.proxy_config["active"],
78+
"solveCaptcha": self.captcha_config["active"],
79+
"sessionTimeout": self.timeout,
80+
"sessionIdleTimeout": self.idle_timeout,
81+
"adBlocker": self.adblock_config["active"],
82+
"popupBlockingActive": self.adblock_config["popup_blocking_active"],
83+
"headless": False,
84+
},
85+
)
86+
response.raise_for_status()
87+
self.session_id = response.json().get("id")
88+
if not self.session_id:
89+
raise ValueError("Failed to create Anchor browser session")
90+
91+
browser = self._playwright.chromium.connect_over_cdp(
92+
f"{self.base_ws_url}/?sessionId={self.session_id}"
93+
)
94+
context = browser.contexts[0]
95+
context.on("page", self._handle_new_page)
96+
page = context.pages[0]
97+
return browser, page
98+
99+
def _handle_new_page(self, page: Page):
100+
"""Handle the creation of a new page."""
101+
print("New page created")
102+
self._page = page
103+
page.on("close", self._handle_page_close)
104+
105+
def _handle_page_close(self, page: Page):
106+
"""Handle the closure of a page."""
107+
print("Page closed")
108+
if self._page == page:
109+
if self._browser.contexts[0].pages:
110+
self._page = self._browser.contexts[0].pages[-1]
111+
else:
112+
print("Warning: All pages have been closed.")
113+
self._page = None
114+
115+
def __exit__(self, exc_type, exc_val, exc_tb):
116+
"""Clean up resources when exiting"""
117+
if self.session_id:
118+
requests.delete(
119+
f"{self.base_url}/sessions/{self.session_id}",
120+
headers={"anchor-api-key": f"{self.api_key}"},
121+
)
122+
if self.debug:
123+
print(f"Ended Anchor session: {self.session_id}")
124+
self.session_id = None
125+
126+
def screenshot(self) -> str:
127+
"""
128+
Capture a screenshot of the current viewport using CDP.
129+
130+
Returns:
131+
str: A base64 encoded string of the screenshot.
132+
"""
133+
try:
134+
# Get CDP session from the page
135+
cdp_session = self._page.context.new_cdp_session(self._page)
136+
137+
# Capture screenshot using CDP
138+
result = cdp_session.send(
139+
"Page.captureScreenshot", {"format": "png", "fromSurface": True}
140+
)
141+
142+
return result["data"]
143+
except PlaywrightError as error:
144+
print(
145+
f"CDP screenshot failed, falling back to standard screenshot: {error}"
146+
)
147+
return super().screenshot()

0 commit comments

Comments
 (0)