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.
- sentience/__init__.py +107 -2
- sentience/_extension_loader.py +156 -1
- sentience/action_executor.py +2 -0
- sentience/actions.py +354 -9
- sentience/agent.py +4 -0
- sentience/agent_runtime.py +840 -0
- sentience/asserts/__init__.py +70 -0
- sentience/asserts/expect.py +621 -0
- sentience/asserts/query.py +383 -0
- sentience/async_api.py +8 -1
- sentience/backends/__init__.py +137 -0
- sentience/backends/actions.py +372 -0
- sentience/backends/browser_use_adapter.py +241 -0
- sentience/backends/cdp_backend.py +393 -0
- sentience/backends/exceptions.py +211 -0
- sentience/backends/playwright_backend.py +194 -0
- sentience/backends/protocol.py +216 -0
- sentience/backends/sentience_context.py +469 -0
- sentience/backends/snapshot.py +483 -0
- sentience/browser.py +230 -74
- sentience/canonicalization.py +207 -0
- sentience/cloud_tracing.py +65 -24
- sentience/constants.py +6 -0
- sentience/cursor_policy.py +142 -0
- sentience/extension/content.js +35 -0
- sentience/extension/injected_api.js +310 -15
- sentience/extension/manifest.json +1 -1
- sentience/extension/pkg/sentience_core.d.ts +22 -22
- sentience/extension/pkg/sentience_core.js +192 -144
- sentience/extension/pkg/sentience_core_bg.wasm +0 -0
- sentience/extension/release.json +29 -29
- sentience/failure_artifacts.py +241 -0
- sentience/integrations/__init__.py +6 -0
- sentience/integrations/langchain/__init__.py +12 -0
- sentience/integrations/langchain/context.py +18 -0
- sentience/integrations/langchain/core.py +326 -0
- sentience/integrations/langchain/tools.py +180 -0
- sentience/integrations/models.py +46 -0
- sentience/integrations/pydanticai/__init__.py +15 -0
- sentience/integrations/pydanticai/deps.py +20 -0
- sentience/integrations/pydanticai/toolset.py +468 -0
- sentience/llm_provider.py +695 -18
- sentience/models.py +536 -3
- sentience/ordinal.py +280 -0
- sentience/query.py +66 -4
- sentience/schemas/trace_v1.json +27 -1
- sentience/snapshot.py +384 -93
- sentience/snapshot_diff.py +39 -54
- sentience/text_search.py +1 -0
- sentience/trace_event_builder.py +20 -1
- sentience/trace_indexing/indexer.py +3 -49
- sentience/tracer_factory.py +1 -3
- sentience/verification.py +618 -0
- sentience/visual_agent.py +3 -1
- {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/METADATA +198 -40
- sentienceapi-0.98.0.dist-info/RECORD +92 -0
- sentience/utils.py +0 -296
- sentienceapi-0.92.2.dist-info/RECORD +0 -65
- {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/WHEEL +0 -0
- {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/entry_points.txt +0 -0
- {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/licenses/LICENSE +0 -0
- {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/licenses/LICENSE-APACHE +0 -0
- {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/licenses/LICENSE-MIT +0 -0
- {sentienceapi-0.92.2.dist-info → sentienceapi-0.98.0.dist-info}/top_level.txt +0 -0
sentience/snapshot_diff.py
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
Snapshot comparison utilities for diff_status detection.
|
|
3
3
|
|
|
4
4
|
Implements change detection logic for the Diff Overlay feature.
|
|
5
|
-
"""
|
|
6
5
|
|
|
7
|
-
from
|
|
6
|
+
Uses shared canonicalization helpers from canonicalization.py to ensure
|
|
7
|
+
consistent comparison behavior with trace_indexing/indexer.py.
|
|
8
|
+
"""
|
|
8
9
|
|
|
10
|
+
from .canonicalization import bbox_changed, content_changed
|
|
9
11
|
from .models import Element, Snapshot
|
|
10
12
|
|
|
11
13
|
|
|
@@ -18,55 +20,30 @@ class SnapshotDiff:
|
|
|
18
20
|
- REMOVED: Element existed in previous but not in current
|
|
19
21
|
- MODIFIED: Element exists in both but has changed
|
|
20
22
|
- MOVED: Element exists in both but position changed
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
@staticmethod
|
|
24
|
-
def _has_bbox_changed(el1: Element, el2: Element, threshold: float = 5.0) -> bool:
|
|
25
|
-
"""
|
|
26
|
-
Check if element's bounding box has changed significantly.
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
True if position or size changed beyond threshold
|
|
35
|
-
"""
|
|
36
|
-
return (
|
|
37
|
-
abs(el1.bbox.x - el2.bbox.x) > threshold
|
|
38
|
-
or abs(el1.bbox.y - el2.bbox.y) > threshold
|
|
39
|
-
or abs(el1.bbox.width - el2.bbox.width) > threshold
|
|
40
|
-
or abs(el1.bbox.height - el2.bbox.height) > threshold
|
|
41
|
-
)
|
|
24
|
+
Uses canonicalized comparisons (normalized text, rounded bbox) to reduce
|
|
25
|
+
noise from insignificant changes like sub-pixel rendering differences
|
|
26
|
+
or whitespace variations.
|
|
27
|
+
"""
|
|
42
28
|
|
|
43
29
|
@staticmethod
|
|
44
|
-
def
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return True
|
|
62
|
-
|
|
63
|
-
# Compare visual cues
|
|
64
|
-
if el1.visual_cues.is_primary != el2.visual_cues.is_primary:
|
|
65
|
-
return True
|
|
66
|
-
if el1.visual_cues.is_clickable != el2.visual_cues.is_clickable:
|
|
67
|
-
return True
|
|
68
|
-
|
|
69
|
-
return False
|
|
30
|
+
def _element_to_dict(el: Element) -> dict:
|
|
31
|
+
"""Convert Element model to dict for canonicalization helpers."""
|
|
32
|
+
return {
|
|
33
|
+
"id": el.id,
|
|
34
|
+
"role": el.role,
|
|
35
|
+
"text": el.text,
|
|
36
|
+
"bbox": {
|
|
37
|
+
"x": el.bbox.x,
|
|
38
|
+
"y": el.bbox.y,
|
|
39
|
+
"width": el.bbox.width,
|
|
40
|
+
"height": el.bbox.height,
|
|
41
|
+
},
|
|
42
|
+
"visual_cues": {
|
|
43
|
+
"is_primary": el.visual_cues.is_primary,
|
|
44
|
+
"is_clickable": el.visual_cues.is_clickable,
|
|
45
|
+
},
|
|
46
|
+
}
|
|
70
47
|
|
|
71
48
|
@staticmethod
|
|
72
49
|
def compute_diff_status(
|
|
@@ -76,6 +53,10 @@ class SnapshotDiff:
|
|
|
76
53
|
"""
|
|
77
54
|
Compare current snapshot with previous and set diff_status on elements.
|
|
78
55
|
|
|
56
|
+
Uses canonicalized comparisons:
|
|
57
|
+
- Text is normalized (trimmed, collapsed whitespace, lowercased)
|
|
58
|
+
- Bbox is rounded to 2px grid to ignore sub-pixel differences
|
|
59
|
+
|
|
79
60
|
Args:
|
|
80
61
|
current: Current snapshot
|
|
81
62
|
previous: Previous snapshot (None if this is the first snapshot)
|
|
@@ -110,19 +91,23 @@ class SnapshotDiff:
|
|
|
110
91
|
# Element is new - mark as ADDED
|
|
111
92
|
el_dict["diff_status"] = "ADDED"
|
|
112
93
|
else:
|
|
113
|
-
# Element existed before - check for changes
|
|
94
|
+
# Element existed before - check for changes using canonicalized comparisons
|
|
114
95
|
prev_el = previous_by_id[el.id]
|
|
115
96
|
|
|
116
|
-
|
|
117
|
-
|
|
97
|
+
# Convert to dicts for canonicalization helpers
|
|
98
|
+
el_data = SnapshotDiff._element_to_dict(el)
|
|
99
|
+
prev_el_data = SnapshotDiff._element_to_dict(prev_el)
|
|
100
|
+
|
|
101
|
+
has_bbox_changed = bbox_changed(el_data["bbox"], prev_el_data["bbox"])
|
|
102
|
+
has_content_changed = content_changed(el_data, prev_el_data)
|
|
118
103
|
|
|
119
|
-
if
|
|
104
|
+
if has_bbox_changed and has_content_changed:
|
|
120
105
|
# Both position and content changed - mark as MODIFIED
|
|
121
106
|
el_dict["diff_status"] = "MODIFIED"
|
|
122
|
-
elif
|
|
107
|
+
elif has_bbox_changed:
|
|
123
108
|
# Only position changed - mark as MOVED
|
|
124
109
|
el_dict["diff_status"] = "MOVED"
|
|
125
|
-
elif
|
|
110
|
+
elif has_content_changed:
|
|
126
111
|
# Only content changed - mark as MODIFIED
|
|
127
112
|
el_dict["diff_status"] = "MODIFIED"
|
|
128
113
|
else:
|
sentience/text_search.py
CHANGED
|
@@ -5,6 +5,7 @@ Text search utilities - find text and get pixel coordinates
|
|
|
5
5
|
from .browser import AsyncSentienceBrowser, SentienceBrowser
|
|
6
6
|
from .browser_evaluator import BrowserEvaluator
|
|
7
7
|
from .models import TextRectSearchResult
|
|
8
|
+
from .sentience_methods import SentienceMethod
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def find_text_rect(
|
sentience/trace_event_builder.py
CHANGED
|
@@ -84,6 +84,7 @@ class TraceEventBuilder:
|
|
|
84
84
|
exec_data: dict[str, Any],
|
|
85
85
|
verify_data: dict[str, Any],
|
|
86
86
|
pre_elements: list[dict[str, Any]] | None = None,
|
|
87
|
+
assertions: list[dict[str, Any]] | None = None,
|
|
87
88
|
) -> dict[str, Any]:
|
|
88
89
|
"""
|
|
89
90
|
Build step_end trace event data.
|
|
@@ -100,6 +101,7 @@ class TraceEventBuilder:
|
|
|
100
101
|
exec_data: Action execution data
|
|
101
102
|
verify_data: Verification data
|
|
102
103
|
pre_elements: Optional list of elements from pre-snapshot (with diff_status)
|
|
104
|
+
assertions: Optional list of assertion results from AgentRuntime
|
|
103
105
|
|
|
104
106
|
Returns:
|
|
105
107
|
Dictionary with step_end event data
|
|
@@ -113,6 +115,23 @@ class TraceEventBuilder:
|
|
|
113
115
|
if pre_elements is not None:
|
|
114
116
|
pre_data["elements"] = pre_elements
|
|
115
117
|
|
|
118
|
+
# Build verify data with assertions if provided
|
|
119
|
+
final_verify_data = verify_data.copy()
|
|
120
|
+
if assertions:
|
|
121
|
+
# Ensure signals dict exists
|
|
122
|
+
if "signals" not in final_verify_data:
|
|
123
|
+
final_verify_data["signals"] = {}
|
|
124
|
+
|
|
125
|
+
# Add assertions to signals
|
|
126
|
+
final_verify_data["signals"]["assertions"] = assertions
|
|
127
|
+
|
|
128
|
+
# Check for task completion (assertions marked as required that passed)
|
|
129
|
+
for a in assertions:
|
|
130
|
+
if a.get("passed") and a.get("required"):
|
|
131
|
+
final_verify_data["signals"]["task_done"] = True
|
|
132
|
+
final_verify_data["signals"]["task_done_label"] = a.get("label")
|
|
133
|
+
break
|
|
134
|
+
|
|
116
135
|
return {
|
|
117
136
|
"v": 1,
|
|
118
137
|
"step_id": step_id,
|
|
@@ -125,5 +144,5 @@ class TraceEventBuilder:
|
|
|
125
144
|
"post": {
|
|
126
145
|
"url": post_url,
|
|
127
146
|
},
|
|
128
|
-
"verify":
|
|
147
|
+
"verify": final_verify_data,
|
|
129
148
|
}
|
|
@@ -9,6 +9,7 @@ from datetime import datetime, timezone
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any, Optional
|
|
11
11
|
|
|
12
|
+
from ..canonicalization import canonicalize_element
|
|
12
13
|
from .index_schema import (
|
|
13
14
|
ActionInfo,
|
|
14
15
|
SnapshotInfo,
|
|
@@ -20,30 +21,6 @@ from .index_schema import (
|
|
|
20
21
|
)
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
def _normalize_text(text: str | None, max_len: int = 80) -> str:
|
|
24
|
-
"""Normalize text for digest: trim, collapse whitespace, lowercase, cap length."""
|
|
25
|
-
if not text:
|
|
26
|
-
return ""
|
|
27
|
-
# Trim and collapse whitespace
|
|
28
|
-
normalized = " ".join(text.split())
|
|
29
|
-
# Lowercase
|
|
30
|
-
normalized = normalized.lower()
|
|
31
|
-
# Cap length
|
|
32
|
-
if len(normalized) > max_len:
|
|
33
|
-
normalized = normalized[:max_len]
|
|
34
|
-
return normalized
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _round_bbox(bbox: dict[str, float], precision: int = 2) -> dict[str, int]:
|
|
38
|
-
"""Round bbox coordinates to reduce noise (default: 2px precision)."""
|
|
39
|
-
return {
|
|
40
|
-
"x": round(bbox.get("x", 0) / precision) * precision,
|
|
41
|
-
"y": round(bbox.get("y", 0) / precision) * precision,
|
|
42
|
-
"width": round(bbox.get("width", 0) / precision) * precision,
|
|
43
|
-
"height": round(bbox.get("height", 0) / precision) * precision,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
24
|
def _compute_snapshot_digest(snapshot_data: dict[str, Any]) -> str:
|
|
48
25
|
"""
|
|
49
26
|
Compute stable digest of snapshot for diffing.
|
|
@@ -55,31 +32,8 @@ def _compute_snapshot_digest(snapshot_data: dict[str, Any]) -> str:
|
|
|
55
32
|
viewport = snapshot_data.get("viewport", {})
|
|
56
33
|
elements = snapshot_data.get("elements", [])
|
|
57
34
|
|
|
58
|
-
# Canonicalize elements
|
|
59
|
-
canonical_elements = []
|
|
60
|
-
for elem in elements:
|
|
61
|
-
# Extract is_primary and is_clickable from visual_cues if present
|
|
62
|
-
visual_cues = elem.get("visual_cues", {})
|
|
63
|
-
is_primary = (
|
|
64
|
-
visual_cues.get("is_primary", False)
|
|
65
|
-
if isinstance(visual_cues, dict)
|
|
66
|
-
else elem.get("is_primary", False)
|
|
67
|
-
)
|
|
68
|
-
is_clickable = (
|
|
69
|
-
visual_cues.get("is_clickable", False)
|
|
70
|
-
if isinstance(visual_cues, dict)
|
|
71
|
-
else elem.get("is_clickable", False)
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
canonical_elem = {
|
|
75
|
-
"id": elem.get("id"),
|
|
76
|
-
"role": elem.get("role", ""),
|
|
77
|
-
"text_norm": _normalize_text(elem.get("text")),
|
|
78
|
-
"bbox": _round_bbox(elem.get("bbox", {"x": 0, "y": 0, "width": 0, "height": 0})),
|
|
79
|
-
"is_primary": is_primary,
|
|
80
|
-
"is_clickable": is_clickable,
|
|
81
|
-
}
|
|
82
|
-
canonical_elements.append(canonical_elem)
|
|
35
|
+
# Canonicalize elements using shared helper
|
|
36
|
+
canonical_elements = [canonicalize_element(elem) for elem in elements]
|
|
83
37
|
|
|
84
38
|
# Sort by element id for determinism
|
|
85
39
|
canonical_elements.sort(key=lambda e: e.get("id", 0))
|
sentience/tracer_factory.py
CHANGED
|
@@ -14,11 +14,9 @@ from typing import Any, Optional
|
|
|
14
14
|
import requests
|
|
15
15
|
|
|
16
16
|
from sentience.cloud_tracing import CloudTraceSink, SentienceLogger
|
|
17
|
+
from sentience.constants import SENTIENCE_API_URL
|
|
17
18
|
from sentience.tracing import JsonlTraceSink, Tracer
|
|
18
19
|
|
|
19
|
-
# Sentience API base URL (constant)
|
|
20
|
-
SENTIENCE_API_URL = "https://api.sentienceapi.com"
|
|
21
|
-
|
|
22
20
|
|
|
23
21
|
def create_tracer(
|
|
24
22
|
api_key: str | None = None,
|