sentienceapi 0.95.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.

Potentially problematic release.


This version of sentienceapi might be problematic. Click here for more details.

Files changed (82) hide show
  1. sentience/__init__.py +253 -0
  2. sentience/_extension_loader.py +195 -0
  3. sentience/action_executor.py +215 -0
  4. sentience/actions.py +1020 -0
  5. sentience/agent.py +1181 -0
  6. sentience/agent_config.py +46 -0
  7. sentience/agent_runtime.py +424 -0
  8. sentience/asserts/__init__.py +70 -0
  9. sentience/asserts/expect.py +621 -0
  10. sentience/asserts/query.py +383 -0
  11. sentience/async_api.py +108 -0
  12. sentience/backends/__init__.py +137 -0
  13. sentience/backends/actions.py +343 -0
  14. sentience/backends/browser_use_adapter.py +241 -0
  15. sentience/backends/cdp_backend.py +393 -0
  16. sentience/backends/exceptions.py +211 -0
  17. sentience/backends/playwright_backend.py +194 -0
  18. sentience/backends/protocol.py +216 -0
  19. sentience/backends/sentience_context.py +469 -0
  20. sentience/backends/snapshot.py +427 -0
  21. sentience/base_agent.py +196 -0
  22. sentience/browser.py +1215 -0
  23. sentience/browser_evaluator.py +299 -0
  24. sentience/canonicalization.py +207 -0
  25. sentience/cli.py +130 -0
  26. sentience/cloud_tracing.py +807 -0
  27. sentience/constants.py +6 -0
  28. sentience/conversational_agent.py +543 -0
  29. sentience/element_filter.py +136 -0
  30. sentience/expect.py +188 -0
  31. sentience/extension/background.js +104 -0
  32. sentience/extension/content.js +161 -0
  33. sentience/extension/injected_api.js +914 -0
  34. sentience/extension/manifest.json +36 -0
  35. sentience/extension/pkg/sentience_core.d.ts +51 -0
  36. sentience/extension/pkg/sentience_core.js +323 -0
  37. sentience/extension/pkg/sentience_core_bg.wasm +0 -0
  38. sentience/extension/pkg/sentience_core_bg.wasm.d.ts +10 -0
  39. sentience/extension/release.json +115 -0
  40. sentience/formatting.py +15 -0
  41. sentience/generator.py +202 -0
  42. sentience/inspector.py +367 -0
  43. sentience/llm_interaction_handler.py +191 -0
  44. sentience/llm_provider.py +875 -0
  45. sentience/llm_provider_utils.py +120 -0
  46. sentience/llm_response_builder.py +153 -0
  47. sentience/models.py +846 -0
  48. sentience/ordinal.py +280 -0
  49. sentience/overlay.py +222 -0
  50. sentience/protocols.py +228 -0
  51. sentience/query.py +303 -0
  52. sentience/read.py +188 -0
  53. sentience/recorder.py +589 -0
  54. sentience/schemas/trace_v1.json +335 -0
  55. sentience/screenshot.py +100 -0
  56. sentience/sentience_methods.py +86 -0
  57. sentience/snapshot.py +706 -0
  58. sentience/snapshot_diff.py +126 -0
  59. sentience/text_search.py +262 -0
  60. sentience/trace_event_builder.py +148 -0
  61. sentience/trace_file_manager.py +197 -0
  62. sentience/trace_indexing/__init__.py +27 -0
  63. sentience/trace_indexing/index_schema.py +199 -0
  64. sentience/trace_indexing/indexer.py +414 -0
  65. sentience/tracer_factory.py +322 -0
  66. sentience/tracing.py +449 -0
  67. sentience/utils/__init__.py +40 -0
  68. sentience/utils/browser.py +46 -0
  69. sentience/utils/element.py +257 -0
  70. sentience/utils/formatting.py +59 -0
  71. sentience/utils.py +296 -0
  72. sentience/verification.py +380 -0
  73. sentience/visual_agent.py +2058 -0
  74. sentience/wait.py +139 -0
  75. sentienceapi-0.95.0.dist-info/METADATA +984 -0
  76. sentienceapi-0.95.0.dist-info/RECORD +82 -0
  77. sentienceapi-0.95.0.dist-info/WHEEL +5 -0
  78. sentienceapi-0.95.0.dist-info/entry_points.txt +2 -0
  79. sentienceapi-0.95.0.dist-info/licenses/LICENSE +24 -0
  80. sentienceapi-0.95.0.dist-info/licenses/LICENSE-APACHE +201 -0
  81. sentienceapi-0.95.0.dist-info/licenses/LICENSE-MIT +21 -0
  82. sentienceapi-0.95.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,427 @@
1
+ """
2
+ Backend-agnostic snapshot for browser-use integration.
3
+
4
+ Takes Sentience snapshots using BrowserBackend protocol,
5
+ enabling element grounding with browser-use or other frameworks.
6
+
7
+ Usage with browser-use:
8
+ from sentience.backends import BrowserUseAdapter, snapshot, CachedSnapshot
9
+
10
+ adapter = BrowserUseAdapter(session)
11
+ backend = await adapter.create_backend()
12
+
13
+ # Take snapshot
14
+ snap = await snapshot(backend)
15
+ print(f"Found {len(snap.elements)} elements")
16
+
17
+ # With caching (reuse if fresh)
18
+ cache = CachedSnapshot(backend, max_age_ms=2000)
19
+ snap1 = await cache.get() # Fresh snapshot
20
+ snap2 = await cache.get() # Returns cached if < 2s old
21
+ cache.invalidate() # Force refresh on next get()
22
+ """
23
+
24
+ import time
25
+ from typing import TYPE_CHECKING, Any
26
+
27
+ from ..constants import SENTIENCE_API_URL
28
+ from ..models import Snapshot, SnapshotOptions
29
+ from ..snapshot import (
30
+ _build_snapshot_payload,
31
+ _merge_api_result_with_local,
32
+ _post_snapshot_to_gateway_async,
33
+ )
34
+ from .exceptions import ExtensionDiagnostics, ExtensionNotLoadedError, SnapshotError
35
+
36
+ if TYPE_CHECKING:
37
+ from .protocol import BrowserBackend
38
+
39
+
40
+ class CachedSnapshot:
41
+ """
42
+ Snapshot cache with staleness detection.
43
+
44
+ Caches snapshots and returns cached version if still fresh.
45
+ Useful for reducing redundant snapshot calls in action loops.
46
+
47
+ Usage:
48
+ cache = CachedSnapshot(backend, max_age_ms=2000)
49
+
50
+ # First call takes fresh snapshot
51
+ snap1 = await cache.get()
52
+
53
+ # Second call returns cached if < 2s old
54
+ snap2 = await cache.get()
55
+
56
+ # Invalidate after actions that change DOM
57
+ await click(backend, element.bbox)
58
+ cache.invalidate()
59
+
60
+ # Next get() will take fresh snapshot
61
+ snap3 = await cache.get()
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ backend: "BrowserBackend",
67
+ max_age_ms: int = 2000,
68
+ options: SnapshotOptions | None = None,
69
+ ) -> None:
70
+ """
71
+ Initialize cached snapshot.
72
+
73
+ Args:
74
+ backend: BrowserBackend implementation
75
+ max_age_ms: Maximum cache age in milliseconds (default: 2000)
76
+ options: Default snapshot options
77
+ """
78
+ self._backend = backend
79
+ self._max_age_ms = max_age_ms
80
+ self._options = options
81
+ self._cached: Snapshot | None = None
82
+ self._cached_at: float = 0 # timestamp in seconds
83
+ self._cached_url: str | None = None
84
+
85
+ async def get(
86
+ self,
87
+ options: SnapshotOptions | None = None,
88
+ force_refresh: bool = False,
89
+ ) -> Snapshot:
90
+ """
91
+ Get snapshot, using cache if fresh.
92
+
93
+ Args:
94
+ options: Override default options for this call
95
+ force_refresh: If True, always take fresh snapshot
96
+
97
+ Returns:
98
+ Snapshot (cached or fresh)
99
+ """
100
+ # Check if we need to refresh
101
+ if force_refresh or self._is_stale():
102
+ self._cached = await snapshot(
103
+ self._backend,
104
+ options or self._options,
105
+ )
106
+ self._cached_at = time.time()
107
+ self._cached_url = self._cached.url
108
+
109
+ assert self._cached is not None
110
+ return self._cached
111
+
112
+ def invalidate(self) -> None:
113
+ """
114
+ Invalidate cache, forcing refresh on next get().
115
+
116
+ Call this after actions that modify the DOM.
117
+ """
118
+ self._cached = None
119
+ self._cached_at = 0
120
+ self._cached_url = None
121
+
122
+ def _is_stale(self) -> bool:
123
+ """Check if cache is stale and needs refresh."""
124
+ if self._cached is None:
125
+ return True
126
+
127
+ # Check age
128
+ age_ms = (time.time() - self._cached_at) * 1000
129
+ if age_ms > self._max_age_ms:
130
+ return True
131
+
132
+ return False
133
+
134
+ @property
135
+ def is_cached(self) -> bool:
136
+ """Check if a cached snapshot exists."""
137
+ return self._cached is not None
138
+
139
+ @property
140
+ def age_ms(self) -> float:
141
+ """Get age of cached snapshot in milliseconds."""
142
+ if self._cached is None:
143
+ return float("inf")
144
+ return (time.time() - self._cached_at) * 1000
145
+
146
+
147
+ async def snapshot(
148
+ backend: "BrowserBackend",
149
+ options: SnapshotOptions | None = None,
150
+ ) -> Snapshot:
151
+ """
152
+ Take a Sentience snapshot using the backend protocol.
153
+
154
+ This function respects the `use_api` option and can call either:
155
+ - Server-side API (Pro/Enterprise tier) when `use_api=True` and API key is provided
156
+ - Local extension (Free tier) when `use_api=False` or no API key
157
+
158
+ Requires:
159
+ - Sentience extension loaded in browser (via --load-extension)
160
+ - Extension injected window.sentience API
161
+
162
+ Args:
163
+ backend: BrowserBackend implementation (CDPBackendV0, PlaywrightBackend, etc.)
164
+ options: Snapshot options (limit, filter, screenshot, use_api, sentience_api_key, etc.)
165
+
166
+ Returns:
167
+ Snapshot with elements, viewport, and optional screenshot
168
+
169
+ Example:
170
+ from sentience.backends import BrowserUseAdapter
171
+ from sentience.backends.snapshot import snapshot
172
+ from sentience.models import SnapshotOptions
173
+
174
+ adapter = BrowserUseAdapter(session)
175
+ backend = await adapter.create_backend()
176
+
177
+ # Basic snapshot (uses local extension)
178
+ snap = await snapshot(backend)
179
+
180
+ # With server-side API (Pro/Enterprise tier)
181
+ snap = await snapshot(backend, SnapshotOptions(
182
+ use_api=True,
183
+ sentience_api_key="sk_pro_xxxxx",
184
+ limit=100,
185
+ screenshot=True
186
+ ))
187
+
188
+ # Force local extension (Free tier)
189
+ snap = await snapshot(backend, SnapshotOptions(
190
+ use_api=False
191
+ ))
192
+ """
193
+ if options is None:
194
+ options = SnapshotOptions()
195
+
196
+ # Determine if we should use server-side API
197
+ # Same logic as main snapshot() function in sentience/snapshot.py
198
+ should_use_api = (
199
+ options.use_api if options.use_api is not None else (options.sentience_api_key is not None)
200
+ )
201
+
202
+ if should_use_api and options.sentience_api_key:
203
+ # Use server-side API (Pro/Enterprise tier)
204
+ return await _snapshot_via_api(backend, options)
205
+ else:
206
+ # Use local extension (Free tier)
207
+ return await _snapshot_via_extension(backend, options)
208
+
209
+
210
+ async def _wait_for_extension(
211
+ backend: "BrowserBackend",
212
+ timeout_ms: int = 5000,
213
+ ) -> None:
214
+ """
215
+ Wait for Sentience extension to inject window.sentience API.
216
+
217
+ Args:
218
+ backend: BrowserBackend implementation
219
+ timeout_ms: Maximum wait time
220
+
221
+ Raises:
222
+ RuntimeError: If extension not injected within timeout
223
+ """
224
+ import asyncio
225
+ import logging
226
+
227
+ logger = logging.getLogger("sentience.backends.snapshot")
228
+
229
+ start = time.monotonic()
230
+ timeout_sec = timeout_ms / 1000.0
231
+ poll_count = 0
232
+
233
+ logger.debug(f"Waiting for extension injection (timeout={timeout_ms}ms)...")
234
+
235
+ while True:
236
+ elapsed = time.monotonic() - start
237
+ poll_count += 1
238
+
239
+ if poll_count % 10 == 0: # Log every 10 polls (~1 second)
240
+ logger.debug(f"Extension poll #{poll_count}, elapsed={elapsed*1000:.0f}ms")
241
+
242
+ if elapsed >= timeout_sec:
243
+ # Gather diagnostics
244
+ try:
245
+ diag_dict = await backend.eval(
246
+ """
247
+ (() => ({
248
+ sentience_defined: typeof window.sentience !== 'undefined',
249
+ sentience_snapshot: typeof window.sentience?.snapshot === 'function',
250
+ url: window.location.href,
251
+ extension_id: document.documentElement.dataset.sentienceExtensionId || null,
252
+ has_content_script: !!document.documentElement.dataset.sentienceExtensionId
253
+ }))()
254
+ """
255
+ )
256
+ diagnostics = ExtensionDiagnostics.from_dict(diag_dict)
257
+ logger.debug(f"Extension diagnostics: {diag_dict}")
258
+ except Exception as e:
259
+ diagnostics = ExtensionDiagnostics(error=f"Could not gather diagnostics: {e}")
260
+
261
+ raise ExtensionNotLoadedError.from_timeout(
262
+ timeout_ms=timeout_ms,
263
+ diagnostics=diagnostics,
264
+ )
265
+
266
+ # Check if extension is ready
267
+ try:
268
+ ready = await backend.eval(
269
+ "typeof window.sentience !== 'undefined' && "
270
+ "typeof window.sentience.snapshot === 'function'"
271
+ )
272
+ if ready:
273
+ return
274
+ except Exception:
275
+ pass # Keep polling
276
+
277
+ await asyncio.sleep(0.1)
278
+
279
+
280
+ async def _snapshot_via_extension(
281
+ backend: "BrowserBackend",
282
+ options: SnapshotOptions,
283
+ ) -> Snapshot:
284
+ """Take snapshot using local extension (Free tier)"""
285
+ # Wait for extension injection
286
+ await _wait_for_extension(backend, timeout_ms=5000)
287
+
288
+ # Build options dict for extension API
289
+ ext_options = _build_extension_options(options)
290
+
291
+ # Call extension's snapshot function
292
+ result = await backend.eval(
293
+ f"""
294
+ (() => {{
295
+ const options = {_json_serialize(ext_options)};
296
+ return window.sentience.snapshot(options);
297
+ }})()
298
+ """
299
+ )
300
+
301
+ if result is None:
302
+ # Try to get URL for better error message
303
+ try:
304
+ url = await backend.eval("window.location.href")
305
+ except Exception:
306
+ url = None
307
+ raise SnapshotError.from_null_result(url=url)
308
+
309
+ # Show overlay if requested
310
+ if options.show_overlay:
311
+ raw_elements = result.get("raw_elements", [])
312
+ if raw_elements:
313
+ await backend.eval(
314
+ f"""
315
+ (() => {{
316
+ if (window.sentience && window.sentience.showOverlay) {{
317
+ window.sentience.showOverlay({_json_serialize(raw_elements)}, null);
318
+ }}
319
+ }})()
320
+ """
321
+ )
322
+
323
+ # Build and return Snapshot
324
+ return Snapshot(**result)
325
+
326
+
327
+ async def _snapshot_via_api(
328
+ backend: "BrowserBackend",
329
+ options: SnapshotOptions,
330
+ ) -> Snapshot:
331
+ """Take snapshot using server-side API (Pro/Enterprise tier)"""
332
+ # Default API URL (same as main snapshot function)
333
+ api_url = SENTIENCE_API_URL
334
+
335
+ # Wait for extension injection (needed even for API mode to collect raw data)
336
+ await _wait_for_extension(backend, timeout_ms=5000)
337
+
338
+ # Step 1: Get raw data from local extension (always happens locally)
339
+ raw_options: dict[str, Any] = {}
340
+ if options.screenshot is not False:
341
+ raw_options["screenshot"] = options.screenshot
342
+
343
+ # Call extension to get raw elements
344
+ raw_result = await backend.eval(
345
+ f"""
346
+ (() => {{
347
+ const options = {_json_serialize(raw_options)};
348
+ return window.sentience.snapshot(options);
349
+ }})()
350
+ """
351
+ )
352
+
353
+ if raw_result is None:
354
+ try:
355
+ url = await backend.eval("window.location.href")
356
+ except Exception:
357
+ url = None
358
+ raise SnapshotError.from_null_result(url=url)
359
+
360
+ # Step 2: Send to server for smart ranking/filtering
361
+ payload = _build_snapshot_payload(raw_result, options)
362
+
363
+ try:
364
+ api_result = await _post_snapshot_to_gateway_async(
365
+ payload, options.sentience_api_key, api_url
366
+ )
367
+
368
+ # Merge API result with local data (screenshot, etc.)
369
+ snapshot_data = _merge_api_result_with_local(api_result, raw_result)
370
+
371
+ # Show visual overlay if requested (use API-ranked elements)
372
+ if options.show_overlay:
373
+ elements = api_result.get("elements", [])
374
+ if elements:
375
+ await backend.eval(
376
+ f"""
377
+ (() => {{
378
+ if (window.sentience && window.sentience.showOverlay) {{
379
+ window.sentience.showOverlay({_json_serialize(elements)}, null);
380
+ }}
381
+ }})()
382
+ """
383
+ )
384
+
385
+ return Snapshot(**snapshot_data)
386
+ except (RuntimeError, ValueError):
387
+ # Re-raise validation errors as-is
388
+ raise
389
+ except Exception as e:
390
+ # Fallback to local extension on API error
391
+ # This matches the behavior of the main snapshot function
392
+ raise RuntimeError(
393
+ f"Server-side snapshot API failed: {e}. "
394
+ "Try using use_api=False to use local extension instead."
395
+ ) from e
396
+
397
+
398
+ def _build_extension_options(options: SnapshotOptions) -> dict[str, Any]:
399
+ """Build options dict for extension API call."""
400
+ ext_options: dict[str, Any] = {}
401
+
402
+ # Screenshot config
403
+ if options.screenshot is not False:
404
+ if hasattr(options.screenshot, "model_dump"):
405
+ ext_options["screenshot"] = options.screenshot.model_dump()
406
+ else:
407
+ ext_options["screenshot"] = options.screenshot
408
+
409
+ # Limit (only if not default)
410
+ if options.limit != 50:
411
+ ext_options["limit"] = options.limit
412
+
413
+ # Filter
414
+ if options.filter is not None:
415
+ if hasattr(options.filter, "model_dump"):
416
+ ext_options["filter"] = options.filter.model_dump()
417
+ else:
418
+ ext_options["filter"] = options.filter
419
+
420
+ return ext_options
421
+
422
+
423
+ def _json_serialize(obj: Any) -> str:
424
+ """Serialize object to JSON string for embedding in JS."""
425
+ import json
426
+
427
+ return json.dumps(obj)
@@ -0,0 +1,196 @@
1
+ from typing import Optional
2
+
3
+ """
4
+ BaseAgent: Abstract base class for all Sentience agents
5
+ Defines the interface that all agent implementations must follow
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+
10
+ from .models import ActionHistory, AgentActionResult, Element, Snapshot, TokenStats
11
+
12
+
13
+ class BaseAgent(ABC):
14
+ """
15
+ Abstract base class for all Sentience agents.
16
+
17
+ Provides a standard interface for:
18
+ - Executing natural language goals (act)
19
+ - Tracking execution history
20
+ - Monitoring token usage
21
+ - Filtering elements based on goals
22
+
23
+ Subclasses must implement:
24
+ - act(): Execute a natural language goal
25
+ - get_history(): Return execution history
26
+ - get_token_stats(): Return token usage statistics
27
+ - clear_history(): Reset history and token counters
28
+
29
+ Subclasses can override:
30
+ - filter_elements(): Customize element filtering logic
31
+ """
32
+
33
+ @abstractmethod
34
+ def act(self, goal: str, **kwargs) -> AgentActionResult:
35
+ """
36
+ Execute a natural language goal using the agent.
37
+
38
+ Args:
39
+ goal: Natural language instruction (e.g., "Click the login button")
40
+ **kwargs: Additional parameters (implementation-specific)
41
+
42
+ Returns:
43
+ AgentActionResult with execution details
44
+
45
+ Raises:
46
+ RuntimeError: If execution fails after retries
47
+ """
48
+ pass
49
+
50
+ @abstractmethod
51
+ def get_history(self) -> list[ActionHistory]:
52
+ """
53
+ Get the execution history of all actions taken.
54
+
55
+ Returns:
56
+ List of ActionHistory entries
57
+ """
58
+ pass
59
+
60
+ @abstractmethod
61
+ def get_token_stats(self) -> TokenStats:
62
+ """
63
+ Get token usage statistics for the agent session.
64
+
65
+ Returns:
66
+ TokenStats with cumulative token counts
67
+ """
68
+ pass
69
+
70
+ @abstractmethod
71
+ def clear_history(self) -> None:
72
+ """
73
+ Clear execution history and reset token counters.
74
+
75
+ This resets the agent to a clean state.
76
+ """
77
+ pass
78
+
79
+ def filter_elements(self, snapshot: Snapshot, goal: str | None = None) -> list[Element]:
80
+ """
81
+ Filter elements from a snapshot based on goal context.
82
+
83
+ Default implementation returns all elements unchanged.
84
+ Subclasses can override to implement custom filtering logic
85
+ such as:
86
+ - Removing irrelevant elements based on goal keywords
87
+ - Boosting importance of matching elements
88
+ - Filtering by role, size, or visual properties
89
+
90
+ Args:
91
+ snapshot: Current page snapshot
92
+ goal: User's goal (can inform filtering strategy)
93
+
94
+ Returns:
95
+ Filtered list of elements (default: all elements)
96
+
97
+ Example:
98
+ >>> agent = SentienceAgent(browser, llm)
99
+ >>> snap = snapshot(browser)
100
+ >>> filtered = agent.filter_elements(snap, goal="Click login")
101
+ >>> # filtered now contains only relevant elements
102
+ """
103
+ return snapshot.elements
104
+
105
+
106
+ class BaseAgentAsync(ABC):
107
+ """
108
+ Abstract base class for all async Sentience agents.
109
+
110
+ Provides a standard interface for:
111
+ - Executing natural language goals (act)
112
+ - Tracking execution history
113
+ - Monitoring token usage
114
+ - Filtering elements based on goals
115
+
116
+ Subclasses must implement:
117
+ - act(): Execute a natural language goal (async)
118
+ - get_history(): Return execution history
119
+ - get_token_stats(): Return token usage statistics
120
+ - clear_history(): Reset history and token counters
121
+
122
+ Subclasses can override:
123
+ - filter_elements(): Customize element filtering logic
124
+ """
125
+
126
+ @abstractmethod
127
+ async def act(self, goal: str, **kwargs) -> AgentActionResult:
128
+ """
129
+ Execute a natural language goal using the agent (async).
130
+
131
+ Args:
132
+ goal: Natural language instruction (e.g., "Click the login button")
133
+ **kwargs: Additional parameters (implementation-specific)
134
+
135
+ Returns:
136
+ AgentActionResult with execution details
137
+
138
+ Raises:
139
+ RuntimeError: If execution fails after retries
140
+ """
141
+ pass
142
+
143
+ @abstractmethod
144
+ def get_history(self) -> list[ActionHistory]:
145
+ """
146
+ Get the execution history of all actions taken.
147
+
148
+ Returns:
149
+ List of ActionHistory entries
150
+ """
151
+ pass
152
+
153
+ @abstractmethod
154
+ def get_token_stats(self) -> TokenStats:
155
+ """
156
+ Get token usage statistics for the agent session.
157
+
158
+ Returns:
159
+ TokenStats with cumulative token counts
160
+ """
161
+ pass
162
+
163
+ @abstractmethod
164
+ def clear_history(self) -> None:
165
+ """
166
+ Clear execution history and reset token counters.
167
+
168
+ This resets the agent to a clean state.
169
+ """
170
+ pass
171
+
172
+ def filter_elements(self, snapshot: Snapshot, goal: str | None = None) -> list[Element]:
173
+ """
174
+ Filter elements from a snapshot based on goal context.
175
+
176
+ Default implementation returns all elements unchanged.
177
+ Subclasses can override to implement custom filtering logic
178
+ such as:
179
+ - Removing irrelevant elements based on goal keywords
180
+ - Boosting importance of matching elements
181
+ - Filtering by role, size, or visual properties
182
+
183
+ Args:
184
+ snapshot: Current page snapshot
185
+ goal: User's goal (can inform filtering strategy)
186
+
187
+ Returns:
188
+ Filtered list of elements (default: all elements)
189
+
190
+ Example:
191
+ >>> agent = SentienceAgentAsync(browser, llm)
192
+ >>> snap = await snapshot_async(browser)
193
+ >>> filtered = agent.filter_elements(snap, goal="Click login")
194
+ >>> # filtered now contains only relevant elements
195
+ """
196
+ return snapshot.elements