sentienceapi 0.92.2__py3-none-any.whl → 0.98.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 (64) hide show
  1. sentience/__init__.py +107 -2
  2. sentience/_extension_loader.py +156 -1
  3. sentience/action_executor.py +2 -0
  4. sentience/actions.py +354 -9
  5. sentience/agent.py +4 -0
  6. sentience/agent_runtime.py +840 -0
  7. sentience/asserts/__init__.py +70 -0
  8. sentience/asserts/expect.py +621 -0
  9. sentience/asserts/query.py +383 -0
  10. sentience/async_api.py +8 -1
  11. sentience/backends/__init__.py +137 -0
  12. sentience/backends/actions.py +372 -0
  13. sentience/backends/browser_use_adapter.py +241 -0
  14. sentience/backends/cdp_backend.py +393 -0
  15. sentience/backends/exceptions.py +211 -0
  16. sentience/backends/playwright_backend.py +194 -0
  17. sentience/backends/protocol.py +216 -0
  18. sentience/backends/sentience_context.py +469 -0
  19. sentience/backends/snapshot.py +483 -0
  20. sentience/browser.py +230 -74
  21. sentience/canonicalization.py +207 -0
  22. sentience/cloud_tracing.py +65 -24
  23. sentience/constants.py +6 -0
  24. sentience/cursor_policy.py +142 -0
  25. sentience/extension/content.js +35 -0
  26. sentience/extension/injected_api.js +310 -15
  27. sentience/extension/manifest.json +1 -1
  28. sentience/extension/pkg/sentience_core.d.ts +22 -22
  29. sentience/extension/pkg/sentience_core.js +192 -144
  30. sentience/extension/pkg/sentience_core_bg.wasm +0 -0
  31. sentience/extension/release.json +29 -29
  32. sentience/failure_artifacts.py +241 -0
  33. sentience/integrations/__init__.py +6 -0
  34. sentience/integrations/langchain/__init__.py +12 -0
  35. sentience/integrations/langchain/context.py +18 -0
  36. sentience/integrations/langchain/core.py +326 -0
  37. sentience/integrations/langchain/tools.py +180 -0
  38. sentience/integrations/models.py +46 -0
  39. sentience/integrations/pydanticai/__init__.py +15 -0
  40. sentience/integrations/pydanticai/deps.py +20 -0
  41. sentience/integrations/pydanticai/toolset.py +468 -0
  42. sentience/llm_provider.py +695 -18
  43. sentience/models.py +536 -3
  44. sentience/ordinal.py +280 -0
  45. sentience/query.py +66 -4
  46. sentience/schemas/trace_v1.json +27 -1
  47. sentience/snapshot.py +384 -93
  48. sentience/snapshot_diff.py +39 -54
  49. sentience/text_search.py +1 -0
  50. sentience/trace_event_builder.py +20 -1
  51. sentience/trace_indexing/indexer.py +3 -49
  52. sentience/tracer_factory.py +1 -3
  53. sentience/verification.py +618 -0
  54. sentience/visual_agent.py +3 -1
  55. {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/METADATA +198 -40
  56. sentienceapi-0.98.0.dist-info/RECORD +92 -0
  57. sentience/utils.py +0 -296
  58. sentienceapi-0.92.2.dist-info/RECORD +0 -65
  59. {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/WHEEL +0 -0
  60. {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/entry_points.txt +0 -0
  61. {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/licenses/LICENSE +0 -0
  62. {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/licenses/LICENSE-APACHE +0 -0
  63. {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/licenses/LICENSE-MIT +0 -0
  64. {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sentienceapi
3
- Version: 0.92.2
3
+ Version: 0.98.0
4
4
  Summary: Python SDK for Sentience AI Agent Browser Automation
5
5
  Author: Sentience Team
6
6
  License: MIT OR Apache-2.0
@@ -26,6 +26,20 @@ Requires-Dist: requests>=2.31.0
26
26
  Requires-Dist: httpx>=0.25.0
27
27
  Requires-Dist: playwright-stealth>=1.0.6
28
28
  Requires-Dist: markdownify>=0.11.6
29
+ Provides-Extra: browser-use
30
+ Requires-Dist: browser-use>=0.1.40; extra == "browser-use"
31
+ Provides-Extra: pydanticai
32
+ Requires-Dist: pydantic-ai; extra == "pydanticai"
33
+ Provides-Extra: langchain
34
+ Requires-Dist: langchain; extra == "langchain"
35
+ Requires-Dist: langgraph; extra == "langchain"
36
+ Provides-Extra: vision-local
37
+ Requires-Dist: pillow>=10.0.0; extra == "vision-local"
38
+ Requires-Dist: torch>=2.2.0; extra == "vision-local"
39
+ Requires-Dist: transformers>=4.46.0; extra == "vision-local"
40
+ Provides-Extra: mlx-vlm
41
+ Requires-Dist: pillow>=10.0.0; extra == "mlx-vlm"
42
+ Requires-Dist: mlx-vlm>=0.1.0; extra == "mlx-vlm"
29
43
  Provides-Extra: dev
30
44
  Requires-Dist: pytest>=7.0.0; extra == "dev"
31
45
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
@@ -33,7 +47,7 @@ Dynamic: license-file
33
47
 
34
48
  # Sentience Python SDK
35
49
 
36
- **Semantic geometry grounding for deterministic, debuggable AI web agents with time-travel traces.**
50
+ **Semantic snapshots and Jest-style assertions for reliable AI web agents with time-travel traces**
37
51
 
38
52
  ## 📦 Installation
39
53
 
@@ -55,6 +69,106 @@ pip install transformers torch # For local LLMs
55
69
  pip install -e .
56
70
  ```
57
71
 
72
+ ## Jest for AI Web Agent
73
+
74
+ ### Semantic snapshots and assertions that let agents act, verify, and know when they're done.
75
+
76
+ Use `AgentRuntime` to add Jest-style assertions to your agent loops. Verify browser state, check task completion, and get clear feedback on what's working:
77
+
78
+ ```python
79
+ import asyncio
80
+ from sentience import AsyncSentienceBrowser, AgentRuntime
81
+ from sentience.verification import (
82
+ url_contains,
83
+ exists,
84
+ all_of,
85
+ is_enabled,
86
+ is_checked,
87
+ value_equals,
88
+ )
89
+ from sentience.tracing import Tracer, JsonlTraceSink
90
+
91
+ async def main():
92
+ # Create tracer
93
+ tracer = Tracer(run_id="my-run", sink=JsonlTraceSink("trace.jsonl"))
94
+
95
+ # Create browser and runtime
96
+ async with AsyncSentienceBrowser() as browser:
97
+ page = await browser.new_page()
98
+ runtime = await AgentRuntime.from_sentience_browser(
99
+ browser=browser,
100
+ page=page,
101
+ tracer=tracer
102
+ )
103
+
104
+ # Navigate and take snapshot
105
+ await page.goto("https://example.com")
106
+ runtime.begin_step("Verify page loaded")
107
+ await runtime.snapshot()
108
+
109
+ # v1: deterministic assertions (Jest-style)
110
+ runtime.assert_(url_contains("example.com"), label="on_correct_domain")
111
+ runtime.assert_(exists("role=heading"), label="has_heading")
112
+ runtime.assert_(all_of([
113
+ exists("role=button"),
114
+ exists("role=link")
115
+ ]), label="has_interactive_elements")
116
+
117
+ # v1: state-aware assertions (when Gateway refinement is enabled)
118
+ runtime.assert_(is_enabled("role=button"), label="button_enabled")
119
+ runtime.assert_(is_checked("role=checkbox name~'subscribe'"), label="subscribe_checked_if_present")
120
+ runtime.assert_(value_equals("role=textbox name~'email'", "user@example.com"), label="email_value_if_present")
121
+
122
+ # v2: retry loop with snapshot confidence gating + exhaustion
123
+ ok = await runtime.check(
124
+ exists("role=heading"),
125
+ label="heading_eventually_visible",
126
+ required=True,
127
+ ).eventually(timeout_s=10.0, poll_s=0.25, min_confidence=0.7, max_snapshot_attempts=3)
128
+ print("eventually() result:", ok)
129
+
130
+ # Check task completion
131
+ if runtime.assert_done(exists("text~'Example'"), label="task_complete"):
132
+ print("✅ Task completed!")
133
+
134
+ print(f"Task done: {runtime.is_task_done}")
135
+
136
+ asyncio.run(main())
137
+ ```
138
+
139
+ ### Failure Artifact Buffer (Phase 1)
140
+
141
+ Capture a short ring buffer of screenshots and persist them when a required assertion fails.
142
+
143
+ ```python
144
+ from sentience.failure_artifacts import FailureArtifactsOptions
145
+
146
+ await runtime.enable_failure_artifacts(
147
+ FailureArtifactsOptions(buffer_seconds=15, capture_on_action=True, fps=0.0)
148
+ )
149
+
150
+ # After each action, record it (best-effort).
151
+ await runtime.record_action("CLICK")
152
+ ```
153
+
154
+ ### Redaction callback (Phase 3)
155
+
156
+ Provide a user-defined callback to redact snapshots and decide whether to persist frames. The SDK does not implement image/video redaction.
157
+
158
+ ```python
159
+ from sentience.failure_artifacts import FailureArtifactsOptions, RedactionContext, RedactionResult
160
+
161
+ def redact(ctx: RedactionContext) -> RedactionResult:
162
+ # Example: drop frames entirely, keep JSON only.
163
+ return RedactionResult(drop_frames=True)
164
+
165
+ await runtime.enable_failure_artifacts(
166
+ FailureArtifactsOptions(on_before_persist=redact)
167
+ )
168
+ ```
169
+
170
+ **See examples:** [`examples/asserts/`](examples/asserts/)
171
+
58
172
  ## 🚀 Quick Start: Choose Your Abstraction Level
59
173
 
60
174
  Sentience SDK offers **three abstraction levels** - use what fits your needs:
@@ -135,56 +249,66 @@ with SentienceBrowser(headless=False) as browser:
135
249
 
136
250
  ---
137
251
 
138
- <details>
139
- <summary><h2>💼 Real-World Example: Amazon Shopping Bot</h2></summary>
252
+ ## 🆕 What's New (2026-01-06)
253
+
254
+ ### Human-like Typing
255
+ Add realistic delays between keystrokes to mimic human typing:
256
+ ```python
257
+ from sentience import type_text
140
258
 
141
- This example demonstrates navigating Amazon, finding products, and adding items to cart:
259
+ # Type instantly (default)
260
+ type_text(browser, element_id, "Hello World")
142
261
 
262
+ # Type with human-like delay (~10ms between keystrokes)
263
+ type_text(browser, element_id, "Hello World", delay_ms=10)
264
+ ```
265
+
266
+ ### Scroll to Element
267
+ Scroll elements into view with smooth animation:
143
268
  ```python
144
- from sentience import SentienceBrowser, snapshot, find, click
145
- import time
269
+ from sentience import snapshot, find, scroll_to
146
270
 
147
- with SentienceBrowser(headless=False) as browser:
148
- # Navigate to Amazon Best Sellers
149
- browser.goto("https://www.amazon.com/gp/bestsellers/", wait_until="domcontentloaded")
150
- time.sleep(2) # Wait for dynamic content
271
+ snap = snapshot(browser)
272
+ button = find(snap, 'role=button text~"Submit"')
151
273
 
152
- # Take snapshot and find products
153
- snap = snapshot(browser)
154
- print(f"Found {len(snap.elements)} elements")
274
+ # Scroll element into view with smooth animation
275
+ scroll_to(browser, button.id)
155
276
 
156
- # Find first product in viewport using spatial filtering
157
- products = [
158
- el for el in snap.elements
159
- if el.role == "link"
160
- and el.visual_cues.is_clickable
161
- and el.in_viewport
162
- and not el.is_occluded
163
- and el.bbox.y < 600 # First row
164
- ]
277
+ # Scroll instantly to top of viewport
278
+ scroll_to(browser, button.id, behavior='instant', block='start')
279
+ ```
165
280
 
166
- if products:
167
- # Sort by position (left to right, top to bottom)
168
- products.sort(key=lambda e: (e.bbox.y, e.bbox.x))
169
- first_product = products[0]
281
+ ---
170
282
 
171
- print(f"Clicking: {first_product.text}")
172
- result = click(browser, first_product.id)
283
+ <details>
284
+ <summary><h2>💼 Real-World Example: Assertion-driven navigation</h2></summary>
173
285
 
174
- # Wait for product page
175
- browser.page.wait_for_load_state("networkidle")
176
- time.sleep(2)
286
+ This example shows how to use **assertions + `.eventually()`** to make an agent loop resilient:
177
287
 
178
- # Find and click "Add to Cart" button
179
- product_snap = snapshot(browser)
180
- add_to_cart = find(product_snap, "role=button text~'add to cart'")
288
+ ```python
289
+ import asyncio
290
+ import os
291
+ from sentience import AsyncSentienceBrowser, AgentRuntime
292
+ from sentience.tracing import Tracer, JsonlTraceSink
293
+ from sentience.verification import url_contains, exists
181
294
 
182
- if add_to_cart:
183
- cart_result = click(browser, add_to_cart.id)
184
- print(f"Added to cart: {cart_result.success}")
185
- ```
295
+ async def main():
296
+ tracer = Tracer(run_id="verified-run", sink=JsonlTraceSink("trace_verified.jsonl"))
297
+ async with AsyncSentienceBrowser(headless=True) as browser:
298
+ page = await browser.new_page()
299
+ runtime = await AgentRuntime.from_sentience_browser(browser=browser, page=page, tracer=tracer)
300
+ runtime.sentience_api_key = os.getenv("SENTIENCE_API_KEY") # optional, enables Gateway diagnostics
301
+
302
+ await page.goto("https://example.com")
303
+ runtime.begin_step("Verify we're on the right page")
186
304
 
187
- **📖 See the complete tutorial:** [Amazon Shopping Guide](../docs/AMAZON_SHOPPING_GUIDE.md)
305
+ await runtime.check(url_contains("example.com"), label="on_domain", required=True).eventually(
306
+ timeout_s=10.0, poll_s=0.25, min_confidence=0.7, max_snapshot_attempts=3
307
+ )
308
+ runtime.assert_(exists("role=heading"), label="heading_present")
309
+
310
+ asyncio.run(main())
311
+ ```
188
312
 
189
313
  </details>
190
314
 
@@ -832,6 +956,40 @@ with browser:
832
956
 
833
957
  </details>
834
958
 
959
+ <details>
960
+ <summary><h3>🔍 Agent Runtime Verification</h3></summary>
961
+
962
+ `AgentRuntime` provides assertion predicates for runtime verification in agent loops, enabling programmatic verification of browser state during execution.
963
+
964
+ ```python
965
+ from sentience import (
966
+ AgentRuntime, SentienceBrowser,
967
+ url_contains, exists, all_of
968
+ )
969
+ from sentience.tracer_factory import create_tracer
970
+
971
+ browser = SentienceBrowser()
972
+ browser.start()
973
+ tracer = create_tracer(run_id="my-run", upload_trace=False)
974
+ runtime = AgentRuntime(browser, browser.page, tracer)
975
+
976
+ # Navigate and take snapshot
977
+ browser.page.goto("https://example.com")
978
+ runtime.begin_step("Verify page")
979
+ runtime.snapshot()
980
+
981
+ # Run assertions
982
+ runtime.assert_(url_contains("example.com"), "on_correct_domain")
983
+ runtime.assert_(exists("role=heading"), "has_heading")
984
+ runtime.assert_done(exists("text~'Example'"), "task_complete")
985
+
986
+ print(f"Task done: {runtime.is_task_done}")
987
+ ```
988
+
989
+ **See example:** [`examples/agent_runtime_verification.py`](examples/agent_runtime_verification.py)
990
+
991
+ </details>
992
+
835
993
  <details>
836
994
  <summary><h3>🧰 Snapshot Utilities</h3></summary>
837
995
 
@@ -0,0 +1,92 @@
1
+ sentience/__init__.py,sha256=ZzolJfPqb2vnvBwrv7G7MgYJqrB8RuQ1VZsYCK4mgGY,6607
2
+ sentience/_extension_loader.py,sha256=z99Kvwh0bbFEbH04-LJ52t6kIpK9_hh1HG_QBdRg7e4,6118
3
+ sentience/action_executor.py,sha256=8ESWrFPvRDs5pp7Yvih7wkb_-8aY3kL0Ssr0KHRp-ZE,8226
4
+ sentience/actions.py,sha256=UUpmCKxws9tU_ppgITu4-nqUI0piFEkqGV9s_afrf-A,38382
5
+ sentience/agent.py,sha256=xcZ6gRucZRN0La3v5Y6jJRH2js8tgKx4qdwjcgfYVxs,49158
6
+ sentience/agent_config.py,sha256=n6HohW5j4VK3kT23xW7x2Vfz7HvEheb9g4AZ3VWfPpg,1588
7
+ sentience/agent_runtime.py,sha256=ak5CU6X-ig_mvwyt6lNV10XMh7lizlz0qeC97_xJ3Y8,30535
8
+ sentience/async_api.py,sha256=Wh2fE8SQ_9wpCPUyqpMubPbmuzzf83H_ncw21QUnTS8,4077
9
+ sentience/base_agent.py,sha256=5yVXe0S72I7GV4NPYySTvHfUAFvHBD4VjsNKSTo8MFo,5786
10
+ sentience/browser.py,sha256=B6UprJYyIcK4UFdxF8b8BhKwcuivgpwujcqgLK0qDBk,52937
11
+ sentience/browser_evaluator.py,sha256=CRiMsutM68dZWHnMIrFzpVzrkUwxVsXpjBplSIWCkso,9772
12
+ sentience/canonicalization.py,sha256=C7pkxaSgnCI0j_8oZ66yTQu6DGBCTM233IavjhSZgjA,6416
13
+ sentience/cli.py,sha256=R95DgCSJmezuymAdfL6fzUdB1HpCF3iMWcLO73KJ8eY,3851
14
+ sentience/cloud_tracing.py,sha256=ghhgDh9gqF1igEtmlAIFkRsSM7iVQanNhmOsJYhnDCk,32542
15
+ sentience/constants.py,sha256=sGiG_-gnchIW0maA0K9cVaq1-TAkwV4GyEW3kZ_6bhE,110
16
+ sentience/conversational_agent.py,sha256=v3FXczfNZdizQA7ejfgEbhGq_zaOboGb3__UaW7Alpo,18551
17
+ sentience/cursor_policy.py,sha256=36RsH387Y0EMj70_hMNLS6NdVbes6FEIcyB4hWtsypk,4137
18
+ sentience/element_filter.py,sha256=aVFrbTDkH-gtMZt07QfBZH3Oq8_KzwQMz17PD1uQ1h0,4021
19
+ sentience/expect.py,sha256=JIhHJ_UGiPvKWbX5SOmX1irOZlko5N5hhhX6d2Ymem0,6177
20
+ sentience/failure_artifacts.py,sha256=aMbAAZt06MJtCwxJSUw8U9REhUSWXR_j1OcVtzcg66g,7938
21
+ sentience/formatting.py,sha256=rptVpktftZZa5YJu4H2-K5dL8pzdIaPO9ONlEGSWnQw,483
22
+ sentience/generator.py,sha256=Wj--yn6bVo02nABBzrAIKPMmU8TRQm3JHAOER9XXT4Y,8035
23
+ sentience/inspector.py,sha256=D4q_GqXUCV3nUJv8uDv2novDbgnkboHOc9ahGeSYw9Q,14562
24
+ sentience/llm_interaction_handler.py,sha256=elvNtmGwIvoRcdra_fvijSONWzhztgdDmuPFJF2Whkw,7218
25
+ sentience/llm_provider.py,sha256=NZjJOPgbIhTSEzVceCA45-ZTleNzt3R4wTwH3_5_xyA,45685
26
+ sentience/llm_provider_utils.py,sha256=Hkr30MzZ5AvROJxEZHW97FINklzU9xoV8lITUQRoFls,3854
27
+ sentience/llm_response_builder.py,sha256=9lKJcfvD2EY7XcJtTYWTLYkxMVv_6nvlKTcGCO1nB2Q,4935
28
+ sentience/models.py,sha256=Je9Hk_9AOo1L0gFNFgIMT_a4DjxQqST1XPqt4raqq1c,38358
29
+ sentience/ordinal.py,sha256=E6XKLiGPhwavsWOghWwsG00JxOupF5bJnwJ9ctF3rP0,9048
30
+ sentience/overlay.py,sha256=awphkU0ZvkQrxSwQz7S8rvc3gQIqrDhztcQzzjEnR2c,7738
31
+ sentience/protocols.py,sha256=jXAa-RTxdIlMgISxTlIC8P79JqoD4i5T-cjbtgLjuUk,5667
32
+ sentience/query.py,sha256=Dx_OU8gm1BS-Vl9Ks3StLeuGCuZe99D_abzo8kVkNGU,12869
33
+ sentience/read.py,sha256=W0VSh9aBaIOMfUBcmjFubN1Ax1TmpCF1E74SLensy-g,7109
34
+ sentience/recorder.py,sha256=6cJVmjTT7HpRaet6KvdpTFAPVwFmD30eXHtbAKdwoUw,20636
35
+ sentience/screenshot.py,sha256=BJTPsIeE8irP1bhj4Vti2ohsZn5RkkGFTaZJOB855Pk,3147
36
+ sentience/sentience_methods.py,sha256=NmWmf_xfDt8t6RJs5Gf317EDBDrLcOUmHjDhrGpr4ws,2552
37
+ sentience/snapshot.py,sha256=1awUnAmxumo7us_-lYGkHHJh3aqqNzyGTlGPVIcJ6kU,29593
38
+ sentience/snapshot_diff.py,sha256=eh0PAl4PGsfAK8H0JopyQ-WwtPpV24BYV5t_sBqBs18,4716
39
+ sentience/text_search.py,sha256=MIKNn9PG-P4-L_0SzBB5F6O9KM9dzEKB4vRa4wPqoBw,10597
40
+ sentience/trace_event_builder.py,sha256=oj8Wb7mbWwoKRwzRivyVWiBXjy3G2SRMiYX0EaNUmBg,5025
41
+ sentience/trace_file_manager.py,sha256=JxvCx-Hzfyy0wP0DHo9JPnrmFNGtLvdCrPfk0mGiqmE,6311
42
+ sentience/tracer_factory.py,sha256=89gkYF5dTFw44_Db_FKm4vcrSaYhwzhr1jRk13MdnWc,12873
43
+ sentience/tracing.py,sha256=48J-vmtfhXjrJ4UB3i_oHbBmcKs5UsJ8ixfK3MrMt_Q,13885
44
+ sentience/verification.py,sha256=MADOKXSj4MjAmpD8DF6VUyW9UBBv-R2GJF9M-B8s8JM,18571
45
+ sentience/visual_agent.py,sha256=jdBC2YAckJWM2gE2bCW_6-3GXHsmY-CF7sryqyWuWgc,83054
46
+ sentience/wait.py,sha256=sx-87AN1TO_f8UbNP9np8zA0t27zQzQg2c2TltOn2h0,4373
47
+ sentience/asserts/__init__.py,sha256=73TN2aAGrgOkz_wakG4hcd9Cy69MF_3fRWiSFvFh-0o,1898
48
+ sentience/asserts/expect.py,sha256=IXf9yUSVDVFhSwfDGZqKb0raRyvBRkL4PtiWVI27lf4,20831
49
+ sentience/asserts/query.py,sha256=DPnHxEcY8-PenSqd-9J_Kmert3j5ri8xIqYigCwAlvM,11752
50
+ sentience/backends/__init__.py,sha256=HMEqcdp3mYfXYJwO4am4ndlCiRYIVCXcWOJR-OvU2Ic,4040
51
+ sentience/backends/actions.py,sha256=HTJy3xqSH6aZgSMVED5cW5hkCdiNNs_01fp1qo8Jawg,11637
52
+ sentience/backends/browser_use_adapter.py,sha256=bFb1PkPoZws4Ykq08a_ChK2Ms-Xobg94xCmEUmeGJDc,8066
53
+ sentience/backends/cdp_backend.py,sha256=sZJuOpSEhKTfGftbHMglja6AhuXUYlv_-eZS4pi9OY8,12674
54
+ sentience/backends/exceptions.py,sha256=vbvVn62HflMLPOc3YT8SngmjxPTLNL4VoE0eBLDO9cw,6687
55
+ sentience/backends/playwright_backend.py,sha256=XWBxFDGhIWJ6StzKdWNAxDP5aqTW7dzo74JN_qUfUOQ,6707
56
+ sentience/backends/protocol.py,sha256=sL11XDFUnxB-87RdWpN2266Z4WYxv74m1DH-Vsen3-k,5599
57
+ sentience/backends/sentience_context.py,sha256=0fAEpuK-YvD_tlqI0as-sIOXOoIoqFFY14rS1vTAT_w,16689
58
+ sentience/backends/snapshot.py,sha256=YAxaYH3dwaikrMswbhWGsncnBdWUV7lRY2n-oPCfKJo,15476
59
+ sentience/extension/background.js,sha256=Om-Wsu-DP0gjKILWjysW7le_BOCKm5YugUuuTMuic14,3754
60
+ sentience/extension/content.js,sha256=zdVveMeX_2bMupuzehSeAR5xtFzhMRC78hUn0O1oXmA,11249
61
+ sentience/extension/injected_api.js,sha256=pb6CGee5e1r9-DwEMWLsugSH7FpGOL-fxwV1nYpTY-w,66861
62
+ sentience/extension/manifest.json,sha256=FFaImjuIKdON9238WF5YypoPLJn_iWD6BaAt5dRIdvo,897
63
+ sentience/extension/release.json,sha256=w7c7qnB9HfqCgCoONOXkeiw1wSVVOpU484VPKczrQdc,6141
64
+ sentience/extension/pkg/sentience_core.d.ts,sha256=E_rJXQknWuU_8m5Nmt7buXy2J20bvMjBzIir31NBi8U,2011
65
+ sentience/extension/pkg/sentience_core.js,sha256=cUkeYs5FwGkFQ6l5zVWeEdSecFcSd4lAc-JC79DK13U,14623
66
+ sentience/extension/pkg/sentience_core_bg.wasm,sha256=drEsNRJ_s1OlU5CfJV6t_6fIHQggVQ2tl_9iPXmiVM4,112142
67
+ sentience/extension/pkg/sentience_core_bg.wasm.d.ts,sha256=O3c3HaUqmB6Aob6Pt0n3GEtTxM4VGeaClaA_-z2m2J4,517
68
+ sentience/integrations/__init__.py,sha256=TbJiIGTqpv9Bq9Kmq1img5aMlpEbfXPNnWxm5AK5H0w,221
69
+ sentience/integrations/models.py,sha256=TAcZhyvyisUcsTIihWPV1KCG2ylN2TuiwUKbvbkXDYk,1112
70
+ sentience/integrations/langchain/__init__.py,sha256=_8_fkan9_GXo-OLBUOFyUeISvtpza1w385sNQ8gK9zI,460
71
+ sentience/integrations/langchain/context.py,sha256=5iMQmANttMvnI8uuuEgUgfAPGqvgse0bYov4nvr8X-0,418
72
+ sentience/integrations/langchain/core.py,sha256=pFGJQqXkPOciC_CKymLGw0_E4_gmyPeu1tVXALbi8gw,11258
73
+ sentience/integrations/langchain/tools.py,sha256=kZ48jVl_VxSjUVVH7a9RYV2VvZu8ti72fi6NQ6zoYS4,7758
74
+ sentience/integrations/pydanticai/__init__.py,sha256=qeSnhoyAnyYQUC8ERk0FiAijPXHzf76Zpc5i5Zm64KU,464
75
+ sentience/integrations/pydanticai/deps.py,sha256=6uwsc1a2xVjMPV4grsymxLfgVmN25rDoQz4GhO7lFcw,460
76
+ sentience/integrations/pydanticai/toolset.py,sha256=J6uHHikFy5J2qjzh4BzlWL-MUVr_JBt4jb6CRhrxr0E,15378
77
+ sentience/schemas/trace_v1.json,sha256=qhMtb8PcuUwFSmuetkvKFcVOc7fcUpNvun372O6f12E,13487
78
+ sentience/trace_indexing/__init__.py,sha256=urjLuqqXCQE8pnwpYBqoMKnzZSqFJkM1SHmFve9RVE8,499
79
+ sentience/trace_indexing/index_schema.py,sha256=4ovMC_4t55DUkclq8NnBEEnKUZGfIieSkwMU_aBExKA,6262
80
+ sentience/trace_indexing/indexer.py,sha256=zQi8kDHisp5Pb6x42D7odM0Bu5fEL2LPpb74oHwyck4,13974
81
+ sentience/utils/__init__.py,sha256=h8vjeonoe9LF6TeeoJuJ9g6BdsCUP24lhmME7T9jhok,1125
82
+ sentience/utils/browser.py,sha256=pcDlAMgvmkjG13lNXe_LXiHmQs2QRQQBliTZiP7f1wA,1313
83
+ sentience/utils/element.py,sha256=S8AL9T-wqrbSJvJE-qr_Qv2FVk_gRiy6nx30hdRXy4E,6962
84
+ sentience/utils/formatting.py,sha256=IQ-3kLZu1k5ae4rlQ_AxnIkVPeLR9aa8J2NE-dj_eos,1829
85
+ sentienceapi-0.98.0.dist-info/licenses/LICENSE,sha256=jePeclQKwKdmz3jc0Oec6-jQuzwxIcGWPhMfxVII34Q,924
86
+ sentienceapi-0.98.0.dist-info/licenses/LICENSE-APACHE,sha256=YIflUygmGOc0hS_1f6EacrQUrH73G3j8VBQbDoqmY6A,11352
87
+ sentienceapi-0.98.0.dist-info/licenses/LICENSE-MIT,sha256=KiWwku3f8ikn6c8a6t0IdelVEu5GBLLr4tUKdGNNgJ8,1082
88
+ sentienceapi-0.98.0.dist-info/METADATA,sha256=HvsKbnvDI-aTxsD0rfW3od7AFH9Hv3vS3u1-tOS0VDg,32680
89
+ sentienceapi-0.98.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
90
+ sentienceapi-0.98.0.dist-info/entry_points.txt,sha256=HdW1BvgRJm3ZAbbqrwTvDWE2KbmVz-Ue0wllW-mLmvA,49
91
+ sentienceapi-0.98.0.dist-info/top_level.txt,sha256=A9IKao--8PsFFz5vDfBIXWHgN6oh3HkMQSiQWgUTUBQ,10
92
+ sentienceapi-0.98.0.dist-info/RECORD,,
sentience/utils.py DELETED
@@ -1,296 +0,0 @@
1
- """
2
- Digest utilities for snapshot canonicalization and hashing.
3
-
4
- Provides functions to compute stable digests of snapshots for determinism diff.
5
- Two digest strategies:
6
- - strict: includes structure + normalized text
7
- - loose: structure only (no text) - detects layout changes vs content changes
8
- """
9
-
10
- import hashlib
11
- import json
12
- import re
13
- from dataclasses import dataclass
14
- from pathlib import Path
15
- from typing import Any, Optional
16
-
17
- from playwright.sync_api import BrowserContext
18
-
19
-
20
- @dataclass
21
- class BBox:
22
- """Bounding box with normalized coordinates."""
23
-
24
- x: int
25
- y: int
26
- width: int
27
- height: int
28
-
29
- @classmethod
30
- def from_dict(cls, bbox_dict: dict[str, Any]) -> "BBox":
31
- """Create BBox from dictionary."""
32
- return cls(
33
- x=int(bbox_dict.get("x", 0)),
34
- y=int(bbox_dict.get("y", 0)),
35
- width=int(bbox_dict.get("width", 0)),
36
- height=int(bbox_dict.get("height", 0)),
37
- )
38
-
39
- def to_normalized(self, bucket_size: int = 2) -> list[int]:
40
- """
41
- Normalize bbox to fixed-size buckets to ignore minor jitter.
42
-
43
- Args:
44
- bucket_size: Pixel bucket size (default 2px)
45
-
46
- Returns:
47
- List of [x, y, width, height] rounded to buckets
48
- """
49
- return [
50
- round(self.x / bucket_size) * bucket_size,
51
- round(self.y / bucket_size) * bucket_size,
52
- round(self.width / bucket_size) * bucket_size,
53
- round(self.height / bucket_size) * bucket_size,
54
- ]
55
-
56
-
57
- @dataclass
58
- class ElementFingerprint:
59
- """Normalized element data for digest computation."""
60
-
61
- id: int
62
- role: str
63
- bbox: list[int] # Normalized
64
- clickable: int # 0 or 1
65
- primary: int # 0 or 1
66
- text: str = "" # Empty for loose digest
67
-
68
- def to_dict(self) -> dict[str, Any]:
69
- """Convert to dictionary for JSON serialization."""
70
- data = {
71
- "id": self.id,
72
- "role": self.role,
73
- "bbox": self.bbox,
74
- "clickable": self.clickable,
75
- "primary": self.primary,
76
- }
77
- if self.text: # Only include text if non-empty
78
- data["text"] = self.text
79
- return data
80
-
81
-
82
- def normalize_text_strict(text: str | None, max_length: int = 80) -> str:
83
- """
84
- Normalize text for strict digest (structure + content).
85
-
86
- Rules:
87
- - Lowercase
88
- - Trim and collapse whitespace
89
- - Cap length at max_length
90
- - Replace digit runs with '#'
91
- - Normalize currency: $79.99 -> $#
92
- - Normalize time patterns: 12:34 -> #:#
93
-
94
- Args:
95
- text: Input text
96
- max_length: Maximum text length (default 80)
97
-
98
- Returns:
99
- Normalized text string
100
- """
101
- if not text:
102
- return ""
103
-
104
- # Lowercase and trim
105
- text = text.strip().lower()
106
-
107
- # Collapse whitespace
108
- text = " ".join(text.split())
109
-
110
- # Cap length
111
- text = text[:max_length]
112
-
113
- # Replace digit runs with #
114
- text = re.sub(r"\d+", "#", text)
115
-
116
- # Normalize currency
117
- text = re.sub(r"\$\s*#", "$#", text)
118
-
119
- # Normalize time patterns (HH:MM or similar)
120
- text = re.sub(r"#:#", "#:#", text)
121
-
122
- # Normalize date patterns (YYYY-MM-DD or similar)
123
- text = re.sub(r"#-#-#", "#-#-#", text)
124
-
125
- return text
126
-
127
-
128
- def normalize_bbox(bbox: dict[str, Any] | BBox, bucket_size: int = 2) -> list[int]:
129
- """
130
- Round bbox to fixed-size buckets to ignore jitter.
131
-
132
- Args:
133
- bbox: BBox object or dict with x, y, width, height
134
- bucket_size: Pixel bucket size (default 2px)
135
-
136
- Returns:
137
- List of [x, y, width, height] rounded to buckets
138
- """
139
- if isinstance(bbox, BBox):
140
- return bbox.to_normalized(bucket_size)
141
-
142
- bbox_obj = BBox.from_dict(bbox)
143
- return bbox_obj.to_normalized(bucket_size)
144
-
145
-
146
- def extract_element_fingerprint(
147
- element: dict[str, Any],
148
- include_text: bool = True,
149
- ) -> ElementFingerprint:
150
- """
151
- Extract normalized fingerprint from element dict.
152
-
153
- Args:
154
- element: Element dict from snapshot
155
- include_text: Whether to include normalized text (False for loose digest)
156
-
157
- Returns:
158
- ElementFingerprint with normalized data
159
- """
160
- # Extract basic fields
161
- element_id = element.get("id", 0)
162
- role = element.get("role", "unknown")
163
-
164
- # Extract and normalize bbox
165
- bbox_data = element.get("bbox", {})
166
- bbox_normalized = normalize_bbox(bbox_data)
167
-
168
- # Extract visual cues
169
- visual_cues = element.get("visual_cues", {})
170
- clickable = 1 if visual_cues.get("is_clickable", False) else 0
171
- primary = 1 if visual_cues.get("is_primary", False) else 0
172
-
173
- # Extract and normalize text (if requested)
174
- text = ""
175
- if include_text:
176
- raw_text = element.get("text", "")
177
- text = normalize_text_strict(raw_text)
178
-
179
- return ElementFingerprint(
180
- id=element_id,
181
- role=role,
182
- bbox=bbox_normalized,
183
- clickable=clickable,
184
- primary=primary,
185
- text=text,
186
- )
187
-
188
-
189
- def canonical_snapshot_strict(elements: list[dict[str, Any]]) -> str:
190
- """
191
- Create strict snapshot digest (structure + normalized text).
192
-
193
- Args:
194
- elements: List of element dicts from snapshot
195
-
196
- Returns:
197
- Canonical JSON string for hashing
198
- """
199
- fingerprints = []
200
-
201
- for element in sorted(elements, key=lambda e: e.get("id", 0)):
202
- fingerprint = extract_element_fingerprint(element, include_text=True)
203
- fingerprints.append(fingerprint.to_dict())
204
-
205
- return json.dumps(fingerprints, sort_keys=True, ensure_ascii=False)
206
-
207
-
208
- def canonical_snapshot_loose(elements: list[dict[str, Any]]) -> str:
209
- """
210
- Create loose snapshot digest (structure only, no text).
211
-
212
- This is more resistant to content churn (prices, ads, timestamps).
213
- Use for detecting structural changes vs content changes.
214
-
215
- Args:
216
- elements: List of element dicts from snapshot
217
-
218
- Returns:
219
- Canonical JSON string for hashing
220
- """
221
- fingerprints = []
222
-
223
- for element in sorted(elements, key=lambda e: e.get("id", 0)):
224
- fingerprint = extract_element_fingerprint(element, include_text=False)
225
- fingerprints.append(fingerprint.to_dict())
226
-
227
- return json.dumps(fingerprints, sort_keys=True, ensure_ascii=False)
228
-
229
-
230
- def sha256_digest(canonical_str: str) -> str:
231
- """
232
- Compute SHA256 hash with 'sha256:' prefix.
233
-
234
- Args:
235
- canonical_str: Canonical string to hash
236
-
237
- Returns:
238
- Hash string with format: "sha256:<hex>"
239
- """
240
- hash_obj = hashlib.sha256(canonical_str.encode("utf-8"))
241
- return f"sha256:{hash_obj.hexdigest()}"
242
-
243
-
244
- def compute_snapshot_digests(elements: list[dict[str, Any]]) -> dict[str, str]:
245
- """
246
- Compute both strict and loose digests for a snapshot.
247
-
248
- Args:
249
- elements: List of element dicts from snapshot
250
-
251
- Returns:
252
- Dict with 'strict' and 'loose' digest strings
253
- """
254
- canonical_strict = canonical_snapshot_strict(elements)
255
- canonical_loose = canonical_snapshot_loose(elements)
256
-
257
- return {
258
- "strict": sha256_digest(canonical_strict),
259
- "loose": sha256_digest(canonical_loose),
260
- }
261
-
262
-
263
- def save_storage_state(context: BrowserContext, file_path: str | Path) -> None:
264
- """
265
- Save current browser storage state (cookies + localStorage) to a file.
266
-
267
- This is useful for capturing a logged-in session to reuse later.
268
-
269
- Args:
270
- context: Playwright BrowserContext
271
- file_path: Path to save the storage state JSON file
272
-
273
- Example:
274
- ```python
275
- from sentience import SentienceBrowser, save_storage_state
276
-
277
- browser = SentienceBrowser()
278
- browser.start()
279
-
280
- # User logs in manually or via agent
281
- browser.goto("https://example.com")
282
- # ... login happens ...
283
-
284
- # Save session for later
285
- save_storage_state(browser.context, "auth.json")
286
- ```
287
-
288
- Raises:
289
- IOError: If file cannot be written
290
- """
291
- storage_state = context.storage_state()
292
- file_path_obj = Path(file_path)
293
- file_path_obj.parent.mkdir(parents=True, exist_ok=True)
294
- with open(file_path_obj, "w") as f:
295
- json.dump(storage_state, f, indent=2)
296
- print(f"✅ [Sentience] Saved storage state to {file_path_obj}")