browser-agent-server 1.0.0__py3-none-any.whl
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.
- browser_agent/__init__.py +128 -0
- browser_agent/chrome.py +591 -0
- browser_agent/cli.py +414 -0
- browser_agent/data/browser-agent.service +26 -0
- browser_agent/server.py +250 -0
- browser_agent/vision_agent.py +165 -0
- browser_agent/x11_input.py +273 -0
- browser_agent_server-1.0.0.dist-info/METADATA +98 -0
- browser_agent_server-1.0.0.dist-info/RECORD +12 -0
- browser_agent_server-1.0.0.dist-info/WHEEL +4 -0
- browser_agent_server-1.0.0.dist-info/entry_points.txt +2 -0
- browser_agent_server-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""System-wide Browser Agent SDK: control real headful Chrome + X11 mouse from any Python project."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import base64
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import urllib.error
|
|
8
|
+
import urllib.request
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .chrome import RealChromeSession, is_cloudflare_challenge, is_warp_proxy_ready
|
|
12
|
+
from .x11_input import X11Display, bezier_curve
|
|
13
|
+
|
|
14
|
+
DEFAULT_AGENT_URL = os.environ.get("BROWSER_AGENT_URL", "http://127.0.0.1:8765")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BrowserClient:
|
|
18
|
+
"""Zero-dependency HTTP client for the local Browser Agent daemon (127.0.0.1:8765)."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, base_url: str = DEFAULT_AGENT_URL, timeout: float = 75.0):
|
|
21
|
+
self.base_url = base_url.rstrip("/")
|
|
22
|
+
self.timeout = timeout
|
|
23
|
+
|
|
24
|
+
def _request(self, method: str, path: str, payload: dict | None = None, timeout: float | None = None) -> dict:
|
|
25
|
+
url = f"{self.base_url}{path}"
|
|
26
|
+
data = json.dumps(payload).encode("utf-8") if payload is not None else None
|
|
27
|
+
headers = {"Content-Type": "application/json"} if data is not None else {}
|
|
28
|
+
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
29
|
+
with urllib.request.urlopen(req, timeout=timeout or self.timeout) as resp:
|
|
30
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
31
|
+
|
|
32
|
+
def is_available(self) -> bool:
|
|
33
|
+
try:
|
|
34
|
+
res = self._request("GET", "/health", timeout=1.2)
|
|
35
|
+
return bool(res.get("ok"))
|
|
36
|
+
except Exception: # noqa: BLE001
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
def status(self) -> dict:
|
|
40
|
+
return self._request("GET", "/status", timeout=3.0)
|
|
41
|
+
|
|
42
|
+
def fetch(
|
|
43
|
+
self,
|
|
44
|
+
url: str,
|
|
45
|
+
wait_sec: float = 3.5,
|
|
46
|
+
solve_cloudflare: bool = True,
|
|
47
|
+
use_proxy: bool | None = None,
|
|
48
|
+
include_screenshot: bool = False,
|
|
49
|
+
) -> dict:
|
|
50
|
+
return self._request(
|
|
51
|
+
"POST",
|
|
52
|
+
"/fetch",
|
|
53
|
+
{
|
|
54
|
+
"url": url,
|
|
55
|
+
"wait_sec": wait_sec,
|
|
56
|
+
"solve_cloudflare": solve_cloudflare,
|
|
57
|
+
"use_proxy": use_proxy,
|
|
58
|
+
"include_screenshot": include_screenshot,
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def goto(self, url: str, wait_sec: float = 2.5, solve_cloudflare: bool = True) -> dict:
|
|
63
|
+
return self._request(
|
|
64
|
+
"POST",
|
|
65
|
+
"/action",
|
|
66
|
+
{"action": "goto", "url": url, "wait_sec": wait_sec, "solve_cloudflare": solve_cloudflare},
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def move_mouse(self, x: int, y: int) -> dict:
|
|
70
|
+
return self._request("POST", "/action", {"action": "move", "x": int(x), "y": int(y)})
|
|
71
|
+
|
|
72
|
+
def click(self, x: int | None = None, y: int | None = None, button: int = 1, double: bool = False) -> dict:
|
|
73
|
+
return self._request(
|
|
74
|
+
"POST",
|
|
75
|
+
"/action",
|
|
76
|
+
{"action": "click", "x": x, "y": y, "button": button, "double": double},
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def type_text(self, text: str, submit: bool = False) -> dict:
|
|
80
|
+
return self._request("POST", "/action", {"action": "type", "text": text, "submit": submit})
|
|
81
|
+
|
|
82
|
+
def press_key(self, key: str) -> dict:
|
|
83
|
+
return self._request("POST", "/action", {"action": "key", "key": key})
|
|
84
|
+
|
|
85
|
+
def scroll(self, clicks: int = 4, direction: str = "down", x: int | None = None, y: int | None = None) -> dict:
|
|
86
|
+
return self._request(
|
|
87
|
+
"POST",
|
|
88
|
+
"/action",
|
|
89
|
+
{"action": "scroll", "clicks": clicks, "direction": direction, "x": x, "y": y},
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def screenshot(self, save_path: str | Path | None = None) -> bytes:
|
|
93
|
+
res = self._request("POST", "/action", {"action": "screenshot"})
|
|
94
|
+
raw = base64.b64decode(res.get("screenshot_b64") or "")
|
|
95
|
+
if save_path and raw:
|
|
96
|
+
Path(save_path).write_bytes(raw)
|
|
97
|
+
return raw
|
|
98
|
+
|
|
99
|
+
def run_agent(self, url: str, goal: str, max_steps: int = 8, use_proxy: bool | None = None) -> dict:
|
|
100
|
+
return self._request(
|
|
101
|
+
"POST",
|
|
102
|
+
"/agent",
|
|
103
|
+
{"url": url, "goal": goal, "max_steps": max_steps, "use_proxy": use_proxy},
|
|
104
|
+
timeout=180.0,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def stop(self) -> dict:
|
|
108
|
+
return self._request("POST", "/action", {"action": "stop"}, timeout=10.0)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
__all__ = [
|
|
112
|
+
"BrowserClient",
|
|
113
|
+
"RealChromeSession",
|
|
114
|
+
"X11Display",
|
|
115
|
+
"bezier_curve",
|
|
116
|
+
"is_cloudflare_challenge",
|
|
117
|
+
"is_warp_proxy_ready",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
from importlib.metadata import PackageNotFoundError # noqa: E402
|
|
121
|
+
from importlib.metadata import version as _v # noqa: E402
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
__version__ = _v("browser-agent-server")
|
|
125
|
+
except PackageNotFoundError:
|
|
126
|
+
__version__ = "0.0.0+local"
|
|
127
|
+
|
|
128
|
+
__all__ = [*__all__, "__version__"]
|