Skip to content

Commit 47c7791

Browse files
committed
fixed
1 parent 5a37621 commit 47c7791

File tree

4 files changed

+136
-18
lines changed

4 files changed

+136
-18
lines changed

sentience/agent.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,9 @@ async def act( # noqa: C901
830830
snap_opts.goal = goal
831831

832832
# Apply AgentConfig screenshot settings if not overridden by snapshot_options
833-
if snapshot_options is None and self.config:
833+
# Only apply if snapshot_options wasn't provided OR if screenshot wasn't explicitly set
834+
# (snapshot_options.screenshot defaults to False, so we check if it's still False)
835+
if self.config and (snapshot_options is None or snap_opts.screenshot is False):
834836
if self.config.capture_screenshots:
835837
# Create ScreenshotConfig from AgentConfig
836838
snap_opts.screenshot = ScreenshotConfig(

sentience/cloud_tracing.py

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ def _do_upload(self, on_progress: Callable[[int, int], None] | None = None) -> N
181181

182182
# Step 2: Upload screenshots separately
183183
if screenshots:
184-
print(f"📸 [Sentience] Uploading {len(screenshots)} screenshots...")
185184
self._upload_screenshots(screenshots, on_progress)
186185

187186
# Step 3: Create cleaned trace file (without screenshot_base64)
@@ -505,16 +504,31 @@ def _request_screenshot_urls(self, sequences: list[int]) -> dict[int, str]:
505504
data = response.json()
506505
# Gateway returns sequences as strings in JSON, convert to int keys
507506
upload_urls = data.get("upload_urls", {})
508-
return {int(k): v for k, v in upload_urls.items()}
507+
result = {int(k): v for k, v in upload_urls.items()}
508+
if self.logger:
509+
self.logger.info(f"Received {len(result)} screenshot upload URLs")
510+
return result
509511
else:
512+
error_msg = f"Failed to get screenshot URLs: HTTP {response.status_code}"
510513
if self.logger:
511-
self.logger.warning(
512-
f"Failed to get screenshot URLs: HTTP {response.status_code}"
513-
)
514+
self.logger.warning(error_msg)
515+
else:
516+
print(f" ⚠️ {error_msg}")
517+
# Try to get error details
518+
try:
519+
error_data = response.json()
520+
error_detail = error_data.get("error") or error_data.get("message", "")
521+
if error_detail:
522+
print(f" Error: {error_detail}")
523+
except Exception:
524+
print(f" Response: {response.text[:200]}")
514525
return {}
515526
except Exception as e:
527+
error_msg = f"Error requesting screenshot URLs: {e}"
516528
if self.logger:
517-
self.logger.warning(f"Error requesting screenshot URLs: {e}")
529+
self.logger.warning(error_msg)
530+
else:
531+
print(f" ⚠️ {error_msg}")
518532
return {}
519533

520534
def _upload_screenshots(
@@ -540,11 +554,18 @@ def _upload_screenshots(
540554

541555
# 1. Request pre-signed URLs from gateway
542556
sequences = sorted(screenshots.keys())
557+
print(f" Requesting upload URLs for {len(sequences)} screenshot(s)...")
543558
upload_urls = self._request_screenshot_urls(sequences)
544559

545560
if not upload_urls:
546561
print("⚠️ [Sentience] No screenshot upload URLs received, skipping upload")
562+
print(" This may indicate:")
563+
print(" - API key doesn't have permission for screenshot uploads")
564+
print(" - Gateway endpoint /v1/screenshots/init returned an error")
565+
print(" - Network issue connecting to gateway")
547566
return
567+
568+
print(f" ✅ Received {len(upload_urls)} upload URL(s) from gateway")
548569

549570
# 2. Upload screenshots in parallel
550571
uploaded_count = 0
@@ -566,6 +587,11 @@ def upload_one(seq: int, url: str) -> bool:
566587
self.screenshot_total_size_bytes += image_size
567588

568589
# Upload to pre-signed URL
590+
# Extract the base URL for logging (without query params)
591+
upload_base_url = url.split('?')[0] if '?' in url else url
592+
if self.verbose if hasattr(self, 'verbose') else False:
593+
print(f" 📤 Uploading screenshot {seq} ({image_size / 1024:.1f} KB) to: {upload_base_url[:80]}...")
594+
569595
response = requests.put(
570596
url,
571597
data=image_bytes, # Binary image data
@@ -576,16 +602,34 @@ def upload_one(seq: int, url: str) -> bool:
576602
)
577603

578604
if response.status_code == 200:
605+
if self.logger:
606+
self.logger.info(f"Screenshot {seq} uploaded successfully ({image_size / 1024:.1f} KB)")
607+
else:
608+
# Extract base URL for logging (without query params for security)
609+
upload_base = url.split('?')[0] if '?' in url else url
610+
upload_base_short = upload_base[:80] + "..." if len(upload_base) > 80 else upload_base
611+
print(f" ✅ Screenshot {seq} uploaded: {image_size / 1024:.1f} KB, format={format_str}, URL={upload_base_short}")
579612
return True
580613
else:
614+
error_msg = f"Screenshot {seq} upload failed: HTTP {response.status_code}"
581615
if self.logger:
582-
self.logger.warning(
583-
f"Screenshot {seq} upload failed: HTTP {response.status_code}"
584-
)
616+
self.logger.warning(error_msg)
617+
else:
618+
print(f" ⚠️ {error_msg}")
619+
# Try to get error details from response
620+
try:
621+
error_detail = response.text[:200]
622+
if error_detail:
623+
print(f" Response: {error_detail}")
624+
except Exception:
625+
pass
585626
return False
586627
except Exception as e:
628+
error_msg = f"Screenshot {seq} upload error: {e}"
587629
if self.logger:
588-
self.logger.warning(f"Screenshot {seq} upload error: {e}")
630+
self.logger.warning(error_msg)
631+
else:
632+
print(f" ⚠️ {error_msg}")
589633
return False
590634

591635
# Upload in parallel (max 10 concurrent)
@@ -605,7 +649,10 @@ def upload_one(seq: int, url: str) -> bool:
605649

606650
# 3. Report results
607651
if uploaded_count == total_count:
608-
print(f"✅ [Sentience] All {total_count} screenshots uploaded successfully")
652+
total_size_mb = self.screenshot_total_size_bytes / 1024 / 1024
653+
print(f"✅ [Sentience] All {total_count} screenshots uploaded successfully!")
654+
print(f" 📊 Total screenshot size: {total_size_mb:.2f} MB")
655+
print(f" 📸 Screenshots are now available in cloud storage")
609656
else:
610657
print(f"⚠️ [Sentience] Uploaded {uploaded_count}/{total_count} screenshots")
611658
if failed_sequences:

sentience/snapshot.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,8 @@ async def _snapshot_via_extension_async(
380380
""",
381381
ext_options,
382382
)
383+
if result.get("error"):
384+
print(f" Snapshot error: {result.get('error')}")
383385

384386
# Save trace if requested
385387
if options.save_trace:
@@ -400,6 +402,16 @@ async def _snapshot_via_extension_async(
400402
raw_elements,
401403
)
402404

405+
# Extract screenshot_format from data URL if not provided by extension
406+
if result.get("screenshot") and not result.get("screenshot_format"):
407+
screenshot_data_url = result.get("screenshot", "")
408+
if screenshot_data_url.startswith("data:image/"):
409+
# Extract format from "data:image/jpeg;base64,..." or "data:image/png;base64,..."
410+
format_match = screenshot_data_url.split(";")[0].split("/")[-1]
411+
if format_match in ["jpeg", "jpg", "png"]:
412+
result["screenshot_format"] = "jpeg" if format_match in ["jpeg", "jpg"] else "png"
413+
414+
403415
# Validate and parse with Pydantic
404416
snapshot_obj = Snapshot(**result)
405417
return snapshot_obj
@@ -429,10 +441,16 @@ async def _snapshot_via_api_async(
429441
"Sentience extension failed to inject. Cannot collect raw data for API processing."
430442
) from e
431443

432-
# Step 1: Get raw data from local extension
444+
# Step 1: Get raw data from local extension (including screenshot)
433445
raw_options: dict[str, Any] = {}
446+
screenshot_requested = False
434447
if options.screenshot is not False:
435-
raw_options["screenshot"] = options.screenshot
448+
screenshot_requested = True
449+
# Serialize ScreenshotConfig to dict if it's a Pydantic model
450+
if hasattr(options.screenshot, "model_dump"):
451+
raw_options["screenshot"] = options.screenshot.model_dump()
452+
else:
453+
raw_options["screenshot"] = options.screenshot
436454

437455
raw_result = await browser.page.evaluate(
438456
"""
@@ -442,6 +460,16 @@ async def _snapshot_via_api_async(
442460
""",
443461
raw_options,
444462
)
463+
464+
# Extract screenshot from raw result (extension captures it, but API doesn't return it)
465+
screenshot_data_url = raw_result.get("screenshot")
466+
screenshot_format = None
467+
if screenshot_data_url:
468+
# Extract format from data URL
469+
if screenshot_data_url.startswith("data:image/"):
470+
format_match = screenshot_data_url.split(";")[0].split("/")[-1]
471+
if format_match in ["jpeg", "jpg", "png"]:
472+
screenshot_format = "jpeg" if format_match in ["jpeg", "jpg"] else "png"
445473

446474
# Save trace if requested
447475
if options.save_trace:
@@ -487,15 +515,22 @@ async def _snapshot_via_api_async(
487515
response.raise_for_status()
488516
api_result = response.json()
489517

518+
# Extract screenshot format from data URL if not provided
519+
if screenshot_data_url and not screenshot_format:
520+
if screenshot_data_url.startswith("data:image/"):
521+
format_match = screenshot_data_url.split(";")[0].split("/")[-1]
522+
if format_match in ["jpeg", "jpg", "png"]:
523+
screenshot_format = "jpeg" if format_match in ["jpeg", "jpg"] else "png"
524+
490525
# Merge API result with local data
491526
snapshot_data = {
492527
"status": api_result.get("status", "success"),
493528
"timestamp": api_result.get("timestamp"),
494529
"url": api_result.get("url", raw_result.get("url", "")),
495530
"viewport": api_result.get("viewport", raw_result.get("viewport")),
496531
"elements": api_result.get("elements", []),
497-
"screenshot": raw_result.get("screenshot"),
498-
"screenshot_format": raw_result.get("screenshot_format"),
532+
"screenshot": screenshot_data_url, # Use the extracted screenshot
533+
"screenshot_format": screenshot_format, # Use the extracted format
499534
"error": api_result.get("error"),
500535
}
501536

sentience/tracer_factory.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,25 @@ def create_tracer(
7474
if api_key and upload_trace:
7575
try:
7676
# Request pre-signed upload URL from backend
77+
print(f"🔗 [Sentience] Attempting to initialize cloud tracing...")
78+
print(f" API URL: {api_url}/v1/traces/init")
79+
print(f" Run ID: {run_id}")
80+
7781
response = requests.post(
7882
f"{api_url}/v1/traces/init",
7983
headers={"Authorization": f"Bearer {api_key}"},
8084
json={"run_id": run_id},
8185
timeout=10,
8286
)
8387

88+
print(f" Response Status: HTTP {response.status_code}")
89+
8490
if response.status_code == 200:
8591
data = response.json()
8692
upload_url = data.get("upload_url")
8793

8894
if upload_url:
89-
print("☁️ [Sentience] Cloud tracing enabled (Pro tier)")
95+
print("☁️ [Sentience] Cloud tracing enabled")
9096
return Tracer(
9197
run_id=run_id,
9298
sink=CloudTraceSink(
@@ -99,13 +105,41 @@ def create_tracer(
99105
)
100106
else:
101107
print("⚠️ [Sentience] Cloud init response missing upload_url")
108+
print(f" Response data: {data}")
102109
print(" Falling back to local-only tracing")
103110

104111
elif response.status_code == 403:
105-
print("⚠️ [Sentience] Cloud tracing requires Pro tier")
112+
print("⚠️ [Sentience] Cloud tracing requires Pro/Enterprise tier")
113+
print(" Your account tier may not support cloud tracing")
114+
try:
115+
error_data = response.json()
116+
error_msg = error_data.get("error") or error_data.get("message", "")
117+
if error_msg:
118+
print(f" API Error: {error_msg}")
119+
except Exception:
120+
pass
121+
print(" Falling back to local-only tracing")
122+
elif response.status_code == 401:
123+
print("⚠️ [Sentience] Cloud init failed: HTTP 401 Unauthorized")
124+
print(" API key is invalid or expired")
125+
try:
126+
error_data = response.json()
127+
error_msg = error_data.get("error") or error_data.get("message", "")
128+
if error_msg:
129+
print(f" API Error: {error_msg}")
130+
except Exception:
131+
pass
106132
print(" Falling back to local-only tracing")
107133
else:
108134
print(f"⚠️ [Sentience] Cloud init failed: HTTP {response.status_code}")
135+
try:
136+
error_data = response.json()
137+
error_msg = error_data.get("error") or error_data.get("message", "Unknown error")
138+
print(f" Error: {error_msg}")
139+
if "tier" in error_msg.lower() or "subscription" in error_msg.lower():
140+
print(f" 💡 This may be a tier/subscription issue")
141+
except Exception:
142+
print(f" Response: {response.text[:200]}")
109143
print(" Falling back to local-only tracing")
110144

111145
except requests.exceptions.Timeout:

0 commit comments

Comments
 (0)