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.

Files changed (82) hide show
  1. sentience/__init__.py +253 -0
  2. sentience/_extension_loader.py +195 -0
  3. sentience/action_executor.py +215 -0
  4. sentience/actions.py +1020 -0
  5. sentience/agent.py +1181 -0
  6. sentience/agent_config.py +46 -0
  7. sentience/agent_runtime.py +424 -0
  8. sentience/asserts/__init__.py +70 -0
  9. sentience/asserts/expect.py +621 -0
  10. sentience/asserts/query.py +383 -0
  11. sentience/async_api.py +108 -0
  12. sentience/backends/__init__.py +137 -0
  13. sentience/backends/actions.py +343 -0
  14. sentience/backends/browser_use_adapter.py +241 -0
  15. sentience/backends/cdp_backend.py +393 -0
  16. sentience/backends/exceptions.py +211 -0
  17. sentience/backends/playwright_backend.py +194 -0
  18. sentience/backends/protocol.py +216 -0
  19. sentience/backends/sentience_context.py +469 -0
  20. sentience/backends/snapshot.py +427 -0
  21. sentience/base_agent.py +196 -0
  22. sentience/browser.py +1215 -0
  23. sentience/browser_evaluator.py +299 -0
  24. sentience/canonicalization.py +207 -0
  25. sentience/cli.py +130 -0
  26. sentience/cloud_tracing.py +807 -0
  27. sentience/constants.py +6 -0
  28. sentience/conversational_agent.py +543 -0
  29. sentience/element_filter.py +136 -0
  30. sentience/expect.py +188 -0
  31. sentience/extension/background.js +104 -0
  32. sentience/extension/content.js +161 -0
  33. sentience/extension/injected_api.js +914 -0
  34. sentience/extension/manifest.json +36 -0
  35. sentience/extension/pkg/sentience_core.d.ts +51 -0
  36. sentience/extension/pkg/sentience_core.js +323 -0
  37. sentience/extension/pkg/sentience_core_bg.wasm +0 -0
  38. sentience/extension/pkg/sentience_core_bg.wasm.d.ts +10 -0
  39. sentience/extension/release.json +115 -0
  40. sentience/formatting.py +15 -0
  41. sentience/generator.py +202 -0
  42. sentience/inspector.py +367 -0
  43. sentience/llm_interaction_handler.py +191 -0
  44. sentience/llm_provider.py +875 -0
  45. sentience/llm_provider_utils.py +120 -0
  46. sentience/llm_response_builder.py +153 -0
  47. sentience/models.py +846 -0
  48. sentience/ordinal.py +280 -0
  49. sentience/overlay.py +222 -0
  50. sentience/protocols.py +228 -0
  51. sentience/query.py +303 -0
  52. sentience/read.py +188 -0
  53. sentience/recorder.py +589 -0
  54. sentience/schemas/trace_v1.json +335 -0
  55. sentience/screenshot.py +100 -0
  56. sentience/sentience_methods.py +86 -0
  57. sentience/snapshot.py +706 -0
  58. sentience/snapshot_diff.py +126 -0
  59. sentience/text_search.py +262 -0
  60. sentience/trace_event_builder.py +148 -0
  61. sentience/trace_file_manager.py +197 -0
  62. sentience/trace_indexing/__init__.py +27 -0
  63. sentience/trace_indexing/index_schema.py +199 -0
  64. sentience/trace_indexing/indexer.py +414 -0
  65. sentience/tracer_factory.py +322 -0
  66. sentience/tracing.py +449 -0
  67. sentience/utils/__init__.py +40 -0
  68. sentience/utils/browser.py +46 -0
  69. sentience/utils/element.py +257 -0
  70. sentience/utils/formatting.py +59 -0
  71. sentience/utils.py +296 -0
  72. sentience/verification.py +380 -0
  73. sentience/visual_agent.py +2058 -0
  74. sentience/wait.py +139 -0
  75. sentienceapi-0.95.0.dist-info/METADATA +984 -0
  76. sentienceapi-0.95.0.dist-info/RECORD +82 -0
  77. sentienceapi-0.95.0.dist-info/WHEEL +5 -0
  78. sentienceapi-0.95.0.dist-info/entry_points.txt +2 -0
  79. sentienceapi-0.95.0.dist-info/licenses/LICENSE +24 -0
  80. sentienceapi-0.95.0.dist-info/licenses/LICENSE-APACHE +201 -0
  81. sentienceapi-0.95.0.dist-info/licenses/LICENSE-MIT +21 -0
  82. sentienceapi-0.95.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,322 @@
1
+ """
2
+ Tracer factory with automatic tier detection.
3
+
4
+ Provides convenient factory function for creating tracers with cloud upload support.
5
+ """
6
+
7
+ import gzip
8
+ import os
9
+ import uuid
10
+ from collections.abc import Callable
11
+ from pathlib import Path
12
+ from typing import Any, Optional
13
+
14
+ import requests
15
+
16
+ from sentience.cloud_tracing import CloudTraceSink, SentienceLogger
17
+ from sentience.constants import SENTIENCE_API_URL
18
+ from sentience.tracing import JsonlTraceSink, Tracer
19
+
20
+
21
+ def create_tracer(
22
+ api_key: str | None = None,
23
+ run_id: str | None = None,
24
+ api_url: str | None = None,
25
+ logger: SentienceLogger | None = None,
26
+ upload_trace: bool = False,
27
+ goal: str | None = None,
28
+ agent_type: str | None = None,
29
+ llm_model: str | None = None,
30
+ start_url: str | None = None,
31
+ screenshot_processor: Callable[[str], str] | None = None,
32
+ ) -> Tracer:
33
+ """
34
+ Create tracer with automatic tier detection.
35
+
36
+ Tier Detection:
37
+ - If api_key is provided: Try to initialize CloudTraceSink (Pro/Enterprise)
38
+ - If cloud init fails or no api_key: Fall back to JsonlTraceSink (Free tier)
39
+
40
+ Args:
41
+ api_key: Sentience API key (e.g., "sk_pro_xxxxx")
42
+ - Free tier: None or empty
43
+ - Pro/Enterprise: Valid API key
44
+ run_id: Unique identifier for this agent run. If not provided, generates UUID.
45
+ api_url: Sentience API base URL (default: https://api.sentienceapi.com)
46
+ logger: Optional logger instance for logging file sizes and errors
47
+ upload_trace: Enable cloud trace upload (default: False). When True and api_key
48
+ is provided, traces will be uploaded to cloud. When False, traces
49
+ are saved locally only.
50
+ goal: User's goal/objective for this trace run. This will be displayed as the
51
+ trace name in the frontend. Should be descriptive and action-oriented.
52
+ Example: "Add wireless headphones to cart on Amazon"
53
+ agent_type: Type of agent running (e.g., "SentienceAgent", "CustomAgent")
54
+ llm_model: LLM model used (e.g., "gpt-4-turbo", "claude-3-5-sonnet")
55
+ start_url: Starting URL of the agent run (e.g., "https://amazon.com")
56
+ screenshot_processor: Optional function to process screenshots before upload.
57
+ Takes base64 string, returns processed base64 string.
58
+ Useful for PII redaction or custom image processing.
59
+
60
+ Returns:
61
+ Tracer configured with appropriate sink
62
+
63
+ Example:
64
+ >>> # Pro tier user with goal
65
+ >>> tracer = create_tracer(
66
+ ... api_key="sk_pro_xyz",
67
+ ... run_id="demo",
68
+ ... goal="Add headphones to cart",
69
+ ... agent_type="SentienceAgent",
70
+ ... llm_model="gpt-4-turbo",
71
+ ... start_url="https://amazon.com"
72
+ ... )
73
+ >>> # Returns: Tracer with CloudTraceSink
74
+ >>>
75
+ >>> # With screenshot processor for PII redaction
76
+ >>> def redact_pii(screenshot_base64: str) -> str:
77
+ ... # Your custom redaction logic
78
+ ... return redacted_screenshot
79
+ >>>
80
+ >>> tracer = create_tracer(
81
+ ... api_key="sk_pro_xyz",
82
+ ... screenshot_processor=redact_pii
83
+ ... )
84
+ >>> # Screenshots will be processed before upload
85
+ >>>
86
+ >>> # Free tier user
87
+ >>> tracer = create_tracer(run_id="demo")
88
+ >>> # Returns: Tracer with JsonlTraceSink (local-only)
89
+ >>>
90
+ >>> # Use with agent
91
+ >>> agent = SentienceAgent(browser, llm, tracer=tracer)
92
+ >>> agent.act("Click search")
93
+ >>> tracer.close() # Uploads to cloud if Pro tier
94
+ """
95
+ if run_id is None:
96
+ run_id = str(uuid.uuid4())
97
+
98
+ if api_url is None:
99
+ api_url = SENTIENCE_API_URL
100
+
101
+ # 0. Check for orphaned traces from previous crashes (if api_key provided and upload enabled)
102
+ if api_key and upload_trace:
103
+ _recover_orphaned_traces(api_key, api_url)
104
+
105
+ # 1. Try to initialize Cloud Sink (Pro/Enterprise tier) if upload enabled
106
+ if api_key and upload_trace:
107
+ try:
108
+ # Build metadata object for trace initialization
109
+ # Only include non-empty fields to avoid sending empty strings
110
+ metadata: dict[str, str] = {}
111
+ if goal and goal.strip():
112
+ metadata["goal"] = goal.strip()
113
+ if agent_type and agent_type.strip():
114
+ metadata["agent_type"] = agent_type.strip()
115
+ if llm_model and llm_model.strip():
116
+ metadata["llm_model"] = llm_model.strip()
117
+ if start_url and start_url.strip():
118
+ metadata["start_url"] = start_url.strip()
119
+
120
+ # Build request payload
121
+ payload: dict[str, Any] = {"run_id": run_id}
122
+ if metadata:
123
+ payload["metadata"] = metadata
124
+
125
+ # Request pre-signed upload URL from backend
126
+ response = requests.post(
127
+ f"{api_url}/v1/traces/init",
128
+ headers={"Authorization": f"Bearer {api_key}"},
129
+ json=payload,
130
+ timeout=10,
131
+ )
132
+
133
+ if response.status_code == 200:
134
+ data = response.json()
135
+ upload_url = data.get("upload_url")
136
+
137
+ if upload_url:
138
+ print("☁️ [Sentience] Cloud tracing enabled (Pro tier)")
139
+ return Tracer(
140
+ run_id=run_id,
141
+ sink=CloudTraceSink(
142
+ upload_url=upload_url,
143
+ run_id=run_id,
144
+ api_key=api_key,
145
+ api_url=api_url,
146
+ logger=logger,
147
+ ),
148
+ screenshot_processor=screenshot_processor,
149
+ )
150
+ else:
151
+ print("⚠️ [Sentience] Cloud init response missing upload_url")
152
+ print(f" Response data: {data}")
153
+ print(" Falling back to local-only tracing")
154
+
155
+ elif response.status_code == 403:
156
+ print("⚠️ [Sentience] Cloud tracing requires Pro tier")
157
+ try:
158
+ error_data = response.json()
159
+ error_msg = error_data.get("error") or error_data.get("message", "")
160
+ if error_msg:
161
+ print(f" API Error: {error_msg}")
162
+ except Exception:
163
+ pass
164
+ print(" Falling back to local-only tracing")
165
+ elif response.status_code == 401:
166
+ print("⚠️ [Sentience] Cloud init failed: HTTP 401 Unauthorized")
167
+ print(" API key is invalid or expired")
168
+ try:
169
+ error_data = response.json()
170
+ error_msg = error_data.get("error") or error_data.get("message", "")
171
+ if error_msg:
172
+ print(f" API Error: {error_msg}")
173
+ except Exception:
174
+ pass
175
+ print(" Falling back to local-only tracing")
176
+ else:
177
+ print(f"⚠️ [Sentience] Cloud init failed: HTTP {response.status_code}")
178
+ try:
179
+ error_data = response.json()
180
+ error_msg = error_data.get("error") or error_data.get(
181
+ "message", "Unknown error"
182
+ )
183
+ print(f" Error: {error_msg}")
184
+ if "tier" in error_msg.lower() or "subscription" in error_msg.lower():
185
+ print(f" 💡 This may be a tier/subscription issue")
186
+ except Exception:
187
+ print(f" Response: {response.text[:200]}")
188
+ print(" Falling back to local-only tracing")
189
+
190
+ except requests.exceptions.Timeout:
191
+ print("⚠️ [Sentience] Cloud init timeout")
192
+ print(" Falling back to local-only tracing")
193
+ except requests.exceptions.ConnectionError:
194
+ print("⚠️ [Sentience] Cloud init connection error")
195
+ print(" Falling back to local-only tracing")
196
+ except Exception as e:
197
+ print(f"⚠️ [Sentience] Cloud init error: {e}")
198
+ print(" Falling back to local-only tracing")
199
+
200
+ # 2. Fallback to Local Sink (Free tier / Offline mode)
201
+ traces_dir = Path("traces")
202
+ traces_dir.mkdir(exist_ok=True)
203
+
204
+ local_path = traces_dir / f"{run_id}.jsonl"
205
+ print(f"💾 [Sentience] Local tracing: {local_path}")
206
+
207
+ return Tracer(
208
+ run_id=run_id,
209
+ sink=JsonlTraceSink(str(local_path)),
210
+ screenshot_processor=screenshot_processor,
211
+ )
212
+
213
+
214
+ def _recover_orphaned_traces(api_key: str, api_url: str = SENTIENCE_API_URL) -> None:
215
+ """
216
+ Attempt to upload orphaned traces from previous crashed runs.
217
+
218
+ Scans ~/.sentience/traces/pending/ for un-uploaded trace files and
219
+ attempts to upload them using the provided API key.
220
+
221
+ Args:
222
+ api_key: Sentience API key for authentication
223
+ api_url: Sentience API base URL (defaults to SENTIENCE_API_URL)
224
+ """
225
+ pending_dir = Path.home() / ".sentience" / "traces" / "pending"
226
+
227
+ if not pending_dir.exists():
228
+ return
229
+
230
+ orphaned = list(pending_dir.glob("*.jsonl"))
231
+
232
+ if not orphaned:
233
+ return
234
+
235
+ # Filter out test files (run_ids that start with "test-" or are clearly test data)
236
+ # These are likely from local testing and shouldn't be uploaded
237
+ test_patterns = ["test-", "test_", "test."]
238
+ valid_orphaned = [
239
+ f
240
+ for f in orphaned
241
+ if not any(f.stem.startswith(pattern) for pattern in test_patterns)
242
+ and not f.stem.startswith("test")
243
+ ]
244
+
245
+ if not valid_orphaned:
246
+ return
247
+
248
+ print(f"⚠️ [Sentience] Found {len(valid_orphaned)} un-uploaded trace(s) from previous runs")
249
+ print(" Attempting to upload now...")
250
+
251
+ for trace_file in valid_orphaned:
252
+ try:
253
+ # Extract run_id from filename (format: {run_id}.jsonl)
254
+ run_id = trace_file.stem
255
+
256
+ # Request new upload URL for this run_id
257
+ response = requests.post(
258
+ f"{api_url}/v1/traces/init",
259
+ headers={"Authorization": f"Bearer {api_key}"},
260
+ json={"run_id": run_id},
261
+ timeout=10,
262
+ )
263
+
264
+ if response.status_code != 200:
265
+ # HTTP 409 means trace already exists (already uploaded)
266
+ # Treat as success and delete local file
267
+ if response.status_code == 409:
268
+ print(f"✅ Trace {run_id} already exists in cloud (skipping re-upload)")
269
+ # Delete local file since it's already in cloud
270
+ try:
271
+ os.remove(trace_file)
272
+ except Exception:
273
+ pass # Ignore cleanup errors
274
+ continue
275
+ # HTTP 422 typically means invalid run_id (e.g., test files)
276
+ # Skip silently for 422, but log other errors
277
+ if response.status_code == 422:
278
+ # Likely a test file or invalid run_id, skip silently
279
+ continue
280
+ print(f"❌ Failed to get upload URL for {run_id}: HTTP {response.status_code}")
281
+ continue
282
+
283
+ data = response.json()
284
+ upload_url = data.get("upload_url")
285
+
286
+ if not upload_url:
287
+ print(f"❌ Upload URL missing for {run_id}")
288
+ continue
289
+
290
+ # Read and compress trace file
291
+ with open(trace_file, "rb") as f:
292
+ trace_data = f.read()
293
+
294
+ compressed_data = gzip.compress(trace_data)
295
+
296
+ # Upload to cloud
297
+ upload_response = requests.put(
298
+ upload_url,
299
+ data=compressed_data,
300
+ headers={
301
+ "Content-Type": "application/x-gzip",
302
+ "Content-Encoding": "gzip",
303
+ },
304
+ timeout=60,
305
+ )
306
+
307
+ if upload_response.status_code == 200:
308
+ print(f"✅ Uploaded orphaned trace: {run_id}")
309
+ # Delete file on successful upload
310
+ try:
311
+ os.remove(trace_file)
312
+ except Exception:
313
+ pass # Ignore cleanup errors
314
+ else:
315
+ print(f"❌ Failed to upload {run_id}: HTTP {upload_response.status_code}")
316
+
317
+ except requests.exceptions.Timeout:
318
+ print(f"❌ Timeout uploading {trace_file.name}")
319
+ except requests.exceptions.ConnectionError:
320
+ print(f"❌ Connection error uploading {trace_file.name}")
321
+ except Exception as e:
322
+ print(f"❌ Error uploading {trace_file.name}: {e}")