notte-sdk 0.0.dev0__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.
- notte_sdk/__init__.py +7 -0
- notte_sdk/client.py +50 -0
- notte_sdk/endpoints/__init__.py +0 -0
- notte_sdk/endpoints/agents.py +504 -0
- notte_sdk/endpoints/base.py +247 -0
- notte_sdk/endpoints/page.py +215 -0
- notte_sdk/endpoints/personas.py +285 -0
- notte_sdk/endpoints/sessions.py +542 -0
- notte_sdk/endpoints/vaults.py +83 -0
- notte_sdk/errors.py +40 -0
- notte_sdk/py.typed +0 -0
- notte_sdk/types.py +851 -0
- notte_sdk/vault.py +68 -0
- notte_sdk/websockets/__init__.py +0 -0
- notte_sdk/websockets/recording.py +106 -0
- notte_sdk-0.0.dev0.dist-info/METADATA +8 -0
- notte_sdk-0.0.dev0.dist-info/RECORD +18 -0
- notte_sdk-0.0.dev0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Unpack
|
|
4
|
+
from webbrowser import open as open_browser
|
|
5
|
+
|
|
6
|
+
from loguru import logger
|
|
7
|
+
from notte_core.browser.observation import Observation
|
|
8
|
+
from notte_core.common.resource import SyncResource
|
|
9
|
+
from notte_core.data.space import DataSpace
|
|
10
|
+
from notte_core.utils.webp_replay import WebpReplay
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from typing_extensions import final, override
|
|
13
|
+
|
|
14
|
+
from notte_sdk.endpoints.base import BaseClient, NotteEndpoint
|
|
15
|
+
from notte_sdk.endpoints.page import PageClient
|
|
16
|
+
from notte_sdk.types import (
|
|
17
|
+
ListRequestDict,
|
|
18
|
+
ObserveRequestDict,
|
|
19
|
+
SessionDebugResponse,
|
|
20
|
+
SessionListRequest,
|
|
21
|
+
SessionResponse,
|
|
22
|
+
SessionStartRequest,
|
|
23
|
+
SessionStartRequestDict,
|
|
24
|
+
StepRequestDict,
|
|
25
|
+
TabSessionDebugRequest,
|
|
26
|
+
TabSessionDebugResponse,
|
|
27
|
+
UploadCookiesRequest,
|
|
28
|
+
UploadCookiesResponse,
|
|
29
|
+
)
|
|
30
|
+
from notte_sdk.websockets.recording import SessionRecordingWebSocket
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@final
|
|
34
|
+
class SessionsClient(BaseClient):
|
|
35
|
+
"""
|
|
36
|
+
Client for the Notte API.
|
|
37
|
+
|
|
38
|
+
Note: this client is only able to handle one session at a time.
|
|
39
|
+
If you need to handle multiple sessions, you need to create a new client for each session.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# Session
|
|
43
|
+
SESSION_START = "start"
|
|
44
|
+
SESSION_STOP = "{session_id}/stop"
|
|
45
|
+
SESSION_STATUS = "{session_id}"
|
|
46
|
+
SESSION_LIST = ""
|
|
47
|
+
# upload cookies
|
|
48
|
+
SESSION_UPLOAD_FILES_COOKIES = "cookies"
|
|
49
|
+
# Session Debug
|
|
50
|
+
SESSION_DEBUG = "{session_id}/debug"
|
|
51
|
+
SESSION_DEBUG_TAB = "{session_id}/debug/tab"
|
|
52
|
+
SESSION_DEBUG_REPLAY = "{session_id}/replay"
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
api_key: str | None = None,
|
|
57
|
+
verbose: bool = False,
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Initialize a SessionsClient instance.
|
|
61
|
+
|
|
62
|
+
Initializes the client with an optional API key and server URL for session management,
|
|
63
|
+
setting the base endpoint to "sessions". Also initializes the last session response to None.
|
|
64
|
+
"""
|
|
65
|
+
super().__init__(base_endpoint_path="sessions", api_key=api_key, verbose=verbose)
|
|
66
|
+
self.page: PageClient = PageClient(api_key=api_key, verbose=verbose)
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def session_start_endpoint() -> NotteEndpoint[SessionResponse]:
|
|
70
|
+
"""
|
|
71
|
+
Returns a NotteEndpoint configured for starting a session.
|
|
72
|
+
|
|
73
|
+
The returned endpoint uses the session start path from SessionsClient with the POST method and expects a SessionResponse.
|
|
74
|
+
"""
|
|
75
|
+
return NotteEndpoint(path=SessionsClient.SESSION_START, response=SessionResponse, method="POST")
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def session_stop_endpoint(session_id: str | None = None) -> NotteEndpoint[SessionResponse]:
|
|
79
|
+
"""
|
|
80
|
+
Constructs a DELETE endpoint for closing a session.
|
|
81
|
+
|
|
82
|
+
If a session ID is provided, it is inserted into the endpoint path. Returns a NotteEndpoint configured
|
|
83
|
+
with the DELETE method and expecting a SessionResponse.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
session_id: Optional session identifier; if provided, it is formatted into the endpoint path.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
A NotteEndpoint instance for closing a session.
|
|
90
|
+
"""
|
|
91
|
+
path = SessionsClient.SESSION_STOP
|
|
92
|
+
if session_id is not None:
|
|
93
|
+
path = path.format(session_id=session_id)
|
|
94
|
+
return NotteEndpoint(path=path, response=SessionResponse, method="DELETE")
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def session_status_endpoint(session_id: str | None = None) -> NotteEndpoint[SessionResponse]:
|
|
98
|
+
"""
|
|
99
|
+
Returns a NotteEndpoint for retrieving the status of a session.
|
|
100
|
+
|
|
101
|
+
If a session_id is provided, it is interpolated into the endpoint path.
|
|
102
|
+
The endpoint uses the GET method and expects a SessionResponse.
|
|
103
|
+
"""
|
|
104
|
+
path = SessionsClient.SESSION_STATUS
|
|
105
|
+
if session_id is not None:
|
|
106
|
+
path = path.format(session_id=session_id)
|
|
107
|
+
return NotteEndpoint(path=path, response=SessionResponse, method="GET")
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def session_list_endpoint(params: SessionListRequest | None = None) -> NotteEndpoint[SessionResponse]:
|
|
111
|
+
"""
|
|
112
|
+
Constructs a NotteEndpoint for listing sessions.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
params (SessionListRequest, optional): Additional filter parameters for the session list request.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
NotteEndpoint[SessionResponse]: An endpoint configured with the session list path and a GET method.
|
|
119
|
+
"""
|
|
120
|
+
return NotteEndpoint(
|
|
121
|
+
path=SessionsClient.SESSION_LIST,
|
|
122
|
+
response=SessionResponse,
|
|
123
|
+
method="GET",
|
|
124
|
+
request=None,
|
|
125
|
+
params=params,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def session_debug_endpoint(session_id: str | None = None) -> NotteEndpoint[SessionDebugResponse]:
|
|
130
|
+
"""
|
|
131
|
+
Creates a NotteEndpoint for retrieving session debug information.
|
|
132
|
+
|
|
133
|
+
If a session ID is provided, it is interpolated into the endpoint path.
|
|
134
|
+
The returned endpoint uses the GET method and expects a SessionDebugResponse.
|
|
135
|
+
"""
|
|
136
|
+
path = SessionsClient.SESSION_DEBUG
|
|
137
|
+
if session_id is not None:
|
|
138
|
+
path = path.format(session_id=session_id)
|
|
139
|
+
return NotteEndpoint(path=path, response=SessionDebugResponse, method="GET")
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def session_debug_tab_endpoint(
|
|
143
|
+
session_id: str | None = None, params: TabSessionDebugRequest | None = None
|
|
144
|
+
) -> NotteEndpoint[TabSessionDebugResponse]:
|
|
145
|
+
"""
|
|
146
|
+
Returns an endpoint for retrieving debug information for a session tab.
|
|
147
|
+
|
|
148
|
+
If a session ID is provided, it is substituted in the URL path.
|
|
149
|
+
Additional query parameters can be specified via the params argument.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
NotteEndpoint[TabSessionDebugResponse]: The configured endpoint for a GET request.
|
|
153
|
+
"""
|
|
154
|
+
path = SessionsClient.SESSION_DEBUG_TAB
|
|
155
|
+
if session_id is not None:
|
|
156
|
+
path = path.format(session_id=session_id)
|
|
157
|
+
return NotteEndpoint(
|
|
158
|
+
path=path,
|
|
159
|
+
response=TabSessionDebugResponse,
|
|
160
|
+
method="GET",
|
|
161
|
+
params=params,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def session_debug_replay_endpoint(session_id: str | None = None) -> NotteEndpoint[BaseModel]:
|
|
166
|
+
"""
|
|
167
|
+
Returns an endpoint for retrieving the replay for a session.
|
|
168
|
+
"""
|
|
169
|
+
path = SessionsClient.SESSION_DEBUG_REPLAY
|
|
170
|
+
if session_id is not None:
|
|
171
|
+
path = path.format(session_id=session_id)
|
|
172
|
+
return NotteEndpoint(path=path, response=BaseModel, method="GET")
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def session_upload_cookies_endpoint() -> NotteEndpoint[UploadCookiesResponse]:
|
|
176
|
+
"""
|
|
177
|
+
Returns a NotteEndpoint for uploading cookies to a session.
|
|
178
|
+
"""
|
|
179
|
+
return NotteEndpoint(
|
|
180
|
+
path=SessionsClient.SESSION_UPLOAD_FILES_COOKIES, response=UploadCookiesResponse, method="POST"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
@override
|
|
184
|
+
@staticmethod
|
|
185
|
+
def endpoints() -> Sequence[NotteEndpoint[BaseModel]]:
|
|
186
|
+
"""Returns a sequence of available session endpoints.
|
|
187
|
+
|
|
188
|
+
Aggregates endpoints from SessionsClient for starting, closing, status checking, listing,
|
|
189
|
+
and debugging sessions (including tab-specific debugging)."""
|
|
190
|
+
return [
|
|
191
|
+
SessionsClient.session_start_endpoint(),
|
|
192
|
+
SessionsClient.session_stop_endpoint(),
|
|
193
|
+
SessionsClient.session_status_endpoint(),
|
|
194
|
+
SessionsClient.session_list_endpoint(),
|
|
195
|
+
SessionsClient.session_debug_endpoint(),
|
|
196
|
+
SessionsClient.session_debug_tab_endpoint(),
|
|
197
|
+
SessionsClient.session_debug_replay_endpoint(),
|
|
198
|
+
SessionsClient.session_upload_cookies_endpoint(),
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
def start(self, **data: Unpack[SessionStartRequestDict]) -> SessionResponse:
|
|
202
|
+
"""
|
|
203
|
+
Starts a new session using the provided keyword arguments.
|
|
204
|
+
|
|
205
|
+
Validates the input data against the session start model, sends a session start
|
|
206
|
+
request to the API, updates the last session response, and returns the response.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
**data: Keyword arguments representing details for starting the session.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
SessionResponse: The response received from the session start endpoint.
|
|
213
|
+
"""
|
|
214
|
+
request = SessionStartRequest.model_validate(data)
|
|
215
|
+
response = self.request(SessionsClient.session_start_endpoint().with_request(request))
|
|
216
|
+
return response
|
|
217
|
+
|
|
218
|
+
def stop(self, session_id: str) -> SessionResponse:
|
|
219
|
+
"""
|
|
220
|
+
Stops an active session.
|
|
221
|
+
|
|
222
|
+
This method sends a request to the session stop endpoint using the specified
|
|
223
|
+
session ID or the currently active session. It validates the server response,
|
|
224
|
+
clears the internal session state, and returns the validated response.
|
|
225
|
+
|
|
226
|
+
Parameters:
|
|
227
|
+
session_id (str, optional): The identifier of the session to close. If not
|
|
228
|
+
provided, the active session ID is used. Raises ValueError if no active
|
|
229
|
+
session exists.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
SessionResponse: The validated response from the session stop request.
|
|
233
|
+
"""
|
|
234
|
+
endpoint = SessionsClient.session_stop_endpoint(session_id=session_id)
|
|
235
|
+
response = self.request(endpoint)
|
|
236
|
+
return response
|
|
237
|
+
|
|
238
|
+
def status(self, session_id: str) -> SessionResponse:
|
|
239
|
+
"""
|
|
240
|
+
Retrieves the current status of a session.
|
|
241
|
+
|
|
242
|
+
If no session_id is provided, the session ID from the last response is used. This method constructs
|
|
243
|
+
the status endpoint, validates the response against the SessionResponse model, updates the stored
|
|
244
|
+
session response, and returns the validated status.
|
|
245
|
+
"""
|
|
246
|
+
endpoint = SessionsClient.session_status_endpoint(session_id=session_id)
|
|
247
|
+
response = self.request(endpoint)
|
|
248
|
+
return response
|
|
249
|
+
|
|
250
|
+
def list(self, **data: Unpack[ListRequestDict]) -> Sequence[SessionResponse]:
|
|
251
|
+
"""
|
|
252
|
+
Retrieves a list of sessions from the API.
|
|
253
|
+
|
|
254
|
+
Validates keyword arguments as session listing criteria and requests the available
|
|
255
|
+
sessions. Returns a sequence of session response objects.
|
|
256
|
+
"""
|
|
257
|
+
params = SessionListRequest.model_validate(data)
|
|
258
|
+
endpoint = SessionsClient.session_list_endpoint(params=params)
|
|
259
|
+
return self.request_list(endpoint)
|
|
260
|
+
|
|
261
|
+
def debug_info(self, session_id: str) -> SessionDebugResponse:
|
|
262
|
+
"""
|
|
263
|
+
Retrieves debug information for a session.
|
|
264
|
+
|
|
265
|
+
If a session ID is provided, it is used; otherwise, the current session ID is retrieved.
|
|
266
|
+
Raises a ValueError if no valid session ID is available.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
session_id (Optional[str]): An optional session identifier to use.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
SessionDebugResponse: The debug information response for the session.
|
|
273
|
+
"""
|
|
274
|
+
endpoint = SessionsClient.session_debug_endpoint(session_id=session_id)
|
|
275
|
+
return self.request(endpoint)
|
|
276
|
+
|
|
277
|
+
def debug_tab_info(self, session_id: str, tab_idx: int | None = None) -> TabSessionDebugResponse:
|
|
278
|
+
"""
|
|
279
|
+
Retrieves debug information for a specific tab in the current session.
|
|
280
|
+
|
|
281
|
+
If no session ID is provided, the active session is used. If a tab index is provided, the
|
|
282
|
+
debug request is scoped to that tab.
|
|
283
|
+
|
|
284
|
+
Parameters:
|
|
285
|
+
session_id (str, optional): The session identifier to use.
|
|
286
|
+
tab_idx (int, optional): The index of the tab for which to retrieve debug info.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
TabSessionDebugResponse: The response containing debug information for the specified tab.
|
|
290
|
+
"""
|
|
291
|
+
params = TabSessionDebugRequest(tab_idx=tab_idx) if tab_idx is not None else None
|
|
292
|
+
endpoint = SessionsClient.session_debug_tab_endpoint(session_id=session_id, params=params)
|
|
293
|
+
return self.request(endpoint)
|
|
294
|
+
|
|
295
|
+
def replay(self, session_id: str) -> WebpReplay:
|
|
296
|
+
"""
|
|
297
|
+
Downloads the replay for the specified session in webp format.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
session_id: The identifier of the session to download the replay for.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
WebpReplay: The replay file in webp format.
|
|
304
|
+
"""
|
|
305
|
+
endpoint = SessionsClient.session_debug_replay_endpoint(session_id=session_id)
|
|
306
|
+
file_bytes = self._request_file(endpoint, file_type="webp")
|
|
307
|
+
return WebpReplay(file_bytes)
|
|
308
|
+
|
|
309
|
+
def recording(self, session_id: str) -> SessionRecordingWebSocket:
|
|
310
|
+
"""
|
|
311
|
+
Returns a SessionRecordingWebSocket for the specified session.
|
|
312
|
+
"""
|
|
313
|
+
debug_info = self.debug_info(session_id=session_id)
|
|
314
|
+
return SessionRecordingWebSocket(wss_url=debug_info.ws.recording)
|
|
315
|
+
|
|
316
|
+
def upload_cookies(self, cookie_file: str | Path) -> UploadCookiesResponse:
|
|
317
|
+
"""
|
|
318
|
+
Uploads cookies to the session.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
cookie_file: The path to the cookie file (json format)
|
|
322
|
+
"""
|
|
323
|
+
request = UploadCookiesRequest.from_json(cookie_file)
|
|
324
|
+
endpoint = SessionsClient.session_upload_cookies_endpoint()
|
|
325
|
+
return self.request(endpoint.with_request(request))
|
|
326
|
+
|
|
327
|
+
def viewer(self, session_id: str) -> None:
|
|
328
|
+
"""
|
|
329
|
+
Opens a browser tab with the debug URL for visualizing the session.
|
|
330
|
+
|
|
331
|
+
Retrieves debug information for the specified session and opens
|
|
332
|
+
its debug URL in the default web browser.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
session_id (str, optional): The session identifier to use.
|
|
336
|
+
If not provided, the current session ID is used.
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
None
|
|
340
|
+
"""
|
|
341
|
+
debug_info = self.debug_info(session_id=session_id)
|
|
342
|
+
# open browser tab with debug_url
|
|
343
|
+
_ = open_browser(debug_info.debug_url)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@final
|
|
347
|
+
class RemoteSessionFactory:
|
|
348
|
+
"""
|
|
349
|
+
Factory for creating RemoteSession instances.
|
|
350
|
+
|
|
351
|
+
This factory provides a convenient way to create RemoteSession instances with
|
|
352
|
+
customizable configurations. It handles the validation of session creation requests
|
|
353
|
+
and sets up the appropriate connections.
|
|
354
|
+
|
|
355
|
+
Attributes:
|
|
356
|
+
client (SessionsClient): The client used to communicate with the Notte API.
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
class RemoteSession(SyncResource):
|
|
360
|
+
"""
|
|
361
|
+
A remote session that can be managed through the Notte API.
|
|
362
|
+
|
|
363
|
+
This class provides an interface for starting, stopping, and monitoring sessions.
|
|
364
|
+
It implements the SyncResource interface for resource management and maintains
|
|
365
|
+
state about the current session execution.
|
|
366
|
+
|
|
367
|
+
Attributes:
|
|
368
|
+
request (SessionStartRequest): The configuration request used to create this session.
|
|
369
|
+
client (SessionsClient): The client used to communicate with the Notte API.
|
|
370
|
+
response (SessionResponse | None): The latest response from the session execution.
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
def __init__(self, client: SessionsClient, request: SessionStartRequest) -> None:
|
|
374
|
+
"""
|
|
375
|
+
Initialize a new RemoteSession instance.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
client (SessionsClient): The client used to communicate with the Notte API.
|
|
379
|
+
request (SessionStartRequest): The configuration request for this session.
|
|
380
|
+
"""
|
|
381
|
+
self.request: SessionStartRequest = request
|
|
382
|
+
self.client: SessionsClient = client
|
|
383
|
+
self.response: SessionResponse | None = None
|
|
384
|
+
|
|
385
|
+
# #######################################################################
|
|
386
|
+
# ############################# Session #################################
|
|
387
|
+
# #######################################################################
|
|
388
|
+
|
|
389
|
+
@override
|
|
390
|
+
def start(self) -> None:
|
|
391
|
+
"""
|
|
392
|
+
Start the session using the configured request.
|
|
393
|
+
|
|
394
|
+
This method sends a start request to the API and logs the session ID
|
|
395
|
+
and request details upon successful start.
|
|
396
|
+
|
|
397
|
+
Raises:
|
|
398
|
+
ValueError: If the session request is invalid.
|
|
399
|
+
"""
|
|
400
|
+
self.response = self.client.start(**self.request.model_dump())
|
|
401
|
+
logger.info(
|
|
402
|
+
f"[Session] {self.session_id} started with request: {self.request.model_dump(exclude_none=True)}"
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
@override
|
|
406
|
+
def stop(self) -> None:
|
|
407
|
+
"""
|
|
408
|
+
Stop the session and clean up resources.
|
|
409
|
+
|
|
410
|
+
This method sends a close request to the API and verifies that the session
|
|
411
|
+
was properly closed. It logs the session closure and raises an error if
|
|
412
|
+
the session fails to close.
|
|
413
|
+
|
|
414
|
+
Raises:
|
|
415
|
+
ValueError: If the session hasn't been started (no session_id available).
|
|
416
|
+
RuntimeError: If the session fails to close properly.
|
|
417
|
+
"""
|
|
418
|
+
logger.info(f"[Session] {self.session_id} stopped")
|
|
419
|
+
self.response = self.client.stop(session_id=self.session_id)
|
|
420
|
+
if self.response.status != "closed":
|
|
421
|
+
raise RuntimeError(f"[Session] {self.session_id} failed to stop")
|
|
422
|
+
|
|
423
|
+
@property
|
|
424
|
+
def session_id(self) -> str:
|
|
425
|
+
"""
|
|
426
|
+
Get the ID of the current session.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
str: The unique identifier of the current session.
|
|
430
|
+
|
|
431
|
+
Raises:
|
|
432
|
+
ValueError: If the session hasn't been started yet (no response available).
|
|
433
|
+
"""
|
|
434
|
+
if self.response is None:
|
|
435
|
+
raise ValueError("You need to start the session first to get the session id")
|
|
436
|
+
return self.response.session_id
|
|
437
|
+
|
|
438
|
+
def replay(self) -> WebpReplay:
|
|
439
|
+
"""
|
|
440
|
+
Get a replay of the session's execution in WEBP format.
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
WebpReplay: The replay data in WEBP format.
|
|
444
|
+
|
|
445
|
+
Raises:
|
|
446
|
+
ValueError: If the session hasn't been started yet (no session_id available).
|
|
447
|
+
"""
|
|
448
|
+
return self.client.replay(session_id=self.session_id)
|
|
449
|
+
|
|
450
|
+
def recording(self) -> SessionRecordingWebSocket:
|
|
451
|
+
"""
|
|
452
|
+
Get a recording of the session's execution in WEBP format.
|
|
453
|
+
"""
|
|
454
|
+
return self.client.recording(session_id=self.session_id)
|
|
455
|
+
|
|
456
|
+
def viewer(self) -> None:
|
|
457
|
+
"""
|
|
458
|
+
Open a browser tab with the debug URL for visualizing the session.
|
|
459
|
+
|
|
460
|
+
This method opens the default web browser to display the session's debug interface.
|
|
461
|
+
|
|
462
|
+
Raises:
|
|
463
|
+
ValueError: If the session hasn't been started yet (no session_id available).
|
|
464
|
+
"""
|
|
465
|
+
self.client.viewer(session_id=self.session_id)
|
|
466
|
+
|
|
467
|
+
def status(self) -> SessionResponse:
|
|
468
|
+
"""
|
|
469
|
+
Get the current status of the session.
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
SessionResponse: The current status information of the session.
|
|
473
|
+
|
|
474
|
+
Raises:
|
|
475
|
+
ValueError: If the session hasn't been started yet (no session_id available).
|
|
476
|
+
"""
|
|
477
|
+
return self.client.status(session_id=self.session_id)
|
|
478
|
+
|
|
479
|
+
def debug_info(self) -> SessionDebugResponse:
|
|
480
|
+
"""
|
|
481
|
+
Get detailed debug information for the session.
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
SessionDebugResponse: Debug information for the session.
|
|
485
|
+
|
|
486
|
+
Raises:
|
|
487
|
+
ValueError: If the session hasn't been started yet (no session_id available).
|
|
488
|
+
"""
|
|
489
|
+
return self.client.debug_info(session_id=self.session_id)
|
|
490
|
+
|
|
491
|
+
def cdp_url(self) -> str:
|
|
492
|
+
"""
|
|
493
|
+
Get the Chrome DevTools Protocol WebSocket URL for the session.
|
|
494
|
+
|
|
495
|
+
This URL can be used to connect to the browser's debugging interface.
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
str: The WebSocket URL for the Chrome DevTools Protocol.
|
|
499
|
+
|
|
500
|
+
Raises:
|
|
501
|
+
ValueError: If the session hasn't been started yet (no session_id available).
|
|
502
|
+
"""
|
|
503
|
+
debug = self.debug_info()
|
|
504
|
+
return debug.ws.cdp
|
|
505
|
+
|
|
506
|
+
# #######################################################################
|
|
507
|
+
# ############################# PAGE ####################################
|
|
508
|
+
# #######################################################################
|
|
509
|
+
|
|
510
|
+
def scrape(self, **data: Unpack[ObserveRequestDict]) -> DataSpace:
|
|
511
|
+
return self.client.page.scrape(session_id=self.session_id, **data)
|
|
512
|
+
|
|
513
|
+
def observe(self, **data: Unpack[ObserveRequestDict]) -> Observation:
|
|
514
|
+
return self.client.page.observe(session_id=self.session_id, **data)
|
|
515
|
+
|
|
516
|
+
def step(self, **data: Unpack[StepRequestDict]) -> Observation:
|
|
517
|
+
return self.client.page.step(session_id=self.session_id, **data)
|
|
518
|
+
|
|
519
|
+
def __init__(self, client: SessionsClient) -> None:
|
|
520
|
+
"""
|
|
521
|
+
Initialize a new RemoteSessionFactory instance.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
client (SessionsClient): The client used to communicate with the Notte API.
|
|
525
|
+
"""
|
|
526
|
+
self.client = client
|
|
527
|
+
|
|
528
|
+
def __call__(self, **data: Unpack[SessionStartRequestDict]) -> RemoteSession:
|
|
529
|
+
"""
|
|
530
|
+
Create a new RemoteSession instance with the specified configuration.
|
|
531
|
+
|
|
532
|
+
This method validates the session creation request and returns a new
|
|
533
|
+
RemoteSession instance configured with the specified parameters.
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
**data: Keyword arguments for the session creation request.
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
RemoteSession: A new RemoteSession instance configured with the specified parameters.
|
|
540
|
+
"""
|
|
541
|
+
request = SessionStartRequest.model_validate(data)
|
|
542
|
+
return RemoteSessionFactory.RemoteSession(self.client, request)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import Unpack
|
|
3
|
+
|
|
4
|
+
from loguru import logger
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
from typing_extensions import final, override
|
|
7
|
+
|
|
8
|
+
from notte_sdk.endpoints.base import BaseClient, NotteEndpoint
|
|
9
|
+
from notte_sdk.endpoints.personas import PersonasClient
|
|
10
|
+
from notte_sdk.types import (
|
|
11
|
+
PersonaCreateRequest,
|
|
12
|
+
PersonaCreateRequestDict,
|
|
13
|
+
PersonaCreateResponse,
|
|
14
|
+
)
|
|
15
|
+
from notte_sdk.vault import NotteVault
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@final
|
|
19
|
+
class VaultsClient(BaseClient):
|
|
20
|
+
"""
|
|
21
|
+
Client for the Notte API.
|
|
22
|
+
|
|
23
|
+
Note: this client is only able to handle one session at a time.
|
|
24
|
+
If you need to handle multiple sessions, you need to create a new client for each session.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
# Session
|
|
28
|
+
CREATE_VAULT = "create"
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
persona_client: PersonasClient,
|
|
33
|
+
api_key: str | None = None,
|
|
34
|
+
verbose: bool = False,
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Initialize a VaultsClient instance.
|
|
38
|
+
|
|
39
|
+
Initializes the client with an optional API key for vault management.
|
|
40
|
+
"""
|
|
41
|
+
super().__init__(base_endpoint_path="vaults", api_key=api_key, verbose=verbose)
|
|
42
|
+
self.persona_client = persona_client
|
|
43
|
+
|
|
44
|
+
def get(self, vault_id: str) -> NotteVault:
|
|
45
|
+
return NotteVault(persona_client=self.persona_client, persona_id=vault_id)
|
|
46
|
+
|
|
47
|
+
@override
|
|
48
|
+
@staticmethod
|
|
49
|
+
def endpoints() -> Sequence[NotteEndpoint[BaseModel]]:
|
|
50
|
+
"""Returns the available vault endpoints.
|
|
51
|
+
|
|
52
|
+
Aggregates endpoints from PersonasClient for creating personas, reading messages, etc..."""
|
|
53
|
+
return [
|
|
54
|
+
VaultsClient.create_vault_endpoint(),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def create_vault_endpoint() -> NotteEndpoint[PersonaCreateResponse]:
|
|
59
|
+
"""
|
|
60
|
+
Returns a NotteEndpoint configured for creating a persona.
|
|
61
|
+
|
|
62
|
+
The returned endpoint uses the credentials from PersonasClient with the POST method and expects a PersonaCreateResponse.
|
|
63
|
+
"""
|
|
64
|
+
return NotteEndpoint(
|
|
65
|
+
path=VaultsClient.CREATE_VAULT,
|
|
66
|
+
response=PersonaCreateResponse,
|
|
67
|
+
method="POST",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def create(self, **data: Unpack[PersonaCreateRequestDict]) -> NotteVault:
|
|
71
|
+
"""
|
|
72
|
+
Create vault
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
PersonaCreateResponse: The persona created
|
|
78
|
+
"""
|
|
79
|
+
params = PersonaCreateRequest.model_validate(data)
|
|
80
|
+
response = self.request(VaultsClient.create_vault_endpoint().with_request(params))
|
|
81
|
+
logger.info(f"Created vault with id: {response.persona_id}. Don't lose this id!")
|
|
82
|
+
|
|
83
|
+
return NotteVault(self.persona_client, persona_id=response.persona_id)
|
notte_sdk/errors.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from notte_core.errors.base import NotteBaseError
|
|
2
|
+
from requests import Response
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class NotteAPIError(NotteBaseError):
|
|
6
|
+
def __init__(self, path: str, response: Response) -> None:
|
|
7
|
+
try:
|
|
8
|
+
error = response.json()
|
|
9
|
+
except Exception:
|
|
10
|
+
error = response.text
|
|
11
|
+
|
|
12
|
+
super().__init__(
|
|
13
|
+
dev_message=f"Request to `{path}` failed with status code {response.status_code}: {error}",
|
|
14
|
+
user_message="An unexpected error occurred during the request to the Notte API.",
|
|
15
|
+
should_notify_team=True,
|
|
16
|
+
# agent message not relevant here
|
|
17
|
+
agent_message=None,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AuthenticationError(NotteBaseError):
|
|
22
|
+
def __init__(self, message: str) -> None:
|
|
23
|
+
super().__init__(
|
|
24
|
+
dev_message=f"Authentication failed. {message}",
|
|
25
|
+
user_message="Authentication failed. Please check your credentials or upgrade your plan.",
|
|
26
|
+
should_retry_later=False,
|
|
27
|
+
# agent message not relevant here
|
|
28
|
+
agent_message=None,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class InvalidRequestError(NotteBaseError):
|
|
33
|
+
def __init__(self, message: str) -> None:
|
|
34
|
+
super().__init__(
|
|
35
|
+
dev_message=f"Invalid request. {message}",
|
|
36
|
+
user_message="Invalid request. Please check your request parameters.",
|
|
37
|
+
should_retry_later=False,
|
|
38
|
+
# agent message not relevant here
|
|
39
|
+
agent_message=None,
|
|
40
|
+
)
|
notte_sdk/py.typed
ADDED
|
File without changes
|