Skip to content

Commit 7fcf91b

Browse files
committed
docs
1 parent 9de198a commit 7fcf91b

File tree

8 files changed

+163
-32
lines changed

8 files changed

+163
-32
lines changed

.github/workflows/test.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,24 @@ jobs:
3232
- name: Install dependencies
3333
run: |
3434
pip install -e ".[dev]"
35+
pip install pre-commit mypy types-requests
36+
37+
- name: Lint with pre-commit
38+
continue-on-error: true
39+
run: |
40+
pre-commit run --all-files
41+
42+
- name: Type check with mypy
43+
continue-on-error: true
44+
run: |
45+
mypy sentience --ignore-missing-imports --no-strict-optional
46+
47+
- name: Check code style
48+
continue-on-error: true
49+
run: |
50+
black --check sentience tests --line-length=100
51+
isort --check-only --profile black sentience tests
52+
flake8 sentience tests --max-line-length=100 --extend-ignore=E203,W503,E501 --max-complexity=15
3553
3654
- name: Build extension (if needed)
3755
if: runner.os != 'Windows'

.pre-commit-config.yaml

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,19 @@ repos:
5050
- '--max-complexity=15'
5151
exclude: ^(venv/|\.venv/|build/|dist/|tests/fixtures/)
5252

53-
# Type checking with mypy (disabled for now - too strict)
54-
# Uncomment to enable strict type checking
55-
# - repo: https://github.com/pre-commit/mirrors-mypy
56-
# rev: v1.8.0
57-
# hooks:
58-
# - id: mypy
59-
# additional_dependencies:
60-
# - pydantic>=2.0
61-
# - types-requests
62-
# args:
63-
# - '--ignore-missing-imports'
64-
# - '--no-strict-optional'
65-
# - '--warn-unused-ignores'
66-
# exclude: ^(tests/|examples/|venv/|\.venv/|build/|dist/)
53+
# Type checking with mypy
54+
- repo: https://github.com/pre-commit/mirrors-mypy
55+
rev: v1.8.0
56+
hooks:
57+
- id: mypy
58+
additional_dependencies:
59+
- pydantic>=2.0
60+
- types-requests
61+
args:
62+
- '--ignore-missing-imports'
63+
- '--no-strict-optional'
64+
- '--warn-unused-ignores'
65+
exclude: ^(tests/|examples/|venv/|\.venv/|build/|dist/)
6766

6867
# Security checks
6968
- repo: https://github.com/PyCQA/bandit

sentience/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
from .read import read
5656
from .recorder import Recorder, Trace, TraceStep, record
5757
from .screenshot import screenshot
58+
from .sentience_methods import AgentAction, SentienceAction
5859
from .snapshot import snapshot
5960
from .text_search import find_text_rect
6061
from .tracer_factory import SENTIENCE_API_URL, create_tracer
@@ -150,4 +151,7 @@
150151
"format_snapshot_for_llm",
151152
# Agent Config (v0.12.0+)
152153
"AgentConfig",
154+
# Enums
155+
"SentienceAction",
156+
"AgentAction",
153157
]

sentience/actions.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .browser import AsyncSentienceBrowser, SentienceBrowser
1010
from .browser_evaluator import BrowserEvaluator
1111
from .models import ActionResult, BBox, Snapshot
12+
from .sentience_methods import SentienceAction
1213
from .snapshot import snapshot, snapshot_async
1314

1415

@@ -62,22 +63,22 @@ def click( # noqa: C901
6263
else:
6364
# Fallback to JS click if element not found in snapshot
6465
try:
65-
success = BrowserEvaluator.call_sentience_method(
66-
browser.page, "click", element_id
66+
success = BrowserEvaluator.invoke(
67+
browser.page, SentienceAction.CLICK, element_id
6768
)
6869
except Exception:
6970
# Navigation might have destroyed context, assume success if URL changed
7071
success = True
7172
except Exception:
7273
# Fallback to JS click on error
7374
try:
74-
success = BrowserEvaluator.call_sentience_method(browser.page, "click", element_id)
75+
success = BrowserEvaluator.invoke(browser.page, SentienceAction.CLICK, element_id)
7576
except Exception:
7677
# Navigation might have destroyed context, assume success if URL changed
7778
success = True
7879
else:
7980
# Legacy JS-based click
80-
success = BrowserEvaluator.call_sentience_method(browser.page, "click", element_id)
81+
success = BrowserEvaluator.invoke(browser.page, SentienceAction.CLICK, element_id)
8182

8283
# Wait a bit for navigation/DOM updates
8384
try:

sentience/browser_evaluator.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from playwright.sync_api import Page
1414

1515
from .browser import AsyncSentienceBrowser, SentienceBrowser
16+
from .sentience_methods import SentienceMethod
1617

1718

1819
class BrowserEvaluator:
@@ -126,18 +127,18 @@ async def _gather_diagnostics_async(page: AsyncPage) -> dict[str, Any]:
126127
return {"error": "Could not gather diagnostics"}
127128

128129
@staticmethod
129-
def call_sentience_method(
130+
def invoke(
130131
page: Page,
131-
method_name: str,
132+
method: SentienceMethod | str,
132133
*args: Any,
133134
**kwargs: Any,
134135
) -> Any:
135136
"""
136-
Call a window.sentience method with error handling.
137+
Invoke a window.sentience method with error handling (sync).
137138
138139
Args:
139140
page: Playwright Page instance (sync)
140-
method_name: Name of the method (e.g., "snapshot", "click")
141+
method: SentienceMethod enum value or method name string (e.g., SentienceMethod.SNAPSHOT or "snapshot")
141142
*args: Positional arguments to pass to the method
142143
**kwargs: Keyword arguments to pass to the method
143144
@@ -146,7 +147,16 @@ def call_sentience_method(
146147
147148
Raises:
148149
RuntimeError: If method is not available or call fails
150+
151+
Example:
152+
```python
153+
result = BrowserEvaluator.invoke(page, SentienceMethod.SNAPSHOT, limit=50)
154+
success = BrowserEvaluator.invoke(page, SentienceMethod.CLICK, element_id)
155+
```
149156
"""
157+
# Convert enum to string if needed
158+
method_name = method.value if isinstance(method, SentienceMethod) else method
159+
150160
# Build JavaScript call
151161
if args and kwargs:
152162
# Both args and kwargs - use object spread
@@ -184,18 +194,18 @@ def call_sentience_method(
184194
return result
185195

186196
@staticmethod
187-
async def call_sentience_method_async(
197+
async def invoke_async(
188198
page: AsyncPage,
189-
method_name: str,
199+
method: SentienceMethod | str,
190200
*args: Any,
191201
**kwargs: Any,
192202
) -> Any:
193203
"""
194-
Call a window.sentience method with error handling (async).
204+
Invoke a window.sentience method with error handling (async).
195205
196206
Args:
197207
page: Playwright AsyncPage instance
198-
method_name: Name of the method (e.g., "snapshot", "click")
208+
method: SentienceMethod enum value or method name string (e.g., SentienceMethod.SNAPSHOT or "snapshot")
199209
*args: Positional arguments to pass to the method
200210
**kwargs: Keyword arguments to pass to the method
201211
@@ -204,7 +214,16 @@ async def call_sentience_method_async(
204214
205215
Raises:
206216
RuntimeError: If method is not available or call fails
217+
218+
Example:
219+
```python
220+
result = await BrowserEvaluator.invoke_async(page, SentienceMethod.SNAPSHOT, limit=50)
221+
success = await BrowserEvaluator.invoke_async(page, SentienceMethod.CLICK, element_id)
222+
```
207223
"""
224+
# Convert enum to string if needed
225+
method_name = method.value if isinstance(method, SentienceMethod) else method
226+
208227
# Build JavaScript call
209228
if args and kwargs:
210229
js_code = f"""
@@ -240,18 +259,19 @@ async def call_sentience_method_async(
240259
@staticmethod
241260
def verify_method_exists(
242261
page: Page,
243-
method_name: str,
262+
method: SentienceMethod | str,
244263
) -> bool:
245264
"""
246265
Verify that a window.sentience method exists.
247266
248267
Args:
249268
page: Playwright Page instance (sync)
250-
method_name: Name of the method to check
269+
method: SentienceMethod enum value or method name string
251270
252271
Returns:
253272
True if method exists, False otherwise
254273
"""
274+
method_name = method.value if isinstance(method, SentienceMethod) else method
255275
try:
256276
return page.evaluate(f"typeof window.sentience.{method_name} !== 'undefined'")
257277
except Exception:
@@ -260,18 +280,19 @@ def verify_method_exists(
260280
@staticmethod
261281
async def verify_method_exists_async(
262282
page: AsyncPage,
263-
method_name: str,
283+
method: SentienceMethod | str,
264284
) -> bool:
265285
"""
266286
Verify that a window.sentience method exists (async).
267287
268288
Args:
269289
page: Playwright AsyncPage instance
270-
method_name: Name of the method to check
290+
method: SentienceMethod enum value or method name string
271291
272292
Returns:
273293
True if method exists, False otherwise
274294
"""
295+
method_name = method.value if isinstance(method, SentienceMethod) else method
275296
try:
276297
return await page.evaluate(f"typeof window.sentience.{method_name} !== 'undefined'")
277298
except Exception:

sentience/sentience_methods.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
Enums for Sentience API methods and agent actions.
3+
4+
This module provides type-safe enums for:
5+
1. window.sentience API methods (extension-level)
6+
2. Agent action types (high-level automation commands)
7+
"""
8+
9+
from enum import Enum
10+
11+
12+
class SentienceMethod(str, Enum):
13+
"""
14+
Enum for window.sentience API methods.
15+
16+
These are the actual methods available on the window.sentience object
17+
injected by the Chrome extension.
18+
"""
19+
20+
# Core snapshot and element discovery
21+
SNAPSHOT = "snapshot"
22+
"""Take a snapshot of the current page with element geometry and metadata."""
23+
24+
# Element interaction
25+
CLICK = "click"
26+
"""Click an element by its ID from the snapshot registry."""
27+
28+
# Content extraction
29+
READ = "read"
30+
"""Read page content as raw HTML, text, or markdown."""
31+
32+
FIND_TEXT_RECT = "findTextRect"
33+
"""Find exact pixel coordinates of text occurrences on the page."""
34+
35+
# Visual overlay
36+
SHOW_OVERLAY = "showOverlay"
37+
"""Show visual overlay highlighting elements with importance scores."""
38+
39+
CLEAR_OVERLAY = "clearOverlay"
40+
"""Clear the visual overlay."""
41+
42+
# Developer tools
43+
START_RECORDING = "startRecording"
44+
"""Start recording mode for golden set collection (developer tool)."""
45+
46+
def __str__(self) -> str:
47+
"""Return the method name as a string."""
48+
return self.value
49+
50+
51+
class AgentAction(str, Enum):
52+
"""
53+
Enum for high-level agent action types.
54+
55+
These are the action commands that agents can execute. They may use
56+
one or more window.sentience methods or Playwright APIs directly.
57+
"""
58+
59+
# Element interaction
60+
CLICK = "click"
61+
"""Click an element by ID. Uses window.sentience.click() or Playwright mouse.click()."""
62+
63+
TYPE = "type"
64+
"""Type text into an input element. Uses Playwright keyboard.type() directly."""
65+
66+
PRESS = "press"
67+
"""Press a keyboard key (Enter, Escape, Tab, etc.). Uses Playwright keyboard.press()."""
68+
69+
# Navigation
70+
NAVIGATE = "navigate"
71+
"""Navigate to a URL. Uses Playwright page.goto() directly."""
72+
73+
SCROLL = "scroll"
74+
"""Scroll the page or an element. Uses Playwright page.mouse.wheel() or element.scrollIntoView()."""
75+
76+
# Completion
77+
FINISH = "finish"
78+
"""Signal that the agent task is complete. No browser action, just status update."""
79+
80+
# Wait/verification
81+
WAIT = "wait"
82+
"""Wait for a condition or duration. Uses Playwright wait_for_* methods."""
83+
84+
def __str__(self) -> str:
85+
"""Return the action name as a string."""
86+
return self.value
87+

sentience/snapshot.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .browser import AsyncSentienceBrowser, SentienceBrowser
1414
from .browser_evaluator import BrowserEvaluator
1515
from .models import Snapshot, SnapshotOptions
16+
from .sentience_methods import SentienceMethod
1617

1718
# Maximum payload size for API requests (10MB server limit)
1819
MAX_PAYLOAD_BYTES = 10 * 1024 * 1024
@@ -171,7 +172,7 @@ def _snapshot_via_api(
171172
if options.screenshot is not False:
172173
raw_options["screenshot"] = options.screenshot
173174

174-
raw_result = BrowserEvaluator.call_sentience_method(browser.page, "snapshot", **raw_options)
175+
raw_result = BrowserEvaluator.invoke(browser.page, SentienceAction.SNAPSHOT, **raw_options)
175176

176177
# Save trace if requested (save raw data before API processing)
177178
if options.save_trace:

sentience/text_search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def find_text_rect(
9595
BrowserEvaluator.wait_for_extension(browser.page, timeout_ms=5000)
9696

9797
# Verify findTextRect method exists (for older extension versions that don't have it)
98-
if not BrowserEvaluator.verify_method_exists(browser.page, "findTextRect"):
98+
if not BrowserEvaluator.verify_method_exists(browser.page, SentienceAction.FIND_TEXT_RECT):
9999
raise RuntimeError(
100100
"window.sentience.findTextRect is not available. "
101101
"Please update the Sentience extension to the latest version."

0 commit comments

Comments
 (0)