sentienceapi 0.90.16__py3-none-any.whl → 0.92.2__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 +14 -5
- sentience/action_executor.py +215 -0
- sentience/actions.py +408 -25
- sentience/agent.py +802 -293
- sentience/agent_config.py +3 -0
- sentience/async_api.py +83 -1142
- sentience/base_agent.py +95 -0
- sentience/browser.py +484 -1
- sentience/browser_evaluator.py +299 -0
- sentience/cloud_tracing.py +457 -33
- sentience/conversational_agent.py +77 -43
- sentience/element_filter.py +136 -0
- sentience/expect.py +98 -2
- sentience/extension/background.js +56 -185
- sentience/extension/content.js +117 -289
- sentience/extension/injected_api.js +799 -1374
- sentience/extension/manifest.json +1 -1
- sentience/extension/pkg/sentience_core.js +190 -396
- sentience/extension/pkg/sentience_core_bg.wasm +0 -0
- sentience/extension/release.json +47 -47
- sentience/formatting.py +9 -53
- sentience/inspector.py +183 -1
- sentience/llm_interaction_handler.py +191 -0
- sentience/llm_provider.py +74 -52
- sentience/llm_provider_utils.py +120 -0
- sentience/llm_response_builder.py +153 -0
- sentience/models.py +60 -1
- sentience/overlay.py +109 -2
- sentience/protocols.py +228 -0
- sentience/query.py +1 -1
- sentience/read.py +95 -3
- sentience/recorder.py +223 -3
- sentience/schemas/trace_v1.json +102 -9
- sentience/screenshot.py +48 -2
- sentience/sentience_methods.py +86 -0
- sentience/snapshot.py +291 -38
- sentience/snapshot_diff.py +141 -0
- sentience/text_search.py +119 -5
- sentience/trace_event_builder.py +129 -0
- sentience/trace_file_manager.py +197 -0
- sentience/trace_indexing/index_schema.py +95 -7
- sentience/trace_indexing/indexer.py +117 -14
- sentience/tracer_factory.py +119 -6
- sentience/tracing.py +172 -8
- 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 +1 -1
- sentience/visual_agent.py +2056 -0
- sentience/wait.py +68 -2
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/METADATA +2 -1
- sentienceapi-0.92.2.dist-info/RECORD +65 -0
- sentience/extension/test-content.js +0 -4
- sentienceapi-0.90.16.dist-info/RECORD +0 -50
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/WHEEL +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/entry_points.txt +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/licenses/LICENSE +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/licenses/LICENSE-APACHE +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/licenses/LICENSE-MIT +0 -0
- {sentienceapi-0.90.16.dist-info → sentienceapi-0.92.2.dist-info}/top_level.txt +0 -0
sentience/overlay.py
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Visual overlay utilities - show/clear element highlights in browser
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any, Optional
|
|
6
6
|
|
|
7
|
-
from .browser import SentienceBrowser
|
|
7
|
+
from .browser import AsyncSentienceBrowser, SentienceBrowser
|
|
8
8
|
from .models import Element, Snapshot
|
|
9
9
|
|
|
10
10
|
|
|
@@ -113,3 +113,110 @@ def clear_overlay(browser: SentienceBrowser) -> None:
|
|
|
113
113
|
}
|
|
114
114
|
"""
|
|
115
115
|
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def show_overlay_async(
|
|
119
|
+
browser: AsyncSentienceBrowser,
|
|
120
|
+
elements: list[Element] | list[dict[str, Any]] | Snapshot,
|
|
121
|
+
target_element_id: int | None = None,
|
|
122
|
+
) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Display visual overlay highlighting elements in the browser (async)
|
|
125
|
+
|
|
126
|
+
This function shows a Shadow DOM overlay with color-coded borders around
|
|
127
|
+
detected elements. Useful for debugging, learning, and validating element detection.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
browser: AsyncSentienceBrowser instance
|
|
131
|
+
elements: Can be:
|
|
132
|
+
- List of Element objects (from snapshot.elements)
|
|
133
|
+
- List of raw element dicts (from snapshot result or API response)
|
|
134
|
+
- Snapshot object (will use snapshot.elements)
|
|
135
|
+
target_element_id: Optional ID of element to highlight in red (default: None)
|
|
136
|
+
|
|
137
|
+
Color Coding:
|
|
138
|
+
- Red: Target element (when target_element_id is specified)
|
|
139
|
+
- Blue: Primary elements (is_primary=true)
|
|
140
|
+
- Green: Regular interactive elements
|
|
141
|
+
|
|
142
|
+
Visual Indicators:
|
|
143
|
+
- Border thickness and opacity scale with importance score
|
|
144
|
+
- Semi-transparent fill for better visibility
|
|
145
|
+
- Importance badges showing scores
|
|
146
|
+
- Star icon for primary elements
|
|
147
|
+
- Target emoji for the target element
|
|
148
|
+
|
|
149
|
+
Auto-clear: Overlay automatically disappears after 5 seconds
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
# Show overlay from snapshot
|
|
153
|
+
snap = await snapshot_async(browser)
|
|
154
|
+
await show_overlay_async(browser, snap)
|
|
155
|
+
|
|
156
|
+
# Show overlay with custom elements
|
|
157
|
+
elements = [{"id": 1, "bbox": {"x": 100, "y": 100, "width": 200, "height": 50}, ...}]
|
|
158
|
+
await show_overlay_async(browser, elements)
|
|
159
|
+
|
|
160
|
+
# Show overlay with target element highlighted in red
|
|
161
|
+
await show_overlay_async(browser, snap, target_element_id=42)
|
|
162
|
+
|
|
163
|
+
# Clear overlay manually before 5 seconds
|
|
164
|
+
await clear_overlay_async(browser)
|
|
165
|
+
"""
|
|
166
|
+
if not browser.page:
|
|
167
|
+
raise RuntimeError("Browser not started. Call await browser.start() first.")
|
|
168
|
+
|
|
169
|
+
# Handle different input types
|
|
170
|
+
if isinstance(elements, Snapshot):
|
|
171
|
+
# Extract elements from Snapshot object
|
|
172
|
+
elements_list = [el.model_dump() for el in elements.elements]
|
|
173
|
+
elif isinstance(elements, list) and len(elements) > 0:
|
|
174
|
+
# Check if it's a list of Element objects or dicts
|
|
175
|
+
if hasattr(elements[0], "model_dump"):
|
|
176
|
+
# List of Element objects
|
|
177
|
+
elements_list = [el.model_dump() for el in elements]
|
|
178
|
+
else:
|
|
179
|
+
# Already a list of dicts
|
|
180
|
+
elements_list = elements
|
|
181
|
+
else:
|
|
182
|
+
raise ValueError("elements must be a Snapshot, list of Element objects, or list of dicts")
|
|
183
|
+
|
|
184
|
+
# Call extension API
|
|
185
|
+
await browser.page.evaluate(
|
|
186
|
+
"""
|
|
187
|
+
(args) => {
|
|
188
|
+
if (window.sentience && window.sentience.showOverlay) {
|
|
189
|
+
window.sentience.showOverlay(args.elements, args.targetId);
|
|
190
|
+
} else {
|
|
191
|
+
console.warn('[Sentience SDK] showOverlay not available - is extension loaded?');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
""",
|
|
195
|
+
{"elements": elements_list, "targetId": target_element_id},
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
async def clear_overlay_async(browser: AsyncSentienceBrowser) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Clear the visual overlay manually (before 5-second auto-clear) (async)
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
browser: AsyncSentienceBrowser instance
|
|
205
|
+
|
|
206
|
+
Example:
|
|
207
|
+
await show_overlay_async(browser, snap)
|
|
208
|
+
# ... inspect overlay ...
|
|
209
|
+
await clear_overlay_async(browser) # Remove immediately
|
|
210
|
+
"""
|
|
211
|
+
if not browser.page:
|
|
212
|
+
raise RuntimeError("Browser not started. Call await browser.start() first.")
|
|
213
|
+
|
|
214
|
+
await browser.page.evaluate(
|
|
215
|
+
"""
|
|
216
|
+
() => {
|
|
217
|
+
if (window.sentience && window.sentience.clearOverlay) {
|
|
218
|
+
window.sentience.clearOverlay();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
"""
|
|
222
|
+
)
|
sentience/protocols.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Protocol definitions for testability and dependency injection.
|
|
3
|
+
|
|
4
|
+
These protocols define the minimal interface required by agent classes,
|
|
5
|
+
enabling better testability through mocking while maintaining type safety.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Optional, Protocol, runtime_checkable
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from playwright.async_api import Page as AsyncPage
|
|
12
|
+
from playwright.sync_api import Page
|
|
13
|
+
|
|
14
|
+
from .models import Snapshot
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@runtime_checkable
|
|
18
|
+
class PageProtocol(Protocol):
|
|
19
|
+
"""
|
|
20
|
+
Protocol for Playwright Page operations used by agents.
|
|
21
|
+
|
|
22
|
+
This protocol defines the minimal interface required from Playwright's Page object.
|
|
23
|
+
Agents use this interface to interact with the browser page.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def url(self) -> str:
|
|
28
|
+
"""Current page URL."""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
def evaluate(self, script: str, *args: Any, **kwargs: Any) -> Any:
|
|
32
|
+
"""
|
|
33
|
+
Evaluate JavaScript in the page context.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
script: JavaScript code to evaluate
|
|
37
|
+
*args: Arguments to pass to the script
|
|
38
|
+
**kwargs: Keyword arguments to pass to the script
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Result of the JavaScript evaluation
|
|
42
|
+
"""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
def goto(self, url: str, **kwargs: Any) -> Any | None:
|
|
46
|
+
"""
|
|
47
|
+
Navigate to a URL.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
url: URL to navigate to
|
|
51
|
+
**kwargs: Additional navigation options
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Response object or None
|
|
55
|
+
"""
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
def wait_for_timeout(self, timeout: int) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Wait for a specified timeout.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
timeout: Timeout in milliseconds
|
|
64
|
+
"""
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
def wait_for_load_state(self, state: str = "load", timeout: int | None = None) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Wait for page load state.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
state: Load state to wait for (e.g., "load", "domcontentloaded", "networkidle")
|
|
73
|
+
timeout: Optional timeout in milliseconds
|
|
74
|
+
"""
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@runtime_checkable
|
|
79
|
+
class BrowserProtocol(Protocol):
|
|
80
|
+
"""
|
|
81
|
+
Protocol for browser operations used by agents.
|
|
82
|
+
|
|
83
|
+
This protocol defines the minimal interface required from SentienceBrowser.
|
|
84
|
+
Agents use this interface to interact with the browser and take snapshots.
|
|
85
|
+
|
|
86
|
+
Note: SentienceBrowser naturally implements this protocol, so no changes
|
|
87
|
+
are required to existing code. This protocol enables better testability
|
|
88
|
+
through mocking.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def page(self) -> PageProtocol | None:
|
|
93
|
+
"""
|
|
94
|
+
Current Playwright Page object.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Page object if browser is started, None otherwise
|
|
98
|
+
"""
|
|
99
|
+
...
|
|
100
|
+
|
|
101
|
+
def start(self) -> None:
|
|
102
|
+
"""Start the browser session."""
|
|
103
|
+
...
|
|
104
|
+
|
|
105
|
+
def close(self, output_path: str | None = None) -> str | None:
|
|
106
|
+
"""
|
|
107
|
+
Close the browser session.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
output_path: Optional path to save browser state/output
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Path to saved output or None
|
|
114
|
+
"""
|
|
115
|
+
...
|
|
116
|
+
|
|
117
|
+
def goto(self, url: str) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Navigate to a URL.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
url: URL to navigate to
|
|
123
|
+
"""
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@runtime_checkable
|
|
128
|
+
class AsyncPageProtocol(Protocol):
|
|
129
|
+
"""
|
|
130
|
+
Protocol for async Playwright Page operations.
|
|
131
|
+
|
|
132
|
+
Similar to PageProtocol but for async operations.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def url(self) -> str:
|
|
137
|
+
"""Current page URL."""
|
|
138
|
+
...
|
|
139
|
+
|
|
140
|
+
async def evaluate(self, script: str, *args: Any, **kwargs: Any) -> Any:
|
|
141
|
+
"""
|
|
142
|
+
Evaluate JavaScript in the page context (async).
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
script: JavaScript code to evaluate
|
|
146
|
+
*args: Arguments to pass to the script
|
|
147
|
+
**kwargs: Keyword arguments to pass to the script
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Result of the JavaScript evaluation
|
|
151
|
+
"""
|
|
152
|
+
...
|
|
153
|
+
|
|
154
|
+
async def goto(self, url: str, **kwargs: Any) -> Any | None:
|
|
155
|
+
"""
|
|
156
|
+
Navigate to a URL (async).
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
url: URL to navigate to
|
|
160
|
+
**kwargs: Additional navigation options
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Response object or None
|
|
164
|
+
"""
|
|
165
|
+
...
|
|
166
|
+
|
|
167
|
+
async def wait_for_timeout(self, timeout: int) -> None:
|
|
168
|
+
"""
|
|
169
|
+
Wait for a specified timeout (async).
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
timeout: Timeout in milliseconds
|
|
173
|
+
"""
|
|
174
|
+
...
|
|
175
|
+
|
|
176
|
+
async def wait_for_load_state(self, state: str = "load", timeout: int | None = None) -> None:
|
|
177
|
+
"""
|
|
178
|
+
Wait for page load state (async).
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
state: Load state to wait for (e.g., "load", "domcontentloaded", "networkidle")
|
|
182
|
+
timeout: Optional timeout in milliseconds
|
|
183
|
+
"""
|
|
184
|
+
...
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@runtime_checkable
|
|
188
|
+
class AsyncBrowserProtocol(Protocol):
|
|
189
|
+
"""
|
|
190
|
+
Protocol for async browser operations.
|
|
191
|
+
|
|
192
|
+
Similar to BrowserProtocol but for async operations.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def page(self) -> AsyncPageProtocol | None:
|
|
197
|
+
"""
|
|
198
|
+
Current Playwright AsyncPage object.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
AsyncPage object if browser is started, None otherwise
|
|
202
|
+
"""
|
|
203
|
+
...
|
|
204
|
+
|
|
205
|
+
async def start(self) -> None:
|
|
206
|
+
"""Start the browser session (async)."""
|
|
207
|
+
...
|
|
208
|
+
|
|
209
|
+
async def close(self, output_path: str | None = None) -> str | None:
|
|
210
|
+
"""
|
|
211
|
+
Close the browser session (async).
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
output_path: Optional path to save browser state/output
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Path to saved output or None
|
|
218
|
+
"""
|
|
219
|
+
...
|
|
220
|
+
|
|
221
|
+
async def goto(self, url: str) -> None:
|
|
222
|
+
"""
|
|
223
|
+
Navigate to a URL (async).
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
url: URL to navigate to
|
|
227
|
+
"""
|
|
228
|
+
...
|
sentience/query.py
CHANGED
sentience/read.py
CHANGED
|
@@ -4,14 +4,15 @@ Read page content - supports raw HTML, text, and markdown formats
|
|
|
4
4
|
|
|
5
5
|
from typing import Literal
|
|
6
6
|
|
|
7
|
-
from .browser import SentienceBrowser
|
|
7
|
+
from .browser import AsyncSentienceBrowser, SentienceBrowser
|
|
8
|
+
from .models import ReadResult
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def read(
|
|
11
12
|
browser: SentienceBrowser,
|
|
12
13
|
output_format: Literal["raw", "text", "markdown"] = "raw",
|
|
13
14
|
enhance_markdown: bool = True,
|
|
14
|
-
) ->
|
|
15
|
+
) -> ReadResult:
|
|
15
16
|
"""
|
|
16
17
|
Read page content as raw HTML, text, or markdown
|
|
17
18
|
|
|
@@ -93,4 +94,95 @@ def read(
|
|
|
93
94
|
{"format": output_format},
|
|
94
95
|
)
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
# Convert dict result to ReadResult model
|
|
98
|
+
return ReadResult(**result)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def read_async(
|
|
102
|
+
browser: AsyncSentienceBrowser,
|
|
103
|
+
output_format: Literal["raw", "text", "markdown"] = "raw",
|
|
104
|
+
enhance_markdown: bool = True,
|
|
105
|
+
) -> ReadResult:
|
|
106
|
+
"""
|
|
107
|
+
Read page content as raw HTML, text, or markdown (async)
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
browser: AsyncSentienceBrowser instance
|
|
111
|
+
output_format: Output format - "raw" (default, returns HTML for external processing),
|
|
112
|
+
"text" (plain text), or "markdown" (lightweight or enhanced markdown).
|
|
113
|
+
enhance_markdown: If True and output_format is "markdown", uses markdownify for better conversion.
|
|
114
|
+
If False, uses the extension's lightweight markdown converter.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
dict with:
|
|
118
|
+
- status: "success" or "error"
|
|
119
|
+
- url: Current page URL
|
|
120
|
+
- format: "raw", "text", or "markdown"
|
|
121
|
+
- content: Page content as string
|
|
122
|
+
- length: Content length in characters
|
|
123
|
+
- error: Error message if status is "error"
|
|
124
|
+
|
|
125
|
+
Examples:
|
|
126
|
+
# Get raw HTML (default) - can be used with markdownify for better conversion
|
|
127
|
+
result = await read_async(browser)
|
|
128
|
+
html_content = result["content"]
|
|
129
|
+
|
|
130
|
+
# Get high-quality markdown (uses markdownify internally)
|
|
131
|
+
result = await read_async(browser, output_format="markdown")
|
|
132
|
+
markdown = result["content"]
|
|
133
|
+
|
|
134
|
+
# Get plain text
|
|
135
|
+
result = await read_async(browser, output_format="text")
|
|
136
|
+
text = result["content"]
|
|
137
|
+
"""
|
|
138
|
+
if not browser.page:
|
|
139
|
+
raise RuntimeError("Browser not started. Call await browser.start() first.")
|
|
140
|
+
|
|
141
|
+
if output_format == "markdown" and enhance_markdown:
|
|
142
|
+
# Get raw HTML from the extension first
|
|
143
|
+
raw_html_result = await browser.page.evaluate(
|
|
144
|
+
"""
|
|
145
|
+
(options) => {
|
|
146
|
+
return window.sentience.read(options);
|
|
147
|
+
}
|
|
148
|
+
""",
|
|
149
|
+
{"format": "raw"},
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if raw_html_result.get("status") == "success":
|
|
153
|
+
html_content = raw_html_result["content"]
|
|
154
|
+
try:
|
|
155
|
+
# Use markdownify for enhanced markdown conversion
|
|
156
|
+
from markdownify import MarkdownifyError, markdownify
|
|
157
|
+
|
|
158
|
+
markdown_content = markdownify(html_content, heading_style="ATX", wrap=True)
|
|
159
|
+
return {
|
|
160
|
+
"status": "success",
|
|
161
|
+
"url": raw_html_result["url"],
|
|
162
|
+
"format": "markdown",
|
|
163
|
+
"content": markdown_content,
|
|
164
|
+
"length": len(markdown_content),
|
|
165
|
+
}
|
|
166
|
+
except ImportError:
|
|
167
|
+
print(
|
|
168
|
+
"Warning: 'markdownify' not installed. Install with 'pip install markdownify' for enhanced markdown. Falling back to extension's markdown."
|
|
169
|
+
)
|
|
170
|
+
except MarkdownifyError as e:
|
|
171
|
+
print(f"Warning: markdownify failed ({e}), falling back to extension's markdown.")
|
|
172
|
+
except Exception as e:
|
|
173
|
+
print(
|
|
174
|
+
f"Warning: An unexpected error occurred with markdownify ({e}), falling back to extension's markdown."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# If not enhanced markdown, or fallback, call extension with requested format
|
|
178
|
+
result = await browser.page.evaluate(
|
|
179
|
+
"""
|
|
180
|
+
(options) => {
|
|
181
|
+
return window.sentience.read(options);
|
|
182
|
+
}
|
|
183
|
+
""",
|
|
184
|
+
{"format": output_format},
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Convert dict result to ReadResult model
|
|
188
|
+
return ReadResult(**result)
|