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,380 @@
1
+ """
2
+ Verification primitives for agent assertion loops.
3
+
4
+ This module provides assertion predicates and outcome types for runtime verification
5
+ in agent loops. Assertions evaluate against the current browser state (snapshot/url)
6
+ and record results into the trace.
7
+
8
+ Key concepts:
9
+ - AssertOutcome: Result of evaluating an assertion
10
+ - AssertContext: Context provided to assertion predicates (snapshot, url, step_id)
11
+ - Predicate: Callable that takes context and returns outcome
12
+
13
+ Example usage:
14
+ from sentience.verification import url_matches, exists, AssertContext
15
+
16
+ # Create predicates
17
+ on_search_page = url_matches(r"/s\\?k=")
18
+ results_loaded = exists("text~'Results'")
19
+
20
+ # Evaluate against context
21
+ ctx = AssertContext(snapshot=snapshot, url="https://example.com/s?k=shoes")
22
+ outcome = on_search_page(ctx)
23
+ print(outcome.passed) # True
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import re
29
+ from collections.abc import Callable
30
+ from dataclasses import dataclass, field
31
+ from typing import TYPE_CHECKING, Any
32
+
33
+ if TYPE_CHECKING:
34
+ from .models import Snapshot
35
+
36
+
37
+ @dataclass
38
+ class AssertOutcome:
39
+ """
40
+ Result of evaluating an assertion predicate.
41
+
42
+ Attributes:
43
+ passed: Whether the assertion passed
44
+ reason: Human-readable explanation (especially useful when failed)
45
+ details: Additional structured data for debugging/display
46
+ """
47
+
48
+ passed: bool
49
+ reason: str = ""
50
+ details: dict[str, Any] = field(default_factory=dict)
51
+
52
+
53
+ @dataclass
54
+ class AssertContext:
55
+ """
56
+ Context provided to assertion predicates.
57
+
58
+ Provides access to current browser state without requiring
59
+ the predicate to know about browser internals.
60
+
61
+ Attributes:
62
+ snapshot: Current page snapshot (may be None if not taken)
63
+ url: Current page URL
64
+ step_id: Current step identifier (for trace correlation)
65
+ """
66
+
67
+ snapshot: Snapshot | None = None
68
+ url: str | None = None
69
+ step_id: str | None = None
70
+
71
+
72
+ # Type alias for assertion predicates
73
+ Predicate = Callable[[AssertContext], AssertOutcome]
74
+
75
+
76
+ def url_matches(pattern: str) -> Predicate:
77
+ """
78
+ Create a predicate that checks if current URL matches a regex pattern.
79
+
80
+ Args:
81
+ pattern: Regular expression pattern to match against URL
82
+
83
+ Returns:
84
+ Predicate function that evaluates URL matching
85
+
86
+ Example:
87
+ >>> pred = url_matches(r"/search\\?q=")
88
+ >>> ctx = AssertContext(url="https://example.com/search?q=shoes")
89
+ >>> outcome = pred(ctx)
90
+ >>> outcome.passed
91
+ True
92
+ """
93
+ rx = re.compile(pattern)
94
+
95
+ def _pred(ctx: AssertContext) -> AssertOutcome:
96
+ url = ctx.url or ""
97
+ ok = rx.search(url) is not None
98
+ return AssertOutcome(
99
+ passed=ok,
100
+ reason="" if ok else f"url did not match pattern: {pattern}",
101
+ details={"pattern": pattern, "url": url[:200]},
102
+ )
103
+
104
+ return _pred
105
+
106
+
107
+ def url_contains(substring: str) -> Predicate:
108
+ """
109
+ Create a predicate that checks if current URL contains a substring.
110
+
111
+ Args:
112
+ substring: String to search for in URL
113
+
114
+ Returns:
115
+ Predicate function that evaluates URL containment
116
+
117
+ Example:
118
+ >>> pred = url_contains("/cart")
119
+ >>> ctx = AssertContext(url="https://example.com/cart/checkout")
120
+ >>> outcome = pred(ctx)
121
+ >>> outcome.passed
122
+ True
123
+ """
124
+
125
+ def _pred(ctx: AssertContext) -> AssertOutcome:
126
+ url = ctx.url or ""
127
+ ok = substring in url
128
+ return AssertOutcome(
129
+ passed=ok,
130
+ reason="" if ok else f"url does not contain: {substring}",
131
+ details={"substring": substring, "url": url[:200]},
132
+ )
133
+
134
+ return _pred
135
+
136
+
137
+ def exists(selector: str) -> Predicate:
138
+ """
139
+ Create a predicate that checks if elements matching selector exist.
140
+
141
+ Uses the SDK's query engine to find matching elements.
142
+
143
+ Args:
144
+ selector: Semantic selector string (e.g., "role=button text~'Sign in'")
145
+
146
+ Returns:
147
+ Predicate function that evaluates element existence
148
+
149
+ Example:
150
+ >>> pred = exists("text~'Results'")
151
+ >>> # Will check if snapshot contains elements with "Results" in text
152
+ """
153
+
154
+ def _pred(ctx: AssertContext) -> AssertOutcome:
155
+ snap = ctx.snapshot
156
+ if snap is None:
157
+ return AssertOutcome(
158
+ passed=False,
159
+ reason="no snapshot available",
160
+ details={"selector": selector},
161
+ )
162
+
163
+ # Import here to avoid circular imports
164
+ from .query import query
165
+
166
+ matches = query(snap, selector)
167
+ ok = len(matches) > 0
168
+ return AssertOutcome(
169
+ passed=ok,
170
+ reason="" if ok else f"no elements matched selector: {selector}",
171
+ details={"selector": selector, "matched": len(matches)},
172
+ )
173
+
174
+ return _pred
175
+
176
+
177
+ def not_exists(selector: str) -> Predicate:
178
+ """
179
+ Create a predicate that checks that NO elements match the selector.
180
+
181
+ Useful for asserting that error messages, loading spinners, etc. are gone.
182
+
183
+ Args:
184
+ selector: Semantic selector string
185
+
186
+ Returns:
187
+ Predicate function that evaluates element non-existence
188
+
189
+ Example:
190
+ >>> pred = not_exists("text~'Loading'")
191
+ >>> # Will pass if no elements contain "Loading" text
192
+ """
193
+
194
+ def _pred(ctx: AssertContext) -> AssertOutcome:
195
+ snap = ctx.snapshot
196
+ if snap is None:
197
+ return AssertOutcome(
198
+ passed=False,
199
+ reason="no snapshot available",
200
+ details={"selector": selector},
201
+ )
202
+
203
+ from .query import query
204
+
205
+ matches = query(snap, selector)
206
+ ok = len(matches) == 0
207
+ return AssertOutcome(
208
+ passed=ok,
209
+ reason="" if ok else f"found {len(matches)} elements matching: {selector}",
210
+ details={"selector": selector, "matched": len(matches)},
211
+ )
212
+
213
+ return _pred
214
+
215
+
216
+ def element_count(selector: str, *, min_count: int = 0, max_count: int | None = None) -> Predicate:
217
+ """
218
+ Create a predicate that checks the number of matching elements.
219
+
220
+ Args:
221
+ selector: Semantic selector string
222
+ min_count: Minimum number of matches required (inclusive)
223
+ max_count: Maximum number of matches allowed (inclusive, None = no limit)
224
+
225
+ Returns:
226
+ Predicate function that evaluates element count
227
+
228
+ Example:
229
+ >>> pred = element_count("role=button", min_count=1, max_count=5)
230
+ >>> # Will pass if 1-5 buttons found
231
+ """
232
+
233
+ def _pred(ctx: AssertContext) -> AssertOutcome:
234
+ snap = ctx.snapshot
235
+ if snap is None:
236
+ return AssertOutcome(
237
+ passed=False,
238
+ reason="no snapshot available",
239
+ details={"selector": selector, "min_count": min_count, "max_count": max_count},
240
+ )
241
+
242
+ from .query import query
243
+
244
+ matches = query(snap, selector)
245
+ count = len(matches)
246
+
247
+ ok = count >= min_count
248
+ if max_count is not None:
249
+ ok = ok and count <= max_count
250
+
251
+ if ok:
252
+ reason = ""
253
+ else:
254
+ if max_count is not None:
255
+ reason = f"expected {min_count}-{max_count} elements, found {count}"
256
+ else:
257
+ reason = f"expected at least {min_count} elements, found {count}"
258
+
259
+ return AssertOutcome(
260
+ passed=ok,
261
+ reason=reason,
262
+ details={
263
+ "selector": selector,
264
+ "matched": count,
265
+ "min_count": min_count,
266
+ "max_count": max_count,
267
+ },
268
+ )
269
+
270
+ return _pred
271
+
272
+
273
+ def all_of(*predicates: Predicate) -> Predicate:
274
+ """
275
+ Create a predicate that passes only if ALL sub-predicates pass.
276
+
277
+ Args:
278
+ *predicates: Predicate functions to combine with AND logic
279
+
280
+ Returns:
281
+ Combined predicate
282
+
283
+ Example:
284
+ >>> pred = all_of(url_contains("/cart"), exists("text~'Checkout'"))
285
+ >>> # Will pass only if both conditions are true
286
+ """
287
+
288
+ def _pred(ctx: AssertContext) -> AssertOutcome:
289
+ failed_reasons = []
290
+ all_details: list[dict[str, Any]] = []
291
+
292
+ for p in predicates:
293
+ outcome = p(ctx)
294
+ all_details.append(outcome.details)
295
+ if not outcome.passed:
296
+ failed_reasons.append(outcome.reason)
297
+
298
+ ok = len(failed_reasons) == 0
299
+ return AssertOutcome(
300
+ passed=ok,
301
+ reason="; ".join(failed_reasons) if failed_reasons else "",
302
+ details={"sub_predicates": all_details, "failed_count": len(failed_reasons)},
303
+ )
304
+
305
+ return _pred
306
+
307
+
308
+ def any_of(*predicates: Predicate) -> Predicate:
309
+ """
310
+ Create a predicate that passes if ANY sub-predicate passes.
311
+
312
+ Args:
313
+ *predicates: Predicate functions to combine with OR logic
314
+
315
+ Returns:
316
+ Combined predicate
317
+
318
+ Example:
319
+ >>> pred = any_of(exists("text~'Success'"), exists("text~'Complete'"))
320
+ >>> # Will pass if either condition is true
321
+ """
322
+
323
+ def _pred(ctx: AssertContext) -> AssertOutcome:
324
+ all_reasons = []
325
+ all_details: list[dict[str, Any]] = []
326
+
327
+ for p in predicates:
328
+ outcome = p(ctx)
329
+ all_details.append(outcome.details)
330
+ if outcome.passed:
331
+ return AssertOutcome(
332
+ passed=True,
333
+ reason="",
334
+ details={
335
+ "sub_predicates": all_details,
336
+ "matched_at_index": len(all_details) - 1,
337
+ },
338
+ )
339
+ all_reasons.append(outcome.reason)
340
+
341
+ return AssertOutcome(
342
+ passed=False,
343
+ reason=f"none of {len(predicates)} predicates passed: " + "; ".join(all_reasons),
344
+ details={"sub_predicates": all_details},
345
+ )
346
+
347
+ return _pred
348
+
349
+
350
+ def custom(check_fn: Callable[[AssertContext], bool], label: str = "custom") -> Predicate:
351
+ """
352
+ Create a predicate from a custom function.
353
+
354
+ Args:
355
+ check_fn: Function that takes AssertContext and returns bool
356
+ label: Label for debugging/display
357
+
358
+ Returns:
359
+ Predicate wrapping the custom function
360
+
361
+ Example:
362
+ >>> pred = custom(lambda ctx: ctx.snapshot and len(ctx.snapshot.elements) > 10, "has_many_elements")
363
+ """
364
+
365
+ def _pred(ctx: AssertContext) -> AssertOutcome:
366
+ try:
367
+ ok = check_fn(ctx)
368
+ return AssertOutcome(
369
+ passed=ok,
370
+ reason="" if ok else f"custom check '{label}' returned False",
371
+ details={"label": label},
372
+ )
373
+ except Exception as e:
374
+ return AssertOutcome(
375
+ passed=False,
376
+ reason=f"custom check '{label}' raised exception: {e}",
377
+ details={"label": label, "error": str(e)},
378
+ )
379
+
380
+ return _pred