Skip to content

Commit 1147aca

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

File tree

3 files changed

+146
-2
lines changed

3 files changed

+146
-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

computers/anchor.py

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

0 commit comments

Comments
 (0)