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,46 @@
1
+ """
2
+ Configuration classes for Sentience agents.
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass
9
+ class AgentConfig:
10
+ """
11
+ Configuration for Sentience Agent execution.
12
+
13
+ This dataclass provides centralized configuration for agent behavior,
14
+ including snapshot limits, retry logic, verification, and screenshot capture.
15
+
16
+ Attributes:
17
+ snapshot_limit: Maximum elements to include in LLM context (default: 50)
18
+ temperature: LLM temperature 0.0-1.0 for response generation (default: 0.0)
19
+ max_retries: Number of retries on action failure (default: 1)
20
+ verify: Whether to run verification step after actions (default: True)
21
+ capture_screenshots: Whether to capture screenshots during execution (default: True)
22
+ screenshot_format: Screenshot format 'png' or 'jpeg' (default: 'jpeg')
23
+ screenshot_quality: JPEG quality 1-100, ignored for PNG (default: 80)
24
+
25
+ Example:
26
+ >>> from sentience import AgentConfig, SentienceAgent
27
+ >>> config = AgentConfig(
28
+ ... snapshot_limit=100,
29
+ ... max_retries=2,
30
+ ... verify=True
31
+ ... )
32
+ >>> agent = SentienceAgent(browser, llm, config=config)
33
+ """
34
+
35
+ snapshot_limit: int = 50
36
+ temperature: float = 0.0
37
+ max_retries: int = 1
38
+ verify: bool = True
39
+
40
+ # Screenshot options
41
+ capture_screenshots: bool = True
42
+ screenshot_format: str = "jpeg" # "png" or "jpeg"
43
+ screenshot_quality: int = 80 # 1-100 (for JPEG only)
44
+
45
+ # Visual overlay options
46
+ show_overlay: bool = False # Show green bbox overlay in browser
@@ -0,0 +1,424 @@
1
+ """
2
+ Agent runtime for verification loop support.
3
+
4
+ This module provides a thin runtime wrapper that combines:
5
+ 1. Browser session management (via BrowserBackend protocol)
6
+ 2. Snapshot/query helpers
7
+ 3. Tracer for event emission
8
+ 4. Assertion/verification methods
9
+
10
+ The AgentRuntime is designed to be used in agent verification loops where
11
+ you need to repeatedly take snapshots, execute actions, and verify results.
12
+
13
+ Example usage with browser-use:
14
+ from browser_use import BrowserSession, BrowserProfile
15
+ from sentience import get_extension_dir
16
+ from sentience.backends import BrowserUseAdapter
17
+ from sentience.agent_runtime import AgentRuntime
18
+ from sentience.verification import url_matches, exists
19
+ from sentience.tracing import Tracer, JsonlTraceSink
20
+
21
+ # Setup browser-use with Sentience extension
22
+ profile = BrowserProfile(args=[f"--load-extension={get_extension_dir()}"])
23
+ session = BrowserSession(browser_profile=profile)
24
+ await session.start()
25
+
26
+ # Create adapter and backend
27
+ adapter = BrowserUseAdapter(session)
28
+ backend = await adapter.create_backend()
29
+
30
+ # Navigate using browser-use
31
+ page = await session.get_current_page()
32
+ await page.goto("https://example.com")
33
+
34
+ # Create runtime with backend
35
+ sink = JsonlTraceSink("trace.jsonl")
36
+ tracer = Tracer(run_id="test-run", sink=sink)
37
+ runtime = AgentRuntime(backend=backend, tracer=tracer)
38
+
39
+ # Take snapshot and run assertions
40
+ await runtime.snapshot()
41
+ runtime.assert_(url_matches(r"example\\.com"), label="on_homepage")
42
+ runtime.assert_(exists("role=button"), label="has_buttons")
43
+
44
+ # Check if task is done
45
+ if runtime.assert_done(exists("text~'Success'"), label="task_complete"):
46
+ print("Task completed!")
47
+
48
+ Example usage with AsyncSentienceBrowser (backward compatible):
49
+ from sentience import AsyncSentienceBrowser
50
+ from sentience.agent_runtime import AgentRuntime
51
+
52
+ async with AsyncSentienceBrowser() as browser:
53
+ page = await browser.new_page()
54
+ await page.goto("https://example.com")
55
+
56
+ runtime = await AgentRuntime.from_sentience_browser(
57
+ browser=browser,
58
+ page=page,
59
+ tracer=tracer,
60
+ )
61
+ await runtime.snapshot()
62
+ """
63
+
64
+ from __future__ import annotations
65
+
66
+ import uuid
67
+ from typing import TYPE_CHECKING, Any
68
+
69
+ from .models import Snapshot, SnapshotOptions
70
+ from .verification import AssertContext, Predicate
71
+
72
+ if TYPE_CHECKING:
73
+ from playwright.async_api import Page
74
+
75
+ from .backends.protocol import BrowserBackend
76
+ from .browser import AsyncSentienceBrowser
77
+ from .tracing import Tracer
78
+
79
+
80
+ class AgentRuntime:
81
+ """
82
+ Runtime wrapper for agent verification loops.
83
+
84
+ Provides ergonomic methods for:
85
+ - snapshot(): Take page snapshot
86
+ - assert_(): Evaluate assertion predicates
87
+ - assert_done(): Assert task completion (required assertion)
88
+
89
+ The runtime manages assertion state per step and emits verification events
90
+ to the tracer for Studio timeline display.
91
+
92
+ Attributes:
93
+ backend: BrowserBackend instance for browser operations
94
+ tracer: Tracer for event emission
95
+ step_id: Current step identifier
96
+ step_index: Current step index (0-based)
97
+ last_snapshot: Most recent snapshot (for assertion context)
98
+ """
99
+
100
+ def __init__(
101
+ self,
102
+ backend: BrowserBackend,
103
+ tracer: Tracer,
104
+ snapshot_options: SnapshotOptions | None = None,
105
+ sentience_api_key: str | None = None,
106
+ ):
107
+ """
108
+ Initialize agent runtime with any BrowserBackend-compatible browser.
109
+
110
+ Args:
111
+ backend: Any browser implementing BrowserBackend protocol.
112
+ Examples:
113
+ - CDPBackendV0 (for browser-use via BrowserUseAdapter)
114
+ - PlaywrightBackend (future, for direct Playwright)
115
+ tracer: Tracer for emitting verification events
116
+ snapshot_options: Default options for snapshots
117
+ sentience_api_key: API key for Pro/Enterprise tier (enables Gateway refinement)
118
+ """
119
+ self.backend = backend
120
+ self.tracer = tracer
121
+
122
+ # Build default snapshot options with API key if provided
123
+ default_opts = snapshot_options or SnapshotOptions()
124
+ if sentience_api_key:
125
+ default_opts.sentience_api_key = sentience_api_key
126
+ if default_opts.use_api is None:
127
+ default_opts.use_api = True
128
+ self._snapshot_options = default_opts
129
+
130
+ # Step tracking
131
+ self.step_id: str | None = None
132
+ self.step_index: int = 0
133
+
134
+ # Snapshot state
135
+ self.last_snapshot: Snapshot | None = None
136
+
137
+ # Cached URL (updated on snapshot or explicit get_url call)
138
+ self._cached_url: str | None = None
139
+
140
+ # Assertions accumulated during current step
141
+ self._assertions_this_step: list[dict[str, Any]] = []
142
+
143
+ # Task completion tracking
144
+ self._task_done: bool = False
145
+ self._task_done_label: str | None = None
146
+
147
+ @classmethod
148
+ async def from_sentience_browser(
149
+ cls,
150
+ browser: AsyncSentienceBrowser,
151
+ page: Page,
152
+ tracer: Tracer,
153
+ snapshot_options: SnapshotOptions | None = None,
154
+ sentience_api_key: str | None = None,
155
+ ) -> AgentRuntime:
156
+ """
157
+ Create AgentRuntime from AsyncSentienceBrowser (backward compatibility).
158
+
159
+ This factory method wraps an AsyncSentienceBrowser + Page combination
160
+ into the new BrowserBackend-based AgentRuntime.
161
+
162
+ Args:
163
+ browser: AsyncSentienceBrowser instance
164
+ page: Playwright Page for browser interaction
165
+ tracer: Tracer for emitting verification events
166
+ snapshot_options: Default options for snapshots
167
+ sentience_api_key: API key for Pro/Enterprise tier
168
+
169
+ Returns:
170
+ AgentRuntime instance
171
+ """
172
+ from .backends.playwright_backend import PlaywrightBackend
173
+
174
+ backend = PlaywrightBackend(page)
175
+ runtime = cls(
176
+ backend=backend,
177
+ tracer=tracer,
178
+ snapshot_options=snapshot_options,
179
+ sentience_api_key=sentience_api_key,
180
+ )
181
+ # Store browser reference for snapshot() to use
182
+ runtime._legacy_browser = browser
183
+ runtime._legacy_page = page
184
+ return runtime
185
+
186
+ def _ctx(self) -> AssertContext:
187
+ """
188
+ Build assertion context from current state.
189
+
190
+ Returns:
191
+ AssertContext with current snapshot and URL
192
+ """
193
+ url = None
194
+ if self.last_snapshot is not None:
195
+ url = self.last_snapshot.url
196
+ elif self._cached_url:
197
+ url = self._cached_url
198
+
199
+ return AssertContext(
200
+ snapshot=self.last_snapshot,
201
+ url=url,
202
+ step_id=self.step_id,
203
+ )
204
+
205
+ async def get_url(self) -> str:
206
+ """
207
+ Get current page URL.
208
+
209
+ Returns:
210
+ Current page URL
211
+ """
212
+ url = await self.backend.get_url()
213
+ self._cached_url = url
214
+ return url
215
+
216
+ async def snapshot(self, **kwargs: Any) -> Snapshot:
217
+ """
218
+ Take a snapshot of the current page state.
219
+
220
+ This updates last_snapshot which is used as context for assertions.
221
+
222
+ Args:
223
+ **kwargs: Override default snapshot options for this call.
224
+ Common options:
225
+ - limit: Maximum elements to return
226
+ - goal: Task goal for ordinal support
227
+ - screenshot: Include screenshot
228
+ - show_overlay: Show visual overlay
229
+
230
+ Returns:
231
+ Snapshot of current page state
232
+ """
233
+ # Check if using legacy browser (backward compat)
234
+ if hasattr(self, "_legacy_browser") and hasattr(self, "_legacy_page"):
235
+ self.last_snapshot = await self._legacy_browser.snapshot(self._legacy_page, **kwargs)
236
+ return self.last_snapshot
237
+
238
+ # Use backend-agnostic snapshot
239
+ from .backends.snapshot import snapshot as backend_snapshot
240
+
241
+ # Merge default options with call-specific kwargs
242
+ options_dict = self._snapshot_options.model_dump(exclude_none=True)
243
+ options_dict.update(kwargs)
244
+ options = SnapshotOptions(**options_dict)
245
+
246
+ self.last_snapshot = await backend_snapshot(self.backend, options=options)
247
+ return self.last_snapshot
248
+
249
+ def begin_step(self, goal: str, step_index: int | None = None) -> str:
250
+ """
251
+ Begin a new step in the verification loop.
252
+
253
+ This:
254
+ - Generates a new step_id
255
+ - Clears assertions from previous step
256
+ - Increments step_index (or uses provided value)
257
+
258
+ Args:
259
+ goal: Description of what this step aims to achieve
260
+ step_index: Optional explicit step index (otherwise auto-increments)
261
+
262
+ Returns:
263
+ Generated step_id
264
+ """
265
+ # Clear previous step state
266
+ self._assertions_this_step = []
267
+
268
+ # Generate new step_id
269
+ self.step_id = str(uuid.uuid4())
270
+
271
+ # Update step index
272
+ if step_index is not None:
273
+ self.step_index = step_index
274
+ else:
275
+ self.step_index += 1
276
+
277
+ return self.step_id
278
+
279
+ def assert_(
280
+ self,
281
+ predicate: Predicate,
282
+ label: str,
283
+ required: bool = False,
284
+ ) -> bool:
285
+ """
286
+ Evaluate an assertion against current snapshot state.
287
+
288
+ The assertion result is:
289
+ 1. Accumulated for inclusion in step_end.data.verify.signals.assertions
290
+ 2. Emitted as a dedicated 'verification' event for Studio timeline
291
+
292
+ Args:
293
+ predicate: Predicate function to evaluate
294
+ label: Human-readable label for this assertion
295
+ required: If True, this assertion gates step success (default: False)
296
+
297
+ Returns:
298
+ True if assertion passed, False otherwise
299
+ """
300
+ outcome = predicate(self._ctx())
301
+
302
+ record = {
303
+ "label": label,
304
+ "passed": outcome.passed,
305
+ "required": required,
306
+ "reason": outcome.reason,
307
+ "details": outcome.details,
308
+ }
309
+ self._assertions_this_step.append(record)
310
+
311
+ # Emit dedicated verification event (Option B from design doc)
312
+ # This makes assertions visible in Studio timeline
313
+ self.tracer.emit(
314
+ "verification",
315
+ data={
316
+ "kind": "assert",
317
+ "passed": outcome.passed,
318
+ **record,
319
+ },
320
+ step_id=self.step_id,
321
+ )
322
+
323
+ return outcome.passed
324
+
325
+ def assert_done(
326
+ self,
327
+ predicate: Predicate,
328
+ label: str,
329
+ ) -> bool:
330
+ """
331
+ Assert task completion (required assertion).
332
+
333
+ This is a convenience wrapper for assert_() with required=True.
334
+ When the assertion passes, it marks the task as done.
335
+
336
+ Use this for final verification that the agent's goal is complete.
337
+
338
+ Args:
339
+ predicate: Predicate function to evaluate
340
+ label: Human-readable label for this assertion
341
+
342
+ Returns:
343
+ True if task is complete (assertion passed), False otherwise
344
+ """
345
+ ok = self.assert_(predicate, label=label, required=True)
346
+ if ok:
347
+ self._task_done = True
348
+ self._task_done_label = label
349
+
350
+ # Emit task_done verification event
351
+ self.tracer.emit(
352
+ "verification",
353
+ data={
354
+ "kind": "task_done",
355
+ "passed": True,
356
+ "label": label,
357
+ },
358
+ step_id=self.step_id,
359
+ )
360
+
361
+ return ok
362
+
363
+ def get_assertions_for_step_end(self) -> dict[str, Any]:
364
+ """
365
+ Get assertions data for inclusion in step_end.data.verify.signals.
366
+
367
+ This is called when building the step_end event to include
368
+ assertion results in the trace.
369
+
370
+ Returns:
371
+ Dictionary with 'assertions', 'task_done', 'task_done_label' keys
372
+ """
373
+ result: dict[str, Any] = {
374
+ "assertions": self._assertions_this_step.copy(),
375
+ }
376
+
377
+ if self._task_done:
378
+ result["task_done"] = True
379
+ result["task_done_label"] = self._task_done_label
380
+
381
+ return result
382
+
383
+ def flush_assertions(self) -> list[dict[str, Any]]:
384
+ """
385
+ Get and clear assertions for current step.
386
+
387
+ Call this at step end to get accumulated assertions
388
+ for the step_end event, then clear for next step.
389
+
390
+ Returns:
391
+ List of assertion records from this step
392
+ """
393
+ assertions = self._assertions_this_step.copy()
394
+ self._assertions_this_step = []
395
+ return assertions
396
+
397
+ @property
398
+ def is_task_done(self) -> bool:
399
+ """Check if task has been marked as done via assert_done()."""
400
+ return self._task_done
401
+
402
+ def reset_task_done(self) -> None:
403
+ """Reset task_done state (for multi-task runs)."""
404
+ self._task_done = False
405
+ self._task_done_label = None
406
+
407
+ def all_assertions_passed(self) -> bool:
408
+ """
409
+ Check if all assertions in current step passed.
410
+
411
+ Returns:
412
+ True if all assertions passed (or no assertions made)
413
+ """
414
+ return all(a["passed"] for a in self._assertions_this_step)
415
+
416
+ def required_assertions_passed(self) -> bool:
417
+ """
418
+ Check if all required assertions in current step passed.
419
+
420
+ Returns:
421
+ True if all required assertions passed (or no required assertions)
422
+ """
423
+ required = [a for a in self._assertions_this_step if a.get("required")]
424
+ return all(a["passed"] for a in required)
@@ -0,0 +1,70 @@
1
+ """
2
+ Assertion DSL for Sentience SDK.
3
+
4
+ This module provides a Playwright/Cypress-like assertion API for verifying
5
+ browser state in agent verification loops.
6
+
7
+ Main exports:
8
+ - E: Element query builder (filters elements by role, text, href, etc.)
9
+ - expect: Expectation builder (creates predicates from queries)
10
+ - in_dominant_list: Query over dominant group elements (ordinal access)
11
+
12
+ Example usage:
13
+ from sentience.asserts import E, expect, in_dominant_list
14
+
15
+ # Basic presence assertions
16
+ runtime.assert_(
17
+ expect(E(role="button", text_contains="Save")).to_exist(),
18
+ label="save_button_visible"
19
+ )
20
+
21
+ # Visibility assertions
22
+ runtime.assert_(
23
+ expect(E(text_contains="Checkout")).to_be_visible(),
24
+ label="checkout_visible"
25
+ )
26
+
27
+ # Global text assertions
28
+ runtime.assert_(
29
+ expect.text_present("Welcome back"),
30
+ label="user_logged_in"
31
+ )
32
+ runtime.assert_(
33
+ expect.no_text("Error"),
34
+ label="no_error_message"
35
+ )
36
+
37
+ # Ordinal assertions on dominant group
38
+ runtime.assert_(
39
+ expect(in_dominant_list().nth(0)).to_have_text_contains("Show HN"),
40
+ label="first_item_is_show_hn"
41
+ )
42
+
43
+ # Task completion
44
+ runtime.assert_done(
45
+ expect.text_present("Order confirmed"),
46
+ label="checkout_complete"
47
+ )
48
+
49
+ The DSL compiles to existing Predicate functions, so it works seamlessly
50
+ with AgentRuntime.assert_() and assert_done().
51
+ """
52
+
53
+ from .expect import EventuallyConfig, EventuallyWrapper, ExpectBuilder, expect, with_eventually
54
+ from .query import E, ElementQuery, ListQuery, MultiQuery, in_dominant_list
55
+
56
+ __all__ = [
57
+ # Query builders
58
+ "E",
59
+ "ElementQuery",
60
+ "ListQuery",
61
+ "MultiQuery",
62
+ "in_dominant_list",
63
+ # Expectation builders
64
+ "expect",
65
+ "ExpectBuilder",
66
+ # Eventually helpers
67
+ "with_eventually",
68
+ "EventuallyWrapper",
69
+ "EventuallyConfig",
70
+ ]