Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "sentienceapi"
version = "0.91.0"
version = "0.91.1"
description = "Python SDK for Sentience AI Agent Browser Automation"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion sentience/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
)
from .wait import wait_for

__version__ = "0.91.0"
__version__ = "0.91.1"

__all__ = [
# Core SDK
Expand Down
42 changes: 8 additions & 34 deletions sentience/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,29 +198,16 @@ def act( # noqa: C901

# Emit snapshot trace event if tracer is enabled
if self.tracer:
# Include element data for live overlay visualization
# Use filtered_elements for overlay (only relevant elements)
elements_data = [
{
"id": el.id,
"bbox": {
"x": el.bbox.x,
"y": el.bbox.y,
"width": el.bbox.width,
"height": el.bbox.height,
},
"role": el.role,
"text": el.text[:50] if el.text else "", # Truncate for brevity
}
for el in filtered_elements[:50] # Limit to first 50 for performance
]
# Include ALL elements with full data for DOM tree display
# Use snap.elements (all elements) not filtered_elements
elements_data = [el.model_dump() for el in snap.elements]

# Build snapshot event data
snapshot_data = {
"url": snap.url,
"element_count": len(snap.elements),
"timestamp": snap.timestamp,
"elements": elements_data, # Add element data for overlay
"elements": elements_data, # Full element data for DOM tree
}

# Always include screenshot in trace event for studio viewer compatibility
Expand Down Expand Up @@ -983,29 +970,16 @@ async def act( # noqa: C901

# Emit snapshot trace event if tracer is enabled
if self.tracer:
# Include element data for live overlay visualization
# Use filtered_elements for overlay (only relevant elements)
elements_data = [
{
"id": el.id,
"bbox": {
"x": el.bbox.x,
"y": el.bbox.y,
"width": el.bbox.width,
"height": el.bbox.height,
},
"role": el.role,
"text": el.text[:50] if el.text else "", # Truncate for brevity
}
for el in filtered_elements[:50] # Limit to first 50 for performance
]
# Include ALL elements with full data for DOM tree display
# Use snap.elements (all elements) not filtered_elements
elements_data = [el.model_dump() for el in snap.elements]

# Build snapshot event data
snapshot_data = {
"url": snap.url,
"element_count": len(snap.elements),
"timestamp": snap.timestamp,
"elements": elements_data, # Add element data for overlay
"elements": elements_data, # Full element data for DOM tree
}

# Always include screenshot in trace event for studio viewer compatibility
Expand Down
29 changes: 25 additions & 4 deletions sentience/cloud_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,10 @@ def _generate_index(self) -> None:
try:
from .trace_indexing import write_trace_index

write_trace_index(str(self._path))
# Use frontend format to ensure 'step' field is present (1-based)
# Frontend derives sequence from step.step - 1, so step must be valid
index_path = Path(str(self._path).replace(".jsonl", ".index.json"))
write_trace_index(str(self._path), str(index_path), frontend_format=True)
except Exception as e:
# Non-fatal: log but don't crash
print(f"⚠️ Failed to generate trace index: {e}")
Expand Down Expand Up @@ -322,10 +325,28 @@ def _upload_index(self) -> None:
self.logger.warning("No upload URL in index upload response")
return

# Read and compress index file
with open(index_path, "rb") as f:
index_data = f.read()
# Read index file and update trace_file.path to cloud storage path
with open(index_path, encoding="utf-8") as f:
index_json = json.load(f)

# Extract cloud storage path from trace upload URL
# upload_url format: https://...digitaloceanspaces.com/traces/{run_id}.jsonl.gz
# Extract path: traces/{run_id}.jsonl.gz
try:
from urllib.parse import urlparse

parsed_url = urlparse(self.upload_url)
# Extract path after domain (e.g., /traces/run-123.jsonl.gz -> traces/run-123.jsonl.gz)
cloud_trace_path = parsed_url.path.lstrip("/")
# Update trace_file.path in index
if "trace_file" in index_json and isinstance(index_json["trace_file"], dict):
index_json["trace_file"]["path"] = cloud_trace_path
except Exception as e:
if self.logger:
self.logger.warning(f"Failed to extract cloud path from upload URL: {e}")

# Serialize updated index to JSON
index_data = json.dumps(index_json, indent=2).encode("utf-8")
compressed_index = gzip.compress(index_data)
index_size = len(compressed_index)
self.index_file_size_bytes = index_size # Track index file size
Expand Down
17 changes: 15 additions & 2 deletions sentience/trace_indexing/indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,26 @@ def _compute_snapshot_digest(snapshot_data: dict[str, Any]) -> str:
# Canonicalize elements
canonical_elements = []
for elem in elements:
# Extract is_primary and is_clickable from visual_cues if present
visual_cues = elem.get("visual_cues", {})
is_primary = (
visual_cues.get("is_primary", False)
if isinstance(visual_cues, dict)
else elem.get("is_primary", False)
)
is_clickable = (
visual_cues.get("is_clickable", False)
if isinstance(visual_cues, dict)
else elem.get("is_clickable", False)
)

canonical_elem = {
"id": elem.get("id"),
"role": elem.get("role", ""),
"text_norm": _normalize_text(elem.get("text")),
"bbox": _round_bbox(elem.get("bbox", {"x": 0, "y": 0, "width": 0, "height": 0})),
"is_primary": elem.get("is_primary", False),
"is_clickable": elem.get("is_clickable", False),
"is_primary": is_primary,
"is_clickable": is_clickable,
}
canonical_elements.append(canonical_elem)

Expand Down
5 changes: 4 additions & 1 deletion sentience/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,10 @@ def _generate_index(self) -> None:
try:
from .trace_indexing import write_trace_index

write_trace_index(str(self.path))
# Use frontend format to ensure 'step' field is present (1-based)
# Frontend derives sequence from step.step - 1, so step must be valid
index_path = Path(self.path).with_suffix(".index.json")
write_trace_index(str(self.path), str(index_path), frontend_format=True)
except Exception as e:
# Non-fatal: log but don't crash
print(f"⚠️ Failed to generate trace index: {e}")
Expand Down