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.
- sentience/__init__.py +253 -0
- sentience/_extension_loader.py +195 -0
- sentience/action_executor.py +215 -0
- sentience/actions.py +1020 -0
- sentience/agent.py +1181 -0
- sentience/agent_config.py +46 -0
- sentience/agent_runtime.py +424 -0
- sentience/asserts/__init__.py +70 -0
- sentience/asserts/expect.py +621 -0
- sentience/asserts/query.py +383 -0
- sentience/async_api.py +108 -0
- sentience/backends/__init__.py +137 -0
- sentience/backends/actions.py +343 -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 +427 -0
- sentience/base_agent.py +196 -0
- sentience/browser.py +1215 -0
- sentience/browser_evaluator.py +299 -0
- sentience/canonicalization.py +207 -0
- sentience/cli.py +130 -0
- sentience/cloud_tracing.py +807 -0
- sentience/constants.py +6 -0
- sentience/conversational_agent.py +543 -0
- sentience/element_filter.py +136 -0
- sentience/expect.py +188 -0
- sentience/extension/background.js +104 -0
- sentience/extension/content.js +161 -0
- sentience/extension/injected_api.js +914 -0
- sentience/extension/manifest.json +36 -0
- sentience/extension/pkg/sentience_core.d.ts +51 -0
- sentience/extension/pkg/sentience_core.js +323 -0
- sentience/extension/pkg/sentience_core_bg.wasm +0 -0
- sentience/extension/pkg/sentience_core_bg.wasm.d.ts +10 -0
- sentience/extension/release.json +115 -0
- sentience/formatting.py +15 -0
- sentience/generator.py +202 -0
- sentience/inspector.py +367 -0
- sentience/llm_interaction_handler.py +191 -0
- sentience/llm_provider.py +875 -0
- sentience/llm_provider_utils.py +120 -0
- sentience/llm_response_builder.py +153 -0
- sentience/models.py +846 -0
- sentience/ordinal.py +280 -0
- sentience/overlay.py +222 -0
- sentience/protocols.py +228 -0
- sentience/query.py +303 -0
- sentience/read.py +188 -0
- sentience/recorder.py +589 -0
- sentience/schemas/trace_v1.json +335 -0
- sentience/screenshot.py +100 -0
- sentience/sentience_methods.py +86 -0
- sentience/snapshot.py +706 -0
- sentience/snapshot_diff.py +126 -0
- sentience/text_search.py +262 -0
- sentience/trace_event_builder.py +148 -0
- sentience/trace_file_manager.py +197 -0
- sentience/trace_indexing/__init__.py +27 -0
- sentience/trace_indexing/index_schema.py +199 -0
- sentience/trace_indexing/indexer.py +414 -0
- sentience/tracer_factory.py +322 -0
- sentience/tracing.py +449 -0
- sentience/utils/__init__.py +40 -0
- sentience/utils/browser.py +46 -0
- sentience/utils/element.py +257 -0
- sentience/utils/formatting.py +59 -0
- sentience/utils.py +296 -0
- sentience/verification.py +380 -0
- sentience/visual_agent.py +2058 -0
- sentience/wait.py +139 -0
- sentienceapi-0.95.0.dist-info/METADATA +984 -0
- sentienceapi-0.95.0.dist-info/RECORD +82 -0
- sentienceapi-0.95.0.dist-info/WHEEL +5 -0
- sentienceapi-0.95.0.dist-info/entry_points.txt +2 -0
- sentienceapi-0.95.0.dist-info/licenses/LICENSE +24 -0
- sentienceapi-0.95.0.dist-info/licenses/LICENSE-APACHE +201 -0
- sentienceapi-0.95.0.dist-info/licenses/LICENSE-MIT +21 -0
- sentienceapi-0.95.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trace file management utilities for consistent file operations.
|
|
3
|
+
|
|
4
|
+
This module provides helper functions for common trace file operations
|
|
5
|
+
shared between JsonlTraceSink and CloudTraceSink.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
from .models import TraceStats
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TraceFileManager:
|
|
17
|
+
"""
|
|
18
|
+
Helper for common trace file operations.
|
|
19
|
+
|
|
20
|
+
Provides static methods for file operations shared across trace sinks.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def write_event(file_handle: Any, event: dict[str, Any]) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Write a trace event to a file handle as JSONL.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
file_handle: Open file handle (must be writable)
|
|
30
|
+
event: Event dictionary to write
|
|
31
|
+
"""
|
|
32
|
+
json_str = json.dumps(event, ensure_ascii=False)
|
|
33
|
+
file_handle.write(json_str + "\n")
|
|
34
|
+
file_handle.flush() # Ensure written to disk
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def ensure_directory(path: Path) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Ensure the parent directory of a path exists.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
path: File path whose parent directory should exist
|
|
43
|
+
"""
|
|
44
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def read_events(path: Path) -> list[dict[str, Any]]:
|
|
48
|
+
"""
|
|
49
|
+
Read all events from a JSONL trace file.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
path: Path to JSONL trace file
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
List of event dictionaries
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
FileNotFoundError: If file doesn't exist
|
|
59
|
+
json.JSONDecodeError: If file contains invalid JSON
|
|
60
|
+
"""
|
|
61
|
+
events = []
|
|
62
|
+
with open(path, encoding="utf-8") as f:
|
|
63
|
+
for line in f:
|
|
64
|
+
line = line.strip()
|
|
65
|
+
if not line:
|
|
66
|
+
continue
|
|
67
|
+
try:
|
|
68
|
+
event = json.loads(line)
|
|
69
|
+
events.append(event)
|
|
70
|
+
except json.JSONDecodeError:
|
|
71
|
+
# Skip invalid lines but continue reading
|
|
72
|
+
continue
|
|
73
|
+
return events
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def extract_stats(
|
|
77
|
+
events: list[dict[str, Any]],
|
|
78
|
+
infer_status_func: None | (
|
|
79
|
+
Callable[[list[dict[str, Any]], dict[str, Any] | None], str]
|
|
80
|
+
) = None,
|
|
81
|
+
) -> TraceStats:
|
|
82
|
+
"""
|
|
83
|
+
Extract execution statistics from trace events.
|
|
84
|
+
|
|
85
|
+
This is a common operation shared between JsonlTraceSink and CloudTraceSink.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
events: List of trace event dictionaries
|
|
89
|
+
infer_status_func: Optional function to infer final_status from events.
|
|
90
|
+
If None, uses default inference logic.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
TraceStats with execution statistics
|
|
94
|
+
"""
|
|
95
|
+
if not events:
|
|
96
|
+
return TraceStats(
|
|
97
|
+
total_steps=0,
|
|
98
|
+
total_events=0,
|
|
99
|
+
duration_ms=None,
|
|
100
|
+
final_status="unknown",
|
|
101
|
+
started_at=None,
|
|
102
|
+
ended_at=None,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Find run_start and run_end events
|
|
106
|
+
run_start = next((e for e in events if e.get("type") == "run_start"), None)
|
|
107
|
+
run_end = next((e for e in events if e.get("type") == "run_end"), None)
|
|
108
|
+
|
|
109
|
+
# Extract timestamps
|
|
110
|
+
started_at: str | None = None
|
|
111
|
+
ended_at: str | None = None
|
|
112
|
+
if run_start:
|
|
113
|
+
started_at = run_start.get("ts")
|
|
114
|
+
if run_end:
|
|
115
|
+
ended_at = run_end.get("ts")
|
|
116
|
+
|
|
117
|
+
# Calculate duration
|
|
118
|
+
duration_ms: int | None = None
|
|
119
|
+
if started_at and ended_at:
|
|
120
|
+
try:
|
|
121
|
+
from datetime import datetime
|
|
122
|
+
|
|
123
|
+
start_dt = datetime.fromisoformat(started_at.replace("Z", "+00:00"))
|
|
124
|
+
end_dt = datetime.fromisoformat(ended_at.replace("Z", "+00:00"))
|
|
125
|
+
delta = end_dt - start_dt
|
|
126
|
+
duration_ms = int(delta.total_seconds() * 1000)
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
# Count steps (from step_start events, only first attempt)
|
|
131
|
+
step_indices = set()
|
|
132
|
+
for event in events:
|
|
133
|
+
if event.get("type") == "step_start":
|
|
134
|
+
step_index = event.get("data", {}).get("step_index")
|
|
135
|
+
if step_index is not None:
|
|
136
|
+
step_indices.add(step_index)
|
|
137
|
+
total_steps = len(step_indices) if step_indices else 0
|
|
138
|
+
|
|
139
|
+
# If run_end has steps count, use that (more accurate)
|
|
140
|
+
if run_end:
|
|
141
|
+
steps_from_end = run_end.get("data", {}).get("steps")
|
|
142
|
+
if steps_from_end is not None:
|
|
143
|
+
total_steps = max(total_steps, steps_from_end)
|
|
144
|
+
|
|
145
|
+
# Count total events
|
|
146
|
+
total_events = len(events)
|
|
147
|
+
|
|
148
|
+
# Infer final status
|
|
149
|
+
if infer_status_func:
|
|
150
|
+
final_status = infer_status_func(events, run_end)
|
|
151
|
+
else:
|
|
152
|
+
final_status = TraceFileManager._infer_final_status(events, run_end)
|
|
153
|
+
|
|
154
|
+
return TraceStats(
|
|
155
|
+
total_steps=total_steps,
|
|
156
|
+
total_events=total_events,
|
|
157
|
+
duration_ms=duration_ms,
|
|
158
|
+
final_status=final_status,
|
|
159
|
+
started_at=started_at,
|
|
160
|
+
ended_at=ended_at,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
def _infer_final_status(
|
|
165
|
+
events: list[dict[str, Any]],
|
|
166
|
+
run_end: dict[str, Any] | None,
|
|
167
|
+
) -> str:
|
|
168
|
+
"""
|
|
169
|
+
Infer final status from trace events.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
events: List of trace event dictionaries
|
|
173
|
+
run_end: Optional run_end event dictionary
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Final status string: "success", "failure", "partial", or "unknown"
|
|
177
|
+
"""
|
|
178
|
+
# Check for run_end event with status
|
|
179
|
+
if run_end:
|
|
180
|
+
status = run_end.get("data", {}).get("status")
|
|
181
|
+
if status in ("success", "failure", "partial", "unknown"):
|
|
182
|
+
return status
|
|
183
|
+
|
|
184
|
+
# Infer from error events
|
|
185
|
+
has_errors = any(e.get("type") == "error" for e in events)
|
|
186
|
+
if has_errors:
|
|
187
|
+
step_ends = [e for e in events if e.get("type") == "step_end"]
|
|
188
|
+
if step_ends:
|
|
189
|
+
return "partial"
|
|
190
|
+
else:
|
|
191
|
+
return "failure"
|
|
192
|
+
else:
|
|
193
|
+
step_ends = [e for e in events if e.get("type") == "step_end"]
|
|
194
|
+
if step_ends:
|
|
195
|
+
return "success"
|
|
196
|
+
else:
|
|
197
|
+
return "unknown"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trace indexing module for Sentience SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .index_schema import (
|
|
6
|
+
ActionInfo,
|
|
7
|
+
SnapshotInfo,
|
|
8
|
+
StepCounters,
|
|
9
|
+
StepIndex,
|
|
10
|
+
TraceFileInfo,
|
|
11
|
+
TraceIndex,
|
|
12
|
+
TraceSummary,
|
|
13
|
+
)
|
|
14
|
+
from .indexer import build_trace_index, read_step_events, write_trace_index
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"build_trace_index",
|
|
18
|
+
"write_trace_index",
|
|
19
|
+
"read_step_events",
|
|
20
|
+
"TraceIndex",
|
|
21
|
+
"StepIndex",
|
|
22
|
+
"TraceSummary",
|
|
23
|
+
"TraceFileInfo",
|
|
24
|
+
"SnapshotInfo",
|
|
25
|
+
"ActionInfo",
|
|
26
|
+
"StepCounters",
|
|
27
|
+
]
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type definitions for trace index schema using concrete classes.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import asdict, dataclass, field
|
|
6
|
+
from typing import List, Literal, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class TraceFileInfo:
|
|
11
|
+
"""Metadata about the trace file."""
|
|
12
|
+
|
|
13
|
+
path: str
|
|
14
|
+
size_bytes: int
|
|
15
|
+
sha256: str
|
|
16
|
+
line_count: int | None = None # Number of lines in the trace file
|
|
17
|
+
|
|
18
|
+
def to_dict(self) -> dict:
|
|
19
|
+
return asdict(self)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class TraceSummary:
|
|
24
|
+
"""High-level summary of the trace."""
|
|
25
|
+
|
|
26
|
+
first_ts: str
|
|
27
|
+
last_ts: str
|
|
28
|
+
event_count: int
|
|
29
|
+
step_count: int
|
|
30
|
+
error_count: int
|
|
31
|
+
final_url: str | None
|
|
32
|
+
status: Literal["success", "failure", "partial", "unknown"] | None = None
|
|
33
|
+
agent_name: str | None = None # Agent name from run_start event
|
|
34
|
+
duration_ms: int | None = None # Calculated duration in milliseconds
|
|
35
|
+
counters: dict[str, int] | None = (
|
|
36
|
+
None # Aggregated counters (snapshot_count, action_count, error_count)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def to_dict(self) -> dict:
|
|
40
|
+
return asdict(self)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class SnapshotInfo:
|
|
45
|
+
"""Snapshot metadata for index."""
|
|
46
|
+
|
|
47
|
+
snapshot_id: str | None = None
|
|
48
|
+
digest: str | None = None
|
|
49
|
+
url: str | None = None
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> dict:
|
|
52
|
+
return asdict(self)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class ActionInfo:
|
|
57
|
+
"""Action metadata for index."""
|
|
58
|
+
|
|
59
|
+
type: str | None = None
|
|
60
|
+
target_element_id: int | None = None
|
|
61
|
+
args_digest: str | None = None
|
|
62
|
+
success: bool | None = None
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> dict:
|
|
65
|
+
return asdict(self)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class StepCounters:
|
|
70
|
+
"""Event counters per step."""
|
|
71
|
+
|
|
72
|
+
events: int = 0
|
|
73
|
+
snapshots: int = 0
|
|
74
|
+
actions: int = 0
|
|
75
|
+
llm_calls: int = 0
|
|
76
|
+
|
|
77
|
+
def to_dict(self) -> dict:
|
|
78
|
+
return asdict(self)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class StepIndex:
|
|
83
|
+
"""Index entry for a single step."""
|
|
84
|
+
|
|
85
|
+
step_index: int
|
|
86
|
+
step_id: str
|
|
87
|
+
goal: str | None
|
|
88
|
+
status: Literal["success", "failure", "partial", "unknown"]
|
|
89
|
+
ts_start: str
|
|
90
|
+
ts_end: str
|
|
91
|
+
offset_start: int
|
|
92
|
+
offset_end: int
|
|
93
|
+
line_number: int | None = None # Line number for byte-range fetching
|
|
94
|
+
url_before: str | None = None
|
|
95
|
+
url_after: str | None = None
|
|
96
|
+
snapshot_before: SnapshotInfo = field(default_factory=SnapshotInfo)
|
|
97
|
+
snapshot_after: SnapshotInfo = field(default_factory=SnapshotInfo)
|
|
98
|
+
action: ActionInfo = field(default_factory=ActionInfo)
|
|
99
|
+
counters: StepCounters = field(default_factory=StepCounters)
|
|
100
|
+
|
|
101
|
+
def to_dict(self) -> dict:
|
|
102
|
+
result = asdict(self)
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class TraceIndex:
|
|
108
|
+
"""Complete trace index schema."""
|
|
109
|
+
|
|
110
|
+
version: int
|
|
111
|
+
run_id: str
|
|
112
|
+
created_at: str
|
|
113
|
+
trace_file: TraceFileInfo
|
|
114
|
+
summary: TraceSummary
|
|
115
|
+
steps: list[StepIndex] = field(default_factory=list)
|
|
116
|
+
|
|
117
|
+
def to_dict(self) -> dict:
|
|
118
|
+
"""Convert to dictionary for JSON serialization."""
|
|
119
|
+
return asdict(self)
|
|
120
|
+
|
|
121
|
+
def to_sentience_studio_dict(self) -> dict:
|
|
122
|
+
"""
|
|
123
|
+
Convert to SS-compatible format.
|
|
124
|
+
|
|
125
|
+
Maps SDK field names to frontend expectations:
|
|
126
|
+
- created_at -> generated_at
|
|
127
|
+
- first_ts -> start_time
|
|
128
|
+
- last_ts -> end_time
|
|
129
|
+
- step_index (0-based) -> step (1-based)
|
|
130
|
+
- ts_start -> timestamp
|
|
131
|
+
- Filters out "unknown" status
|
|
132
|
+
"""
|
|
133
|
+
from datetime import datetime
|
|
134
|
+
|
|
135
|
+
# Calculate duration if not already set
|
|
136
|
+
duration_ms = self.summary.duration_ms
|
|
137
|
+
if duration_ms is None and self.summary.first_ts and self.summary.last_ts:
|
|
138
|
+
try:
|
|
139
|
+
start = datetime.fromisoformat(self.summary.first_ts.replace("Z", "+00:00"))
|
|
140
|
+
end = datetime.fromisoformat(self.summary.last_ts.replace("Z", "+00:00"))
|
|
141
|
+
duration_ms = int((end - start).total_seconds() * 1000)
|
|
142
|
+
except (ValueError, AttributeError):
|
|
143
|
+
duration_ms = None
|
|
144
|
+
|
|
145
|
+
# Aggregate counters if not already set
|
|
146
|
+
counters = self.summary.counters
|
|
147
|
+
if counters is None:
|
|
148
|
+
snapshot_count = sum(step.counters.snapshots for step in self.steps)
|
|
149
|
+
action_count = sum(step.counters.actions for step in self.steps)
|
|
150
|
+
counters = {
|
|
151
|
+
"snapshot_count": snapshot_count,
|
|
152
|
+
"action_count": action_count,
|
|
153
|
+
"error_count": self.summary.error_count,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
"version": self.version,
|
|
158
|
+
"run_id": self.run_id,
|
|
159
|
+
"generated_at": self.created_at, # Renamed from created_at
|
|
160
|
+
"trace_file": {
|
|
161
|
+
"path": self.trace_file.path,
|
|
162
|
+
"size_bytes": self.trace_file.size_bytes,
|
|
163
|
+
"line_count": self.trace_file.line_count, # Added
|
|
164
|
+
},
|
|
165
|
+
"summary": {
|
|
166
|
+
"agent_name": self.summary.agent_name, # Added
|
|
167
|
+
"total_steps": self.summary.step_count, # Renamed from step_count
|
|
168
|
+
"status": (
|
|
169
|
+
self.summary.status if self.summary.status != "unknown" else None
|
|
170
|
+
), # Filter out unknown
|
|
171
|
+
"start_time": self.summary.first_ts, # Renamed from first_ts
|
|
172
|
+
"end_time": self.summary.last_ts, # Renamed from last_ts
|
|
173
|
+
"duration_ms": duration_ms, # Added
|
|
174
|
+
"counters": counters, # Added
|
|
175
|
+
},
|
|
176
|
+
"steps": [
|
|
177
|
+
{
|
|
178
|
+
"step": s.step_index + 1, # Convert 0-based to 1-based
|
|
179
|
+
"byte_offset": s.offset_start,
|
|
180
|
+
"line_number": s.line_number, # Added
|
|
181
|
+
"timestamp": s.ts_start, # Use start time
|
|
182
|
+
"action": {
|
|
183
|
+
"type": s.action.type or "",
|
|
184
|
+
"goal": s.goal, # Move goal into action
|
|
185
|
+
"digest": s.action.args_digest,
|
|
186
|
+
},
|
|
187
|
+
"snapshot": (
|
|
188
|
+
{
|
|
189
|
+
"url": s.snapshot_after.url,
|
|
190
|
+
"digest": s.snapshot_after.digest,
|
|
191
|
+
}
|
|
192
|
+
if s.snapshot_after.url
|
|
193
|
+
else None
|
|
194
|
+
),
|
|
195
|
+
"status": s.status if s.status != "unknown" else None, # Filter out unknown
|
|
196
|
+
}
|
|
197
|
+
for s in self.steps
|
|
198
|
+
],
|
|
199
|
+
}
|