Skip to content

Commit 9c62a9e

Browse files
author
SentienceDev
committed
Phase 1: CDP backend
1 parent e555737 commit 9c62a9e

File tree

12 files changed

+1683
-77
lines changed

12 files changed

+1683
-77
lines changed

sentience/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,31 @@
22
Sentience Python SDK - AI Agent Browser Automation
33
"""
44

5+
# Extension helpers (for browser-use integration)
6+
from ._extension_loader import (
7+
get_extension_dir,
8+
get_extension_version,
9+
verify_extension_injected,
10+
verify_extension_injected_async,
11+
verify_extension_version,
12+
verify_extension_version_async,
13+
)
514
from .actions import click, click_rect, press, scroll_to, type_text
615
from .agent import SentienceAgent, SentienceAgentAsync
716
from .agent_config import AgentConfig
817
from .agent_runtime import AgentRuntime
918

19+
# Browser backends (for browser-use integration)
20+
from .backends import (
21+
BrowserBackendV0,
22+
BrowserUseAdapter,
23+
BrowserUseCDPTransport,
24+
CDPBackendV0,
25+
CDPTransport,
26+
LayoutMetrics,
27+
ViewportInfo,
28+
)
29+
1030
# Agent Layer (Phase 1 & 2)
1131
from .base_agent import BaseAgent
1232
from .browser import SentienceBrowser
@@ -92,6 +112,21 @@
92112
__version__ = "0.92.3"
93113

94114
__all__ = [
115+
# Extension helpers (for browser-use integration)
116+
"get_extension_dir",
117+
"get_extension_version",
118+
"verify_extension_injected",
119+
"verify_extension_injected_async",
120+
"verify_extension_version",
121+
"verify_extension_version_async",
122+
# Browser backends (for browser-use integration)
123+
"BrowserBackendV0",
124+
"CDPTransport",
125+
"CDPBackendV0",
126+
"BrowserUseAdapter",
127+
"BrowserUseCDPTransport",
128+
"ViewportInfo",
129+
"LayoutMetrics",
95130
# Core SDK
96131
"SentienceBrowser",
97132
"Snapshot",

sentience/_extension_loader.py

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
"""
2-
Shared extension loading logic for sync and async implementations
2+
Shared extension loading logic for sync and async implementations.
3+
4+
Provides:
5+
- get_extension_dir(): Returns path to bundled extension (for browser-use integration)
6+
- verify_extension_injected(): Verifies window.sentience API is available
7+
- get_extension_version(): Gets extension version from manifest
8+
- verify_extension_version(): Checks SDK-extension version compatibility
39
"""
410

11+
import json
512
from pathlib import Path
13+
from typing import TYPE_CHECKING, Any
14+
15+
if TYPE_CHECKING:
16+
from .protocols import AsyncPageProtocol, PageProtocol
617

718

819
def find_extension_path() -> Path:
@@ -38,3 +49,147 @@ def find_extension_path() -> Path:
3849
f"2. {dev_ext_path}\n"
3950
"Make sure the extension is built and 'sentience/extension' directory exists."
4051
)
52+
53+
54+
def get_extension_dir() -> str:
55+
"""
56+
Get path to the bundled Sentience extension directory.
57+
58+
Use this to load the extension into browser-use or other Chromium-based browsers:
59+
60+
from sentience import get_extension_dir
61+
from browser_use import BrowserSession, BrowserProfile
62+
63+
profile = BrowserProfile(
64+
args=[f"--load-extension={get_extension_dir()}"],
65+
)
66+
session = BrowserSession(browser_profile=profile)
67+
68+
Returns:
69+
Absolute path to extension directory as string
70+
71+
Raises:
72+
FileNotFoundError: If extension not found in package
73+
"""
74+
return str(find_extension_path())
75+
76+
77+
def get_extension_version() -> str:
78+
"""
79+
Get the version of the bundled extension from manifest.json.
80+
81+
Returns:
82+
Version string (e.g., "2.2.0")
83+
84+
Raises:
85+
FileNotFoundError: If extension or manifest not found
86+
"""
87+
ext_path = find_extension_path()
88+
manifest_path = ext_path / "manifest.json"
89+
with open(manifest_path) as f:
90+
manifest = json.load(f)
91+
return manifest.get("version", "unknown")
92+
93+
94+
def verify_extension_injected(page: "PageProtocol") -> bool:
95+
"""
96+
Verify the Sentience extension injected window.sentience API (sync).
97+
98+
Call this after navigating to a page to confirm the extension is working:
99+
100+
browser.goto("https://example.com")
101+
if not verify_extension_injected(browser.page):
102+
raise RuntimeError("Extension not injected")
103+
104+
Args:
105+
page: Playwright Page object (sync)
106+
107+
Returns:
108+
True if window.sentience.snapshot is available, False otherwise
109+
"""
110+
try:
111+
result = page.evaluate(
112+
"(() => !!(window.sentience && typeof window.sentience.snapshot === 'function'))()"
113+
)
114+
return bool(result)
115+
except Exception:
116+
return False
117+
118+
119+
async def verify_extension_injected_async(page: "AsyncPageProtocol") -> bool:
120+
"""
121+
Verify the Sentience extension injected window.sentience API (async).
122+
123+
Call this after navigating to a page to confirm the extension is working:
124+
125+
await browser.goto("https://example.com")
126+
if not await verify_extension_injected_async(browser.page):
127+
raise RuntimeError("Extension not injected")
128+
129+
Args:
130+
page: Playwright Page object (async)
131+
132+
Returns:
133+
True if window.sentience.snapshot is available, False otherwise
134+
"""
135+
try:
136+
result = await page.evaluate(
137+
"(() => !!(window.sentience && typeof window.sentience.snapshot === 'function'))()"
138+
)
139+
return bool(result)
140+
except Exception:
141+
return False
142+
143+
144+
def verify_extension_version(page: "PageProtocol", expected: str | None = None) -> str | None:
145+
"""
146+
Check extension version exposed in page (sync).
147+
148+
The extension sets window.__SENTIENCE_EXTENSION_VERSION__ when injected.
149+
150+
Args:
151+
page: Playwright Page object (sync)
152+
expected: If provided, raises RuntimeError on mismatch
153+
154+
Returns:
155+
Version string if found, None if not set (page may not have injected yet)
156+
157+
Raises:
158+
RuntimeError: If expected version provided and doesn't match
159+
"""
160+
try:
161+
got = page.evaluate("window.__SENTIENCE_EXTENSION_VERSION__ || null")
162+
except Exception:
163+
got = None
164+
165+
if expected and got and got != expected:
166+
raise RuntimeError(f"Sentience extension version mismatch: expected {expected}, got {got}")
167+
return got
168+
169+
170+
async def verify_extension_version_async(
171+
page: "AsyncPageProtocol", expected: str | None = None
172+
) -> str | None:
173+
"""
174+
Check extension version exposed in page (async).
175+
176+
The extension sets window.__SENTIENCE_EXTENSION_VERSION__ when injected.
177+
178+
Args:
179+
page: Playwright Page object (async)
180+
expected: If provided, raises RuntimeError on mismatch
181+
182+
Returns:
183+
Version string if found, None if not set (page may not have injected yet)
184+
185+
Raises:
186+
RuntimeError: If expected version provided and doesn't match
187+
"""
188+
try:
189+
got = await page.evaluate("window.__SENTIENCE_EXTENSION_VERSION__ || null")
190+
except Exception:
191+
got = None
192+
193+
if expected and got and got != expected:
194+
raise RuntimeError(f"Sentience extension version mismatch: expected {expected}, got {got}")
195+
return got

sentience/agent_runtime.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ def assert_done(
233233
Returns:
234234
True if task is complete (assertion passed), False otherwise
235235
"""
236-
ok = self.assert_(predicate, label=label, required=True)
236+
ok = self.assertTrue(predicate, label=label, required=True)
237237

238238
if ok:
239239
self._task_done = True

sentience/backends/__init__.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
Browser backend abstractions for Sentience SDK.
3+
4+
This module provides backend protocols and implementations that allow
5+
Sentience actions (click, type, scroll) to work with different browser
6+
automation frameworks.
7+
8+
Supported backends:
9+
- PlaywrightBackend: Default backend using Playwright (existing SentienceBrowser)
10+
- CDPBackendV0: CDP-based backend for browser-use integration
11+
12+
For browser-use integration:
13+
from browser_use import BrowserSession, BrowserProfile
14+
from sentience import get_extension_dir
15+
from sentience.backends import BrowserUseAdapter, CDPBackendV0
16+
17+
# Setup browser-use with Sentience extension
18+
profile = BrowserProfile(args=[f"--load-extension={get_extension_dir()}"])
19+
session = BrowserSession(browser_profile=profile)
20+
await session.start()
21+
22+
# Create adapter and backend
23+
adapter = BrowserUseAdapter(session)
24+
backend = await adapter.create_backend()
25+
26+
# Use backend for precise operations
27+
await backend.mouse_click(100, 200)
28+
"""
29+
30+
from .browser_use_adapter import BrowserUseAdapter, BrowserUseCDPTransport
31+
from .cdp_backend import CDPBackendV0, CDPTransport
32+
from .protocol_v0 import BrowserBackendV0, LayoutMetrics, ViewportInfo
33+
34+
__all__ = [
35+
# Protocol
36+
"BrowserBackendV0",
37+
# Models
38+
"ViewportInfo",
39+
"LayoutMetrics",
40+
# CDP Backend
41+
"CDPTransport",
42+
"CDPBackendV0",
43+
# browser-use adapter
44+
"BrowserUseAdapter",
45+
"BrowserUseCDPTransport",
46+
]

0 commit comments

Comments
 (0)