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
notte_sdk/vault.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import traceback
|
|
4
|
+
from typing import final
|
|
5
|
+
|
|
6
|
+
from loguru import logger
|
|
7
|
+
from notte_core.credentials.base import (
|
|
8
|
+
BaseVault,
|
|
9
|
+
CredentialField,
|
|
10
|
+
VaultCredentials,
|
|
11
|
+
)
|
|
12
|
+
from notte_core.utils.url import get_root_domain
|
|
13
|
+
from typing_extensions import override
|
|
14
|
+
|
|
15
|
+
from notte_sdk.endpoints.personas import PersonasClient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@final
|
|
19
|
+
class NotteVault(BaseVault):
|
|
20
|
+
"""Vault that fetches credentials stored using the sdk"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, persona_client: PersonasClient, persona_id: str):
|
|
23
|
+
self.persona_client = persona_client
|
|
24
|
+
self.persona_id = persona_id
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def vault_id(self):
|
|
28
|
+
return self.persona_id
|
|
29
|
+
|
|
30
|
+
@override
|
|
31
|
+
def _set_singleton_credentials(self, creds: list[CredentialField]) -> None:
|
|
32
|
+
for cred in creds:
|
|
33
|
+
if not cred.singleton:
|
|
34
|
+
raise ValueError(f"{cred.__class__} can't be set as singleton credential: url-specific only")
|
|
35
|
+
|
|
36
|
+
creds_dict = BaseVault.credential_fields_to_dict(creds)
|
|
37
|
+
_ = self.persona_client.add_credentials(self.persona_id, url=None, **creds_dict)
|
|
38
|
+
|
|
39
|
+
@override
|
|
40
|
+
def get_singleton_credentials(self) -> list[CredentialField]:
|
|
41
|
+
try:
|
|
42
|
+
return self.persona_client.get_credentials(self.persona_id, url=None).credentials
|
|
43
|
+
except Exception as e:
|
|
44
|
+
logger.warning(f"Could not get singleton credentials: {e} {traceback.format_exc()}")
|
|
45
|
+
return []
|
|
46
|
+
|
|
47
|
+
@override
|
|
48
|
+
def _add_credentials(self, creds: VaultCredentials) -> None:
|
|
49
|
+
for cred in creds.creds:
|
|
50
|
+
if cred.singleton:
|
|
51
|
+
raise ValueError(f"{cred.__class__} can't be set as url specific credential: singleton only")
|
|
52
|
+
|
|
53
|
+
domain = get_root_domain(creds.url)
|
|
54
|
+
creds_dict = BaseVault.credential_fields_to_dict(creds.creds)
|
|
55
|
+
_ = self.persona_client.add_credentials(self.persona_id, url=domain, **creds_dict)
|
|
56
|
+
|
|
57
|
+
@override
|
|
58
|
+
def _get_credentials_impl(self, url: str) -> VaultCredentials | None:
|
|
59
|
+
try:
|
|
60
|
+
domain = get_root_domain(url)
|
|
61
|
+
creds = self.persona_client.get_credentials(self.persona_id, url=domain).credentials
|
|
62
|
+
return VaultCredentials(url=url, creds=creds)
|
|
63
|
+
except Exception:
|
|
64
|
+
logger.warning(f"Failed to get creds: {traceback.format_exc()}")
|
|
65
|
+
|
|
66
|
+
@override
|
|
67
|
+
def remove_credentials(self, url: str | None) -> None:
|
|
68
|
+
_ = self.persona_client.delete_credentials(self.persona_id, url=url)
|
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import threading
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
import websockets.client
|
|
8
|
+
from loguru import logger
|
|
9
|
+
from notte_core.common.resource import SyncResource
|
|
10
|
+
from pydantic import BaseModel, Field, PrivateAttr
|
|
11
|
+
from typing_extensions import override
|
|
12
|
+
|
|
13
|
+
# def save_frames_to_video(frames: list[bytes], output_path: Path, fps: int = 10):
|
|
14
|
+
# import numpy as np
|
|
15
|
+
# import imagio
|
|
16
|
+
# with imageio.get_writer('output.mp4', fps=fps) as writer:
|
|
17
|
+
# for img in frames:
|
|
18
|
+
# writer.append_data(np.array(img))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class JupyterKernelViewer:
|
|
22
|
+
@staticmethod
|
|
23
|
+
def display_image(image_data: bytes):
|
|
24
|
+
from IPython.display import clear_output, display # type: ignore
|
|
25
|
+
from notte_core.utils.image import image_from_bytes
|
|
26
|
+
|
|
27
|
+
image = image_from_bytes(image_data)
|
|
28
|
+
clear_output(wait=True)
|
|
29
|
+
return display(image)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SessionRecordingWebSocket(BaseModel, SyncResource): # type: ignore
|
|
33
|
+
"""WebSocket client for receiving session recording data in binary format."""
|
|
34
|
+
|
|
35
|
+
wss_url: str
|
|
36
|
+
fps: int = 10
|
|
37
|
+
max_frames: int = 300
|
|
38
|
+
frames: list[bytes] = Field(default_factory=list)
|
|
39
|
+
on_frame: Callable[[bytes], None] | None = None
|
|
40
|
+
output_path: Path | None = None
|
|
41
|
+
_thread: threading.Thread | None = PrivateAttr(default=None)
|
|
42
|
+
_stop_event: threading.Event | None = PrivateAttr(default=None)
|
|
43
|
+
display_image: bool = True
|
|
44
|
+
|
|
45
|
+
def _run_async_loop(self) -> None:
|
|
46
|
+
"""Run the async event loop in a separate thread."""
|
|
47
|
+
loop = asyncio.new_event_loop()
|
|
48
|
+
asyncio.set_event_loop(loop)
|
|
49
|
+
try:
|
|
50
|
+
loop.run_until_complete(self.watch())
|
|
51
|
+
finally:
|
|
52
|
+
loop.close()
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
def start(self) -> None:
|
|
56
|
+
"""Start recording in a separate thread."""
|
|
57
|
+
self._stop_event = threading.Event()
|
|
58
|
+
self._thread = threading.Thread(target=self._run_async_loop)
|
|
59
|
+
self._thread.start()
|
|
60
|
+
|
|
61
|
+
@override
|
|
62
|
+
def stop(self) -> None:
|
|
63
|
+
"""Stop the recording thread."""
|
|
64
|
+
if self._stop_event:
|
|
65
|
+
self._stop_event.set()
|
|
66
|
+
if self._thread:
|
|
67
|
+
self._thread.join()
|
|
68
|
+
self._thread = None
|
|
69
|
+
self._stop_event = None
|
|
70
|
+
|
|
71
|
+
async def connect(self) -> AsyncIterator[bytes]:
|
|
72
|
+
"""Connect to the WebSocket and yield binary recording data.
|
|
73
|
+
|
|
74
|
+
Yields:
|
|
75
|
+
Binary data chunks from the recording stream
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
async with websockets.client.connect(self.wss_url) as websocket:
|
|
79
|
+
async for message in websocket:
|
|
80
|
+
if isinstance(message, bytes):
|
|
81
|
+
if len(self.frames) >= self.max_frames:
|
|
82
|
+
break
|
|
83
|
+
self.frames.append(message)
|
|
84
|
+
yield message
|
|
85
|
+
else:
|
|
86
|
+
logger.warning(f"[Session Recording] Received non-binary message: {message}")
|
|
87
|
+
except websockets.exceptions.WebSocketException as e:
|
|
88
|
+
logger.error(f"[Session Recording] WebSocket error: {e}")
|
|
89
|
+
raise
|
|
90
|
+
|
|
91
|
+
async def watch(self) -> None:
|
|
92
|
+
"""Save the recording stream to a file."""
|
|
93
|
+
output_path = self.output_path or Path(".recordings/{dt.strftime('%Y-%m-%d_%H-%M-%S')}/")
|
|
94
|
+
output_path = Path(output_path)
|
|
95
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
96
|
+
|
|
97
|
+
with output_path.open("wb") as f:
|
|
98
|
+
async for chunk in self.connect():
|
|
99
|
+
if self._stop_event and self._stop_event.is_set():
|
|
100
|
+
break
|
|
101
|
+
_ = f.write(chunk)
|
|
102
|
+
if self.on_frame:
|
|
103
|
+
self.on_frame(chunk)
|
|
104
|
+
if self.display_image:
|
|
105
|
+
_ = JupyterKernelViewer.display_image(chunk)
|
|
106
|
+
logger.info(f"[Session Recording] Recording saved to {output_path}")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
notte_sdk/__init__.py,sha256=cco3JQI3_a0U1S-4V2XER7w5YuGCwyNGQpkhAkCj5RA,160
|
|
2
|
+
notte_sdk/client.py,sha256=uMg7X4wmxBlX50Kfk17v_55srGiV3-dzoxjauzFU0dg,1807
|
|
3
|
+
notte_sdk/errors.py,sha256=yETg6rOO817W5XE37YiFFZxwiqj1z4uHOa6d0tyv31w,1429
|
|
4
|
+
notte_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
notte_sdk/types.py,sha256=1D4HWtDc1ZACWOeaUSPupjLhO55igBOzekMnl-m2q7A,28523
|
|
6
|
+
notte_sdk/vault.py,sha256=t2dP54RAZRCo25DZKx2h0LNVXwBh_M1LQzrPUIJ2ivA,2450
|
|
7
|
+
notte_sdk/endpoints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
notte_sdk/endpoints/agents.py,sha256=wqHtgQF_65uGNJA153LW0vi2hifkWDZwkM8Y7pd0Ov0,19039
|
|
9
|
+
notte_sdk/endpoints/base.py,sha256=wloIHfEV5WTv82yaTczN8IjqXretMk6ov66wqvzNF-w,10272
|
|
10
|
+
notte_sdk/endpoints/page.py,sha256=RuZk0MfyDZSd-W7VMN2lPyTxeiMQmj2uheM-pPIf_QE,8835
|
|
11
|
+
notte_sdk/endpoints/personas.py,sha256=7KHzcRIoqSYfJZNVLhnBhAny-9adJRn0EjojmXAc1E4,10542
|
|
12
|
+
notte_sdk/endpoints/sessions.py,sha256=F8cjcWmhSihQtEYoCSeHgica-bSNdPTdap3Q-UreZQ4,21347
|
|
13
|
+
notte_sdk/endpoints/vaults.py,sha256=1qvKYXsJzjJjcS8xLDnrGQ_o6yCKEQ-T11uORUjR5R4,2614
|
|
14
|
+
notte_sdk/websockets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
notte_sdk/websockets/recording.py,sha256=4vi-GNW_LKLK3vHPAxG36VJVR7k2RhFRykRsediE6R8,3868
|
|
16
|
+
notte_sdk-0.0.dev0.dist-info/METADATA,sha256=Qt1vM2FiBAWh1cCsDsLzn7Ok-I0NUTpte1JD1jDEtUU,217
|
|
17
|
+
notte_sdk-0.0.dev0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
18
|
+
notte_sdk-0.0.dev0.dist-info/RECORD,,
|