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.90.7"
version = "0.90.8"
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.90.7"
__version__ = "0.90.8"

__all__ = [
# Core SDK
Expand Down
13 changes: 13 additions & 0 deletions sentience/cloud_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ def close(
# Close file first
self._trace_file.close()

# Generate index after closing file
self._generate_index()

if not blocking:
# Fire-and-forget background upload
thread = threading.Thread(
Expand Down Expand Up @@ -231,6 +234,16 @@ def _do_upload(self, on_progress: Callable[[int, int], None] | None = None) -> N
print(f" Local trace preserved at: {self._path}")
# Don't raise - preserve trace locally even if upload fails

def _generate_index(self) -> None:
"""Generate trace index file (automatic on close)."""
try:
from .trace_indexing import write_trace_index

write_trace_index(str(self._path))
except Exception as e:
# Non-fatal: log but don't crash
print(f"⚠️ Failed to generate trace index: {e}")

def _complete_trace(self) -> None:
"""
Call /v1/traces/complete to report file sizes to gateway.
Expand Down
27 changes: 27 additions & 0 deletions sentience/trace_indexing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Trace indexing module for Sentience SDK.
"""

from .indexer import build_trace_index, write_trace_index, read_step_events
from .index_schema import (
TraceIndex,
StepIndex,
TraceSummary,
TraceFileInfo,
SnapshotInfo,
ActionInfo,
StepCounters,
)

__all__ = [
"build_trace_index",
"write_trace_index",
"read_step_events",
"TraceIndex",
"StepIndex",
"TraceSummary",
"TraceFileInfo",
"SnapshotInfo",
"ActionInfo",
"StepCounters",
]
111 changes: 111 additions & 0 deletions sentience/trace_indexing/index_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""
Type definitions for trace index schema using concrete classes.
"""

from dataclasses import dataclass, field, asdict
from typing import Optional, List, Literal


@dataclass
class TraceFileInfo:
"""Metadata about the trace file."""

path: str
size_bytes: int
sha256: str

def to_dict(self) -> dict:
return asdict(self)


@dataclass
class TraceSummary:
"""High-level summary of the trace."""

first_ts: str
last_ts: str
event_count: int
step_count: int
error_count: int
final_url: Optional[str]

def to_dict(self) -> dict:
return asdict(self)


@dataclass
class SnapshotInfo:
"""Snapshot metadata for index."""

snapshot_id: Optional[str] = None
digest: Optional[str] = None
url: Optional[str] = None

def to_dict(self) -> dict:
return asdict(self)


@dataclass
class ActionInfo:
"""Action metadata for index."""

type: Optional[str] = None
target_element_id: Optional[int] = None
args_digest: Optional[str] = None
success: Optional[bool] = None

def to_dict(self) -> dict:
return asdict(self)


@dataclass
class StepCounters:
"""Event counters per step."""

events: int = 0
snapshots: int = 0
actions: int = 0
llm_calls: int = 0

def to_dict(self) -> dict:
return asdict(self)


@dataclass
class StepIndex:
"""Index entry for a single step."""

step_index: int
step_id: str
goal: Optional[str]
status: Literal["ok", "error", "partial"]
ts_start: str
ts_end: str
offset_start: int
offset_end: int
url_before: Optional[str]
url_after: Optional[str]
snapshot_before: SnapshotInfo
snapshot_after: SnapshotInfo
action: ActionInfo
counters: StepCounters

def to_dict(self) -> dict:
result = asdict(self)
return result


@dataclass
class TraceIndex:
"""Complete trace index schema."""

version: int
run_id: str
created_at: str
trace_file: TraceFileInfo
summary: TraceSummary
steps: List[StepIndex] = field(default_factory=list)

def to_dict(self) -> dict:
"""Convert to dictionary for JSON serialization."""
return asdict(self)
Loading