sentienceapi 0.90.9__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 (46) hide show
  1. sentience/__init__.py +153 -0
  2. sentience/actions.py +439 -0
  3. sentience/agent.py +687 -0
  4. sentience/agent_config.py +43 -0
  5. sentience/base_agent.py +101 -0
  6. sentience/browser.py +409 -0
  7. sentience/cli.py +130 -0
  8. sentience/cloud_tracing.py +292 -0
  9. sentience/conversational_agent.py +509 -0
  10. sentience/expect.py +92 -0
  11. sentience/extension/background.js +233 -0
  12. sentience/extension/content.js +298 -0
  13. sentience/extension/injected_api.js +1473 -0
  14. sentience/extension/manifest.json +36 -0
  15. sentience/extension/pkg/sentience_core.d.ts +51 -0
  16. sentience/extension/pkg/sentience_core.js +529 -0
  17. sentience/extension/pkg/sentience_core_bg.wasm +0 -0
  18. sentience/extension/pkg/sentience_core_bg.wasm.d.ts +10 -0
  19. sentience/extension/release.json +115 -0
  20. sentience/extension/test-content.js +4 -0
  21. sentience/formatting.py +59 -0
  22. sentience/generator.py +202 -0
  23. sentience/inspector.py +185 -0
  24. sentience/llm_provider.py +431 -0
  25. sentience/models.py +406 -0
  26. sentience/overlay.py +115 -0
  27. sentience/query.py +303 -0
  28. sentience/read.py +96 -0
  29. sentience/recorder.py +369 -0
  30. sentience/schemas/trace_v1.json +216 -0
  31. sentience/screenshot.py +54 -0
  32. sentience/snapshot.py +282 -0
  33. sentience/text_search.py +107 -0
  34. sentience/trace_indexing/__init__.py +27 -0
  35. sentience/trace_indexing/index_schema.py +111 -0
  36. sentience/trace_indexing/indexer.py +363 -0
  37. sentience/tracer_factory.py +211 -0
  38. sentience/tracing.py +285 -0
  39. sentience/utils.py +296 -0
  40. sentience/wait.py +73 -0
  41. sentienceapi-0.90.9.dist-info/METADATA +878 -0
  42. sentienceapi-0.90.9.dist-info/RECORD +46 -0
  43. sentienceapi-0.90.9.dist-info/WHEEL +5 -0
  44. sentienceapi-0.90.9.dist-info/entry_points.txt +2 -0
  45. sentienceapi-0.90.9.dist-info/licenses/LICENSE.md +43 -0
  46. sentienceapi-0.90.9.dist-info/top_level.txt +1 -0
sentience/__init__.py ADDED
@@ -0,0 +1,153 @@
1
+ """
2
+ Sentience Python SDK - AI Agent Browser Automation
3
+ """
4
+
5
+ from .actions import click, click_rect, press, type_text
6
+ from .agent import SentienceAgent
7
+ from .agent_config import AgentConfig
8
+
9
+ # Agent Layer (Phase 1 & 2)
10
+ from .base_agent import BaseAgent
11
+ from .browser import SentienceBrowser
12
+
13
+ # Tracing (v0.12.0+)
14
+ from .cloud_tracing import CloudTraceSink, SentienceLogger
15
+ from .conversational_agent import ConversationalAgent
16
+ from .expect import expect
17
+
18
+ # Formatting (v0.12.0+)
19
+ from .formatting import format_snapshot_for_llm
20
+ from .generator import ScriptGenerator, generate
21
+ from .inspector import Inspector, inspect
22
+ from .llm_provider import (
23
+ AnthropicProvider,
24
+ LLMProvider,
25
+ LLMResponse,
26
+ LocalLLMProvider,
27
+ OpenAIProvider,
28
+ )
29
+ from .models import ( # Agent Layer Models
30
+ ActionHistory,
31
+ ActionResult,
32
+ ActionTokenUsage,
33
+ AgentActionResult,
34
+ BBox,
35
+ Cookie,
36
+ Element,
37
+ LocalStorageItem,
38
+ OriginStorage,
39
+ ScreenshotConfig,
40
+ Snapshot,
41
+ SnapshotFilter,
42
+ SnapshotOptions,
43
+ StorageState,
44
+ TextContext,
45
+ TextMatch,
46
+ TextRect,
47
+ TextRectSearchResult,
48
+ TokenStats,
49
+ Viewport,
50
+ ViewportRect,
51
+ WaitResult,
52
+ )
53
+ from .overlay import clear_overlay, show_overlay
54
+ from .query import find, query
55
+ from .read import read
56
+ from .recorder import Recorder, Trace, TraceStep, record
57
+ from .screenshot import screenshot
58
+ from .snapshot import snapshot
59
+ from .text_search import find_text_rect
60
+ from .tracer_factory import SENTIENCE_API_URL, create_tracer
61
+ from .tracing import JsonlTraceSink, TraceEvent, Tracer, TraceSink
62
+
63
+ # Utilities (v0.12.0+)
64
+ from .utils import (
65
+ canonical_snapshot_loose,
66
+ canonical_snapshot_strict,
67
+ compute_snapshot_digests,
68
+ save_storage_state,
69
+ sha256_digest,
70
+ )
71
+ from .wait import wait_for
72
+
73
+ __version__ = "0.90.9"
74
+
75
+ __all__ = [
76
+ # Core SDK
77
+ "SentienceBrowser",
78
+ "Snapshot",
79
+ "Element",
80
+ "BBox",
81
+ "Viewport",
82
+ "ActionResult",
83
+ "WaitResult",
84
+ "snapshot",
85
+ "query",
86
+ "find",
87
+ "click",
88
+ "type_text",
89
+ "press",
90
+ "click_rect",
91
+ "wait_for",
92
+ "expect",
93
+ "Inspector",
94
+ "inspect",
95
+ "Recorder",
96
+ "Trace",
97
+ "TraceStep",
98
+ "record",
99
+ "ScriptGenerator",
100
+ "generate",
101
+ "read",
102
+ "screenshot",
103
+ "show_overlay",
104
+ "clear_overlay",
105
+ # Text Search
106
+ "find_text_rect",
107
+ "TextRectSearchResult",
108
+ "TextMatch",
109
+ "TextRect",
110
+ "ViewportRect",
111
+ "TextContext",
112
+ # Agent Layer (Phase 1 & 2)
113
+ "BaseAgent",
114
+ "LLMProvider",
115
+ "LLMResponse",
116
+ "OpenAIProvider",
117
+ "AnthropicProvider",
118
+ "LocalLLMProvider",
119
+ "SentienceAgent",
120
+ "ConversationalAgent",
121
+ # Agent Layer Models
122
+ "AgentActionResult",
123
+ "TokenStats",
124
+ "ActionHistory",
125
+ "ActionTokenUsage",
126
+ "SnapshotOptions",
127
+ "SnapshotFilter",
128
+ "ScreenshotConfig",
129
+ # Storage State Models (Auth Injection)
130
+ "StorageState",
131
+ "Cookie",
132
+ "LocalStorageItem",
133
+ "OriginStorage",
134
+ # Tracing (v0.12.0+)
135
+ "Tracer",
136
+ "TraceSink",
137
+ "JsonlTraceSink",
138
+ "CloudTraceSink",
139
+ "SentienceLogger",
140
+ "TraceEvent",
141
+ "create_tracer",
142
+ "SENTIENCE_API_URL",
143
+ # Utilities (v0.12.0+)
144
+ "canonical_snapshot_strict",
145
+ "canonical_snapshot_loose",
146
+ "compute_snapshot_digests",
147
+ "sha256_digest",
148
+ "save_storage_state",
149
+ # Formatting (v0.12.0+)
150
+ "format_snapshot_for_llm",
151
+ # Agent Config (v0.12.0+)
152
+ "AgentConfig",
153
+ ]
sentience/actions.py ADDED
@@ -0,0 +1,439 @@
1
+ """
2
+ Actions v1 - click, type, press
3
+ """
4
+
5
+ import time
6
+
7
+ from .browser import SentienceBrowser
8
+ from .models import ActionResult, BBox, Snapshot
9
+ from .snapshot import snapshot
10
+
11
+
12
+ def click( # noqa: C901
13
+ browser: SentienceBrowser,
14
+ element_id: int,
15
+ use_mouse: bool = True,
16
+ take_snapshot: bool = False,
17
+ ) -> ActionResult:
18
+ """
19
+ Click an element by ID using hybrid approach (mouse simulation by default)
20
+
21
+ Args:
22
+ browser: SentienceBrowser instance
23
+ element_id: Element ID from snapshot
24
+ use_mouse: If True, use Playwright's mouse.click() at element center (hybrid approach).
25
+ If False, use JS-based window.sentience.click() (legacy).
26
+ take_snapshot: Whether to take snapshot after action
27
+
28
+ Returns:
29
+ ActionResult
30
+ """
31
+ if not browser.page:
32
+ raise RuntimeError("Browser not started. Call browser.start() first.")
33
+
34
+ start_time = time.time()
35
+ url_before = browser.page.url
36
+
37
+ if use_mouse:
38
+ # Hybrid approach: Get element bbox from snapshot, calculate center, use mouse.click()
39
+ try:
40
+ snap = snapshot(browser)
41
+ element = None
42
+ for el in snap.elements:
43
+ if el.id == element_id:
44
+ element = el
45
+ break
46
+
47
+ if element:
48
+ # Calculate center of element bbox
49
+ center_x = element.bbox.x + element.bbox.width / 2
50
+ center_y = element.bbox.y + element.bbox.height / 2
51
+ # Use Playwright's native mouse click for realistic simulation
52
+ try:
53
+ browser.page.mouse.click(center_x, center_y)
54
+ success = True
55
+ except Exception:
56
+ # If navigation happens, mouse.click might fail, but that's OK
57
+ # The click still happened, just check URL change
58
+ success = True
59
+ else:
60
+ # Fallback to JS click if element not found in snapshot
61
+ try:
62
+ success = browser.page.evaluate(
63
+ """
64
+ (id) => {
65
+ return window.sentience.click(id);
66
+ }
67
+ """,
68
+ element_id,
69
+ )
70
+ except Exception:
71
+ # Navigation might have destroyed context, assume success if URL changed
72
+ success = True
73
+ except Exception:
74
+ # Fallback to JS click on error
75
+ try:
76
+ success = browser.page.evaluate(
77
+ """
78
+ (id) => {
79
+ return window.sentience.click(id);
80
+ }
81
+ """,
82
+ element_id,
83
+ )
84
+ except Exception:
85
+ # Navigation might have destroyed context, assume success if URL changed
86
+ success = True
87
+ else:
88
+ # Legacy JS-based click
89
+ success = browser.page.evaluate(
90
+ """
91
+ (id) => {
92
+ return window.sentience.click(id);
93
+ }
94
+ """,
95
+ element_id,
96
+ )
97
+
98
+ # Wait a bit for navigation/DOM updates
99
+ try:
100
+ browser.page.wait_for_timeout(500)
101
+ except Exception:
102
+ # Navigation might have happened, context destroyed
103
+ pass
104
+
105
+ duration_ms = int((time.time() - start_time) * 1000)
106
+
107
+ # Check if URL changed (handle navigation gracefully)
108
+ try:
109
+ url_after = browser.page.url
110
+ url_changed = url_before != url_after
111
+ except Exception:
112
+ # Context destroyed due to navigation - assume URL changed
113
+ url_after = url_before
114
+ url_changed = True
115
+
116
+ # Determine outcome
117
+ outcome: str | None = None
118
+ if url_changed:
119
+ outcome = "navigated"
120
+ elif success:
121
+ outcome = "dom_updated"
122
+ else:
123
+ outcome = "error"
124
+
125
+ # Optional snapshot after
126
+ snapshot_after: Snapshot | None = None
127
+ if take_snapshot:
128
+ try:
129
+ snapshot_after = snapshot(browser)
130
+ except Exception:
131
+ # Navigation might have destroyed context
132
+ pass
133
+
134
+ return ActionResult(
135
+ success=success,
136
+ duration_ms=duration_ms,
137
+ outcome=outcome,
138
+ url_changed=url_changed,
139
+ snapshot_after=snapshot_after,
140
+ error=(
141
+ None
142
+ if success
143
+ else {
144
+ "code": "click_failed",
145
+ "reason": "Element not found or not clickable",
146
+ }
147
+ ),
148
+ )
149
+
150
+
151
+ def type_text(
152
+ browser: SentienceBrowser, element_id: int, text: str, take_snapshot: bool = False
153
+ ) -> ActionResult:
154
+ """
155
+ Type text into an element (focus then input)
156
+
157
+ Args:
158
+ browser: SentienceBrowser instance
159
+ element_id: Element ID from snapshot
160
+ text: Text to type
161
+ take_snapshot: Whether to take snapshot after action
162
+
163
+ Returns:
164
+ ActionResult
165
+ """
166
+ if not browser.page:
167
+ raise RuntimeError("Browser not started. Call browser.start() first.")
168
+
169
+ start_time = time.time()
170
+ url_before = browser.page.url
171
+
172
+ # Focus element first using extension registry
173
+ focused = browser.page.evaluate(
174
+ """
175
+ (id) => {
176
+ const el = window.sentience_registry[id];
177
+ if (el) {
178
+ el.focus();
179
+ return true;
180
+ }
181
+ return false;
182
+ }
183
+ """,
184
+ element_id,
185
+ )
186
+
187
+ if not focused:
188
+ return ActionResult(
189
+ success=False,
190
+ duration_ms=int((time.time() - start_time) * 1000),
191
+ outcome="error",
192
+ error={"code": "focus_failed", "reason": "Element not found"},
193
+ )
194
+
195
+ # Type using Playwright keyboard
196
+ browser.page.keyboard.type(text)
197
+
198
+ duration_ms = int((time.time() - start_time) * 1000)
199
+ url_after = browser.page.url
200
+ url_changed = url_before != url_after
201
+
202
+ outcome = "navigated" if url_changed else "dom_updated"
203
+
204
+ snapshot_after: Snapshot | None = None
205
+ if take_snapshot:
206
+ snapshot_after = snapshot(browser)
207
+
208
+ return ActionResult(
209
+ success=True,
210
+ duration_ms=duration_ms,
211
+ outcome=outcome,
212
+ url_changed=url_changed,
213
+ snapshot_after=snapshot_after,
214
+ )
215
+
216
+
217
+ def press(browser: SentienceBrowser, key: str, take_snapshot: bool = False) -> ActionResult:
218
+ """
219
+ Press a keyboard key
220
+
221
+ Args:
222
+ browser: SentienceBrowser instance
223
+ key: Key to press (e.g., "Enter", "Escape", "Tab")
224
+ take_snapshot: Whether to take snapshot after action
225
+
226
+ Returns:
227
+ ActionResult
228
+ """
229
+ if not browser.page:
230
+ raise RuntimeError("Browser not started. Call browser.start() first.")
231
+
232
+ start_time = time.time()
233
+ url_before = browser.page.url
234
+
235
+ # Press key using Playwright
236
+ browser.page.keyboard.press(key)
237
+
238
+ # Wait a bit for navigation/DOM updates
239
+ browser.page.wait_for_timeout(500)
240
+
241
+ duration_ms = int((time.time() - start_time) * 1000)
242
+ url_after = browser.page.url
243
+ url_changed = url_before != url_after
244
+
245
+ outcome = "navigated" if url_changed else "dom_updated"
246
+
247
+ snapshot_after: Snapshot | None = None
248
+ if take_snapshot:
249
+ snapshot_after = snapshot(browser)
250
+
251
+ return ActionResult(
252
+ success=True,
253
+ duration_ms=duration_ms,
254
+ outcome=outcome,
255
+ url_changed=url_changed,
256
+ snapshot_after=snapshot_after,
257
+ )
258
+
259
+
260
+ def _highlight_rect(
261
+ browser: SentienceBrowser, rect: dict[str, float], duration_sec: float = 2.0
262
+ ) -> None:
263
+ """
264
+ Highlight a rectangle with a red border overlay
265
+
266
+ Args:
267
+ browser: SentienceBrowser instance
268
+ rect: Dictionary with x, y, width (w), height (h) keys
269
+ duration_sec: How long to show the highlight (default: 2 seconds)
270
+ """
271
+ if not browser.page:
272
+ return
273
+
274
+ # Create a unique ID for this highlight
275
+ highlight_id = f"sentience_highlight_{int(time.time() * 1000)}"
276
+
277
+ # Combine all arguments into a single object for Playwright
278
+ args = {
279
+ "rect": {
280
+ "x": rect["x"],
281
+ "y": rect["y"],
282
+ "w": rect["w"],
283
+ "h": rect["h"],
284
+ },
285
+ "highlightId": highlight_id,
286
+ "durationSec": duration_sec,
287
+ }
288
+
289
+ # Inject CSS and create overlay element
290
+ browser.page.evaluate(
291
+ """
292
+ (args) => {
293
+ const { rect, highlightId, durationSec } = args;
294
+ // Create overlay div
295
+ const overlay = document.createElement('div');
296
+ overlay.id = highlightId;
297
+ overlay.style.position = 'fixed';
298
+ overlay.style.left = `${rect.x}px`;
299
+ overlay.style.top = `${rect.y}px`;
300
+ overlay.style.width = `${rect.w}px`;
301
+ overlay.style.height = `${rect.h}px`;
302
+ overlay.style.border = '3px solid red';
303
+ overlay.style.borderRadius = '2px';
304
+ overlay.style.boxSizing = 'border-box';
305
+ overlay.style.pointerEvents = 'none';
306
+ overlay.style.zIndex = '999999';
307
+ overlay.style.backgroundColor = 'rgba(255, 0, 0, 0.1)';
308
+ overlay.style.transition = 'opacity 0.3s ease-out';
309
+
310
+ document.body.appendChild(overlay);
311
+
312
+ // Remove after duration
313
+ setTimeout(() => {
314
+ overlay.style.opacity = '0';
315
+ setTimeout(() => {
316
+ if (overlay.parentNode) {
317
+ overlay.parentNode.removeChild(overlay);
318
+ }
319
+ }, 300); // Wait for fade-out transition
320
+ }, durationSec * 1000);
321
+ }
322
+ """,
323
+ args,
324
+ )
325
+
326
+
327
+ def click_rect(
328
+ browser: SentienceBrowser,
329
+ rect: dict[str, float],
330
+ highlight: bool = True,
331
+ highlight_duration: float = 2.0,
332
+ take_snapshot: bool = False,
333
+ ) -> ActionResult:
334
+ """
335
+ Click at the center of a rectangle using Playwright's native mouse simulation.
336
+ This uses a hybrid approach: calculates center coordinates and uses mouse.click()
337
+ for realistic event simulation (triggers hover, focus, mousedown, mouseup).
338
+
339
+ Args:
340
+ browser: SentienceBrowser instance
341
+ rect: Dictionary with x, y, width (w), height (h) keys, or BBox object
342
+ highlight: Whether to show a red border highlight when clicking (default: True)
343
+ highlight_duration: How long to show the highlight in seconds (default: 2.0)
344
+ take_snapshot: Whether to take snapshot after action
345
+
346
+ Returns:
347
+ ActionResult
348
+
349
+ Example:
350
+ >>> click_rect(browser, {"x": 100, "y": 200, "w": 50, "h": 30})
351
+ >>> # Or using BBox object
352
+ >>> from sentience import BBox
353
+ >>> bbox = BBox(x=100, y=200, width=50, height=30)
354
+ >>> click_rect(browser, {"x": bbox.x, "y": bbox.y, "w": bbox.width, "h": bbox.height})
355
+ """
356
+ if not browser.page:
357
+ raise RuntimeError("Browser not started. Call browser.start() first.")
358
+
359
+ # Handle BBox object or dict
360
+ if isinstance(rect, BBox):
361
+ x = rect.x
362
+ y = rect.y
363
+ w = rect.width
364
+ h = rect.height
365
+ else:
366
+ x = rect.get("x", 0)
367
+ y = rect.get("y", 0)
368
+ w = rect.get("w") or rect.get("width", 0)
369
+ h = rect.get("h") or rect.get("height", 0)
370
+
371
+ if w <= 0 or h <= 0:
372
+ return ActionResult(
373
+ success=False,
374
+ duration_ms=0,
375
+ outcome="error",
376
+ error={
377
+ "code": "invalid_rect",
378
+ "reason": "Rectangle width and height must be positive",
379
+ },
380
+ )
381
+
382
+ start_time = time.time()
383
+ url_before = browser.page.url
384
+
385
+ # Calculate center of rectangle
386
+ center_x = x + w / 2
387
+ center_y = y + h / 2
388
+
389
+ # Show highlight before clicking (if enabled)
390
+ if highlight:
391
+ _highlight_rect(browser, {"x": x, "y": y, "w": w, "h": h}, highlight_duration)
392
+ # Small delay to ensure highlight is visible
393
+ browser.page.wait_for_timeout(50)
394
+
395
+ # Use Playwright's native mouse click for realistic simulation
396
+ # This triggers hover, focus, mousedown, mouseup sequences
397
+ try:
398
+ browser.page.mouse.click(center_x, center_y)
399
+ success = True
400
+ except Exception as e:
401
+ success = False
402
+ error_msg = str(e)
403
+
404
+ # Wait a bit for navigation/DOM updates
405
+ browser.page.wait_for_timeout(500)
406
+
407
+ duration_ms = int((time.time() - start_time) * 1000)
408
+ url_after = browser.page.url
409
+ url_changed = url_before != url_after
410
+
411
+ # Determine outcome
412
+ outcome: str | None = None
413
+ if url_changed:
414
+ outcome = "navigated"
415
+ elif success:
416
+ outcome = "dom_updated"
417
+ else:
418
+ outcome = "error"
419
+
420
+ # Optional snapshot after
421
+ snapshot_after: Snapshot | None = None
422
+ if take_snapshot:
423
+ snapshot_after = snapshot(browser)
424
+
425
+ return ActionResult(
426
+ success=success,
427
+ duration_ms=duration_ms,
428
+ outcome=outcome,
429
+ url_changed=url_changed,
430
+ snapshot_after=snapshot_after,
431
+ error=(
432
+ None
433
+ if success
434
+ else {
435
+ "code": "click_failed",
436
+ "reason": error_msg if not success else "Click failed",
437
+ }
438
+ ),
439
+ )