Skip to content

Commit ec6fa71

Browse files
committed
Added anchorbrowser computer implementation
1 parent a842bec commit ec6fa71

File tree

5 files changed

+153
-2
lines changed

5 files changed

+153
-2
lines changed

.env.example

Lines changed: 2 additions & 1 deletion
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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Other included sample [computer environments](#computer-environments):
2929
- [Docker](https://docker.com/) (containerized desktop)
3030
- [Browserbase](https://www.browserbase.com/) (remote browser, requires account)
3131
- [Scrapybara](https://scrapybara.com) (remote browser or computer, requires account)
32+
- [Anchor](https://anchorbrowser.io) (remote browser, requires account)
3233
- ...or implement your own `Computer`!
3334

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

102103
```shell

computers/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
"browserbase": BrowserbaseBrowser,
88
"scrapybara-browser": ScrapybaraBrowser,
99
"scrapybara-ubuntu": ScrapybaraUbuntu,
10+
"anchorbrowser": AnchorBrowser,
1011
}

computers/default/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .browserbase import BrowserbaseBrowser
2+
from .anchor import AnchorBrowser
23
from .local_playwright import LocalPlaywrightBrowser
34
from .docker import DockerComputer
45
from .scrapybara import ScrapybaraBrowser, ScrapybaraUbuntu

computers/default/anchor.py

Lines changed: 147 additions & 0 deletions
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 ..shared.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+
59+
if self.debug:
60+
print(f"Anchor browser initialized with viewport {width}x{height}")
61+
62+
def _get_browser_and_page(self) -> Tuple[Browser, Page]:
63+
"""
64+
Get browser and page objects.
65+
For Anchor, we don't have direct browser/page objects, but we simulate them
66+
for compatibility with the BasePlaywrightComputer interface.
67+
"""
68+
# Create a session with Anchor API
69+
width, height = self.dimensions
70+
response = requests.post(
71+
f"{self.base_url}/sessions",
72+
headers={"anchor-api-key": f"{self.api_key}"},
73+
json={
74+
"width": width,
75+
"height": height,
76+
"useProxy": self.proxy_config["active"],
77+
"solveCaptcha": self.captcha_config["active"],
78+
"sessionTimeout": self.timeout,
79+
"sessionIdleTimeout": self.idle_timeout,
80+
"adBlocker": self.adblock_config["active"],
81+
"popupBlockingActive": self.adblock_config["popup_blocking_active"],
82+
"headless": False,
83+
},
84+
)
85+
response.raise_for_status()
86+
self.session_id = response.json().get("id")
87+
if not self.session_id:
88+
raise ValueError("Failed to create Anchor browser session")
89+
90+
browser = self._playwright.chromium.connect_over_cdp(
91+
f"{self.base_ws_url}/?sessionId={self.session_id}"
92+
)
93+
context = browser.contexts[0]
94+
context.on("page", self._handle_new_page)
95+
page = context.pages[0]
96+
page.goto("https://bing.com")
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)