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,194 @@
1
+ """
2
+ Playwright backend implementation for BrowserBackend protocol.
3
+
4
+ This wraps existing SentienceBrowser/AsyncSentienceBrowser to provide
5
+ a unified interface, enabling code that works with both browser-use
6
+ (CDPBackendV0) and native Playwright (PlaywrightBackend).
7
+
8
+ Usage:
9
+ from sentience import SentienceBrowserAsync
10
+ from sentience.backends import PlaywrightBackend, snapshot_from_backend
11
+
12
+ browser = SentienceBrowserAsync()
13
+ await browser.start()
14
+ await browser.goto("https://example.com")
15
+
16
+ # Create backend from existing browser
17
+ backend = PlaywrightBackend(browser.page)
18
+
19
+ # Use backend-agnostic functions
20
+ snap = await snapshot_from_backend(backend)
21
+ await click(backend, element.bbox)
22
+ """
23
+
24
+ import asyncio
25
+ import base64
26
+ import time
27
+ from typing import TYPE_CHECKING, Any, Literal
28
+
29
+ from .protocol import BrowserBackend, LayoutMetrics, ViewportInfo
30
+
31
+ if TYPE_CHECKING:
32
+ from playwright.async_api import Page as AsyncPage
33
+
34
+
35
+ class PlaywrightBackend:
36
+ """
37
+ Playwright-based implementation of BrowserBackend.
38
+
39
+ Wraps a Playwright async Page to provide the standard backend interface.
40
+ This enables using backend-agnostic actions with existing SentienceBrowser code.
41
+ """
42
+
43
+ def __init__(self, page: "AsyncPage") -> None:
44
+ """
45
+ Initialize Playwright backend.
46
+
47
+ Args:
48
+ page: Playwright async Page object
49
+ """
50
+ self._page = page
51
+ self._cached_viewport: ViewportInfo | None = None
52
+
53
+ @property
54
+ def page(self) -> "AsyncPage":
55
+ """Access the underlying Playwright page."""
56
+ return self._page
57
+
58
+ async def refresh_page_info(self) -> ViewportInfo:
59
+ """Cache viewport + scroll offsets; cheap & safe to call often."""
60
+ result = await self._page.evaluate(
61
+ """
62
+ (() => ({
63
+ width: window.innerWidth,
64
+ height: window.innerHeight,
65
+ scroll_x: window.scrollX,
66
+ scroll_y: window.scrollY,
67
+ content_width: document.documentElement.scrollWidth,
68
+ content_height: document.documentElement.scrollHeight
69
+ }))()
70
+ """
71
+ )
72
+
73
+ self._cached_viewport = ViewportInfo(
74
+ width=result.get("width", 0),
75
+ height=result.get("height", 0),
76
+ scroll_x=result.get("scroll_x", 0),
77
+ scroll_y=result.get("scroll_y", 0),
78
+ content_width=result.get("content_width"),
79
+ content_height=result.get("content_height"),
80
+ )
81
+ return self._cached_viewport
82
+
83
+ async def eval(self, expression: str) -> Any:
84
+ """Evaluate JavaScript expression in page context."""
85
+ return await self._page.evaluate(expression)
86
+
87
+ async def call(
88
+ self,
89
+ function_declaration: str,
90
+ args: list[Any] | None = None,
91
+ ) -> Any:
92
+ """Call JavaScript function with arguments."""
93
+ if args:
94
+ return await self._page.evaluate(function_declaration, *args)
95
+ return await self._page.evaluate(f"({function_declaration})()")
96
+
97
+ async def get_layout_metrics(self) -> LayoutMetrics:
98
+ """Get page layout metrics."""
99
+ # Playwright doesn't expose CDP directly in the same way,
100
+ # so we approximate using JavaScript
101
+ result = await self._page.evaluate(
102
+ """
103
+ (() => ({
104
+ viewport_x: window.scrollX,
105
+ viewport_y: window.scrollY,
106
+ viewport_width: window.innerWidth,
107
+ viewport_height: window.innerHeight,
108
+ content_width: document.documentElement.scrollWidth,
109
+ content_height: document.documentElement.scrollHeight,
110
+ device_scale_factor: window.devicePixelRatio || 1
111
+ }))()
112
+ """
113
+ )
114
+
115
+ return LayoutMetrics(
116
+ viewport_x=result.get("viewport_x", 0),
117
+ viewport_y=result.get("viewport_y", 0),
118
+ viewport_width=result.get("viewport_width", 0),
119
+ viewport_height=result.get("viewport_height", 0),
120
+ content_width=result.get("content_width", 0),
121
+ content_height=result.get("content_height", 0),
122
+ device_scale_factor=result.get("device_scale_factor", 1.0),
123
+ )
124
+
125
+ async def screenshot_png(self) -> bytes:
126
+ """Capture viewport screenshot as PNG bytes."""
127
+ return await self._page.screenshot(type="png")
128
+
129
+ async def mouse_move(self, x: float, y: float) -> None:
130
+ """Move mouse to viewport coordinates."""
131
+ await self._page.mouse.move(x, y)
132
+
133
+ async def mouse_click(
134
+ self,
135
+ x: float,
136
+ y: float,
137
+ button: Literal["left", "right", "middle"] = "left",
138
+ click_count: int = 1,
139
+ ) -> None:
140
+ """Click at viewport coordinates."""
141
+ await self._page.mouse.click(x, y, button=button, click_count=click_count)
142
+
143
+ async def wheel(
144
+ self,
145
+ delta_y: float,
146
+ x: float | None = None,
147
+ y: float | None = None,
148
+ ) -> None:
149
+ """Scroll using mouse wheel."""
150
+ # Get viewport center if coordinates not provided
151
+ if x is None or y is None:
152
+ if self._cached_viewport is None:
153
+ await self.refresh_page_info()
154
+ assert self._cached_viewport is not None
155
+ x = x if x is not None else self._cached_viewport.width / 2
156
+ y = y if y is not None else self._cached_viewport.height / 2
157
+
158
+ await self._page.mouse.wheel(0, delta_y)
159
+
160
+ async def type_text(self, text: str) -> None:
161
+ """Type text using keyboard input."""
162
+ await self._page.keyboard.type(text)
163
+
164
+ async def wait_ready_state(
165
+ self,
166
+ state: Literal["interactive", "complete"] = "interactive",
167
+ timeout_ms: int = 15000,
168
+ ) -> None:
169
+ """Wait for document.readyState to reach target state."""
170
+ acceptable_states = {"complete"} if state == "complete" else {"interactive", "complete"}
171
+
172
+ start = time.monotonic()
173
+ timeout_sec = timeout_ms / 1000.0
174
+
175
+ while True:
176
+ elapsed = time.monotonic() - start
177
+ if elapsed >= timeout_sec:
178
+ raise TimeoutError(
179
+ f"Timed out waiting for document.readyState='{state}' " f"after {timeout_ms}ms"
180
+ )
181
+
182
+ current_state = await self._page.evaluate("document.readyState")
183
+ if current_state in acceptable_states:
184
+ return
185
+
186
+ await asyncio.sleep(0.1)
187
+
188
+ async def get_url(self) -> str:
189
+ """Get current page URL."""
190
+ return self._page.url
191
+
192
+
193
+ # Verify protocol compliance at import time
194
+ assert isinstance(PlaywrightBackend.__new__(PlaywrightBackend), BrowserBackend)
@@ -0,0 +1,216 @@
1
+ """
2
+ v0 BrowserBackend Protocol - Minimal interface for browser-use integration.
3
+
4
+ This protocol defines the minimal interface required to:
5
+ - Take Sentience snapshots (DOM/geometry via extension)
6
+ - Compute viewport-coord clicks
7
+ - Scroll + re-snapshot + click
8
+ - Stabilize after action
9
+
10
+ No navigation API required (browser-use already handles navigation).
11
+
12
+ Design principle: Keep it so small that nothing can break.
13
+ """
14
+
15
+ from typing import Any, Literal, Protocol, runtime_checkable
16
+
17
+ from pydantic import BaseModel
18
+
19
+
20
+ class ViewportInfo(BaseModel):
21
+ """Viewport and scroll position information."""
22
+
23
+ width: int
24
+ height: int
25
+ scroll_x: float = 0.0
26
+ scroll_y: float = 0.0
27
+ content_width: float | None = None
28
+ content_height: float | None = None
29
+
30
+
31
+ class LayoutMetrics(BaseModel):
32
+ """Page layout metrics from CDP Page.getLayoutMetrics."""
33
+
34
+ # Viewport dimensions
35
+ viewport_x: float = 0.0
36
+ viewport_y: float = 0.0
37
+ viewport_width: float = 0.0
38
+ viewport_height: float = 0.0
39
+
40
+ # Content dimensions (scrollable area)
41
+ content_width: float = 0.0
42
+ content_height: float = 0.0
43
+
44
+ # Device scale factor
45
+ device_scale_factor: float = 1.0
46
+
47
+
48
+ @runtime_checkable
49
+ class BrowserBackend(Protocol):
50
+ """
51
+ Minimal backend protocol for v0 proof-of-concept.
52
+
53
+ This is enough to:
54
+ - Take Sentience snapshots (DOM/geometry via extension)
55
+ - Execute JavaScript for element interaction
56
+ - Perform mouse operations (move, click, scroll)
57
+ - Wait for page stability
58
+
59
+ Implementers:
60
+ - CDPBackendV0: For browser-use integration via CDP
61
+ - PlaywrightBackend: Wrapper around existing SentienceBrowser (future)
62
+ """
63
+
64
+ async def refresh_page_info(self) -> ViewportInfo:
65
+ """
66
+ Cache viewport + scroll offsets + url; cheap & safe to call often.
67
+
68
+ Returns:
69
+ ViewportInfo with current viewport state
70
+ """
71
+ ...
72
+
73
+ async def eval(self, expression: str) -> Any:
74
+ """
75
+ Evaluate JavaScript expression in page context.
76
+
77
+ Uses CDP Runtime.evaluate with returnByValue=True.
78
+
79
+ Args:
80
+ expression: JavaScript expression to evaluate
81
+
82
+ Returns:
83
+ Result value (JSON-serializable)
84
+ """
85
+ ...
86
+
87
+ async def call(
88
+ self,
89
+ function_declaration: str,
90
+ args: list[Any] | None = None,
91
+ ) -> Any:
92
+ """
93
+ Call a JavaScript function with arguments.
94
+
95
+ Uses CDP Runtime.callFunctionOn for safe argument passing.
96
+ Safer than eval() for passing complex arguments.
97
+
98
+ Args:
99
+ function_declaration: JavaScript function body, e.g., "(x, y) => x + y"
100
+ args: Arguments to pass to the function
101
+
102
+ Returns:
103
+ Result value (JSON-serializable)
104
+ """
105
+ ...
106
+
107
+ async def get_layout_metrics(self) -> LayoutMetrics:
108
+ """
109
+ Get page layout metrics.
110
+
111
+ Uses CDP Page.getLayoutMetrics to get viewport and content dimensions.
112
+
113
+ Returns:
114
+ LayoutMetrics with viewport and content size info
115
+ """
116
+ ...
117
+
118
+ async def screenshot_png(self) -> bytes:
119
+ """
120
+ Capture viewport screenshot as PNG bytes.
121
+
122
+ Uses CDP Page.captureScreenshot.
123
+
124
+ Returns:
125
+ PNG image bytes
126
+ """
127
+ ...
128
+
129
+ async def mouse_move(self, x: float, y: float) -> None:
130
+ """
131
+ Move mouse to viewport coordinates.
132
+
133
+ Uses CDP Input.dispatchMouseEvent with type="mouseMoved".
134
+
135
+ Args:
136
+ x: X coordinate in viewport
137
+ y: Y coordinate in viewport
138
+ """
139
+ ...
140
+
141
+ async def mouse_click(
142
+ self,
143
+ x: float,
144
+ y: float,
145
+ button: Literal["left", "right", "middle"] = "left",
146
+ click_count: int = 1,
147
+ ) -> None:
148
+ """
149
+ Click at viewport coordinates.
150
+
151
+ Uses CDP Input.dispatchMouseEvent with mousePressed + mouseReleased.
152
+
153
+ Args:
154
+ x: X coordinate in viewport
155
+ y: Y coordinate in viewport
156
+ button: Mouse button to click
157
+ click_count: Number of clicks (1 for single, 2 for double)
158
+ """
159
+ ...
160
+
161
+ async def wheel(
162
+ self,
163
+ delta_y: float,
164
+ x: float | None = None,
165
+ y: float | None = None,
166
+ ) -> None:
167
+ """
168
+ Scroll using mouse wheel.
169
+
170
+ Uses CDP Input.dispatchMouseEvent with type="mouseWheel".
171
+
172
+ Args:
173
+ delta_y: Scroll amount (positive = down, negative = up)
174
+ x: X coordinate for scroll (default: viewport center)
175
+ y: Y coordinate for scroll (default: viewport center)
176
+ """
177
+ ...
178
+
179
+ async def type_text(self, text: str) -> None:
180
+ """
181
+ Type text using keyboard input.
182
+
183
+ Uses CDP Input.dispatchKeyEvent for each character.
184
+
185
+ Args:
186
+ text: Text to type
187
+ """
188
+ ...
189
+
190
+ async def wait_ready_state(
191
+ self,
192
+ state: Literal["interactive", "complete"] = "interactive",
193
+ timeout_ms: int = 15000,
194
+ ) -> None:
195
+ """
196
+ Wait for document.readyState to reach target state.
197
+
198
+ Uses polling instead of CDP events (no leak from unregistered listeners).
199
+
200
+ Args:
201
+ state: Target state ("interactive" or "complete")
202
+ timeout_ms: Maximum time to wait in milliseconds
203
+
204
+ Raises:
205
+ TimeoutError: If state not reached within timeout
206
+ """
207
+ ...
208
+
209
+ async def get_url(self) -> str:
210
+ """
211
+ Get current page URL.
212
+
213
+ Returns:
214
+ Current page URL (window.location.href)
215
+ """
216
+ ...