Skip to content

Commit d5fb951

Browse files
authored
Merge pull request #74 from SentienceAPI/report_size
file size reporting
2 parents c08fba8 + 663e345 commit d5fb951

File tree

15 files changed

+450
-134
lines changed

15 files changed

+450
-134
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "sentienceapi"
7-
version = "0.90.3"
7+
version = "0.90.5"
88
description = "Python SDK for Sentience AI Agent Browser Automation"
99
readme = "README.md"
1010
requires-python = ">=3.11"

sentience/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .browser import SentienceBrowser
1212

1313
# Tracing (v0.12.0+)
14-
from .cloud_tracing import CloudTraceSink
14+
from .cloud_tracing import CloudTraceSink, SentienceLogger
1515
from .conversational_agent import ConversationalAgent
1616
from .expect import expect
1717

@@ -64,7 +64,7 @@
6464
)
6565
from .wait import wait_for
6666

67-
__version__ = "0.90.3"
67+
__version__ = "0.90.5"
6868

6969
__all__ = [
7070
# Core SDK
@@ -123,6 +123,7 @@
123123
"TraceSink",
124124
"JsonlTraceSink",
125125
"CloudTraceSink",
126+
"SentienceLogger",
126127
"TraceEvent",
127128
"create_tracer",
128129
"SENTIENCE_API_URL",

sentience/cloud_tracing.py

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,29 @@
1010
import threading
1111
from collections.abc import Callable
1212
from pathlib import Path
13-
from typing import Any
13+
from typing import Any, Protocol
1414

1515
import requests
1616

1717
from sentience.tracing import TraceSink
1818

1919

20+
class SentienceLogger(Protocol):
21+
"""Protocol for optional logger interface."""
22+
23+
def info(self, message: str) -> None:
24+
"""Log info message."""
25+
...
26+
27+
def warning(self, message: str) -> None:
28+
"""Log warning message."""
29+
...
30+
31+
def error(self, message: str) -> None:
32+
"""Log error message."""
33+
...
34+
35+
2036
class CloudTraceSink(TraceSink):
2137
"""
2238
Enterprise Cloud Sink: "Local Write, Batch Upload" pattern.
@@ -51,17 +67,30 @@ class CloudTraceSink(TraceSink):
5167
>>> tracer.close(blocking=False) # Returns immediately
5268
"""
5369

54-
def __init__(self, upload_url: str, run_id: str):
70+
def __init__(
71+
self,
72+
upload_url: str,
73+
run_id: str,
74+
api_key: str | None = None,
75+
api_url: str | None = None,
76+
logger: SentienceLogger | None = None,
77+
):
5578
"""
5679
Initialize cloud trace sink.
5780
5881
Args:
5982
upload_url: Pre-signed PUT URL from Sentience API
6083
(e.g., "https://sentience.nyc3.digitaloceanspaces.com/...")
6184
run_id: Unique identifier for this agent run (used for persistent cache)
85+
api_key: Sentience API key for calling /v1/traces/complete
86+
api_url: Sentience API base URL (default: https://api.sentienceapi.com)
87+
logger: Optional logger instance for logging file sizes and errors
6288
"""
6389
self.upload_url = upload_url
6490
self.run_id = run_id
91+
self.api_key = api_key
92+
self.api_url = api_url or "https://api.sentienceapi.com"
93+
self.logger = logger
6594

6695
# Use persistent cache directory instead of temp file
6796
# This ensures traces survive process crashes
@@ -74,6 +103,10 @@ def __init__(self, upload_url: str, run_id: str):
74103
self._closed = False
75104
self._upload_successful = False
76105

106+
# File size tracking (NEW)
107+
self.trace_file_size_bytes = 0
108+
self.screenshot_total_size_bytes = 0
109+
77110
def emit(self, event: dict[str, Any]) -> None:
78111
"""
79112
Write event to local persistent file (Fast, non-blocking).
@@ -140,6 +173,18 @@ def _do_upload(self, on_progress: Callable[[int, int], None] | None = None) -> N
140173
compressed_data = gzip.compress(trace_data)
141174
compressed_size = len(compressed_data)
142175

176+
# Measure trace file size (NEW)
177+
self.trace_file_size_bytes = compressed_size
178+
179+
# Log file sizes if logger is provided (NEW)
180+
if self.logger:
181+
self.logger.info(
182+
f"Trace file size: {self.trace_file_size_bytes / 1024 / 1024:.2f} MB"
183+
)
184+
self.logger.info(
185+
f"Screenshot total: {self.screenshot_total_size_bytes / 1024 / 1024:.2f} MB"
186+
)
187+
143188
# Report progress: start
144189
if on_progress:
145190
on_progress(0, compressed_size)
@@ -165,6 +210,9 @@ def _do_upload(self, on_progress: Callable[[int, int], None] | None = None) -> N
165210
if on_progress:
166211
on_progress(compressed_size, compressed_size)
167212

213+
# Call /v1/traces/complete to report file sizes (NEW)
214+
self._complete_trace()
215+
168216
# Delete file only on successful upload
169217
if os.path.exists(self._path):
170218
try:
@@ -183,6 +231,44 @@ def _do_upload(self, on_progress: Callable[[int, int], None] | None = None) -> N
183231
print(f" Local trace preserved at: {self._path}")
184232
# Don't raise - preserve trace locally even if upload fails
185233

234+
def _complete_trace(self) -> None:
235+
"""
236+
Call /v1/traces/complete to report file sizes to gateway.
237+
238+
This is a best-effort call - failures are logged but don't affect upload success.
239+
"""
240+
if not self.api_key:
241+
# No API key - skip complete call
242+
return
243+
244+
try:
245+
response = requests.post(
246+
f"{self.api_url}/v1/traces/complete",
247+
headers={"Authorization": f"Bearer {self.api_key}"},
248+
json={
249+
"run_id": self.run_id,
250+
"stats": {
251+
"trace_file_size_bytes": self.trace_file_size_bytes,
252+
"screenshot_total_size_bytes": self.screenshot_total_size_bytes,
253+
},
254+
},
255+
timeout=10,
256+
)
257+
258+
if response.status_code == 200:
259+
if self.logger:
260+
self.logger.info("Trace completion reported to gateway")
261+
else:
262+
if self.logger:
263+
self.logger.warning(
264+
f"Failed to report trace completion: HTTP {response.status_code}"
265+
)
266+
267+
except Exception as e:
268+
# Best-effort - log but don't fail
269+
if self.logger:
270+
self.logger.warning(f"Error reporting trace completion: {e}")
271+
186272
def __enter__(self):
187273
"""Context manager support."""
188274
return self

sentience/extension/background.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,13 @@ async function handleScreenshotCapture(_tabId, options = {}) {
144144
async function handleSnapshotProcessing(rawData, options = {}) {
145145
const MAX_ELEMENTS = 10000; // Safety limit to prevent hangs
146146
const startTime = performance.now();
147-
147+
148148
try {
149149
// Safety check: limit element count to prevent hangs
150150
if (!Array.isArray(rawData)) {
151151
throw new Error('rawData must be an array');
152152
}
153-
153+
154154
if (rawData.length > MAX_ELEMENTS) {
155155
console.warn(`[Sentience Background] ⚠️ Large dataset: ${rawData.length} elements. Limiting to ${MAX_ELEMENTS} to prevent hangs.`);
156156
rawData = rawData.slice(0, MAX_ELEMENTS);
@@ -186,7 +186,7 @@ async function handleSnapshotProcessing(rawData, options = {}) {
186186
// Add timeout protection (18 seconds - less than content.js timeout)
187187
analyzedElements = await Promise.race([
188188
wasmPromise,
189-
new Promise((_, reject) =>
189+
new Promise((_, reject) =>
190190
setTimeout(() => reject(new Error('WASM processing timeout (>18s)')), 18000)
191191
)
192192
]);

sentience/extension/content.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ function handleSnapshotRequest(data) {
9292
if (responded) return; // Already responded via timeout
9393
responded = true;
9494
clearTimeout(timeoutId);
95-
95+
9696
const duration = performance.now() - startTime;
9797

9898
// Handle Chrome extension errors (e.g., background script crashed)

0 commit comments

Comments
 (0)